diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 3011cf6ee4..2f7a357672 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -32,6 +32,7 @@ Daniel Cantarín David Cavar Dennis E. Mungai (Brainiarc7) Evgeny Zajcev +Felicia Lim Gabe Kopley Geoff Jukes Haoming Chen diff --git a/README.md b/README.md index a4aa91e305..1d7476d17d 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Shaka Packager supports: | FLAC | I / O | - | - | - | - | | Opus | I / O³ | I / O | - | - | - | | Vorbis | - | I / O | - | - | - | + | IAMF | I / O | - | - | - | - | NOTES: - I for input and O for output. diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 79ee48f5af..f3f2c502c2 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -1104,6 +1104,38 @@ class PackagerFunctionalTest(PackagerAppTest): self._GetFlags(output_dash=True, output_hls=True)) self._CheckTestResults('av1-webm') + def testIamfWithBaseProfileAndPcm(self): + self.assertPackageSuccess( + self._GetStreams(['audio'], + output_format='mp4', + test_files=['bear-iamf-base-pcm.mp4']), + self._GetFlags(output_dash=True, output_hls=True)) + self._CheckTestResults('iamf-base-pcm-mp4') + + def testIamfWithBaseProfileAndOpus(self): + self.assertPackageSuccess( + self._GetStreams(['audio'], + output_format='mp4', + test_files=['bear-iamf-base-opus.mp4']), + self._GetFlags(output_dash=True, output_hls=True)) + self._CheckTestResults('iamf-base-opus-mp4') + + def testIamfWithSimpleProfileAndAacLc(self): + self.assertPackageSuccess( + self._GetStreams(['audio'], + output_format='mp4', + test_files=['bear-iamf-simple-aac-lc.mp4']), + self._GetFlags(output_dash=True, output_hls=True)) + self._CheckTestResults('iamf-simple-aac-lc-mp4') + + def testIamfWithSimpleProfileAndFlac(self): + self.assertPackageSuccess( + self._GetStreams(['audio'], + output_format='mp4', + test_files=['bear-iamf-simple-flac.mp4']), + self._GetFlags(output_dash=True, output_hls=True)) + self._CheckTestResults('iamf-simple-flac-mp4') + def testEncryption(self): self.assertPackageSuccess( self._GetStreams(['audio', 'video']), diff --git a/packager/app/test/testdata/iamf-base-opus-mp4/bear-iamf-base-opus-audio.mp4 b/packager/app/test/testdata/iamf-base-opus-mp4/bear-iamf-base-opus-audio.mp4 new file mode 100644 index 0000000000..e0d870b737 Binary files /dev/null and b/packager/app/test/testdata/iamf-base-opus-mp4/bear-iamf-base-opus-audio.mp4 differ diff --git a/packager/app/test/testdata/iamf-base-opus-mp4/output.m3u8 b/packager/app/test/testdata/iamf-base-opus-mp4/output.m3u8 new file mode 100644 index 0000000000..9064d0762e --- /dev/null +++ b/packager/app/test/testdata/iamf-base-opus-mp4/output.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_0.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="0" + +#EXT-X-STREAM-INF:BANDWIDTH=136796,AVERAGE-BANDWIDTH=134443,CODECS="iamf.001.001.Opus",AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE +stream_0.m3u8 diff --git a/packager/app/test/testdata/iamf-base-opus-mp4/output.mpd b/packager/app/test/testdata/iamf-base-opus-mp4/output.mpd new file mode 100644 index 0000000000..3636024499 --- /dev/null +++ b/packager/app/test/testdata/iamf-base-opus-mp4/output.mpd @@ -0,0 +1,15 @@ + + + + + + + + bear-iamf-base-opus-audio.mp4 + + + + + + + diff --git a/packager/app/test/testdata/iamf-base-opus-mp4/stream_0.m3u8 b/packager/app/test/testdata/iamf-base-opus-mp4/stream_0.m3u8 new file mode 100644 index 0000000000..118d1cf91d --- /dev/null +++ b/packager/app/test/testdata/iamf-base-opus-mp4/stream_0.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-iamf-base-opus-audio.mp4",BYTERANGE="892@0" +#EXTINF:1.014, +#EXT-X-BYTERANGE:16835@960 +bear-iamf-base-opus-audio.mp4 +#EXTINF:1.000, +#EXT-X-BYTERANGE:16789 +bear-iamf-base-opus-audio.mp4 +#EXTINF:0.726, +#EXT-X-BYTERANGE:12422 +bear-iamf-base-opus-audio.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/iamf-base-pcm-mp4/bear-iamf-base-pcm-audio.mp4 b/packager/app/test/testdata/iamf-base-pcm-mp4/bear-iamf-base-pcm-audio.mp4 new file mode 100644 index 0000000000..6ac18f8058 Binary files /dev/null and b/packager/app/test/testdata/iamf-base-pcm-mp4/bear-iamf-base-pcm-audio.mp4 differ diff --git a/packager/app/test/testdata/iamf-base-pcm-mp4/output.m3u8 b/packager/app/test/testdata/iamf-base-pcm-mp4/output.m3u8 new file mode 100644 index 0000000000..0913afc537 --- /dev/null +++ b/packager/app/test/testdata/iamf-base-pcm-mp4/output.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_0.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="0" + +#EXT-X-STREAM-INF:BANDWIDTH=2177226,AVERAGE-BANDWIDTH=1803234,CODECS="iamf.001.001.ipcm",AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE +stream_0.m3u8 diff --git a/packager/app/test/testdata/iamf-base-pcm-mp4/output.mpd b/packager/app/test/testdata/iamf-base-pcm-mp4/output.mpd new file mode 100644 index 0000000000..01eebadd90 --- /dev/null +++ b/packager/app/test/testdata/iamf-base-pcm-mp4/output.mpd @@ -0,0 +1,15 @@ + + + + + + + + bear-iamf-base-pcm-audio.mp4 + + + + + + + diff --git a/packager/app/test/testdata/iamf-base-pcm-mp4/stream_0.m3u8 b/packager/app/test/testdata/iamf-base-pcm-mp4/stream_0.m3u8 new file mode 100644 index 0000000000..8bbf20ce8b --- /dev/null +++ b/packager/app/test/testdata/iamf-base-pcm-mp4/stream_0.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:1 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-iamf-base-pcm-audio.mp4",BYTERANGE="850@0" +#EXTINF:1.000, +#EXT-X-BYTERANGE:208108@918 +bear-iamf-base-pcm-audio.mp4 +#EXTINF:1.000, +#EXT-X-BYTERANGE:208108 +bear-iamf-base-pcm-audio.mp4 +#EXTINF:0.740, +#EXT-X-BYTERANGE:201382 +bear-iamf-base-pcm-audio.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/iamf-simple-aac-lc-mp4/bear-iamf-simple-aac-lc-audio.mp4 b/packager/app/test/testdata/iamf-simple-aac-lc-mp4/bear-iamf-simple-aac-lc-audio.mp4 new file mode 100644 index 0000000000..11f5a097b0 Binary files /dev/null and b/packager/app/test/testdata/iamf-simple-aac-lc-mp4/bear-iamf-simple-aac-lc-audio.mp4 differ diff --git a/packager/app/test/testdata/iamf-simple-aac-lc-mp4/output.m3u8 b/packager/app/test/testdata/iamf-simple-aac-lc-mp4/output.m3u8 new file mode 100644 index 0000000000..04026b8d02 --- /dev/null +++ b/packager/app/test/testdata/iamf-simple-aac-lc-mp4/output.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_0.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="0" + +#EXT-X-STREAM-INF:BANDWIDTH=604125,AVERAGE-BANDWIDTH=590976,CODECS="iamf.000.000.mp4a.40.2",AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE +stream_0.m3u8 diff --git a/packager/app/test/testdata/iamf-simple-aac-lc-mp4/output.mpd b/packager/app/test/testdata/iamf-simple-aac-lc-mp4/output.mpd new file mode 100644 index 0000000000..19302589ba --- /dev/null +++ b/packager/app/test/testdata/iamf-simple-aac-lc-mp4/output.mpd @@ -0,0 +1,15 @@ + + + + + + + + bear-iamf-simple-aac-lc-audio.mp4 + + + + + + + diff --git a/packager/app/test/testdata/iamf-simple-aac-lc-mp4/stream_0.m3u8 b/packager/app/test/testdata/iamf-simple-aac-lc-mp4/stream_0.m3u8 new file mode 100644 index 0000000000..337f779c91 --- /dev/null +++ b/packager/app/test/testdata/iamf-simple-aac-lc-mp4/stream_0.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-iamf-simple-aac-lc-audio.mp4",BYTERANGE="900@0" +#EXTINF:1.003, +#EXT-X-BYTERANGE:75717@968 +bear-iamf-simple-aac-lc-audio.mp4 +#EXTINF:1.003, +#EXT-X-BYTERANGE:72441 +bear-iamf-simple-aac-lc-audio.mp4 +#EXTINF:0.735, +#EXT-X-BYTERANGE:54248 +bear-iamf-simple-aac-lc-audio.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/iamf-simple-flac-mp4/bear-iamf-simple-flac-audio.mp4 b/packager/app/test/testdata/iamf-simple-flac-mp4/bear-iamf-simple-flac-audio.mp4 new file mode 100644 index 0000000000..f490af956b Binary files /dev/null and b/packager/app/test/testdata/iamf-simple-flac-mp4/bear-iamf-simple-flac-audio.mp4 differ diff --git a/packager/app/test/testdata/iamf-simple-flac-mp4/output.m3u8 b/packager/app/test/testdata/iamf-simple-flac-mp4/output.m3u8 new file mode 100644 index 0000000000..9fb81b1279 --- /dev/null +++ b/packager/app/test/testdata/iamf-simple-flac-mp4/output.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_0.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="0" + +#EXT-X-STREAM-INF:BANDWIDTH=977240,AVERAGE-BANDWIDTH=926327,CODECS="iamf.000.000.fLaC",AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE +stream_0.m3u8 diff --git a/packager/app/test/testdata/iamf-simple-flac-mp4/output.mpd b/packager/app/test/testdata/iamf-simple-flac-mp4/output.mpd new file mode 100644 index 0000000000..412ce0a887 --- /dev/null +++ b/packager/app/test/testdata/iamf-simple-flac-mp4/output.mpd @@ -0,0 +1,15 @@ + + + + + + + + bear-iamf-simple-flac-audio.mp4 + + + + + + + diff --git a/packager/app/test/testdata/iamf-simple-flac-mp4/stream_0.m3u8 b/packager/app/test/testdata/iamf-simple-flac-mp4/stream_0.m3u8 new file mode 100644 index 0000000000..48051fad19 --- /dev/null +++ b/packager/app/test/testdata/iamf-simple-flac-mp4/stream_0.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:1 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-iamf-simple-flac-audio.mp4",BYTERANGE="883@0" +#EXTINF:1.000, +#EXT-X-BYTERANGE:116763@951 +bear-iamf-simple-flac-audio.mp4 +#EXTINF:1.000, +#EXT-X-BYTERANGE:122155 +bear-iamf-simple-flac-audio.mp4 +#EXTINF:0.740, +#EXT-X-BYTERANGE:78344 +bear-iamf-simple-flac-audio.mp4 +#EXT-X-ENDLIST diff --git a/packager/media/base/CMakeLists.txt b/packager/media/base/CMakeLists.txt index 7d863fcadd..bbc2fbf422 100644 --- a/packager/media/base/CMakeLists.txt +++ b/packager/media/base/CMakeLists.txt @@ -80,6 +80,7 @@ target_link_libraries(media_handler_test_base add_executable(media_base_unittest aes_cryptor_unittest.cc aes_pattern_cryptor_unittest.cc + audio_stream_info_unittest.cc audio_timestamp_helper_unittest.cc bit_reader_unittest.cc bit_writer_unittest.cc diff --git a/packager/media/base/audio_stream_info.cc b/packager/media/base/audio_stream_info.cc index bb1a6b1975..264bf79278 100644 --- a/packager/media/base/audio_stream_info.cc +++ b/packager/media/base/audio_stream_info.cc @@ -45,6 +45,8 @@ std::string AudioCodecToString(Codec codec) { return "AC4"; case kCodecFlac: return "FLAC"; + case kCodecIAMF: + return "IAMF"; case kCodecOpus: return "Opus"; case kCodecVorbis: @@ -168,6 +170,39 @@ std::string AudioStreamInfo::GetCodecString(Codec codec, (audio_object_type & 0x18) >> 3, audio_object_type & 0x7); case kCodecFlac: return "flac"; + case kCodecIAMF: { + // https://aomediacodec.github.io/iamf/#codecsparameter + // The codecs parameter string is composed as + // + // iamf.xxx.yyy. + // + // - xxx is the IAMF primary profile + // - yyy is the IAMF additional profile + // - are the elements of the codecs parameter + // string if that stream was carried in its own track + // + // audio_object_type is composed of primary_profile (2 bits), + // additional_profile (2 bits) and (IAMF codec - kCodecAudio) (4 bits). + const int iamf_codec = (audio_object_type & 0xF) + kCodecAudio; + + const std::string iamf_codec_string = + absl::StrFormat("iamf.%03d.%03d", (audio_object_type & 0xC0) >> 6, + (audio_object_type & 0x30) >> 4); + + switch (iamf_codec) { + case kCodecOpus: + return absl::StrFormat("%s.%s", iamf_codec_string, "Opus"); + case kCodecAAC: + return absl::StrFormat("%s.%s", iamf_codec_string, "mp4a.40.2"); + case kCodecFlac: + return absl::StrFormat("%s.%s", iamf_codec_string, "fLaC"); + case kCodecPcm: + return absl::StrFormat("%s.%s", iamf_codec_string, "ipcm"); + default: + LOG(WARNING) << "Unknown IAMF codec: " << iamf_codec; + return "unknown"; + } + } case kCodecOpus: return "opus"; case kCodecMP3: diff --git a/packager/media/base/audio_stream_info_unittest.cc b/packager/media/base/audio_stream_info_unittest.cc new file mode 100644 index 0000000000..849b238ee8 --- /dev/null +++ b/packager/media/base/audio_stream_info_unittest.cc @@ -0,0 +1,73 @@ +// 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 + +namespace shaka { +namespace media { + +const int kSimpleProfile = 0; +const int kBaseProfile = 1; + +TEST(AudioStreamInfo, IamfGetCodecStringForSimpleProfilesAndPcm) { + const uint8_t audio_object_type = + ((kSimpleProfile << 6) | // primary_profile + ((kSimpleProfile << 4) & 0x3F) | // additional_profile + ((kCodecPcm - kCodecAudio) & 0xF)); // IAMF codec + + std::string codec_string = + AudioStreamInfo::GetCodecString(kCodecIAMF, audio_object_type); + EXPECT_EQ("iamf.000.000.ipcm", codec_string); +} + +TEST(AudioStreamInfo, IamfGetCodecStringForSimpleProfilesAndOpus) { + const uint8_t audio_object_type = + ((kSimpleProfile << 6) | // primary_profile + ((kSimpleProfile << 4) & 0x3F) | // additional_profile + ((kCodecOpus - kCodecAudio) & 0xF)); // IAMF codec + + std::string codec_string = + AudioStreamInfo::GetCodecString(kCodecIAMF, audio_object_type); + EXPECT_EQ("iamf.000.000.Opus", codec_string); +} + +TEST(AudioStreamInfo, IamfGetCodecStringForSimpleProfilesAndMp4a) { + const uint8_t audio_object_type = + ((kSimpleProfile << 6) | // primary_profile + ((kSimpleProfile << 4) & 0x3F) | // additional_profile + ((kCodecAAC - kCodecAudio) & 0xF)); // IAMF codec + + std::string codec_string = + AudioStreamInfo::GetCodecString(kCodecIAMF, audio_object_type); + EXPECT_EQ("iamf.000.000.mp4a.40.2", codec_string); +} + +TEST(AudioStreamInfo, IamfGetCodecStringForSimpleProfilesAndFlac) { + const uint8_t audio_object_type = + ((kSimpleProfile << 6) | // primary_profile + ((kSimpleProfile << 4) & 0x3F) | // additional_profile + ((kCodecFlac - kCodecAudio) & 0xF)); // IAMF codec + + std::string codec_string = + AudioStreamInfo::GetCodecString(kCodecIAMF, audio_object_type); + EXPECT_EQ("iamf.000.000.fLaC", codec_string); +} + +TEST(AudioStreamInfo, IamfGetCodecStringForBaseProfilesAndPcm) { + const uint8_t audio_object_type = + ((kBaseProfile << 6) | // primary_profile = 1 + ((kBaseProfile << 4) & 0x3F) | // additional_profile = 1 + ((kCodecPcm - kCodecAudio) & 0xF)); // IAMF codec + + std::string codec_string = + AudioStreamInfo::GetCodecString(kCodecIAMF, audio_object_type); + EXPECT_EQ("iamf.001.001.ipcm", codec_string); +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/base/fourccs.h b/packager/media/base/fourccs.h index 335ca3723f..aff345f8f8 100644 --- a/packager/media/base/fourccs.h +++ b/packager/media/base/fourccs.h @@ -86,7 +86,10 @@ enum FourCC : uint32_t { FOURCC_hvc1 = 0x68766331, FOURCC_hvcC = 0x68766343, FOURCC_hvcE = 0x68766345, + FOURCC_iamf = 0x69616d66, + FOURCC_iacb = 0x69616362, FOURCC_iden = 0x6964656e, + FOURCC_ipcm = 0x6970636d, FOURCC_iso6 = 0x69736f36, FOURCC_iso8 = 0x69736f38, FOURCC_isom = 0x69736f6d, diff --git a/packager/media/base/stream_info.h b/packager/media/base/stream_info.h index 1a40816673..9f80290e07 100644 --- a/packager/media/base/stream_info.h +++ b/packager/media/base/stream_info.h @@ -53,7 +53,9 @@ enum Codec { kCodecDTSX, kCodecEAC3, kCodecFlac, + kCodecIAMF, kCodecOpus, + kCodecPcm, kCodecVorbis, kCodecMP3, kCodecMha1, diff --git a/packager/media/codecs/CMakeLists.txt b/packager/media/codecs/CMakeLists.txt index b5bcecbe01..38f4768192 100644 --- a/packager/media/codecs/CMakeLists.txt +++ b/packager/media/codecs/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(media_codecs STATIC h26x_byte_to_unit_stream_converter.cc hevc_decoder_configuration_record.cc hls_audio_util.cc + iamf_audio_util.cc nal_unit_to_byte_stream_converter.cc nalu_reader.cc video_slice_header_parser.cc @@ -52,6 +53,7 @@ add_executable(media_codecs_unittest h26x_bit_reader_unittest.cc hevc_decoder_configuration_record_unittest.cc hls_audio_util_unittest.cc + iamf_audio_util_unittest.cc nal_unit_to_byte_stream_converter_unittest.cc nalu_reader_unittest.cc video_slice_header_parser_unittest.cc diff --git a/packager/media/codecs/iamf_audio_util.cc b/packager/media/codecs/iamf_audio_util.cc new file mode 100644 index 0000000000..a5a71653b6 --- /dev/null +++ b/packager/media/codecs/iamf_audio_util.cc @@ -0,0 +1,209 @@ +// 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; + uint8_t additional_profile; + Codec iamf_codec; // codec used to encode IAMF audio substreams + 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 diff --git a/packager/media/codecs/iamf_audio_util.h b/packager/media/codecs/iamf_audio_util.h new file mode 100644 index 0000000000..9fb3cae52c --- /dev/null +++ b/packager/media/codecs/iamf_audio_util.h @@ -0,0 +1,27 @@ +// 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 +// +// IAMF audio utility functions. + +#ifndef PACKAGER_MEDIA_CODECS_IAMF_AUDIO_UTIL_H_ +#define PACKAGER_MEDIA_CODECS_IAMF_AUDIO_UTIL_H_ + +#include +#include + +namespace shaka { +namespace media { + +/// Parse data from IAMFSpecific box and obtain the profile and codec +/// information needed to construct its Codec String (Section 6.4). +/// @return false if there are parsing errors. +bool GetIamfCodecStringInfo(const std::vector& iamf_data, + uint8_t& codec_string_info); + +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_CODECS_IAMF_AUDIO_UTIL_H_ diff --git a/packager/media/codecs/iamf_audio_util_unittest.cc b/packager/media/codecs/iamf_audio_util_unittest.cc new file mode 100644 index 0000000000..b1f0f35987 --- /dev/null +++ b/packager/media/codecs/iamf_audio_util_unittest.cc @@ -0,0 +1,227 @@ +// 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 + +namespace shaka { +namespace media { + +namespace { + +const int kSimpleProfile = 0; +const int kBaseProfile = 1; + +const std::vector kSimpleIaSequenceObu = { + // OBU header + 0xf8, // obu_type (5 bits), obu_redundant_copy (1 bit), + // obu_trimming_status_flag (1 bit), obu_extension_flag (1 bit) + 0x06, // obu_size + // IASequenceHeaderOBU + 0x69, 0x61, 0x6d, 0x66, // ia_code + 0x00, // primary_profile = simple + 0x00 // additional_profile = simple +}; + +const std::vector kOpusCodecConfigObu = { + // OBU header + 0x00, // obu_type (5 bits), obu_redundant_copy (1 bit), + // obu_trimming_status_flag (1 bit), obu_extension_flag (1 bit) + 0x0A, // obu_size + // CodecConfigOBU + 0xc8, 0x01, // codec_config_id + 0x4f, 0x70, 0x75, 0x73, // codec_id = 'Opus' + 0x46, 0x41, 0x4B, 0x45 // 'F''A''K''E' remainder codec config OBU +}; + +const std::vector kFakeAudioElementObu = { + // OBU header + 0x08, // obu_type (5 bits), obu_redundant_copy (1 bit), + // obu_trimming_status_flag (1 bit), obu_extension_flag (1 bit) + 0x04, // obu_size + // 'F''A''K''E' AudioElementOBU + 0x46, 0x41, 0x4B, 0x45}; + +const std::vector kFakeMixPresentationObu = { + // OBU header + 0x10, // obu_type (5 bits), obu_redundant_copy (1 bit), + // obu_trimming_status_flag (1 bit), obu_extension_flag (1 bit) + 0x04, // obu_size + // 'F''A''K''E' MixPresentationOBU + 0x46, 0x41, 0x4B, 0x45}; + +} // namespace + +TEST(IamfAudioUtilTest, GetCodecStringInfoWithSimpleProfiles) { + std::vector iacb = { + 0x01, // configurationVersion + 0x20 // configOBUs_size + }; + iacb.insert(iacb.end(), kSimpleIaSequenceObu.begin(), + kSimpleIaSequenceObu.end()); + iacb.insert(iacb.end(), kOpusCodecConfigObu.begin(), + kOpusCodecConfigObu.end()); + iacb.insert(iacb.end(), kFakeAudioElementObu.begin(), + kFakeAudioElementObu.end()); + iacb.insert(iacb.end(), kFakeMixPresentationObu.begin(), + kFakeMixPresentationObu.end()); + + uint8_t codec_string_info; + uint8_t expected_codec_string_info = + ((kSimpleProfile << 6) | // primary_profile + ((kSimpleProfile << 4) & 0x3F) | // additional_profile + ((kCodecOpus - kCodecAudio) & 0xF)); // IAMF codec + + ASSERT_TRUE(GetIamfCodecStringInfo(iacb, codec_string_info)); + EXPECT_EQ(expected_codec_string_info, codec_string_info); +} + +TEST(IamfAudioUtilTest, CodecStringInfoTestWithBaseProfiles) { + const std::vector base_ia_sequence_obu = { + // OBU header + 0xf8, // obu_type (5 bits), obu_redundant_copy (1 bit), + // obu_trimming_status_flag (1 bit), obu_extension_flag (1 bit) + 0x06, // obu_size + // IASequenceHeaderOBU + 0x69, 0x61, 0x6d, 0x66, // ia_code + 0x01, // primary_profile = base + 0x01 // additional_profile = base + }; + + std::vector iacb = { + 0x01, // configurationVersion + 0x20 // configOBUs_size + }; + iacb.insert(iacb.end(), base_ia_sequence_obu.begin(), + base_ia_sequence_obu.end()); + iacb.insert(iacb.end(), kOpusCodecConfigObu.begin(), + kOpusCodecConfigObu.end()); + iacb.insert(iacb.end(), kFakeAudioElementObu.begin(), + kFakeAudioElementObu.end()); + iacb.insert(iacb.end(), kFakeMixPresentationObu.begin(), + kFakeMixPresentationObu.end()); + + uint8_t codec_string_info; + uint8_t expected_codec_string_info = + ((kBaseProfile << 6) | // primary_profile = 1 + ((kBaseProfile << 4) & 0x3F) | // additional_profile = 1 + ((kCodecOpus - kCodecAudio) & 0xF)); // IAMF codec + + ASSERT_TRUE(GetIamfCodecStringInfo(iacb, codec_string_info)); + EXPECT_EQ(expected_codec_string_info, codec_string_info); +} + +TEST(IamfAudioUtilTest, CodecStringInfoWithPcm) { + const std::vector pcm_codec_config_obu = { + // OBU header + 0x00, // obu_type (5 bits), obu_redundant_copy (1 bit), + // obu_trimming_status_flag (1 bit), obu_extension_flag (1 bit) + 0x0A, // obu_size + // CodecConfigOBU + 0xc8, 0x01, // codec_config_id + 0x69, 0x70, 0x63, 0x6d, // codec_id = 'ipcm' + 0x46, 0x41, 0x4B, 0x45 // 'F''A''K''E' remainder codec config OBU + }; + + std::vector iacb = { + 0x01, // configurationVersion + 0x20 // configOBUs_size + }; + iacb.insert(iacb.end(), kSimpleIaSequenceObu.begin(), + kSimpleIaSequenceObu.end()); + iacb.insert(iacb.end(), pcm_codec_config_obu.begin(), + pcm_codec_config_obu.end()); + iacb.insert(iacb.end(), kFakeAudioElementObu.begin(), + kFakeAudioElementObu.end()); + iacb.insert(iacb.end(), kFakeMixPresentationObu.begin(), + kFakeMixPresentationObu.end()); + + uint8_t codec_string_info; + uint8_t expected_codec_string_info = + ((kSimpleProfile << 6) | // primary_profile + ((kSimpleProfile << 4) & 0x3F) | // additional_profile + ((kCodecPcm - kCodecAudio) & 0xF)); // IAMF codec + + ASSERT_TRUE(GetIamfCodecStringInfo(iacb, codec_string_info)); + EXPECT_EQ(expected_codec_string_info, codec_string_info); +} + +TEST(IamfAudioUtilTest, CodecStringInfoWithMp4a) { + const std::vector mp4a_codec_config_obu = { + // OBU header + 0x00, // obu_type (5 bits), obu_redundant_copy (1 bit), + // obu_trimming_status_flag (1 bit), obu_extension_flag (1 bit) + 0x0A, // obu_size + // CodecConfigOBU + 0xc8, 0x01, // codec_config_id + 0x6d, 0x70, 0x34, 0x61, // codec_id = 'mp4a' + 0x46, 0x41, 0x4B, 0x45 // 'F''A''K''E' remainder codec config OBU + }; + + std::vector iacb = { + 0x01, // configurationVersion + 0x20 // configOBUs_size + }; + iacb.insert(iacb.end(), kSimpleIaSequenceObu.begin(), + kSimpleIaSequenceObu.end()); + iacb.insert(iacb.end(), mp4a_codec_config_obu.begin(), + mp4a_codec_config_obu.end()); + iacb.insert(iacb.end(), kFakeAudioElementObu.begin(), + kFakeAudioElementObu.end()); + iacb.insert(iacb.end(), kFakeMixPresentationObu.begin(), + kFakeMixPresentationObu.end()); + + uint8_t codec_string_info; + uint8_t expected_codec_string_info = + ((kSimpleProfile << 6) | // primary_profile + ((kSimpleProfile << 4) & 0x3F) | // additional_profile + ((kCodecAAC - kCodecAudio) & 0xF)); // IAMF codec + + ASSERT_TRUE(GetIamfCodecStringInfo(iacb, codec_string_info)); + EXPECT_EQ(expected_codec_string_info, codec_string_info); +} + +TEST(IamfAudioUtilTest, CodecStringInfoWithFlac) { + const std::vector flac_codec_config_obu = { + // OBU header + 0x00, // obu_type (5 bits), obu_redundant_copy (1 bit), + // obu_trimming_status_flag (1 bit), obu_extension_flag (1 bit) + 0x0A, // obu_size + // CodecConfigOBU + 0xc8, 0x01, // codec_config_id + 0x66, 0x4C, 0x61, 0x43, // codec_id = 'fLaC' + 0x46, 0x41, 0x4B, 0x45 // 'F''A''K''E' remainder codec config OBU + }; + + std::vector iacb = { + 0x01, // configurationVersion + 0x20 // configOBUs_size + }; + iacb.insert(iacb.end(), kSimpleIaSequenceObu.begin(), + kSimpleIaSequenceObu.end()); + iacb.insert(iacb.end(), flac_codec_config_obu.begin(), + flac_codec_config_obu.end()); + iacb.insert(iacb.end(), kFakeAudioElementObu.begin(), + kFakeAudioElementObu.end()); + iacb.insert(iacb.end(), kFakeMixPresentationObu.begin(), + kFakeMixPresentationObu.end()); + + uint8_t codec_string_info; + uint8_t expected_codec_string_info = + ((kSimpleProfile << 6) | // primary_profile + ((kSimpleProfile << 4) & 0x3F) | // additional_profile + ((kCodecFlac - kCodecAudio) & 0xF)); // IAMF codec + + ASSERT_TRUE(GetIamfCodecStringInfo(iacb, codec_string_info)); + EXPECT_EQ(expected_codec_string_info, codec_string_info); +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index 1f3cd260b4..8061796c4c 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -1958,6 +1958,27 @@ size_t OpusSpecific::ComputeSizeInternal() { kOpusMagicSignatureSize; } +IAMFSpecific::IAMFSpecific() = default; +IAMFSpecific::~IAMFSpecific() = default; + +FourCC IAMFSpecific::BoxType() const { + return FOURCC_iacb; +} + +bool IAMFSpecific::ReadWriteInternal(BoxBuffer* buffer) { + RCHECK(ReadWriteHeaderInternal(buffer)); + size_t size = buffer->Reading() ? buffer->BytesLeft() : data.size(); + RCHECK(buffer->ReadWriteVector(&data, size)); + return true; +} + +size_t IAMFSpecific::ComputeSizeInternal() { + // This box is optional. Skip it if not initialized. + if (data.empty()) + return 0; + return HeaderSize() + data.size(); +} + FlacSpecific::FlacSpecific() = default; FlacSpecific::~FlacSpecific() = default; @@ -2040,6 +2061,7 @@ bool AudioSampleEntry::ReadWriteInternal(BoxBuffer* buffer) { RCHECK(buffer->TryReadWriteChild(&dec3)); RCHECK(buffer->TryReadWriteChild(&dac4)); RCHECK(buffer->TryReadWriteChild(&dops)); + RCHECK(buffer->TryReadWriteChild(&iacb)); RCHECK(buffer->TryReadWriteChild(&dfla)); RCHECK(buffer->TryReadWriteChild(&mhac)); RCHECK(buffer->TryReadWriteChild(&alac)); @@ -2069,7 +2091,7 @@ size_t AudioSampleEntry::ComputeSizeInternal() { esds.ComputeSize() + ddts.ComputeSize() + dac3.ComputeSize() + dec3.ComputeSize() + dops.ComputeSize() + dfla.ComputeSize() + dac4.ComputeSize() + mhac.ComputeSize() + udts.ComputeSize() + - alac.ComputeSize() + + alac.ComputeSize() + iacb.ComputeSize() + // Reserved and predefined bytes. 6 + 8 + // 6 + 8 bytes reserved. 4; // 4 bytes predefined. diff --git a/packager/media/formats/mp4/box_definitions.h b/packager/media/formats/mp4/box_definitions.h index a3debb60c1..1c4aacb7dd 100644 --- a/packager/media/formats/mp4/box_definitions.h +++ b/packager/media/formats/mp4/box_definitions.h @@ -373,6 +373,12 @@ struct OpusSpecific : Box { uint16_t preskip = 0u; }; +struct IAMFSpecific : Box { + DECLARE_BOX_METHODS(IAMFSpecific); + + std::vector data; +}; + // FLAC specific decoder configuration box: // https://github.com/xiph/flac/blob/master/doc/isoflac.txt // We do not care about the actual data inside, which is simply copied over. @@ -416,6 +422,7 @@ struct AudioSampleEntry : Box { EC3Specific dec3; AC4Specific dac4; OpusSpecific dops; + IAMFSpecific iacb; FlacSpecific dfla; MHAConfiguration mhac; ALACSpecific alac; diff --git a/packager/media/formats/mp4/box_definitions_unittest.cc b/packager/media/formats/mp4/box_definitions_unittest.cc index 2515951c1e..d8c4b6ec02 100644 --- a/packager/media/formats/mp4/box_definitions_unittest.cc +++ b/packager/media/formats/mp4/box_definitions_unittest.cc @@ -1316,6 +1316,21 @@ TEST_F(BoxDefinitionsTest, OpusSampleEntry) { ASSERT_EQ(entry, entry_readback); } +TEST_F(BoxDefinitionsTest, IAMFSampleEntry) { + AudioSampleEntry entry; + entry.format = FOURCC_iamf; + entry.data_reference_index = 2; + entry.channelcount = 0; + entry.samplesize = 16; + entry.samplerate = 0; + Fill(&entry.iacb); + entry.Write(this->buffer_.get()); + + AudioSampleEntry entry_readback; + ASSERT_TRUE(ReadBack(&entry_readback)); + ASSERT_EQ(entry, entry_readback); +} + TEST_F(BoxDefinitionsTest, FlacSampleEntry) { AudioSampleEntry entry; entry.format = FOURCC_fLaC; diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 7aac01590f..59fc7b166c 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -113,6 +114,10 @@ Codec FourCCToCodec(FourCC fourcc) { return kCodecALAC; case FOURCC_fLaC: return kCodecFlac; + case FOURCC_iamf: + return kCodecIAMF; + case FOURCC_ipcm: + return kCodecPcm; case FOURCC_mha1: return kCodecMha1; case FOURCC_mhm1: @@ -583,6 +588,13 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { codec_delay_ns = entry.dops.preskip * kNanosecondsPerSecond / sampling_frequency; break; + case FOURCC_iamf: + codec_config = entry.iacb.data; + if (!GetIamfCodecStringInfo(codec_config, audio_object_type)) { + LOG(ERROR) << "Failed to parse iamf."; + return false; + } + break; case FOURCC_mha1: case FOURCC_mhm1: codec_config = entry.mhac.data; @@ -616,7 +628,12 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { const int16_t roll_distance_in_samples = audio_roll_recovery_entries[0].roll_distance; if (roll_distance_in_samples < 0) { - RCHECK(sampling_frequency != 0); + // IAMF requires the `samplerate` field to be set to 0. + // (https://aomediacodec.github.io/iamf/#iasampleentry-section) + if (actual_format == FOURCC_iamf) + continue; + + RCHECK((sampling_frequency != 0)); seek_preroll_ns = kNanosecondsPerSecond * (-roll_distance_in_samples) / sampling_frequency; } else { diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index b681dc448c..dd74a99986 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -98,6 +98,8 @@ FourCC CodecToFourCC(Codec codec, H26xStreamFormat h26x_stream_format) { return FOURCC_fLaC; case kCodecOpus: return FOURCC_Opus; + case kCodecIAMF: + return FOURCC_iamf; case kCodecMha1: return FOURCC_mha1; case kCodecMhm1: @@ -260,6 +262,13 @@ Status MP4Muxer::DelayInitializeMuxer() { // supported yet. if (codec_fourcc != FOURCC_avc3 && codec_fourcc != FOURCC_hev1) ftyp->compatible_brands.push_back(FOURCC_cmfc); + + if (streams()[0]->stream_type() == kStreamAudio) { + codec_fourcc = + CodecToFourCC(streams()[0]->codec(), H26xStreamFormat::kUnSpecified); + if (codec_fourcc == FOURCC_iamf) + ftyp->compatible_brands.push_back(FOURCC_iamf); + } } moov->header.creation_time = IsoTimeNow(); @@ -556,6 +565,9 @@ bool MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info, case kCodecOpus: audio.dops.opus_identification_header = audio_info->codec_config(); break; + case kCodecIAMF: + audio.iacb.data = audio_info->codec_config(); + break; case kCodecMha1: case kCodecMhm1: audio.mhac.data = audio_info->codec_config(); @@ -576,11 +588,20 @@ bool MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info, audio.channelcount = audio_info->num_channels(); //ETSI TS 103 190-2, E.4.6 samplesize shall be set to 16. audio.samplesize = 16; + } else if (audio_info->codec() == kCodecIAMF) { + // IAMF sets channelcount to 0 + // https://aomediacodec.github.io/iamf/#iasampleentry-section + audio.channelcount = 0; } else { audio.channelcount = audio_info->num_channels(); audio.samplesize = audio_info->sample_bits(); } - audio.samplerate = audio_info->sampling_frequency(); + + // IAMF sets samplerate to 0 + // https://aomediacodec.github.io/iamf/#iasampleentry-section + audio.samplerate = + audio_info->codec() == kCodecIAMF ? 0 : audio_info->sampling_frequency(); + SampleTable& sample_table = trak->media.information.sample_table; SampleDescription& sample_description = sample_table.description; sample_description.type = kAudio; diff --git a/packager/media/test/data/bear-iamf-base-opus.mp4 b/packager/media/test/data/bear-iamf-base-opus.mp4 new file mode 100644 index 0000000000..92167d9223 Binary files /dev/null and b/packager/media/test/data/bear-iamf-base-opus.mp4 differ diff --git a/packager/media/test/data/bear-iamf-base-pcm.mp4 b/packager/media/test/data/bear-iamf-base-pcm.mp4 new file mode 100644 index 0000000000..90f1612501 Binary files /dev/null and b/packager/media/test/data/bear-iamf-base-pcm.mp4 differ diff --git a/packager/media/test/data/bear-iamf-simple-aac-lc.mp4 b/packager/media/test/data/bear-iamf-simple-aac-lc.mp4 new file mode 100644 index 0000000000..e458480039 Binary files /dev/null and b/packager/media/test/data/bear-iamf-simple-aac-lc.mp4 differ diff --git a/packager/media/test/data/bear-iamf-simple-flac.mp4 b/packager/media/test/data/bear-iamf-simple-flac.mp4 new file mode 100644 index 0000000000..3b6f89ce8f Binary files /dev/null and b/packager/media/test/data/bear-iamf-simple-flac.mp4 differ