DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator
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 <libxml/tree.h>
10 #include <libxml/xmlstring.h>
11 
12 #include <cmath>
13 #include <iterator>
14 #include <list>
15 #include <memory>
16 #include <string>
17 
18 #include "packager/base/base64.h"
19 #include "packager/base/bind.h"
20 #include "packager/base/files/file_path.h"
21 #include "packager/base/logging.h"
22 #include "packager/base/strings/string_number_conversions.h"
23 #include "packager/base/strings/stringprintf.h"
24 #include "packager/base/synchronization/lock.h"
25 #include "packager/base/time/default_clock.h"
26 #include "packager/base/time/time.h"
27 #include "packager/file/file.h"
28 #include "packager/media/base/language_utils.h"
29 #include "packager/mpd/base/content_protection_element.h"
30 #include "packager/mpd/base/mpd_utils.h"
31 #include "packager/mpd/base/xml/xml_node.h"
32 #include "packager/version/version.h"
33 
34 namespace shaka {
35 
36 using base::FilePath;
37 using xml::XmlNode;
38 using xml::RepresentationXmlNode;
39 using xml::AdaptationSetXmlNode;
40 
41 namespace {
42 
43 AdaptationSet::Role MediaInfoTextTypeToRole(
44  MediaInfo::TextInfo::TextType type) {
45  switch (type) {
46  case MediaInfo::TextInfo::UNKNOWN:
47  LOG(WARNING) << "Unknown text type, assuming subtitle.";
48  return AdaptationSet::kRoleSubtitle;
49  case MediaInfo::TextInfo::CAPTION:
50  return AdaptationSet::kRoleCaption;
51  case MediaInfo::TextInfo::SUBTITLE:
52  return AdaptationSet::kRoleSubtitle;
53  default:
54  NOTREACHED() << "Unknown MediaInfo TextType: " << type
55  << " assuming subtitle.";
56  return AdaptationSet::kRoleSubtitle;
57  }
58 }
59 
60 std::string GetMimeType(const std::string& prefix,
61  MediaInfo::ContainerType container_type) {
62  switch (container_type) {
63  case MediaInfo::CONTAINER_MP4:
64  return prefix + "/mp4";
65  case MediaInfo::CONTAINER_MPEG2_TS:
66  // NOTE: DASH MPD spec uses lowercase but RFC3555 says uppercase.
67  return prefix + "/MP2T";
68  case MediaInfo::CONTAINER_WEBM:
69  return prefix + "/webm";
70  default:
71  break;
72  }
73 
74  // Unsupported container types should be rejected/handled by the caller.
75  LOG(ERROR) << "Unrecognized container type: " << container_type;
76  return std::string();
77 }
78 
79 void AddMpdNameSpaceInfo(XmlNode* mpd) {
80  DCHECK(mpd);
81 
82  static const char kXmlNamespace[] = "urn:mpeg:dash:schema:mpd:2011";
83  static const char kXmlNamespaceXsi[] =
84  "http://www.w3.org/2001/XMLSchema-instance";
85  static const char kXmlNamespaceXlink[] = "http://www.w3.org/1999/xlink";
86  static const char kDashSchemaMpd2011[] =
87  "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
88  static const char kCencNamespace[] = "urn:mpeg:cenc:2013";
89 
90  mpd->SetStringAttribute("xmlns", kXmlNamespace);
91  mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi);
92  mpd->SetStringAttribute("xmlns:xlink", kXmlNamespaceXlink);
93  mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011);
94  mpd->SetStringAttribute("xmlns:cenc", kCencNamespace);
95 }
96 
97 bool IsPeriodNode(xmlNodePtr node) {
98  DCHECK(node);
99  int kEqual = 0;
100  return xmlStrcmp(node->name, reinterpret_cast<const xmlChar*>("Period")) ==
101  kEqual;
102 }
103 
104 // Find the first <Period> element. This does not recurse down the tree,
105 // only checks direct children. Returns the pointer to Period element on
106 // success, otherwise returns false.
107 // As noted here, we must traverse.
108 // http://www.xmlsoft.org/tutorial/ar01s04.html
109 xmlNodePtr FindPeriodNode(XmlNode* xml_node) {
110  for (xmlNodePtr node = xml_node->GetRawPtr()->xmlChildrenNode; node != NULL;
111  node = node->next) {
112  if (IsPeriodNode(node))
113  return node;
114  }
115 
116  return NULL;
117 }
118 
119 bool Positive(double d) {
120  return d > 0.0;
121 }
122 
123 // Return current time in XML DateTime format. The value is in UTC, so the
124 // string ends with a 'Z'.
125 std::string XmlDateTimeNowWithOffset(
126  int32_t offset_seconds,
127  base::Clock* clock) {
128  base::Time time = clock->Now();
129  time += base::TimeDelta::FromSeconds(offset_seconds);
130  base::Time::Exploded time_exploded;
131  time.UTCExplode(&time_exploded);
132 
133  return base::StringPrintf("%4d-%02d-%02dT%02d:%02d:%02dZ", time_exploded.year,
134  time_exploded.month, time_exploded.day_of_month,
135  time_exploded.hour, time_exploded.minute,
136  time_exploded.second);
137 }
138 
139 void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
140  if (Positive(value)) {
141  mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
142  }
143 }
144 
145 uint32_t GetTimeScale(const MediaInfo& media_info) {
146  if (media_info.has_reference_time_scale()) {
147  return media_info.reference_time_scale();
148  }
149 
150  if (media_info.has_video_info()) {
151  return media_info.video_info().time_scale();
152  }
153 
154  if (media_info.has_audio_info()) {
155  return media_info.audio_info().time_scale();
156  }
157 
158  LOG(WARNING) << "No timescale specified, using 1 as timescale.";
159  return 1;
160 }
161 
162 uint64_t LastSegmentStartTime(const SegmentInfo& segment_info) {
163  return segment_info.start_time + segment_info.duration * segment_info.repeat;
164 }
165 
166 // This is equal to |segment_info| end time
167 uint64_t LastSegmentEndTime(const SegmentInfo& segment_info) {
168  return segment_info.start_time +
169  segment_info.duration * (segment_info.repeat + 1);
170 }
171 
172 uint64_t LatestSegmentStartTime(const std::list<SegmentInfo>& segments) {
173  DCHECK(!segments.empty());
174  const SegmentInfo& latest_segment = segments.back();
175  return LastSegmentStartTime(latest_segment);
176 }
177 
178 // Given |timeshift_limit|, finds out the number of segments that are no longer
179 // valid and should be removed from |segment_info|.
180 int SearchTimedOutRepeatIndex(uint64_t timeshift_limit,
181  const SegmentInfo& segment_info) {
182  DCHECK_LE(timeshift_limit, LastSegmentEndTime(segment_info));
183  if (timeshift_limit < segment_info.start_time)
184  return 0;
185 
186  return (timeshift_limit - segment_info.start_time) / segment_info.duration;
187 }
188 
189 std::string MakePathRelative(const std::string& path,
190  const std::string& mpd_dir) {
191  return (path.find(mpd_dir) == 0) ? path.substr(mpd_dir.size()) : path;
192 }
193 
194 // Check whether the video info has width and height.
195 // DASH IOP also requires several other fields for video representations, namely
196 // width, height, framerate, and sar.
197 bool HasRequiredVideoFields(const MediaInfo_VideoInfo& video_info) {
198  if (!video_info.has_height() || !video_info.has_width()) {
199  LOG(ERROR)
200  << "Width and height are required fields for generating a valid MPD.";
201  return false;
202  }
203  // These fields are not required for a valid MPD, but required for DASH IOP
204  // compliant MPD. MpdBuilder can keep generating MPDs without these fields.
205  LOG_IF(WARNING, !video_info.has_time_scale())
206  << "Video info does not contain timescale required for "
207  "calculating framerate. @frameRate is required for DASH IOP.";
208  LOG_IF(WARNING, !video_info.has_pixel_width())
209  << "Video info does not contain pixel_width to calculate the sample "
210  "aspect ratio required for DASH IOP.";
211  LOG_IF(WARNING, !video_info.has_pixel_height())
212  << "Video info does not contain pixel_height to calculate the sample "
213  "aspect ratio required for DASH IOP.";
214  return true;
215 }
216 
217 // Returns the picture aspect ratio string e.g. "16:9", "4:3".
218 // "Reducing the quotient to minimal form" does not work well in practice as
219 // there may be some rounding performed in the input, e.g. the resolution of
220 // 480p is 854:480 for 16:9 aspect ratio, can only be reduced to 427:240.
221 // The algorithm finds out the pair of integers, num and den, where num / den is
222 // the closest ratio to scaled_width / scaled_height, by looping den through
223 // common values.
224 std::string GetPictureAspectRatio(uint32_t width,
225  uint32_t height,
226  uint32_t pixel_width,
227  uint32_t pixel_height) {
228  const uint32_t scaled_width = pixel_width * width;
229  const uint32_t scaled_height = pixel_height * height;
230  const double par = static_cast<double>(scaled_width) / scaled_height;
231 
232  // Typical aspect ratios have par_y less than or equal to 19:
233  // https://en.wikipedia.org/wiki/List_of_common_resolutions
234  const uint32_t kLargestPossibleParY = 19;
235 
236  uint32_t par_num = 0;
237  uint32_t par_den = 0;
238  double min_error = 1.0;
239  for (uint32_t den = 1; den <= kLargestPossibleParY; ++den) {
240  uint32_t num = par * den + 0.5;
241  double error = fabs(par - static_cast<double>(num) / den);
242  if (error < min_error) {
243  min_error = error;
244  par_num = num;
245  par_den = den;
246  if (error == 0) break;
247  }
248  }
249  VLOG(2) << "width*pix_width : height*pixel_height (" << scaled_width << ":"
250  << scaled_height << ") reduced to " << par_num << ":" << par_den
251  << " with error " << min_error << ".";
252 
253  return base::IntToString(par_num) + ":" + base::IntToString(par_den);
254 }
255 
256 // Adds an entry to picture_aspect_ratio if the size of picture_aspect_ratio is
257 // less than 2 and video_info has both pixel width and pixel height.
258 void AddPictureAspectRatio(
259  const MediaInfo::VideoInfo& video_info,
260  std::set<std::string>* picture_aspect_ratio) {
261  // If there are more than one entries in picture_aspect_ratio, the @par
262  // attribute cannot be set, so skip.
263  if (picture_aspect_ratio->size() > 1)
264  return;
265 
266  if (video_info.width() == 0 || video_info.height() == 0 ||
267  video_info.pixel_width() == 0 || video_info.pixel_height() == 0) {
268  // If there is even one Representation without a @sar attribute, @par cannot
269  // be calculated.
270  // Just populate the set with at least 2 bogus strings so that further call
271  // to this function will bail out immediately.
272  picture_aspect_ratio->insert("bogus");
273  picture_aspect_ratio->insert("entries");
274  return;
275  }
276 
277  const std::string par = GetPictureAspectRatio(
278  video_info.width(), video_info.height(),
279  video_info.pixel_width(), video_info.pixel_height());
280  DVLOG(1) << "Setting par as: " << par
281  << " for video with width: " << video_info.width()
282  << " height: " << video_info.height()
283  << " pixel_width: " << video_info.pixel_width() << " pixel_height; "
284  << video_info.pixel_height();
285  picture_aspect_ratio->insert(par);
286 }
287 
288 std::string RoleToText(AdaptationSet::Role role) {
289  // Using switch so that the compiler can detect whether there is a case that's
290  // not being handled.
291  switch (role) {
292  case AdaptationSet::kRoleCaption:
293  return "caption";
294  case AdaptationSet::kRoleSubtitle:
295  return "subtitle";
296  case AdaptationSet::kRoleMain:
297  return "main";
298  case AdaptationSet::kRoleAlternate:
299  return "alternate";
300  case AdaptationSet::kRoleSupplementary:
301  return "supplementary";
302  case AdaptationSet::kRoleCommentary:
303  return "commentary";
304  case AdaptationSet::kRoleDub:
305  return "dub";
306  default:
307  break;
308  }
309 
310  NOTREACHED();
311  return "";
312 }
313 
314 // Spooky static initialization/cleanup of libxml.
315 class LibXmlInitializer {
316  public:
317  LibXmlInitializer() : initialized_(false) {
318  base::AutoLock lock(lock_);
319  if (!initialized_) {
320  xmlInitParser();
321  initialized_ = true;
322  }
323  }
324 
325  ~LibXmlInitializer() {
326  base::AutoLock lock(lock_);
327  if (initialized_) {
328  xmlCleanupParser();
329  initialized_ = false;
330  }
331  }
332 
333  private:
334  base::Lock lock_;
335  bool initialized_;
336 
337  DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
338 };
339 
340 class RepresentationStateChangeListenerImpl
341  : public RepresentationStateChangeListener {
342  public:
343  // |adaptation_set| is not owned by this class.
344  RepresentationStateChangeListenerImpl(uint32_t representation_id,
345  AdaptationSet* adaptation_set)
346  : representation_id_(representation_id), adaptation_set_(adaptation_set) {
347  DCHECK(adaptation_set_);
348  }
349  ~RepresentationStateChangeListenerImpl() override {}
350 
351  // RepresentationStateChangeListener implementation.
352  void OnNewSegmentForRepresentation(uint64_t start_time,
353  uint64_t duration) override {
354  adaptation_set_->OnNewSegmentForRepresentation(representation_id_,
355  start_time, duration);
356  }
357 
358  void OnSetFrameRateForRepresentation(uint32_t frame_duration,
359  uint32_t timescale) override {
360  adaptation_set_->OnSetFrameRateForRepresentation(representation_id_,
361  frame_duration, timescale);
362  }
363 
364  private:
365  const uint32_t representation_id_;
366  AdaptationSet* const adaptation_set_;
367 
368  DISALLOW_COPY_AND_ASSIGN(RepresentationStateChangeListenerImpl);
369 };
370 
371 } // namespace
372 
374  : mpd_options_(mpd_options), clock_(new base::DefaultClock()) {}
375 
376 MpdBuilder::~MpdBuilder() {}
377 
378 void MpdBuilder::AddBaseUrl(const std::string& base_url) {
379  base_urls_.push_back(base_url);
380 }
381 
382 AdaptationSet* MpdBuilder::AddAdaptationSet(const std::string& lang) {
383  std::unique_ptr<AdaptationSet> adaptation_set(
384  new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_,
385  &representation_counter_));
386  DCHECK(adaptation_set);
387 
388  if (!lang.empty() && lang == mpd_options_.default_language) {
389  adaptation_set->AddRole(AdaptationSet::kRoleMain);
390  }
391 
392  adaptation_sets_.push_back(std::move(adaptation_set));
393  return adaptation_sets_.back().get();
394 }
395 
396 bool MpdBuilder::ToString(std::string* output) {
397  DCHECK(output);
398  static LibXmlInitializer lib_xml_initializer;
399 
400  xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
401  if (!doc.get())
402  return false;
403 
404  static const int kNiceFormat = 1;
405  int doc_str_size = 0;
406  xmlChar* doc_str = nullptr;
407  xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size, "UTF-8",
408  kNiceFormat);
409  output->assign(doc_str, doc_str + doc_str_size);
410  xmlFree(doc_str);
411 
412  // Cleanup, free the doc.
413  doc.reset();
414  return true;
415 }
416 
417 xmlDocPtr MpdBuilder::GenerateMpd() {
418  // Setup nodes.
419  static const char kXmlVersion[] = "1.0";
420  xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
421  XmlNode mpd("MPD");
422 
423  // Iterate thru AdaptationSets and add them to one big Period element.
424  XmlNode period("Period");
425 
426  // Always set id=0 for now. Since this class can only generate one Period
427  // at the moment, just use a constant.
428  // Required for 'dynamic' MPDs.
429  period.SetId(0);
430  for (const std::unique_ptr<AdaptationSet>& adaptation_set :
431  adaptation_sets_) {
432  xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml());
433  if (!child.get() || !period.AddChild(std::move(child)))
434  return NULL;
435  }
436 
437  // Add baseurls to MPD.
438  std::list<std::string>::const_iterator base_urls_it = base_urls_.begin();
439  for (; base_urls_it != base_urls_.end(); ++base_urls_it) {
440  XmlNode base_url("BaseURL");
441  base_url.SetContent(*base_urls_it);
442 
443  if (!mpd.AddChild(base_url.PassScopedPtr()))
444  return NULL;
445  }
446 
447  // TODO(kqyang): Should we set @start unconditionally to 0?
448  if (mpd_options_.mpd_type == MpdType::kDynamic) {
449  // This is the only Period and it is a regular period.
450  period.SetStringAttribute("start", "PT0S");
451  }
452 
453  if (!mpd.AddChild(period.PassScopedPtr()))
454  return NULL;
455 
456  AddMpdNameSpaceInfo(&mpd);
457 
458  static const char kOnDemandProfile[] =
459  "urn:mpeg:dash:profile:isoff-on-demand:2011";
460  static const char kLiveProfile[] =
461  "urn:mpeg:dash:profile:isoff-live:2011";
462  switch (mpd_options_.dash_profile) {
463  case DashProfile::kOnDemand:
464  mpd.SetStringAttribute("profiles", kOnDemandProfile);
465  break;
466  case DashProfile::kLive:
467  mpd.SetStringAttribute("profiles", kLiveProfile);
468  break;
469  default:
470  NOTREACHED() << "Unknown DASH profile: "
471  << static_cast<int>(mpd_options_.dash_profile);
472  break;
473  }
474 
475  AddCommonMpdInfo(&mpd);
476  switch (mpd_options_.mpd_type) {
477  case MpdType::kStatic:
478  AddStaticMpdInfo(&mpd);
479  break;
480  case MpdType::kDynamic:
481  AddDynamicMpdInfo(&mpd);
482  break;
483  default:
484  NOTREACHED() << "Unknown MPD type: "
485  << static_cast<int>(mpd_options_.mpd_type);
486  break;
487  }
488 
489  DCHECK(doc);
490  const std::string version = GetPackagerVersion();
491  if (!version.empty()) {
492  std::string version_string =
493  base::StringPrintf("Generated with %s version %s",
494  GetPackagerProjectUrl().c_str(), version.c_str());
495  xml::scoped_xml_ptr<xmlNode> comment(
496  xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str()));
497  xmlDocSetRootElement(doc.get(), comment.get());
498  xmlAddSibling(comment.release(), mpd.Release());
499  } else {
500  xmlDocSetRootElement(doc.get(), mpd.Release());
501  }
502  return doc.release();
503 }
504 
505 void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
506  if (Positive(mpd_options_.min_buffer_time)) {
507  mpd_node->SetStringAttribute(
508  "minBufferTime", SecondsToXmlDuration(mpd_options_.min_buffer_time));
509  } else {
510  LOG(ERROR) << "minBufferTime value not specified.";
511  // TODO(tinskip): Propagate error.
512  }
513 }
514 
515 void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
516  DCHECK(mpd_node);
517  DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
518 
519  static const char kStaticMpdType[] = "static";
520  mpd_node->SetStringAttribute("type", kStaticMpdType);
521  mpd_node->SetStringAttribute(
522  "mediaPresentationDuration",
523  SecondsToXmlDuration(GetStaticMpdDuration(mpd_node)));
524 }
525 
526 void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
527  DCHECK(mpd_node);
528  DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
529 
530  static const char kDynamicMpdType[] = "dynamic";
531  mpd_node->SetStringAttribute("type", kDynamicMpdType);
532 
533  // No offset from NOW.
534  mpd_node->SetStringAttribute("publishTime",
535  XmlDateTimeNowWithOffset(0, clock_.get()));
536 
537  // 'availabilityStartTime' is required for dynamic profile. Calculate if
538  // not already calculated.
539  if (availability_start_time_.empty()) {
540  double earliest_presentation_time;
541  if (GetEarliestTimestamp(&earliest_presentation_time)) {
542  availability_start_time_ = XmlDateTimeNowWithOffset(
543  -std::ceil(earliest_presentation_time), clock_.get());
544  } else {
545  LOG(ERROR) << "Could not determine the earliest segment presentation "
546  "time for availabilityStartTime calculation.";
547  // TODO(tinskip). Propagate an error.
548  }
549  }
550  if (!availability_start_time_.empty())
551  mpd_node->SetStringAttribute("availabilityStartTime",
552  availability_start_time_);
553 
554  if (Positive(mpd_options_.minimum_update_period)) {
555  mpd_node->SetStringAttribute(
556  "minimumUpdatePeriod",
557  SecondsToXmlDuration(mpd_options_.minimum_update_period));
558  } else {
559  LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
560  "specified.";
561  }
562 
563  SetIfPositive("timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth,
564  mpd_node);
565  SetIfPositive("suggestedPresentationDelay",
566  mpd_options_.suggested_presentation_delay, mpd_node);
567 }
568 
569 float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
570  DCHECK(mpd_node);
571  DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
572 
573  xmlNodePtr period_node = FindPeriodNode(mpd_node);
574  DCHECK(period_node) << "Period element must be a child of mpd_node.";
575  DCHECK(IsPeriodNode(period_node));
576 
577  // TODO(kqyang): Verify if this works for static + live profile.
578  // Attribute mediaPresentationDuration must be present for 'static' MPD. So
579  // setting "PT0S" is required even if none of the representaions have duration
580  // attribute.
581  float max_duration = 0.0f;
582  for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node);
583  adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
584  for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
585  representation;
586  representation = xmlNextElementSibling(representation)) {
587  float duration = 0.0f;
588  if (GetDurationAttribute(representation, &duration)) {
589  max_duration = max_duration > duration ? max_duration : duration;
590 
591  // 'duration' attribute is there only to help generate MPD, not
592  // necessary for MPD, remove the attribute.
593  xmlUnsetProp(representation, BAD_CAST "duration");
594  }
595  }
596  }
597 
598  return max_duration;
599 }
600 
601 bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
602  DCHECK(timestamp_seconds);
603 
604  double earliest_timestamp(-1);
605  for (const std::unique_ptr<AdaptationSet>& adaptation_set :
606  adaptation_sets_) {
607  double timestamp;
608  if (adaptation_set->GetEarliestTimestamp(&timestamp) &&
609  ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
610  earliest_timestamp = timestamp;
611  }
612  }
613  if (earliest_timestamp < 0)
614  return false;
615 
616  *timestamp_seconds = earliest_timestamp;
617  return true;
618 }
619 
620 void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,
621  MediaInfo* media_info) {
622  DCHECK(media_info);
623  const std::string kFileProtocol("file://");
624  std::string mpd_file_path = (mpd_path.find(kFileProtocol) == 0)
625  ? mpd_path.substr(kFileProtocol.size())
626  : mpd_path;
627 
628  if (!mpd_file_path.empty()) {
629  std::string mpd_dir(FilePath::FromUTF8Unsafe(mpd_file_path)
630  .DirName().AsEndingWithSeparator().AsUTF8Unsafe());
631  if (!mpd_dir.empty()) {
632  if (media_info->has_media_file_name()) {
633  media_info->set_media_file_name(
634  MakePathRelative(media_info->media_file_name(), mpd_dir));
635  }
636  if (media_info->has_init_segment_name()) {
637  media_info->set_init_segment_name(
638  MakePathRelative(media_info->init_segment_name(), mpd_dir));
639  }
640  if (media_info->has_segment_template()) {
641  media_info->set_segment_template(
642  MakePathRelative(media_info->segment_template(), mpd_dir));
643  }
644  }
645  }
646 }
647 
648 AdaptationSet::AdaptationSet(uint32_t adaptation_set_id,
649  const std::string& lang,
650  const MpdOptions& mpd_options,
651  base::AtomicSequenceNumber* counter)
652  : representation_counter_(counter),
653  id_(adaptation_set_id),
654  lang_(lang),
655  mpd_options_(mpd_options),
656  segments_aligned_(kSegmentAlignmentUnknown),
657  force_set_segment_alignment_(false) {
658  DCHECK(counter);
659 }
660 
661 AdaptationSet::~AdaptationSet() {}
662 
663 Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) {
664  const uint32_t representation_id = representation_counter_->GetNext();
665  // Note that AdaptationSet outlive Representation, so this object
666  // will die before AdaptationSet.
667  std::unique_ptr<RepresentationStateChangeListener> listener(
668  new RepresentationStateChangeListenerImpl(representation_id, this));
669  std::unique_ptr<Representation> representation(new Representation(
670  media_info, mpd_options_, representation_id, std::move(listener)));
671 
672  if (!representation->Init()) {
673  LOG(ERROR) << "Failed to initialize Representation.";
674  return NULL;
675  }
676 
677  // For videos, record the width, height, and the frame rate to calculate the
678  // max {width,height,framerate} required for DASH IOP.
679  if (media_info.has_video_info()) {
680  const MediaInfo::VideoInfo& video_info = media_info.video_info();
681  DCHECK(video_info.has_width());
682  DCHECK(video_info.has_height());
683  video_widths_.insert(video_info.width());
684  video_heights_.insert(video_info.height());
685 
686  if (video_info.has_time_scale() && video_info.has_frame_duration())
687  RecordFrameRate(video_info.frame_duration(), video_info.time_scale());
688 
689  AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
690  }
691 
692  if (media_info.has_video_info()) {
693  content_type_ = "video";
694  } else if (media_info.has_audio_info()) {
695  content_type_ = "audio";
696  } else if (media_info.has_text_info()) {
697  content_type_ = "text";
698 
699  if (media_info.text_info().has_type() &&
700  (media_info.text_info().type() != MediaInfo::TextInfo::UNKNOWN)) {
701  roles_.insert(MediaInfoTextTypeToRole(media_info.text_info().type()));
702  }
703  }
704 
705  representations_.push_back(std::move(representation));
706  return representations_.back().get();
707 }
708 
710  const ContentProtectionElement& content_protection_element) {
711  content_protection_elements_.push_back(content_protection_element);
712  RemoveDuplicateAttributes(&content_protection_elements_.back());
713 }
714 
715 void AdaptationSet::UpdateContentProtectionPssh(const std::string& drm_uuid,
716  const std::string& pssh) {
717  UpdateContentProtectionPsshHelper(drm_uuid, pssh,
718  &content_protection_elements_);
719 }
720 
721 void AdaptationSet::AddRole(Role role) {
722  roles_.insert(role);
723 }
724 
725 // Creates a copy of <AdaptationSet> xml element, iterate thru all the
726 // <Representation> (child) elements and add them to the copy.
727 // Set all the attributes first and then add the children elements so that flags
728 // can be passed to Representation to avoid setting redundant attributes. For
729 // example, if AdaptationSet@width is set, then Representation@width is
730 // redundant and should not be set.
731 xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
732  AdaptationSetXmlNode adaptation_set;
733 
734  bool suppress_representation_width = false;
735  bool suppress_representation_height = false;
736  bool suppress_representation_frame_rate = false;
737 
738  adaptation_set.SetId(id_);
739  adaptation_set.SetStringAttribute("contentType", content_type_);
740  if (!lang_.empty() && lang_ != "und") {
741  adaptation_set.SetStringAttribute("lang", LanguageToShortestForm(lang_));
742  }
743 
744  // Note that std::{set,map} are ordered, so the last element is the max value.
745  if (video_widths_.size() == 1) {
746  suppress_representation_width = true;
747  adaptation_set.SetIntegerAttribute("width", *video_widths_.begin());
748  } else if (video_widths_.size() > 1) {
749  adaptation_set.SetIntegerAttribute("maxWidth", *video_widths_.rbegin());
750  }
751  if (video_heights_.size() == 1) {
752  suppress_representation_height = true;
753  adaptation_set.SetIntegerAttribute("height", *video_heights_.begin());
754  } else if (video_heights_.size() > 1) {
755  adaptation_set.SetIntegerAttribute("maxHeight", *video_heights_.rbegin());
756  }
757 
758  if (video_frame_rates_.size() == 1) {
759  suppress_representation_frame_rate = true;
760  adaptation_set.SetStringAttribute("frameRate",
761  video_frame_rates_.begin()->second);
762  } else if (video_frame_rates_.size() > 1) {
763  adaptation_set.SetStringAttribute("maxFrameRate",
764  video_frame_rates_.rbegin()->second);
765  }
766 
767  // Note: must be checked before checking segments_aligned_ (below). So that
768  // segments_aligned_ is set before checking below.
769  if (mpd_options_.dash_profile == DashProfile::kOnDemand) {
770  CheckVodSegmentAlignment();
771  }
772 
773  if (segments_aligned_ == kSegmentAlignmentTrue) {
774  adaptation_set.SetStringAttribute(
775  mpd_options_.dash_profile == DashProfile::kOnDemand
776  ? "subsegmentAlignment"
777  : "segmentAlignment",
778  "true");
779  }
780 
781  if (picture_aspect_ratio_.size() == 1)
782  adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin());
783 
784  if (!adaptation_set.AddContentProtectionElements(
785  content_protection_elements_)) {
786  return xml::scoped_xml_ptr<xmlNode>();
787  }
788 
789  if (!trick_play_reference_ids_.empty()) {
790  std::string id_string;
791  for (uint32_t id : trick_play_reference_ids_) {
792  id_string += std::to_string(id) + ",";
793  }
794  DCHECK(!id_string.empty());
795  id_string.resize(id_string.size() - 1);
796  adaptation_set.AddEssentialProperty(
797  "http://dashif.org/guidelines/trickmode", id_string);
798  }
799 
800  std::string switching_ids;
801  for (uint32_t id : adaptation_set_switching_ids_) {
802  if (!switching_ids.empty())
803  switching_ids += ',';
804  switching_ids += base::UintToString(id);
805  }
806  if (!switching_ids.empty()) {
807  adaptation_set.AddSupplementalProperty(
808  "urn:mpeg:dash:adaptation-set-switching:2016", switching_ids);
809  }
810 
811  for (AdaptationSet::Role role : roles_)
812  adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011", RoleToText(role));
813 
814  for (const std::unique_ptr<Representation>& representation :
815  representations_) {
816  if (suppress_representation_width)
817  representation->SuppressOnce(Representation::kSuppressWidth);
818  if (suppress_representation_height)
819  representation->SuppressOnce(Representation::kSuppressHeight);
820  if (suppress_representation_frame_rate)
821  representation->SuppressOnce(Representation::kSuppressFrameRate);
822  xml::scoped_xml_ptr<xmlNode> child(representation->GetXml());
823  if (!child || !adaptation_set.AddChild(std::move(child)))
824  return xml::scoped_xml_ptr<xmlNode>();
825  }
826 
827  return adaptation_set.PassScopedPtr();
828 }
829 
830 void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) {
831  segments_aligned_ =
832  segment_alignment ? kSegmentAlignmentTrue : kSegmentAlignmentFalse;
833  force_set_segment_alignment_ = true;
834 }
835 
836 void AdaptationSet::AddAdaptationSetSwitching(uint32_t adaptation_set_id) {
837  adaptation_set_switching_ids_.push_back(adaptation_set_id);
838 }
839 
840 // Check segmentAlignment for Live here. Storing all start_time and duration
841 // will out-of-memory because there's no way of knowing when it will end.
842 // VOD subsegmentAlignment check is *not* done here because it is possible
843 // that some Representations might not have been added yet (e.g. a thread is
844 // assigned per muxer so one might run faster than others).
845 // To be clear, for Live, all Representations should be added before a
846 // segment is added.
847 void AdaptationSet::OnNewSegmentForRepresentation(uint32_t representation_id,
848  uint64_t start_time,
849  uint64_t duration) {
850  if (mpd_options_.dash_profile == DashProfile::kLive) {
851  CheckLiveSegmentAlignment(representation_id, start_time, duration);
852  } else {
853  representation_segment_start_times_[representation_id].push_back(
854  start_time);
855  }
856 }
857 
859  uint32_t representation_id,
860  uint32_t frame_duration,
861  uint32_t timescale) {
862  RecordFrameRate(frame_duration, timescale);
863 }
864 
866  trick_play_reference_ids_.insert(id);
867 }
868 
869 bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) {
870  DCHECK(timestamp_seconds);
871 
872  double earliest_timestamp(-1);
873  for (const std::unique_ptr<Representation>& representation :
874  representations_) {
875  double timestamp;
876  if (representation->GetEarliestTimestamp(&timestamp) &&
877  ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
878  earliest_timestamp = timestamp;
879  }
880  }
881  if (earliest_timestamp < 0)
882  return false;
883 
884  *timestamp_seconds = earliest_timestamp;
885  return true;
886 }
887 
888 // This implementation assumes that each representations' segments' are
889 // contiguous.
890 // Also assumes that all Representations are added before this is called.
891 // This checks whether the first elements of the lists in
892 // representation_segment_start_times_ are aligned.
893 // For example, suppose this method was just called with args rep_id=2
894 // start_time=1.
895 // 1 -> [1, 100, 200]
896 // 2 -> [1]
897 // The timestamps of the first elements match, so this flags
898 // segments_aligned_=true.
899 // Also since the first segment start times match, the first element of all the
900 // lists are removed, so the map of lists becomes:
901 // 1 -> [100, 200]
902 // 2 -> []
903 // Note that there could be false positives.
904 // e.g. just got rep_id=3 start_time=1 duration=300, and the duration of the
905 // whole AdaptationSet is 300.
906 // 1 -> [1, 100, 200]
907 // 2 -> [1, 90, 100]
908 // 3 -> [1]
909 // They are not aligned but this will be marked as aligned.
910 // But since this is unlikely to happen in the packager (and to save
911 // computation), this isn't handled at the moment.
912 void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id,
913  uint64_t start_time,
914  uint64_t /* duration */) {
915  if (segments_aligned_ == kSegmentAlignmentFalse ||
916  force_set_segment_alignment_) {
917  return;
918  }
919 
920  std::list<uint64_t>& representation_start_times =
921  representation_segment_start_times_[representation_id];
922  representation_start_times.push_back(start_time);
923  // There's no way to detemine whether the segments are aligned if some
924  // representations do not have any segments.
925  if (representation_segment_start_times_.size() != representations_.size())
926  return;
927 
928  DCHECK(!representation_start_times.empty());
929  const uint64_t expected_start_time = representation_start_times.front();
930  for (RepresentationTimeline::const_iterator it =
931  representation_segment_start_times_.begin();
932  it != representation_segment_start_times_.end(); ++it) {
933  // If there are no entries in a list, then there is no way for the
934  // segment alignment status to change.
935  // Note that it can be empty because entries get deleted below.
936  if (it->second.empty())
937  return;
938 
939  if (expected_start_time != it->second.front()) {
940  // Flag as false and clear the start times data, no need to keep it
941  // around.
942  segments_aligned_ = kSegmentAlignmentFalse;
943  representation_segment_start_times_.clear();
944  return;
945  }
946  }
947  segments_aligned_ = kSegmentAlignmentTrue;
948 
949  for (RepresentationTimeline::iterator it =
950  representation_segment_start_times_.begin();
951  it != representation_segment_start_times_.end(); ++it) {
952  it->second.pop_front();
953  }
954 }
955 
956 // Make sure all segements start times match for all Representations.
957 // This assumes that the segments are contiguous.
958 void AdaptationSet::CheckVodSegmentAlignment() {
959  if (segments_aligned_ == kSegmentAlignmentFalse ||
960  force_set_segment_alignment_) {
961  return;
962  }
963  if (representation_segment_start_times_.empty())
964  return;
965  if (representation_segment_start_times_.size() == 1) {
966  segments_aligned_ = kSegmentAlignmentTrue;
967  return;
968  }
969 
970  // This is not the most efficient implementation to compare the values
971  // because expected_time_line is compared against all other time lines, but
972  // probably the most readable.
973  const std::list<uint64_t>& expected_time_line =
974  representation_segment_start_times_.begin()->second;
975 
976  bool all_segment_time_line_same_length = true;
977  // Note that the first entry is skipped because it is expected_time_line.
978  RepresentationTimeline::const_iterator it =
979  representation_segment_start_times_.begin();
980  for (++it; it != representation_segment_start_times_.end(); ++it) {
981  const std::list<uint64_t>& other_time_line = it->second;
982  if (expected_time_line.size() != other_time_line.size()) {
983  all_segment_time_line_same_length = false;
984  }
985 
986  const std::list<uint64_t>* longer_list = &other_time_line;
987  const std::list<uint64_t>* shorter_list = &expected_time_line;
988  if (expected_time_line.size() > other_time_line.size()) {
989  shorter_list = &other_time_line;
990  longer_list = &expected_time_line;
991  }
992 
993  if (!std::equal(shorter_list->begin(), shorter_list->end(),
994  longer_list->begin())) {
995  // Some segments are definitely unaligned.
996  segments_aligned_ = kSegmentAlignmentFalse;
997  representation_segment_start_times_.clear();
998  return;
999  }
1000  }
1001 
1002  // TODO(rkuroiwa): The right way to do this is to also check the durations.
1003  // For example:
1004  // (a) 3 4 5
1005  // (b) 3 4 5 6
1006  // could be true or false depending on the length of the third segment of (a).
1007  // i.e. if length of the third segment is 2, then this is not aligned.
1008  if (!all_segment_time_line_same_length) {
1009  segments_aligned_ = kSegmentAlignmentUnknown;
1010  return;
1011  }
1012 
1013  segments_aligned_ = kSegmentAlignmentTrue;
1014 }
1015 
1016 // Since all AdaptationSet cares about is the maxFrameRate, representation_id
1017 // is not passed to this method.
1018 void AdaptationSet::RecordFrameRate(uint32_t frame_duration,
1019  uint32_t timescale) {
1020  if (frame_duration == 0) {
1021  LOG(ERROR) << "Frame duration is 0 and cannot be set.";
1022  return;
1023  }
1024  video_frame_rates_[static_cast<double>(timescale) / frame_duration] =
1025  base::IntToString(timescale) + "/" + base::IntToString(frame_duration);
1026 }
1027 
1029  const MediaInfo& media_info,
1030  const MpdOptions& mpd_options,
1031  uint32_t id,
1032  std::unique_ptr<RepresentationStateChangeListener> state_change_listener)
1033  : media_info_(media_info),
1034  id_(id),
1035  bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks),
1036  mpd_options_(mpd_options),
1037  start_number_(1),
1038  state_change_listener_(std::move(state_change_listener)),
1039  output_suppression_flags_(0) {}
1040 
1041 Representation::~Representation() {}
1042 
1044  if (!AtLeastOneTrue(media_info_.has_video_info(),
1045  media_info_.has_audio_info(),
1046  media_info_.has_text_info())) {
1047  // This is an error. Segment information can be in AdaptationSet, Period, or
1048  // MPD but the interface does not provide a way to set them.
1049  // See 5.3.9.1 ISO 23009-1:2012 for segment info.
1050  LOG(ERROR) << "Representation needs one of video, audio, or text.";
1051  return false;
1052  }
1053 
1054  if (MoreThanOneTrue(media_info_.has_video_info(),
1055  media_info_.has_audio_info(),
1056  media_info_.has_text_info())) {
1057  LOG(ERROR) << "Only one of VideoInfo, AudioInfo, or TextInfo can be set.";
1058  return false;
1059  }
1060 
1061  if (media_info_.container_type() == MediaInfo::CONTAINER_UNKNOWN) {
1062  LOG(ERROR) << "'container_type' in MediaInfo cannot be CONTAINER_UNKNOWN.";
1063  return false;
1064  }
1065 
1066  if (media_info_.has_video_info()) {
1067  mime_type_ = GetVideoMimeType();
1068  if (!HasRequiredVideoFields(media_info_.video_info())) {
1069  LOG(ERROR) << "Missing required fields to create a video Representation.";
1070  return false;
1071  }
1072  } else if (media_info_.has_audio_info()) {
1073  mime_type_ = GetAudioMimeType();
1074  } else if (media_info_.has_text_info()) {
1075  mime_type_ = GetTextMimeType();
1076  }
1077 
1078  if (mime_type_.empty())
1079  return false;
1080 
1081  codecs_ = GetCodecs(media_info_);
1082  return true;
1083 }
1084 
1086  const ContentProtectionElement& content_protection_element) {
1087  content_protection_elements_.push_back(content_protection_element);
1088  RemoveDuplicateAttributes(&content_protection_elements_.back());
1089 }
1090 
1091 void Representation::UpdateContentProtectionPssh(const std::string& drm_uuid,
1092  const std::string& pssh) {
1093  UpdateContentProtectionPsshHelper(drm_uuid, pssh,
1094  &content_protection_elements_);
1095 }
1096 
1097 void Representation::AddNewSegment(uint64_t start_time,
1098  uint64_t duration,
1099  uint64_t size) {
1100  if (start_time == 0 && duration == 0) {
1101  LOG(WARNING) << "Got segment with start_time and duration == 0. Ignoring.";
1102  return;
1103  }
1104 
1105  if (state_change_listener_)
1106  state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
1107  if (IsContiguous(start_time, duration, size)) {
1108  ++segment_infos_.back().repeat;
1109  } else {
1110  SegmentInfo s = {start_time, duration, /* Not repeat. */ 0};
1111  segment_infos_.push_back(s);
1112  }
1113 
1114  bandwidth_estimator_.AddBlock(
1115  size, static_cast<double>(duration) / media_info_.reference_time_scale());
1116 
1117  SlideWindow();
1118  DCHECK_GE(segment_infos_.size(), 1u);
1119 }
1120 
1121 void Representation::SetSampleDuration(uint32_t sample_duration) {
1122  if (media_info_.has_video_info()) {
1123  media_info_.mutable_video_info()->set_frame_duration(sample_duration);
1124  if (state_change_listener_) {
1125  state_change_listener_->OnSetFrameRateForRepresentation(
1126  sample_duration, media_info_.video_info().time_scale());
1127  }
1128  }
1129 }
1130 
1131 // Uses info in |media_info_| and |content_protection_elements_| to create a
1132 // "Representation" node.
1133 // MPD schema has strict ordering. The following must be done in order.
1134 // AddVideoInfo() (possibly adds FramePacking elements), AddAudioInfo() (Adds
1135 // AudioChannelConfig elements), AddContentProtectionElements*(), and
1136 // AddVODOnlyInfo() (Adds segment info).
1137 xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
1138  if (!HasRequiredMediaInfoFields()) {
1139  LOG(ERROR) << "MediaInfo missing required fields.";
1140  return xml::scoped_xml_ptr<xmlNode>();
1141  }
1142 
1143  const uint64_t bandwidth = media_info_.has_bandwidth()
1144  ? media_info_.bandwidth()
1145  : bandwidth_estimator_.Estimate();
1146 
1147  DCHECK(!(HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)));
1148 
1149  RepresentationXmlNode representation;
1150  // Mandatory fields for Representation.
1151  representation.SetId(id_);
1152  representation.SetIntegerAttribute("bandwidth", bandwidth);
1153  if (!codecs_.empty())
1154  representation.SetStringAttribute("codecs", codecs_);
1155  representation.SetStringAttribute("mimeType", mime_type_);
1156 
1157  const bool has_video_info = media_info_.has_video_info();
1158  const bool has_audio_info = media_info_.has_audio_info();
1159 
1160  if (has_video_info &&
1161  !representation.AddVideoInfo(
1162  media_info_.video_info(),
1163  !(output_suppression_flags_ & kSuppressWidth),
1164  !(output_suppression_flags_ & kSuppressHeight),
1165  !(output_suppression_flags_ & kSuppressFrameRate))) {
1166  LOG(ERROR) << "Failed to add video info to Representation XML.";
1167  return xml::scoped_xml_ptr<xmlNode>();
1168  }
1169 
1170  if (has_audio_info &&
1171  !representation.AddAudioInfo(media_info_.audio_info())) {
1172  LOG(ERROR) << "Failed to add audio info to Representation XML.";
1173  return xml::scoped_xml_ptr<xmlNode>();
1174  }
1175 
1176  if (!representation.AddContentProtectionElements(
1177  content_protection_elements_)) {
1178  return xml::scoped_xml_ptr<xmlNode>();
1179  }
1180 
1181  // Set media duration for static mpd.
1182  if (mpd_options_.mpd_type == MpdType::kStatic &&
1183  media_info_.has_media_duration_seconds()) {
1184  // Adding 'duration' attribute, so that this information can be used when
1185  // generating one MPD file. This should be removed from the final MPD.
1186  representation.SetFloatingPointAttribute(
1187  "duration", media_info_.media_duration_seconds());
1188  }
1189 
1190  if (HasVODOnlyFields(media_info_) &&
1191  !representation.AddVODOnlyInfo(media_info_)) {
1192  LOG(ERROR) << "Failed to add VOD segment info.";
1193  return xml::scoped_xml_ptr<xmlNode>();
1194  }
1195 
1196  if (HasLiveOnlyFields(media_info_) &&
1197  !representation.AddLiveOnlyInfo(media_info_, segment_infos_,
1198  start_number_)) {
1199  LOG(ERROR) << "Failed to add Live info.";
1200  return xml::scoped_xml_ptr<xmlNode>();
1201  }
1202  // TODO(rkuroiwa): It is likely that all representations have the exact same
1203  // SegmentTemplate. Optimize and propagate the tag up to AdaptationSet level.
1204 
1205  output_suppression_flags_ = 0;
1206  return representation.PassScopedPtr();
1207 }
1208 
1209 void Representation::SuppressOnce(SuppressFlag flag) {
1210  output_suppression_flags_ |= flag;
1211 }
1212 
1213 bool Representation::HasRequiredMediaInfoFields() {
1214  if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) {
1215  LOG(ERROR) << "MediaInfo cannot have both VOD and Live fields.";
1216  return false;
1217  }
1218 
1219  if (!media_info_.has_container_type()) {
1220  LOG(ERROR) << "MediaInfo missing required field: container_type.";
1221  return false;
1222  }
1223 
1224  if (HasVODOnlyFields(media_info_) && !media_info_.has_bandwidth()) {
1225  LOG(ERROR) << "Missing 'bandwidth' field. MediaInfo requires bandwidth for "
1226  "static profile for generating a valid MPD.";
1227  return false;
1228  }
1229 
1230  VLOG_IF(3, HasLiveOnlyFields(media_info_) && !media_info_.has_bandwidth())
1231  << "MediaInfo missing field 'bandwidth'. Using estimated from "
1232  "segment size.";
1233 
1234  return true;
1235 }
1236 
1237 bool Representation::IsContiguous(uint64_t start_time,
1238  uint64_t duration,
1239  uint64_t size) const {
1240  if (segment_infos_.empty())
1241  return false;
1242 
1243  // Contiguous segment.
1244  const SegmentInfo& previous = segment_infos_.back();
1245  const uint64_t previous_segment_end_time =
1246  previous.start_time + previous.duration * (previous.repeat + 1);
1247  if (previous_segment_end_time == start_time &&
1248  segment_infos_.back().duration == duration) {
1249  return true;
1250  }
1251 
1252  // No out of order segments.
1253  const uint64_t previous_segment_start_time =
1254  previous.start_time + previous.duration * previous.repeat;
1255  if (previous_segment_start_time >= start_time) {
1256  LOG(ERROR) << "Segments should not be out of order segment. Adding segment "
1257  "with start_time == "
1258  << start_time << " but the previous segment starts at "
1259  << previous_segment_start_time << ".";
1260  return false;
1261  }
1262 
1263  // A gap since previous.
1264  const uint64_t kRoundingErrorGrace = 5;
1265  if (previous_segment_end_time + kRoundingErrorGrace < start_time) {
1266  LOG(WARNING) << "Found a gap of size "
1267  << (start_time - previous_segment_end_time)
1268  << " > kRoundingErrorGrace (" << kRoundingErrorGrace
1269  << "). The new segment starts at " << start_time
1270  << " but the previous segment ends at "
1271  << previous_segment_end_time << ".";
1272  return false;
1273  }
1274 
1275  // No overlapping segments.
1276  if (start_time < previous_segment_end_time - kRoundingErrorGrace) {
1277  LOG(WARNING)
1278  << "Segments should not be overlapping. The new segment starts at "
1279  << start_time << " but the previous segment ends at "
1280  << previous_segment_end_time << ".";
1281  return false;
1282  }
1283 
1284  // Within rounding error grace but technically not contiguous in terms of MPD.
1285  return false;
1286 }
1287 
1288 void Representation::SlideWindow() {
1289  DCHECK(!segment_infos_.empty());
1290  if (mpd_options_.time_shift_buffer_depth <= 0.0 ||
1291  mpd_options_.mpd_type == MpdType::kStatic)
1292  return;
1293 
1294  const uint32_t time_scale = GetTimeScale(media_info_);
1295  DCHECK_GT(time_scale, 0u);
1296 
1297  uint64_t time_shift_buffer_depth =
1298  static_cast<uint64_t>(mpd_options_.time_shift_buffer_depth * time_scale);
1299 
1300  // The start time of the latest segment is considered the current_play_time,
1301  // and this should guarantee that the latest segment will stay in the list.
1302  const uint64_t current_play_time = LatestSegmentStartTime(segment_infos_);
1303  if (current_play_time <= time_shift_buffer_depth)
1304  return;
1305 
1306  const uint64_t timeshift_limit = current_play_time - time_shift_buffer_depth;
1307 
1308  // First remove all the SegmentInfos that are completely out of range, by
1309  // looking at the very last segment's end time.
1310  std::list<SegmentInfo>::iterator first = segment_infos_.begin();
1311  std::list<SegmentInfo>::iterator last = first;
1312  size_t num_segments_removed = 0;
1313  for (; last != segment_infos_.end(); ++last) {
1314  const uint64_t last_segment_end_time = LastSegmentEndTime(*last);
1315  if (timeshift_limit < last_segment_end_time)
1316  break;
1317  num_segments_removed += last->repeat + 1;
1318  }
1319  segment_infos_.erase(first, last);
1320  start_number_ += num_segments_removed;
1321 
1322  // Now some segment in the first SegmentInfo should be left in the list.
1323  SegmentInfo* first_segment_info = &segment_infos_.front();
1324  DCHECK_LE(timeshift_limit, LastSegmentEndTime(*first_segment_info));
1325 
1326  // Identify which segments should still be in the SegmentInfo.
1327  const int repeat_index =
1328  SearchTimedOutRepeatIndex(timeshift_limit, *first_segment_info);
1329  CHECK_GE(repeat_index, 0);
1330  if (repeat_index == 0)
1331  return;
1332 
1333  first_segment_info->start_time = first_segment_info->start_time +
1334  first_segment_info->duration * repeat_index;
1335 
1336  first_segment_info->repeat = first_segment_info->repeat - repeat_index;
1337  start_number_ += repeat_index;
1338 }
1339 
1340 std::string Representation::GetVideoMimeType() const {
1341  return GetMimeType("video", media_info_.container_type());
1342 }
1343 
1344 std::string Representation::GetAudioMimeType() const {
1345  return GetMimeType("audio", media_info_.container_type());
1346 }
1347 
1348 std::string Representation::GetTextMimeType() const {
1349  CHECK(media_info_.has_text_info());
1350  if (media_info_.text_info().format() == "ttml") {
1351  switch (media_info_.container_type()) {
1352  case MediaInfo::CONTAINER_TEXT:
1353  return "application/ttml+xml";
1354  case MediaInfo::CONTAINER_MP4:
1355  return "application/mp4";
1356  default:
1357  LOG(ERROR) << "Failed to determine MIME type for TTML container: "
1358  << media_info_.container_type();
1359  return "";
1360  }
1361  }
1362  if (media_info_.text_info().format() == "vtt") {
1363  if (media_info_.container_type() == MediaInfo::CONTAINER_TEXT) {
1364  return "text/vtt";
1365  } else if (media_info_.container_type() == MediaInfo::CONTAINER_MP4) {
1366  return "application/mp4";
1367  }
1368  LOG(ERROR) << "Failed to determine MIME type for VTT container: "
1369  << media_info_.container_type();
1370  return "";
1371  }
1372 
1373  LOG(ERROR) << "Cannot determine MIME type for format: "
1374  << media_info_.text_info().format()
1375  << " container: " << media_info_.container_type();
1376  return "";
1377 }
1378 
1379 bool Representation::GetEarliestTimestamp(double* timestamp_seconds) {
1380  DCHECK(timestamp_seconds);
1381 
1382  if (segment_infos_.empty())
1383  return false;
1384 
1385  *timestamp_seconds = static_cast<double>(segment_infos_.begin()->start_time) /
1386  GetTimeScale(media_info_);
1387  return true;
1388 }
1389 
1390 } // namespace shaka
void OnSetFrameRateForRepresentation(uint32_t representation_id, uint32_t frame_duration, uint32_t timescale)
Definition: mpd_builder.cc:858
virtual void AddNewSegment(uint64_t start_time, uint64_t duration, uint64_t size)
Representation(const MediaInfo &media_info, const MpdOptions &mpd_options, uint32_t representation_id, std::unique_ptr< RepresentationStateChangeListener > state_change_listener)
virtual void SetSampleDuration(uint32_t sample_duration)
virtual Representation * AddRepresentation(const MediaInfo &media_info)
Definition: mpd_builder.cc:663
std::string LanguageToShortestForm(const std::string &language)
virtual void AddContentProtectionElement(const ContentProtectionElement &element)
Definition: mpd_builder.cc:709
virtual void AddTrickPlayReferenceId(uint32_t id)
Definition: mpd_builder.cc:865
MpdBuilder(const MpdOptions &mpd_options)
Definition: mpd_builder.cc:373
virtual void AddRole(Role role)
Definition: mpd_builder.cc:721
void AddBaseUrl(const std::string &base_url)
Definition: mpd_builder.cc:378
virtual void UpdateContentProtectionPssh(const std::string &drm_uuid, const std::string &pssh)
Definition: mpd_builder.cc:715
AdaptationSet(uint32_t adaptation_set_id, const std::string &lang, const MpdOptions &mpd_options, base::AtomicSequenceNumber *representation_counter)
Definition: mpd_builder.cc:648
xml::scoped_xml_ptr< xmlNode > GetXml()
virtual bool ToString(std::string *output)
Definition: mpd_builder.cc:396
void AddAdaptationSetSwitching(uint32_t adaptation_set_id)
Definition: mpd_builder.cc:836
virtual void ForceSetSegmentAlignment(bool segment_alignment)
Definition: mpd_builder.cc:830
static void MakePathsRelativeToMpd(const std::string &mpd_path, MediaInfo *media_info)
Definition: mpd_builder.cc:620
xml::scoped_xml_ptr< xmlNode > GetXml()
Definition: mpd_builder.cc:731
virtual void AddContentProtectionElement(const ContentProtectionElement &element)
virtual AdaptationSet * AddAdaptationSet(const std::string &lang)
Definition: mpd_builder.cc:382
Defines Mpd Options.
Definition: mpd_options.h:23
void OnNewSegmentForRepresentation(uint32_t representation_id, uint64_t start_time, uint64_t duration)
Definition: mpd_builder.cc:847
virtual void UpdateContentProtectionPssh(const std::string &drm_uuid, const std::string &pssh)
void SuppressOnce(SuppressFlag flag)