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:
parent
21e43966db
commit
5c8efd332e
|
@ -14,6 +14,8 @@ namespace {
|
|||
const uint8_t kWidevineSystemId[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6,
|
||||
0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc,
|
||||
0xd5, 0x1d, 0x21, 0xed};
|
||||
const char kDefaultUUID[] = "";
|
||||
const char kDefaultSystemName[] = "";
|
||||
} // namespace
|
||||
|
||||
namespace edash_packager {
|
||||
|
@ -67,6 +69,14 @@ Status KeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
|
|||
return Status(error::UNIMPLEMENTED, "");
|
||||
}
|
||||
|
||||
std::string KeySource::UUID() {
|
||||
return kDefaultUUID;
|
||||
}
|
||||
|
||||
std::string KeySource::SystemName() {
|
||||
return kDefaultSystemName;
|
||||
}
|
||||
|
||||
scoped_ptr<KeySource> KeySource::CreateFromHexStrings(
|
||||
const std::string& key_id_hex,
|
||||
const std::string& key_hex,
|
||||
|
|
|
@ -83,6 +83,19 @@ class KeySource {
|
|||
TrackType track_type,
|
||||
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.
|
||||
/// @param key_id_hex is the key id in hex string.
|
||||
/// @param key_hex is the key in hex string.
|
||||
|
|
|
@ -236,6 +236,10 @@ Status WidevineKeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
|
|||
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) {
|
||||
signer_ = signer.Pass();
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ class WidevineKeySource : public KeySource {
|
|||
virtual Status GetCryptoPeriodKey(uint32_t crypto_period_index,
|
||||
TrackType track_type,
|
||||
EncryptionKey* key) OVERRIDE;
|
||||
virtual std::string UUID() OVERRIDE;
|
||||
/// @}
|
||||
|
||||
/// Set signer for the key source.
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace edash_packager {
|
|||
namespace media {
|
||||
|
||||
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->dash_profile() == kOnDemandProfile ||
|
||||
mpd_notifier->dash_profile() == kLiveProfile);
|
||||
|
@ -32,12 +32,23 @@ void MpdNotifyMuxerListener::SetContentProtectionSchemeIdUri(
|
|||
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(
|
||||
const MuxerOptions& muxer_options,
|
||||
const StreamInfo& stream_info,
|
||||
uint32_t time_scale,
|
||||
ContainerType container_type,
|
||||
bool is_encrypted) {
|
||||
ContainerType container_type) {
|
||||
scoped_ptr<MediaInfo> media_info(new MediaInfo());
|
||||
if (!internal::GenerateMediaInfo(muxer_options,
|
||||
stream_info,
|
||||
|
@ -48,7 +59,17 @@ void MpdNotifyMuxerListener::OnMediaStart(
|
|||
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(
|
||||
container_type, scheme_id_uri_, media_info.get())) {
|
||||
LOG(ERROR) << "Failed to add content protection elements.";
|
||||
|
|
|
@ -36,11 +36,15 @@ class MpdNotifyMuxerListener : public MuxerListener {
|
|||
|
||||
/// @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,
|
||||
const StreamInfo& stream_info,
|
||||
uint32_t time_scale,
|
||||
ContainerType container_type,
|
||||
bool is_encrypted) OVERRIDE;
|
||||
ContainerType container_type) OVERRIDE;
|
||||
virtual void OnSampleDurationReady(uint32_t sample_duration) OVERRIDE;
|
||||
virtual void OnMediaEnd(bool has_init_range,
|
||||
uint64_t init_range_start,
|
||||
|
@ -61,6 +65,13 @@ class MpdNotifyMuxerListener : public MuxerListener {
|
|||
scoped_ptr<MediaInfo> media_info_;
|
||||
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);
|
||||
};
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
//
|
||||
// Event handler for events fired by Muxer.
|
||||
|
||||
// TODO(rkuroiwa): Document using doxygen style comments.
|
||||
|
||||
#ifndef MEDIA_EVENT_MUXER_LISTENER_H_
|
||||
#define MEDIA_EVENT_MUXER_LISTENER_H_
|
||||
|
||||
|
@ -34,7 +36,24 @@ class 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
|
||||
// available from StreamInfo.
|
||||
// |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,
|
||||
const StreamInfo& stream_info,
|
||||
uint32_t time_scale,
|
||||
ContainerType container_type,
|
||||
bool is_encrypted) = 0;
|
||||
ContainerType container_type) = 0;
|
||||
|
||||
/// Called when the average sample duration of the media is determined.
|
||||
/// @param sample_duration in timescale of the media.
|
||||
|
|
|
@ -201,6 +201,8 @@ bool SetVodInformation(bool has_init_range,
|
|||
return true;
|
||||
}
|
||||
|
||||
// TODO(rkuroiwa): Move this logic to MpdBuilder? MuxerListener probably doesn't
|
||||
// need to know this.
|
||||
bool AddContentProtectionElements(MuxerListener::ContainerType container_type,
|
||||
const std::string& user_scheme_id_uri,
|
||||
MediaInfo* media_info) {
|
||||
|
@ -237,6 +239,36 @@ bool AddContentProtectionElements(MuxerListener::ContainerType container_type,
|
|||
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 media
|
||||
} // namespace edash_packager
|
||||
|
|
|
@ -54,6 +54,22 @@ bool AddContentProtectionElements(MuxerListener::ContainerType container_type,
|
|||
const std::string& user_scheme_id_uri,
|
||||
MediaInfo* media_info);
|
||||
|
||||
/// @param content_protection_uuid is the UUID of the content protection
|
||||
/// 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 media
|
||||
} // namespace edash_packager
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace media {
|
|||
|
||||
VodMediaInfoDumpMuxerListener::VodMediaInfoDumpMuxerListener(
|
||||
const std::string& output_file_name)
|
||||
: output_file_name_(output_file_name) {}
|
||||
: output_file_name_(output_file_name), is_encrypted_(false) {}
|
||||
|
||||
VodMediaInfoDumpMuxerListener::~VodMediaInfoDumpMuxerListener() {}
|
||||
|
||||
|
@ -29,12 +29,23 @@ void VodMediaInfoDumpMuxerListener::SetContentProtectionSchemeIdUri(
|
|||
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(
|
||||
const MuxerOptions& muxer_options,
|
||||
const StreamInfo& stream_info,
|
||||
uint32_t time_scale,
|
||||
ContainerType container_type,
|
||||
bool is_encrypted) {
|
||||
ContainerType container_type) {
|
||||
DCHECK(muxer_options.single_segment);
|
||||
media_info_.reset(new MediaInfo());
|
||||
if (!internal::GenerateMediaInfo(muxer_options,
|
||||
|
@ -46,7 +57,13 @@ void VodMediaInfoDumpMuxerListener::OnMediaStart(
|
|||
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(
|
||||
container_type, scheme_id_uri_, media_info_.get())) {
|
||||
LOG(ERROR) << "Failed to add content protection elements.";
|
||||
|
|
|
@ -36,11 +36,16 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
|
|||
|
||||
/// @name MuxerListener implementation overrides.
|
||||
/// @{
|
||||
virtual void OnMediaStart(const MuxerOptions& muxer_options,
|
||||
const StreamInfo& stream_info,
|
||||
uint32_t time_scale,
|
||||
ContainerType container_type,
|
||||
bool is_encrypted) OVERRIDE;
|
||||
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,
|
||||
uint32_t time_scale,
|
||||
ContainerType container_type) OVERRIDE;
|
||||
virtual void OnSampleDurationReady(uint32_t sample_duration) OVERRIDE;
|
||||
virtual void OnMediaEnd(bool has_init_range,
|
||||
uint64_t init_range_start,
|
||||
|
@ -63,6 +68,13 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
|
|||
std::string scheme_id_uri_;
|
||||
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);
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,19 @@
|
|||
|
||||
namespace {
|
||||
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 edash_packager {
|
||||
|
@ -178,11 +191,21 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test {
|
|||
MuxerOptions muxer_options;
|
||||
SetDefaultMuxerOptionsValues(&muxer_options);
|
||||
const uint32_t kReferenceTimeScale = 1000;
|
||||
listener_->OnMediaStart(muxer_options,
|
||||
stream_info,
|
||||
kReferenceTimeScale,
|
||||
MuxerListener::kContainerMp4,
|
||||
enable_encryption);
|
||||
if (enable_encryption) {
|
||||
std::vector<uint8_t> bogus_default_key_id(
|
||||
kBogusDefaultKeyId,
|
||||
kBogusDefaultKeyId + arraysize(kBogusDefaultKeyId));
|
||||
|
||||
// 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) {
|
||||
|
@ -285,7 +308,16 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, EncryptedStream_Normal) {
|
|||
"reference_time_scale: 1000\n"
|
||||
"container_type: 1\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));
|
||||
}
|
||||
|
||||
|
|
|
@ -244,8 +244,7 @@ void MP4Muxer::FireOnMediaStartEvent() {
|
|||
muxer_listener()->OnMediaStart(options(),
|
||||
*streams().front()->info(),
|
||||
timescale,
|
||||
MuxerListener::kContainerMp4,
|
||||
encryption_key_source() != NULL);
|
||||
MuxerListener::kContainerMp4);
|
||||
}
|
||||
|
||||
void MP4Muxer::FireOnMediaEndEvent() {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "packager/media/base/media_stream.h"
|
||||
#include "packager/media/base/muxer_options.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/formats/mp4/box_definitions.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()) {
|
||||
moov_->pssh.resize(1);
|
||||
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(
|
||||
|
|
|
@ -55,6 +55,26 @@ message MediaInfo {
|
|||
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 AttributeNameValuePair {
|
||||
optional string name = 1;
|
||||
|
@ -85,6 +105,10 @@ message MediaInfo {
|
|||
optional TextInfo text_info = 4;
|
||||
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
|
||||
// AudioInfo.
|
||||
optional uint32 reference_time_scale = 13;
|
||||
|
|
Loading…
Reference in New Issue