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; + } +}