Add redundant MSDO support to decoder and multiplexer Fixes issues #1312 and #1381
diff --git a/av2/decoder/decoder.h b/av2/decoder/decoder.h index 2261020..47a9feb 100644 --- a/av2/decoder/decoder.h +++ b/av2/decoder/decoder.h
@@ -319,6 +319,17 @@ bool mfh_valid_buf[MAX_MFH_NUM]; } StreamInfo; +/*! + * \brief MSDO configuration snapshot for change detection + */ +typedef struct MsdoConfig { + BITSTREAM_PROFILE multistream_profile_idc; + AV2_LEVEL multistream_level_idx; + uint8_t multistream_tier_idx; + int num_streams; + int stream_ids[AVM_MAX_NUM_STREAMS]; +} MsdoConfig; + typedef struct AV2Decoder { DecoderCodingBlock dcb;
diff --git a/av2/decoder/obu.c b/av2/decoder/obu.c index 09ddf8b..db6b6c9 100644 --- a/av2/decoder/obu.c +++ b/av2/decoder/obu.c
@@ -281,6 +281,11 @@ int stream_idx = av2_get_stream_index(cm, xlayer_id); if (stream_idx < 0) return; // Invalid or GLOBAL_XLAYER_ID + // Check if stream_info is valid + if (pbi->stream_info == NULL) { + return; + } + for (int i = 0; i < REF_FRAMES; i++) { cm->ref_frame_map[i] = pbi->stream_info[stream_idx].ref_frame_map_buf[i]; pbi->valid_for_referencing[i] = @@ -368,6 +373,32 @@ } } +/*! + * \brief Save current MSDO configuration + */ +static void save_msdo_config(const AV2Decoder *pbi, MsdoConfig *config) { + const AV2_COMMON *const cm = &pbi->common; + config->multistream_profile_idc = + pbi->common.msdo_params.multistream_profile_idc; + config->multistream_tier_idx = pbi->common.msdo_params.multistream_tier_idx; + config->multistream_level_idx = pbi->common.msdo_params.multistream_level_idx; + config->num_streams = cm->num_streams; + memcpy(config->stream_ids, cm->stream_ids, cm->num_streams * sizeof(int)); +} + +/*! + * \brief Compare two MSDO configurations + * \return true if configurations are identical, false otherwise + */ +static bool msdo_config_equal(const MsdoConfig *a, const MsdoConfig *b) { + if (a->multistream_profile_idc != b->multistream_profile_idc) return false; + if (a->multistream_tier_idx != b->multistream_tier_idx) return false; + if (a->multistream_level_idx != b->multistream_level_idx) return false; + if (a->num_streams != b->num_streams) return false; + + return true; +} + static uint32_t read_multi_stream_decoder_operation_obu( AV2Decoder *pbi, struct avm_read_bit_buffer *rb) { AV2_COMMON *const cm = &pbi->common; @@ -375,6 +406,16 @@ // Verify rb has been configured to report errors. assert(rb->error_handler); + + // Save previous configuration if it exists + MsdoConfig prev_config; + bool has_previous = false; + + if (pbi->stream_info != NULL) { + save_msdo_config(pbi, &prev_config); + has_previous = true; + } + const int num_streams = avm_rb_read_literal(rb, 3) + 2; // read number of streams if (num_streams > AVM_MAX_NUM_STREAMS) { @@ -416,19 +457,32 @@ avm_rb_read_bit(rb); // read tier of multistream (void)substream_tier_idx; } - // Allocate intermediate buffers to store internal variables per sub-stream - if (pbi->stream_info != NULL) { + + // Check if configuration changed + MsdoConfig new_config; + save_msdo_config(pbi, &new_config); + bool config_changed = + !has_previous || !msdo_config_equal(&prev_config, &new_config); + + // Only free if stream_info exists AND config changed + if (pbi->stream_info != NULL && config_changed) { avm_free(pbi->stream_info); pbi->stream_info = NULL; } - pbi->stream_info = (StreamInfo *)avm_malloc(num_streams * sizeof(StreamInfo)); + + // Only allocate if stream_info is NULL (first time OR after freeing due to + // config change) if (pbi->stream_info == NULL) { - avm_internal_error(&cm->error, AVM_CODEC_MEM_ERROR, - "Memory allocation failed for pbi->stream_info\n"); - } - memset(pbi->stream_info, 0, num_streams * sizeof(StreamInfo)); - for (int i = 0; i < num_streams; i++) { - init_stream_info(&pbi->stream_info[i]); + pbi->stream_info = + (StreamInfo *)avm_malloc(num_streams * sizeof(StreamInfo)); + if (pbi->stream_info == NULL) { + avm_internal_error(&cm->error, AVM_CODEC_MEM_ERROR, + "Memory allocation failed for pbi->stream_info\n"); + } + memset(pbi->stream_info, 0, num_streams * sizeof(StreamInfo)); + for (int i = 0; i < num_streams; i++) { + init_stream_info(&pbi->stream_info[i]); + } } pbi->msdo_is_present_in_tu = 1; @@ -2957,40 +3011,38 @@ cm->tlayer_id = obu_header.obu_tlayer_id; cm->mlayer_id = obu_header.obu_mlayer_id; - if (obu_header.type == OBU_MSDO) { + + if (!pbi->multi_stream_mode || + (obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID && + cm->xlayer_id == GLOBAL_XLAYER_ID)) { cm->xlayer_id = obu_header.obu_xlayer_id; - } else { - if (!pbi->multi_stream_mode || - (obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID && - cm->xlayer_id == GLOBAL_XLAYER_ID)) { - cm->xlayer_id = obu_header.obu_xlayer_id; - } else if (cm->xlayer_id != GLOBAL_XLAYER_ID && - obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID) { - // Store xlayer context - av2_store_xlayer_context(pbi, cm, cm->xlayer_id); - cm->xlayer_id = obu_header.obu_xlayer_id; - } else if (cm->xlayer_id == GLOBAL_XLAYER_ID && - obu_header.obu_xlayer_id != GLOBAL_XLAYER_ID) { - // Restore xlayer context - cm->xlayer_id = obu_header.obu_xlayer_id; - av2_restore_xlayer_context(pbi, cm, cm->xlayer_id); - } else if (cm->xlayer_id != obu_header.obu_xlayer_id) { - // Store and restore xlayer context - av2_store_xlayer_context(pbi, cm, cm->xlayer_id); - cm->xlayer_id = obu_header.obu_xlayer_id; - av2_restore_xlayer_context(pbi, cm, cm->xlayer_id); - } - if (obu_header.type == OBU_LEADING_TILE_GROUP || - obu_header.type == OBU_REGULAR_TILE_GROUP) { - if (prev_obu_xlayer_id == -1) { - prev_obu_xlayer_id = obu_header.obu_xlayer_id; - } else { - if (pbi->multi_stream_mode && prev_obu_xlayer_id >= 0 && - obu_header.obu_xlayer_id != prev_obu_xlayer_id) { - avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM, - "tile group OBUs with the same stream_id shall " - "be contiguous within a temporal unit"); - } + } else if (cm->xlayer_id != GLOBAL_XLAYER_ID && + obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID) { + // Store xlayer context + av2_store_xlayer_context(pbi, cm, cm->xlayer_id); + cm->xlayer_id = obu_header.obu_xlayer_id; + } else if (cm->xlayer_id == GLOBAL_XLAYER_ID && + obu_header.obu_xlayer_id != GLOBAL_XLAYER_ID) { + // Restore xlayer context + cm->xlayer_id = obu_header.obu_xlayer_id; + av2_restore_xlayer_context(pbi, cm, cm->xlayer_id); + } else if (cm->xlayer_id != obu_header.obu_xlayer_id) { + // Store and restore xlayer context + av2_store_xlayer_context(pbi, cm, cm->xlayer_id); + cm->xlayer_id = obu_header.obu_xlayer_id; + av2_restore_xlayer_context(pbi, cm, cm->xlayer_id); + } + + if (obu_header.type == OBU_LEADING_TILE_GROUP || + obu_header.type == OBU_REGULAR_TILE_GROUP) { + if (prev_obu_xlayer_id == -1) { + prev_obu_xlayer_id = obu_header.obu_xlayer_id; + } else { + if (pbi->multi_stream_mode && prev_obu_xlayer_id >= 0 && + obu_header.obu_xlayer_id != prev_obu_xlayer_id) { + avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM, + "tile group OBUs with the same stream_id shall " + "be contiguous within a temporal unit"); } } }
diff --git a/tools/stream_multiplexer.cc b/tools/stream_multiplexer.cc index 0a43f64..122568c 100644 --- a/tools/stream_multiplexer.cc +++ b/tools/stream_multiplexer.cc
@@ -96,7 +96,7 @@ std::vector<uint8_t> WriteTU(const uint8_t *data, int length, int *obu_overhead_bytes, int seg_idx, int num_streams, int *stream_ids, - int *stream_buffer_units) { + int *stream_buffer_units, bool redundant_msdo) { std::vector<uint8_t> tu_obus; const uint8_t *data_ptr = data; const int kObuHeaderSizeBytes = 1; @@ -105,6 +105,7 @@ int obu_overhead = 0; int msdo_signaled = 0; ObuHeader obu_header; + while (consumed < length) { const int remaining = length - consumed; if (remaining < kMinimumBytesRequired) { @@ -162,15 +163,16 @@ data_ptr + obu_total_size + length_field_size); if (!msdo_signaled && - (obu_header.type == OBU_LAYER_CONFIGURATION_RECORD || - obu_header.type == OBU_OPERATING_POINT_SET || - obu_header.type == OBU_ATLAS_SEGMENT || - ((obu_header.type == OBU_METADATA_SHORT || - obu_header.type == OBU_METADATA_GROUP) && - obu_header.obu_header_extension_flag && - obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID) || - obu_header.type == OBU_SEQUENCE_HEADER) && - (seg_idx == 0)) { + (redundant_msdo || + ((obu_header.type == OBU_LAYER_CONFIGURATION_RECORD || + obu_header.type == OBU_OPERATING_POINT_SET || + obu_header.type == OBU_ATLAS_SEGMENT || + ((obu_header.type == OBU_METADATA_SHORT || + obu_header.type == OBU_METADATA_GROUP) && + obu_header.obu_header_extension_flag && + obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID) || + obu_header.type == OBU_SEQUENCE_HEADER) && + seg_idx == 0))) { std::vector<uint8_t> multi_stream_obu(num_streams * 2 + 4); int multi_header_obu_size = write_multi_stream_decoder_operation_obu( multi_stream_obu.data(), num_streams, stream_ids, @@ -226,24 +228,34 @@ } int main(int argc, const char *argv[]) { - if (argc < 3 || (argc - 2) % 3) { + bool redundant_msdo = false; + int arg_offset = 0; + + // Check for --redundant-msdo flag + if (argc > 1 && strcmp(argv[1], "--redundant-msdo") == 0) { + redundant_msdo = true; + arg_offset = 1; + } + + if (argc - arg_offset < 3 || (argc - arg_offset - 2) % 3) { fprintf(stderr, - "command: %s [input file1], [stream ID 1], [unit size 1], [input " + "command: %s [--redundant-msdo] [input file1], [stream ID 1], " + "[unit size 1], [input " "file2], [stream ID 2], [unit size 2], ... [outfile]\n", argv[0]); return -1; - } else if (argc > (AVM_MAX_NUM_STREAMS * 3 + 2)) { + } else if (argc - arg_offset > (AVM_MAX_NUM_STREAMS * 3 + 2)) { fprintf(stderr, "The number of input files cannot exceed the maximum number of " "streams (8)\n"); return -1; } - int num_streams = (argc - 2) / 3; + int num_streams = (argc - arg_offset - 2) / 3; int sum_buffer_units = 0; for (int i = 0; i < num_streams; ++i) { - int stream_id = atoi(argv[i * 3 + 2]); + int stream_id = atoi(argv[arg_offset + i * 3 + 2]); if (stream_id > 7) { fprintf(stderr, "The value of stream_id cannot exceed the max value (7)\n"); @@ -251,7 +263,7 @@ } } for (int i = 0; i < num_streams; ++i) { - sum_buffer_units += atoi(argv[i * 3 + 3]); + sum_buffer_units += atoi(argv[arg_offset + i * 3 + 3]); } if (sum_buffer_units > 8) { fprintf(stderr, @@ -278,7 +290,7 @@ // Initialize file read for each stream for (int i = 0; i < num_streams; ++i) { - fin[i] = fopen(argv[i * 3 + 1], "rb"); + fin[i] = fopen(argv[arg_offset + i * 3 + 1], "rb"); if (fin[i] == nullptr) { fprintf(stderr, "Error: failed to open the input file\n"); } @@ -313,7 +325,7 @@ printf(" Stream IDs: "); #endif // PRINT_TU_INFO for (int i = 0; i < num_streams; ++i) { - stream_ids[i] = atoi(argv[i * 3 + 2]); + stream_ids[i] = atoi(argv[arg_offset + i * 3 + 2]); #if PRINT_TU_INFO printf("[%d] ", stream_ids[i]); #endif // PRINT_TU_INFO @@ -325,7 +337,7 @@ // Set the values of unit sizes of streams int stream_buffer_units[AVM_MAX_NUM_STREAMS]; for (int i = 0; i < num_streams; ++i) { - stream_buffer_units[i] = atoi(argv[i * 3 + 3]); + stream_buffer_units[i] = atoi(argv[arg_offset + i * 3 + 3]); #if PRINT_TU_INFO printf("[%d] ", stream_buffer_units[i]); #endif // PRINT_TU_INFO @@ -353,7 +365,7 @@ segments = WriteTU(input_ctx[i].unit_buffer, static_cast<int>(unit_size), &obu_overhead_current_unit, i, num_streams, stream_ids, - stream_buffer_units); + stream_buffer_units, redundant_msdo); fwrite(segments.data(), 1, segments.size(), fout); #if PRINT_TU_INFO printf(" TU overhead: %d\n", obu_overhead_current_unit);