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 } // namespace
85 
86 namespace xml {
87 
88 XmlNode::XmlNode(const char* name) : node_(xmlNewNode(NULL, BAD_CAST name)) {
89  DCHECK(name);
90  DCHECK(node_);
91 }
92 
93 XmlNode::~XmlNode() {}
94 
95 bool XmlNode::AddChild(scoped_xml_ptr<xmlNode> child) {
96  DCHECK(node_);
97  DCHECK(child);
98  if (!xmlAddChild(node_.get(), child.get()))
99  return false;
100 
101  // Reaching here means the ownership of |child| transfered to |node_|.
102  // Release the pointer so that it doesn't get destructed in this scope.
103  ignore_result(child.release());
104  return true;
105 }
106 
107 bool XmlNode::AddElements(const std::vector<Element>& elements) {
108  for (size_t element_index = 0; element_index < elements.size();
109  ++element_index) {
110  const Element& child_element = elements[element_index];
111  XmlNode child_node(child_element.name.c_str());
112  for (std::map<std::string, std::string>::const_iterator attribute_it =
113  child_element.attributes.begin();
114  attribute_it != child_element.attributes.end(); ++attribute_it) {
115  child_node.SetStringAttribute(attribute_it->first.c_str(),
116  attribute_it->second);
117  }
118  // Recursively set children for the child.
119  if (!child_node.AddElements(child_element.subelements))
120  return false;
121 
122  child_node.SetContent(child_element.content);
123 
124  if (!xmlAddChild(node_.get(), child_node.GetRawPtr())) {
125  LOG(ERROR) << "Failed to set child " << child_element.name
126  << " to parent element "
127  << reinterpret_cast<const char*>(node_->name);
128  return false;
129  }
130  // Reaching here means the ownership of |child_node| transfered to |node_|.
131  // Release the pointer so that it doesn't get destructed in this scope.
132  ignore_result(child_node.Release());
133  }
134  return true;
135 }
136 
137 void XmlNode::SetStringAttribute(const char* attribute_name,
138  const std::string& attribute) {
139  DCHECK(node_);
140  DCHECK(attribute_name);
141  xmlSetProp(node_.get(), BAD_CAST attribute_name, BAD_CAST attribute.c_str());
142 }
143 
144 void XmlNode::SetIntegerAttribute(const char* attribute_name, uint64_t number) {
145  DCHECK(node_);
146  DCHECK(attribute_name);
147  xmlSetProp(node_.get(),
148  BAD_CAST attribute_name,
149  BAD_CAST (base::Uint64ToString(number).c_str()));
150 }
151 
152 void XmlNode::SetFloatingPointAttribute(const char* attribute_name,
153  double number) {
154  DCHECK(node_);
155  DCHECK(attribute_name);
156  xmlSetProp(node_.get(), BAD_CAST attribute_name,
157  BAD_CAST(base::DoubleToString(number).c_str()));
158 }
159 
160 void XmlNode::SetId(uint32_t id) {
161  SetIntegerAttribute("id", id);
162 }
163 
164 void XmlNode::SetContent(const std::string& content) {
165  DCHECK(node_);
166  xmlNodeSetContent(node_.get(), BAD_CAST content.c_str());
167 }
168 
169 scoped_xml_ptr<xmlNode> XmlNode::PassScopedPtr() {
170  DVLOG(2) << "Passing node_.";
171  DCHECK(node_);
172  return std::move(node_);
173 }
174 
175 xmlNodePtr XmlNode::Release() {
176  DVLOG(2) << "Releasing node_.";
177  DCHECK(node_);
178  return node_.release();
179 }
180 
181 xmlNodePtr XmlNode::GetRawPtr() {
182  return node_.get();
183 }
184 
185 RepresentationBaseXmlNode::RepresentationBaseXmlNode(const char* name)
186  : XmlNode(name) {}
187 RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {}
188 
189 bool RepresentationBaseXmlNode::AddContentProtectionElements(
190  const std::list<ContentProtectionElement>& content_protection_elements) {
191  std::list<ContentProtectionElement>::const_iterator content_protection_it =
192  content_protection_elements.begin();
193  for (; content_protection_it != content_protection_elements.end();
194  ++content_protection_it) {
195  if (!AddContentProtectionElement(*content_protection_it))
196  return false;
197  }
198 
199  return true;
200 }
201 
203  const std::string& scheme_id_uri,
204  const std::string& value) {
205  XmlNode supplemental_property("SupplementalProperty");
206  supplemental_property.SetStringAttribute("schemeIdUri", scheme_id_uri);
207  supplemental_property.SetStringAttribute("value", value);
208  AddChild(supplemental_property.PassScopedPtr());
209 }
210 
212  const std::string& scheme_id_uri,
213  const std::string& value) {
214  XmlNode essential_property("EssentialProperty");
215  essential_property.SetStringAttribute("schemeIdUri", scheme_id_uri);
216  essential_property.SetStringAttribute("value", value);
217  AddChild(essential_property.PassScopedPtr());
218 }
219 
220 bool RepresentationBaseXmlNode::AddContentProtectionElement(
221  const ContentProtectionElement& content_protection_element) {
222  XmlNode content_protection_node("ContentProtection");
223 
224  // @value is an optional attribute.
225  if (!content_protection_element.value.empty()) {
226  content_protection_node.SetStringAttribute(
227  "value", content_protection_element.value);
228  }
229  content_protection_node.SetStringAttribute(
230  "schemeIdUri", content_protection_element.scheme_id_uri);
231 
232  typedef std::map<std::string, std::string> AttributesMapType;
233  const AttributesMapType& additional_attributes =
234  content_protection_element.additional_attributes;
235 
236  AttributesMapType::const_iterator attributes_it =
237  additional_attributes.begin();
238  for (; attributes_it != additional_attributes.end(); ++attributes_it) {
239  content_protection_node.SetStringAttribute(attributes_it->first.c_str(),
240  attributes_it->second);
241  }
242 
243  if (!content_protection_node.AddElements(
244  content_protection_element.subelements)) {
245  return false;
246  }
247  return AddChild(content_protection_node.PassScopedPtr());
248 }
249 
250 AdaptationSetXmlNode::AdaptationSetXmlNode()
251  : RepresentationBaseXmlNode("AdaptationSet") {}
252 AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
253 
254 void AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
255  const std::string& value) {
256  XmlNode role("Role");
257  role.SetStringAttribute("schemeIdUri", scheme_id_uri);
258  role.SetStringAttribute("value", value);
259  AddChild(role.PassScopedPtr());
260 }
261 
262 RepresentationXmlNode::RepresentationXmlNode()
263  : RepresentationBaseXmlNode("Representation") {}
264 RepresentationXmlNode::~RepresentationXmlNode() {}
265 
266 bool RepresentationXmlNode::AddVideoInfo(const VideoInfo& video_info,
267  bool set_width,
268  bool set_height,
269  bool set_frame_rate) {
270  if (!video_info.has_width() || !video_info.has_height()) {
271  LOG(ERROR) << "Missing width or height for adding a video info.";
272  return false;
273  }
274 
275  if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
276  SetStringAttribute("sar", base::IntToString(video_info.pixel_width()) +
277  ":" +
278  base::IntToString(video_info.pixel_height()));
279  }
280 
281  if (set_width)
282  SetIntegerAttribute("width", video_info.width());
283  if (set_height)
284  SetIntegerAttribute("height", video_info.height());
285  if (set_frame_rate) {
286  SetStringAttribute("frameRate",
287  base::IntToString(video_info.time_scale()) + "/" +
288  base::IntToString(video_info.frame_duration()));
289  }
290 
291  if (video_info.has_playback_rate()) {
292  SetStringAttribute("maxPlayoutRate",
293  base::IntToString(video_info.playback_rate()));
294  // Since the trick play stream contains only key frames, there is no coding
295  // dependency on the main stream. Simply set the codingDependency to false.
296  // TODO(hmchen): propagate this attribute up to the AdaptationSet, since
297  // all are set to false.
298  SetStringAttribute("codingDependency", "false");
299  }
300  return true;
301 }
302 
303 bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) {
304  if (!AddAudioChannelInfo(audio_info))
305  return false;
306 
307  AddAudioSamplingRateInfo(audio_info);
308  return true;
309 }
310 
311 bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
312  if (media_info.has_media_file_url()) {
313  XmlNode base_url("BaseURL");
314  base_url.SetContent(media_info.media_file_url());
315 
316  if (!AddChild(base_url.PassScopedPtr()))
317  return false;
318  }
319 
320  const bool need_segment_base = media_info.has_index_range() ||
321  media_info.has_init_range() ||
322  media_info.has_reference_time_scale();
323 
324  if (need_segment_base) {
325  XmlNode segment_base("SegmentBase");
326  if (media_info.has_index_range()) {
327  segment_base.SetStringAttribute("indexRange",
328  RangeToString(media_info.index_range()));
329  }
330 
331  if (media_info.has_reference_time_scale()) {
332  segment_base.SetIntegerAttribute("timescale",
333  media_info.reference_time_scale());
334  }
335 
336  if (media_info.has_presentation_time_offset()) {
337  segment_base.SetIntegerAttribute("presentationTimeOffset",
338  media_info.presentation_time_offset());
339  }
340 
341  if (media_info.has_init_range()) {
342  XmlNode initialization("Initialization");
343  initialization.SetStringAttribute("range",
344  RangeToString(media_info.init_range()));
345 
346  if (!segment_base.AddChild(initialization.PassScopedPtr()))
347  return false;
348  }
349 
350  if (!AddChild(segment_base.PassScopedPtr()))
351  return false;
352  }
353 
354  return true;
355 }
356 
358  const MediaInfo& media_info,
359  const std::list<SegmentInfo>& segment_infos,
360  uint32_t start_number) {
361  XmlNode segment_template("SegmentTemplate");
362  if (media_info.has_reference_time_scale()) {
363  segment_template.SetIntegerAttribute("timescale",
364  media_info.reference_time_scale());
365  }
366 
367  if (media_info.has_presentation_time_offset()) {
368  segment_template.SetIntegerAttribute("presentationTimeOffset",
369  media_info.presentation_time_offset());
370  }
371 
372  if (media_info.has_init_segment_url()) {
373  segment_template.SetStringAttribute("initialization",
374  media_info.init_segment_url());
375  }
376 
377  if (media_info.has_segment_template_url()) {
378  segment_template.SetStringAttribute("media",
379  media_info.segment_template_url());
380  segment_template.SetIntegerAttribute("startNumber", start_number);
381  }
382 
383  if (!segment_infos.empty()) {
384  // Don't use SegmentTimeline if all segments except the last one are of
385  // the same duration.
386  if (IsTimelineConstantDuration(segment_infos, start_number)) {
387  segment_template.SetIntegerAttribute("duration",
388  segment_infos.front().duration);
389  } else {
390  XmlNode segment_timeline("SegmentTimeline");
391  if (!PopulateSegmentTimeline(segment_infos, &segment_timeline) ||
392  !segment_template.AddChild(segment_timeline.PassScopedPtr())) {
393  return false;
394  }
395  }
396  }
397  return AddChild(segment_template.PassScopedPtr());
398 }
399 
400 bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
401  std::string audio_channel_config_scheme;
402  std::string audio_channel_config_value;
403 
404  if (audio_info.codec() == kEC3Codec) {
405  // Convert EC3 channel map into string of hexadecimal digits. Spec: DASH-IF
406  // Interoperability Points v3.0 9.2.1.2.
407  const uint16_t ec3_channel_map =
408  base::HostToNet16(audio_info.codec_specific_data().ec3_channel_map());
409  audio_channel_config_value =
410  base::HexEncode(&ec3_channel_map, sizeof(ec3_channel_map));
411  audio_channel_config_scheme =
412  "tag:dolby.com,2014:dash:audio_channel_configuration:2011";
413  } else {
414  audio_channel_config_value = base::UintToString(audio_info.num_channels());
415  audio_channel_config_scheme =
416  "urn:mpeg:dash:23003:3:audio_channel_configuration:2011";
417  }
418 
419  XmlNode audio_channel_config("AudioChannelConfiguration");
420  audio_channel_config.SetStringAttribute("schemeIdUri",
421  audio_channel_config_scheme);
422  audio_channel_config.SetStringAttribute("value", audio_channel_config_value);
423 
424  return AddChild(audio_channel_config.PassScopedPtr());
425 }
426 
427 // MPD expects one number for sampling frequency, or if it is a range it should
428 // be space separated.
429 void RepresentationXmlNode::AddAudioSamplingRateInfo(
430  const AudioInfo& audio_info) {
431  if (audio_info.has_sampling_frequency())
432  SetIntegerAttribute("audioSamplingRate", audio_info.sampling_frequency());
433 }
434 
435 } // namespace xml
436 } // namespace shaka
bool AddVideoInfo(const MediaInfo::VideoInfo &video_info, bool set_width, bool set_height, bool set_frame_rate)
Definition: xml_node.cc:266
void SetFloatingPointAttribute(const char *attribute_name, double number)
Definition: xml_node.cc:152
scoped_xml_ptr< xmlNode > PassScopedPtr()
Definition: xml_node.cc:169
XmlNode(const char *name)
Definition: xml_node.cc:88
All the methods that are virtual are virtual for mocking.
bool AddVODOnlyInfo(const MediaInfo &media_info)
Definition: xml_node.cc:311
void AddEssentialProperty(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:211
void SetStringAttribute(const char *attribute_name, const std::string &attribute)
Definition: xml_node.cc:137
bool AddChild(scoped_xml_ptr< xmlNode > child)
Definition: xml_node.cc:95
xmlNodePtr Release()
Definition: xml_node.cc:175
bool AddLiveOnlyInfo(const MediaInfo &media_info, const std::list< SegmentInfo > &segment_infos, uint32_t start_number)
Definition: xml_node.cc:357
void SetId(uint32_t id)
Definition: xml_node.cc:160
bool AddElements(const std::vector< Element > &elements)
Adds Elements to this node using the Element struct.
Definition: xml_node.cc:107
void AddSupplementalProperty(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:202
void SetIntegerAttribute(const char *attribute_name, uint64_t number)
Definition: xml_node.cc:144
void AddRoleElement(const std::string &scheme_id_uri, const std::string &value)
Definition: xml_node.cc:254
void SetContent(const std::string &content)
Definition: xml_node.cc:164
bool AddAudioInfo(const MediaInfo::AudioInfo &audio_info)
Definition: xml_node.cc:303
xmlNodePtr GetRawPtr()
Definition: xml_node.cc:181