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/optional.h"
14 #include "packager/base/strings/string_number_conversions.h"
15 #include "packager/base/strings/stringprintf.h"
16 #include "packager/base/synchronization/lock.h"
17 #include "packager/base/time/default_clock.h"
18 #include "packager/base/time/time.h"
19 #include "packager/media/base/rcheck.h"
20 #include "packager/mpd/base/adaptation_set.h"
21 #include "packager/mpd/base/mpd_utils.h"
22 #include "packager/mpd/base/period.h"
23 #include "packager/mpd/base/representation.h"
24 #include "packager/version/version.h"
33 bool AddMpdNameSpaceInfo(XmlNode* mpd) {
36 const std::set<std::string> namespaces = mpd->ExtractReferencedNamespaces();
38 static const char kXmlNamespace[] =
"urn:mpeg:dash:schema:mpd:2011";
39 static const char kXmlNamespaceXsi[] =
40 "http://www.w3.org/2001/XMLSchema-instance";
41 static const char kDashSchemaMpd2011[] =
42 "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
44 RCHECK(mpd->SetStringAttribute(
"xmlns", kXmlNamespace));
45 RCHECK(mpd->SetStringAttribute(
"xmlns:xsi", kXmlNamespaceXsi));
46 RCHECK(mpd->SetStringAttribute(
"xsi:schemaLocation", kDashSchemaMpd2011));
48 static const char kCencNamespace[] =
"urn:mpeg:cenc:2013";
49 static const char kMarlinNamespace[] =
50 "urn:marlin:mas:1-0:services:schemas:mpd";
51 static const char kXmlNamespaceXlink[] =
"http://www.w3.org/1999/xlink";
52 static const char kMsprNamespace[] =
"urn:microsoft:playready";
54 const std::map<std::string, std::string> uris = {
55 {
"cenc", kCencNamespace},
56 {
"mas", kMarlinNamespace},
57 {
"xlink", kXmlNamespaceXlink},
58 {
"mspr", kMsprNamespace},
61 for (
const std::string& namespace_name : namespaces) {
62 auto iter = uris.find(namespace_name);
63 CHECK(iter != uris.end()) <<
" unexpected namespace " << namespace_name;
65 RCHECK(mpd->SetStringAttribute(
66 base::StringPrintf(
"xmlns:%s", namespace_name.c_str()).c_str(),
72 bool Positive(
double d) {
78 std::string XmlDateTimeNowWithOffset(
79 int32_t offset_seconds,
81 base::Time time = clock->Now();
82 time += base::TimeDelta::FromSeconds(offset_seconds);
83 base::Time::Exploded time_exploded;
84 time.UTCExplode(&time_exploded);
86 return base::StringPrintf(
"%4d-%02d-%02dT%02d:%02d:%02dZ", time_exploded.year,
87 time_exploded.month, time_exploded.day_of_month,
88 time_exploded.hour, time_exploded.minute,
89 time_exploded.second);
92 bool SetIfPositive(
const char* attr_name,
double value, XmlNode* mpd) {
93 return !Positive(value) ||
94 mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
97 std::string MakePathRelative(
const std::string& media_path,
98 const FilePath& parent_path) {
99 FilePath relative_path;
100 const FilePath child_path = FilePath::FromUTF8Unsafe(media_path);
101 const bool is_child =
102 parent_path.AppendRelativePath(child_path, &relative_path);
104 relative_path = child_path;
105 return relative_path.NormalizePathSeparatorsTo(
'/').AsUTF8Unsafe();
109 class LibXmlInitializer {
111 LibXmlInitializer() : initialized_(false) {
112 base::AutoLock lock(lock_);
119 ~LibXmlInitializer() {
120 base::AutoLock lock(lock_);
123 initialized_ =
false;
131 DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
137 : mpd_options_(mpd_options), clock_(new base::DefaultClock()) {}
139 MpdBuilder::~MpdBuilder() {}
142 base_urls_.push_back(base_url);
146 for (
auto& period : periods_) {
147 const double kPeriodTimeDriftThresholdInSeconds = 1.0;
149 std::fabs(period->start_time_in_seconds() - start_time_in_seconds) <
150 kPeriodTimeDriftThresholdInSeconds;
154 periods_.emplace_back(
new Period(period_counter_++, start_time_in_seconds,
155 mpd_options_, &representation_counter_));
156 return periods_.back().get();
161 static LibXmlInitializer lib_xml_initializer;
163 auto mpd = GenerateMpd();
167 std::string version = GetPackagerVersion();
168 if (!version.empty()) {
170 base::StringPrintf(
"Generated with %s version %s",
171 GetPackagerProjectUrl().c_str(), version.c_str());
173 *output = mpd->ToString(version);
177 base::Optional<xml::XmlNode> MpdBuilder::GenerateMpd() {
181 for (
const std::string& base_url : base_urls_) {
182 XmlNode xml_base_url(
"BaseURL");
183 xml_base_url.SetContent(base_url);
185 if (!mpd.AddChild(std::move(xml_base_url)))
186 return base::nullopt;
189 bool output_period_duration =
false;
190 if (mpd_options_.mpd_type == MpdType::kStatic) {
191 UpdatePeriodDurationAndPresentationTimestamp();
195 output_period_duration = periods_.size() > 1;
198 for (
const auto& period : periods_) {
199 auto period_node = period->GetXml(output_period_duration);
200 if (!period_node || !mpd.AddChild(std::move(*period_node)))
201 return base::nullopt;
204 if (!AddMpdNameSpaceInfo(&mpd))
205 return base::nullopt;
207 static const char kOnDemandProfile[] =
208 "urn:mpeg:dash:profile:isoff-on-demand:2011";
209 static const char kLiveProfile[] =
210 "urn:mpeg:dash:profile:isoff-live:2011";
211 switch (mpd_options_.dash_profile) {
212 case DashProfile::kOnDemand:
213 if (!mpd.SetStringAttribute(
"profiles", kOnDemandProfile))
214 return base::nullopt;
216 case DashProfile::kLive:
217 if (!mpd.SetStringAttribute(
"profiles", kLiveProfile))
218 return base::nullopt;
221 NOTREACHED() <<
"Unknown DASH profile: "
222 <<
static_cast<int>(mpd_options_.dash_profile);
226 if (!AddCommonMpdInfo(&mpd))
227 return base::nullopt;
228 switch (mpd_options_.mpd_type) {
229 case MpdType::kStatic:
230 if (!AddStaticMpdInfo(&mpd))
231 return base::nullopt;
233 case MpdType::kDynamic:
234 if (!AddDynamicMpdInfo(&mpd))
235 return base::nullopt;
237 if (!AddUtcTiming(&mpd))
238 return base::nullopt;
241 NOTREACHED() <<
"Unknown MPD type: "
242 <<
static_cast<int>(mpd_options_.mpd_type);
248 bool MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
250 RCHECK(mpd_node->SetStringAttribute(
254 LOG(ERROR) <<
"minBufferTime value not specified.";
260 bool MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
262 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
264 static const char kStaticMpdType[] =
"static";
265 return mpd_node->SetStringAttribute(
"type", kStaticMpdType) &&
266 mpd_node->SetStringAttribute(
267 "mediaPresentationDuration",
268 SecondsToXmlDuration(GetStaticMpdDuration()));
271 bool MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
273 DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
275 static const char kDynamicMpdType[] =
"dynamic";
276 RCHECK(mpd_node->SetStringAttribute(
"type", kDynamicMpdType));
279 RCHECK(mpd_node->SetStringAttribute(
280 "publishTime", XmlDateTimeNowWithOffset(0, clock_.get())));
284 if (availability_start_time_.empty()) {
285 double earliest_presentation_time;
286 if (GetEarliestTimestamp(&earliest_presentation_time)) {
287 availability_start_time_ = XmlDateTimeNowWithOffset(
288 -std::ceil(earliest_presentation_time), clock_.get());
290 LOG(ERROR) <<
"Could not determine the earliest segment presentation "
291 "time for availabilityStartTime calculation.";
295 if (!availability_start_time_.empty()) {
296 RCHECK(mpd_node->SetStringAttribute(
"availabilityStartTime",
297 availability_start_time_));
301 RCHECK(mpd_node->SetStringAttribute(
302 "minimumUpdatePeriod",
305 LOG(WARNING) <<
"The profile is dynamic but no minimumUpdatePeriod "
309 return SetIfPositive(
"timeShiftBufferDepth",
312 SetIfPositive(
"suggestedPresentationDelay",
313 mpd_options_.mpd_params.suggested_presentation_delay,
317 bool MpdBuilder::AddUtcTiming(XmlNode* mpd_node) {
319 DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
321 for (
const MpdParams::UtcTiming& utc_timing :
322 mpd_options_.mpd_params.utc_timings) {
323 XmlNode utc_timing_node(
"UTCTiming");
324 RCHECK(utc_timing_node.SetStringAttribute(
"schemeIdUri",
325 utc_timing.scheme_id_uri));
326 RCHECK(utc_timing_node.SetStringAttribute(
"value", utc_timing.value));
327 RCHECK(mpd_node->AddChild(std::move(utc_timing_node)));
332 float MpdBuilder::GetStaticMpdDuration() {
333 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
335 float total_duration = 0.0f;
336 for (
const auto& period : periods_) {
337 total_duration += period->duration_seconds();
339 return total_duration;
342 bool MpdBuilder::GetEarliestTimestamp(
double* timestamp_seconds) {
343 DCHECK(timestamp_seconds);
344 DCHECK(!periods_.empty());
345 double timestamp = 0;
346 double earliest_timestamp = -1;
351 for (
const auto* adaptation_set : periods_.front()->GetAdaptationSets()) {
352 for (
const auto* representation : adaptation_set->GetRepresentations()) {
353 if (representation->GetStartAndEndTimestamps(×tamp,
nullptr) &&
354 (earliest_timestamp < 0 || timestamp < earliest_timestamp)) {
355 earliest_timestamp = timestamp;
359 if (earliest_timestamp < 0)
361 *timestamp_seconds = earliest_timestamp;
365 void MpdBuilder::UpdatePeriodDurationAndPresentationTimestamp() {
366 DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
368 for (
const auto& period : periods_) {
369 std::list<Representation*> video_representations;
370 std::list<Representation*> non_video_representations;
371 for (
const auto& adaptation_set : period->GetAdaptationSets()) {
372 const auto& representations = adaptation_set->GetRepresentations();
373 if (adaptation_set->IsVideo()) {
374 video_representations.insert(video_representations.end(),
375 representations.begin(),
376 representations.end());
378 non_video_representations.insert(non_video_representations.end(),
379 representations.begin(),
380 representations.end());
384 base::Optional<double> earliest_start_time;
385 base::Optional<double> latest_end_time;
387 const auto& representations = video_representations.size() > 0
388 ? video_representations
389 : non_video_representations;
390 for (
const auto& representation : representations) {
391 double start_time = 0;
393 if (representation->GetStartAndEndTimestamps(&start_time, &end_time)) {
394 earliest_start_time =
395 std::min(earliest_start_time.value_or(start_time), start_time);
397 std::max(latest_end_time.value_or(end_time), end_time);
401 if (!earliest_start_time)
404 period->set_duration_seconds(*latest_end_time - *earliest_start_time);
406 double presentation_time_offset = *earliest_start_time;
407 for (
const auto& adaptation_set : period->GetAdaptationSets()) {
408 for (
const auto& representation : adaptation_set->GetRepresentations()) {
409 representation->SetPresentationTimeOffset(presentation_time_offset);
416 MediaInfo* media_info) {
418 const std::string kFileProtocol(
"file://");
419 std::string mpd_file_path = (mpd_path.find(kFileProtocol) == 0)
420 ? mpd_path.substr(kFileProtocol.size())
423 if (!mpd_file_path.empty()) {
424 const FilePath mpd_dir(FilePath::FromUTF8Unsafe(mpd_file_path)
426 .AsEndingWithSeparator());
427 if (!mpd_dir.empty()) {
428 if (media_info->has_media_file_name()) {
429 media_info->set_media_file_url(
430 MakePathRelative(media_info->media_file_name(), mpd_dir));
432 if (media_info->has_init_segment_name()) {
433 media_info->set_init_segment_url(
434 MakePathRelative(media_info->init_segment_name(), mpd_dir));
436 if (media_info->has_segment_template()) {
437 media_info->set_segment_template_url(
438 MakePathRelative(media_info->segment_template(), mpd_dir));