| /* |
| * Copyright (c) 2025, Alliance for Open Media. All rights reserved |
| * |
| * This source code is subject to the terms of the BSD 3-Clause Clear License |
| * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear |
| * License was not distributed with this source code in the LICENSE file, you |
| * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. If the |
| * Alliance for Open Media Patent License 1.0 was not distributed with this |
| * source code in the PATENTS file, you can obtain it at |
| * aomedia.org/license/patent-license/. |
| */ |
| |
| // multi-streams muxing test |
| // =========================== |
| // |
| // This is an example demonstrating how to mux multiple sub-bitstreams. |
| // |
| |
| #include "tools/stream_mux.h" |
| |
| // When 1, rewrite TD with 2-byte header (extension_flag=1, xlayer_id=GLOBAL). |
| // When 0, preserve the original 1-byte TD header from the input stream. |
| #define REWRITE_TD_WITH_GLOBAL_XLAYER 0 |
| |
| // This function writes a multi-stream decoder operation OBU, |
| // when multiple sub-streams are merged into one bitstream. |
| static int write_multi_stream_decoder_operation_obu(uint8_t *const dst, |
| int num_streams, |
| int *stream_ids, |
| int *stream_buffer_units) { |
| struct avm_write_bit_buffer wb = { dst, 0 }; |
| int obu_type = OBU_MULTI_STREAM_DECODER_OPERATION; |
| uint32_t size = 0; |
| |
| avm_wb_write_literal(&wb, 1, 1); // obu_header_extension_flag |
| avm_wb_write_literal(&wb, (int)obu_type, 5); // obu_type |
| avm_wb_write_literal(&wb, 0, 2); // obu_tlayer |
| avm_wb_write_literal(&wb, 0, 3); // obu_mlayer |
| avm_wb_write_literal(&wb, 31, 5); // obu_xlayer |
| |
| avm_wb_write_literal(&wb, num_streams - 2, 3); // signal number of streams |
| avm_wb_write_literal(&wb, 2, PROFILE_BITS); // multistream_profile_idc |
| avm_wb_write_literal(&wb, SEQ_LEVEL_4_0, |
| LEVEL_BITS); // multistream_level_idx |
| avm_wb_write_bit(&wb, 0); // multistream_tier_idx |
| |
| int multistream_even_allocation_flag = 1; |
| int multistream_large_picture_idc = 0; |
| int max_buffer_unit = stream_buffer_units[0]; |
| for (int i = 0; i < num_streams; i++) { |
| if (stream_buffer_units[i] != max_buffer_unit) { |
| multistream_even_allocation_flag = 0; |
| if (stream_buffer_units[i] > max_buffer_unit) { |
| multistream_large_picture_idc = i; |
| max_buffer_unit = stream_buffer_units[i]; |
| } |
| } |
| } |
| |
| avm_wb_write_bit( |
| &wb, |
| multistream_even_allocation_flag); // multistream_even_allocation_flag |
| if (!multistream_even_allocation_flag) |
| avm_wb_write_literal(&wb, multistream_large_picture_idc, |
| 3); // multistream_large_picture_idc |
| |
| for (int i = 0; i < num_streams; i++) { |
| avm_wb_write_literal(&wb, stream_ids[i], XLAYER_BITS); // signal stream IDs |
| avm_wb_write_literal(&wb, 0, PROFILE_BITS); // substream profile_idc |
| avm_wb_write_literal(&wb, SEQ_LEVEL_4_0, |
| LEVEL_BITS); // substream level_idx |
| avm_wb_write_bit(&wb, 0); // substream tier_idx |
| } |
| |
| avm_wb_write_bit(&wb, 1); // msdo_doh_constraint_flag |
| |
| if ((wb.bit_offset % CHAR_BIT == 0)) { |
| avm_wb_write_literal(&wb, 0x80, 8); |
| } else { |
| // assumes that the other bits are already 0s |
| avm_wb_write_bit(&wb, 1); |
| } |
| |
| size = avm_wb_bytes_written(&wb); |
| return size; |
| } |
| |
| static void write_obu_header_with_stream_id(uint8_t *const dst, |
| ObuHeader *obu_header, |
| int stream_id) { |
| struct avm_write_bit_buffer wb = { dst, 0 }; |
| avm_wb_write_bit(&wb, 1); // extention flag |
| avm_wb_write_literal(&wb, obu_header->type, 5); // obu_type |
| avm_wb_write_literal(&wb, obu_header->obu_tlayer_id, 2); // obu_temporal |
| avm_wb_write_literal(&wb, obu_header->obu_mlayer_id, 3); // obu_mlayer |
| avm_wb_write_literal(&wb, stream_id, 5); // obu_xlayer |
| } |
| |
| static bool TuHasKeyFrame(const uint8_t *data, int length) { |
| int pos = 0; |
| while (pos < length) { |
| size_t lfs = 0; |
| uint64_t obu_sz = 0; |
| if (avm_uleb_decode(data + pos, length - pos, &obu_sz, &lfs) != 0) break; |
| ObuHeader hdr; |
| memset(&hdr, 0, sizeof(hdr)); |
| if (!ParseAV2ObuHeader(*(data + pos + lfs), &hdr)) break; |
| if (hdr.type == OBU_CLOSED_LOOP_KEY || hdr.type == OBU_OPEN_LOOP_KEY || |
| hdr.type == OBU_RAS_FRAME) |
| return true; |
| pos += static_cast<int>(obu_sz) + static_cast<int>(lfs); |
| } |
| return false; |
| } |
| |
| // Extract non-TD OBUs from a single input TU, rewriting headers with stream_id. |
| // Used by the combined TU path. |
| static std::vector<uint8_t> WriteStreamOBUs(const uint8_t *data, int length, |
| int stream_id) { |
| std::vector<uint8_t> obus; |
| const int kObuHeaderSizeBytes = 1; |
| int consumed = 0; |
| ObuHeader obu_header; |
| |
| while (consumed < length) { |
| const int remaining = length - consumed; |
| size_t length_field_size = 0; |
| uint64_t obu_total_size = 0; |
| |
| memset(&obu_header, 0, sizeof(obu_header)); |
| if (avm_uleb_decode(data + consumed, remaining, &obu_total_size, |
| &length_field_size) != 0) { |
| fprintf(stderr, "OBU size parsing failed at offset %d.\n", consumed); |
| break; |
| } |
| |
| const uint8_t obu_header_byte = *(data + consumed + length_field_size); |
| if (!ParseAV2ObuHeader(obu_header_byte, &obu_header)) { |
| fprintf(stderr, "OBU parsing failed at offset %d.\n", |
| consumed + static_cast<int>(length_field_size)); |
| break; |
| } |
| |
| int obu_header_size = 1; |
| if (obu_header.obu_header_extension_flag) { |
| const uint8_t obu_header_ext_byte = |
| *(data + consumed + length_field_size + kObuHeaderSizeBytes); |
| ParseAV2ObuHeaderExtension(obu_header_ext_byte, &obu_header); |
| ++obu_header_size; |
| } |
| |
| consumed += |
| static_cast<int>(obu_total_size) + static_cast<int>(length_field_size); |
| |
| // Skip TD OBUs — the combined TU path writes its own single TD. |
| if (obu_header.type == OBU_TEMPORAL_DELIMITER) continue; |
| |
| // Rewrite OBU header with stream_id as xlayer_id |
| std::vector<uint8_t> obu_size_data(length_field_size + 1); |
| size_t coded_obu_size; |
| avm_uleb_encode(obu_total_size - obu_header_size + 2, |
| sizeof(obu_total_size), obu_size_data.data(), |
| &coded_obu_size); |
| obus.insert(obus.end(), obu_size_data.begin(), |
| obu_size_data.begin() + coded_obu_size); |
| |
| std::vector<uint8_t> obu_header_data(2); |
| write_obu_header_with_stream_id(obu_header_data.data(), &obu_header, |
| stream_id); |
| obus.insert(obus.end(), obu_header_data.begin(), obu_header_data.end()); |
| |
| // Append OBU payload (after original header) |
| const uint8_t *payload_start = |
| data + (consumed - static_cast<int>(obu_total_size)) + obu_header_size; |
| int payload_size = static_cast<int>(obu_total_size) - obu_header_size; |
| obus.insert(obus.end(), payload_start, payload_start + payload_size); |
| } |
| |
| return obus; |
| } |
| |
| // Write a single combined TU containing OBUs from all streams. |
| // sorted_indices provides stream indices in ascending stream_id order. |
| static std::vector<uint8_t> WriteCombinedTU( |
| std::vector<std::vector<uint8_t>> &per_stream_obus, |
| const int *sorted_indices, int num_streams, int *stream_ids, |
| int *stream_buffer_units, bool any_key_frame, bool redundant_msdo) { |
| std::vector<uint8_t> tu; |
| |
| // Write TD OBU |
| #if REWRITE_TD_WITH_GLOBAL_XLAYER |
| uint8_t td_obu_size = 2; |
| tu.push_back(td_obu_size); |
| tu.push_back(0x88); |
| tu.push_back(0x1F); |
| #else |
| // Write a minimal 1-byte TD: size=1, header byte for TD type |
| uint8_t td_header = (OBU_TEMPORAL_DELIMITER << 2); // ext=0, tlayer=0 |
| tu.push_back(1); // leb128 size = 1 |
| tu.push_back(td_header); |
| #endif |
| |
| // Insert MSDO if any stream has a key frame or redundant mode |
| if (redundant_msdo || any_key_frame) { |
| 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, stream_buffer_units); |
| std::vector<uint8_t> msdo_size_data(8); |
| size_t msdo_length_field_size = 0; |
| avm_uleb_encode(multi_header_obu_size, sizeof(multi_header_obu_size), |
| msdo_size_data.data(), &msdo_length_field_size); |
| tu.insert(tu.end(), msdo_size_data.begin(), |
| msdo_size_data.begin() + msdo_length_field_size); |
| tu.insert(tu.end(), multi_stream_obu.begin(), |
| multi_stream_obu.begin() + multi_header_obu_size); |
| } |
| |
| // Append OBUs from each stream in ascending stream_id order |
| for (int j = 0; j < num_streams; ++j) { |
| const auto &obus = per_stream_obus[sorted_indices[j]]; |
| tu.insert(tu.end(), obus.begin(), obus.end()); |
| } |
| |
| return tu; |
| } |
| |
| // This function read a temporal unit from a sub-stream, |
| // writes the temporal unit with updates of xlayer_id, |
| // and multiplex the temporal units into a merged bitstream. |
| 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, bool redundant_msdo) { |
| std::vector<uint8_t> tu_obus; |
| const uint8_t *data_ptr = data; |
| const int kObuHeaderSizeBytes = 1; |
| const int kMinimumBytesRequired = 1 + kObuHeaderSizeBytes; |
| int consumed = 0; |
| int obu_overhead = 0; |
| ObuHeader obu_header; |
| |
| const bool tu_has_key_frame = TuHasKeyFrame(data, length); |
| |
| while (consumed < length) { |
| const int remaining = length - consumed; |
| if (remaining < kMinimumBytesRequired) { |
| fprintf(stderr, |
| "OBU parse error. Did not consume all data, %d bytes remain.\n", |
| remaining); |
| } |
| |
| int obu_header_size = 0; |
| size_t length_field_size = 0; |
| uint64_t obu_total_size = 0; |
| |
| memset(&obu_header, 0, sizeof(obu_header)); |
| |
| if (avm_uleb_decode(data + consumed, remaining, &obu_total_size, |
| &length_field_size) != 0) { |
| fprintf(stderr, "OBU size parsing failed at offset %d.\n", consumed); |
| } |
| |
| const uint8_t obu_header_byte = *(data + consumed + length_field_size); |
| if (!ParseAV2ObuHeader(obu_header_byte, &obu_header)) { |
| fprintf(stderr, "OBU parsing failed at offset %d.\n", |
| consumed + static_cast<int>(length_field_size)); |
| } |
| ++obu_overhead; |
| ++obu_header_size; |
| |
| if (obu_header.obu_header_extension_flag) { |
| const uint8_t obu_header_ext_byte = |
| *(data + consumed + length_field_size + kObuHeaderSizeBytes); |
| if (!ParseAV2ObuHeaderExtension(obu_header_ext_byte, &obu_header)) { |
| fprintf(stderr, "OBU header extension parsing failed at offset %d.\n", |
| static_cast<int>(consumed + length_field_size + |
| kObuHeaderSizeBytes)); |
| } |
| |
| ++obu_overhead; |
| ++obu_header_size; |
| } |
| |
| int current_obu_length = static_cast<int>(obu_total_size) - obu_header_size; |
| if (obu_header_size + static_cast<int>(length_field_size) + |
| current_obu_length > |
| remaining) { |
| fprintf(stderr, "OBU parsing failed: not enough OBU data.\n"); |
| } |
| consumed += |
| static_cast<int>(obu_total_size) + static_cast<int>(length_field_size); |
| |
| #if PRINT_TU_INFO |
| PrintObuHeader(&obu_header); |
| #endif // PRINT_TU_INFO |
| |
| std::vector<uint8_t> obu_tmp(data_ptr + length_field_size, |
| data_ptr + obu_total_size + length_field_size); |
| |
| // Write the temporal delimiter first, before any MSDO insertion, |
| // since the MSDO must come after the TD in the OBU order. |
| if (obu_header.type == OBU_TEMPORAL_DELIMITER) { |
| #if REWRITE_TD_WITH_GLOBAL_XLAYER |
| // Rewrite TD with 2-byte header: extension_flag=1, xlayer_id=31(GLOBAL) |
| // Byte 0: ext=1 | type=00010(TD) | tlayer=00 = 0x88 |
| // Byte 1: mlayer=000 | xlayer=11111 = 0x1F |
| uint8_t td_obu_size = 2; // leb128 encoding of OBU size (2 header bytes) |
| tu_obus.push_back(td_obu_size); |
| tu_obus.push_back(0x88); |
| tu_obus.push_back(0x1F); |
| #else |
| std::vector<uint8_t> obu_size_data(length_field_size); |
| size_t coded_obu_size; |
| avm_uleb_encode(obu_total_size, sizeof(obu_total_size), |
| obu_size_data.data(), &coded_obu_size); |
| tu_obus.insert(tu_obus.end(), obu_size_data.begin(), |
| obu_size_data.begin() + coded_obu_size); |
| tu_obus.insert(tu_obus.end(), obu_tmp.begin(), obu_tmp.end()); |
| #endif |
| |
| // Insert MSDO immediately after the TD when required. |
| if (redundant_msdo || tu_has_key_frame) { |
| 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, |
| stream_buffer_units); |
| std::vector<uint8_t> multi_header_obu_size_data(length_field_size); |
| size_t multi_header_length_field_size = 0; |
| avm_uleb_encode(multi_header_obu_size, sizeof(multi_header_obu_size), |
| multi_header_obu_size_data.data(), |
| &multi_header_length_field_size); |
| tu_obus.insert(tu_obus.end(), multi_header_obu_size_data.begin(), |
| multi_header_obu_size_data.begin() + |
| multi_header_length_field_size); |
| tu_obus.insert(tu_obus.end(), multi_stream_obu.begin(), |
| multi_stream_obu.begin() + multi_header_obu_size); |
| } |
| |
| data_ptr += static_cast<int>(obu_total_size) + |
| static_cast<int>(length_field_size); |
| continue; |
| } |
| |
| // Rewrite OBU header with signaling stream_id |
| { |
| std::vector<uint8_t> obu_size_data(length_field_size + 1); |
| size_t coded_obu_size; |
| avm_uleb_encode(obu_total_size - obu_header_size + 2, |
| sizeof(obu_total_size), obu_size_data.data(), |
| &coded_obu_size); |
| tu_obus.insert(tu_obus.end(), obu_size_data.begin(), |
| obu_size_data.begin() + coded_obu_size); |
| |
| std::vector<uint8_t> obu_header_data(2); |
| write_obu_header_with_stream_id(obu_header_data.data(), &obu_header, |
| stream_ids[seg_idx]); |
| tu_obus.insert(tu_obus.end(), obu_header_data.begin(), |
| obu_header_data.end()); |
| tu_obus.insert(tu_obus.end(), obu_tmp.begin() + obu_header_size, |
| obu_tmp.end()); |
| } |
| data_ptr += |
| static_cast<int>(obu_total_size) + static_cast<int>(length_field_size); |
| } |
| |
| if (obu_overhead_bytes != nullptr) *obu_overhead_bytes = obu_overhead; |
| |
| return tu_obus; |
| } |
| |
| int main(int argc, const char *argv[]) { |
| bool redundant_msdo = false; |
| bool separate_tu = false; |
| int arg_offset = 0; |
| |
| // Parse optional flags |
| while (arg_offset + 1 < argc) { |
| if (strcmp(argv[arg_offset + 1], "--redundant-msdo") == 0) { |
| redundant_msdo = true; |
| ++arg_offset; |
| } else if (strcmp(argv[arg_offset + 1], "--separate-tu") == 0) { |
| separate_tu = true; |
| ++arg_offset; |
| } else { |
| break; |
| } |
| } |
| |
| if (argc - arg_offset < 3 || (argc - arg_offset - 2) % 3) { |
| fprintf(stderr, |
| "command: %s [--redundant-msdo] [--separate-tu] " |
| "[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 - 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 - arg_offset - 2) / 3; |
| int sum_buffer_units = 0; |
| |
| for (int i = 0; i < num_streams; ++i) { |
| int stream_id = atoi(argv[arg_offset + i * 3 + 2]); |
| if (stream_id < 0 || stream_id >= (1 << XLAYER_BITS)) { |
| fprintf(stderr, "The value of stream_id must be in range [0, %d]\n", |
| (1 << XLAYER_BITS) - 1); |
| return -1; |
| } |
| } |
| for (int i = 0; i < num_streams; ++i) { |
| sum_buffer_units += atoi(argv[arg_offset + i * 3 + 3]); |
| } |
| if (sum_buffer_units > 8) { |
| fprintf(stderr, |
| "The sum of stream buffer units cannot exceed the max value (8)\n"); |
| return -1; |
| } |
| |
| FILE *fout = fopen(argv[argc - 1], "wb"); |
| |
| if (fout == nullptr) { |
| fprintf(stderr, "Error: failed to open the output file: %s", |
| argv[argc - 1]); |
| exit(1); |
| } |
| |
| InputContext input_ctx[AVM_MAX_NUM_STREAMS]; |
| AvxInputContext avx_ctx[AVM_MAX_NUM_STREAMS]; |
| ObuDecInputContext obu_ctx[AVM_MAX_NUM_STREAMS]; |
| #if CONFIG_WEBM_IO |
| WebmInputContext webm_ctx[AVM_MAX_NUM_STREAMS]; |
| #endif |
| std::vector<uint8_t> segments; |
| FILE *fin[AVM_MAX_NUM_STREAMS]; |
| |
| // Initialize file read for each stream |
| for (int i = 0; i < num_streams; ++i) { |
| fin[i] = fopen(argv[arg_offset + i * 3 + 1], "rb"); |
| if (fin[i] == nullptr) { |
| fprintf(stderr, "Error: failed to open the input file\n"); |
| } |
| |
| input_ctx[i].avx_ctx = &avx_ctx[i]; |
| input_ctx[i].obu_ctx = &obu_ctx[i]; |
| #if CONFIG_WEBM_IO |
| input_ctx[i].webm_ctx = &webm_ctx[i]; |
| #endif |
| |
| input_ctx[i].Init(); |
| avx_ctx[i].file = fin[i]; |
| avx_ctx[i].file_type = GetFileType(&input_ctx[i]); |
| |
| // behavior underneath the function calls. |
| input_ctx[i].unit_buffer = |
| reinterpret_cast<uint8_t *>(calloc(kInitialBufferSize, 1)); |
| if (!input_ctx[i].unit_buffer) { |
| fprintf(stderr, "Error: No memory, can't alloc input buffer.\n"); |
| } |
| input_ctx[i].unit_buffer_size = kInitialBufferSize; |
| } |
| |
| #if PRINT_TU_INFO |
| printf("\n =========== Start muxing bitstreams ==============\n\n"); |
| printf(" Number of streams: %d\n", num_streams); |
| #endif // PRINT_TU_INFO |
| |
| // Set the values of unit sizes of streams |
| int stream_ids[AVM_MAX_NUM_STREAMS]; |
| #if PRINT_TU_INFO |
| printf(" Stream IDs: "); |
| #endif // PRINT_TU_INFO |
| for (int i = 0; i < num_streams; ++i) { |
| stream_ids[i] = atoi(argv[arg_offset + i * 3 + 2]); |
| #if PRINT_TU_INFO |
| printf("[%d] ", stream_ids[i]); |
| #endif // PRINT_TU_INFO |
| } |
| |
| #if PRINT_TU_INFO |
| printf("\n Stream_buffer_units: "); |
| #endif // PRINT_TU_INFO |
| // 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[arg_offset + i * 3 + 3]); |
| #if PRINT_TU_INFO |
| printf("[%d] ", stream_buffer_units[i]); |
| #endif // PRINT_TU_INFO |
| } |
| #if PRINT_TU_INFO |
| printf("\n\n"); |
| #endif // PRINT_TU_INFO |
| |
| // Multiplex TUs of multi-streams |
| int num_tu_read = 1; |
| int num_total_tus = 0; |
| int unit_number[AVM_MAX_NUM_STREAMS]; |
| for (int i = 0; i < num_streams; ++i) unit_number[i] = 0; |
| |
| if (separate_tu) { |
| // Legacy path: one output TU per input TU, round-robin |
| while (num_tu_read) { |
| num_tu_read = 0; |
| for (int i = 0; i < num_streams; ++i) { |
| size_t unit_size = 0; |
| if (ReadTemporalUnit(&input_ctx[i], &unit_size)) { |
| #if PRINT_TU_INFO |
| printf("Stream Idx %d\n", i); |
| printf("Temporal unit %d\n", unit_number[i]); |
| #endif // PRINT_TU_INFO |
| int obu_overhead_current_unit = 0; |
| segments = |
| WriteTU(input_ctx[i].unit_buffer, static_cast<int>(unit_size), |
| &obu_overhead_current_unit, i, num_streams, stream_ids, |
| 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); |
| printf(" TU total: %ld\n", unit_size); |
| #endif // PRINT_TU_INFO |
| ++unit_number[i]; |
| ++num_tu_read; |
| } |
| } |
| } |
| } else { |
| // Combined TU path: merge corresponding TUs into a single output TU |
| |
| // Pre-compute stream indices sorted by ascending stream_id |
| int sorted_indices[AVM_MAX_NUM_STREAMS]; |
| for (int i = 0; i < num_streams; ++i) sorted_indices[i] = i; |
| for (int i = 0; i < num_streams - 1; ++i) { |
| for (int j = i + 1; j < num_streams; ++j) { |
| if (stream_ids[sorted_indices[j]] < stream_ids[sorted_indices[i]]) { |
| int tmp = sorted_indices[i]; |
| sorted_indices[i] = sorted_indices[j]; |
| sorted_indices[j] = tmp; |
| } |
| } |
| } |
| |
| while (num_tu_read) { |
| num_tu_read = 0; |
| bool any_key_frame = false; |
| std::vector<std::vector<uint8_t>> per_stream_obus(num_streams); |
| |
| // Phase 1: Read one TU from each active stream |
| for (int i = 0; i < num_streams; ++i) { |
| size_t unit_size = 0; |
| if (ReadTemporalUnit(&input_ctx[i], &unit_size)) { |
| #if PRINT_TU_INFO |
| printf("Stream Idx %d\n", i); |
| printf("Temporal unit %d\n", unit_number[i]); |
| #endif // PRINT_TU_INFO |
| if (TuHasKeyFrame(input_ctx[i].unit_buffer, |
| static_cast<int>(unit_size))) { |
| any_key_frame = true; |
| } |
| |
| per_stream_obus[i] = |
| WriteStreamOBUs(input_ctx[i].unit_buffer, |
| static_cast<int>(unit_size), stream_ids[i]); |
| |
| ++unit_number[i]; |
| ++num_tu_read; |
| } |
| } |
| |
| // Phase 2: Write single combined TU |
| if (num_tu_read > 0) { |
| segments = WriteCombinedTU(per_stream_obus, sorted_indices, num_streams, |
| stream_ids, stream_buffer_units, |
| any_key_frame, redundant_msdo); |
| fwrite(segments.data(), 1, segments.size(), fout); |
| } |
| } |
| } |
| |
| for (int i = 0; i < num_streams; ++i) num_total_tus += unit_number[i]; |
| #if PRINT_TU_INFO |
| printf(" Total number of TUs: %d\n\n", num_total_tus); |
| for (int i = 0; i < num_streams; ++i) |
| printf(" Number of TUs with stream ID %d: %d\n", stream_ids[i], |
| unit_number[i]); |
| printf("\n ========== Completed muxing bitstreams ========== \n\n"); |
| #endif // PRINT_TU_INFO |
| // Initialize file read for each stream |
| for (int i = 0; i < num_streams; ++i) { |
| if (fin[i] != nullptr) { |
| fclose(fin[i]); |
| fin[i] = nullptr; |
| } |
| } |
| fclose(fout); |
| return EXIT_SUCCESS; |
| } |