7 #include "packager/mpd/base/mpd_builder.h"
9 #include <libxml/tree.h>
10 #include <libxml/xmlstring.h>
17 #include "packager/base/base64.h"
18 #include "packager/base/bind.h"
19 #include "packager/base/files/file_path.h"
20 #include "packager/base/logging.h"
21 #include "packager/base/memory/scoped_ptr.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/file/file.h"
28 #include "packager/mpd/base/content_protection_element.h"
29 #include "packager/mpd/base/language_utils.h"
30 #include "packager/mpd/base/mpd_utils.h"
31 #include "packager/mpd/base/xml/xml_node.h"
33 namespace edash_packager {
37 using xml::RepresentationXmlNode;
38 using xml::AdaptationSetXmlNode;
42 const int kAdaptationSetGroupNotSet = -1;
44 AdaptationSet::Role MediaInfoTextTypeToRole(
45 MediaInfo::TextInfo::TextType type) {
47 case MediaInfo::TextInfo::UNKNOWN:
48 LOG(WARNING) <<
"Unknown text type, assuming subtitle.";
49 return AdaptationSet::kRoleSubtitle;
50 case MediaInfo::TextInfo::CAPTION:
51 return AdaptationSet::kRoleCaption;
52 case MediaInfo::TextInfo::SUBTITLE:
53 return AdaptationSet::kRoleSubtitle;
55 NOTREACHED() <<
"Unknown MediaInfo TextType: " << type
56 <<
" assuming subtitle.";
57 return AdaptationSet::kRoleSubtitle;
61 std::string GetMimeType(
const std::string& prefix,
62 MediaInfo::ContainerType container_type) {
63 switch (container_type) {
64 case MediaInfo::CONTAINER_MP4:
65 return prefix +
"/mp4";
66 case MediaInfo::CONTAINER_MPEG2_TS:
68 return prefix +
"/MP2T";
69 case MediaInfo::CONTAINER_WEBM:
70 return prefix +
"/webm";
76 LOG(ERROR) <<
"Unrecognized container type: " << container_type;
80 void AddMpdNameSpaceInfo(XmlNode* mpd) {
83 static const char kXmlNamespace[] =
"urn:mpeg:dash:schema:mpd:2011";
84 static const char kXmlNamespaceXsi[] =
85 "http://www.w3.org/2001/XMLSchema-instance";
86 static const char kXmlNamespaceXlink[] =
"http://www.w3.org/1999/xlink";
87 static const char kDashSchemaMpd2011[] =
88 "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
89 static const char kCencNamespace[] =
"urn:mpeg:cenc:2013";
91 mpd->SetStringAttribute(
"xmlns", kXmlNamespace);
92 mpd->SetStringAttribute(
"xmlns:xsi", kXmlNamespaceXsi);
93 mpd->SetStringAttribute(
"xmlns:xlink", kXmlNamespaceXlink);
94 mpd->SetStringAttribute(
"xsi:schemaLocation", kDashSchemaMpd2011);
95 mpd->SetStringAttribute(
"xmlns:cenc", kCencNamespace);
98 bool IsPeriodNode(xmlNodePtr node) {
101 return xmlStrcmp(node->name, reinterpret_cast<const xmlChar*>(
"Period")) ==
110 xmlNodePtr FindPeriodNode(XmlNode* xml_node) {
111 for (xmlNodePtr node = xml_node->GetRawPtr()->xmlChildrenNode; node != NULL;
113 if (IsPeriodNode(node))
120 bool Positive(
double d) {
126 std::string XmlDateTimeNowWithOffset(
127 int32_t offset_seconds,
128 base::Clock* clock) {
129 base::Time time = clock->Now();
130 time += base::TimeDelta::FromSeconds(offset_seconds);
131 base::Time::Exploded time_exploded;
132 time.UTCExplode(&time_exploded);
134 return base::StringPrintf(
"%4d-%02d-%02dT%02d:%02d:%02dZ", time_exploded.year,
135 time_exploded.month, time_exploded.day_of_month,
136 time_exploded.hour, time_exploded.minute,
137 time_exploded.second);
140 void SetIfPositive(
const char* attr_name,
double value, XmlNode* mpd) {
141 if (Positive(value)) {
142 mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
146 uint32_t GetTimeScale(
const MediaInfo& media_info) {
147 if (media_info.has_reference_time_scale()) {
148 return media_info.reference_time_scale();
151 if (media_info.has_video_info()) {
152 return media_info.video_info().time_scale();
155 if (media_info.has_audio_info()) {
156 return media_info.audio_info().time_scale();
159 LOG(WARNING) <<
"No timescale specified, using 1 as timescale.";
163 uint64_t LastSegmentStartTime(
const SegmentInfo& segment_info) {
164 return segment_info.start_time + segment_info.duration * segment_info.repeat;
168 uint64_t LastSegmentEndTime(
const SegmentInfo& segment_info) {
169 return segment_info.start_time +
170 segment_info.duration * (segment_info.repeat + 1);
173 uint64_t LatestSegmentStartTime(
const std::list<SegmentInfo>& segments) {
174 DCHECK(!segments.empty());
175 const SegmentInfo& latest_segment = segments.back();
176 return LastSegmentStartTime(latest_segment);
181 int SearchTimedOutRepeatIndex(uint64_t timeshift_limit,
182 const SegmentInfo& segment_info) {
183 DCHECK_LE(timeshift_limit, LastSegmentEndTime(segment_info));
184 if (timeshift_limit < segment_info.start_time)
187 return (timeshift_limit - segment_info.start_time) / segment_info.duration;
193 bool WriteXmlCharArrayToOutput(xmlChar* doc,
195 std::string* output) {
198 output->assign(doc, doc + doc_size);
202 bool WriteXmlCharArrayToOutput(xmlChar* doc,
204 media::File* output) {
207 if (output->Write(doc, doc_size) < doc_size)
210 return output->Flush();
213 std::string MakePathRelative(
const std::string& path,
214 const std::string& mpd_dir) {
215 return (path.find(mpd_dir) == 0) ? path.substr(mpd_dir.size()) : path;
221 bool HasRequiredVideoFields(
const MediaInfo_VideoInfo& video_info) {
222 if (!video_info.has_height() || !video_info.has_width()) {
224 <<
"Width and height are required fields for generating a valid MPD.";
229 LOG_IF(WARNING, !video_info.has_time_scale())
230 <<
"Video info does not contain timescale required for "
231 "calculating framerate. @frameRate is required for DASH IOP.";
232 LOG_IF(WARNING, !video_info.has_frame_duration())
233 <<
"Video info does not contain frame duration required "
234 "for calculating framerate. @frameRate is required for DASH IOP.";
235 LOG_IF(WARNING, !video_info.has_pixel_width())
236 <<
"Video info does not contain pixel_width to calculate the sample "
237 "aspect ratio required for DASH IOP.";
238 LOG_IF(WARNING, !video_info.has_pixel_height())
239 <<
"Video info does not contain pixel_height to calculate the sample "
240 "aspect ratio required for DASH IOP.";
251 std::string GetPictureAspectRatio(uint32_t width,
253 uint32_t pixel_width,
254 uint32_t pixel_height) {
255 const uint32_t scaled_width = pixel_width * width;
256 const uint32_t scaled_height = pixel_height * height;
257 const double par =
static_cast<double>(scaled_width) / scaled_height;
261 const uint32_t kLargestPossibleParY = 19;
263 uint32_t par_num = 0;
264 uint32_t par_den = 0;
265 double min_error = 1.0;
266 for (uint32_t den = 1; den <= kLargestPossibleParY; ++den) {
267 uint32_t num = par * den + 0.5;
268 double error = fabs(par - static_cast<double>(num) / den);
269 if (error < min_error) {
273 if (error == 0)
break;
276 VLOG(2) <<
"width*pix_width : height*pixel_height (" << scaled_width <<
":"
277 << scaled_height <<
") reduced to " << par_num <<
":" << par_den
278 <<
" with error " << min_error <<
".";
280 return base::IntToString(par_num) +
":" + base::IntToString(par_den);
285 void AddPictureAspectRatio(
286 const MediaInfo::VideoInfo& video_info,
287 std::set<std::string>* picture_aspect_ratio) {
290 if (picture_aspect_ratio->size() > 1)
293 if (video_info.width() == 0 || video_info.height() == 0 ||
294 video_info.pixel_width() == 0 || video_info.pixel_height() == 0) {
299 picture_aspect_ratio->insert(
"bogus");
300 picture_aspect_ratio->insert(
"entries");
304 const std::string par = GetPictureAspectRatio(
305 video_info.width(), video_info.height(),
306 video_info.pixel_width(), video_info.pixel_height());
307 DVLOG(1) <<
"Setting par as: " << par
308 <<
" for video with width: " << video_info.width()
309 <<
" height: " << video_info.height()
310 <<
" pixel_width: " << video_info.pixel_width() <<
" pixel_height; "
311 << video_info.pixel_height();
312 picture_aspect_ratio->insert(par);
315 std::string RoleToText(AdaptationSet::Role role) {
319 case AdaptationSet::kRoleCaption:
321 case AdaptationSet::kRoleSubtitle:
323 case AdaptationSet::kRoleMain:
325 case AdaptationSet::kRoleAlternate:
327 case AdaptationSet::kRoleSupplementary:
328 return "supplementary";
329 case AdaptationSet::kRoleCommentary:
331 case AdaptationSet::kRoleDub:
343 class LibXmlInitializer {
345 LibXmlInitializer() : initialized_(false) {
346 base::AutoLock lock(lock_);
353 ~LibXmlInitializer() {
354 base::AutoLock lock(lock_);
357 initialized_ =
false;
365 DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
368 class RepresentationStateChangeListenerImpl
369 :
public RepresentationStateChangeListener {
372 RepresentationStateChangeListenerImpl(uint32_t representation_id,
373 AdaptationSet* adaptation_set)
374 : representation_id_(representation_id), adaptation_set_(adaptation_set) {
375 DCHECK(adaptation_set_);
377 ~RepresentationStateChangeListenerImpl()
override {}
380 void OnNewSegmentForRepresentation(uint64_t start_time,
381 uint64_t duration)
override {
382 adaptation_set_->OnNewSegmentForRepresentation(representation_id_,
383 start_time, duration);
386 void OnSetFrameRateForRepresentation(uint32_t frame_duration,
387 uint32_t timescale)
override {
388 adaptation_set_->OnSetFrameRateForRepresentation(representation_id_,
389 frame_duration, timescale);
393 const uint32_t representation_id_;
394 AdaptationSet*
const adaptation_set_;
396 DISALLOW_COPY_AND_ASSIGN(RepresentationStateChangeListenerImpl);
403 mpd_options_(mpd_options),
404 adaptation_sets_deleter_(&adaptation_sets_),
405 clock_(new base::DefaultClock()) {}
407 MpdBuilder::~MpdBuilder() {}
410 base_urls_.push_back(base_url);
414 scoped_ptr<AdaptationSet> adaptation_set(
415 new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_,
416 type_, &representation_counter_));
418 DCHECK(adaptation_set);
419 adaptation_sets_.push_back(adaptation_set.get());
420 return adaptation_set.release();
425 return WriteMpdToOutput(output_file);
430 return WriteMpdToOutput(output);
432 template <
typename OutputType>
433 bool MpdBuilder::WriteMpdToOutput(OutputType* output) {
434 static LibXmlInitializer lib_xml_initializer;
436 xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
440 static const int kNiceFormat = 1;
441 int doc_str_size = 0;
442 xmlChar* doc_str = NULL;
443 xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size,
"UTF-8",
446 bool result = WriteXmlCharArrayToOutput(doc_str, doc_str_size, output);
454 xmlDocPtr MpdBuilder::GenerateMpd() {
456 static const char kXmlVersion[] =
"1.0";
457 xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
461 XmlNode period(
"Period");
467 std::list<AdaptationSet*>::iterator adaptation_sets_it =
468 adaptation_sets_.begin();
469 for (; adaptation_sets_it != adaptation_sets_.end(); ++adaptation_sets_it) {
470 xml::scoped_xml_ptr<xmlNode> child((*adaptation_sets_it)->GetXml());
471 if (!child.get() || !period.AddChild(child.Pass()))
476 std::list<std::string>::const_iterator base_urls_it = base_urls_.begin();
477 for (; base_urls_it != base_urls_.end(); ++base_urls_it) {
478 XmlNode base_url(
"BaseURL");
479 base_url.SetContent(*base_urls_it);
481 if (!mpd.AddChild(base_url.PassScopedPtr()))
485 if (type_ == kDynamic) {
487 period.SetStringAttribute(
"start",
"PT0S");
490 if (!mpd.AddChild(period.PassScopedPtr()))
493 AddMpdNameSpaceInfo(&mpd);
494 AddCommonMpdInfo(&mpd);
497 AddStaticMpdInfo(&mpd);
500 AddDynamicMpdInfo(&mpd);
503 NOTREACHED() <<
"Unknown MPD type: " << type_;
508 std::string version_string =
509 "Generated with https://github.com/google/edash-packager version " +
510 mpd_options_.packager_version_string;
511 xml::scoped_xml_ptr<xmlNode> comment(
512 xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str()));
513 xmlDocSetRootElement(doc.get(), comment.get());
514 xmlAddSibling(comment.release(), mpd.Release());
515 return doc.release();
518 void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
519 if (Positive(mpd_options_.min_buffer_time)) {
520 mpd_node->SetStringAttribute(
521 "minBufferTime", SecondsToXmlDuration(mpd_options_.min_buffer_time));
523 LOG(ERROR) <<
"minBufferTime value not specified.";
528 void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
530 DCHECK_EQ(MpdBuilder::kStatic, type_);
532 static const char kStaticMpdType[] =
"static";
533 static const char kStaticMpdProfile[] =
534 "urn:mpeg:dash:profile:isoff-on-demand:2011";
535 mpd_node->SetStringAttribute(
"type", kStaticMpdType);
536 mpd_node->SetStringAttribute(
"profiles", kStaticMpdProfile);
537 mpd_node->SetStringAttribute(
538 "mediaPresentationDuration",
539 SecondsToXmlDuration(GetStaticMpdDuration(mpd_node)));
542 void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
544 DCHECK_EQ(MpdBuilder::kDynamic, type_);
546 static const char kDynamicMpdType[] =
"dynamic";
547 static const char kDynamicMpdProfile[] =
548 "urn:mpeg:dash:profile:isoff-live:2011";
549 mpd_node->SetStringAttribute(
"type", kDynamicMpdType);
550 mpd_node->SetStringAttribute(
"profiles", kDynamicMpdProfile);
553 mpd_node->SetStringAttribute(
"publishTime",
554 XmlDateTimeNowWithOffset(0, clock_.get()));
558 if (availability_start_time_.empty()) {
559 double earliest_presentation_time;
560 if (GetEarliestTimestamp(&earliest_presentation_time)) {
561 availability_start_time_ =
562 XmlDateTimeNowWithOffset(mpd_options_.availability_time_offset -
563 std::ceil(earliest_presentation_time),
566 LOG(ERROR) <<
"Could not determine the earliest segment presentation "
567 "time for availabilityStartTime calculation.";
571 if (!availability_start_time_.empty())
572 mpd_node->SetStringAttribute(
"availabilityStartTime",
573 availability_start_time_);
575 if (Positive(mpd_options_.minimum_update_period)) {
576 mpd_node->SetStringAttribute(
577 "minimumUpdatePeriod",
578 SecondsToXmlDuration(mpd_options_.minimum_update_period));
580 LOG(WARNING) <<
"The profile is dynamic but no minimumUpdatePeriod "
584 SetIfPositive(
"timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth,
586 SetIfPositive(
"suggestedPresentationDelay",
587 mpd_options_.suggested_presentation_delay, mpd_node);
590 float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
592 DCHECK_EQ(MpdBuilder::kStatic, type_);
594 xmlNodePtr period_node = FindPeriodNode(mpd_node);
595 DCHECK(period_node) <<
"Period element must be a child of mpd_node.";
596 DCHECK(IsPeriodNode(period_node));
601 float max_duration = 0.0f;
602 for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node);
603 adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
604 for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
606 representation = xmlNextElementSibling(representation)) {
607 float duration = 0.0f;
608 if (GetDurationAttribute(representation, &duration)) {
609 max_duration = max_duration > duration ? max_duration : duration;
613 xmlUnsetProp(representation, BAD_CAST
"duration");
621 bool MpdBuilder::GetEarliestTimestamp(
double* timestamp_seconds) {
622 DCHECK(timestamp_seconds);
624 double earliest_timestamp(-1);
625 for (std::list<AdaptationSet*>::const_iterator iter =
626 adaptation_sets_.begin();
627 iter != adaptation_sets_.end(); ++iter) {
629 if ((*iter)->GetEarliestTimestamp(×tamp) &&
630 ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
631 earliest_timestamp = timestamp;
634 if (earliest_timestamp < 0)
637 *timestamp_seconds = earliest_timestamp;
642 MediaInfo* media_info) {
644 const std::string kFileProtocol(
"file://");
645 std::string mpd_file_path = (mpd_path.find(kFileProtocol) == 0)
646 ? mpd_path.substr(kFileProtocol.size())
649 if (!mpd_file_path.empty()) {
651 FilePath(mpd_file_path).DirName().AsEndingWithSeparator().value());
652 if (!mpd_dir.empty()) {
653 if (media_info->has_media_file_name()) {
654 media_info->set_media_file_name(
655 MakePathRelative(media_info->media_file_name(), mpd_dir));
657 if (media_info->has_init_segment_name()) {
658 media_info->set_init_segment_name(
659 MakePathRelative(media_info->init_segment_name(), mpd_dir));
661 if (media_info->has_segment_template()) {
662 media_info->set_segment_template(
663 MakePathRelative(media_info->segment_template(), mpd_dir));
670 const std::string& lang,
672 MpdBuilder::MpdType mpd_type,
673 base::AtomicSequenceNumber* counter)
674 : representations_deleter_(&representations_),
675 representation_counter_(counter),
676 id_(adaptation_set_id),
678 mpd_options_(mpd_options),
680 group_(kAdaptationSetGroupNotSet),
681 segments_aligned_(kSegmentAlignmentUnknown),
682 force_set_segment_alignment_(false) {
686 AdaptationSet::~AdaptationSet() {}
689 const uint32_t representation_id = representation_counter_->GetNext();
692 scoped_ptr<RepresentationStateChangeListener> listener(
693 new RepresentationStateChangeListenerImpl(representation_id,
this));
695 media_info, mpd_options_, representation_id, listener.Pass()));
697 if (!representation->Init())
702 if (media_info.has_video_info()) {
703 const MediaInfo::VideoInfo& video_info = media_info.video_info();
704 DCHECK(video_info.has_width());
705 DCHECK(video_info.has_height());
706 video_widths_.insert(video_info.width());
707 video_heights_.insert(video_info.height());
709 if (video_info.has_time_scale() && video_info.has_frame_duration())
710 RecordFrameRate(video_info.frame_duration(), video_info.time_scale());
712 AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
715 if (media_info.has_video_info()) {
716 content_type_ =
"video";
717 }
else if (media_info.has_audio_info()) {
718 content_type_ =
"audio";
719 }
else if (media_info.has_text_info()) {
720 content_type_ =
"text";
722 if (media_info.text_info().has_type() &&
723 (media_info.text_info().type() != MediaInfo::TextInfo::UNKNOWN)) {
724 roles_.insert(MediaInfoTextTypeToRole(media_info.text_info().type()));
728 representations_.push_back(representation.get());
729 return representation.release();
734 content_protection_elements_.push_back(content_protection_element);
735 RemoveDuplicateAttributes(&content_protection_elements_.back());
739 const std::string& pssh) {
740 UpdateContentProtectionPsshHelper(drm_uuid, pssh,
741 &content_protection_elements_);
755 AdaptationSetXmlNode adaptation_set;
757 bool suppress_representation_width =
false;
758 bool suppress_representation_height =
false;
759 bool suppress_representation_frame_rate =
false;
761 adaptation_set.SetId(id_);
762 adaptation_set.SetStringAttribute(
"contentType", content_type_);
763 if (!lang_.empty() && lang_ !=
"und") {
768 if (video_widths_.size() == 1) {
769 suppress_representation_width =
true;
770 adaptation_set.SetIntegerAttribute(
"width", *video_widths_.begin());
771 }
else if (video_widths_.size() > 1) {
772 adaptation_set.SetIntegerAttribute(
"maxWidth", *video_widths_.rbegin());
774 if (video_heights_.size() == 1) {
775 suppress_representation_height =
true;
776 adaptation_set.SetIntegerAttribute(
"height", *video_heights_.begin());
777 }
else if (video_heights_.size() > 1) {
778 adaptation_set.SetIntegerAttribute(
"maxHeight", *video_heights_.rbegin());
781 if (video_frame_rates_.size() == 1) {
782 suppress_representation_frame_rate =
true;
783 adaptation_set.SetStringAttribute(
"frameRate",
784 video_frame_rates_.begin()->second);
785 }
else if (video_frame_rates_.size() > 1) {
786 adaptation_set.SetStringAttribute(
"maxFrameRate",
787 video_frame_rates_.rbegin()->second);
792 if (mpd_type_ == MpdBuilder::kStatic) {
793 CheckVodSegmentAlignment();
796 if (segments_aligned_ == kSegmentAlignmentTrue) {
797 adaptation_set.SetStringAttribute(mpd_type_ == MpdBuilder::kStatic
798 ?
"subsegmentAlignment"
799 :
"segmentAlignment",
803 if (picture_aspect_ratio_.size() == 1)
804 adaptation_set.SetStringAttribute(
"par", *picture_aspect_ratio_.begin());
807 adaptation_set.SetIntegerAttribute(
"group", group_);
809 if (!adaptation_set.AddContentProtectionElements(
810 content_protection_elements_)) {
811 return xml::scoped_xml_ptr<xmlNode>();
813 for (AdaptationSet::Role role : roles_)
814 adaptation_set.AddRoleElement(
"urn:mpeg:dash:role:2011", RoleToText(role));
817 if (suppress_representation_width)
818 representation->SuppressOnce(Representation::kSuppressWidth);
819 if (suppress_representation_height)
820 representation->SuppressOnce(Representation::kSuppressHeight);
821 if (suppress_representation_frame_rate)
822 representation->SuppressOnce(Representation::kSuppressFrameRate);
823 xml::scoped_xml_ptr<xmlNode> child(representation->GetXml());
824 if (!child || !adaptation_set.AddChild(child.Pass()))
825 return xml::scoped_xml_ptr<xmlNode>();
828 return adaptation_set.PassScopedPtr();
833 segment_alignment ? kSegmentAlignmentTrue : kSegmentAlignmentFalse;
834 force_set_segment_alignment_ =
true;
838 group_ = group_number;
855 if (mpd_type_ == MpdBuilder::kDynamic) {
856 CheckLiveSegmentAlignment(representation_id, start_time, duration);
858 representation_segment_start_times_[representation_id].push_back(
864 uint32_t representation_id,
865 uint32_t frame_duration,
866 uint32_t timescale) {
867 RecordFrameRate(frame_duration, timescale);
870 bool AdaptationSet::GetEarliestTimestamp(
double* timestamp_seconds) {
871 DCHECK(timestamp_seconds);
873 double earliest_timestamp(-1);
874 for (std::list<Representation*>::const_iterator iter =
875 representations_.begin();
876 iter != representations_.end(); ++iter) {
878 if ((*iter)->GetEarliestTimestamp(×tamp) &&
879 ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
880 earliest_timestamp = timestamp;
883 if (earliest_timestamp < 0)
886 *timestamp_seconds = earliest_timestamp;
914 void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id,
917 if (segments_aligned_ == kSegmentAlignmentFalse ||
918 force_set_segment_alignment_) {
922 std::list<uint64_t>& representation_start_times =
923 representation_segment_start_times_[representation_id];
924 representation_start_times.push_back(start_time);
927 if (representation_segment_start_times_.size() != representations_.size())
930 DCHECK(!representation_start_times.empty());
931 const uint64_t expected_start_time = representation_start_times.front();
932 for (RepresentationTimeline::const_iterator it =
933 representation_segment_start_times_.begin();
934 it != representation_segment_start_times_.end(); ++it) {
938 if (it->second.empty())
941 if (expected_start_time != it->second.front()) {
944 segments_aligned_ = kSegmentAlignmentFalse;
945 representation_segment_start_times_.clear();
949 segments_aligned_ = kSegmentAlignmentTrue;
951 for (RepresentationTimeline::iterator it =
952 representation_segment_start_times_.begin();
953 it != representation_segment_start_times_.end(); ++it) {
954 it->second.pop_front();
960 void AdaptationSet::CheckVodSegmentAlignment() {
961 if (segments_aligned_ == kSegmentAlignmentFalse ||
962 force_set_segment_alignment_) {
965 if (representation_segment_start_times_.empty())
967 if (representation_segment_start_times_.size() == 1) {
968 segments_aligned_ = kSegmentAlignmentTrue;
975 const std::list<uint64_t>& expected_time_line =
976 representation_segment_start_times_.begin()->second;
978 bool all_segment_time_line_same_length =
true;
980 RepresentationTimeline::const_iterator it =
981 representation_segment_start_times_.begin();
982 for (++it; it != representation_segment_start_times_.end(); ++it) {
983 const std::list<uint64_t>& other_time_line = it->second;
984 if (expected_time_line.size() != other_time_line.size()) {
985 all_segment_time_line_same_length =
false;
988 const std::list<uint64_t>* longer_list = &other_time_line;
989 const std::list<uint64_t>* shorter_list = &expected_time_line;
990 if (expected_time_line.size() > other_time_line.size()) {
991 shorter_list = &other_time_line;
992 longer_list = &expected_time_line;
995 if (!std::equal(shorter_list->begin(), shorter_list->end(),
996 longer_list->begin())) {
998 segments_aligned_ = kSegmentAlignmentFalse;
999 representation_segment_start_times_.clear();
1010 if (!all_segment_time_line_same_length) {
1011 segments_aligned_ = kSegmentAlignmentUnknown;
1015 segments_aligned_ = kSegmentAlignmentTrue;
1020 void AdaptationSet::RecordFrameRate(uint32_t frame_duration,
1021 uint32_t timescale) {
1022 if (frame_duration == 0) {
1023 LOG(ERROR) <<
"Frame duration is 0 and cannot be set.";
1026 video_frame_rates_[
static_cast<double>(timescale) / frame_duration] =
1027 base::IntToString(timescale) +
"/" + base::IntToString(frame_duration);
1031 const MediaInfo& media_info,
1034 scoped_ptr<RepresentationStateChangeListener> state_change_listener)
1035 : media_info_(media_info),
1038 mpd_options_(mpd_options),
1040 state_change_listener_(state_change_listener.Pass()),
1041 output_suppression_flags_(0) {}
1043 Representation::~Representation() {}
1046 if (!AtLeastOneTrue(media_info_.has_video_info(),
1047 media_info_.has_audio_info(),
1048 media_info_.has_text_info())) {
1052 LOG(ERROR) <<
"Representation needs one of video, audio, or text.";
1056 if (MoreThanOneTrue(media_info_.has_video_info(),
1057 media_info_.has_audio_info(),
1058 media_info_.has_text_info())) {
1059 LOG(ERROR) <<
"Only one of VideoInfo, AudioInfo, or TextInfo can be set.";
1063 if (media_info_.container_type() == MediaInfo::CONTAINER_UNKNOWN) {
1064 LOG(ERROR) <<
"'container_type' in MediaInfo cannot be CONTAINER_UNKNOWN.";
1068 if (media_info_.has_video_info()) {
1069 mime_type_ = GetVideoMimeType();
1070 if (!HasRequiredVideoFields(media_info_.video_info())) {
1071 LOG(ERROR) <<
"Missing required fields to create a video Representation.";
1074 }
else if (media_info_.has_audio_info()) {
1075 mime_type_ = GetAudioMimeType();
1076 }
else if (media_info_.has_text_info()) {
1077 mime_type_ = GetTextMimeType();
1080 if (mime_type_.empty())
1083 codecs_ = GetCodecs(media_info_);
1089 content_protection_elements_.push_back(content_protection_element);
1090 RemoveDuplicateAttributes(&content_protection_elements_.back());
1094 const std::string& pssh) {
1095 UpdateContentProtectionPsshHelper(drm_uuid, pssh,
1096 &content_protection_elements_);
1102 if (start_time == 0 && duration == 0) {
1103 LOG(WARNING) <<
"Got segment with start_time and duration == 0. Ignoring.";
1107 if (state_change_listener_)
1108 state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
1109 if (IsContiguous(start_time, duration, size)) {
1110 ++segment_infos_.back().repeat;
1113 segment_infos_.push_back(s);
1116 bandwidth_estimator_.AddBlock(
1117 size, static_cast<double>(duration) / media_info_.reference_time_scale());
1120 DCHECK_GE(segment_infos_.size(), 1u);
1124 if (media_info_.has_video_info()) {
1125 media_info_.mutable_video_info()->set_frame_duration(sample_duration);
1126 if (state_change_listener_) {
1127 state_change_listener_->OnSetFrameRateForRepresentation(
1128 sample_duration, media_info_.video_info().time_scale());
1140 if (!HasRequiredMediaInfoFields()) {
1141 LOG(ERROR) <<
"MediaInfo missing required fields.";
1142 return xml::scoped_xml_ptr<xmlNode>();
1145 const uint64_t bandwidth = media_info_.has_bandwidth()
1146 ? media_info_.bandwidth()
1147 : bandwidth_estimator_.Estimate();
1149 DCHECK(!(HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)));
1151 RepresentationXmlNode representation;
1153 representation.SetId(id_);
1154 representation.SetIntegerAttribute(
"bandwidth", bandwidth);
1155 if (!codecs_.empty())
1156 representation.SetStringAttribute(
"codecs", codecs_);
1157 representation.SetStringAttribute(
"mimeType", mime_type_);
1159 const bool has_video_info = media_info_.has_video_info();
1160 const bool has_audio_info = media_info_.has_audio_info();
1162 if (has_video_info &&
1163 !representation.AddVideoInfo(
1164 media_info_.video_info(),
1165 !(output_suppression_flags_ & kSuppressWidth),
1166 !(output_suppression_flags_ & kSuppressHeight),
1167 !(output_suppression_flags_ & kSuppressFrameRate))) {
1168 LOG(ERROR) <<
"Failed to add video info to Representation XML.";
1169 return xml::scoped_xml_ptr<xmlNode>();
1172 if (has_audio_info &&
1173 !representation.AddAudioInfo(media_info_.audio_info())) {
1174 LOG(ERROR) <<
"Failed to add audio info to Representation XML.";
1175 return xml::scoped_xml_ptr<xmlNode>();
1178 if (!representation.AddContentProtectionElements(
1179 content_protection_elements_)) {
1180 return xml::scoped_xml_ptr<xmlNode>();
1183 if (HasVODOnlyFields(media_info_) &&
1184 !representation.AddVODOnlyInfo(media_info_)) {
1185 LOG(ERROR) <<
"Failed to add VOD segment info.";
1186 return xml::scoped_xml_ptr<xmlNode>();
1189 if (HasLiveOnlyFields(media_info_) &&
1190 !representation.AddLiveOnlyInfo(media_info_, segment_infos_,
1192 LOG(ERROR) <<
"Failed to add Live info.";
1193 return xml::scoped_xml_ptr<xmlNode>();
1198 output_suppression_flags_ = 0;
1199 return representation.PassScopedPtr();
1203 output_suppression_flags_ |= flag;
1206 bool Representation::HasRequiredMediaInfoFields() {
1207 if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) {
1208 LOG(ERROR) <<
"MediaInfo cannot have both VOD and Live fields.";
1212 if (!media_info_.has_container_type()) {
1213 LOG(ERROR) <<
"MediaInfo missing required field: container_type.";
1217 if (HasVODOnlyFields(media_info_) && !media_info_.has_bandwidth()) {
1218 LOG(ERROR) <<
"Missing 'bandwidth' field. MediaInfo requires bandwidth for "
1219 "static profile for generating a valid MPD.";
1223 VLOG_IF(3, HasLiveOnlyFields(media_info_) && !media_info_.has_bandwidth())
1224 <<
"MediaInfo missing field 'bandwidth'. Using estimated from "
1230 bool Representation::IsContiguous(uint64_t start_time,
1232 uint64_t size)
const {
1233 if (segment_infos_.empty())
1237 const SegmentInfo& previous = segment_infos_.back();
1238 const uint64_t previous_segment_end_time =
1239 previous.start_time + previous.duration * (previous.repeat + 1);
1240 if (previous_segment_end_time == start_time &&
1241 segment_infos_.back().duration == duration) {
1246 const uint64_t previous_segment_start_time =
1247 previous.start_time + previous.duration * previous.repeat;
1248 if (previous_segment_start_time >= start_time) {
1249 LOG(ERROR) <<
"Segments should not be out of order segment. Adding segment "
1250 "with start_time == "
1251 << start_time <<
" but the previous segment starts at "
1252 << previous.start_time <<
".";
1257 const uint64_t kRoundingErrorGrace = 5;
1258 if (previous_segment_end_time + kRoundingErrorGrace < start_time) {
1259 LOG(WARNING) <<
"Found a gap of size "
1260 << (start_time - previous_segment_end_time)
1261 <<
" > kRoundingErrorGrace (" << kRoundingErrorGrace
1262 <<
"). The new segment starts at " << start_time
1263 <<
" but the previous segment ends at "
1264 << previous_segment_end_time <<
".";
1269 if (start_time < previous_segment_end_time - kRoundingErrorGrace) {
1271 <<
"Segments should not be overlapping. The new segment starts at "
1272 << start_time <<
" but the previous segment ends at "
1273 << previous_segment_end_time <<
".";
1281 void Representation::SlideWindow() {
1282 DCHECK(!segment_infos_.empty());
1283 if (mpd_options_.time_shift_buffer_depth <= 0.0)
1286 const uint32_t time_scale = GetTimeScale(media_info_);
1287 DCHECK_GT(time_scale, 0u);
1289 uint64_t time_shift_buffer_depth =
1290 static_cast<uint64_t
>(mpd_options_.time_shift_buffer_depth * time_scale);
1294 const uint64_t current_play_time = LatestSegmentStartTime(segment_infos_);
1295 if (current_play_time <= time_shift_buffer_depth)
1298 const uint64_t timeshift_limit = current_play_time - time_shift_buffer_depth;
1302 std::list<SegmentInfo>::iterator first = segment_infos_.begin();
1303 std::list<SegmentInfo>::iterator last = first;
1304 size_t num_segments_removed = 0;
1305 for (; last != segment_infos_.end(); ++last) {
1306 const uint64_t last_segment_end_time = LastSegmentEndTime(*last);
1307 if (timeshift_limit < last_segment_end_time)
1309 num_segments_removed += last->repeat + 1;
1311 segment_infos_.erase(first, last);
1312 start_number_ += num_segments_removed;
1315 SegmentInfo* first_segment_info = &segment_infos_.front();
1316 DCHECK_LE(timeshift_limit, LastSegmentEndTime(*first_segment_info));
1319 const int repeat_index =
1320 SearchTimedOutRepeatIndex(timeshift_limit, *first_segment_info);
1321 CHECK_GE(repeat_index, 0);
1322 if (repeat_index == 0)
1325 first_segment_info->start_time = first_segment_info->start_time +
1326 first_segment_info->duration * repeat_index;
1328 first_segment_info->repeat = first_segment_info->repeat - repeat_index;
1329 start_number_ += repeat_index;
1332 std::string Representation::GetVideoMimeType()
const {
1333 return GetMimeType(
"video", media_info_.container_type());
1336 std::string Representation::GetAudioMimeType()
const {
1337 return GetMimeType(
"audio", media_info_.container_type());
1340 std::string Representation::GetTextMimeType()
const {
1341 CHECK(media_info_.has_text_info());
1342 if (media_info_.text_info().format() ==
"ttml") {
1343 switch (media_info_.container_type()) {
1344 case MediaInfo::CONTAINER_TEXT:
1345 return "application/ttml+xml";
1346 case MediaInfo::CONTAINER_MP4:
1347 return "application/mp4";
1349 LOG(ERROR) <<
"Failed to determine MIME type for TTML container: "
1350 << media_info_.container_type();
1354 if (media_info_.text_info().format() ==
"vtt") {
1355 if (media_info_.container_type() == MediaInfo::CONTAINER_TEXT) {
1358 LOG(ERROR) <<
"Failed to determine MIME type for VTT container: "
1359 << media_info_.container_type();
1363 LOG(ERROR) <<
"Cannot determine MIME type for format: "
1364 << media_info_.text_info().format()
1365 <<
" container: " << media_info_.container_type();
1369 bool Representation::GetEarliestTimestamp(
double* timestamp_seconds) {
1370 DCHECK(timestamp_seconds);
1372 if (segment_infos_.empty())
1375 *timestamp_seconds =
static_cast<double>(segment_infos_.begin()->start_time) /
1376 GetTimeScale(media_info_);
std::string LanguageToShortestForm(const std::string &language)
virtual void AddNewSegment(uint64_t start_time, uint64_t duration, uint64_t size)
AdaptationSet(uint32_t adaptation_set_id, const std::string &lang, const MpdOptions &mpd_options, MpdBuilder::MpdType mpd_type, base::AtomicSequenceNumber *representation_counter)
virtual int Group() const
virtual AdaptationSet * AddAdaptationSet(const std::string &lang)
xml::scoped_xml_ptr< xmlNode > GetXml()
static void MakePathsRelativeToMpd(const std::string &mpd_path, MediaInfo *media_info)
virtual void ForceSetSegmentAlignment(bool segment_alignment)
virtual void SetSampleDuration(uint32_t sample_duration)
void AddBaseUrl(const std::string &base_url)
virtual void UpdateContentProtectionPssh(const std::string &drm_uuid, const std::string &pssh)
Representation(const MediaInfo &media_info, const MpdOptions &mpd_options, uint32_t representation_id, scoped_ptr< RepresentationStateChangeListener > state_change_listener)
virtual void SetGroup(int group_number)
virtual void AddContentProtectionElement(const ContentProtectionElement &element)
bool WriteMpdToFile(media::File *output_file)
void OnSetFrameRateForRepresentation(uint32_t representation_id, uint32_t frame_duration, uint32_t timescale)
virtual Representation * AddRepresentation(const MediaInfo &media_info)
xml::scoped_xml_ptr< xmlNode > GetXml()
void SuppressOnce(SuppressFlag flag)
virtual void UpdateContentProtectionPssh(const std::string &drm_uuid, const std::string &pssh)
virtual bool ToString(std::string *output)
virtual void AddRole(Role role)
MpdBuilder(MpdType type, const MpdOptions &mpd_options)
virtual void AddContentProtectionElement(const ContentProtectionElement &element)
void OnNewSegmentForRepresentation(uint32_t representation_id, uint64_t start_time, uint64_t duration)