From 7f393bc44f736afc09e8487a77c1975a5e5182d5 Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Mon, 18 Nov 2013 13:01:48 -0800 Subject: [PATCH] XML wrapper classes for MPD Change-Id: Ia43b38a2a20a9df9cf6f2cc3da4ca389a81d89cf --- mpd/base/xml/xml_node.cc | 282 +++++++++++++++++++++++++++++++++++++++ mpd/base/xml/xml_node.h | 126 +++++++++++++++++ 2 files changed, 408 insertions(+) create mode 100644 mpd/base/xml/xml_node.cc create mode 100644 mpd/base/xml/xml_node.h diff --git a/mpd/base/xml/xml_node.cc b/mpd/base/xml/xml_node.cc new file mode 100644 index 0000000000..f33d8a764c --- /dev/null +++ b/mpd/base/xml/xml_node.cc @@ -0,0 +1,282 @@ +#include "mpd/base/xml/xml_node.h" + +#include + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "mpd/base/media_info.pb.h" + + +namespace { + +std::string RangeToString(const dash_packager::Range& range) { + return base::Uint64ToString(range.begin()) + "-" + + base::Uint64ToString(range.end()); +} + +} // namespace + +namespace dash_packager { +namespace xml { + +XmlNode::XmlNode(const char* name) : node_(xmlNewNode(NULL, BAD_CAST name)) { + DCHECK(name); + DCHECK(node_); +} + +XmlNode::~XmlNode() {} + +bool XmlNode::AddChild(ScopedXmlPtr::type child) { + DCHECK(node_); + DCHECK(child); + if (!xmlAddChild(node_.get(), child.get())) + return false; + + // Reaching here means the ownership of |child| transfered to |node_|. + // Release the pointer so that it doesn't get destructed in this scope. + child.release(); + return true; +} + +void XmlNode::SetStringAttribute(const char* attribute_name, + const std::string& attribute) { + DCHECK(node_); + DCHECK(attribute_name); + xmlNewProp(node_.get(), BAD_CAST attribute_name, BAD_CAST attribute.c_str()); +} + +void XmlNode::SetNumberAttribute(const char* attribute_name, uint64 number) { + DCHECK(node_); + DCHECK(attribute_name); + xmlNewProp(node_.get(), + BAD_CAST attribute_name, + BAD_CAST (base::Uint64ToString(number).c_str())); +} + +void XmlNode::SetId(uint32 id) { + SetNumberAttribute("id", id); +} + +void XmlNode::SetContent(const std::string& content) { + DCHECK(node_); + xmlNodeSetContent(node_.get(), BAD_CAST content.c_str()); +} + +ScopedXmlPtr::type XmlNode::PassScopedPtr() { + DLOG(INFO) << "Passing node_."; + DCHECK(node_); + return node_.Pass(); +} + +xmlNodePtr XmlNode::Release() { + DLOG(INFO) << "Releasing node_."; + DCHECK(node_); + return node_.release(); +} + +xmlNodePtr XmlNode::GetRawPtr() { + return node_.get(); +} + +RepresentationBaseXmlNode::RepresentationBaseXmlNode(const char* name) + : XmlNode(name) {} +RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {} + +bool RepresentationBaseXmlNode::AddContentProtectionElements( + const std::list& content_protection_elements) { + std::list::const_iterator content_protection_it = + content_protection_elements.begin(); + for (; content_protection_it != content_protection_elements.end(); + ++content_protection_it) { + if (!AddContentProtectionElement(*content_protection_it)) + return false; + } + + return true; +} + +bool RepresentationBaseXmlNode::AddContentProtectionElement( + const ContentProtectionElement& content_protection_element) { + XmlNode content_protection_node("ContentProtection"); + + content_protection_node.SetStringAttribute("value", + content_protection_element.value); + content_protection_node.SetStringAttribute( + "schemeIdUri", content_protection_element.scheme_id_uri); + + typedef std::map AttributesMapType; + const AttributesMapType& additional_attributes = + content_protection_element.additional_attributes; + + AttributesMapType::const_iterator attributes_it = + additional_attributes.begin(); + for (; attributes_it != additional_attributes.end(); ++attributes_it) { + content_protection_node.SetStringAttribute(attributes_it->first.c_str(), + attributes_it->second); + } + + content_protection_node.SetContent(content_protection_element.subelements); + return AddChild(content_protection_node.PassScopedPtr()); +} + +AdaptationSetXmlNode::AdaptationSetXmlNode() + : RepresentationBaseXmlNode("AdaptationSet") {} +AdaptationSetXmlNode::~AdaptationSetXmlNode() {} + +RepresentationXmlNode::RepresentationXmlNode() + : RepresentationBaseXmlNode("Representation") {} +RepresentationXmlNode::~RepresentationXmlNode() {} + +bool RepresentationXmlNode::AddVideoInfo( + const RepeatedVideoInfo& repeated_video_info) { + uint32 width = 0; + uint32 height = 0; + + // Make sure that all the widths and heights match. + for (int i = 0; i < repeated_video_info.size(); ++i) { + const MediaInfo_VideoInfo& video_info = repeated_video_info.Get(i); + if (video_info.width() <= 0 || video_info.height() <= 0) + return false; + + if (width == 0) { + width = video_info.width(); + } else if (width != video_info.width()) { + return false; + } + + if (height == 0) { + height = video_info.height(); + } else if (height != video_info.height()) { + return false; + } + } + + if (width != 0) + SetNumberAttribute("width", width); + + if (height != 0) + SetNumberAttribute("height", height); + + return true; +} + +bool RepresentationXmlNode::AddAudioInfo( + const RepeatedAudioInfo& repeated_audio_info) { + if (!AddAudioChannelInfo(repeated_audio_info)) + return false; + + AddAudioSamplingRateInfo(repeated_audio_info); + + // TODO(rkuroiwa): Find out where language goes. + return true; +} + +// TODO(rkuroiwa): Add 'mimeType' field to this element. Maybe MPD builder +// classes should do this or maybe in this class. +bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) { + const bool need_segment_base = media_info.has_index_range() || + media_info.has_time_scale() || + media_info.has_init_range(); + + if (need_segment_base) { + XmlNode segment_base("SegmentBase"); + if (media_info.has_index_range()) { + segment_base.SetStringAttribute("indexRange", + RangeToString(media_info.index_range())); + } + + if (media_info.has_time_scale()) + segment_base.SetNumberAttribute("timescale", media_info.time_scale()); + + if (media_info.has_init_range()) { + XmlNode initialization("Initialization"); + initialization.SetStringAttribute("range", + RangeToString(media_info.init_range())); + + if (!segment_base.AddChild(initialization.PassScopedPtr())) + return false; + } + + if (!AddChild(segment_base.PassScopedPtr())) + return false; + } + + if (media_info.has_media_file_name()) { + XmlNode base_url("BaseURL"); + base_url.SetContent(media_info.media_file_name()); + + if (!AddChild(base_url.PassScopedPtr())) + return false; + } + + if (media_info.has_media_duration()) { + // Adding 'duration' attribute, so that this information can be used when + // generating one MPD file. This should be removed from the final MPD. + SetNumberAttribute("duration", media_info.media_duration()); + } + + return true; +} + +// Find all the unique number-of-channels in |repeated_audio_info|, and make +// AudioChannelConfiguration for each number-of-channels. +bool RepresentationXmlNode::AddAudioChannelInfo( + const RepeatedAudioInfo& repeated_audio_info) { + std::set num_channels; + for (int i = 0; i < repeated_audio_info.size(); ++i) { + if (repeated_audio_info.Get(i).has_num_channels()) + num_channels.insert(repeated_audio_info.Get(i).num_channels()); + } + + std::set::const_iterator num_channels_it = num_channels.begin(); + for (; num_channels_it != num_channels.end(); ++num_channels_it) { + XmlNode audio_channel_config("AudioChannelConfiguration"); + + const char kAudioChannelConfigScheme[] = + "urn:mpeg:dash:23003:3:audio_channel_configuration:2011"; + audio_channel_config.SetStringAttribute("schemeIdUri", + kAudioChannelConfigScheme); + audio_channel_config.SetNumberAttribute("value", *num_channels_it); + + if (!AddChild(audio_channel_config.PassScopedPtr())) + return false; + } + + return true; +} + +// MPD expects one number for sampling frequency, or if it is a range it should +// be space separated. +void RepresentationXmlNode::AddAudioSamplingRateInfo( + const RepeatedAudioInfo& repeated_audio_info) { + bool has_sampling_frequency = false; + uint32 min_sampling_frequency = UINT32_MAX; + uint32 max_sampling_frequency = 0; + + for (int i = 0; i < repeated_audio_info.size(); ++i) { + const MediaInfo_AudioInfo &audio_info = repeated_audio_info.Get(i); + if (audio_info.has_sampling_frequency()) { + has_sampling_frequency = true; + const uint32 sampling_frequency = audio_info.sampling_frequency(); + if (sampling_frequency < min_sampling_frequency) + min_sampling_frequency = sampling_frequency; + + if (sampling_frequency > max_sampling_frequency) + max_sampling_frequency = sampling_frequency; + } + } + + if (has_sampling_frequency) { + if (min_sampling_frequency == max_sampling_frequency) { + SetNumberAttribute("audioSamplingRate", min_sampling_frequency); + } else { + std::string sample_rate_string = + base::UintToString(min_sampling_frequency) + " " + + base::UintToString(max_sampling_frequency); + SetStringAttribute("audioSamplingRate", sample_rate_string); + } + } +} + +} // namespace xml +} // namespace dash_packager diff --git a/mpd/base/xml/xml_node.h b/mpd/base/xml/xml_node.h new file mode 100644 index 0000000000..eaa00cc5e1 --- /dev/null +++ b/mpd/base/xml/xml_node.h @@ -0,0 +1,126 @@ +// Classes to wrap XML operations. XmlNode is a generic wrapper class for +// XmlNode in libxml2. There are also MPD XML specific classes as well. +#ifndef MPD_BASE_XML_XML_NODE_H_ +#define MPD_BASE_XML_XML_NODE_H_ + +#include + +#include "base/basictypes.h" +#include "mpd/base/content_protection_element.h" +#include "mpd/base/media_info.pb.h" +#include "mpd/base/xml/scoped_xml_ptr.h" +#include "third_party/libxml/src/include/libxml/tree.h" + +namespace dash_packager { +namespace xml { + +// These classes are wrapper classes for XML elements for generating MPD. +// None of the pointer parameters should be NULL. None of the methods are meant +// to be overridden. +class XmlNode { + public: + // Makes an XML element with |name|. |name| should not be NULL. + explicit XmlNode(const char* name); + virtual ~XmlNode(); + + // The ownership transfers to this object. On failure, this method will + // destruct |child|. + bool AddChild(ScopedXmlPtr::type child); + + void SetStringAttribute(const char* attribute_name, + const std::string& attribute); + + void SetNumberAttribute(const char* attribute_name, uint64 number); + + void SetId(uint32 id); + + // This is like 'innerHTML' setter. + // This function does not work well with AddChild(). Use either AddChild() or + // SetContent() when setting the content of this node. + void SetContent(const std::string& content); + + // Ownership transfer. After calling this method, calling any methods of + // this object, except the destructor, is undefined. + ScopedXmlPtr::type PassScopedPtr(); + + // Release the xmlNodePtr of this object. After calling this method, calling + // any methods of this object, except the destructor, is undefined. + xmlNodePtr Release(); + + // TODO(rkuroiwa): This isn't elegant. The only place this is used is when + // getting the duration of the MPD. Maybe make MpdXmlNode that does stuff + // internally, for example get 'duration' from all Representation nodes? + xmlNodePtr GetRawPtr(); + + private: + ScopedXmlPtr::type node_; + + DISALLOW_COPY_AND_ASSIGN(XmlNode); +}; + +// This corresponds to RepresentationBaseType in MPD. RepresentationBaseType is +// not a concrete element type so this should not get instantiated on its own. +// AdaptationSet and Representation are subtypes of this. +class RepresentationBaseXmlNode : public XmlNode { + public: + virtual ~RepresentationBaseXmlNode(); + bool AddContentProtectionElements( + const std::list& content_protection_elements); + + protected: + explicit RepresentationBaseXmlNode(const char* name); + + private: + bool AddContentProtectionElement( + const ContentProtectionElement& content_protection_element); + + DISALLOW_COPY_AND_ASSIGN(RepresentationBaseXmlNode); +}; + +// AdaptationSetType in MPD. +class AdaptationSetXmlNode : public RepresentationBaseXmlNode { + public: + AdaptationSetXmlNode(); + virtual ~AdaptationSetXmlNode(); + + private: + DISALLOW_COPY_AND_ASSIGN(AdaptationSetXmlNode); +}; + +// RepresentationType in MPD. +// TODO(rkuroiwa): Maybe provide methods to add mimetype, codecs, and bandwidth? +class RepresentationXmlNode : public RepresentationBaseXmlNode { + public: + typedef ::google::protobuf::RepeatedPtrField + RepeatedVideoInfo; + + typedef ::google::protobuf::RepeatedPtrField + RepeatedAudioInfo; + + RepresentationXmlNode(); + virtual ~RepresentationXmlNode(); + + // Returns true if successfully set attributes and children elements (if + // applicable). repeated info of size 0 is valid input. + bool AddVideoInfo(const RepeatedVideoInfo& repeated_video_info); + bool AddAudioInfo(const RepeatedAudioInfo& repeated_audio_info); + + // Check MediaInfo protobuf definition for which fields are specific to VOD. + bool AddVODOnlyInfo(const MediaInfo& media_info); + // TODO(rkuroiwa): Add Live info. + + private: + // Add AudioChannelConfiguration elements. This will add multiple + // AudioChannelConfiguration if |repeated_audio_info| contains multiple + // distinct channel configs (e.g. 2 channels and 6 channels adds 2 elements). + bool AddAudioChannelInfo(const RepeatedAudioInfo& repeated_audio_info); + + // Add audioSamplingRate attribute to this element. + void AddAudioSamplingRateInfo(const RepeatedAudioInfo& repeated_audio_info); + + DISALLOW_COPY_AND_ASSIGN(RepresentationXmlNode); +}; + +} // namespace xml +} // namespace dash_packager +#endif // MPD_BASE_XML_XML_NODE_H_