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>
|
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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue