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