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:
parent
fa4a172b04
commit
c75d319985
|
@ -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());
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(¬ifier, 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,
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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().
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(¬ifier, 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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue