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 
11 #include <limits>
12 #include <set>
13 
14 #include "packager/base/logging.h"
15 #include "packager/base/macros.h"
16 #include "packager/base/strings/string_number_conversions.h"
17 #include "packager/base/sys_byteorder.h"
18 #include "packager/mpd/base/media_info.pb.h"
19 #include "packager/mpd/base/mpd_utils.h"
20 #include "packager/mpd/base/segment_info.h"
21 
22 DEFINE_bool(segment_template_constant_duration,
23  false,
24  "Generates SegmentTemplate@duration if all segments except the "
25  "last one has the same duration if this flag is set to true.");
26 
27 namespace shaka {
28 
29 using xml::XmlNode;
30 typedef MediaInfo::AudioInfo AudioInfo;
31 typedef MediaInfo::VideoInfo VideoInfo;
32 
33 namespace {
34 const char kEC3Codec[] = "ec-3";
35 
36 std::string RangeToString(const Range& range) {
37  return base::Uint64ToString(range.begin()) + "-" +
38  base::Uint64ToString(range.end());
39 }
40 
41 // Check if segments are continuous and all segments except the last one are of
42 // the same duration.
43 bool IsTimelineConstantDuration(const std::list<SegmentInfo>& segment_infos,
44  uint32_t start_number) {
45  if (!FLAGS_segment_template_constant_duration)
46  return false;
47 
48  DCHECK(!segment_infos.empty());
49  if (segment_infos.size() > 2)
50  return false;
51 
52  const SegmentInfo& first_segment = segment_infos.front();
53  if (first_segment.start_time != first_segment.duration * (start_number - 1))
54  return false;
55 
56  if (segment_infos.size() == 1)
57  return true;
58 
59  const SegmentInfo& last_segment = segment_infos.back();
60  if (last_segment.repeat != 0)
61  return false;
62 
63  const int64_t expected_last_segment_start_time =
64  first_segment.start_time +
65  first_segment.duration * (first_segment.repeat + 1);
66  return expected_last_segment_start_time == last_segment.start_time;
67 }
68 
69 bool PopulateSegmentTimeline(const std::list<SegmentInfo>& segment_infos,
70  XmlNode* segment_timeline) {
71  for (const SegmentInfo& segment_info : segment_infos) {
72  XmlNode s_element("S");
73  s_element.SetIntegerAttribute("t", segment_info.start_time);
74  s_element.SetIntegerAttribute("d", segment_info.duration);
75  if (segment_info.repeat > 0)
76  s_element.SetIntegerAttribute("r", segment_info.repeat);
77 
78  CHECK(segment_timeline->AddChild(s_element.PassScopedPtr()));
79  }
80 
81  return true;
82 }
83 
84 void CollectNamespaceFromName(const std::string& name,
85  std::set<std::string>* namespaces) {
86  const size_t pos = name.find(':');
87  if (pos != std::string::npos)
88  namespaces->insert(name.substr(0, pos));
89 }
90 
91 void TraverseAttrsAndCollectNamespaces(const xmlAttr* attr,
92  std::set<std::string>* namespaces) {
93  for (const xmlAttr* cur_attr = attr; cur_attr; cur_attr = cur_attr->next) {
94  CollectNamespaceFromName(reinterpret_cast<const char*>(cur_attr->name),
95  namespaces);
96  }
97 }
98 
99 void TraverseNodesAndCollectNamespaces(const xmlNode* node,
100  std::set<std::string>* namespaces) {
101  for (const xmlNode* cur_node = node; cur_node; cur_node = cur_node->next) {
102  CollectNamespaceFromName(reinterpret_cast<const char*>(cur_node->name),
103  namespaces);
104 
105  TraverseNodesAndCollectNamespaces(cur_node->children, namespaces);
106  TraverseAttrsAndCollectNamespaces(cur_node->properties, namespaces);
107  }
108 }
109 
110 } // namespace
111 
112 namespace xml {
113 
114 XmlNode::XmlNode(const char* name) : node_(xmlNewNode(NULL, BAD_CAST name)) {
115  DCHECK(name);
116  DCHECK(node_);
117 }
118 
119 XmlNode::~XmlNode() {}
120 
121 bool XmlNode::AddChild(scoped_xml_ptr<xmlNode> child) {
122  DCHECK(node_);
123  DCHECK(child);
124  if (!xmlAddChild(node_.get(), child.get()))
125  return false;
126 
127  // Reaching here means the ownership of |child| transfered to |node_|.
128  // Release the pointer so that it doesn't get destructed in this scope.
129  ignore_result(child.release());
130  return true;
131 }
132 
133 bool XmlNode::AddElements(const std::vector<Element>& elements) {
134  for (size_t element_index = 0; element_index < elements.size();
135  ++element_index) {
136  const Element& child_element = elements[element_index];
137  XmlNode child_node(child_element.name.c_str());
138  for (std::map<std::string, std::string>::const_iterator attribute_it =
139  child_element.attributes.begin();
140  attribute_it != child_element.attributes.end(); ++attribute_it) {
141  child_node.SetStringAttribute(attribute_it->first.c_str(),
142  attribute_it->second);
143  }
144 
145  // Note that somehow |SetContent| needs to be called before |AddElements|
146  // otherwise the added children will be overwritten by the content.
147  child_node.SetContent(child_element.content);
148 
149  // Recursively set children for the child.
150  if (!child_node.AddElements(child_element.subelements))
151  return false;
152 
153  if (!xmlAddChild(node_.get(), child_node.GetRawPtr())) {
154  LOG(ERROR) << "Failed to set child " << child_element.name
155  << " to parent element "
156  << reinterpret_cast<const char*>(node_->name);
157  return false;
158  }
159  // Reaching here means the ownership of |child_node| transfered to |node_|.
160  // Release the pointer so that it doesn't get destructed in this scope.
161  ignore_result(child_node.Release());
162  }
163  return true;
164 }
165 
166 void XmlNode::SetStringAttribute(const char* attribute_name,
167  const std::string& attribute) {
168  DCHECK(node_);
169  DCHECK(attribute_name);
170  xmlSetProp(node_.get(), BAD_CAST attribute_name, BAD_CAST attribute.c_str());
171 }
172 
173 void XmlNode::SetIntegerAttribute(const char* attribute_name, uint64_t number) {
174  DCHECK(node_);
175  DCHECK(attribute_name);
176  xmlSetProp(node_.get(),
177  BAD_CAST attribute_name,
178  BAD_CAST (base::Uint64ToString(number).c_str()));
179 }
180 
181 void XmlNode::SetFloatingPointAttribute(const char* attribute_name,
182  double number) {
183  DCHECK(node_);
184  DCHECK(attribute_name);
185  xmlSetProp(node_.get(), BAD_CAST attribute_name,
186  BAD_CAST(base::DoubleToString(number).c_str()));
187 }
188 
189 void XmlNode::SetId(uint32_t id) {
190  SetIntegerAttribute("id", id);
191 }
192 
193 void XmlNode::SetContent(const std::string& content) {
194  DCHECK(node_);
195  xmlNodeSetContent(node_.get(), BAD_CAST content.c_str());
196 }
197 
198 std::set<std::string> XmlNode::ExtractReferencedNamespaces() {
199  std::set<std::string> namespaces;
200  TraverseNodesAndCollectNamespaces(node_.get(), &namespaces);
201  return namespaces;
202 }
203 
204 scoped_xml_ptr<xmlNode> XmlNode::PassScopedPtr() {
205  DVLOG(2) << "Passing node_.";
206  DCHECK(node_);
207  return std::move(node_);
208 }
209 
210 xmlNodePtr XmlNode::Release() {
211  DVLOG(2) << "Releasing node_.";
212  DCHECK(node_);
213  return node_.release();
214 }
215 
216 xmlNodePtr XmlNode::GetRawPtr() {
217  return node_.get();
218 }
219 
220 RepresentationBaseXmlNode::RepresentationBaseXmlNode(const char* name)
221  : XmlNode(name) {}
222 RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {}
223 
224 bool RepresentationBaseXmlNode::AddContentProtectionElements(
225  const std::list<ContentProtectionElement>& content_protection_elements) {
226  std::list<ContentProtectionElement>::const_iterator content_protection_it =
227  content_protection_elements.begin();
228  for (; content_protection_it != content_protection_elements.end();
229  ++content_protection_it) {
230  if (!AddContentProtectionElement(*content_protection_it))
231  return false;
232  }
233 
234  return true;
235 }
236 
238  const std::string& scheme_id_uri,
239  const std::string& value) {
240  AddDescriptor("SupplementalProperty", scheme_id_uri, value);
241 }
242 
244  const std::string& scheme_id_uri,
245  const std::string& value) {
246  AddDescriptor("EssentialProperty", scheme_id_uri, value);
247 }
248 
250  const std::string& descriptor_name,
251  const std::string& scheme_id_uri,
252  const std::string& value) {
253  XmlNode descriptor(descriptor_name.c_str());
254  descriptor.SetStringAttribute("schemeIdUri", scheme_id_uri);
255  if (!value.empty())
256  descriptor.SetStringAttribute("value", value);
257  return AddChild(descriptor.PassScopedPtr());
258 }
259 
260 bool RepresentationBaseXmlNode::AddContentProtectionElement(
261  const ContentProtectionElement& content_protection_element) {
262  XmlNode content_protection_node("ContentProtection");
263 
264  // @value is an optional attribute.
265  if (!content_protection_element.value.empty()) {
266  content_protection_node.SetStringAttribute(
267  "value", content_protection_element.value);
268  }
269  content_protection_node.SetStringAttribute(
270  "schemeIdUri", content_protection_element.scheme_id_uri);
271 
272  typedef std::map<std::string, std::string> AttributesMapType;
273  const AttributesMapType& additional_attributes =
274  content_protection_element.additional_attributes;
275 
276  AttributesMapType::const_iterator attributes_it =
277  additional_attributes.begin();
278  for (; attributes_it != additional_attributes.end(); ++attributes_it) {
279  content_protection_node.SetStringAttribute(attributes_it->first.c_str(),
280  attributes_it->second);
281  }
282 
283  if (!content_protection_node.AddElements(
284  content_protection_element.subelements)) {
285  return false;
286  }
287  return AddChild(content_protection_node.PassScopedPtr());
288 }
289 
290 AdaptationSetXmlNode::AdaptationSetXmlNode()
291  : RepresentationBaseXmlNode("AdaptationSet") {}
292 AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
293 
295  const std::string& scheme_id_uri,
296  const std::string& value) {
297  AddDescriptor("Accessibility", scheme_id_uri, value);
298 }
299 
300 void AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
301  const std::string& value) {
302  AddDescriptor("Role", scheme_id_uri, value);
303 }
304 
305 RepresentationXmlNode::RepresentationXmlNode()
306  : RepresentationBaseXmlNode("Representation") {}
307 RepresentationXmlNode::~RepresentationXmlNode() {}
308 
309 bool RepresentationXmlNode::AddVideoInfo(const VideoInfo& video_info,
310  bool set_width,
311  bool set_height,
312  bool set_frame_rate) {
313  if (!video_info.has_width() || !video_info.has_height()) {
314  LOG(ERROR) << "Missing width or height for adding a video info.";
315  return false;
316  }
317 
318  if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
319  SetStringAttribute("sar", base::IntToString(video_info.pixel_width()) +
320  ":" +
321  base::IntToString(video_info.pixel_height()));
322  }
323 
324  if (set_width)
325  SetIntegerAttribute("width", video_info.width());
326  if (set_height)
327  SetIntegerAttribute("height", video_info.height());
328  if (set_frame_rate) {
329  SetStringAttribute("frameRate",
330  base::IntToString(video_info.time_scale()) + "/" +
331  base::IntToString(video_info.frame_duration()));
332  }
333 
334  if (video_info.has_playback_rate()) {
335  SetStringAttribute("maxPlayoutRate",
336  base::IntToString(video_info.playback_rate()));
337  // Since the trick play stream contains only key frames, there is no coding
338  // dependency on the main stream. Simply set the codingDependency to false.
339  // TODO(hmchen): propagate this attribute up to the AdaptationSet, since
340  // all are set to false.
341  SetStringAttribute("codingDependency", "false");
342  }
343  return true;
344 }
345 
346 bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) {
347  if (!AddAudioChannelInfo(audio_info))
348  return false;
349 
350  AddAudioSamplingRateInfo(audio_info);
351  return true;
352 }
353 
354 bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
355  if (media_info.has_media_file_url()) {
356  XmlNode base_url("BaseURL");
357  base_url.SetContent(media_info.media_file_url());
358 
359  if (!AddChild(base_url.PassScopedPtr()))
360  return false;
361  }
362 
363  const bool need_segment_base = media_info.has_index_range() ||
364  media_info.has_init_range() ||
365  media_info.has_reference_time_scale();
366 
367  if (need_segment_base) {
368  XmlNode segment_base("SegmentBase");
369  if (media_info.has_index_range()) {
370  segment_base.SetStringAttribute("indexRange",
371  RangeToString(media_info.index_range()));
372  }
373 
374  if (media_info.has_reference_time_scale()) {
375  segment_base.SetIntegerAttribute("timescale",
376  media_info.reference_time_scale());
377  }
378 
379  if (media_info.has_presentation_time_offset()) {
380  segment_base.SetIntegerAttribute("presentationTimeOffset",
381  media_info.presentation_time_offset());
382  }
383 
384  if (media_info.has_init_range()) {
385  XmlNode initialization("Initialization");
386  initialization.SetStringAttribute("range",
387  RangeToString(media_info.init_range()));
388 
389  if (!segment_base.AddChild(initialization.PassScopedPtr()))
390  return false;
391  }
392 
393  if (!AddChild(segment_base.PassScopedPtr()))
394  return false;
395  }
396 
397  return true;
398 }
399 
401  const MediaInfo& media_info,
402  const std::list<SegmentInfo>& segment_infos,
403  uint32_t start_number) {
404  XmlNode segment_template("SegmentTemplate");
405  if (media_info.has_reference_time_scale()) {
406  segment_template.SetIntegerAttribute("timescale",
407  media_info.reference_time_scale());
408  }
409 
410  if (media_info.has_presentation_time_offset()) {
411  segment_template.SetIntegerAttribute("presentationTimeOffset",
412  media_info.presentation_time_offset());
413  }
414 
415  if (media_info.has_init_segment_url()) {
416  segment_template.SetStringAttribute("initialization",
417  media_info.init_segment_url());
418  }
419 
420  if (media_info.has_segment_template_url()) {
421  segment_template.SetStringAttribute("media",
422  media_info.segment_template_url());
423  segment_template.SetIntegerAttribute("startNumber", start_number);
424  }
425 
426  if (!segment_infos.empty()) {
427  // Don't use SegmentTimeline if all segments except the last one are of
428  // the same duration.
429  if (IsTimelineConstantDuration(segment_infos, start_number)) {
430  segment_template.SetIntegerAttribute("duration",
431  segment_infos.front().duration);
432  } else {
433  XmlNode segment_timeline("SegmentTimeline");
434  if (!PopulateSegmentTimeline(segment_infos, &segment_timeline) ||
435  !segment_template.AddChild(segment_timeline.PassScopedPtr())) {
436  return false;
437  }
438  }
439  }
440  return AddChild(segment_template.PassScopedPtr());
441 }
442 
443 bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
444  std::string audio_channel_config_scheme;
445  std::string audio_channel_config_value;
446 
447  if (audio_info.codec() == kEC3Codec) {
448  // Convert EC3 channel map into string of hexadecimal digits. Spec: DASH-IF
449  // Interoperability Points v3.0 9.2.1.2.
450  const uint16_t ec3_channel_map =
451  base::HostToNet16(audio_info.codec_specific_data().ec3_channel_map());
452  audio_channel_config_value =
453  base::HexEncode(&ec3_channel_map, sizeof(ec3_channel_map));
454  audio_channel_config_scheme =
455  "tag:dolby.com,2014:dash:audio_channel_configuration:2011";
456  } else {
457  audio_channel_config_value = base::UintToString(audio_info.num_channels());
458  audio_channel_config_scheme =
459  "urn:mpeg:dash:23003:3:audio_channel_configuration:2011";
460  }
461 
462  return AddDescriptor("AudioChannelConfiguration", audio_channel_config_scheme,
463  audio_channel_config_value);
464 }
465 
466 // MPD expects one number for sampling frequency, or if it is a range it should
467 // be space separated.
468 void RepresentationXmlNode::AddAudioSamplingRateInfo(
469  const AudioInfo& audio_info) {
470  if (audio_info.has_sampling_frequency())
471  SetIntegerAttribute("audioSamplingRate", audio_info.sampling_frequency());
472 }
473 
474 } // namespace xml
475 } // namespace shaka
bool AddVideoInfo(const MediaInfo::VideoInfo &video_info, bool set_width, bool set_height, bool set_frame_rate)
Definition: xml_node.cc:309
bool AddDescriptor(const std::string &descriptor_name, const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:249
std::set< std::string > ExtractReferencedNamespaces()
Definition: xml_node.cc:198
void SetFloatingPointAttribute(const char *attribute_name, double number)
Definition: xml_node.cc:181
scoped_xml_ptr< xmlNode > PassScopedPtr()
Definition: xml_node.cc:204
XmlNode(const char *name)
Definition: xml_node.cc:114
All the methods that are virtual are virtual for mocking.
bool AddVODOnlyInfo(const MediaInfo &media_info)
Definition: xml_node.cc:354
void AddEssentialProperty(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:243
void SetStringAttribute(const char *attribute_name, const std::string &attribute)
Definition: xml_node.cc:166
bool AddChild(scoped_xml_ptr< xmlNode > child)
Definition: xml_node.cc:121
xmlNodePtr Release()
Definition: xml_node.cc:210
bool AddLiveOnlyInfo(const MediaInfo &media_info, const std::list< SegmentInfo > &segment_infos, uint32_t start_number)
Definition: xml_node.cc:400
void SetId(uint32_t id)
Definition: xml_node.cc:189
void AddAccessibilityElement(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:294
bool AddElements(const std::vector< Element > &elements)
Adds Elements to this node using the Element struct.
Definition: xml_node.cc:133
void AddSupplementalProperty(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:237
void SetIntegerAttribute(const char *attribute_name, uint64_t number)
Definition: xml_node.cc:173
void AddRoleElement(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:300
void SetContent(const std::string &content)
Definition: xml_node.cc:193
bool AddAudioInfo(const MediaInfo::AudioInfo &audio_info)
Definition: xml_node.cc:346
xmlNodePtr GetRawPtr()
Definition: xml_node.cc:216