|
32 | 32 | #include <vrs/helpers/FileMacros.h> |
33 | 33 | #include <vrs/helpers/Throttler.h> |
34 | 34 | #include <vrs/utils/BufferRecordReader.hpp> |
| 35 | +#include <vrs/utils/DecoderFactory.h> |
35 | 36 | #include <vrs/utils/converters/Grey10PackedConverters.h> |
36 | 37 | #include <vrs/utils/converters/Raw10ToGrey10Converter.h> |
37 | 38 | #include <utility> |
@@ -170,6 +171,8 @@ bool PixelFrame::readFrame(RecordReader* reader, const ContentBlock& cb) { |
170 | 171 | return readJpegFrame(reader, cb.getBlockSize()); |
171 | 172 | case ImageFormat::JXL: |
172 | 173 | return readJxlFrame(reader, cb.getBlockSize()); |
| 174 | + case ImageFormat::CUSTOM_CODEC: |
| 175 | + return readCustomCodecFrame(reader, cb); |
173 | 176 | default: |
174 | 177 | return false; |
175 | 178 | } |
@@ -213,12 +216,91 @@ bool PixelFrame::decompressImage(VideoFrameHandler* videoFrameHandler) { |
213 | 216 | vector<uint8_t> compressedData(std::move(frameBytes_)); |
214 | 217 | return readJxlFrame(compressedData); |
215 | 218 | } |
| 219 | + case ImageFormat::CUSTOM_CODEC: { |
| 220 | + // No ContentBlock here, so imageSpec_ is the only source of the codec name. It is |
| 221 | + // authoritative only when the frame came from readDiskImageData, which preserves the spec |
| 222 | + // verbatim. |
| 223 | + if (!XR_VERIFY(imageSpec_.getImageFormat() == ImageFormat::CUSTOM_CODEC) || |
| 224 | + !XR_VERIFY(!imageSpec_.getCodecName().empty())) { |
| 225 | + return false; |
| 226 | + } |
| 227 | + vector<uint8_t> compressedData(std::move(frameBytes_)); |
| 228 | + return decodeCustomCodecFrame(compressedData, imageSpec_); |
| 229 | + } |
216 | 230 | default: |
217 | 231 | return false; |
218 | 232 | } |
219 | 233 | return false; |
220 | 234 | } |
221 | 235 |
|
| 236 | +bool PixelFrame::readCustomCodecFrame(RecordReader* reader, const ContentBlock& cb) { |
| 237 | + // The codec spec, which carries the codec name, comes from the content block; imageSpec_ is not |
| 238 | + // authoritative here. |
| 239 | + const ImageContentBlockSpec& codecSpec = cb.image(); |
| 240 | + if (!XR_VERIFY(codecSpec.getImageFormat() == ImageFormat::CUSTOM_CODEC) || |
| 241 | + !XR_VERIFY(!codecSpec.getCodecName().empty())) { |
| 242 | + return false; |
| 243 | + } |
| 244 | + size_t sizeBytes = cb.getBlockSize(); |
| 245 | + if (sizeBytes == 0 || sizeBytes == ContentBlock::kSizeUnknown) { |
| 246 | + return false; |
| 247 | + } |
| 248 | + // Bound against the record before allocating, like readRawFrame, so a bad size can't throw |
| 249 | + // bad_alloc out of this bool API. |
| 250 | + if (sizeBytes > reader->getUnreadBytes()) { |
| 251 | + THROTTLED_LOGE( |
| 252 | + reader->getRef(), |
| 253 | + "Custom codec image {} needs {} bytes, only {} available. Recording bug?", |
| 254 | + codecSpec.asString(), |
| 255 | + sizeBytes, |
| 256 | + reader->getUnreadBytes()); |
| 257 | + return false; |
| 258 | + } |
| 259 | + vector<uint8_t> compressedData(sizeBytes); |
| 260 | + if (!VERIFY_SUCCESS(reader->read(compressedData.data(), sizeBytes))) { |
| 261 | + return false; |
| 262 | + } |
| 263 | + return decodeCustomCodecFrame(compressedData, codecSpec); |
| 264 | +} |
| 265 | + |
| 266 | +bool PixelFrame::decodeCustomCodecFrame( |
| 267 | + const vector<uint8_t>& compressedData, |
| 268 | + const ImageContentBlockSpec& codecSpec) { |
| 269 | + if (codecSpec.getImageFormat() != ImageFormat::CUSTOM_CODEC || codecSpec.getCodecName().empty()) { |
| 270 | + return false; |
| 271 | + } |
| 272 | + PixelFormat pixelFormat = codecSpec.getPixelFormat(); |
| 273 | + uint32_t width = codecSpec.getWidth(); |
| 274 | + uint32_t height = codecSpec.getHeight(); |
| 275 | + if (pixelFormat == PixelFormat::UNDEFINED || width == 0 || height == 0) { |
| 276 | + return false; |
| 277 | + } |
| 278 | + // Decode into a local buffer and commit only on success, as png/jpg/jxl do, so a failed decode |
| 279 | + // can't leave a reused frame presenting the prior image as fresh. |
| 280 | + ImageContentBlockSpec rawSpec(pixelFormat, width, height); |
| 281 | + size_t rawSize = rawSpec.getRawImageSize(); |
| 282 | + if (rawSize == ContentBlock::kSizeUnknown || rawSize == 0) { |
| 283 | + return false; |
| 284 | + } |
| 285 | + vector<uint8_t> decoded(rawSize); |
| 286 | + // Hand the decoder a spec with default strides so getRawImageSize() matches decoded.size(); |
| 287 | + // codecSpec may carry an explicit stride a stride-honoring decoder would overrun. |
| 288 | + ImageContentBlockSpec decodeSpec( |
| 289 | + ImageFormat::CUSTOM_CODEC, |
| 290 | + codecSpec.getCodecName(), |
| 291 | + ImageContentBlockSpec::kQualityUndefined, |
| 292 | + pixelFormat, |
| 293 | + width, |
| 294 | + height); |
| 295 | + unique_ptr<DecoderI> decoder = |
| 296 | + DecoderFactory::get().makeDecoder(compressedData, decoded.data(), decodeSpec); |
| 297 | + if (!decoder || decoder->decode(compressedData, decoded.data(), decodeSpec) != 0) { |
| 298 | + return false; |
| 299 | + } |
| 300 | + init(rawSpec, std::move(decoded)); |
| 301 | + return true; |
| 302 | +} |
| 303 | + |
222 | 304 | bool PixelFrame::readRawFrame( |
223 | 305 | RecordReader* reader, |
224 | 306 | const ImageContentBlockSpec& inputImageSpec, |
|
0 commit comments