More fix for live

- Neither live with or without key rotation did not output the right
  result.
- No key rotation: did not produce any ContentProtection element.
- With key rotation: did not have DRM specific ContentProtection.
- To get key rotation working with shaka player, <cenc:pssh> elements
  are removed.

Change-Id: I1f34d303ae1f3ea81820e3368ab66b8420498372
This commit is contained in:
Rintaro Kuroiwa 2015-09-24 15:03:52 -07:00
parent 8a850af9cd
commit 76e5be3756
23 changed files with 407 additions and 93 deletions

View File

@ -33,24 +33,25 @@ void MpdNotifyMuxerListener::SetContentProtectionSchemeIdUri(
}
void MpdNotifyMuxerListener::OnEncryptionInfoReady(
bool is_initial_encryption_info,
const std::string& content_protection_uuid,
const std::string& content_protection_name_version,
const std::vector<uint8_t>& default_key_id,
const std::vector<uint8_t>& key_id,
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.";
if (is_initial_encryption_info) {
LOG_IF(WARNING, is_encrypted_)
<< "Updating initial encryption information.";
content_protection_uuid_ = content_protection_uuid;
content_protection_name_version_ = content_protection_name_version;
default_key_id_.assign(key_id.begin(), key_id.end());
pssh_.assign(pssh.begin(), pssh.end());
is_encrypted_ = true;
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_name_version_ = content_protection_name_version;
default_key_id_.assign(default_key_id.begin(), default_key_id.end());
pssh_.assign(pssh.begin(), pssh.end());
is_encrypted_ = true;
bool updated = mpd_notifier_->NotifyEncryptionUpdate(
notification_id_, content_protection_uuid, key_id, pssh);
LOG_IF(WARNING, !updated) << "Failed to update encryption info.";
}
void MpdNotifyMuxerListener::OnMediaStart(

View File

@ -28,7 +28,7 @@ class MpdNotifyMuxerListener : public MuxerListener {
public:
/// @param mpd_notifier must be initialized, i.e mpd_notifier->Init() must be
/// called.
MpdNotifyMuxerListener(MpdNotifier* mpd_notifier);
explicit MpdNotifyMuxerListener(MpdNotifier* mpd_notifier);
virtual ~MpdNotifyMuxerListener();
/// If the stream is encrypted use this as 'schemeIdUri' attribute for
@ -38,9 +38,10 @@ class MpdNotifyMuxerListener : public MuxerListener {
/// @name MuxerListener implementation overrides.
/// @{
virtual void OnEncryptionInfoReady(
bool is_initial_encryption_info,
const std::string& content_protection_uuid,
const std::string& content_protection_name_version,
const std::vector<uint8_t>& default_key_id,
const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& pssh) OVERRIDE;
virtual void OnMediaStart(const MuxerOptions& muxer_options,
const StreamInfo& stream_info,

View File

@ -27,6 +27,15 @@ namespace edash_packager {
namespace {
// Can be any string, we just want to check that it is preserved in the
// protobuf.
const char kTestUUID[] = "somebogusuuid";
const char kDrmName[] = "drmname";
const char kDefaultKeyId[] = "defaultkeyid";
const char kPssh[] = "pssh";
const bool kInitialEncryptionInfo = true;
const bool kNonInitialEncryptionInfo = false;
// TODO(rkuroiwa): This is copied from mpd_builder_test_helper.cc. Make a
// common target that only has mpd_builder_test_helper and its dependencies
// so the two test targets can share this.
@ -37,6 +46,18 @@ MediaInfo ConvertToMediaInfo(const std::string& media_info_string) {
return media_info;
}
void SetDefaultLiveMuxerOptionsValues(media::MuxerOptions* muxer_options) {
muxer_options->single_segment = false;
muxer_options->segment_duration = 10.0;
muxer_options->fragment_duration = 10.0;
muxer_options->segment_sap_aligned = true;
muxer_options->fragment_sap_aligned = true;
muxer_options->num_subsegments_per_sidx = 0;
muxer_options->output_file_name = "liveinit.mp4";
muxer_options->segment_template = "live-$NUMBER$.mp4";
muxer_options->temp_dir.clear();
}
} // namespace
namespace media {
@ -44,9 +65,14 @@ namespace media {
class MpdNotifyMuxerListenerTest : public ::testing::Test {
public:
// Set up objects for VOD profile.
void SetupForVod() {
notifier_.reset(new MockMpdNotifier(kOnDemandProfile));
listener_.reset(
new MpdNotifyMuxerListener(notifier_.get()));
}
void SetupForLive() {
notifier_.reset(new MockMpdNotifier(kLiveProfile));
listener_.reset(new MpdNotifyMuxerListener(notifier_.get()));
}
@ -124,12 +150,6 @@ TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContent) {
scoped_refptr<StreamInfo> video_stream_info =
CreateVideoStreamInfo(video_params);
// Can be anystring, we just want to check that it is preserved in the
// protobuf.
const char kTestUUID[] = "somebogusuuid";
const char kDrmName[] = "drmname";
const char kDefaultKeyId[] = "defaultkeyid";
const char kPssh[] = "pssh";
const std::vector<uint8_t> default_key_id(
kDefaultKeyId, kDefaultKeyId + arraysize(kDefaultKeyId) - 1);
const std::vector<uint8_t> pssh(kPssh, kPssh + arraysize(kPssh) - 1);
@ -146,7 +166,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContent) {
"}\n";
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0);
listener_->OnEncryptionInfoReady(kTestUUID, kDrmName, default_key_id, pssh);
listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, kTestUUID, kDrmName,
default_key_id, pssh);
listener_->OnMediaStart(muxer_options, *video_stream_info,
kDefaultReferenceTimeScale,
@ -244,7 +265,137 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) {
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
}
// TODO(rkuroiwa): Add tests for live.
// Live without key rotation. Note that OnEncryptionInfoReady() is called before
// OnMediaStart() but no more calls.
TEST_F(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) {
SetupForLive();
MuxerOptions muxer_options;
SetDefaultLiveMuxerOptionsValues(&muxer_options);
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
scoped_refptr<StreamInfo> video_stream_info =
CreateVideoStreamInfo(video_params);
const char kExpectedMediaInfo[] =
"video_info {\n"
" codec: \"avc1.010101\"\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"init_segment_name: \"liveinit.mp4\"\n"
"segment_template: \"live-$NUMBER$.mp4\"\n"
"reference_time_scale: 1000\n"
"container_type: CONTAINER_MP4\n"
"protected_content {\n"
" default_key_id: \"defaultkeyid\"\n"
" content_protection_entry {\n"
" uuid: \"somebogusuuid\"\n"
" name_version: \"drmname\"\n"
" pssh: \"pssh\"\n"
" }\n"
"}\n";
const uint64_t kStartTime1 = 0u;
const uint64_t kDuration1 = 1000u;
const uint64_t kSegmentFileSize1 = 29812u;
const uint64_t kStartTime2 = 1001u;
const uint64_t kDuration2 = 3787u;
const uint64_t kSegmentFileSize2 = 83743u;
const std::vector<uint8_t> default_key_id(
kDefaultKeyId, kDefaultKeyId + arraysize(kDefaultKeyId) - 1);
const std::vector<uint8_t> pssh(kPssh, kPssh + arraysize(kPssh) - 1);
InSequence s;
EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(0);
EXPECT_CALL(*notifier_,
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
.Times(1);
EXPECT_CALL(*notifier_,
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1));
EXPECT_CALL(*notifier_, Flush());
EXPECT_CALL(*notifier_,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2));
EXPECT_CALL(*notifier_, Flush());
listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, kTestUUID, kDrmName,
default_key_id, pssh);
listener_->OnMediaStart(muxer_options, *video_stream_info,
kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4);
listener_->OnNewSegment(kStartTime1, kDuration1, kSegmentFileSize1);
listener_->OnNewSegment(kStartTime2, kDuration2, kSegmentFileSize2);
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
EXPECT_CALL(*notifier_, Flush()).Times(0);
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
}
// Live with key rotation. Note that OnEncryptionInfoReady() is called before
// and after OnMediaStart().
TEST_F(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) {
SetupForLive();
MuxerOptions muxer_options;
SetDefaultLiveMuxerOptionsValues(&muxer_options);
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
scoped_refptr<StreamInfo> video_stream_info =
CreateVideoStreamInfo(video_params);
// Note that this media info has protected_content with default key id.
const char kExpectedMediaInfo[] =
"video_info {\n"
" codec: \"avc1.010101\"\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"init_segment_name: \"liveinit.mp4\"\n"
"segment_template: \"live-$NUMBER$.mp4\"\n"
"reference_time_scale: 1000\n"
"container_type: CONTAINER_MP4\n"
"protected_content {\n"
" default_key_id: \"defaultkeyid\"\n"
"}\n";
const uint64_t kStartTime1 = 0u;
const uint64_t kDuration1 = 1000u;
const uint64_t kSegmentFileSize1 = 29812u;
const uint64_t kStartTime2 = 1001u;
const uint64_t kDuration2 = 3787u;
const uint64_t kSegmentFileSize2 = 83743u;
const std::vector<uint8_t> default_key_id(
kDefaultKeyId, kDefaultKeyId + arraysize(kDefaultKeyId) - 1);
const std::vector<uint8_t> pssh(kPssh, kPssh + arraysize(kPssh) - 1);
InSequence s;
EXPECT_CALL(*notifier_,
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
.Times(1);
EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1);
EXPECT_CALL(*notifier_,
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1));
EXPECT_CALL(*notifier_, Flush());
EXPECT_CALL(*notifier_,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2));
EXPECT_CALL(*notifier_, Flush());
listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, "", "",
default_key_id, std::vector<uint8_t>());
listener_->OnMediaStart(muxer_options, *video_stream_info,
kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4);
listener_->OnEncryptionInfoReady(kNonInitialEncryptionInfo, kTestUUID,
kDrmName, std::vector<uint8_t>(), pssh);
listener_->OnNewSegment(kStartTime1, kDuration1, kSegmentFileSize1);
listener_->OnNewSegment(kStartTime2, kDuration2, kSegmentFileSize2);
::testing::Mock::VerifyAndClearExpectations(notifier_.get());
EXPECT_CALL(*notifier_, Flush()).Times(0);
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
}
} // namespace media
} // namespace edash_packager

View File

@ -13,6 +13,7 @@
#include <stdint.h>
#include <string>
#include <vector>
namespace edash_packager {
@ -42,17 +43,25 @@ class MuxerListener {
// |content_protection_uuid| is one of the UUIDs listed here
// http://dashif.org/identifiers/protection/. This should be in human
// readable form.
// |is_initial_encryption_info| is true if this is the first encryption info
// for the media.
// In general, this flag should always be true for non-key-rotated media and
// should be called only once.
// |content_protection_name_version| is the DRM system and version name.
// |key_id| is the key ID for the media.
// The format should be a vector of uint8_t, i.e. not (necessarily) human
// readable hex string.
// For ISO BMFF (MP4) media:
// |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.
// If |is_initial_encryption_info| is true then |key_id| is the default_KID in
// 'tenc' box.
// If |is_initial_encryption_info| is false then |key_id| is the new key ID
// for the for the next crypto period.
// |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(
bool is_initial_encryption_info,
const std::string& content_protection_uuid,
const std::string& content_protection_name_version,
const std::vector<uint8_t>& default_key_id,
const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& pssh) = 0;
// Called when muxing starts.

View File

@ -30,10 +30,14 @@ void VodMediaInfoDumpMuxerListener::SetContentProtectionSchemeIdUri(
}
void VodMediaInfoDumpMuxerListener::OnEncryptionInfoReady(
bool is_initial_encryption_info,
const std::string& content_protection_uuid,
const std::string& content_protection_name_version,
const std::vector<uint8_t>& default_key_id,
const std::vector<uint8_t>& pssh) {
LOG_IF(WARNING, !is_initial_encryption_info)
<< "Updating (non initial) encryption info is not supported by "
"this module.";
content_protection_uuid_ = content_protection_uuid;
content_protection_name_version_ = content_protection_name_version;
default_key_id_.assign(default_key_id.begin(), default_key_id.end());

View File

@ -37,6 +37,7 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
/// @name MuxerListener implementation overrides.
/// @{
virtual void OnEncryptionInfoReady(
bool is_initial_encryption_info,
const std::string& content_protection_uuid,
const std::string& content_protection_name_version,
const std::vector<uint8_t>& default_key_id,

View File

@ -33,6 +33,7 @@ const uint8_t kInvalidPssh[] = {
// format but the protobof generation shouldn't care.
const char kTestUUID[] = "myuuid";
const char kTestContentProtectionName[] = "MyContentProtection version 1";
const bool kInitialEncryptionInfo = true;
} // namespace
namespace edash_packager {
@ -89,7 +90,8 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test {
std::vector<uint8_t> invalid_pssh(kInvalidPssh,
kInvalidPssh + arraysize(kInvalidPssh));
listener_->OnEncryptionInfoReady(kTestUUID, kTestContentProtectionName,
listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, kTestUUID,
kTestContentProtectionName,
bogus_default_key_id, invalid_pssh);
}
listener_->OnMediaStart(muxer_options, stream_info, kReferenceTimeScale,

View File

@ -13,6 +13,10 @@ namespace edash_packager {
namespace media {
namespace mp4 {
namespace {
const bool kInitialEncryptionInfo = false;
} // namespace
KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof,
TrackFragment* traf,
KeySource* encryption_key_source,
@ -62,6 +66,7 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption(
if (muxer_listener_) {
muxer_listener_->OnEncryptionInfoReady(
!kInitialEncryptionInfo,
encryption_key_source_->UUID(), encryption_key_source_->SystemName(),
encryption_key()->key_id, encryption_key()->pssh);
}

View File

@ -33,6 +33,14 @@ const size_t kCencKeyIdSize = 16u;
// The version of cenc implemented here. CENC 4.
const int kCencSchemeVersion = 0x00010000;
// The default KID for key rotation is all 0s.
const uint8_t kKeyRotationDefaultKeyId[] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
COMPILE_ASSERT(arraysize(kKeyRotationDefaultKeyId) == kCencKeyIdSize,
cenc_key_id_must_be_size_16);
uint64_t Rescale(uint64_t time_in_old_scale,
uint32_t old_scale,
uint32_t new_scale) {
@ -81,16 +89,6 @@ void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key,
}
}
void GenerateEncryptedSampleEntryForKeyRotation(
double clear_lead_in_seconds,
SampleDescription* description) {
// Fill encrypted sample entry with default key.
EncryptionKey encryption_key;
encryption_key.key_id.assign(kCencKeyIdSize, 0);
GenerateEncryptedSampleEntry(
encryption_key, clear_lead_in_seconds, description);
}
uint8_t GetNaluLengthSize(const StreamInfo& stream_info) {
if (stream_info.stream_type() != kStreamVideo)
return 0;
@ -127,8 +125,7 @@ Segmenter::Segmenter(const MuxerOptions& options,
progress_listener_(NULL),
progress_target_(0),
accumulated_progress_(0),
sample_duration_(0u) {
}
sample_duration_(0u) {}
Segmenter::~Segmenter() { STLDeleteElements(&fragmenters_); }
@ -147,6 +144,9 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
moof_->tracks.resize(streams.size());
segment_durations_.resize(streams.size());
fragmenters_.resize(streams.size());
const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0;
const bool kInitialEncryptionInfo = true;
for (uint32_t i = 0; i < streams.size(); ++i) {
stream_map_[streams[i]] = i;
moof_->tracks[i].header.track_id = i + 1;
@ -166,10 +166,20 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
SampleDescription& description =
moov_->tracks[i].media.information.sample_table.description;
const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0;
if (key_rotation_enabled) {
GenerateEncryptedSampleEntryForKeyRotation(clear_lead_in_seconds,
&description);
// Fill encrypted sample entry with default key.
EncryptionKey encryption_key;
encryption_key.key_id.assign(
kKeyRotationDefaultKeyId,
kKeyRotationDefaultKeyId + arraysize(kKeyRotationDefaultKeyId));
GenerateEncryptedSampleEntry(encryption_key, clear_lead_in_seconds,
&description);
if (muxer_listener_) {
muxer_listener_->OnEncryptionInfoReady(
kInitialEncryptionInfo, encryption_key_source->UUID(),
encryption_key_source->SystemName(), encryption_key.key_id,
std::vector<uint8_t>());
}
fragmenters_[i] = new KeyRotationFragmenter(
moof_.get(),
@ -189,8 +199,8 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
if (!status.ok())
return status;
GenerateEncryptedSampleEntry(
*encryption_key, clear_lead_in_seconds, &description);
GenerateEncryptedSampleEntry(*encryption_key, clear_lead_in_seconds,
&description);
// One and only one pssh box is needed.
if (moov_->pssh.empty()) {
@ -200,6 +210,7 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
// Also only one default key id.
if (muxer_listener_) {
muxer_listener_->OnEncryptionInfoReady(
kInitialEncryptionInfo,
encryption_key_source->UUID(), encryption_key_source->SystemName(),
encryption_key->key_id, encryption_key->pssh);
}

View File

@ -128,6 +128,7 @@ bool DashIopMpdNotifier::NotifyNewSegment(uint32_t container_id,
bool DashIopMpdNotifier::NotifyEncryptionUpdate(
uint32_t container_id,
const std::string& drm_uuid,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) {
base::AutoLock auto_lock(lock_);
@ -140,7 +141,7 @@ bool DashIopMpdNotifier::NotifyEncryptionUpdate(
AdaptationSet* adaptation_set_for_representation =
representation_id_to_adaptation_set_[it->second->id()];
adaptation_set_for_representation->UpdateContentProtectionPssh(
Uint8VectorToBase64(new_pssh));
drm_uuid, Uint8VectorToBase64(new_pssh));
return true;
}

View File

@ -48,6 +48,7 @@ class DashIopMpdNotifier : public MpdNotifier {
uint64_t size) OVERRIDE;
virtual bool NotifyEncryptionUpdate(
uint32_t container_id,
const std::string& drm_uuid,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) OVERRIDE;
virtual bool AddContentProtectionElement(

View File

@ -720,9 +720,10 @@ TEST_P(DashIopMpdNotifierTest, UpdateEncryption) {
const char kBogusNewPsshInBase64[] = "cHNzaHNvbWV0aGluZ2Vsc2U=";
EXPECT_CALL(*default_mock_adaptation_set_,
UpdateContentProtectionPssh(StrEq(kBogusNewPsshInBase64)));
UpdateContentProtectionPssh(StrEq("myuuid"),
StrEq(kBogusNewPsshInBase64)));
EXPECT_TRUE(notifier.NotifyEncryptionUpdate(
container_id, std::vector<uint8_t>(), kBogusNewPsshVector));
container_id, "myuuid", std::vector<uint8_t>(), kBogusNewPsshVector));
}
INSTANTIATE_TEST_CASE_P(StaticAndDynamic,

View File

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

View File

@ -31,8 +31,9 @@ class MockMpdNotifier : public MpdNotifier {
uint64_t start_time,
uint64_t duration,
uint64_t size));
MOCK_METHOD3(NotifyEncryptionUpdate,
MOCK_METHOD4(NotifyEncryptionUpdate,
bool(uint32_t container_id,
const std::string& drm_uuid,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh));
MOCK_METHOD2(

View File

@ -697,10 +697,11 @@ void AdaptationSet::AddContentProtectionElement(
RemoveDuplicateAttributes(&content_protection_elements_.back());
}
void AdaptationSet::UpdateContentProtectionPssh(
const std::string& pssh) {
void AdaptationSet::UpdateContentProtectionPssh(const std::string& drm_uuid,
const std::string& pssh) {
base::AutoLock scoped_lock(lock_);
UpdateContentProtectionPsshHelper(pssh, &content_protection_elements_);
UpdateContentProtectionPsshHelper(drm_uuid, pssh,
&content_protection_elements_);
}
void AdaptationSet::AddRole(Role role) {
@ -1041,10 +1042,11 @@ void Representation::AddContentProtectionElement(
RemoveDuplicateAttributes(&content_protection_elements_.back());
}
void Representation::UpdateContentProtectionPssh(
const std::string& pssh) {
void Representation::UpdateContentProtectionPssh(const std::string& drm_uuid,
const std::string& pssh) {
base::AutoLock scoped_lock(lock_);
UpdateContentProtectionPsshHelper(pssh, &content_protection_elements_);
UpdateContentProtectionPsshHelper(drm_uuid, pssh,
&content_protection_elements_);
}
void Representation::AddNewSegment(uint64_t start_time,

View File

@ -189,14 +189,19 @@ class AdaptationSet {
virtual void AddContentProtectionElement(
const ContentProtectionElement& element);
/// Update the <cenc:pssh> element for MP4 specific ContentProtection element.
/// Update the 'cenc:pssh' element for @a drm_uuid ContentProtection element.
/// If the element does not exist, this will add one.
/// @param drm_uuid is the UUID of the DRM for encryption.
/// @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);
/// @attention This might get removed once DASH IF IOP specification makes a
/// a clear guideline on how to handle key rotation. Also to get
/// this working with shaka-player, this method *DOES NOT* update
/// the PSSH element. Instead, it removes the element regardless of
/// the content of @a pssh.
virtual void UpdateContentProtectionPssh(const std::string& drm_uuid,
const std::string& pssh);
/// Set the Role element for this AdaptationSet.
/// The Role element's is schemeIdUri='urn:mpeg:dash:role:2011'.
@ -445,14 +450,19 @@ class Representation {
virtual void AddContentProtectionElement(
const ContentProtectionElement& element);
/// Update the 'cenc:pssh' element for mp4 specific ContentProtection element.
/// Update the 'cenc:pssh' element for @a drm_uuid ContentProtection element.
/// If the element does not exist, this will add one.
/// @param drm_uuid is the UUID of the DRM for encryption.
/// @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);
/// a clear guideline on how to handle key rotation. Also to get
/// this working with shaka-player, this method *DOES NOT* update
/// the PSSH element. Instead, it removes the element regardless of
/// the content of @a pssh.
virtual void UpdateContentProtectionPssh(const std::string& drm_uuid,
const std::string& pssh);
/// Add a media (sub)segment to the representation.
/// AdaptationSet@{subSegmentAlignment,segmentAlignment} cannot be set

View File

@ -1265,7 +1265,8 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) {
"}\n"
"container_type: 1\n";
ContentProtectionElement content_protection;
content_protection.scheme_id_uri = "urn:mpeg:dash:mp4protection:2011";
content_protection.scheme_id_uri =
"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
content_protection.value = "some value";
Element pssh;
pssh.name = "cenc:pssh";
@ -1292,7 +1293,7 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) {
" <AdaptationSet id=\"0\" contentType=\"video\" width=\"1920\""
" height=\"1080\" frameRate=\"3000/100\">"
" <ContentProtection"
" schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\""
" schemeIdUri=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\""
" value=\"some value\">"
" <cenc:pssh>any value</cenc:pssh>"
" </ContentProtection>"
@ -1306,7 +1307,8 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) {
ASSERT_TRUE(mpd_.ToString(&mpd_output));
EXPECT_TRUE(XmlEqual(kExpectedOutput1, mpd_output));
video_adaptation_set->UpdateContentProtectionPssh("new pssh value");
video_adaptation_set->UpdateContentProtectionPssh(
"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "new pssh value");
const char kExpectedOutput2[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\""
@ -1321,9 +1323,98 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) {
" <AdaptationSet id=\"0\" contentType=\"video\" width=\"1920\""
" height=\"1080\" frameRate=\"3000/100\">"
" <ContentProtection"
" schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\""
" schemeIdUri=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\""
" value=\"some value\">"
" <cenc:pssh>new pssh value</cenc:pssh>"
// TODO(rkuroiwa): Commenting this out for now because we want to remove
// the PSSH from the MPD. Uncomment this when the player supports updating
// pssh.
//" <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));
}
// Verify that if the ContentProtection element for the DRM without <cenc:pssh>
// element is updated via UpdateContentProtectionPssh(), the element gets added.
// TODO(rkuroiwa): Until the player supports PSSH update, we remove the pssh
// element. Rename this test once it is supported.
TEST_F(CommonMpdBuilderTest, UpdateToRemovePsshElement) {
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 =
"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
content_protection.value = "some value";
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 kExpectedOutput1[] =
"<?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:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\""
" value=\"some value\">"
" </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(kExpectedOutput1, mpd_output));
video_adaptation_set->UpdateContentProtectionPssh(
"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",
"added 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:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\""
" value=\"some value\">"
// TODO(rkuroiwa): Commenting this out for now because we want to remove
// teh PSSH from the MPD. Uncomment this when the player supports updating
// pssh.
//" <cenc:pssh>added pssh value</cenc:pssh>"
" </ContentProtection>"
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
" mimeType=\"video/mp4\" width=\"1920\" height=\"1080\""

View File

@ -11,6 +11,7 @@
#define MPD_BASE_MPD_NOTIFIER_H_
#include <stdint.h>
#include <string>
#include <vector>
#include "packager/base/macros.h"
@ -78,11 +79,13 @@ class MpdNotifier {
/// This may be called whenever the key has to change, e.g. key rotation.
/// @param container_id Container ID obtained from calling
/// NotifyNewContainer().
/// @param drm_uuid is the UUID of the DRM for encryption.
/// @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::string& drm_uuid,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) = 0;

View File

@ -133,40 +133,52 @@ bool HexToUUID(const std::string& data, std::string* uuid_format) {
}
void UpdateContentProtectionPsshHelper(
const std::string& drm_uuid,
const std::string& pssh,
std::list<ContentProtectionElement>* conetent_protection_elements) {
std::list<ContentProtectionElement>* content_protection_elements) {
const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid;
for (std::list<ContentProtectionElement>::iterator protection =
conetent_protection_elements->begin();
protection != conetent_protection_elements->end(); ++protection) {
if (protection->scheme_id_uri != kEncryptedMp4Scheme)
content_protection_elements->begin();
protection != content_protection_elements->end(); ++protection) {
if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) {
continue;
}
for (std::vector<Element>::iterator subelement =
protection->subelements.begin();
subelement != protection->subelements.end(); ++subelement) {
if (subelement->name == kPsshElementName) {
subelement->content = pssh;
// For now, we want to remove the PSSH element because some players do
// not support updating pssh.
protection->subelements.erase(subelement);
// TODO(rkuroiwa): Uncomment this and remove the line above when
// shaka-player supports updating PSSH.
// 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;
// Reaching here means <cenc:pssh> does not exist under the
// ContentProtection element. Add it.
// TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH.
// Element cenc_pssh;
// cenc_pssh.name = kPsshElementName;
// cenc_pssh.content = pssh;
// protection->subelements.push_back(cenc_pssh);
return;
}
// Reaching here means that MP4 specific ContentProtection does not exist.
// Reaching here means that ContentProtection for the DRM 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);
content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form;
// TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH.
// Element cenc_pssh;
// cenc_pssh.name = kPsshElementName;
// cenc_pssh.content = pssh;
// content_protection.subelements.push_back(cenc_pssh);
content_protection_elements->push_back(content_protection);
return;
}

View File

@ -58,11 +58,12 @@ bool OnlyOneTrue(bool b1, bool b2, bool b3);
/// @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.
// Update the <cenc:pssh> element for |drm_uuid| ContentProtection element.
// If the element does not exist, this will add one.
void UpdateContentProtectionPsshHelper(
const std::string& drm_uuid,
const std::string& pssh,
std::list<ContentProtectionElement>* conetent_protection_elements);
std::list<ContentProtectionElement>* content_protection_elements);
/// Adds <ContentProtection> elements specified by @a media_info to
/// @a adaptation_set.

View File

@ -99,6 +99,7 @@ bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id,
bool SimpleMpdNotifier::NotifyEncryptionUpdate(
uint32_t container_id,
const std::string& drm_uuid,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) {
base::AutoLock auto_lock(lock_);
@ -107,7 +108,8 @@ bool SimpleMpdNotifier::NotifyEncryptionUpdate(
LOG(ERROR) << "Unexpected container_id: " << container_id;
return false;
}
it->second->UpdateContentProtectionPssh(Uint8VectorToBase64(new_pssh));
it->second->UpdateContentProtectionPssh(drm_uuid,
Uint8VectorToBase64(new_pssh));
return true;
}

View File

@ -49,6 +49,7 @@ class SimpleMpdNotifier : public MpdNotifier {
uint64_t size) OVERRIDE;
virtual bool NotifyEncryptionUpdate(
uint32_t container_id,
const std::string& drm_uuid,
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) OVERRIDE;
virtual bool AddContentProtectionElement(

View File

@ -286,9 +286,10 @@ TEST_P(SimpleMpdNotifierTest, UpdateEncryption) {
const char kBogusNewPsshInBase64[] = "cHNzaHNvbWV0aGluZ2Vsc2U=";
EXPECT_CALL(*mock_representation,
UpdateContentProtectionPssh(StrEq(kBogusNewPsshInBase64)));
UpdateContentProtectionPssh(StrEq("myuuid"),
StrEq(kBogusNewPsshInBase64)));
EXPECT_TRUE(notifier.NotifyEncryptionUpdate(
container_id, std::vector<uint8_t>(), kBogusNewPsshVector));
container_id, "myuuid", std::vector<uint8_t>(), kBogusNewPsshVector));
}
INSTANTIATE_TEST_CASE_P(StaticAndDynamic,