Shaka Packager SDK
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 
31 ABSL_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 
37 ABSL_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 
44 namespace shaka {
45 
46 using xml::XmlNode;
47 typedef MediaInfo::AudioInfo AudioInfo;
48 typedef MediaInfo::VideoInfo VideoInfo;
49 
50 namespace {
51 const char kEC3Codec[] = "ec-3";
52 const char kAC4Codec[] = "ac-4";
53 const char kDTSCCodec[] = "dtsc";
54 const char kDTSECodec[] = "dtse";
55 const char kDTSXCodec[] = "dtsx";
56 
57 std::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 
69 std::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.
75 bool 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 
101 bool 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 
116 void 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 
123 void 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 
131 void 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 
144 namespace xml {
145 
146 class XmlNode::Impl {
147  public:
148  scoped_xml_ptr<xmlNode> node;
149 };
150 
151 XmlNode::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 
156 XmlNode::XmlNode(XmlNode&&) = default;
157 
158 XmlNode::~XmlNode() {}
159 
160 XmlNode& 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 
173 bool 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 
205 bool 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 
212 bool 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 
220 bool 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 
227 bool XmlNode::SetId(uint32_t id) {
228  return SetIntegerAttribute("id", id);
229 }
230 
231 void XmlNode::AddContent(const std::string& content) {
232  DCHECK(impl_->node);
233  xmlNodeAddContent(impl_->node.get(), BAD_CAST content.c_str());
234 }
235 
236 void XmlNode::AddUrlEncodedContent(const std::string& content) {
237  AddContent(urlEncode(content));
238 }
239 
240 void XmlNode::SetContent(const std::string& content) {
241  DCHECK(impl_->node);
242  xmlNodeSetContent(impl_->node.get(), BAD_CAST content.c_str());
243 }
244 
245 void XmlNode::SetUrlEncodedContent(const std::string& content) {
246  SetContent(urlEncode(content));
247 }
248 
249 std::set<std::string> XmlNode::ExtractReferencedNamespaces() const {
250  std::set<std::string> namespaces;
251  TraverseNodesAndCollectNamespaces(impl_->node.get(), &namespaces);
252  return namespaces;
253 }
254 
255 std::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 
279 bool 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 
288 xmlNode* XmlNode::GetRawPtr() const {
289  return impl_->node.get();
290 }
291 
292 RepresentationBaseXmlNode::RepresentationBaseXmlNode(const std::string& name)
293  : XmlNode(name) {}
294 RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {}
295 
296 bool 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 
328 bool 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 
349 AdaptationSetXmlNode::AdaptationSetXmlNode()
350  : RepresentationBaseXmlNode("AdaptationSet") {}
351 AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
352 
354  const std::string& scheme_id_uri,
355  const std::string& value) {
356  return AddDescriptor("Accessibility", scheme_id_uri, value);
357 }
358 
359 bool AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
360  const std::string& value) {
361  return AddDescriptor("Role", scheme_id_uri, value);
362 }
363 
364 bool AdaptationSetXmlNode::AddLabelElement(const std::string& value) {
365  XmlNode descriptor("Label");
366  descriptor.SetContent(value);
367  return AddChild(std::move(descriptor));
368 }
369 
370 RepresentationXmlNode::RepresentationXmlNode()
371  : RepresentationBaseXmlNode("Representation") {}
372 RepresentationXmlNode::~RepresentationXmlNode() {}
373 
374 bool 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 
411 bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) {
412  return AddAudioChannelInfo(audio_info) &&
413  AddAudioSamplingRateInfo(audio_info);
414 }
415 
416 bool 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 
568 bool 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.
674 bool 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.
Definition: crypto_flags.cc:66