Split up AdaptationSets by language.

Additionally, for XML schema correctness, convert ISO-639-2 language
tags (three-letter codes) to ISO-639-1 tags (two-letter codes) when
possible.  This follows BCP-47, which says to always use the shortest
language tag when there are multiple possibilities.

b/18613148

Change-Id: I120fb7b42ac4da5feb4ca046bba93d9e58acd7a9
This commit is contained in:
Joey Parrish 2015-02-02 09:26:09 -08:00
parent 80db1c7bbf
commit 77ec23afe4
17 changed files with 352 additions and 32 deletions

View File

@ -22,6 +22,7 @@
# #
# Please keep the list sorted. # Please keep the list sorted.
Joey Parrish <joeyparrish@google.com>
Kongqun Yang <kqyang@google.com> Kongqun Yang <kqyang@google.com>
Rintaro Kuroiwa <rkuroiwa@google.com> Rintaro Kuroiwa <rkuroiwa@google.com>
Thomas Inskip <tinskip@google.com> Thomas Inskip <tinskip@google.com>

View File

@ -0,0 +1,114 @@
// Copyright 2015 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/mpd/base/language_utils.h"
#include "packager/base/logging.h"
namespace {
// A map from 3-letter language codes (ISO 639-2) to 2-letter language codes
// (ISO 639-1) for all languages which have both in the registry.
typedef struct {
const char iso_639_2[4]; // 3 letters + nul
const char iso_639_1[3]; // 2 letters + nul
} LanguageMapPairType;
const LanguageMapPairType kLanguageMap[] = {
{ "aar", "aa" }, { "abk", "ab" }, { "afr", "af" }, { "aka", "ak" },
{ "alb", "sq" }, { "amh", "am" }, { "ara", "ar" }, { "arg", "an" },
{ "arm", "hy" }, { "asm", "as" }, { "ava", "av" }, { "ave", "ae" },
{ "aym", "ay" }, { "aze", "az" }, { "bak", "ba" }, { "bam", "bm" },
{ "baq", "eu" }, { "bel", "be" }, { "ben", "bn" }, { "bih", "bh" },
{ "bis", "bi" }, { "bod", "bo" }, { "bos", "bs" }, { "bre", "br" },
{ "bul", "bg" }, { "bur", "my" }, { "cat", "ca" }, { "ces", "cs" },
{ "cha", "ch" }, { "che", "ce" }, { "chi", "zh" }, { "chu", "cu" },
{ "chv", "cv" }, { "cor", "kw" }, { "cos", "co" }, { "cre", "cr" },
{ "cym", "cy" }, { "cze", "cs" }, { "dan", "da" }, { "deu", "de" },
{ "div", "dv" }, { "dut", "nl" }, { "dzo", "dz" }, { "ell", "el" },
{ "eng", "en" }, { "epo", "eo" }, { "est", "et" }, { "eus", "eu" },
{ "ewe", "ee" }, { "fao", "fo" }, { "fas", "fa" }, { "fij", "fj" },
{ "fin", "fi" }, { "fra", "fr" }, { "fre", "fr" }, { "fry", "fy" },
{ "ful", "ff" }, { "geo", "ka" }, { "ger", "de" }, { "gla", "gd" },
{ "gle", "ga" }, { "glg", "gl" }, { "glv", "gv" }, { "gre", "el" },
{ "grn", "gn" }, { "guj", "gu" }, { "hat", "ht" }, { "hau", "ha" },
{ "heb", "he" }, { "her", "hz" }, { "hin", "hi" }, { "hmo", "ho" },
{ "hrv", "hr" }, { "hun", "hu" }, { "hye", "hy" }, { "ibo", "ig" },
{ "ice", "is" }, { "ido", "io" }, { "iii", "ii" }, { "iku", "iu" },
{ "ile", "ie" }, { "ina", "ia" }, { "ind", "id" }, { "ipk", "ik" },
{ "isl", "is" }, { "ita", "it" }, { "jav", "jv" }, { "jpn", "ja" },
{ "kal", "kl" }, { "kan", "kn" }, { "kas", "ks" }, { "kat", "ka" },
{ "kau", "kr" }, { "kaz", "kk" }, { "khm", "km" }, { "kik", "ki" },
{ "kin", "rw" }, { "kir", "ky" }, { "kom", "kv" }, { "kon", "kg" },
{ "kor", "ko" }, { "kua", "kj" }, { "kur", "ku" }, { "lao", "lo" },
{ "lat", "la" }, { "lav", "lv" }, { "lim", "li" }, { "lin", "ln" },
{ "lit", "lt" }, { "ltz", "lb" }, { "lub", "lu" }, { "lug", "lg" },
{ "mac", "mk" }, { "mah", "mh" }, { "mal", "ml" }, { "mao", "mi" },
{ "mar", "mr" }, { "may", "ms" }, { "mkd", "mk" }, { "mlg", "mg" },
{ "mlt", "mt" }, { "mon", "mn" }, { "mri", "mi" }, { "msa", "ms" },
{ "mya", "my" }, { "nau", "na" }, { "nav", "nv" }, { "nbl", "nr" },
{ "nde", "nd" }, { "ndo", "ng" }, { "nep", "ne" }, { "nld", "nl" },
{ "nno", "nn" }, { "nob", "nb" }, { "nor", "no" }, { "nya", "ny" },
{ "oci", "oc" }, { "oji", "oj" }, { "ori", "or" }, { "orm", "om" },
{ "oss", "os" }, { "pan", "pa" }, { "per", "fa" }, { "pli", "pi" },
{ "pol", "pl" }, { "por", "pt" }, { "pus", "ps" }, { "que", "qu" },
{ "roh", "rm" }, { "ron", "ro" }, { "rum", "ro" }, { "run", "rn" },
{ "rus", "ru" }, { "sag", "sg" }, { "san", "sa" }, { "sin", "si" },
{ "slk", "sk" }, { "slo", "sk" }, { "slv", "sl" }, { "sme", "se" },
{ "smo", "sm" }, { "sna", "sn" }, { "snd", "sd" }, { "som", "so" },
{ "sot", "st" }, { "spa", "es" }, { "sqi", "sq" }, { "srd", "sc" },
{ "srp", "sr" }, { "ssw", "ss" }, { "sun", "su" }, { "swa", "sw" },
{ "swe", "sv" }, { "tah", "ty" }, { "tam", "ta" }, { "tat", "tt" },
{ "tel", "te" }, { "tgk", "tg" }, { "tgl", "tl" }, { "tha", "th" },
{ "tib", "bo" }, { "tir", "ti" }, { "ton", "to" }, { "tsn", "tn" },
{ "tso", "ts" }, { "tuk", "tk" }, { "tur", "tr" }, { "twi", "tw" },
{ "uig", "ug" }, { "ukr", "uk" }, { "urd", "ur" }, { "uzb", "uz" },
{ "ven", "ve" }, { "vie", "vi" }, { "vol", "vo" }, { "wel", "cy" },
{ "wln", "wa" }, { "wol", "wo" }, { "xho", "xh" }, { "yid", "yi" },
{ "yor", "yo" }, { "zha", "za" }, { "zho", "zh" }, { "zul", "zu" },
};
} // namespace
namespace edash_packager {
std::string LanguageToShortestForm(const std::string& language) {
if (language.size() == 2) {
// Presumably already a valid ISO-639-1 code, and therefore conforms to
// BCP-47's requirement to use the shortest possible code.
return language;
}
for (size_t i = 0; i < arraysize(kLanguageMap); ++i) {
if (language == kLanguageMap[i].iso_639_2) {
return kLanguageMap[i].iso_639_1;
}
}
// This could happen legitimately for languages which have no 2-letter code,
// but that would imply that the input language code is a 3-letter code.
DCHECK_EQ(3, language.size());
return language;
}
std::string LanguageToISO_639_2(const std::string& language) {
if (language.size() == 3) {
// Presumably already a valid ISO-639-2 code.
return language;
}
for (size_t i = 0; i < arraysize(kLanguageMap); ++i) {
if (language == kLanguageMap[i].iso_639_1) {
return kLanguageMap[i].iso_639_2;
}
}
LOG(WARNING) << "No equivalent 3-letter language code for " << language;
// This is probably a mistake on the part of the user and should be treated
// as invalid input.
return "und";
}
} // namespace edash_packager

View File

@ -0,0 +1,28 @@
// Copyright 2015 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
//
// Funtions used by MpdBuilder class to generate an MPD file.
#ifndef MPD_BASE_LANGUAGE_UTILS_H_
#define MPD_BASE_LANGUAGE_UTILS_H_
#include <string>
namespace edash_packager {
/// Convert a language tag to its shortest form, as required by RFC 5646
/// indicated in the MPD spec. Assumes the input is a valid ISO-639-2 or
/// ISO-639-1 language tag. Regions and variants are not supported.
std::string LanguageToShortestForm(const std::string& language);
/// Convert a language tag to a 3-letter ISO-639-2 code, as required by the ISO
/// BMFF spec. The input is assumed to be a valid ISO-639-2 or ISO-639-1
/// language code. Regions and variants are not supported.
std::string LanguageToISO_639_2(const std::string& language);
} // namespace edash_packager
#endif // MPD_BASE_LANGUAGE_UTILS_H_

View File

@ -21,6 +21,7 @@
#include "packager/base/time/time.h" #include "packager/base/time/time.h"
#include "packager/media/file/file.h" #include "packager/media/file/file.h"
#include "packager/mpd/base/content_protection_element.h" #include "packager/mpd/base/content_protection_element.h"
#include "packager/mpd/base/language_utils.h"
#include "packager/mpd/base/mpd_utils.h" #include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/xml/xml_node.h" #include "packager/mpd/base/xml/xml_node.h"
@ -201,10 +202,11 @@ void MpdBuilder::AddBaseUrl(const std::string& base_url) {
base_urls_.push_back(base_url); base_urls_.push_back(base_url);
} }
AdaptationSet* MpdBuilder::AddAdaptationSet() { AdaptationSet* MpdBuilder::AddAdaptationSet(const std::string& lang) {
base::AutoLock scoped_lock(lock_); base::AutoLock scoped_lock(lock_);
scoped_ptr<AdaptationSet> adaptation_set(new AdaptationSet( scoped_ptr<AdaptationSet> adaptation_set(new AdaptationSet(
adaptation_set_counter_.GetNext(), mpd_options_, &representation_counter_)); adaptation_set_counter_.GetNext(), lang, mpd_options_,
&representation_counter_));
DCHECK(adaptation_set); DCHECK(adaptation_set);
adaptation_sets_.push_back(adaptation_set.get()); adaptation_sets_.push_back(adaptation_set.get());
@ -447,11 +449,13 @@ void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,
} }
AdaptationSet::AdaptationSet(uint32_t adaptation_set_id, AdaptationSet::AdaptationSet(uint32_t adaptation_set_id,
const std::string& lang,
const MpdOptions& mpd_options, const MpdOptions& mpd_options,
base::AtomicSequenceNumber* counter) base::AtomicSequenceNumber* counter)
: representations_deleter_(&representations_), : representations_deleter_(&representations_),
representation_counter_(counter), representation_counter_(counter),
id_(adaptation_set_id), id_(adaptation_set_id),
lang_(lang),
mpd_options_(mpd_options) { mpd_options_(mpd_options) {
DCHECK(counter); DCHECK(counter);
} }
@ -498,6 +502,9 @@ xml::ScopedXmlPtr<xmlNode>::type AdaptationSet::GetXml() {
} }
adaptation_set.SetId(id_); adaptation_set.SetId(id_);
if (!lang_.empty() && lang_ != "und") {
adaptation_set.SetStringAttribute("lang", LanguageToShortestForm(lang_));
}
return adaptation_set.PassScopedPtr(); return adaptation_set.PassScopedPtr();
} }

View File

@ -66,7 +66,7 @@ class MpdBuilder {
/// Adds <AdaptationSet> to the MPD. /// Adds <AdaptationSet> to the MPD.
/// @return The new adaptation set, which is owned by this instance. /// @return The new adaptation set, which is owned by this instance.
AdaptationSet* AddAdaptationSet(); AdaptationSet* AddAdaptationSet(const std::string& lang);
/// Write the MPD to specified file. /// Write the MPD to specified file.
/// @param[out] output_file is MPD destination. output_file will be /// @param[out] output_file is MPD destination. output_file will be
@ -180,7 +180,8 @@ class AdaptationSet {
/// @param representation_counter is a Counter for assigning ID numbers to /// @param representation_counter is a Counter for assigning ID numbers to
/// Representation. It can not be NULL. /// Representation. It can not be NULL.
AdaptationSet(uint32_t adaptation_set_id, AdaptationSet(uint32_t adaptation_set_id,
const MpdOptions& mpd_options_, const std::string& lang,
const MpdOptions& mpd_options,
base::AtomicSequenceNumber* representation_counter); base::AtomicSequenceNumber* representation_counter);
// Gets the earliest, normalized segment timestamp. Returns true if // Gets the earliest, normalized segment timestamp. Returns true if
@ -196,6 +197,7 @@ class AdaptationSet {
base::AtomicSequenceNumber* const representation_counter_; base::AtomicSequenceNumber* const representation_counter_;
const uint32_t id_; const uint32_t id_;
const std::string lang_;
const MpdOptions& mpd_options_; const MpdOptions& mpd_options_;
DISALLOW_COPY_AND_ASSIGN(AdaptationSet); DISALLOW_COPY_AND_ASSIGN(AdaptationSet);

View File

@ -74,7 +74,7 @@ class MpdBuilderTest: public ::testing::Test {
protected: protected:
void AddRepresentation(const MediaInfo& media_info) { void AddRepresentation(const MediaInfo& media_info) {
AdaptationSet* adaptation_set = mpd_.AddAdaptationSet(); AdaptationSet* adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(adaptation_set); ASSERT_TRUE(adaptation_set);
Representation* representation = Representation* representation =
@ -294,7 +294,7 @@ TEST_F(StaticMpdBuilderTest, CheckAdaptationSetId) {
const uint32_t kAdaptationSetId = 42; const uint32_t kAdaptationSetId = 42;
AdaptationSet adaptation_set( AdaptationSet adaptation_set(
kAdaptationSetId, MpdOptions(), &sequence_counter); kAdaptationSetId, "", MpdOptions(), &sequence_counter);
ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kAdaptationSetId, &adaptation_set)); ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kAdaptationSetId, &adaptation_set));
} }
@ -321,10 +321,10 @@ TEST_F(StaticMpdBuilderTest, VideoAndAudio) {
MediaInfo audio_media_info = GetTestMediaInfo(kFileNameAudioMediaInfo1); MediaInfo audio_media_info = GetTestMediaInfo(kFileNameAudioMediaInfo1);
// The order matters here to check against expected output. // The order matters here to check against expected output.
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet(); AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set); ASSERT_TRUE(video_adaptation_set);
AdaptationSet* audio_adaptation_set = mpd_.AddAdaptationSet(); AdaptationSet* audio_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(audio_adaptation_set); ASSERT_TRUE(audio_adaptation_set);
Representation* audio_representation = Representation* audio_representation =
@ -344,7 +344,7 @@ TEST_F(StaticMpdBuilderTest, AudioChannelConfigurationWithContentProtection) {
MediaInfo encrypted_audio_media_info = MediaInfo encrypted_audio_media_info =
GetTestMediaInfo(kFileNameEncytpedAudioMediaInfo); GetTestMediaInfo(kFileNameEncytpedAudioMediaInfo);
AdaptationSet* audio_adaptation_set = mpd_.AddAdaptationSet(); AdaptationSet* audio_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(audio_adaptation_set); ASSERT_TRUE(audio_adaptation_set);
Representation* audio_representation = Representation* audio_representation =
@ -367,7 +367,7 @@ TEST_F(StaticMpdBuilderTest, MediaInfoMissingBandwidth) {
TEST_F(StaticMpdBuilderTest, WriteToFile) { TEST_F(StaticMpdBuilderTest, WriteToFile) {
MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1); MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet(); AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set); ASSERT_TRUE(video_adaptation_set);
Representation* video_representation = Representation* video_representation =

View File

@ -48,9 +48,13 @@ bool SimpleMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
// TODO(kqyang): Consider adding a new method MpdBuilder::AddRepresentation. // TODO(kqyang): Consider adding a new method MpdBuilder::AddRepresentation.
// Most of the codes here can be moved inside. // Most of the codes here can be moved inside.
AdaptationSet** adaptation_set = &adaptation_set_map_[content_type]; std::string lang;
if (media_info.audio_info().size() > 0) {
lang = media_info.audio_info(0).language();
}
AdaptationSet** adaptation_set = &adaptation_set_map_[content_type][lang];
if (*adaptation_set == NULL) if (*adaptation_set == NULL)
*adaptation_set = mpd_builder_->AddAdaptationSet(); *adaptation_set = mpd_builder_->AddAdaptationSet(lang);
DCHECK(*adaptation_set); DCHECK(*adaptation_set);
MediaInfo adjusted_media_info(media_info); MediaInfo adjusted_media_info(media_info);

View File

@ -63,7 +63,9 @@ class SimpleMpdNotifier : public MpdNotifier {
base::Lock lock_; base::Lock lock_;
typedef std::map<ContentType, AdaptationSet*> AdaptationSetMap; // [type][lang] = AdaptationSet
typedef std::map<ContentType, std::map<std::string, AdaptationSet*> >
AdaptationSetMap;
AdaptationSetMap adaptation_set_map_; AdaptationSetMap adaptation_set_map_;
typedef std::map<uint32_t, Representation*> RepresentationMap; typedef std::map<uint32_t, Representation*> RepresentationMap;

View File

@ -37,6 +37,8 @@
'base/bandwidth_estimator.h', 'base/bandwidth_estimator.h',
'base/content_protection_element.cc', 'base/content_protection_element.cc',
'base/content_protection_element.h', 'base/content_protection_element.h',
'base/language_utils.cc',
'base/language_utils.h',
'base/mpd_builder.cc', 'base/mpd_builder.cc',
'base/mpd_builder.h', 'base/mpd_builder.h',
'base/mpd_notifier.h', 'base/mpd_notifier.h',

View File

@ -0,0 +1,20 @@
bandwidth: 400
audio_info {
codec: "mp4a.40.2"
sampling_frequency: 44100
time_scale: 1200
num_channels: 2
language: "eng"
}
init_range {
begin: 0
end: 120
}
index_range {
begin: 121
end: 221
}
reference_time_scale: 50
container_type: 1
media_file_name: "test_output_file_name_audio_eng1.mp4"
media_duration_seconds: 10.5

View File

@ -0,0 +1,20 @@
bandwidth: 800
audio_info {
codec: "mp4a.40.2"
sampling_frequency: 44100
time_scale: 1200
num_channels: 2
language: "eng"
}
init_range {
begin: 0
end: 120
}
index_range {
begin: 121
end: 221
}
reference_time_scale: 50
container_type: 1
media_file_name: "test_output_file_name_audio_eng2.mp4"
media_duration_seconds: 10.5

View File

@ -0,0 +1,20 @@
bandwidth: 400
audio_info {
codec: "mp4a.40.2"
sampling_frequency: 44100
time_scale: 1200
num_channels: 2
language: "ger"
}
init_range {
begin: 0
end: 120
}
index_range {
begin: 121
end: 221
}
reference_time_scale: 50
container_type: 1
media_file_name: "test_output_file_name_audio_ger1.mp4"
media_duration_seconds: 10.5

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT10.5S">
<Period>
<AdaptationSet id="0">
<Representation id="3" bandwidth="7620" codecs="avc1.010101" mimeType="video/mp4" width="720" height="480">
<BaseURL>test_output_file_name1.mp4</BaseURL>
<SegmentBase indexRange="121-221" timescale="1000">
<Initialization range="0-120"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" lang="en">
<Representation id="0" bandwidth="400" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>test_output_file_name_audio_eng1.mp4</BaseURL>
<SegmentBase indexRange="121-221" timescale="50">
<Initialization range="0-120"/>
</SegmentBase>
</Representation>
<Representation id="1" bandwidth="800" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>test_output_file_name_audio_eng2.mp4</BaseURL>
<SegmentBase indexRange="121-221" timescale="50">
<Initialization range="0-120"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="2" lang="de">
<Representation id="2" bandwidth="400" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>test_output_file_name_audio_ger1.mp4</BaseURL>
<SegmentBase indexRange="121-221" timescale="50">
<Initialization range="0-120"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View File

@ -0,0 +1,19 @@
bandwidth: 7620
video_info {
codec: "avc1.010101"
width: 720
height: 480
time_scale: 10
}
init_range {
begin: 0
end: 120
}
index_range {
begin: 121
end: 221
}
reference_time_scale: 1000
container_type: 1
media_file_name: "test_output_file_name1.mp4"
media_duration_seconds: 10.5

View File

@ -20,6 +20,14 @@ const char kFileNameVideoMediaInfo1[] = "video_media_info1.txt";
const char kFileNameVideoMediaInfo2[] = "video_media_info2.txt"; const char kFileNameVideoMediaInfo2[] = "video_media_info2.txt";
const char kFileNameAudioMediaInfo1[] = "audio_media_info1.txt"; const char kFileNameAudioMediaInfo1[] = "audio_media_info1.txt";
const char kFileNameEncytpedAudioMediaInfo[] = "encrypted_audio_media_info.txt"; const char kFileNameEncytpedAudioMediaInfo[] = "encrypted_audio_media_info.txt";
const char kFileNameLanguageAudioMediaInfo1[] =
"language_audio_media_info1.txt";
const char kFileNameLanguageAudioMediaInfo2[] =
"language_audio_media_info2.txt";
const char kFileNameLanguageAudioMediaInfo3[] =
"language_audio_media_info3.txt";
const char kFileNameLanguageVideoMediaInfo1[] =
"language_video_media_info1.txt";
// These are the expected output files. // These are the expected output files.
const char kFileNameExpectedMpdOutputVideo1[] = const char kFileNameExpectedMpdOutputVideo1[] =
@ -39,6 +47,9 @@ const char kFileNameExpectedMpdOutputEncryptedAudio[] =
const char kFileNameExpectedMpdOutputDynamicNormal[] = "dynamic_normal_mpd.txt"; const char kFileNameExpectedMpdOutputDynamicNormal[] = "dynamic_normal_mpd.txt";
const char kFileNameExpectedMpdOutputLanguageAudio[] =
"language_audio_media_info_expected_output.txt";
// Returns the path to test data with |file_name|. Use constants above to get // Returns the path to test data with |file_name|. Use constants above to get
// path to the test files. // path to the test files.
base::FilePath GetTestDataFilePath(const std::string& file_name); base::FilePath GetTestDataFilePath(const std::string& file_name);

View File

@ -88,14 +88,15 @@ bool SetMediaInfosToMpdBuilder(const std::list<MediaInfo>& media_infos,
return false; return false;
DCHECK(mpd_builder); DCHECK(mpd_builder);
AdaptationSet* video_adaptation_set =
has_video ? mpd_builder->AddAdaptationSet() : NULL;
AdaptationSet* audio_adaptation_set =
has_audio ? mpd_builder->AddAdaptationSet() : NULL;
AdaptationSet* text_adaptation_set =
has_text ? mpd_builder->AddAdaptationSet() : NULL;
DCHECK(video_adaptation_set || audio_adaptation_set || text_adaptation_set); // [type][lang] = AdaptationSet
std::map<std::string, std::map<std::string, AdaptationSet*> > map;
// This puts video sets into the map first, which keeps some pre-existing
// test expectations from changing.
if (has_video) {
map["video"][""] = mpd_builder->AddAdaptationSet("");
}
for (std::list<MediaInfo>::const_iterator it = media_infos.begin(); for (std::list<MediaInfo>::const_iterator it = media_infos.begin();
it != media_infos.end(); it != media_infos.end();
++it) { ++it) {
@ -103,15 +104,22 @@ bool SetMediaInfosToMpdBuilder(const std::list<MediaInfo>& media_infos,
DCHECK(OnlyOneTrue( DCHECK(OnlyOneTrue(
HasVideo(media_info), HasAudio(media_info), HasText(media_info))); HasVideo(media_info), HasAudio(media_info), HasText(media_info)));
Representation* representation = NULL; std::string lang;
AdaptationSet** adaptation_set = NULL;
if (HasVideo(media_info)) { if (HasVideo(media_info)) {
representation = video_adaptation_set->AddRepresentation(media_info); adaptation_set = &map["video"][lang];
} else if (HasAudio(media_info)) { } else if (HasAudio(media_info)) {
representation = audio_adaptation_set->AddRepresentation(media_info); lang = media_info.audio_info(0).language();
adaptation_set = &map["audio"][lang];
} else if (HasText(media_info)) { } else if (HasText(media_info)) {
representation = text_adaptation_set->AddRepresentation(media_info); adaptation_set = &map["text"][lang];
}
if (!*adaptation_set) {
*adaptation_set = mpd_builder->AddAdaptationSet(lang);
} }
Representation* representation =
(*adaptation_set)->AddRepresentation(media_info);
if (!representation) { if (!representation) {
LOG(ERROR) << "Failed to add representation."; LOG(ERROR) << "Failed to add representation.";
return false; return false;

View File

@ -19,7 +19,7 @@ TEST(MpdWriterTest, VideoMediaInfo) {
MpdWriter mpd_writer; MpdWriter mpd_writer;
base::FilePath media_info_file = GetTestDataFilePath(kFileNameVideoMediaInfo1); base::FilePath media_info_file = GetTestDataFilePath(kFileNameVideoMediaInfo1);
ASSERT_TRUE(mpd_writer.AddFile(media_info_file.value().c_str(), "")); ASSERT_TRUE(mpd_writer.AddFile(media_info_file.value(), ""));
std::string generated_mpd; std::string generated_mpd;
ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd)); ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd));
ASSERT_TRUE(ValidateMpdSchema(generated_mpd)); ASSERT_TRUE(ValidateMpdSchema(generated_mpd));
@ -36,8 +36,8 @@ TEST(MpdWriterTest, TwoVideoMediaInfo) {
base::FilePath media_info_file2 = base::FilePath media_info_file2 =
GetTestDataFilePath(kFileNameVideoMediaInfo2); GetTestDataFilePath(kFileNameVideoMediaInfo2);
ASSERT_TRUE(mpd_writer.AddFile(media_info_file1.value().c_str(), "")); ASSERT_TRUE(mpd_writer.AddFile(media_info_file1.value(), ""));
ASSERT_TRUE(mpd_writer.AddFile(media_info_file2.value().c_str(), "")); ASSERT_TRUE(mpd_writer.AddFile(media_info_file2.value(), ""));
std::string generated_mpd; std::string generated_mpd;
ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd)); ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd));
@ -52,7 +52,7 @@ TEST(MpdWriterTest, AudioMediaInfo) {
MpdWriter mpd_writer; MpdWriter mpd_writer;
base::FilePath media_info_file = GetTestDataFilePath(kFileNameAudioMediaInfo1); base::FilePath media_info_file = GetTestDataFilePath(kFileNameAudioMediaInfo1);
ASSERT_TRUE(mpd_writer.AddFile(media_info_file.value().c_str(), "")); ASSERT_TRUE(mpd_writer.AddFile(media_info_file.value(), ""));
std::string generated_mpd; std::string generated_mpd;
ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd)); ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd));
ASSERT_TRUE(ValidateMpdSchema(generated_mpd)); ASSERT_TRUE(ValidateMpdSchema(generated_mpd));
@ -69,8 +69,8 @@ TEST(MpdWriterTest, VideoAudioMediaInfo) {
base::FilePath video_media_info = base::FilePath video_media_info =
GetTestDataFilePath(kFileNameVideoMediaInfo1); GetTestDataFilePath(kFileNameVideoMediaInfo1);
ASSERT_TRUE(mpd_writer.AddFile(audio_media_info.value().c_str(), "")); ASSERT_TRUE(mpd_writer.AddFile(audio_media_info.value(), ""));
ASSERT_TRUE(mpd_writer.AddFile(video_media_info.value().c_str(), "")); ASSERT_TRUE(mpd_writer.AddFile(video_media_info.value(), ""));
std::string generated_mpd; std::string generated_mpd;
ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd)); ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd));
@ -86,8 +86,7 @@ TEST(MpdWriterTest, EncryptedAudioMediaInfo) {
base::FilePath encrypted_audio_media_info = base::FilePath encrypted_audio_media_info =
GetTestDataFilePath(kFileNameEncytpedAudioMediaInfo); GetTestDataFilePath(kFileNameEncytpedAudioMediaInfo);
ASSERT_TRUE(mpd_writer.AddFile(encrypted_audio_media_info.value().c_str(), ASSERT_TRUE(mpd_writer.AddFile(encrypted_audio_media_info.value(), ""));
""));
std::string generated_mpd; std::string generated_mpd;
ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd)); ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd));
@ -97,4 +96,29 @@ TEST(MpdWriterTest, EncryptedAudioMediaInfo) {
generated_mpd, kFileNameExpectedMpdOutputEncryptedAudio)); generated_mpd, kFileNameExpectedMpdOutputEncryptedAudio));
} }
TEST(MpdWriterTest, LanguageMediaInfo) {
MpdWriter mpd_writer;
base::FilePath audio_media_info1 =
GetTestDataFilePath(kFileNameLanguageAudioMediaInfo1);
base::FilePath audio_media_info2 =
GetTestDataFilePath(kFileNameLanguageAudioMediaInfo2);
base::FilePath audio_media_info3 =
GetTestDataFilePath(kFileNameLanguageAudioMediaInfo3);
base::FilePath video_media_info1 =
GetTestDataFilePath(kFileNameLanguageVideoMediaInfo1);
ASSERT_TRUE(mpd_writer.AddFile(audio_media_info1.value(), ""));
ASSERT_TRUE(mpd_writer.AddFile(audio_media_info2.value(), ""));
ASSERT_TRUE(mpd_writer.AddFile(audio_media_info3.value(), ""));
ASSERT_TRUE(mpd_writer.AddFile(video_media_info1.value(), ""));
std::string generated_mpd;
ASSERT_TRUE(mpd_writer.WriteMpdToString(&generated_mpd));
ASSERT_TRUE(ValidateMpdSchema(generated_mpd));
ASSERT_NO_FATAL_FAILURE(ExpectMpdToEqualExpectedOutputFile(
generated_mpd,
kFileNameExpectedMpdOutputLanguageAudio));
}
} // namespace edash_packager } // namespace edash_packager