Add WebM decryption support
- Also refactor decryptor management code out of mp4_media_parser.cc - Move decryptor managment logic to DecryptorSource class to make it available for webm as well - Remove data_offset member from DecryptConfig which is not useful - Add widevine_pssh_data.proto file Closes #72 Change-Id: I1d32baf4013ebd3382b5372c7433fae5033a260e
This commit is contained in:
parent
940c3571aa
commit
22498e125a
|
@ -11,14 +11,9 @@ namespace media {
|
|||
|
||||
DecryptConfig::DecryptConfig(const std::vector<uint8_t>& key_id,
|
||||
const std::vector<uint8_t>& iv,
|
||||
const int data_offset,
|
||||
const std::vector<SubsampleEntry>& subsamples)
|
||||
: key_id_(key_id),
|
||||
iv_(iv),
|
||||
data_offset_(data_offset),
|
||||
subsamples_(subsamples) {
|
||||
: key_id_(key_id), iv_(iv), subsamples_(subsamples) {
|
||||
CHECK_GT(key_id.size(), 0u);
|
||||
CHECK_GE(data_offset, 0);
|
||||
}
|
||||
|
||||
DecryptConfig::~DecryptConfig() {}
|
||||
|
|
|
@ -38,22 +38,16 @@ class DecryptConfig {
|
|||
|
||||
/// @param key_id is the ID that references the decryption key.
|
||||
/// @param iv is the initialization vector defined by the encryptor.
|
||||
/// @param data_offset is the amount of data that should be discarded from
|
||||
/// the head of the sample buffer before applying subsample
|
||||
/// information. A decrypted buffer will be shorter than an encrypted
|
||||
/// buffer by this amount.
|
||||
/// @param subsamples defines the clear and encrypted portions of the sample
|
||||
/// as described in SubsampleEntry. A decrypted buffer will be equal
|
||||
/// in size to the sum of the subsample sizes.
|
||||
DecryptConfig(const std::vector<uint8_t>& key_id,
|
||||
const std::vector<uint8_t>& iv,
|
||||
const int data_offset,
|
||||
const std::vector<SubsampleEntry>& subsamples);
|
||||
~DecryptConfig();
|
||||
|
||||
const std::vector<uint8_t>& key_id() const { return key_id_; }
|
||||
const std::vector<uint8_t>& iv() const { return iv_; }
|
||||
int data_offset() const { return data_offset_; }
|
||||
const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; }
|
||||
|
||||
private:
|
||||
|
@ -62,9 +56,6 @@ class DecryptConfig {
|
|||
// Initialization vector.
|
||||
const std::vector<uint8_t> iv_;
|
||||
|
||||
// Amount of data to be discarded before applying subsample information.
|
||||
const int data_offset_;
|
||||
|
||||
// Subsample information. May be empty for some formats, meaning entire frame
|
||||
// (less data ignored by data_offset_) is encrypted.
|
||||
const std::vector<SubsampleEntry> subsamples_;
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include "packager/media/base/decryptor_source.h"
|
||||
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/base/stl_util.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
|
||||
DecryptorSource::DecryptorSource(KeySource* key_source)
|
||||
: key_source_(key_source) {
|
||||
CHECK(key_source);
|
||||
}
|
||||
DecryptorSource::~DecryptorSource() {
|
||||
STLDeleteValues(&decryptor_map_);
|
||||
}
|
||||
|
||||
bool DecryptorSource::DecryptSampleBuffer(const DecryptConfig* decrypt_config,
|
||||
uint8_t* buffer,
|
||||
size_t buffer_size) {
|
||||
DCHECK(decrypt_config);
|
||||
DCHECK(buffer);
|
||||
|
||||
// Get the decryptor object.
|
||||
AesCtrEncryptor* decryptor;
|
||||
auto found = decryptor_map_.find(decrypt_config->key_id());
|
||||
if (found == decryptor_map_.end()) {
|
||||
// Create new AesCtrEncryptor
|
||||
EncryptionKey key;
|
||||
Status status(key_source_->GetKey(decrypt_config->key_id(), &key));
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Error retrieving decryption key: " << status;
|
||||
return false;
|
||||
}
|
||||
scoped_ptr<AesCtrEncryptor> aes_ctr_encryptor(new AesCtrEncryptor);
|
||||
if (!aes_ctr_encryptor->InitializeWithIv(key.key, decrypt_config->iv())) {
|
||||
LOG(ERROR) << "Failed to initialize AesCtrEncryptor for decryption.";
|
||||
return false;
|
||||
}
|
||||
decryptor = aes_ctr_encryptor.release();
|
||||
decryptor_map_[decrypt_config->key_id()] = decryptor;
|
||||
} else {
|
||||
decryptor = found->second;
|
||||
}
|
||||
if (!decryptor->SetIv(decrypt_config->iv())) {
|
||||
LOG(ERROR) << "Invalid initialization vector.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (decrypt_config->subsamples().empty()) {
|
||||
// Sample not encrypted using subsample encryption. Decrypt whole.
|
||||
if (!decryptor->Decrypt(buffer, buffer_size, buffer)) {
|
||||
LOG(ERROR) << "Error during bulk sample decryption.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Subsample decryption.
|
||||
const std::vector<SubsampleEntry>& subsamples = decrypt_config->subsamples();
|
||||
uint8_t* current_ptr = buffer;
|
||||
const uint8_t* const buffer_end = buffer + buffer_size;
|
||||
for (const auto& subsample : subsamples) {
|
||||
if ((current_ptr + subsample.clear_bytes + subsample.cipher_bytes) >
|
||||
buffer_end) {
|
||||
LOG(ERROR) << "Subsamples overflow sample buffer.";
|
||||
return false;
|
||||
}
|
||||
current_ptr += subsample.clear_bytes;
|
||||
if (!decryptor->Decrypt(current_ptr, subsample.cipher_bytes, current_ptr)) {
|
||||
LOG(ERROR) << "Error decrypting subsample buffer.";
|
||||
return false;
|
||||
}
|
||||
current_ptr += subsample.cipher_bytes;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
|
@ -7,28 +7,30 @@
|
|||
#ifndef MEDIA_BASE_DECRYPTOR_SOURCE_H_
|
||||
#define MEDIA_BASE_DECRYPTOR_SOURCE_H_
|
||||
|
||||
#include "packager/base/memory/scoped_ptr.h"
|
||||
#include "packager/media/base/container_names.h"
|
||||
#include "packager/media/base/status.h"
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "packager/media/base/aes_encryptor.h"
|
||||
#include "packager/media/base/decrypt_config.h"
|
||||
#include "packager/media/base/key_source.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
|
||||
/// DecryptorSource is responsible for decryption key acquisition.
|
||||
/// DecryptorSource wraps KeySource and is responsible for decryptor management.
|
||||
class DecryptorSource {
|
||||
public:
|
||||
DecryptorSource() {}
|
||||
virtual ~DecryptorSource() {}
|
||||
explicit DecryptorSource(KeySource* key_source);
|
||||
~DecryptorSource();
|
||||
|
||||
/// NeedKey event handler.
|
||||
/// @param container indicates the container format.
|
||||
/// @param init_data specifies container dependent initialization data that
|
||||
/// is used to initialize the decryption key.
|
||||
/// @return OK on success, an adequate status on error.
|
||||
virtual Status OnNeedKey(MediaContainerName container,
|
||||
const std::string& init_data) = 0;
|
||||
bool DecryptSampleBuffer(const DecryptConfig* decrypt_config,
|
||||
uint8_t* buffer,
|
||||
size_t buffer_size);
|
||||
|
||||
private:
|
||||
KeySource* key_source_;
|
||||
std::map<std::vector<uint8_t>, AesCtrEncryptor*> decryptor_map_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DecryptorSource);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include "packager/media/base/decryptor_source.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "packager/base/macros.h"
|
||||
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
using ::testing::Mock;
|
||||
using ::testing::_;
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
namespace {
|
||||
|
||||
const uint8_t kKeyId[] = {
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
};
|
||||
|
||||
const uint8_t kMockKey[] = {
|
||||
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
};
|
||||
|
||||
const uint8_t kIv[] = {
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
};
|
||||
const uint8_t kBuffer[] = {
|
||||
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x04, 0x05, 0x06,
|
||||
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
};
|
||||
// Expected decrypted buffer with the above kMockKey and kIv.
|
||||
const uint8_t kExpectedDecryptedBuffer[] = {
|
||||
0xfd, 0xf9, 0x8b, 0xb2, 0x1d, 0xd3, 0x07, 0x72, 0x51, 0xf4, 0xdf,
|
||||
0xf9, 0x16, 0x6a, 0x14, 0xcb, 0xde, 0xaa, 0x6a, 0x04, 0x85,
|
||||
};
|
||||
|
||||
const uint8_t kIv2[] = {
|
||||
0x14, 0x25, 0x36, 0x47, 0x58, 0x69, 0x7a, 0x8b,
|
||||
};
|
||||
const uint8_t kBuffer2[] = {0x05, 0x02};
|
||||
// Expected decrypted buffer with the above kMockKey and kIv2.
|
||||
const uint8_t kExpectedDecryptedBuffer2[] = {0x20, 0x62};
|
||||
|
||||
class MockKeySource : public KeySource {
|
||||
public:
|
||||
MOCK_METHOD2(GetKey,
|
||||
Status(const std::vector<uint8_t>& key_id, EncryptionKey* key));
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class DecryptorSourceTest : public ::testing::Test {
|
||||
public:
|
||||
DecryptorSourceTest()
|
||||
: decryptor_source_(&mock_key_source_),
|
||||
key_id_(std::vector<uint8_t>(kKeyId, kKeyId + arraysize(kKeyId))),
|
||||
buffer_(std::vector<uint8_t>(kBuffer, kBuffer + arraysize(kBuffer))) {}
|
||||
|
||||
protected:
|
||||
StrictMock<MockKeySource> mock_key_source_;
|
||||
DecryptorSource decryptor_source_;
|
||||
std::vector<uint8_t> key_id_;
|
||||
std::vector<uint8_t> buffer_;
|
||||
};
|
||||
|
||||
TEST_F(DecryptorSourceTest, FullSampleDecryption) {
|
||||
EncryptionKey encryption_key;
|
||||
encryption_key.key.assign(kMockKey, kMockKey + arraysize(kMockKey));
|
||||
EXPECT_CALL(mock_key_source_, GetKey(key_id_, _))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
|
||||
|
||||
DecryptConfig decrypt_config(key_id_,
|
||||
std::vector<uint8_t>(kIv, kIv + arraysize(kIv)),
|
||||
std::vector<SubsampleEntry>());
|
||||
ASSERT_TRUE(decryptor_source_.DecryptSampleBuffer(&decrypt_config, &buffer_[0],
|
||||
buffer_.size()));
|
||||
EXPECT_EQ(std::vector<uint8_t>(
|
||||
kExpectedDecryptedBuffer,
|
||||
kExpectedDecryptedBuffer + arraysize(kExpectedDecryptedBuffer)),
|
||||
buffer_);
|
||||
|
||||
// DecryptSampleBuffer can be called repetitively. No GetKey call again with
|
||||
// the same key id.
|
||||
buffer_.assign(kBuffer2, kBuffer2 + arraysize(kBuffer2));
|
||||
DecryptConfig decrypt_config2(
|
||||
key_id_, std::vector<uint8_t>(kIv2, kIv2 + arraysize(kIv2)),
|
||||
std::vector<SubsampleEntry>());
|
||||
ASSERT_TRUE(decryptor_source_.DecryptSampleBuffer(
|
||||
&decrypt_config2, &buffer_[0], buffer_.size()));
|
||||
EXPECT_EQ(std::vector<uint8_t>(kExpectedDecryptedBuffer2,
|
||||
kExpectedDecryptedBuffer2 +
|
||||
arraysize(kExpectedDecryptedBuffer2)),
|
||||
buffer_);
|
||||
}
|
||||
|
||||
TEST_F(DecryptorSourceTest, SubsampleDecryption) {
|
||||
EncryptionKey encryption_key;
|
||||
encryption_key.key.assign(kMockKey, kMockKey + arraysize(kMockKey));
|
||||
EXPECT_CALL(mock_key_source_, GetKey(key_id_, _))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
|
||||
|
||||
const SubsampleEntry kSubsamples[] = {
|
||||
{2, 3},
|
||||
{3, 13},
|
||||
};
|
||||
// Expected decrypted buffer with the above subsamples.
|
||||
const uint8_t kExpectedDecryptedBuffer[] = {
|
||||
// Subsample[0].clear
|
||||
0x03, 0x04,
|
||||
// Subsample[0].cipher
|
||||
0xfb, 0xfb, 0x89,
|
||||
// Subsample[1].clear
|
||||
0x08, 0x09, 0x0a,
|
||||
// Subsample[1].cipher
|
||||
0xb0, 0x1f, 0xdd, 0x09, 0x70, 0x5c, 0xfb, 0xd2,
|
||||
0xfb, 0x18, 0x64, 0x16, 0xc9,
|
||||
};
|
||||
|
||||
DecryptConfig decrypt_config(
|
||||
key_id_, std::vector<uint8_t>(kIv, kIv + arraysize(kIv)),
|
||||
std::vector<SubsampleEntry>(kSubsamples,
|
||||
kSubsamples + arraysize(kSubsamples)));
|
||||
ASSERT_TRUE(decryptor_source_.DecryptSampleBuffer(
|
||||
&decrypt_config, &buffer_[0], buffer_.size()));
|
||||
EXPECT_EQ(std::vector<uint8_t>(
|
||||
kExpectedDecryptedBuffer,
|
||||
kExpectedDecryptedBuffer + arraysize(kExpectedDecryptedBuffer)),
|
||||
buffer_);
|
||||
}
|
||||
|
||||
TEST_F(DecryptorSourceTest, SubsampleDecryptionSizeValidation) {
|
||||
EncryptionKey encryption_key;
|
||||
encryption_key.key.assign(kMockKey, kMockKey + arraysize(kMockKey));
|
||||
EXPECT_CALL(mock_key_source_, GetKey(key_id_, _))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
|
||||
|
||||
// Total size exceeds buffer size.
|
||||
const SubsampleEntry kSubsamples[] = {
|
||||
{2, 3},
|
||||
{3, 14},
|
||||
};
|
||||
|
||||
DecryptConfig decrypt_config(
|
||||
key_id_, std::vector<uint8_t>(kIv, kIv + arraysize(kIv)),
|
||||
std::vector<SubsampleEntry>(kSubsamples,
|
||||
kSubsamples + arraysize(kSubsamples)));
|
||||
ASSERT_FALSE(decryptor_source_.DecryptSampleBuffer(
|
||||
&decrypt_config, &buffer_[0], buffer_.size()));
|
||||
}
|
||||
|
||||
TEST_F(DecryptorSourceTest, DecryptFailedIfGetKeyFailed) {
|
||||
EXPECT_CALL(mock_key_source_, GetKey(key_id_, _))
|
||||
.WillOnce(Return(Status::UNKNOWN));
|
||||
|
||||
DecryptConfig decrypt_config(key_id_,
|
||||
std::vector<uint8_t>(kIv, kIv + arraysize(kIv)),
|
||||
std::vector<SubsampleEntry>());
|
||||
ASSERT_FALSE(decryptor_source_.DecryptSampleBuffer(
|
||||
&decrypt_config, &buffer_[0], buffer_.size()));
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
|
@ -35,6 +35,7 @@
|
|||
'demuxer.h',
|
||||
'decrypt_config.cc',
|
||||
'decrypt_config.h',
|
||||
'decryptor_source.cc',
|
||||
'decryptor_source.h',
|
||||
'http_key_fetcher.cc',
|
||||
'http_key_fetcher.h',
|
||||
|
@ -87,6 +88,19 @@
|
|||
'../../version/version.gyp:version',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'widevine_pssh_data_proto',
|
||||
'type': '<(component)',
|
||||
'sources': ['widevine_pssh_data.proto'],
|
||||
'variables': {
|
||||
'proto_in_dir': '.',
|
||||
'proto_out_dir': 'packager/media/base',
|
||||
},
|
||||
'includes': ['../../build/protoc.gypi'],
|
||||
'dependencies': [
|
||||
'../../third_party/protobuf/protobuf.gyp:protobuf_lite',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'media_base_unittest',
|
||||
'type': '<(gtest_target_type)',
|
||||
|
@ -97,6 +111,7 @@
|
|||
'buffer_writer_unittest.cc',
|
||||
'closure_thread_unittest.cc',
|
||||
'container_names_unittest.cc',
|
||||
'decryptor_source_unittest.cc',
|
||||
'http_key_fetcher_unittest.cc',
|
||||
'muxer_util_unittest.cc',
|
||||
'offset_byte_queue_unittest.cc',
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
//
|
||||
// This file defines Widevine Pssh Data proto format.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package edash_packager.media;
|
||||
|
||||
message WidevinePsshData {
|
||||
enum Algorithm {
|
||||
UNENCRYPTED = 0;
|
||||
AESCTR = 1;
|
||||
};
|
||||
optional Algorithm algorithm = 1;
|
||||
repeated bytes key_id = 2;
|
||||
|
||||
// Content provider name.
|
||||
optional string provider = 3;
|
||||
|
||||
// A content identifier, specified by content provider.
|
||||
optional bytes content_id = 4;
|
||||
|
||||
// The name of a registered policy to be used for this asset.
|
||||
optional string policy = 6;
|
||||
|
||||
// Crypto period index, for media using key rotation.
|
||||
optional uint32 crypto_period_index = 7;
|
||||
|
||||
// Optional protected context for group content. The grouped_license is a
|
||||
// serialized SignedMessage.
|
||||
optional bytes grouped_license = 8;
|
||||
}
|
|
@ -11,7 +11,6 @@
|
|||
#include "packager/base/logging.h"
|
||||
#include "packager/base/memory/ref_counted.h"
|
||||
#include "packager/base/strings/string_number_conversions.h"
|
||||
#include "packager/media/base/aes_encryptor.h"
|
||||
#include "packager/media/base/audio_stream_info.h"
|
||||
#include "packager/media/base/buffer_reader.h"
|
||||
#include "packager/media/base/decrypt_config.h"
|
||||
|
@ -90,11 +89,12 @@ const uint8_t kDtsAudioNumChannels = 6;
|
|||
} // namespace
|
||||
|
||||
MP4MediaParser::MP4MediaParser()
|
||||
: state_(kWaitingForInit), moof_head_(0), mdat_tail_(0) {}
|
||||
: state_(kWaitingForInit),
|
||||
decryption_key_source_(NULL),
|
||||
moof_head_(0),
|
||||
mdat_tail_(0) {}
|
||||
|
||||
MP4MediaParser::~MP4MediaParser() {
|
||||
STLDeleteValues(&decryptor_map_);
|
||||
}
|
||||
MP4MediaParser::~MP4MediaParser() {}
|
||||
|
||||
void MP4MediaParser::Init(const InitCB& init_cb,
|
||||
const NewSampleCB& new_sample_cb,
|
||||
|
@ -108,6 +108,8 @@ void MP4MediaParser::Init(const InitCB& init_cb,
|
|||
init_cb_ = init_cb;
|
||||
new_sample_cb_ = new_sample_cb;
|
||||
decryption_key_source_ = decryption_key_source;
|
||||
if (decryption_key_source)
|
||||
decryptor_source_.reset(new DecryptorSource(decryption_key_source));
|
||||
}
|
||||
|
||||
void MP4MediaParser::Reset() {
|
||||
|
@ -652,11 +654,18 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
|||
scoped_refptr<MediaSample> stream_sample(MediaSample::CopyFrom(
|
||||
buf, runs_->sample_size(), runs_->is_keyframe()));
|
||||
if (runs_->is_encrypted()) {
|
||||
if (!decryptor_source_) {
|
||||
*err = true;
|
||||
LOG(ERROR) << "Encrypted media sample encountered, but decryption is not "
|
||||
"enabled";
|
||||
return false;
|
||||
}
|
||||
|
||||
scoped_ptr<DecryptConfig> decrypt_config = runs_->GetDecryptConfig();
|
||||
if (!decrypt_config ||
|
||||
!DecryptSampleBuffer(decrypt_config.get(),
|
||||
stream_sample->writable_data(),
|
||||
stream_sample->data_size())) {
|
||||
!decryptor_source_->DecryptSampleBuffer(decrypt_config.get(),
|
||||
stream_sample->writable_data(),
|
||||
stream_sample->data_size())) {
|
||||
*err = true;
|
||||
LOG(ERROR) << "Cannot decrypt samples.";
|
||||
return false;
|
||||
|
@ -684,80 +693,6 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MP4MediaParser::DecryptSampleBuffer(const DecryptConfig* decrypt_config,
|
||||
uint8_t* buffer,
|
||||
size_t buffer_size) {
|
||||
DCHECK(decrypt_config);
|
||||
DCHECK(buffer);
|
||||
|
||||
if (!decryption_key_source_) {
|
||||
LOG(ERROR) << "Encrypted media sample encountered, but decryption is not "
|
||||
"enabled";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the encryptor object.
|
||||
AesCtrEncryptor* encryptor;
|
||||
DecryptorMap::iterator found = decryptor_map_.find(decrypt_config->key_id());
|
||||
if (found == decryptor_map_.end()) {
|
||||
// Create new AesCtrEncryptor
|
||||
EncryptionKey key;
|
||||
Status status(decryption_key_source_->GetKey(decrypt_config->key_id(),
|
||||
&key));
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Error retrieving decryption key: " << status;
|
||||
return false;
|
||||
}
|
||||
scoped_ptr<AesCtrEncryptor> new_encryptor(new AesCtrEncryptor);
|
||||
if (!new_encryptor->InitializeWithIv(key.key, decrypt_config->iv())) {
|
||||
LOG(ERROR) << "Failed to initialize AesCtrEncryptor for decryption.";
|
||||
return false;
|
||||
}
|
||||
encryptor = new_encryptor.release();
|
||||
decryptor_map_[decrypt_config->key_id()] = encryptor;
|
||||
} else {
|
||||
encryptor = found->second;
|
||||
}
|
||||
if (!encryptor->SetIv(decrypt_config->iv())) {
|
||||
LOG(ERROR) << "Invalid initialization vector.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (decrypt_config->subsamples().empty()) {
|
||||
// Sample not encrypted using subsample encryption. Decrypt whole.
|
||||
if (!encryptor->Decrypt(buffer, buffer_size, buffer)) {
|
||||
LOG(ERROR) << "Error during bulk sample decryption.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Subsample decryption.
|
||||
const std::vector<SubsampleEntry>& subsamples = decrypt_config->subsamples();
|
||||
uint8_t* current_ptr = buffer;
|
||||
const uint8_t* buffer_end = buffer + buffer_size;
|
||||
current_ptr += decrypt_config->data_offset();
|
||||
if (current_ptr > buffer_end) {
|
||||
LOG(ERROR) << "Subsample data_offset too large.";
|
||||
return false;
|
||||
}
|
||||
for (std::vector<SubsampleEntry>::const_iterator iter = subsamples.begin();
|
||||
iter != subsamples.end();
|
||||
++iter) {
|
||||
if ((current_ptr + iter->clear_bytes + iter->cipher_bytes) > buffer_end) {
|
||||
LOG(ERROR) << "Subsamples overflow sample buffer.";
|
||||
return false;
|
||||
}
|
||||
current_ptr += iter->clear_bytes;
|
||||
if (!encryptor->Decrypt(current_ptr, iter->cipher_bytes, current_ptr)) {
|
||||
LOG(ERROR) << "Error decrypting subsample buffer.";
|
||||
return false;
|
||||
}
|
||||
current_ptr += iter->cipher_bytes;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MP4MediaParser::ReadAndDiscardMDATsUntil(const int64_t offset) {
|
||||
bool err = false;
|
||||
while (mdat_tail_ < offset) {
|
||||
|
|
|
@ -17,15 +17,12 @@
|
|||
#include "packager/base/memory/scoped_ptr.h"
|
||||
#include "packager/base/memory/ref_counted.h"
|
||||
#include "packager/base/memory/scoped_ptr.h"
|
||||
#include "packager/media/base/decryptor_source.h"
|
||||
#include "packager/media/base/media_parser.h"
|
||||
#include "packager/media/base/offset_byte_queue.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
|
||||
class AesCtrEncryptor;
|
||||
class DecryptConfig;
|
||||
|
||||
namespace mp4 {
|
||||
|
||||
class BoxReader;
|
||||
|
@ -70,10 +67,6 @@ class MP4MediaParser : public MediaParser {
|
|||
bool FetchKeysIfNecessary(
|
||||
const std::vector<ProtectionSystemSpecificHeader>& headers);
|
||||
|
||||
bool DecryptSampleBuffer(const DecryptConfig* decrypt_config,
|
||||
uint8_t* buffer,
|
||||
size_t buffer_size);
|
||||
|
||||
// To retain proper framing, each 'mdat' box must be read; to limit memory
|
||||
// usage, the box's data needs to be discarded incrementally as frames are
|
||||
// extracted from the stream. This function discards data from the stream up
|
||||
|
@ -94,6 +87,7 @@ class MP4MediaParser : public MediaParser {
|
|||
InitCB init_cb_;
|
||||
NewSampleCB new_sample_cb_;
|
||||
KeySource* decryption_key_source_;
|
||||
scoped_ptr<DecryptorSource> decryptor_source_;
|
||||
|
||||
OffsetByteQueue queue_;
|
||||
|
||||
|
@ -110,9 +104,6 @@ class MP4MediaParser : public MediaParser {
|
|||
scoped_ptr<Movie> moov_;
|
||||
scoped_ptr<TrackRunIterator> runs_;
|
||||
|
||||
typedef std::map<std::vector<uint8_t>, AesCtrEncryptor*> DecryptorMap;
|
||||
DecryptorMap decryptor_map_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MP4MediaParser);
|
||||
};
|
||||
|
||||
|
|
|
@ -600,7 +600,6 @@ scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
|
|||
return scoped_ptr<DecryptConfig>(new DecryptConfig(
|
||||
track_encryption().default_kid,
|
||||
sample_encryption_entry.initialization_vector,
|
||||
0, // No offset to start of media data in MP4 using CENC.
|
||||
sample_encryption_entry.subsamples));
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
'../../../third_party/boringssl/boringssl.gyp:boringssl',
|
||||
'../../../third_party/libwebm/libwebm.gyp:libwebm',
|
||||
'../../base/media_base.gyp:base',
|
||||
'../../base/media_base.gyp:widevine_pssh_data_proto',
|
||||
'../../filters/filters.gyp:filters'
|
||||
],
|
||||
},
|
||||
|
|
|
@ -60,7 +60,8 @@ WebMClusterParser::WebMClusterParser(
|
|||
const std::string& audio_encryption_key_id,
|
||||
const std::string& video_encryption_key_id,
|
||||
const MediaParser::NewSampleCB& new_sample_cb,
|
||||
const MediaParser::InitCB& init_cb)
|
||||
const MediaParser::InitCB& init_cb,
|
||||
KeySource* decryption_key_source)
|
||||
: timecode_multiplier_(timecode_scale / 1000.0),
|
||||
audio_stream_info_(audio_stream_info),
|
||||
video_stream_info_(video_stream_info),
|
||||
|
@ -79,6 +80,8 @@ WebMClusterParser::WebMClusterParser(
|
|||
true,
|
||||
video_default_duration,
|
||||
new_sample_cb) {
|
||||
if (decryption_key_source)
|
||||
decryptor_source_.reset(new DecryptorSource(decryption_key_source));
|
||||
for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin();
|
||||
it != text_tracks.end();
|
||||
++it) {
|
||||
|
@ -326,11 +329,12 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
|
|||
}
|
||||
|
||||
Track* track = NULL;
|
||||
StreamType stream_type = kStreamAudio;
|
||||
StreamType stream_type = kStreamUnknown;
|
||||
std::string encryption_key_id;
|
||||
if (track_num == audio_.track_num()) {
|
||||
track = &audio_;
|
||||
encryption_key_id = audio_encryption_key_id_;
|
||||
stream_type = kStreamAudio;
|
||||
} else if (track_num == video_.track_num()) {
|
||||
track = &video_;
|
||||
encryption_key_id = video_encryption_key_id_;
|
||||
|
@ -348,6 +352,7 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
|
|||
LOG(ERROR) << "Unexpected track number " << track_num;
|
||||
return false;
|
||||
}
|
||||
DCHECK_NE(stream_type, kStreamUnknown);
|
||||
|
||||
last_block_timecode_ = timecode;
|
||||
|
||||
|
@ -384,9 +389,19 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
|
|||
buffer = MediaSample::CopyFrom(data + data_offset, size - data_offset,
|
||||
additional, additional_size, is_keyframe);
|
||||
|
||||
if (decrypt_config) {
|
||||
// TODO(kqyang): Decrypt it if it is encrypted.
|
||||
buffer->set_is_encrypted(true);
|
||||
// An empty iv indicates that this sample is not encrypted.
|
||||
if (decrypt_config && !decrypt_config->iv().empty()) {
|
||||
if (!decryptor_source_) {
|
||||
LOG(ERROR) << "Encrypted media sample encountered, but decryption is "
|
||||
"not enabled";
|
||||
return false;
|
||||
}
|
||||
if (!decryptor_source_->DecryptSampleBuffer(decrypt_config.get(),
|
||||
buffer->writable_data(),
|
||||
buffer->data_size())) {
|
||||
LOG(ERROR) << "Cannot decrypt samples";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::string id, settings, content;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <string>
|
||||
|
||||
#include "packager/base/memory/scoped_ptr.h"
|
||||
#include "packager/media/base/decryptor_source.h"
|
||||
#include "packager/media/base/media_parser.h"
|
||||
#include "packager/media/base/media_sample.h"
|
||||
#include "packager/media/formats/webm/webm_parser.h"
|
||||
|
@ -93,6 +94,28 @@ class WebMClusterParser : public WebMParserClient {
|
|||
typedef std::map<int, Track> TextTrackMap;
|
||||
|
||||
public:
|
||||
/// Create a WebMClusterParser from given parameters.
|
||||
/// @param timecode_scale indicates timecode scale for the clusters.
|
||||
/// @param audio_stream_info references audio stream information. It will
|
||||
/// be NULL if there are no audio tracks available.
|
||||
/// @param video_stream_info references video stream information. It will
|
||||
/// be NULL if there are no video tracks available.
|
||||
/// @param audio_default_duration indicates default duration for audio
|
||||
/// samples.
|
||||
/// @param video_default_duration indicates default duration for video
|
||||
/// samples.
|
||||
/// @param text_tracks contains text track information.
|
||||
/// @param ignored_tracks contains a list of ignored track ids.
|
||||
/// @param audio_encryption_key_id indicates the encryption key id for audio
|
||||
/// samples if there is an audio stream and the audio stream is
|
||||
/// encrypted. It is empty otherwise.
|
||||
/// @param video_encryption_key_id indicates the encryption key id for video
|
||||
/// samples if there is a video stream and the video stream is
|
||||
/// encrypted. It is empty otherwise.
|
||||
/// @param new_sample_cb is the callback to emit new samples.
|
||||
/// @param init_cb is the callback to initialize streams.
|
||||
/// @param decryption_key_source points to a decryption key source to fetch
|
||||
/// decryption keys. Should not be NULL if the tracks are encrypted.
|
||||
WebMClusterParser(int64_t timecode_scale,
|
||||
scoped_refptr<AudioStreamInfo> audio_stream_info,
|
||||
scoped_refptr<VideoStreamInfo> video_stream_info,
|
||||
|
@ -103,7 +126,8 @@ class WebMClusterParser : public WebMParserClient {
|
|||
const std::string& audio_encryption_key_id,
|
||||
const std::string& video_encryption_key_id,
|
||||
const MediaParser::NewSampleCB& new_sample_cb,
|
||||
const MediaParser::InitCB& init_cb);
|
||||
const MediaParser::InitCB& init_cb,
|
||||
KeySource* decryption_key_source);
|
||||
~WebMClusterParser() override;
|
||||
|
||||
/// Resets the parser state so it can accept a new cluster.
|
||||
|
@ -162,6 +186,8 @@ class WebMClusterParser : public WebMParserClient {
|
|||
scoped_refptr<AudioStreamInfo> audio_stream_info_;
|
||||
scoped_refptr<VideoStreamInfo> video_stream_info_;
|
||||
std::set<int64_t> ignored_tracks_;
|
||||
|
||||
scoped_ptr<DecryptorSource> decryptor_source_;
|
||||
std::string audio_encryption_key_id_;
|
||||
std::string video_encryption_key_id_;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
using ::testing::HasSubstr;
|
||||
using ::testing::InSequence;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
using ::testing::Mock;
|
||||
using ::testing::_;
|
||||
|
@ -137,6 +138,23 @@ const uint8_t kEncryptedFrame[] = {
|
|||
// Some dummy encrypted data
|
||||
0x01,
|
||||
};
|
||||
const uint8_t kMockKey[] = {
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
|
||||
};
|
||||
const uint8_t kExpectedDecryptedFrame[] = {
|
||||
0x41,
|
||||
};
|
||||
|
||||
const uint8_t kClearFrameInEncryptedTrack[] = {
|
||||
// Block is not encrypted
|
||||
0x00,
|
||||
// Some dummy frame data
|
||||
0x01, 0x02, 0x03,
|
||||
};
|
||||
const uint8_t kExpectedClearFrame[] = {
|
||||
0x01, 0x02, 0x03,
|
||||
};
|
||||
|
||||
const uint8_t kVP8Frame[] = {
|
||||
0x52, 0x04, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00, 0x00, 0x47,
|
||||
|
@ -153,6 +171,12 @@ const uint8_t kVP9Frame[] = {
|
|||
0xe1, 0xe6, 0xef, 0xff, 0xfd, 0xf7, 0x4f, 0x0f,
|
||||
};
|
||||
|
||||
class MockKeySource : public KeySource {
|
||||
public:
|
||||
MOCK_METHOD2(GetKey,
|
||||
Status(const std::vector<uint8_t>& key_id, EncryptionKey* key));
|
||||
};
|
||||
|
||||
scoped_ptr<Cluster> CreateCluster(int timecode,
|
||||
const BlockInfo* block_info,
|
||||
int block_count) {
|
||||
|
@ -284,10 +308,6 @@ bool VerifyTextBuffers(const BlockInfo* block_info_ptr,
|
|||
return true;
|
||||
}
|
||||
|
||||
void VerifyEncryptedBuffer(scoped_refptr<MediaSample> buffer) {
|
||||
EXPECT_TRUE(buffer->is_encrypted());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class WebMClusterParserTest : public testing::Test {
|
||||
|
@ -382,7 +402,7 @@ class WebMClusterParserTest : public testing::Test {
|
|||
ignored_tracks, audio_encryption_key_id, video_encryption_key_id,
|
||||
base::Bind(&WebMClusterParserTest::NewSampleEvent,
|
||||
base::Unretained(this)),
|
||||
init_cb);
|
||||
init_cb, &mock_key_source_);
|
||||
}
|
||||
|
||||
// Create a default version of the parser for test.
|
||||
|
@ -450,6 +470,7 @@ class WebMClusterParserTest : public testing::Test {
|
|||
BufferQueue audio_buffers_;
|
||||
BufferQueue video_buffers_;
|
||||
TextBufferQueueMap text_buffers_map_;
|
||||
StrictMock<MockKeySource> mock_key_source_;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(WebMClusterParserTest);
|
||||
|
@ -806,6 +827,36 @@ TEST_F(WebMClusterParserTest, ParseVP9) {
|
|||
}
|
||||
|
||||
TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
|
||||
const std::string video_key_id("video_key_id");
|
||||
|
||||
EncryptionKey encryption_key;
|
||||
encryption_key.key.assign(kMockKey, kMockKey + arraysize(kMockKey));
|
||||
EXPECT_CALL(
|
||||
mock_key_source_,
|
||||
GetKey(std::vector<uint8_t>(video_key_id.begin(), video_key_id.end()), _))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
|
||||
|
||||
scoped_ptr<Cluster> cluster(
|
||||
CreateCluster(kEncryptedFrame, arraysize(kEncryptedFrame)));
|
||||
|
||||
parser_.reset(CreateParserWithKeyIdsAndAudioCodec(std::string(), video_key_id,
|
||||
kUnknownAudioCodec));
|
||||
|
||||
int result = parser_->Parse(cluster->data(), cluster->size());
|
||||
EXPECT_EQ(cluster->size(), result);
|
||||
parser_->Flush();
|
||||
ASSERT_EQ(1UL, video_buffers_.size());
|
||||
scoped_refptr<MediaSample> buffer = video_buffers_[0];
|
||||
EXPECT_EQ(std::vector<uint8_t>(
|
||||
kExpectedDecryptedFrame,
|
||||
kExpectedDecryptedFrame + arraysize(kExpectedDecryptedFrame)),
|
||||
std::vector<uint8_t>(buffer->data(),
|
||||
buffer->data() + buffer->data_size()));
|
||||
}
|
||||
|
||||
TEST_F(WebMClusterParserTest, ParseEncryptedBlockGetKeyFailed) {
|
||||
EXPECT_CALL(mock_key_source_, GetKey(_, _)).WillOnce(Return(Status::UNKNOWN));
|
||||
|
||||
scoped_ptr<Cluster> cluster(
|
||||
CreateCluster(kEncryptedFrame, arraysize(kEncryptedFrame)));
|
||||
|
||||
|
@ -813,11 +864,7 @@ TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
|
|||
std::string(), "video_key_id", kUnknownAudioCodec));
|
||||
|
||||
int result = parser_->Parse(cluster->data(), cluster->size());
|
||||
EXPECT_EQ(cluster->size(), result);
|
||||
parser_->Flush();
|
||||
ASSERT_EQ(1UL, video_buffers_.size());
|
||||
scoped_refptr<MediaSample> buffer = video_buffers_[0];
|
||||
VerifyEncryptedBuffer(buffer);
|
||||
EXPECT_EQ(-1, result);
|
||||
}
|
||||
|
||||
TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
|
||||
|
@ -830,6 +877,25 @@ TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
|
|||
EXPECT_EQ(-1, result);
|
||||
}
|
||||
|
||||
TEST_F(WebMClusterParserTest, ParseClearFrameInEncryptedTrack) {
|
||||
scoped_ptr<Cluster> cluster(CreateCluster(
|
||||
kClearFrameInEncryptedTrack, arraysize(kClearFrameInEncryptedTrack)));
|
||||
|
||||
parser_.reset(CreateParserWithKeyIdsAndAudioCodec(
|
||||
std::string(), "video_key_id", kUnknownAudioCodec));
|
||||
|
||||
int result = parser_->Parse(cluster->data(), cluster->size());
|
||||
EXPECT_EQ(cluster->size(), result);
|
||||
parser_->Flush();
|
||||
ASSERT_EQ(1UL, video_buffers_.size());
|
||||
scoped_refptr<MediaSample> buffer = video_buffers_[0];
|
||||
EXPECT_EQ(std::vector<uint8_t>(
|
||||
kExpectedClearFrame,
|
||||
kExpectedClearFrame + arraysize(kExpectedClearFrame)),
|
||||
std::vector<uint8_t>(buffer->data(),
|
||||
buffer->data() + buffer->data_size()));
|
||||
}
|
||||
|
||||
TEST_F(WebMClusterParserTest, ParseInvalidZeroSizedCluster) {
|
||||
const uint8_t kBuffer[] = {
|
||||
0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0)
|
||||
|
|
|
@ -56,7 +56,7 @@ bool WebMCreateDecryptConfig(const uint8_t* data,
|
|||
decrypt_config->reset(new DecryptConfig(
|
||||
std::vector<uint8_t>(key_id, key_id + key_id_size),
|
||||
std::vector<uint8_t>(counter_block.begin(), counter_block.end()),
|
||||
frame_offset, std::vector<SubsampleEntry>()));
|
||||
std::vector<SubsampleEntry>()));
|
||||
*data_offset = frame_offset;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
#include "packager/base/callback.h"
|
||||
#include "packager/base/callback_helpers.h"
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/media/base/buffer_writer.h"
|
||||
#include "packager/media/base/timestamp.h"
|
||||
#include "packager/media/base/widevine_pssh_data.pb.h"
|
||||
#include "packager/media/formats/webm/webm_cluster_parser.h"
|
||||
#include "packager/media/formats/webm/webm_constants.h"
|
||||
#include "packager/media/formats/webm/webm_content_encodings.h"
|
||||
|
@ -185,8 +187,6 @@ int WebMMediaParser::ParseInfoAndTracks(const uint8_t* data, int size) {
|
|||
tracks_parser.audio_stream_info();
|
||||
if (audio_stream_info) {
|
||||
audio_stream_info->set_duration(duration_in_us);
|
||||
if (audio_stream_info->is_encrypted())
|
||||
OnEncryptedMediaInitData(tracks_parser.audio_encryption_key_id());
|
||||
} else {
|
||||
VLOG(1) << "No audio track info found.";
|
||||
}
|
||||
|
@ -195,19 +195,23 @@ int WebMMediaParser::ParseInfoAndTracks(const uint8_t* data, int size) {
|
|||
tracks_parser.video_stream_info();
|
||||
if (video_stream_info) {
|
||||
video_stream_info->set_duration(duration_in_us);
|
||||
if (video_stream_info->is_encrypted())
|
||||
OnEncryptedMediaInitData(tracks_parser.video_encryption_key_id());
|
||||
} else {
|
||||
VLOG(1) << "No video track info found.";
|
||||
}
|
||||
|
||||
if (!FetchKeysIfNecessary(tracks_parser.audio_encryption_key_id(),
|
||||
tracks_parser.video_encryption_key_id())) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cluster_parser_.reset(new WebMClusterParser(
|
||||
info_parser.timecode_scale(), audio_stream_info, video_stream_info,
|
||||
tracks_parser.GetAudioDefaultDuration(timecode_scale_in_us),
|
||||
tracks_parser.GetVideoDefaultDuration(timecode_scale_in_us),
|
||||
tracks_parser.text_tracks(), tracks_parser.ignored_tracks(),
|
||||
tracks_parser.audio_encryption_key_id(),
|
||||
tracks_parser.video_encryption_key_id(), new_sample_cb_, init_cb_));
|
||||
tracks_parser.video_encryption_key_id(), new_sample_cb_, init_cb_,
|
||||
decryption_key_source_));
|
||||
|
||||
return bytes_parsed;
|
||||
}
|
||||
|
@ -228,8 +232,30 @@ int WebMMediaParser::ParseCluster(const uint8_t* data, int size) {
|
|||
return bytes_parsed;
|
||||
}
|
||||
|
||||
void WebMMediaParser::OnEncryptedMediaInitData(const std::string& key_id) {
|
||||
NOTIMPLEMENTED() << "WebM decryption is not implemented yet.";
|
||||
bool WebMMediaParser::FetchKeysIfNecessary(
|
||||
const std::string& audio_encryption_key_id,
|
||||
const std::string& video_encryption_key_id) {
|
||||
if (audio_encryption_key_id.empty() && video_encryption_key_id.empty())
|
||||
return true;
|
||||
// An error will be returned later if the samples need to be derypted.
|
||||
if (!decryption_key_source_)
|
||||
return true;
|
||||
|
||||
// Generate WidevinePsshData from key_id.
|
||||
WidevinePsshData widevine_pssh_data;
|
||||
if (!audio_encryption_key_id.empty())
|
||||
widevine_pssh_data.add_key_id(audio_encryption_key_id);
|
||||
if (!video_encryption_key_id.empty())
|
||||
widevine_pssh_data.add_key_id(video_encryption_key_id);
|
||||
|
||||
const std::string serialized_string = widevine_pssh_data.SerializeAsString();
|
||||
Status status = decryption_key_source_->FetchKeys(
|
||||
std::vector<uint8_t>(serialized_string.begin(), serialized_string.end()));
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Error fetching decryption keys: " << status;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
|
|
|
@ -56,8 +56,9 @@ class WebMMediaParser : public MediaParser {
|
|||
// Returning > 0 indicates success & the number of bytes parsed.
|
||||
int ParseCluster(const uint8_t* data, int size);
|
||||
|
||||
// Fire needkey event through the |encrypted_media_init_data_cb_|.
|
||||
void OnEncryptedMediaInitData(const std::string& key_id);
|
||||
// Fetch keys for the input key ids. Returns true on success, false otherwise.
|
||||
bool FetchKeysIfNecessary(const std::string& audio_encryption_key_id,
|
||||
const std::string& video_encryption_key_id);
|
||||
|
||||
State state_;
|
||||
InitCB init_cb_;
|
||||
|
|
Loading…
Reference in New Issue