Shaka Packager SDK
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends
xml_node.cc
1// Copyright 2014 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/mpd/base/xml/xml_node.h>
8
9#include <cinttypes>
10#include <cmath>
11#include <limits>
12#include <set>
13
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>
21#include <curl/curl.h>
22#include <libxml/tree.h>
23
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>
30
31ABSL_FLAG(bool,
32 segment_template_constant_duration,
33 false,
34 "Generates SegmentTemplate@duration if all segments except the "
35 "last one has the same duration if this flag is set to true.");
36
37ABSL_FLAG(bool,
38 dash_add_last_segment_number_when_needed,
39 false,
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.");
43
44namespace shaka {
45
46using xml::XmlNode;
47typedef MediaInfo::AudioInfo AudioInfo;
48typedef MediaInfo::VideoInfo VideoInfo;
49
50namespace {
51const char kEC3Codec[] = "ec-3";
52const char kAC4Codec[] = "ac-4";
53const char kDTSCCodec[] = "dtsc";
54const char kDTSECodec[] = "dtse";
55const char kDTSXCodec[] = "dtsx";
56
57std::string urlEncode(const std::string& input) {
58 // NOTE: According to the docs, "Since 7.82.0, the curl parameter is ignored".
59 CURL* curl = NULL;
60 char* output = curl_easy_escape(curl, input.c_str(), input.length());
61 if (output) {
62 std::string encodedUrl(output);
63 curl_free(output); // Free the output string when done
64 return encodedUrl;
65 }
66 return ""; // Return empty string if initialization fails
67}
68
69std::string RangeToString(const Range& range) {
70 return absl::StrFormat("%u-%u", range.begin(), range.end());
71}
72
73// Check if segments are continuous and all segments except the last one are of
74// the same duration.
75bool IsTimelineConstantDuration(const std::list<SegmentInfo>& segment_infos,
76 uint32_t start_number) {
77 if (!absl::GetFlag(FLAGS_segment_template_constant_duration))
78 return false;
79
80 DCHECK(!segment_infos.empty());
81 if (segment_infos.size() > 2)
82 return false;
83
84 const SegmentInfo& first_segment = segment_infos.front();
85 if (first_segment.start_time != first_segment.duration * (start_number - 1))
86 return false;
87
88 if (segment_infos.size() == 1)
89 return true;
90
91 const SegmentInfo& last_segment = segment_infos.back();
92 if (last_segment.repeat != 0)
93 return false;
94
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;
99}
100
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));
109
110 RCHECK(segment_timeline->AddChild(std::move(s_element)));
111 }
112
113 return true;
114}
115
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));
121}
122
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),
127 namespaces);
128 }
129}
130
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),
135 namespaces);
136
137 TraverseNodesAndCollectNamespaces(cur_node->children, namespaces);
138 TraverseAttrsAndCollectNamespaces(cur_node->properties, namespaces);
139 }
140}
141
142} // namespace
143
144namespace xml {
145
146class XmlNode::Impl {
147 public:
148 scoped_xml_ptr<xmlNode> node;
149};
150
151XmlNode::XmlNode(const std::string& name) : impl_(new Impl) {
152 impl_->node.reset(xmlNewNode(NULL, BAD_CAST name.c_str()));
153 DCHECK(impl_->node);
154}
155
156XmlNode::XmlNode(XmlNode&&) = default;
157
158XmlNode::~XmlNode() {}
159
160XmlNode& XmlNode::operator=(XmlNode&&) = default;
161
163 DCHECK(impl_->node);
164 DCHECK(child.impl_->node);
165 RCHECK(xmlAddChild(impl_->node.get(), child.impl_->node.get()));
166
167 // Reaching here means the ownership of |child| transfered to |node|.
168 // Release the pointer so that it doesn't get destructed in this scope.
169 UNUSED(child.impl_->node.release());
170 return true;
171}
172
173bool XmlNode::AddElements(const std::vector<Element>& elements) {
174 for (size_t element_index = 0; element_index < elements.size();
175 ++element_index) {
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) {
181 RCHECK(child_node.SetStringAttribute(attribute_it->first,
182 attribute_it->second));
183 }
184
185 // Note that somehow |SetContent| needs to be called before |AddElements|
186 // otherwise the added children will be overwritten by the content.
187 child_node.SetContent(child_element.content);
188
189 // Recursively set children for the child.
190 RCHECK(child_node.AddElements(child_element.subelements));
191
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);
196 return false;
197 }
198 // Reaching here means the ownership of |child_node| transfered to |node|.
199 // Release the pointer so that it doesn't get destructed in this scope.
200 child_node.impl_->node.release();
201 }
202 return true;
203}
204
205bool XmlNode::SetStringAttribute(const std::string& attribute_name,
206 const std::string& attribute) {
207 DCHECK(impl_->node);
208 return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
209 BAD_CAST attribute.c_str()) != nullptr;
210}
211
212bool XmlNode::SetIntegerAttribute(const std::string& attribute_name,
213 uint64_t number) {
214 DCHECK(impl_->node);
215 return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
216 BAD_CAST(absl::StrFormat("%" PRIu64, number).c_str())) !=
217 nullptr;
218}
219
220bool XmlNode::SetFloatingPointAttribute(const std::string& attribute_name,
221 double number) {
222 DCHECK(impl_->node);
223 return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
224 BAD_CAST(FloatToXmlString(number).c_str())) != nullptr;
225}
226
227bool XmlNode::SetId(uint32_t id) {
228 return SetIntegerAttribute("id", id);
229}
230
231void XmlNode::AddContent(const std::string& content) {
232 DCHECK(impl_->node);
233 xmlNodeAddContent(impl_->node.get(), BAD_CAST content.c_str());
234}
235
236void XmlNode::AddUrlEncodedContent(const std::string& content) {
237 AddContent(urlEncode(content));
238}
239
240void XmlNode::SetContent(const std::string& content) {
241 DCHECK(impl_->node);
242 xmlNodeSetContent(impl_->node.get(), BAD_CAST content.c_str());
243}
244
245void XmlNode::SetUrlEncodedContent(const std::string& content) {
246 SetContent(urlEncode(content));
247}
248
249std::set<std::string> XmlNode::ExtractReferencedNamespaces() const {
250 std::set<std::string> namespaces;
251 TraverseNodesAndCollectNamespaces(impl_->node.get(), &namespaces);
252 return namespaces;
253}
254
255std::string XmlNode::ToString(const std::string& comment) const {
256 // Create an xmlDoc from xmlNodePtr. The node is copied so ownership does not
257 // transfer.
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));
261 } else {
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));
266 }
267
268 // Format the xmlDoc to string.
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",
273 kNiceFormat);
274 std::string output(doc_str, doc_str + doc_str_size);
275 xmlFree(doc_str);
276 return output;
277}
278
279bool XmlNode::GetAttribute(const std::string& name, std::string* value) const {
280 xml::scoped_xml_ptr<xmlChar> str(
281 xmlGetProp(impl_->node.get(), BAD_CAST name.c_str()));
282 if (!str)
283 return false;
284 *value = reinterpret_cast<const char*>(str.get());
285 return true;
286}
287
288xmlNode* XmlNode::GetRawPtr() const {
289 return impl_->node.get();
290}
291
292RepresentationBaseXmlNode::RepresentationBaseXmlNode(const std::string& name)
293 : XmlNode(name) {}
294RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {}
295
296bool RepresentationBaseXmlNode::AddContentProtectionElements(
297 const std::list<ContentProtectionElement>& content_protection_elements) {
298 for (const auto& elem : content_protection_elements) {
299 RCHECK(AddContentProtectionElement(elem));
300 }
301
302 return true;
303}
304
306 const std::string& scheme_id_uri,
307 const std::string& value) {
308 return AddDescriptor("SupplementalProperty", scheme_id_uri, value);
309}
310
312 const std::string& scheme_id_uri,
313 const std::string& value) {
314 return AddDescriptor("EssentialProperty", scheme_id_uri, value);
315}
316
318 const std::string& descriptor_name,
319 const std::string& scheme_id_uri,
320 const std::string& value) {
321 XmlNode descriptor(descriptor_name);
322 RCHECK(descriptor.SetStringAttribute("schemeIdUri", scheme_id_uri));
323 if (!value.empty())
324 RCHECK(descriptor.SetStringAttribute("value", value));
325 return AddChild(std::move(descriptor));
326}
327
328bool RepresentationBaseXmlNode::AddContentProtectionElement(
329 const ContentProtectionElement& content_protection_element) {
330 XmlNode content_protection_node("ContentProtection");
331
332 // @value is an optional attribute.
333 if (!content_protection_element.value.empty()) {
334 RCHECK(content_protection_node.SetStringAttribute(
335 "value", content_protection_element.value));
336 }
337 RCHECK(content_protection_node.SetStringAttribute(
338 "schemeIdUri", content_protection_element.scheme_id_uri));
339
340 for (const auto& pair : content_protection_element.additional_attributes) {
341 RCHECK(content_protection_node.SetStringAttribute(pair.first, pair.second));
342 }
343
344 RCHECK(content_protection_node.AddElements(
345 content_protection_element.subelements));
346 return AddChild(std::move(content_protection_node));
347}
348
349AdaptationSetXmlNode::AdaptationSetXmlNode()
350 : RepresentationBaseXmlNode("AdaptationSet") {}
351AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
352
354 const std::string& scheme_id_uri,
355 const std::string& value) {
356 return AddDescriptor("Accessibility", scheme_id_uri, value);
357}
358
359bool AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
360 const std::string& value) {
361 return AddDescriptor("Role", scheme_id_uri, value);
362}
363
364bool AdaptationSetXmlNode::AddLabelElement(const std::string& value) {
365 XmlNode descriptor("Label");
366 descriptor.SetContent(value);
367 return AddChild(std::move(descriptor));
368}
369
370RepresentationXmlNode::RepresentationXmlNode()
371 : RepresentationBaseXmlNode("Representation") {}
372RepresentationXmlNode::~RepresentationXmlNode() {}
373
374bool RepresentationXmlNode::AddVideoInfo(const VideoInfo& video_info,
375 bool set_width,
376 bool set_height,
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.";
380 return false;
381 }
382
383 if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
384 RCHECK(SetStringAttribute("sar",
385 absl::StrFormat("%d:%d", video_info.pixel_width(),
386 video_info.pixel_height())));
387 }
388
389 if (set_width)
390 RCHECK(SetIntegerAttribute("width", video_info.width()));
391 if (set_height)
392 RCHECK(SetIntegerAttribute("height", video_info.height()));
393 if (set_frame_rate) {
394 RCHECK(SetStringAttribute("frameRate",
395 absl::StrFormat("%d/%d", video_info.time_scale(),
396 video_info.frame_duration())));
397 }
398
399 if (video_info.has_playback_rate()) {
400 RCHECK(SetStringAttribute(
401 "maxPlayoutRate", absl::StrFormat("%d", video_info.playback_rate())));
402 // Since the trick play stream contains only key frames, there is no coding
403 // dependency on the main stream. Simply set the codingDependency to false.
404 // TODO(hmchen): propagate this attribute up to the AdaptationSet, since
405 // all are set to false.
406 RCHECK(SetStringAttribute("codingDependency", "false"));
407 }
408 return true;
409}
410
411bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) {
412 return AddAudioChannelInfo(audio_info) &&
413 AddAudioSamplingRateInfo(audio_info);
414}
415
416bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info,
417 bool use_segment_list,
418 double target_segment_duration) {
419 const bool use_single_segment_url_with_media =
420 media_info.has_text_info() && media_info.has_presentation_time_offset();
421
422 if (media_info.has_media_file_url() && !use_single_segment_url_with_media) {
423 XmlNode base_url("BaseURL");
424 base_url.SetUrlEncodedContent(media_info.media_file_url());
425
426 RCHECK(AddChild(std::move(base_url)));
427 }
428
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 use_single_segment_url_with_media;
434
435 if (!need_segment_base_or_list) {
436 return true;
437 }
438
439 XmlNode child(use_segment_list || use_single_segment_url_with_media
440 ? "SegmentList"
441 : "SegmentBase");
442
443 // Forcing SegmentList for longer audio causes sidx atom to not be
444 // generated, therefore indexRange is not added to MPD if flag is set.
445 if (media_info.has_index_range() && !use_segment_list) {
446 RCHECK(child.SetStringAttribute("indexRange",
447 RangeToString(media_info.index_range())));
448 }
449
450 if (media_info.has_reference_time_scale()) {
451 RCHECK(child.SetIntegerAttribute("timescale",
452 media_info.reference_time_scale()));
453
454 if (use_segment_list && !use_single_segment_url_with_media) {
455 const auto duration_seconds = static_cast<int64_t>(
456 floor(target_segment_duration * media_info.reference_time_scale()));
457 RCHECK(child.SetIntegerAttribute("duration", duration_seconds));
458 }
459 }
460
461 if (media_info.has_presentation_time_offset()) {
462 RCHECK(child.SetIntegerAttribute("presentationTimeOffset",
463 media_info.presentation_time_offset()));
464 }
465
466 if (media_info.has_init_range()) {
467 XmlNode initialization("Initialization");
468 RCHECK(initialization.SetStringAttribute(
469 "range", RangeToString(media_info.init_range())));
470
471 RCHECK(child.AddChild(std::move(initialization)));
472 }
473
474 if (use_single_segment_url_with_media) {
475 XmlNode media_url("SegmentURL");
476 RCHECK(media_url.SetStringAttribute(
477 "media", urlEncode(media_info.media_file_url())));
478 RCHECK(child.AddChild(std::move(media_url)));
479 }
480
481 // Since the SegmentURLs here do not have a @media element,
482 // BaseURL element is mapped to the @media attribute.
483 if (use_segment_list) {
484 for (const Range& subsegment_range : media_info.subsegment_ranges()) {
485 XmlNode subsegment("SegmentURL");
486 RCHECK(subsegment.SetStringAttribute("mediaRange",
487 RangeToString(subsegment_range)));
488
489 RCHECK(child.AddChild(std::move(subsegment)));
490 }
491 }
492
493 RCHECK(AddChild(std::move(child)));
494 return true;
495}
496
498 const MediaInfo& media_info,
499 const std::list<SegmentInfo>& segment_infos,
500 bool low_latency_dash_mode) {
501 XmlNode segment_template("SegmentTemplate");
502
503 int start_number =
504 segment_infos.empty() ? 1 : segment_infos.begin()->start_segment_number;
505
506 if (media_info.has_reference_time_scale()) {
507 RCHECK(segment_template.SetIntegerAttribute(
508 "timescale", media_info.reference_time_scale()));
509 }
510
511 if (media_info.has_segment_duration()) {
512 RCHECK(segment_template.SetIntegerAttribute("duration",
513 media_info.segment_duration()));
514 }
515
516 if (media_info.has_presentation_time_offset()) {
517 RCHECK(segment_template.SetIntegerAttribute(
518 "presentationTimeOffset", media_info.presentation_time_offset()));
519 }
520
521 if (media_info.has_availability_time_offset()) {
522 RCHECK(segment_template.SetFloatingPointAttribute(
523 "availabilityTimeOffset", media_info.availability_time_offset()));
524 }
525
526 if (low_latency_dash_mode) {
527 RCHECK(segment_template.SetStringAttribute("availabilityTimeComplete",
528 "false"));
529 }
530
531 if (media_info.has_init_segment_url()) {
532 RCHECK(segment_template.SetStringAttribute("initialization",
533 media_info.init_segment_url()));
534 }
535
536 if (media_info.has_segment_template_url()) {
537 RCHECK(segment_template.SetStringAttribute(
538 "media", media_info.segment_template_url()));
539 RCHECK(segment_template.SetIntegerAttribute("startNumber", start_number));
540 }
541
542 if (!segment_infos.empty()) {
543 // Don't use SegmentTimeline if all segments except the last one are of
544 // the same duration.
545 if (IsTimelineConstantDuration(segment_infos, start_number)) {
546 RCHECK(segment_template.SetIntegerAttribute(
547 "duration", segment_infos.front().duration));
548 if (absl::GetFlag(FLAGS_dash_add_last_segment_number_when_needed)) {
549 uint32_t last_segment_number = start_number - 1;
550 for (const auto& segment_info_element : segment_infos)
551 last_segment_number += segment_info_element.repeat + 1;
552
554 "http://dashif.org/guidelines/last-segment-number",
555 std::to_string(last_segment_number)));
556 }
557 } else {
558 if (!low_latency_dash_mode) {
559 XmlNode segment_timeline("SegmentTimeline");
560 RCHECK(PopulateSegmentTimeline(segment_infos, &segment_timeline));
561 RCHECK(segment_template.AddChild(std::move(segment_timeline)));
562 }
563 }
564 }
565 return AddChild(std::move(segment_template));
566}
567
568bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
569 std::string audio_channel_config_scheme;
570 std::string audio_channel_config_value;
571
572 if (audio_info.codec() == kEC3Codec) {
573 const auto& codec_data = audio_info.codec_specific_data();
574 // Use MPEG scheme if the mpeg value is available and valid, fallback to
575 // EC3 channel mapping otherwise.
576 // See https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/268
577 const uint32_t ec3_channel_mpeg_value = codec_data.channel_mpeg_value();
578 const uint32_t NO_MAPPING = 0xFFFFFFFF;
579 if (ec3_channel_mpeg_value == NO_MAPPING) {
580 // Convert EC3 channel map into string of hexadecimal digits. Spec:
581 // DASH-IF Interoperability Points v3.0 9.2.1.2.
582 audio_channel_config_value =
583 absl::StrFormat("%04X", codec_data.channel_mask());
584 audio_channel_config_scheme =
585 "tag:dolby.com,2014:dash:audio_channel_configuration:2011";
586 } else {
587 // Calculate EC3 channel configuration descriptor value with MPEG scheme.
588 // Spec: ETSI TS 102 366 V1.4.1 Digital Audio Compression
589 // (AC-3, Enhanced AC-3) I.1.2.
590 audio_channel_config_value =
591 absl::StrFormat("%u", ec3_channel_mpeg_value);
592 audio_channel_config_scheme = "urn:mpeg:mpegB:cicp:ChannelConfiguration";
593 }
594 bool ret = AddDescriptor("AudioChannelConfiguration",
595 audio_channel_config_scheme,
596 audio_channel_config_value);
597 // Dolby Digital Plus JOC descriptor. Spec: ETSI TS 103 420 v1.2.1
598 // Backwards-compatible object audio carriage using Enhanced AC-3 Standard
599 // D.2.2.
600 if (codec_data.ec3_joc_complexity() != 0) {
601 std::string ec3_joc_complexity =
602 absl::StrFormat("%u", codec_data.ec3_joc_complexity());
603 ret &= AddDescriptor("SupplementalProperty",
604 "tag:dolby.com,2018:dash:EC3_ExtensionType:2018",
605 "JOC");
606 ret &= AddDescriptor("SupplementalProperty",
607 "tag:dolby.com,2018:dash:"
608 "EC3_ExtensionComplexityIndex:2018",
609 ec3_joc_complexity);
610 }
611 return ret;
612 } else if (audio_info.codec().substr(0, 4) == kAC4Codec) {
613 const auto& codec_data = audio_info.codec_specific_data();
614 const bool ac4_ims_flag = codec_data.ac4_ims_flag();
615 // Use MPEG scheme if the mpeg value is available and valid, fallback to
616 // AC4 channel mask otherwise.
617 // See https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/268
618 const uint32_t ac4_channel_mpeg_value = codec_data.channel_mpeg_value();
619 const uint32_t NO_MAPPING = 0xFFFFFFFF;
620 if (ac4_channel_mpeg_value == NO_MAPPING) {
621 // Calculate AC-4 channel mask. Spec: ETSI TS 103 190-2 V1.2.1 Digital
622 // Audio Compression (AC-4) Standard; Part 2: Immersive and personalized
623 // audio G.3.1.
624 //
625 // this needs to print only 3 bytes of the 32-bit value
626 audio_channel_config_value =
627 absl::StrFormat("%06X", codec_data.channel_mask());
628 // Note that the channel config schemes for EC-3 and AC-4 are different.
629 // See https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/268.
630 audio_channel_config_scheme =
631 "tag:dolby.com,2015:dash:audio_channel_configuration:2015";
632 } else {
633 // Calculate AC-4 channel configuration descriptor value with MPEG scheme.
634 // Spec: ETSI TS 103 190-2 V1.2.1 Digital Audio Compression (AC-4) Standard;
635 // Part 2: Immersive and personalized audio G.3.2.
636 audio_channel_config_value =
637 absl::StrFormat("%u", ac4_channel_mpeg_value);
638 audio_channel_config_scheme = "urn:mpeg:mpegB:cicp:ChannelConfiguration";
639 }
640 bool ret = AddDescriptor("AudioChannelConfiguration",
641 audio_channel_config_scheme,
642 audio_channel_config_value);
643 if (ac4_ims_flag) {
644 ret &= AddDescriptor("SupplementalProperty",
645 "tag:dolby.com,2016:dash:virtualized_content:2016",
646 "1");
647 }
648 return ret;
649 } else if (audio_info.codec() == kDTSCCodec ||
650 audio_info.codec() == kDTSECodec) {
651 audio_channel_config_value =
652 absl::StrFormat("%u", audio_info.num_channels());
653 audio_channel_config_scheme =
654 "tag:dts.com,2014:dash:audio_channel_configuration:2012";
655 } else if (audio_info.codec() == kDTSXCodec) {
656 const auto& codec_data = audio_info.codec_specific_data();
657 audio_channel_config_value =
658 absl::StrFormat("%08X", codec_data.channel_mask());
659 audio_channel_config_scheme =
660 "tag:dts.com,2018:uhd:audio_channel_configuration";
661 } else {
662 audio_channel_config_value =
663 absl::StrFormat("%u", audio_info.num_channels());
664 audio_channel_config_scheme =
665 "urn:mpeg:dash:23003:3:audio_channel_configuration:2011";
666 }
667
668 return AddDescriptor("AudioChannelConfiguration", audio_channel_config_scheme,
669 audio_channel_config_value);
670}
671
672// MPD expects one number for sampling frequency, or if it is a range it should
673// be space separated.
674bool RepresentationXmlNode::AddAudioSamplingRateInfo(
675 const AudioInfo& audio_info) {
676 return !audio_info.has_sampling_frequency() ||
677 SetIntegerAttribute("audioSamplingRate",
678 audio_info.sampling_frequency());
679}
680
681} // namespace xml
682} // namespace shaka
bool AddAccessibilityElement(const std::string &scheme_id_uri, const std::string &value)
Definition xml_node.cc:353
bool AddLabelElement(const std::string &value)
Definition xml_node.cc:364
bool AddRoleElement(const std::string &scheme_id_uri, const std::string &value)
Definition xml_node.cc:359
bool AddDescriptor(const std::string &descriptor_name, const std::string &scheme_id_uri, const std::string &value)
Definition xml_node.cc:317
bool AddEssentialProperty(const std::string &scheme_id_uri, const std::string &value)
Definition xml_node.cc:311
bool AddSupplementalProperty(const std::string &scheme_id_uri, const std::string &value)
Definition xml_node.cc:305
bool AddVODOnlyInfo(const MediaInfo &media_info, bool use_segment_list, double target_segment_duration)
Definition xml_node.cc:416
bool AddLiveOnlyInfo(const MediaInfo &media_info, const std::list< SegmentInfo > &segment_infos, bool low_latency_dash_mode)
Definition xml_node.cc:497
bool AddAudioInfo(const MediaInfo::AudioInfo &audio_info)
Definition xml_node.cc:411
bool AddVideoInfo(const MediaInfo::VideoInfo &video_info, bool set_width, bool set_height, bool set_frame_rate)
Definition xml_node.cc:374
bool AddChild(XmlNode child)
Definition xml_node.cc:162
bool AddElements(const std::vector< Element > &elements)
Adds Elements to this node using the Element struct.
Definition xml_node.cc:173
std::set< std::string > ExtractReferencedNamespaces() const
Definition xml_node.cc:249
void AddContent(const std::string &content)
Similar to SetContent, but appends to the end of existing content.
Definition xml_node.cc:231
bool SetStringAttribute(const std::string &attribute_name, const std::string &attribute)
Definition xml_node.cc:205
void SetContent(const std::string &content)
Definition xml_node.cc:240
bool SetId(uint32_t id)
Definition xml_node.cc:227
XmlNode(const std::string &name)
Definition xml_node.cc:151
bool SetIntegerAttribute(const std::string &attribute_name, uint64_t number)
Definition xml_node.cc:212
bool GetAttribute(const std::string &name, std::string *value) const
Definition xml_node.cc:279
std::string ToString(const std::string &comment) const
Definition xml_node.cc:255
bool SetFloatingPointAttribute(const std::string &attribute_name, double number)
Definition xml_node.cc:220
All the methods that are virtual are virtual for mocking.