7#include <packager/mpd/base/xml/xml_node.h>
14#include <absl/base/internal/endian.h>
15#include <absl/flags/flag.h>
16#include <absl/log/check.h>
17#include <absl/log/log.h>
18#include <absl/strings/escaping.h>
19#include <absl/strings/numbers.h>
20#include <absl/strings/str_format.h>
22#include <libxml/tree.h>
24#include <packager/macros/compiler.h>
25#include <packager/media/base/rcheck.h>
26#include <packager/mpd/base/media_info.pb.h>
27#include <packager/mpd/base/mpd_utils.h>
28#include <packager/mpd/base/segment_info.h>
29#include <packager/mpd/base/xml/scoped_xml_ptr.h>
32 segment_template_constant_duration,
34 "Generates SegmentTemplate@duration if all segments except the "
35 "last one has the same duration if this flag is set to true.");
38 dash_add_last_segment_number_when_needed,
40 "Adds a Supplemental Descriptor with @schemeIdUri "
41 "set to http://dashif.org/guidelines/last-segment-number with "
42 "the @value set to the last segment number.");
47typedef MediaInfo::AudioInfo AudioInfo;
48typedef MediaInfo::VideoInfo VideoInfo;
51const char kEC3Codec[] =
"ec-3";
52const char kAC4Codec[] =
"ac-4";
53const char kDTSCCodec[] =
"dtsc";
54const char kDTSECodec[] =
"dtse";
55const char kDTSXCodec[] =
"dtsx";
57std::string urlEncode(
const std::string& input) {
60 char* output = curl_easy_escape(curl, input.c_str(), input.length());
62 std::string encodedUrl(output);
69std::string RangeToString(
const Range& range) {
70 return absl::StrFormat(
"%u-%u", range.begin(), range.end());
75bool IsTimelineConstantDuration(
const std::list<SegmentInfo>& segment_infos,
76 uint32_t start_number) {
77 if (!absl::GetFlag(FLAGS_segment_template_constant_duration))
80 DCHECK(!segment_infos.empty());
81 if (segment_infos.size() > 2)
84 const SegmentInfo& first_segment = segment_infos.front();
85 if (first_segment.start_time != first_segment.duration * (start_number - 1))
88 if (segment_infos.size() == 1)
91 const SegmentInfo& last_segment = segment_infos.back();
92 if (last_segment.repeat != 0)
95 const int64_t expected_last_segment_start_time =
96 first_segment.start_time +
97 first_segment.duration * (first_segment.repeat + 1);
98 return expected_last_segment_start_time == last_segment.start_time;
101bool PopulateSegmentTimeline(
const std::list<SegmentInfo>& segment_infos,
102 XmlNode* segment_timeline) {
103 for (
const SegmentInfo& segment_info : segment_infos) {
104 XmlNode s_element(
"S");
105 RCHECK(s_element.SetIntegerAttribute(
"t", segment_info.start_time));
106 RCHECK(s_element.SetIntegerAttribute(
"d", segment_info.duration));
107 if (segment_info.repeat > 0)
108 RCHECK(s_element.SetIntegerAttribute(
"r", segment_info.repeat));
110 RCHECK(segment_timeline->AddChild(std::move(s_element)));
116void CollectNamespaceFromName(
const std::string& name,
117 std::set<std::string>* namespaces) {
118 const size_t pos = name.find(
':');
119 if (pos != std::string::npos)
120 namespaces->insert(name.substr(0, pos));
123void TraverseAttrsAndCollectNamespaces(
const xmlAttr* attr,
124 std::set<std::string>* namespaces) {
125 for (
const xmlAttr* cur_attr = attr; cur_attr; cur_attr = cur_attr->next) {
126 CollectNamespaceFromName(
reinterpret_cast<const char*
>(cur_attr->name),
131void TraverseNodesAndCollectNamespaces(
const xmlNode* node,
132 std::set<std::string>* namespaces) {
133 for (
const xmlNode* cur_node = node; cur_node; cur_node = cur_node->next) {
134 CollectNamespaceFromName(
reinterpret_cast<const char*
>(cur_node->name),
137 TraverseNodesAndCollectNamespaces(cur_node->children, namespaces);
138 TraverseAttrsAndCollectNamespaces(cur_node->properties, namespaces);
148 scoped_xml_ptr<xmlNode> node;
152 impl_->node.reset(xmlNewNode(NULL, BAD_CAST name.c_str()));
158XmlNode::~XmlNode() {}
164 DCHECK(child.impl_->node);
165 RCHECK(xmlAddChild(impl_->node.get(), child.impl_->node.get()));
169 UNUSED(child.impl_->node.release());
174 for (
size_t element_index = 0; element_index < elements.size();
176 const Element& child_element = elements[element_index];
177 XmlNode child_node(child_element.name);
178 for (std::map<std::string, std::string>::const_iterator attribute_it =
179 child_element.attributes.begin();
180 attribute_it != child_element.attributes.end(); ++attribute_it) {
182 attribute_it->second));
190 RCHECK(child_node.
AddElements(child_element.subelements));
192 if (!xmlAddChild(impl_->node.get(), child_node.impl_->node.get())) {
193 LOG(ERROR) <<
"Failed to set child " << child_element.name
194 <<
" to parent element "
195 <<
reinterpret_cast<const char*
>(impl_->node->name);
200 child_node.impl_->node.release();
206 const std::string& attribute) {
208 return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
209 BAD_CAST attribute.c_str()) !=
nullptr;
215 return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
216 BAD_CAST(absl::StrFormat(
"%" PRIu64, number).c_str())) !=
223 return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
224 BAD_CAST(FloatToXmlString(number).c_str())) !=
nullptr;
233 xmlNodeAddContent(impl_->node.get(), BAD_CAST content.c_str());
236void XmlNode::AddUrlEncodedContent(
const std::string& content) {
242 xmlNodeSetContent(impl_->node.get(), BAD_CAST content.c_str());
245void XmlNode::SetUrlEncodedContent(
const std::string& content) {
250 std::set<std::string> namespaces;
251 TraverseNodesAndCollectNamespaces(impl_->node.get(), &namespaces);
258 xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST
"1.0"));
259 if (comment.empty()) {
260 xmlDocSetRootElement(doc.get(), xmlCopyNode(impl_->node.get(),
true));
262 xml::scoped_xml_ptr<xmlNode> comment_xml(
263 xmlNewDocComment(doc.get(), BAD_CAST comment.c_str()));
264 xmlDocSetRootElement(doc.get(), comment_xml.get());
265 xmlAddSibling(comment_xml.release(), xmlCopyNode(impl_->node.get(),
true));
269 static const int kNiceFormat = 1;
270 int doc_str_size = 0;
271 xmlChar* doc_str =
nullptr;
272 xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size,
"UTF-8",
274 std::string output(doc_str, doc_str + doc_str_size);
280 xml::scoped_xml_ptr<xmlChar> str(
281 xmlGetProp(impl_->node.get(), BAD_CAST name.c_str()));
284 *value =
reinterpret_cast<const char*
>(str.get());
288xmlNode* XmlNode::GetRawPtr()
const {
289 return impl_->node.get();
292RepresentationBaseXmlNode::RepresentationBaseXmlNode(
const std::string& name)
294RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {}
296bool RepresentationBaseXmlNode::AddContentProtectionElements(
297 const std::list<ContentProtectionElement>& content_protection_elements) {
298 for (
const auto& elem : content_protection_elements) {
299 RCHECK(AddContentProtectionElement(elem));
306 const std::string& scheme_id_uri,
307 const std::string& value) {
308 return AddDescriptor(
"SupplementalProperty", scheme_id_uri, value);
312 const std::string& scheme_id_uri,
313 const std::string& value) {
314 return AddDescriptor(
"EssentialProperty", scheme_id_uri, value);
318 const std::string& descriptor_name,
319 const std::string& scheme_id_uri,
320 const std::string& value) {
321 XmlNode descriptor(descriptor_name);
325 return AddChild(std::move(descriptor));
328bool RepresentationBaseXmlNode::AddContentProtectionElement(
330 XmlNode content_protection_node(
"ContentProtection");
333 if (!content_protection_element.value.empty()) {
334 RCHECK(content_protection_node.SetStringAttribute(
335 "value", content_protection_element.value));
337 RCHECK(content_protection_node.SetStringAttribute(
338 "schemeIdUri", content_protection_element.scheme_id_uri));
340 for (
const auto& pair : content_protection_element.additional_attributes) {
341 RCHECK(content_protection_node.SetStringAttribute(pair.first, pair.second));
344 RCHECK(content_protection_node.AddElements(
345 content_protection_element.subelements));
346 return AddChild(std::move(content_protection_node));
349AdaptationSetXmlNode::AdaptationSetXmlNode()
350 : RepresentationBaseXmlNode(
"AdaptationSet") {}
351AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
354 const std::string& scheme_id_uri,
355 const std::string& value) {
360 const std::string& value) {
367 return AddChild(std::move(descriptor));
370RepresentationXmlNode::RepresentationXmlNode()
372RepresentationXmlNode::~RepresentationXmlNode() {}
377 bool set_frame_rate) {
378 if (!video_info.has_width() || !video_info.has_height()) {
379 LOG(ERROR) <<
"Missing width or height for adding a video info.";
383 if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
385 absl::StrFormat(
"%d:%d", video_info.pixel_width(),
386 video_info.pixel_height())));
393 if (set_frame_rate) {
395 absl::StrFormat(
"%d/%d", video_info.time_scale(),
396 video_info.frame_duration())));
399 if (video_info.has_playback_rate()) {
401 "maxPlayoutRate", absl::StrFormat(
"%d", video_info.playback_rate())));
412 return AddAudioChannelInfo(audio_info) &&
413 AddAudioSamplingRateInfo(audio_info);
417 bool use_segment_list,
418 double target_segment_duration) {
419 if (media_info.has_media_file_url()) {
421 base_url.SetUrlEncodedContent(media_info.media_file_url());
423 RCHECK(
AddChild(std::move(base_url)));
429 const bool need_segment_base_or_list =
430 use_segment_list || media_info.has_index_range() ||
431 media_info.has_init_range() ||
432 (media_info.has_reference_time_scale() && !media_info.has_text_info()) ||
433 (media_info.has_text_info() && media_info.has_presentation_time_offset());
435 if (!need_segment_base_or_list) {
439 XmlNode child(use_segment_list ?
"SegmentList" :
"SegmentBase");
443 if (media_info.has_index_range() && !use_segment_list) {
445 RangeToString(media_info.index_range())));
448 if (media_info.has_reference_time_scale()) {
450 media_info.reference_time_scale()));
452 if (use_segment_list) {
453 const auto duration_seconds =
static_cast<int64_t
>(
454 floor(target_segment_duration * media_info.reference_time_scale()));
459 if (media_info.has_presentation_time_offset()) {
461 media_info.presentation_time_offset()));
464 if (media_info.has_init_range()) {
465 XmlNode initialization(
"Initialization");
467 "range", RangeToString(media_info.init_range())));
469 RCHECK(child.
AddChild(std::move(initialization)));
474 if (use_segment_list) {
475 for (
const Range& subsegment_range : media_info.subsegment_ranges()) {
476 XmlNode subsegment(
"SegmentURL");
478 RangeToString(subsegment_range)));
480 RCHECK(child.
AddChild(std::move(subsegment)));
489 const MediaInfo& media_info,
490 const std::list<SegmentInfo>& segment_infos,
491 bool low_latency_dash_mode) {
492 XmlNode segment_template(
"SegmentTemplate");
495 segment_infos.empty() ? 1 : segment_infos.begin()->start_segment_number;
497 if (media_info.has_reference_time_scale()) {
499 "timescale", media_info.reference_time_scale()));
502 if (media_info.has_segment_duration()) {
504 media_info.segment_duration()));
507 if (media_info.has_presentation_time_offset()) {
509 "presentationTimeOffset", media_info.presentation_time_offset()));
512 if (media_info.has_availability_time_offset()) {
514 "availabilityTimeOffset", media_info.availability_time_offset()));
517 if (low_latency_dash_mode) {
522 if (media_info.has_init_segment_url()) {
524 media_info.init_segment_url()));
527 if (media_info.has_segment_template_url()) {
529 "media", media_info.segment_template_url()));
533 if (!segment_infos.empty()) {
536 if (IsTimelineConstantDuration(segment_infos, start_number)) {
538 "duration", segment_infos.front().duration));
539 if (absl::GetFlag(FLAGS_dash_add_last_segment_number_when_needed)) {
540 uint32_t last_segment_number = start_number - 1;
541 for (
const auto& segment_info_element : segment_infos)
542 last_segment_number += segment_info_element.repeat + 1;
545 "http://dashif.org/guidelines/last-segment-number",
546 std::to_string(last_segment_number)));
549 if (!low_latency_dash_mode) {
550 XmlNode segment_timeline(
"SegmentTimeline");
551 RCHECK(PopulateSegmentTimeline(segment_infos, &segment_timeline));
552 RCHECK(segment_template.
AddChild(std::move(segment_timeline)));
556 return AddChild(std::move(segment_template));
559bool RepresentationXmlNode::AddAudioChannelInfo(
const AudioInfo& audio_info) {
560 std::string audio_channel_config_scheme;
561 std::string audio_channel_config_value;
563 if (audio_info.codec() == kEC3Codec) {
564 const auto& codec_data = audio_info.codec_specific_data();
568 const uint32_t ec3_channel_mpeg_value = codec_data.channel_mpeg_value();
569 const uint32_t NO_MAPPING = 0xFFFFFFFF;
570 if (ec3_channel_mpeg_value == NO_MAPPING) {
573 audio_channel_config_value =
574 absl::StrFormat(
"%04X", codec_data.channel_mask());
575 audio_channel_config_scheme =
576 "tag:dolby.com,2014:dash:audio_channel_configuration:2011";
581 audio_channel_config_value =
582 absl::StrFormat(
"%u", ec3_channel_mpeg_value);
583 audio_channel_config_scheme =
"urn:mpeg:mpegB:cicp:ChannelConfiguration";
586 AddDescriptor(
"AudioChannelConfiguration", audio_channel_config_scheme,
587 audio_channel_config_value);
591 if (codec_data.ec3_joc_complexity() != 0) {
592 std::string ec3_joc_complexity =
593 absl::StrFormat(
"%u", codec_data.ec3_joc_complexity());
595 "tag:dolby.com,2018:dash:EC3_ExtensionType:2018",
598 "tag:dolby.com,2018:dash:"
599 "EC3_ExtensionComplexityIndex:2018",
603 }
else if (audio_info.codec().substr(0, 4) == kAC4Codec) {
604 const auto& codec_data = audio_info.codec_specific_data();
605 const bool ac4_ims_flag = codec_data.ac4_ims_flag();
609 const uint32_t ac4_channel_mpeg_value = codec_data.channel_mpeg_value();
610 const uint32_t NO_MAPPING = 0xFFFFFFFF;
611 if (ac4_channel_mpeg_value == NO_MAPPING) {
617 audio_channel_config_value =
618 absl::StrFormat(
"%06X", codec_data.channel_mask());
621 audio_channel_config_scheme =
622 "tag:dolby.com,2015:dash:audio_channel_configuration:2015";
627 audio_channel_config_value =
628 absl::StrFormat(
"%u", ac4_channel_mpeg_value);
629 audio_channel_config_scheme =
"urn:mpeg:mpegB:cicp:ChannelConfiguration";
632 AddDescriptor(
"AudioChannelConfiguration", audio_channel_config_scheme,
633 audio_channel_config_value);
636 "tag:dolby.com,2016:dash:virtualized_content:2016",
640 }
else if (audio_info.codec() == kDTSCCodec ||
641 audio_info.codec() == kDTSECodec) {
642 audio_channel_config_value =
643 absl::StrFormat(
"%u", audio_info.num_channels());
644 audio_channel_config_scheme =
645 "tag:dts.com,2014:dash:audio_channel_configuration:2012";
646 }
else if (audio_info.codec() == kDTSXCodec) {
647 const auto& codec_data = audio_info.codec_specific_data();
648 audio_channel_config_value =
649 absl::StrFormat(
"%08X", codec_data.channel_mask());
650 audio_channel_config_scheme =
651 "tag:dts.com,2018:uhd:audio_channel_configuration";
653 audio_channel_config_value =
654 absl::StrFormat(
"%u", audio_info.num_channels());
655 audio_channel_config_scheme =
656 "urn:mpeg:dash:23003:3:audio_channel_configuration:2011";
659 return AddDescriptor(
"AudioChannelConfiguration", audio_channel_config_scheme,
660 audio_channel_config_value);
665bool RepresentationXmlNode::AddAudioSamplingRateInfo(
666 const AudioInfo& audio_info) {
667 return !audio_info.has_sampling_frequency() ||
669 audio_info.sampling_frequency());
bool AddAccessibilityElement(const std::string &scheme_id_uri, const std::string &value)
bool AddLabelElement(const std::string &value)
bool AddRoleElement(const std::string &scheme_id_uri, const std::string &value)
bool AddDescriptor(const std::string &descriptor_name, const std::string &scheme_id_uri, const std::string &value)
bool AddEssentialProperty(const std::string &scheme_id_uri, const std::string &value)
bool AddSupplementalProperty(const std::string &scheme_id_uri, const std::string &value)
bool AddVODOnlyInfo(const MediaInfo &media_info, bool use_segment_list, double target_segment_duration)
bool AddLiveOnlyInfo(const MediaInfo &media_info, const std::list< SegmentInfo > &segment_infos, bool low_latency_dash_mode)
bool AddAudioInfo(const MediaInfo::AudioInfo &audio_info)
bool AddVideoInfo(const MediaInfo::VideoInfo &video_info, bool set_width, bool set_height, bool set_frame_rate)
bool AddChild(XmlNode child)
bool AddElements(const std::vector< Element > &elements)
Adds Elements to this node using the Element struct.
std::set< std::string > ExtractReferencedNamespaces() const
void AddContent(const std::string &content)
Similar to SetContent, but appends to the end of existing content.
bool SetStringAttribute(const std::string &attribute_name, const std::string &attribute)
void SetContent(const std::string &content)
XmlNode(const std::string &name)
bool SetIntegerAttribute(const std::string &attribute_name, uint64_t number)
bool GetAttribute(const std::string &name, std::string *value) const
std::string ToString(const std::string &comment) const
bool SetFloatingPointAttribute(const std::string &attribute_name, double number)
All the methods that are virtual are virtual for mocking.