7 #include "packager/mpd/base/mpd_builder.h" 11 #include "packager/base/files/file_path.h" 12 #include "packager/base/logging.h" 13 #include "packager/base/strings/string_number_conversions.h" 14 #include "packager/base/strings/stringprintf.h" 15 #include "packager/base/synchronization/lock.h" 16 #include "packager/base/time/default_clock.h" 17 #include "packager/base/time/time.h" 18 #include "packager/mpd/base/adaptation_set.h" 19 #include "packager/mpd/base/mpd_utils.h" 20 #include "packager/mpd/base/period.h" 21 #include "packager/mpd/base/representation.h" 22 #include "packager/mpd/base/xml/xml_node.h" 23 #include "packager/version/version.h" 32 void AddMpdNameSpaceInfo(XmlNode* mpd) {
35 static const char kXmlNamespace[] =
"urn:mpeg:dash:schema:mpd:2011";
36 static const char kXmlNamespaceXsi[] =
37 "http://www.w3.org/2001/XMLSchema-instance";
38 static const char kXmlNamespaceXlink[] =
"http://www.w3.org/1999/xlink";
39 static const char kDashSchemaMpd2011[] =
40 "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
41 static const char kCencNamespace[] =
"urn:mpeg:cenc:2013";
43 mpd->SetStringAttribute(
"xmlns", kXmlNamespace);
44 mpd->SetStringAttribute(
"xmlns:xsi", kXmlNamespaceXsi);
45 mpd->SetStringAttribute(
"xmlns:xlink", kXmlNamespaceXlink);
46 mpd->SetStringAttribute(
"xsi:schemaLocation", kDashSchemaMpd2011);
47 mpd->SetStringAttribute(
"xmlns:cenc", kCencNamespace);
50 bool Positive(
double d) {
56 std::string XmlDateTimeNowWithOffset(
57 int32_t offset_seconds,
59 base::Time time = clock->Now();
60 time += base::TimeDelta::FromSeconds(offset_seconds);
61 base::Time::Exploded time_exploded;
62 time.UTCExplode(&time_exploded);
64 return base::StringPrintf(
"%4d-%02d-%02dT%02d:%02d:%02dZ", time_exploded.year,
65 time_exploded.month, time_exploded.day_of_month,
66 time_exploded.hour, time_exploded.minute,
67 time_exploded.second);
70 void SetIfPositive(
const char* attr_name,
double value, XmlNode* mpd) {
71 if (Positive(value)) {
72 mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
76 std::string MakePathRelative(
const std::string& media_path,
77 const FilePath& parent_path) {
78 FilePath relative_path;
79 const FilePath child_path = FilePath::FromUTF8Unsafe(media_path);
81 parent_path.AppendRelativePath(child_path, &relative_path);
83 relative_path = child_path;
84 return relative_path.NormalizePathSeparatorsTo(
'/').AsUTF8Unsafe();
88 class LibXmlInitializer {
90 LibXmlInitializer() : initialized_(false) {
91 base::AutoLock lock(lock_);
98 ~LibXmlInitializer() {
99 base::AutoLock lock(lock_);
102 initialized_ =
false;
110 DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
116 : mpd_options_(mpd_options), clock_(new
base::DefaultClock()) {}
118 MpdBuilder::~MpdBuilder() {}
121 base_urls_.push_back(base_url);
125 for (
auto& period : periods_) {
126 const double kPeriodTimeDriftThresholdInSeconds = 1.0;
128 std::fabs(period->start_time_in_seconds() - start_time_in_seconds) <
129 kPeriodTimeDriftThresholdInSeconds;
133 periods_.emplace_back(
134 new Period(period_counter_.GetNext(), start_time_in_seconds, mpd_options_,
135 &adaptation_set_counter_, &representation_counter_));
136 return periods_.back().get();
141 static LibXmlInitializer lib_xml_initializer;
143 xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
147 static const int kNiceFormat = 1;
148 int doc_str_size = 0;
149 xmlChar* doc_str =
nullptr;
150 xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size,
"UTF-8",
152 output->assign(doc_str, doc_str + doc_str_size);
160 xmlDocPtr MpdBuilder::GenerateMpd() {
162 static const char kXmlVersion[] =
"1.0";
163 xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
167 for (
const std::string& base_url : base_urls_) {
168 XmlNode xml_base_url(
"BaseURL");
177 if (mpd_options_.mpd_type == MpdType::kStatic && periods_.size() > 1) {
180 double next_period_start_time = GetStaticMpdDuration();
182 periods_.rbegin(), periods_.rend(),
183 [&next_period_start_time](
const std::unique_ptr<Period>& period) {
184 period->set_duration_seconds(next_period_start_time -
185 period->start_time_in_seconds());
186 next_period_start_time = period->start_time_in_seconds();
190 for (
const auto& period : periods_) {
191 xml::scoped_xml_ptr<xmlNode> period_node(period->GetXml());
192 if (!period_node || !mpd.
AddChild(std::move(period_node)))
196 AddMpdNameSpaceInfo(&mpd);
198 static const char kOnDemandProfile[] =
199 "urn:mpeg:dash:profile:isoff-on-demand:2011";
200 static const char kLiveProfile[] =
201 "urn:mpeg:dash:profile:isoff-live:2011";
202 switch (mpd_options_.dash_profile) {
203 case DashProfile::kOnDemand:
206 case DashProfile::kLive:
210 NOTREACHED() <<
"Unknown DASH profile: " 211 <<
static_cast<int>(mpd_options_.dash_profile);
215 AddCommonMpdInfo(&mpd);
216 switch (mpd_options_.mpd_type) {
217 case MpdType::kStatic:
218 AddStaticMpdInfo(&mpd);
220 case MpdType::kDynamic:
221 AddDynamicMpdInfo(&mpd);
224 NOTREACHED() <<
"Unknown MPD type: " 225 <<
static_cast<int>(mpd_options_.mpd_type);
230 const std::string version = GetPackagerVersion();
231 if (!version.empty()) {
232 std::string version_string =
233 base::StringPrintf(
"Generated with %s version %s",
234 GetPackagerProjectUrl().c_str(), version.c_str());
235 xml::scoped_xml_ptr<xmlNode> comment(
236 xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str()));
237 xmlDocSetRootElement(doc.get(), comment.get());
238 xmlAddSibling(comment.release(), mpd.
Release());
240 xmlDocSetRootElement(doc.get(), mpd.
Release());
242 return doc.release();
245 void MpdBuilder::AddCommonMpdInfo(
XmlNode* mpd_node) {
251 LOG(ERROR) <<
"minBufferTime value not specified.";
256 void MpdBuilder::AddStaticMpdInfo(
XmlNode* mpd_node) {
258 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
260 static const char kStaticMpdType[] =
"static";
263 SecondsToXmlDuration(GetStaticMpdDuration()));
266 void MpdBuilder::AddDynamicMpdInfo(
XmlNode* mpd_node) {
268 DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
270 static const char kDynamicMpdType[] =
"dynamic";
275 XmlDateTimeNowWithOffset(0, clock_.get()));
279 if (availability_start_time_.empty()) {
280 double earliest_presentation_time;
281 if (GetEarliestTimestamp(&earliest_presentation_time)) {
282 availability_start_time_ = XmlDateTimeNowWithOffset(
283 -std::ceil(earliest_presentation_time), clock_.get());
285 LOG(ERROR) <<
"Could not determine the earliest segment presentation " 286 "time for availabilityStartTime calculation.";
290 if (!availability_start_time_.empty())
292 availability_start_time_);
296 "minimumUpdatePeriod",
299 LOG(WARNING) <<
"The profile is dynamic but no minimumUpdatePeriod " 303 SetIfPositive(
"timeShiftBufferDepth",
305 SetIfPositive(
"suggestedPresentationDelay",
306 mpd_options_.mpd_params.suggested_presentation_delay, mpd_node);
309 float MpdBuilder::GetStaticMpdDuration() {
310 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
312 if (periods_.empty()) {
313 LOG(WARNING) <<
"No Period found. Set MPD duration to 0.";
320 float max_duration = 0.0f;
325 for (
const auto* adaptation_set : periods_.front()->GetAdaptationSets()) {
326 for (
const auto* representation : adaptation_set->GetRepresentations()) {
328 std::max(representation->GetDurationSeconds(), max_duration);
334 bool MpdBuilder::GetEarliestTimestamp(
double* timestamp_seconds) {
335 DCHECK(timestamp_seconds);
336 DCHECK(!periods_.empty());
337 double timestamp = 0;
338 double earliest_timestamp = -1;
340 for (
const auto* adaptation_set : periods_.front()->GetAdaptationSets()) {
341 for (
const auto* representation : adaptation_set->GetRepresentations()) {
342 if (representation->GetEarliestTimestamp(×tamp) &&
343 (earliest_timestamp < 0 || timestamp < earliest_timestamp)) {
344 earliest_timestamp = timestamp;
348 if (earliest_timestamp < 0)
350 *timestamp_seconds = earliest_timestamp;
355 MediaInfo* media_info) {
357 const std::string kFileProtocol(
"file://");
358 std::string mpd_file_path = (mpd_path.find(kFileProtocol) == 0)
359 ? mpd_path.substr(kFileProtocol.size())
362 if (!mpd_file_path.empty()) {
363 const FilePath mpd_dir(FilePath::FromUTF8Unsafe(mpd_file_path)
365 .AsEndingWithSeparator());
366 if (!mpd_dir.empty()) {
367 if (media_info->has_media_file_name()) {
368 media_info->set_media_file_name(
369 MakePathRelative(media_info->media_file_name(), mpd_dir));
371 if (media_info->has_init_segment_name()) {
372 media_info->set_init_segment_name(
373 MakePathRelative(media_info->init_segment_name(), mpd_dir));
375 if (media_info->has_segment_template()) {
376 media_info->set_segment_template(
377 MakePathRelative(media_info->segment_template(), mpd_dir));
scoped_xml_ptr< xmlNode > PassScopedPtr()
All the methods that are virtual are virtual for mocking.
void SetStringAttribute(const char *attribute_name, const std::string &attribute)
bool AddChild(scoped_xml_ptr< xmlNode > child)
virtual Period * GetOrCreatePeriod(double start_time_in_seconds)
MpdBuilder(const MpdOptions &mpd_options)
void AddBaseUrl(const std::string &base_url)
virtual bool ToString(std::string *output)
static void MakePathsRelativeToMpd(const std::string &mpd_path, MediaInfo *media_info)
double minimum_update_period
void SetContent(const std::string &content)
double time_shift_buffer_depth