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:
Jacob Trimble 2020-11-09 16:32:58 -08:00
parent 9a34b9c3f3
commit 26334f2808
17 changed files with 678 additions and 648 deletions

View File

@ -237,41 +237,55 @@ void AdaptationSet::AddRole(Role role) {
// can be passed to Representation to avoid setting redundant attributes. For // can be passed to Representation to avoid setting redundant attributes. For
// example, if AdaptationSet@width is set, then Representation@width is // example, if AdaptationSet@width is set, then Representation@width is
// redundant and should not be set. // redundant and should not be set.
xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() { base::Optional<xml::XmlNode> AdaptationSet::GetXml() {
xml::AdaptationSetXmlNode adaptation_set; xml::AdaptationSetXmlNode adaptation_set;
bool suppress_representation_width = false; bool suppress_representation_width = false;
bool suppress_representation_height = false; bool suppress_representation_height = false;
bool suppress_representation_frame_rate = false; bool suppress_representation_frame_rate = false;
if (id_) if (id_ && !adaptation_set.SetId(id_.value()))
adaptation_set.SetId(id_.value()); return base::nullopt;
adaptation_set.SetStringAttribute("contentType", content_type_); if (!adaptation_set.SetStringAttribute("contentType", content_type_))
if (!language_.empty() && language_ != "und") { return base::nullopt;
adaptation_set.SetStringAttribute("lang", language_); 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. // Note that std::{set,map} are ordered, so the last element is the max value.
if (video_widths_.size() == 1) { if (video_widths_.size() == 1) {
suppress_representation_width = true; 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) { } 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) { if (video_heights_.size() == 1) {
suppress_representation_height = true; 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) { } 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) { if (video_frame_rates_.size() == 1) {
suppress_representation_frame_rate = true; suppress_representation_frame_rate = true;
adaptation_set.SetStringAttribute("frameRate", if (!adaptation_set.SetStringAttribute(
video_frame_rates_.begin()->second); "frameRate", video_frame_rates_.begin()->second)) {
return base::nullopt;
}
} else if (video_frame_rates_.size() > 1) { } else if (video_frame_rates_.size() > 1) {
adaptation_set.SetStringAttribute("maxFrameRate", if (!adaptation_set.SetStringAttribute(
video_frame_rates_.rbegin()->second); "maxFrameRate", video_frame_rates_.rbegin()->second)) {
return base::nullopt;
}
} }
// Note: must be checked before checking segments_aligned_ (below). So that // 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) { if (segments_aligned_ == kSegmentAlignmentTrue) {
adaptation_set.SetStringAttribute( if (!adaptation_set.SetStringAttribute(
mpd_options_.dash_profile == DashProfile::kOnDemand mpd_options_.dash_profile == DashProfile::kOnDemand
? "subsegmentAlignment" ? "subsegmentAlignment"
: "segmentAlignment", : "segmentAlignment",
"true"); "true")) {
return base::nullopt;
}
} }
if (picture_aspect_ratio_.size() == 1) if (picture_aspect_ratio_.size() == 1 &&
adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin()); !adaptation_set.SetStringAttribute("par",
*picture_aspect_ratio_.begin())) {
return base::nullopt;
}
if (!adaptation_set.AddContentProtectionElements( if (!adaptation_set.AddContentProtectionElements(
content_protection_elements_)) { content_protection_elements_)) {
return xml::scoped_xml_ptr<xmlNode>(); return base::nullopt;
} }
std::string trick_play_reference_ids; std::string trick_play_reference_ids;
@ -304,9 +323,10 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
CHECK(adaptation_set->has_id()); CHECK(adaptation_set->has_id());
trick_play_reference_ids += std::to_string(adaptation_set->id()); trick_play_reference_ids += std::to_string(adaptation_set->id());
} }
if (!trick_play_reference_ids.empty()) { if (!trick_play_reference_ids.empty() &&
adaptation_set.AddEssentialProperty( !adaptation_set.AddEssentialProperty(
"http://dashif.org/guidelines/trickmode", trick_play_reference_ids); "http://dashif.org/guidelines/trickmode", trick_play_reference_ids)) {
return base::nullopt;
} }
std::string switching_ids; std::string switching_ids;
@ -317,18 +337,25 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
CHECK(adaptation_set->has_id()); CHECK(adaptation_set->has_id());
switching_ids += std::to_string(adaptation_set->id()); switching_ids += std::to_string(adaptation_set->id());
} }
if (!switching_ids.empty()) { if (!switching_ids.empty() &&
adaptation_set.AddSupplementalProperty( !adaptation_set.AddSupplementalProperty(
"urn:mpeg:dash:adaptation-set-switching:2016", switching_ids); "urn:mpeg:dash:adaptation-set-switching:2016", switching_ids)) {
return base::nullopt;
} }
for (const AdaptationSet::Accessibility& accessibility : accessibilities_) { for (const AdaptationSet::Accessibility& accessibility : accessibilities_) {
adaptation_set.AddAccessibilityElement(accessibility.scheme, if (!adaptation_set.AddAccessibilityElement(accessibility.scheme,
accessibility.value); accessibility.value)) {
return base::nullopt;
}
} }
for (AdaptationSet::Role role : roles_) for (AdaptationSet::Role role : roles_) {
adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011", RoleToText(role)); if (!adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011",
RoleToText(role))) {
return base::nullopt;
}
}
for (const auto& representation_pair : representation_map_) { for (const auto& representation_pair : representation_map_) {
const auto& representation = representation_pair.second; const auto& representation = representation_pair.second;
@ -338,12 +365,12 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
representation->SuppressOnce(Representation::kSuppressHeight); representation->SuppressOnce(Representation::kSuppressHeight);
if (suppress_representation_frame_rate) if (suppress_representation_frame_rate)
representation->SuppressOnce(Representation::kSuppressFrameRate); representation->SuppressOnce(Representation::kSuppressFrameRate);
xml::scoped_xml_ptr<xmlNode> child(representation->GetXml()); auto child = representation->GetXml();
if (!child || !adaptation_set.AddChild(std::move(child))) if (!child || !adaptation_set.AddChild(std::move(*child)))
return xml::scoped_xml_ptr<xmlNode>(); return base::nullopt;
} }
return adaptation_set.PassScopedPtr(); return std::move(adaptation_set);
} }
void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) { void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) {

View File

@ -18,7 +18,7 @@
#include <vector> #include <vector>
#include "packager/base/optional.h" #include "packager/base/optional.h"
#include "packager/mpd/base/xml/scoped_xml_ptr.h" #include "packager/mpd/base/xml/xml_node.h"
namespace shaka { namespace shaka {
@ -28,10 +28,6 @@ class Representation;
struct ContentProtectionElement; struct ContentProtectionElement;
struct MpdOptions; struct MpdOptions;
namespace xml {
class XmlNode;
} // namespace xml
/// AdaptationSet class provides methods to add Representations and /// AdaptationSet class provides methods to add Representations and
/// <ContentProtection> elements to the AdaptationSet element. /// <ContentProtection> elements to the AdaptationSet element.
class AdaptationSet { class AdaptationSet {
@ -112,7 +108,7 @@ class AdaptationSet {
/// and ContentProtection elements. /// and ContentProtection elements.
/// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a /// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a
/// NULL scoped_xml_ptr. /// 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. /// 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 /// Use this if you are certain that the (sub)segments are alinged/unaligned

View File

@ -69,7 +69,7 @@ TEST_F(AdaptationSetTest, AddAdaptationSetSwitching) {
" schemeIdUri=\"urn:mpeg:dash:adaptation-set-switching:2016\" " " schemeIdUri=\"urn:mpeg:dash:adaptation-set-switching:2016\" "
" value=\"1,2,8\"/>" " value=\"1,2,8\"/>"
"</AdaptationSet>"; "</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 // 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); auto adaptation_set = CreateAdaptationSet(kNoLanguage);
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo));
EXPECT_THAT(adaptation_set->GetXml().get(), EXPECT_THAT(adaptation_set->GetXml(), AttributeEqual("contentType", "video"));
AttributeEqual("contentType", "video"));
} }
// Verify that content type is set correctly if audio info is present in // 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); auto adaptation_set = CreateAdaptationSet(kNoLanguage);
adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo));
EXPECT_THAT(adaptation_set->GetXml().get(), EXPECT_THAT(adaptation_set->GetXml(), AttributeEqual("contentType", "audio"));
AttributeEqual("contentType", "audio"));
} }
// Verify that content type is set correctly if text info is present in // 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"); auto adaptation_set = CreateAdaptationSet("en");
adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo));
EXPECT_THAT(adaptation_set->GetXml().get(), EXPECT_THAT(adaptation_set->GetXml(), AttributeEqual("contentType", "text"));
AttributeEqual("contentType", "text"));
} }
TEST_F(AdaptationSetTest, CopyRepresentation) { 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. // Verify that language passed to the constructor sets the @lang field is set.
TEST_F(AdaptationSetTest, CheckLanguageAttributeSet) { TEST_F(AdaptationSetTest, CheckLanguageAttributeSet) {
auto adaptation_set = CreateAdaptationSet("en"); 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) { TEST_F(AdaptationSetTest, CheckAdaptationSetId) {
auto adaptation_set = CreateAdaptationSet(kNoLanguage); auto adaptation_set = CreateAdaptationSet(kNoLanguage);
const uint32_t kAdaptationSetId = 42; const uint32_t kAdaptationSetId = 42;
adaptation_set->set_id(kAdaptationSetId); adaptation_set->set_id(kAdaptationSetId);
EXPECT_THAT(adaptation_set->GetXml().get(), EXPECT_THAT(adaptation_set->GetXml(),
AttributeEqual("id", std::to_string(kAdaptationSetId))); AttributeEqual("id", std::to_string(kAdaptationSetId)));
} }
@ -176,7 +173,7 @@ TEST_F(AdaptationSetTest, AddAccessibilityElement) {
" <Accessibility schemeIdUri=\"urn:tva:metadata:cs:AudioPurposeCS:2007\"" " <Accessibility schemeIdUri=\"urn:tva:metadata:cs:AudioPurposeCS:2007\""
" value=\"2\"/>\n" " value=\"2\"/>\n"
"</AdaptationSet>"; "</AdaptationSet>";
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput));
} }
// Verify AdaptationSet::AddRole() works for "main" role. // Verify AdaptationSet::AddRole() works for "main" role.
@ -190,7 +187,7 @@ TEST_F(AdaptationSetTest, AdaptationAddRoleElementMain) {
"<AdaptationSet contentType=\"\">\n" "<AdaptationSet contentType=\"\">\n"
" <Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"main\"/>\n" " <Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"main\"/>\n"
"</AdaptationSet>"; "</AdaptationSet>";
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput));
} }
// Add Role, ContentProtection, and Representation elements. Verify that // Add Role, ContentProtection, and Representation elements. Verify that
@ -211,7 +208,6 @@ TEST_F(AdaptationSetTest, CheckContentProtectionRoleRepresentationOrder) {
"container_type: 1\n"; "container_type: 1\n";
adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo));
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
const char kExpectedOutput[] = const char kExpectedOutput[] =
"<AdaptationSet contentType=\"audio\">\n" "<AdaptationSet contentType=\"audio\">\n"
" <ContentProtection schemeIdUri=\"any_scheme\"/>\n" " <ContentProtection schemeIdUri=\"any_scheme\"/>\n"
@ -224,7 +220,7 @@ TEST_F(AdaptationSetTest, CheckContentProtectionRoleRepresentationOrder) {
" value=\"2\"/>\n" " value=\"2\"/>\n"
" </Representation>\n" " </Representation>\n"
"</AdaptationSet>"; "</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 // Verify that if all video Representations in an AdaptationSet have the same
@ -254,9 +250,9 @@ TEST_F(AdaptationSetTest, AdapatationSetFrameRate) {
ASSERT_TRUE( ASSERT_TRUE(
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2))); adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2)));
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml()); auto adaptation_set_xml = adaptation_set->GetXml();
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("frameRate", "10/3")); EXPECT_THAT(adaptation_set_xml, AttributeEqual("frameRate", "10/3"));
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxFrameRate"))); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("maxFrameRate")));
} }
// Verify that if there are videos with different frame rates, the 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( ASSERT_TRUE(adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo15fps))); ConvertToMediaInfo(kVideoMediaInfo15fps)));
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml()); auto adaptation_set_xml = adaptation_set->GetXml();
EXPECT_THAT(adaptation_set_xml.get(), EXPECT_THAT(adaptation_set_xml, AttributeEqual("maxFrameRate", "3000/100"));
AttributeEqual("maxFrameRate", "3000/100")); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("frameRate")));
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate")));
} }
// Verify that (max)FrameRate can be set by calling // 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 // First, make sure that maxFrameRate nor frameRate are set because
// frame durations were not provided in the MediaInfo. // frame durations were not provided in the MediaInfo.
xml::scoped_xml_ptr<xmlNode> no_frame_rate(adaptation_set->GetXml()); auto no_frame_rate = adaptation_set->GetXml();
EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("maxFrameRate"))); EXPECT_THAT(no_frame_rate, Not(AttributeSet("maxFrameRate")));
EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("frameRate"))); EXPECT_THAT(no_frame_rate, Not(AttributeSet("frameRate")));
// Then set same frame duration for the representations. (Given that the // Then set same frame duration for the representations. (Given that the
// time scales match). // time scales match).
@ -341,9 +336,9 @@ TEST_F(AdaptationSetTest,
representation_480p->SetSampleDuration(kSameFrameDuration); representation_480p->SetSampleDuration(kSameFrameDuration);
representation_360p->SetSampleDuration(kSameFrameDuration); representation_360p->SetSampleDuration(kSameFrameDuration);
xml::scoped_xml_ptr<xmlNode> same_frame_rate(adaptation_set->GetXml()); auto same_frame_rate = adaptation_set->GetXml();
EXPECT_THAT(same_frame_rate.get(), Not(AttributeSet("maxFrameRate"))); EXPECT_THAT(same_frame_rate, Not(AttributeSet("maxFrameRate")));
EXPECT_THAT(same_frame_rate.get(), AttributeEqual("frameRate", "10/3")); EXPECT_THAT(same_frame_rate, AttributeEqual("frameRate", "10/3"));
// Then set 480p to be 5fps (10/2) so that maxFrameRate is set. // Then set 480p to be 5fps (10/2) so that maxFrameRate is set.
const uint32_t k5FPSFrameDuration = 2; const uint32_t k5FPSFrameDuration = 2;
@ -351,9 +346,9 @@ TEST_F(AdaptationSetTest,
"frame_duration_must_be_shorter_for_max_frame_rate"); "frame_duration_must_be_shorter_for_max_frame_rate");
representation_480p->SetSampleDuration(k5FPSFrameDuration); representation_480p->SetSampleDuration(k5FPSFrameDuration);
xml::scoped_xml_ptr<xmlNode> max_frame_rate(adaptation_set->GetXml()); auto max_frame_rate = adaptation_set->GetXml();
EXPECT_THAT(max_frame_rate.get(), AttributeEqual("maxFrameRate", "10/2")); EXPECT_THAT(max_frame_rate, AttributeEqual("maxFrameRate", "10/2"));
EXPECT_THAT(max_frame_rate.get(), Not(AttributeSet("frameRate"))); EXPECT_THAT(max_frame_rate, Not(AttributeSet("frameRate")));
} }
// Verify that if the picture aspect ratio of all the Representations are the // Verify that if the picture aspect ratio of all the Representations are the
@ -417,8 +412,8 @@ TEST_F(AdaptationSetTest, AdaptationSetParAllSame) {
ASSERT_TRUE( ASSERT_TRUE(
adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pVideoInfo))); adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pVideoInfo)));
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml()); auto adaptation_set_xml = adaptation_set->GetXml();
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("par", "16:9")); EXPECT_THAT(adaptation_set_xml, AttributeEqual("par", "16:9"));
} }
// Verify that adding Representations with different par will generate // Verify that adding Representations with different par will generate
@ -454,8 +449,8 @@ TEST_F(AdaptationSetTest, AdaptationSetParDifferent) {
ASSERT_TRUE( ASSERT_TRUE(
adaptation_set->AddRepresentation(ConvertToMediaInfo(k2by1VideoInfo))); adaptation_set->AddRepresentation(ConvertToMediaInfo(k2by1VideoInfo)));
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml()); auto adaptation_set_xml = adaptation_set->GetXml();
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("par"))); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("par")));
} }
// Verify that adding Representation without pixel_width and pixel_height will // Verify that adding Representation without pixel_width and pixel_height will
@ -475,8 +470,8 @@ TEST_F(AdaptationSetTest, AdaptationSetParUnknown) {
ASSERT_TRUE(adaptation_set->AddRepresentation( ASSERT_TRUE(adaptation_set->AddRepresentation(
ConvertToMediaInfo(kUknownPixelWidthAndHeight))); ConvertToMediaInfo(kUknownPixelWidthAndHeight)));
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml()); auto adaptation_set_xml = adaptation_set->GetXml();
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("par"))); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("par")));
} }
// Catch the case where it ends up wrong if integer division is used to check // 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( ASSERT_TRUE(
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2))); adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2)));
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml()); auto adaptation_set_xml = adaptation_set->GetXml();
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxFrameRate", "11/3")); EXPECT_THAT(adaptation_set_xml, AttributeEqual("maxFrameRate", "11/3"));
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate"))); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("frameRate")));
} }
// Attribute values that are common to all the children Representations should // Attribute values that are common to all the children Representations should
@ -571,35 +566,34 @@ TEST_F(AdaptationSetTest, BubbleUpAttributesToAdaptationSet) {
auto adaptation_set = CreateAdaptationSet(kNoLanguage); auto adaptation_set = CreateAdaptationSet(kNoLanguage);
ASSERT_TRUE(adaptation_set->AddRepresentation(ConvertToMediaInfo(k1080p))); ASSERT_TRUE(adaptation_set->AddRepresentation(ConvertToMediaInfo(k1080p)));
xml::scoped_xml_ptr<xmlNode> all_attributes_on_adaptation_set( auto all_attributes_on_adaptation_set = adaptation_set->GetXml();
adaptation_set->GetXml()); EXPECT_THAT(all_attributes_on_adaptation_set,
EXPECT_THAT(all_attributes_on_adaptation_set.get(),
AttributeEqual("width", "1920")); AttributeEqual("width", "1920"));
EXPECT_THAT(all_attributes_on_adaptation_set.get(), EXPECT_THAT(all_attributes_on_adaptation_set,
AttributeEqual("height", "1080")); AttributeEqual("height", "1080"));
EXPECT_THAT(all_attributes_on_adaptation_set.get(), EXPECT_THAT(all_attributes_on_adaptation_set,
AttributeEqual("frameRate", "30/1")); AttributeEqual("frameRate", "30/1"));
ASSERT_TRUE( ASSERT_TRUE(
adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentWidth))); adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentWidth)));
xml::scoped_xml_ptr<xmlNode> width_not_set(adaptation_set->GetXml()); auto width_not_set = adaptation_set->GetXml();
EXPECT_THAT(width_not_set.get(), Not(AttributeSet("width"))); EXPECT_THAT(width_not_set, Not(AttributeSet("width")));
EXPECT_THAT(width_not_set.get(), AttributeEqual("height", "1080")); EXPECT_THAT(width_not_set, AttributeEqual("height", "1080"));
EXPECT_THAT(width_not_set.get(), AttributeEqual("frameRate", "30/1")); EXPECT_THAT(width_not_set, AttributeEqual("frameRate", "30/1"));
ASSERT_TRUE( ASSERT_TRUE(
adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentHeight))); adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentHeight)));
xml::scoped_xml_ptr<xmlNode> width_height_not_set(adaptation_set->GetXml()); auto width_height_not_set = adaptation_set->GetXml();
EXPECT_THAT(width_height_not_set.get(), Not(AttributeSet("width"))); EXPECT_THAT(width_height_not_set, Not(AttributeSet("width")));
EXPECT_THAT(width_height_not_set.get(), Not(AttributeSet("height"))); EXPECT_THAT(width_height_not_set, Not(AttributeSet("height")));
EXPECT_THAT(width_height_not_set.get(), AttributeEqual("frameRate", "30/1")); EXPECT_THAT(width_height_not_set, AttributeEqual("frameRate", "30/1"));
ASSERT_TRUE(adaptation_set->AddRepresentation( ASSERT_TRUE(adaptation_set->AddRepresentation(
ConvertToMediaInfo(kDifferentFrameRate))); ConvertToMediaInfo(kDifferentFrameRate)));
xml::scoped_xml_ptr<xmlNode> no_common_attributes(adaptation_set->GetXml()); auto no_common_attributes = adaptation_set->GetXml();
EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("width"))); EXPECT_THAT(no_common_attributes, Not(AttributeSet("width")));
EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("height"))); EXPECT_THAT(no_common_attributes, Not(AttributeSet("height")));
EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("frameRate"))); EXPECT_THAT(no_common_attributes, Not(AttributeSet("frameRate")));
} }
TEST_F(AdaptationSetTest, GetRepresentations) { TEST_F(AdaptationSetTest, GetRepresentations) {
@ -694,21 +688,20 @@ TEST_F(OnDemandAdaptationSetTest, SubsegmentAlignment) {
adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize);
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml()); auto aligned = adaptation_set->GetXml();
EXPECT_THAT(aligned.get(), AttributeEqual("subsegmentAlignment", "true")); EXPECT_THAT(aligned, AttributeEqual("subsegmentAlignment", "true"));
// Unknown because 480p has an extra subsegments. // Unknown because 480p has an extra subsegments.
representation_480p->AddNewSegment(11, 20, kAnySize); representation_480p->AddNewSegment(11, 20, kAnySize);
xml::scoped_xml_ptr<xmlNode> alignment_unknown(adaptation_set->GetXml()); auto alignment_unknown = adaptation_set->GetXml();
EXPECT_THAT(alignment_unknown.get(), EXPECT_THAT(alignment_unknown, Not(AttributeSet("subsegmentAlignment")));
Not(AttributeSet("subsegmentAlignment")));
// Add segments that make them not aligned. // Add segments that make them not aligned.
representation_360p->AddNewSegment(10, 1, kAnySize); representation_360p->AddNewSegment(10, 1, kAnySize);
representation_360p->AddNewSegment(11, 19, kAnySize); representation_360p->AddNewSegment(11, 19, kAnySize);
xml::scoped_xml_ptr<xmlNode> unaligned(adaptation_set->GetXml()); auto unaligned = adaptation_set->GetXml();
EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment"))); EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment")));
} }
// Verify that subsegmentAlignment can be force set to true. // Verify that subsegmentAlignment can be force set to true.
@ -749,13 +742,13 @@ TEST_F(OnDemandAdaptationSetTest, ForceSetsubsegmentAlignment) {
const uint64_t kAnySize = 19834u; const uint64_t kAnySize = 19834u;
representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize); representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize);
representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize); representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize);
xml::scoped_xml_ptr<xmlNode> unaligned(adaptation_set->GetXml()); auto unaligned = adaptation_set->GetXml();
EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment"))); EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment")));
// Then force set the segment alignment attribute to true. // Then force set the segment alignment attribute to true.
adaptation_set->ForceSetSegmentAlignment(true); adaptation_set->ForceSetSegmentAlignment(true);
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml()); auto aligned = adaptation_set->GetXml();
EXPECT_THAT(aligned.get(), AttributeEqual("subsegmentAlignment", "true")); EXPECT_THAT(aligned, AttributeEqual("subsegmentAlignment", "true"));
} }
// Verify that segmentAlignment is set to true if all the Representations // 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_480p->AddNewSegment(kStartTime, kDuration, kAnySize);
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize);
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml()); auto aligned = adaptation_set->GetXml();
EXPECT_THAT(aligned.get(), AttributeEqual("segmentAlignment", "true")); EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true"));
// Add segments that make them not aligned. // Add segments that make them not aligned.
representation_480p->AddNewSegment(11, 20, kAnySize); representation_480p->AddNewSegment(11, 20, kAnySize);
representation_360p->AddNewSegment(10, 1, kAnySize); representation_360p->AddNewSegment(10, 1, kAnySize);
representation_360p->AddNewSegment(11, 19, kAnySize); representation_360p->AddNewSegment(11, 19, kAnySize);
xml::scoped_xml_ptr<xmlNode> unaligned(adaptation_set->GetXml()); auto unaligned = adaptation_set->GetXml();
EXPECT_THAT(unaligned.get(), Not(AttributeSet("segmentAlignment"))); EXPECT_THAT(unaligned, Not(AttributeSet("segmentAlignment")));
} }
// Verify that segmentAlignment is set to true if all the Representations // 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, representation_360p->AddNewSegment(kStartTime + kDuration, kDuration,
kAnySize); kAnySize);
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml()); auto aligned = adaptation_set->GetXml();
EXPECT_THAT(aligned.get(), AttributeEqual("segmentAlignment", "true")); EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true"));
} }
// Verify that the width and height attribute are set if all the video // Verify that the width and height attribute are set if all the video
@ -894,11 +887,11 @@ TEST_F(OnDemandAdaptationSetTest, AdapatationSetWidthAndHeight) {
ASSERT_TRUE( ASSERT_TRUE(
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2))); adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2)));
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml()); auto adaptation_set_xml = adaptation_set->GetXml();
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("width", "1280")); EXPECT_THAT(adaptation_set_xml, AttributeEqual("width", "1280"));
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("height", "720")); EXPECT_THAT(adaptation_set_xml, AttributeEqual("height", "720"));
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxWidth"))); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("maxWidth")));
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxHeight"))); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("maxHeight")));
} }
// Verify that the maxWidth and maxHeight attribute are set if there are // 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( ASSERT_TRUE(adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo720p))); ConvertToMediaInfo(kVideoMediaInfo720p)));
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml()); auto adaptation_set_xml = adaptation_set->GetXml();
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxWidth", "1920")); EXPECT_THAT(adaptation_set_xml, AttributeEqual("maxWidth", "1920"));
EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxHeight", "1080")); EXPECT_THAT(adaptation_set_xml, AttributeEqual("maxHeight", "1080"));
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("width"))); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("width")));
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("height"))); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("height")));
} }
// Verify that Representation::SetSampleDuration() works by checking that // Verify that Representation::SetSampleDuration() works by checking that
@ -955,12 +948,12 @@ TEST_F(AdaptationSetTest, SetSampleDuration) {
adaptation_set->AddRepresentation(video_media_info); adaptation_set->AddRepresentation(video_media_info);
EXPECT_TRUE(representation->Init()); EXPECT_TRUE(representation->Init());
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml()); auto adaptation_set_xml = adaptation_set->GetXml();
EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate"))); EXPECT_THAT(adaptation_set_xml, Not(AttributeSet("frameRate")));
representation->SetSampleDuration(2u); representation->SetSampleDuration(2u);
adaptation_set_xml = adaptation_set->GetXml(); 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 // Verify that AdaptationSet::AddContentProtection() and
@ -1000,7 +993,7 @@ TEST_F(AdaptationSetTest, AdaptationSetAddContentProtectionAndUpdate) {
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\"" " <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
" mimeType=\"video/mp4\"/>" " mimeType=\"video/mp4\"/>"
"</AdaptationSet>"; "</AdaptationSet>";
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput1)); EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput1));
adaptation_set->UpdateContentProtectionPssh( adaptation_set->UpdateContentProtectionPssh(
"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "new pssh value"); "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "new pssh value");
@ -1018,7 +1011,7 @@ TEST_F(AdaptationSetTest, AdaptationSetAddContentProtectionAndUpdate) {
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\"" " <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
" mimeType=\"video/mp4\"/>" " mimeType=\"video/mp4\"/>"
"</AdaptationSet>"; "</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> // 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\"" " <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
" mimeType=\"video/mp4\"/>" " mimeType=\"video/mp4\"/>"
"</AdaptationSet>"; "</AdaptationSet>";
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput1)); EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput1));
adaptation_set->UpdateContentProtectionPssh( adaptation_set->UpdateContentProtectionPssh(
"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "added pssh value"); "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "added pssh value");
@ -1073,7 +1066,7 @@ TEST_F(AdaptationSetTest, UpdateToRemovePsshElement) {
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\"" " <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
" mimeType=\"video/mp4\"/>" " mimeType=\"video/mp4\"/>"
"</AdaptationSet>"; "</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 // MPD schema has strict ordering. AudioChannelConfiguration must appear before
@ -1132,7 +1125,7 @@ TEST_F(OnDemandAdaptationSetTest,
adaptation_set->AddRepresentation(ConvertToMediaInfo(kTestMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(kTestMediaInfo));
ASSERT_TRUE(audio_representation); ASSERT_TRUE(audio_representation);
audio_representation->AddContentProtectionElement(content_protection); 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. // Verify that a text path works.
@ -1163,7 +1156,7 @@ TEST_F(OnDemandAdaptationSetTest, Text) {
adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo));
ASSERT_TRUE(text_representation); ASSERT_TRUE(text_representation);
EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); EXPECT_THAT(adaptation_set->GetXml(), XmlNodeEqual(kExpectedOutput));
} }
} // namespace shaka } // namespace shaka

View File

@ -16,11 +16,11 @@
#include "packager/base/synchronization/lock.h" #include "packager/base/synchronization/lock.h"
#include "packager/base/time/default_clock.h" #include "packager/base/time/default_clock.h"
#include "packager/base/time/time.h" #include "packager/base/time/time.h"
#include "packager/media/base/rcheck.h"
#include "packager/mpd/base/adaptation_set.h" #include "packager/mpd/base/adaptation_set.h"
#include "packager/mpd/base/mpd_utils.h" #include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/period.h" #include "packager/mpd/base/period.h"
#include "packager/mpd/base/representation.h" #include "packager/mpd/base/representation.h"
#include "packager/mpd/base/xml/xml_node.h"
#include "packager/version/version.h" #include "packager/version/version.h"
namespace shaka { namespace shaka {
@ -30,7 +30,7 @@ using xml::XmlNode;
namespace { namespace {
void AddMpdNameSpaceInfo(XmlNode* mpd) { bool AddMpdNameSpaceInfo(XmlNode* mpd) {
DCHECK(mpd); DCHECK(mpd);
const std::set<std::string> namespaces = mpd->ExtractReferencedNamespaces(); const std::set<std::string> namespaces = mpd->ExtractReferencedNamespaces();
@ -41,9 +41,9 @@ void AddMpdNameSpaceInfo(XmlNode* mpd) {
static const char kDashSchemaMpd2011[] = static const char kDashSchemaMpd2011[] =
"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd"; "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
mpd->SetStringAttribute("xmlns", kXmlNamespace); RCHECK(mpd->SetStringAttribute("xmlns", kXmlNamespace));
mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi); RCHECK(mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi));
mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011); RCHECK(mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011));
static const char kCencNamespace[] = "urn:mpeg:cenc:2013"; static const char kCencNamespace[] = "urn:mpeg:cenc:2013";
static const char kMarlinNamespace[] = static const char kMarlinNamespace[] =
@ -62,10 +62,11 @@ void AddMpdNameSpaceInfo(XmlNode* mpd) {
auto iter = uris.find(namespace_name); auto iter = uris.find(namespace_name);
CHECK(iter != uris.end()) << " unexpected namespace " << 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(), base::StringPrintf("xmlns:%s", namespace_name.c_str()).c_str(),
iter->second); iter->second));
} }
return true;
} }
bool Positive(double d) { bool Positive(double d) {
@ -88,10 +89,9 @@ std::string XmlDateTimeNowWithOffset(
time_exploded.second); time_exploded.second);
} }
void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) { bool SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
if (Positive(value)) { return !Positive(value) ||
mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value)); mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
}
} }
std::string MakePathRelative(const std::string& media_path, std::string MakePathRelative(const std::string& media_path,
@ -160,27 +160,21 @@ bool MpdBuilder::ToString(std::string* output) {
DCHECK(output); DCHECK(output);
static LibXmlInitializer lib_xml_initializer; static LibXmlInitializer lib_xml_initializer;
xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd()); auto mpd = GenerateMpd();
if (!doc) if (!mpd)
return false; return false;
static const int kNiceFormat = 1; std::string version = GetPackagerVersion();
int doc_str_size = 0; if (!version.empty()) {
xmlChar* doc_str = nullptr; version =
xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size, "UTF-8", base::StringPrintf("Generated with %s version %s",
kNiceFormat); GetPackagerProjectUrl().c_str(), version.c_str());
output->assign(doc_str, doc_str + doc_str_size); }
xmlFree(doc_str); *output = mpd->ToString(version);
// Cleanup, free the doc.
doc.reset();
return true; return true;
} }
xmlDocPtr MpdBuilder::GenerateMpd() { base::Optional<xml::XmlNode> MpdBuilder::GenerateMpd() {
// Setup nodes.
static const char kXmlVersion[] = "1.0";
xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
XmlNode mpd("MPD"); XmlNode mpd("MPD");
// Add baseurls to MPD. // Add baseurls to MPD.
@ -188,8 +182,8 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
XmlNode xml_base_url("BaseURL"); XmlNode xml_base_url("BaseURL");
xml_base_url.SetContent(base_url); xml_base_url.SetContent(base_url);
if (!mpd.AddChild(xml_base_url.PassScopedPtr())) if (!mpd.AddChild(std::move(xml_base_url)))
return nullptr; return base::nullopt;
} }
bool output_period_duration = false; bool output_period_duration = false;
@ -202,13 +196,13 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
} }
for (const auto& period : periods_) { for (const auto& period : periods_) {
xml::scoped_xml_ptr<xmlNode> period_node( auto period_node = period->GetXml(output_period_duration);
period->GetXml(output_period_duration)); if (!period_node || !mpd.AddChild(std::move(*period_node)))
if (!period_node || !mpd.AddChild(std::move(period_node))) return base::nullopt;
return nullptr;
} }
AddMpdNameSpaceInfo(&mpd); if (!AddMpdNameSpaceInfo(&mpd))
return base::nullopt;
static const char kOnDemandProfile[] = static const char kOnDemandProfile[] =
"urn:mpeg:dash:profile:isoff-on-demand:2011"; "urn:mpeg:dash:profile:isoff-on-demand:2011";
@ -216,10 +210,12 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
"urn:mpeg:dash:profile:isoff-live:2011"; "urn:mpeg:dash:profile:isoff-live:2011";
switch (mpd_options_.dash_profile) { switch (mpd_options_.dash_profile) {
case DashProfile::kOnDemand: case DashProfile::kOnDemand:
mpd.SetStringAttribute("profiles", kOnDemandProfile); if (!mpd.SetStringAttribute("profiles", kOnDemandProfile))
return base::nullopt;
break; break;
case DashProfile::kLive: case DashProfile::kLive:
mpd.SetStringAttribute("profiles", kLiveProfile); if (!mpd.SetStringAttribute("profiles", kLiveProfile))
return base::nullopt;
break; break;
default: default:
NOTREACHED() << "Unknown DASH profile: " NOTREACHED() << "Unknown DASH profile: "
@ -227,69 +223,61 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
break; break;
} }
AddCommonMpdInfo(&mpd); if (!AddCommonMpdInfo(&mpd))
return base::nullopt;
switch (mpd_options_.mpd_type) { switch (mpd_options_.mpd_type) {
case MpdType::kStatic: case MpdType::kStatic:
AddStaticMpdInfo(&mpd); if (!AddStaticMpdInfo(&mpd))
return base::nullopt;
break; break;
case MpdType::kDynamic: case MpdType::kDynamic:
AddDynamicMpdInfo(&mpd); if (!AddDynamicMpdInfo(&mpd))
return base::nullopt;
// Must be after Period element. // Must be after Period element.
AddUtcTiming(&mpd); if (!AddUtcTiming(&mpd))
return base::nullopt;
break; break;
default: default:
NOTREACHED() << "Unknown MPD type: " NOTREACHED() << "Unknown MPD type: "
<< static_cast<int>(mpd_options_.mpd_type); << static_cast<int>(mpd_options_.mpd_type);
break; break;
} }
return mpd;
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();
} }
void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) { bool MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
if (Positive(mpd_options_.mpd_params.min_buffer_time)) { if (Positive(mpd_options_.mpd_params.min_buffer_time)) {
mpd_node->SetStringAttribute( RCHECK(mpd_node->SetStringAttribute(
"minBufferTime", "minBufferTime",
SecondsToXmlDuration(mpd_options_.mpd_params.min_buffer_time)); SecondsToXmlDuration(mpd_options_.mpd_params.min_buffer_time)));
} else { } else {
LOG(ERROR) << "minBufferTime value not specified."; 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(mpd_node);
DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type); DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
static const char kStaticMpdType[] = "static"; static const char kStaticMpdType[] = "static";
mpd_node->SetStringAttribute("type", kStaticMpdType); return mpd_node->SetStringAttribute("type", kStaticMpdType) &&
mpd_node->SetStringAttribute("mediaPresentationDuration", mpd_node->SetStringAttribute(
SecondsToXmlDuration(GetStaticMpdDuration())); "mediaPresentationDuration",
SecondsToXmlDuration(GetStaticMpdDuration()));
} }
void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) { bool MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
DCHECK(mpd_node); DCHECK(mpd_node);
DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type); DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
static const char kDynamicMpdType[] = "dynamic"; static const char kDynamicMpdType[] = "dynamic";
mpd_node->SetStringAttribute("type", kDynamicMpdType); RCHECK(mpd_node->SetStringAttribute("type", kDynamicMpdType));
// No offset from NOW. // No offset from NOW.
mpd_node->SetStringAttribute("publishTime", RCHECK(mpd_node->SetStringAttribute(
XmlDateTimeNowWithOffset(0, clock_.get())); "publishTime", XmlDateTimeNowWithOffset(0, clock_.get())));
// 'availabilityStartTime' is required for dynamic profile. Calculate if // 'availabilityStartTime' is required for dynamic profile. Calculate if
// not already calculated. // not already calculated.
@ -304,36 +292,41 @@ void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
// TODO(tinskip). Propagate an error. // TODO(tinskip). Propagate an error.
} }
} }
if (!availability_start_time_.empty()) if (!availability_start_time_.empty()) {
mpd_node->SetStringAttribute("availabilityStartTime", RCHECK(mpd_node->SetStringAttribute("availabilityStartTime",
availability_start_time_); availability_start_time_));
}
if (Positive(mpd_options_.mpd_params.minimum_update_period)) { if (Positive(mpd_options_.mpd_params.minimum_update_period)) {
mpd_node->SetStringAttribute( RCHECK(mpd_node->SetStringAttribute(
"minimumUpdatePeriod", "minimumUpdatePeriod",
SecondsToXmlDuration(mpd_options_.mpd_params.minimum_update_period)); SecondsToXmlDuration(mpd_options_.mpd_params.minimum_update_period)));
} else { } else {
LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod " LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
"specified."; "specified.";
} }
SetIfPositive("timeShiftBufferDepth", return SetIfPositive("timeShiftBufferDepth",
mpd_options_.mpd_params.time_shift_buffer_depth, mpd_node); mpd_options_.mpd_params.time_shift_buffer_depth,
SetIfPositive("suggestedPresentationDelay", mpd_node) &&
mpd_options_.mpd_params.suggested_presentation_delay, 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(mpd_node);
DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type); DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
for (const MpdParams::UtcTiming& utc_timing : for (const MpdParams::UtcTiming& utc_timing :
mpd_options_.mpd_params.utc_timings) { mpd_options_.mpd_params.utc_timings) {
XmlNode utc_timing_node("UTCTiming"); XmlNode utc_timing_node("UTCTiming");
utc_timing_node.SetStringAttribute("schemeIdUri", utc_timing.scheme_id_uri); RCHECK(utc_timing_node.SetStringAttribute("schemeIdUri",
utc_timing_node.SetStringAttribute("value", utc_timing.value); utc_timing.scheme_id_uri));
mpd_node->AddChild(utc_timing_node.PassScopedPtr()); RCHECK(utc_timing_node.SetStringAttribute("value", utc_timing.value));
RCHECK(mpd_node->AddChild(std::move(utc_timing_node)));
} }
return true;
} }
float MpdBuilder::GetStaticMpdDuration() { float MpdBuilder::GetStaticMpdDuration() {

View File

@ -17,8 +17,11 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "packager/base/compiler_specific.h"
#include "packager/base/optional.h"
#include "packager/base/time/clock.h" #include "packager/base/time/clock.h"
#include "packager/mpd/base/mpd_options.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 // 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 // the MPD (XML) generation functions take care of assigning an ID to each
@ -29,10 +32,6 @@ class AdaptationSet;
class MediaInfo; class MediaInfo;
class Period; class Period;
namespace xml {
class XmlNode;
} // namespace xml
/// This class generates DASH MPDs (Media Presentation Descriptions). /// This class generates DASH MPDs (Media Presentation Descriptions).
class MpdBuilder { class MpdBuilder {
public: public:
@ -57,7 +56,7 @@ class MpdBuilder {
/// @param[out] output is an output string where the MPD gets written. /// @param[out] output is an output string where the MPD gets written.
/// @return true on success, false otherwise. /// @return true on success, false otherwise.
// TODO(kqyang): Handle file IO in this class as in HLS media_playlist? // 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 /// Adjusts the fields of MediaInfo so that paths are relative to the
/// specified MPD path. /// specified MPD path.
@ -86,21 +85,21 @@ class MpdBuilder {
// Returns the document pointer to the MPD. This must be freed by the caller // Returns the document pointer to the MPD. This must be freed by the caller
// using appropriate xmlDocPtr freeing function. // using appropriate xmlDocPtr freeing function.
// On failure, this returns NULL. // 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 MPD attributes common to all profiles. Uses non-zero |mpd_options_| to
// set attributes for the MPD. // 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 // Adds 'static' MPD attributes and elements to |mpd_node|. This assumes that
// the first child element is a Period element. // 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. // 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. // 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(); float GetStaticMpdDuration();
@ -110,7 +109,7 @@ class MpdBuilder {
// Gets the earliest, normalized segment timestamp. Returns true if // Gets the earliest, normalized segment timestamp. Returns true if
// successful, false otherwise. // successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds); bool GetEarliestTimestamp(double* timestamp_seconds) WARN_UNUSED_RESULT;
// Update Period durations and presentation timestamps. // Update Period durations and presentation timestamps.
void UpdatePeriodDurationAndPresentationTimestamp(); void UpdatePeriodDurationAndPresentationTimestamp();

View File

@ -72,7 +72,7 @@ TEST_F(MpdUtilsTest, ContentProtectionGeneral) {
" <Representation id='0' bandwidth='0' codecs='avc1'" " <Representation id='0' bandwidth='0' codecs='avc1'"
" mimeType='video/mp4'/>" " mimeType='video/mp4'/>"
"</AdaptationSet>"; "</AdaptationSet>";
EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput)); EXPECT_THAT(adaptation_set_.GetXml(), XmlNodeEqual(kExpectedOutput));
} }
TEST_F(MpdUtilsTest, ContentProtectionMarlin) { TEST_F(MpdUtilsTest, ContentProtectionMarlin) {
@ -114,7 +114,7 @@ TEST_F(MpdUtilsTest, ContentProtectionMarlin) {
" <Representation id='0' bandwidth='0' codecs='avc1'" " <Representation id='0' bandwidth='0' codecs='avc1'"
" mimeType='video/mp4'/>" " mimeType='video/mp4'/>"
"</AdaptationSet>"; "</AdaptationSet>";
EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput)); EXPECT_THAT(adaptation_set_.GetXml(), XmlNodeEqual(kExpectedOutput));
} }
TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCencMspr) { TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCencMspr) {
@ -154,22 +154,24 @@ TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCencMspr) {
ASSERT_TRUE(adaptation_set_.AddRepresentation(media_info)); ASSERT_TRUE(adaptation_set_.AddRepresentation(media_info));
const char kExpectedOutput[] = const char kExpectedOutput[] =
"<AdaptationSet contentType='video' width='1920'" "<AdaptationSet contentType='video' width='1920'"
" height='1080' frameRate='3000/100'>" " height='1080' frameRate='3000/100'>"
" <ContentProtection value='cenc'" " <ContentProtection value='cenc'"
" schemeIdUri='urn:mpeg:dash:mp4protection:2011'" " schemeIdUri='urn:mpeg:dash:mp4protection:2011'"
" cenc:default_KID='30313233-3435-3637-3839-3a3b3c3d3e3f'/>" " cenc:default_KID='30313233-3435-3637-3839-3a3b3c3d3e3f'/>"
" <ContentProtection value='MSPR 2.0'" " <ContentProtection value='MSPR 2.0'"
" schemeIdUri='urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95'>" " schemeIdUri='urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95'>"
" <cenc:pssh>" " <cenc:pssh>"
"AAAAOHBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAERIjNEVWZ3iJkAqrvM3e7/AAAABDAxMjM=" "AAAAOHBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAERIjNEVWZ3iJkAqrvM3e7/"
" </cenc:pssh>" "AAAABDAxMjM="
" <mspr:pro>MDEyMw==</mspr:pro>" " </cenc:pssh>"
" </ContentProtection>" " <mspr:pro>MDEyMw==</mspr:pro>"
" <Representation id='0' bandwidth='0' codecs='avc1' mimeType='video/mp4'/>" " </ContentProtection>"
" <Representation id='0' bandwidth='0' codecs='avc1' "
"mimeType='video/mp4'/>"
"</AdaptationSet>"; "</AdaptationSet>";
EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput)); EXPECT_THAT(adaptation_set_.GetXml(), XmlNodeEqual(kExpectedOutput));
} }
TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCenc) { TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCenc) {
@ -223,7 +225,7 @@ TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCenc) {
" <Representation id='0' bandwidth='0' codecs='avc1' mimeType='video/mp4'/>" " <Representation id='0' bandwidth='0' codecs='avc1' mimeType='video/mp4'/>"
"</AdaptationSet>"; "</AdaptationSet>";
EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput)); EXPECT_THAT(adaptation_set_.GetXml(), XmlNodeEqual(kExpectedOutput));
} }
} // namespace shaka } // namespace shaka

View File

@ -120,7 +120,7 @@ AdaptationSet* Period::GetOrCreateAdaptationSet(
return adaptation_set_ptr; 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( adaptation_sets_.sort(
[](const std::unique_ptr<AdaptationSet>& adaptation_set_a, [](const std::unique_ptr<AdaptationSet>& adaptation_set_a,
const std::unique_ptr<AdaptationSet>& adaptation_set_b) { 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"); xml::XmlNode period("Period");
// Required for 'dynamic' MPDs. // 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. // Iterate thru AdaptationSets and add them to one big Period element.
for (const auto& adaptation_set : adaptation_sets_) { for (const auto& adaptation_set : adaptation_sets_) {
xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml()); auto child = adaptation_set->GetXml();
if (!child || !period.AddChild(std::move(child))) if (!child || !period.AddChild(std::move(*child)))
return nullptr; return base::nullopt;
} }
if (output_period_duration) { if (output_period_duration) {
period.SetStringAttribute("duration", if (!period.SetStringAttribute("duration",
SecondsToXmlDuration(duration_seconds_)); SecondsToXmlDuration(duration_seconds_))) {
return base::nullopt;
}
} else if (mpd_options_.mpd_type == MpdType::kDynamic) { } else if (mpd_options_.mpd_type == MpdType::kDynamic) {
period.SetStringAttribute("start", if (!period.SetStringAttribute(
SecondsToXmlDuration(start_time_in_seconds_)); "start", SecondsToXmlDuration(start_time_in_seconds_))) {
return base::nullopt;
}
} }
return period.PassScopedPtr(); return period;
} }
const std::list<AdaptationSet*> Period::GetAdaptationSets() const { const std::list<AdaptationSet*> Period::GetAdaptationSets() const {

View File

@ -15,16 +15,12 @@
#include "packager/base/optional.h" #include "packager/base/optional.h"
#include "packager/mpd/base/adaptation_set.h" #include "packager/mpd/base/adaptation_set.h"
#include "packager/mpd/base/media_info.pb.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 { namespace shaka {
struct MpdOptions; struct MpdOptions;
namespace xml {
class XmlNode;
} // namespace xml
/// Period class maps to <Period> element and provides methods to add /// Period class maps to <Period> element and provides methods to add
/// AdaptationSets. /// AdaptationSets.
class Period { class Period {
@ -50,7 +46,7 @@ class Period {
/// Generates <Period> xml element with its child AdaptationSet elements. /// Generates <Period> xml element with its child AdaptationSet elements.
/// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a /// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a
/// NULL scoped_xml_ptr. /// 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. /// @return The list of AdaptationSets in this Period.
const std::list<AdaptationSet*> GetAdaptationSets() const; const std::list<AdaptationSet*> GetAdaptationSets() const;

View File

@ -137,7 +137,7 @@ TEST_F(PeriodTest, GetXml) {
// Representation::Init() is called. // Representation::Init() is called.
" <AdaptationSet contentType=\"\"/>" " <AdaptationSet contentType=\"\"/>"
"</Period>"; "</Period>";
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(), EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration),
XmlNodeEqual(kExpectedXml)); XmlNodeEqual(kExpectedXml));
} }
@ -169,7 +169,7 @@ TEST_F(PeriodTest, DynamicMpdGetXml) {
// Representation::Init() is called. // Representation::Init() is called.
" <AdaptationSet contentType=\"\"/>" " <AdaptationSet contentType=\"\"/>"
"</Period>"; "</Period>";
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(), EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration),
XmlNodeEqual(kExpectedXml)); XmlNodeEqual(kExpectedXml));
} }
@ -202,7 +202,7 @@ TEST_F(PeriodTest, SetDurationAndGetXml) {
// Representation::Init() is called. // Representation::Init() is called.
" <AdaptationSet contentType=\"\"/>" " <AdaptationSet contentType=\"\"/>"
"</Period>"; "</Period>";
EXPECT_THAT(testable_period_.GetXml(kOutputPeriodDuration).get(), EXPECT_THAT(testable_period_.GetXml(kOutputPeriodDuration),
XmlNodeEqual(kExpectedXml)); XmlNodeEqual(kExpectedXml));
const char kExpectedXmlSuppressDuration[] = const char kExpectedXmlSuppressDuration[] =
"<Period id=\"9\">" "<Period id=\"9\">"
@ -210,7 +210,7 @@ TEST_F(PeriodTest, SetDurationAndGetXml) {
// Representation::Init() is called. // Representation::Init() is called.
" <AdaptationSet contentType=\"\"/>" " <AdaptationSet contentType=\"\"/>"
"</Period>"; "</Period>";
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(), EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration),
XmlNodeEqual(kExpectedXmlSuppressDuration)); XmlNodeEqual(kExpectedXmlSuppressDuration));
} }
@ -551,7 +551,7 @@ TEST_F(PeriodTest, OrderedByAdaptationSetId) {
R"( <AdaptationSet id="1" contentType=""/>)" R"( <AdaptationSet id="1" contentType=""/>)"
R"( <AdaptationSet id="2" contentType=""/>)" R"( <AdaptationSet id="2" contentType=""/>)"
R"(</Period>)"; R"(</Period>)";
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(), EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration),
XmlNodeEqual(kExpectedXml)); XmlNodeEqual(kExpectedXml));
} }

View File

@ -218,10 +218,10 @@ const MediaInfo& Representation::GetMediaInfo() const {
// AddVideoInfo() (possibly adds FramePacking elements), AddAudioInfo() (Adds // AddVideoInfo() (possibly adds FramePacking elements), AddAudioInfo() (Adds
// AudioChannelConfig elements), AddContentProtectionElements*(), and // AudioChannelConfig elements), AddContentProtectionElements*(), and
// AddVODOnlyInfo() (Adds segment info). // AddVODOnlyInfo() (Adds segment info).
xml::scoped_xml_ptr<xmlNode> Representation::GetXml() { base::Optional<xml::XmlNode> Representation::GetXml() {
if (!HasRequiredMediaInfoFields()) { if (!HasRequiredMediaInfoFields()) {
LOG(ERROR) << "MediaInfo missing required fields."; LOG(ERROR) << "MediaInfo missing required fields.";
return xml::scoped_xml_ptr<xmlNode>(); return base::nullopt;
} }
const uint64_t bandwidth = media_info_.has_bandwidth() const uint64_t bandwidth = media_info_.has_bandwidth()
@ -232,11 +232,13 @@ xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
xml::RepresentationXmlNode representation; xml::RepresentationXmlNode representation;
// Mandatory fields for Representation. // Mandatory fields for Representation.
representation.SetId(id_); if (!representation.SetId(id_) ||
representation.SetIntegerAttribute("bandwidth", bandwidth); !representation.SetIntegerAttribute("bandwidth", bandwidth) ||
if (!codecs_.empty()) !(codecs_.empty() ||
representation.SetStringAttribute("codecs", codecs_); representation.SetStringAttribute("codecs", codecs_)) ||
representation.SetStringAttribute("mimeType", mime_type_); !representation.SetStringAttribute("mimeType", mime_type_)) {
return base::nullopt;
}
const bool has_video_info = media_info_.has_video_info(); const bool has_video_info = media_info_.has_video_info();
const bool has_audio_info = media_info_.has_audio_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_ & kSuppressHeight),
!(output_suppression_flags_ & kSuppressFrameRate))) { !(output_suppression_flags_ & kSuppressFrameRate))) {
LOG(ERROR) << "Failed to add video info to Representation XML."; LOG(ERROR) << "Failed to add video info to Representation XML.";
return xml::scoped_xml_ptr<xmlNode>(); return base::nullopt;
} }
if (has_audio_info && if (has_audio_info &&
!representation.AddAudioInfo(media_info_.audio_info())) { !representation.AddAudioInfo(media_info_.audio_info())) {
LOG(ERROR) << "Failed to add audio info to Representation XML."; LOG(ERROR) << "Failed to add audio info to Representation XML.";
return xml::scoped_xml_ptr<xmlNode>(); return base::nullopt;
} }
if (!representation.AddContentProtectionElements( if (!representation.AddContentProtectionElements(
content_protection_elements_)) { content_protection_elements_)) {
return xml::scoped_xml_ptr<xmlNode>(); return base::nullopt;
} }
if (HasVODOnlyFields(media_info_) && if (HasVODOnlyFields(media_info_) &&
!representation.AddVODOnlyInfo(media_info_)) { !representation.AddVODOnlyInfo(media_info_)) {
LOG(ERROR) << "Failed to add VOD info."; LOG(ERROR) << "Failed to add VOD info.";
return xml::scoped_xml_ptr<xmlNode>(); return base::nullopt;
} }
if (HasLiveOnlyFields(media_info_) && if (HasLiveOnlyFields(media_info_) &&
!representation.AddLiveOnlyInfo(media_info_, segment_infos_, !representation.AddLiveOnlyInfo(media_info_, segment_infos_,
start_number_)) { start_number_)) {
LOG(ERROR) << "Failed to add Live info."; 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 // TODO(rkuroiwa): It is likely that all representations have the exact same
// SegmentTemplate. Optimize and propagate the tag up to AdaptationSet level. // SegmentTemplate. Optimize and propagate the tag up to AdaptationSet level.
output_suppression_flags_ = 0; output_suppression_flags_ = 0;
return representation.PassScopedPtr(); return std::move(representation);
} }
void Representation::SuppressOnce(SuppressFlag flag) { void Representation::SuppressOnce(SuppressFlag flag) {

View File

@ -9,26 +9,22 @@
#ifndef PACKAGER_MPD_BASE_REPRESENTATION_H_ #ifndef PACKAGER_MPD_BASE_REPRESENTATION_H_
#define 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 <stdint.h>
#include <list> #include <list>
#include <memory> #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 { namespace shaka {
struct ContentProtectionElement; struct ContentProtectionElement;
struct MpdOptions; struct MpdOptions;
namespace xml {
class XmlNode;
class RepresentationXmlNode;
} // namespace xml
class RepresentationStateChangeListener { class RepresentationStateChangeListener {
public: public:
RepresentationStateChangeListener() {} RepresentationStateChangeListener() {}
@ -116,7 +112,7 @@ class Representation {
virtual const MediaInfo& GetMediaInfo() const; virtual const MediaInfo& GetMediaInfo() const;
/// @return Copy of <Representation>. /// @return Copy of <Representation>.
xml::scoped_xml_ptr<xmlNode> GetXml(); base::Optional<xml::XmlNode> GetXml();
/// By calling this methods, the next time GetXml() is /// By calling this methods, the next time GetXml() is
/// called, the corresponding attributes will not be set. /// called, the corresponding attributes will not be set.

View File

@ -169,7 +169,7 @@ TEST_F(RepresentationTest, CheckVideoInfoReflectedInXml) {
" codecs=\"avc1\" mimeType=\"video/mp4\" " " codecs=\"avc1\" mimeType=\"video/mp4\" "
" sar=\"1:1\" width=\"1280\" height=\"720\" " " sar=\"1:1\" width=\"1280\" height=\"720\" "
" frameRate=\"10/10\"/>"; " frameRate=\"10/10\"/>";
EXPECT_THAT(representation->GetXml().get(), XmlNodeEqual(kExpectedOutput)); EXPECT_THAT(representation->GetXml(), XmlNodeEqual(kExpectedOutput));
} }
TEST_F(RepresentationTest, CheckVideoInfoVp8CodecInMp4) { TEST_F(RepresentationTest, CheckVideoInfoVp8CodecInMp4) {
@ -188,7 +188,7 @@ TEST_F(RepresentationTest, CheckVideoInfoVp8CodecInMp4) {
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8), CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8),
kAnyRepresentationId, NoListener()); kAnyRepresentationId, NoListener());
ASSERT_TRUE(representation->Init()); ASSERT_TRUE(representation->Init());
EXPECT_THAT(representation->GetXml().get(), EXPECT_THAT(representation->GetXml(),
AttributeEqual("codecs", "vp08.00.00.08.01.01.00.00")); AttributeEqual("codecs", "vp08.00.00.08.01.01.00.00"));
} }
@ -210,7 +210,7 @@ TEST_F(RepresentationTest, CheckVideoInfoVp8CodecInWebm) {
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8), CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8),
kAnyRepresentationId, NoListener()); kAnyRepresentationId, NoListener());
ASSERT_TRUE(representation->Init()); ASSERT_TRUE(representation->Init());
EXPECT_THAT(representation->GetXml().get(), AttributeEqual("codecs", "vp8")); EXPECT_THAT(representation->GetXml(), AttributeEqual("codecs", "vp8"));
} }
TEST_F(RepresentationTest, CheckVideoInfoVp9CodecInWebm) { TEST_F(RepresentationTest, CheckVideoInfoVp9CodecInWebm) {
@ -229,7 +229,7 @@ TEST_F(RepresentationTest, CheckVideoInfoVp9CodecInWebm) {
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp9), CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp9),
kAnyRepresentationId, NoListener()); kAnyRepresentationId, NoListener());
ASSERT_TRUE(representation->Init()); ASSERT_TRUE(representation->Init());
EXPECT_THAT(representation->GetXml().get(), EXPECT_THAT(representation->GetXml(),
AttributeEqual("codecs", "vp09.00.00.08.01.01.00.00")); AttributeEqual("codecs", "vp09.00.00.08.01.01.00.00"));
} }
@ -251,7 +251,7 @@ TEST_F(RepresentationTest, CheckVideoInfoLegacyVp9CodecInWebm) {
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp9), CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp9),
kAnyRepresentationId, NoListener()); kAnyRepresentationId, NoListener());
ASSERT_TRUE(representation->Init()); ASSERT_TRUE(representation->Init());
EXPECT_THAT(representation->GetXml().get(), AttributeEqual("codecs", "vp9")); EXPECT_THAT(representation->GetXml(), AttributeEqual("codecs", "vp9"));
} }
// Make sure RepresentationStateChangeListener::OnNewSegmentForRepresentation() // Make sure RepresentationStateChangeListener::OnNewSegmentForRepresentation()
@ -325,7 +325,7 @@ TEST_F(RepresentationTest, TtmlXmlMimeType) {
CreateRepresentation(ConvertToMediaInfo(kTtmlXmlMediaInfo), CreateRepresentation(ConvertToMediaInfo(kTtmlXmlMediaInfo),
kAnyRepresentationId, NoListener()); kAnyRepresentationId, NoListener());
ASSERT_TRUE(representation->Init()); ASSERT_TRUE(representation->Init());
EXPECT_THAT(representation->GetXml().get(), EXPECT_THAT(representation->GetXml(),
AttributeEqual("mimeType", "application/ttml+xml")); AttributeEqual("mimeType", "application/ttml+xml"));
} }
@ -340,7 +340,7 @@ TEST_F(RepresentationTest, TtmlMp4MimeType) {
CreateRepresentation(ConvertToMediaInfo(kTtmlMp4MediaInfo), CreateRepresentation(ConvertToMediaInfo(kTtmlMp4MediaInfo),
kAnyRepresentationId, NoListener()); kAnyRepresentationId, NoListener());
ASSERT_TRUE(representation->Init()); ASSERT_TRUE(representation->Init());
EXPECT_THAT(representation->GetXml().get(), EXPECT_THAT(representation->GetXml(),
AttributeEqual("mimeType", "application/mp4")); AttributeEqual("mimeType", "application/mp4"));
} }
@ -354,8 +354,7 @@ TEST_F(RepresentationTest, WebVttMimeType) {
auto representation = CreateRepresentation( auto representation = CreateRepresentation(
ConvertToMediaInfo(kWebVttMediaInfo), kAnyRepresentationId, NoListener()); ConvertToMediaInfo(kWebVttMediaInfo), kAnyRepresentationId, NoListener());
ASSERT_TRUE(representation->Init()); ASSERT_TRUE(representation->Init());
EXPECT_THAT(representation->GetXml().get(), EXPECT_THAT(representation->GetXml(), AttributeEqual("mimeType", "text/vtt"));
AttributeEqual("mimeType", "text/vtt"));
} }
// Verify that Suppress*() methods work. // Verify that Suppress*() methods work.
@ -376,22 +375,22 @@ TEST_F(RepresentationTest, SuppressRepresentationAttributes) {
ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener());
representation->SuppressOnce(Representation::kSuppressWidth); representation->SuppressOnce(Representation::kSuppressWidth);
xml::scoped_xml_ptr<xmlNode> no_width(representation->GetXml()); auto no_width = representation->GetXml();
EXPECT_THAT(no_width.get(), Not(AttributeSet("width"))); EXPECT_THAT(no_width, Not(AttributeSet("width")));
EXPECT_THAT(no_width.get(), AttributeEqual("height", "480")); EXPECT_THAT(no_width, AttributeEqual("height", "480"));
EXPECT_THAT(no_width.get(), AttributeEqual("frameRate", "10/10")); EXPECT_THAT(no_width, AttributeEqual("frameRate", "10/10"));
representation->SuppressOnce(Representation::kSuppressHeight); representation->SuppressOnce(Representation::kSuppressHeight);
xml::scoped_xml_ptr<xmlNode> no_height(representation->GetXml()); auto no_height = representation->GetXml();
EXPECT_THAT(no_height.get(), Not(AttributeSet("height"))); EXPECT_THAT(no_height, Not(AttributeSet("height")));
EXPECT_THAT(no_height.get(), AttributeEqual("width", "720")); EXPECT_THAT(no_height, AttributeEqual("width", "720"));
EXPECT_THAT(no_height.get(), AttributeEqual("frameRate", "10/10")); EXPECT_THAT(no_height, AttributeEqual("frameRate", "10/10"));
representation->SuppressOnce(Representation::kSuppressFrameRate); representation->SuppressOnce(Representation::kSuppressFrameRate);
xml::scoped_xml_ptr<xmlNode> no_frame_rate(representation->GetXml()); auto no_frame_rate = representation->GetXml();
EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("frameRate"))); EXPECT_THAT(no_frame_rate, Not(AttributeSet("frameRate")));
EXPECT_THAT(no_frame_rate.get(), AttributeEqual("width", "720")); EXPECT_THAT(no_frame_rate, AttributeEqual("width", "720"));
EXPECT_THAT(no_frame_rate.get(), AttributeEqual("height", "480")); EXPECT_THAT(no_frame_rate, AttributeEqual("height", "480"));
} }
TEST_F(RepresentationTest, CheckRepresentationId) { TEST_F(RepresentationTest, CheckRepresentationId) {
@ -401,7 +400,7 @@ TEST_F(RepresentationTest, CheckRepresentationId) {
auto representation = auto representation =
CreateRepresentation(video_media_info, kRepresentationId, NoListener()); CreateRepresentation(video_media_info, kRepresentationId, NoListener());
EXPECT_TRUE(representation->Init()); EXPECT_TRUE(representation->Init());
EXPECT_THAT(representation->GetXml().get(), EXPECT_THAT(representation->GetXml(),
AttributeEqual("id", std::to_string(kRepresentationId))); AttributeEqual("id", std::to_string(kRepresentationId)));
} }
@ -508,7 +507,7 @@ TEST_F(SegmentTemplateTest, OneSegmentNormal) {
AddSegments(kStartTime, kDuration, kSize, 0); AddSegments(kStartTime, kDuration, kSize, 0);
expected_s_elements_ = "<S t=\"0\" d=\"10\"/>"; 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) { TEST_F(SegmentTemplateTest, RepresentationClone) {
@ -533,8 +532,7 @@ TEST_F(SegmentTemplateTest, RepresentationClone) {
" media=\"$Number$.mp4\" startNumber=\"2\">\n" " media=\"$Number$.mp4\" startNumber=\"2\">\n"
" </SegmentTemplate>\n" " </SegmentTemplate>\n"
"</Representation>\n"; "</Representation>\n";
EXPECT_THAT(cloned_representation->GetXml().get(), EXPECT_THAT(cloned_representation->GetXml(), XmlNodeEqual(kExpectedXml));
XmlNodeEqual(kExpectedXml));
} }
TEST_F(SegmentTemplateTest, PresentationTimeOffset) { TEST_F(SegmentTemplateTest, PresentationTimeOffset) {
@ -557,7 +555,7 @@ TEST_F(SegmentTemplateTest, PresentationTimeOffset) {
" </SegmentTimeline>\n" " </SegmentTimeline>\n"
" </SegmentTemplate>\n" " </SegmentTemplate>\n"
"</Representation>\n"; "</Representation>\n";
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml)); EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(kExpectedXml));
} }
TEST_F(SegmentTemplateTest, GetStartAndEndTimestamps) { TEST_F(SegmentTemplateTest, GetStartAndEndTimestamps) {
@ -597,7 +595,7 @@ TEST_F(SegmentTemplateTest, NormalRepeatedSegmentDuration) {
repeat = 0; repeat = 0;
AddSegments(start_time, duration, kSize, repeat); AddSegments(start_time, duration, kSize, repeat);
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml())); EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
} }
TEST_F(SegmentTemplateTest, RepeatedSegmentsFromNonZeroStartTime) { TEST_F(SegmentTemplateTest, RepeatedSegmentsFromNonZeroStartTime) {
@ -617,7 +615,7 @@ TEST_F(SegmentTemplateTest, RepeatedSegmentsFromNonZeroStartTime) {
repeat = 3; repeat = 3;
AddSegments(start_time, duration, kSize, repeat); AddSegments(start_time, duration, kSize, repeat);
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml())); EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(ExpectedXml()));
} }
// Segments not starting from 0. // Segments not starting from 0.
@ -629,7 +627,7 @@ TEST_F(SegmentTemplateTest, NonZeroStartTime) {
const int kRepeat = 1; const int kRepeat = 1;
AddSegments(kStartTime, kDuration, kSize, kRepeat); 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. // There is a gap in the segments, but still valid.
@ -643,7 +641,7 @@ TEST_F(SegmentTemplateTest, NonContiguousLiveInfo) {
const int64_t kStartTimeOffset = 100; const int64_t kStartTimeOffset = 100;
AddSegments(kDuration + kStartTimeOffset, kDuration, kSize, kRepeat); 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 // 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(kLaterStartTime, kDuration, kSize, kRepeat);
AddSegments(kEarlierStartTime, 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. // No segments should be overlapping.
@ -674,7 +672,7 @@ TEST_F(SegmentTemplateTest, OverlappingSegments) {
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat); AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
AddSegments(kOverlappingSegmentStartTime, 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 // 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(kEarlierStartTime, kDuration, kSize, kRepeat);
AddSegments(kOverlappingSegmentStartTime, 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 { class SegmentTimelineTestBase : public SegmentTemplateTest {
@ -782,7 +780,7 @@ TEST_P(ApproximateSegmentTimelineTest, SegmentDurationAdjusted) {
expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR, expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR,
kStartTime, kDurationSmaller); kStartTime, kDurationSmaller);
} }
EXPECT_THAT(representation_->GetXml().get(), EXPECT_THAT(representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_elements))); XmlNodeEqual(ExpectedXml(expected_s_elements)));
} }
@ -803,7 +801,7 @@ TEST_P(ApproximateSegmentTimelineTest,
expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR, expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR,
kStartTime, kDurationSmaller); kStartTime, kDurationSmaller);
} }
EXPECT_THAT(representation_->GetXml().get(), EXPECT_THAT(representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_elements))); XmlNodeEqual(ExpectedXml(expected_s_elements)));
} }
@ -835,7 +833,7 @@ TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations) {
kStartTime + kDurationSmaller + kDurationLarger, kStartTime + kDurationSmaller + kDurationLarger,
kDurationSmaller); kDurationSmaller);
} }
EXPECT_THAT(representation_->GetXml().get(), EXPECT_THAT(representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_elements))); XmlNodeEqual(ExpectedXml(expected_s_elements)));
} }
@ -861,7 +859,7 @@ TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations2) {
expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime, expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime,
kDurationLarger, kNumSegments - 1); kDurationLarger, kNumSegments - 1);
} }
EXPECT_THAT(representation_->GetXml().get(), EXPECT_THAT(representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_elements))); XmlNodeEqual(ExpectedXml(expected_s_elements)));
} }
@ -885,7 +883,7 @@ TEST_P(ApproximateSegmentTimelineTest, FillSmallGap) {
base::StringPrintf(kSElementTemplate, kStartTime + kDuration + kGap, base::StringPrintf(kSElementTemplate, kStartTime + kDuration + kGap,
kDuration, 1 /* repeat */); kDuration, 1 /* repeat */);
} }
EXPECT_THAT(representation_->GetXml().get(), EXPECT_THAT(representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_elements))); XmlNodeEqual(ExpectedXml(expected_s_elements)));
} }
@ -909,7 +907,7 @@ TEST_P(ApproximateSegmentTimelineTest, FillSmallOverlap) {
base::StringPrintf(kSElementTemplate, kStartTime + kDuration - kOverlap, base::StringPrintf(kSElementTemplate, kStartTime + kDuration - kOverlap,
kDuration, 1 /* repeat */); kDuration, 1 /* repeat */);
} }
EXPECT_THAT(representation_->GetXml().get(), EXPECT_THAT(representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_elements))); XmlNodeEqual(ExpectedXml(expected_s_elements)));
} }
@ -946,7 +944,7 @@ TEST_P(ApproximateSegmentTimelineTest, NoSampleDuration) {
" </SegmentTimeline>\n" " </SegmentTimeline>\n"
" </SegmentTemplate>\n" " </SegmentTemplate>\n"
"</Representation>\n"; "</Representation>\n";
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml)); EXPECT_THAT(representation_->GetXml(), XmlNodeEqual(kExpectedXml));
} }
INSTANTIATE_TEST_CASE_P(ApproximateSegmentTimelineTest, INSTANTIATE_TEST_CASE_P(ApproximateSegmentTimelineTest,
@ -996,7 +994,7 @@ TEST_P(TimeShiftBufferDepthTest, Normal) {
initial_start_time_ + kDuration * (kRepeat - kExpectedRepeatsLeft), initial_start_time_ + kDuration * (kRepeat - kExpectedRepeatsLeft),
kDuration, kExpectedRepeatsLeft); kDuration, kExpectedRepeatsLeft);
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber))); XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber)));
} }
@ -1020,7 +1018,7 @@ TEST_P(TimeShiftBufferDepthTest, TimeShiftBufferDepthShorterThanSegmentLength) {
const std::string expected_s_element = base::StringPrintf( const std::string expected_s_element = base::StringPrintf(
kSElementTemplate, initial_start_time_, kDuration, kRepeat); kSElementTemplate, initial_start_time_, kDuration, kRepeat);
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber))); XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber)));
} }
@ -1053,7 +1051,7 @@ TEST_P(TimeShiftBufferDepthTest, Generic) {
const int kExpectedRemovedSegments = kRepeat + 1; const int kExpectedRemovedSegments = kRepeat + 1;
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml(),
XmlNodeEqual(ExpectedXml( XmlNodeEqual(ExpectedXml(
expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments))); expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments)));
} }
@ -1095,7 +1093,7 @@ TEST_P(TimeShiftBufferDepthTest, MoreThanOneS) {
kTwoSecondDuration, kTwoSecondSegmentRepeat); kTwoSecondDuration, kTwoSecondSegmentRepeat);
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml(),
XmlNodeEqual(ExpectedXml( XmlNodeEqual(ExpectedXml(
expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments))); expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments)));
} }
@ -1136,7 +1134,7 @@ TEST_P(TimeShiftBufferDepthTest, UseLastSegmentInS) {
expected_s_element += expected_s_element +=
base::StringPrintf(kSElementTemplate, first_s_element_end_time, base::StringPrintf(kSElementTemplate, first_s_element_end_time,
kTwoSecondDuration, kTwoSecondSegmentRepeat); kTwoSecondDuration, kTwoSecondSegmentRepeat);
EXPECT_THAT(representation_->GetXml().get(), EXPECT_THAT(representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_element, 2))); XmlNodeEqual(ExpectedXml(expected_s_element, 2)));
} }
@ -1168,7 +1166,7 @@ TEST_P(TimeShiftBufferDepthTest, NormalGap) {
gap_s_element_start_time, kDuration); gap_s_element_start_time, kDuration);
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber))); XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber)));
} }
@ -1204,7 +1202,7 @@ TEST_P(TimeShiftBufferDepthTest, HugeGap) {
kSecondSElementRepeat); kSecondSElementRepeat);
const int kExpectedRemovedSegments = kRepeat; const int kExpectedRemovedSegments = kRepeat;
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml(),
XmlNodeEqual(ExpectedXml( XmlNodeEqual(ExpectedXml(
expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments))); expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments)));
} }
@ -1233,7 +1231,7 @@ TEST_P(TimeShiftBufferDepthTest, ManySegments) {
initial_start_time_ + kExpectedRemovedSegments * kDuration, kDuration, initial_start_time_ + kExpectedRemovedSegments * kDuration, kDuration,
kExpectedSegmentsRepeat); kExpectedSegmentsRepeat);
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml(),
XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber))); XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber)));
} }

View File

@ -7,6 +7,7 @@
#include "packager/mpd/base/xml/xml_node.h" #include "packager/mpd/base/xml/xml_node.h"
#include <gflags/gflags.h> #include <gflags/gflags.h>
#include <libxml/tree.h>
#include <limits> #include <limits>
#include <set> #include <set>
@ -15,9 +16,11 @@
#include "packager/base/macros.h" #include "packager/base/macros.h"
#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_number_conversions.h"
#include "packager/base/sys_byteorder.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/media_info.pb.h"
#include "packager/mpd/base/mpd_utils.h" #include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/segment_info.h" #include "packager/mpd/base/segment_info.h"
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
DEFINE_bool(segment_template_constant_duration, DEFINE_bool(segment_template_constant_duration,
false, false,
@ -77,12 +80,12 @@ bool PopulateSegmentTimeline(const std::list<SegmentInfo>& segment_infos,
XmlNode* segment_timeline) { XmlNode* segment_timeline) {
for (const SegmentInfo& segment_info : segment_infos) { for (const SegmentInfo& segment_info : segment_infos) {
XmlNode s_element("S"); XmlNode s_element("S");
s_element.SetIntegerAttribute("t", segment_info.start_time); RCHECK(s_element.SetIntegerAttribute("t", segment_info.start_time));
s_element.SetIntegerAttribute("d", segment_info.duration); RCHECK(s_element.SetIntegerAttribute("d", segment_info.duration));
if (segment_info.repeat > 0) 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; return true;
@ -118,22 +121,30 @@ void TraverseNodesAndCollectNamespaces(const xmlNode* node,
namespace xml { namespace xml {
XmlNode::XmlNode(const char* name) : node_(xmlNewNode(NULL, BAD_CAST name)) { class XmlNode::Impl {
DCHECK(name); public:
DCHECK(node_); 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() {} XmlNode::~XmlNode() {}
bool XmlNode::AddChild(scoped_xml_ptr<xmlNode> child) { XmlNode& XmlNode::operator=(XmlNode&&) = default;
DCHECK(node_);
DCHECK(child);
if (!xmlAddChild(node_.get(), child.get()))
return false;
// 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. // 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; return true;
} }
@ -141,12 +152,12 @@ bool XmlNode::AddElements(const std::vector<Element>& elements) {
for (size_t element_index = 0; element_index < elements.size(); for (size_t element_index = 0; element_index < elements.size();
++element_index) { ++element_index) {
const Element& child_element = elements[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 = for (std::map<std::string, std::string>::const_iterator attribute_it =
child_element.attributes.begin(); child_element.attributes.begin();
attribute_it != child_element.attributes.end(); ++attribute_it) { attribute_it != child_element.attributes.end(); ++attribute_it) {
child_node.SetStringAttribute(attribute_it->first.c_str(), RCHECK(child_node.SetStringAttribute(attribute_it->first,
attribute_it->second); attribute_it->second));
} }
// Note that somehow |SetContent| needs to be called before |AddElements| // 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); child_node.SetContent(child_element.content);
// Recursively set children for the child. // Recursively set children for the child.
if (!child_node.AddElements(child_element.subelements)) RCHECK(child_node.AddElements(child_element.subelements));
return false;
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 LOG(ERROR) << "Failed to set child " << child_element.name
<< " to parent element " << " to parent element "
<< reinterpret_cast<const char*>(node_->name); << reinterpret_cast<const char*>(impl_->node->name);
return false; 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. // 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; return true;
} }
void XmlNode::SetStringAttribute(const char* attribute_name, bool XmlNode::SetStringAttribute(const std::string& attribute_name,
const std::string& attribute) { const std::string& attribute) {
DCHECK(node_); DCHECK(impl_->node);
DCHECK(attribute_name); return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
xmlSetProp(node_.get(), BAD_CAST attribute_name, BAD_CAST attribute.c_str()); BAD_CAST attribute.c_str()) != nullptr;
} }
void XmlNode::SetIntegerAttribute(const char* attribute_name, uint64_t number) { bool XmlNode::SetIntegerAttribute(const std::string& attribute_name,
DCHECK(node_); uint64_t number) {
DCHECK(attribute_name); DCHECK(impl_->node);
xmlSetProp(node_.get(), return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
BAD_CAST attribute_name, BAD_CAST(base::Uint64ToString(number).c_str())) != nullptr;
BAD_CAST (base::Uint64ToString(number).c_str()));
} }
void XmlNode::SetFloatingPointAttribute(const char* attribute_name, bool XmlNode::SetFloatingPointAttribute(const std::string& attribute_name,
double number) { double number) {
DCHECK(node_); DCHECK(impl_->node);
DCHECK(attribute_name); return xmlSetProp(impl_->node.get(), BAD_CAST attribute_name.c_str(),
xmlSetProp(node_.get(), BAD_CAST attribute_name, BAD_CAST(base::DoubleToString(number).c_str())) != nullptr;
BAD_CAST(base::DoubleToString(number).c_str()));
} }
void XmlNode::SetId(uint32_t id) { bool XmlNode::SetId(uint32_t id) {
SetIntegerAttribute("id", id); return SetIntegerAttribute("id", id);
} }
void XmlNode::SetContent(const std::string& content) { void XmlNode::SetContent(const std::string& content) {
DCHECK(node_); DCHECK(impl_->node);
xmlNodeSetContent(node_.get(), BAD_CAST content.c_str()); 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; std::set<std::string> namespaces;
TraverseNodesAndCollectNamespaces(node_.get(), &namespaces); TraverseNodesAndCollectNamespaces(impl_->node.get(), &namespaces);
return namespaces; return namespaces;
} }
scoped_xml_ptr<xmlNode> XmlNode::PassScopedPtr() { std::string XmlNode::ToString(const std::string& comment) const {
DVLOG(2) << "Passing node_."; // Create an xmlDoc from xmlNodePtr. The node is copied so ownership does not
DCHECK(node_); // transfer.
return std::move(node_); 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() { bool XmlNode::GetAttribute(const std::string& name, std::string* value) const {
DVLOG(2) << "Releasing node_."; xml::scoped_xml_ptr<xmlChar> str(
DCHECK(node_); xmlGetProp(impl_->node.get(), BAD_CAST name.c_str()));
return node_.release(); if (!str)
return false;
*value = reinterpret_cast<const char*>(str.get());
return true;
} }
xmlNodePtr XmlNode::GetRawPtr() { xmlNode* XmlNode::GetRawPtr() const {
return node_.get(); return impl_->node.get();
} }
RepresentationBaseXmlNode::RepresentationBaseXmlNode(const char* name) RepresentationBaseXmlNode::RepresentationBaseXmlNode(const std::string& name)
: XmlNode(name) {} : XmlNode(name) {}
RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {} RepresentationBaseXmlNode::~RepresentationBaseXmlNode() {}
bool RepresentationBaseXmlNode::AddContentProtectionElements( bool RepresentationBaseXmlNode::AddContentProtectionElements(
const std::list<ContentProtectionElement>& content_protection_elements) { const std::list<ContentProtectionElement>& content_protection_elements) {
std::list<ContentProtectionElement>::const_iterator content_protection_it = for (const auto& elem : content_protection_elements) {
content_protection_elements.begin(); RCHECK(AddContentProtectionElement(elem));
for (; content_protection_it != content_protection_elements.end();
++content_protection_it) {
if (!AddContentProtectionElement(*content_protection_it))
return false;
} }
return true; return true;
} }
void RepresentationBaseXmlNode::AddSupplementalProperty( bool RepresentationBaseXmlNode::AddSupplementalProperty(
const std::string& scheme_id_uri, const std::string& scheme_id_uri,
const std::string& value) { 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& scheme_id_uri,
const std::string& value) { const std::string& value) {
AddDescriptor("EssentialProperty", scheme_id_uri, value); return AddDescriptor("EssentialProperty", scheme_id_uri, value);
} }
bool RepresentationBaseXmlNode::AddDescriptor( bool RepresentationBaseXmlNode::AddDescriptor(
const std::string& descriptor_name, const std::string& descriptor_name,
const std::string& scheme_id_uri, const std::string& scheme_id_uri,
const std::string& value) { const std::string& value) {
XmlNode descriptor(descriptor_name.c_str()); XmlNode descriptor(descriptor_name);
descriptor.SetStringAttribute("schemeIdUri", scheme_id_uri); RCHECK(descriptor.SetStringAttribute("schemeIdUri", scheme_id_uri));
if (!value.empty()) if (!value.empty())
descriptor.SetStringAttribute("value", value); RCHECK(descriptor.SetStringAttribute("value", value));
return AddChild(descriptor.PassScopedPtr()); return AddChild(std::move(descriptor));
} }
bool RepresentationBaseXmlNode::AddContentProtectionElement( bool RepresentationBaseXmlNode::AddContentProtectionElement(
@ -270,43 +295,34 @@ bool RepresentationBaseXmlNode::AddContentProtectionElement(
// @value is an optional attribute. // @value is an optional attribute.
if (!content_protection_element.value.empty()) { if (!content_protection_element.value.empty()) {
content_protection_node.SetStringAttribute( RCHECK(content_protection_node.SetStringAttribute(
"value", content_protection_element.value); "value", content_protection_element.value));
} }
content_protection_node.SetStringAttribute( RCHECK(content_protection_node.SetStringAttribute(
"schemeIdUri", content_protection_element.scheme_id_uri); "schemeIdUri", content_protection_element.scheme_id_uri));
typedef std::map<std::string, std::string> AttributesMapType; for (const auto& pair : content_protection_element.additional_attributes) {
const AttributesMapType& additional_attributes = RCHECK(content_protection_node.SetStringAttribute(pair.first, pair.second));
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);
} }
if (!content_protection_node.AddElements( RCHECK(content_protection_node.AddElements(
content_protection_element.subelements)) { content_protection_element.subelements));
return false; return AddChild(std::move(content_protection_node));
}
return AddChild(content_protection_node.PassScopedPtr());
} }
AdaptationSetXmlNode::AdaptationSetXmlNode() AdaptationSetXmlNode::AdaptationSetXmlNode()
: RepresentationBaseXmlNode("AdaptationSet") {} : RepresentationBaseXmlNode("AdaptationSet") {}
AdaptationSetXmlNode::~AdaptationSetXmlNode() {} AdaptationSetXmlNode::~AdaptationSetXmlNode() {}
void AdaptationSetXmlNode::AddAccessibilityElement( bool AdaptationSetXmlNode::AddAccessibilityElement(
const std::string& scheme_id_uri, const std::string& scheme_id_uri,
const std::string& value) { 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) { const std::string& value) {
AddDescriptor("Role", scheme_id_uri, value); return AddDescriptor("Role", scheme_id_uri, value);
} }
RepresentationXmlNode::RepresentationXmlNode() RepresentationXmlNode::RepresentationXmlNode()
@ -323,39 +339,36 @@ bool RepresentationXmlNode::AddVideoInfo(const VideoInfo& video_info,
} }
if (video_info.has_pixel_width() && video_info.has_pixel_height()) { if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
SetStringAttribute("sar", base::IntToString(video_info.pixel_width()) + RCHECK(SetStringAttribute(
":" + "sar", base::IntToString(video_info.pixel_width()) + ":" +
base::IntToString(video_info.pixel_height())); base::IntToString(video_info.pixel_height())));
} }
if (set_width) if (set_width)
SetIntegerAttribute("width", video_info.width()); RCHECK(SetIntegerAttribute("width", video_info.width()));
if (set_height) if (set_height)
SetIntegerAttribute("height", video_info.height()); RCHECK(SetIntegerAttribute("height", video_info.height()));
if (set_frame_rate) { if (set_frame_rate) {
SetStringAttribute("frameRate", RCHECK(SetStringAttribute(
base::IntToString(video_info.time_scale()) + "/" + "frameRate", base::IntToString(video_info.time_scale()) + "/" +
base::IntToString(video_info.frame_duration())); base::IntToString(video_info.frame_duration())));
} }
if (video_info.has_playback_rate()) { if (video_info.has_playback_rate()) {
SetStringAttribute("maxPlayoutRate", RCHECK(SetStringAttribute("maxPlayoutRate",
base::IntToString(video_info.playback_rate())); base::IntToString(video_info.playback_rate())));
// Since the trick play stream contains only key frames, there is no coding // Since the trick play stream contains only key frames, there is no coding
// dependency on the main stream. Simply set the codingDependency to false. // dependency on the main stream. Simply set the codingDependency to false.
// TODO(hmchen): propagate this attribute up to the AdaptationSet, since // TODO(hmchen): propagate this attribute up to the AdaptationSet, since
// all are set to false. // all are set to false.
SetStringAttribute("codingDependency", "false"); RCHECK(SetStringAttribute("codingDependency", "false"));
} }
return true; return true;
} }
bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) { bool RepresentationXmlNode::AddAudioInfo(const AudioInfo& audio_info) {
if (!AddAudioChannelInfo(audio_info)) return AddAudioChannelInfo(audio_info) &&
return false; AddAudioSamplingRateInfo(audio_info);
AddAudioSamplingRateInfo(audio_info);
return true;
} }
bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) { bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
@ -363,8 +376,7 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
XmlNode base_url("BaseURL"); XmlNode base_url("BaseURL");
base_url.SetContent(media_info.media_file_url()); base_url.SetContent(media_info.media_file_url());
if (!AddChild(base_url.PassScopedPtr())) RCHECK(AddChild(std::move(base_url)));
return false;
} }
const bool need_segment_base = const bool need_segment_base =
@ -374,31 +386,29 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
if (need_segment_base) { if (need_segment_base) {
XmlNode segment_base("SegmentBase"); XmlNode segment_base("SegmentBase");
if (media_info.has_index_range()) { if (media_info.has_index_range()) {
segment_base.SetStringAttribute("indexRange", RCHECK(segment_base.SetStringAttribute(
RangeToString(media_info.index_range())); "indexRange", RangeToString(media_info.index_range())));
} }
if (media_info.has_reference_time_scale()) { if (media_info.has_reference_time_scale()) {
segment_base.SetIntegerAttribute("timescale", RCHECK(segment_base.SetIntegerAttribute(
media_info.reference_time_scale()); "timescale", media_info.reference_time_scale()));
} }
if (media_info.has_presentation_time_offset()) { if (media_info.has_presentation_time_offset()) {
segment_base.SetIntegerAttribute("presentationTimeOffset", RCHECK(segment_base.SetIntegerAttribute(
media_info.presentation_time_offset()); "presentationTimeOffset", media_info.presentation_time_offset()));
} }
if (media_info.has_init_range()) { if (media_info.has_init_range()) {
XmlNode initialization("Initialization"); XmlNode initialization("Initialization");
initialization.SetStringAttribute("range", RCHECK(initialization.SetStringAttribute(
RangeToString(media_info.init_range())); "range", RangeToString(media_info.init_range())));
if (!segment_base.AddChild(initialization.PassScopedPtr())) RCHECK(segment_base.AddChild(std::move(initialization)));
return false;
} }
if (!AddChild(segment_base.PassScopedPtr())) RCHECK(AddChild(std::move(segment_base)));
return false;
} }
return true; return true;
@ -410,50 +420,48 @@ bool RepresentationXmlNode::AddLiveOnlyInfo(
uint32_t start_number) { uint32_t start_number) {
XmlNode segment_template("SegmentTemplate"); XmlNode segment_template("SegmentTemplate");
if (media_info.has_reference_time_scale()) { if (media_info.has_reference_time_scale()) {
segment_template.SetIntegerAttribute("timescale", RCHECK(segment_template.SetIntegerAttribute(
media_info.reference_time_scale()); "timescale", media_info.reference_time_scale()));
} }
if (media_info.has_presentation_time_offset()) { if (media_info.has_presentation_time_offset()) {
segment_template.SetIntegerAttribute("presentationTimeOffset", RCHECK(segment_template.SetIntegerAttribute(
media_info.presentation_time_offset()); "presentationTimeOffset", media_info.presentation_time_offset()));
} }
if (media_info.has_init_segment_url()) { if (media_info.has_init_segment_url()) {
segment_template.SetStringAttribute("initialization", RCHECK(segment_template.SetStringAttribute("initialization",
media_info.init_segment_url()); media_info.init_segment_url()));
} }
if (media_info.has_segment_template_url()) { if (media_info.has_segment_template_url()) {
segment_template.SetStringAttribute("media", RCHECK(segment_template.SetStringAttribute(
media_info.segment_template_url()); "media", media_info.segment_template_url()));
segment_template.SetIntegerAttribute("startNumber", start_number); RCHECK(segment_template.SetIntegerAttribute("startNumber", start_number));
} }
if (!segment_infos.empty()) { if (!segment_infos.empty()) {
// Don't use SegmentTimeline if all segments except the last one are of // Don't use SegmentTimeline if all segments except the last one are of
// the same duration. // the same duration.
if (IsTimelineConstantDuration(segment_infos, start_number)) { if (IsTimelineConstantDuration(segment_infos, start_number)) {
segment_template.SetIntegerAttribute("duration", RCHECK(segment_template.SetIntegerAttribute(
segment_infos.front().duration); "duration", segment_infos.front().duration));
if (FLAGS_dash_add_last_segment_number_when_needed) { if (FLAGS_dash_add_last_segment_number_when_needed) {
uint32_t last_segment_number = start_number - 1; uint32_t last_segment_number = start_number - 1;
for (const auto& segment_info_element : segment_infos) for (const auto& segment_info_element : segment_infos)
last_segment_number += segment_info_element.repeat + 1; last_segment_number += segment_info_element.repeat + 1;
AddSupplementalProperty( RCHECK(AddSupplementalProperty(
"http://dashif.org/guidelines/last-segment-number", "http://dashif.org/guidelines/last-segment-number",
std::to_string(last_segment_number)); std::to_string(last_segment_number)));
} }
} else { } else {
XmlNode segment_timeline("SegmentTimeline"); XmlNode segment_timeline("SegmentTimeline");
if (!PopulateSegmentTimeline(segment_infos, &segment_timeline) || RCHECK(PopulateSegmentTimeline(segment_infos, &segment_timeline));
!segment_template.AddChild(segment_timeline.PassScopedPtr())) { RCHECK(segment_template.AddChild(std::move(segment_timeline)));
return false;
}
} }
} }
return AddChild(segment_template.PassScopedPtr()); return AddChild(std::move(segment_template));
} }
bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) { 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 // MPD expects one number for sampling frequency, or if it is a range it should
// be space separated. // be space separated.
void RepresentationXmlNode::AddAudioSamplingRateInfo( bool RepresentationXmlNode::AddAudioSamplingRateInfo(
const AudioInfo& audio_info) { const AudioInfo& audio_info) {
if (audio_info.has_sampling_frequency()) return !audio_info.has_sampling_frequency() ||
SetIntegerAttribute("audioSamplingRate", audio_info.sampling_frequency()); SetIntegerAttribute("audioSamplingRate",
audio_info.sampling_frequency());
} }
} // namespace xml } // namespace xml

View File

@ -10,21 +10,32 @@
#ifndef MPD_BASE_XML_XML_NODE_H_ #ifndef MPD_BASE_XML_XML_NODE_H_
#define MPD_BASE_XML_XML_NODE_H_ #define MPD_BASE_XML_XML_NODE_H_
#include <libxml/tree.h>
#include <stdint.h> #include <stdint.h>
#include <list> #include <list>
#include <set> #include <set>
#include <string>
#include <vector>
#include "packager/base/compiler_specific.h"
#include "packager/base/macros.h" #include "packager/base/macros.h"
#include "packager/mpd/base/content_protection_element.h" #include "packager/mpd/base/content_protection_element.h"
#include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
typedef struct _xmlNode xmlNode;
namespace shaka { namespace shaka {
class MpdBuilder;
struct SegmentInfo; 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 { namespace xml {
/// These classes are wrapper classes for XML elements for generating MPD. /// These classes are wrapper classes for XML elements for generating MPD.
@ -34,37 +45,41 @@ class XmlNode {
public: public:
/// Make an XML element. /// Make an XML element.
/// @param name is the name of the element, which should not be NULL. /// @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(); virtual ~XmlNode();
XmlNode& operator=(XmlNode&&);
/// Add a child element to this element. /// Add a child element to this element.
/// @param child is a xmlNode to add as a child for this element. Ownership /// @param child is an XmlNode to add as a child for this element.
/// of the child node is transferred.
/// @return true on success, false otherwise. /// @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. /// 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. /// Set a string attribute.
/// @param attribute_name The name (lhs) of the attribute. /// @param attribute_name The name (lhs) of the attribute.
/// @param attribute The value (rhs) of the attribute. /// @param attribute The value (rhs) of the attribute.
void SetStringAttribute(const char* attribute_name, bool SetStringAttribute(const std::string& attribute_name,
const std::string& attribute); const std::string& attribute) WARN_UNUSED_RESULT;
/// Sets an integer attribute. /// Sets an integer attribute.
/// @param attribute_name The name (lhs) of the attribute. /// @param attribute_name The name (lhs) of the attribute.
/// @param number The value (rhs) 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. /// Set a floating point number attribute.
/// @param attribute_name is the name of the attribute to set. /// @param attribute_name is the name of the attribute to set.
/// @param number is the value (rhs) of the attribute. /// @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. /// Sets 'id=@a id' attribute.
/// @param id is the ID for this element. /// @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. /// Set the contents of an XML element using a string.
/// This cannot set child elements because <> will become &lt; and &rt; /// This cannot set child elements because <> will become &lt; and &rt;
@ -75,22 +90,28 @@ class XmlNode {
void SetContent(const std::string& content); void SetContent(const std::string& content);
/// @return namespaces used in the node and its descendents. /// @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 /// @param comment The body of a comment to add to the top of the XML.
/// behavior of any methods, except the destructor, is undefined. /// @return A string containing the XML.
/// @return The resource of this object. std::string ToString(const std::string& comment) const;
scoped_xml_ptr<xmlNode> PassScopedPtr();
/// Release the xmlNodePtr of this object. After calling this method, the /// Gets the attribute with the given name.
/// behavior of any methods, except the destructor, is undefined. /// @param name The name of the attribute to get.
xmlNodePtr Release(); /// @param value [OUT] where to put the resulting value.
/// @return True if the attribute exists, false if not.
/// @return Raw pointer to the element. bool GetAttribute(const std::string& name, std::string* value) const;
xmlNodePtr GetRawPtr();
private: 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); DISALLOW_COPY_AND_ASSIGN(XmlNode);
}; };
@ -102,20 +123,21 @@ class RepresentationBaseXmlNode : public XmlNode {
public: public:
~RepresentationBaseXmlNode() override; ~RepresentationBaseXmlNode() override;
bool AddContentProtectionElements( 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 scheme_id_uri is content of the schemeIdUri attribute.
/// @param value is the content of value attribute. /// @param value is the content of value attribute.
void AddSupplementalProperty(const std::string& scheme_id_uri, bool AddSupplementalProperty(const std::string& scheme_id_uri,
const std::string& value); const std::string& value) WARN_UNUSED_RESULT;
/// @param scheme_id_uri is content of the schemeIdUri attribute. /// @param scheme_id_uri is content of the schemeIdUri attribute.
/// @param value is the content of value attribute. /// @param value is the content of value attribute.
void AddEssentialProperty(const std::string& scheme_id_uri, bool AddEssentialProperty(const std::string& scheme_id_uri,
const std::string& value); const std::string& value) WARN_UNUSED_RESULT;
protected: protected:
explicit RepresentationBaseXmlNode(const char* name); explicit RepresentationBaseXmlNode(const std::string& name);
/// Add a Descriptor. /// Add a Descriptor.
/// @param descriptor_name is the name of the 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. /// @param value is the content of value attribute.
bool AddDescriptor(const std::string& descriptor_name, bool AddDescriptor(const std::string& descriptor_name,
const std::string& scheme_id_uri, const std::string& scheme_id_uri,
const std::string& value); const std::string& value) WARN_UNUSED_RESULT;
private: private:
bool AddContentProtectionElement( bool AddContentProtectionElement(
const ContentProtectionElement& content_protection_element); const ContentProtectionElement& content_protection_element)
WARN_UNUSED_RESULT;
DISALLOW_COPY_AND_ASSIGN(RepresentationBaseXmlNode); DISALLOW_COPY_AND_ASSIGN(RepresentationBaseXmlNode);
}; };
@ -140,13 +163,13 @@ class AdaptationSetXmlNode : public RepresentationBaseXmlNode {
/// @param scheme_id_uri is content of the schemeIdUri attribute. /// @param scheme_id_uri is content of the schemeIdUri attribute.
/// @param value is the content of value attribute. /// @param value is the content of value attribute.
void AddAccessibilityElement(const std::string& scheme_id_uri, bool AddAccessibilityElement(const std::string& scheme_id_uri,
const std::string& value); const std::string& value) WARN_UNUSED_RESULT;
/// @param scheme_id_uri is content of the schemeIdUri attribute. /// @param scheme_id_uri is content of the schemeIdUri attribute.
/// @param value is the content of value attribute. /// @param value is the content of value attribute.
void AddRoleElement(const std::string& scheme_id_uri, bool AddRoleElement(const std::string& scheme_id_uri,
const std::string& value); const std::string& value) WARN_UNUSED_RESULT;
private: private:
DISALLOW_COPY_AND_ASSIGN(AdaptationSetXmlNode); DISALLOW_COPY_AND_ASSIGN(AdaptationSetXmlNode);
@ -168,33 +191,35 @@ class RepresentationXmlNode : public RepresentationBaseXmlNode {
bool AddVideoInfo(const MediaInfo::VideoInfo& video_info, bool AddVideoInfo(const MediaInfo::VideoInfo& video_info,
bool set_width, bool set_width,
bool set_height, bool set_height,
bool set_frame_rate); bool set_frame_rate) WARN_UNUSED_RESULT;
/// Adds audio metadata to the MPD. /// Adds audio metadata to the MPD.
/// @param audio_info constains the AudioInfos for a Representation. /// @param audio_info constains the AudioInfos for a Representation.
/// @return true if successfully set attributes and children elements (if /// @return true if successfully set attributes and children elements (if
/// applicable), false otherwise. /// 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 /// Adds fields that are specific to VOD. This ignores @a media_info fields
/// for Live. /// for Live.
/// @param media_info is a MediaInfo with VOD information. /// @param media_info is a MediaInfo with VOD information.
/// @return true on success, false otherwise. /// @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 /// @param segment_infos is a set of SegmentInfos. This method assumes that
/// SegmentInfos are sorted by its start time. /// SegmentInfos are sorted by its start time.
bool AddLiveOnlyInfo(const MediaInfo& media_info, bool AddLiveOnlyInfo(const MediaInfo& media_info,
const std::list<SegmentInfo>& segment_infos, const std::list<SegmentInfo>& segment_infos,
uint32_t start_number); uint32_t start_number) WARN_UNUSED_RESULT;
private: private:
// Add AudioChannelConfiguration element. Note that it is a required element // Add AudioChannelConfiguration element. Note that it is a required element
// for audio Representations. // 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. // 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); DISALLOW_COPY_AND_ASSIGN(RepresentationXmlNode);
}; };

View File

@ -145,14 +145,14 @@ TEST(XmlNodeTest, ExtractReferencedNamespaces) {
XmlNode child("child1"); XmlNode child("child1");
child.SetContent("child1 content"); 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"); XmlNode child_with_namespace("child_ns:child2");
child_with_namespace.SetContent("child2 content"); child_with_namespace.SetContent("child2 content");
XmlNode root("root"); XmlNode root("root");
root.AddChild(child.PassScopedPtr()); ASSERT_TRUE(root.AddChild(std::move(child)));
root.AddChild(child_with_namespace.PassScopedPtr()); ASSERT_TRUE(root.AddChild(std::move(child_with_namespace)));
EXPECT_THAT(root.ExtractReferencedNamespaces(), EXPECT_THAT(root.ExtractReferencedNamespaces(),
ElementsAre("child_ns", "grand_ns")); ElementsAre("child_ns", "grand_ns"));
@ -160,13 +160,13 @@ TEST(XmlNodeTest, ExtractReferencedNamespaces) {
TEST(XmlNodeTest, ExtractReferencedNamespacesFromAttributes) { TEST(XmlNodeTest, ExtractReferencedNamespacesFromAttributes) {
XmlNode child("child"); XmlNode child("child");
child.SetStringAttribute("child_attribute_ns:attribute", ASSERT_TRUE(child.SetStringAttribute("child_attribute_ns:attribute",
"child attribute value"); "child attribute value"));
XmlNode root("root"); XmlNode root("root");
root.AddChild(child.PassScopedPtr()); ASSERT_TRUE(root.AddChild(std::move(child)));
root.SetStringAttribute("root_attribute_ns:attribute", ASSERT_TRUE(root.SetStringAttribute("root_attribute_ns:attribute",
"root attribute value"); "root attribute value"));
EXPECT_THAT(root.ExtractReferencedNamespaces(), EXPECT_THAT(root.ExtractReferencedNamespaces(),
ElementsAre("child_attribute_ns", "root_attribute_ns")); ElementsAre("child_attribute_ns", "root_attribute_ns"));
@ -195,9 +195,9 @@ TEST(XmlNodeTest, AddContentProtectionElements) {
content_protections.push_back(content_protection_clearkey); content_protections.push_back(content_protection_clearkey);
RepresentationXmlNode representation; RepresentationXmlNode representation;
representation.AddContentProtectionElements(content_protections); ASSERT_TRUE(representation.AddContentProtectionElements(content_protections));
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation,
XmlNodeEqual( XmlNodeEqual(
"<Representation>\n" "<Representation>\n"
" <ContentProtection\n" " <ContentProtection\n"
@ -220,9 +220,9 @@ TEST(XmlNodeTest, AddEC3AudioInfo) {
0xFFFFFFFF); 0xFFFFFFFF);
RepresentationXmlNode representation; RepresentationXmlNode representation;
representation.AddAudioInfo(audio_info); ASSERT_TRUE(representation.AddAudioInfo(audio_info));
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation,
XmlNodeEqual( XmlNodeEqual(
"<Representation audioSamplingRate=\"48000\">\n" "<Representation audioSamplingRate=\"48000\">\n"
" <AudioChannelConfiguration\n" " <AudioChannelConfiguration\n"
@ -240,16 +240,14 @@ TEST(XmlNodeTest, AddEC3AudioInfoMPEGScheme) {
audio_info.mutable_codec_specific_data()->set_channel_mpeg_value(6); audio_info.mutable_codec_specific_data()->set_channel_mpeg_value(6);
RepresentationXmlNode representation; RepresentationXmlNode representation;
representation.AddAudioInfo(audio_info); ASSERT_TRUE(representation.AddAudioInfo(audio_info));
EXPECT_THAT( EXPECT_THAT(representation,
representation.GetRawPtr(), XmlNodeEqual("<Representation audioSamplingRate=\"48000\">\n"
XmlNodeEqual( " <AudioChannelConfiguration\n"
"<Representation audioSamplingRate=\"48000\">\n" " schemeIdUri=\n"
" <AudioChannelConfiguration\n" " \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
" schemeIdUri=\n" " value=\"6\"/>\n"
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n" "</Representation>\n"));
" value=\"6\"/>\n"
"</Representation>\n"));
} }
TEST(XmlNodeTest, AddEC3AudioInfoMPEGSchemeJOC) { TEST(XmlNodeTest, AddEC3AudioInfoMPEGSchemeJOC) {
@ -261,9 +259,9 @@ TEST(XmlNodeTest, AddEC3AudioInfoMPEGSchemeJOC) {
audio_info.mutable_codec_specific_data()->set_ec3_joc_complexity(16); audio_info.mutable_codec_specific_data()->set_ec3_joc_complexity(16);
RepresentationXmlNode representation; RepresentationXmlNode representation;
representation.AddAudioInfo(audio_info); ASSERT_TRUE(representation.AddAudioInfo(audio_info));
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation,
XmlNodeEqual( XmlNodeEqual(
"<Representation audioSamplingRate=\"48000\">\n" "<Representation audioSamplingRate=\"48000\">\n"
" <AudioChannelConfiguration\n" " <AudioChannelConfiguration\n"
@ -292,16 +290,16 @@ TEST(XmlNodeTest, AddAC4AudioInfo) {
codec_data->set_ac4_cbi_flag(false); codec_data->set_ac4_cbi_flag(false);
RepresentationXmlNode representation; RepresentationXmlNode representation;
representation.AddAudioInfo(audio_info); ASSERT_TRUE(representation.AddAudioInfo(audio_info));
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation,
XmlNodeEqual( XmlNodeEqual(
"<Representation audioSamplingRate=\"48000\">\n" "<Representation audioSamplingRate=\"48000\">\n"
" <AudioChannelConfiguration\n" " <AudioChannelConfiguration\n"
" schemeIdUri=\n" " schemeIdUri=\n"
" \"tag:dolby.com,2015:dash:audio_channel_configuration:2015\"\n" " \"tag:dolby.com,2015:dash:audio_channel_configuration:2015\"\n"
" value=\"0000C7\"/>\n" " value=\"0000C7\"/>\n"
"</Representation>\n")); "</Representation>\n"));
} }
TEST(XmlNodeTest, AddAC4AudioInfoMPEGScheme) { TEST(XmlNodeTest, AddAC4AudioInfoMPEGScheme) {
@ -315,16 +313,14 @@ TEST(XmlNodeTest, AddAC4AudioInfoMPEGScheme) {
codec_data->set_ac4_cbi_flag(false); codec_data->set_ac4_cbi_flag(false);
RepresentationXmlNode representation; RepresentationXmlNode representation;
representation.AddAudioInfo(audio_info); ASSERT_TRUE(representation.AddAudioInfo(audio_info));
EXPECT_THAT( EXPECT_THAT(representation,
representation.GetRawPtr(), XmlNodeEqual("<Representation audioSamplingRate=\"48000\">\n"
XmlNodeEqual( " <AudioChannelConfiguration\n"
"<Representation audioSamplingRate=\"48000\">\n" " schemeIdUri=\n"
" <AudioChannelConfiguration\n" " \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
" schemeIdUri=\n" " value=\"2\"/>\n"
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n" "</Representation>\n"));
" value=\"2\"/>\n"
"</Representation>\n"));
} }
TEST(XmlNodeTest, AddAC4AudioInfoMPEGSchemeIMS) { TEST(XmlNodeTest, AddAC4AudioInfoMPEGSchemeIMS) {
@ -338,20 +334,19 @@ TEST(XmlNodeTest, AddAC4AudioInfoMPEGSchemeIMS) {
codec_data->set_ac4_cbi_flag(false); codec_data->set_ac4_cbi_flag(false);
RepresentationXmlNode representation; RepresentationXmlNode representation;
representation.AddAudioInfo(audio_info); ASSERT_TRUE(representation.AddAudioInfo(audio_info));
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation,
XmlNodeEqual( XmlNodeEqual("<Representation audioSamplingRate=\"48000\">\n"
"<Representation audioSamplingRate=\"48000\">\n" " <AudioChannelConfiguration\n"
" <AudioChannelConfiguration\n" " schemeIdUri=\n"
" schemeIdUri=\n" " \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n"
" \"urn:mpeg:mpegB:cicp:ChannelConfiguration\"\n" " value=\"2\"/>\n"
" value=\"2\"/>\n" " <SupplementalProperty\n"
" <SupplementalProperty\n" " schemeIdUri=\n"
" schemeIdUri=\n" " \"tag:dolby.com,2016:dash:virtualized_content:2016\"\n"
" \"tag:dolby.com,2016:dash:virtualized_content:2016\"\n" " value=\"1\"/>\n"
" value=\"1\"/>\n" "</Representation>\n"));
"</Representation>\n"));
} }
class LiveSegmentTimelineTest : public ::testing::Test { class LiveSegmentTimelineTest : public ::testing::Test {
@ -380,7 +375,7 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) {
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation,
XmlNodeEqual("<Representation>" XmlNodeEqual("<Representation>"
" <SegmentTemplate media=\"$Number$.m4s\" " " <SegmentTemplate media=\"$Number$.m4s\" "
" startNumber=\"1\" duration=\"100\"/>" " startNumber=\"1\" duration=\"100\"/>"
@ -400,7 +395,7 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) {
ASSERT_TRUE( ASSERT_TRUE(
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
EXPECT_THAT(representation.GetRawPtr(), EXPECT_THAT(representation,
XmlNodeEqual( XmlNodeEqual(
"<Representation>" "<Representation>"
" <SegmentTemplate media=\"$Number$.m4s\" startNumber=\"1\">" " <SegmentTemplate media=\"$Number$.m4s\" startNumber=\"1\">"
@ -425,7 +420,7 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) {
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation,
XmlNodeEqual("<Representation>" XmlNodeEqual("<Representation>"
" <SegmentTemplate media=\"$Number$.m4s\" " " <SegmentTemplate media=\"$Number$.m4s\" "
" startNumber=\"6\" duration=\"100\"/>" " startNumber=\"6\" duration=\"100\"/>"
@ -452,7 +447,7 @@ TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) {
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation,
XmlNodeEqual("<Representation>" XmlNodeEqual("<Representation>"
" <SegmentTemplate media=\"$Number$.m4s\" " " <SegmentTemplate media=\"$Number$.m4s\" "
" startNumber=\"1\" duration=\"100\"/>" " startNumber=\"1\" duration=\"100\"/>"
@ -478,7 +473,7 @@ TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) {
ASSERT_TRUE( ASSERT_TRUE(
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
EXPECT_THAT(representation.GetRawPtr(), EXPECT_THAT(representation,
XmlNodeEqual( XmlNodeEqual(
"<Representation>" "<Representation>"
" <SegmentTemplate media=\"$Number$.m4s\" startNumber=\"1\">" " <SegmentTemplate media=\"$Number$.m4s\" startNumber=\"1\">"
@ -510,7 +505,7 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) {
ASSERT_TRUE( ASSERT_TRUE(
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
EXPECT_THAT(representation.GetRawPtr(), EXPECT_THAT(representation,
XmlNodeEqual( XmlNodeEqual(
"<Representation>" "<Representation>"
" <SegmentTemplate media=\"$Number$.m4s\" startNumber=\"1\">" " <SegmentTemplate media=\"$Number$.m4s\" startNumber=\"1\">"
@ -522,31 +517,31 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) {
"</Representation>")); "</Representation>"));
} }
TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) { TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) {
const uint32_t kStartNumber = 1; const uint32_t kStartNumber = 1;
const uint64_t kStartTime = 0; const uint64_t kStartTime = 0;
const uint64_t kDuration = 100; const uint64_t kDuration = 100;
const uint64_t kRepeat = 9; const uint64_t kRepeat = 9;
std::list<SegmentInfo> segment_infos = { std::list<SegmentInfo> segment_infos = {
{kStartTime, kDuration, kRepeat}, {kStartTime, kDuration, kRepeat},
}; };
RepresentationXmlNode representation; RepresentationXmlNode representation;
FLAGS_dash_add_last_segment_number_when_needed = true; FLAGS_dash_add_last_segment_number_when_needed = true;
ASSERT_TRUE( ASSERT_TRUE(
representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
EXPECT_THAT( EXPECT_THAT(
representation.GetRawPtr(), representation,
XmlNodeEqual("<Representation>" XmlNodeEqual("<Representation>"
"<SupplementalProperty schemeIdUri=\"http://dashif.org/" "<SupplementalProperty schemeIdUri=\"http://dashif.org/"
"guidelines/last-segment-number\" value=\"10\"/>" "guidelines/last-segment-number\" value=\"10\"/>"
" <SegmentTemplate media=\"$Number$.m4s\" " " <SegmentTemplate media=\"$Number$.m4s\" "
" startNumber=\"1\" duration=\"100\"/>" " startNumber=\"1\" duration=\"100\"/>"
"</Representation>")); "</Representation>"));
FLAGS_dash_add_last_segment_number_when_needed = false; FLAGS_dash_add_last_segment_number_when_needed = false;
} }
} // namespace xml } // namespace xml
} // namespace shaka } // namespace shaka

View File

@ -144,54 +144,41 @@ bool CompareNodes(xmlNodePtr node1, xmlNodePtr node2) {
bool XmlEqual(const std::string& xml1, const std::string& xml2) { bool XmlEqual(const std::string& xml1, const std::string& xml2) {
xml::scoped_xml_ptr<xmlDoc> xml1_doc(GetDocFromString(xml1)); xml::scoped_xml_ptr<xmlDoc> xml1_doc(GetDocFromString(xml1));
xml::scoped_xml_ptr<xmlDoc> xml2_doc(GetDocFromString(xml2)); xml::scoped_xml_ptr<xmlDoc> xml2_doc(GetDocFromString(xml2));
return XmlEqual(xml1_doc.get(), xml2_doc.get()); if (!xml1_doc || !xml2_doc) {
} LOG(ERROR) << "xml1/xml2 is not valid XML.";
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.";
return false; return false;
} }
xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1); xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1_doc.get());
xmlNodePtr xml2_root_element = xmlDocGetRootElement(xml2); xmlNodePtr xml2_root_element = xmlDocGetRootElement(xml2_doc.get());
if (!xml1_root_element || !xml2_root_element) if (!xml1_root_element || !xml2_root_element)
return xml1_root_element == xml2_root_element; return false;
return CompareNodes(xml1_root_element, xml2_root_element); 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)); xml::scoped_xml_ptr<xmlDoc> xml1_doc(GetDocFromString(xml1));
if (!xml1_doc) { if (!xml1_doc) {
LOG(ERROR) << "xml1 are not valid XML."; LOG(ERROR) << "xml1 is not valid XML.";
return false; return false;
} }
xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1_doc.get()); xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1_doc.get());
if (!xml1_root_element) if (!xml1_root_element)
return false; return false;
return CompareNodes(xml1_root_element, xml2); return CompareNodes(xml1_root_element, xml2.GetRawPtr());
} }
std::string XmlNodeToString(xmlNodePtr xml_node) { std::string XmlNodeToString(const base::Optional<xml::XmlNode>& xml_node) {
// Create an xmlDoc from xmlNodePtr. The node is copied so ownership does not return xml_node ? XmlNodeToString(*xml_node) : "$ERROR$";
// transfer. }
xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST ""));
xmlDocSetRootElement(doc.get(), xmlCopyNode(xml_node, true));
// Format the xmlDoc to string. std::string XmlNodeToString(const xml::XmlNode& xml_node) {
static const int kNiceFormat = 1; std::string output = xml_node.ToString(/* comment= */ "");
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);
// Remove the first line from the formatted string: // Remove the first line from the formatted string:
// <?xml version="" encoding="UTF-8"?> // <?xml version="" encoding="UTF-8"?>

View File

@ -6,7 +6,9 @@
#include <string> #include <string>
#include "packager/base/optional.h"
#include "packager/mpd/base/xml/scoped_xml_ptr.h" #include "packager/mpd/base/xml/scoped_xml_ptr.h"
#include "packager/mpd/base/xml/xml_node.h"
namespace shaka { namespace shaka {
@ -19,13 +21,13 @@ namespace shaka {
/// @param xml2 is compared against @a xml1. /// @param xml2 is compared against @a xml1.
/// @return true if @a xml1 and @a xml2 are equivalent, false otherwise. /// @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, const std::string& xml2);
bool XmlEqual(const std::string& xml1, xmlDocPtr xml2); bool XmlEqual(const std::string& xml1, const xml::XmlNode& xml2);
bool XmlEqual(xmlDocPtr xml1, xmlDocPtr xml2); bool XmlEqual(const std::string& xml1,
bool XmlEqual(const std::string& xml1, xmlNodePtr xml2); const base::Optional<xml::XmlNode>& xml2);
/// Get string representation of the xml node. /// Get string representation of the xml node.
/// Note that the ownership is not transferred. std::string XmlNodeToString(const xml::XmlNode& xml_node);
std::string XmlNodeToString(xmlNodePtr xml_node); std::string XmlNodeToString(const base::Optional<xml::XmlNode>& xml_node);
/// Match an xmlNodePtr with an xml in string representation. /// Match an xmlNodePtr with an xml in string representation.
MATCHER_P(XmlNodeEqual, MATCHER_P(XmlNodeEqual,
@ -38,14 +40,16 @@ MATCHER_P(XmlNodeEqual,
/// Match the attribute of an xmlNodePtr with expected value. /// Match the attribute of an xmlNodePtr with expected value.
/// Note that the ownership is not transferred. /// Note that the ownership is not transferred.
MATCHER_P2(AttributeEqual, attribute, expected_value, "") { MATCHER_P2(AttributeEqual, attribute, expected_value, "") {
xml::scoped_xml_ptr<xmlChar> attribute_xml_str( if (!arg) {
xmlGetProp(arg, BAD_CAST attribute)); *result_listener << "returned error";
if (!attribute_xml_str) { return false;
}
std::string actual_value;
if (!arg->GetAttribute(attribute, &actual_value)) {
*result_listener << "no attribute '" << attribute << "'"; *result_listener << "no attribute '" << attribute << "'";
return false; return false;
} }
std::string actual_value =
reinterpret_cast<const char*>(attribute_xml_str.get());
*result_listener << actual_value; *result_listener << actual_value;
return expected_value == 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. /// Check if the attribute is set in an xmlNodePtr.
/// Note that the ownership is not transferred. /// Note that the ownership is not transferred.
MATCHER_P(AttributeSet, attribute, "") { MATCHER_P(AttributeSet, attribute, "") {
xml::scoped_xml_ptr<xmlChar> attribute_xml_str( if (!arg) {
xmlGetProp(arg, BAD_CAST attribute)); *result_listener << "returned error";
return attribute_xml_str != nullptr; return false;
}
std::string unused;
return arg->GetAttribute(attribute, &unused);
} }
} // namespace shaka } // namespace shaka