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.
This commit is contained in:
parent
f51e98c422
commit
4f068bfaa8
1
AUTHORS
1
AUTHORS
|
@ -17,6 +17,7 @@ Alen Vrecko <alen.vrecko@gmail.com>
|
|||
Anders Hasselqvist <anders.hasselqvist@gmail.com>
|
||||
Chun-da Chen <capitalm.c@gmail.com>
|
||||
Daniel Cantarín <canta@canta.com.ar>
|
||||
Dolby Laboratories <*@dolby.com>
|
||||
Evgeny Zajcev <zevlg@yandex.ru>
|
||||
Google Inc. <*@google.com>
|
||||
Ivi.ru LLC <*@ivi.ru>
|
||||
|
|
|
@ -43,3 +43,4 @@ Sanil Raut <sr1990003@gmail.com>
|
|||
Sergio Ammirata <sergio@ammirata.net>
|
||||
Thomas Inskip <tinskip@google.com>
|
||||
Tim Lansen <tim.lansen@gmail.com>
|
||||
Weiguo Shao <weiguo.shao@dolby.com>
|
||||
|
|
|
@ -308,10 +308,24 @@ void BuildMediaTag(const MediaPlaylist& playlist,
|
|||
const MediaPlaylist::MediaPlaylistStreamType kAudio =
|
||||
MediaPlaylist::MediaPlaylistStreamType::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());
|
||||
tag.AddQuotedString("CHANNELS", channel_string);
|
||||
}
|
||||
|
||||
}
|
||||
out->append("\n");
|
||||
}
|
||||
|
||||
|
|
|
@ -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<MockMediaPlaylist> CreateVideoPlaylist(
|
||||
const std::string& filename,
|
||||
|
@ -81,11 +83,14 @@ std::unique_ptr<MockMediaPlaylist> 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<MockMediaPlaylist> 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<MockMediaPlaylist> english_playlist = CreateAudioPlaylist(
|
||||
"eng.m3u8", "english", "audiogroup", "audiocodec", "en", kAudio1Channels,
|
||||
kAudio1MaxBitrate, kAudio1AvgBitrate);
|
||||
kAudio1MaxBitrate, kAudio1AvgBitrate, kEC3JocComplexityZero);
|
||||
|
||||
// Second audio, spanish.m3u8.
|
||||
std::unique_ptr<MockMediaPlaylist> 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<MockMediaPlaylist> 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<MockMediaPlaylist> 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<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(
|
||||
"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<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.
|
||||
std::unique_ptr<MockMediaPlaylist> 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<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 shaka
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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<uint8_t>& ec3_data,
|
|||
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) {
|
||||
uint32_t 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;
|
||||
}
|
||||
|
||||
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 shaka
|
||||
|
|
|
@ -27,6 +27,19 @@ bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data,
|
|||
/// success; otherwise 0 is returned.
|
||||
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 shaka
|
||||
|
||||
|
|
|
@ -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<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 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<uint8_t> 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<uint8_t> 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<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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -460,14 +460,46 @@ bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
|
|||
std::string audio_channel_config_value;
|
||||
|
||||
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
|
||||
// Interoperability Points v3.0 9.2.1.2.
|
||||
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 =
|
||||
base::HexEncode(&ec3_channel_map, sizeof(ec3_channel_map));
|
||||
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 =
|
||||
|
|
|
@ -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(
|
||||
"<Representation audioSamplingRate=\"44100\">\n"
|
||||
"<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"tag:dolby.com,2014:dash:audio_channel_configuration:2011\"\n"
|
||||
|
@ -230,6 +232,55 @@ TEST(XmlNodeTest, AddEC3AudioInfo) {
|
|||
"</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 {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
|
|
Loading…
Reference in New Issue