Pass content protection information to MuxerListener

- The UUID and DRM name can be fetched from KeySource.
- Add ProtectedContent message to MediaInfo. The message contains basic
  information for the protected content, such as the default key ID for
  the content.
- The message is required to separate Representations with different
  content protection information into different AdaptationSets.

Change-Id: Ib9dc834ae0abf93b7ca0acdf52a865b1394a4816
This commit is contained in:
Rintaro Kuroiwa 2015-07-07 17:52:28 -07:00
parent 21e43966db
commit 5c8efd332e
15 changed files with 244 additions and 26 deletions

View File

@ -14,6 +14,8 @@ namespace {
const uint8_t kWidevineSystemId[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, const uint8_t kWidevineSystemId[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6,
0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc,
0xd5, 0x1d, 0x21, 0xed}; 0xd5, 0x1d, 0x21, 0xed};
const char kDefaultUUID[] = "";
const char kDefaultSystemName[] = "";
} // namespace } // namespace
namespace edash_packager { namespace edash_packager {
@ -67,6 +69,14 @@ Status KeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
return Status(error::UNIMPLEMENTED, ""); return Status(error::UNIMPLEMENTED, "");
} }
std::string KeySource::UUID() {
return kDefaultUUID;
}
std::string KeySource::SystemName() {
return kDefaultSystemName;
}
scoped_ptr<KeySource> KeySource::CreateFromHexStrings( scoped_ptr<KeySource> KeySource::CreateFromHexStrings(
const std::string& key_id_hex, const std::string& key_id_hex,
const std::string& key_hex, const std::string& key_hex,

View File

@ -83,6 +83,19 @@ class KeySource {
TrackType track_type, TrackType track_type,
EncryptionKey* key); EncryptionKey* key);
/// Returns the UUID of the key source in human readable form.
/// UUIDs are listed here:
/// http://dashif.org/identifiers/protection/
/// @return UUID of the key source, empty string if not specified.
virtual std::string UUID();
/// Returns the name, and possibly with a version number, of the key source.
/// (This would be the ContentProtection@value attribute in the MPD. DASH-IF-
/// IOP v3.0 recommends this to be the DRM system and version name in human
/// readable from.)
/// @return the name of the key source, empty string if not specified.
virtual std::string SystemName();
/// Create KeySource object from hex strings. /// Create KeySource object from hex strings.
/// @param key_id_hex is the key id in hex string. /// @param key_id_hex is the key id in hex string.
/// @param key_hex is the key in hex string. /// @param key_hex is the key in hex string.

View File

@ -236,6 +236,10 @@ Status WidevineKeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
return GetKeyInternal(crypto_period_index, track_type, key); return GetKeyInternal(crypto_period_index, track_type, key);
} }
std::string WidevineKeySource::UUID() {
return "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
}
void WidevineKeySource::set_signer(scoped_ptr<RequestSigner> signer) { void WidevineKeySource::set_signer(scoped_ptr<RequestSigner> signer) {
signer_ = signer.Pass(); signer_ = signer.Pass();
} }

View File

@ -43,6 +43,7 @@ class WidevineKeySource : public KeySource {
virtual Status GetCryptoPeriodKey(uint32_t crypto_period_index, virtual Status GetCryptoPeriodKey(uint32_t crypto_period_index,
TrackType track_type, TrackType track_type,
EncryptionKey* key) OVERRIDE; EncryptionKey* key) OVERRIDE;
virtual std::string UUID() OVERRIDE;
/// @} /// @}
/// Set signer for the key source. /// Set signer for the key source.

View File

@ -19,7 +19,7 @@ namespace edash_packager {
namespace media { namespace media {
MpdNotifyMuxerListener::MpdNotifyMuxerListener(MpdNotifier* mpd_notifier) MpdNotifyMuxerListener::MpdNotifyMuxerListener(MpdNotifier* mpd_notifier)
: mpd_notifier_(mpd_notifier), notification_id_(0) { : mpd_notifier_(mpd_notifier), notification_id_(0), is_encrypted_(false) {
DCHECK(mpd_notifier); DCHECK(mpd_notifier);
DCHECK(mpd_notifier->dash_profile() == kOnDemandProfile || DCHECK(mpd_notifier->dash_profile() == kOnDemandProfile ||
mpd_notifier->dash_profile() == kLiveProfile); mpd_notifier->dash_profile() == kLiveProfile);
@ -32,12 +32,23 @@ void MpdNotifyMuxerListener::SetContentProtectionSchemeIdUri(
scheme_id_uri_ = scheme_id_uri; scheme_id_uri_ = scheme_id_uri;
} }
void MpdNotifyMuxerListener::OnEncryptionInfoReady(
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) {
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;
}
void MpdNotifyMuxerListener::OnMediaStart( void MpdNotifyMuxerListener::OnMediaStart(
const MuxerOptions& muxer_options, const MuxerOptions& muxer_options,
const StreamInfo& stream_info, const StreamInfo& stream_info,
uint32_t time_scale, uint32_t time_scale,
ContainerType container_type, ContainerType container_type) {
bool is_encrypted) {
scoped_ptr<MediaInfo> media_info(new MediaInfo()); scoped_ptr<MediaInfo> media_info(new MediaInfo());
if (!internal::GenerateMediaInfo(muxer_options, if (!internal::GenerateMediaInfo(muxer_options,
stream_info, stream_info,
@ -48,7 +59,17 @@ void MpdNotifyMuxerListener::OnMediaStart(
return; return;
} }
if (is_encrypted) { if (is_encrypted_) {
internal::SetContentProtectionFields(
content_protection_uuid_, content_protection_name_version_,
default_key_id_, pssh_, media_info.get());
}
if (is_encrypted_) {
// TODO(rkuroiwa): When MediaInfo's content protection fields are processed
// in MpdBuilder (e.g. content_protection_uuid, default_key_id) then skip
// this step if scheme_id_uri_'s UUID == content_protection_uuid_.
// Also consider removing SetContentProtectionSchemeIdUri().
if (!internal::AddContentProtectionElements( if (!internal::AddContentProtectionElements(
container_type, scheme_id_uri_, media_info.get())) { container_type, scheme_id_uri_, media_info.get())) {
LOG(ERROR) << "Failed to add content protection elements."; LOG(ERROR) << "Failed to add content protection elements.";

View File

@ -36,11 +36,15 @@ class MpdNotifyMuxerListener : public MuxerListener {
/// @name MuxerListener implementation overrides. /// @name MuxerListener implementation overrides.
/// @{ /// @{
virtual void OnEncryptionInfoReady(
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) OVERRIDE;
virtual void OnMediaStart(const MuxerOptions& muxer_options, virtual void OnMediaStart(const MuxerOptions& muxer_options,
const StreamInfo& stream_info, const StreamInfo& stream_info,
uint32_t time_scale, uint32_t time_scale,
ContainerType container_type, ContainerType container_type) OVERRIDE;
bool is_encrypted) OVERRIDE;
virtual void OnSampleDurationReady(uint32_t sample_duration) OVERRIDE; virtual void OnSampleDurationReady(uint32_t sample_duration) OVERRIDE;
virtual void OnMediaEnd(bool has_init_range, virtual void OnMediaEnd(bool has_init_range,
uint64_t init_range_start, uint64_t init_range_start,
@ -61,6 +65,13 @@ class MpdNotifyMuxerListener : public MuxerListener {
scoped_ptr<MediaInfo> media_info_; scoped_ptr<MediaInfo> media_info_;
std::string scheme_id_uri_; std::string scheme_id_uri_;
bool is_encrypted_;
// Storage for values passed to OnEncryptionInfoReady().
std::string content_protection_uuid_;
std::string content_protection_name_version_;
std::string default_key_id_;
std::string pssh_;
DISALLOW_COPY_AND_ASSIGN(MpdNotifyMuxerListener); DISALLOW_COPY_AND_ASSIGN(MpdNotifyMuxerListener);
}; };

View File

@ -6,6 +6,8 @@
// //
// Event handler for events fired by Muxer. // Event handler for events fired by Muxer.
// TODO(rkuroiwa): Document using doxygen style comments.
#ifndef MEDIA_EVENT_MUXER_LISTENER_H_ #ifndef MEDIA_EVENT_MUXER_LISTENER_H_
#define MEDIA_EVENT_MUXER_LISTENER_H_ #define MEDIA_EVENT_MUXER_LISTENER_H_
@ -34,7 +36,24 @@ class MuxerListener {
virtual ~MuxerListener() {}; virtual ~MuxerListener() {};
// Called when muxing starts. This event happens before any other events. // Called when the media's encryption information is ready. This should be
// called before OnMediaStart(), if the media is encrypted.
// All the parameters may be empty just to notify that the media is encrypted.
// |content_protection_uuid| is one of the UUIDs listed here
// http://dashif.org/identifiers/protection/. This should be in human
// readable form.
// |content_protection_name_version| is the DRM system and version name.
// 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.
// |pssh| is the whole 'pssh' box.
virtual void OnEncryptionInfoReady(
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) = 0;
// Called when muxing starts.
// For MPEG DASH Live profile, the initialization segment information is // For MPEG DASH Live profile, the initialization segment information is
// available from StreamInfo. // available from StreamInfo.
// |time_scale| is a reference time scale that overrides the time scale // |time_scale| is a reference time scale that overrides the time scale
@ -42,8 +61,7 @@ class MuxerListener {
virtual void OnMediaStart(const MuxerOptions& muxer_options, virtual void OnMediaStart(const MuxerOptions& muxer_options,
const StreamInfo& stream_info, const StreamInfo& stream_info,
uint32_t time_scale, uint32_t time_scale,
ContainerType container_type, ContainerType container_type) = 0;
bool is_encrypted) = 0;
/// Called when the average sample duration of the media is determined. /// Called when the average sample duration of the media is determined.
/// @param sample_duration in timescale of the media. /// @param sample_duration in timescale of the media.

View File

@ -201,6 +201,8 @@ bool SetVodInformation(bool has_init_range,
return true; return true;
} }
// TODO(rkuroiwa): Move this logic to MpdBuilder? MuxerListener probably doesn't
// need to know this.
bool AddContentProtectionElements(MuxerListener::ContainerType container_type, bool AddContentProtectionElements(MuxerListener::ContainerType container_type,
const std::string& user_scheme_id_uri, const std::string& user_scheme_id_uri,
MediaInfo* media_info) { MediaInfo* media_info) {
@ -237,6 +239,36 @@ bool AddContentProtectionElements(MuxerListener::ContainerType container_type,
return true; return true;
} }
void SetContentProtectionFields(
const std::string& content_protection_uuid,
const std::string& content_protection_name_version,
const std::string& default_key_id,
const std::string& pssh,
MediaInfo* media_info) {
DCHECK(media_info);
MediaInfo::ProtectedContent* protected_content =
media_info->mutable_protected_content();
if (!default_key_id.empty())
protected_content->set_default_key_id(default_key_id);
if (content_protection_uuid.empty() &&
content_protection_name_version.empty() && pssh.empty()) {
return;
}
MediaInfo::ProtectedContent::ContentProtectionEntry* entry =
protected_content->add_content_protection_entry();
if (!content_protection_uuid.empty())
entry->set_uuid(content_protection_uuid);
if (!content_protection_name_version.empty())
entry->set_name_version(content_protection_name_version);
if (!pssh.empty())
entry->set_pssh(pssh);
}
} // namespace internal } // namespace internal
} // namespace media } // namespace media
} // namespace edash_packager } // namespace edash_packager

View File

@ -54,6 +54,22 @@ bool AddContentProtectionElements(MuxerListener::ContainerType container_type,
const std::string& user_scheme_id_uri, const std::string& user_scheme_id_uri,
MediaInfo* media_info); MediaInfo* media_info);
/// @param content_protection_uuid is the UUID of the content protection
/// in human readable form.
/// @param content_protection_name_version is the DRM name and verion.
/// @param default_key_id is the key ID for this media in hex (i.e. non-human
/// readable, typically 16 bytes.)
/// @param pssh is the pssh for the media in hex (i.e. non-human readable, raw
/// 'pssh' box.)
/// @param media_info is where the content protection information is stored and
/// cannot be null.
void SetContentProtectionFields(
const std::string& content_protection_uuid,
const std::string& content_protection_name_version,
const std::string& default_key_id,
const std::string& pssh,
MediaInfo* media_info);
} // namespace internal } // namespace internal
} // namespace media } // namespace media
} // namespace edash_packager } // namespace edash_packager

View File

@ -20,7 +20,7 @@ namespace media {
VodMediaInfoDumpMuxerListener::VodMediaInfoDumpMuxerListener( VodMediaInfoDumpMuxerListener::VodMediaInfoDumpMuxerListener(
const std::string& output_file_name) const std::string& output_file_name)
: output_file_name_(output_file_name) {} : output_file_name_(output_file_name), is_encrypted_(false) {}
VodMediaInfoDumpMuxerListener::~VodMediaInfoDumpMuxerListener() {} VodMediaInfoDumpMuxerListener::~VodMediaInfoDumpMuxerListener() {}
@ -29,12 +29,23 @@ void VodMediaInfoDumpMuxerListener::SetContentProtectionSchemeIdUri(
scheme_id_uri_ = scheme_id_uri; scheme_id_uri_ = scheme_id_uri;
} }
void VodMediaInfoDumpMuxerListener::OnEncryptionInfoReady(
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) {
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;
}
void VodMediaInfoDumpMuxerListener::OnMediaStart( void VodMediaInfoDumpMuxerListener::OnMediaStart(
const MuxerOptions& muxer_options, const MuxerOptions& muxer_options,
const StreamInfo& stream_info, const StreamInfo& stream_info,
uint32_t time_scale, uint32_t time_scale,
ContainerType container_type, ContainerType container_type) {
bool is_encrypted) {
DCHECK(muxer_options.single_segment); DCHECK(muxer_options.single_segment);
media_info_.reset(new MediaInfo()); media_info_.reset(new MediaInfo());
if (!internal::GenerateMediaInfo(muxer_options, if (!internal::GenerateMediaInfo(muxer_options,
@ -46,7 +57,13 @@ void VodMediaInfoDumpMuxerListener::OnMediaStart(
return; return;
} }
if (is_encrypted) { if (is_encrypted_) {
internal::SetContentProtectionFields(
content_protection_uuid_, content_protection_name_version_,
default_key_id_, pssh_, media_info_.get());
}
if (is_encrypted_) {
if (!internal::AddContentProtectionElements( if (!internal::AddContentProtectionElements(
container_type, scheme_id_uri_, media_info_.get())) { container_type, scheme_id_uri_, media_info_.get())) {
LOG(ERROR) << "Failed to add content protection elements."; LOG(ERROR) << "Failed to add content protection elements.";

View File

@ -36,11 +36,16 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
/// @name MuxerListener implementation overrides. /// @name MuxerListener implementation overrides.
/// @{ /// @{
virtual void OnMediaStart(const MuxerOptions& muxer_options, virtual void OnEncryptionInfoReady(
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) OVERRIDE;
virtual void OnMediaStart(
const MuxerOptions& muxer_options,
const StreamInfo& stream_info, const StreamInfo& stream_info,
uint32_t time_scale, uint32_t time_scale,
ContainerType container_type, ContainerType container_type) OVERRIDE;
bool is_encrypted) OVERRIDE;
virtual void OnSampleDurationReady(uint32_t sample_duration) OVERRIDE; virtual void OnSampleDurationReady(uint32_t sample_duration) OVERRIDE;
virtual void OnMediaEnd(bool has_init_range, virtual void OnMediaEnd(bool has_init_range,
uint64_t init_range_start, uint64_t init_range_start,
@ -63,6 +68,13 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
std::string scheme_id_uri_; std::string scheme_id_uri_;
scoped_ptr<MediaInfo> media_info_; scoped_ptr<MediaInfo> media_info_;
bool is_encrypted_;
// Storage for values passed to OnEncryptionInfoReady().
std::string content_protection_uuid_;
std::string content_protection_name_version_;
std::string default_key_id_;
std::string pssh_;
DISALLOW_COPY_AND_ASSIGN(VodMediaInfoDumpMuxerListener); DISALLOW_COPY_AND_ASSIGN(VodMediaInfoDumpMuxerListener);
}; };

View File

@ -20,6 +20,19 @@
namespace { namespace {
const bool kEnableEncryption = true; const bool kEnableEncryption = true;
// '_default_key_id_' (length 16).
const uint8_t kBogusDefaultKeyId[] = {0x5f, 0x64, 0x65, 0x66, 0x61, 0x75,
0x6c, 0x74, 0x5f, 0x6b, 0x65, 0x79,
0x5f, 0x69, 0x64, 0x5f};
// 'pssh'. Not a valid pssh box.
const uint8_t kInvalidPssh[] = {
0x70, 0x73, 0x73, 0x68
};
// This should be in the uuid field of the protobuf. This is not a valid UUID
// format but the protobof generation shouldn't care.
const char kTestUUID[] = "myuuid";
const char kTestContentProtectionName[] = "MyContentProtection version 1";
} // namespace } // namespace
namespace edash_packager { namespace edash_packager {
@ -178,11 +191,21 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test {
MuxerOptions muxer_options; MuxerOptions muxer_options;
SetDefaultMuxerOptionsValues(&muxer_options); SetDefaultMuxerOptionsValues(&muxer_options);
const uint32_t kReferenceTimeScale = 1000; const uint32_t kReferenceTimeScale = 1000;
listener_->OnMediaStart(muxer_options, if (enable_encryption) {
stream_info, std::vector<uint8_t> bogus_default_key_id(
kReferenceTimeScale, kBogusDefaultKeyId,
MuxerListener::kContainerMp4, kBogusDefaultKeyId + arraysize(kBogusDefaultKeyId));
enable_encryption);
// This isn't a valid pssh box but the MediaInfo protobuf creator
// shouldn't worry about it.
std::vector<uint8_t> invalid_pssh(kInvalidPssh,
kInvalidPssh + arraysize(kInvalidPssh));
listener_->OnEncryptionInfoReady(kTestUUID, kTestContentProtectionName,
bogus_default_key_id, invalid_pssh);
}
listener_->OnMediaStart(muxer_options, stream_info, kReferenceTimeScale,
MuxerListener::kContainerMp4);
} }
void FireOnMediaEndWithParams(const OnMediaEndParameters& params) { void FireOnMediaEndWithParams(const OnMediaEndParameters& params) {
@ -285,7 +308,16 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, EncryptedStream_Normal) {
"reference_time_scale: 1000\n" "reference_time_scale: 1000\n"
"container_type: 1\n" "container_type: 1\n"
"media_file_name: 'test_output_file_name.mp4'\n" "media_file_name: 'test_output_file_name.mp4'\n"
"media_duration_seconds: 10.5\n"; "media_duration_seconds: 10.5\n"
"protected_content {\n"
" content_protection_entry {\n"
" uuid: 'myuuid'\n"
" name_version: 'MyContentProtection version 1'\n"
" pssh: 'pssh'\n"
" }\n"
" default_key_id: '_default_key_id_'\n"
"}\n";
ASSERT_NO_FATAL_FAILURE(ExpectTempFileToEqual(kExpectedProtobufOutput)); ASSERT_NO_FATAL_FAILURE(ExpectTempFileToEqual(kExpectedProtobufOutput));
} }

View File

@ -244,8 +244,7 @@ void MP4Muxer::FireOnMediaStartEvent() {
muxer_listener()->OnMediaStart(options(), muxer_listener()->OnMediaStart(options(),
*streams().front()->info(), *streams().front()->info(),
timescale, timescale,
MuxerListener::kContainerMp4, MuxerListener::kContainerMp4);
encryption_key_source() != NULL);
} }
void MP4Muxer::FireOnMediaEndEvent() { void MP4Muxer::FireOnMediaEndEvent() {

View File

@ -15,6 +15,7 @@
#include "packager/media/base/media_stream.h" #include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/base/video_stream_info.h" #include "packager/media/base/video_stream_info.h"
#include "packager/media/event/muxer_listener.h"
#include "packager/media/event/progress_listener.h" #include "packager/media/event/progress_listener.h"
#include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/box_definitions.h"
#include "packager/media/formats/mp4/key_rotation_fragmenter.h" #include "packager/media/formats/mp4/key_rotation_fragmenter.h"
@ -196,6 +197,13 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
if (moov_->pssh.empty()) { if (moov_->pssh.empty()) {
moov_->pssh.resize(1); moov_->pssh.resize(1);
moov_->pssh[0].raw_box = encryption_key->pssh; moov_->pssh[0].raw_box = encryption_key->pssh;
// Also only one default key id.
if (muxer_listener_) {
muxer_listener_->OnEncryptionInfoReady(
encryption_key_source->UUID(), encryption_key_source->SystemName(),
encryption_key->key_id, encryption_key->pssh);
}
} }
fragmenters_[i] = new EncryptingFragmenter( fragmenters_[i] = new EncryptingFragmenter(

View File

@ -55,6 +55,26 @@ message MediaInfo {
optional string language = 2; optional string language = 2;
} }
message ProtectedContent {
message ContentProtectionEntry {
// Human readable UUID of the DRM.
optional string uuid = 1;
// Human readable DRM name and version string.
// e.g. "My Content Protection v1.0"
optional string name_version = 2;
// The raw 'pssh' box for the media.
optional bytes pssh = 3;
}
// The default key ID for the encrypted media.
optional bytes default_key_id = 1;
repeated ContentProtectionEntry content_protection_entry = 2;
}
// TODO(rkuroiwa): Remove this. <ContentProtection> element that must be added
// should be done by directly using the MpdBuilder interface.
// Use this to specify ContentProtection elements that should be set in
// the MPD, if ContentProtectionEntry is not sufficient.
message ContentProtectionXml { message ContentProtectionXml {
message AttributeNameValuePair { message AttributeNameValuePair {
optional string name = 1; optional string name = 1;
@ -85,6 +105,10 @@ message MediaInfo {
optional TextInfo text_info = 4; optional TextInfo text_info = 4;
repeated ContentProtectionXml content_protections = 5; repeated ContentProtectionXml content_protections = 5;
// This is set if the content is protected with a content protection,
// i.e. encrypted.
optional ProtectedContent protected_content = 15;
// This is the reference time scale if there are multiple VideoInfo and/or // This is the reference time scale if there are multiple VideoInfo and/or
// AudioInfo. // AudioInfo.
optional uint32 reference_time_scale = 13; optional uint32 reference_time_scale = 13;