Live profile MPD did not have ContentProtection

- Because MpdBuilder no longer sets ContentProtection, live was broken.
- For key-rotation, <cenc:pssh> element is updated.

Change-Id: I45bb80a12faff39ccf5deb82439c0732ed9cea59
This commit is contained in:
Rintaro Kuroiwa 2015-08-26 13:25:29 -07:00
parent fa4a172b04
commit c75d319985
23 changed files with 477 additions and 155 deletions

View File

@ -37,6 +37,15 @@ void MpdNotifyMuxerListener::OnEncryptionInfoReady(
const std::string& content_protection_name_version, const std::string& content_protection_name_version,
const std::vector<uint8_t>& default_key_id, const std::vector<uint8_t>& default_key_id,
const std::vector<uint8_t>& pssh) { const std::vector<uint8_t>& pssh) {
if (mpd_notifier_->dash_profile() == kLiveProfile) {
bool updated = mpd_notifier_->NotifyEncryptionUpdate(notification_id_,
default_key_id, pssh);
LOG_IF(WARNING, !updated) << "Failed to update pssh.";
return;
}
LOG_IF(WARNING, is_encrypted_) << "Updating encryption information, but key "
"rotation for VOD is not supported.";
content_protection_uuid_ = content_protection_uuid; content_protection_uuid_ = content_protection_uuid;
content_protection_name_version_ = content_protection_name_version; content_protection_name_version_ = content_protection_name_version;
default_key_id_.assign(default_key_id.begin(), default_key_id.end()); default_key_id_.assign(default_key_id.begin(), default_key_id.end());

View File

@ -51,6 +51,10 @@ class MockMpdNotifier : public MpdNotifier {
uint64_t start_time, uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t size)); uint64_t size));
MOCK_METHOD3(NotifyEncryptionUpdate,
bool(uint32_t container_id,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh));
MOCK_METHOD2( MOCK_METHOD2(
AddContentProtectionElement, AddContentProtectionElement,
bool(uint32_t container_id, bool(uint32_t container_id,

View File

@ -47,6 +47,8 @@ class MuxerListener {
// |default_key_id| is the default_KID in 'tenc' box. The format should // |default_key_id| is the default_KID in 'tenc' box. The format should
// be a vector of uint8_t, i.e. not (necessarily) human readable hex string. // be a vector of uint8_t, i.e. not (necessarily) human readable hex string.
// |pssh| is the whole 'pssh' box. // |pssh| is the whole 'pssh' box.
// This method may be called multiple times to notify the event handler that
// the encryption info has changed.
virtual void OnEncryptionInfoReady( virtual void OnEncryptionInfoReady(
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

@ -19,7 +19,8 @@ KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof,
KeySource::TrackType track_type, KeySource::TrackType track_type,
int64_t crypto_period_duration, int64_t crypto_period_duration,
int64_t clear_time, int64_t clear_time,
uint8_t nalu_length_size) uint8_t nalu_length_size,
MuxerListener* muxer_listener)
: EncryptingFragmenter(traf, : EncryptingFragmenter(traf,
scoped_ptr<EncryptionKey>(new EncryptionKey()), scoped_ptr<EncryptionKey>(new EncryptionKey()),
clear_time, clear_time,
@ -28,7 +29,8 @@ KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof,
encryption_key_source_(encryption_key_source), encryption_key_source_(encryption_key_source),
track_type_(track_type), track_type_(track_type),
crypto_period_duration_(crypto_period_duration), crypto_period_duration_(crypto_period_duration),
prev_crypto_period_index_(-1) { prev_crypto_period_index_(-1),
muxer_listener_(muxer_listener) {
DCHECK(moof); DCHECK(moof);
DCHECK(encryption_key_source); DCHECK(encryption_key_source);
} }
@ -58,6 +60,12 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption(
DCHECK(encryption_key()); DCHECK(encryption_key());
moof_->pssh[0].raw_box = encryption_key()->pssh; moof_->pssh[0].raw_box = encryption_key()->pssh;
if (muxer_listener_) {
muxer_listener_->OnEncryptionInfoReady(
encryption_key_source_->UUID(), encryption_key_source_->SystemName(),
encryption_key()->key_id, encryption_key()->pssh);
}
// Skip the following steps if the current fragment is not going to be // Skip the following steps if the current fragment is not going to be
// encrypted. 'pssh' box needs to be included in the fragment, which is // encrypted. 'pssh' box needs to be included in the fragment, which is
// performed above, regardless of whether the fragment is encrypted. This is // performed above, regardless of whether the fragment is encrypted. This is

View File

@ -8,6 +8,7 @@
#define MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_ #define MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_
#include "packager/media/base/key_source.h" #include "packager/media/base/key_source.h"
#include "packager/media/event/muxer_listener.h"
#include "packager/media/formats/mp4/encrypting_fragmenter.h" #include "packager/media/formats/mp4/encrypting_fragmenter.h"
namespace edash_packager { namespace edash_packager {
@ -32,13 +33,16 @@ class KeyRotationFragmenter : public EncryptingFragmenter {
/// track's timescale. /// track's timescale.
/// @param nalu_length_size NAL unit length size, in bytes, for subsample /// @param nalu_length_size NAL unit length size, in bytes, for subsample
/// encryption. /// encryption.
/// @param muxer_listener is a pointer to MuxerListener for notifying
/// muxer related events. This may be null.
KeyRotationFragmenter(MovieFragment* moof, KeyRotationFragmenter(MovieFragment* moof,
TrackFragment* traf, TrackFragment* traf,
KeySource* encryption_key_source, KeySource* encryption_key_source,
KeySource::TrackType track_type, KeySource::TrackType track_type,
int64_t crypto_period_duration, int64_t crypto_period_duration,
int64_t clear_time, int64_t clear_time,
uint8_t nalu_length_size); uint8_t nalu_length_size,
MuxerListener* muxer_listener);
virtual ~KeyRotationFragmenter(); virtual ~KeyRotationFragmenter();
protected: protected:
@ -56,6 +60,9 @@ class KeyRotationFragmenter : public EncryptingFragmenter {
const int64_t crypto_period_duration_; const int64_t crypto_period_duration_;
size_t prev_crypto_period_index_; size_t prev_crypto_period_index_;
// For notifying new pssh boxes to the event handler.
MuxerListener* const muxer_listener_;
DISALLOW_COPY_AND_ASSIGN(KeyRotationFragmenter); DISALLOW_COPY_AND_ASSIGN(KeyRotationFragmenter);
}; };

View File

@ -58,6 +58,7 @@
'dependencies': [ 'dependencies': [
'../../../third_party/openssl/openssl.gyp:openssl', '../../../third_party/openssl/openssl.gyp:openssl',
'../../base/media_base.gyp:base', '../../base/media_base.gyp:base',
'../../event/media_event.gyp:media_event',
'../../filters/filters.gyp:filters', '../../filters/filters.gyp:filters',
], ],
}, },

View File

@ -178,7 +178,8 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
track_type, track_type,
crypto_period_duration_in_seconds * streams[i]->info()->time_scale(), crypto_period_duration_in_seconds * streams[i]->info()->time_scale(),
clear_lead_in_seconds * streams[i]->info()->time_scale(), clear_lead_in_seconds * streams[i]->info()->time_scale(),
nalu_length_size); nalu_length_size,
muxer_listener_);
continue; continue;
} }

View File

@ -8,6 +8,7 @@
#include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/mpd_notifier_util.h" #include "packager/mpd/base/mpd_notifier_util.h"
#include "packager/mpd/base/mpd_utils.h"
namespace edash_packager { namespace edash_packager {
@ -89,6 +90,8 @@ bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
if (!representation) if (!representation)
return false; return false;
representation_id_to_adaptation_set_[representation->id()] = adaptation_set;
SetGroupId(content_type, lang, adaptation_set); SetGroupId(content_type, lang, adaptation_set);
*container_id = representation->id(); *container_id = representation->id();
@ -123,6 +126,24 @@ bool DashIopMpdNotifier::NotifyNewSegment(uint32_t container_id,
return true; return true;
} }
bool DashIopMpdNotifier::NotifyEncryptionUpdate(
uint32_t container_id,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) {
base::AutoLock auto_lock(lock_);
RepresentationMap::iterator it = representation_map_.find(container_id);
if (it == representation_map_.end()) {
LOG(ERROR) << "Unexpected container_id: " << container_id;
return false;
}
AdaptationSet* adaptation_set_for_representation =
representation_id_to_adaptation_set_[it->second->id()];
adaptation_set_for_representation->UpdateContentProtectionPssh(
Uint8VectorToBase64(new_pssh));
return true;
}
bool DashIopMpdNotifier::AddContentProtectionElement( bool DashIopMpdNotifier::AddContentProtectionElement(
uint32_t container_id, uint32_t container_id,
const ContentProtectionElement& content_protection_element) { const ContentProtectionElement& content_protection_element) {

View File

@ -46,6 +46,10 @@ class DashIopMpdNotifier : public MpdNotifier {
uint64_t start_time, uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t size) OVERRIDE; uint64_t size) OVERRIDE;
virtual bool NotifyEncryptionUpdate(
uint32_t container_id,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) OVERRIDE;
virtual bool AddContentProtectionElement( virtual bool AddContentProtectionElement(
uint32_t id, uint32_t id,
const ContentProtectionElement& content_protection_element) OVERRIDE; const ContentProtectionElement& content_protection_element) OVERRIDE;
@ -110,6 +114,9 @@ class DashIopMpdNotifier : public MpdNotifier {
// Next group ID to use for AdapationSets that can be grouped. // Next group ID to use for AdapationSets that can be grouped.
int next_group_id_; int next_group_id_;
// Maps Representation ID to AdaptationSet. This is for updating the PSSH.
std::map<uint32_t, AdaptationSet*> representation_id_to_adaptation_set_;
}; };
} // namespace edash_packager } // namespace edash_packager

View File

@ -670,6 +670,60 @@ TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) {
ConvertToMediaInfo(kAudioContent), &unused_container_id)); ConvertToMediaInfo(kAudioContent), &unused_container_id));
} }
TEST_P(DashIopMpdNotifierTest, UpdateEncryption) {
const char kProtectedContent[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 640\n"
" height: 360\n"
" time_scale: 10\n"
" frame_duration: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"protected_content {\n"
" content_protection_entry {\n"
" uuid: 'myuuid'\n"
" name_version: 'MyContentProtection version 1'\n"
" pssh: 'pssh1'\n"
" }\n"
" default_key_id: '_default_key_id_'\n"
"}\n"
"container_type: 1\n";
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
empty_base_urls_, output_path_);
scoped_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder(mpd_type()));
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRole(_)).Times(0);
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(default_mock_representation_.get()));
uint32_t container_id;
SetMpdBuilder(&notifier, mock_mpd_builder.PassAs<MpdBuilder>());
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kProtectedContent),
&container_id));
::testing::Mock::VerifyAndClearExpectations(
default_mock_adaptation_set_.get());
const uint8_t kBogusNewPssh[] = {// "psshsomethingelse" as uint8 array.
0x70, 0x73, 0x73, 0x68, 0x73, 0x6f,
0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e,
0x67, 0x65, 0x6c, 0x73, 0x65};
const std::vector<uint8_t> kBogusNewPsshVector(
kBogusNewPssh, kBogusNewPssh + arraysize(kBogusNewPssh));
const char kBogusNewPsshInBase64[] = "cHNzaHNvbWV0aGluZ2Vsc2U=";
EXPECT_CALL(*default_mock_adaptation_set_,
UpdateContentProtectionPssh(kBogusNewPsshInBase64));
EXPECT_TRUE(notifier.NotifyEncryptionUpdate(
container_id, std::vector<uint8_t>(), kBogusNewPsshVector));
}
INSTANTIATE_TEST_CASE_P(StaticAndDynamic, INSTANTIATE_TEST_CASE_P(StaticAndDynamic,
DashIopMpdNotifierTest, DashIopMpdNotifierTest,
::testing::Values(MpdBuilder::kStatic, ::testing::Values(MpdBuilder::kStatic,

View File

@ -35,6 +35,7 @@ class MockAdaptationSet : public AdaptationSet {
MOCK_METHOD1(AddRepresentation, Representation*(const MediaInfo& media_info)); MOCK_METHOD1(AddRepresentation, Representation*(const MediaInfo& media_info));
MOCK_METHOD1(AddContentProtectionElement, MOCK_METHOD1(AddContentProtectionElement,
void(const ContentProtectionElement& element)); void(const ContentProtectionElement& element));
MOCK_METHOD1(UpdateContentProtectionPssh, void(const std::string& pssh));
MOCK_METHOD1(AddRole, void(AdaptationSet::Role role)); MOCK_METHOD1(AddRole, void(AdaptationSet::Role role));
MOCK_METHOD1(SetGroup, void(int group_number)); MOCK_METHOD1(SetGroup, void(int group_number));
@ -53,6 +54,7 @@ class MockRepresentation : public Representation {
MOCK_METHOD1(AddContentProtectionElement, MOCK_METHOD1(AddContentProtectionElement,
void(const ContentProtectionElement& element)); void(const ContentProtectionElement& element));
MOCK_METHOD1(UpdateContentProtectionPssh, void(const std::string& pssh));
MOCK_METHOD3(AddNewSegment, MOCK_METHOD3(AddNewSegment,
void(uint64_t start_time, uint64_t duration, uint64_t size)); void(uint64_t start_time, uint64_t duration, uint64_t size));
MOCK_METHOD1(SetSampleDuration, void(uint32_t sample_duration)); MOCK_METHOD1(SetSampleDuration, void(uint32_t sample_duration));

View File

@ -14,6 +14,7 @@
#include <list> #include <list>
#include <string> #include <string>
#include "packager/base/base64.h"
#include "packager/base/files/file_path.h" #include "packager/base/files/file_path.h"
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/memory/scoped_ptr.h" #include "packager/base/memory/scoped_ptr.h"
@ -696,6 +697,12 @@ void AdaptationSet::AddContentProtectionElement(
RemoveDuplicateAttributes(&content_protection_elements_.back()); RemoveDuplicateAttributes(&content_protection_elements_.back());
} }
void AdaptationSet::UpdateContentProtectionPssh(
const std::string& pssh) {
base::AutoLock scoped_lock(lock_);
UpdateContentProtectionPsshHelper(pssh, &content_protection_elements_);
}
void AdaptationSet::AddRole(Role role) { void AdaptationSet::AddRole(Role role) {
roles_.insert(role); roles_.insert(role);
} }
@ -1034,6 +1041,12 @@ void Representation::AddContentProtectionElement(
RemoveDuplicateAttributes(&content_protection_elements_.back()); RemoveDuplicateAttributes(&content_protection_elements_.back());
} }
void Representation::UpdateContentProtectionPssh(
const std::string& pssh) {
base::AutoLock scoped_lock(lock_);
UpdateContentProtectionPsshHelper(pssh, &content_protection_elements_);
}
void Representation::AddNewSegment(uint64_t start_time, void Representation::AddNewSegment(uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t size) { uint64_t size) {

View File

@ -30,7 +30,6 @@
#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/mpd_options.h" #include "packager/mpd/base/mpd_options.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" #include "packager/mpd/base/xml/scoped_xml_ptr.h"
@ -190,6 +189,15 @@ class AdaptationSet {
virtual void AddContentProtectionElement( virtual void AddContentProtectionElement(
const ContentProtectionElement& element); const ContentProtectionElement& element);
/// Update the <cenc:pssh> element for MP4 specific ContentProtection element.
/// If the element does not exist, this will add one.
/// @param pssh is the content of <cenc:pssh> element.
/// Note that DASH IF IOP mentions that this should be base64 encoded
/// string of the whole pssh box.
/// @attention This might get removed once DASH IF IOP specification writes
/// a clear guideline on how to handle key rotation.
virtual void UpdateContentProtectionPssh(const std::string& pssh);
/// Set the Role element for this AdaptationSet. /// Set the Role element for this AdaptationSet.
/// The Role element's is schemeIdUri='urn:mpeg:dash:role:2011'. /// The Role element's is schemeIdUri='urn:mpeg:dash:role:2011'.
/// See ISO/IEC 23009-1:2012 section 5.8.5.5. /// See ISO/IEC 23009-1:2012 section 5.8.5.5.
@ -437,6 +445,15 @@ class Representation {
virtual void AddContentProtectionElement( virtual void AddContentProtectionElement(
const ContentProtectionElement& element); const ContentProtectionElement& element);
/// Update the 'cenc:pssh' element for mp4 specific ContentProtection element.
/// If the element does not exist, this will add one.
/// @param pssh is the content of <cenc:pssh> element.
/// Note that DASH IF IOP mentions that this should be base64 encoded
/// string of the whole pssh box.
/// @attention This might get removed once DASH IF IOP specification makes a
/// a clear guideline on how to handle key rotation.
virtual void UpdateContentProtectionPssh(const std::string& pssh);
/// Add a media (sub)segment to the representation. /// Add a media (sub)segment to the representation.
/// AdaptationSet@{subSegmentAlignment,segmentAlignment} cannot be set /// AdaptationSet@{subSegmentAlignment,segmentAlignment} cannot be set
/// if this is not called for all Representations. /// if this is not called for all Representations.

View File

@ -1252,8 +1252,9 @@ TEST_F(CommonMpdBuilderTest, SetSampleDuration) {
representation.media_info_.video_info().frame_duration()); representation.media_info_.video_info().frame_duration());
} }
// Verify that AdaptationSet::AddContentProtection() works. // Verify that AdaptationSet::AddContentProtection() and
TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) { // UpdateContentProtectionPssh() works.
TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) {
const char kVideoMediaInfo1080p[] = const char kVideoMediaInfo1080p[] =
"video_info {\n" "video_info {\n"
" codec: \"avc1\"\n" " codec: \"avc1\"\n"
@ -1264,7 +1265,7 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) {
"}\n" "}\n"
"container_type: 1\n"; "container_type: 1\n";
ContentProtectionElement content_protection; ContentProtectionElement content_protection;
content_protection.scheme_id_uri = "someuri"; content_protection.scheme_id_uri = "urn:mpeg:dash:mp4protection:2011";
content_protection.value = "some value"; content_protection.value = "some value";
Element pssh; Element pssh;
pssh.name = "cenc:pssh"; pssh.name = "cenc:pssh";
@ -1277,7 +1278,7 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) {
ConvertToMediaInfo(kVideoMediaInfo1080p))); ConvertToMediaInfo(kVideoMediaInfo1080p)));
video_adaptation_set->AddContentProtectionElement(content_protection); video_adaptation_set->AddContentProtectionElement(content_protection);
const char kExpectedOutput[] = const char kExpectedOutput1[] =
"<?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:cenc=\"urn:mpeg:cenc:2013\"" " xmlns:cenc=\"urn:mpeg:cenc:2013\""
@ -1290,7 +1291,9 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) {
" <Period>" " <Period>"
" <AdaptationSet id=\"0\" contentType=\"video\" width=\"1920\"" " <AdaptationSet id=\"0\" contentType=\"video\" width=\"1920\""
" height=\"1080\" frameRate=\"3000/100\">" " height=\"1080\" frameRate=\"3000/100\">"
" <ContentProtection schemeIdUri=\"someuri\" value=\"some value\">" " <ContentProtection"
" schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\""
" value=\"some value\">"
" <cenc:pssh>any value</cenc:pssh>" " <cenc:pssh>any value</cenc:pssh>"
" </ContentProtection>" " </ContentProtection>"
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\"" " <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
@ -1301,7 +1304,35 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) {
"</MPD>"; "</MPD>";
std::string mpd_output; std::string mpd_output;
ASSERT_TRUE(mpd_.ToString(&mpd_output)); ASSERT_TRUE(mpd_.ToString(&mpd_output));
EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output)); EXPECT_TRUE(XmlEqual(kExpectedOutput1, mpd_output));
video_adaptation_set->UpdateContentProtectionPssh("new pssh value");
const char kExpectedOutput2[] =
"<?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=\"urn:mpeg:dash:mp4protection:2011\""
" value=\"some value\">"
" <cenc:pssh>new pssh value</cenc:pssh>"
" </ContentProtection>"
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
" mimeType=\"video/mp4\" width=\"1920\" height=\"1080\""
" frameRate=\"3000/100\"/>"
" </AdaptationSet>"
" </Period>"
"</MPD>";
ASSERT_TRUE(mpd_.ToString(&mpd_output));
EXPECT_TRUE(XmlEqual(kExpectedOutput2, mpd_output));
} }
// Add one video check the output. // Add one video check the output.

View File

@ -11,6 +11,7 @@
#define MPD_BASE_MPD_NOTIFIER_H_ #define MPD_BASE_MPD_NOTIFIER_H_
#include <stdint.h> #include <stdint.h>
#include <vector>
#include "packager/base/macros.h" #include "packager/base/macros.h"
@ -73,6 +74,18 @@ class MpdNotifier {
uint64_t duration, uint64_t duration,
uint64_t size) = 0; uint64_t size) = 0;
/// Notifiers MpdBuilder that there is a new PSSH for the container.
/// This may be called whenever the key has to change, e.g. key rotation.
/// @param container_id Container ID obtained from calling
/// NotifyNewContainer().
/// @param new_key_id is the new key ID for the key.
/// @param new_pssh is the new pssh box (including the header).
/// @attention This might change or get removed once DASH IF IOP specification
/// writes a clear guideline on how to handle key rotation.
virtual bool NotifyEncryptionUpdate(uint32_t container_id,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) = 0;
/// Adds content protection information to the MPD. /// Adds content protection information to the MPD.
/// @param container_id is the nummeric container ID obtained from calling /// @param container_id is the nummeric container ID obtained from calling
/// NotifyNewContainer(). /// NotifyNewContainer().

View File

@ -17,118 +17,6 @@ namespace edash_packager {
using media::File; using media::File;
using media::FileCloser; using media::FileCloser;
namespace {
// Helper function for adding ContentProtection for AdaptatoinSet or
// Representation.
// Works because both classes have AddContentProtectionElement().
template <typename ContentProtectionParent>
void AddContentProtectionElementsHelper(const MediaInfo& media_info,
ContentProtectionParent* parent) {
DCHECK(parent);
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.";
}
}
parent->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);
}
parent->AddContentProtectionElement(drm_content_protection);
}
LOG_IF(WARNING, protected_content.content_protection_entry().size() == 0)
<< "The media is encrypted but no content protection specified.";
}
} // 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) << "UUID size is expected to be " << kExpectedUUIDSize
<< " but is " << data.size() << " and the data in hex is "
<< base::HexEncode(data.data(), 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;
}
bool WriteMpdToFile(const std::string& output_path, MpdBuilder* mpd_builder) { bool WriteMpdToFile(const std::string& output_path, MpdBuilder* mpd_builder) {
CHECK(!output_path.empty()); CHECK(!output_path.empty());
@ -177,14 +65,11 @@ ContentType GetContentType(const MediaInfo& media_info) {
: (has_audio ? kContentTypeAudio : kContentTypeText); : (has_audio ? kContentTypeAudio : kContentTypeText);
} }
void AddContentProtectionElements(const MediaInfo& media_info, std::string Uint8VectorToBase64(const std::vector<uint8_t>& input) {
AdaptationSet* adaptation_set) { std::string output;
AddContentProtectionElementsHelper(media_info, adaptation_set); std::string input_in_string(input.begin(), input.end());
} base::Base64Encode(input_in_string, &output);
return output;
void AddContentProtectionElements(const MediaInfo& media_info,
Representation* representation) {
AddContentProtectionElementsHelper(media_info, representation);
} }
} // namespace edash_packager } // namespace edash_packager

View File

@ -11,6 +11,7 @@
#define MPD_BASE_MPD_NOTIFIER_UTIL_H_ #define MPD_BASE_MPD_NOTIFIER_UTIL_H_
#include <string> #include <string>
#include <vector>
#include "packager/base/base64.h" #include "packager/base/base64.h"
#include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/media_info.pb.h"
@ -25,11 +26,6 @@ enum ContentType {
kContentTypeText kContentTypeText
}; };
/// Converts hex data to UUID format. Hex data must be size 16.
/// @param data input hex data.
/// @param uuid_format is the UUID format of the input.
bool HexToUUID(const std::string& data, std::string* uuid_format);
/// Outputs MPD to @a output_path. /// Outputs MPD to @a output_path.
/// @param output_path is the path to the MPD output location. /// @param output_path is the path to the MPD output location.
/// @param mpd_builder is the MPD builder instance. /// @param mpd_builder is the MPD builder instance.
@ -40,23 +36,8 @@ bool WriteMpdToFile(const std::string& output_path, MpdBuilder* mpd_builder);
/// @return content type of the @a media_info. /// @return content type of the @a media_info.
ContentType GetContentType(const MediaInfo& media_info); ContentType GetContentType(const MediaInfo& media_info);
/// Adds <ContentProtection> elements specified by @a media_info to /// Converts uint8 vector into base64 encoded string.
/// @a adaptation_set. std::string Uint8VectorToBase64(const std::vector<uint8_t>& input);
/// Note that this will add the elements as direct chlidren of AdaptationSet.
/// @param media_info may or may not have protected_content field.
/// @param adaptation_set is the parent element that owns the ContentProtection
/// elements.
void AddContentProtectionElements(const MediaInfo& media_info,
AdaptationSet* adaptation_set);
/// Adds <ContentProtection> elements specified by @a media_info to
/// @a representation.
/// @param media_info may or may not have protected_content field.
/// @param representation is the parent element that owns the ContentProtection
/// elements.
void AddContentProtectionElements(const MediaInfo& media_info,
Representation* representation);
} // namespace edash_packager } // namespace edash_packager

View File

@ -10,8 +10,6 @@
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_number_conversions.h"
#include "packager/mpd/base/content_protection_element.h"
#include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/xml/scoped_xml_ptr.h" #include "packager/mpd/base/xml/scoped_xml_ptr.h"
namespace edash_packager { namespace edash_packager {
@ -95,4 +93,160 @@ bool OnlyOneTrue(bool b1, bool b2, bool b3) {
return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3); return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3);
} }
// 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) << "UUID size is expected to be " << kExpectedUUIDSize
<< " but is " << data.size() << " and the data in hex is "
<< base::HexEncode(data.data(), 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;
}
void UpdateContentProtectionPsshHelper(
const std::string& pssh,
std::list<ContentProtectionElement>* conetent_protection_elements) {
for (std::list<ContentProtectionElement>::iterator protection =
conetent_protection_elements->begin();
protection != conetent_protection_elements->end(); ++protection) {
if (protection->scheme_id_uri != kEncryptedMp4Scheme)
continue;
for (std::vector<Element>::iterator subelement =
protection->subelements.begin();
subelement != protection->subelements.end(); ++subelement) {
if (subelement->name == kPsshElementName) {
subelement->content = pssh;
return;
}
}
// Reaching here means <cenc:pssh> does not exist under the MP4 specific
// ContentProtection. Add it.
Element cenc_pssh;
cenc_pssh.name = kPsshElementName;
cenc_pssh.content = pssh;
return;
}
// Reaching here means that MP4 specific ContentProtection does not exist.
// Add it.
ContentProtectionElement content_protection;
content_protection.scheme_id_uri = kEncryptedMp4Scheme;
content_protection.value = kEncryptedMp4Value;
Element cenc_pssh;
cenc_pssh.name = kPsshElementName;
cenc_pssh.content = pssh;
content_protection.subelements.push_back(cenc_pssh);
conetent_protection_elements->push_back(content_protection);
return;
}
namespace {
// Helper function. This works because Representation and AdaptationSet both
// have AddContentProtectionElement().
template <typename ContentProtectionParent>
void AddContentProtectionElementsHelperTemplated(
const MediaInfo& media_info,
ContentProtectionParent* parent) {
DCHECK(parent);
if (!media_info.has_protected_content())
return;
const MediaInfo::ProtectedContent& protected_content =
media_info.protected_content();
// 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 = kEncryptedMp4Scheme;
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.";
}
}
parent->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 = kPsshElementName;
cenc_pssh.content = base64_encoded_pssh;
drm_content_protection.subelements.push_back(cenc_pssh);
}
parent->AddContentProtectionElement(drm_content_protection);
}
LOG_IF(WARNING, protected_content.content_protection_entry().size() == 0)
<< "The media is encrypted but no content protection specified.";
}
} // namespace
void AddContentProtectionElements(const MediaInfo& media_info,
Representation* parent) {
AddContentProtectionElementsHelperTemplated(media_info, parent);
}
void AddContentProtectionElements(const MediaInfo& media_info,
AdaptationSet* parent) {
AddContentProtectionElementsHelperTemplated(media_info, parent);
}
} // namespace edash_packager } // namespace edash_packager

View File

@ -11,14 +11,25 @@
#include <libxml/tree.h> #include <libxml/tree.h>
#include <list>
#include <string> #include <string>
#include "packager/base/base64.h"
#include "packager/base/strings/string_util.h"
#include "packager/mpd/base/content_protection_element.h"
#include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/mpd_builder.h"
namespace edash_packager { namespace edash_packager {
class MediaInfo; class MediaInfo;
struct ContentProtectionElement; struct ContentProtectionElement;
struct SegmentInfo; struct SegmentInfo;
const char kEncryptedMp4Scheme[] = "urn:mpeg:dash:mp4protection:2011";
const char kPsshElementName[] = "cenc:pssh";
const char kEncryptedMp4Value[] = "cenc";
bool HasVODOnlyFields(const MediaInfo& media_info); bool HasVODOnlyFields(const MediaInfo& media_info);
bool HasLiveOnlyFields(const MediaInfo& media_info); bool HasLiveOnlyFields(const MediaInfo& media_info);
@ -42,6 +53,34 @@ bool MoreThanOneTrue(bool b1, bool b2, bool b3);
bool AtLeastOneTrue(bool b1, bool b2, bool b3); bool AtLeastOneTrue(bool b1, bool b2, bool b3);
bool OnlyOneTrue(bool b1, bool b2, bool b3); bool OnlyOneTrue(bool b1, bool b2, bool b3);
/// Converts hex data to UUID format. Hex data must be size 16.
/// @param data input hex data.
/// @param uuid_format is the UUID format of the input.
bool HexToUUID(const std::string& data, std::string* uuid_format);
// Update the <cenc:pssh> element for MP4 specific ContentProtection element.
// If the element does not exist, this will add one.
void UpdateContentProtectionPsshHelper(
const std::string& pssh,
std::list<ContentProtectionElement>* conetent_protection_elements);
/// Adds <ContentProtection> elements specified by @a media_info to
/// @a adaptation_set.
/// Note that this will add the elements as direct chlidren of AdaptationSet.
/// @param media_info may or may not have protected_content field.
/// @param adaptation_set is the parent element that owns the ContentProtection
/// elements.
void AddContentProtectionElements(const MediaInfo& media_info,
Representation* parent);
/// Adds <ContentProtection> elements specified by @a media_info to
/// @a representation.
/// @param media_info may or may not have protected_content field.
/// @param representation is the parent element that owns the ContentProtection
/// elements.
void AddContentProtectionElements(const MediaInfo& media_info,
AdaptationSet* parent);
} // namespace edash_packager } // namespace edash_packager
#endif // MPD_BASE_MPD_UTILS_H_ #endif // MPD_BASE_MPD_UTILS_H_

View File

@ -97,6 +97,20 @@ bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id,
return true; return true;
} }
bool SimpleMpdNotifier::NotifyEncryptionUpdate(
uint32_t container_id,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) {
base::AutoLock auto_lock(lock_);
RepresentationMap::iterator it = representation_map_.find(container_id);
if (it == representation_map_.end()) {
LOG(ERROR) << "Unexpected container_id: " << container_id;
return false;
}
it->second->UpdateContentProtectionPssh(Uint8VectorToBase64(new_pssh));
return true;
}
bool SimpleMpdNotifier::AddContentProtectionElement( bool SimpleMpdNotifier::AddContentProtectionElement(
uint32_t container_id, uint32_t container_id,
const ContentProtectionElement& content_protection_element) { const ContentProtectionElement& content_protection_element) {

View File

@ -47,6 +47,10 @@ class SimpleMpdNotifier : public MpdNotifier {
uint64_t start_time, uint64_t start_time,
uint64_t duration, uint64_t duration,
uint64_t size) OVERRIDE; uint64_t size) OVERRIDE;
virtual bool NotifyEncryptionUpdate(
uint32_t container_id,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) OVERRIDE;
virtual bool AddContentProtectionElement( virtual bool AddContentProtectionElement(
uint32_t id, uint32_t id,
const ContentProtectionElement& content_protection_element) OVERRIDE; const ContentProtectionElement& content_protection_element) OVERRIDE;

View File

@ -236,6 +236,60 @@ TEST_F(SimpleMpdNotifierTest, AddContentProtectionElement) {
EXPECT_TRUE(notifier.AddContentProtectionElement(kRepresentationId, element)); EXPECT_TRUE(notifier.AddContentProtectionElement(kRepresentationId, element));
} }
TEST_P(SimpleMpdNotifierTest, UpdateEncryption) {
const char kProtectedContent[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 640\n"
" height: 360\n"
" time_scale: 10\n"
" frame_duration: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"protected_content {\n"
" content_protection_entry {\n"
" uuid: 'myuuid'\n"
" name_version: 'MyContentProtection version 1'\n"
" pssh: 'pssh1'\n"
" }\n"
" default_key_id: '_default_key_id_'\n"
"}\n"
"container_type: 1\n";
SimpleMpdNotifier notifier(kLiveProfile, empty_mpd_option_, empty_base_urls_,
output_path_);
const uint32_t kRepresentationId = 447834u;
scoped_ptr<MockMpdBuilder> mock_mpd_builder(DynamicMpdBuilderMock());
scoped_ptr<MockRepresentation> mock_representation(
new MockRepresentation(kRepresentationId));
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(mock_representation.get()));
uint32_t container_id;
SetMpdBuilder(&notifier, mock_mpd_builder.PassAs<MpdBuilder>());
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kProtectedContent),
&container_id));
::testing::Mock::VerifyAndClearExpectations(
default_mock_adaptation_set_.get());
// "psshsomethingelse" as uint8 array.
const uint8_t kBogusNewPssh[] = {0x70, 0x73, 0x73, 0x68, 0x73, 0x6f,
0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e,
0x67, 0x65, 0x6c, 0x73, 0x65};
const std::vector<uint8_t> kBogusNewPsshVector(
kBogusNewPssh, kBogusNewPssh + arraysize(kBogusNewPssh));
const char kBogusNewPsshInBase64[] = "cHNzaHNvbWV0aGluZ2Vsc2U=";
EXPECT_CALL(*mock_representation,
UpdateContentProtectionPssh(kBogusNewPsshInBase64));
EXPECT_TRUE(notifier.NotifyEncryptionUpdate(
container_id, std::vector<uint8_t>(), kBogusNewPsshVector));
}
INSTANTIATE_TEST_CASE_P(StaticAndDynamic, INSTANTIATE_TEST_CASE_P(StaticAndDynamic,
SimpleMpdNotifierTest, SimpleMpdNotifierTest,
::testing::Values(MpdBuilder::kStatic, ::testing::Values(MpdBuilder::kStatic,

View File

@ -11,6 +11,7 @@
#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"
using edash_packager::media::File; using edash_packager::media::File;