Hooked MPD flags up to MpdOptions.

Added calculation of availabilityStartTime MPD attribute.

Change-Id: I00876005c71f28ea83fb5d9ba0ad1f19f1d08e69
This commit is contained in:
Thomas Inskip 2014-06-25 18:33:09 -07:00 committed by Gerrit Code Review
parent 6651ae1c3d
commit 79d3c4f4ec
9 changed files with 202 additions and 95 deletions

View File

@ -29,3 +29,25 @@ DEFINE_string(base_urls,
"", "",
"Comma separated BaseURLs for the MPD. The values will be added " "Comma separated BaseURLs for the MPD. The values will be added "
"as <BaseURL> element(s) immediately under the <MPD> element."); "as <BaseURL> element(s) immediately under the <MPD> element.");
DEFINE_double(min_buffer_time,
2.0,
"Specifies, in seconds, a common duration used in the definition "
"of the MPD Representation data rate.");
DEFINE_double(availability_time_offset,
10.0,
"Offset with respect to the wall clock time for MPD "
"availabilityStartTime and availabilityEndTime values, in "
" seconds. This value is used for live profile only.");
DEFINE_double(minimum_update_period,
5.0,
"Indicates to the player how often to refresh the media "
"presentation description in seconds. This value is used for "
"live profile only.");
DEFINE_double(time_shift_buffer_depth,
1800.0,
"Guaranteed duration of the time shifting buffer for dynamic "
"media presentations, in seconds.");
DEFINE_double(suggested_presentation_delay,
0.0,
"Specifies a delay, in seconds, to be added to the media "
"presentation time. This value is used for live profile only.");

View File

@ -15,5 +15,10 @@ DECLARE_bool(output_media_info);
DECLARE_string(mpd_output); DECLARE_string(mpd_output);
DECLARE_string(scheme_id_uri); DECLARE_string(scheme_id_uri);
DECLARE_string(base_urls); DECLARE_string(base_urls);
DECLARE_double(availability_time_offset);
DECLARE_double(minimum_update_period);
DECLARE_double(min_buffer_time);
DECLARE_double(time_shift_buffer_depth);
DECLARE_double(suggested_presentation_delay);
#endif // APP_MPD_FLAGS_H_ #endif // APP_MPD_FLAGS_H_

View File

@ -234,6 +234,10 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
if (!GetMuxerOptions(&muxer_options)) if (!GetMuxerOptions(&muxer_options))
return false; return false;
MpdOptions mpd_options;
if (!GetMpdOptions(&mpd_options))
return false;
// Create encryption key source if needed. // Create encryption key source if needed.
scoped_ptr<EncryptionKeySource> encryption_key_source; scoped_ptr<EncryptionKeySource> encryption_key_source;
if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption) { if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption) {
@ -248,8 +252,7 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
FLAGS_single_segment ? kOnDemandProfile : kLiveProfile; FLAGS_single_segment ? kOnDemandProfile : kLiveProfile;
std::vector<std::string> base_urls; std::vector<std::string> base_urls;
base::SplitString(FLAGS_base_urls, ',', &base_urls); base::SplitString(FLAGS_base_urls, ',', &base_urls);
// TODO(rkuroiwa,kqyang): Get mpd options from command line. mpd_notifier.reset(new SimpleMpdNotifier(profile, mpd_options, base_urls,
mpd_notifier.reset(new SimpleMpdNotifier(profile, MpdOptions(), base_urls,
FLAGS_mpd_output)); FLAGS_mpd_output));
if (!mpd_notifier->Init()) { if (!mpd_notifier->Init()) {
LOG(ERROR) << "MpdNotifier failed to initialize."; LOG(ERROR) << "MpdNotifier failed to initialize.";

View File

@ -8,6 +8,7 @@
#include <iostream> #include <iostream>
#include "app/fixed_key_encryption_flags.h" #include "app/fixed_key_encryption_flags.h"
#include "app/mpd_flags.h"
#include "app/muxer_flags.h" #include "app/muxer_flags.h"
#include "app/widevine_encryption_flags.h" #include "app/widevine_encryption_flags.h"
#include "base/logging.h" #include "base/logging.h"
@ -19,9 +20,12 @@
#include "media/base/stream_info.h" #include "media/base/stream_info.h"
#include "media/base/widevine_encryption_key_source.h" #include "media/base/widevine_encryption_key_source.h"
#include "media/file/file.h" #include "media/file/file.h"
#include "mpd/base/mpd_builder.h"
DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info."); DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info.");
using dash_packager::MpdOptions;
namespace media { namespace media {
void DumpStreamInfo(const std::vector<MediaStream*>& streams) { void DumpStreamInfo(const std::vector<MediaStream*>& streams) {
@ -128,6 +132,18 @@ bool GetMuxerOptions(MuxerOptions* muxer_options) {
return true; return true;
} }
bool GetMpdOptions(MpdOptions* mpd_options) {
DCHECK(mpd_options);
mpd_options->availability_time_offset = FLAGS_availability_time_offset;
mpd_options->minimum_update_period = FLAGS_minimum_update_period;
mpd_options->min_buffer_time = FLAGS_min_buffer_time;
mpd_options->time_shift_buffer_depth = FLAGS_time_shift_buffer_depth;
mpd_options->suggested_presentation_delay =
FLAGS_suggested_presentation_delay;
return true;
}
MediaStream* FindFirstStreamOfType(const std::vector<MediaStream*>& streams, MediaStream* FindFirstStreamOfType(const std::vector<MediaStream*>& streams,
StreamType stream_type) { StreamType stream_type) {
typedef std::vector<MediaStream*>::const_iterator StreamIterator; typedef std::vector<MediaStream*>::const_iterator StreamIterator;

View File

@ -17,6 +17,10 @@
DECLARE_bool(dump_stream_info); DECLARE_bool(dump_stream_info);
namespace dash_packager {
struct MpdOptions;
}
namespace media { namespace media {
class EncryptionKeySource; class EncryptionKeySource;
@ -39,6 +43,9 @@ bool AssignFlagsFromProfile();
/// Fill MuxerOptions members using provided command line options. /// Fill MuxerOptions members using provided command line options.
bool GetMuxerOptions(MuxerOptions* muxer_options); bool GetMuxerOptions(MuxerOptions* muxer_options);
/// Fill MpdOptions members using provided command line options.
bool GetMpdOptions(dash_packager::MpdOptions* mpd_options);
/// Select and add a stream from a provided set to a muxer. /// Select and add a stream from a provided set to a muxer.
/// @param streams contains the set of MediaStreams from which to select. /// @param streams contains the set of MediaStreams from which to select.
/// @param stream_selector is a string containing one of the following values: /// @param stream_selector is a string containing one of the following values:

View File

@ -7,6 +7,8 @@
#ifndef MEDIA_BASE_MUXER_OPTIONS_H_ #ifndef MEDIA_BASE_MUXER_OPTIONS_H_
#define MEDIA_BASE_MUXER_OPTIONS_H_ #define MEDIA_BASE_MUXER_OPTIONS_H_
#include "base/basictypes.h"
#include <string> #include <string>
#include "base/basictypes.h" #include "base/basictypes.h"
@ -65,7 +67,8 @@ struct MuxerOptions {
/// Specify temporary directory for intermediate files. /// Specify temporary directory for intermediate files.
std::string temp_dir; std::string temp_dir;
/// User-specified bandwidth for the stream. zero means "unspecified". /// User-specified bit rate for the media stream. If zero, the muxer will
/// attempt to estimate.
uint32 bandwidth; uint32 bandwidth;
}; };

View File

@ -6,6 +6,7 @@
#include "mpd/base/mpd_builder.h" #include "mpd/base/mpd_builder.h"
#include <cmath>
#include <list> #include <list>
#include <string> #include <string>
@ -90,18 +91,19 @@ bool Positive(double d) {
} }
// Return current time in XML DateTime format. // Return current time in XML DateTime format.
std::string XmlDateTimeNow() { std::string XmlDateTimeNowWithOffset(int32 offset_seconds) {
base::Time now = base::Time::Now(); base::Time time = base::Time::Now();
base::Time::Exploded now_exploded; time += base::TimeDelta::FromSeconds(offset_seconds);
now.UTCExplode(&now_exploded); base::Time::Exploded time_exploded;
time.UTCExplode(&time_exploded);
return base::StringPrintf("%4d-%02d-%02dT%02d:%02d:%02d", return base::StringPrintf("%4d-%02d-%02dT%02d:%02d:%02d",
now_exploded.year, time_exploded.year,
now_exploded.month, time_exploded.month,
now_exploded.day_of_month, time_exploded.day_of_month,
now_exploded.hour, time_exploded.hour,
now_exploded.minute, time_exploded.minute,
now_exploded.second); time_exploded.second);
} }
void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) { void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
@ -157,13 +159,12 @@ int SearchTimedOutRepeatIndex(uint64 timeshift_limit,
} // namespace } // namespace
MpdOptions::MpdOptions() MpdOptions::MpdOptions()
: minimum_update_period(), : availability_time_offset(0),
min_buffer_time(), minimum_update_period(0),
time_shift_buffer_depth(), // TODO(tinskip): Set min_buffer_time in unit tests rather than here.
suggested_presentation_delay(), min_buffer_time(2.0),
max_segment_duration(), time_shift_buffer_depth(0),
max_subsegment_duration(), suggested_presentation_delay(0) {}
number_of_blocks_for_bandwidth_estimation() {}
MpdOptions::~MpdOptions() {} MpdOptions::~MpdOptions() {}
@ -222,9 +223,6 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
static const char kXmlVersion[] = "1.0"; static const char kXmlVersion[] = "1.0";
xml::ScopedXmlPtr<xmlDoc>::type doc(xmlNewDoc(BAD_CAST kXmlVersion)); xml::ScopedXmlPtr<xmlDoc>::type doc(xmlNewDoc(BAD_CAST kXmlVersion));
XmlNode mpd("MPD"); XmlNode mpd("MPD");
AddMpdNameSpaceInfo(&mpd);
SetMpdOptionsValues(&mpd);
// Iterate thru AdaptationSets and add them to one big Period element. // Iterate thru AdaptationSets and add them to one big Period element.
XmlNode period("Period"); XmlNode period("Period");
@ -254,6 +252,8 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
if (!mpd.AddChild(period.PassScopedPtr())) if (!mpd.AddChild(period.PassScopedPtr()))
return NULL; return NULL;
AddMpdNameSpaceInfo(&mpd);
AddCommonMpdInfo(&mpd);
switch (type_) { switch (type_) {
case kStatic: case kStatic:
AddStaticMpdInfo(&mpd); AddStaticMpdInfo(&mpd);
@ -271,6 +271,17 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
return doc.release(); return doc.release();
} }
void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
if (Positive(mpd_options_.min_buffer_time)) {
mpd_node->SetStringAttribute(
"minBufferTime",
SecondsToXmlDuration(mpd_options_.min_buffer_time));
} else {
LOG(ERROR) << "minBufferTime value not specified.";
// TODO(tinskip): Propagate error.
}
}
void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) { void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
DCHECK(mpd_node); DCHECK(mpd_node);
DCHECK_EQ(MpdBuilder::kStatic, type_); DCHECK_EQ(MpdBuilder::kStatic, type_);
@ -294,6 +305,38 @@ void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
"urn:mpeg:dash:profile:isoff-live:2011"; "urn:mpeg:dash:profile:isoff-live:2011";
mpd_node->SetStringAttribute("type", kDynamicMpdType); mpd_node->SetStringAttribute("type", kDynamicMpdType);
mpd_node->SetStringAttribute("profiles", kDynamicMpdProfile); mpd_node->SetStringAttribute("profiles", kDynamicMpdProfile);
// 'availabilityStartTime' is required for dynamic profile. Calculate if
// not already calculated.
if (availability_start_time_.empty()) {
double earliest_presentation_time;
if (GetEarliestTimestamp(&earliest_presentation_time)) {
availability_start_time_ =
XmlDateTimeNowWithOffset(mpd_options_.availability_time_offset
- std::ceil(earliest_presentation_time));
} else {
LOG(ERROR) << "Could not determine the earliest segment presentation "
"time for availabilityStartTime calculation.";
// TODO(tinskip). Propagate an error.
}
}
if (!availability_start_time_.empty())
mpd_node->SetStringAttribute("availabilityStartTime", availability_start_time_);
if (Positive(mpd_options_.minimum_update_period)) {
mpd_node->SetStringAttribute(
"minimumUpdatePeriod",
SecondsToXmlDuration(mpd_options_.minimum_update_period));
} else {
LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
"specified.";
}
SetIfPositive(
"timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth, mpd_node);
SetIfPositive("suggestedPresentationDelay",
mpd_options_.suggested_presentation_delay,
mpd_node);
} }
float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) { float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
@ -328,62 +371,25 @@ float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
return max_duration; return max_duration;
} }
void MpdBuilder::SetMpdOptionsValues(XmlNode* mpd) { bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
if (type_ == kStatic) { DCHECK(timestamp_seconds);
if (!mpd_options_.availability_start_time.empty()) {
mpd->SetStringAttribute("availabilityStartTime", double earliest_timestamp(-1);
mpd_options_.availability_start_time); for (std::list<AdaptationSet*>::const_iterator iter =
adaptation_sets_.begin();
iter != adaptation_sets_.end();
++iter) {
double timestamp;
if ((*iter)->GetEarliestTimestamp(&timestamp) &&
((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
earliest_timestamp = timestamp;
} }
LOG_IF(WARNING, Positive(mpd_options_.minimum_update_period))
<< "minimumUpdatePeriod should not be present in 'static' profile. "
"Ignoring.";
LOG_IF(WARNING, Positive(mpd_options_.time_shift_buffer_depth))
<< "timeShiftBufferDepth will not be used for 'static' profile. "
"Ignoring.";
LOG_IF(WARNING, Positive(mpd_options_.suggested_presentation_delay))
<< "suggestedPresentationDelay will not be used for 'static' profile. "
"Ignoring.";
} else if (type_ == kDynamic) {
// 'availabilityStartTime' is required for dynamic profile, so use current
// time if not specified.
const std::string avail_start =
!mpd_options_.availability_start_time.empty()
? mpd_options_.availability_start_time
: XmlDateTimeNow();
mpd->SetStringAttribute("availabilityStartTime", avail_start);
if (Positive(mpd_options_.minimum_update_period)) {
mpd->SetStringAttribute(
"minimumUpdatePeriod",
SecondsToXmlDuration(mpd_options_.minimum_update_period));
} else {
// TODO(rkuroiwa): Set minimumUpdatePeriod to some default value.
LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
"specified. Setting minimumUpdatePeriod to 0.";
}
SetIfPositive(
"timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth, mpd);
SetIfPositive("suggestedPresentationDelay",
mpd_options_.suggested_presentation_delay,
mpd);
} }
if (earliest_timestamp < 0)
return false;
const double kDefaultMinBufferTime = 2.0; *timestamp_seconds = earliest_timestamp;
const double min_buffer_time = Positive(mpd_options_.min_buffer_time) return true;
? mpd_options_.min_buffer_time
: kDefaultMinBufferTime;
mpd->SetStringAttribute("minBufferTime",
SecondsToXmlDuration(min_buffer_time));
if (!mpd_options_.availability_end_time.empty()) {
mpd->SetStringAttribute("availabilityEndTime",
mpd_options_.availability_end_time);
}
SetIfPositive("maxSegmentDuration", mpd_options_.max_segment_duration, mpd);
SetIfPositive(
"maxSubsegmentDuration", mpd_options_.max_subsegment_duration, mpd);
} }
AdaptationSet::AdaptationSet(uint32 adaptation_set_id, AdaptationSet::AdaptationSet(uint32 adaptation_set_id,
@ -441,6 +447,29 @@ xml::ScopedXmlPtr<xmlNode>::type AdaptationSet::GetXml() {
return adaptation_set.PassScopedPtr(); return adaptation_set.PassScopedPtr();
} }
bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) {
DCHECK(timestamp_seconds);
base::AutoLock scoped_lock(lock_);
double earliest_timestamp(-1);
for (std::list<Representation*>::const_iterator iter =
representations_.begin();
iter != representations_.end();
++iter) {
double timestamp;
if ((*iter)->GetEarliestTimestamp(&timestamp) &&
((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
earliest_timestamp = timestamp;
}
}
if (earliest_timestamp < 0)
return false;
*timestamp_seconds = earliest_timestamp;
return true;
}
Representation::Representation(const MediaInfo& media_info, Representation::Representation(const MediaInfo& media_info,
const MpdOptions& mpd_options, const MpdOptions& mpd_options,
uint32 id) uint32 id)
@ -715,4 +744,17 @@ std::string Representation::GetAudioMimeType() const {
return GetMimeType("audio", media_info_.container_type()); return GetMimeType("audio", media_info_.container_type());
} }
bool Representation::GetEarliestTimestamp(double* timestamp_seconds) {
DCHECK(timestamp_seconds);
base::AutoLock scoped_lock(lock_);
if (segment_infos_.empty())
return false;
*timestamp_seconds =
static_cast<double>(segment_infos_.begin()->start_time) /
GetTimeScale(media_info_);
return true;
}
} // namespace dash_packager } // namespace dash_packager

View File

@ -41,18 +41,11 @@ struct MpdOptions {
MpdOptions(); MpdOptions();
~MpdOptions(); ~MpdOptions();
std::string availability_start_time; double availability_time_offset;
std::string availability_end_time;
double minimum_update_period; double minimum_update_period;
double min_buffer_time; double min_buffer_time;
double time_shift_buffer_depth; double time_shift_buffer_depth;
double suggested_presentation_delay; double suggested_presentation_delay;
double max_segment_duration;
double max_subsegment_duration;
/// Value passed to BandwidthEstimator's contructor. See BandwidthEstimator
/// for more.
int number_of_blocks_for_bandwidth_estimation;
}; };
/// This class generates DASH MPDs (Media Presentation Descriptions). /// This class generates DASH MPDs (Media Presentation Descriptions).
@ -86,8 +79,8 @@ class MpdBuilder {
MpdType type() { return type_; } MpdType type() { return type_; }
private: private:
// DynamicMpdBuilderTest uses SetMpdOptionsValues to set availabilityStartTime // DynamicMpdBuilderTest needs to set availabilityStartTime so that the test
// so that the test doesn't need to depend on current time. // doesn't need to depend on current time.
friend class DynamicMpdBuilderTest; friend class DynamicMpdBuilderTest;
bool ToStringImpl(std::string* output); bool ToStringImpl(std::string* output);
@ -97,6 +90,10 @@ class MpdBuilder {
// On failure, this returns NULL. // On failure, this returns NULL.
xmlDocPtr GenerateMpd(); xmlDocPtr GenerateMpd();
// Set MPD attributes common to all profiles. Uses non-zero |mpd_options_| to
// set attributes for the MPD.
void AddCommonMpdInfo(xml::XmlNode* mpd_node);
// Adds 'static' MPD attributes and elements to |mpd_node|. This assumes that // Adds 'static' MPD attributes and elements to |mpd_node|. This assumes that
// the first child element is a Period element. // the first child element is a Period element.
void AddStaticMpdInfo(xml::XmlNode* mpd_node); void AddStaticMpdInfo(xml::XmlNode* mpd_node);
@ -106,10 +103,13 @@ class MpdBuilder {
float GetStaticMpdDuration(xml::XmlNode* mpd_node); float GetStaticMpdDuration(xml::XmlNode* mpd_node);
// Use |mpd_options_| to set attributes for MPD. Only values that are set will be // Set MPD attributes for dynamic profile MPD. Uses non-zero |mpd_options_| as
// used, i.e. if a string field is not empty and numeric field is not 0. // well as various calculations to set attributes for the MPD.
// Required fields will be set with some reasonable values. void SetDynamicMpdAttributes(xml::XmlNode* mpd_node);
void SetMpdOptionsValues(xml::XmlNode* mpd_node);
// Gets the earliest, normalized segment timestamp. Returns true if
// successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
MpdType type_; MpdType type_;
MpdOptions mpd_options_; MpdOptions mpd_options_;
@ -117,6 +117,7 @@ class MpdBuilder {
::STLElementDeleter<std::list<AdaptationSet*> > adaptation_sets_deleter_; ::STLElementDeleter<std::list<AdaptationSet*> > adaptation_sets_deleter_;
std::list<std::string> base_urls_; std::list<std::string> base_urls_;
std::string availability_start_time_;
base::Lock lock_; base::Lock lock_;
base::AtomicSequenceNumber adaptation_set_counter_; base::AtomicSequenceNumber adaptation_set_counter_;
@ -167,6 +168,10 @@ class AdaptationSet {
const MpdOptions& mpd_options_, const MpdOptions& mpd_options_,
base::AtomicSequenceNumber* representation_counter); base::AtomicSequenceNumber* representation_counter);
// Gets the earliest, normalized segment timestamp. Returns true if
// successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
std::list<ContentProtectionElement> content_protection_elements_; std::list<ContentProtectionElement> content_protection_elements_;
std::list<Representation*> representations_; std::list<Representation*> representations_;
::STLElementDeleter<std::list<Representation*> > representations_deleter_; ::STLElementDeleter<std::list<Representation*> > representations_deleter_;
@ -248,6 +253,10 @@ class Representation {
std::string GetVideoMimeType() const; std::string GetVideoMimeType() const;
std::string GetAudioMimeType() const; std::string GetAudioMimeType() const;
// Gets the earliest, normalized segment timestamp. Returns true if
// successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
MediaInfo media_info_; MediaInfo media_info_;
std::list<ContentProtectionElement> content_protection_elements_; std::list<ContentProtectionElement> content_protection_elements_;
std::list<SegmentInfo> segment_infos_; std::list<SegmentInfo> segment_infos_;
@ -258,7 +267,7 @@ class Representation {
std::string mime_type_; std::string mime_type_;
std::string codecs_; std::string codecs_;
BandwidthEstimator bandwidth_estimator_; BandwidthEstimator bandwidth_estimator_;
const MpdOptions& mpd_options_;; const MpdOptions& mpd_options_;
// startNumber attribute for SegmentTemplate. // startNumber attribute for SegmentTemplate.
// Starts from 1. // Starts from 1.

View File

@ -99,7 +99,7 @@ class DynamicMpdBuilderTest : public MpdBuilderTest<MpdBuilder::kDynamic> {
// Anchors availabilityStartTime so that the test result doesn't depend on the // Anchors availabilityStartTime so that the test result doesn't depend on the
// current time. // current time.
virtual void SetUp() { virtual void SetUp() {
mpd_.mpd_options_.availability_start_time = "2011-12-25T12:30:00"; mpd_.availability_start_time_ = "2011-12-25T12:30:00";
} }
MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; } MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; }
@ -370,9 +370,9 @@ TEST_F(DynamicMpdBuilderTest, CheckMpdAttributes) {
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" " "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
"xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 " "xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 "
"DASH-MPD.xsd\" availabilityStartTime=\"2011-12-25T12:30:00\" " "DASH-MPD.xsd\" minBufferTime=\"PT2S\" type=\"dynamic\" "
"minBufferTime=\"PT2S\" type=\"dynamic\" " "profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
"profiles=\"urn:mpeg:dash:profile:isoff-live:2011\">\n" "availabilityStartTime=\"2011-12-25T12:30:00\">\n"
" <Period start=\"PT0S\"/>\n" " <Period start=\"PT0S\"/>\n"
"</MPD>\n"; "</MPD>\n";