Support the latest AVM code
Check the AVM experiment macros CONFIG_NEW_OBU_HEADER and CONFIG_NEW_CSP
to detect the new code.
CONFIG_NEW_OBU_HEADER: New OBU header, and new location of the OBU size
(before the OBU header). The spatial ID becomes the embedded layer
(mlayer) ID.
CONFIG_NEW_CSP: New 4:2:0 chroma sample positions in the range 0..5. The
special value 6 means unspecified. 4:2:2 also has chroma sample
positions in the range 0..1.
diff --git a/src/codec_avm.c b/src/codec_avm.c
index 93b55d6..fa05cc2 100644
--- a/src/codec_avm.c
+++ b/src/codec_avm.c
@@ -88,7 +88,11 @@
nextFrame = aom_codec_get_frame(&codec->internal->decoder, &codec->internal->iter);
if (nextFrame) {
if (spatialID != AVIF_SPATIAL_ID_UNSET) {
+#if CONFIG_NEW_OBU_HEADER
+ if (spatialID == nextFrame->mlayer_id) {
+#else
if (spatialID == nextFrame->spatial_id) {
+#endif // CONFIG_NEW_OBU_HEADER
// Found the correct spatial_id.
break;
}
@@ -302,12 +306,12 @@
return AVIF_FALSE;
}
-static const struct aomOptionEnumList endUsageEnum[] = { //
- { "vbr", AOM_VBR }, // Variable Bit Rate (VBR) mode
- { "cbr", AOM_CBR }, // Constant Bit Rate (CBR) mode
- { "cq", AOM_CQ }, // Constrained Quality (CQ) mode
- { "q", AOM_Q }, // Constant Quality (Q) mode
- { NULL, 0 }
+static const struct aomOptionEnumList endUsageEnum[] = { //
+ { "vbr", AOM_VBR }, // Variable Bit Rate (VBR) mode
+ { "cbr", AOM_CBR }, // Constant Bit Rate (CBR) mode
+ { "cq", AOM_CQ }, // Constrained Quality (CQ) mode
+ { "q", AOM_Q }, // Constant Quality (Q) mode
+ { NULL, 0 }
};
// Returns true if <key> equals <name> or <prefix><name>, where <prefix> is "color:" or "alpha:"
@@ -611,7 +615,13 @@
}
if (encoder->extraLayerCount > 0) {
int layerCount = encoder->extraLayerCount + 1;
- if (aom_codec_control(&codec->internal->encoder, AOME_SET_NUMBER_SPATIAL_LAYERS, layerCount) != AOM_CODEC_OK) {
+ if (aom_codec_control(&codec->internal->encoder,
+#if CONFIG_NEW_OBU_HEADER
+ AOME_SET_NUMBER_MLAYERS,
+#else
+ AOME_SET_NUMBER_SPATIAL_LAYERS,
+#endif // CONFIG_NEW_OBU_HEADER
+ layerCount) != AOM_CODEC_OK) {
return AVIF_RESULT_UNKNOWN_ERROR;
}
}
@@ -757,7 +767,13 @@
return AVIF_RESULT_INVALID_ARGUMENT;
}
if (encoder->extraLayerCount > 0) {
- aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, codec->internal->currentLayer);
+ aom_codec_control(&codec->internal->encoder,
+#if CONFIG_NEW_OBU_HEADER
+ AOME_SET_MLAYER_ID,
+#else
+ AOME_SET_SPATIAL_LAYER_ID,
+#endif // CONFIG_NEW_OBU_HEADER
+ codec->internal->currentLayer);
}
aom_scaling_mode_t aomScalingMode;
diff --git a/src/obu.c b/src/obu.c
index a6e341b..23539a6 100644
--- a/src/obu.c
+++ b/src/obu.c
@@ -260,7 +260,7 @@
}
// Note: Does not parse separate_uv_delta_q.
-static avifBool parseSequenceHeaderColorConfig(avifBits * bits, avifSequenceHeader * header)
+static avifBool parseAV1SequenceHeaderColorConfig(avifBits * bits, avifSequenceHeader * header)
{
header->bitDepth = 8;
header->chromaSamplePosition = AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN;
@@ -348,6 +348,132 @@
return !bits->error;
}
+#if defined(AVIF_CODEC_AVM)
+// Note: Does not parse separate_uv_delta_q.
+static avifBool parseAV2SequenceHeaderColorConfig(avifBits * bits, avifSequenceHeader * header)
+{
+ header->bitDepth = 8;
+ header->chromaSamplePosition = AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN;
+ header->av1C.chromaSamplePosition = (uint8_t)header->chromaSamplePosition;
+ uint32_t high_bitdepth = avifBitsRead(bits, 1);
+ header->av1C.highBitdepth = (uint8_t)high_bitdepth;
+ if ((header->av1C.seqProfile == 2) && high_bitdepth) {
+ uint32_t twelve_bit = avifBitsRead(bits, 1);
+ header->bitDepth = twelve_bit ? 12 : 10;
+ header->av1C.twelveBit = (uint8_t)twelve_bit;
+ } else /* if (seq_profile <= 2) */ {
+ header->bitDepth = high_bitdepth ? 10 : 8;
+ header->av1C.twelveBit = 0;
+ }
+ uint32_t mono_chrome = 0;
+ if (header->av1C.seqProfile != 1) {
+ mono_chrome = avifBitsRead(bits, 1);
+ }
+ header->av1C.monochrome = (uint8_t)mono_chrome;
+ uint32_t color_description_present_flag = avifBitsRead(bits, 1);
+ if (color_description_present_flag) {
+ header->colorPrimaries = (avifColorPrimaries)avifBitsRead(bits, 8); // color_primaries
+ header->transferCharacteristics = (avifTransferCharacteristics)avifBitsRead(bits, 8); // transfer_characteristics
+ header->matrixCoefficients = (avifMatrixCoefficients)avifBitsRead(bits, 8); // matrix_coefficients
+ } else {
+ header->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
+ header->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
+ header->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED;
+ }
+ if (mono_chrome) {
+ header->range = avifBitsRead(bits, 1) ? AVIF_RANGE_FULL : AVIF_RANGE_LIMITED; // color_range
+ header->av1C.chromaSubsamplingX = 1;
+ header->av1C.chromaSubsamplingY = 1;
+ header->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
+ } else if (header->colorPrimaries == AVIF_COLOR_PRIMARIES_BT709 &&
+ header->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_SRGB &&
+ header->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY) {
+ header->range = AVIF_RANGE_FULL;
+ header->av1C.chromaSubsamplingX = 0;
+ header->av1C.chromaSubsamplingY = 0;
+ header->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
+ } else {
+ uint32_t subsampling_x = 0;
+ uint32_t subsampling_y = 0;
+ header->range = avifBitsRead(bits, 1) ? AVIF_RANGE_FULL : AVIF_RANGE_LIMITED; // color_range
+ switch (header->av1C.seqProfile) {
+ case 0:
+ subsampling_x = 1;
+ subsampling_y = 1;
+ header->yuvFormat = AVIF_PIXEL_FORMAT_YUV420;
+ break;
+ case 1:
+ subsampling_x = 0;
+ subsampling_y = 0;
+ header->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
+ break;
+ case 2:
+ if (header->bitDepth == 12) {
+ subsampling_x = avifBitsRead(bits, 1);
+ if (subsampling_x) {
+ subsampling_y = avifBitsRead(bits, 1);
+ }
+ } else {
+ subsampling_x = 1;
+ subsampling_y = 0;
+ }
+ if (subsampling_x) {
+ header->yuvFormat = subsampling_y ? AVIF_PIXEL_FORMAT_YUV420 : AVIF_PIXEL_FORMAT_YUV422;
+ } else {
+ header->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
+ }
+ break;
+ default:
+ return AVIF_FALSE;
+ }
+
+#if CONFIG_NEW_CSP
+ if (subsampling_x && !subsampling_y) {
+ // YUV 4:2:2
+ uint8_t chromaSamplePosition = 6; // CSP_UNSPECIFIED
+ const int csp_present_flag = avifBitsRead(bits, 1);
+ if (csp_present_flag) {
+ chromaSamplePosition = avifBitsRead(bits, 1);
+ }
+ } else if (subsampling_x && subsampling_y) {
+ // YUV 4:2:0
+ uint8_t chromaSamplePosition = 6; // CSP_UNSPECIFIED
+ const int csp_present_flag = avifBitsRead(bits, 1);
+ if (csp_present_flag) {
+ chromaSamplePosition = avifBitsRead(bits, 3);
+ if (chromaSamplePosition > 5) {
+ // Invaid chroma_sample_position.
+ return AVIF_FALSE;
+ }
+ }
+ if (chromaSamplePosition == 0) {
+ // CSP_LEFT: Horizontal offset 0, vertical offset 0.5
+ header->chromaSamplePosition = AVIF_CHROMA_SAMPLE_POSITION_VERTICAL;
+ } else if (chromaSamplePosition == 1) {
+ // CSP_CENTER: Horizontal offset 0.5, vertical offset 0.5
+ header->chromaSamplePosition = AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN;
+ } else if (chromaSamplePosition == 2) {
+ // CSP_TOPLEFT: Horizontal offset 0, vertical offset 0
+ header->chromaSamplePosition = AVIF_CHROMA_SAMPLE_POSITION_COLOCATED;
+ } else {
+ header->chromaSamplePosition = AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN;
+ }
+ header->av1C.chromaSamplePosition = (uint8_t)header->chromaSamplePosition;
+ }
+#else // !CONFIG_NEW_CSP
+ if (subsampling_x && subsampling_y) {
+ header->chromaSamplePosition = (avifChromaSamplePosition)avifBitsRead(bits, 2); // chroma_sample_position
+ header->av1C.chromaSamplePosition = (uint8_t)header->chromaSamplePosition;
+ }
+#endif // CONFIG_NEW_CSP
+ header->av1C.chromaSubsamplingX = (uint8_t)subsampling_x;
+ header->av1C.chromaSubsamplingY = (uint8_t)subsampling_y;
+ }
+
+ return !bits->error;
+}
+#endif // defined(AVIF_CODEC_AVM)
+
static avifBool parseAV1SequenceHeader(avifBits * bits, avifSequenceHeader * header)
{
AVIF_CHECK(parseSequenceHeaderProfile(bits, header));
@@ -359,7 +485,7 @@
avifBitsRead(bits, 3); // enable_superres, enable_cdef, enable_restoration
- AVIF_CHECK(parseSequenceHeaderColorConfig(bits, header));
+ AVIF_CHECK(parseAV1SequenceHeaderColorConfig(bits, header));
if (!header->av1C.monochrome) {
avifBitsRead(bits, 1); // separate_uv_delta_q
}
@@ -381,7 +507,7 @@
header->maxHeight = avifBitsRead(bits, frame_height_bits) + 1; // max_frame_height
// See av1_read_color_config() in avm.
- AVIF_CHECK(parseSequenceHeaderColorConfig(bits, header));
+ AVIF_CHECK(parseAV2SequenceHeaderColorConfig(bits, header));
// See read_sequence_header_obu() in avm.
AVIF_CHECK(parseSequenceHeaderLevelIdxAndTier(bits, header));
@@ -396,7 +522,7 @@
}
#endif
-avifBool avifSequenceHeaderParse(avifSequenceHeader * header, const avifROData * sample, avifCodecType codecType)
+static avifBool av1SequenceHeaderParse(avifSequenceHeader * header, const avifROData * sample)
{
avifROData obus = *sample;
@@ -437,16 +563,7 @@
if (obu_type == 1) { // Sequence Header
avifBits seqHdrBits;
avifBitsInit(&seqHdrBits, obus.data + init_byte_pos, obu_size);
- switch (codecType) {
- case AVIF_CODEC_TYPE_AV1:
- return parseAV1SequenceHeader(&seqHdrBits, header);
-#if defined(AVIF_CODEC_AVM)
- case AVIF_CODEC_TYPE_AV2:
- return parseAV2SequenceHeader(&seqHdrBits, header);
-#endif
- default:
- return AVIF_FALSE;
- }
+ return parseAV1SequenceHeader(&seqHdrBits, header);
}
// Skip this OBU
@@ -455,3 +572,117 @@
}
return AVIF_FALSE;
}
+
+#if defined(AVIF_CODEC_AVM)
+static avifBool av2SequenceHeaderParse(avifSequenceHeader * header, const avifROData * sample)
+{
+#if CONFIG_NEW_OBU_HEADER
+ avifROData obus = *sample;
+
+ // Find the sequence header OBU
+ while (obus.size > 0) {
+ avifBits bits;
+ avifBitsInit(&bits, obus.data, obus.size);
+
+ const uint32_t obu_size = avifBitsReadUleb128(&bits);
+
+ // obu_header()
+ const uint32_t obu_extension_flag = avifBitsRead(&bits, 1);
+ const uint32_t obu_type = avifBitsRead(&bits, 5);
+ avifBitsRead(&bits, 2); // obu_tlayer_id
+
+ if (obu_extension_flag) {
+ avifBitsRead(&bits, 8); // obu_mlayer_id, obu_xlayer_id
+ }
+
+ if (bits.error) {
+ return AVIF_FALSE;
+ }
+
+ const uint32_t obu_header_size = 1 + obu_extension_flag;
+ if (obu_size < obu_header_size) {
+ return AVIF_FALSE;
+ }
+ const uint32_t obu_payload_size = obu_size - obu_header_size;
+ const uint32_t init_bit_pos = avifBitsReadPos(&bits);
+ const uint32_t init_byte_pos = init_bit_pos >> 3;
+ if (obu_payload_size > obus.size - init_byte_pos) {
+ return AVIF_FALSE;
+ }
+
+ if (obu_type == 1) { // Sequence Header
+ avifBits seqHdrBits;
+ avifBitsInit(&seqHdrBits, obus.data + init_byte_pos, obu_payload_size);
+ return parseAV2SequenceHeader(&seqHdrBits, header);
+ }
+
+ // Skip this OBU
+ obus.data += (size_t)obu_payload_size + init_byte_pos;
+ obus.size -= (size_t)obu_payload_size + init_byte_pos;
+ }
+ return AVIF_FALSE;
+#else // !CONFIG_NEW_OBU_HEADER
+ avifROData obus = *sample;
+
+ // Find the sequence header OBU
+ while (obus.size > 0) {
+ avifBits bits;
+ avifBitsInit(&bits, obus.data, obus.size);
+
+ // obu_header()
+ const uint32_t obu_forbidden_bit = avifBitsRead(&bits, 1);
+ if (obu_forbidden_bit != 0) {
+ return AVIF_FALSE;
+ }
+ const uint32_t obu_type = avifBitsRead(&bits, 4);
+ const uint32_t obu_extension_flag = avifBitsRead(&bits, 1);
+ const uint32_t obu_has_size_field = avifBitsRead(&bits, 1);
+ avifBitsRead(&bits, 1); // obu_reserved_1bit
+
+ if (obu_extension_flag) { // obu_extension_header()
+ avifBitsRead(&bits, 8); // temporal_id, spatial_id, extension_header_reserved_3bits
+ }
+
+ uint32_t obu_size = 0;
+ if (obu_has_size_field)
+ obu_size = avifBitsReadUleb128(&bits);
+ else
+ obu_size = (int)obus.size - 1 - obu_extension_flag;
+
+ if (bits.error) {
+ return AVIF_FALSE;
+ }
+
+ const uint32_t init_bit_pos = avifBitsReadPos(&bits);
+ const uint32_t init_byte_pos = init_bit_pos >> 3;
+ if (obu_size > obus.size - init_byte_pos)
+ return AVIF_FALSE;
+
+ if (obu_type == 1) { // Sequence Header
+ avifBits seqHdrBits;
+ avifBitsInit(&seqHdrBits, obus.data + init_byte_pos, obu_size);
+ return parseAV2SequenceHeader(&seqHdrBits, header);
+ }
+
+ // Skip this OBU
+ obus.data += (size_t)obu_size + init_byte_pos;
+ obus.size -= (size_t)obu_size + init_byte_pos;
+ }
+ return AVIF_FALSE;
+#endif // CONFIG_NEW_OBU_HEADER
+}
+#endif // defined(AVIF_CODEC_AVM)
+
+avifBool avifSequenceHeaderParse(avifSequenceHeader * header, const avifROData * sample, avifCodecType codecType)
+{
+ switch (codecType) {
+ case AVIF_CODEC_TYPE_AV1:
+ return av1SequenceHeaderParse(header, sample);
+#if defined(AVIF_CODEC_AVM)
+ case AVIF_CODEC_TYPE_AV2:
+ return av2SequenceHeaderParse(header, sample);
+#endif
+ default:
+ return AVIF_FALSE;
+ }
+}