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 std::string GetMimeType(
const std::string& prefix,
43 MediaInfo::ContainerType container_type) {
44 switch (container_type) {
45 case MediaInfo::CONTAINER_MP4:
46 return prefix +
"/mp4";
47 case MediaInfo::CONTAINER_MPEG2_TS:
49 return prefix +
"/MP2T";
50 case MediaInfo::CONTAINER_WEBM:
51 return prefix +
"/webm";
57 NOTREACHED() <<
"Unrecognized container type: " << container_type;
61 void AddMpdNameSpaceInfo(XmlNode* mpd) {
64 static const char kXmlNamespace[] =
"urn:mpeg:DASH:schema:MPD:2011";
65 static const char kXmlNamespaceXsi[] =
66 "http://www.w3.org/2001/XMLSchema-instance";
67 static const char kXmlNamespaceXlink[] =
"http://www.w3.org/1999/xlink";
68 static const char kDashSchemaMpd2011[] =
69 "urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd";
70 static const char kCencNamespace[] =
"urn:mpeg:cenc:2013";
72 mpd->SetStringAttribute(
"xmlns", kXmlNamespace);
73 mpd->SetStringAttribute(
"xmlns:xsi", kXmlNamespaceXsi);
74 mpd->SetStringAttribute(
"xmlns:xlink", kXmlNamespaceXlink);
75 mpd->SetStringAttribute(
"xsi:schemaLocation", kDashSchemaMpd2011);
76 mpd->SetStringAttribute(
"xmlns:cenc", kCencNamespace);
79 bool IsPeriodNode(xmlNodePtr node) {
82 return xmlStrcmp(node->name, reinterpret_cast<const xmlChar*>(
"Period")) ==
91 xmlNodePtr FindPeriodNode(XmlNode* xml_node) {
92 for (xmlNodePtr node = xml_node->GetRawPtr()->xmlChildrenNode; node != NULL;
94 if (IsPeriodNode(node))
101 bool Positive(
double d) {
107 std::string XmlDateTimeNowWithOffset(int32_t offset_seconds) {
108 base::Time time = base::Time::Now();
109 time += base::TimeDelta::FromSeconds(offset_seconds);
110 base::Time::Exploded time_exploded;
111 time.UTCExplode(&time_exploded);
113 return base::StringPrintf(
"%4d-%02d-%02dT%02d:%02d:%02dZ", time_exploded.year,
114 time_exploded.month, time_exploded.day_of_month,
115 time_exploded.hour, time_exploded.minute,
116 time_exploded.second);
119 void SetIfPositive(
const char* attr_name,
double value, XmlNode* mpd) {
120 if (Positive(value)) {
121 mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
125 uint32_t GetTimeScale(
const MediaInfo& media_info) {
126 if (media_info.has_reference_time_scale()) {
127 return media_info.reference_time_scale();
130 if (media_info.has_video_info()) {
131 return media_info.video_info().time_scale();
134 if (media_info.has_audio_info()) {
135 return media_info.audio_info().time_scale();
138 LOG(WARNING) <<
"No timescale specified, using 1 as timescale.";
142 uint64_t LastSegmentStartTime(
const SegmentInfo& segment_info) {
143 return segment_info.start_time + segment_info.duration * segment_info.repeat;
147 uint64_t LastSegmentEndTime(
const SegmentInfo& segment_info) {
148 return segment_info.start_time +
149 segment_info.duration * (segment_info.repeat + 1);
152 uint64_t LatestSegmentStartTime(
const std::list<SegmentInfo>& segments) {
153 DCHECK(!segments.empty());
154 const SegmentInfo& latest_segment = segments.back();
155 return LastSegmentStartTime(latest_segment);
160 int SearchTimedOutRepeatIndex(uint64_t timeshift_limit,
161 const SegmentInfo& segment_info) {
162 DCHECK_LE(timeshift_limit, LastSegmentEndTime(segment_info));
163 if (timeshift_limit < segment_info.start_time)
166 return (timeshift_limit - segment_info.start_time) / segment_info.duration;
172 bool WriteXmlCharArrayToOutput(xmlChar* doc,
174 std::string* output) {
177 output->assign(doc, doc + doc_size);
181 bool WriteXmlCharArrayToOutput(xmlChar* doc,
183 media::File* output) {
186 if (output->Write(doc, doc_size) < doc_size)
189 return output->Flush();
192 std::string MakePathRelative(
const std::string& path,
193 const std::string& mpd_dir) {
194 return (path.find(mpd_dir) == 0) ? path.substr(mpd_dir.size()) : path;
200 bool HasRequiredVideoFields(
const MediaInfo_VideoInfo& video_info) {
201 if (!video_info.has_height() || !video_info.has_width()) {
203 <<
"Width and height are required fields for generating a valid MPD.";
208 LOG_IF(WARNING, !video_info.has_time_scale())
209 <<
"Video info does not contain timescale required for "
210 "calculating framerate. @frameRate is required for DASH IOP.";
211 LOG_IF(WARNING, !video_info.has_frame_duration())
212 <<
"Video info does not contain frame duration required "
213 "for calculating framerate. @frameRate is required for DASH IOP.";
214 LOG_IF(WARNING, !video_info.has_pixel_width())
215 <<
"Video info does not contain pixel_width to calculate the sample "
216 "aspect ratio required for DASH IOP.";
217 LOG_IF(WARNING, !video_info.has_pixel_height())
218 <<
"Video info does not contain pixel_height to calculate the sample "
219 "aspect ratio required for DASH IOP.";
230 std::string GetPictureAspectRatio(uint32_t width,
232 uint32_t pixel_width,
233 uint32_t pixel_height) {
234 const uint32_t scaled_width = pixel_width * width;
235 const uint32_t scaled_height = pixel_height * height;
236 const double par =
static_cast<double>(scaled_width) / scaled_height;
240 const uint32_t kLargestPossibleParY = 19;
242 uint32_t par_num = 0;
243 uint32_t par_den = 0;
244 double min_error = 1.0;
245 for (uint32_t den = 1; den <= kLargestPossibleParY; ++den) {
246 uint32_t num = par * den + 0.5;
247 double error = fabs(par - static_cast<double>(num) / den);
248 if (error < min_error) {
252 if (error == 0)
break;
255 VLOG(2) <<
"width*pix_width : height*pixel_height (" << scaled_width <<
":"
256 << scaled_height <<
") reduced to " << par_num <<
":" << par_den
257 <<
" with error " << min_error <<
".";
259 return base::IntToString(par_num) +
":" + base::IntToString(par_den);
264 void AddPictureAspectRatio(
265 const MediaInfo::VideoInfo& video_info,
266 std::set<std::string>* picture_aspect_ratio) {
269 if (picture_aspect_ratio->size() > 1)
272 if (video_info.width() == 0 || video_info.height() == 0 ||
273 video_info.pixel_width() == 0 || video_info.pixel_height() == 0) {
278 picture_aspect_ratio->insert(
"bogus");
279 picture_aspect_ratio->insert(
"entries");
283 const std::string par = GetPictureAspectRatio(
284 video_info.width(), video_info.height(),
285 video_info.pixel_width(), video_info.pixel_height());
286 DVLOG(1) <<
"Setting par as: " << par
287 <<
" for video with width: " << video_info.width()
288 <<
" height: " << video_info.height()
289 <<
" pixel_width: " << video_info.pixel_width() <<
" pixel_height; "
290 << video_info.pixel_height();
291 picture_aspect_ratio->insert(par);
294 std::string RoleToText(AdaptationSet::Role role) {
298 case AdaptationSet::kRoleCaption:
300 case AdaptationSet::kRoleSubtitle:
302 case AdaptationSet::kRoleMain:
304 case AdaptationSet::kRoleAlternate:
306 case AdaptationSet::kRoleSupplementary:
307 return "supplementary";
308 case AdaptationSet::kRoleCommentary:
310 case AdaptationSet::kRoleDub:
322 class LibXmlInitializer {
324 LibXmlInitializer() : initialized_(false) {
325 base::AutoLock lock(lock_);
332 ~LibXmlInitializer() {
333 base::AutoLock lock(lock_);
336 initialized_ =
false;
344 DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
347 class RepresentationStateChangeListenerImpl
348 :
public RepresentationStateChangeListener {
351 RepresentationStateChangeListenerImpl(uint32_t representation_id,
352 AdaptationSet* adaptation_set)
353 : representation_id_(representation_id), adaptation_set_(adaptation_set) {
354 DCHECK(adaptation_set_);
356 virtual ~RepresentationStateChangeListenerImpl() OVERRIDE {}
359 virtual void OnNewSegmentForRepresentation(uint64_t start_time,
360 uint64_t duration) OVERRIDE {
361 adaptation_set_->OnNewSegmentForRepresentation(representation_id_,
362 start_time, duration);
365 virtual void OnSetFrameRateForRepresentation(uint32_t frame_duration,
366 uint32_t timescale) OVERRIDE {
367 adaptation_set_->OnSetFrameRateForRepresentation(representation_id_,
368 frame_duration, timescale);
372 const uint32_t representation_id_;
373 AdaptationSet*
const adaptation_set_;
375 DISALLOW_COPY_AND_ASSIGN(RepresentationStateChangeListenerImpl);
382 mpd_options_(mpd_options),
383 adaptation_sets_deleter_(&adaptation_sets_) {}
385 MpdBuilder::~MpdBuilder() {}
388 base::AutoLock scoped_lock(lock_);
389 base_urls_.push_back(base_url);
393 base::AutoLock scoped_lock(lock_);
394 scoped_ptr<AdaptationSet> adaptation_set(
395 new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_,
397 &representation_counter_));
399 DCHECK(adaptation_set);
400 adaptation_sets_.push_back(adaptation_set.get());
401 return adaptation_set.release();
405 base::AutoLock scoped_lock(lock_);
407 return WriteMpdToOutput(output_file);
411 base::AutoLock scoped_lock(lock_);
413 return WriteMpdToOutput(output);
415 template <
typename OutputType>
416 bool MpdBuilder::WriteMpdToOutput(OutputType* output) {
417 static LibXmlInitializer lib_xml_initializer;
419 xml::ScopedXmlPtr<xmlDoc>::type doc(GenerateMpd());
423 static const int kNiceFormat = 1;
424 int doc_str_size = 0;
425 xmlChar* doc_str = NULL;
426 xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size,
"UTF-8",
429 bool result = WriteXmlCharArrayToOutput(doc_str, doc_str_size, output);
437 xmlDocPtr MpdBuilder::GenerateMpd() {
439 static const char kXmlVersion[] =
"1.0";
440 xml::ScopedXmlPtr<xmlDoc>::type doc(xmlNewDoc(BAD_CAST kXmlVersion));
444 XmlNode period(
"Period");
445 std::list<AdaptationSet*>::iterator adaptation_sets_it =
446 adaptation_sets_.begin();
447 for (; adaptation_sets_it != adaptation_sets_.end(); ++adaptation_sets_it) {
448 xml::ScopedXmlPtr<xmlNode>::type child((*adaptation_sets_it)->GetXml());
449 if (!child.get() || !period.AddChild(child.Pass()))
454 std::list<std::string>::const_iterator base_urls_it = base_urls_.begin();
455 for (; base_urls_it != base_urls_.end(); ++base_urls_it) {
456 XmlNode base_url(
"BaseURL");
457 base_url.SetContent(*base_urls_it);
459 if (!mpd.AddChild(base_url.PassScopedPtr()))
463 if (type_ == kDynamic) {
465 period.SetStringAttribute(
"start",
"PT0S");
468 if (!mpd.AddChild(period.PassScopedPtr()))
471 AddMpdNameSpaceInfo(&mpd);
472 AddCommonMpdInfo(&mpd);
475 AddStaticMpdInfo(&mpd);
478 AddDynamicMpdInfo(&mpd);
481 NOTREACHED() <<
"Unknown MPD type: " << type_;
486 xmlDocSetRootElement(doc.get(), mpd.Release());
487 return doc.release();
490 void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
491 if (Positive(mpd_options_.min_buffer_time)) {
492 mpd_node->SetStringAttribute(
493 "minBufferTime", SecondsToXmlDuration(mpd_options_.min_buffer_time));
495 LOG(ERROR) <<
"minBufferTime value not specified.";
500 void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
502 DCHECK_EQ(MpdBuilder::kStatic, type_);
504 static const char kStaticMpdType[] =
"static";
505 static const char kStaticMpdProfile[] =
506 "urn:mpeg:dash:profile:isoff-on-demand:2011";
507 mpd_node->SetStringAttribute(
"type", kStaticMpdType);
508 mpd_node->SetStringAttribute(
"profiles", kStaticMpdProfile);
509 mpd_node->SetStringAttribute(
510 "mediaPresentationDuration",
511 SecondsToXmlDuration(GetStaticMpdDuration(mpd_node)));
514 void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
516 DCHECK_EQ(MpdBuilder::kDynamic, type_);
518 static const char kDynamicMpdType[] =
"dynamic";
519 static const char kDynamicMpdProfile[] =
520 "urn:mpeg:dash:profile:isoff-live:2011";
521 mpd_node->SetStringAttribute(
"type", kDynamicMpdType);
522 mpd_node->SetStringAttribute(
"profiles", kDynamicMpdProfile);
526 if (availability_start_time_.empty()) {
527 double earliest_presentation_time;
528 if (GetEarliestTimestamp(&earliest_presentation_time)) {
529 availability_start_time_ =
530 XmlDateTimeNowWithOffset(mpd_options_.availability_time_offset -
531 std::ceil(earliest_presentation_time));
533 LOG(ERROR) <<
"Could not determine the earliest segment presentation "
534 "time for availabilityStartTime calculation.";
538 if (!availability_start_time_.empty())
539 mpd_node->SetStringAttribute(
"availabilityStartTime",
540 availability_start_time_);
542 if (Positive(mpd_options_.minimum_update_period)) {
543 mpd_node->SetStringAttribute(
544 "minimumUpdatePeriod",
545 SecondsToXmlDuration(mpd_options_.minimum_update_period));
547 LOG(WARNING) <<
"The profile is dynamic but no minimumUpdatePeriod "
551 SetIfPositive(
"timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth,
553 SetIfPositive(
"suggestedPresentationDelay",
554 mpd_options_.suggested_presentation_delay, mpd_node);
557 float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
559 DCHECK_EQ(MpdBuilder::kStatic, type_);
561 xmlNodePtr period_node = FindPeriodNode(mpd_node);
562 DCHECK(period_node) <<
"Period element must be a child of mpd_node.";
563 DCHECK(IsPeriodNode(period_node));
568 float max_duration = 0.0f;
569 for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node);
570 adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
571 for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
573 representation = xmlNextElementSibling(representation)) {
574 float duration = 0.0f;
575 if (GetDurationAttribute(representation, &duration)) {
576 max_duration = max_duration > duration ? max_duration : duration;
580 xmlUnsetProp(representation, BAD_CAST
"duration");
588 bool MpdBuilder::GetEarliestTimestamp(
double* timestamp_seconds) {
589 DCHECK(timestamp_seconds);
591 double earliest_timestamp(-1);
592 for (std::list<AdaptationSet*>::const_iterator iter =
593 adaptation_sets_.begin();
594 iter != adaptation_sets_.end(); ++iter) {
596 if ((*iter)->GetEarliestTimestamp(×tamp) &&
597 ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
598 earliest_timestamp = timestamp;
601 if (earliest_timestamp < 0)
604 *timestamp_seconds = earliest_timestamp;
609 MediaInfo* media_info) {
611 const std::string kFileProtocol(
"file://");
612 std::string mpd_file_path = (mpd_path.find(kFileProtocol) == 0)
613 ? mpd_path.substr(kFileProtocol.size())
616 if (!mpd_file_path.empty()) {
618 FilePath(mpd_file_path).DirName().AsEndingWithSeparator().value());
619 if (!mpd_dir.empty()) {
620 if (media_info->has_media_file_name()) {
621 media_info->set_media_file_name(
622 MakePathRelative(media_info->media_file_name(), mpd_dir));
624 if (media_info->has_init_segment_name()) {
625 media_info->set_init_segment_name(
626 MakePathRelative(media_info->init_segment_name(), mpd_dir));
628 if (media_info->has_segment_template()) {
629 media_info->set_segment_template(
630 MakePathRelative(media_info->segment_template(), mpd_dir));
637 const std::string& lang,
639 MpdBuilder::MpdType mpd_type,
640 base::AtomicSequenceNumber* counter)
641 : representations_deleter_(&representations_),
642 representation_counter_(counter),
643 id_(adaptation_set_id),
645 mpd_options_(mpd_options),
647 group_(kAdaptationSetGroupNotSet),
648 segments_aligned_(kSegmentAlignmentUnknown),
649 force_set_segment_alignment_(false) {
653 AdaptationSet::~AdaptationSet() {}
656 base::AutoLock scoped_lock(lock_);
657 const uint32_t representation_id = representation_counter_->GetNext();
660 scoped_ptr<RepresentationStateChangeListener> listener(
661 new RepresentationStateChangeListenerImpl(representation_id,
this));
663 media_info, mpd_options_, representation_id, listener.Pass()));
665 if (!representation->Init())
670 if (media_info.has_video_info()) {
671 const MediaInfo::VideoInfo& video_info = media_info.video_info();
672 DCHECK(video_info.has_width());
673 DCHECK(video_info.has_height());
674 video_widths_.insert(video_info.width());
675 video_heights_.insert(video_info.height());
677 if (video_info.has_time_scale() && video_info.has_frame_duration())
678 RecordFrameRate(video_info.frame_duration(), video_info.time_scale());
680 AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
683 if (media_info.has_video_info()) {
684 content_type_ =
"video";
685 }
else if (media_info.has_audio_info()) {
686 content_type_ =
"audio";
689 representations_.push_back(representation.get());
690 return representation.release();
695 base::AutoLock scoped_lock(lock_);
696 content_protection_elements_.push_back(content_protection_element);
697 RemoveDuplicateAttributes(&content_protection_elements_.back());
701 const std::string& pssh) {
702 base::AutoLock scoped_lock(lock_);
703 UpdateContentProtectionPsshHelper(drm_uuid, pssh,
704 &content_protection_elements_);
714 base::AutoLock scoped_lock(lock_);
715 AdaptationSetXmlNode adaptation_set;
717 if (!adaptation_set.AddContentProtectionElements(
718 content_protection_elements_)) {
719 return xml::ScopedXmlPtr<xmlNode>::type();
721 for (std::set<Role>::const_iterator role_it = roles_.begin();
722 role_it != roles_.end(); ++role_it) {
723 adaptation_set.AddRoleElement(
"urn:mpeg:dash:role:2011",
724 RoleToText(*role_it));
727 std::list<Representation*>::iterator representation_it =
728 representations_.begin();
730 for (; representation_it != representations_.end(); ++representation_it) {
731 xml::ScopedXmlPtr<xmlNode>::type child((*representation_it)->GetXml());
732 if (!child || !adaptation_set.AddChild(child.Pass()))
733 return xml::ScopedXmlPtr<xmlNode>::type();
736 adaptation_set.SetId(id_);
737 adaptation_set.SetStringAttribute(
"contentType", content_type_);
738 if (!lang_.empty() && lang_ !=
"und") {
743 if (video_widths_.size() == 1) {
744 adaptation_set.SetIntegerAttribute(
"width", *video_widths_.begin());
745 }
else if (video_widths_.size() > 1) {
746 adaptation_set.SetIntegerAttribute(
"maxWidth", *video_widths_.rbegin());
748 if (video_heights_.size() == 1) {
749 adaptation_set.SetIntegerAttribute(
"height", *video_heights_.begin());
750 }
else if (video_heights_.size() > 1) {
751 adaptation_set.SetIntegerAttribute(
"maxHeight", *video_heights_.rbegin());
754 if (video_frame_rates_.size() == 1) {
755 adaptation_set.SetStringAttribute(
"frameRate",
756 video_frame_rates_.begin()->second);
757 }
else if (video_frame_rates_.size() > 1) {
758 adaptation_set.SetStringAttribute(
"maxFrameRate",
759 video_frame_rates_.rbegin()->second);
763 if (mpd_type_ == MpdBuilder::kStatic) {
764 CheckVodSegmentAlignment();
767 if (segments_aligned_ == kSegmentAlignmentTrue) {
768 adaptation_set.SetStringAttribute(mpd_type_ == MpdBuilder::kStatic
769 ?
"subSegmentAlignment"
770 :
"segmentAlignment",
774 if (picture_aspect_ratio_.size() == 1)
775 adaptation_set.SetStringAttribute(
"par", *picture_aspect_ratio_.begin());
778 adaptation_set.SetIntegerAttribute(
"group", group_);
780 return adaptation_set.PassScopedPtr();
785 segment_alignment ? kSegmentAlignmentTrue : kSegmentAlignmentFalse;
786 force_set_segment_alignment_ =
true;
790 group_ = group_number;
807 base::AutoLock scoped_lock(lock_);
809 if (mpd_type_ == MpdBuilder::kDynamic) {
810 CheckLiveSegmentAlignment(representation_id, start_time, duration);
812 representation_segment_start_times_[representation_id].push_back(
819 uint32_t frame_duration,
820 uint32_t timescale) {
821 base::AutoLock scoped_lock(lock_);
822 RecordFrameRate(frame_duration, timescale);
825 bool AdaptationSet::GetEarliestTimestamp(
double* timestamp_seconds) {
826 DCHECK(timestamp_seconds);
828 base::AutoLock scoped_lock(lock_);
829 double earliest_timestamp(-1);
830 for (std::list<Representation*>::const_iterator iter =
831 representations_.begin();
832 iter != representations_.end(); ++iter) {
834 if ((*iter)->GetEarliestTimestamp(×tamp) &&
835 ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
836 earliest_timestamp = timestamp;
839 if (earliest_timestamp < 0)
842 *timestamp_seconds = earliest_timestamp;
870 void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id,
873 if (segments_aligned_ == kSegmentAlignmentFalse ||
874 force_set_segment_alignment_) {
878 std::list<uint64_t>& representation_start_times =
879 representation_segment_start_times_[representation_id];
880 representation_start_times.push_back(start_time);
883 if (representation_segment_start_times_.size() != representations_.size())
886 DCHECK(!representation_start_times.empty());
887 const uint64_t expected_start_time = representation_start_times.front();
888 for (RepresentationTimeline::const_iterator it =
889 representation_segment_start_times_.begin();
890 it != representation_segment_start_times_.end(); ++it) {
894 if (it->second.empty())
897 if (expected_start_time != it->second.front()) {
900 segments_aligned_ = kSegmentAlignmentFalse;
901 representation_segment_start_times_.clear();
905 segments_aligned_ = kSegmentAlignmentTrue;
907 for (RepresentationTimeline::iterator it =
908 representation_segment_start_times_.begin();
909 it != representation_segment_start_times_.end(); ++it) {
910 it->second.pop_front();
916 void AdaptationSet::CheckVodSegmentAlignment() {
917 if (segments_aligned_ == kSegmentAlignmentFalse ||
918 force_set_segment_alignment_) {
921 if (representation_segment_start_times_.empty())
923 if (representation_segment_start_times_.size() == 1) {
924 segments_aligned_ = kSegmentAlignmentTrue;
931 const std::list<uint64_t>& expected_time_line =
932 representation_segment_start_times_.begin()->second;
934 bool all_segment_time_line_same_length =
true;
936 RepresentationTimeline::const_iterator it =
937 representation_segment_start_times_.begin();
938 for (++it; it != representation_segment_start_times_.end(); ++it) {
939 const std::list<uint64_t>& other_time_line = it->second;
940 if (expected_time_line.size() != other_time_line.size()) {
941 all_segment_time_line_same_length =
false;
944 const std::list<uint64_t>* longer_list = &other_time_line;
945 const std::list<uint64_t>* shorter_list = &expected_time_line;
946 if (expected_time_line.size() > other_time_line.size()) {
947 shorter_list = &other_time_line;
948 longer_list = &expected_time_line;
951 if (!std::equal(shorter_list->begin(), shorter_list->end(),
952 longer_list->begin())) {
954 segments_aligned_ = kSegmentAlignmentFalse;
955 representation_segment_start_times_.clear();
966 if (!all_segment_time_line_same_length) {
967 segments_aligned_ = kSegmentAlignmentUnknown;
971 segments_aligned_ = kSegmentAlignmentTrue;
976 void AdaptationSet::RecordFrameRate(uint32_t frame_duration,
977 uint32_t timescale) {
978 if (frame_duration == 0) {
979 LOG(ERROR) <<
"Frame duration is 0 and cannot be set.";
982 video_frame_rates_[
static_cast<double>(timescale) / frame_duration] =
983 base::IntToString(timescale) +
"/" + base::IntToString(frame_duration);
987 const MediaInfo& media_info,
990 scoped_ptr<RepresentationStateChangeListener> state_change_listener)
991 : media_info_(media_info),
994 mpd_options_(mpd_options),
996 state_change_listener_(state_change_listener.Pass()) {}
998 Representation::~Representation() {}
1001 codecs_ = GetCodecs(media_info_);
1002 if (codecs_.empty()) {
1003 LOG(ERROR) <<
"Missing codec info in MediaInfo.";
1007 const bool has_video_info = media_info_.has_video_info();
1008 const bool has_audio_info = media_info_.has_audio_info();
1010 if (!has_video_info && !has_audio_info) {
1014 LOG(ERROR) <<
"Representation needs video or audio.";
1018 if (media_info_.container_type() == MediaInfo::CONTAINER_UNKNOWN) {
1019 LOG(ERROR) <<
"'container_type' in MediaInfo cannot be CONTAINER_UNKNOWN.";
1025 if (has_video_info) {
1026 mime_type_ = GetVideoMimeType();
1027 if (!HasRequiredVideoFields(media_info_.video_info())) {
1028 LOG(ERROR) <<
"Missing required fields to create a video Representation.";
1031 }
else if (has_audio_info) {
1032 mime_type_ = GetAudioMimeType();
1040 base::AutoLock scoped_lock(lock_);
1041 content_protection_elements_.push_back(content_protection_element);
1042 RemoveDuplicateAttributes(&content_protection_elements_.back());
1046 const std::string& pssh) {
1047 base::AutoLock scoped_lock(lock_);
1048 UpdateContentProtectionPsshHelper(drm_uuid, pssh,
1049 &content_protection_elements_);
1055 if (start_time == 0 && duration == 0) {
1056 LOG(WARNING) <<
"Got segment with start_time and duration == 0. Ignoring.";
1060 base::AutoLock scoped_lock(lock_);
1061 if (state_change_listener_)
1062 state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
1063 if (IsContiguous(start_time, duration, size)) {
1064 ++segment_infos_.back().repeat;
1067 segment_infos_.push_back(s);
1070 bandwidth_estimator_.AddBlock(
1071 size, static_cast<double>(duration) / media_info_.reference_time_scale());
1074 DCHECK_GE(segment_infos_.size(), 1u);
1078 base::AutoLock scoped_lock(lock_);
1080 if (media_info_.has_video_info()) {
1081 media_info_.mutable_video_info()->set_frame_duration(sample_duration);
1082 if (state_change_listener_) {
1083 state_change_listener_->OnSetFrameRateForRepresentation(
1084 sample_duration, media_info_.video_info().time_scale());
1096 base::AutoLock scoped_lock(lock_);
1098 if (!HasRequiredMediaInfoFields()) {
1099 LOG(ERROR) <<
"MediaInfo missing required fields.";
1100 return xml::ScopedXmlPtr<xmlNode>::type();
1103 const uint64_t bandwidth = media_info_.has_bandwidth()
1104 ? media_info_.bandwidth()
1105 : bandwidth_estimator_.Estimate();
1107 DCHECK(!(HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)));
1109 RepresentationXmlNode representation;
1111 representation.SetId(id_);
1112 representation.SetIntegerAttribute(
"bandwidth", bandwidth);
1113 representation.SetStringAttribute(
"codecs", codecs_);
1114 representation.SetStringAttribute(
"mimeType", mime_type_);
1116 const bool has_video_info = media_info_.has_video_info();
1117 const bool has_audio_info = media_info_.has_audio_info();
1119 if (has_video_info &&
1120 !representation.AddVideoInfo(media_info_.video_info())) {
1121 LOG(ERROR) <<
"Failed to add video info to Representation XML.";
1122 return xml::ScopedXmlPtr<xmlNode>::type();
1125 if (has_audio_info &&
1126 !representation.AddAudioInfo(media_info_.audio_info())) {
1127 LOG(ERROR) <<
"Failed to add audio info to Representation XML.";
1128 return xml::ScopedXmlPtr<xmlNode>::type();
1131 if (!representation.AddContentProtectionElements(
1132 content_protection_elements_)) {
1133 return xml::ScopedXmlPtr<xmlNode>::type();
1136 if (HasVODOnlyFields(media_info_) &&
1137 !representation.AddVODOnlyInfo(media_info_)) {
1138 LOG(ERROR) <<
"Failed to add VOD segment info.";
1139 return xml::ScopedXmlPtr<xmlNode>::type();
1142 if (HasLiveOnlyFields(media_info_) &&
1143 !representation.AddLiveOnlyInfo(media_info_, segment_infos_,
1145 LOG(ERROR) <<
"Failed to add Live info.";
1146 return xml::ScopedXmlPtr<xmlNode>::type();
1151 return representation.PassScopedPtr();
1154 bool Representation::HasRequiredMediaInfoFields() {
1155 if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) {
1156 LOG(ERROR) <<
"MediaInfo cannot have both VOD and Live fields.";
1160 if (!media_info_.has_container_type()) {
1161 LOG(ERROR) <<
"MediaInfo missing required field: container_type.";
1165 if (HasVODOnlyFields(media_info_) && !media_info_.has_bandwidth()) {
1166 LOG(ERROR) <<
"Missing 'bandwidth' field. MediaInfo requires bandwidth for "
1167 "static profile for generating a valid MPD.";
1171 VLOG_IF(3, HasLiveOnlyFields(media_info_) && !media_info_.has_bandwidth())
1172 <<
"MediaInfo missing field 'bandwidth'. Using estimated from "
1178 bool Representation::IsContiguous(uint64_t start_time,
1180 uint64_t size)
const {
1181 if (segment_infos_.empty())
1185 const SegmentInfo& previous = segment_infos_.back();
1186 const uint64_t previous_segment_end_time =
1187 previous.start_time + previous.duration * (previous.repeat + 1);
1188 if (previous_segment_end_time == start_time &&
1189 segment_infos_.back().duration == duration) {
1194 const uint64_t previous_segment_start_time =
1195 previous.start_time + previous.duration * previous.repeat;
1196 if (previous_segment_start_time >= start_time) {
1197 LOG(ERROR) <<
"Segments should not be out of order segment. Adding segment "
1198 "with start_time == "
1199 << start_time <<
" but the previous segment starts at "
1200 << previous.start_time <<
".";
1205 const uint64_t kRoundingErrorGrace = 5;
1206 if (previous_segment_end_time + kRoundingErrorGrace < start_time) {
1207 LOG(WARNING) <<
"Found a gap of size "
1208 << (start_time - previous_segment_end_time)
1209 <<
" > kRoundingErrorGrace (" << kRoundingErrorGrace
1210 <<
"). The new segment starts at " << start_time
1211 <<
" but the previous segment ends at "
1212 << previous_segment_end_time <<
".";
1217 if (start_time < previous_segment_end_time - kRoundingErrorGrace) {
1219 <<
"Segments should not be overlapping. The new segment starts at "
1220 << start_time <<
" but the previous segment ends at "
1221 << previous_segment_end_time <<
".";
1229 void Representation::SlideWindow() {
1230 DCHECK(!segment_infos_.empty());
1231 if (mpd_options_.time_shift_buffer_depth <= 0.0)
1234 const uint32_t time_scale = GetTimeScale(media_info_);
1235 DCHECK_GT(time_scale, 0u);
1237 uint64_t time_shift_buffer_depth =
1238 static_cast<uint64_t
>(mpd_options_.time_shift_buffer_depth * time_scale);
1242 const uint64_t current_play_time = LatestSegmentStartTime(segment_infos_);
1243 if (current_play_time <= time_shift_buffer_depth)
1246 const uint64_t timeshift_limit = current_play_time - time_shift_buffer_depth;
1250 std::list<SegmentInfo>::iterator first = segment_infos_.begin();
1251 std::list<SegmentInfo>::iterator last = first;
1252 size_t num_segments_removed = 0;
1253 for (; last != segment_infos_.end(); ++last) {
1254 const uint64_t last_segment_end_time = LastSegmentEndTime(*last);
1255 if (timeshift_limit < last_segment_end_time)
1257 num_segments_removed += last->repeat + 1;
1259 segment_infos_.erase(first, last);
1260 start_number_ += num_segments_removed;
1263 SegmentInfo* first_segment_info = &segment_infos_.front();
1264 DCHECK_LE(timeshift_limit, LastSegmentEndTime(*first_segment_info));
1267 const int repeat_index =
1268 SearchTimedOutRepeatIndex(timeshift_limit, *first_segment_info);
1269 CHECK_GE(repeat_index, 0);
1270 if (repeat_index == 0)
1273 first_segment_info->start_time = first_segment_info->start_time +
1274 first_segment_info->duration * repeat_index;
1276 first_segment_info->repeat = first_segment_info->repeat - repeat_index;
1277 start_number_ += repeat_index;
1280 std::string Representation::GetVideoMimeType()
const {
1281 return GetMimeType(
"video", media_info_.container_type());
1284 std::string Representation::GetAudioMimeType()
const {
1285 return GetMimeType(
"audio", media_info_.container_type());
1288 bool Representation::GetEarliestTimestamp(
double* timestamp_seconds) {
1289 DCHECK(timestamp_seconds);
1291 base::AutoLock scoped_lock(lock_);
1292 if (segment_infos_.empty())
1295 *timestamp_seconds =
static_cast<double>(segment_infos_.begin()->start_time) /
1296 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::ScopedXmlPtr< xmlNode >::type GetXml()
static void MakePathsRelativeToMpd(const std::string &mpd_path, MediaInfo *media_info)
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)
xml::ScopedXmlPtr< xmlNode >::type GetXml()
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)
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)