7 #include <packager/mpd/base/mpd_builder.h>
14 #include <absl/log/check.h>
15 #include <absl/log/log.h>
16 #include <absl/strings/numbers.h>
17 #include <absl/strings/str_format.h>
18 #include <absl/synchronization/mutex.h>
20 #include <packager/file/file_util.h>
21 #include <packager/macros/classes.h>
22 #include <packager/macros/logging.h>
23 #include <packager/media/base/rcheck.h>
24 #include <packager/mpd/base/adaptation_set.h>
25 #include <packager/mpd/base/mpd_utils.h>
26 #include <packager/mpd/base/period.h>
27 #include <packager/mpd/base/representation.h>
28 #include <packager/version/version.h>
36 bool AddMpdNameSpaceInfo(XmlNode* mpd) {
39 const std::set<std::string> namespaces = mpd->ExtractReferencedNamespaces();
41 static const char kXmlNamespace[] =
"urn:mpeg:dash:schema:mpd:2011";
42 static const char kXmlNamespaceXsi[] =
43 "http://www.w3.org/2001/XMLSchema-instance";
44 static const char kDashSchemaMpd2011[] =
45 "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
47 RCHECK(mpd->SetStringAttribute(
"xmlns", kXmlNamespace));
48 RCHECK(mpd->SetStringAttribute(
"xmlns:xsi", kXmlNamespaceXsi));
49 RCHECK(mpd->SetStringAttribute(
"xsi:schemaLocation", kDashSchemaMpd2011));
51 static const char kCencNamespace[] =
"urn:mpeg:cenc:2013";
52 static const char kMarlinNamespace[] =
53 "urn:marlin:mas:1-0:services:schemas:mpd";
54 static const char kXmlNamespaceXlink[] =
"http://www.w3.org/1999/xlink";
55 static const char kMsprNamespace[] =
"urn:microsoft:playready";
56 static const char kScte214Namespace[] =
"urn:scte:dash:scte214-extensions";
58 const std::map<std::string, std::string> uris = {
59 {
"cenc", kCencNamespace}, {
"mas", kMarlinNamespace},
60 {
"xlink", kXmlNamespaceXlink}, {
"mspr", kMsprNamespace},
61 {
"scte214", kScte214Namespace},
64 for (
const std::string& namespace_name : namespaces) {
65 auto iter = uris.find(namespace_name);
66 CHECK(iter != uris.end()) <<
" unexpected namespace " << namespace_name;
68 RCHECK(mpd->SetStringAttribute(
69 absl::StrFormat(
"xmlns:%s", namespace_name.c_str()).c_str(),
75 bool Positive(
double d) {
81 std::string XmlDateTimeNowWithOffset(int32_t offset_seconds, Clock* clock) {
82 auto time_t = std::chrono::system_clock::to_time_t(
83 clock->now() + std::chrono::seconds(offset_seconds));
84 std::tm* tm = std::gmtime(&time_t);
87 ss << std::put_time(tm,
"%Y-%m-%dT%H:%M:%SZ");
91 bool SetIfPositive(
const char* attr_name,
double value, XmlNode* mpd) {
92 return !Positive(value) ||
93 mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
97 class LibXmlInitializer {
99 LibXmlInitializer() : initialized_(false) {
100 absl::MutexLock lock(&lock_);
107 ~LibXmlInitializer() {
108 absl::MutexLock lock(&lock_);
111 initialized_ =
false;
119 DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
125 : mpd_options_(mpd_options), clock_(new
Clock{}) {}
127 MpdBuilder::~MpdBuilder() {}
130 base_urls_.push_back(base_url);
134 for (
auto& period : periods_) {
135 const double kPeriodTimeDriftThresholdInSeconds = 1.0;
137 std::fabs(period->start_time_in_seconds() - start_time_in_seconds) <
138 kPeriodTimeDriftThresholdInSeconds;
142 periods_.emplace_back(
new Period(period_counter_++, start_time_in_seconds,
143 mpd_options_, &representation_counter_));
144 return periods_.back().get();
149 static LibXmlInitializer lib_xml_initializer;
151 auto mpd = GenerateMpd();
155 std::string version = GetPackagerVersion();
156 if (!version.empty()) {
157 version = absl::StrFormat(
"Generated with %s version %s",
158 GetPackagerProjectUrl().c_str(), version.c_str());
160 *output = mpd->ToString(version);
164 std::optional<xml::XmlNode> MpdBuilder::GenerateMpd() {
168 for (
const std::string& base_url : base_urls_) {
169 XmlNode xml_base_url(
"BaseURL");
170 xml_base_url.SetUrlEncodedContent(base_url);
172 if (!mpd.AddChild(std::move(xml_base_url)))
176 bool output_period_duration =
false;
177 if (mpd_options_.mpd_type == MpdType::kStatic) {
178 UpdatePeriodDurationAndPresentationTimestamp();
182 output_period_duration = periods_.size() > 1;
185 for (
const auto& period : periods_) {
186 auto period_node = period->GetXml(output_period_duration);
187 if (!period_node || !mpd.AddChild(std::move(*period_node)))
191 if (!AddMpdNameSpaceInfo(&mpd))
194 static const char kOnDemandProfile[] =
195 "urn:mpeg:dash:profile:isoff-on-demand:2011";
196 static const char kLiveProfile[] =
197 "urn:mpeg:dash:profile:isoff-live:2011";
198 switch (mpd_options_.dash_profile) {
199 case DashProfile::kOnDemand:
200 if (!mpd.SetStringAttribute(
"profiles", kOnDemandProfile))
203 case DashProfile::kLive:
204 if (!mpd.SetStringAttribute(
"profiles", kLiveProfile))
208 NOTIMPLEMENTED() <<
"Unknown DASH profile: "
209 <<
static_cast<int>(mpd_options_.dash_profile);
213 if (!AddCommonMpdInfo(&mpd))
215 switch (mpd_options_.mpd_type) {
216 case MpdType::kStatic:
217 if (!AddStaticMpdInfo(&mpd))
220 case MpdType::kDynamic:
221 if (!AddDynamicMpdInfo(&mpd))
224 if (!AddUtcTiming(&mpd))
228 NOTIMPLEMENTED() <<
"Unknown MPD type: "
229 <<
static_cast<int>(mpd_options_.mpd_type);
235 bool MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
236 if (Positive(mpd_options_.mpd_params.min_buffer_time)) {
237 RCHECK(mpd_node->SetStringAttribute(
239 SecondsToXmlDuration(mpd_options_.mpd_params.min_buffer_time)));
241 LOG(ERROR) <<
"minBufferTime value not specified.";
247 bool MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
249 DCHECK_EQ(
static_cast<int>(MpdType::kStatic),
250 static_cast<int>(mpd_options_.mpd_type));
252 static const char kStaticMpdType[] =
"static";
253 return mpd_node->SetStringAttribute(
"type", kStaticMpdType) &&
254 mpd_node->SetStringAttribute(
255 "mediaPresentationDuration",
256 SecondsToXmlDuration(GetStaticMpdDuration()));
259 bool MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
261 DCHECK_EQ(
static_cast<int>(MpdType::kDynamic),
262 static_cast<int>(mpd_options_.mpd_type));
264 static const char kDynamicMpdType[] =
"dynamic";
265 RCHECK(mpd_node->SetStringAttribute(
"type", kDynamicMpdType));
268 RCHECK(mpd_node->SetStringAttribute(
269 "publishTime", XmlDateTimeNowWithOffset(0, clock_.get())));
273 if (availability_start_time_.empty()) {
274 double earliest_presentation_time;
275 if (GetEarliestTimestamp(&earliest_presentation_time)) {
276 availability_start_time_ = XmlDateTimeNowWithOffset(
277 -std::ceil(earliest_presentation_time), clock_.get());
279 LOG(ERROR) <<
"Could not determine the earliest segment presentation "
280 "time for availabilityStartTime calculation.";
284 if (!availability_start_time_.empty()) {
285 RCHECK(mpd_node->SetStringAttribute(
"availabilityStartTime",
286 availability_start_time_));
289 if (Positive(mpd_options_.mpd_params.minimum_update_period)) {
290 RCHECK(mpd_node->SetStringAttribute(
291 "minimumUpdatePeriod",
292 SecondsToXmlDuration(mpd_options_.mpd_params.minimum_update_period)));
294 LOG(WARNING) <<
"The profile is dynamic but no minimumUpdatePeriod "
298 return SetIfPositive(
"timeShiftBufferDepth",
299 mpd_options_.mpd_params.time_shift_buffer_depth,
301 SetIfPositive(
"suggestedPresentationDelay",
302 mpd_options_.mpd_params.suggested_presentation_delay,
306 bool MpdBuilder::AddUtcTiming(XmlNode* mpd_node) {
308 DCHECK_EQ(
static_cast<int>(MpdType::kDynamic),
309 static_cast<int>(mpd_options_.mpd_type));
311 for (
const MpdParams::UtcTiming& utc_timing :
312 mpd_options_.mpd_params.utc_timings) {
313 XmlNode utc_timing_node(
"UTCTiming");
314 RCHECK(utc_timing_node.SetStringAttribute(
"schemeIdUri",
315 utc_timing.scheme_id_uri));
316 RCHECK(utc_timing_node.SetStringAttribute(
"value", utc_timing.value));
317 RCHECK(mpd_node->AddChild(std::move(utc_timing_node)));
322 float MpdBuilder::GetStaticMpdDuration() {
323 DCHECK_EQ(
static_cast<int>(MpdType::kStatic),
324 static_cast<int>(mpd_options_.mpd_type));
326 float total_duration = 0.0f;
327 for (
const auto& period : periods_) {
328 total_duration += period->duration_seconds();
330 return total_duration;
333 bool MpdBuilder::GetEarliestTimestamp(
double* timestamp_seconds) {
334 DCHECK(timestamp_seconds);
335 DCHECK(!periods_.empty());
336 if (periods_.empty())
338 double timestamp = 0;
339 double earliest_timestamp = -1;
344 for (
const auto* adaptation_set : periods_.front()->GetAdaptationSets()) {
345 for (
const auto* representation : adaptation_set->GetRepresentations()) {
346 if (representation->GetStartAndEndTimestamps(×tamp,
nullptr) &&
347 (earliest_timestamp < 0 || timestamp < earliest_timestamp)) {
348 earliest_timestamp = timestamp;
352 if (earliest_timestamp < 0)
354 *timestamp_seconds = earliest_timestamp;
358 void MpdBuilder::UpdatePeriodDurationAndPresentationTimestamp() {
359 DCHECK_EQ(
static_cast<int>(MpdType::kStatic),
360 static_cast<int>(mpd_options_.mpd_type));
362 for (
const auto& period : periods_) {
363 std::list<Representation*> video_representations;
364 std::list<Representation*> non_video_representations;
365 for (
const auto& adaptation_set : period->GetAdaptationSets()) {
366 const auto& representations = adaptation_set->GetRepresentations();
367 if (adaptation_set->IsVideo()) {
368 video_representations.insert(video_representations.end(),
369 representations.begin(),
370 representations.end());
372 non_video_representations.insert(non_video_representations.end(),
373 representations.begin(),
374 representations.end());
378 std::optional<double> earliest_start_time;
379 std::optional<double> latest_end_time;
381 const auto& representations = video_representations.size() > 0
382 ? video_representations
383 : non_video_representations;
384 for (
const auto& representation : representations) {
385 double start_time = 0;
387 if (representation->GetStartAndEndTimestamps(&start_time, &end_time)) {
388 earliest_start_time =
389 std::min(earliest_start_time.value_or(start_time), start_time);
391 std::max(latest_end_time.value_or(end_time), end_time);
395 if (!earliest_start_time)
398 period->set_duration_seconds(*latest_end_time - *earliest_start_time);
400 double presentation_time_offset = *earliest_start_time;
401 for (
const auto& adaptation_set : period->GetAdaptationSets()) {
402 for (
const auto& representation : adaptation_set->GetRepresentations()) {
403 representation->SetPresentationTimeOffset(presentation_time_offset);
410 MediaInfo* media_info) {
412 const std::string kFileProtocol(
"file://");
413 std::filesystem::path mpd_file_path =
414 (mpd_path.find(kFileProtocol) == 0)
415 ? mpd_path.substr(kFileProtocol.size())
418 if (!mpd_file_path.empty()) {
419 const std::filesystem::path mpd_dir(mpd_file_path.parent_path());
420 if (media_info->has_media_file_name()) {
421 media_info->set_media_file_url(
422 MakePathRelative(media_info->media_file_name(), mpd_dir));
424 if (media_info->has_init_segment_name()) {
425 media_info->set_init_segment_url(
426 MakePathRelative(media_info->init_segment_name(), mpd_dir));
428 if (media_info->has_segment_template()) {
429 media_info->set_segment_template_url(
430 MakePathRelative(media_info->segment_template(), mpd_dir));
static void MakePathsRelativeToMpd(const std::string &mpd_path, MediaInfo *media_info)
MpdBuilder(const MpdOptions &mpd_options)
void AddBaseUrl(const std::string &base_url)
virtual Period * GetOrCreatePeriod(double start_time_in_seconds)
virtual bool ToString(std::string *output)
All the methods that are virtual are virtual for mocking.