DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator
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 <limits>
10 #include <set>
11 
12 #include "packager/base/logging.h"
13 #include "packager/base/macros.h"
14 #include "packager/base/strings/string_number_conversions.h"
15 #include "packager/base/sys_byteorder.h"
16 #include "packager/mpd/base/media_info.pb.h"
17 #include "packager/mpd/base/mpd_utils.h"
18 #include "packager/mpd/base/segment_info.h"
19 
20 namespace shaka {
21 
22 using xml::XmlNode;
23 typedef MediaInfo::AudioInfo AudioInfo;
24 typedef MediaInfo::VideoInfo VideoInfo;
25 
26 namespace {
27 const char kEC3Codec[] = "ec-3";
28 
29 std::string RangeToString(const Range& range) {
30  return base::Uint64ToString(range.begin()) + "-" +
31  base::Uint64ToString(range.end());
32 }
33 
34 bool PopulateSegmentTimeline(const std::list<SegmentInfo>& segment_infos,
35  XmlNode* segment_timeline) {
36  for (std::list<SegmentInfo>::const_iterator it = segment_infos.begin();
37  it != segment_infos.end();
38  ++it) {
39  XmlNode s_element("S");
40  s_element.SetIntegerAttribute("t", it->start_time);
41  s_element.SetIntegerAttribute("d", it->duration);
42  if (it->repeat > 0)
43  s_element.SetIntegerAttribute("r", it->repeat);
44 
45  CHECK(segment_timeline->AddChild(s_element.PassScopedPtr()));
46  }
47 
48  return true;
49 }
50 
51 } // namespace
52 
53 namespace xml {
54 
55 XmlNode::XmlNode(const char* name) : node_(xmlNewNode(NULL, BAD_CAST name)) {
56  DCHECK(name);
57  DCHECK(node_);
58 }
59 
60 XmlNode::~XmlNode() {}
61 
62 bool XmlNode::AddChild(scoped_xml_ptr<xmlNode> child) {
63  DCHECK(node_);
64  DCHECK(child);
65  if (!xmlAddChild(node_.get(), child.get()))
66  return false;
67 
68  // Reaching here means the ownership of |child| transfered to |node_|.
69  // Release the pointer so that it doesn't get destructed in this scope.
70  ignore_result(child.release());
71  return true;
72 }
73 
74 bool XmlNode::AddElements(const std::vector<Element>& elements) {
75  for (size_t element_index = 0; element_index < elements.size();
76  ++element_index) {
77  const Element& child_element = elements[element_index];
78  XmlNode child_node(child_element.name.c_str());
79  for (std::map<std::string, std::string>::const_iterator attribute_it =
80  child_element.attributes.begin();
81  attribute_it != child_element.attributes.end(); ++attribute_it) {
82  child_node.SetStringAttribute(attribute_it->first.c_str(),
83  attribute_it->second);
84  }
85  // Recursively set children for the child.
86  if (!child_node.AddElements(child_element.subelements))
87  return false;
88 
89  child_node.SetContent(child_element.content);
90 
91  if (!xmlAddChild(node_.get(), child_node.GetRawPtr())) {
92  LOG(ERROR) << "Failed to set child " << child_element.name
93  << " to parent element "
94  << reinterpret_cast<const char*>(node_->name);
95  return false;
96  }
97  // Reaching here means the ownership of |child_node| transfered to |node_|.
98  // Release the pointer so that it doesn't get destructed in this scope.
99  ignore_result(child_node.Release());
100  }
101  return true;
102 }
103 
104 void XmlNode::SetStringAttribute(const char* attribute_name,
105  const std::string& attribute) {
106  DCHECK(node_);
107  DCHECK(attribute_name);
108  xmlSetProp(node_.get(), BAD_CAST attribute_name, BAD_CAST attribute.c_str());
109 }
110 
111 void XmlNode::SetIntegerAttribute(const char* attribute_name, uint64_t number) {
112  DCHECK(node_);
113  DCHECK(attribute_name);
114  xmlSetProp(node_.get(),
115  BAD_CAST attribute_name,
116  BAD_CAST (base::Uint64ToString(number).c_str()));
117 }
118 
119 void XmlNode::SetFloatingPointAttribute(const char* attribute_name,
120  double number) {
121  DCHECK(node_);
122  DCHECK(attribute_name);
123  xmlSetProp(node_.get(), BAD_CAST attribute_name,
124  BAD_CAST(DoubleToString(number).c_str()));
125 }
126 
127 void XmlNode::SetId(uint32_t id) {
128  SetIntegerAttribute("id", id);
129 }
130 
131 void XmlNode::SetContent(const std::string& content) {
132  DCHECK(node_);
133  xmlNodeSetContent(node_.get(), BAD_CAST content.c_str());
134 }
135 
136 scoped_xml_ptr<xmlNode> XmlNode::PassScopedPtr() {
137  DVLOG(2) << "Passing node_.";
138  DCHECK(node_);
139  return std::move(node_);
140 }
141 
142 xmlNodePtr XmlNode::Release() {
143  DVLOG(2) << "Releasing node_.";
144  DCHECK(node_);
145  return node_.release();
146 }
147 
148 xmlNodePtr XmlNode::GetRawPtr() {
149  return node_.get();
150 }
151 
152 RepresentationBaseXmlNode::RepresentationBaseXmlNode(const char* name)
153  : XmlNode(name) {}
154 RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {}
155 
156 bool RepresentationBaseXmlNode::AddContentProtectionElements(
157  const std::list<ContentProtectionElement>& content_protection_elements) {
158  std::list<ContentProtectionElement>::const_iterator content_protection_it =
159  content_protection_elements.begin();
160  for (; content_protection_it != content_protection_elements.end();
161  ++content_protection_it) {
162  if (!AddContentProtectionElement(*content_protection_it))
163  return false;
164  }
165 
166  return true;
167 }
168 
170  const std::string& scheme_id_uri,
171  const std::string& value) {
172  XmlNode supplemental_property("SupplementalProperty");
173  supplemental_property.SetStringAttribute("schemeIdUri", scheme_id_uri);
174  supplemental_property.SetStringAttribute("value", value);
175  AddChild(supplemental_property.PassScopedPtr());
176 }
177 
179  const std::string& scheme_id_uri,
180  const std::string& value) {
181  XmlNode essential_property("EssentialProperty");
182  essential_property.SetStringAttribute("schemeIdUri", scheme_id_uri);
183  essential_property.SetStringAttribute("value", value);
184  AddChild(essential_property.PassScopedPtr());
185 }
186 
187 bool RepresentationBaseXmlNode::AddContentProtectionElement(
188  const ContentProtectionElement& content_protection_element) {
189  XmlNode content_protection_node("ContentProtection");
190 
191  // @value is an optional attribute.
192  if (!content_protection_element.value.empty()) {
193  content_protection_node.SetStringAttribute(
194  "value", content_protection_element.value);
195  }
196  content_protection_node.SetStringAttribute(
197  "schemeIdUri", content_protection_element.scheme_id_uri);
198 
199  typedef std::map<std::string, std::string> AttributesMapType;
200  const AttributesMapType& additional_attributes =
201  content_protection_element.additional_attributes;
202 
203  AttributesMapType::const_iterator attributes_it =
204  additional_attributes.begin();
205  for (; attributes_it != additional_attributes.end(); ++attributes_it) {
206  content_protection_node.SetStringAttribute(attributes_it->first.c_str(),
207  attributes_it->second);
208  }
209 
210  if (!content_protection_node.AddElements(
211  content_protection_element.subelements)) {
212  return false;
213  }
214  return AddChild(content_protection_node.PassScopedPtr());
215 }
216 
217 AdaptationSetXmlNode::AdaptationSetXmlNode()
218  : RepresentationBaseXmlNode("AdaptationSet") {}
219 AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
220 
221 void AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
222  const std::string& value) {
223  XmlNode role("Role");
224  role.SetStringAttribute("schemeIdUri", scheme_id_uri);
225  role.SetStringAttribute("value", value);
226  AddChild(role.PassScopedPtr());
227 }
228 
229 RepresentationXmlNode::RepresentationXmlNode()
230  : RepresentationBaseXmlNode("Representation") {}
231 RepresentationXmlNode::~RepresentationXmlNode() {}
232 
233 bool RepresentationXmlNode::AddVideoInfo(const VideoInfo& video_info,
234  bool set_width,
235  bool set_height,
236  bool set_frame_rate) {
237  if (!video_info.has_width() || !video_info.has_height()) {
238  LOG(ERROR) << "Missing width or height for adding a video info.";
239  return false;
240  }
241 
242  if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
243  SetStringAttribute("sar", base::IntToString(video_info.pixel_width()) +
244  ":" +
245  base::IntToString(video_info.pixel_height()));
246  }
247 
248  if (set_width)
249  SetIntegerAttribute("width", video_info.width());
250  if (set_height)
251  SetIntegerAttribute("height", video_info.height());
252  if (set_frame_rate) {
253  SetStringAttribute("frameRate",
254  base::IntToString(video_info.time_scale()) + "/" +
255  base::IntToString(video_info.frame_duration()));
256  }
257 
258  if (video_info.has_playback_rate()) {
259  SetStringAttribute("maxPlayoutRate",
260  base::IntToString(video_info.playback_rate()));
261  // Since the trick play stream contains only key frames, there is no coding
262  // dependency on the main stream. Simply set the codingDependency to false.
263  // TODO(hmchen): propagate this attribute up to the AdaptationSet, since
264  // all are set to false.
265  SetStringAttribute("codingDependency", "false");
266  }
267  return true;
268 }
269 
270 bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) {
271  if (!AddAudioChannelInfo(audio_info))
272  return false;
273 
274  AddAudioSamplingRateInfo(audio_info);
275  return true;
276 }
277 
278 bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
279  if (media_info.has_media_file_name()) {
280  XmlNode base_url("BaseURL");
281  base_url.SetContent(media_info.media_file_name());
282 
283  if (!AddChild(base_url.PassScopedPtr()))
284  return false;
285  }
286 
287  const bool need_segment_base = media_info.has_index_range() ||
288  media_info.has_init_range() ||
289  media_info.has_reference_time_scale();
290 
291  if (need_segment_base) {
292  XmlNode segment_base("SegmentBase");
293  if (media_info.has_index_range()) {
294  segment_base.SetStringAttribute("indexRange",
295  RangeToString(media_info.index_range()));
296  }
297 
298  if (media_info.has_reference_time_scale()) {
299  segment_base.SetIntegerAttribute("timescale",
300  media_info.reference_time_scale());
301  }
302 
303  if (media_info.has_init_range()) {
304  XmlNode initialization("Initialization");
305  initialization.SetStringAttribute("range",
306  RangeToString(media_info.init_range()));
307 
308  if (!segment_base.AddChild(initialization.PassScopedPtr()))
309  return false;
310  }
311 
312  if (!AddChild(segment_base.PassScopedPtr()))
313  return false;
314  }
315 
316  return true;
317 }
318 
320  const MediaInfo& media_info,
321  const std::list<SegmentInfo>& segment_infos,
322  uint32_t start_number) {
323  XmlNode segment_template("SegmentTemplate");
324  if (media_info.has_reference_time_scale()) {
325  segment_template.SetIntegerAttribute("timescale",
326  media_info.reference_time_scale());
327  }
328 
329  if (media_info.has_init_segment_name()) {
330  // The spec does not allow '$Number$' and '$Time$' in initialization
331  // attribute.
332  // TODO(rkuroiwa, kqyang): Swap this check out with a better check. These
333  // templates allow formatting as well.
334  const std::string& init_segment_name = media_info.init_segment_name();
335  if (init_segment_name.find("$Number$") != std::string::npos ||
336  init_segment_name.find("$Time$") != std::string::npos) {
337  LOG(ERROR) << "$Number$ and $Time$ cannot be used for "
338  "SegmentTemplate@initialization";
339  return false;
340  }
341  segment_template.SetStringAttribute("initialization",
342  media_info.init_segment_name());
343  }
344 
345  if (media_info.has_segment_template()) {
346  segment_template.SetStringAttribute("media", media_info.segment_template());
347 
348  // TODO(rkuroiwa): Need a better check. $$Number is legitimate but not a
349  // template.
350  if (media_info.segment_template().find("$Number") != std::string::npos) {
351  DCHECK_GE(start_number, 1u);
352  segment_template.SetIntegerAttribute("startNumber", start_number);
353  }
354  }
355 
356  // TODO(rkuroiwa): Find out when a live MPD doesn't require SegmentTimeline.
357  XmlNode segment_timeline("SegmentTimeline");
358 
359  return PopulateSegmentTimeline(segment_infos, &segment_timeline) &&
360  segment_template.AddChild(segment_timeline.PassScopedPtr()) &&
361  AddChild(segment_template.PassScopedPtr());
362 }
363 
364 bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
365  std::string audio_channel_config_scheme;
366  std::string audio_channel_config_value;
367 
368  if (audio_info.codec() == kEC3Codec) {
369  // Convert EC3 channel map into string of hexadecimal digits. Spec: DASH-IF
370  // Interoperability Points v3.0 9.2.1.2.
371  const uint16_t ec3_channel_map =
372  base::HostToNet16(audio_info.codec_specific_data().ec3_channel_map());
373  audio_channel_config_value =
374  base::HexEncode(&ec3_channel_map, sizeof(ec3_channel_map));
375  audio_channel_config_scheme =
376  "tag:dolby.com,2014:dash:audio_channel_configuration:2011";
377  } else {
378  audio_channel_config_value = base::UintToString(audio_info.num_channels());
379  audio_channel_config_scheme =
380  "urn:mpeg:dash:23003:3:audio_channel_configuration:2011";
381  }
382 
383  XmlNode audio_channel_config("AudioChannelConfiguration");
384  audio_channel_config.SetStringAttribute("schemeIdUri",
385  audio_channel_config_scheme);
386  audio_channel_config.SetStringAttribute("value", audio_channel_config_value);
387 
388  return AddChild(audio_channel_config.PassScopedPtr());
389 }
390 
391 // MPD expects one number for sampling frequency, or if it is a range it should
392 // be space separated.
393 void RepresentationXmlNode::AddAudioSamplingRateInfo(
394  const AudioInfo& audio_info) {
395  if (audio_info.has_sampling_frequency())
396  SetIntegerAttribute("audioSamplingRate", audio_info.sampling_frequency());
397 }
398 
399 } // namespace xml
400 } // namespace shaka
bool AddVideoInfo(const MediaInfo::VideoInfo &video_info, bool set_width, bool set_height, bool set_frame_rate)
Definition: xml_node.cc:233
void SetFloatingPointAttribute(const char *attribute_name, double number)
Definition: xml_node.cc:119
scoped_xml_ptr< xmlNode > PassScopedPtr()
Definition: xml_node.cc:136
XmlNode(const char *name)
Definition: xml_node.cc:55
bool AddVODOnlyInfo(const MediaInfo &media_info)
Definition: xml_node.cc:278
void AddEssentialProperty(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:178
void SetStringAttribute(const char *attribute_name, const std::string &attribute)
Definition: xml_node.cc:104
bool AddChild(scoped_xml_ptr< xmlNode > child)
Definition: xml_node.cc:62
xmlNodePtr Release()
Definition: xml_node.cc:142
bool AddLiveOnlyInfo(const MediaInfo &media_info, const std::list< SegmentInfo > &segment_infos, uint32_t start_number)
Definition: xml_node.cc:319
void SetId(uint32_t id)
Definition: xml_node.cc:127
bool AddElements(const std::vector< Element > &elements)
Adds Elements to this node using the Element struct.
Definition: xml_node.cc:74
std::string DoubleToString(double value)
Definition: mpd_utils.cc:192
void AddSupplementalProperty(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:169
void SetIntegerAttribute(const char *attribute_name, uint64_t number)
Definition: xml_node.cc:111
void AddRoleElement(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:221
void SetContent(const std::string &content)
Definition: xml_node.cc:131
bool AddAudioInfo(const MediaInfo::AudioInfo &audio_info)
Definition: xml_node.cc:270
xmlNodePtr GetRawPtr()
Definition: xml_node.cc:148