7 #include "packager/mpd/base/xml/xml_node.h"
9 #include <gflags/gflags.h>
10 #include <libxml/tree.h>
15 #include "packager/base/logging.h"
16 #include "packager/base/macros.h"
17 #include "packager/base/strings/string_number_conversions.h"
18 #include "packager/base/sys_byteorder.h"
19 #include "packager/media/base/rcheck.h"
20 #include "packager/mpd/base/media_info.pb.h"
21 #include "packager/mpd/base/mpd_utils.h"
22 #include "packager/mpd/base/segment_info.h"
23 #include "packager/mpd/base/xml/scoped_xml_ptr.h"
25 DEFINE_bool(segment_template_constant_duration,
27 "Generates SegmentTemplate@duration if all segments except the "
28 "last one has the same duration if this flag is set to true.");
30 DEFINE_bool(dash_add_last_segment_number_when_needed,
32 "Adds a Supplemental Descriptor with @schemeIdUri "
33 "set to http://dashif.org/guidelines/last-segment-number with "
34 "the @value set to the last segment number.");
39 typedef MediaInfo::AudioInfo AudioInfo;
40 typedef MediaInfo::VideoInfo VideoInfo;
43 const char kEC3Codec[] =
"ec-3";
44 const char kAC4Codec[] =
"ac-4";
46 std::string RangeToString(
const Range& range) {
47 return base::Uint64ToString(range.begin()) +
"-" +
48 base::Uint64ToString(range.end());
53 bool IsTimelineConstantDuration(
const std::list<SegmentInfo>& segment_infos,
54 uint32_t start_number) {
55 if (!FLAGS_segment_template_constant_duration)
58 DCHECK(!segment_infos.empty());
59 if (segment_infos.size() > 2)
62 const SegmentInfo& first_segment = segment_infos.front();
63 if (first_segment.start_time != first_segment.duration * (start_number - 1))
66 if (segment_infos.size() == 1)
69 const SegmentInfo& last_segment = segment_infos.back();
70 if (last_segment.repeat != 0)
73 const int64_t expected_last_segment_start_time =
74 first_segment.start_time +
75 first_segment.duration * (first_segment.repeat + 1);
76 return expected_last_segment_start_time == last_segment.start_time;
79 bool PopulateSegmentTimeline(
const std::list<SegmentInfo>& segment_infos,
80 XmlNode* segment_timeline) {
81 for (
const SegmentInfo& segment_info : segment_infos) {
82 XmlNode s_element(
"S");
83 RCHECK(s_element.SetIntegerAttribute(
"t", segment_info.start_time));
84 RCHECK(s_element.SetIntegerAttribute(
"d", segment_info.duration));
85 if (segment_info.repeat > 0)
86 RCHECK(s_element.SetIntegerAttribute(
"r", segment_info.repeat));
88 RCHECK(segment_timeline->AddChild(std::move(s_element)));
94 void CollectNamespaceFromName(
const std::string& name,
95 std::set<std::string>* namespaces) {
96 const size_t pos = name.find(
':');
97 if (pos != std::string::npos)
98 namespaces->insert(name.substr(0, pos));
101 void TraverseAttrsAndCollectNamespaces(
const xmlAttr* attr,
102 std::set<std::string>* namespaces) {
103 for (
const xmlAttr* cur_attr = attr; cur_attr; cur_attr = cur_attr->next) {
104 CollectNamespaceFromName(
reinterpret_cast<const char*
>(cur_attr->name),
109 void TraverseNodesAndCollectNamespaces(
const xmlNode* node,
110 std::set<std::string>* namespaces) {
111 for (
const xmlNode* cur_node = node; cur_node; cur_node = cur_node->next) {
112 CollectNamespaceFromName(
reinterpret_cast<const char*
>(cur_node->name),
115 TraverseNodesAndCollectNamespaces(cur_node->children, namespaces);
116 TraverseAttrsAndCollectNamespaces(cur_node->properties, namespaces);
124 class XmlNode::Impl {
126 scoped_xml_ptr<xmlNode> node;
130 impl_->node.reset(xmlNewNode(NULL, BAD_CAST name.c_str()));
136 XmlNode::~XmlNode() {}
142 DCHECK(child.impl_->node);
143 RCHECK(xmlAddChild(impl_->node.get(), child.impl_->node.get()));
147 ignore_result(child.impl_->node.release());
152 for (
size_t element_index = 0; element_index < elements.size();
154 const Element& child_element = elements[element_index];
155 XmlNode child_node(child_element.name);
156 for (std::map<std::string, std::string>::const_iterator attribute_it =
157 child_element.attributes.begin();
158 attribute_it != child_element.attributes.end(); ++attribute_it) {
160 attribute_it->second));
168 RCHECK(child_node.
AddElements(child_element.subelements));
170 if (!xmlAddChild(impl_->node.get(), child_node.impl_->node.get())) {
171 LOG(ERROR) <<
"Failed to set child " << child_element.name
172 <<
" to parent element "
173 <<
reinterpret_cast<const char*
>(impl_->node->name);
178 child_node.impl_->node.release();
184 const std::string& attribute) {
186 return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
187 BAD_CAST attribute.c_str()) !=
nullptr;
193 return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
194 BAD_CAST(base::Uint64ToString(number).c_str())) !=
nullptr;
200 return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
201 BAD_CAST(base::DoubleToString(number).c_str())) !=
nullptr;
210 xmlNodeAddContent(impl_->node.get(), BAD_CAST content.c_str());
215 xmlNodeSetContent(impl_->node.get(), BAD_CAST content.c_str());
219 std::set<std::string> namespaces;
220 TraverseNodesAndCollectNamespaces(impl_->node.get(), &namespaces);
227 xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST
"1.0"));
228 if (comment.empty()) {
229 xmlDocSetRootElement(doc.get(), xmlCopyNode(impl_->node.get(),
true));
231 xml::scoped_xml_ptr<xmlNode> comment_xml(
232 xmlNewDocComment(doc.get(), BAD_CAST comment.c_str()));
233 xmlDocSetRootElement(doc.get(), comment_xml.get());
234 xmlAddSibling(comment_xml.release(), xmlCopyNode(impl_->node.get(),
true));
238 static const int kNiceFormat = 1;
239 int doc_str_size = 0;
240 xmlChar* doc_str =
nullptr;
241 xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size,
"UTF-8",
243 std::string output(doc_str, doc_str + doc_str_size);
249 xml::scoped_xml_ptr<xmlChar> str(
250 xmlGetProp(impl_->node.get(), BAD_CAST name.c_str()));
253 *value =
reinterpret_cast<const char*
>(str.get());
257 xmlNode* XmlNode::GetRawPtr()
const {
258 return impl_->node.get();
261 RepresentationBaseXmlNode::RepresentationBaseXmlNode(
const std::string& name)
263 RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {}
265 bool RepresentationBaseXmlNode::AddContentProtectionElements(
266 const std::list<ContentProtectionElement>& content_protection_elements) {
267 for (
const auto& elem : content_protection_elements) {
268 RCHECK(AddContentProtectionElement(elem));
275 const std::string& scheme_id_uri,
276 const std::string& value) {
277 return AddDescriptor(
"SupplementalProperty", scheme_id_uri, value);
281 const std::string& scheme_id_uri,
282 const std::string& value) {
283 return AddDescriptor(
"EssentialProperty", scheme_id_uri, value);
287 const std::string& descriptor_name,
288 const std::string& scheme_id_uri,
289 const std::string& value) {
290 XmlNode descriptor(descriptor_name);
294 return AddChild(std::move(descriptor));
297 bool RepresentationBaseXmlNode::AddContentProtectionElement(
299 XmlNode content_protection_node(
"ContentProtection");
302 if (!content_protection_element.value.empty()) {
303 RCHECK(content_protection_node.SetStringAttribute(
304 "value", content_protection_element.value));
306 RCHECK(content_protection_node.SetStringAttribute(
307 "schemeIdUri", content_protection_element.scheme_id_uri));
309 for (
const auto& pair : content_protection_element.additional_attributes) {
310 RCHECK(content_protection_node.SetStringAttribute(pair.first, pair.second));
313 RCHECK(content_protection_node.AddElements(
314 content_protection_element.subelements));
315 return AddChild(std::move(content_protection_node));
318 AdaptationSetXmlNode::AdaptationSetXmlNode()
319 : RepresentationBaseXmlNode(
"AdaptationSet") {}
320 AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
323 const std::string& scheme_id_uri,
324 const std::string& value) {
329 const std::string& value) {
333 RepresentationXmlNode::RepresentationXmlNode()
335 RepresentationXmlNode::~RepresentationXmlNode() {}
340 bool set_frame_rate) {
341 if (!video_info.has_width() || !video_info.has_height()) {
342 LOG(ERROR) <<
"Missing width or height for adding a video info.";
346 if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
348 "sar", base::IntToString(video_info.pixel_width()) +
":" +
349 base::IntToString(video_info.pixel_height())));
356 if (set_frame_rate) {
358 "frameRate", base::IntToString(video_info.time_scale()) +
"/" +
359 base::IntToString(video_info.frame_duration())));
362 if (video_info.has_playback_rate()) {
364 base::IntToString(video_info.playback_rate())));
375 return AddAudioChannelInfo(audio_info) &&
376 AddAudioSamplingRateInfo(audio_info);
380 const bool use_segment_list_text =
381 media_info.has_text_info() && media_info.has_presentation_time_offset();
383 if (media_info.has_media_file_url() && !use_segment_list_text) {
385 base_url.
SetContent(media_info.media_file_url());
387 RCHECK(
AddChild(std::move(base_url)));
390 const bool need_segment_base =
391 media_info.has_index_range() || media_info.has_init_range() ||
392 (media_info.has_reference_time_scale() && !media_info.has_text_info());
393 DCHECK(!need_segment_base || !use_segment_list_text);
395 if (need_segment_base || use_segment_list_text) {
396 XmlNode child(need_segment_base ?
"SegmentBase" :
"SegmentList");
397 if (media_info.has_index_range()) {
399 RangeToString(media_info.index_range())));
402 if (media_info.has_reference_time_scale()) {
404 media_info.reference_time_scale()));
407 if (media_info.has_presentation_time_offset()) {
409 media_info.presentation_time_offset()));
412 if (media_info.has_init_range()) {
413 XmlNode initialization(
"Initialization");
415 "range", RangeToString(media_info.init_range())));
417 RCHECK(child.
AddChild(std::move(initialization)));
420 if (use_segment_list_text) {
421 XmlNode media_url(
"SegmentURL");
424 RCHECK(child.
AddChild(std::move(media_url)));
434 const MediaInfo& media_info,
435 const std::list<SegmentInfo>& segment_infos,
436 uint32_t start_number) {
437 XmlNode segment_template(
"SegmentTemplate");
438 if (media_info.has_reference_time_scale()) {
440 "timescale", media_info.reference_time_scale()));
443 if (media_info.has_presentation_time_offset()) {
445 "presentationTimeOffset", media_info.presentation_time_offset()));
448 if (media_info.has_init_segment_url()) {
450 media_info.init_segment_url()));
453 if (media_info.has_segment_template_url()) {
455 "media", media_info.segment_template_url()));
459 if (!segment_infos.empty()) {
462 if (IsTimelineConstantDuration(segment_infos, start_number)) {
464 "duration", segment_infos.front().duration));
465 if (FLAGS_dash_add_last_segment_number_when_needed) {
466 uint32_t last_segment_number = start_number - 1;
467 for (
const auto& segment_info_element : segment_infos)
468 last_segment_number += segment_info_element.repeat + 1;
471 "http://dashif.org/guidelines/last-segment-number",
472 std::to_string(last_segment_number)));
475 XmlNode segment_timeline(
"SegmentTimeline");
476 RCHECK(PopulateSegmentTimeline(segment_infos, &segment_timeline));
477 RCHECK(segment_template.
AddChild(std::move(segment_timeline)));
480 return AddChild(std::move(segment_template));
483 bool RepresentationXmlNode::AddAudioChannelInfo(
const AudioInfo& audio_info) {
484 std::string audio_channel_config_scheme;
485 std::string audio_channel_config_value;
487 if (audio_info.codec() == kEC3Codec) {
488 const auto& codec_data = audio_info.codec_specific_data();
492 const uint32_t ec3_channel_mpeg_value = codec_data.channel_mpeg_value();
493 const uint32_t NO_MAPPING = 0xFFFFFFFF;
494 if (ec3_channel_mpeg_value == NO_MAPPING) {
497 const uint16_t ec3_channel_map =
498 base::HostToNet16(codec_data.channel_mask());
499 audio_channel_config_value =
500 base::HexEncode(&ec3_channel_map,
sizeof(ec3_channel_map));
501 audio_channel_config_scheme =
502 "tag:dolby.com,2014:dash:audio_channel_configuration:2011";
507 audio_channel_config_value = base::UintToString(ec3_channel_mpeg_value);
508 audio_channel_config_scheme =
"urn:mpeg:mpegB:cicp:ChannelConfiguration";
511 audio_channel_config_scheme,
512 audio_channel_config_value);
516 if (codec_data.ec3_joc_complexity() != 0) {
517 std::string ec3_joc_complexity =
518 base::UintToString(codec_data.ec3_joc_complexity());
520 "tag:dolby.com,2018:dash:EC3_ExtensionType:2018",
523 "tag:dolby.com,2018:dash:"
524 "EC3_ExtensionComplexityIndex:2018",
528 }
else if (audio_info.codec().substr(0, 4) == kAC4Codec) {
529 const auto& codec_data = audio_info.codec_specific_data();
530 const bool ac4_ims_flag = codec_data.ac4_ims_flag();
534 const uint32_t ac4_channel_mpeg_value = codec_data.channel_mpeg_value();
535 const uint32_t NO_MAPPING = 0xFFFFFFFF;
536 if (ac4_channel_mpeg_value == NO_MAPPING) {
540 const uint32_t ac4_channel_mask =
541 base::HostToNet32(codec_data.channel_mask() << 8);
542 audio_channel_config_value =
543 base::HexEncode(&ac4_channel_mask,
sizeof(ac4_channel_mask) - 1);
546 audio_channel_config_scheme =
547 "tag:dolby.com,2015:dash:audio_channel_configuration:2015";
552 audio_channel_config_value = base::UintToString(ac4_channel_mpeg_value);
553 audio_channel_config_scheme =
"urn:mpeg:mpegB:cicp:ChannelConfiguration";
556 audio_channel_config_scheme,
557 audio_channel_config_value);
560 "tag:dolby.com,2016:dash:virtualized_content:2016",
565 audio_channel_config_value = base::UintToString(audio_info.num_channels());
566 audio_channel_config_scheme =
567 "urn:mpeg:dash:23003:3:audio_channel_configuration:2011";
570 return AddDescriptor(
"AudioChannelConfiguration", audio_channel_config_scheme,
571 audio_channel_config_value);
576 bool RepresentationXmlNode::AddAudioSamplingRateInfo(
577 const AudioInfo& audio_info) {
578 return !audio_info.has_sampling_frequency() ||
580 audio_info.sampling_frequency());
bool AddRoleElement(const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
bool AddAccessibilityElement(const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
bool AddDescriptor(const std::string &descriptor_name, const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
bool AddSupplementalProperty(const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
bool AddEssentialProperty(const std::string &scheme_id_uri, const std::string &value) WARN_UNUSED_RESULT
bool AddVideoInfo(const MediaInfo::VideoInfo &video_info, bool set_width, bool set_height, bool set_frame_rate) WARN_UNUSED_RESULT
bool AddVODOnlyInfo(const MediaInfo &media_info) WARN_UNUSED_RESULT
bool AddLiveOnlyInfo(const MediaInfo &media_info, const std::list< SegmentInfo > &segment_infos, uint32_t start_number) WARN_UNUSED_RESULT
bool AddAudioInfo(const MediaInfo::AudioInfo &audio_info) WARN_UNUSED_RESULT
bool AddChild(XmlNode child) WARN_UNUSED_RESULT
std::set< std::string > ExtractReferencedNamespaces() const
void AddContent(const std::string &content)
Similar to SetContent, but appends to the end of existing content.
void SetContent(const std::string &content)
bool SetFloatingPointAttribute(const std::string &attribute_name, double number) WARN_UNUSED_RESULT
bool SetIntegerAttribute(const std::string &attribute_name, uint64_t number) WARN_UNUSED_RESULT
XmlNode(const std::string &name)
bool SetStringAttribute(const std::string &attribute_name, const std::string &attribute) WARN_UNUSED_RESULT
bool GetAttribute(const std::string &name, std::string *value) const
bool SetId(uint32_t id) WARN_UNUSED_RESULT
bool AddElements(const std::vector< Element > &elements) WARN_UNUSED_RESULT
Adds Elements to this node using the Element struct.
std::string ToString(const std::string &comment) const
All the methods that are virtual are virtual for mocking.