// Copyright 2024 Google LLC. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd #include #include #include #include #include #include namespace shaka { namespace media { namespace { const uint8_t kMaxIamfProfile = 2; // 3.2. OBU type // Only the IA Sequence Header and Codec Configs are used in this // implementation. enum ObuType { OBU_IA_Codec_Config = 0, OBU_IA_Sequence_Header = 31, }; // 8.1.1. leb128(). Unsigned integer represented by a variable number of // little-endian bytes. bool ReadLeb128(BitReader& reader, size_t* size, size_t* leb128_bytes) { size_t value = 0; size_t bytes_read = 0; for (int i = 0; i < 8; i++) { size_t leb128_byte = 0; RCHECK(reader.ReadBits(8, &leb128_byte)); value |= (leb128_byte & 0x7f) << (i * 7); bytes_read += 1; if (!(leb128_byte & 0x80)) break; } // It is a requirement of bitstream conformance that the value returned from // the leb128 parsing process is less than or equal to (1<<32) - 1. RCHECK(value <= ((1ull << 32) - 1)); if (size != nullptr) { *size = value; } if (leb128_bytes != nullptr) { *leb128_bytes = bytes_read; } return true; } bool ParseObuHeader(BitReader& reader, int& obu_type, size_t& obu_size) { size_t leb128_bytes; bool obu_trimming_status_flag; bool obu_extension_flag; RCHECK(reader.ReadBits(5, &obu_type)); RCHECK(reader.SkipBits(1)); // Skip obu_redundant_copy RCHECK(reader.ReadBits(1, &obu_trimming_status_flag)); RCHECK(reader.ReadBits(1, &obu_extension_flag)); RCHECK(ReadLeb128(reader, &obu_size, &leb128_bytes)); if (obu_trimming_status_flag) { // Skip num_samples_to_trim_at_end RCHECK(ReadLeb128(reader, nullptr, &leb128_bytes)); obu_size -= leb128_bytes; // Skip num_samples_to_trim_at_start RCHECK(ReadLeb128(reader, nullptr, &leb128_bytes)); obu_size -= leb128_bytes; } if (obu_extension_flag) { size_t extension_header_size; RCHECK(ReadLeb128(reader, &extension_header_size, &leb128_bytes)); obu_size -= leb128_bytes; RCHECK(reader.SkipBits(extension_header_size * 8)); obu_size -= extension_header_size * 8; } return true; } bool ParseSequenceHeaderObu(BitReader& reader, uint8_t& primary_profile, uint8_t& additional_profile) { uint32_t ia_code; RCHECK(reader.ReadBits(32, &ia_code)); if (ia_code != FOURCC_iamf) { LOG(WARNING) << "Unknown ia_code= " << std::setfill('0') << std::setw(8) << std::hex << ia_code; return false; } RCHECK(reader.ReadBits(8, &primary_profile)); if (primary_profile > kMaxIamfProfile) { LOG(WARNING) << "Unknown primary_profile= " << primary_profile; return false; } RCHECK(reader.ReadBits(8, &additional_profile)); if (additional_profile > kMaxIamfProfile) { LOG(WARNING) << "Unknown additional_profile= " << additional_profile; return false; } return true; } bool ParseCodecConfigObu(BitReader& reader, size_t obu_size, Codec& codec) { uint32_t codec_id; size_t leb128_bytes; // Skip codec_config_id RCHECK(ReadLeb128(reader, nullptr, &leb128_bytes)); obu_size -= leb128_bytes; RCHECK(reader.ReadBits(32, &codec_id)); obu_size -= 4; // Skip the remainder of the OBU. RCHECK(reader.SkipBits(obu_size * 8)); switch (codec_id) { case FOURCC_Opus: codec = kCodecOpus; break; case FOURCC_mp4a: codec = kCodecAAC; break; case FOURCC_fLaC: codec = kCodecFlac; break; case FOURCC_ipcm: codec = kCodecPcm; break; default: LOG(WARNING) << "Unknown codec_id= " << std::setfill('0') << std::setw(8) << std::hex << codec_id; return false; } return true; } } // namespace bool GetIamfCodecStringInfo(const std::vector& iacb, uint8_t& codec_string_info) { uint8_t primary_profile = 0; uint8_t additional_profile = 0; // codec used to encode IAMF audio substreams Codec iamf_codec = Codec::kUnknownCodec; int obu_type; size_t obu_size; BitReader reader(iacb.data(), iacb.size()); // configurationVersion RCHECK(reader.SkipBits(8)); // configOBUs_size RCHECK(ReadLeb128(reader, &obu_size, nullptr)); while (reader.bits_available() > 0) { RCHECK(ParseObuHeader(reader, obu_type, obu_size)); switch (obu_type) { case OBU_IA_Sequence_Header: RCHECK(ParseSequenceHeaderObu(reader, primary_profile, additional_profile)); break; case OBU_IA_Codec_Config: RCHECK(ParseCodecConfigObu(reader, obu_size, iamf_codec)); break; default: // Skip other irrelevant OBUs. RCHECK(reader.SkipBits(obu_size * 8)); break; } } // In IAMF v1.1 (https://aomediacodec.github.io/iamf), // the valid values of primary_profile and additional_profile are {0, 1, 2}. // The valid codec_ids are {Opus, mp4a, fLaC, ipcm}. // // This can be represented in uint8_t as: // primary_profile (2bits) + additional_profile (2bits) + iamf_codec (4bits), // where iamf_codec is represented using the Codec enum. // // Since iamf_codec is limited to 16 values, subtract the value of kCodecAudio // to ensure it fits. If future audio codecs are added to the Codec enum, // it may break the assumption that IAMF supported codecs are present within // the first 16 audio codec entries. // Further, if these values change in future version of IAMF, this format may // need to be changed, and AudioStreamInfo::GetCodecString needs to be updated // accordingly. codec_string_info = ((primary_profile << 6) | ((additional_profile << 4) & 0x3F) | ((iamf_codec - kCodecAudio) & 0xF)); return true; } } // namespace media } // namespace shaka