Refactor XmlNode and libxml usages.
- Use std::move to transfer ownership. - Avoid libxml primitives outside XmlNode. - Have attribute setting return errors. - Mark bool returns with WARN_UNUSED_RESULTS and use return value. - Use std::string over char*. Change-Id: Ia00bb29c690025275e270f10e5c065722481c0c5
This commit is contained in:
parent
9a34b9c3f3
commit
26334f2808
|
@ -237,41 +237,55 @@ void AdaptationSet::AddRole(Role role) {
|
|||
// can be passed to Representation to avoid setting redundant attributes. For
|
||||
// example, if AdaptationSet@width is set, then Representation@width is
|
||||
// redundant and should not be set.
|
||||
xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
|
||||
base::Optional<xml::XmlNode> AdaptationSet::GetXml() {
|
||||
xml::AdaptationSetXmlNode adaptation_set;
|
||||
|
||||
bool suppress_representation_width = false;
|
||||
bool suppress_representation_height = false;
|
||||
bool suppress_representation_frame_rate = false;
|
||||
|
||||
if (id_)
|
||||
adaptation_set.SetId(id_.value());
|
||||
adaptation_set.SetStringAttribute("contentType", content_type_);
|
||||
if (!language_.empty() && language_ != "und") {
|
||||
adaptation_set.SetStringAttribute("lang", language_);
|
||||
if (id_ && !adaptation_set.SetId(id_.value()))
|
||||
return base::nullopt;
|
||||
if (!adaptation_set.SetStringAttribute("contentType", content_type_))
|
||||
return base::nullopt;
|
||||
if (!language_.empty() && language_ != "und" &&
|
||||
!adaptation_set.SetStringAttribute("lang", language_)) {
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
// Note that std::{set,map} are ordered, so the last element is the max value.
|
||||
if (video_widths_.size() == 1) {
|
||||
suppress_representation_width = true;
|
||||
adaptation_set.SetIntegerAttribute("width", *video_widths_.begin());
|
||||
if (!adaptation_set.SetIntegerAttribute("width", *video_widths_.begin()))
|
||||
return base::nullopt;
|
||||
} else if (video_widths_.size() > 1) {
|
||||
adaptation_set.SetIntegerAttribute("maxWidth", *video_widths_.rbegin());
|
||||
if (!adaptation_set.SetIntegerAttribute("maxWidth",
|
||||
*video_widths_.rbegin())) {
|
||||
return base::nullopt;
|
||||
}
|
||||
}
|
||||
if (video_heights_.size() == 1) {
|
||||
suppress_representation_height = true;
|
||||
adaptation_set.SetIntegerAttribute("height", *video_heights_.begin());
|
||||
if (!adaptation_set.SetIntegerAttribute("height", *video_heights_.begin()))
|
||||
return base::nullopt;
|
||||
} else if (video_heights_.size() > 1) {
|
||||
adaptation_set.SetIntegerAttribute("maxHeight", *video_heights_.rbegin());
|
||||
if (!adaptation_set.SetIntegerAttribute("maxHeight",
|
||||
*video_heights_.rbegin())) {
|
||||
return base::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (video_frame_rates_.size() == 1) {
|
||||
suppress_representation_frame_rate = true;
|
||||
adaptation_set.SetStringAttribute("frameRate",
|
||||
video_frame_rates_.begin()->second);
|
||||
if (!adaptation_set.SetStringAttribute(
|
||||
"frameRate", video_frame_rates_.begin()->second)) {
|
||||
return base::nullopt;
|
||||
}
|
||||
} else if (video_frame_rates_.size() > 1) {
|
||||
adaptation_set.SetStringAttribute("maxFrameRate",
|
||||
video_frame_rates_.rbegin()->second);
|
||||
if (!adaptation_set.SetStringAttribute(
|
||||
"maxFrameRate", video_frame_rates_.rbegin()->second)) {
|
||||
return base::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: must be checked before checking segments_aligned_ (below). So that
|
||||
|
@ -281,19 +295,24 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
|
|||
}
|
||||
|
||||
if (segments_aligned_ == kSegmentAlignmentTrue) {
|
||||
adaptation_set.SetStringAttribute(
|
||||
mpd_options_.dash_profile == DashProfile::kOnDemand
|
||||
? "subsegmentAlignment"
|
||||
: "segmentAlignment",
|
||||
"true");
|
||||
if (!adaptation_set.SetStringAttribute(
|
||||
mpd_options_.dash_profile == DashProfile::kOnDemand
|
||||
? "subsegmentAlignment"
|
||||
: "segmentAlignment",
|
||||
"true")) {
|
||||
return base::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (picture_aspect_ratio_.size() == 1)
|
||||
adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin());
|
||||
if (picture_aspect_ratio_.size() == 1 &&
|
||||
!adaptation_set.SetStringAttribute("par",
|
||||
*picture_aspect_ratio_.begin())) {
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
if (!adaptation_set.AddContentProtectionElements(
|
||||
content_protection_elements_)) {
|
||||
return xml::scoped_xml_ptr<xmlNode>();
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
std::string trick_play_reference_ids;
|
||||
|
@ -304,9 +323,10 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
|
|||
CHECK(adaptation_set->has_id());
|
||||
trick_play_reference_ids += std::to_string(adaptation_set->id());
|
||||
}
|
||||
if (!trick_play_reference_ids.empty()) {
|
||||
adaptation_set.AddEssentialProperty(
|
||||
"http://dashif.org/guidelines/trickmode", trick_play_reference_ids);
|
||||
if (!trick_play_reference_ids.empty() &&
|
||||
!adaptation_set.AddEssentialProperty(
|
||||
"http://dashif.org/guidelines/trickmode", trick_play_reference_ids)) {
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
std::string switching_ids;
|
||||
|
@ -317,18 +337,25 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
|
|||
CHECK(adaptation_set->has_id());
|
||||
switching_ids += std::to_string(adaptation_set->id());
|
||||
}
|
||||
if (!switching_ids.empty()) {
|
||||
adaptation_set.AddSupplementalProperty(
|
||||
"urn:mpeg:dash:adaptation-set-switching:2016", switching_ids);
|
||||
if (!switching_ids.empty() &&
|
||||
!adaptation_set.AddSupplementalProperty(
|
||||
"urn:mpeg:dash:adaptation-set-switching:2016", switching_ids)) {
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
for (const AdaptationSet::Accessibility& accessibility : accessibilities_) {
|
||||
adaptation_set.AddAccessibilityElement(accessibility.scheme,
|
||||
accessibility.value);
|
||||
if (!adaptation_set.AddAccessibilityElement(accessibility.scheme,
|
||||
accessibility.value)) {
|
||||
return base::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
for (AdaptationSet::Role role : roles_)
|
||||
adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011", RoleToText(role));
|
||||
for (AdaptationSet::Role role : roles_) {
|
||||
if (!adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011",
|
||||
RoleToText(role))) {
|
||||
return base::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& representation_pair : representation_map_) {
|
||||
const auto& representation = representation_pair.second;
|
||||
|
@ -338,12 +365,12 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
|
|||
representation->SuppressOnce(Representation::kSuppressHeight);
|
||||
if (suppress_representation_frame_rate)
|
||||
representation->SuppressOnce(Representation::kSuppressFrameRate);
|
||||
xml::scoped_xml_ptr<xmlNode> child(representation->GetXml());
|
||||
if (!child || !adaptation_set.AddChild(std::move(child)))
|
||||
return xml::scoped_xml_ptr<xmlNode>();
|
||||
auto child = representation->GetXml();
|
||||
if (!child || !adaptation_set.AddChild(std::move(*child)))
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
return adaptation_set.PassScopedPtr();
|
||||
return std::move(adaptation_set);
|
||||
}
|
||||
|
||||
void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "packager/base/optional.h"
|
||||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||
#include "packager/mpd/base/xml/xml_node.h"
|
||||
|
||||
namespace shaka {
|
||||
|
||||
|
@ -28,10 +28,6 @@ class Representation;
|
|||
struct ContentProtectionElement;
|
||||
struct MpdOptions;
|
||||
|
||||
namespace xml {
|
||||
class XmlNode;
|
||||
} // namespace xml
|
||||
|
||||
/// AdaptationSet class provides methods to add Representations and
|
||||
/// <ContentProtection> elements to the AdaptationSet element.
|
||||
class AdaptationSet {
|
||||
|
@ -112,7 +108,7 @@ class AdaptationSet {
|
|||
/// and ContentProtection elements.
|
||||
/// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a
|
||||
/// NULL scoped_xml_ptr.
|
||||
xml::scoped_xml_ptr<xmlNode> GetXml();
|
||||
base::Optional<xml::XmlNode> GetXml();
|
||||
|
||||
/// Forces the (sub)segmentAlignment field to be set to @a segment_alignment.
|
||||
/// Use this if you are certain that the (sub)segments are alinged/unaligned
|
||||
|
|
|
@ -69,7 +69,7 @@ TEST_F(AdaptationSetTest, AddAdaptationSetSwitching) {
|
|||
" schemeIdUri=\"urn:mpeg:dash:adaptation-set-switching:2016\" "
|
||||
" value=\"1,2,8\"/>"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
// Verify that content type is set correctly if video info is present in
|
||||
|
@ -89,8 +89,7 @@ TEST_F(AdaptationSetTest, CheckAdaptationSetVideoContentType) {
|
|||
|
||||
auto adaptation_set = CreateAdaptationSet(kNoLanguage);
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo));
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(),
|
||||
AttributeEqual("contentType", "video"));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), AttributeEqual("contentType", "video"));
|
||||
}
|
||||
|
||||
// Verify that content type is set correctly if audio info is present in
|
||||
|
@ -107,8 +106,7 @@ TEST_F(AdaptationSetTest, CheckAdaptationSetAudioContentType) {
|
|||
|
||||
auto adaptation_set = CreateAdaptationSet(kNoLanguage);
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo));
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(),
|
||||
AttributeEqual("contentType", "audio"));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), AttributeEqual("contentType", "audio"));
|
||||
}
|
||||
|
||||
// Verify that content type is set correctly if text info is present in
|
||||
|
@ -123,8 +121,7 @@ TEST_F(AdaptationSetTest, CheckAdaptationSetTextContentType) {
|
|||
|
||||
auto adaptation_set = CreateAdaptationSet("en");
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo));
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(),
|
||||
AttributeEqual("contentType", "text"));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), AttributeEqual("contentType", "text"));
|
||||
}
|
||||
|
||||
TEST_F(AdaptationSetTest, CopyRepresentation) {
|
||||
|
@ -152,14 +149,14 @@ TEST_F(AdaptationSetTest, CopyRepresentation) {
|
|||
// Verify that language passed to the constructor sets the @lang field is set.
|
||||
TEST_F(AdaptationSetTest, CheckLanguageAttributeSet) {
|
||||
auto adaptation_set = CreateAdaptationSet("en");
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), AttributeEqual("lang", "en"));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), AttributeEqual("lang", "en"));
|
||||
}
|
||||
|
||||
TEST_F(AdaptationSetTest, CheckAdaptationSetId) {
|
||||
auto adaptation_set = CreateAdaptationSet(kNoLanguage);
|
||||
const uint32_t kAdaptationSetId = 42;
|
||||
adaptation_set->set_id(kAdaptationSetId);
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(),
|
||||
EXPECT_THAT(adaptation_set->GetXml(),
|
||||
AttributeEqual("id", std::to_string(kAdaptationSetId)));
|
||||
}
|
||||
|
||||
|
@ -176,7 +173,7 @@ TEST_F(AdaptationSetTest, AddAccessibilityElement) {
|
|||
" <Accessibility schemeIdUri=\"urn:tva:metadata:cs:AudioPurposeCS:2007\""
|
||||
" value=\"2\"/>\n"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
// Verify AdaptationSet::AddRole() works for "main" role.
|
||||
|
@ -190,7 +187,7 @@ TEST_F(AdaptationSetTest, AdaptationAddRoleElementMain) {
|
|||
"<AdaptationSet contentType=\"\">\n"
|
||||
" <Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"main\"/>\n"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
// Add Role, ContentProtection, and Representation elements. Verify that
|
||||
|
@ -211,7 +208,6 @@ TEST_F(AdaptationSetTest, CheckContentProtectionRoleRepresentationOrder) {
|
|||
"container_type: 1\n";
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
const char kExpectedOutput[] =
|
||||
"<AdaptationSet contentType=\"audio\">\n"
|
||||
" <ContentProtection schemeIdUri=\"any_scheme\"/>\n"
|
||||
|
@ -224,7 +220,7 @@ TEST_F(AdaptationSetTest, CheckContentProtectionRoleRepresentationOrder) {
|
|||
" value=\"2\"/>\n"
|
||||
" </Representation>\n"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
// Verify that if all video Representations in an AdaptationSet have the same
|
||||
|
@ -254,9 +250,9 @@ TEST_F(AdaptationSetTest, AdapatationSetFrameRate) {
|
|||
ASSERT_TRUE(
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2)));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("frameRate", "10/3"));
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxFrameRate")));
|
||||
auto adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml, AttributeEqual("frameRate", "10/3"));
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("maxFrameRate")));
|
||||
}
|
||||
|
||||
// Verify that if there are videos with different frame rates, the maxFrameRate
|
||||
|
@ -287,10 +283,9 @@ TEST_F(AdaptationSetTest, AdapatationSetMaxFrameRate) {
|
|||
ASSERT_TRUE(adaptation_set->AddRepresentation(
|
||||
ConvertToMediaInfo(kVideoMediaInfo15fps)));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
EXPECT_THAT(adaptation_set_xml.get(),
|
||||
AttributeEqual("maxFrameRate", "3000/100"));
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate")));
|
||||
auto adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml, AttributeEqual("maxFrameRate", "3000/100"));
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("frameRate")));
|
||||
}
|
||||
|
||||
// Verify that (max)FrameRate can be set by calling
|
||||
|
@ -331,9 +326,9 @@ TEST_F(AdaptationSetTest,
|
|||
|
||||
// First, make sure that maxFrameRate nor frameRate are set because
|
||||
// frame durations were not provided in the MediaInfo.
|
||||
xml::scoped_xml_ptr<xmlNode> no_frame_rate(adaptation_set->GetXml());
|
||||
EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("maxFrameRate")));
|
||||
EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("frameRate")));
|
||||
auto no_frame_rate = adaptation_set->GetXml();
|
||||
EXPECT_THAT(no_frame_rate, Not(AttributeSet("maxFrameRate")));
|
||||
EXPECT_THAT(no_frame_rate, Not(AttributeSet("frameRate")));
|
||||
|
||||
// Then set same frame duration for the representations. (Given that the
|
||||
// time scales match).
|
||||
|
@ -341,9 +336,9 @@ TEST_F(AdaptationSetTest,
|
|||
representation_480p->SetSampleDuration(kSameFrameDuration);
|
||||
representation_360p->SetSampleDuration(kSameFrameDuration);
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> same_frame_rate(adaptation_set->GetXml());
|
||||
EXPECT_THAT(same_frame_rate.get(), Not(AttributeSet("maxFrameRate")));
|
||||
EXPECT_THAT(same_frame_rate.get(), AttributeEqual("frameRate", "10/3"));
|
||||
auto same_frame_rate = adaptation_set->GetXml();
|
||||
EXPECT_THAT(same_frame_rate, Not(AttributeSet("maxFrameRate")));
|
||||
EXPECT_THAT(same_frame_rate, AttributeEqual("frameRate", "10/3"));
|
||||
|
||||
// Then set 480p to be 5fps (10/2) so that maxFrameRate is set.
|
||||
const uint32_t k5FPSFrameDuration = 2;
|
||||
|
@ -351,9 +346,9 @@ TEST_F(AdaptationSetTest,
|
|||
"frame_duration_must_be_shorter_for_max_frame_rate");
|
||||
representation_480p->SetSampleDuration(k5FPSFrameDuration);
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> max_frame_rate(adaptation_set->GetXml());
|
||||
EXPECT_THAT(max_frame_rate.get(), AttributeEqual("maxFrameRate", "10/2"));
|
||||
EXPECT_THAT(max_frame_rate.get(), Not(AttributeSet("frameRate")));
|
||||
auto max_frame_rate = adaptation_set->GetXml();
|
||||
EXPECT_THAT(max_frame_rate, AttributeEqual("maxFrameRate", "10/2"));
|
||||
EXPECT_THAT(max_frame_rate, Not(AttributeSet("frameRate")));
|
||||
}
|
||||
|
||||
// Verify that if the picture aspect ratio of all the Representations are the
|
||||
|
@ -417,8 +412,8 @@ TEST_F(AdaptationSetTest, AdaptationSetParAllSame) {
|
|||
ASSERT_TRUE(
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pVideoInfo)));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("par", "16:9"));
|
||||
auto adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml, AttributeEqual("par", "16:9"));
|
||||
}
|
||||
|
||||
// Verify that adding Representations with different par will generate
|
||||
|
@ -454,8 +449,8 @@ TEST_F(AdaptationSetTest, AdaptationSetParDifferent) {
|
|||
ASSERT_TRUE(
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(k2by1VideoInfo)));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("par")));
|
||||
auto adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("par")));
|
||||
}
|
||||
|
||||
// Verify that adding Representation without pixel_width and pixel_height will
|
||||
|
@ -475,8 +470,8 @@ TEST_F(AdaptationSetTest, AdaptationSetParUnknown) {
|
|||
ASSERT_TRUE(adaptation_set->AddRepresentation(
|
||||
ConvertToMediaInfo(kUknownPixelWidthAndHeight)));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("par")));
|
||||
auto adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("par")));
|
||||
}
|
||||
|
||||
// Catch the case where it ends up wrong if integer division is used to check
|
||||
|
@ -509,9 +504,9 @@ TEST_F(AdaptationSetTest, AdapatationSetMaxFrameRateIntegerDivisionEdgeCase) {
|
|||
ASSERT_TRUE(
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2)));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxFrameRate", "11/3"));
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate")));
|
||||
auto adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml, AttributeEqual("maxFrameRate", "11/3"));
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("frameRate")));
|
||||
}
|
||||
|
||||
// Attribute values that are common to all the children Representations should
|
||||
|
@ -571,35 +566,34 @@ TEST_F(AdaptationSetTest, BubbleUpAttributesToAdaptationSet) {
|
|||
auto adaptation_set = CreateAdaptationSet(kNoLanguage);
|
||||
ASSERT_TRUE(adaptation_set->AddRepresentation(ConvertToMediaInfo(k1080p)));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> all_attributes_on_adaptation_set(
|
||||
adaptation_set->GetXml());
|
||||
EXPECT_THAT(all_attributes_on_adaptation_set.get(),
|
||||
auto all_attributes_on_adaptation_set = adaptation_set->GetXml();
|
||||
EXPECT_THAT(all_attributes_on_adaptation_set,
|
||||
AttributeEqual("width", "1920"));
|
||||
EXPECT_THAT(all_attributes_on_adaptation_set.get(),
|
||||
EXPECT_THAT(all_attributes_on_adaptation_set,
|
||||
AttributeEqual("height", "1080"));
|
||||
EXPECT_THAT(all_attributes_on_adaptation_set.get(),
|
||||
EXPECT_THAT(all_attributes_on_adaptation_set,
|
||||
AttributeEqual("frameRate", "30/1"));
|
||||
|
||||
ASSERT_TRUE(
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentWidth)));
|
||||
xml::scoped_xml_ptr<xmlNode> width_not_set(adaptation_set->GetXml());
|
||||
EXPECT_THAT(width_not_set.get(), Not(AttributeSet("width")));
|
||||
EXPECT_THAT(width_not_set.get(), AttributeEqual("height", "1080"));
|
||||
EXPECT_THAT(width_not_set.get(), AttributeEqual("frameRate", "30/1"));
|
||||
auto width_not_set = adaptation_set->GetXml();
|
||||
EXPECT_THAT(width_not_set, Not(AttributeSet("width")));
|
||||
EXPECT_THAT(width_not_set, AttributeEqual("height", "1080"));
|
||||
EXPECT_THAT(width_not_set, AttributeEqual("frameRate", "30/1"));
|
||||
|
||||
ASSERT_TRUE(
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentHeight)));
|
||||
xml::scoped_xml_ptr<xmlNode> width_height_not_set(adaptation_set->GetXml());
|
||||
EXPECT_THAT(width_height_not_set.get(), Not(AttributeSet("width")));
|
||||
EXPECT_THAT(width_height_not_set.get(), Not(AttributeSet("height")));
|
||||
EXPECT_THAT(width_height_not_set.get(), AttributeEqual("frameRate", "30/1"));
|
||||
auto width_height_not_set = adaptation_set->GetXml();
|
||||
EXPECT_THAT(width_height_not_set, Not(AttributeSet("width")));
|
||||
EXPECT_THAT(width_height_not_set, Not(AttributeSet("height")));
|
||||
EXPECT_THAT(width_height_not_set, AttributeEqual("frameRate", "30/1"));
|
||||
|
||||
ASSERT_TRUE(adaptation_set->AddRepresentation(
|
||||
ConvertToMediaInfo(kDifferentFrameRate)));
|
||||
xml::scoped_xml_ptr<xmlNode> no_common_attributes(adaptation_set->GetXml());
|
||||
EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("width")));
|
||||
EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("height")));
|
||||
EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("frameRate")));
|
||||
auto no_common_attributes = adaptation_set->GetXml();
|
||||
EXPECT_THAT(no_common_attributes, Not(AttributeSet("width")));
|
||||
EXPECT_THAT(no_common_attributes, Not(AttributeSet("height")));
|
||||
EXPECT_THAT(no_common_attributes, Not(AttributeSet("frameRate")));
|
||||
}
|
||||
|
||||
TEST_F(AdaptationSetTest, GetRepresentations) {
|
||||
|
@ -694,21 +688,20 @@ TEST_F(OnDemandAdaptationSetTest, SubsegmentAlignment) {
|
|||
adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
|
||||
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize);
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml());
|
||||
EXPECT_THAT(aligned.get(), AttributeEqual("subsegmentAlignment", "true"));
|
||||
auto aligned = adaptation_set->GetXml();
|
||||
EXPECT_THAT(aligned, AttributeEqual("subsegmentAlignment", "true"));
|
||||
|
||||
// Unknown because 480p has an extra subsegments.
|
||||
representation_480p->AddNewSegment(11, 20, kAnySize);
|
||||
xml::scoped_xml_ptr<xmlNode> alignment_unknown(adaptation_set->GetXml());
|
||||
EXPECT_THAT(alignment_unknown.get(),
|
||||
Not(AttributeSet("subsegmentAlignment")));
|
||||
auto alignment_unknown = adaptation_set->GetXml();
|
||||
EXPECT_THAT(alignment_unknown, Not(AttributeSet("subsegmentAlignment")));
|
||||
|
||||
// Add segments that make them not aligned.
|
||||
representation_360p->AddNewSegment(10, 1, kAnySize);
|
||||
representation_360p->AddNewSegment(11, 19, kAnySize);
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> unaligned(adaptation_set->GetXml());
|
||||
EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment")));
|
||||
auto unaligned = adaptation_set->GetXml();
|
||||
EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment")));
|
||||
}
|
||||
|
||||
// Verify that subsegmentAlignment can be force set to true.
|
||||
|
@ -749,13 +742,13 @@ TEST_F(OnDemandAdaptationSetTest, ForceSetsubsegmentAlignment) {
|
|||
const uint64_t kAnySize = 19834u;
|
||||
representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize);
|
||||
representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize);
|
||||
xml::scoped_xml_ptr<xmlNode> unaligned(adaptation_set->GetXml());
|
||||
EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment")));
|
||||
auto unaligned = adaptation_set->GetXml();
|
||||
EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment")));
|
||||
|
||||
// Then force set the segment alignment attribute to true.
|
||||
adaptation_set->ForceSetSegmentAlignment(true);
|
||||
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml());
|
||||
EXPECT_THAT(aligned.get(), AttributeEqual("subsegmentAlignment", "true"));
|
||||
auto aligned = adaptation_set->GetXml();
|
||||
EXPECT_THAT(aligned, AttributeEqual("subsegmentAlignment", "true"));
|
||||
}
|
||||
|
||||
// Verify that segmentAlignment is set to true if all the Representations
|
||||
|
@ -800,16 +793,16 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignmentDynamicMpd) {
|
|||
|
||||
representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize);
|
||||
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize);
|
||||
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml());
|
||||
EXPECT_THAT(aligned.get(), AttributeEqual("segmentAlignment", "true"));
|
||||
auto aligned = adaptation_set->GetXml();
|
||||
EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true"));
|
||||
|
||||
// Add segments that make them not aligned.
|
||||
representation_480p->AddNewSegment(11, 20, kAnySize);
|
||||
representation_360p->AddNewSegment(10, 1, kAnySize);
|
||||
representation_360p->AddNewSegment(11, 19, kAnySize);
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> unaligned(adaptation_set->GetXml());
|
||||
EXPECT_THAT(unaligned.get(), Not(AttributeSet("segmentAlignment")));
|
||||
auto unaligned = adaptation_set->GetXml();
|
||||
EXPECT_THAT(unaligned, Not(AttributeSet("segmentAlignment")));
|
||||
}
|
||||
|
||||
// Verify that segmentAlignment is set to true if all the Representations
|
||||
|
@ -862,8 +855,8 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignmentStaticMpd) {
|
|||
representation_360p->AddNewSegment(kStartTime + kDuration, kDuration,
|
||||
kAnySize);
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml());
|
||||
EXPECT_THAT(aligned.get(), AttributeEqual("segmentAlignment", "true"));
|
||||
auto aligned = adaptation_set->GetXml();
|
||||
EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true"));
|
||||
}
|
||||
|
||||
// Verify that the width and height attribute are set if all the video
|
||||
|
@ -894,11 +887,11 @@ TEST_F(OnDemandAdaptationSetTest, AdapatationSetWidthAndHeight) {
|
|||
ASSERT_TRUE(
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2)));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("width", "1280"));
|
||||
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("height", "720"));
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxWidth")));
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxHeight")));
|
||||
auto adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml, AttributeEqual("width", "1280"));
|
||||
EXPECT_THAT(adaptation_set_xml, AttributeEqual("height", "720"));
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("maxWidth")));
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("maxHeight")));
|
||||
}
|
||||
|
||||
// Verify that the maxWidth and maxHeight attribute are set if there are
|
||||
|
@ -928,11 +921,11 @@ TEST_F(OnDemandAdaptationSetTest, AdaptationSetMaxWidthAndMaxHeight) {
|
|||
ASSERT_TRUE(adaptation_set->AddRepresentation(
|
||||
ConvertToMediaInfo(kVideoMediaInfo720p)));
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxWidth", "1920"));
|
||||
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxHeight", "1080"));
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("width")));
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("height")));
|
||||
auto adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml, AttributeEqual("maxWidth", "1920"));
|
||||
EXPECT_THAT(adaptation_set_xml, AttributeEqual("maxHeight", "1080"));
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("width")));
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("height")));
|
||||
}
|
||||
|
||||
// Verify that Representation::SetSampleDuration() works by checking that
|
||||
|
@ -955,12 +948,12 @@ TEST_F(AdaptationSetTest, SetSampleDuration) {
|
|||
adaptation_set->AddRepresentation(video_media_info);
|
||||
EXPECT_TRUE(representation->Init());
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate")));
|
||||
auto adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("frameRate")));
|
||||
|
||||
representation->SetSampleDuration(2u);
|
||||
adaptation_set_xml = adaptation_set->GetXml();
|
||||
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("frameRate", "3000/2"));
|
||||
EXPECT_THAT(adaptation_set_xml, AttributeEqual("frameRate", "3000/2"));
|
||||
}
|
||||
|
||||
// Verify that AdaptationSet::AddContentProtection() and
|
||||
|
@ -1000,7 +993,7 @@ TEST_F(AdaptationSetTest, AdaptationSetAddContentProtectionAndUpdate) {
|
|||
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
|
||||
" mimeType=\"video/mp4\"/>"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput1));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput1));
|
||||
|
||||
adaptation_set->UpdateContentProtectionPssh(
|
||||
"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "new pssh value");
|
||||
|
@ -1018,7 +1011,7 @@ TEST_F(AdaptationSetTest, AdaptationSetAddContentProtectionAndUpdate) {
|
|||
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
|
||||
" mimeType=\"video/mp4\"/>"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput2));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput2));
|
||||
}
|
||||
|
||||
// Verify that if the ContentProtection element for the DRM without <cenc:pssh>
|
||||
|
@ -1055,7 +1048,7 @@ TEST_F(AdaptationSetTest, UpdateToRemovePsshElement) {
|
|||
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
|
||||
" mimeType=\"video/mp4\"/>"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput1));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput1));
|
||||
|
||||
adaptation_set->UpdateContentProtectionPssh(
|
||||
"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "added pssh value");
|
||||
|
@ -1073,7 +1066,7 @@ TEST_F(AdaptationSetTest, UpdateToRemovePsshElement) {
|
|||
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
|
||||
" mimeType=\"video/mp4\"/>"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput2));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput2));
|
||||
}
|
||||
|
||||
// MPD schema has strict ordering. AudioChannelConfiguration must appear before
|
||||
|
@ -1132,7 +1125,7 @@ TEST_F(OnDemandAdaptationSetTest,
|
|||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kTestMediaInfo));
|
||||
ASSERT_TRUE(audio_representation);
|
||||
audio_representation->AddContentProtectionElement(content_protection);
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
// Verify that a text path works.
|
||||
|
@ -1163,7 +1156,7 @@ TEST_F(OnDemandAdaptationSetTest, Text) {
|
|||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo));
|
||||
ASSERT_TRUE(text_representation);
|
||||
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
} // namespace shaka
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
#include "packager/base/synchronization/lock.h"
|
||||
#include "packager/base/time/default_clock.h"
|
||||
#include "packager/base/time/time.h"
|
||||
#include "packager/media/base/rcheck.h"
|
||||
#include "packager/mpd/base/adaptation_set.h"
|
||||
#include "packager/mpd/base/mpd_utils.h"
|
||||
#include "packager/mpd/base/period.h"
|
||||
#include "packager/mpd/base/representation.h"
|
||||
#include "packager/mpd/base/xml/xml_node.h"
|
||||
#include "packager/version/version.h"
|
||||
|
||||
namespace shaka {
|
||||
|
@ -30,7 +30,7 @@ using xml::XmlNode;
|
|||
|
||||
namespace {
|
||||
|
||||
void AddMpdNameSpaceInfo(XmlNode* mpd) {
|
||||
bool AddMpdNameSpaceInfo(XmlNode* mpd) {
|
||||
DCHECK(mpd);
|
||||
|
||||
const std::set<std::string> namespaces = mpd->ExtractReferencedNamespaces();
|
||||
|
@ -41,9 +41,9 @@ void AddMpdNameSpaceInfo(XmlNode* mpd) {
|
|||
static const char kDashSchemaMpd2011[] =
|
||||
"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
|
||||
|
||||
mpd->SetStringAttribute("xmlns", kXmlNamespace);
|
||||
mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi);
|
||||
mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011);
|
||||
RCHECK(mpd->SetStringAttribute("xmlns", kXmlNamespace));
|
||||
RCHECK(mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi));
|
||||
RCHECK(mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011));
|
||||
|
||||
static const char kCencNamespace[] = "urn:mpeg:cenc:2013";
|
||||
static const char kMarlinNamespace[] =
|
||||
|
@ -62,10 +62,11 @@ void AddMpdNameSpaceInfo(XmlNode* mpd) {
|
|||
auto iter = uris.find(namespace_name);
|
||||
CHECK(iter != uris.end()) << " unexpected namespace " << namespace_name;
|
||||
|
||||
mpd->SetStringAttribute(
|
||||
RCHECK(mpd->SetStringAttribute(
|
||||
base::StringPrintf("xmlns:%s", namespace_name.c_str()).c_str(),
|
||||
iter->second);
|
||||
iter->second));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Positive(double d) {
|
||||
|
@ -88,10 +89,9 @@ std::string XmlDateTimeNowWithOffset(
|
|||
time_exploded.second);
|
||||
}
|
||||
|
||||
void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
|
||||
if (Positive(value)) {
|
||||
mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
|
||||
}
|
||||
bool SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
|
||||
return !Positive(value) ||
|
||||
mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
|
||||
}
|
||||
|
||||
std::string MakePathRelative(const std::string& media_path,
|
||||
|
@ -160,27 +160,21 @@ bool MpdBuilder::ToString(std::string* output) {
|
|||
DCHECK(output);
|
||||
static LibXmlInitializer lib_xml_initializer;
|
||||
|
||||
xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
|
||||
if (!doc)
|
||||
auto mpd = GenerateMpd();
|
||||
if (!mpd)
|
||||
return false;
|
||||
|
||||
static const int kNiceFormat = 1;
|
||||
int doc_str_size = 0;
|
||||
xmlChar* doc_str = nullptr;
|
||||
xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size, "UTF-8",
|
||||
kNiceFormat);
|
||||
output->assign(doc_str, doc_str + doc_str_size);
|
||||
xmlFree(doc_str);
|
||||
|
||||
// Cleanup, free the doc.
|
||||
doc.reset();
|
||||
std::string version = GetPackagerVersion();
|
||||
if (!version.empty()) {
|
||||
version =
|
||||
base::StringPrintf("Generated with %s version %s",
|
||||
GetPackagerProjectUrl().c_str(), version.c_str());
|
||||
}
|
||||
*output = mpd->ToString(version);
|
||||
return true;
|
||||
}
|
||||
|
||||
xmlDocPtr MpdBuilder::GenerateMpd() {
|
||||
// Setup nodes.
|
||||
static const char kXmlVersion[] = "1.0";
|
||||
xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
|
||||
base::Optional<xml::XmlNode> MpdBuilder::GenerateMpd() {
|
||||
XmlNode mpd("MPD");
|
||||
|
||||
// Add baseurls to MPD.
|
||||
|
@ -188,8 +182,8 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
|
|||
XmlNode xml_base_url("BaseURL");
|
||||
xml_base_url.SetContent(base_url);
|
||||
|
||||
if (!mpd.AddChild(xml_base_url.PassScopedPtr()))
|
||||
return nullptr;
|
||||
if (!mpd.AddChild(std::move(xml_base_url)))
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
bool output_period_duration = false;
|
||||
|
@ -202,13 +196,13 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
|
|||
}
|
||||
|
||||
for (const auto& period : periods_) {
|
||||
xml::scoped_xml_ptr<xmlNode> period_node(
|
||||
period->GetXml(output_period_duration));
|
||||
if (!period_node || !mpd.AddChild(std::move(period_node)))
|
||||
return nullptr;
|
||||
auto period_node = period->GetXml(output_period_duration);
|
||||
if (!period_node || !mpd.AddChild(std::move(*period_node)))
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
AddMpdNameSpaceInfo(&mpd);
|
||||
if (!AddMpdNameSpaceInfo(&mpd))
|
||||
return base::nullopt;
|
||||
|
||||
static const char kOnDemandProfile[] =
|
||||
"urn:mpeg:dash:profile:isoff-on-demand:2011";
|
||||
|
@ -216,10 +210,12 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
|
|||
"urn:mpeg:dash:profile:isoff-live:2011";
|
||||
switch (mpd_options_.dash_profile) {
|
||||
case DashProfile::kOnDemand:
|
||||
mpd.SetStringAttribute("profiles", kOnDemandProfile);
|
||||
if (!mpd.SetStringAttribute("profiles", kOnDemandProfile))
|
||||
return base::nullopt;
|
||||
break;
|
||||
case DashProfile::kLive:
|
||||
mpd.SetStringAttribute("profiles", kLiveProfile);
|
||||
if (!mpd.SetStringAttribute("profiles", kLiveProfile))
|
||||
return base::nullopt;
|
||||
break;
|
||||
default:
|
||||
NOTREACHED() << "Unknown DASH profile: "
|
||||
|
@ -227,69 +223,61 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
|
|||
break;
|
||||
}
|
||||
|
||||
AddCommonMpdInfo(&mpd);
|
||||
if (!AddCommonMpdInfo(&mpd))
|
||||
return base::nullopt;
|
||||
switch (mpd_options_.mpd_type) {
|
||||
case MpdType::kStatic:
|
||||
AddStaticMpdInfo(&mpd);
|
||||
if (!AddStaticMpdInfo(&mpd))
|
||||
return base::nullopt;
|
||||
break;
|
||||
case MpdType::kDynamic:
|
||||
AddDynamicMpdInfo(&mpd);
|
||||
if (!AddDynamicMpdInfo(&mpd))
|
||||
return base::nullopt;
|
||||
// Must be after Period element.
|
||||
AddUtcTiming(&mpd);
|
||||
if (!AddUtcTiming(&mpd))
|
||||
return base::nullopt;
|
||||
break;
|
||||
default:
|
||||
NOTREACHED() << "Unknown MPD type: "
|
||||
<< static_cast<int>(mpd_options_.mpd_type);
|
||||
break;
|
||||
}
|
||||
|
||||
DCHECK(doc);
|
||||
const std::string version = GetPackagerVersion();
|
||||
if (!version.empty()) {
|
||||
std::string version_string =
|
||||
base::StringPrintf("Generated with %s version %s",
|
||||
GetPackagerProjectUrl().c_str(), version.c_str());
|
||||
xml::scoped_xml_ptr<xmlNode> comment(
|
||||
xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str()));
|
||||
xmlDocSetRootElement(doc.get(), comment.get());
|
||||
xmlAddSibling(comment.release(), mpd.Release());
|
||||
} else {
|
||||
xmlDocSetRootElement(doc.get(), mpd.Release());
|
||||
}
|
||||
return doc.release();
|
||||
return mpd;
|
||||
}
|
||||
|
||||
void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
|
||||
bool MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
|
||||
if (Positive(mpd_options_.mpd_params.min_buffer_time)) {
|
||||
mpd_node->SetStringAttribute(
|
||||
RCHECK(mpd_node->SetStringAttribute(
|
||||
"minBufferTime",
|
||||
SecondsToXmlDuration(mpd_options_.mpd_params.min_buffer_time));
|
||||
SecondsToXmlDuration(mpd_options_.mpd_params.min_buffer_time)));
|
||||
} else {
|
||||
LOG(ERROR) << "minBufferTime value not specified.";
|
||||
// TODO(tinskip): Propagate error.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
|
||||
bool MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
|
||||
DCHECK(mpd_node);
|
||||
DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
|
||||
|
||||
static const char kStaticMpdType[] = "static";
|
||||
mpd_node->SetStringAttribute("type", kStaticMpdType);
|
||||
mpd_node->SetStringAttribute("mediaPresentationDuration",
|
||||
SecondsToXmlDuration(GetStaticMpdDuration()));
|
||||
return mpd_node->SetStringAttribute("type", kStaticMpdType) &&
|
||||
mpd_node->SetStringAttribute(
|
||||
"mediaPresentationDuration",
|
||||
SecondsToXmlDuration(GetStaticMpdDuration()));
|
||||
}
|
||||
|
||||
void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
|
||||
bool MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
|
||||
DCHECK(mpd_node);
|
||||
DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
|
||||
|
||||
static const char kDynamicMpdType[] = "dynamic";
|
||||
mpd_node->SetStringAttribute("type", kDynamicMpdType);
|
||||
RCHECK(mpd_node->SetStringAttribute("type", kDynamicMpdType));
|
||||
|
||||
// No offset from NOW.
|
||||
mpd_node->SetStringAttribute("publishTime",
|
||||
XmlDateTimeNowWithOffset(0, clock_.get()));
|
||||
RCHECK(mpd_node->SetStringAttribute(
|
||||
"publishTime", XmlDateTimeNowWithOffset(0, clock_.get())));
|
||||
|
||||
// 'availabilityStartTime' is required for dynamic profile. Calculate if
|
||||
// not already calculated.
|
||||
|
@ -304,36 +292,41 @@ void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
|
|||
// TODO(tinskip). Propagate an error.
|
||||
}
|
||||
}
|
||||
if (!availability_start_time_.empty())
|
||||
mpd_node->SetStringAttribute("availabilityStartTime",
|
||||
availability_start_time_);
|
||||
if (!availability_start_time_.empty()) {
|
||||
RCHECK(mpd_node->SetStringAttribute("availabilityStartTime",
|
||||
availability_start_time_));
|
||||
}
|
||||
|
||||
if (Positive(mpd_options_.mpd_params.minimum_update_period)) {
|
||||
mpd_node->SetStringAttribute(
|
||||
RCHECK(mpd_node->SetStringAttribute(
|
||||
"minimumUpdatePeriod",
|
||||
SecondsToXmlDuration(mpd_options_.mpd_params.minimum_update_period));
|
||||
SecondsToXmlDuration(mpd_options_.mpd_params.minimum_update_period)));
|
||||
} else {
|
||||
LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
|
||||
"specified.";
|
||||
}
|
||||
|
||||
SetIfPositive("timeShiftBufferDepth",
|
||||
mpd_options_.mpd_params.time_shift_buffer_depth, mpd_node);
|
||||
SetIfPositive("suggestedPresentationDelay",
|
||||
mpd_options_.mpd_params.suggested_presentation_delay, mpd_node);
|
||||
return SetIfPositive("timeShiftBufferDepth",
|
||||
mpd_options_.mpd_params.time_shift_buffer_depth,
|
||||
mpd_node) &&
|
||||
SetIfPositive("suggestedPresentationDelay",
|
||||
mpd_options_.mpd_params.suggested_presentation_delay,
|
||||
mpd_node);
|
||||
}
|
||||
|
||||
void MpdBuilder::AddUtcTiming(XmlNode* mpd_node) {
|
||||
bool MpdBuilder::AddUtcTiming(XmlNode* mpd_node) {
|
||||
DCHECK(mpd_node);
|
||||
DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
|
||||
|
||||
for (const MpdParams::UtcTiming& utc_timing :
|
||||
mpd_options_.mpd_params.utc_timings) {
|
||||
XmlNode utc_timing_node("UTCTiming");
|
||||
utc_timing_node.SetStringAttribute("schemeIdUri", utc_timing.scheme_id_uri);
|
||||
utc_timing_node.SetStringAttribute("value", utc_timing.value);
|
||||
mpd_node->AddChild(utc_timing_node.PassScopedPtr());
|
||||
RCHECK(utc_timing_node.SetStringAttribute("schemeIdUri",
|
||||
utc_timing.scheme_id_uri));
|
||||
RCHECK(utc_timing_node.SetStringAttribute("value", utc_timing.value));
|
||||
RCHECK(mpd_node->AddChild(std::move(utc_timing_node)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float MpdBuilder::GetStaticMpdDuration() {
|
||||
|
|
|
@ -17,8 +17,11 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "packager/base/compiler_specific.h"
|
||||
#include "packager/base/optional.h"
|
||||
#include "packager/base/time/clock.h"
|
||||
#include "packager/mpd/base/mpd_options.h"
|
||||
#include "packager/mpd/base/xml/xml_node.h"
|
||||
|
||||
// TODO(rkuroiwa): For classes with |id_|, consider removing the field and let
|
||||
// the MPD (XML) generation functions take care of assigning an ID to each
|
||||
|
@ -29,10 +32,6 @@ class AdaptationSet;
|
|||
class MediaInfo;
|
||||
class Period;
|
||||
|
||||
namespace xml {
|
||||
class XmlNode;
|
||||
} // namespace xml
|
||||
|
||||
/// This class generates DASH MPDs (Media Presentation Descriptions).
|
||||
class MpdBuilder {
|
||||
public:
|
||||
|
@ -57,7 +56,7 @@ class MpdBuilder {
|
|||
/// @param[out] output is an output string where the MPD gets written.
|
||||
/// @return true on success, false otherwise.
|
||||
// TODO(kqyang): Handle file IO in this class as in HLS media_playlist?
|
||||
virtual bool ToString(std::string* output);
|
||||
virtual bool ToString(std::string* output) WARN_UNUSED_RESULT;
|
||||
|
||||
/// Adjusts the fields of MediaInfo so that paths are relative to the
|
||||
/// specified MPD path.
|
||||
|
@ -86,21 +85,21 @@ class MpdBuilder {
|
|||
// Returns the document pointer to the MPD. This must be freed by the caller
|
||||
// using appropriate xmlDocPtr freeing function.
|
||||
// On failure, this returns NULL.
|
||||
xmlDocPtr GenerateMpd();
|
||||
base::Optional<xml::XmlNode> GenerateMpd();
|
||||
|
||||
// Set MPD attributes common to all profiles. Uses non-zero |mpd_options_| to
|
||||
// set attributes for the MPD.
|
||||
void AddCommonMpdInfo(xml::XmlNode* mpd_node);
|
||||
bool AddCommonMpdInfo(xml::XmlNode* mpd_node) WARN_UNUSED_RESULT;
|
||||
|
||||
// Adds 'static' MPD attributes and elements to |mpd_node|. This assumes that
|
||||
// the first child element is a Period element.
|
||||
void AddStaticMpdInfo(xml::XmlNode* mpd_node);
|
||||
bool AddStaticMpdInfo(xml::XmlNode* mpd_node) WARN_UNUSED_RESULT;
|
||||
|
||||
// Same as AddStaticMpdInfo() but for 'dynamic' MPDs.
|
||||
void AddDynamicMpdInfo(xml::XmlNode* mpd_node);
|
||||
bool AddDynamicMpdInfo(xml::XmlNode* mpd_node) WARN_UNUSED_RESULT;
|
||||
|
||||
// Add UTCTiming element if utc timing is provided.
|
||||
void AddUtcTiming(xml::XmlNode* mpd_node);
|
||||
bool AddUtcTiming(xml::XmlNode* mpd_node) WARN_UNUSED_RESULT;
|
||||
|
||||
float GetStaticMpdDuration();
|
||||
|
||||
|
@ -110,7 +109,7 @@ class MpdBuilder {
|
|||
|
||||
// Gets the earliest, normalized segment timestamp. Returns true if
|
||||
// successful, false otherwise.
|
||||
bool GetEarliestTimestamp(double* timestamp_seconds);
|
||||
bool GetEarliestTimestamp(double* timestamp_seconds) WARN_UNUSED_RESULT;
|
||||
|
||||
// Update Period durations and presentation timestamps.
|
||||
void UpdatePeriodDurationAndPresentationTimestamp();
|
||||
|
|
|
@ -72,7 +72,7 @@ TEST_F(MpdUtilsTest, ContentProtectionGeneral) {
|
|||
" <Representation id='0' bandwidth='0' codecs='avc1'"
|
||||
" mimeType='video/mp4'/>"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set_.GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
TEST_F(MpdUtilsTest, ContentProtectionMarlin) {
|
||||
|
@ -114,7 +114,7 @@ TEST_F(MpdUtilsTest, ContentProtectionMarlin) {
|
|||
" <Representation id='0' bandwidth='0' codecs='avc1'"
|
||||
" mimeType='video/mp4'/>"
|
||||
"</AdaptationSet>";
|
||||
EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set_.GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCencMspr) {
|
||||
|
@ -162,14 +162,16 @@ TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCencMspr) {
|
|||
" <ContentProtection value='MSPR 2.0'"
|
||||
" schemeIdUri='urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95'>"
|
||||
" <cenc:pssh>"
|
||||
"AAAAOHBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAERIjNEVWZ3iJkAqrvM3e7/AAAABDAxMjM="
|
||||
"AAAAOHBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAERIjNEVWZ3iJkAqrvM3e7/"
|
||||
"AAAABDAxMjM="
|
||||
" </cenc:pssh>"
|
||||
" <mspr:pro>MDEyMw==</mspr:pro>"
|
||||
" </ContentProtection>"
|
||||
" <Representation id='0' bandwidth='0' codecs='avc1' mimeType='video/mp4'/>"
|
||||
" <Representation id='0' bandwidth='0' codecs='avc1' "
|
||||
"mimeType='video/mp4'/>"
|
||||
"</AdaptationSet>";
|
||||
|
||||
EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set_.GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCenc) {
|
||||
|
@ -223,7 +225,7 @@ TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCenc) {
|
|||
" <Representation id='0' bandwidth='0' codecs='avc1' mimeType='video/mp4'/>"
|
||||
"</AdaptationSet>";
|
||||
|
||||
EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(adaptation_set_.GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
} // namespace shaka
|
||||
|
|
|
@ -120,7 +120,7 @@ AdaptationSet* Period::GetOrCreateAdaptationSet(
|
|||
return adaptation_set_ptr;
|
||||
}
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> Period::GetXml(bool output_period_duration) {
|
||||
base::Optional<xml::XmlNode> Period::GetXml(bool output_period_duration) {
|
||||
adaptation_sets_.sort(
|
||||
[](const std::unique_ptr<AdaptationSet>& adaptation_set_a,
|
||||
const std::unique_ptr<AdaptationSet>& adaptation_set_b) {
|
||||
|
@ -134,22 +134,27 @@ xml::scoped_xml_ptr<xmlNode> Period::GetXml(bool output_period_duration) {
|
|||
xml::XmlNode period("Period");
|
||||
|
||||
// Required for 'dynamic' MPDs.
|
||||
period.SetId(id_);
|
||||
if (!period.SetId(id_))
|
||||
return base::nullopt;
|
||||
// Iterate thru AdaptationSets and add them to one big Period element.
|
||||
for (const auto& adaptation_set : adaptation_sets_) {
|
||||
xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml());
|
||||
if (!child || !period.AddChild(std::move(child)))
|
||||
return nullptr;
|
||||
auto child = adaptation_set->GetXml();
|
||||
if (!child || !period.AddChild(std::move(*child)))
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
if (output_period_duration) {
|
||||
period.SetStringAttribute("duration",
|
||||
SecondsToXmlDuration(duration_seconds_));
|
||||
if (!period.SetStringAttribute("duration",
|
||||
SecondsToXmlDuration(duration_seconds_))) {
|
||||
return base::nullopt;
|
||||
}
|
||||
} else if (mpd_options_.mpd_type == MpdType::kDynamic) {
|
||||
period.SetStringAttribute("start",
|
||||
SecondsToXmlDuration(start_time_in_seconds_));
|
||||
if (!period.SetStringAttribute(
|
||||
"start", SecondsToXmlDuration(start_time_in_seconds_))) {
|
||||
return base::nullopt;
|
||||
}
|
||||
}
|
||||
return period.PassScopedPtr();
|
||||
return period;
|
||||
}
|
||||
|
||||
const std::list<AdaptationSet*> Period::GetAdaptationSets() const {
|
||||
|
|
|
@ -15,16 +15,12 @@
|
|||
#include "packager/base/optional.h"
|
||||
#include "packager/mpd/base/adaptation_set.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||
#include "packager/mpd/base/xml/xml_node.h"
|
||||
|
||||
namespace shaka {
|
||||
|
||||
struct MpdOptions;
|
||||
|
||||
namespace xml {
|
||||
class XmlNode;
|
||||
} // namespace xml
|
||||
|
||||
/// Period class maps to <Period> element and provides methods to add
|
||||
/// AdaptationSets.
|
||||
class Period {
|
||||
|
@ -50,7 +46,7 @@ class Period {
|
|||
/// Generates <Period> xml element with its child AdaptationSet elements.
|
||||
/// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a
|
||||
/// NULL scoped_xml_ptr.
|
||||
xml::scoped_xml_ptr<xmlNode> GetXml(bool output_period_duration);
|
||||
base::Optional<xml::XmlNode> GetXml(bool output_period_duration);
|
||||
|
||||
/// @return The list of AdaptationSets in this Period.
|
||||
const std::list<AdaptationSet*> GetAdaptationSets() const;
|
||||
|
|
|
@ -137,7 +137,7 @@ TEST_F(PeriodTest, GetXml) {
|
|||
// Representation::Init() is called.
|
||||
" <AdaptationSet contentType=\"\"/>"
|
||||
"</Period>";
|
||||
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(),
|
||||
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration),
|
||||
XmlNodeEqual(kExpectedXml));
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ TEST_F(PeriodTest, DynamicMpdGetXml) {
|
|||
// Representation::Init() is called.
|
||||
" <AdaptationSet contentType=\"\"/>"
|
||||
"</Period>";
|
||||
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(),
|
||||
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration),
|
||||
XmlNodeEqual(kExpectedXml));
|
||||
}
|
||||
|
||||
|
@ -202,7 +202,7 @@ TEST_F(PeriodTest, SetDurationAndGetXml) {
|
|||
// Representation::Init() is called.
|
||||
" <AdaptationSet contentType=\"\"/>"
|
||||
"</Period>";
|
||||
EXPECT_THAT(testable_period_.GetXml(kOutputPeriodDuration).get(),
|
||||
EXPECT_THAT(testable_period_.GetXml(kOutputPeriodDuration),
|
||||
XmlNodeEqual(kExpectedXml));
|
||||
const char kExpectedXmlSuppressDuration[] =
|
||||
"<Period id=\"9\">"
|
||||
|
@ -210,7 +210,7 @@ TEST_F(PeriodTest, SetDurationAndGetXml) {
|
|||
// Representation::Init() is called.
|
||||
" <AdaptationSet contentType=\"\"/>"
|
||||
"</Period>";
|
||||
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(),
|
||||
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration),
|
||||
XmlNodeEqual(kExpectedXmlSuppressDuration));
|
||||
}
|
||||
|
||||
|
@ -551,7 +551,7 @@ TEST_F(PeriodTest, OrderedByAdaptationSetId) {
|
|||
R"( <AdaptationSet id="1" contentType=""/>)"
|
||||
R"( <AdaptationSet id="2" contentType=""/>)"
|
||||
R"(</Period>)";
|
||||
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(),
|
||||
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration),
|
||||
XmlNodeEqual(kExpectedXml));
|
||||
}
|
||||
|
||||
|
|
|
@ -218,10 +218,10 @@ const MediaInfo& Representation::GetMediaInfo() const {
|
|||
// AddVideoInfo() (possibly adds FramePacking elements), AddAudioInfo() (Adds
|
||||
// AudioChannelConfig elements), AddContentProtectionElements*(), and
|
||||
// AddVODOnlyInfo() (Adds segment info).
|
||||
xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
|
||||
base::Optional<xml::XmlNode> Representation::GetXml() {
|
||||
if (!HasRequiredMediaInfoFields()) {
|
||||
LOG(ERROR) << "MediaInfo missing required fields.";
|
||||
return xml::scoped_xml_ptr<xmlNode>();
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
const uint64_t bandwidth = media_info_.has_bandwidth()
|
||||
|
@ -232,11 +232,13 @@ xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
|
|||
|
||||
xml::RepresentationXmlNode representation;
|
||||
// Mandatory fields for Representation.
|
||||
representation.SetId(id_);
|
||||
representation.SetIntegerAttribute("bandwidth", bandwidth);
|
||||
if (!codecs_.empty())
|
||||
representation.SetStringAttribute("codecs", codecs_);
|
||||
representation.SetStringAttribute("mimeType", mime_type_);
|
||||
if (!representation.SetId(id_) ||
|
||||
!representation.SetIntegerAttribute("bandwidth", bandwidth) ||
|
||||
!(codecs_.empty() ||
|
||||
representation.SetStringAttribute("codecs", codecs_)) ||
|
||||
!representation.SetStringAttribute("mimeType", mime_type_)) {
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
const bool has_video_info = media_info_.has_video_info();
|
||||
const bool has_audio_info = media_info_.has_audio_info();
|
||||
|
@ -248,37 +250,37 @@ xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
|
|||
!(output_suppression_flags_ & kSuppressHeight),
|
||||
!(output_suppression_flags_ & kSuppressFrameRate))) {
|
||||
LOG(ERROR) << "Failed to add video info to Representation XML.";
|
||||
return xml::scoped_xml_ptr<xmlNode>();
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
if (has_audio_info &&
|
||||
!representation.AddAudioInfo(media_info_.audio_info())) {
|
||||
LOG(ERROR) << "Failed to add audio info to Representation XML.";
|
||||
return xml::scoped_xml_ptr<xmlNode>();
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
if (!representation.AddContentProtectionElements(
|
||||
content_protection_elements_)) {
|
||||
return xml::scoped_xml_ptr<xmlNode>();
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
if (HasVODOnlyFields(media_info_) &&
|
||||
!representation.AddVODOnlyInfo(media_info_)) {
|
||||
LOG(ERROR) << "Failed to add VOD info.";
|
||||
return xml::scoped_xml_ptr<xmlNode>();
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
if (HasLiveOnlyFields(media_info_) &&
|
||||
!representation.AddLiveOnlyInfo(media_info_, segment_infos_,
|
||||
start_number_)) {
|
||||
LOG(ERROR) << "Failed to add Live info.";
|
||||
return xml::scoped_xml_ptr<xmlNode>();
|
||||
return base::nullopt;
|
||||
}
|
||||
// TODO(rkuroiwa): It is likely that all representations have the exact same
|
||||
// SegmentTemplate. Optimize and propagate the tag up to AdaptationSet level.
|
||||
|
||||
output_suppression_flags_ = 0;
|
||||
return representation.PassScopedPtr();
|
||||
return std::move(representation);
|
||||
}
|
||||
|
||||
void Representation::SuppressOnce(SuppressFlag flag) {
|
||||
|
|
|
@ -9,26 +9,22 @@
|
|||
#ifndef PACKAGER_MPD_BASE_REPRESENTATION_H_
|
||||
#define PACKAGER_MPD_BASE_REPRESENTATION_H_
|
||||
|
||||
#include "packager/mpd/base/bandwidth_estimator.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
#include "packager/mpd/base/segment_info.h"
|
||||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "packager/base/optional.h"
|
||||
#include "packager/mpd/base/bandwidth_estimator.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
#include "packager/mpd/base/segment_info.h"
|
||||
#include "packager/mpd/base/xml/xml_node.h"
|
||||
|
||||
namespace shaka {
|
||||
|
||||
struct ContentProtectionElement;
|
||||
struct MpdOptions;
|
||||
|
||||
namespace xml {
|
||||
class XmlNode;
|
||||
class RepresentationXmlNode;
|
||||
} // namespace xml
|
||||
|
||||
class RepresentationStateChangeListener {
|
||||
public:
|
||||
RepresentationStateChangeListener() {}
|
||||
|
@ -116,7 +112,7 @@ class Representation {
|
|||
virtual const MediaInfo& GetMediaInfo() const;
|
||||
|
||||
/// @return Copy of <Representation>.
|
||||
xml::scoped_xml_ptr<xmlNode> GetXml();
|
||||
base::Optional<xml::XmlNode> GetXml();
|
||||
|
||||
/// By calling this methods, the next time GetXml() is
|
||||
/// called, the corresponding attributes will not be set.
|
||||
|
|
|
@ -169,7 +169,7 @@ TEST_F(RepresentationTest, CheckVideoInfoReflectedInXml) {
|
|||
" codecs=\"avc1\" mimeType=\"video/mp4\" "
|
||||
" sar=\"1:1\" width=\"1280\" height=\"720\" "
|
||||
" frameRate=\"10/10\"/>";
|
||||
EXPECT_THAT(representation->GetXml().get(), XmlNodeEqual(kExpectedOutput));
|
||||
EXPECT_THAT(representation->GetXml(), XmlNodeEqual(kExpectedOutput));
|
||||
}
|
||||
|
||||
TEST_F(RepresentationTest, CheckVideoInfoVp8CodecInMp4) {
|
||||
|
@ -188,7 +188,7 @@ TEST_F(RepresentationTest, CheckVideoInfoVp8CodecInMp4) {
|
|||
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8),
|
||||
kAnyRepresentationId, NoListener());
|
||||
ASSERT_TRUE(representation->Init());
|
||||
EXPECT_THAT(representation->GetXml().get(),
|
||||
EXPECT_THAT(representation->GetXml(),
|
||||
AttributeEqual("codecs", "vp08.00.00.08.01.01.00.00"));
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ TEST_F(RepresentationTest, CheckVideoInfoVp8CodecInWebm) {
|
|||
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8),
|
||||
kAnyRepresentationId, NoListener());
|
||||
ASSERT_TRUE(representation->Init());
|
||||
EXPECT_THAT(representation->GetXml().get(), AttributeEqual("codecs", "vp8"));
|
||||
EXPECT_THAT(representation->GetXml(), AttributeEqual("codecs", "vp8"));
|
||||
}
|
||||
|
||||
TEST_F(RepresentationTest, CheckVideoInfoVp9CodecInWebm) {
|
||||
|
@ -229,7 +229,7 @@ TEST_F(RepresentationTest, CheckVideoInfoVp9CodecInWebm) {
|
|||
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp9),
|
||||
kAnyRepresentationId, NoListener());
|
||||
ASSERT_TRUE(representation->Init());
|
||||
EXPECT_THAT(representation->GetXml().get(),
|
||||
EXPECT_THAT(representation->GetXml(),
|
||||
AttributeEqual("codecs", "vp09.00.00.08.01.01.00.00"));
|
||||
}
|
||||
|
||||
|
@ -251,7 +251,7 @@ TEST_F(RepresentationTest, CheckVideoInfoLegacyVp9CodecInWebm) {
|
|||
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp9),
|
||||
kAnyRepresentationId, NoListener());
|
||||
ASSERT_TRUE(representation->Init());
|
||||
EXPECT_THAT(representation->GetXml().get(), AttributeEqual("codecs", "vp9"));
|
||||
EXPECT_THAT(representation->GetXml(), AttributeEqual("codecs", "vp9"));
|
||||
}
|
||||
|
||||
// Make sure RepresentationStateChangeListener::OnNewSegmentForRepresentation()
|
||||
|
@ -325,7 +325,7 @@ TEST_F(RepresentationTest, TtmlXmlMimeType) {
|
|||
CreateRepresentation(ConvertToMediaInfo(kTtmlXmlMediaInfo),
|
||||
kAnyRepresentationId, NoListener());
|
||||
ASSERT_TRUE(representation->Init());
|
||||
EXPECT_THAT(representation->GetXml().get(),
|
||||
EXPECT_THAT(representation->GetXml(),
|
||||
AttributeEqual("mimeType", "application/ttml+xml"));
|
||||
}
|
||||
|
||||
|
@ -340,7 +340,7 @@ TEST_F(RepresentationTest, TtmlMp4MimeType) {
|
|||
CreateRepresentation(ConvertToMediaInfo(kTtmlMp4MediaInfo),
|
||||
kAnyRepresentationId, NoListener());
|
||||
ASSERT_TRUE(representation->Init());
|
||||
EXPECT_THAT(representation->GetXml().get(),
|
||||
EXPECT_THAT(representation->GetXml(),
|
||||
AttributeEqual("mimeType", "application/mp4"));
|
||||
}
|
||||
|
||||
|
@ -354,8 +354,7 @@ TEST_F(RepresentationTest, WebVttMimeType) {
|
|||
auto representation = CreateRepresentation(
|
||||
ConvertToMediaInfo(kWebVttMediaInfo), kAnyRepresentationId, NoListener());
|
||||
ASSERT_TRUE(representation->Init());
|
||||
EXPECT_THAT(representation->GetXml().get(),
|
||||
AttributeEqual("mimeType", "text/vtt"));
|
||||
EXPECT_THAT(representation->GetXml(), AttributeEqual("mimeType", "text/vtt"));
|
||||
}
|
||||
|
||||
// Verify that Suppress*() methods work.
|
||||
|
@ -376,22 +375,22 @@ TEST_F(RepresentationTest, SuppressRepresentationAttributes) {
|
|||
ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener());
|
||||
|
||||
representation->SuppressOnce(Representation::kSuppressWidth);
|
||||
xml::scoped_xml_ptr<xmlNode> no_width(representation->GetXml());
|
||||
EXPECT_THAT(no_width.get(), Not(AttributeSet("width")));
|
||||
EXPECT_THAT(no_width.get(), AttributeEqual("height", "480"));
|
||||
EXPECT_THAT(no_width.get(), AttributeEqual("frameRate", "10/10"));
|
||||
auto no_width = representation->GetXml();
|
||||
EXPECT_THAT(no_width, Not(AttributeSet("width")));
|
||||
EXPECT_THAT(no_width, AttributeEqual("height", "480"));
|
||||
EXPECT_THAT(no_width, AttributeEqual("frameRate", "10/10"));
|
||||
|
||||
representation->SuppressOnce(Representation::kSuppressHeight);
|
||||
xml::scoped_xml_ptr<xmlNode> no_height(representation->GetXml());
|
||||
EXPECT_THAT(no_height.get(), Not(AttributeSet("height")));
|
||||
EXPECT_THAT(no_height.get(), AttributeEqual("width", "720"));
|
||||
EXPECT_THAT(no_height.get(), AttributeEqual("frameRate", "10/10"));
|
||||
auto no_height = representation->GetXml();
|
||||
EXPECT_THAT(no_height, Not(AttributeSet("height")));
|
||||
EXPECT_THAT(no_height, AttributeEqual("width", "720"));
|
||||
EXPECT_THAT(no_height, AttributeEqual("frameRate", "10/10"));
|
||||
|
||||
representation->SuppressOnce(Representation::kSuppressFrameRate);
|
||||
xml::scoped_xml_ptr<xmlNode> no_frame_rate(representation->GetXml());
|
||||
EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("frameRate")));
|
||||
EXPECT_THAT(no_frame_rate.get(), AttributeEqual("width", "720"));
|
||||
EXPECT_THAT(no_frame_rate.get(), AttributeEqual("height", "480"));
|
||||
auto no_frame_rate = representation->GetXml();
|
||||
EXPECT_THAT(no_frame_rate, Not(AttributeSet("frameRate")));
|
||||
EXPECT_THAT(no_frame_rate, AttributeEqual("width", "720"));
|
||||
EXPECT_THAT(no_frame_rate, AttributeEqual("height", "480"));
|
||||
}
|
||||
|
||||
TEST_F(RepresentationTest, CheckRepresentationId) {
|
||||
|
@ -401,7 +400,7 @@ TEST_F(RepresentationTest, CheckRepresentationId) {
|
|||
auto representation =
|
||||
CreateRepresentation(video_media_info, kRepresentationId, NoListener());
|
||||
EXPECT_TRUE(representation->Init());
|
||||
EXPECT_THAT(representation->GetXml().get(),
|
||||
EXPECT_THAT(representation->GetXml(),
|
||||
AttributeEqual("id", std::to_string(kRepresentationId)));
|
||||
}
|
||||
|
||||
|
@ -508,7 +507,7 @@ TEST_F(SegmentTemplateTest, OneSegmentNormal) {
|
|||
AddSegments(kStartTime, kDuration, kSize, 0);
|
||||
|
||||
expected_s_elements_ = "<S t=\"0\" d=\"10\"/>";
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
TEST_F(SegmentTemplateTest, RepresentationClone) {
|
||||
|
@ -533,8 +532,7 @@ TEST_F(SegmentTemplateTest, RepresentationClone) {
|
|||
" media=\"$Number$.mp4\" startNumber=\"2\">\n"
|
||||
" </SegmentTemplate>\n"
|
||||
"</Representation>\n";
|
||||
EXPECT_THAT(cloned_representation->GetXml().get(),
|
||||
XmlNodeEqual(kExpectedXml));
|
||||
EXPECT_THAT(cloned_representation->GetXml(), XmlNodeEqual(kExpectedXml));
|
||||
}
|
||||
|
||||
TEST_F(SegmentTemplateTest, PresentationTimeOffset) {
|
||||
|
@ -557,7 +555,7 @@ TEST_F(SegmentTemplateTest, PresentationTimeOffset) {
|
|||
" </SegmentTimeline>\n"
|
||||
" </SegmentTemplate>\n"
|
||||
"</Representation>\n";
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(kExpectedXml));
|
||||
}
|
||||
|
||||
TEST_F(SegmentTemplateTest, GetStartAndEndTimestamps) {
|
||||
|
@ -597,7 +595,7 @@ TEST_F(SegmentTemplateTest, NormalRepeatedSegmentDuration) {
|
|||
repeat = 0;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
TEST_F(SegmentTemplateTest, RepeatedSegmentsFromNonZeroStartTime) {
|
||||
|
@ -617,7 +615,7 @@ TEST_F(SegmentTemplateTest, RepeatedSegmentsFromNonZeroStartTime) {
|
|||
repeat = 3;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// Segments not starting from 0.
|
||||
|
@ -629,7 +627,7 @@ TEST_F(SegmentTemplateTest, NonZeroStartTime) {
|
|||
const int kRepeat = 1;
|
||||
AddSegments(kStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// There is a gap in the segments, but still valid.
|
||||
|
@ -643,7 +641,7 @@ TEST_F(SegmentTemplateTest, NonContiguousLiveInfo) {
|
|||
const int64_t kStartTimeOffset = 100;
|
||||
AddSegments(kDuration + kStartTimeOffset, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// Add segments out of order. Segments that start before the previous segment
|
||||
|
@ -658,7 +656,7 @@ TEST_F(SegmentTemplateTest, OutOfOrder) {
|
|||
AddSegments(kLaterStartTime, kDuration, kSize, kRepeat);
|
||||
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// No segments should be overlapping.
|
||||
|
@ -674,7 +672,7 @@ TEST_F(SegmentTemplateTest, OverlappingSegments) {
|
|||
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
|
||||
AddSegments(kOverlappingSegmentStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// Some segments can be overlapped due to rounding errors. As long as it falls
|
||||
|
@ -692,7 +690,7 @@ TEST_F(SegmentTemplateTest, OverlappingSegmentsWithinErrorRange) {
|
|||
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
|
||||
AddSegments(kOverlappingSegmentStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
class SegmentTimelineTestBase : public SegmentTemplateTest {
|
||||
|
@ -782,7 +780,7 @@ TEST_P(ApproximateSegmentTimelineTest, SegmentDurationAdjusted) {
|
|||
expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR,
|
||||
kStartTime, kDurationSmaller);
|
||||
}
|
||||
EXPECT_THAT(representation_->GetXml().get(),
|
||||
EXPECT_THAT(representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_elements)));
|
||||
}
|
||||
|
||||
|
@ -803,7 +801,7 @@ TEST_P(ApproximateSegmentTimelineTest,
|
|||
expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR,
|
||||
kStartTime, kDurationSmaller);
|
||||
}
|
||||
EXPECT_THAT(representation_->GetXml().get(),
|
||||
EXPECT_THAT(representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_elements)));
|
||||
}
|
||||
|
||||
|
@ -835,7 +833,7 @@ TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations) {
|
|||
kStartTime + kDurationSmaller + kDurationLarger,
|
||||
kDurationSmaller);
|
||||
}
|
||||
EXPECT_THAT(representation_->GetXml().get(),
|
||||
EXPECT_THAT(representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_elements)));
|
||||
}
|
||||
|
||||
|
@ -861,7 +859,7 @@ TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations2) {
|
|||
expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime,
|
||||
kDurationLarger, kNumSegments - 1);
|
||||
}
|
||||
EXPECT_THAT(representation_->GetXml().get(),
|
||||
EXPECT_THAT(representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_elements)));
|
||||
}
|
||||
|
||||
|
@ -885,7 +883,7 @@ TEST_P(ApproximateSegmentTimelineTest, FillSmallGap) {
|
|||
base::StringPrintf(kSElementTemplate, kStartTime + kDuration + kGap,
|
||||
kDuration, 1 /* repeat */);
|
||||
}
|
||||
EXPECT_THAT(representation_->GetXml().get(),
|
||||
EXPECT_THAT(representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_elements)));
|
||||
}
|
||||
|
||||
|
@ -909,7 +907,7 @@ TEST_P(ApproximateSegmentTimelineTest, FillSmallOverlap) {
|
|||
base::StringPrintf(kSElementTemplate, kStartTime + kDuration - kOverlap,
|
||||
kDuration, 1 /* repeat */);
|
||||
}
|
||||
EXPECT_THAT(representation_->GetXml().get(),
|
||||
EXPECT_THAT(representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_elements)));
|
||||
}
|
||||
|
||||
|
@ -946,7 +944,7 @@ TEST_P(ApproximateSegmentTimelineTest, NoSampleDuration) {
|
|||
" </SegmentTimeline>\n"
|
||||
" </SegmentTemplate>\n"
|
||||
"</Representation>\n";
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml));
|
||||
EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(kExpectedXml));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ApproximateSegmentTimelineTest,
|
||||
|
@ -996,7 +994,7 @@ TEST_P(TimeShiftBufferDepthTest, Normal) {
|
|||
initial_start_time_ + kDuration * (kRepeat - kExpectedRepeatsLeft),
|
||||
kDuration, kExpectedRepeatsLeft);
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber)));
|
||||
}
|
||||
|
||||
|
@ -1020,7 +1018,7 @@ TEST_P(TimeShiftBufferDepthTest, TimeShiftBufferDepthShorterThanSegmentLength) {
|
|||
const std::string expected_s_element = base::StringPrintf(
|
||||
kSElementTemplate, initial_start_time_, kDuration, kRepeat);
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber)));
|
||||
}
|
||||
|
||||
|
@ -1053,7 +1051,7 @@ TEST_P(TimeShiftBufferDepthTest, Generic) {
|
|||
|
||||
const int kExpectedRemovedSegments = kRepeat + 1;
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(
|
||||
expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments)));
|
||||
}
|
||||
|
@ -1095,7 +1093,7 @@ TEST_P(TimeShiftBufferDepthTest, MoreThanOneS) {
|
|||
kTwoSecondDuration, kTwoSecondSegmentRepeat);
|
||||
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(
|
||||
expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments)));
|
||||
}
|
||||
|
@ -1136,7 +1134,7 @@ TEST_P(TimeShiftBufferDepthTest, UseLastSegmentInS) {
|
|||
expected_s_element +=
|
||||
base::StringPrintf(kSElementTemplate, first_s_element_end_time,
|
||||
kTwoSecondDuration, kTwoSecondSegmentRepeat);
|
||||
EXPECT_THAT(representation_->GetXml().get(),
|
||||
EXPECT_THAT(representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_element, 2)));
|
||||
}
|
||||
|
||||
|
@ -1168,7 +1166,7 @@ TEST_P(TimeShiftBufferDepthTest, NormalGap) {
|
|||
gap_s_element_start_time, kDuration);
|
||||
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber)));
|
||||
}
|
||||
|
||||
|
@ -1204,7 +1202,7 @@ TEST_P(TimeShiftBufferDepthTest, HugeGap) {
|
|||
kSecondSElementRepeat);
|
||||
const int kExpectedRemovedSegments = kRepeat;
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(
|
||||
expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments)));
|
||||
}
|
||||
|
@ -1233,7 +1231,7 @@ TEST_P(TimeShiftBufferDepthTest, ManySegments) {
|
|||
initial_start_time_ + kExpectedRemovedSegments * kDuration, kDuration,
|
||||
kExpectedSegmentsRepeat);
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
representation_->GetXml(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber)));
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "packager/mpd/base/xml/xml_node.h"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <libxml/tree.h>
|
||||
|
||||
#include <limits>
|
||||
#include <set>
|
||||
|
@ -15,9 +16,11 @@
|
|||
#include "packager/base/macros.h"
|
||||
#include "packager/base/strings/string_number_conversions.h"
|
||||
#include "packager/base/sys_byteorder.h"
|
||||
#include "packager/media/base/rcheck.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
#include "packager/mpd/base/mpd_utils.h"
|
||||
#include "packager/mpd/base/segment_info.h"
|
||||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||
|
||||
DEFINE_bool(segment_template_constant_duration,
|
||||
false,
|
||||
|
@ -77,12 +80,12 @@ bool PopulateSegmentTimeline(const std::list<SegmentInfo>& segment_infos,
|
|||
XmlNode* segment_timeline) {
|
||||
for (const SegmentInfo& segment_info : segment_infos) {
|
||||
XmlNode s_element("S");
|
||||
s_element.SetIntegerAttribute("t", segment_info.start_time);
|
||||
s_element.SetIntegerAttribute("d", segment_info.duration);
|
||||
RCHECK(s_element.SetIntegerAttribute("t", segment_info.start_time));
|
||||
RCHECK(s_element.SetIntegerAttribute("d", segment_info.duration));
|
||||
if (segment_info.repeat > 0)
|
||||
s_element.SetIntegerAttribute("r", segment_info.repeat);
|
||||
RCHECK(s_element.SetIntegerAttribute("r", segment_info.repeat));
|
||||
|
||||
CHECK(segment_timeline->AddChild(s_element.PassScopedPtr()));
|
||||
RCHECK(segment_timeline->AddChild(std::move(s_element)));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -118,22 +121,30 @@ void TraverseNodesAndCollectNamespaces(const xmlNode* node,
|
|||
|
||||
namespace xml {
|
||||
|
||||
XmlNode::XmlNode(const char* name) : node_(xmlNewNode(NULL, BAD_CAST name)) {
|
||||
DCHECK(name);
|
||||
DCHECK(node_);
|
||||
class XmlNode::Impl {
|
||||
public:
|
||||
scoped_xml_ptr<xmlNode> node;
|
||||
};
|
||||
|
||||
XmlNode::XmlNode(const std::string& name) : impl_(new Impl) {
|
||||
impl_->node.reset(xmlNewNode(NULL, BAD_CAST name.c_str()));
|
||||
DCHECK(impl_->node);
|
||||
}
|
||||
|
||||
XmlNode::XmlNode(XmlNode&&) = default;
|
||||
|
||||
XmlNode::~XmlNode() {}
|
||||
|
||||
bool XmlNode::AddChild(scoped_xml_ptr<xmlNode> child) {
|
||||
DCHECK(node_);
|
||||
DCHECK(child);
|
||||
if (!xmlAddChild(node_.get(), child.get()))
|
||||
return false;
|
||||
XmlNode& XmlNode::operator=(XmlNode&&) = default;
|
||||
|
||||
// Reaching here means the ownership of |child| transfered to |node_|.
|
||||
bool XmlNode::AddChild(XmlNode child) {
|
||||
DCHECK(impl_->node);
|
||||
DCHECK(child.impl_->node);
|
||||
RCHECK(xmlAddChild(impl_->node.get(), child.impl_->node.get()));
|
||||
|
||||
// Reaching here means the ownership of |child| transfered to |node|.
|
||||
// Release the pointer so that it doesn't get destructed in this scope.
|
||||
ignore_result(child.release());
|
||||
ignore_result(child.impl_->node.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -141,12 +152,12 @@ bool XmlNode::AddElements(const std::vector<Element>& elements) {
|
|||
for (size_t element_index = 0; element_index < elements.size();
|
||||
++element_index) {
|
||||
const Element& child_element = elements[element_index];
|
||||
XmlNode child_node(child_element.name.c_str());
|
||||
XmlNode child_node(child_element.name);
|
||||
for (std::map<std::string, std::string>::const_iterator attribute_it =
|
||||
child_element.attributes.begin();
|
||||
attribute_it != child_element.attributes.end(); ++attribute_it) {
|
||||
child_node.SetStringAttribute(attribute_it->first.c_str(),
|
||||
attribute_it->second);
|
||||
RCHECK(child_node.SetStringAttribute(attribute_it->first,
|
||||
attribute_it->second));
|
||||
}
|
||||
|
||||
// Note that somehow |SetContent| needs to be called before |AddElements|
|
||||
|
@ -154,114 +165,128 @@ bool XmlNode::AddElements(const std::vector<Element>& elements) {
|
|||
child_node.SetContent(child_element.content);
|
||||
|
||||
// Recursively set children for the child.
|
||||
if (!child_node.AddElements(child_element.subelements))
|
||||
return false;
|
||||
RCHECK(child_node.AddElements(child_element.subelements));
|
||||
|
||||
if (!xmlAddChild(node_.get(), child_node.GetRawPtr())) {
|
||||
if (!xmlAddChild(impl_->node.get(), child_node.impl_->node.get())) {
|
||||
LOG(ERROR) << "Failed to set child " << child_element.name
|
||||
<< " to parent element "
|
||||
<< reinterpret_cast<const char*>(node_->name);
|
||||
<< reinterpret_cast<const char*>(impl_->node->name);
|
||||
return false;
|
||||
}
|
||||
// Reaching here means the ownership of |child_node| transfered to |node_|.
|
||||
// Reaching here means the ownership of |child_node| transfered to |node|.
|
||||
// Release the pointer so that it doesn't get destructed in this scope.
|
||||
ignore_result(child_node.Release());
|
||||
child_node.impl_->node.release();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void XmlNode::SetStringAttribute(const char* attribute_name,
|
||||
bool XmlNode::SetStringAttribute(const std::string& attribute_name,
|
||||
const std::string& attribute) {
|
||||
DCHECK(node_);
|
||||
DCHECK(attribute_name);
|
||||
xmlSetProp(node_.get(), BAD_CAST attribute_name, BAD_CAST attribute.c_str());
|
||||
DCHECK(impl_->node);
|
||||
return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
|
||||
BAD_CAST attribute.c_str()) != nullptr;
|
||||
}
|
||||
|
||||
void XmlNode::SetIntegerAttribute(const char* attribute_name, uint64_t number) {
|
||||
DCHECK(node_);
|
||||
DCHECK(attribute_name);
|
||||
xmlSetProp(node_.get(),
|
||||
BAD_CAST attribute_name,
|
||||
BAD_CAST (base::Uint64ToString(number).c_str()));
|
||||
bool XmlNode::SetIntegerAttribute(const std::string& attribute_name,
|
||||
uint64_t number) {
|
||||
DCHECK(impl_->node);
|
||||
return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
|
||||
BAD_CAST(base::Uint64ToString(number).c_str())) != nullptr;
|
||||
}
|
||||
|
||||
void XmlNode::SetFloatingPointAttribute(const char* attribute_name,
|
||||
bool XmlNode::SetFloatingPointAttribute(const std::string& attribute_name,
|
||||
double number) {
|
||||
DCHECK(node_);
|
||||
DCHECK(attribute_name);
|
||||
xmlSetProp(node_.get(), BAD_CAST attribute_name,
|
||||
BAD_CAST(base::DoubleToString(number).c_str()));
|
||||
DCHECK(impl_->node);
|
||||
return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
|
||||
BAD_CAST(base::DoubleToString(number).c_str())) != nullptr;
|
||||
}
|
||||
|
||||
void XmlNode::SetId(uint32_t id) {
|
||||
SetIntegerAttribute("id", id);
|
||||
bool XmlNode::SetId(uint32_t id) {
|
||||
return SetIntegerAttribute("id", id);
|
||||
}
|
||||
|
||||
void XmlNode::SetContent(const std::string& content) {
|
||||
DCHECK(node_);
|
||||
xmlNodeSetContent(node_.get(), BAD_CAST content.c_str());
|
||||
DCHECK(impl_->node);
|
||||
xmlNodeSetContent(impl_->node.get(), BAD_CAST content.c_str());
|
||||
}
|
||||
|
||||
std::set<std::string> XmlNode::ExtractReferencedNamespaces() {
|
||||
std::set<std::string> XmlNode::ExtractReferencedNamespaces() const {
|
||||
std::set<std::string> namespaces;
|
||||
TraverseNodesAndCollectNamespaces(node_.get(), &namespaces);
|
||||
TraverseNodesAndCollectNamespaces(impl_->node.get(), &namespaces);
|
||||
return namespaces;
|
||||
}
|
||||
|
||||
scoped_xml_ptr<xmlNode> XmlNode::PassScopedPtr() {
|
||||
DVLOG(2) << "Passing node_.";
|
||||
DCHECK(node_);
|
||||
return std::move(node_);
|
||||
std::string XmlNode::ToString(const std::string& comment) const {
|
||||
// Create an xmlDoc from xmlNodePtr. The node is copied so ownership does not
|
||||
// transfer.
|
||||
xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST "1.0"));
|
||||
if (comment.empty()) {
|
||||
xmlDocSetRootElement(doc.get(), xmlCopyNode(impl_->node.get(), true));
|
||||
} else {
|
||||
xml::scoped_xml_ptr<xmlNode> comment_xml(
|
||||
xmlNewDocComment(doc.get(), BAD_CAST comment.c_str()));
|
||||
xmlDocSetRootElement(doc.get(), comment_xml.get());
|
||||
xmlAddSibling(comment_xml.release(), xmlCopyNode(impl_->node.get(), true));
|
||||
}
|
||||
|
||||
// Format the xmlDoc to string.
|
||||
static const int kNiceFormat = 1;
|
||||
int doc_str_size = 0;
|
||||
xmlChar* doc_str = nullptr;
|
||||
xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size, "UTF-8",
|
||||
kNiceFormat);
|
||||
std::string output(doc_str, doc_str + doc_str_size);
|
||||
xmlFree(doc_str);
|
||||
return output;
|
||||
}
|
||||
|
||||
xmlNodePtr XmlNode::Release() {
|
||||
DVLOG(2) << "Releasing node_.";
|
||||
DCHECK(node_);
|
||||
return node_.release();
|
||||
bool XmlNode::GetAttribute(const std::string& name, std::string* value) const {
|
||||
xml::scoped_xml_ptr<xmlChar> str(
|
||||
xmlGetProp(impl_->node.get(), BAD_CAST name.c_str()));
|
||||
if (!str)
|
||||
return false;
|
||||
*value = reinterpret_cast<const char*>(str.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
xmlNodePtr XmlNode::GetRawPtr() {
|
||||
return node_.get();
|
||||
xmlNode* XmlNode::GetRawPtr() const {
|
||||
return impl_->node.get();
|
||||
}
|
||||
|
||||
RepresentationBaseXmlNode::RepresentationBaseXmlNode(const char* name)
|
||||
RepresentationBaseXmlNode::RepresentationBaseXmlNode(const std::string& 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;
|
||||
for (const auto& elem : content_protection_elements) {
|
||||
RCHECK(AddContentProtectionElement(elem));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RepresentationBaseXmlNode::AddSupplementalProperty(
|
||||
bool RepresentationBaseXmlNode::AddSupplementalProperty(
|
||||
const std::string& scheme_id_uri,
|
||||
const std::string& value) {
|
||||
AddDescriptor("SupplementalProperty", scheme_id_uri, value);
|
||||
return AddDescriptor("SupplementalProperty", scheme_id_uri, value);
|
||||
}
|
||||
|
||||
void RepresentationBaseXmlNode::AddEssentialProperty(
|
||||
bool RepresentationBaseXmlNode::AddEssentialProperty(
|
||||
const std::string& scheme_id_uri,
|
||||
const std::string& value) {
|
||||
AddDescriptor("EssentialProperty", scheme_id_uri, value);
|
||||
return AddDescriptor("EssentialProperty", scheme_id_uri, value);
|
||||
}
|
||||
|
||||
bool RepresentationBaseXmlNode::AddDescriptor(
|
||||
const std::string& descriptor_name,
|
||||
const std::string& scheme_id_uri,
|
||||
const std::string& value) {
|
||||
XmlNode descriptor(descriptor_name.c_str());
|
||||
descriptor.SetStringAttribute("schemeIdUri", scheme_id_uri);
|
||||
XmlNode descriptor(descriptor_name);
|
||||
RCHECK(descriptor.SetStringAttribute("schemeIdUri", scheme_id_uri));
|
||||
if (!value.empty())
|
||||
descriptor.SetStringAttribute("value", value);
|
||||
return AddChild(descriptor.PassScopedPtr());
|
||||
RCHECK(descriptor.SetStringAttribute("value", value));
|
||||
return AddChild(std::move(descriptor));
|
||||
}
|
||||
|
||||
bool RepresentationBaseXmlNode::AddContentProtectionElement(
|
||||
|
@ -270,43 +295,34 @@ bool RepresentationBaseXmlNode::AddContentProtectionElement(
|
|||
|
||||
// @value is an optional attribute.
|
||||
if (!content_protection_element.value.empty()) {
|
||||
content_protection_node.SetStringAttribute(
|
||||
"value", content_protection_element.value);
|
||||
RCHECK(content_protection_node.SetStringAttribute(
|
||||
"value", content_protection_element.value));
|
||||
}
|
||||
content_protection_node.SetStringAttribute(
|
||||
"schemeIdUri", content_protection_element.scheme_id_uri);
|
||||
RCHECK(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);
|
||||
for (const auto& pair : content_protection_element.additional_attributes) {
|
||||
RCHECK(content_protection_node.SetStringAttribute(pair.first, pair.second));
|
||||
}
|
||||
|
||||
if (!content_protection_node.AddElements(
|
||||
content_protection_element.subelements)) {
|
||||
return false;
|
||||
}
|
||||
return AddChild(content_protection_node.PassScopedPtr());
|
||||
RCHECK(content_protection_node.AddElements(
|
||||
content_protection_element.subelements));
|
||||
return AddChild(std::move(content_protection_node));
|
||||
}
|
||||
|
||||
AdaptationSetXmlNode::AdaptationSetXmlNode()
|
||||
: RepresentationBaseXmlNode("AdaptationSet") {}
|
||||
AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
|
||||
|
||||
void AdaptationSetXmlNode::AddAccessibilityElement(
|
||||
bool AdaptationSetXmlNode::AddAccessibilityElement(
|
||||
const std::string& scheme_id_uri,
|
||||
const std::string& value) {
|
||||
AddDescriptor("Accessibility", scheme_id_uri, value);
|
||||
return AddDescriptor("Accessibility", scheme_id_uri, value);
|
||||
}
|
||||
|
||||
void AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
|
||||
bool AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
|
||||
const std::string& value) {
|
||||
AddDescriptor("Role", scheme_id_uri, value);
|
||||
return AddDescriptor("Role", scheme_id_uri, value);
|
||||
}
|
||||
|
||||
RepresentationXmlNode::RepresentationXmlNode()
|
||||
|
@ -323,39 +339,36 @@ bool RepresentationXmlNode::AddVideoInfo(const VideoInfo& video_info,
|
|||
}
|
||||
|
||||
if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
|
||||
SetStringAttribute("sar", base::IntToString(video_info.pixel_width()) +
|
||||
":" +
|
||||
base::IntToString(video_info.pixel_height()));
|
||||
RCHECK(SetStringAttribute(
|
||||
"sar", base::IntToString(video_info.pixel_width()) + ":" +
|
||||
base::IntToString(video_info.pixel_height())));
|
||||
}
|
||||
|
||||
if (set_width)
|
||||
SetIntegerAttribute("width", video_info.width());
|
||||
RCHECK(SetIntegerAttribute("width", video_info.width()));
|
||||
if (set_height)
|
||||
SetIntegerAttribute("height", video_info.height());
|
||||
RCHECK(SetIntegerAttribute("height", video_info.height()));
|
||||
if (set_frame_rate) {
|
||||
SetStringAttribute("frameRate",
|
||||
base::IntToString(video_info.time_scale()) + "/" +
|
||||
base::IntToString(video_info.frame_duration()));
|
||||
RCHECK(SetStringAttribute(
|
||||
"frameRate", base::IntToString(video_info.time_scale()) + "/" +
|
||||
base::IntToString(video_info.frame_duration())));
|
||||
}
|
||||
|
||||
if (video_info.has_playback_rate()) {
|
||||
SetStringAttribute("maxPlayoutRate",
|
||||
base::IntToString(video_info.playback_rate()));
|
||||
RCHECK(SetStringAttribute("maxPlayoutRate",
|
||||
base::IntToString(video_info.playback_rate())));
|
||||
// Since the trick play stream contains only key frames, there is no coding
|
||||
// dependency on the main stream. Simply set the codingDependency to false.
|
||||
// TODO(hmchen): propagate this attribute up to the AdaptationSet, since
|
||||
// all are set to false.
|
||||
SetStringAttribute("codingDependency", "false");
|
||||
RCHECK(SetStringAttribute("codingDependency", "false"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) {
|
||||
if (!AddAudioChannelInfo(audio_info))
|
||||
return false;
|
||||
|
||||
AddAudioSamplingRateInfo(audio_info);
|
||||
return true;
|
||||
return AddAudioChannelInfo(audio_info) &&
|
||||
AddAudioSamplingRateInfo(audio_info);
|
||||
}
|
||||
|
||||
bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
|
||||
|
@ -363,8 +376,7 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
|
|||
XmlNode base_url("BaseURL");
|
||||
base_url.SetContent(media_info.media_file_url());
|
||||
|
||||
if (!AddChild(base_url.PassScopedPtr()))
|
||||
return false;
|
||||
RCHECK(AddChild(std::move(base_url)));
|
||||
}
|
||||
|
||||
const bool need_segment_base =
|
||||
|
@ -374,31 +386,29 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
|
|||
if (need_segment_base) {
|
||||
XmlNode segment_base("SegmentBase");
|
||||
if (media_info.has_index_range()) {
|
||||
segment_base.SetStringAttribute("indexRange",
|
||||
RangeToString(media_info.index_range()));
|
||||
RCHECK(segment_base.SetStringAttribute(
|
||||
"indexRange", RangeToString(media_info.index_range())));
|
||||
}
|
||||
|
||||
if (media_info.has_reference_time_scale()) {
|
||||
segment_base.SetIntegerAttribute("timescale",
|
||||
media_info.reference_time_scale());
|
||||
RCHECK(segment_base.SetIntegerAttribute(
|
||||
"timescale", media_info.reference_time_scale()));
|
||||
}
|
||||
|
||||
if (media_info.has_presentation_time_offset()) {
|
||||
segment_base.SetIntegerAttribute("presentationTimeOffset",
|
||||
media_info.presentation_time_offset());
|
||||
RCHECK(segment_base.SetIntegerAttribute(
|
||||
"presentationTimeOffset", media_info.presentation_time_offset()));
|
||||
}
|
||||
|
||||
if (media_info.has_init_range()) {
|
||||
XmlNode initialization("Initialization");
|
||||
initialization.SetStringAttribute("range",
|
||||
RangeToString(media_info.init_range()));
|
||||
RCHECK(initialization.SetStringAttribute(
|
||||
"range", RangeToString(media_info.init_range())));
|
||||
|
||||
if (!segment_base.AddChild(initialization.PassScopedPtr()))
|
||||
return false;
|
||||
RCHECK(segment_base.AddChild(std::move(initialization)));
|
||||
}
|
||||
|
||||
if (!AddChild(segment_base.PassScopedPtr()))
|
||||
return false;
|
||||
RCHECK(AddChild(std::move(segment_base)));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -410,50 +420,48 @@ bool RepresentationXmlNode::AddLiveOnlyInfo(
|
|||
uint32_t start_number) {
|
||||
XmlNode segment_template("SegmentTemplate");
|
||||
if (media_info.has_reference_time_scale()) {
|
||||
segment_template.SetIntegerAttribute("timescale",
|
||||
media_info.reference_time_scale());
|
||||
RCHECK(segment_template.SetIntegerAttribute(
|
||||
"timescale", media_info.reference_time_scale()));
|
||||
}
|
||||
|
||||
if (media_info.has_presentation_time_offset()) {
|
||||
segment_template.SetIntegerAttribute("presentationTimeOffset",
|
||||
media_info.presentation_time_offset());
|
||||
RCHECK(segment_template.SetIntegerAttribute(
|
||||
"presentationTimeOffset", media_info.presentation_time_offset()));
|
||||
}
|
||||
|
||||
if (media_info.has_init_segment_url()) {
|
||||
segment_template.SetStringAttribute("initialization",
|
||||
media_info.init_segment_url());
|
||||
RCHECK(segment_template.SetStringAttribute("initialization",
|
||||
media_info.init_segment_url()));
|
||||
}
|
||||
|
||||
if (media_info.has_segment_template_url()) {
|
||||
segment_template.SetStringAttribute("media",
|
||||
media_info.segment_template_url());
|
||||
segment_template.SetIntegerAttribute("startNumber", start_number);
|
||||
RCHECK(segment_template.SetStringAttribute(
|
||||
"media", media_info.segment_template_url()));
|
||||
RCHECK(segment_template.SetIntegerAttribute("startNumber", start_number));
|
||||
}
|
||||
|
||||
if (!segment_infos.empty()) {
|
||||
// Don't use SegmentTimeline if all segments except the last one are of
|
||||
// the same duration.
|
||||
if (IsTimelineConstantDuration(segment_infos, start_number)) {
|
||||
segment_template.SetIntegerAttribute("duration",
|
||||
segment_infos.front().duration);
|
||||
RCHECK(segment_template.SetIntegerAttribute(
|
||||
"duration", segment_infos.front().duration));
|
||||
if (FLAGS_dash_add_last_segment_number_when_needed) {
|
||||
uint32_t last_segment_number = start_number - 1;
|
||||
for (const auto& segment_info_element : segment_infos)
|
||||
last_segment_number += segment_info_element.repeat + 1;
|
||||
|
||||
AddSupplementalProperty(
|
||||
RCHECK(AddSupplementalProperty(
|
||||
"http://dashif.org/guidelines/last-segment-number",
|
||||
std::to_string(last_segment_number));
|
||||
std::to_string(last_segment_number)));
|
||||
}
|
||||
} else {
|
||||
XmlNode segment_timeline("SegmentTimeline");
|
||||
if (!PopulateSegmentTimeline(segment_infos, &segment_timeline) ||
|
||||
!segment_template.AddChild(segment_timeline.PassScopedPtr())) {
|
||||
return false;
|
||||
}
|
||||
RCHECK(PopulateSegmentTimeline(segment_infos, &segment_timeline));
|
||||
RCHECK(segment_template.AddChild(std::move(segment_timeline)));
|
||||
}
|
||||
}
|
||||
return AddChild(segment_template.PassScopedPtr());
|
||||
return AddChild(std::move(segment_template));
|
||||
}
|
||||
|
||||
bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
|
||||
|
@ -549,10 +557,11 @@ bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
|
|||
|
||||
// MPD expects one number for sampling frequency, or if it is a range it should
|
||||
// be space separated.
|
||||
void RepresentationXmlNode::AddAudioSamplingRateInfo(
|
||||
bool RepresentationXmlNode::AddAudioSamplingRateInfo(
|
||||
const AudioInfo& audio_info) {
|
||||
if (audio_info.has_sampling_frequency())
|
||||
SetIntegerAttribute("audioSamplingRate", audio_info.sampling_frequency());
|
||||
return !audio_info.has_sampling_frequency() ||
|
||||
SetIntegerAttribute("audioSamplingRate",
|
||||
audio_info.sampling_frequency());
|
||||
}
|
||||
|
||||
} // namespace xml
|
||||
|
|
|
@ -10,21 +10,32 @@
|
|||
#ifndef MPD_BASE_XML_XML_NODE_H_
|
||||
#define MPD_BASE_XML_XML_NODE_H_
|
||||
|
||||
#include <libxml/tree.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "packager/base/compiler_specific.h"
|
||||
#include "packager/base/macros.h"
|
||||
#include "packager/mpd/base/content_protection_element.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||
|
||||
typedef struct _xmlNode xmlNode;
|
||||
|
||||
namespace shaka {
|
||||
|
||||
class MpdBuilder;
|
||||
struct SegmentInfo;
|
||||
|
||||
namespace xml {
|
||||
class XmlNode;
|
||||
} // namespace xml
|
||||
|
||||
// Defined in tests under mpd/test/xml_compare.h
|
||||
bool XmlEqual(const std::string& xml1, const xml::XmlNode& xml2);
|
||||
|
||||
namespace xml {
|
||||
|
||||
/// These classes are wrapper classes for XML elements for generating MPD.
|
||||
|
@ -34,37 +45,41 @@ class XmlNode {
|
|||
public:
|
||||
/// Make an XML element.
|
||||
/// @param name is the name of the element, which should not be NULL.
|
||||
explicit XmlNode(const char* name);
|
||||
explicit XmlNode(const std::string& name);
|
||||
XmlNode(XmlNode&&);
|
||||
virtual ~XmlNode();
|
||||
|
||||
XmlNode& operator=(XmlNode&&);
|
||||
|
||||
/// Add a child element to this element.
|
||||
/// @param child is a xmlNode to add as a child for this element. Ownership
|
||||
/// of the child node is transferred.
|
||||
/// @param child is an XmlNode to add as a child for this element.
|
||||
/// @return true on success, false otherwise.
|
||||
bool AddChild(scoped_xml_ptr<xmlNode> child);
|
||||
bool AddChild(XmlNode child) WARN_UNUSED_RESULT;
|
||||
|
||||
/// Adds Elements to this node using the Element struct.
|
||||
bool AddElements(const std::vector<Element>& elements);
|
||||
bool AddElements(const std::vector<Element>& elements) WARN_UNUSED_RESULT;
|
||||
|
||||
/// Set a string attribute.
|
||||
/// @param attribute_name The name (lhs) of the attribute.
|
||||
/// @param attribute The value (rhs) of the attribute.
|
||||
void SetStringAttribute(const char* attribute_name,
|
||||
const std::string& attribute);
|
||||
bool SetStringAttribute(const std::string& attribute_name,
|
||||
const std::string& attribute) WARN_UNUSED_RESULT;
|
||||
|
||||
/// Sets an integer attribute.
|
||||
/// @param attribute_name The name (lhs) of the attribute.
|
||||
/// @param number The value (rhs) of the attribute.
|
||||
void SetIntegerAttribute(const char* attribute_name, uint64_t number);
|
||||
bool SetIntegerAttribute(const std::string& attribute_name,
|
||||
uint64_t number) WARN_UNUSED_RESULT;
|
||||
|
||||
/// Set a floating point number attribute.
|
||||
/// @param attribute_name is the name of the attribute to set.
|
||||
/// @param number is the value (rhs) of the attribute.
|
||||
void SetFloatingPointAttribute(const char* attribute_name, double number);
|
||||
bool SetFloatingPointAttribute(const std::string& attribute_name,
|
||||
double number) WARN_UNUSED_RESULT;
|
||||
|
||||
/// Sets 'id=@a id' attribute.
|
||||
/// @param id is the ID for this element.
|
||||
void SetId(uint32_t id);
|
||||
bool SetId(uint32_t id) WARN_UNUSED_RESULT;
|
||||
|
||||
/// Set the contents of an XML element using a string.
|
||||
/// This cannot set child elements because <> will become < and &rt;
|
||||
|
@ -75,22 +90,28 @@ class XmlNode {
|
|||
void SetContent(const std::string& content);
|
||||
|
||||
/// @return namespaces used in the node and its descendents.
|
||||
std::set<std::string> ExtractReferencedNamespaces();
|
||||
std::set<std::string> ExtractReferencedNamespaces() const;
|
||||
|
||||
/// Transfer the ownership of the xmlNodePtr. After calling this method, the
|
||||
/// behavior of any methods, except the destructor, is undefined.
|
||||
/// @return The resource of this object.
|
||||
scoped_xml_ptr<xmlNode> PassScopedPtr();
|
||||
/// @param comment The body of a comment to add to the top of the XML.
|
||||
/// @return A string containing the XML.
|
||||
std::string ToString(const std::string& comment) const;
|
||||
|
||||
/// Release the xmlNodePtr of this object. After calling this method, the
|
||||
/// behavior of any methods, except the destructor, is undefined.
|
||||
xmlNodePtr Release();
|
||||
|
||||
/// @return Raw pointer to the element.
|
||||
xmlNodePtr GetRawPtr();
|
||||
/// Gets the attribute with the given name.
|
||||
/// @param name The name of the attribute to get.
|
||||
/// @param value [OUT] where to put the resulting value.
|
||||
/// @return True if the attribute exists, false if not.
|
||||
bool GetAttribute(const std::string& name, std::string* value) const;
|
||||
|
||||
private:
|
||||
scoped_xml_ptr<xmlNode> node_;
|
||||
friend bool shaka::XmlEqual(const std::string& xml1,
|
||||
const xml::XmlNode& xml2);
|
||||
xmlNode* GetRawPtr() const;
|
||||
|
||||
// Don't use xmlNode directly so we don't have to forward-declare a bunch of
|
||||
// libxml types to define the scoped_xml_ptr type. This allows us to only
|
||||
// include libxml headers in a few source files.
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(XmlNode);
|
||||
};
|
||||
|
@ -102,20 +123,21 @@ class RepresentationBaseXmlNode : public XmlNode {
|
|||
public:
|
||||
~RepresentationBaseXmlNode() override;
|
||||
bool AddContentProtectionElements(
|
||||
const std::list<ContentProtectionElement>& content_protection_elements);
|
||||
const std::list<ContentProtectionElement>& content_protection_elements)
|
||||
WARN_UNUSED_RESULT;
|
||||
|
||||
/// @param scheme_id_uri is content of the schemeIdUri attribute.
|
||||
/// @param value is the content of value attribute.
|
||||
void AddSupplementalProperty(const std::string& scheme_id_uri,
|
||||
const std::string& value);
|
||||
bool AddSupplementalProperty(const std::string& scheme_id_uri,
|
||||
const std::string& value) WARN_UNUSED_RESULT;
|
||||
|
||||
/// @param scheme_id_uri is content of the schemeIdUri attribute.
|
||||
/// @param value is the content of value attribute.
|
||||
void AddEssentialProperty(const std::string& scheme_id_uri,
|
||||
const std::string& value);
|
||||
bool AddEssentialProperty(const std::string& scheme_id_uri,
|
||||
const std::string& value) WARN_UNUSED_RESULT;
|
||||
|
||||
protected:
|
||||
explicit RepresentationBaseXmlNode(const char* name);
|
||||
explicit RepresentationBaseXmlNode(const std::string& name);
|
||||
|
||||
/// Add a Descriptor.
|
||||
/// @param descriptor_name is the name of the descriptor.
|
||||
|
@ -123,11 +145,12 @@ class RepresentationBaseXmlNode : public XmlNode {
|
|||
/// @param value is the content of value attribute.
|
||||
bool AddDescriptor(const std::string& descriptor_name,
|
||||
const std::string& scheme_id_uri,
|
||||
const std::string& value);
|
||||
const std::string& value) WARN_UNUSED_RESULT;
|
||||
|
||||
private:
|
||||
bool AddContentProtectionElement(
|
||||
const ContentProtectionElement& content_protection_element);
|
||||
const ContentProtectionElement& content_protection_element)
|
||||
WARN_UNUSED_RESULT;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RepresentationBaseXmlNode);
|
||||
};
|
||||
|
@ -140,13 +163,13 @@ class AdaptationSetXmlNode : public RepresentationBaseXmlNode {
|
|||
|
||||
/// @param scheme_id_uri is content of the schemeIdUri attribute.
|
||||
/// @param value is the content of value attribute.
|
||||
void AddAccessibilityElement(const std::string& scheme_id_uri,
|
||||
const std::string& value);
|
||||
bool AddAccessibilityElement(const std::string& scheme_id_uri,
|
||||
const std::string& value) WARN_UNUSED_RESULT;
|
||||
|
||||
/// @param scheme_id_uri is content of the schemeIdUri attribute.
|
||||
/// @param value is the content of value attribute.
|
||||
void AddRoleElement(const std::string& scheme_id_uri,
|
||||
const std::string& value);
|
||||
bool AddRoleElement(const std::string& scheme_id_uri,
|
||||
const std::string& value) WARN_UNUSED_RESULT;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(AdaptationSetXmlNode);
|
||||
|
@ -168,33 +191,35 @@ class RepresentationXmlNode : public RepresentationBaseXmlNode {
|
|||
bool AddVideoInfo(const MediaInfo::VideoInfo& video_info,
|
||||
bool set_width,
|
||||
bool set_height,
|
||||
bool set_frame_rate);
|
||||
bool set_frame_rate) WARN_UNUSED_RESULT;
|
||||
|
||||
/// Adds audio metadata to the MPD.
|
||||
/// @param audio_info constains the AudioInfos for a Representation.
|
||||
/// @return true if successfully set attributes and children elements (if
|
||||
/// applicable), false otherwise.
|
||||
bool AddAudioInfo(const MediaInfo::AudioInfo& audio_info);
|
||||
bool AddAudioInfo(const MediaInfo::AudioInfo& audio_info) WARN_UNUSED_RESULT;
|
||||
|
||||
/// Adds fields that are specific to VOD. This ignores @a media_info fields
|
||||
/// for Live.
|
||||
/// @param media_info is a MediaInfo with VOD information.
|
||||
/// @return true on success, false otherwise.
|
||||
bool AddVODOnlyInfo(const MediaInfo& media_info);
|
||||
bool AddVODOnlyInfo(const MediaInfo& media_info) WARN_UNUSED_RESULT;
|
||||
|
||||
/// @param segment_infos is a set of SegmentInfos. This method assumes that
|
||||
/// SegmentInfos are sorted by its start time.
|
||||
bool AddLiveOnlyInfo(const MediaInfo& media_info,
|
||||
const std::list<SegmentInfo>& segment_infos,
|
||||
uint32_t start_number);
|
||||
uint32_t start_number) WARN_UNUSED_RESULT;
|
||||
|
||||
private:
|
||||
// Add AudioChannelConfiguration element. Note that it is a required element
|
||||
// for audio Representations.
|
||||
bool AddAudioChannelInfo(const MediaInfo::AudioInfo& audio_info);
|
||||
bool AddAudioChannelInfo(const MediaInfo::AudioInfo& audio_info)
|
||||
WARN_UNUSED_RESULT;
|
||||
|
||||
// Add audioSamplingRate attribute to this element, if present.
|
||||
void AddAudioSamplingRateInfo(const MediaInfo::AudioInfo& audio_info);
|
||||
bool AddAudioSamplingRateInfo(const MediaInfo::AudioInfo& audio_info)
|
||||
WARN_UNUSED_RESULT;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RepresentationXmlNode);
|
||||
};
|
||||
|
|
|
@ -145,14 +145,14 @@ TEST(XmlNodeTest, ExtractReferencedNamespaces) {
|
|||
|
||||
XmlNode child("child1");
|
||||
child.SetContent("child1 content");
|
||||
child.AddChild(grand_child_with_namespace.PassScopedPtr());
|
||||
ASSERT_TRUE(child.AddChild(std::move(grand_child_with_namespace)));
|
||||
|
||||
XmlNode child_with_namespace("child_ns:child2");
|
||||
child_with_namespace.SetContent("child2 content");
|
||||
|
||||
XmlNode root("root");
|
||||
root.AddChild(child.PassScopedPtr());
|
||||
root.AddChild(child_with_namespace.PassScopedPtr());
|
||||
ASSERT_TRUE(root.AddChild(std::move(child)));
|
||||
ASSERT_TRUE(root.AddChild(std::move(child_with_namespace)));
|
||||
|
||||
EXPECT_THAT(root.ExtractReferencedNamespaces(),
|
||||
ElementsAre("child_ns", "grand_ns"));
|
||||
|
@ -160,13 +160,13 @@ TEST(XmlNodeTest, ExtractReferencedNamespaces) {
|
|||
|
||||
TEST(XmlNodeTest, ExtractReferencedNamespacesFromAttributes) {
|
||||
XmlNode child("child");
|
||||
child.SetStringAttribute("child_attribute_ns:attribute",
|
||||
"child attribute value");
|
||||
ASSERT_TRUE(child.SetStringAttribute("child_attribute_ns:attribute",
|
||||
"child attribute value"));
|
||||
|
||||
XmlNode root("root");
|
||||
root.AddChild(child.PassScopedPtr());
|
||||
root.SetStringAttribute("root_attribute_ns:attribute",
|
||||
"root attribute value");
|
||||
ASSERT_TRUE(root.AddChild(std::move(child)));
|
||||
ASSERT_TRUE(root.SetStringAttribute("root_attribute_ns:attribute",
|
||||
"root attribute value"));
|
||||
|
||||
EXPECT_THAT(root.ExtractReferencedNamespaces(),
|
||||
ElementsAre("child_attribute_ns", "root_attribute_ns"));
|
||||
|
@ -195,9 +195,9 @@ TEST(XmlNodeTest, AddContentProtectionElements) {
|
|||
content_protections.push_back(content_protection_clearkey);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
representation.AddContentProtectionElements(content_protections);
|
||||
ASSERT_TRUE(representation.AddContentProtectionElements(content_protections));
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
representation,
|
||||
XmlNodeEqual(
|
||||
"<Representation>\n"
|
||||
" <ContentProtection\n"
|
||||
|
@ -220,9 +220,9 @@ TEST(XmlNodeTest, AddEC3AudioInfo) {
|
|||
0xFFFFFFFF);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
representation.AddAudioInfo(audio_info);
|
||||
ASSERT_TRUE(representation.AddAudioInfo(audio_info));
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
representation,
|
||||
XmlNodeEqual(
|
||||
"<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
|
@ -240,16 +240,14 @@ TEST(XmlNodeTest, AddEC3AudioInfoMPEGScheme) {
|
|||
audio_info.mutable_codec_specific_data()->set_channel_mpeg_value(6);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
representation.AddAudioInfo(audio_info);
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
XmlNodeEqual(
|
||||
"<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
|
||||
" value=\"6\"/>\n"
|
||||
"</Representation>\n"));
|
||||
ASSERT_TRUE(representation.AddAudioInfo(audio_info));
|
||||
EXPECT_THAT(representation,
|
||||
XmlNodeEqual("<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
|
||||
" value=\"6\"/>\n"
|
||||
"</Representation>\n"));
|
||||
}
|
||||
|
||||
TEST(XmlNodeTest, AddEC3AudioInfoMPEGSchemeJOC) {
|
||||
|
@ -261,9 +259,9 @@ TEST(XmlNodeTest, AddEC3AudioInfoMPEGSchemeJOC) {
|
|||
audio_info.mutable_codec_specific_data()->set_ec3_joc_complexity(16);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
representation.AddAudioInfo(audio_info);
|
||||
ASSERT_TRUE(representation.AddAudioInfo(audio_info));
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
representation,
|
||||
XmlNodeEqual(
|
||||
"<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
|
@ -292,16 +290,16 @@ TEST(XmlNodeTest, AddAC4AudioInfo) {
|
|||
codec_data->set_ac4_cbi_flag(false);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
representation.AddAudioInfo(audio_info);
|
||||
ASSERT_TRUE(representation.AddAudioInfo(audio_info));
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
XmlNodeEqual(
|
||||
"<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"tag:dolby.com,2015:dash:audio_channel_configuration:2015\"\n"
|
||||
" value=\"0000C7\"/>\n"
|
||||
"</Representation>\n"));
|
||||
representation,
|
||||
XmlNodeEqual(
|
||||
"<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"tag:dolby.com,2015:dash:audio_channel_configuration:2015\"\n"
|
||||
" value=\"0000C7\"/>\n"
|
||||
"</Representation>\n"));
|
||||
}
|
||||
|
||||
TEST(XmlNodeTest, AddAC4AudioInfoMPEGScheme) {
|
||||
|
@ -315,16 +313,14 @@ TEST(XmlNodeTest, AddAC4AudioInfoMPEGScheme) {
|
|||
codec_data->set_ac4_cbi_flag(false);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
representation.AddAudioInfo(audio_info);
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
XmlNodeEqual(
|
||||
"<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
|
||||
" value=\"2\"/>\n"
|
||||
"</Representation>\n"));
|
||||
ASSERT_TRUE(representation.AddAudioInfo(audio_info));
|
||||
EXPECT_THAT(representation,
|
||||
XmlNodeEqual("<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
|
||||
" value=\"2\"/>\n"
|
||||
"</Representation>\n"));
|
||||
}
|
||||
|
||||
TEST(XmlNodeTest, AddAC4AudioInfoMPEGSchemeIMS) {
|
||||
|
@ -338,20 +334,19 @@ TEST(XmlNodeTest, AddAC4AudioInfoMPEGSchemeIMS) {
|
|||
codec_data->set_ac4_cbi_flag(false);
|
||||
|
||||
RepresentationXmlNode representation;
|
||||
representation.AddAudioInfo(audio_info);
|
||||
ASSERT_TRUE(representation.AddAudioInfo(audio_info));
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
XmlNodeEqual(
|
||||
"<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
|
||||
" value=\"2\"/>\n"
|
||||
" <SupplementalProperty\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"tag:dolby.com,2016:dash:virtualized_content:2016\"\n"
|
||||
" value=\"1\"/>\n"
|
||||
"</Representation>\n"));
|
||||
representation,
|
||||
XmlNodeEqual("<Representation audioSamplingRate=\"48000\">\n"
|
||||
" <AudioChannelConfiguration\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
|
||||
" value=\"2\"/>\n"
|
||||
" <SupplementalProperty\n"
|
||||
" schemeIdUri=\n"
|
||||
" \"tag:dolby.com,2016:dash:virtualized_content:2016\"\n"
|
||||
" value=\"1\"/>\n"
|
||||
"</Representation>\n"));
|
||||
}
|
||||
|
||||
class LiveSegmentTimelineTest : public ::testing::Test {
|
||||
|
@ -380,7 +375,7 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) {
|
|||
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
|
||||
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
representation,
|
||||
XmlNodeEqual("<Representation>"
|
||||
" <SegmentTemplate media=\"$Number$.m4s\" "
|
||||
" startNumber=\"1\" duration=\"100\"/>"
|
||||
|
@ -400,7 +395,7 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) {
|
|||
ASSERT_TRUE(
|
||||
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
|
||||
|
||||
EXPECT_THAT(representation.GetRawPtr(),
|
||||
EXPECT_THAT(representation,
|
||||
XmlNodeEqual(
|
||||
"<Representation>"
|
||||
" <SegmentTemplate media=\"$Number$.m4s\" startNumber=\"1\">"
|
||||
|
@ -425,7 +420,7 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) {
|
|||
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
|
||||
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
representation,
|
||||
XmlNodeEqual("<Representation>"
|
||||
" <SegmentTemplate media=\"$Number$.m4s\" "
|
||||
" startNumber=\"6\" duration=\"100\"/>"
|
||||
|
@ -452,7 +447,7 @@ TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) {
|
|||
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
|
||||
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
representation,
|
||||
XmlNodeEqual("<Representation>"
|
||||
" <SegmentTemplate media=\"$Number$.m4s\" "
|
||||
" startNumber=\"1\" duration=\"100\"/>"
|
||||
|
@ -478,7 +473,7 @@ TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) {
|
|||
ASSERT_TRUE(
|
||||
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
|
||||
|
||||
EXPECT_THAT(representation.GetRawPtr(),
|
||||
EXPECT_THAT(representation,
|
||||
XmlNodeEqual(
|
||||
"<Representation>"
|
||||
" <SegmentTemplate media=\"$Number$.m4s\" startNumber=\"1\">"
|
||||
|
@ -510,7 +505,7 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) {
|
|||
ASSERT_TRUE(
|
||||
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
|
||||
|
||||
EXPECT_THAT(representation.GetRawPtr(),
|
||||
EXPECT_THAT(representation,
|
||||
XmlNodeEqual(
|
||||
"<Representation>"
|
||||
" <SegmentTemplate media=\"$Number$.m4s\" startNumber=\"1\">"
|
||||
|
@ -538,7 +533,7 @@ TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) {
|
|||
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
|
||||
|
||||
EXPECT_THAT(
|
||||
representation.GetRawPtr(),
|
||||
representation,
|
||||
XmlNodeEqual("<Representation>"
|
||||
"<SupplementalProperty schemeIdUri=\"http://dashif.org/"
|
||||
"guidelines/last-segment-number\" value=\"10\"/>"
|
||||
|
|
|
@ -144,54 +144,41 @@ bool CompareNodes(xmlNodePtr node1, xmlNodePtr node2) {
|
|||
bool XmlEqual(const std::string& xml1, const std::string& xml2) {
|
||||
xml::scoped_xml_ptr<xmlDoc> xml1_doc(GetDocFromString(xml1));
|
||||
xml::scoped_xml_ptr<xmlDoc> xml2_doc(GetDocFromString(xml2));
|
||||
return XmlEqual(xml1_doc.get(), xml2_doc.get());
|
||||
}
|
||||
|
||||
bool XmlEqual(const std::string& xml1, xmlDocPtr xml2) {
|
||||
xml::scoped_xml_ptr<xmlDoc> xml1_doc(GetDocFromString(xml1));
|
||||
return XmlEqual(xml1_doc.get(), xml2);
|
||||
}
|
||||
|
||||
bool XmlEqual(xmlDocPtr xml1, xmlDocPtr xml2) {
|
||||
if (!xml1 || !xml2) {
|
||||
LOG(ERROR) << "xml1 and/or xml2 are not valid XML.";
|
||||
if (!xml1_doc || !xml2_doc) {
|
||||
LOG(ERROR) << "xml1/xml2 is not valid XML.";
|
||||
return false;
|
||||
}
|
||||
|
||||
xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1);
|
||||
xmlNodePtr xml2_root_element = xmlDocGetRootElement(xml2);
|
||||
xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1_doc.get());
|
||||
xmlNodePtr xml2_root_element = xmlDocGetRootElement(xml2_doc.get());
|
||||
if (!xml1_root_element || !xml2_root_element)
|
||||
return xml1_root_element == xml2_root_element;
|
||||
|
||||
return false;
|
||||
return CompareNodes(xml1_root_element, xml2_root_element);
|
||||
}
|
||||
|
||||
bool XmlEqual(const std::string& xml1, xmlNodePtr xml2) {
|
||||
bool XmlEqual(const std::string& xml1,
|
||||
const base::Optional<xml::XmlNode>& xml2) {
|
||||
return xml2 && XmlEqual(xml1, *xml2);
|
||||
}
|
||||
|
||||
bool XmlEqual(const std::string& xml1, const xml::XmlNode& xml2) {
|
||||
xml::scoped_xml_ptr<xmlDoc> xml1_doc(GetDocFromString(xml1));
|
||||
if (!xml1_doc) {
|
||||
LOG(ERROR) << "xml1 are not valid XML.";
|
||||
LOG(ERROR) << "xml1 is not valid XML.";
|
||||
return false;
|
||||
}
|
||||
xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1_doc.get());
|
||||
if (!xml1_root_element)
|
||||
return false;
|
||||
return CompareNodes(xml1_root_element, xml2);
|
||||
return CompareNodes(xml1_root_element, xml2.GetRawPtr());
|
||||
}
|
||||
|
||||
std::string XmlNodeToString(xmlNodePtr xml_node) {
|
||||
// Create an xmlDoc from xmlNodePtr. The node is copied so ownership does not
|
||||
// transfer.
|
||||
xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST ""));
|
||||
xmlDocSetRootElement(doc.get(), xmlCopyNode(xml_node, true));
|
||||
std::string XmlNodeToString(const base::Optional<xml::XmlNode>& xml_node) {
|
||||
return xml_node ? XmlNodeToString(*xml_node) : "$ERROR$";
|
||||
}
|
||||
|
||||
// Format the xmlDoc to string.
|
||||
static const int kNiceFormat = 1;
|
||||
int doc_str_size = 0;
|
||||
xmlChar* doc_str = nullptr;
|
||||
xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size, "UTF-8",
|
||||
kNiceFormat);
|
||||
std::string output(doc_str, doc_str + doc_str_size);
|
||||
xmlFree(doc_str);
|
||||
std::string XmlNodeToString(const xml::XmlNode& xml_node) {
|
||||
std::string output = xml_node.ToString(/* comment= */ "");
|
||||
|
||||
// Remove the first line from the formatted string:
|
||||
// <?xml version="" encoding="UTF-8"?>
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include "packager/base/optional.h"
|
||||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||
#include "packager/mpd/base/xml/xml_node.h"
|
||||
|
||||
namespace shaka {
|
||||
|
||||
|
@ -19,13 +21,13 @@ namespace shaka {
|
|||
/// @param xml2 is compared against @a xml1.
|
||||
/// @return true if @a xml1 and @a xml2 are equivalent, false otherwise.
|
||||
bool XmlEqual(const std::string& xml1, const std::string& xml2);
|
||||
bool XmlEqual(const std::string& xml1, xmlDocPtr xml2);
|
||||
bool XmlEqual(xmlDocPtr xml1, xmlDocPtr xml2);
|
||||
bool XmlEqual(const std::string& xml1, xmlNodePtr xml2);
|
||||
bool XmlEqual(const std::string& xml1, const xml::XmlNode& xml2);
|
||||
bool XmlEqual(const std::string& xml1,
|
||||
const base::Optional<xml::XmlNode>& xml2);
|
||||
|
||||
/// Get string representation of the xml node.
|
||||
/// Note that the ownership is not transferred.
|
||||
std::string XmlNodeToString(xmlNodePtr xml_node);
|
||||
std::string XmlNodeToString(const xml::XmlNode& xml_node);
|
||||
std::string XmlNodeToString(const base::Optional<xml::XmlNode>& xml_node);
|
||||
|
||||
/// Match an xmlNodePtr with an xml in string representation.
|
||||
MATCHER_P(XmlNodeEqual,
|
||||
|
@ -38,14 +40,16 @@ MATCHER_P(XmlNodeEqual,
|
|||
/// Match the attribute of an xmlNodePtr with expected value.
|
||||
/// Note that the ownership is not transferred.
|
||||
MATCHER_P2(AttributeEqual, attribute, expected_value, "") {
|
||||
xml::scoped_xml_ptr<xmlChar> attribute_xml_str(
|
||||
xmlGetProp(arg, BAD_CAST attribute));
|
||||
if (!attribute_xml_str) {
|
||||
if (!arg) {
|
||||
*result_listener << "returned error";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string actual_value;
|
||||
if (!arg->GetAttribute(attribute, &actual_value)) {
|
||||
*result_listener << "no attribute '" << attribute << "'";
|
||||
return false;
|
||||
}
|
||||
std::string actual_value =
|
||||
reinterpret_cast<const char*>(attribute_xml_str.get());
|
||||
*result_listener << actual_value;
|
||||
return expected_value == actual_value;
|
||||
}
|
||||
|
@ -53,9 +57,12 @@ MATCHER_P2(AttributeEqual, attribute, expected_value, "") {
|
|||
/// Check if the attribute is set in an xmlNodePtr.
|
||||
/// Note that the ownership is not transferred.
|
||||
MATCHER_P(AttributeSet, attribute, "") {
|
||||
xml::scoped_xml_ptr<xmlChar> attribute_xml_str(
|
||||
xmlGetProp(arg, BAD_CAST attribute));
|
||||
return attribute_xml_str != nullptr;
|
||||
if (!arg) {
|
||||
*result_listener << "returned error";
|
||||
return false;
|
||||
}
|
||||
std::string unused;
|
||||
return arg->GetAttribute(attribute, &unused);
|
||||
}
|
||||
} // namespace shaka
|
||||
|
||||
|
|
Loading…
Reference in New Issue