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