Add support for --default_text_language

It allows users to override the default language for text tracks.

If not specified, --default_language applies to both audio and text
tracks.

Issue #430.

Change-Id: I86a9baba2072be27b6661fa7b65a8bc8b6adb3cc
This commit is contained in:
KongQun Yang 2018-11-19 16:09:24 -08:00
parent 273ab09f05
commit 4b97a6d8a2
15 changed files with 450 additions and 229 deletions

View File

@ -64,6 +64,14 @@ DASH options
<Role ... value=\"main\" /> in the manifest. This allows the player to
choose the correct default language for the content.
This applies to both audio and text tracks. The default language for text
tracks can be overriden by 'default_text_language'.
--default_text_language <text_language>
Same as above, but this applies to text tracks only, and overrides the
default language for text tracks.
--allow_approximate_segment_timeline
For live profile only.

View File

@ -43,3 +43,11 @@ HLS options
The first audio/text rendition in a group tagged with this language will
have 'DEFAULT' attribute set to 'YES'. This allows the player to choose the
correct default language for the content.
This applies to both audio and text tracks. The default language for text
tracks can be overriden by 'default_text_language'.
--default_text_language <text_language>
Same as above, but this applies to text tracks only, and overrides the
default language for text tracks.

View File

@ -25,4 +25,11 @@ DEFINE_string(default_language,
"have <Role ... value=\"main\" /> in the manifest; For HLS, the "
"first audio/text rendition in a group tagged with this language "
"will have 'DEFAULT' attribute set to 'YES'. This allows the "
"player to choose the correct default language for the content.");
"player to choose the correct default language for the content."
"This applies to both audio and text tracks. The default "
"language for text tracks can be overriden by "
"'--default_text_language'.");
DEFINE_string(default_text_language,
"",
"Same as above, but this applies to text tracks only, and "
"overrides the default language for text tracks.");

View File

@ -14,5 +14,6 @@
DECLARE_double(time_shift_buffer_depth);
DECLARE_uint64(preserved_segments_outside_live_window);
DECLARE_string(default_language);
DECLARE_string(default_text_language);
#endif // PACKAGER_APP_MANIFEST_FLAGS_H_

View File

@ -443,6 +443,7 @@ base::Optional<PackagingParams> GetPackagingParams() {
}
mpd_params.default_language = FLAGS_default_language;
mpd_params.default_text_language = FLAGS_default_text_language;
mpd_params.generate_static_live_mpd = FLAGS_generate_static_mpd;
mpd_params.generate_dash_if_iop_compliant_mpd =
FLAGS_generate_dash_if_iop_compliant_mpd;
@ -460,6 +461,7 @@ base::Optional<PackagingParams> GetPackagingParams() {
hls_params.preserved_segments_outside_live_window =
FLAGS_preserved_segments_outside_live_window;
hls_params.default_language = FLAGS_default_language;
hls_params.default_text_language = FLAGS_default_text_language;
TestParams& test_params = packaging_params.test_params;
test_params.dump_stream_info = FLAGS_dump_stream_info;

View File

@ -343,7 +343,8 @@ void BuildMediaTags(
}
}
void AppendPlaylists(const std::string& default_language,
void AppendPlaylists(const std::string& default_audio_language,
const std::string& default_text_language,
const std::string& base_url,
const std::list<MediaPlaylist*>& playlists,
std::string* content) {
@ -374,12 +375,13 @@ void AppendPlaylists(const std::string& default_language,
if (!audio_playlist_groups.empty()) {
content->append("\n");
BuildMediaTags(audio_playlist_groups, default_language, base_url, content);
BuildMediaTags(audio_playlist_groups, default_audio_language, base_url,
content);
}
if (!subtitle_playlist_groups.empty()) {
content->append("\n");
BuildMediaTags(subtitle_playlist_groups, default_language, base_url,
BuildMediaTags(subtitle_playlist_groups, default_text_language, base_url,
content);
}
@ -406,8 +408,12 @@ void AppendPlaylists(const std::string& default_language,
} // namespace
MasterPlaylist::MasterPlaylist(const std::string& file_name,
const std::string& default_language)
: file_name_(file_name), default_language_(default_language) {}
const std::string& default_audio_language,
const std::string& default_text_language)
: file_name_(file_name),
default_audio_language_(default_audio_language),
default_text_language_(default_text_language) {}
MasterPlaylist::~MasterPlaylist() {}
bool MasterPlaylist::WriteMasterPlaylist(
@ -416,7 +422,8 @@ bool MasterPlaylist::WriteMasterPlaylist(
const std::list<MediaPlaylist*>& playlists) {
std::string content = "#EXTM3U\n";
AppendVersionString(&content);
AppendPlaylists(default_language_, base_url, playlists, &content);
AppendPlaylists(default_audio_language_, default_text_language_, base_url,
playlists, &content);
// Skip if the playlist is already written.
if (content == written_playlist_)

View File

@ -20,10 +20,13 @@ class MediaPlaylist;
class MasterPlaylist {
public:
/// @param file_name is the file name of the master playlist.
/// @param default_language determines the rendition that should be tagged
/// with 'DEFAULT'.
/// @param default_audio_language determines the audio rendition that should
/// be tagged with 'DEFAULT'.
/// @param default_text_language determines the text rendition that should be
/// tagged with 'DEFAULT'.
MasterPlaylist(const std::string& file_name,
const std::string& default_language);
const std::string& default_audio_language,
const std::string& default_text_language);
virtual ~MasterPlaylist();
/// Writes Master Playlist to output_dir + <name of playlist>.
@ -45,7 +48,8 @@ class MasterPlaylist {
std::string written_playlist_;
const std::string file_name_;
const std::string default_language_;
const std::string default_audio_language_;
const std::string default_text_language_;
};
} // namespace hls

View File

@ -28,7 +28,8 @@ using ::testing::StrEq;
namespace {
const char kDefaultMasterPlaylistName[] = "playlist.m3u8";
const char kDefaultLanguage[] = "en";
const char kDefaultAudioLanguage[] = "en";
const char kDefaultTextLanguage[] = "fr";
const uint32_t kWidth = 800;
const uint32_t kHeight = 600;
@ -123,7 +124,9 @@ std::unique_ptr<MockMediaPlaylist> CreateTextPlaylist(
class MasterPlaylistTest : public ::testing::Test {
protected:
MasterPlaylistTest()
: master_playlist_(kDefaultMasterPlaylistName, kDefaultLanguage),
: master_playlist_(kDefaultMasterPlaylistName,
kDefaultAudioLanguage,
kDefaultTextLanguage),
test_output_dir_("memory://test_dir"),
master_playlist_path_(
FilePath::FromUTF8Unsafe(test_output_dir_)
@ -382,10 +385,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideosAndTexts) {
"test\n"
"\n"
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES,"
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\","
"AUTOSELECT=YES\n"
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\","
"GROUP-ID=\"textgroup\",LANGUAGE=\"fr\",NAME=\"french\",AUTOSELECT=YES\n"
"GROUP-ID=\"textgroup\",LANGUAGE=\"fr\",NAME=\"french\",DEFAULT=YES,"
"AUTOSELECT=YES\n"
"\n"
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
"CODECS=\"sdvideocodec,textcodec\",RESOLUTION=800x600,"
@ -423,8 +427,8 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextWithCharacteritics) {
"test\n"
"\n"
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES,"
"AUTOSELECT=YES,CHARACTERISTICS=\""
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",AUTOSELECT=YES,"
"CHARACTERISTICS=\""
"public.accessibility.transcribes-spoken-dialog,public.easy-to-read\"\n"
"\n"
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
@ -463,10 +467,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextGroups) {
"\n"
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
"GROUP-ID=\"en-text-group\",LANGUAGE=\"en\",NAME=\"english\","
"DEFAULT=YES,AUTOSELECT=YES\n"
"AUTOSELECT=YES\n"
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\","
"GROUP-ID=\"fr-text-group\",LANGUAGE=\"fr\",NAME=\"french\","
"AUTOSELECT=YES\n"
"DEFAULT=YES,AUTOSELECT=YES\n"
"\n"
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
"CODECS=\"sdvideocodec,textcodec\",RESOLUTION=800x600,"
@ -511,7 +515,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) {
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n"
"\n"
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES,"
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\","
"AUTOSELECT=YES\n"
"\n"
"#EXT-X-STREAM-INF:BANDWIDTH=350000,AVERAGE-BANDWIDTH=230000,"
@ -537,14 +541,14 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMixedPlaylistsDifferentGroups) {
"audiocodec", "en", kAudioChannels, kAudioMaxBitrate,
kAudioAvgBitrate),
CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2",
"audiocodec", "en", kAudioChannels, kAudioMaxBitrate,
"audiocodec", "fr", kAudioChannels, kAudioMaxBitrate,
kAudioAvgBitrate),
// SUBTITLES
CreateTextPlaylist("text-1.m3u8", "text 1", "text-group-1", "textcodec",
"en"),
CreateTextPlaylist("text-2.m3u8", "text 2", "text-group-2", "textcodec",
"en"),
"fr"),
// VIDEO
CreateVideoPlaylist("video-1.m3u8", "sdvideocodec", kVideoMaxBitrate,
@ -581,14 +585,14 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMixedPlaylistsDifferentGroups) {
"GROUP-ID=\"audio-group-1\",LANGUAGE=\"en\",NAME=\"audio 1\","
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\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=\"2\"\n"
"GROUP-ID=\"audio-group-2\",LANGUAGE=\"fr\",NAME=\"audio 2\","
"AUTOSELECT=YES,CHANNELS=\"2\"\n"
"\n"
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-1.m3u8\","
"GROUP-ID=\"text-group-1\",LANGUAGE=\"en\",NAME=\"text 1\","
"DEFAULT=YES,AUTOSELECT=YES\n"
"AUTOSELECT=YES\n"
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-2.m3u8\","
"GROUP-ID=\"text-group-2\",LANGUAGE=\"en\",NAME=\"text 2\","
"GROUP-ID=\"text-group-2\",LANGUAGE=\"fr\",NAME=\"text 2\","
"DEFAULT=YES,AUTOSELECT=YES\n"
"\n"
"#EXT-X-STREAM-INF:BANDWIDTH=350000,AVERAGE-BANDWIDTH=130000,"

View File

@ -282,9 +282,14 @@ SimpleHlsNotifier::SimpleHlsNotifier(const HlsParams& hls_params)
const base::FilePath master_playlist_path(
base::FilePath::FromUTF8Unsafe(hls_params.master_playlist_output));
output_dir_ = master_playlist_path.DirName().AsUTF8Unsafe();
const std::string& default_audio_langauge = hls_params.default_language;
const std::string& default_text_language =
hls_params.default_text_language.empty()
? hls_params.default_language
: hls_params.default_text_language;
master_playlist_.reset(
new MasterPlaylist(master_playlist_path.BaseName().AsUTF8Unsafe(),
hls_params.default_language));
default_audio_langauge, default_text_language));
}
SimpleHlsNotifier::~SimpleHlsNotifier() {}

View File

@ -35,7 +35,8 @@ using ::testing::WithParamInterface;
namespace {
const char kMasterPlaylistName[] = "master.m3u8";
const char kDefaultLanguage[] = "en";
const char kDefaultAudioLanguage[] = "en";
const char kDefaultTextLanguage[] = "fr";
const char kEmptyKeyUri[] = "";
const char kFairPlayKeyUri[] = "skd://www.license.com/getkey?key_id=testing";
const char kIdentityKeyUri[] = "https://www.license.com/getkey?key_id=testing";
@ -45,7 +46,9 @@ const HlsPlaylistType kLivePlaylist = HlsPlaylistType::kLive;
class MockMasterPlaylist : public MasterPlaylist {
public:
MockMasterPlaylist()
: MasterPlaylist(kMasterPlaylistName, kDefaultLanguage) {}
: MasterPlaylist(kMasterPlaylistName,
kDefaultAudioLanguage,
kDefaultTextLanguage) {}
MOCK_METHOD3(WriteMasterPlaylist,
bool(const std::string& prefix,

View File

@ -45,7 +45,12 @@ struct HlsParams {
/// The renditions tagged with this language will have 'DEFAULT' set to 'YES'
/// in 'EXT-X-MEDIA' tag. This allows the player to choose the correct default
/// language for the content.
/// This applies to both audio and text tracks. The default language for text
/// tracks can be overriden by 'default_text_language'.
std::string default_language;
/// Same as above, but this overrides the default language for text tracks,
/// i.e. subtitles or close-captions.
std::string default_text_language;
/// This is the target segment duration requested by the user. The actual
/// segment duration may be different to the target segment duration.
/// This parameter is included here to for bandwidth estimator to exclude the

View File

@ -32,6 +32,16 @@ std::set<std::string> GetUUIDs(
return uuids;
}
const std::string& GetDefaultAudioLanguage(const MpdOptions& mpd_options) {
return mpd_options.mpd_params.default_language;
}
const std::string& GetDefaultTextLanguage(const MpdOptions& mpd_options) {
return mpd_options.mpd_params.default_text_language.empty()
? mpd_options.mpd_params.default_language
: mpd_options.mpd_params.default_text_language;
}
} // namespace
Period::Period(uint32_t period_id,
@ -149,8 +159,14 @@ bool Period::SetNewAdaptationSetAttributes(
const MediaInfo& media_info,
const std::list<AdaptationSet*>& adaptation_sets,
AdaptationSet* new_adaptation_set) {
if (!language.empty() && language == mpd_options_.mpd_params.default_language)
new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
if (!language.empty()) {
const bool is_main_role =
language == (media_info.has_audio_info()
? GetDefaultAudioLanguage(mpd_options_)
: GetDefaultTextLanguage(mpd_options_));
if (is_main_role)
new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
}
if (media_info.has_video_info()) {
// Because 'language' is ignored for videos, |adaptation_sets| must have

View File

@ -93,15 +93,13 @@ class TestablePeriod : public Period {
} // namespace
class PeriodTest : public ::testing::TestWithParam<bool> {
class PeriodTest : public ::testing::Test {
public:
PeriodTest()
: testable_period_(mpd_options_),
default_adaptation_set_(new StrictMock<MockAdaptationSet>()),
default_adaptation_set_ptr_(default_adaptation_set_.get()) {}
void SetUp() override { content_protection_in_adaptation_set_ = GetParam(); }
protected:
MpdOptions mpd_options_;
TestablePeriod testable_period_;
@ -112,7 +110,7 @@ class PeriodTest : public ::testing::TestWithParam<bool> {
StrictMock<MockAdaptationSet>* default_adaptation_set_ptr_;
};
TEST_P(PeriodTest, GetXml) {
TEST_F(PeriodTest, GetXml) {
const char kVideoMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
@ -143,7 +141,7 @@ TEST_P(PeriodTest, GetXml) {
XmlNodeEqual(kExpectedXml));
}
TEST_P(PeriodTest, DynamicMpdGetXml) {
TEST_F(PeriodTest, DynamicMpdGetXml) {
const char kVideoMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
@ -175,7 +173,7 @@ TEST_P(PeriodTest, DynamicMpdGetXml) {
XmlNodeEqual(kExpectedXml));
}
TEST_P(PeriodTest, SetDurationAndGetXml) {
TEST_F(PeriodTest, SetDurationAndGetXml) {
const char kVideoMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
@ -217,7 +215,7 @@ TEST_P(PeriodTest, SetDurationAndGetXml) {
}
// Verify ForceSetSegmentAlignment is called.
TEST_P(PeriodTest, Text) {
TEST_F(PeriodTest, Text) {
const char kTextMediaInfo[] =
"text_info {\n"
" codec: 'ttml'\n"
@ -235,7 +233,7 @@ TEST_P(PeriodTest, Text) {
content_protection_in_adaptation_set_));
}
TEST_P(PeriodTest, TrickPlayWithMatchingAdaptationSet) {
TEST_F(PeriodTest, TrickPlayWithMatchingAdaptationSet) {
const char kVideoMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
@ -282,7 +280,7 @@ TEST_P(PeriodTest, TrickPlayWithMatchingAdaptationSet) {
}
// Verify no AdaptationSet is returned on trickplay media info.
TEST_P(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
TEST_F(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
const char kVideoMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
@ -324,11 +322,338 @@ TEST_P(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
content_protection_in_adaptation_set_));
}
// Don't put different audio languages or codecs in the same AdaptationSet.
TEST_F(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) {
const char kAacEnglishAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kAacGermanAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kVorbisGermanAudioContent1[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
const char kVorbisGermanAudioContent2[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_eng_adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* aac_eng_adaptation_set_ptr = aac_eng_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_ger_adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* aac_ger_adaptation_set_ptr = aac_ger_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> vorbis_german_adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* vorbis_german_adaptation_set_ptr = vorbis_german_adaptation_set.get();
// We expect three AdaptationSets.
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(aac_eng_adaptation_set))))
.WillOnce(Return(ByMove(std::move(aac_ger_adaptation_set))))
.WillOnce(Return(ByMove(std::move(vorbis_german_adaptation_set))));
ASSERT_EQ(aac_eng_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kAacEnglishAudioContent),
content_protection_in_adaptation_set_));
ASSERT_EQ(aac_ger_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kAacGermanAudioContent),
content_protection_in_adaptation_set_));
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVorbisGermanAudioContent1),
content_protection_in_adaptation_set_));
// The same AdaptationSet is returned.
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVorbisGermanAudioContent2),
content_protection_in_adaptation_set_));
}
TEST_F(PeriodTest, GetAdaptationSets) {
const char kContent1[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kContent2[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_1(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_1_ptr = adaptation_set_1.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_2(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_2_ptr = adaptation_set_2.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set_1))))
.WillOnce(Return(ByMove(std::move(adaptation_set_2))));
ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kContent1),
content_protection_in_adaptation_set_));
EXPECT_THAT(testable_period_.GetAdaptationSets(),
ElementsAre(adaptation_set_1_ptr));
ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kContent2),
content_protection_in_adaptation_set_));
EXPECT_THAT(testable_period_.GetAdaptationSets(),
ElementsAre(adaptation_set_1_ptr, adaptation_set_2_ptr));
}
TEST_F(PeriodTest, OrderedByAdaptationSetId) {
const char kContent1[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kContent2[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_1(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_1_ptr = adaptation_set_1.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_2(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_2_ptr = adaptation_set_2.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set_1))))
.WillOnce(Return(ByMove(std::move(adaptation_set_2))));
ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kContent1),
content_protection_in_adaptation_set_));
ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kContent2),
content_protection_in_adaptation_set_));
adaptation_set_1_ptr->set_id(2);
adaptation_set_2_ptr->set_id(1);
const char kExpectedXml[] =
R"(<Period id="9">)"
// ContentType and Representation elements are populated after
// Representation::Init() is called.
R"( <AdaptationSet id="1" contentType=""/>)"
R"( <AdaptationSet id="2" contentType=""/>)"
R"(</Period>)";
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(),
XmlNodeEqual(kExpectedXml));
}
TEST_F(PeriodTest, AudioAdaptationSetDefaultLanguage) {
mpd_options_.mpd_params.default_language = "en";
const char kEnglishAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'en'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_ptr = adaptation_set.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set))));
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kEnglishAudioContent),
content_protection_in_adaptation_set_));
}
TEST_F(PeriodTest, AudioAdaptationSetNonDefaultLanguage) {
mpd_options_.mpd_params.default_language = "fr";
const char kEnglishAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'en'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_ptr = adaptation_set.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set))));
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain)).Times(0);
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kEnglishAudioContent),
content_protection_in_adaptation_set_));
}
TEST_F(PeriodTest, TextAdaptationSetDefaultLanguage) {
mpd_options_.mpd_params.default_language = "en";
const char kEnglishTextContent[] =
"text_info {\n"
" codec: 'webvtt'\n"
" language: 'en'\n"
" type: SUBTITLE\n"
"}";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_ptr = adaptation_set.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set))));
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
EXPECT_CALL(*adaptation_set_ptr, ForceSetSegmentAlignment(true));
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kEnglishTextContent),
content_protection_in_adaptation_set_));
}
TEST_F(PeriodTest, TextAdaptationSetNonDefaultLanguage) {
mpd_options_.mpd_params.default_language = "fr";
const char kEnglishTextContent[] =
"text_info {\n"
" codec: 'webvtt'\n"
" language: 'en'\n"
" type: SUBTITLE\n"
"}";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_ptr = adaptation_set.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set))));
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain)).Times(0);
EXPECT_CALL(*adaptation_set_ptr, ForceSetSegmentAlignment(true));
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kEnglishTextContent),
content_protection_in_adaptation_set_));
}
TEST_F(PeriodTest, TextAdaptationSetNonDefaultLanguageButDefaultTextLanguage) {
mpd_options_.mpd_params.default_language = "fr";
mpd_options_.mpd_params.default_text_language = "en";
const char kEnglishTextContent[] =
"text_info {\n"
" codec: 'webvtt'\n"
" language: 'en'\n"
" type: SUBTITLE\n"
"}";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_ptr = adaptation_set.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set))));
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
EXPECT_CALL(*adaptation_set_ptr, ForceSetSegmentAlignment(true));
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kEnglishTextContent),
content_protection_in_adaptation_set_));
}
TEST_F(PeriodTest, TextAdaptationSetDefaultLanguageButNonDefaultTextLanguage) {
mpd_options_.mpd_params.default_language = "en";
mpd_options_.mpd_params.default_text_language = "fr";
const char kEnglishTextContent[] =
"text_info {\n"
" codec: 'webvtt'\n"
" language: 'en'\n"
" type: SUBTITLE\n"
"}";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_ptr = adaptation_set.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set))));
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain)).Times(0);
EXPECT_CALL(*adaptation_set_ptr, ForceSetSegmentAlignment(true));
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kEnglishTextContent),
content_protection_in_adaptation_set_));
}
class PeriodTestWithContentProtection
: public PeriodTest,
public ::testing::WithParamInterface<bool> {
void SetUp() override { content_protection_in_adaptation_set_ = GetParam(); }
};
// With content_protection_adaptation_set_ == true, verify with different
// MediaInfo::ProtectedContent, two AdaptationSets should be created.
// AdaptationSets with different DRM won't be switchable.
// Otherwise, only one AdaptationSet is created.
TEST_P(PeriodTest, DifferentProtectedContent) {
TEST_P(PeriodTestWithContentProtection, DifferentProtectedContent) {
// Note they both have different (bogus) pssh, like real use case.
// default Key ID = _default_key_id_
const char kSdProtectedContent[] =
@ -435,7 +760,7 @@ TEST_P(PeriodTest, DifferentProtectedContent) {
// Verify with the same MediaInfo::ProtectedContent, only one AdaptationSets
// should be created regardless of the value of
// content_protection_in_adaptation_set_.
TEST_P(PeriodTest, SameProtectedContent) {
TEST_P(PeriodTestWithContentProtection, SameProtectedContent) {
// These have the same default key ID and PSSH.
const char kSdProtectedContent[] =
"video_info {\n"
@ -527,7 +852,7 @@ TEST_P(PeriodTest, SameProtectedContent) {
// 3. Add a 4k protected content. This should also make a new AdaptationSet.
// It should be switchable with SD/HD AdaptationSet.
// Otherwise only one AdaptationSet is created.
TEST_P(PeriodTest, SetAdaptationSetSwitching) {
TEST_P(PeriodTestWithContentProtection, SetAdaptationSetSwitching) {
// These have the same default key ID and PSSH.
const char kSdProtectedContent[] =
"video_info {\n"
@ -668,7 +993,8 @@ TEST_P(PeriodTest, SetAdaptationSetSwitching) {
// Even if the UUIDs match, video and audio AdaptationSets should not be
// switchable.
TEST_P(PeriodTest, DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
TEST_P(PeriodTestWithContentProtection,
DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
// These have the same default key ID and PSSH.
const char kVideoContent[] =
"video_info {\n"
@ -745,192 +1071,8 @@ TEST_P(PeriodTest, DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
content_protection_in_adaptation_set_));
}
// Don't put different audio languages or codecs in the same AdaptationSet.
TEST_P(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) {
const char kAacEnglishAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kAacGermanAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kVorbisGermanAudioContent1[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
const char kVorbisGermanAudioContent2[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_eng_adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* aac_eng_adaptation_set_ptr = aac_eng_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_ger_adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* aac_ger_adaptation_set_ptr = aac_ger_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> vorbis_german_adaptation_set(
new StrictMock<MockAdaptationSet>());
auto* vorbis_german_adaptation_set_ptr = vorbis_german_adaptation_set.get();
// We expect three AdaptationSets.
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(aac_eng_adaptation_set))))
.WillOnce(Return(ByMove(std::move(aac_ger_adaptation_set))))
.WillOnce(Return(ByMove(std::move(vorbis_german_adaptation_set))));
ASSERT_EQ(aac_eng_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kAacEnglishAudioContent),
content_protection_in_adaptation_set_));
ASSERT_EQ(aac_ger_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kAacGermanAudioContent),
content_protection_in_adaptation_set_));
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVorbisGermanAudioContent1),
content_protection_in_adaptation_set_));
// The same AdaptationSet is returned.
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVorbisGermanAudioContent2),
content_protection_in_adaptation_set_));
}
TEST_P(PeriodTest, GetAdaptationSets) {
const char kContent1[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kContent2[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_1(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_1_ptr = adaptation_set_1.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_2(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_2_ptr = adaptation_set_2.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set_1))))
.WillOnce(Return(ByMove(std::move(adaptation_set_2))));
ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kContent1),
content_protection_in_adaptation_set_));
EXPECT_THAT(testable_period_.GetAdaptationSets(),
ElementsAre(adaptation_set_1_ptr));
ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kContent2),
content_protection_in_adaptation_set_));
EXPECT_THAT(testable_period_.GetAdaptationSets(),
ElementsAre(adaptation_set_1_ptr, adaptation_set_2_ptr));
}
TEST_P(PeriodTest, OrderedByAdaptationSetId) {
const char kContent1[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kContent2[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_1(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_1_ptr = adaptation_set_1.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_2(
new StrictMock<MockAdaptationSet>());
auto* adaptation_set_2_ptr = adaptation_set_2.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
.WillOnce(Return(ByMove(std::move(adaptation_set_1))))
.WillOnce(Return(ByMove(std::move(adaptation_set_2))));
ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kContent1),
content_protection_in_adaptation_set_));
ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kContent2),
content_protection_in_adaptation_set_));
adaptation_set_1_ptr->set_id(2);
adaptation_set_2_ptr->set_id(1);
const char kExpectedXml[] =
R"(<Period id="9">)"
// ContentType and Representation elements are populated after
// Representation::Init() is called.
R"( <AdaptationSet id="1" contentType=""/>)"
R"( <AdaptationSet id="2" contentType=""/>)"
R"(</Period>)";
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(),
XmlNodeEqual(kExpectedXml));
}
INSTANTIATE_TEST_CASE_P(ContentProtectionInAdaptationSet,
PeriodTest,
PeriodTestWithContentProtection,
::testing::Bool());
} // namespace shaka

View File

@ -53,7 +53,12 @@ struct MpdParams {
/// The tracks tagged with this language will have <Role ... value=\"main\" />
/// in the manifest. This allows the player to choose the correct default
/// language for the content.
/// This applies to both audio and text tracks. The default language for text
/// tracks can be overriden by 'default_text_language'.
std::string default_language;
/// Same as above, but this overrides the default language for text tracks,
/// i.e. subtitles or close-captions.
std::string default_text_language;
/// Generate static MPD for live profile. Note that this flag has no effect
/// for on-demand profile, in which case static MPD is always used.
bool generate_static_live_mpd = false;

View File

@ -895,8 +895,12 @@ Status Packager::Initialize(
// in the shortest form.
mpd_params.default_language =
LanguageToShortestForm(mpd_params.default_language);
mpd_params.default_text_language =
LanguageToShortestForm(mpd_params.default_text_language);
hls_params.default_language =
LanguageToShortestForm(hls_params.default_language);
hls_params.default_text_language =
LanguageToShortestForm(hls_params.default_text_language);
if (!mpd_params.mpd_output.empty()) {
const bool on_demand_dash_profile =