MpdBuilder should not automatically add ContentProtection elements

- ContentProtection elements should be added explicitly by the
  AddContentProtection() methods.
- This is because some MPDs should have ContentProtection at
  AdaptationSet level instead of Representation.
- Change SimpleMpdNotifier, which uses MpdBuilder, to add
  ContentProtectionElements. The logic is moved from MuxerListener.
- Add Element class for specifying subelements for
  ContentProtectionElement.

Change-Id: I9bedfb3e5a5ac0b3d5c702f1e6e4a8608c978d1d
This commit is contained in:
Rintaro Kuroiwa 2015-07-15 14:57:47 -07:00
parent f492cccc1d
commit 60c54975d7
16 changed files with 393 additions and 285 deletions

View File

@ -65,18 +65,6 @@ void MpdNotifyMuxerListener::OnMediaStart(
default_key_id_, pssh_, media_info.get()); default_key_id_, pssh_, media_info.get());
} }
if (is_encrypted_) {
// TODO(rkuroiwa): When MediaInfo's content protection fields are processed
// in MpdBuilder (e.g. content_protection_uuid, default_key_id) then skip
// this step if scheme_id_uri_'s UUID == content_protection_uuid_.
// Also consider removing SetContentProtectionSchemeIdUri().
if (!internal::AddContentProtectionElements(
container_type, scheme_id_uri_, media_info.get())) {
LOG(ERROR) << "Failed to add content protection elements.";
return;
}
}
if (mpd_notifier_->dash_profile() == kLiveProfile) { if (mpd_notifier_->dash_profile() == kLiveProfile) {
// TODO(kqyang): Check return result. // TODO(kqyang): Check return result.
mpd_notifier_->NotifyNewContainer(*media_info, &notification_id_); mpd_notifier_->NotifyNewContainer(*media_info, &notification_id_);

View File

@ -201,44 +201,6 @@ bool SetVodInformation(bool has_init_range,
return true; return true;
} }
// TODO(rkuroiwa): Move this logic to MpdBuilder? MuxerListener probably doesn't
// need to know this.
bool AddContentProtectionElements(MuxerListener::ContainerType container_type,
const std::string& user_scheme_id_uri,
MediaInfo* media_info) {
DCHECK(media_info);
const char kEncryptedMp4Uri[] = "urn:mpeg:dash:mp4protection:2011";
const char kEncryptedMp4Value[] = "cenc";
// DASH MPD spec specifies a default ContentProtection element for ISO BMFF
// (MP4) files.
const bool is_mp4_container = container_type == MuxerListener::kContainerMp4;
if (is_mp4_container) {
MediaInfo::ContentProtectionXml* mp4_protection =
media_info->add_content_protections();
mp4_protection->set_scheme_id_uri(kEncryptedMp4Uri);
mp4_protection->set_value(kEncryptedMp4Value);
}
if (!user_scheme_id_uri.empty()) {
MediaInfo::ContentProtectionXml* content_protection =
media_info->add_content_protections();
content_protection->set_scheme_id_uri(user_scheme_id_uri);
} else if (is_mp4_container) {
LOG(WARNING) << "schemeIdUri is not specified. Added default "
"ContentProtection only.";
}
if (media_info->content_protections_size() == 0) {
LOG(ERROR) << "The stream is encrypted but no schemeIdUri specified for "
"ContentProtection.";
return false;
}
return true;
}
void SetContentProtectionFields( void SetContentProtectionFields(
const std::string& content_protection_uuid, const std::string& content_protection_uuid,
const std::string& content_protection_name_version, const std::string& content_protection_name_version,

View File

@ -45,15 +45,6 @@ bool SetVodInformation(bool has_init_range,
uint64_t file_size, uint64_t file_size,
MediaInfo* media_info); MediaInfo* media_info);
/// @param container_type specifies container type. A default ContentProtection
/// element will be added if the container is MP4.
/// @param user_scheme_id_uri is the user specified schemeIdUri for
/// ContentProtection.
/// @return true if a ContentProtectionXml is added, false otherwise.
bool AddContentProtectionElements(MuxerListener::ContainerType container_type,
const std::string& user_scheme_id_uri,
MediaInfo* media_info);
/// @param content_protection_uuid is the UUID of the content protection /// @param content_protection_uuid is the UUID of the content protection
/// in human readable form. /// in human readable form.
/// @param content_protection_name_version is the DRM name and verion. /// @param content_protection_name_version is the DRM name and verion.

View File

@ -62,14 +62,6 @@ void VodMediaInfoDumpMuxerListener::OnMediaStart(
content_protection_uuid_, content_protection_name_version_, content_protection_uuid_, content_protection_name_version_,
default_key_id_, pssh_, media_info_.get()); default_key_id_, pssh_, media_info_.get());
} }
if (is_encrypted_) {
if (!internal::AddContentProtectionElements(
container_type, scheme_id_uri_, media_info_.get())) {
LOG(ERROR) << "Failed to add content protection elements.";
return;
}
}
} }
void VodMediaInfoDumpMuxerListener::OnSampleDurationReady( void VodMediaInfoDumpMuxerListener::OnSampleDurationReady(

View File

@ -290,13 +290,6 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, EncryptedStream_Normal) {
" pixel_width: 1\n" " pixel_width: 1\n"
" pixel_height: 1\n" " pixel_height: 1\n"
"}\n" "}\n"
"content_protections {\n"
" scheme_id_uri: 'urn:mpeg:dash:mp4protection:2011'\n"
" value: 'cenc'\n"
"}\n"
"content_protections {\n"
" scheme_id_uri: 'http://foo.com/bar'\n"
"}\n"
"init_range {\n" "init_range {\n"
" begin: 0\n" " begin: 0\n"
" end: 120\n" " end: 120\n"

View File

@ -7,6 +7,8 @@
#include "packager/mpd/base/content_protection_element.h" #include "packager/mpd/base/content_protection_element.h"
namespace edash_packager { namespace edash_packager {
Element::Element() {}
Element::~Element() {}
ContentProtectionElement::ContentProtectionElement() {} ContentProtectionElement::ContentProtectionElement() {}
ContentProtectionElement::~ContentProtectionElement() {} ContentProtectionElement::~ContentProtectionElement() {}
} // namespace edash_packager } // namespace edash_packager

View File

@ -14,9 +14,23 @@
#include <map> #include <map>
#include <string> #include <string>
#include <vector>
namespace edash_packager { namespace edash_packager {
// This is any (XML) element.
struct Element {
Element();
~Element();
// Name of this element.
std::string name;
// attributes for this element.
std::map<std::string, std::string> attributes;
// Content of this element.
std::string content;
std::vector<Element> subelements;
};
/// Structure to represent <ContentProtection> element in DASH MPD spec (ISO /// Structure to represent <ContentProtection> element in DASH MPD spec (ISO
/// 23009-1:2012 MPD and Segment Formats). /// 23009-1:2012 MPD and Segment Formats).
struct ContentProtectionElement { struct ContentProtectionElement {
@ -29,8 +43,8 @@ struct ContentProtectionElement {
// Other attributes for this element. // Other attributes for this element.
std::map<std::string, std::string> additional_attributes; std::map<std::string, std::string> additional_attributes;
// The elements that will be in this element. // The subelements that will be in this element.
std::string subelements; std::vector<Element> subelements;
}; };
} // namespace edash_packager } // namespace edash_packager

View File

@ -60,15 +60,18 @@ void AddMpdNameSpaceInfo(XmlNode* mpd) {
DCHECK(mpd); DCHECK(mpd);
static const char kXmlNamespace[] = "urn:mpeg:DASH:schema:MPD:2011"; static const char kXmlNamespace[] = "urn:mpeg:DASH:schema:MPD:2011";
mpd->SetStringAttribute("xmlns", kXmlNamespace);
static const char kXmlNamespaceXsi[] = static const char kXmlNamespaceXsi[] =
"http://www.w3.org/2001/XMLSchema-instance"; "http://www.w3.org/2001/XMLSchema-instance";
mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi);
static const char kXmlNamespaceXlink[] = "http://www.w3.org/1999/xlink"; static const char kXmlNamespaceXlink[] = "http://www.w3.org/1999/xlink";
mpd->SetStringAttribute("xmlns:xlink", kXmlNamespaceXlink);
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";
static const char kCencNamespace[] = "urn:mpeg:cenc:2013";
mpd->SetStringAttribute("xmlns", kXmlNamespace);
mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi);
mpd->SetStringAttribute("xmlns:xlink", kXmlNamespaceXlink);
mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011); mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011);
mpd->SetStringAttribute("xmlns:cenc", kCencNamespace);
} }
bool IsPeriodNode(xmlNodePtr node) { bool IsPeriodNode(xmlNodePtr node) {
@ -864,8 +867,6 @@ xml::ScopedXmlPtr<xmlNode>::type Representation::GetXml() {
content_protection_elements_)) { content_protection_elements_)) {
return xml::ScopedXmlPtr<xmlNode>::type(); return xml::ScopedXmlPtr<xmlNode>::type();
} }
if (!representation.AddContentProtectionElementsFromMediaInfo(media_info_))
return xml::ScopedXmlPtr<xmlNode>::type();
if (HasVODOnlyFields(media_info_) && if (HasVODOnlyFields(media_info_) &&
!representation.AddVODOnlyInfo(media_info_)) { !representation.AddVODOnlyInfo(media_info_)) {

View File

@ -178,6 +178,10 @@ class AdaptationSet {
Representation* AddRepresentation(const MediaInfo& media_info); Representation* AddRepresentation(const MediaInfo& media_info);
/// Add a ContenProtection element to the adaptation set. /// Add a ContenProtection element to the adaptation set.
/// AdaptationSet does not add <ContentProtection> elements
/// automatically to itself even if @a media_info.protected_content is
/// populated. This is because some MPDs should have the elements at
/// AdaptationSet level and some at Representation level.
/// @param element contains the ContentProtection element contents. /// @param element contains the ContentProtection element contents.
/// If @a element has {value, schemeIdUri} set and has /// If @a element has {value, schemeIdUri} set and has
/// {“value”, “schemeIdUri”} as key for @a additional_attributes, /// {“value”, “schemeIdUri”} as key for @a additional_attributes,
@ -294,6 +298,11 @@ class Representation {
bool Init(); bool Init();
/// Add a ContenProtection element to the representation. /// Add a ContenProtection element to the representation.
/// Representation does not add <ContentProtection> elements
/// automatically to itself even if @a media_info passed to
/// AdaptationSet::AddRepresentation() has @a media_info.protected_content
/// populated. This is because some MPDs should have the elements at
/// AdaptationSet level and some at Representation level.
/// @param element contains the ContentProtection element contents. /// @param element contains the ContentProtection element contents.
/// If @a element has {value, schemeIdUri} set and has /// If @a element has {value, schemeIdUri} set and has
/// {“value”, “schemeIdUri”} as key for @a additional_attributes, /// {“value”, “schemeIdUri”} as key for @a additional_attributes,

View File

@ -856,6 +856,58 @@ TEST_F(CommonMpdBuilderTest, SetSampleDuration) {
representation.media_info_.video_info().frame_duration()); representation.media_info_.video_info().frame_duration());
} }
// Verify that AdaptationSet::AddContentProtection() works.
TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) {
const char kVideoMediaInfo1080p[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 1920\n"
" height: 1080\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
"}\n"
"container_type: 1\n";
ContentProtectionElement content_protection;
content_protection.scheme_id_uri = "someuri";
content_protection.value = "some value";
Element pssh;
pssh.name = "cenc:pssh";
pssh.content = "any value";
content_protection.subelements.push_back(pssh);
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo1080p)));
video_adaptation_set->AddContentProtectionElement(content_protection);
const char kExpectedOutput[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\""
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
" xmlns:xlink=\"http://www.w3.org/1999/xlink\""
" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\""
" minBufferTime=\"PT2S\" type=\"static\""
" profiles=\"urn:mpeg:dash:profile:isoff-on-demand:2011\""
" mediaPresentationDuration=\"PT0S\">"
" <Period>"
" <AdaptationSet id=\"0\" contentType=\"video\" width=\"1920\""
" height=\"1080\" frameRate=\"3000/100\">"
" <ContentProtection schemeIdUri=\"someuri\" value=\"some value\">"
" <cenc:pssh>any value</cenc:pssh>"
" </ContentProtection>"
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
" mimeType=\"video/mp4\" width=\"1920\" height=\"1080\""
" frameRate=\"3000/100\"/>"
" </AdaptationSet>"
" </Period>"
"</MPD>";
std::string mpd_output;
ASSERT_TRUE(mpd_.ToString(&mpd_output));
EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output));
}
// Add one video check the output. // Add one video check the output.
TEST_F(StaticMpdBuilderTest, Video) { TEST_F(StaticMpdBuilderTest, Video) {
MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1); MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
@ -888,18 +940,78 @@ TEST_F(StaticMpdBuilderTest, VideoAndAudio) {
// MPD schema has strict ordering. AudioChannelConfiguration must appear before // MPD schema has strict ordering. AudioChannelConfiguration must appear before
// ContentProtection. // ContentProtection.
// Also test that Representation::AddContentProtection() works.
TEST_F(StaticMpdBuilderTest, AudioChannelConfigurationWithContentProtection) { TEST_F(StaticMpdBuilderTest, AudioChannelConfigurationWithContentProtection) {
MediaInfo encrypted_audio_media_info = const char kTestMediaInfo[] =
GetTestMediaInfo(kFileNameEncytpedAudioMediaInfo); "bandwidth: 195857\n"
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 44100\n"
" num_channels: 2\n"
"}\n"
"init_range {\n"
" begin: 0\n"
" end: 863\n"
"}\n"
"index_range {\n"
" begin: 864\n"
" end: 931\n"
"}\n"
"media_file_name: 'encrypted_audio.mp4'\n"
"media_duration_seconds: 24.009434\n"
"reference_time_scale: 44100\n"
"container_type: CONTAINER_MP4\n";
const char kExpectedOutput[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\""
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
" xmlns:xlink=\"http://www.w3.org/1999/xlink\""
" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\""
" minBufferTime=\"PT2S\" type=\"static\""
" profiles=\"urn:mpeg:dash:profile:isoff-on-demand:2011\""
" mediaPresentationDuration=\"PT24.00943374633789S\">"
" <Period>"
" <AdaptationSet id=\"0\" contentType=\"audio\">"
" <Representation id=\"0\" bandwidth=\"195857\" codecs=\"mp4a.40.2\""
" mimeType=\"audio/mp4\" audioSamplingRate=\"44100\">"
" <AudioChannelConfiguration"
" schemeIdUri="
" \"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\""
" value=\"2\"/>"
" <ContentProtection schemeIdUri=\"http://foo.com/\">"
" <cenc:pssh>anything</cenc:pssh>"
" </ContentProtection>"
" <BaseURL>encrypted_audio.mp4</BaseURL>"
" <SegmentBase indexRange=\"864-931\" timescale=\"44100\">"
" <Initialization range=\"0-863\"/>"
" </SegmentBase>"
" </Representation>"
" </AdaptationSet>"
" </Period>"
"</MPD>";
ContentProtectionElement content_protection;
content_protection.scheme_id_uri = "http://foo.com/";
Element pssh;
pssh.name = "cenc:pssh";
pssh.content = "anything";
content_protection.subelements.push_back(pssh);
MediaInfo audio_media_info = ConvertToMediaInfo(kTestMediaInfo);
AdaptationSet* audio_adaptation_set = mpd_.AddAdaptationSet(""); AdaptationSet* audio_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(audio_adaptation_set); ASSERT_TRUE(audio_adaptation_set);
Representation* audio_representation = Representation* audio_representation =
audio_adaptation_set->AddRepresentation(encrypted_audio_media_info); audio_adaptation_set->AddRepresentation(audio_media_info);
ASSERT_TRUE(audio_representation); ASSERT_TRUE(audio_representation);
audio_representation->AddContentProtectionElement(content_protection);
EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputEncryptedAudio)); std::string mpd_output;
ASSERT_TRUE(mpd_.ToString(&mpd_output));
EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output));
} }
// Static profile requires bandwidth to be set because it has no other way to // Static profile requires bandwidth to be set because it has no other way to
@ -939,14 +1051,19 @@ TEST_F(StaticMpdBuilderTest, WriteToFile) {
} }
// Check whether the attributes are set correctly for dynamic <MPD> element. // Check whether the attributes are set correctly for dynamic <MPD> element.
// This test must use ASSERT_EQ for comparison because XmlEqual() cannot
// handle namespaces correctly yet.
TEST_F(DynamicMpdBuilderTest, CheckMpdAttributes) { TEST_F(DynamicMpdBuilderTest, CheckMpdAttributes) {
static const char kExpectedOutput[] = static const char kExpectedOutput[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" " "<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" "
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" " "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
"xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 " "xsi:schemaLocation="
"DASH-MPD.xsd\" minBufferTime=\"PT2S\" type=\"dynamic\" " "\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\" "
"xmlns:cenc=\"urn:mpeg:cenc:2013\" "
"minBufferTime=\"PT2S\" "
"type=\"dynamic\" "
"profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" " "profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
"availabilityStartTime=\"2011-12-25T12:30:00\">\n" "availabilityStartTime=\"2011-12-25T12:30:00\">\n"
" <Period start=\"PT0S\"/>\n" " <Period start=\"PT0S\"/>\n"

View File

@ -6,7 +6,10 @@
#include "packager/mpd/base/simple_mpd_notifier.h" #include "packager/mpd/base/simple_mpd_notifier.h"
#include "packager/base/base64.h"
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h"
#include "packager/base/strings/string_util.h"
#include "packager/media/file/file.h" #include "packager/media/file/file.h"
#include "packager/mpd/base/mpd_builder.h" #include "packager/mpd/base/mpd_builder.h"
#include "packager/mpd/base/mpd_utils.h" #include "packager/mpd/base/mpd_utils.h"
@ -15,6 +18,116 @@ using edash_packager::media::File;
namespace edash_packager { namespace edash_packager {
namespace {
// Coverts binary data into human readable UUID format.
bool HexToUUID(const std::string& data, std::string* uuid_format) {
DCHECK(uuid_format);
const size_t kExpectedUUIDSize = 16;
if (data.size() != kExpectedUUIDSize) {
LOG(ERROR) << "Default key ID size is expected to be " << kExpectedUUIDSize
<< " but is " << data.size();
return false;
}
const std::string hex_encoded =
StringToLowerASCII(base::HexEncode(data.data(), data.size()));
DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2);
base::StringPiece all(hex_encoded);
// Note UUID has 5 parts separated with dashes.
// e.g. 123e4567-e89b-12d3-a456-426655440000
// These StringPieces have each part.
base::StringPiece first = all.substr(0, 8);
base::StringPiece second = all.substr(8, 4);
base::StringPiece third = all.substr(12, 4);
base::StringPiece fourth = all.substr(16, 4);
base::StringPiece fifth= all.substr(20, 12);
// 32 hexadecimal characters with 4 hyphens.
const size_t kHumanReadableUUIDSize = 36;
uuid_format->reserve(kHumanReadableUUIDSize);
first.CopyToString(uuid_format);
uuid_format->append("-");
second.AppendToString(uuid_format);
uuid_format->append("-");
third.AppendToString(uuid_format);
uuid_format->append("-");
fourth.AppendToString(uuid_format);
uuid_format->append("-");
fifth.AppendToString(uuid_format);
return true;
}
// This might be useful for DashIopCompliantMpdNotifier. If so it might make
// sense to template this so that it accepts Representation and AdaptationSet.
// For SimpleMpdNotifier, just put it in Representation. It should still
// generate a valid MPD.
void AddContentProtectionElements(const MediaInfo& media_info,
Representation* representation) {
DCHECK(representation);
if (!media_info.has_protected_content())
return;
const MediaInfo::ProtectedContent& protected_content =
media_info.protected_content();
const char kEncryptedMp4Uri[] = "urn:mpeg:dash:mp4protection:2011";
const char kEncryptedMp4Value[] = "cenc";
// DASH MPD spec specifies a default ContentProtection element for ISO BMFF
// (MP4) files.
const bool is_mp4_container =
media_info.container_type() == MediaInfo::CONTAINER_MP4;
if (is_mp4_container) {
ContentProtectionElement mp4_content_protection;
mp4_content_protection.scheme_id_uri = kEncryptedMp4Uri;
mp4_content_protection.value = kEncryptedMp4Value;
if (protected_content.has_default_key_id()) {
std::string key_id_uuid_format;
if (HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) {
mp4_content_protection.additional_attributes["cenc:default_KID"] =
key_id_uuid_format;
} else {
LOG(ERROR) << "Failed to convert default key ID into UUID format.";
}
}
representation->AddContentProtectionElement(mp4_content_protection);
}
for (int i = 0; i < protected_content.content_protection_entry().size();
++i) {
const MediaInfo::ProtectedContent::ContentProtectionEntry& entry =
protected_content.content_protection_entry(i);
if (!entry.has_uuid()) {
LOG(WARNING)
<< "ContentProtectionEntry was specified but no UUID is set for "
<< entry.name_version() << ", skipping.";
continue;
}
ContentProtectionElement drm_content_protection;
drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid();
if (entry.has_name_version())
drm_content_protection.value = entry.name_version();
if (entry.has_pssh()) {
std::string base64_encoded_pssh;
base::Base64Encode(entry.pssh(), &base64_encoded_pssh);
Element cenc_pssh;
cenc_pssh.name = "cenc:pssh";
cenc_pssh.content = base64_encoded_pssh;
drm_content_protection.subelements.push_back(cenc_pssh);
}
representation->AddContentProtectionElement(drm_content_protection);
}
LOG_IF(WARNING, protected_content.content_protection_entry().size() == 0)
<< "The media is encrypted but no content protection specified.";
}
} // namespace
SimpleMpdNotifier::SimpleMpdNotifier(DashProfile dash_profile, SimpleMpdNotifier::SimpleMpdNotifier(DashProfile dash_profile,
const MpdOptions& mpd_options, const MpdOptions& mpd_options,
const std::vector<std::string>& base_urls, const std::vector<std::string>& base_urls,
@ -64,6 +177,7 @@ bool SimpleMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
if (representation == NULL) if (representation == NULL)
return false; return false;
AddContentProtectionElements(media_info, representation);
*container_id = representation->id(); *container_id = representation->id();
if (mpd_builder_->type() == MpdBuilder::kStatic) if (mpd_builder_->type() == MpdBuilder::kStatic)

View File

@ -33,132 +33,6 @@ std::string RangeToString(const Range& range) {
base::Uint64ToString(range.end()); base::Uint64ToString(range.end());
} }
bool SetAttributes(const google::protobuf::RepeatedPtrField<
AttributeNameValuePair>& attributes,
XmlNode* xml_node) {
DCHECK(xml_node);
for (int i = 0; i < attributes.size(); ++i) {
const AttributeNameValuePair& attribute = attributes.Get(i);
const std::string& name = attribute.name();
const std::string& value = attribute.value();
if (name.empty()) {
LOG(ERROR) << "For element "
<< reinterpret_cast<const char*>(xml_node->GetRawPtr()->name)
<< ", no name specified for attribute with value: " << value;
return false;
}
xml_node->SetStringAttribute(name.c_str(), value);
}
return true;
}
// This function is recursive. Note that elements.size() == 0 is a terminating
// condition.
bool AddSubelements(const google::protobuf::RepeatedPtrField<
ContentProtectionXml::Element>& elements,
XmlNode* xml_node) {
DCHECK(xml_node);
for (int i = 0; i < elements.size(); ++i) {
const ContentProtectionXml::Element& subelement = elements.Get(i);
const std::string& subelement_name = subelement.name();
if (subelement_name.empty()) {
LOG(ERROR) << "Subelement name was not specified for node "
<< reinterpret_cast<const char*>(xml_node->GetRawPtr()->name);
return false;
}
XmlNode subelement_xml_node(subelement_name.c_str());
if (!SetAttributes(subelement.attributes(), &subelement_xml_node)) {
LOG(ERROR) << "Failed to set attributes for " << subelement_name;
return false;
}
if (!AddSubelements(subelement.subelements(), &subelement_xml_node)) {
LOG(ERROR) << "Failed to add subelements to " << subelement_name;
return false;
}
if (!xml_node->AddChild(subelement_xml_node.PassScopedPtr())) {
LOG(ERROR) << "Failed to add subelement " << subelement_name << " to "
<< reinterpret_cast<const char*>(xml_node->GetRawPtr()->name);
return false;
}
}
return true;
}
// Returns true if 'schemeIdUri' is set in |content_protection_xml| and sets
// |scheme_id_uri_output|. This function checks
// ContentProtectionXml::scheme_id_uri before searching thru attributes.
bool GetSchemeIdAttribute(const ContentProtectionXml& content_protection_xml,
std::string* scheme_id_uri_output) {
// Common case where 'schemeIdUri' is set directly.
if (content_protection_xml.has_scheme_id_uri()) {
scheme_id_uri_output->assign(content_protection_xml.scheme_id_uri());
return true;
}
// 'schemeIdUri' is one of the attributes.
for (int i = 0; i < content_protection_xml.attributes().size(); ++i) {
const AttributeNameValuePair& attribute =
content_protection_xml.attributes(i);
const std::string& name = attribute.name();
const std::string& value = attribute.value();
if (name == "schemeIdUri") {
if (value.empty())
LOG(WARNING) << "schemeIdUri is specified with an empty string.";
// 'schemeIdUri' is a mandatory field but MPD doesn't care what the actual
// value is, proceed.
scheme_id_uri_output->assign(value);
return true;
}
}
return false;
}
// Translates ContentProtectionXml to XmlNode.
// content_protection_xml.scheme_id_uri and content_protection_xml.value takes
// precedence over attributes in content_protection_xml.attributes.
bool TranslateToContentProtectionXmlNode(
const ContentProtectionXml& content_protection_xml,
XmlNode* xml_node_content_protection) {
std::string scheme_id_uri;
if (!GetSchemeIdAttribute(content_protection_xml, &scheme_id_uri)) {
LOG(ERROR) << "ContentProtection element requires schemeIdUri.";
return false;
}
if (!SetAttributes(content_protection_xml.attributes(),
xml_node_content_protection)) {
LOG(ERROR) << "Failed to set attributes for ContentProtection.";
return false;
}
if (!AddSubelements(content_protection_xml.subelements(),
xml_node_content_protection)) {
LOG(ERROR) << "Failed to add sublements to ContentProtection.";
return false;
}
// Add 'schemeIdUri' and 'value' attributes after SetAttributes() to avoid
// being overridden by content_protection_xml.attributes().
xml_node_content_protection->SetStringAttribute("schemeIdUri", scheme_id_uri);
if (content_protection_xml.has_value()) {
// Note that |value| is an optional field.
xml_node_content_protection->SetStringAttribute(
"value", content_protection_xml.value());
}
return true;
}
bool PopulateSegmentTimeline(const std::list<SegmentInfo>& segment_infos, bool PopulateSegmentTimeline(const std::list<SegmentInfo>& segment_infos,
XmlNode* segment_timeline) { XmlNode* segment_timeline) {
for (std::list<SegmentInfo>::const_iterator it = segment_infos.begin(); for (std::list<SegmentInfo>::const_iterator it = segment_infos.begin();
@ -199,6 +73,36 @@ bool XmlNode::AddChild(ScopedXmlPtr<xmlNode>::type child) {
return true; return true;
} }
bool XmlNode::AddElements(const std::vector<Element>& elements) {
for (size_t element_index = 0; element_index < elements.size();
++element_index) {
const Element& child_element = elements[element_index];
XmlNode child_node(child_element.name.c_str());
for (std::map<std::string, std::string>::const_iterator attribute_it =
child_element.attributes.begin();
attribute_it != child_element.attributes.end(); ++attribute_it) {
child_node.SetStringAttribute(attribute_it->first.c_str(),
attribute_it->second);
}
// Recursively set children for the child.
if (!child_node.AddElements(child_element.subelements))
return false;
child_node.SetContent(child_element.content);
if (!xmlAddChild(node_.get(), child_node.GetRawPtr())) {
LOG(ERROR) << "Failed to set child " << child_element.name
<< " to parent element "
<< reinterpret_cast<const char*>(node_->name);
return false;
}
// Reaching here means the ownership of |child_node| transfered to |node_|.
// Release the pointer so that it doesn't get destructed in this scope.
ignore_result(child_node.Release());
}
return true;
}
void XmlNode::SetStringAttribute(const char* attribute_name, void XmlNode::SetStringAttribute(const char* attribute_name,
const std::string& attribute) { const std::string& attribute) {
DCHECK(node_); DCHECK(node_);
@ -265,39 +169,15 @@ bool RepresentationBaseXmlNode::AddContentProtectionElements(
return true; return true;
} }
bool RepresentationBaseXmlNode::AddContentProtectionElementsFromMediaInfo(
const MediaInfo& media_info) {
const bool has_content_protections =
media_info.content_protections().size() > 0;
if (!has_content_protections)
return true;
for (int i = 0; i < media_info.content_protections().size(); ++i) {
const ContentProtectionXml& content_protection_xml =
media_info.content_protections(i);
XmlNode content_protection_node("ContentProtection");
if (!TranslateToContentProtectionXmlNode(content_protection_xml,
&content_protection_node)) {
LOG(ERROR) << "Failed to make ContentProtection element from MediaInfo.";
return false;
}
if (!AddChild(content_protection_node.PassScopedPtr())) {
LOG(ERROR) << "Failed to add ContentProtection to Representation.";
return false;
}
}
return true;
}
bool RepresentationBaseXmlNode::AddContentProtectionElement( bool RepresentationBaseXmlNode::AddContentProtectionElement(
const ContentProtectionElement& content_protection_element) { const ContentProtectionElement& content_protection_element) {
XmlNode content_protection_node("ContentProtection"); XmlNode content_protection_node("ContentProtection");
content_protection_node.SetStringAttribute("value", // @value is an optional attribute.
content_protection_element.value); if (!content_protection_element.value.empty()) {
content_protection_node.SetStringAttribute(
"value", content_protection_element.value);
}
content_protection_node.SetStringAttribute( content_protection_node.SetStringAttribute(
"schemeIdUri", content_protection_element.scheme_id_uri); "schemeIdUri", content_protection_element.scheme_id_uri);
@ -312,7 +192,10 @@ bool RepresentationBaseXmlNode::AddContentProtectionElement(
attributes_it->second); attributes_it->second);
} }
content_protection_node.SetContent(content_protection_element.subelements); if (!content_protection_node.AddElements(
content_protection_element.subelements)) {
return false;
}
return AddChild(content_protection_node.PassScopedPtr()); return AddChild(content_protection_node.PassScopedPtr());
} }

View File

@ -41,6 +41,9 @@ class XmlNode {
/// @return true on success, false otherwise. /// @return true on success, false otherwise.
bool AddChild(ScopedXmlPtr<xmlNode>::type child); bool AddChild(ScopedXmlPtr<xmlNode>::type child);
/// Adds Elements to this node using the Element struct.
bool AddElements(const std::vector<Element>& elements);
/// 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.
@ -62,8 +65,9 @@ class XmlNode {
void SetId(uint32_t id); void SetId(uint32_t id);
/// Set the contents of an XML element using a string. /// Set the contents of an XML element using a string.
/// Note: This function does not work well with AddChild(). Use either /// This cannot set child elements because <> will become &lt; and &rt;
/// AddChild() or SetContent() when setting the content of this node. /// This should be used to set the text for the element, e.g. setting
/// a URL for <BaseURL> element.
/// @param content is a string containing the text-encoded child elements to /// @param content is a string containing the text-encoded child elements to
/// be added to the element. /// be added to the element.
void SetContent(const std::string& content); void SetContent(const std::string& content);
@ -95,12 +99,6 @@ class RepresentationBaseXmlNode : public XmlNode {
bool AddContentProtectionElements( bool AddContentProtectionElements(
const std::list<ContentProtectionElement>& content_protection_elements); const std::list<ContentProtectionElement>& content_protection_elements);
/// Add a ContentProtection elements to this element.
/// @param media_info is a MediaInfo containing the ContentProtection
/// elements to add.
/// @return true on success, false otherwise.
bool AddContentProtectionElementsFromMediaInfo(const MediaInfo& media_info);
protected: protected:
explicit RepresentationBaseXmlNode(const char* name); explicit RepresentationBaseXmlNode(const char* name);

View File

@ -84,7 +84,7 @@ class RepresentationTest : public ::testing::Test {
// Make sure XmlEqual() is functioning correctly. // Make sure XmlEqual() is functioning correctly.
// TODO(rkuroiwa): Move this to a separate file. This requires it to be TEST_F // TODO(rkuroiwa): Move this to a separate file. This requires it to be TEST_F
// due to gtest /test // due to gtest /test
TEST_F(RepresentationTest, MetaTest_XmlEqual) { TEST_F(RepresentationTest, MetaTestXmlElementsEqual) {
static const char kXml1[] = static const char kXml1[] =
"<A>\n" "<A>\n"
" <B\n" " <B\n"
@ -167,37 +167,54 @@ TEST_F(RepresentationTest, MetaTest_XmlEqual) {
ASSERT_FALSE(XmlEqual(kXml1AttributeReorder, kXml1ChildrenReordered)); ASSERT_FALSE(XmlEqual(kXml1AttributeReorder, kXml1ChildrenReordered));
} }
TEST_F(RepresentationTest, AddContentProtectionXml) { // Verify that if contents are different, XmlEqual returns false.
static const char kExpectedRepresentaionString[] = // This is to catch the case where just using xmlNodeGetContent() on elements
// that have subelements don't quite work well.
// xmlNodeGetContent(<A>) (for both <A>s) will return "content1content2".
// But if it is run on <B> for the first XML, it will return "content1", but
// for second XML will return "c".
TEST_F(RepresentationTest, MetaTestXmlEqualDifferentContent) {
ASSERT_FALSE(XmlEqual(
"<A><B>content1</B><B>content2</B></A>",
"<A><B>c</B><B>ontent1content2</B></A>"));
}
// Verify that AddContentProtectionElements work.
// xmlReadMemory() (used in XmlEqual()) doesn't like XML fragments that have
// namespaces without context, e.g. <cenc:pssh> element.
// The MpdBuilderTests work because the MPD element has xmlns:cenc attribute.
// Tests that have <cenc:pssh> is in mpd_builder_unittest.
TEST_F(RepresentationTest, AddContentProtectionElements) {
std::list<ContentProtectionElement> content_protections;
ContentProtectionElement content_protection_widevine;
content_protection_widevine.scheme_id_uri =
"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
content_protection_widevine.value = "SOME bogus Widevine DRM version";
Element any_element;
any_element.name = "AnyElement";
any_element.content = "any content";
content_protection_widevine.subelements.push_back(any_element);
content_protections.push_back(content_protection_widevine);
ContentProtectionElement content_protection_clearkey;
content_protection_clearkey.scheme_id_uri =
"urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b";
content_protections.push_back(content_protection_clearkey);
representation_.AddContentProtectionElements(content_protections);
ScopedXmlPtr<xmlDoc>::type doc(MakeDoc(representation_.PassScopedPtr()));
ASSERT_TRUE(XmlEqual(
"<Representation>\n" "<Representation>\n"
" <ContentProtection\n" " <ContentProtection\n"
" a=\"1\"\n" " schemeIdUri=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\"\n"
" b=\"2\"\n" " value=\"SOME bogus Widevine DRM version\">\n"
" schemeIdUri=\"http://www.foo.com/drm\"\n" " <AnyElement>any content</AnyElement>\n"
" value=\"somevalue\">\n"
" <TestSubElement c=\"3\" d=\"4\"/>\n"
" </ContentProtection>\n" " </ContentProtection>\n"
"</Representation>"; " <ContentProtection\n"
" schemeIdUri=\"urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b\">"
MediaInfo media_info; " </ContentProtection>\n"
MediaInfo::ContentProtectionXml* content_protection_xml = "</Representation>",
media_info.add_content_protections(); doc.get()));
content_protection_xml->set_scheme_id_uri("http://www.foo.com/drm");
content_protection_xml->set_value("somevalue");
AddAttribute("a", "1", content_protection_xml);
AddAttribute("b", "2", content_protection_xml);
MediaInfo::ContentProtectionXml::Element* subelement =
content_protection_xml->add_subelements();
subelement->set_name("TestSubElement");
AddAttribute("c", "3", subelement);
AddAttribute("d", "4", subelement);
ASSERT_TRUE(
representation_.AddContentProtectionElementsFromMediaInfo(media_info));
ScopedXmlPtr<xmlDoc>::type doc(MakeDoc(representation_.PassScopedPtr()));
ASSERT_TRUE(
XmlEqual(kExpectedRepresentaionString, doc.get()));
} }
// Some template names cannot be used for init segment name. // Some template names cannot be used for init segment name.

View File

@ -4,7 +4,6 @@
<AdaptationSet id="0" contentType="audio"> <AdaptationSet id="0" contentType="audio">
<Representation id="0" bandwidth="195857" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100"> <Representation id="0" bandwidth="195857" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<ContentProtection schemeIdUri="http://foo.com/"/>
<BaseURL>encrypted_audio.mp4</BaseURL> <BaseURL>encrypted_audio.mp4</BaseURL>
<SegmentBase indexRange="864-931" timescale="44100"> <SegmentBase indexRange="864-931" timescale="44100">
<Initialization range="0-863"/> <Initialization range="0-863"/>

View File

@ -8,15 +8,16 @@
#include <utility> #include <utility>
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/strings/string_util.h"
#include "packager/mpd/base/xml/scoped_xml_ptr.h" #include "packager/mpd/base/xml/scoped_xml_ptr.h"
#include "packager/third_party/libxml/src/include/libxml/parser.h"
namespace edash_packager { namespace edash_packager {
namespace { namespace {
xml::ScopedXmlPtr<xmlDoc>::type GetDocFromString(const std::string& xml_str) { xml::ScopedXmlPtr<xmlDoc>::type GetDocFromString(const std::string& xml_str) {
xml::ScopedXmlPtr<xmlDoc>::type schema_as_doc( xml::ScopedXmlPtr<xmlDoc>::type schema_as_doc(xmlReadMemory(
xmlReadMemory(xml_str.data(), xml_str.size(), NULL, NULL, 0)); xml_str.data(), xml_str.size(), NULL, NULL, 0));
return schema_as_doc.Pass(); return schema_as_doc.Pass();
} }
@ -74,12 +75,33 @@ bool CompareNames(xmlNodePtr node1, xmlNodePtr node2) {
return xmlStrcmp(node1->name, node2->name) == 0; return xmlStrcmp(node1->name, node2->name) == 0;
} }
bool CompareContents(xmlNodePtr node1, xmlNodePtr node2) {
std::string node1_content =
reinterpret_cast<const char*>(xmlNodeGetContent(node1));
std::string node2_content =
reinterpret_cast<const char*>(xmlNodeGetContent(node2));
base::ReplaceChars(node1_content, "\n", "", &node1_content);
base::TrimString(node1_content, " ", &node1_content);
base::ReplaceChars(node2_content, "\n", "", &node2_content);
base::TrimString(node2_content, " ", &node2_content);
DVLOG(2) << "Comparing contents of "
<< reinterpret_cast<const char*>(node1->name) << "\n"
<< "First node's content:\n" << node1_content << "\n"
<< "Second node's content:\n" << node2_content;
const bool same_content = node1_content == node2_content;
LOG_IF(ERROR, !same_content)
<< "Contents of " << reinterpret_cast<const char*>(node1->name)
<< " do not match.\n"
<< "First node's content:\n" << node1_content << "\n"
<< "Second node's content:\n" << node2_content;
return same_content;
}
// Recursively check the elements. // Recursively check the elements.
// Note that the terminating condition of the recursion is when the children do // Note that the terminating condition of the recursion is when the children do
// not match (inside the loop). // not match (inside the loop).
bool CompareNodes(xmlNodePtr node1, xmlNodePtr node2) { bool CompareNodes(xmlNodePtr node1, xmlNodePtr node2) {
DCHECK(node1 && node2); DCHECK(node1 && node2);
if (!CompareNames(node1, node2)) { if (!CompareNames(node1, node2)) {
LOG(ERROR) << "Names of the nodes do not match: " LOG(ERROR) << "Names of the nodes do not match: "
<< reinterpret_cast<const char*>(node1->name) << " " << reinterpret_cast<const char*>(node1->name) << " "
@ -96,6 +118,12 @@ bool CompareNodes(xmlNodePtr node1, xmlNodePtr node2) {
xmlNodePtr node1_child = xmlFirstElementChild(node1); xmlNodePtr node1_child = xmlFirstElementChild(node1);
xmlNodePtr node2_child = xmlFirstElementChild(node2); xmlNodePtr node2_child = xmlFirstElementChild(node2);
if (!node1_child && !node2_child) {
// Note that xmlFirstElementChild() returns NULL if there are only
// text type children.
return CompareContents(node1, node2);
}
do { do {
if (!node1_child || !node2_child) if (!node1_child || !node2_child)
return node1_child == node2_child; return node1_child == node2_child;