| /* |
| * 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/. |
| */ |
| |
| #include "tools/stream_mux.h" |
| // This function read a multi-stream decoder operation OBU. |
| static int read_multi_stream_decoder_operation(struct aom_read_bit_buffer *rb, |
| int *stream_ids) { |
| #if PRINT_TU_INFO |
| printf("\n==Parse mutl-stream header==\n"); |
| #endif // PRINT_TU_INFO |
| const int num_streams = |
| aom_rb_read_literal(rb, 3) + 2; // read number of streams |
| #if PRINT_TU_INFO |
| printf("--num_streams: %d\n", num_streams); |
| #endif // PRINT_TU_INFO |
| if (num_streams > AOM_MAX_NUM_STREAMS) { |
| fprintf(stderr, "The number of streams cannot exceed the max value (4).\n"); |
| return -1; |
| } |
| |
| const int multistream_profile_idx = |
| aom_rb_read_literal(rb, PROFILE_BITS); // read profile of multistream |
| #if PRINT_TU_INFO |
| printf("--multistream_profile_idx: %d\n", multistream_profile_idx); |
| #endif // PRINT_TU_INFO |
| (void)multistream_profile_idx; |
| |
| const int multistream_level_idx = |
| aom_rb_read_literal(rb, LEVEL_BITS); // read level of multistream |
| #if PRINT_TU_INFO |
| printf("--multistream_level_idx: %d\n", multistream_level_idx); |
| #endif // PRINT_TU_INFO |
| (void)multistream_level_idx; |
| |
| const int multistream_tier_idx = |
| aom_rb_read_bit(rb); // read tier of multistream |
| #if PRINT_TU_INFO |
| printf("--multistream_tier_idx: %d\n", multistream_tier_idx); |
| #endif // PRINT_TU_INFO |
| (void)multistream_tier_idx; |
| |
| const int multistream_even_allocation_flag = |
| aom_rb_read_bit(rb); // read multistream_even_allocation_flag |
| |
| if (!multistream_even_allocation_flag) { |
| const int multistream_large_picture_idc = |
| aom_rb_read_literal(rb, 3); // read multistream_large_picture_idc |
| (void)multistream_large_picture_idc; |
| } |
| |
| for (int i = 0; i < num_streams; i++) { |
| stream_ids[i] = aom_rb_read_literal(rb, 5); // read stream ID |
| #if PRINT_TU_INFO |
| printf("--stream_ids[%d]: %d\n", i, stream_ids[i]); |
| #endif // PRINT_TU_INFO |
| const int substream_profile_idx = |
| aom_rb_read_literal(rb, PROFILE_BITS); // read profile of multistream |
| #if PRINT_TU_INFO |
| printf("--sub-stream_profile_idx[%d]: %d\n", stream_ids[i], |
| substream_profile_idx); |
| #endif // PRINT_TU_INFO |
| (void)substream_profile_idx; |
| const int substream_level_idx = |
| aom_rb_read_literal(rb, LEVEL_BITS); // read level of multistream |
| #if PRINT_TU_INFO |
| printf("--sub-stream_level_idx[%d]: %d\n", stream_ids[i], |
| substream_level_idx); |
| #endif // PRINT_TU_INFO |
| (void)substream_level_idx; |
| const int substream_tier_idx = |
| aom_rb_read_bit(rb); // read tier of multistream |
| #if PRINT_TU_INFO |
| printf("--sub-stream_tier_idx[%d]: %d\n", stream_ids[i], |
| substream_tier_idx); |
| #endif // PRINT_TU_INFO |
| (void)substream_tier_idx; |
| } |
| |
| return num_streams; |
| } |
| |
| static void write_obu_header_without_stream_id(uint8_t *const dst, |
| ObuHeader *obu_header) { |
| struct aom_write_bit_buffer wb = { dst, 0 }; |
| aom_wb_write_bit(&wb, 0); // extention flag |
| aom_wb_write_literal(&wb, obu_header->type, 5); // obu_type |
| aom_wb_write_literal(&wb, obu_header->obu_tlayer_id, 2); // obu_temporal |
| } |
| |
| // This function read a temporal unit from a merged bitstream, |
| // writes the temporal unit into each sub-stream. |
| std::vector<uint8_t> ExtractTU(const uint8_t *data, int length, |
| int *obu_overhead_bytes, int *num_streams, |
| int *stream_ids, int *current_stream_id) { |
| std::vector<uint8_t> tu_obus; |
| const uint8_t *data_ptr = data; |
| |
| struct aom_read_bit_buffer rb; |
| |
| const int kObuHeaderSizeBytes = 1; |
| const int kMinimumBytesRequired = 1 + kObuHeaderSizeBytes; |
| int consumed = 0; |
| int obu_overhead = 0; |
| ObuHeader obu_header; |
| 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 (aom_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_extension_flag) { |
| const uint8_t obu_ext_header_byte = |
| *(data + consumed + length_field_size + kObuHeaderSizeBytes); |
| if (!ParseAV2ObuExtensionHeader(obu_ext_header_byte, &obu_header)) { |
| fprintf(stderr, "OBU extension parsing failed at offset %d.\n", |
| static_cast<int>(consumed + length_field_size + |
| kObuHeaderSizeBytes)); |
| } |
| |
| ++obu_overhead; |
| ++obu_header_size; |
| } |
| |
| if (obu_header.obu_extension_flag) { |
| *current_stream_id = obu_header.obu_xlayer_id; |
| } else if (obu_header.type == OBU_MSDO) { |
| *current_stream_id = 31; |
| } else { |
| *current_stream_id = 0; |
| } |
| |
| 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); |
| |
| // Rewrite OBU header with signaling stream_id |
| if (obu_header.type == OBU_TEMPORAL_DELIMITER) { |
| std::vector<uint8_t> obu_size_data(length_field_size); |
| size_t coded_obu_size; |
| aom_uleb_encode(obu_total_size, sizeof(obu_total_size), |
| obu_size_data.data(), &coded_obu_size); |
| if (length_field_size != coded_obu_size) |
| fprintf(stderr, "\nError: length_field_size != coded_obu_size\n"); |
| tu_obus.insert(tu_obus.end(), obu_size_data.begin(), obu_size_data.end()); |
| tu_obus.insert(tu_obus.end(), obu_tmp.begin(), obu_tmp.end()); |
| } else if (obu_header.type == OBU_MSDO) { |
| init_read_bit_buffer( |
| &rb, data_ptr + obu_header_size + static_cast<int>(length_field_size), |
| data_ptr + obu_total_size + length_field_size); |
| *num_streams = read_multi_stream_decoder_operation(&rb, stream_ids); |
| } else { |
| std::vector<uint8_t> obu_size_data(length_field_size); |
| size_t coded_obu_size; |
| aom_uleb_encode(obu_total_size - 1, sizeof(obu_total_size), |
| obu_size_data.data(), &coded_obu_size); |
| if (length_field_size != coded_obu_size) |
| fprintf(stderr, "\nError: length_field_size != coded_obu_size\n"); |
| tu_obus.insert(tu_obus.end(), obu_size_data.begin(), obu_size_data.end()); |
| |
| std::vector<uint8_t> obu_header_data(1); |
| write_obu_header_without_stream_id(obu_header_data.data(), &obu_header); |
| 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; |
| } |
| |
| void generate_filenames(char *base_filename, int num_files, char **file_names) { |
| char *dot_pos; |
| #if PRINT_TU_INFO |
| printf("generate_filenames : base filne name :%s, num of riles: %d\n", |
| base_filename, num_files); |
| #endif // PRINT_TU_INFO |
| // Find the position of the last dot (extension) |
| dot_pos = strrchr(base_filename, '.'); |
| |
| // Generate filenames for the specified number of files |
| for (int index = 0; index < num_files; index++) { |
| if (dot_pos != NULL) { |
| // Copy the part before the extension |
| strncpy(file_names[index], base_filename, dot_pos - base_filename); |
| file_names[index][dot_pos - base_filename] = '\0'; |
| |
| // Add the index |
| sprintf(file_names[index] + strlen(file_names[index]), "_"); |
| sprintf(file_names[index] + strlen(file_names[index]), "%d", index); |
| |
| // Add the extension |
| strcat(file_names[index], dot_pos); |
| } else { |
| // No extension in the base filename |
| strcpy(file_names[index], base_filename); |
| sprintf(file_names[index] + strlen(file_names[index]), "_"); |
| sprintf(file_names[index] + strlen(file_names[index]), "%d", index); |
| } |
| } |
| #if PRINT_TU_INFO |
| for (int index = 0; index < num_files; index++) |
| printf("File name: %s\n", file_names[index]); |
| #endif // PRINT_TU_INFO |
| } |
| |
| int main(int argc, const char *argv[]) { |
| if (argc != 3) { |
| fprintf(stderr, "command: %s [inputfile] [outfile]\n", argv[0]); |
| return -1; |
| } |
| |
| int num_streams = 1; |
| int stream_ids[AOM_MAX_NUM_STREAMS] = { -1, -1, -1, -1 }; |
| char base_filename[100]; |
| strcpy(base_filename, argv[2]); |
| |
| // Initialize file read for the merged stream |
| FILE *fin; |
| fin = fopen(argv[1], "rb"); |
| |
| if (fin == nullptr) { |
| fprintf(stderr, "Error: failed to open the output file: %s", |
| argv[argc - 1]); |
| exit(1); |
| } |
| |
| InputContext input_ctx; |
| AvxInputContext avx_ctx; |
| ObuDecInputContext obu_ctx; |
| #if CONFIG_WEBM_IO |
| WebmInputContext webm_ctx; |
| #endif |
| std::vector<uint8_t> segments; |
| |
| input_ctx.avx_ctx = &avx_ctx; |
| input_ctx.obu_ctx = &obu_ctx; |
| #if CONFIG_WEBM_IO |
| input_ctx.webm_ctx = &webm_ctx; |
| #endif |
| |
| input_ctx.Init(); |
| avx_ctx.file = fin; |
| avx_ctx.file_type = GetFileType(&input_ctx); |
| |
| // behavior underneath the function calls. |
| input_ctx.unit_buffer = |
| reinterpret_cast<uint8_t *>(calloc(kInitialBufferSize, 1)); |
| if (!input_ctx.unit_buffer) { |
| fprintf(stderr, "Error: No memory, can't alloc input buffer.\n"); |
| exit(1); |
| } |
| input_ctx.unit_buffer_size = kInitialBufferSize; |
| |
| // Initialize file write for each stream |
| char *output_file_names[AOM_MAX_NUM_STREAMS]; |
| for (int i = 0; i < AOM_MAX_NUM_STREAMS; ++i) { |
| output_file_names[i] = (char *)malloc(100 * sizeof(char)); |
| if (output_file_names[i] == NULL) { |
| fprintf(stderr, "Error: No memory, can't alloc output_file_names.\n"); |
| exit(1); |
| } |
| } |
| |
| FILE *fout[AOM_MAX_NUM_STREAMS]; |
| |
| generate_filenames(base_filename, AOM_MAX_NUM_STREAMS, output_file_names); |
| |
| for (int i = 0; i < num_streams; ++i) { |
| fout[i] = fopen(output_file_names[i], "wb"); |
| |
| if (fout[i] == nullptr) { |
| fprintf(stderr, "Error: failed to open the output file: %s", |
| output_file_names[i]); |
| exit(1); |
| } |
| } |
| #if PRINT_TU_INFO |
| printf("\n ========== Start demuxing bitstreams =============\n\n"); |
| #endif // PRINT_TU_INFO |
| |
| // Multiplex TUs of multi-streams |
| int num_tu_read = 1; |
| int num_total_tus = 0; |
| int unit_number[AOM_MAX_NUM_STREAMS]; |
| for (int i = 0; i < AOM_MAX_NUM_STREAMS; ++i) unit_number[i] = 0; |
| |
| while (num_tu_read) { |
| int idx = 0; |
| size_t unit_size = 0; |
| int current_stream_id = -1; |
| num_tu_read = 0; |
| if (ReadTemporalUnit(&input_ctx, &unit_size)) { |
| int updated_num_streams = num_streams; |
| int obu_overhead_current_unit = 0; |
| segments = ExtractTU(input_ctx.unit_buffer, static_cast<int>(unit_size), |
| &obu_overhead_current_unit, &updated_num_streams, |
| stream_ids, ¤t_stream_id); |
| if (current_stream_id < 0) |
| fprintf(stderr, |
| "Error: the stream ID value of a frame (header) OBU shall be " |
| "equal to or greater than 0\n"); |
| for (int i = 0; i < updated_num_streams; ++i) { |
| if (current_stream_id == stream_ids[i]) { |
| idx = i; |
| break; |
| } |
| } |
| #if PRINT_TU_INFO |
| printf("Stream Id %d\n", current_stream_id); |
| printf("Temporal unit %d\n", unit_number[idx]); |
| #endif // PRINT_TU_INFO |
| |
| if (updated_num_streams != num_streams) { |
| #if PRINT_TU_INFO |
| printf(" Update number of streams: %d to %d\n", num_streams, |
| updated_num_streams); |
| #endif // PRINT_TU_INFO |
| if (updated_num_streams < 0) { |
| fprintf(stderr, |
| "Error: the parameters in multi_stream header OBU do not " |
| "fullfill the requirements.\n"); |
| return -1; |
| } else if (updated_num_streams > num_streams) { |
| for (int i = num_tu_read; i < updated_num_streams; ++i) { |
| fout[i] = fopen(output_file_names[i], "wb"); |
| |
| if (fout[i] == nullptr) { |
| fprintf(stderr, "Error: failed to open the output file: %s", |
| output_file_names[i]); |
| exit(1); |
| } |
| } |
| } |
| num_streams = updated_num_streams; |
| } |
| fwrite(segments.data(), 1, segments.size(), fout[idx]); |
| #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[idx]; |
| ++num_tu_read; |
| } |
| } |
| |
| 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 demuxing bitstreams ========= \n\n"); |
| #endif // PRINT_TU_INFO |
| for (int i = 0; i < num_streams; ++i) fclose(fout[i]); |
| |
| for (int i = 0; i < AOM_MAX_NUM_STREAMS; ++i) { |
| free(output_file_names[i]); |
| } |
| |
| return EXIT_SUCCESS; |
| } |