From 4f068bfaa83146cfa0288de7c02ff3751315b754 Mon Sep 17 00:00:00 2001 From: Weiguo Shao Date: Thu, 4 Jun 2020 13:02:49 +0800 Subject: [PATCH] Support DD+JOC in DASH and HLS (#775) Spec: ETSI TS 102 366 V1.4.1 HLS: https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices/hls_authoring_specification_for_apple_devices_appendices DASH: https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/268 Closes #753. --- AUTHORS | 1 + CONTRIBUTORS | 1 + packager/hls/base/master_playlist.cc | 20 +++- packager/hls/base/master_playlist_unittest.cc | 83 ++++++++++++-- packager/hls/base/media_playlist.cc | 4 + packager/hls/base/media_playlist.h | 5 + packager/hls/base/media_playlist_unittest.cc | 18 +++ packager/hls/base/mock_media_playlist.h | 1 + packager/media/codecs/ec3_audio_util.cc | 108 +++++++++++++++++- packager/media/codecs/ec3_audio_util.h | 13 +++ .../media/codecs/ec3_audio_util_unittest.cc | 38 +++++- .../media/event/muxer_listener_internal.cc | 17 ++- packager/mpd/base/media_info.proto | 10 ++ packager/mpd/base/xml/xml_node.cc | 44 ++++++- packager/mpd/base/xml/xml_node_unittest.cc | 55 ++++++++- 15 files changed, 390 insertions(+), 28 deletions(-) diff --git a/AUTHORS b/AUTHORS index 4c46524d6e..a1c7831bcf 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,6 +17,7 @@ Alen Vrecko Anders Hasselqvist Chun-da Chen Daniel CantarĂ­n +Dolby Laboratories <*@dolby.com> Evgeny Zajcev Google Inc. <*@google.com> Ivi.ru LLC <*@ivi.ru> diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 393125947d..4571ca25f9 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -43,3 +43,4 @@ Sanil Raut Sergio Ammirata Thomas Inskip Tim Lansen +Weiguo Shao diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index f0abf23390..9e23fc2b9a 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -308,10 +308,24 @@ void BuildMediaTag(const MediaPlaylist& playlist, const MediaPlaylist::MediaPlaylistStreamType kAudio = MediaPlaylist::MediaPlaylistStreamType::kAudio; if (playlist.stream_type() == kAudio) { - std::string channel_string = std::to_string(playlist.GetNumChannels()); - tag.AddQuotedString("CHANNELS", channel_string); + // According to HLS spec: + // https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis 4.4.6.1. + // CHANNELS is a quoted-string that specifies an ordered, + // slash-separated ("/") list of parameters. The first parameter is a count + // of audio channels, and the second parameter identifies the encoding of + // object-based audio used by the Rendition. HLS Authoring Specification + // for Apple Devices Appendices documents how to handle Dolby Digital Plus + // JOC content. + // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices/hls_authoring_specification_for_apple_devices_appendices + if (playlist.GetEC3JocComplexity() != 0) { + std::string channel_string = + std::to_string(playlist.GetEC3JocComplexity()) + "/JOC"; + tag.AddQuotedString("CHANNELS", channel_string); + } else { + std::string channel_string = std::to_string(playlist.GetNumChannels()); + tag.AddQuotedString("CHANNELS", channel_string); + } } - out->append("\n"); } diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc index 22efb50a72..bce023a60b 100644 --- a/packager/hls/base/master_playlist_unittest.cc +++ b/packager/hls/base/master_playlist_unittest.cc @@ -32,6 +32,8 @@ const char kDefaultAudioLanguage[] = "en"; const char kDefaultTextLanguage[] = "fr"; const uint32_t kWidth = 800; const uint32_t kHeight = 600; +const uint32_t kEC3JocComplexityZero = 0; +const uint32_t kEC3JocComplexity = 16; std::unique_ptr CreateVideoPlaylist( const std::string& filename, @@ -81,11 +83,14 @@ std::unique_ptr CreateAudioPlaylist( const std::string& language, uint64_t channels, uint64_t max_bitrate, - uint64_t avg_bitrate) { + uint64_t avg_bitrate, + uint64_t ec3_joc_complexity) { std::unique_ptr playlist( new MockMediaPlaylist(filename, name, group)); EXPECT_CALL(*playlist, GetNumChannels()).WillRepeatedly(Return(channels)); + EXPECT_CALL(*playlist, GetEC3JocComplexity()) + .WillRepeatedly(Return(ec3_joc_complexity)); playlist->SetStreamTypeForTesting( MediaPlaylist::MediaPlaylistStreamType::kAudio); @@ -246,12 +251,12 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { // First audio, english.m3u8. std::unique_ptr english_playlist = CreateAudioPlaylist( "eng.m3u8", "english", "audiogroup", "audiocodec", "en", kAudio1Channels, - kAudio1MaxBitrate, kAudio1AvgBitrate); + kAudio1MaxBitrate, kAudio1AvgBitrate, kEC3JocComplexityZero); // Second audio, spanish.m3u8. std::unique_ptr spanish_playlist = CreateAudioPlaylist( "spa.m3u8", "espanol", "audiogroup", "audiocodec", "es", kAudio2Channels, - kAudio2MaxBitrate, kAudio2AvgBitrate); + kAudio2MaxBitrate, kAudio2AvgBitrate, kEC3JocComplexityZero); const char kBaseUrl[] = "http://playlists.org/"; EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( @@ -305,12 +310,14 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { // First audio, eng_lo.m3u8. std::unique_ptr eng_lo_playlist = CreateAudioPlaylist( "eng_lo.m3u8", "english_lo", "audio_lo", "audiocodec_lo", "en", - kAudio1Channels, kAudio1MaxBitrate, kAudio1AvgBitrate); + kAudio1Channels, kAudio1MaxBitrate, kAudio1AvgBitrate, + kEC3JocComplexityZero); // Second audio, eng_hi.m3u8. std::unique_ptr eng_hi_playlist = CreateAudioPlaylist( "eng_hi.m3u8", "english_hi", "audio_hi", "audiocodec_hi", "en", - kAudio2Channels, kAudio2MaxBitrate, kAudio2AvgBitrate); + kAudio2Channels, kAudio2MaxBitrate, kAudio2AvgBitrate, + kEC3JocComplexityZero); const char kBaseUrl[] = "http://anydomain.com/"; EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( @@ -352,10 +359,12 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) { // First audio, eng_lo.m3u8. std::unique_ptr eng_lo_playlist = CreateAudioPlaylist( - "eng_lo.m3u8", "english", "audio", "audiocodec", "en", 1, 50000, 40000); + "eng_lo.m3u8", "english", "audio", "audiocodec", "en", 1, 50000, 40000, + kEC3JocComplexityZero); std::unique_ptr eng_hi_playlist = CreateAudioPlaylist( - "eng_hi.m3u8", "english", "audio", "audiocodec", "en", 8, 100000, 80000); + "eng_hi.m3u8", "english", "audio", "audiocodec", "en", 8, 100000, 80000, + kEC3JocComplexityZero); const char kBaseUrl[] = "http://anydomain.com/"; EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( @@ -521,7 +530,8 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) { // Audio, english.m3u8. std::unique_ptr audio = CreateAudioPlaylist( - "eng.m3u8", "english", "audiogroup", "audiocodec", "en", 2, 50000, 30000); + "eng.m3u8", "english", "audiogroup", "audiocodec", "en", 2, 50000, 30000, + kEC3JocComplexityZero); // Text, english.m3u8. std::unique_ptr text = @@ -568,10 +578,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMixedPlaylistsDifferentGroups) { // AUDIO CreateAudioPlaylist("audio-1.m3u8", "audio 1", "audio-group-1", "audiocodec", "en", kAudioChannels, kAudioMaxBitrate, - kAudioAvgBitrate), + kAudioAvgBitrate, kEC3JocComplexityZero), CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2", "audiocodec", "fr", kAudioChannels, kAudioMaxBitrate, - kAudioAvgBitrate), + kAudioAvgBitrate, kEC3JocComplexityZero), // SUBTITLES CreateTextPlaylist("text-1.m3u8", "text 1", "text-group-1", "textcodec", @@ -679,10 +689,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistAudioOnly) { // AUDIO CreateAudioPlaylist("audio-1.m3u8", "audio 1", "audio-group-1", "audiocodec", "en", kAudioChannels, kAudioMaxBitrate, - kAudioAvgBitrate), + kAudioAvgBitrate, kEC3JocComplexityZero), CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2", "audiocodec", "fr", kAudioChannels, kAudioMaxBitrate, - kAudioAvgBitrate), + kAudioAvgBitrate, kEC3JocComplexityZero), }; // Add all the media playlists to the master playlist. @@ -720,5 +730,54 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistAudioOnly) { ASSERT_EQ(expected, actual); } +TEST_F(MasterPlaylistTest, WriteMasterPlaylistAudioOnlyJOC) { + const uint64_t kAudioChannels = 6; + const uint64_t kAudioMaxBitrate = 50000; + const uint64_t kAudioAvgBitrate = 30000; + + std::unique_ptr media_playlists[] = { + // AUDIO + CreateAudioPlaylist("audio-1.m3u8", "audio 1", "audio-group-1", + "audiocodec", "en", kAudioChannels, kAudioMaxBitrate, + kAudioAvgBitrate, kEC3JocComplexityZero), + CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2", + "audiocodec", "en", kAudioChannels, kAudioMaxBitrate, + kAudioAvgBitrate, kEC3JocComplexity), + }; + + // Add all the media playlists to the master playlist. + std::list media_playlist_list; + for (const auto& media_playlist : media_playlists) { + media_playlist_list.push_back(media_playlist.get()); + } + + const char kBaseUrl[] = "http://playlists.org/"; + EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_, + media_playlist_list)); + + std::string actual; + ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); + + const std::string expected = + "#EXTM3U\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\n" + "\n" + "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/audio-1.m3u8\"," + "GROUP-ID=\"audio-group-1\",LANGUAGE=\"en\",NAME=\"audio 1\"," + "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"6\"\n" + "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/audio-2.m3u8\"," + "GROUP-ID=\"audio-group-2\",LANGUAGE=\"en\",NAME=\"audio 2\"," + "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"16/JOC\"\n" + "\n" + "#EXT-X-STREAM-INF:BANDWIDTH=50000,AVERAGE-BANDWIDTH=30000," + "CODECS=\"audiocodec\",AUDIO=\"audio-group-1\"\n" + "http://playlists.org/audio-1.m3u8\n" + "#EXT-X-STREAM-INF:BANDWIDTH=50000,AVERAGE-BANDWIDTH=30000," + "CODECS=\"audiocodec\",AUDIO=\"audio-group-2\"\n" + "http://playlists.org/audio-2.m3u8\n"; + + ASSERT_EQ(expected, actual); +} } // namespace hls } // namespace shaka diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 9d547f43b4..450d1a75a6 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -520,6 +520,10 @@ int MediaPlaylist::GetNumChannels() const { return media_info_.audio_info().num_channels(); } +int MediaPlaylist::GetEC3JocComplexity() const { + return media_info_.audio_info().codec_specific_data().ec3_joc_complexity(); +} + bool MediaPlaylist::GetDisplayResolution(uint32_t* width, uint32_t* height) const { DCHECK(width); diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index 52465c8835..e3a1aa96c7 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -188,6 +188,11 @@ class MediaPlaylist { /// @return number of channels for audio. 0 is returned for video. virtual int GetNumChannels() const; + /// @return Dolby Digital Plus JOC decoding complexity, ETSI TS 103 420 v1.2.1 + /// Backwards-compatible object audio carriage using Enhanced AC-3 + /// Standard C.3.2.3. + virtual int GetEC3JocComplexity() const; + /// @return true if |width| and |height| have been set with a valid /// resolution values. virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const; diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index f62800e44c..68806b8e83 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -486,6 +486,24 @@ TEST_F(MediaPlaylistMultiSegmentTest, GetNumChannels) { EXPECT_EQ(8, media_playlist_->GetNumChannels()); } +TEST_F(MediaPlaylistMultiSegmentTest, GetEC3JocComplexity) { + MediaInfo media_info; + media_info.set_reference_time_scale(kTimeScale); + + // Returns 0 by default if not audio. + EXPECT_EQ(0, media_playlist_->GetEC3JocComplexity()); + + media_info.mutable_audio_info()->mutable_codec_specific_data()-> + set_ec3_joc_complexity(16); + ASSERT_TRUE(media_playlist_->SetMediaInfo(media_info)); + EXPECT_EQ(16, media_playlist_->GetEC3JocComplexity()); + + media_info.mutable_audio_info()->mutable_codec_specific_data()-> + set_ec3_joc_complexity(6); + ASSERT_TRUE(media_playlist_->SetMediaInfo(media_info)); + EXPECT_EQ(6, media_playlist_->GetEC3JocComplexity()); +} + TEST_F(MediaPlaylistMultiSegmentTest, Characteristics) { MediaInfo media_info; media_info.set_reference_time_scale(kTimeScale); diff --git a/packager/hls/base/mock_media_playlist.h b/packager/hls/base/mock_media_playlist.h index cf9f483448..e757271a3b 100644 --- a/packager/hls/base/mock_media_playlist.h +++ b/packager/hls/base/mock_media_playlist.h @@ -48,6 +48,7 @@ class MockMediaPlaylist : public MediaPlaylist { MOCK_CONST_METHOD0(GetLongestSegmentDuration, double()); MOCK_METHOD1(SetTargetDuration, void(uint32_t target_duration)); MOCK_CONST_METHOD0(GetNumChannels, int()); + MOCK_CONST_METHOD0(GetEC3JocComplexity, int()); MOCK_CONST_METHOD2(GetDisplayResolution, bool(uint32_t* width, uint32_t* height)); MOCK_CONST_METHOD0(GetFrameRate, double()); diff --git a/packager/media/codecs/ec3_audio_util.cc b/packager/media/codecs/ec3_audio_util.cc index faa1cf173a..384032ad73 100644 --- a/packager/media/codecs/ec3_audio_util.cc +++ b/packager/media/codecs/ec3_audio_util.cc @@ -81,10 +81,77 @@ uint8_t ReverseBits8(uint8_t n) { return ((n >> 4) & 0x0f) | ((n & 0x0f) << 4); } +// Mapping of channel configurations to the MPEG audio value based on +// ETSI TS 102 366 V1.4.1 Digital Audio Compression (AC-3, Enhanced AC-3) +// Standard Table I.1.1 +uint32_t EC3ChannelMaptoMPEGValue(uint32_t channel_map) { + uint32_t ret = 0; + + switch (channel_map) { + case kCenter: + ret = 1; + break; + case kLeft | kRight: + ret = 2; + break; + case kCenter| kLeft | kRight: + ret = 3; + break; + case kCenter | kLeft | kRight | kCenterSurround: + ret = 4; + break; + case kCenter | kLeft | kRight | kLeftSurround | kRightSurround: + ret = 5; + break; + case kCenter | kLeft | kRight | kLeftSurround | kRightSurround | + kLFEScreen: + ret = 6; + break; + case kCenter | kLeft | kRight | kLwRwPair | kLeftSurround | kRightSurround | + kLFEScreen: + ret = 7; + break; + case kLeft | kRight | kCenterSurround: + ret = 9; + break; + case kLeft | kRight | kLeftSurround | kRightSurround: + ret = 10; + break; + case kCenter | kLeft | kRight | kLrsRrsPair | kCenterSurround | kLFEScreen: + ret = 11; + break; + case kCenter | kLeft | kRight | kLeftSurround | kRightSurround | + kLrsRrsPair | kLFEScreen: + ret = 12; + break; + case kCenter | kLeft | kRight | kLeftSurround | kRightSurround | + kLFEScreen | kLvhRvhPair: + ret = 14; + break; + case kCenter | kLeft | kRight | kLeftSurround | kRightSurround | + kLFEScreen | kLvhRvhPair | kLtsRtsPair: + ret = 16; + break; + case kCenter | kLeft | kRight | kLeftSurround | kRightSurround | + kLFEScreen | kLvhRvhPair | kCenterVerticalHeight | kLtsRtsPair | + kTopCenterSurround: + ret = 17; + break; + case kCenter | kLeft | kRight | kLsdRsdPair | kLrsRrsPair | kLFEScreen | + kLvhRvhPair | kLtsRtsPair: + ret = 19; + break; + default: + ret = 0xFFFFFFFF; + } + return ret; +} + bool ExtractEc3Data(const std::vector& ec3_data, uint8_t* audio_coding_mode, bool* lfe_channel_on, - uint16_t* dependent_substreams_layout) { + uint16_t* dependent_substreams_layout, + uint32_t* ec3_joc_complexity) { BitReader bit_reader(ec3_data.data(), ec3_data.size()); // Read number of independent substreams and parse the independent substreams. uint8_t number_independent_substreams; @@ -121,8 +188,20 @@ bool ExtractEc3Data(const std::vector& ec3_data, *dependent_substreams_layout = 0; if (number_dependent_substreams > 0) { RCHECK(bit_reader.ReadBits(9, dependent_substreams_layout)); + } else { + RCHECK(bit_reader.SkipBits(1)); + } + *ec3_joc_complexity = 0; + if (bit_reader.bits_available() < 16) { + return true; } + RCHECK(bit_reader.SkipBits(7)); + bool ec3_joc_flag; + RCHECK(bit_reader.ReadBits(1, &ec3_joc_flag)); + if (ec3_joc_flag) { + RCHECK(bit_reader.ReadBits(8, ec3_joc_complexity)); + } return true; } @@ -133,8 +212,9 @@ bool CalculateEC3ChannelMap(const std::vector& ec3_data, uint8_t audio_coding_mode; bool lfe_channel_on; uint16_t dependent_substreams_layout; + uint32_t ec3_joc_complexity; if (!ExtractEc3Data(ec3_data, &audio_coding_mode, &lfe_channel_on, - &dependent_substreams_layout)) { + &dependent_substreams_layout, &ec3_joc_complexity)) { LOG(WARNING) << "Seeing invalid EC3 data: " << base::HexEncode(ec3_data.data(), ec3_data.size()); return false; @@ -165,6 +245,15 @@ bool CalculateEC3ChannelMap(const std::vector& ec3_data, return true; } +bool CalculateEC3ChannelMPEGValue(const std::vector& ec3_data, + uint32_t* ec3_channel_mpeg_value) { + uint32_t channel_map; + if (!CalculateEC3ChannelMap(ec3_data, &channel_map)) + return false; + *ec3_channel_mpeg_value = EC3ChannelMaptoMPEGValue(channel_map); + return true; +} + size_t GetEc3NumChannels(const std::vector& ec3_data) { uint32_t channel_map; if (!CalculateEC3ChannelMap(ec3_data, &channel_map)) @@ -181,5 +270,20 @@ size_t GetEc3NumChannels(const std::vector& ec3_data) { return num_channels; } +bool GetEc3JocComplexity(const std::vector& ec3_data, + uint32_t* ec3_joc_complexity) { + uint8_t audio_coding_mode; + bool lfe_channel_on; + uint16_t dependent_substreams_layout; + + if (!ExtractEc3Data(ec3_data, &audio_coding_mode, &lfe_channel_on, + &dependent_substreams_layout, ec3_joc_complexity)) { + LOG(WARNING) << "Seeing invalid EC3 data: " + << base::HexEncode(ec3_data.data(), ec3_data.size()); + return false; + } + return true; +} + } // namespace media } // namespace shaka diff --git a/packager/media/codecs/ec3_audio_util.h b/packager/media/codecs/ec3_audio_util.h index a698c7c819..cc78a624ad 100644 --- a/packager/media/codecs/ec3_audio_util.h +++ b/packager/media/codecs/ec3_audio_util.h @@ -27,6 +27,19 @@ bool CalculateEC3ChannelMap(const std::vector& ec3_data, /// success; otherwise 0 is returned. size_t GetEc3NumChannels(const std::vector& ec3_data); +/// Parse data from EC3Specific box, calculate EC3 channel map and then +/// obtain channel configuration descriptor value with MPEG scheme based on +/// ETSI TS 102 366 V1.4.1 Digital Audio Compression (AC-3, Enhanced AC-3) +/// Standard, Table I.1.1. +bool CalculateEC3ChannelMPEGValue(const std::vector& ec3_data, + uint32_t* ec3_channel_mpeg_value); + +/// Parse data from EC3Specific box and obtain Dolby Digital Plus JOC +/// decoding complexity based on ETSI TS 103 420 v1.2.1 Backwards-compatible +/// object audio carriage using Enhanced AC-3 Standard chapter C.3.1. +bool GetEc3JocComplexity(const std::vector& ec3_data, + uint32_t* ec3_joc_complexity); + } // namespace media } // namespace shaka diff --git a/packager/media/codecs/ec3_audio_util_unittest.cc b/packager/media/codecs/ec3_audio_util_unittest.cc index 77a81d166a..18d3bc34f5 100644 --- a/packager/media/codecs/ec3_audio_util_unittest.cc +++ b/packager/media/codecs/ec3_audio_util_unittest.cc @@ -14,12 +14,18 @@ namespace media { TEST(EC3AudioUtilTest, ChannelTest1) { // audio_coding_mode is 7, which is Left, Center, Right, Left surround, Right // surround. No dependent substreams. LFE channel on. - const std::vector ec3_data = {0, 0, 0, 0x0f, 0}; + const std::vector ec3_data = {0, 0, 0, 0x0f, 0, 0x02, 0x10}; uint32_t channel_map; + uint32_t ec3_channel_mpeg_value; + uint32_t ec3_joc_complexity; EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map)); EXPECT_EQ(0xF801u, channel_map); EXPECT_EQ(6u, GetEc3NumChannels(ec3_data)); + EXPECT_TRUE(CalculateEC3ChannelMPEGValue(ec3_data, &ec3_channel_mpeg_value)); + EXPECT_EQ(6u, ec3_channel_mpeg_value); + EXPECT_TRUE(GetEc3JocComplexity(ec3_data, &ec3_joc_complexity)); + EXPECT_EQ(0u, ec3_joc_complexity); } TEST(EC3AudioUtilTest, ChannelTest2) { @@ -28,9 +34,15 @@ TEST(EC3AudioUtilTest, ChannelTest2) { const std::vector ec3_data = {0, 0, 0, 0x04, 0}; uint32_t channel_map; + uint32_t ec3_channel_mpeg_value; + uint32_t ec3_joc_complexity; EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map)); EXPECT_EQ(0xA000u, channel_map); EXPECT_EQ(2u, GetEc3NumChannels(ec3_data)); + EXPECT_TRUE(CalculateEC3ChannelMPEGValue(ec3_data, &ec3_channel_mpeg_value)); + EXPECT_EQ(2u, ec3_channel_mpeg_value); + EXPECT_TRUE(GetEc3JocComplexity(ec3_data, &ec3_joc_complexity)); + EXPECT_EQ(0u, ec3_joc_complexity); } TEST(EC3AudioUtilTest, ChannelTest3) { @@ -40,9 +52,33 @@ TEST(EC3AudioUtilTest, ChannelTest3) { const std::vector ec3_data = {0, 0, 0, 0x07, 0x07, 0x03}; uint32_t channel_map; + uint32_t ec3_channel_mpeg_value; + uint32_t ec3_joc_complexity; EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map)); EXPECT_EQ(0xE603u, channel_map); EXPECT_EQ(9u, GetEc3NumChannels(ec3_data)); + EXPECT_TRUE(CalculateEC3ChannelMPEGValue(ec3_data, &ec3_channel_mpeg_value)); + EXPECT_EQ(0xFFFFFFFFu, ec3_channel_mpeg_value); + EXPECT_TRUE(GetEc3JocComplexity(ec3_data, &ec3_joc_complexity)); + EXPECT_EQ(0u, ec3_joc_complexity); +} + +TEST(EC3AudioUtilTest, ChannelTest4) { + // audio_coding_mode is 7, which is Left, Center, Right, Left surround and + // Right surround. LFE channel on. + const std::vector ec3_data = {0x14, 0x00, 0x20, 0x0f, 0x00, 0x01, + 0x10}; + + uint32_t channel_map; + uint32_t ec3_channel_mpeg_value; + uint32_t ec3_joc_complexity; + EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map)); + EXPECT_EQ(0xF801u, channel_map); + EXPECT_EQ(6u, GetEc3NumChannels(ec3_data)); + EXPECT_TRUE(CalculateEC3ChannelMPEGValue(ec3_data, &ec3_channel_mpeg_value)); + EXPECT_EQ(6u, ec3_channel_mpeg_value); + EXPECT_TRUE(GetEc3JocComplexity(ec3_data, &ec3_joc_complexity)); + EXPECT_EQ(16u, ec3_joc_complexity); } } // namespace media diff --git a/packager/media/event/muxer_listener_internal.cc b/packager/media/event/muxer_listener_internal.cc index 8681c07dc9..fb5cfe90e7 100644 --- a/packager/media/event/muxer_listener_internal.cc +++ b/packager/media/event/muxer_listener_internal.cc @@ -120,8 +120,21 @@ void AddAudioInfo(const AudioStreamInfo* audio_stream_info, LOG(ERROR) << "Failed to calculate EC3 channel map."; return; } - audio_info->mutable_codec_specific_data()->set_ec3_channel_map( - ec3_channel_map); + auto* codec_data = audio_info->mutable_codec_specific_data(); + codec_data->set_ec3_channel_map(ec3_channel_map); + uint32_t ec3_channel_mpeg_value; + if (!CalculateEC3ChannelMPEGValue(codec_config, &ec3_channel_mpeg_value)) { + LOG(ERROR) << "Failed to calculate EC3 channel configuration " + << "descriptor value with MPEG scheme."; + return; + } + codec_data->set_ec3_channel_mpeg_value(ec3_channel_mpeg_value); + uint32_t ec3_joc_complexity = 0; + if (!GetEc3JocComplexity(codec_config, &ec3_joc_complexity)) { + LOG(ERROR) << "Failed to obtain DD+JOC Information."; + return; + } + codec_data->set_ec3_joc_complexity(ec3_joc_complexity); } } diff --git a/packager/mpd/base/media_info.proto b/packager/mpd/base/media_info.proto index 92eaf704ec..da7f28dfe7 100644 --- a/packager/mpd/base/media_info.proto +++ b/packager/mpd/base/media_info.proto @@ -65,6 +65,16 @@ message MediaInfo { // EC3 Channel map bit fields, encoded based on ETSI TS 102 366 V1.3.1 // Digital Audio Compression (AC-3, Enhanced AC-3) Standard E.1.3.1.8. optional uint32 ec3_channel_map = 1; + + // EC3 Channel configuration descriptor with MPEG scheme fields, + // encoded based on ETSI TS 102 366 V1.4.1 Digital Audio Compression + // (AC-3, Enhanced AC-3) Standard I.1.2.1. + optional uint32 ec3_channel_mpeg_value = 2; + + // Dolby Digital Plus JOC decoding complexity fields, ETSI TS 103 420 v1.2.1 + // Backwards-compatible object audio carriage using Enhanced AC-3 Standard + // C.3.2.3. + optional uint32 ec3_joc_complexity = 3; } message TextInfo { diff --git a/packager/mpd/base/xml/xml_node.cc b/packager/mpd/base/xml/xml_node.cc index 379155d9fd..a231b3683f 100644 --- a/packager/mpd/base/xml/xml_node.cc +++ b/packager/mpd/base/xml/xml_node.cc @@ -460,14 +460,46 @@ bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) { std::string audio_channel_config_value; if (audio_info.codec() == kEC3Codec) { - // Convert EC3 channel map into string of hexadecimal digits. Spec: DASH-IF - // Interoperability Points v3.0 9.2.1.2. - const uint16_t ec3_channel_map = - base::HostToNet16(audio_info.codec_specific_data().ec3_channel_map()); - audio_channel_config_value = + const auto& codec_data = audio_info.codec_specific_data(); + // Use MPEG scheme if the mpeg value is available and valid, fallback to + // EC3 channel mapping otherwise. + // See https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/268 + const uint32_t ec3_channel_mpeg_value = codec_data.ec3_channel_mpeg_value(); + const uint32_t NO_MAPPING = 0xFFFFFFFF; + if (ec3_channel_mpeg_value == NO_MAPPING) { + // Convert EC3 channel map into string of hexadecimal digits. Spec: DASH-IF + // Interoperability Points v3.0 9.2.1.2. + const uint16_t ec3_channel_map = + base::HostToNet16(codec_data.ec3_channel_map()); + audio_channel_config_value = base::HexEncode(&ec3_channel_map, sizeof(ec3_channel_map)); - audio_channel_config_scheme = + audio_channel_config_scheme = "tag:dolby.com,2014:dash:audio_channel_configuration:2011"; + } else { + // Calculate EC3 channel configuration descriptor value with MPEG scheme. + // Spec: ETSI TS 102 366 V1.4.1 Digital Audio Compression + // (AC-3, Enhanced AC-3) I.1.2. + audio_channel_config_value = base::UintToString(ec3_channel_mpeg_value); + audio_channel_config_scheme = "urn:mpeg:mpegB:cicp:ChannelConfiguration"; + } + bool ret = AddDescriptor("AudioChannelConfiguration", + audio_channel_config_scheme, + audio_channel_config_value); + // Dolby Digital Plus JOC descriptor. Spec: ETSI TS 103 420 v1.2.1 + // Backwards-compatible object audio carriage using Enhanced AC-3 Standard + // D.2.2. + if (codec_data.ec3_joc_complexity() != 0) { + std::string ec3_joc_complexity = + base::UintToString(codec_data.ec3_joc_complexity()); + ret &= AddDescriptor("SupplementalProperty", + "tag:dolby.com,2018:dash:EC3_ExtensionType:2018", + "JOC"); + ret &= AddDescriptor("SupplementalProperty", + "tag:dolby.com,2018:dash:" + "EC3_ExtensionComplexityIndex:2018", + ec3_joc_complexity); + } + return ret; } else { audio_channel_config_value = base::UintToString(audio_info.num_channels()); audio_channel_config_scheme = diff --git a/packager/mpd/base/xml/xml_node_unittest.cc b/packager/mpd/base/xml/xml_node_unittest.cc index ec2abf83f1..1840765c70 100644 --- a/packager/mpd/base/xml/xml_node_unittest.cc +++ b/packager/mpd/base/xml/xml_node_unittest.cc @@ -214,15 +214,17 @@ TEST(XmlNodeTest, AddContentProtectionElements) { TEST(XmlNodeTest, AddEC3AudioInfo) { MediaInfo::AudioInfo audio_info; audio_info.set_codec("ec-3"); - audio_info.set_sampling_frequency(44100); + audio_info.set_sampling_frequency(48000); audio_info.mutable_codec_specific_data()->set_ec3_channel_map(0xF801); + audio_info.mutable_codec_specific_data()->set_ec3_channel_mpeg_value( + 0xFFFFFFFF); RepresentationXmlNode representation; representation.AddAudioInfo(audio_info); EXPECT_THAT( representation.GetRawPtr(), XmlNodeEqual( - "\n" + "\n" " \n")); } +TEST(XmlNodeTest, AddEC3AudioInfoMPEGScheme) { + MediaInfo::AudioInfo audio_info; + audio_info.set_codec("ec-3"); + audio_info.set_sampling_frequency(48000); + audio_info.mutable_codec_specific_data()->set_ec3_channel_map(0xF801); + audio_info.mutable_codec_specific_data()->set_ec3_channel_mpeg_value(6); + + RepresentationXmlNode representation; + representation.AddAudioInfo(audio_info); + EXPECT_THAT( + representation.GetRawPtr(), + XmlNodeEqual( + "\n" + " \n" + "\n")); +} + +TEST(XmlNodeTest, AddEC3AudioInfoMPEGSchemeJOC) { + MediaInfo::AudioInfo audio_info; + audio_info.set_codec("ec-3"); + audio_info.set_sampling_frequency(48000); + audio_info.mutable_codec_specific_data()->set_ec3_channel_map(0xF801); + audio_info.mutable_codec_specific_data()->set_ec3_channel_mpeg_value(6); + audio_info.mutable_codec_specific_data()->set_ec3_joc_complexity(16); + + RepresentationXmlNode representation; + representation.AddAudioInfo(audio_info); + EXPECT_THAT( + representation.GetRawPtr(), + XmlNodeEqual( + "\n" + " \n" + " \n" + " \n" + "\n")); +} + class LiveSegmentTimelineTest : public ::testing::Test { protected: void SetUp() override {