Shaka Packager SDK
mpd_builder.cc
1 // Copyright 2014 Google Inc. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd
6 
7 #include "packager/mpd/base/mpd_builder.h"
8 
9 #include <algorithm>
10 
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"
25 
26 namespace shaka {
27 
28 using base::FilePath;
29 using xml::XmlNode;
30 
31 namespace {
32 
33 void AddMpdNameSpaceInfo(XmlNode* mpd) {
34  DCHECK(mpd);
35 
36  const std::set<std::string> namespaces = mpd->ExtractReferencedNamespaces();
37 
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";
43 
44  mpd->SetStringAttribute("xmlns", kXmlNamespace);
45  mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi);
46  mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011);
47 
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";
52 
53  const std::map<std::string, std::string> uris = {
54  {"cenc", kCencNamespace},
55  {"mas", kMarlinNamespace},
56  {"xlink", kXmlNamespaceXlink},
57  };
58 
59  for (const std::string& namespace_name : namespaces) {
60  auto iter = uris.find(namespace_name);
61  CHECK(iter != uris.end()) << " unexpected namespace " << namespace_name;
62 
63  mpd->SetStringAttribute(
64  base::StringPrintf("xmlns:%s", namespace_name.c_str()).c_str(),
65  iter->second);
66  }
67 }
68 
69 bool Positive(double d) {
70  return d > 0.0;
71 }
72 
73 // Return current time in XML DateTime format. The value is in UTC, so the
74 // string ends with a 'Z'.
75 std::string XmlDateTimeNowWithOffset(
76  int32_t offset_seconds,
77  base::Clock* clock) {
78  base::Time time = clock->Now();
79  time += base::TimeDelta::FromSeconds(offset_seconds);
80  base::Time::Exploded time_exploded;
81  time.UTCExplode(&time_exploded);
82 
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);
87 }
88 
89 void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
90  if (Positive(value)) {
91  mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
92  }
93 }
94 
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);
99  const bool is_child =
100  parent_path.AppendRelativePath(child_path, &relative_path);
101  if (!is_child)
102  relative_path = child_path;
103  return relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe();
104 }
105 
106 // Spooky static initialization/cleanup of libxml.
107 class LibXmlInitializer {
108  public:
109  LibXmlInitializer() : initialized_(false) {
110  base::AutoLock lock(lock_);
111  if (!initialized_) {
112  xmlInitParser();
113  initialized_ = true;
114  }
115  }
116 
117  ~LibXmlInitializer() {
118  base::AutoLock lock(lock_);
119  if (initialized_) {
120  xmlCleanupParser();
121  initialized_ = false;
122  }
123  }
124 
125  private:
126  base::Lock lock_;
127  bool initialized_;
128 
129  DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
130 };
131 
132 } // namespace
133 
135  : mpd_options_(mpd_options), clock_(new base::DefaultClock()) {}
136 
137 MpdBuilder::~MpdBuilder() {}
138 
139 void MpdBuilder::AddBaseUrl(const std::string& base_url) {
140  base_urls_.push_back(base_url);
141 }
142 
143 Period* MpdBuilder::GetOrCreatePeriod(double start_time_in_seconds) {
144  for (auto& period : periods_) {
145  const double kPeriodTimeDriftThresholdInSeconds = 1.0;
146  const bool match =
147  std::fabs(period->start_time_in_seconds() - start_time_in_seconds) <
148  kPeriodTimeDriftThresholdInSeconds;
149  if (match)
150  return period.get();
151  }
152  periods_.emplace_back(new Period(period_counter_++, start_time_in_seconds,
153  mpd_options_, &representation_counter_));
154  return periods_.back().get();
155 }
156 
157 bool MpdBuilder::ToString(std::string* output) {
158  DCHECK(output);
159  static LibXmlInitializer lib_xml_initializer;
160 
161  xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
162  if (!doc)
163  return false;
164 
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",
169  kNiceFormat);
170  output->assign(doc_str, doc_str + doc_str_size);
171  xmlFree(doc_str);
172 
173  // Cleanup, free the doc.
174  doc.reset();
175  return true;
176 }
177 
178 xmlDocPtr MpdBuilder::GenerateMpd() {
179  // Setup nodes.
180  static const char kXmlVersion[] = "1.0";
181  xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
182  XmlNode mpd("MPD");
183 
184  // Add baseurls to MPD.
185  for (const std::string& base_url : base_urls_) {
186  XmlNode xml_base_url("BaseURL");
187  xml_base_url.SetContent(base_url);
188 
189  if (!mpd.AddChild(xml_base_url.PassScopedPtr()))
190  return nullptr;
191  }
192 
193  bool output_period_duration = false;
194  if (mpd_options_.mpd_type == MpdType::kStatic) {
195  UpdatePeriodDurationAndPresentationTimestamp();
196  // Only output period duration if there are more than one period. In the
197  // case of only one period, Period@duration is redundant as it is identical
198  // to Mpd Duration so the convention is not to output Period@duration.
199  output_period_duration = periods_.size() > 1;
200  }
201 
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)))
206  return nullptr;
207  }
208 
209  AddMpdNameSpaceInfo(&mpd);
210 
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:
217  mpd.SetStringAttribute("profiles", kOnDemandProfile);
218  break;
219  case DashProfile::kLive:
220  mpd.SetStringAttribute("profiles", kLiveProfile);
221  break;
222  default:
223  NOTREACHED() << "Unknown DASH profile: "
224  << static_cast<int>(mpd_options_.dash_profile);
225  break;
226  }
227 
228  AddCommonMpdInfo(&mpd);
229  switch (mpd_options_.mpd_type) {
230  case MpdType::kStatic:
231  AddStaticMpdInfo(&mpd);
232  break;
233  case MpdType::kDynamic:
234  AddDynamicMpdInfo(&mpd);
235  // Must be after Period element.
236  AddUtcTiming(&mpd);
237  break;
238  default:
239  NOTREACHED() << "Unknown MPD type: "
240  << static_cast<int>(mpd_options_.mpd_type);
241  break;
242  }
243 
244  DCHECK(doc);
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());
254  } else {
255  xmlDocSetRootElement(doc.get(), mpd.Release());
256  }
257  return doc.release();
258 }
259 
260 void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
261  if (Positive(mpd_options_.mpd_params.min_buffer_time)) {
262  mpd_node->SetStringAttribute(
263  "minBufferTime",
264  SecondsToXmlDuration(mpd_options_.mpd_params.min_buffer_time));
265  } else {
266  LOG(ERROR) << "minBufferTime value not specified.";
267  // TODO(tinskip): Propagate error.
268  }
269 }
270 
271 void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
272  DCHECK(mpd_node);
273  DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
274 
275  static const char kStaticMpdType[] = "static";
276  mpd_node->SetStringAttribute("type", kStaticMpdType);
277  mpd_node->SetStringAttribute("mediaPresentationDuration",
278  SecondsToXmlDuration(GetStaticMpdDuration()));
279 }
280 
281 void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
282  DCHECK(mpd_node);
283  DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
284 
285  static const char kDynamicMpdType[] = "dynamic";
286  mpd_node->SetStringAttribute("type", kDynamicMpdType);
287 
288  // No offset from NOW.
289  mpd_node->SetStringAttribute("publishTime",
290  XmlDateTimeNowWithOffset(0, clock_.get()));
291 
292  // 'availabilityStartTime' is required for dynamic profile. Calculate if
293  // not already calculated.
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());
299  } else {
300  LOG(ERROR) << "Could not determine the earliest segment presentation "
301  "time for availabilityStartTime calculation.";
302  // TODO(tinskip). Propagate an error.
303  }
304  }
305  if (!availability_start_time_.empty())
306  mpd_node->SetStringAttribute("availabilityStartTime",
307  availability_start_time_);
308 
309  if (Positive(mpd_options_.mpd_params.minimum_update_period)) {
310  mpd_node->SetStringAttribute(
311  "minimumUpdatePeriod",
312  SecondsToXmlDuration(mpd_options_.mpd_params.minimum_update_period));
313  } else {
314  LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
315  "specified.";
316  }
317 
318  SetIfPositive("timeShiftBufferDepth",
319  mpd_options_.mpd_params.time_shift_buffer_depth, mpd_node);
320  SetIfPositive("suggestedPresentationDelay",
321  mpd_options_.mpd_params.suggested_presentation_delay, mpd_node);
322 }
323 
324 void MpdBuilder::AddUtcTiming(XmlNode* mpd_node) {
325  DCHECK(mpd_node);
326  DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
327 
328  for (const MpdParams::UtcTiming& utc_timing :
329  mpd_options_.mpd_params.utc_timings) {
330  XmlNode utc_timing_node("UTCTiming");
331  utc_timing_node.SetStringAttribute("schemeIdUri", utc_timing.scheme_id_uri);
332  utc_timing_node.SetStringAttribute("value", utc_timing.value);
333  mpd_node->AddChild(utc_timing_node.PassScopedPtr());
334  }
335 }
336 
337 float MpdBuilder::GetStaticMpdDuration() {
338  DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
339 
340  float total_duration = 0.0f;
341  for (const auto& period : periods_) {
342  total_duration += period->duration_seconds();
343  }
344  return total_duration;
345 }
346 
347 bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
348  DCHECK(timestamp_seconds);
349  DCHECK(!periods_.empty());
350  double timestamp = 0;
351  double earliest_timestamp = -1;
352  // TODO(kqyang): This is used to set availabilityStartTime. We may consider
353  // set presentationTimeOffset in the Representations then we can set
354  // availabilityStartTime to the time when MPD is first generated.
355  // The first period should have the earliest timestamp.
356  for (const auto* adaptation_set : periods_.front()->GetAdaptationSets()) {
357  for (const auto* representation : adaptation_set->GetRepresentations()) {
358  if (representation->GetStartAndEndTimestamps(&timestamp, nullptr) &&
359  (earliest_timestamp < 0 || timestamp < earliest_timestamp)) {
360  earliest_timestamp = timestamp;
361  }
362  }
363  }
364  if (earliest_timestamp < 0)
365  return false;
366  *timestamp_seconds = earliest_timestamp;
367  return true;
368 }
369 
370 void MpdBuilder::UpdatePeriodDurationAndPresentationTimestamp() {
371  DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
372 
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());
382  } else {
383  non_video_representations.insert(non_video_representations.end(),
384  representations.begin(),
385  representations.end());
386  }
387  }
388 
389  base::Optional<double> earliest_start_time;
390  base::Optional<double> latest_end_time;
391  // The timestamps are based on Video Representations if exist.
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;
397  double end_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);
401  latest_end_time =
402  std::max(latest_end_time.value_or(end_time), end_time);
403  }
404  }
405 
406  if (!earliest_start_time)
407  return;
408 
409  period->set_duration_seconds(*latest_end_time - *earliest_start_time);
410 
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);
415  }
416  }
417  }
418 }
419 
420 void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,
421  MediaInfo* media_info) {
422  DCHECK(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())
426  : mpd_path;
427 
428  if (!mpd_file_path.empty()) {
429  const FilePath mpd_dir(FilePath::FromUTF8Unsafe(mpd_file_path)
430  .DirName()
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));
436  }
437  if (media_info->has_init_segment_name()) {
438  media_info->set_init_segment_url(
439  MakePathRelative(media_info->init_segment_name(), mpd_dir));
440  }
441  if (media_info->has_segment_template()) {
442  media_info->set_segment_template_url(
443  MakePathRelative(media_info->segment_template(), mpd_dir));
444  }
445  }
446  }
447 }
448 
449 } // namespace shaka
double min_buffer_time
Definition: mpd_params.h:27
UTCTimings. For dynamic MPD only.
Definition: mpd_params.h:48
scoped_xml_ptr< xmlNode > PassScopedPtr()
Definition: xml_node.cc:204
All the methods that are virtual are virtual for mocking.
void SetStringAttribute(const char *attribute_name, const std::string &attribute)
Definition: xml_node.cc:166
bool AddChild(scoped_xml_ptr< xmlNode > child)
Definition: xml_node.cc:121
xmlNodePtr Release()
Definition: xml_node.cc:210
virtual Period * GetOrCreatePeriod(double start_time_in_seconds)
Definition: mpd_builder.cc:143
MpdBuilder(const MpdOptions &mpd_options)
Definition: mpd_builder.cc:134
void AddBaseUrl(const std::string &base_url)
Definition: mpd_builder.cc:139
virtual bool ToString(std::string *output)
Definition: mpd_builder.cc:157
static void MakePathsRelativeToMpd(const std::string &mpd_path, MediaInfo *media_info)
Definition: mpd_builder.cc:420
double minimum_update_period
Definition: mpd_params.h:30
Defines Mpd Options.
Definition: mpd_options.h:25
void SetContent(const std::string &content)
Definition: xml_node.cc:193
double time_shift_buffer_depth
Definition: mpd_params.h:39