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/files/file_path.h"
19 #include "packager/base/logging.h"
20 #include "packager/base/memory/scoped_ptr.h"
21 #include "packager/base/strings/string_number_conversions.h"
22 #include "packager/base/strings/stringprintf.h"
23 #include "packager/base/synchronization/lock.h"
24 #include "packager/base/time/time.h"
25 #include "packager/media/file/file.h"
26 #include "packager/mpd/base/content_protection_element.h"
27 #include "packager/mpd/base/language_utils.h"
28 #include "packager/mpd/base/mpd_utils.h"
29 #include "packager/mpd/base/xml/xml_node.h"
31 namespace edash_packager {
35 using xml::RepresentationXmlNode;
36 using xml::AdaptationSetXmlNode;
40 const int kAdaptationSetGroupNotSet = -1;
42 AdaptationSet::Role MediaInfoTextTypeToRole(
43 MediaInfo::TextInfo::TextType type) {
45 case MediaInfo::TextInfo::UNKNOWN:
46 LOG(WARNING) <<
"Unknown text type, assuming subtitle.";
47 return AdaptationSet::kRoleSubtitle;
48 case MediaInfo::TextInfo::CAPTION:
49 return AdaptationSet::kRoleCaption;
50 case MediaInfo::TextInfo::SUBTITLE:
51 return AdaptationSet::kRoleSubtitle;
53 NOTREACHED() <<
"Unknown MediaInfo TextType: " << type
54 <<
" assuming subtitle.";
55 return AdaptationSet::kRoleSubtitle;
59 std::string GetMimeType(
const std::string& prefix,
60 MediaInfo::ContainerType container_type) {
61 switch (container_type) {
62 case MediaInfo::CONTAINER_MP4:
63 return prefix +
"/mp4";
64 case MediaInfo::CONTAINER_MPEG2_TS:
66 return prefix +
"/MP2T";
67 case MediaInfo::CONTAINER_WEBM:
68 return prefix +
"/webm";
74 LOG(ERROR) <<
"Unrecognized container type: " << container_type;
78 void AddMpdNameSpaceInfo(XmlNode* mpd) {
81 static const char kXmlNamespace[] =
"urn:mpeg:dash:schema:mpd:2011";
82 static const char kXmlNamespaceXsi[] =
83 "http://www.w3.org/2001/XMLSchema-instance";
84 static const char kXmlNamespaceXlink[] =
"http://www.w3.org/1999/xlink";
85 static const char kDashSchemaMpd2011[] =
86 "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
87 static const char kCencNamespace[] =
"urn:mpeg:cenc:2013";
89 mpd->SetStringAttribute(
"xmlns", kXmlNamespace);
90 mpd->SetStringAttribute(
"xmlns:xsi", kXmlNamespaceXsi);
91 mpd->SetStringAttribute(
"xmlns:xlink", kXmlNamespaceXlink);
92 mpd->SetStringAttribute(
"xsi:schemaLocation", kDashSchemaMpd2011);
93 mpd->SetStringAttribute(
"xmlns:cenc", kCencNamespace);
96 bool IsPeriodNode(xmlNodePtr node) {
99 return xmlStrcmp(node->name, reinterpret_cast<const xmlChar*>(
"Period")) ==
108 xmlNodePtr FindPeriodNode(XmlNode* xml_node) {
109 for (xmlNodePtr node = xml_node->GetRawPtr()->xmlChildrenNode; node != NULL;
111 if (IsPeriodNode(node))
118 bool Positive(
double d) {
124 std::string XmlDateTimeNowWithOffset(int32_t offset_seconds) {
125 base::Time time = base::Time::Now();
126 time += base::TimeDelta::FromSeconds(offset_seconds);
127 base::Time::Exploded time_exploded;
128 time.UTCExplode(&time_exploded);
130 return base::StringPrintf(
"%4d-%02d-%02dT%02d:%02d:%02dZ", time_exploded.year,
131 time_exploded.month, time_exploded.day_of_month,
132 time_exploded.hour, time_exploded.minute,
133 time_exploded.second);
136 void SetIfPositive(
const char* attr_name,
double value, XmlNode* mpd) {
137 if (Positive(value)) {
138 mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
142 uint32_t GetTimeScale(
const MediaInfo& media_info) {
143 if (media_info.has_reference_time_scale()) {
144 return media_info.reference_time_scale();
147 if (media_info.has_video_info()) {
148 return media_info.video_info().time_scale();
151 if (media_info.has_audio_info()) {
152 return media_info.audio_info().time_scale();
155 LOG(WARNING) <<
"No timescale specified, using 1 as timescale.";
159 uint64_t LastSegmentStartTime(
const SegmentInfo& segment_info) {
160 return segment_info.start_time + segment_info.duration * segment_info.repeat;
164 uint64_t LastSegmentEndTime(
const SegmentInfo& segment_info) {
165 return segment_info.start_time +
166 segment_info.duration * (segment_info.repeat + 1);
169 uint64_t LatestSegmentStartTime(
const std::list<SegmentInfo>& segments) {
170 DCHECK(!segments.empty());
171 const SegmentInfo& latest_segment = segments.back();
172 return LastSegmentStartTime(latest_segment);
177 int SearchTimedOutRepeatIndex(uint64_t timeshift_limit,
178 const SegmentInfo& segment_info) {
179 DCHECK_LE(timeshift_limit, LastSegmentEndTime(segment_info));
180 if (timeshift_limit < segment_info.start_time)
183 return (timeshift_limit - segment_info.start_time) / segment_info.duration;
189 bool WriteXmlCharArrayToOutput(xmlChar* doc,
191 std::string* output) {
194 output->assign(doc, doc + doc_size);
198 bool WriteXmlCharArrayToOutput(xmlChar* doc,
200 media::File* output) {
203 if (output->Write(doc, doc_size) < doc_size)
206 return output->Flush();
209 std::string MakePathRelative(
const std::string& path,
210 const std::string& mpd_dir) {
211 return (path.find(mpd_dir) == 0) ? path.substr(mpd_dir.size()) : path;
217 bool HasRequiredVideoFields(
const MediaInfo_VideoInfo& video_info) {
218 if (!video_info.has_height() || !video_info.has_width()) {
220 <<
"Width and height are required fields for generating a valid MPD.";
225 LOG_IF(WARNING, !video_info.has_time_scale())
226 <<
"Video info does not contain timescale required for "
227 "calculating framerate. @frameRate is required for DASH IOP.";
228 LOG_IF(WARNING, !video_info.has_frame_duration())
229 <<
"Video info does not contain frame duration required "
230 "for calculating framerate. @frameRate is required for DASH IOP.";
231 LOG_IF(WARNING, !video_info.has_pixel_width())
232 <<
"Video info does not contain pixel_width to calculate the sample "
233 "aspect ratio required for DASH IOP.";
234 LOG_IF(WARNING, !video_info.has_pixel_height())
235 <<
"Video info does not contain pixel_height to calculate the sample "
236 "aspect ratio required for DASH IOP.";
247 std::string GetPictureAspectRatio(uint32_t width,
249 uint32_t pixel_width,
250 uint32_t pixel_height) {
251 const uint32_t scaled_width = pixel_width * width;
252 const uint32_t scaled_height = pixel_height * height;
253 const double par =
static_cast<double>(scaled_width) / scaled_height;
257 const uint32_t kLargestPossibleParY = 19;
259 uint32_t par_num = 0;
260 uint32_t par_den = 0;
261 double min_error = 1.0;
262 for (uint32_t den = 1; den <= kLargestPossibleParY; ++den) {
263 uint32_t num = par * den + 0.5;
264 double error = fabs(par - static_cast<double>(num) / den);
265 if (error < min_error) {
269 if (error == 0)
break;
272 VLOG(2) <<
"width*pix_width : height*pixel_height (" << scaled_width <<
":"
273 << scaled_height <<
") reduced to " << par_num <<
":" << par_den
274 <<
" with error " << min_error <<
".";
276 return base::IntToString(par_num) +
":" + base::IntToString(par_den);
281 void AddPictureAspectRatio(
282 const MediaInfo::VideoInfo& video_info,
283 std::set<std::string>* picture_aspect_ratio) {
286 if (picture_aspect_ratio->size() > 1)
289 if (video_info.width() == 0 || video_info.height() == 0 ||
290 video_info.pixel_width() == 0 || video_info.pixel_height() == 0) {
295 picture_aspect_ratio->insert(
"bogus");
296 picture_aspect_ratio->insert(
"entries");
300 const std::string par = GetPictureAspectRatio(
301 video_info.width(), video_info.height(),
302 video_info.pixel_width(), video_info.pixel_height());
303 DVLOG(1) <<
"Setting par as: " << par
304 <<
" for video with width: " << video_info.width()
305 <<
" height: " << video_info.height()
306 <<
" pixel_width: " << video_info.pixel_width() <<
" pixel_height; "
307 << video_info.pixel_height();
308 picture_aspect_ratio->insert(par);
311 std::string RoleToText(AdaptationSet::Role role) {
315 case AdaptationSet::kRoleCaption:
317 case AdaptationSet::kRoleSubtitle:
319 case AdaptationSet::kRoleMain:
321 case AdaptationSet::kRoleAlternate:
323 case AdaptationSet::kRoleSupplementary:
324 return "supplementary";
325 case AdaptationSet::kRoleCommentary:
327 case AdaptationSet::kRoleDub:
339 class LibXmlInitializer {
341 LibXmlInitializer() : initialized_(false) {
342 base::AutoLock lock(lock_);
349 ~LibXmlInitializer() {
350 base::AutoLock lock(lock_);
353 initialized_ =
false;
361 DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
364 class RepresentationStateChangeListenerImpl
365 :
public RepresentationStateChangeListener {
368 RepresentationStateChangeListenerImpl(uint32_t representation_id,
369 AdaptationSet* adaptation_set)
370 : representation_id_(representation_id), adaptation_set_(adaptation_set) {
371 DCHECK(adaptation_set_);
373 ~RepresentationStateChangeListenerImpl()
override {}
376 void OnNewSegmentForRepresentation(uint64_t start_time,
377 uint64_t duration)
override {
378 adaptation_set_->OnNewSegmentForRepresentation(representation_id_,
379 start_time, duration);
382 void OnSetFrameRateForRepresentation(uint32_t frame_duration,
383 uint32_t timescale)
override {
384 adaptation_set_->OnSetFrameRateForRepresentation(representation_id_,
385 frame_duration, timescale);
389 const uint32_t representation_id_;
390 AdaptationSet*
const adaptation_set_;
392 DISALLOW_COPY_AND_ASSIGN(RepresentationStateChangeListenerImpl);
399 mpd_options_(mpd_options),
400 adaptation_sets_deleter_(&adaptation_sets_) {}
402 MpdBuilder::~MpdBuilder() {}
405 base_urls_.push_back(base_url);
409 scoped_ptr<AdaptationSet> adaptation_set(
410 new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_,
411 type_, &representation_counter_));
413 DCHECK(adaptation_set);
414 adaptation_sets_.push_back(adaptation_set.get());
415 return adaptation_set.release();
420 return WriteMpdToOutput(output_file);
425 return WriteMpdToOutput(output);
427 template <
typename OutputType>
428 bool MpdBuilder::WriteMpdToOutput(OutputType* output) {
429 static LibXmlInitializer lib_xml_initializer;
431 xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
435 static const int kNiceFormat = 1;
436 int doc_str_size = 0;
437 xmlChar* doc_str = NULL;
438 xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size,
"UTF-8",
441 bool result = WriteXmlCharArrayToOutput(doc_str, doc_str_size, output);
449 xmlDocPtr MpdBuilder::GenerateMpd() {
451 static const char kXmlVersion[] =
"1.0";
452 xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
456 XmlNode period(
"Period");
457 std::list<AdaptationSet*>::iterator adaptation_sets_it =
458 adaptation_sets_.begin();
459 for (; adaptation_sets_it != adaptation_sets_.end(); ++adaptation_sets_it) {
460 xml::scoped_xml_ptr<xmlNode> child((*adaptation_sets_it)->GetXml());
461 if (!child.get() || !period.AddChild(child.Pass()))
466 std::list<std::string>::const_iterator base_urls_it = base_urls_.begin();
467 for (; base_urls_it != base_urls_.end(); ++base_urls_it) {
468 XmlNode base_url(
"BaseURL");
469 base_url.SetContent(*base_urls_it);
471 if (!mpd.AddChild(base_url.PassScopedPtr()))
475 if (type_ == kDynamic) {
477 period.SetStringAttribute(
"start",
"PT0S");
480 if (!mpd.AddChild(period.PassScopedPtr()))
483 AddMpdNameSpaceInfo(&mpd);
484 AddCommonMpdInfo(&mpd);
487 AddStaticMpdInfo(&mpd);
490 AddDynamicMpdInfo(&mpd);
493 NOTREACHED() <<
"Unknown MPD type: " << type_;
498 std::string version_string =
499 "Generated with https://github.com/google/edash-packager version " +
500 mpd_options_.packager_version_string;
501 xml::scoped_xml_ptr<xmlNode> comment(
502 xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str()));
503 xmlDocSetRootElement(doc.get(), comment.get());
504 xmlAddSibling(comment.release(), mpd.Release());
505 return doc.release();
508 void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
509 if (Positive(mpd_options_.min_buffer_time)) {
510 mpd_node->SetStringAttribute(
511 "minBufferTime", SecondsToXmlDuration(mpd_options_.min_buffer_time));
513 LOG(ERROR) <<
"minBufferTime value not specified.";
518 void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
520 DCHECK_EQ(MpdBuilder::kStatic, type_);
522 static const char kStaticMpdType[] =
"static";
523 static const char kStaticMpdProfile[] =
524 "urn:mpeg:dash:profile:isoff-on-demand:2011";
525 mpd_node->SetStringAttribute(
"type", kStaticMpdType);
526 mpd_node->SetStringAttribute(
"profiles", kStaticMpdProfile);
527 mpd_node->SetStringAttribute(
528 "mediaPresentationDuration",
529 SecondsToXmlDuration(GetStaticMpdDuration(mpd_node)));
532 void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
534 DCHECK_EQ(MpdBuilder::kDynamic, type_);
536 static const char kDynamicMpdType[] =
"dynamic";
537 static const char kDynamicMpdProfile[] =
538 "urn:mpeg:dash:profile:isoff-live:2011";
539 mpd_node->SetStringAttribute(
"type", kDynamicMpdType);
540 mpd_node->SetStringAttribute(
"profiles", kDynamicMpdProfile);
544 if (availability_start_time_.empty()) {
545 double earliest_presentation_time;
546 if (GetEarliestTimestamp(&earliest_presentation_time)) {
547 availability_start_time_ =
548 XmlDateTimeNowWithOffset(mpd_options_.availability_time_offset -
549 std::ceil(earliest_presentation_time));
551 LOG(ERROR) <<
"Could not determine the earliest segment presentation "
552 "time for availabilityStartTime calculation.";
556 if (!availability_start_time_.empty())
557 mpd_node->SetStringAttribute(
"availabilityStartTime",
558 availability_start_time_);
560 if (Positive(mpd_options_.minimum_update_period)) {
561 mpd_node->SetStringAttribute(
562 "minimumUpdatePeriod",
563 SecondsToXmlDuration(mpd_options_.minimum_update_period));
565 LOG(WARNING) <<
"The profile is dynamic but no minimumUpdatePeriod "
569 SetIfPositive(
"timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth,
571 SetIfPositive(
"suggestedPresentationDelay",
572 mpd_options_.suggested_presentation_delay, mpd_node);
575 float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
577 DCHECK_EQ(MpdBuilder::kStatic, type_);
579 xmlNodePtr period_node = FindPeriodNode(mpd_node);
580 DCHECK(period_node) <<
"Period element must be a child of mpd_node.";
581 DCHECK(IsPeriodNode(period_node));
586 float max_duration = 0.0f;
587 for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node);
588 adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
589 for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
591 representation = xmlNextElementSibling(representation)) {
592 float duration = 0.0f;
593 if (GetDurationAttribute(representation, &duration)) {
594 max_duration = max_duration > duration ? max_duration : duration;
598 xmlUnsetProp(representation, BAD_CAST
"duration");
606 bool MpdBuilder::GetEarliestTimestamp(
double* timestamp_seconds) {
607 DCHECK(timestamp_seconds);
609 double earliest_timestamp(-1);
610 for (std::list<AdaptationSet*>::const_iterator iter =
611 adaptation_sets_.begin();
612 iter != adaptation_sets_.end(); ++iter) {
614 if ((*iter)->GetEarliestTimestamp(×tamp) &&
615 ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
616 earliest_timestamp = timestamp;
619 if (earliest_timestamp < 0)
622 *timestamp_seconds = earliest_timestamp;
627 MediaInfo* media_info) {
629 const std::string kFileProtocol(
"file://");
630 std::string mpd_file_path = (mpd_path.find(kFileProtocol) == 0)
631 ? mpd_path.substr(kFileProtocol.size())
634 if (!mpd_file_path.empty()) {
636 FilePath(mpd_file_path).DirName().AsEndingWithSeparator().value());
637 if (!mpd_dir.empty()) {
638 if (media_info->has_media_file_name()) {
639 media_info->set_media_file_name(
640 MakePathRelative(media_info->media_file_name(), mpd_dir));
642 if (media_info->has_init_segment_name()) {
643 media_info->set_init_segment_name(
644 MakePathRelative(media_info->init_segment_name(), mpd_dir));
646 if (media_info->has_segment_template()) {
647 media_info->set_segment_template(
648 MakePathRelative(media_info->segment_template(), mpd_dir));
655 const std::string& lang,
657 MpdBuilder::MpdType mpd_type,
658 base::AtomicSequenceNumber* counter)
659 : representations_deleter_(&representations_),
660 representation_counter_(counter),
661 id_(adaptation_set_id),
663 mpd_options_(mpd_options),
665 group_(kAdaptationSetGroupNotSet),
666 segments_aligned_(kSegmentAlignmentUnknown),
667 force_set_segment_alignment_(false) {
671 AdaptationSet::~AdaptationSet() {}
674 const uint32_t representation_id = representation_counter_->GetNext();
677 scoped_ptr<RepresentationStateChangeListener> listener(
678 new RepresentationStateChangeListenerImpl(representation_id,
this));
680 media_info, mpd_options_, representation_id, listener.Pass()));
682 if (!representation->Init())
687 if (media_info.has_video_info()) {
688 const MediaInfo::VideoInfo& video_info = media_info.video_info();
689 DCHECK(video_info.has_width());
690 DCHECK(video_info.has_height());
691 video_widths_.insert(video_info.width());
692 video_heights_.insert(video_info.height());
694 if (video_info.has_time_scale() && video_info.has_frame_duration())
695 RecordFrameRate(video_info.frame_duration(), video_info.time_scale());
697 AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
700 if (media_info.has_video_info()) {
701 content_type_ =
"video";
702 }
else if (media_info.has_audio_info()) {
703 content_type_ =
"audio";
704 }
else if (media_info.has_text_info()) {
705 content_type_ =
"text";
707 if (media_info.text_info().has_type() &&
708 (media_info.text_info().type() != MediaInfo::TextInfo::UNKNOWN)) {
709 roles_.insert(MediaInfoTextTypeToRole(media_info.text_info().type()));
713 representations_.push_back(representation.get());
714 return representation.release();
719 content_protection_elements_.push_back(content_protection_element);
720 RemoveDuplicateAttributes(&content_protection_elements_.back());
724 const std::string& pssh) {
725 UpdateContentProtectionPsshHelper(drm_uuid, pssh,
726 &content_protection_elements_);
740 AdaptationSetXmlNode adaptation_set;
742 bool suppress_representation_width =
false;
743 bool suppress_representation_height =
false;
744 bool suppress_representation_frame_rate =
false;
746 adaptation_set.SetId(id_);
747 adaptation_set.SetStringAttribute(
"contentType", content_type_);
748 if (!lang_.empty() && lang_ !=
"und") {
753 if (video_widths_.size() == 1) {
754 suppress_representation_width =
true;
755 adaptation_set.SetIntegerAttribute(
"width", *video_widths_.begin());
756 }
else if (video_widths_.size() > 1) {
757 adaptation_set.SetIntegerAttribute(
"maxWidth", *video_widths_.rbegin());
759 if (video_heights_.size() == 1) {
760 suppress_representation_height =
true;
761 adaptation_set.SetIntegerAttribute(
"height", *video_heights_.begin());
762 }
else if (video_heights_.size() > 1) {
763 adaptation_set.SetIntegerAttribute(
"maxHeight", *video_heights_.rbegin());
766 if (video_frame_rates_.size() == 1) {
767 suppress_representation_frame_rate =
true;
768 adaptation_set.SetStringAttribute(
"frameRate",
769 video_frame_rates_.begin()->second);
770 }
else if (video_frame_rates_.size() > 1) {
771 adaptation_set.SetStringAttribute(
"maxFrameRate",
772 video_frame_rates_.rbegin()->second);
777 if (mpd_type_ == MpdBuilder::kStatic) {
778 CheckVodSegmentAlignment();
781 if (segments_aligned_ == kSegmentAlignmentTrue) {
782 adaptation_set.SetStringAttribute(mpd_type_ == MpdBuilder::kStatic
783 ?
"subsegmentAlignment"
784 :
"segmentAlignment",
788 if (picture_aspect_ratio_.size() == 1)
789 adaptation_set.SetStringAttribute(
"par", *picture_aspect_ratio_.begin());
792 adaptation_set.SetIntegerAttribute(
"group", group_);
794 if (!adaptation_set.AddContentProtectionElements(
795 content_protection_elements_)) {
796 return xml::scoped_xml_ptr<xmlNode>();
798 for (AdaptationSet::Role role : roles_)
799 adaptation_set.AddRoleElement(
"urn:mpeg:dash:role:2011", RoleToText(role));
802 if (suppress_representation_width)
803 representation->SuppressOnce(Representation::kSuppressWidth);
804 if (suppress_representation_height)
805 representation->SuppressOnce(Representation::kSuppressHeight);
806 if (suppress_representation_frame_rate)
807 representation->SuppressOnce(Representation::kSuppressFrameRate);
808 xml::scoped_xml_ptr<xmlNode> child(representation->GetXml());
809 if (!child || !adaptation_set.AddChild(child.Pass()))
810 return xml::scoped_xml_ptr<xmlNode>();
813 return adaptation_set.PassScopedPtr();
818 segment_alignment ? kSegmentAlignmentTrue : kSegmentAlignmentFalse;
819 force_set_segment_alignment_ =
true;
823 group_ = group_number;
840 if (mpd_type_ == MpdBuilder::kDynamic) {
841 CheckLiveSegmentAlignment(representation_id, start_time, duration);
843 representation_segment_start_times_[representation_id].push_back(
849 uint32_t representation_id,
850 uint32_t frame_duration,
851 uint32_t timescale) {
852 RecordFrameRate(frame_duration, timescale);
855 bool AdaptationSet::GetEarliestTimestamp(
double* timestamp_seconds) {
856 DCHECK(timestamp_seconds);
858 double earliest_timestamp(-1);
859 for (std::list<Representation*>::const_iterator iter =
860 representations_.begin();
861 iter != representations_.end(); ++iter) {
863 if ((*iter)->GetEarliestTimestamp(×tamp) &&
864 ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
865 earliest_timestamp = timestamp;
868 if (earliest_timestamp < 0)
871 *timestamp_seconds = earliest_timestamp;
899 void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id,
902 if (segments_aligned_ == kSegmentAlignmentFalse ||
903 force_set_segment_alignment_) {
907 std::list<uint64_t>& representation_start_times =
908 representation_segment_start_times_[representation_id];
909 representation_start_times.push_back(start_time);
912 if (representation_segment_start_times_.size() != representations_.size())
915 DCHECK(!representation_start_times.empty());
916 const uint64_t expected_start_time = representation_start_times.front();
917 for (RepresentationTimeline::const_iterator it =
918 representation_segment_start_times_.begin();
919 it != representation_segment_start_times_.end(); ++it) {
923 if (it->second.empty())
926 if (expected_start_time != it->second.front()) {
929 segments_aligned_ = kSegmentAlignmentFalse;
930 representation_segment_start_times_.clear();
934 segments_aligned_ = kSegmentAlignmentTrue;
936 for (RepresentationTimeline::iterator it =
937 representation_segment_start_times_.begin();
938 it != representation_segment_start_times_.end(); ++it) {
939 it->second.pop_front();
945 void AdaptationSet::CheckVodSegmentAlignment() {
946 if (segments_aligned_ == kSegmentAlignmentFalse ||
947 force_set_segment_alignment_) {
950 if (representation_segment_start_times_.empty())
952 if (representation_segment_start_times_.size() == 1) {
953 segments_aligned_ = kSegmentAlignmentTrue;
960 const std::list<uint64_t>& expected_time_line =
961 representation_segment_start_times_.begin()->second;
963 bool all_segment_time_line_same_length =
true;
965 RepresentationTimeline::const_iterator it =
966 representation_segment_start_times_.begin();
967 for (++it; it != representation_segment_start_times_.end(); ++it) {
968 const std::list<uint64_t>& other_time_line = it->second;
969 if (expected_time_line.size() != other_time_line.size()) {
970 all_segment_time_line_same_length =
false;
973 const std::list<uint64_t>* longer_list = &other_time_line;
974 const std::list<uint64_t>* shorter_list = &expected_time_line;
975 if (expected_time_line.size() > other_time_line.size()) {
976 shorter_list = &other_time_line;
977 longer_list = &expected_time_line;
980 if (!std::equal(shorter_list->begin(), shorter_list->end(),
981 longer_list->begin())) {
983 segments_aligned_ = kSegmentAlignmentFalse;
984 representation_segment_start_times_.clear();
995 if (!all_segment_time_line_same_length) {
996 segments_aligned_ = kSegmentAlignmentUnknown;
1000 segments_aligned_ = kSegmentAlignmentTrue;
1005 void AdaptationSet::RecordFrameRate(uint32_t frame_duration,
1006 uint32_t timescale) {
1007 if (frame_duration == 0) {
1008 LOG(ERROR) <<
"Frame duration is 0 and cannot be set.";
1011 video_frame_rates_[
static_cast<double>(timescale) / frame_duration] =
1012 base::IntToString(timescale) +
"/" + base::IntToString(frame_duration);
1016 const MediaInfo& media_info,
1019 scoped_ptr<RepresentationStateChangeListener> state_change_listener)
1020 : media_info_(media_info),
1023 mpd_options_(mpd_options),
1025 state_change_listener_(state_change_listener.Pass()),
1026 output_suppression_flags_(0) {}
1028 Representation::~Representation() {}
1031 if (!AtLeastOneTrue(media_info_.has_video_info(),
1032 media_info_.has_audio_info(),
1033 media_info_.has_text_info())) {
1037 LOG(ERROR) <<
"Representation needs one of video, audio, or text.";
1041 if (MoreThanOneTrue(media_info_.has_video_info(),
1042 media_info_.has_audio_info(),
1043 media_info_.has_text_info())) {
1044 LOG(ERROR) <<
"Only one of VideoInfo, AudioInfo, or TextInfo can be set.";
1048 if (media_info_.container_type() == MediaInfo::CONTAINER_UNKNOWN) {
1049 LOG(ERROR) <<
"'container_type' in MediaInfo cannot be CONTAINER_UNKNOWN.";
1053 if (media_info_.has_video_info()) {
1054 mime_type_ = GetVideoMimeType();
1055 if (!HasRequiredVideoFields(media_info_.video_info())) {
1056 LOG(ERROR) <<
"Missing required fields to create a video Representation.";
1059 }
else if (media_info_.has_audio_info()) {
1060 mime_type_ = GetAudioMimeType();
1061 }
else if (media_info_.has_text_info()) {
1062 mime_type_ = GetTextMimeType();
1065 if (mime_type_.empty())
1068 codecs_ = GetCodecs(media_info_);
1074 content_protection_elements_.push_back(content_protection_element);
1075 RemoveDuplicateAttributes(&content_protection_elements_.back());
1079 const std::string& pssh) {
1080 UpdateContentProtectionPsshHelper(drm_uuid, pssh,
1081 &content_protection_elements_);
1087 if (start_time == 0 && duration == 0) {
1088 LOG(WARNING) <<
"Got segment with start_time and duration == 0. Ignoring.";
1092 if (state_change_listener_)
1093 state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
1094 if (IsContiguous(start_time, duration, size)) {
1095 ++segment_infos_.back().repeat;
1098 segment_infos_.push_back(s);
1101 bandwidth_estimator_.AddBlock(
1102 size, static_cast<double>(duration) / media_info_.reference_time_scale());
1105 DCHECK_GE(segment_infos_.size(), 1u);
1109 if (media_info_.has_video_info()) {
1110 media_info_.mutable_video_info()->set_frame_duration(sample_duration);
1111 if (state_change_listener_) {
1112 state_change_listener_->OnSetFrameRateForRepresentation(
1113 sample_duration, media_info_.video_info().time_scale());
1125 if (!HasRequiredMediaInfoFields()) {
1126 LOG(ERROR) <<
"MediaInfo missing required fields.";
1127 return xml::scoped_xml_ptr<xmlNode>();
1130 const uint64_t bandwidth = media_info_.has_bandwidth()
1131 ? media_info_.bandwidth()
1132 : bandwidth_estimator_.Estimate();
1134 DCHECK(!(HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)));
1136 RepresentationXmlNode representation;
1138 representation.SetId(id_);
1139 representation.SetIntegerAttribute(
"bandwidth", bandwidth);
1140 if (!codecs_.empty())
1141 representation.SetStringAttribute(
"codecs", codecs_);
1142 representation.SetStringAttribute(
"mimeType", mime_type_);
1144 const bool has_video_info = media_info_.has_video_info();
1145 const bool has_audio_info = media_info_.has_audio_info();
1147 if (has_video_info &&
1148 !representation.AddVideoInfo(
1149 media_info_.video_info(),
1150 !(output_suppression_flags_ & kSuppressWidth),
1151 !(output_suppression_flags_ & kSuppressHeight),
1152 !(output_suppression_flags_ & kSuppressFrameRate))) {
1153 LOG(ERROR) <<
"Failed to add video info to Representation XML.";
1154 return xml::scoped_xml_ptr<xmlNode>();
1157 if (has_audio_info &&
1158 !representation.AddAudioInfo(media_info_.audio_info())) {
1159 LOG(ERROR) <<
"Failed to add audio info to Representation XML.";
1160 return xml::scoped_xml_ptr<xmlNode>();
1163 if (!representation.AddContentProtectionElements(
1164 content_protection_elements_)) {
1165 return xml::scoped_xml_ptr<xmlNode>();
1168 if (HasVODOnlyFields(media_info_) &&
1169 !representation.AddVODOnlyInfo(media_info_)) {
1170 LOG(ERROR) <<
"Failed to add VOD segment info.";
1171 return xml::scoped_xml_ptr<xmlNode>();
1174 if (HasLiveOnlyFields(media_info_) &&
1175 !representation.AddLiveOnlyInfo(media_info_, segment_infos_,
1177 LOG(ERROR) <<
"Failed to add Live info.";
1178 return xml::scoped_xml_ptr<xmlNode>();
1183 output_suppression_flags_ = 0;
1184 return representation.PassScopedPtr();
1188 output_suppression_flags_ |= flag;
1191 bool Representation::HasRequiredMediaInfoFields() {
1192 if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) {
1193 LOG(ERROR) <<
"MediaInfo cannot have both VOD and Live fields.";
1197 if (!media_info_.has_container_type()) {
1198 LOG(ERROR) <<
"MediaInfo missing required field: container_type.";
1202 if (HasVODOnlyFields(media_info_) && !media_info_.has_bandwidth()) {
1203 LOG(ERROR) <<
"Missing 'bandwidth' field. MediaInfo requires bandwidth for "
1204 "static profile for generating a valid MPD.";
1208 VLOG_IF(3, HasLiveOnlyFields(media_info_) && !media_info_.has_bandwidth())
1209 <<
"MediaInfo missing field 'bandwidth'. Using estimated from "
1215 bool Representation::IsContiguous(uint64_t start_time,
1217 uint64_t size)
const {
1218 if (segment_infos_.empty())
1222 const SegmentInfo& previous = segment_infos_.back();
1223 const uint64_t previous_segment_end_time =
1224 previous.start_time + previous.duration * (previous.repeat + 1);
1225 if (previous_segment_end_time == start_time &&
1226 segment_infos_.back().duration == duration) {
1231 const uint64_t previous_segment_start_time =
1232 previous.start_time + previous.duration * previous.repeat;
1233 if (previous_segment_start_time >= start_time) {
1234 LOG(ERROR) <<
"Segments should not be out of order segment. Adding segment "
1235 "with start_time == "
1236 << start_time <<
" but the previous segment starts at "
1237 << previous.start_time <<
".";
1242 const uint64_t kRoundingErrorGrace = 5;
1243 if (previous_segment_end_time + kRoundingErrorGrace < start_time) {
1244 LOG(WARNING) <<
"Found a gap of size "
1245 << (start_time - previous_segment_end_time)
1246 <<
" > kRoundingErrorGrace (" << kRoundingErrorGrace
1247 <<
"). The new segment starts at " << start_time
1248 <<
" but the previous segment ends at "
1249 << previous_segment_end_time <<
".";
1254 if (start_time < previous_segment_end_time - kRoundingErrorGrace) {
1256 <<
"Segments should not be overlapping. The new segment starts at "
1257 << start_time <<
" but the previous segment ends at "
1258 << previous_segment_end_time <<
".";
1266 void Representation::SlideWindow() {
1267 DCHECK(!segment_infos_.empty());
1268 if (mpd_options_.time_shift_buffer_depth <= 0.0)
1271 const uint32_t time_scale = GetTimeScale(media_info_);
1272 DCHECK_GT(time_scale, 0u);
1274 uint64_t time_shift_buffer_depth =
1275 static_cast<uint64_t
>(mpd_options_.time_shift_buffer_depth * time_scale);
1279 const uint64_t current_play_time = LatestSegmentStartTime(segment_infos_);
1280 if (current_play_time <= time_shift_buffer_depth)
1283 const uint64_t timeshift_limit = current_play_time - time_shift_buffer_depth;
1287 std::list<SegmentInfo>::iterator first = segment_infos_.begin();
1288 std::list<SegmentInfo>::iterator last = first;
1289 size_t num_segments_removed = 0;
1290 for (; last != segment_infos_.end(); ++last) {
1291 const uint64_t last_segment_end_time = LastSegmentEndTime(*last);
1292 if (timeshift_limit < last_segment_end_time)
1294 num_segments_removed += last->repeat + 1;
1296 segment_infos_.erase(first, last);
1297 start_number_ += num_segments_removed;
1300 SegmentInfo* first_segment_info = &segment_infos_.front();
1301 DCHECK_LE(timeshift_limit, LastSegmentEndTime(*first_segment_info));
1304 const int repeat_index =
1305 SearchTimedOutRepeatIndex(timeshift_limit, *first_segment_info);
1306 CHECK_GE(repeat_index, 0);
1307 if (repeat_index == 0)
1310 first_segment_info->start_time = first_segment_info->start_time +
1311 first_segment_info->duration * repeat_index;
1313 first_segment_info->repeat = first_segment_info->repeat - repeat_index;
1314 start_number_ += repeat_index;
1317 std::string Representation::GetVideoMimeType()
const {
1318 return GetMimeType(
"video", media_info_.container_type());
1321 std::string Representation::GetAudioMimeType()
const {
1322 return GetMimeType(
"audio", media_info_.container_type());
1325 std::string Representation::GetTextMimeType()
const {
1326 CHECK(media_info_.has_text_info());
1327 if (media_info_.text_info().format() ==
"ttml") {
1328 switch (media_info_.container_type()) {
1329 case MediaInfo::CONTAINER_TEXT:
1330 return "application/ttml+xml";
1331 case MediaInfo::CONTAINER_MP4:
1332 return "application/mp4";
1334 LOG(ERROR) <<
"Failed to determine MIME type for TTML container: "
1335 << media_info_.container_type();
1339 if (media_info_.text_info().format() ==
"vtt") {
1340 if (media_info_.container_type() == MediaInfo::CONTAINER_TEXT) {
1343 LOG(ERROR) <<
"Failed to determine MIME type for VTT container: "
1344 << media_info_.container_type();
1348 LOG(ERROR) <<
"Cannot determine MIME type for format: "
1349 << media_info_.text_info().format()
1350 <<
" container: " << media_info_.container_type();
1354 bool Representation::GetEarliestTimestamp(
double* timestamp_seconds) {
1355 DCHECK(timestamp_seconds);
1357 if (segment_infos_.empty())
1360 *timestamp_seconds =
static_cast<double>(segment_infos_.begin()->start_time) /
1361 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)