XML wrapper classes for MPD
Change-Id: Ia43b38a2a20a9df9cf6f2cc3da4ca389a81d89cf
This commit is contained in:
parent
4f1aab5e51
commit
7f393bc44f
|
@ -0,0 +1,282 @@
|
||||||
|
#include "mpd/base/xml/xml_node.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#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<xmlNode>::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<xmlNode>::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<ContentProtectionElement>& content_protection_elements) {
|
||||||
|
std::list<ContentProtectionElement>::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<std::string, std::string> 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<uint32> 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<uint32>::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
|
|
@ -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 <list>
|
||||||
|
|
||||||
|
#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<xmlNode>::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<xmlNode>::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<xmlNode>::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<ContentProtectionElement>& 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<MediaInfo_VideoInfo>
|
||||||
|
RepeatedVideoInfo;
|
||||||
|
|
||||||
|
typedef ::google::protobuf::RepeatedPtrField<MediaInfo_AudioInfo>
|
||||||
|
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_
|
Loading…
Reference in New Issue