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,
|
DecryptConfig::DecryptConfig(const std::vector<uint8_t>& key_id,
|
||||||
const std::vector<uint8_t>& iv,
|
const std::vector<uint8_t>& iv,
|
||||||
const int data_offset,
|
|
||||||
const std::vector<SubsampleEntry>& subsamples)
|
const std::vector<SubsampleEntry>& subsamples)
|
||||||
: key_id_(key_id),
|
: key_id_(key_id), iv_(iv), subsamples_(subsamples) {
|
||||||
iv_(iv),
|
|
||||||
data_offset_(data_offset),
|
|
||||||
subsamples_(subsamples) {
|
|
||||||
CHECK_GT(key_id.size(), 0u);
|
CHECK_GT(key_id.size(), 0u);
|
||||||
CHECK_GE(data_offset, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DecryptConfig::~DecryptConfig() {}
|
DecryptConfig::~DecryptConfig() {}
|
||||||
|
|
|
@ -38,22 +38,16 @@ class DecryptConfig {
|
||||||
|
|
||||||
/// @param key_id is the ID that references the decryption key.
|
/// @param key_id is the ID that references the decryption key.
|
||||||
/// @param iv is the initialization vector defined by the encryptor.
|
/// @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
|
/// @param subsamples defines the clear and encrypted portions of the sample
|
||||||
/// as described in SubsampleEntry. A decrypted buffer will be equal
|
/// as described in SubsampleEntry. A decrypted buffer will be equal
|
||||||
/// in size to the sum of the subsample sizes.
|
/// in size to the sum of the subsample sizes.
|
||||||
DecryptConfig(const std::vector<uint8_t>& key_id,
|
DecryptConfig(const std::vector<uint8_t>& key_id,
|
||||||
const std::vector<uint8_t>& iv,
|
const std::vector<uint8_t>& iv,
|
||||||
const int data_offset,
|
|
||||||
const std::vector<SubsampleEntry>& subsamples);
|
const std::vector<SubsampleEntry>& subsamples);
|
||||||
~DecryptConfig();
|
~DecryptConfig();
|
||||||
|
|
||||||
const std::vector<uint8_t>& key_id() const { return key_id_; }
|
const std::vector<uint8_t>& key_id() const { return key_id_; }
|
||||||
const std::vector<uint8_t>& iv() const { return iv_; }
|
const std::vector<uint8_t>& iv() const { return iv_; }
|
||||||
int data_offset() const { return data_offset_; }
|
|
||||||
const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; }
|
const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -62,9 +56,6 @@ class DecryptConfig {
|
||||||
// Initialization vector.
|
// Initialization vector.
|
||||||
const std::vector<uint8_t> iv_;
|
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
|
// Subsample information. May be empty for some formats, meaning entire frame
|
||||||
// (less data ignored by data_offset_) is encrypted.
|
// (less data ignored by data_offset_) is encrypted.
|
||||||
const std::vector<SubsampleEntry> subsamples_;
|
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
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
|
@ -7,28 +7,30 @@
|
||||||
#ifndef MEDIA_BASE_DECRYPTOR_SOURCE_H_
|
#ifndef MEDIA_BASE_DECRYPTOR_SOURCE_H_
|
||||||
#define MEDIA_BASE_DECRYPTOR_SOURCE_H_
|
#define MEDIA_BASE_DECRYPTOR_SOURCE_H_
|
||||||
|
|
||||||
#include "packager/base/memory/scoped_ptr.h"
|
#include <map>
|
||||||
#include "packager/media/base/container_names.h"
|
#include <vector>
|
||||||
#include "packager/media/base/status.h"
|
|
||||||
|
#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 edash_packager {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
/// DecryptorSource is responsible for decryption key acquisition.
|
/// DecryptorSource wraps KeySource and is responsible for decryptor management.
|
||||||
class DecryptorSource {
|
class DecryptorSource {
|
||||||
public:
|
public:
|
||||||
DecryptorSource() {}
|
explicit DecryptorSource(KeySource* key_source);
|
||||||
virtual ~DecryptorSource() {}
|
~DecryptorSource();
|
||||||
|
|
||||||
/// NeedKey event handler.
|
bool DecryptSampleBuffer(const DecryptConfig* decrypt_config,
|
||||||
/// @param container indicates the container format.
|
uint8_t* buffer,
|
||||||
/// @param init_data specifies container dependent initialization data that
|
size_t buffer_size);
|
||||||
/// 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;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
KeySource* key_source_;
|
||||||
|
std::map<std::vector<uint8_t>, AesCtrEncryptor*> decryptor_map_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(DecryptorSource);
|
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',
|
'demuxer.h',
|
||||||
'decrypt_config.cc',
|
'decrypt_config.cc',
|
||||||
'decrypt_config.h',
|
'decrypt_config.h',
|
||||||
|
'decryptor_source.cc',
|
||||||
'decryptor_source.h',
|
'decryptor_source.h',
|
||||||
'http_key_fetcher.cc',
|
'http_key_fetcher.cc',
|
||||||
'http_key_fetcher.h',
|
'http_key_fetcher.h',
|
||||||
|
@ -87,6 +88,19 @@
|
||||||
'../../version/version.gyp:version',
|
'../../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',
|
'target_name': 'media_base_unittest',
|
||||||
'type': '<(gtest_target_type)',
|
'type': '<(gtest_target_type)',
|
||||||
|
@ -97,6 +111,7 @@
|
||||||
'buffer_writer_unittest.cc',
|
'buffer_writer_unittest.cc',
|
||||||
'closure_thread_unittest.cc',
|
'closure_thread_unittest.cc',
|
||||||
'container_names_unittest.cc',
|
'container_names_unittest.cc',
|
||||||
|
'decryptor_source_unittest.cc',
|
||||||
'http_key_fetcher_unittest.cc',
|
'http_key_fetcher_unittest.cc',
|
||||||
'muxer_util_unittest.cc',
|
'muxer_util_unittest.cc',
|
||||||
'offset_byte_queue_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/logging.h"
|
||||||
#include "packager/base/memory/ref_counted.h"
|
#include "packager/base/memory/ref_counted.h"
|
||||||
#include "packager/base/strings/string_number_conversions.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/audio_stream_info.h"
|
||||||
#include "packager/media/base/buffer_reader.h"
|
#include "packager/media/base/buffer_reader.h"
|
||||||
#include "packager/media/base/decrypt_config.h"
|
#include "packager/media/base/decrypt_config.h"
|
||||||
|
@ -90,11 +89,12 @@ const uint8_t kDtsAudioNumChannels = 6;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MP4MediaParser::MP4MediaParser()
|
MP4MediaParser::MP4MediaParser()
|
||||||
: state_(kWaitingForInit), moof_head_(0), mdat_tail_(0) {}
|
: state_(kWaitingForInit),
|
||||||
|
decryption_key_source_(NULL),
|
||||||
|
moof_head_(0),
|
||||||
|
mdat_tail_(0) {}
|
||||||
|
|
||||||
MP4MediaParser::~MP4MediaParser() {
|
MP4MediaParser::~MP4MediaParser() {}
|
||||||
STLDeleteValues(&decryptor_map_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MP4MediaParser::Init(const InitCB& init_cb,
|
void MP4MediaParser::Init(const InitCB& init_cb,
|
||||||
const NewSampleCB& new_sample_cb,
|
const NewSampleCB& new_sample_cb,
|
||||||
|
@ -108,6 +108,8 @@ void MP4MediaParser::Init(const InitCB& init_cb,
|
||||||
init_cb_ = init_cb;
|
init_cb_ = init_cb;
|
||||||
new_sample_cb_ = new_sample_cb;
|
new_sample_cb_ = new_sample_cb;
|
||||||
decryption_key_source_ = decryption_key_source;
|
decryption_key_source_ = decryption_key_source;
|
||||||
|
if (decryption_key_source)
|
||||||
|
decryptor_source_.reset(new DecryptorSource(decryption_key_source));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4MediaParser::Reset() {
|
void MP4MediaParser::Reset() {
|
||||||
|
@ -652,11 +654,18 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
||||||
scoped_refptr<MediaSample> stream_sample(MediaSample::CopyFrom(
|
scoped_refptr<MediaSample> stream_sample(MediaSample::CopyFrom(
|
||||||
buf, runs_->sample_size(), runs_->is_keyframe()));
|
buf, runs_->sample_size(), runs_->is_keyframe()));
|
||||||
if (runs_->is_encrypted()) {
|
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();
|
scoped_ptr<DecryptConfig> decrypt_config = runs_->GetDecryptConfig();
|
||||||
if (!decrypt_config ||
|
if (!decrypt_config ||
|
||||||
!DecryptSampleBuffer(decrypt_config.get(),
|
!decryptor_source_->DecryptSampleBuffer(decrypt_config.get(),
|
||||||
stream_sample->writable_data(),
|
stream_sample->writable_data(),
|
||||||
stream_sample->data_size())) {
|
stream_sample->data_size())) {
|
||||||
*err = true;
|
*err = true;
|
||||||
LOG(ERROR) << "Cannot decrypt samples.";
|
LOG(ERROR) << "Cannot decrypt samples.";
|
||||||
return false;
|
return false;
|
||||||
|
@ -684,80 +693,6 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
||||||
return true;
|
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 MP4MediaParser::ReadAndDiscardMDATsUntil(const int64_t offset) {
|
||||||
bool err = false;
|
bool err = false;
|
||||||
while (mdat_tail_ < offset) {
|
while (mdat_tail_ < offset) {
|
||||||
|
|
|
@ -17,15 +17,12 @@
|
||||||
#include "packager/base/memory/scoped_ptr.h"
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
#include "packager/base/memory/ref_counted.h"
|
#include "packager/base/memory/ref_counted.h"
|
||||||
#include "packager/base/memory/scoped_ptr.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/media_parser.h"
|
||||||
#include "packager/media/base/offset_byte_queue.h"
|
#include "packager/media/base/offset_byte_queue.h"
|
||||||
|
|
||||||
namespace edash_packager {
|
namespace edash_packager {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
class AesCtrEncryptor;
|
|
||||||
class DecryptConfig;
|
|
||||||
|
|
||||||
namespace mp4 {
|
namespace mp4 {
|
||||||
|
|
||||||
class BoxReader;
|
class BoxReader;
|
||||||
|
@ -70,10 +67,6 @@ class MP4MediaParser : public MediaParser {
|
||||||
bool FetchKeysIfNecessary(
|
bool FetchKeysIfNecessary(
|
||||||
const std::vector<ProtectionSystemSpecificHeader>& headers);
|
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
|
// 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
|
// 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
|
// extracted from the stream. This function discards data from the stream up
|
||||||
|
@ -94,6 +87,7 @@ class MP4MediaParser : public MediaParser {
|
||||||
InitCB init_cb_;
|
InitCB init_cb_;
|
||||||
NewSampleCB new_sample_cb_;
|
NewSampleCB new_sample_cb_;
|
||||||
KeySource* decryption_key_source_;
|
KeySource* decryption_key_source_;
|
||||||
|
scoped_ptr<DecryptorSource> decryptor_source_;
|
||||||
|
|
||||||
OffsetByteQueue queue_;
|
OffsetByteQueue queue_;
|
||||||
|
|
||||||
|
@ -110,9 +104,6 @@ class MP4MediaParser : public MediaParser {
|
||||||
scoped_ptr<Movie> moov_;
|
scoped_ptr<Movie> moov_;
|
||||||
scoped_ptr<TrackRunIterator> runs_;
|
scoped_ptr<TrackRunIterator> runs_;
|
||||||
|
|
||||||
typedef std::map<std::vector<uint8_t>, AesCtrEncryptor*> DecryptorMap;
|
|
||||||
DecryptorMap decryptor_map_;
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(MP4MediaParser);
|
DISALLOW_COPY_AND_ASSIGN(MP4MediaParser);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -600,7 +600,6 @@ scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
|
||||||
return scoped_ptr<DecryptConfig>(new DecryptConfig(
|
return scoped_ptr<DecryptConfig>(new DecryptConfig(
|
||||||
track_encryption().default_kid,
|
track_encryption().default_kid,
|
||||||
sample_encryption_entry.initialization_vector,
|
sample_encryption_entry.initialization_vector,
|
||||||
0, // No offset to start of media data in MP4 using CENC.
|
|
||||||
sample_encryption_entry.subsamples));
|
sample_encryption_entry.subsamples));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
'../../../third_party/boringssl/boringssl.gyp:boringssl',
|
'../../../third_party/boringssl/boringssl.gyp:boringssl',
|
||||||
'../../../third_party/libwebm/libwebm.gyp:libwebm',
|
'../../../third_party/libwebm/libwebm.gyp:libwebm',
|
||||||
'../../base/media_base.gyp:base',
|
'../../base/media_base.gyp:base',
|
||||||
|
'../../base/media_base.gyp:widevine_pssh_data_proto',
|
||||||
'../../filters/filters.gyp:filters'
|
'../../filters/filters.gyp:filters'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -60,7 +60,8 @@ WebMClusterParser::WebMClusterParser(
|
||||||
const std::string& audio_encryption_key_id,
|
const std::string& audio_encryption_key_id,
|
||||||
const std::string& video_encryption_key_id,
|
const std::string& video_encryption_key_id,
|
||||||
const MediaParser::NewSampleCB& new_sample_cb,
|
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),
|
: timecode_multiplier_(timecode_scale / 1000.0),
|
||||||
audio_stream_info_(audio_stream_info),
|
audio_stream_info_(audio_stream_info),
|
||||||
video_stream_info_(video_stream_info),
|
video_stream_info_(video_stream_info),
|
||||||
|
@ -79,6 +80,8 @@ WebMClusterParser::WebMClusterParser(
|
||||||
true,
|
true,
|
||||||
video_default_duration,
|
video_default_duration,
|
||||||
new_sample_cb) {
|
new_sample_cb) {
|
||||||
|
if (decryption_key_source)
|
||||||
|
decryptor_source_.reset(new DecryptorSource(decryption_key_source));
|
||||||
for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin();
|
for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin();
|
||||||
it != text_tracks.end();
|
it != text_tracks.end();
|
||||||
++it) {
|
++it) {
|
||||||
|
@ -326,11 +329,12 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
|
||||||
}
|
}
|
||||||
|
|
||||||
Track* track = NULL;
|
Track* track = NULL;
|
||||||
StreamType stream_type = kStreamAudio;
|
StreamType stream_type = kStreamUnknown;
|
||||||
std::string encryption_key_id;
|
std::string encryption_key_id;
|
||||||
if (track_num == audio_.track_num()) {
|
if (track_num == audio_.track_num()) {
|
||||||
track = &audio_;
|
track = &audio_;
|
||||||
encryption_key_id = audio_encryption_key_id_;
|
encryption_key_id = audio_encryption_key_id_;
|
||||||
|
stream_type = kStreamAudio;
|
||||||
} else if (track_num == video_.track_num()) {
|
} else if (track_num == video_.track_num()) {
|
||||||
track = &video_;
|
track = &video_;
|
||||||
encryption_key_id = video_encryption_key_id_;
|
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;
|
LOG(ERROR) << "Unexpected track number " << track_num;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
DCHECK_NE(stream_type, kStreamUnknown);
|
||||||
|
|
||||||
last_block_timecode_ = timecode;
|
last_block_timecode_ = timecode;
|
||||||
|
|
||||||
|
@ -384,9 +389,19 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
|
||||||
buffer = MediaSample::CopyFrom(data + data_offset, size - data_offset,
|
buffer = MediaSample::CopyFrom(data + data_offset, size - data_offset,
|
||||||
additional, additional_size, is_keyframe);
|
additional, additional_size, is_keyframe);
|
||||||
|
|
||||||
if (decrypt_config) {
|
// An empty iv indicates that this sample is not encrypted.
|
||||||
// TODO(kqyang): Decrypt it if it is encrypted.
|
if (decrypt_config && !decrypt_config->iv().empty()) {
|
||||||
buffer->set_is_encrypted(true);
|
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 {
|
} else {
|
||||||
std::string id, settings, content;
|
std::string id, settings, content;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "packager/base/memory/scoped_ptr.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/media_parser.h"
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
#include "packager/media/formats/webm/webm_parser.h"
|
#include "packager/media/formats/webm/webm_parser.h"
|
||||||
|
@ -93,6 +94,28 @@ class WebMClusterParser : public WebMParserClient {
|
||||||
typedef std::map<int, Track> TextTrackMap;
|
typedef std::map<int, Track> TextTrackMap;
|
||||||
|
|
||||||
public:
|
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,
|
WebMClusterParser(int64_t timecode_scale,
|
||||||
scoped_refptr<AudioStreamInfo> audio_stream_info,
|
scoped_refptr<AudioStreamInfo> audio_stream_info,
|
||||||
scoped_refptr<VideoStreamInfo> video_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& audio_encryption_key_id,
|
||||||
const std::string& video_encryption_key_id,
|
const std::string& video_encryption_key_id,
|
||||||
const MediaParser::NewSampleCB& new_sample_cb,
|
const MediaParser::NewSampleCB& new_sample_cb,
|
||||||
const MediaParser::InitCB& init_cb);
|
const MediaParser::InitCB& init_cb,
|
||||||
|
KeySource* decryption_key_source);
|
||||||
~WebMClusterParser() override;
|
~WebMClusterParser() override;
|
||||||
|
|
||||||
/// Resets the parser state so it can accept a new cluster.
|
/// 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<AudioStreamInfo> audio_stream_info_;
|
||||||
scoped_refptr<VideoStreamInfo> video_stream_info_;
|
scoped_refptr<VideoStreamInfo> video_stream_info_;
|
||||||
std::set<int64_t> ignored_tracks_;
|
std::set<int64_t> ignored_tracks_;
|
||||||
|
|
||||||
|
scoped_ptr<DecryptorSource> decryptor_source_;
|
||||||
std::string audio_encryption_key_id_;
|
std::string audio_encryption_key_id_;
|
||||||
std::string video_encryption_key_id_;
|
std::string video_encryption_key_id_;
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
using ::testing::HasSubstr;
|
using ::testing::HasSubstr;
|
||||||
using ::testing::InSequence;
|
using ::testing::InSequence;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
|
using ::testing::SetArgPointee;
|
||||||
using ::testing::StrictMock;
|
using ::testing::StrictMock;
|
||||||
using ::testing::Mock;
|
using ::testing::Mock;
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
|
@ -137,6 +138,23 @@ const uint8_t kEncryptedFrame[] = {
|
||||||
// Some dummy encrypted data
|
// Some dummy encrypted data
|
||||||
0x01,
|
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[] = {
|
const uint8_t kVP8Frame[] = {
|
||||||
0x52, 0x04, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00, 0x00, 0x47,
|
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,
|
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,
|
scoped_ptr<Cluster> CreateCluster(int timecode,
|
||||||
const BlockInfo* block_info,
|
const BlockInfo* block_info,
|
||||||
int block_count) {
|
int block_count) {
|
||||||
|
@ -284,10 +308,6 @@ bool VerifyTextBuffers(const BlockInfo* block_info_ptr,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyEncryptedBuffer(scoped_refptr<MediaSample> buffer) {
|
|
||||||
EXPECT_TRUE(buffer->is_encrypted());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class WebMClusterParserTest : public testing::Test {
|
class WebMClusterParserTest : public testing::Test {
|
||||||
|
@ -382,7 +402,7 @@ class WebMClusterParserTest : public testing::Test {
|
||||||
ignored_tracks, audio_encryption_key_id, video_encryption_key_id,
|
ignored_tracks, audio_encryption_key_id, video_encryption_key_id,
|
||||||
base::Bind(&WebMClusterParserTest::NewSampleEvent,
|
base::Bind(&WebMClusterParserTest::NewSampleEvent,
|
||||||
base::Unretained(this)),
|
base::Unretained(this)),
|
||||||
init_cb);
|
init_cb, &mock_key_source_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a default version of the parser for test.
|
// Create a default version of the parser for test.
|
||||||
|
@ -450,6 +470,7 @@ class WebMClusterParserTest : public testing::Test {
|
||||||
BufferQueue audio_buffers_;
|
BufferQueue audio_buffers_;
|
||||||
BufferQueue video_buffers_;
|
BufferQueue video_buffers_;
|
||||||
TextBufferQueueMap text_buffers_map_;
|
TextBufferQueueMap text_buffers_map_;
|
||||||
|
StrictMock<MockKeySource> mock_key_source_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DISALLOW_COPY_AND_ASSIGN(WebMClusterParserTest);
|
DISALLOW_COPY_AND_ASSIGN(WebMClusterParserTest);
|
||||||
|
@ -806,6 +827,36 @@ TEST_F(WebMClusterParserTest, ParseVP9) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
|
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(
|
scoped_ptr<Cluster> cluster(
|
||||||
CreateCluster(kEncryptedFrame, arraysize(kEncryptedFrame)));
|
CreateCluster(kEncryptedFrame, arraysize(kEncryptedFrame)));
|
||||||
|
|
||||||
|
@ -813,11 +864,7 @@ TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
|
||||||
std::string(), "video_key_id", kUnknownAudioCodec));
|
std::string(), "video_key_id", kUnknownAudioCodec));
|
||||||
|
|
||||||
int result = parser_->Parse(cluster->data(), cluster->size());
|
int result = parser_->Parse(cluster->data(), cluster->size());
|
||||||
EXPECT_EQ(cluster->size(), result);
|
EXPECT_EQ(-1, result);
|
||||||
parser_->Flush();
|
|
||||||
ASSERT_EQ(1UL, video_buffers_.size());
|
|
||||||
scoped_refptr<MediaSample> buffer = video_buffers_[0];
|
|
||||||
VerifyEncryptedBuffer(buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
|
TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
|
||||||
|
@ -830,6 +877,25 @@ TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
|
||||||
EXPECT_EQ(-1, result);
|
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) {
|
TEST_F(WebMClusterParserTest, ParseInvalidZeroSizedCluster) {
|
||||||
const uint8_t kBuffer[] = {
|
const uint8_t kBuffer[] = {
|
||||||
0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0)
|
0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0)
|
||||||
|
|
|
@ -56,7 +56,7 @@ bool WebMCreateDecryptConfig(const uint8_t* data,
|
||||||
decrypt_config->reset(new DecryptConfig(
|
decrypt_config->reset(new DecryptConfig(
|
||||||
std::vector<uint8_t>(key_id, key_id + key_id_size),
|
std::vector<uint8_t>(key_id, key_id + key_id_size),
|
||||||
std::vector<uint8_t>(counter_block.begin(), counter_block.end()),
|
std::vector<uint8_t>(counter_block.begin(), counter_block.end()),
|
||||||
frame_offset, std::vector<SubsampleEntry>()));
|
std::vector<SubsampleEntry>()));
|
||||||
*data_offset = frame_offset;
|
*data_offset = frame_offset;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
#include "packager/base/callback.h"
|
#include "packager/base/callback.h"
|
||||||
#include "packager/base/callback_helpers.h"
|
#include "packager/base/callback_helpers.h"
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
|
#include "packager/media/base/buffer_writer.h"
|
||||||
#include "packager/media/base/timestamp.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_cluster_parser.h"
|
||||||
#include "packager/media/formats/webm/webm_constants.h"
|
#include "packager/media/formats/webm/webm_constants.h"
|
||||||
#include "packager/media/formats/webm/webm_content_encodings.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();
|
tracks_parser.audio_stream_info();
|
||||||
if (audio_stream_info) {
|
if (audio_stream_info) {
|
||||||
audio_stream_info->set_duration(duration_in_us);
|
audio_stream_info->set_duration(duration_in_us);
|
||||||
if (audio_stream_info->is_encrypted())
|
|
||||||
OnEncryptedMediaInitData(tracks_parser.audio_encryption_key_id());
|
|
||||||
} else {
|
} else {
|
||||||
VLOG(1) << "No audio track info found.";
|
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();
|
tracks_parser.video_stream_info();
|
||||||
if (video_stream_info) {
|
if (video_stream_info) {
|
||||||
video_stream_info->set_duration(duration_in_us);
|
video_stream_info->set_duration(duration_in_us);
|
||||||
if (video_stream_info->is_encrypted())
|
|
||||||
OnEncryptedMediaInitData(tracks_parser.video_encryption_key_id());
|
|
||||||
} else {
|
} else {
|
||||||
VLOG(1) << "No video track info found.";
|
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(
|
cluster_parser_.reset(new WebMClusterParser(
|
||||||
info_parser.timecode_scale(), audio_stream_info, video_stream_info,
|
info_parser.timecode_scale(), audio_stream_info, video_stream_info,
|
||||||
tracks_parser.GetAudioDefaultDuration(timecode_scale_in_us),
|
tracks_parser.GetAudioDefaultDuration(timecode_scale_in_us),
|
||||||
tracks_parser.GetVideoDefaultDuration(timecode_scale_in_us),
|
tracks_parser.GetVideoDefaultDuration(timecode_scale_in_us),
|
||||||
tracks_parser.text_tracks(), tracks_parser.ignored_tracks(),
|
tracks_parser.text_tracks(), tracks_parser.ignored_tracks(),
|
||||||
tracks_parser.audio_encryption_key_id(),
|
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;
|
return bytes_parsed;
|
||||||
}
|
}
|
||||||
|
@ -228,8 +232,30 @@ int WebMMediaParser::ParseCluster(const uint8_t* data, int size) {
|
||||||
return bytes_parsed;
|
return bytes_parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebMMediaParser::OnEncryptedMediaInitData(const std::string& key_id) {
|
bool WebMMediaParser::FetchKeysIfNecessary(
|
||||||
NOTIMPLEMENTED() << "WebM decryption is not implemented yet.";
|
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
|
} // namespace media
|
||||||
|
|
|
@ -56,8 +56,9 @@ class WebMMediaParser : public MediaParser {
|
||||||
// Returning > 0 indicates success & the number of bytes parsed.
|
// Returning > 0 indicates success & the number of bytes parsed.
|
||||||
int ParseCluster(const uint8_t* data, int size);
|
int ParseCluster(const uint8_t* data, int size);
|
||||||
|
|
||||||
// Fire needkey event through the |encrypted_media_init_data_cb_|.
|
// Fetch keys for the input key ids. Returns true on success, false otherwise.
|
||||||
void OnEncryptedMediaInitData(const std::string& key_id);
|
bool FetchKeysIfNecessary(const std::string& audio_encryption_key_id,
|
||||||
|
const std::string& video_encryption_key_id);
|
||||||
|
|
||||||
State state_;
|
State state_;
|
||||||
InitCB init_cb_;
|
InitCB init_cb_;
|
||||||
|
|
Loading…
Reference in New Issue