Weiguo Shao 2020-06-04 13:02:49 +08:00 committed by GitHub
parent f51e98c422
commit 4f068bfaa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 390 additions and 28 deletions

View File

@ -17,6 +17,7 @@ Alen Vrecko <alen.vrecko@gmail.com>
Anders Hasselqvist <anders.hasselqvist@gmail.com> Anders Hasselqvist <anders.hasselqvist@gmail.com>
Chun-da Chen <capitalm.c@gmail.com> Chun-da Chen <capitalm.c@gmail.com>
Daniel Cantarín <canta@canta.com.ar> Daniel Cantarín <canta@canta.com.ar>
Dolby Laboratories <*@dolby.com>
Evgeny Zajcev <zevlg@yandex.ru> Evgeny Zajcev <zevlg@yandex.ru>
Google Inc. <*@google.com> Google Inc. <*@google.com>
Ivi.ru LLC <*@ivi.ru> Ivi.ru LLC <*@ivi.ru>

View File

@ -43,3 +43,4 @@ Sanil Raut <sr1990003@gmail.com>
Sergio Ammirata <sergio@ammirata.net> Sergio Ammirata <sergio@ammirata.net>
Thomas Inskip <tinskip@google.com> Thomas Inskip <tinskip@google.com>
Tim Lansen <tim.lansen@gmail.com> Tim Lansen <tim.lansen@gmail.com>
Weiguo Shao <weiguo.shao@dolby.com>

View File

@ -308,10 +308,24 @@ void BuildMediaTag(const MediaPlaylist& playlist,
const MediaPlaylist::MediaPlaylistStreamType kAudio = const MediaPlaylist::MediaPlaylistStreamType kAudio =
MediaPlaylist::MediaPlaylistStreamType::kAudio; MediaPlaylist::MediaPlaylistStreamType::kAudio;
if (playlist.stream_type() == kAudio) { if (playlist.stream_type() == kAudio) {
// 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()); std::string channel_string = std::to_string(playlist.GetNumChannels());
tag.AddQuotedString("CHANNELS", channel_string); tag.AddQuotedString("CHANNELS", channel_string);
} }
}
out->append("\n"); out->append("\n");
} }

View File

@ -32,6 +32,8 @@ const char kDefaultAudioLanguage[] = "en";
const char kDefaultTextLanguage[] = "fr"; const char kDefaultTextLanguage[] = "fr";
const uint32_t kWidth = 800; const uint32_t kWidth = 800;
const uint32_t kHeight = 600; const uint32_t kHeight = 600;
const uint32_t kEC3JocComplexityZero = 0;
const uint32_t kEC3JocComplexity = 16;
std::unique_ptr<MockMediaPlaylist> CreateVideoPlaylist( std::unique_ptr<MockMediaPlaylist> CreateVideoPlaylist(
const std::string& filename, const std::string& filename,
@ -81,11 +83,14 @@ std::unique_ptr<MockMediaPlaylist> CreateAudioPlaylist(
const std::string& language, const std::string& language,
uint64_t channels, uint64_t channels,
uint64_t max_bitrate, uint64_t max_bitrate,
uint64_t avg_bitrate) { uint64_t avg_bitrate,
uint64_t ec3_joc_complexity) {
std::unique_ptr<MockMediaPlaylist> playlist( std::unique_ptr<MockMediaPlaylist> playlist(
new MockMediaPlaylist(filename, name, group)); new MockMediaPlaylist(filename, name, group));
EXPECT_CALL(*playlist, GetNumChannels()).WillRepeatedly(Return(channels)); EXPECT_CALL(*playlist, GetNumChannels()).WillRepeatedly(Return(channels));
EXPECT_CALL(*playlist, GetEC3JocComplexity())
.WillRepeatedly(Return(ec3_joc_complexity));
playlist->SetStreamTypeForTesting( playlist->SetStreamTypeForTesting(
MediaPlaylist::MediaPlaylistStreamType::kAudio); MediaPlaylist::MediaPlaylistStreamType::kAudio);
@ -246,12 +251,12 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
// First audio, english.m3u8. // First audio, english.m3u8.
std::unique_ptr<MockMediaPlaylist> english_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> english_playlist = CreateAudioPlaylist(
"eng.m3u8", "english", "audiogroup", "audiocodec", "en", kAudio1Channels, "eng.m3u8", "english", "audiogroup", "audiocodec", "en", kAudio1Channels,
kAudio1MaxBitrate, kAudio1AvgBitrate); kAudio1MaxBitrate, kAudio1AvgBitrate, kEC3JocComplexityZero);
// Second audio, spanish.m3u8. // Second audio, spanish.m3u8.
std::unique_ptr<MockMediaPlaylist> spanish_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> spanish_playlist = CreateAudioPlaylist(
"spa.m3u8", "espanol", "audiogroup", "audiocodec", "es", kAudio2Channels, "spa.m3u8", "espanol", "audiogroup", "audiocodec", "es", kAudio2Channels,
kAudio2MaxBitrate, kAudio2AvgBitrate); kAudio2MaxBitrate, kAudio2AvgBitrate, kEC3JocComplexityZero);
const char kBaseUrl[] = "http://playlists.org/"; const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
@ -305,12 +310,14 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
// First audio, eng_lo.m3u8. // First audio, eng_lo.m3u8.
std::unique_ptr<MockMediaPlaylist> eng_lo_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> eng_lo_playlist = CreateAudioPlaylist(
"eng_lo.m3u8", "english_lo", "audio_lo", "audiocodec_lo", "en", "eng_lo.m3u8", "english_lo", "audio_lo", "audiocodec_lo", "en",
kAudio1Channels, kAudio1MaxBitrate, kAudio1AvgBitrate); kAudio1Channels, kAudio1MaxBitrate, kAudio1AvgBitrate,
kEC3JocComplexityZero);
// Second audio, eng_hi.m3u8. // Second audio, eng_hi.m3u8.
std::unique_ptr<MockMediaPlaylist> eng_hi_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> eng_hi_playlist = CreateAudioPlaylist(
"eng_hi.m3u8", "english_hi", "audio_hi", "audiocodec_hi", "en", "eng_hi.m3u8", "english_hi", "audio_hi", "audiocodec_hi", "en",
kAudio2Channels, kAudio2MaxBitrate, kAudio2AvgBitrate); kAudio2Channels, kAudio2MaxBitrate, kAudio2AvgBitrate,
kEC3JocComplexityZero);
const char kBaseUrl[] = "http://anydomain.com/"; const char kBaseUrl[] = "http://anydomain.com/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
@ -352,10 +359,12 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) {
// First audio, eng_lo.m3u8. // First audio, eng_lo.m3u8.
std::unique_ptr<MockMediaPlaylist> eng_lo_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> 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<MockMediaPlaylist> eng_hi_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> 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/"; const char kBaseUrl[] = "http://anydomain.com/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist( EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
@ -521,7 +530,8 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) {
// Audio, english.m3u8. // Audio, english.m3u8.
std::unique_ptr<MockMediaPlaylist> audio = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> audio = CreateAudioPlaylist(
"eng.m3u8", "english", "audiogroup", "audiocodec", "en", 2, 50000, 30000); "eng.m3u8", "english", "audiogroup", "audiocodec", "en", 2, 50000, 30000,
kEC3JocComplexityZero);
// Text, english.m3u8. // Text, english.m3u8.
std::unique_ptr<MockMediaPlaylist> text = std::unique_ptr<MockMediaPlaylist> text =
@ -568,10 +578,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMixedPlaylistsDifferentGroups) {
// AUDIO // AUDIO
CreateAudioPlaylist("audio-1.m3u8", "audio 1", "audio-group-1", CreateAudioPlaylist("audio-1.m3u8", "audio 1", "audio-group-1",
"audiocodec", "en", kAudioChannels, kAudioMaxBitrate, "audiocodec", "en", kAudioChannels, kAudioMaxBitrate,
kAudioAvgBitrate), kAudioAvgBitrate, kEC3JocComplexityZero),
CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2", CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2",
"audiocodec", "fr", kAudioChannels, kAudioMaxBitrate, "audiocodec", "fr", kAudioChannels, kAudioMaxBitrate,
kAudioAvgBitrate), kAudioAvgBitrate, kEC3JocComplexityZero),
// SUBTITLES // SUBTITLES
CreateTextPlaylist("text-1.m3u8", "text 1", "text-group-1", "textcodec", CreateTextPlaylist("text-1.m3u8", "text 1", "text-group-1", "textcodec",
@ -679,10 +689,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistAudioOnly) {
// AUDIO // AUDIO
CreateAudioPlaylist("audio-1.m3u8", "audio 1", "audio-group-1", CreateAudioPlaylist("audio-1.m3u8", "audio 1", "audio-group-1",
"audiocodec", "en", kAudioChannels, kAudioMaxBitrate, "audiocodec", "en", kAudioChannels, kAudioMaxBitrate,
kAudioAvgBitrate), kAudioAvgBitrate, kEC3JocComplexityZero),
CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2", CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2",
"audiocodec", "fr", kAudioChannels, kAudioMaxBitrate, "audiocodec", "fr", kAudioChannels, kAudioMaxBitrate,
kAudioAvgBitrate), kAudioAvgBitrate, kEC3JocComplexityZero),
}; };
// Add all the media playlists to the master playlist. // Add all the media playlists to the master playlist.
@ -720,5 +730,54 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistAudioOnly) {
ASSERT_EQ(expected, actual); 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<MockMediaPlaylist> 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<MediaPlaylist*> 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 hls
} // namespace shaka } // namespace shaka

View File

@ -520,6 +520,10 @@ int MediaPlaylist::GetNumChannels() const {
return media_info_.audio_info().num_channels(); 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, bool MediaPlaylist::GetDisplayResolution(uint32_t* width,
uint32_t* height) const { uint32_t* height) const {
DCHECK(width); DCHECK(width);

View File

@ -188,6 +188,11 @@ class MediaPlaylist {
/// @return number of channels for audio. 0 is returned for video. /// @return number of channels for audio. 0 is returned for video.
virtual int GetNumChannels() const; 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 /// @return true if |width| and |height| have been set with a valid
/// resolution values. /// resolution values.
virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const; virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const;

View File

@ -486,6 +486,24 @@ TEST_F(MediaPlaylistMultiSegmentTest, GetNumChannels) {
EXPECT_EQ(8, media_playlist_->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) { TEST_F(MediaPlaylistMultiSegmentTest, Characteristics) {
MediaInfo media_info; MediaInfo media_info;
media_info.set_reference_time_scale(kTimeScale); media_info.set_reference_time_scale(kTimeScale);

View File

@ -48,6 +48,7 @@ class MockMediaPlaylist : public MediaPlaylist {
MOCK_CONST_METHOD0(GetLongestSegmentDuration, double()); MOCK_CONST_METHOD0(GetLongestSegmentDuration, double());
MOCK_METHOD1(SetTargetDuration, void(uint32_t target_duration)); MOCK_METHOD1(SetTargetDuration, void(uint32_t target_duration));
MOCK_CONST_METHOD0(GetNumChannels, int()); MOCK_CONST_METHOD0(GetNumChannels, int());
MOCK_CONST_METHOD0(GetEC3JocComplexity, int());
MOCK_CONST_METHOD2(GetDisplayResolution, MOCK_CONST_METHOD2(GetDisplayResolution,
bool(uint32_t* width, uint32_t* height)); bool(uint32_t* width, uint32_t* height));
MOCK_CONST_METHOD0(GetFrameRate, double()); MOCK_CONST_METHOD0(GetFrameRate, double());

View File

@ -81,10 +81,77 @@ uint8_t ReverseBits8(uint8_t n) {
return ((n >> 4) & 0x0f) | ((n & 0x0f) << 4); 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<uint8_t>& ec3_data, bool ExtractEc3Data(const std::vector<uint8_t>& ec3_data,
uint8_t* audio_coding_mode, uint8_t* audio_coding_mode,
bool* lfe_channel_on, 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()); BitReader bit_reader(ec3_data.data(), ec3_data.size());
// Read number of independent substreams and parse the independent substreams. // Read number of independent substreams and parse the independent substreams.
uint8_t number_independent_substreams; uint8_t number_independent_substreams;
@ -121,8 +188,20 @@ bool ExtractEc3Data(const std::vector<uint8_t>& ec3_data,
*dependent_substreams_layout = 0; *dependent_substreams_layout = 0;
if (number_dependent_substreams > 0) { if (number_dependent_substreams > 0) {
RCHECK(bit_reader.ReadBits(9, dependent_substreams_layout)); 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; return true;
} }
@ -133,8 +212,9 @@ bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data,
uint8_t audio_coding_mode; uint8_t audio_coding_mode;
bool lfe_channel_on; bool lfe_channel_on;
uint16_t dependent_substreams_layout; uint16_t dependent_substreams_layout;
uint32_t ec3_joc_complexity;
if (!ExtractEc3Data(ec3_data, &audio_coding_mode, &lfe_channel_on, 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: " LOG(WARNING) << "Seeing invalid EC3 data: "
<< base::HexEncode(ec3_data.data(), ec3_data.size()); << base::HexEncode(ec3_data.data(), ec3_data.size());
return false; return false;
@ -165,6 +245,15 @@ bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data,
return true; return true;
} }
bool CalculateEC3ChannelMPEGValue(const std::vector<uint8_t>& 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<uint8_t>& ec3_data) { size_t GetEc3NumChannels(const std::vector<uint8_t>& ec3_data) {
uint32_t channel_map; uint32_t channel_map;
if (!CalculateEC3ChannelMap(ec3_data, &channel_map)) if (!CalculateEC3ChannelMap(ec3_data, &channel_map))
@ -181,5 +270,20 @@ size_t GetEc3NumChannels(const std::vector<uint8_t>& ec3_data) {
return num_channels; return num_channels;
} }
bool GetEc3JocComplexity(const std::vector<uint8_t>& 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 media
} // namespace shaka } // namespace shaka

View File

@ -27,6 +27,19 @@ bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data,
/// success; otherwise 0 is returned. /// success; otherwise 0 is returned.
size_t GetEc3NumChannels(const std::vector<uint8_t>& ec3_data); size_t GetEc3NumChannels(const std::vector<uint8_t>& 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<uint8_t>& 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<uint8_t>& ec3_data,
uint32_t* ec3_joc_complexity);
} // namespace media } // namespace media
} // namespace shaka } // namespace shaka

View File

@ -14,12 +14,18 @@ namespace media {
TEST(EC3AudioUtilTest, ChannelTest1) { TEST(EC3AudioUtilTest, ChannelTest1) {
// audio_coding_mode is 7, which is Left, Center, Right, Left surround, Right // audio_coding_mode is 7, which is Left, Center, Right, Left surround, Right
// surround. No dependent substreams. LFE channel on. // surround. No dependent substreams. LFE channel on.
const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x0f, 0}; const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x0f, 0, 0x02, 0x10};
uint32_t channel_map; uint32_t channel_map;
uint32_t ec3_channel_mpeg_value;
uint32_t ec3_joc_complexity;
EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map)); EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map));
EXPECT_EQ(0xF801u, channel_map); EXPECT_EQ(0xF801u, channel_map);
EXPECT_EQ(6u, GetEc3NumChannels(ec3_data)); 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) { TEST(EC3AudioUtilTest, ChannelTest2) {
@ -28,9 +34,15 @@ TEST(EC3AudioUtilTest, ChannelTest2) {
const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x04, 0}; const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x04, 0};
uint32_t channel_map; uint32_t channel_map;
uint32_t ec3_channel_mpeg_value;
uint32_t ec3_joc_complexity;
EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map)); EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map));
EXPECT_EQ(0xA000u, channel_map); EXPECT_EQ(0xA000u, channel_map);
EXPECT_EQ(2u, GetEc3NumChannels(ec3_data)); 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) { TEST(EC3AudioUtilTest, ChannelTest3) {
@ -40,9 +52,33 @@ TEST(EC3AudioUtilTest, ChannelTest3) {
const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x07, 0x07, 0x03}; const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x07, 0x07, 0x03};
uint32_t channel_map; uint32_t channel_map;
uint32_t ec3_channel_mpeg_value;
uint32_t ec3_joc_complexity;
EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map)); EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map));
EXPECT_EQ(0xE603u, channel_map); EXPECT_EQ(0xE603u, channel_map);
EXPECT_EQ(9u, GetEc3NumChannels(ec3_data)); 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<uint8_t> 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 } // namespace media

View File

@ -120,8 +120,21 @@ void AddAudioInfo(const AudioStreamInfo* audio_stream_info,
LOG(ERROR) << "Failed to calculate EC3 channel map."; LOG(ERROR) << "Failed to calculate EC3 channel map.";
return; return;
} }
audio_info->mutable_codec_specific_data()->set_ec3_channel_map( auto* codec_data = audio_info->mutable_codec_specific_data();
ec3_channel_map); 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);
} }
} }

View File

@ -65,6 +65,16 @@ message MediaInfo {
// EC3 Channel map bit fields, encoded based on ETSI TS 102 366 V1.3.1 // 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. // Digital Audio Compression (AC-3, Enhanced AC-3) Standard E.1.3.1.8.
optional uint32 ec3_channel_map = 1; 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 { message TextInfo {

View File

@ -460,14 +460,46 @@ bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
std::string audio_channel_config_value; std::string audio_channel_config_value;
if (audio_info.codec() == kEC3Codec) { if (audio_info.codec() == kEC3Codec) {
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 // Convert EC3 channel map into string of hexadecimal digits. Spec: DASH-IF
// Interoperability Points v3.0 9.2.1.2. // Interoperability Points v3.0 9.2.1.2.
const uint16_t ec3_channel_map = const uint16_t ec3_channel_map =
base::HostToNet16(audio_info.codec_specific_data().ec3_channel_map()); base::HostToNet16(codec_data.ec3_channel_map());
audio_channel_config_value = audio_channel_config_value =
base::HexEncode(&ec3_channel_map, sizeof(ec3_channel_map)); 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"; "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 { } else {
audio_channel_config_value = base::UintToString(audio_info.num_channels()); audio_channel_config_value = base::UintToString(audio_info.num_channels());
audio_channel_config_scheme = audio_channel_config_scheme =

View File

@ -214,15 +214,17 @@ TEST(XmlNodeTest, AddContentProtectionElements) {
TEST(XmlNodeTest, AddEC3AudioInfo) { TEST(XmlNodeTest, AddEC3AudioInfo) {
MediaInfo::AudioInfo audio_info; MediaInfo::AudioInfo audio_info;
audio_info.set_codec("ec-3"); 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_map(0xF801);
audio_info.mutable_codec_specific_data()->set_ec3_channel_mpeg_value(
0xFFFFFFFF);
RepresentationXmlNode representation; RepresentationXmlNode representation;
representation.AddAudioInfo(audio_info); representation.AddAudioInfo(audio_info);
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation.GetRawPtr(),
XmlNodeEqual( XmlNodeEqual(
"<Representation audioSamplingRate=\"44100\">\n" "<Representation audioSamplingRate=\"48000\">\n"
" <AudioChannelConfiguration\n" " <AudioChannelConfiguration\n"
" schemeIdUri=\n" " schemeIdUri=\n"
" \"tag:dolby.com,2014:dash:audio_channel_configuration:2011\"\n" " \"tag:dolby.com,2014:dash:audio_channel_configuration:2011\"\n"
@ -230,6 +232,55 @@ TEST(XmlNodeTest, AddEC3AudioInfo) {
"</Representation>\n")); "</Representation>\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(
"<Representation audioSamplingRate=\"48000\">\n"
" <AudioChannelConfiguration\n"
" schemeIdUri=\n"
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
" value=\"6\"/>\n"
"</Representation>\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(
"<Representation audioSamplingRate=\"48000\">\n"
" <AudioChannelConfiguration\n"
" schemeIdUri=\n"
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
" value=\"6\"/>\n"
" <SupplementalProperty\n"
" schemeIdUri=\n"
" \"tag:dolby.com,2018:dash:EC3_ExtensionType:2018\"\n"
" value=\"JOC\"/>\n"
" <SupplementalProperty\n"
" schemeIdUri=\n"
" \"tag:dolby.com,2018:dash:EC3_ExtensionComplexityIndex:2018\"\n"
" value=\"16\"/>\n"
"</Representation>\n"));
}
class LiveSegmentTimelineTest : public ::testing::Test { class LiveSegmentTimelineTest : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {