7 #include "packager/mpd/base/mpd_builder.h" 11 #include "packager/base/files/file_path.h" 12 #include "packager/base/logging.h" 13 #include "packager/base/optional.h" 14 #include "packager/base/strings/string_number_conversions.h" 15 #include "packager/base/strings/stringprintf.h" 16 #include "packager/base/synchronization/lock.h" 17 #include "packager/base/time/default_clock.h" 18 #include "packager/base/time/time.h" 19 #include "packager/mpd/base/adaptation_set.h" 20 #include "packager/mpd/base/mpd_utils.h" 21 #include "packager/mpd/base/period.h" 22 #include "packager/mpd/base/representation.h" 23 #include "packager/mpd/base/xml/xml_node.h" 24 #include "packager/version/version.h" 33 void AddMpdNameSpaceInfo(XmlNode* mpd) {
36 const std::set<std::string> namespaces = mpd->ExtractReferencedNamespaces();
38 static const char kXmlNamespace[] =
"urn:mpeg:dash:schema:mpd:2011";
39 static const char kXmlNamespaceXsi[] =
40 "http://www.w3.org/2001/XMLSchema-instance";
41 static const char kDashSchemaMpd2011[] =
42 "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
44 mpd->SetStringAttribute(
"xmlns", kXmlNamespace);
45 mpd->SetStringAttribute(
"xmlns:xsi", kXmlNamespaceXsi);
46 mpd->SetStringAttribute(
"xsi:schemaLocation", kDashSchemaMpd2011);
48 static const char kCencNamespace[] =
"urn:mpeg:cenc:2013";
49 static const char kMarlinNamespace[] =
50 "urn:marlin:mas:1-0:services:schemas:mpd";
51 static const char kXmlNamespaceXlink[] =
"http://www.w3.org/1999/xlink";
53 const std::map<std::string, std::string> uris = {
54 {
"cenc", kCencNamespace},
55 {
"mas", kMarlinNamespace},
56 {
"xlink", kXmlNamespaceXlink},
59 for (
const std::string& namespace_name : namespaces) {
60 auto iter = uris.find(namespace_name);
61 CHECK(iter != uris.end()) <<
" unexpected namespace " << namespace_name;
63 mpd->SetStringAttribute(
64 base::StringPrintf(
"xmlns:%s", namespace_name.c_str()).c_str(),
69 bool Positive(
double d) {
75 std::string XmlDateTimeNowWithOffset(
76 int32_t offset_seconds,
78 base::Time time = clock->Now();
79 time += base::TimeDelta::FromSeconds(offset_seconds);
80 base::Time::Exploded time_exploded;
81 time.UTCExplode(&time_exploded);
83 return base::StringPrintf(
"%4d-%02d-%02dT%02d:%02d:%02dZ", time_exploded.year,
84 time_exploded.month, time_exploded.day_of_month,
85 time_exploded.hour, time_exploded.minute,
86 time_exploded.second);
89 void SetIfPositive(
const char* attr_name,
double value, XmlNode* mpd) {
90 if (Positive(value)) {
91 mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
95 std::string MakePathRelative(
const std::string& media_path,
96 const FilePath& parent_path) {
97 FilePath relative_path;
98 const FilePath child_path = FilePath::FromUTF8Unsafe(media_path);
100 parent_path.AppendRelativePath(child_path, &relative_path);
102 relative_path = child_path;
103 return relative_path.NormalizePathSeparatorsTo(
'/').AsUTF8Unsafe();
107 class LibXmlInitializer {
109 LibXmlInitializer() : initialized_(false) {
110 base::AutoLock lock(lock_);
117 ~LibXmlInitializer() {
118 base::AutoLock lock(lock_);
121 initialized_ =
false;
129 DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
135 : mpd_options_(mpd_options), clock_(new
base::DefaultClock()) {}
137 MpdBuilder::~MpdBuilder() {}
140 base_urls_.push_back(base_url);
144 for (
auto& period : periods_) {
145 const double kPeriodTimeDriftThresholdInSeconds = 1.0;
147 std::fabs(period->start_time_in_seconds() - start_time_in_seconds) <
148 kPeriodTimeDriftThresholdInSeconds;
152 periods_.emplace_back(
new Period(period_counter_++, start_time_in_seconds,
153 mpd_options_, &representation_counter_));
154 return periods_.back().get();
159 static LibXmlInitializer lib_xml_initializer;
161 xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
165 static const int kNiceFormat = 1;
166 int doc_str_size = 0;
167 xmlChar* doc_str =
nullptr;
168 xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size,
"UTF-8",
170 output->assign(doc_str, doc_str + doc_str_size);
178 xmlDocPtr MpdBuilder::GenerateMpd() {
180 static const char kXmlVersion[] =
"1.0";
181 xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
185 for (
const std::string& base_url : base_urls_) {
186 XmlNode xml_base_url(
"BaseURL");
193 bool output_period_duration =
false;
194 if (mpd_options_.mpd_type == MpdType::kStatic) {
195 UpdatePeriodDurationAndPresentationTimestamp();
199 output_period_duration = periods_.size() > 1;
202 for (
const auto& period : periods_) {
203 xml::scoped_xml_ptr<xmlNode> period_node(
204 period->GetXml(output_period_duration));
205 if (!period_node || !mpd.
AddChild(std::move(period_node)))
209 AddMpdNameSpaceInfo(&mpd);
211 static const char kOnDemandProfile[] =
212 "urn:mpeg:dash:profile:isoff-on-demand:2011";
213 static const char kLiveProfile[] =
214 "urn:mpeg:dash:profile:isoff-live:2011";
215 switch (mpd_options_.dash_profile) {
216 case DashProfile::kOnDemand:
219 case DashProfile::kLive:
223 NOTREACHED() <<
"Unknown DASH profile: " 224 <<
static_cast<int>(mpd_options_.dash_profile);
228 AddCommonMpdInfo(&mpd);
229 switch (mpd_options_.mpd_type) {
230 case MpdType::kStatic:
231 AddStaticMpdInfo(&mpd);
233 case MpdType::kDynamic:
234 AddDynamicMpdInfo(&mpd);
239 NOTREACHED() <<
"Unknown MPD type: " 240 <<
static_cast<int>(mpd_options_.mpd_type);
245 const std::string version = GetPackagerVersion();
246 if (!version.empty()) {
247 std::string version_string =
248 base::StringPrintf(
"Generated with %s version %s",
249 GetPackagerProjectUrl().c_str(), version.c_str());
250 xml::scoped_xml_ptr<xmlNode> comment(
251 xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str()));
252 xmlDocSetRootElement(doc.get(), comment.get());
253 xmlAddSibling(comment.release(), mpd.
Release());
255 xmlDocSetRootElement(doc.get(), mpd.
Release());
257 return doc.release();
260 void MpdBuilder::AddCommonMpdInfo(
XmlNode* mpd_node) {
266 LOG(ERROR) <<
"minBufferTime value not specified.";
271 void MpdBuilder::AddStaticMpdInfo(
XmlNode* mpd_node) {
273 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
275 static const char kStaticMpdType[] =
"static";
278 SecondsToXmlDuration(GetStaticMpdDuration()));
281 void MpdBuilder::AddDynamicMpdInfo(
XmlNode* mpd_node) {
283 DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
285 static const char kDynamicMpdType[] =
"dynamic";
290 XmlDateTimeNowWithOffset(0, clock_.get()));
294 if (availability_start_time_.empty()) {
295 double earliest_presentation_time;
296 if (GetEarliestTimestamp(&earliest_presentation_time)) {
297 availability_start_time_ = XmlDateTimeNowWithOffset(
298 -std::ceil(earliest_presentation_time), clock_.get());
300 LOG(ERROR) <<
"Could not determine the earliest segment presentation " 301 "time for availabilityStartTime calculation.";
305 if (!availability_start_time_.empty())
307 availability_start_time_);
311 "minimumUpdatePeriod",
314 LOG(WARNING) <<
"The profile is dynamic but no minimumUpdatePeriod " 318 SetIfPositive(
"timeShiftBufferDepth",
320 SetIfPositive(
"suggestedPresentationDelay",
321 mpd_options_.mpd_params.suggested_presentation_delay, mpd_node);
324 void MpdBuilder::AddUtcTiming(
XmlNode* mpd_node) {
326 DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
329 mpd_options_.mpd_params.utc_timings) {
330 XmlNode utc_timing_node(
"UTCTiming");
337 float MpdBuilder::GetStaticMpdDuration() {
338 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
340 float total_duration = 0.0f;
341 for (
const auto& period : periods_) {
342 total_duration += period->duration_seconds();
344 return total_duration;
347 bool MpdBuilder::GetEarliestTimestamp(
double* timestamp_seconds) {
348 DCHECK(timestamp_seconds);
349 DCHECK(!periods_.empty());
350 double timestamp = 0;
351 double earliest_timestamp = -1;
356 for (
const auto* adaptation_set : periods_.front()->GetAdaptationSets()) {
357 for (
const auto* representation : adaptation_set->GetRepresentations()) {
358 if (representation->GetStartAndEndTimestamps(×tamp,
nullptr) &&
359 (earliest_timestamp < 0 || timestamp < earliest_timestamp)) {
360 earliest_timestamp = timestamp;
364 if (earliest_timestamp < 0)
366 *timestamp_seconds = earliest_timestamp;
370 void MpdBuilder::UpdatePeriodDurationAndPresentationTimestamp() {
371 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
373 for (
const auto& period : periods_) {
374 std::list<Representation*> video_representations;
375 std::list<Representation*> non_video_representations;
376 for (
const auto& adaptation_set : period->GetAdaptationSets()) {
377 const auto& representations = adaptation_set->GetRepresentations();
378 if (adaptation_set->IsVideo()) {
379 video_representations.insert(video_representations.end(),
380 representations.begin(),
381 representations.end());
383 non_video_representations.insert(non_video_representations.end(),
384 representations.begin(),
385 representations.end());
389 base::Optional<double> earliest_start_time;
390 base::Optional<double> latest_end_time;
392 const auto& representations = video_representations.size() > 0
393 ? video_representations
394 : non_video_representations;
395 for (
const auto& representation : representations) {
396 double start_time = 0;
398 if (representation->GetStartAndEndTimestamps(&start_time, &end_time)) {
399 earliest_start_time =
400 std::min(earliest_start_time.value_or(start_time), start_time);
402 std::max(latest_end_time.value_or(end_time), end_time);
406 if (!earliest_start_time)
409 period->set_duration_seconds(*latest_end_time - *earliest_start_time);
411 double presentation_time_offset = *earliest_start_time;
412 for (
const auto& adaptation_set : period->GetAdaptationSets()) {
413 for (
const auto& representation : adaptation_set->GetRepresentations()) {
414 representation->SetPresentationTimeOffset(presentation_time_offset);
421 MediaInfo* media_info) {
423 const std::string kFileProtocol(
"file://");
424 std::string mpd_file_path = (mpd_path.find(kFileProtocol) == 0)
425 ? mpd_path.substr(kFileProtocol.size())
428 if (!mpd_file_path.empty()) {
429 const FilePath mpd_dir(FilePath::FromUTF8Unsafe(mpd_file_path)
431 .AsEndingWithSeparator());
432 if (!mpd_dir.empty()) {
433 if (media_info->has_media_file_name()) {
434 media_info->set_media_file_url(
435 MakePathRelative(media_info->media_file_name(), mpd_dir));
437 if (media_info->has_init_segment_name()) {
438 media_info->set_init_segment_url(
439 MakePathRelative(media_info->init_segment_name(), mpd_dir));
441 if (media_info->has_segment_template()) {
442 media_info->set_segment_template_url(
443 MakePathRelative(media_info->segment_template(), mpd_dir));
UTCTimings. For dynamic MPD only.
scoped_xml_ptr< xmlNode > PassScopedPtr()
All the methods that are virtual are virtual for mocking.
void SetStringAttribute(const char *attribute_name, const std::string &attribute)
bool AddChild(scoped_xml_ptr< xmlNode > child)
virtual Period * GetOrCreatePeriod(double start_time_in_seconds)
MpdBuilder(const MpdOptions &mpd_options)
void AddBaseUrl(const std::string &base_url)
virtual bool ToString(std::string *output)
static void MakePathsRelativeToMpd(const std::string &mpd_path, MediaInfo *media_info)
double minimum_update_period
void SetContent(const std::string &content)
double time_shift_buffer_depth