7 #include "packager/mpd/base/mpd_builder.h"
9 #include <libxml/tree.h>
10 #include <libxml/xmlstring.h>
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/media/base/language_utils.h"
28 #include "packager/media/file/file.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"
38 using xml::RepresentationXmlNode;
39 using xml::AdaptationSetXmlNode;
43 AdaptationSet::Role MediaInfoTextTypeToRole(
44 MediaInfo::TextInfo::TextType 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;
54 NOTREACHED() <<
"Unknown MediaInfo TextType: " << type
55 <<
" assuming subtitle.";
56 return AdaptationSet::kRoleSubtitle;
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:
67 return prefix +
"/MP2T";
68 case MediaInfo::CONTAINER_WEBM:
69 return prefix +
"/webm";
75 LOG(ERROR) <<
"Unrecognized container type: " << container_type;
79 void AddMpdNameSpaceInfo(XmlNode* mpd) {
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";
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);
97 bool IsPeriodNode(xmlNodePtr node) {
100 return xmlStrcmp(node->name, reinterpret_cast<const xmlChar*>(
"Period")) ==
109 xmlNodePtr FindPeriodNode(XmlNode* xml_node) {
110 for (xmlNodePtr node = xml_node->GetRawPtr()->xmlChildrenNode; node != NULL;
112 if (IsPeriodNode(node))
119 bool Positive(
double d) {
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);
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);
139 void SetIfPositive(
const char* attr_name,
double value, XmlNode* mpd) {
140 if (Positive(value)) {
141 mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
145 uint32_t GetTimeScale(
const MediaInfo& media_info) {
146 if (media_info.has_reference_time_scale()) {
147 return media_info.reference_time_scale();
150 if (media_info.has_video_info()) {
151 return media_info.video_info().time_scale();
154 if (media_info.has_audio_info()) {
155 return media_info.audio_info().time_scale();
158 LOG(WARNING) <<
"No timescale specified, using 1 as timescale.";
162 uint64_t LastSegmentStartTime(
const SegmentInfo& segment_info) {
163 return segment_info.start_time + segment_info.duration * segment_info.repeat;
167 uint64_t LastSegmentEndTime(
const SegmentInfo& segment_info) {
168 return segment_info.start_time +
169 segment_info.duration * (segment_info.repeat + 1);
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);
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)
186 return (timeshift_limit - segment_info.start_time) / segment_info.duration;
192 bool WriteXmlCharArrayToOutput(xmlChar* doc,
194 std::string* output) {
197 output->assign(doc, doc + doc_size);
201 bool WriteXmlCharArrayToOutput(xmlChar* doc,
203 media::File* output) {
206 if (output->Write(doc, doc_size) < doc_size)
209 return output->Flush();
212 std::string MakePathRelative(
const std::string& path,
213 const std::string& mpd_dir) {
214 return (path.find(mpd_dir) == 0) ? path.substr(mpd_dir.size()) : path;
220 bool HasRequiredVideoFields(
const MediaInfo_VideoInfo& video_info) {
221 if (!video_info.has_height() || !video_info.has_width()) {
223 <<
"Width and height are required fields for generating a valid MPD.";
228 LOG_IF(WARNING, !video_info.has_time_scale())
229 <<
"Video info does not contain timescale required for "
230 "calculating framerate. @frameRate is required for DASH IOP.";
231 LOG_IF(WARNING, !video_info.has_frame_duration())
232 <<
"Video info does not contain frame duration required "
233 "for calculating framerate. @frameRate is required for DASH IOP.";
234 LOG_IF(WARNING, !video_info.has_pixel_width())
235 <<
"Video info does not contain pixel_width to calculate the sample "
236 "aspect ratio required for DASH IOP.";
237 LOG_IF(WARNING, !video_info.has_pixel_height())
238 <<
"Video info does not contain pixel_height to calculate the sample "
239 "aspect ratio required for DASH IOP.";
250 std::string GetPictureAspectRatio(uint32_t width,
252 uint32_t pixel_width,
253 uint32_t pixel_height) {
254 const uint32_t scaled_width = pixel_width * width;
255 const uint32_t scaled_height = pixel_height * height;
256 const double par =
static_cast<double>(scaled_width) / scaled_height;
260 const uint32_t kLargestPossibleParY = 19;
262 uint32_t par_num = 0;
263 uint32_t par_den = 0;
264 double min_error = 1.0;
265 for (uint32_t den = 1; den <= kLargestPossibleParY; ++den) {
266 uint32_t num = par * den + 0.5;
267 double error = fabs(par - static_cast<double>(num) / den);
268 if (error < min_error) {
272 if (error == 0)
break;
275 VLOG(2) <<
"width*pix_width : height*pixel_height (" << scaled_width <<
":"
276 << scaled_height <<
") reduced to " << par_num <<
":" << par_den
277 <<
" with error " << min_error <<
".";
279 return base::IntToString(par_num) +
":" + base::IntToString(par_den);
284 void AddPictureAspectRatio(
285 const MediaInfo::VideoInfo& video_info,
286 std::set<std::string>* picture_aspect_ratio) {
289 if (picture_aspect_ratio->size() > 1)
292 if (video_info.width() == 0 || video_info.height() == 0 ||
293 video_info.pixel_width() == 0 || video_info.pixel_height() == 0) {
298 picture_aspect_ratio->insert(
"bogus");
299 picture_aspect_ratio->insert(
"entries");
303 const std::string par = GetPictureAspectRatio(
304 video_info.width(), video_info.height(),
305 video_info.pixel_width(), video_info.pixel_height());
306 DVLOG(1) <<
"Setting par as: " << par
307 <<
" for video with width: " << video_info.width()
308 <<
" height: " << video_info.height()
309 <<
" pixel_width: " << video_info.pixel_width() <<
" pixel_height; "
310 << video_info.pixel_height();
311 picture_aspect_ratio->insert(par);
314 std::string RoleToText(AdaptationSet::Role role) {
318 case AdaptationSet::kRoleCaption:
320 case AdaptationSet::kRoleSubtitle:
322 case AdaptationSet::kRoleMain:
324 case AdaptationSet::kRoleAlternate:
326 case AdaptationSet::kRoleSupplementary:
327 return "supplementary";
328 case AdaptationSet::kRoleCommentary:
330 case AdaptationSet::kRoleDub:
341 class LibXmlInitializer {
343 LibXmlInitializer() : initialized_(false) {
344 base::AutoLock lock(lock_);
351 ~LibXmlInitializer() {
352 base::AutoLock lock(lock_);
355 initialized_ =
false;
363 DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
366 class RepresentationStateChangeListenerImpl
367 :
public RepresentationStateChangeListener {
370 RepresentationStateChangeListenerImpl(uint32_t representation_id,
371 AdaptationSet* adaptation_set)
372 : representation_id_(representation_id), adaptation_set_(adaptation_set) {
373 DCHECK(adaptation_set_);
375 ~RepresentationStateChangeListenerImpl()
override {}
378 void OnNewSegmentForRepresentation(uint64_t start_time,
379 uint64_t duration)
override {
380 adaptation_set_->OnNewSegmentForRepresentation(representation_id_,
381 start_time, duration);
384 void OnSetFrameRateForRepresentation(uint32_t frame_duration,
385 uint32_t timescale)
override {
386 adaptation_set_->OnSetFrameRateForRepresentation(representation_id_,
387 frame_duration, timescale);
391 const uint32_t representation_id_;
392 AdaptationSet*
const adaptation_set_;
394 DISALLOW_COPY_AND_ASSIGN(RepresentationStateChangeListenerImpl);
400 : mpd_options_(mpd_options), clock_(new base::DefaultClock()) {}
402 MpdBuilder::~MpdBuilder() {}
405 base_urls_.push_back(base_url);
409 std::unique_ptr<AdaptationSet> adaptation_set(
410 new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_,
411 &representation_counter_));
412 DCHECK(adaptation_set);
414 if (!lang.empty() && lang == mpd_options_.default_language) {
415 adaptation_set->AddRole(AdaptationSet::kRoleMain);
418 adaptation_sets_.push_back(std::move(adaptation_set));
419 return adaptation_sets_.back().get();
424 return WriteMpdToOutput(output_file);
429 return WriteMpdToOutput(output);
431 template <
typename OutputType>
432 bool MpdBuilder::WriteMpdToOutput(OutputType* output) {
433 static LibXmlInitializer lib_xml_initializer;
435 xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
439 static const int kNiceFormat = 1;
440 int doc_str_size = 0;
441 xmlChar* doc_str = NULL;
442 xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size,
"UTF-8",
445 bool result = WriteXmlCharArrayToOutput(doc_str, doc_str_size, output);
453 xmlDocPtr MpdBuilder::GenerateMpd() {
455 static const char kXmlVersion[] =
"1.0";
456 xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
460 XmlNode period(
"Period");
466 for (
const std::unique_ptr<AdaptationSet>& adaptation_set :
468 xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml());
469 if (!child.get() || !period.AddChild(std::move(child)))
474 std::list<std::string>::const_iterator base_urls_it = base_urls_.begin();
475 for (; base_urls_it != base_urls_.end(); ++base_urls_it) {
476 XmlNode base_url(
"BaseURL");
477 base_url.SetContent(*base_urls_it);
479 if (!mpd.AddChild(base_url.PassScopedPtr()))
484 if (mpd_options_.mpd_type == MpdType::kDynamic) {
486 period.SetStringAttribute(
"start",
"PT0S");
489 if (!mpd.AddChild(period.PassScopedPtr()))
492 AddMpdNameSpaceInfo(&mpd);
494 static const char kOnDemandProfile[] =
495 "urn:mpeg:dash:profile:isoff-on-demand:2011";
496 static const char kLiveProfile[] =
497 "urn:mpeg:dash:profile:isoff-live:2011";
498 switch (mpd_options_.dash_profile) {
499 case DashProfile::kOnDemand:
500 mpd.SetStringAttribute(
"profiles", kOnDemandProfile);
502 case DashProfile::kLive:
503 mpd.SetStringAttribute(
"profiles", kLiveProfile);
506 NOTREACHED() <<
"Unknown DASH profile: "
507 <<
static_cast<int>(mpd_options_.dash_profile);
511 AddCommonMpdInfo(&mpd);
512 switch (mpd_options_.mpd_type) {
513 case MpdType::kStatic:
514 AddStaticMpdInfo(&mpd);
516 case MpdType::kDynamic:
517 AddDynamicMpdInfo(&mpd);
520 NOTREACHED() <<
"Unknown MPD type: "
521 <<
static_cast<int>(mpd_options_.mpd_type);
526 const std::string version = GetPackagerVersion();
527 if (!version.empty()) {
528 std::string version_string =
529 base::StringPrintf(
"Generated with %s version %s",
530 GetPackagerProjectUrl().c_str(), version.c_str());
531 xml::scoped_xml_ptr<xmlNode> comment(
532 xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str()));
533 xmlDocSetRootElement(doc.get(), comment.get());
534 xmlAddSibling(comment.release(), mpd.Release());
536 xmlDocSetRootElement(doc.get(), mpd.Release());
538 return doc.release();
541 void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
542 if (Positive(mpd_options_.min_buffer_time)) {
543 mpd_node->SetStringAttribute(
544 "minBufferTime", SecondsToXmlDuration(mpd_options_.min_buffer_time));
546 LOG(ERROR) <<
"minBufferTime value not specified.";
551 void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
553 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
555 static const char kStaticMpdType[] =
"static";
556 mpd_node->SetStringAttribute(
"type", kStaticMpdType);
557 mpd_node->SetStringAttribute(
558 "mediaPresentationDuration",
559 SecondsToXmlDuration(GetStaticMpdDuration(mpd_node)));
562 void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
564 DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
566 static const char kDynamicMpdType[] =
"dynamic";
567 mpd_node->SetStringAttribute(
"type", kDynamicMpdType);
570 mpd_node->SetStringAttribute(
"publishTime",
571 XmlDateTimeNowWithOffset(0, clock_.get()));
575 if (availability_start_time_.empty()) {
576 double earliest_presentation_time;
577 if (GetEarliestTimestamp(&earliest_presentation_time)) {
578 availability_start_time_ =
579 XmlDateTimeNowWithOffset(mpd_options_.availability_time_offset -
580 std::ceil(earliest_presentation_time),
583 LOG(ERROR) <<
"Could not determine the earliest segment presentation "
584 "time for availabilityStartTime calculation.";
588 if (!availability_start_time_.empty())
589 mpd_node->SetStringAttribute(
"availabilityStartTime",
590 availability_start_time_);
592 if (Positive(mpd_options_.minimum_update_period)) {
593 mpd_node->SetStringAttribute(
594 "minimumUpdatePeriod",
595 SecondsToXmlDuration(mpd_options_.minimum_update_period));
597 LOG(WARNING) <<
"The profile is dynamic but no minimumUpdatePeriod "
601 SetIfPositive(
"timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth,
603 SetIfPositive(
"suggestedPresentationDelay",
604 mpd_options_.suggested_presentation_delay, mpd_node);
607 float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
609 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
611 xmlNodePtr period_node = FindPeriodNode(mpd_node);
612 DCHECK(period_node) <<
"Period element must be a child of mpd_node.";
613 DCHECK(IsPeriodNode(period_node));
619 float max_duration = 0.0f;
620 for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node);
621 adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
622 for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
624 representation = xmlNextElementSibling(representation)) {
625 float duration = 0.0f;
626 if (GetDurationAttribute(representation, &duration)) {
627 max_duration = max_duration > duration ? max_duration : duration;
631 xmlUnsetProp(representation, BAD_CAST
"duration");
639 bool MpdBuilder::GetEarliestTimestamp(
double* timestamp_seconds) {
640 DCHECK(timestamp_seconds);
642 double earliest_timestamp(-1);
643 for (
const std::unique_ptr<AdaptationSet>& adaptation_set :
646 if (adaptation_set->GetEarliestTimestamp(×tamp) &&
647 ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
648 earliest_timestamp = timestamp;
651 if (earliest_timestamp < 0)
654 *timestamp_seconds = earliest_timestamp;
659 MediaInfo* media_info) {
661 const std::string kFileProtocol(
"file://");
662 std::string mpd_file_path = (mpd_path.find(kFileProtocol) == 0)
663 ? mpd_path.substr(kFileProtocol.size())
666 if (!mpd_file_path.empty()) {
667 std::string mpd_dir(FilePath::FromUTF8Unsafe(mpd_file_path)
668 .DirName().AsEndingWithSeparator().AsUTF8Unsafe());
669 if (!mpd_dir.empty()) {
670 if (media_info->has_media_file_name()) {
671 media_info->set_media_file_name(
672 MakePathRelative(media_info->media_file_name(), mpd_dir));
674 if (media_info->has_init_segment_name()) {
675 media_info->set_init_segment_name(
676 MakePathRelative(media_info->init_segment_name(), mpd_dir));
678 if (media_info->has_segment_template()) {
679 media_info->set_segment_template(
680 MakePathRelative(media_info->segment_template(), mpd_dir));
687 const std::string& lang,
689 base::AtomicSequenceNumber* counter)
690 : representation_counter_(counter),
691 id_(adaptation_set_id),
693 mpd_options_(mpd_options),
694 segments_aligned_(kSegmentAlignmentUnknown),
695 force_set_segment_alignment_(false) {
699 AdaptationSet::~AdaptationSet() {}
702 const uint32_t representation_id = representation_counter_->GetNext();
705 std::unique_ptr<RepresentationStateChangeListener> listener(
706 new RepresentationStateChangeListenerImpl(representation_id,
this));
707 std::unique_ptr<Representation> representation(
new Representation(
708 media_info, mpd_options_, representation_id, std::move(listener)));
710 if (!representation->Init()) {
711 LOG(ERROR) <<
"Failed to initialize Representation.";
717 if (media_info.has_video_info()) {
718 const MediaInfo::VideoInfo& video_info = media_info.video_info();
719 DCHECK(video_info.has_width());
720 DCHECK(video_info.has_height());
721 video_widths_.insert(video_info.width());
722 video_heights_.insert(video_info.height());
724 if (video_info.has_time_scale() && video_info.has_frame_duration())
725 RecordFrameRate(video_info.frame_duration(), video_info.time_scale());
727 AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
730 if (media_info.has_video_info()) {
731 content_type_ =
"video";
732 }
else if (media_info.has_audio_info()) {
733 content_type_ =
"audio";
734 }
else if (media_info.has_text_info()) {
735 content_type_ =
"text";
737 if (media_info.text_info().has_type() &&
738 (media_info.text_info().type() != MediaInfo::TextInfo::UNKNOWN)) {
739 roles_.insert(MediaInfoTextTypeToRole(media_info.text_info().type()));
743 representations_.push_back(std::move(representation));
744 return representations_.back().get();
749 content_protection_elements_.push_back(content_protection_element);
750 RemoveDuplicateAttributes(&content_protection_elements_.back());
754 const std::string& pssh) {
755 UpdateContentProtectionPsshHelper(drm_uuid, pssh,
756 &content_protection_elements_);
770 AdaptationSetXmlNode adaptation_set;
772 bool suppress_representation_width =
false;
773 bool suppress_representation_height =
false;
774 bool suppress_representation_frame_rate =
false;
776 adaptation_set.SetId(id_);
777 adaptation_set.SetStringAttribute(
"contentType", content_type_);
778 if (!lang_.empty() && lang_ !=
"und") {
783 if (video_widths_.size() == 1) {
784 suppress_representation_width =
true;
785 adaptation_set.SetIntegerAttribute(
"width", *video_widths_.begin());
786 }
else if (video_widths_.size() > 1) {
787 adaptation_set.SetIntegerAttribute(
"maxWidth", *video_widths_.rbegin());
789 if (video_heights_.size() == 1) {
790 suppress_representation_height =
true;
791 adaptation_set.SetIntegerAttribute(
"height", *video_heights_.begin());
792 }
else if (video_heights_.size() > 1) {
793 adaptation_set.SetIntegerAttribute(
"maxHeight", *video_heights_.rbegin());
796 if (video_frame_rates_.size() == 1) {
797 suppress_representation_frame_rate =
true;
798 adaptation_set.SetStringAttribute(
"frameRate",
799 video_frame_rates_.begin()->second);
800 }
else if (video_frame_rates_.size() > 1) {
801 adaptation_set.SetStringAttribute(
"maxFrameRate",
802 video_frame_rates_.rbegin()->second);
807 if (mpd_options_.dash_profile == DashProfile::kOnDemand) {
808 CheckVodSegmentAlignment();
811 if (segments_aligned_ == kSegmentAlignmentTrue) {
812 adaptation_set.SetStringAttribute(
813 mpd_options_.dash_profile == DashProfile::kOnDemand
814 ?
"subsegmentAlignment"
815 :
"segmentAlignment",
819 if (picture_aspect_ratio_.size() == 1)
820 adaptation_set.SetStringAttribute(
"par", *picture_aspect_ratio_.begin());
822 if (!adaptation_set.AddContentProtectionElements(
823 content_protection_elements_)) {
824 return xml::scoped_xml_ptr<xmlNode>();
827 if (!trick_play_reference_ids_.empty()) {
828 std::string id_string;
829 for (uint32_t
id : trick_play_reference_ids_) {
830 id_string += std::to_string(
id) +
",";
832 DCHECK(!id_string.empty());
833 id_string.resize(id_string.size() - 1);
834 adaptation_set.AddEssentialProperty(
835 "http://dashif.org/guidelines/trickmode", id_string);
838 std::string switching_ids;
839 for (uint32_t
id : adaptation_set_switching_ids_) {
840 if (!switching_ids.empty())
841 switching_ids +=
',';
842 switching_ids += base::UintToString(
id);
844 if (!switching_ids.empty()) {
845 adaptation_set.AddSupplementalProperty(
846 "urn:mpeg:dash:adaptation-set-switching:2016", switching_ids);
849 for (AdaptationSet::Role role : roles_)
850 adaptation_set.AddRoleElement(
"urn:mpeg:dash:role:2011", RoleToText(role));
852 for (
const std::unique_ptr<Representation>& representation :
854 if (suppress_representation_width)
855 representation->SuppressOnce(Representation::kSuppressWidth);
856 if (suppress_representation_height)
857 representation->SuppressOnce(Representation::kSuppressHeight);
858 if (suppress_representation_frame_rate)
859 representation->SuppressOnce(Representation::kSuppressFrameRate);
860 xml::scoped_xml_ptr<xmlNode> child(representation->GetXml());
861 if (!child || !adaptation_set.AddChild(std::move(child)))
862 return xml::scoped_xml_ptr<xmlNode>();
865 return adaptation_set.PassScopedPtr();
870 segment_alignment ? kSegmentAlignmentTrue : kSegmentAlignmentFalse;
871 force_set_segment_alignment_ =
true;
875 adaptation_set_switching_ids_.push_back(adaptation_set_id);
888 if (mpd_options_.dash_profile == DashProfile::kLive) {
889 CheckLiveSegmentAlignment(representation_id, start_time, duration);
891 representation_segment_start_times_[representation_id].push_back(
897 uint32_t representation_id,
898 uint32_t frame_duration,
899 uint32_t timescale) {
900 RecordFrameRate(frame_duration, timescale);
904 trick_play_reference_ids_.insert(
id);
907 bool AdaptationSet::GetEarliestTimestamp(
double* timestamp_seconds) {
908 DCHECK(timestamp_seconds);
910 double earliest_timestamp(-1);
911 for (
const std::unique_ptr<Representation>& representation :
914 if (representation->GetEarliestTimestamp(×tamp) &&
915 ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
916 earliest_timestamp = timestamp;
919 if (earliest_timestamp < 0)
922 *timestamp_seconds = earliest_timestamp;
950 void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id,
953 if (segments_aligned_ == kSegmentAlignmentFalse ||
954 force_set_segment_alignment_) {
958 std::list<uint64_t>& representation_start_times =
959 representation_segment_start_times_[representation_id];
960 representation_start_times.push_back(start_time);
963 if (representation_segment_start_times_.size() != representations_.size())
966 DCHECK(!representation_start_times.empty());
967 const uint64_t expected_start_time = representation_start_times.front();
968 for (RepresentationTimeline::const_iterator it =
969 representation_segment_start_times_.begin();
970 it != representation_segment_start_times_.end(); ++it) {
974 if (it->second.empty())
977 if (expected_start_time != it->second.front()) {
980 segments_aligned_ = kSegmentAlignmentFalse;
981 representation_segment_start_times_.clear();
985 segments_aligned_ = kSegmentAlignmentTrue;
987 for (RepresentationTimeline::iterator it =
988 representation_segment_start_times_.begin();
989 it != representation_segment_start_times_.end(); ++it) {
990 it->second.pop_front();
996 void AdaptationSet::CheckVodSegmentAlignment() {
997 if (segments_aligned_ == kSegmentAlignmentFalse ||
998 force_set_segment_alignment_) {
1001 if (representation_segment_start_times_.empty())
1003 if (representation_segment_start_times_.size() == 1) {
1004 segments_aligned_ = kSegmentAlignmentTrue;
1011 const std::list<uint64_t>& expected_time_line =
1012 representation_segment_start_times_.begin()->second;
1014 bool all_segment_time_line_same_length =
true;
1016 RepresentationTimeline::const_iterator it =
1017 representation_segment_start_times_.begin();
1018 for (++it; it != representation_segment_start_times_.end(); ++it) {
1019 const std::list<uint64_t>& other_time_line = it->second;
1020 if (expected_time_line.size() != other_time_line.size()) {
1021 all_segment_time_line_same_length =
false;
1024 const std::list<uint64_t>* longer_list = &other_time_line;
1025 const std::list<uint64_t>* shorter_list = &expected_time_line;
1026 if (expected_time_line.size() > other_time_line.size()) {
1027 shorter_list = &other_time_line;
1028 longer_list = &expected_time_line;
1031 if (!std::equal(shorter_list->begin(), shorter_list->end(),
1032 longer_list->begin())) {
1034 segments_aligned_ = kSegmentAlignmentFalse;
1035 representation_segment_start_times_.clear();
1046 if (!all_segment_time_line_same_length) {
1047 segments_aligned_ = kSegmentAlignmentUnknown;
1051 segments_aligned_ = kSegmentAlignmentTrue;
1056 void AdaptationSet::RecordFrameRate(uint32_t frame_duration,
1057 uint32_t timescale) {
1058 if (frame_duration == 0) {
1059 LOG(ERROR) <<
"Frame duration is 0 and cannot be set.";
1062 video_frame_rates_[
static_cast<double>(timescale) / frame_duration] =
1063 base::IntToString(timescale) +
"/" + base::IntToString(frame_duration);
1067 const MediaInfo& media_info,
1070 std::unique_ptr<RepresentationStateChangeListener> state_change_listener)
1071 : media_info_(media_info),
1074 mpd_options_(mpd_options),
1076 state_change_listener_(std::move(state_change_listener)),
1077 output_suppression_flags_(0) {}
1079 Representation::~Representation() {}
1082 if (!AtLeastOneTrue(media_info_.has_video_info(),
1083 media_info_.has_audio_info(),
1084 media_info_.has_text_info())) {
1088 LOG(ERROR) <<
"Representation needs one of video, audio, or text.";
1092 if (MoreThanOneTrue(media_info_.has_video_info(),
1093 media_info_.has_audio_info(),
1094 media_info_.has_text_info())) {
1095 LOG(ERROR) <<
"Only one of VideoInfo, AudioInfo, or TextInfo can be set.";
1099 if (media_info_.container_type() == MediaInfo::CONTAINER_UNKNOWN) {
1100 LOG(ERROR) <<
"'container_type' in MediaInfo cannot be CONTAINER_UNKNOWN.";
1104 if (media_info_.has_video_info()) {
1105 mime_type_ = GetVideoMimeType();
1106 if (!HasRequiredVideoFields(media_info_.video_info())) {
1107 LOG(ERROR) <<
"Missing required fields to create a video Representation.";
1110 }
else if (media_info_.has_audio_info()) {
1111 mime_type_ = GetAudioMimeType();
1112 }
else if (media_info_.has_text_info()) {
1113 mime_type_ = GetTextMimeType();
1116 if (mime_type_.empty())
1119 codecs_ = GetCodecs(media_info_);
1125 content_protection_elements_.push_back(content_protection_element);
1126 RemoveDuplicateAttributes(&content_protection_elements_.back());
1130 const std::string& pssh) {
1131 UpdateContentProtectionPsshHelper(drm_uuid, pssh,
1132 &content_protection_elements_);
1138 if (start_time == 0 && duration == 0) {
1139 LOG(WARNING) <<
"Got segment with start_time and duration == 0. Ignoring.";
1143 if (state_change_listener_)
1144 state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
1145 if (IsContiguous(start_time, duration, size)) {
1146 ++segment_infos_.back().repeat;
1149 segment_infos_.push_back(s);
1152 bandwidth_estimator_.AddBlock(
1153 size, static_cast<double>(duration) / media_info_.reference_time_scale());
1156 DCHECK_GE(segment_infos_.size(), 1u);
1160 if (media_info_.has_video_info()) {
1161 media_info_.mutable_video_info()->set_frame_duration(sample_duration);
1162 if (state_change_listener_) {
1163 state_change_listener_->OnSetFrameRateForRepresentation(
1164 sample_duration, media_info_.video_info().time_scale());
1176 if (!HasRequiredMediaInfoFields()) {
1177 LOG(ERROR) <<
"MediaInfo missing required fields.";
1178 return xml::scoped_xml_ptr<xmlNode>();
1181 const uint64_t bandwidth = media_info_.has_bandwidth()
1182 ? media_info_.bandwidth()
1183 : bandwidth_estimator_.Estimate();
1185 DCHECK(!(HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)));
1187 RepresentationXmlNode representation;
1189 representation.SetId(id_);
1190 representation.SetIntegerAttribute(
"bandwidth", bandwidth);
1191 if (!codecs_.empty())
1192 representation.SetStringAttribute(
"codecs", codecs_);
1193 representation.SetStringAttribute(
"mimeType", mime_type_);
1195 const bool has_video_info = media_info_.has_video_info();
1196 const bool has_audio_info = media_info_.has_audio_info();
1198 if (has_video_info &&
1199 !representation.AddVideoInfo(
1200 media_info_.video_info(),
1201 !(output_suppression_flags_ & kSuppressWidth),
1202 !(output_suppression_flags_ & kSuppressHeight),
1203 !(output_suppression_flags_ & kSuppressFrameRate))) {
1204 LOG(ERROR) <<
"Failed to add video info to Representation XML.";
1205 return xml::scoped_xml_ptr<xmlNode>();
1208 if (has_audio_info &&
1209 !representation.AddAudioInfo(media_info_.audio_info())) {
1210 LOG(ERROR) <<
"Failed to add audio info to Representation XML.";
1211 return xml::scoped_xml_ptr<xmlNode>();
1214 if (!representation.AddContentProtectionElements(
1215 content_protection_elements_)) {
1216 return xml::scoped_xml_ptr<xmlNode>();
1220 if (mpd_options_.mpd_type == MpdType::kStatic &&
1221 media_info_.has_media_duration_seconds()) {
1224 representation.SetFloatingPointAttribute(
1225 "duration", media_info_.media_duration_seconds());
1228 if (HasVODOnlyFields(media_info_) &&
1229 !representation.AddVODOnlyInfo(media_info_)) {
1230 LOG(ERROR) <<
"Failed to add VOD segment info.";
1231 return xml::scoped_xml_ptr<xmlNode>();
1234 if (HasLiveOnlyFields(media_info_) &&
1235 !representation.AddLiveOnlyInfo(media_info_, segment_infos_,
1237 LOG(ERROR) <<
"Failed to add Live info.";
1238 return xml::scoped_xml_ptr<xmlNode>();
1243 output_suppression_flags_ = 0;
1244 return representation.PassScopedPtr();
1248 output_suppression_flags_ |= flag;
1251 bool Representation::HasRequiredMediaInfoFields() {
1252 if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) {
1253 LOG(ERROR) <<
"MediaInfo cannot have both VOD and Live fields.";
1257 if (!media_info_.has_container_type()) {
1258 LOG(ERROR) <<
"MediaInfo missing required field: container_type.";
1262 if (HasVODOnlyFields(media_info_) && !media_info_.has_bandwidth()) {
1263 LOG(ERROR) <<
"Missing 'bandwidth' field. MediaInfo requires bandwidth for "
1264 "static profile for generating a valid MPD.";
1268 VLOG_IF(3, HasLiveOnlyFields(media_info_) && !media_info_.has_bandwidth())
1269 <<
"MediaInfo missing field 'bandwidth'. Using estimated from "
1275 bool Representation::IsContiguous(uint64_t start_time,
1277 uint64_t size)
const {
1278 if (segment_infos_.empty())
1282 const SegmentInfo& previous = segment_infos_.back();
1283 const uint64_t previous_segment_end_time =
1284 previous.start_time + previous.duration * (previous.repeat + 1);
1285 if (previous_segment_end_time == start_time &&
1286 segment_infos_.back().duration == duration) {
1291 const uint64_t previous_segment_start_time =
1292 previous.start_time + previous.duration * previous.repeat;
1293 if (previous_segment_start_time >= start_time) {
1294 LOG(ERROR) <<
"Segments should not be out of order segment. Adding segment "
1295 "with start_time == "
1296 << start_time <<
" but the previous segment starts at "
1297 << previous_segment_start_time <<
".";
1302 const uint64_t kRoundingErrorGrace = 5;
1303 if (previous_segment_end_time + kRoundingErrorGrace < start_time) {
1304 LOG(WARNING) <<
"Found a gap of size "
1305 << (start_time - previous_segment_end_time)
1306 <<
" > kRoundingErrorGrace (" << kRoundingErrorGrace
1307 <<
"). The new segment starts at " << start_time
1308 <<
" but the previous segment ends at "
1309 << previous_segment_end_time <<
".";
1314 if (start_time < previous_segment_end_time - kRoundingErrorGrace) {
1316 <<
"Segments should not be overlapping. The new segment starts at "
1317 << start_time <<
" but the previous segment ends at "
1318 << previous_segment_end_time <<
".";
1326 void Representation::SlideWindow() {
1327 DCHECK(!segment_infos_.empty());
1328 if (mpd_options_.time_shift_buffer_depth <= 0.0 ||
1329 mpd_options_.mpd_type == MpdType::kStatic)
1332 const uint32_t time_scale = GetTimeScale(media_info_);
1333 DCHECK_GT(time_scale, 0u);
1335 uint64_t time_shift_buffer_depth =
1336 static_cast<uint64_t
>(mpd_options_.time_shift_buffer_depth * time_scale);
1340 const uint64_t current_play_time = LatestSegmentStartTime(segment_infos_);
1341 if (current_play_time <= time_shift_buffer_depth)
1344 const uint64_t timeshift_limit = current_play_time - time_shift_buffer_depth;
1348 std::list<SegmentInfo>::iterator first = segment_infos_.begin();
1349 std::list<SegmentInfo>::iterator last = first;
1350 size_t num_segments_removed = 0;
1351 for (; last != segment_infos_.end(); ++last) {
1352 const uint64_t last_segment_end_time = LastSegmentEndTime(*last);
1353 if (timeshift_limit < last_segment_end_time)
1355 num_segments_removed += last->repeat + 1;
1357 segment_infos_.erase(first, last);
1358 start_number_ += num_segments_removed;
1361 SegmentInfo* first_segment_info = &segment_infos_.front();
1362 DCHECK_LE(timeshift_limit, LastSegmentEndTime(*first_segment_info));
1365 const int repeat_index =
1366 SearchTimedOutRepeatIndex(timeshift_limit, *first_segment_info);
1367 CHECK_GE(repeat_index, 0);
1368 if (repeat_index == 0)
1371 first_segment_info->start_time = first_segment_info->start_time +
1372 first_segment_info->duration * repeat_index;
1374 first_segment_info->repeat = first_segment_info->repeat - repeat_index;
1375 start_number_ += repeat_index;
1378 std::string Representation::GetVideoMimeType()
const {
1379 return GetMimeType(
"video", media_info_.container_type());
1382 std::string Representation::GetAudioMimeType()
const {
1383 return GetMimeType(
"audio", media_info_.container_type());
1386 std::string Representation::GetTextMimeType()
const {
1387 CHECK(media_info_.has_text_info());
1388 if (media_info_.text_info().format() ==
"ttml") {
1389 switch (media_info_.container_type()) {
1390 case MediaInfo::CONTAINER_TEXT:
1391 return "application/ttml+xml";
1392 case MediaInfo::CONTAINER_MP4:
1393 return "application/mp4";
1395 LOG(ERROR) <<
"Failed to determine MIME type for TTML container: "
1396 << media_info_.container_type();
1400 if (media_info_.text_info().format() ==
"vtt") {
1401 if (media_info_.container_type() == MediaInfo::CONTAINER_TEXT) {
1403 }
else if (media_info_.container_type() == MediaInfo::CONTAINER_MP4) {
1404 return "application/mp4";
1406 LOG(ERROR) <<
"Failed to determine MIME type for VTT container: "
1407 << media_info_.container_type();
1411 LOG(ERROR) <<
"Cannot determine MIME type for format: "
1412 << media_info_.text_info().format()
1413 <<
" container: " << media_info_.container_type();
1417 bool Representation::GetEarliestTimestamp(
double* timestamp_seconds) {
1418 DCHECK(timestamp_seconds);
1420 if (segment_infos_.empty())
1423 *timestamp_seconds =
static_cast<double>(segment_infos_.begin()->start_time) /
1424 GetTimeScale(media_info_);
void OnSetFrameRateForRepresentation(uint32_t representation_id, uint32_t frame_duration, uint32_t timescale)
virtual void AddNewSegment(uint64_t start_time, uint64_t duration, uint64_t size)
bool WriteMpdToFile(media::File *output_file)
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)
std::string LanguageToShortestForm(const std::string &language)
virtual void AddContentProtectionElement(const ContentProtectionElement &element)
virtual void AddTrickPlayReferenceId(uint32_t id)
MpdBuilder(const MpdOptions &mpd_options)
virtual void AddRole(Role role)
void AddBaseUrl(const std::string &base_url)
virtual void UpdateContentProtectionPssh(const std::string &drm_uuid, const std::string &pssh)
AdaptationSet(uint32_t adaptation_set_id, const std::string &lang, const MpdOptions &mpd_options, base::AtomicSequenceNumber *representation_counter)
xml::scoped_xml_ptr< xmlNode > GetXml()
virtual bool ToString(std::string *output)
void AddAdaptationSetSwitching(uint32_t adaptation_set_id)
virtual void ForceSetSegmentAlignment(bool segment_alignment)
static void MakePathsRelativeToMpd(const std::string &mpd_path, MediaInfo *media_info)
xml::scoped_xml_ptr< xmlNode > GetXml()
virtual void AddContentProtectionElement(const ContentProtectionElement &element)
virtual AdaptationSet * AddAdaptationSet(const std::string &lang)
void OnNewSegmentForRepresentation(uint32_t representation_id, uint64_t start_time, uint64_t duration)
virtual void UpdateContentProtectionPssh(const std::string &drm_uuid, const std::string &pssh)
void SuppressOnce(SuppressFlag flag)