Added Widevine and fixed key decryption.
Change-Id: Ia29ce678c0e64e886cdd92bbfda727768356fd21
This commit is contained in:
parent
65e558b19c
commit
7b1640a8f7
|
@ -11,13 +11,22 @@
|
||||||
DEFINE_bool(enable_fixed_key_encryption,
|
DEFINE_bool(enable_fixed_key_encryption,
|
||||||
false,
|
false,
|
||||||
"Enable encryption with fixed key.");
|
"Enable encryption with fixed key.");
|
||||||
|
DEFINE_bool(enable_fixed_key_decryption,
|
||||||
|
false,
|
||||||
|
"Enable decryption with fixed key.");
|
||||||
DEFINE_string(key_id, "", "Key id in hex string format.");
|
DEFINE_string(key_id, "", "Key id in hex string format.");
|
||||||
DEFINE_string(key, "", "Key in hex string format.");
|
DEFINE_string(key, "", "Key in hex string format.");
|
||||||
DEFINE_string(pssh, "", "PSSH in hex string format.");
|
DEFINE_string(pssh, "", "PSSH in hex string format.");
|
||||||
|
|
||||||
static bool IsNotEmptyWithFixedKeyEncryption(const char* flag_name,
|
static bool IsNotEmptyWithFixedKeyEncryption(const char* flag_name,
|
||||||
const std::string& flag_value) {
|
const std::string& flag_value) {
|
||||||
return FLAGS_enable_fixed_key_encryption ? !flag_value.empty() : true;
|
if (FLAGS_enable_fixed_key_encryption && flag_value.empty())
|
||||||
|
return false;
|
||||||
|
std::string flag_name_str(flag_name);
|
||||||
|
if (FLAGS_enable_fixed_key_decryption && (flag_name_str != "pssh") &&
|
||||||
|
flag_value.empty())
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dummy_key_id_validator =
|
static bool dummy_key_id_validator =
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <gflags/gflags.h>
|
#include <gflags/gflags.h>
|
||||||
|
|
||||||
DECLARE_bool(enable_fixed_key_encryption);
|
DECLARE_bool(enable_fixed_key_encryption);
|
||||||
|
DECLARE_bool(enable_fixed_key_decryption);
|
||||||
DECLARE_string(key_id);
|
DECLARE_string(key_id);
|
||||||
DECLARE_string(key);
|
DECLARE_string(key);
|
||||||
DECLARE_string(pssh);
|
DECLARE_string(pssh);
|
||||||
|
|
|
@ -123,19 +123,20 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
|
|
||||||
if (stream_iter->input != previous_input) {
|
if (stream_iter->input != previous_input) {
|
||||||
// New remux job needed. Create demux and job thread.
|
// New remux job needed. Create demux and job thread.
|
||||||
scoped_ptr<Demuxer> demux(new Demuxer(stream_iter->input, NULL));
|
scoped_ptr<Demuxer> demuxer(new Demuxer(stream_iter->input));
|
||||||
Status status = demux->Initialize();
|
demuxer->SetKeySource(CreateDecryptionKeySource());
|
||||||
|
Status status = demuxer->Initialize();
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString();
|
LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (FLAGS_dump_stream_info) {
|
if (FLAGS_dump_stream_info) {
|
||||||
printf("\nFile \"%s\":\n", stream_iter->input.c_str());
|
printf("\nFile \"%s\":\n", stream_iter->input.c_str());
|
||||||
DumpStreamInfo(demux->streams());
|
DumpStreamInfo(demuxer->streams());
|
||||||
if (stream_iter->output.empty())
|
if (stream_iter->output.empty())
|
||||||
continue; // just need stream info.
|
continue; // just need stream info.
|
||||||
}
|
}
|
||||||
remux_jobs->push_back(new RemuxJob(demux.Pass()));
|
remux_jobs->push_back(new RemuxJob(demuxer.Pass()));
|
||||||
previous_input = stream_iter->input;
|
previous_input = stream_iter->input;
|
||||||
}
|
}
|
||||||
DCHECK(!remux_jobs->empty());
|
DCHECK(!remux_jobs->empty());
|
||||||
|
|
|
@ -100,6 +100,9 @@ scoped_ptr<KeySource> CreateDecryptionKeySource() {
|
||||||
scoped_ptr<RequestSigner> signer(CreateSigner());
|
scoped_ptr<RequestSigner> signer(CreateSigner());
|
||||||
decryption_key_source.reset(new WidevineKeySource(FLAGS_key_server_url,
|
decryption_key_source.reset(new WidevineKeySource(FLAGS_key_server_url,
|
||||||
signer.Pass()));
|
signer.Pass()));
|
||||||
|
} else if (FLAGS_enable_fixed_key_decryption) {
|
||||||
|
decryption_key_source = KeySource::CreateFromHexStrings(
|
||||||
|
FLAGS_key_id, FLAGS_key, "", "");
|
||||||
}
|
}
|
||||||
return decryption_key_source.Pass();
|
return decryption_key_source.Pass();
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,9 @@ static bool VerifyEncryptionAndDecryptionParams(const char* flag_name,
|
||||||
const std::string& flag_value) {
|
const std::string& flag_value) {
|
||||||
DCHECK(flag_name);
|
DCHECK(flag_name);
|
||||||
|
|
||||||
|
const std::string flag_name_str = flag_name;
|
||||||
|
bool is_common_param = (flag_name_str == "key_server_url") ||
|
||||||
|
(flag_name_str == "signer");
|
||||||
if (FLAGS_enable_widevine_encryption) {
|
if (FLAGS_enable_widevine_encryption) {
|
||||||
if (flag_value.empty()) {
|
if (flag_value.empty()) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
|
@ -63,12 +66,12 @@ static bool VerifyEncryptionAndDecryptionParams(const char* flag_name,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (FLAGS_enable_widevine_decryption) {
|
} else if (FLAGS_enable_widevine_decryption) {
|
||||||
const std::string flag_name_str = flag_name;
|
if (is_common_param) {
|
||||||
if (flag_name_str == "key_server_url") {
|
|
||||||
if (flag_value.empty()) {
|
if (flag_value.empty()) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"ERROR: %s required if --enable_widevine_decryption is true\n",
|
"ERROR: %s required if --enable_widevine_encryption or "
|
||||||
flag_name);
|
"--enable_widevine_decryption is true\n",
|
||||||
|
flag_name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -80,9 +83,8 @@ static bool VerifyEncryptionAndDecryptionParams(const char* flag_name,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!flag_value.empty()) {
|
if (!flag_value.empty()) {
|
||||||
const std::string flag_name_str = flag_name;
|
|
||||||
fprintf(stderr, "ERROR: %s should only be specified if %s"
|
fprintf(stderr, "ERROR: %s should only be specified if %s"
|
||||||
" is true\n", flag_name, flag_name_str == "key_server_url" ?
|
" is true\n", flag_name, is_common_param ?
|
||||||
"--enable_widevine_encryption or --enable_widevine_decryption" :
|
"--enable_widevine_encryption or --enable_widevine_decryption" :
|
||||||
"--enable_widevine_encryption");
|
"--enable_widevine_encryption");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
DecryptConfig::DecryptConfig(const std::string& key_id,
|
DecryptConfig::DecryptConfig(const std::vector<uint8>& key_id,
|
||||||
const std::string& iv,
|
const std::vector<uint8>& iv,
|
||||||
const int data_offset,
|
const int data_offset,
|
||||||
const std::vector<SubsampleEntry>& subsamples)
|
const std::vector<SubsampleEntry>& subsamples)
|
||||||
: key_id_(key_id),
|
: key_id_(key_id),
|
||||||
|
|
|
@ -19,12 +19,12 @@ namespace media {
|
||||||
/// encrypted bytes in a sample should be considered a single logical stream,
|
/// encrypted bytes in a sample should be considered a single logical stream,
|
||||||
/// regardless of how they are divided into subsamples, and the clear bytes
|
/// regardless of how they are divided into subsamples, and the clear bytes
|
||||||
/// should not be considered as part of decryption. This is logically equivalent
|
/// should not be considered as part of decryption. This is logically equivalent
|
||||||
/// to concatenating all @a cypher_bytes portions of subsamples, decrypting that
|
/// to concatenating all @a cipher_bytes portions of subsamples, decrypting that
|
||||||
/// result, and then copying each byte from the decrypted block over the
|
/// result, and then copying each byte from the decrypted block over the
|
||||||
/// corresponding encrypted byte.
|
/// corresponding encrypted byte.
|
||||||
struct SubsampleEntry {
|
struct SubsampleEntry {
|
||||||
uint16 clear_bytes;
|
uint16 clear_bytes;
|
||||||
uint32 cypher_bytes;
|
uint32 cipher_bytes;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Contains all the information that a decryptor needs to decrypt a media
|
/// Contains all the information that a decryptor needs to decrypt a media
|
||||||
|
@ -43,22 +43,22 @@ class DecryptConfig {
|
||||||
/// @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::string& key_id,
|
DecryptConfig(const std::vector<uint8>& key_id,
|
||||||
const std::string& iv,
|
const std::vector<uint8>& iv,
|
||||||
const int data_offset,
|
const int data_offset,
|
||||||
const std::vector<SubsampleEntry>& subsamples);
|
const std::vector<SubsampleEntry>& subsamples);
|
||||||
~DecryptConfig();
|
~DecryptConfig();
|
||||||
|
|
||||||
const std::string& key_id() const { return key_id_; }
|
const std::vector<uint8>& key_id() const { return key_id_; }
|
||||||
const std::string& iv() const { return iv_; }
|
const std::vector<uint8>& iv() const { return iv_; }
|
||||||
int data_offset() const { return data_offset_; }
|
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:
|
||||||
const std::string key_id_;
|
const std::vector<uint8> key_id_;
|
||||||
|
|
||||||
// Initialization vector.
|
// Initialization vector.
|
||||||
const std::string iv_;
|
const std::vector<uint8> iv_;
|
||||||
|
|
||||||
// Amount of data to be discarded before applying subsample information.
|
// Amount of data to be discarded before applying subsample information.
|
||||||
const int data_offset_;
|
const int data_offset_;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "base/stl_util.h"
|
#include "base/stl_util.h"
|
||||||
#include "media/base/container_names.h"
|
#include "media/base/container_names.h"
|
||||||
#include "media/base/decryptor_source.h"
|
#include "media/base/decryptor_source.h"
|
||||||
|
#include "media/base/key_source.h"
|
||||||
#include "media/base/media_sample.h"
|
#include "media/base/media_sample.h"
|
||||||
#include "media/base/media_stream.h"
|
#include "media/base/media_stream.h"
|
||||||
#include "media/base/stream_info.h"
|
#include "media/base/stream_info.h"
|
||||||
|
@ -24,10 +25,8 @@ const size_t kBufSize = 0x40000; // 256KB.
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
Demuxer::Demuxer(const std::string& file_name,
|
Demuxer::Demuxer(const std::string& file_name)
|
||||||
DecryptorSource* decryptor_source)
|
: file_name_(file_name),
|
||||||
: decryptor_source_(decryptor_source),
|
|
||||||
file_name_(file_name),
|
|
||||||
media_file_(NULL),
|
media_file_(NULL),
|
||||||
init_event_received_(false),
|
init_event_received_(false),
|
||||||
buffer_(new uint8[kBufSize]) {}
|
buffer_(new uint8[kBufSize]) {}
|
||||||
|
@ -38,6 +37,10 @@ Demuxer::~Demuxer() {
|
||||||
STLDeleteElements(&streams_);
|
STLDeleteElements(&streams_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Demuxer::SetKeySource(scoped_ptr<KeySource> key_source) {
|
||||||
|
key_source_ = key_source.Pass();
|
||||||
|
}
|
||||||
|
|
||||||
Status Demuxer::Initialize() {
|
Status Demuxer::Initialize() {
|
||||||
DCHECK(!media_file_);
|
DCHECK(!media_file_);
|
||||||
DCHECK(!init_event_received_);
|
DCHECK(!init_event_received_);
|
||||||
|
@ -69,7 +72,7 @@ Status Demuxer::Initialize() {
|
||||||
|
|
||||||
parser_->Init(base::Bind(&Demuxer::ParserInitEvent, base::Unretained(this)),
|
parser_->Init(base::Bind(&Demuxer::ParserInitEvent, base::Unretained(this)),
|
||||||
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
|
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
|
||||||
base::Bind(&Demuxer::KeyNeededEvent, base::Unretained(this)));
|
key_source_.get());
|
||||||
|
|
||||||
if (!parser_->Parse(buffer_.get(), bytes_read))
|
if (!parser_->Parse(buffer_.get(), bytes_read))
|
||||||
return Status(error::PARSER_FAILURE,
|
return Status(error::PARSER_FAILURE,
|
||||||
|
@ -104,12 +107,6 @@ bool Demuxer::NewSampleEvent(uint32 track_id,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Demuxer::KeyNeededEvent(MediaContainerName container,
|
|
||||||
scoped_ptr<uint8[]> init_data,
|
|
||||||
int init_data_size) {
|
|
||||||
NOTIMPLEMENTED();
|
|
||||||
}
|
|
||||||
|
|
||||||
Status Demuxer::Run() {
|
Status Demuxer::Run() {
|
||||||
Status status;
|
Status status;
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
class Decryptor;
|
class Decryptor;
|
||||||
class DecryptorSource;
|
|
||||||
class File;
|
class File;
|
||||||
|
class KeySource;
|
||||||
class MediaParser;
|
class MediaParser;
|
||||||
class MediaSample;
|
class MediaSample;
|
||||||
class MediaStream;
|
class MediaStream;
|
||||||
|
@ -31,11 +31,15 @@ class Demuxer {
|
||||||
/// @param file_name specifies the input source. It uses prefix matching to
|
/// @param file_name specifies the input source. It uses prefix matching to
|
||||||
/// create a proper File object. The user can extend File to support
|
/// create a proper File object. The user can extend File to support
|
||||||
/// a custom File object with its own prefix.
|
/// a custom File object with its own prefix.
|
||||||
/// @param decryptor_source generates decryptor(s) from decryption
|
explicit Demuxer(const std::string& file_name);
|
||||||
/// initialization data. It can be NULL if the media is not encrypted.
|
|
||||||
Demuxer(const std::string& file_name, DecryptorSource* decryptor_source);
|
|
||||||
~Demuxer();
|
~Demuxer();
|
||||||
|
|
||||||
|
/// Set the KeySource for media decryption.
|
||||||
|
/// @param key_source points to the source of decryption keys. The key
|
||||||
|
/// source must support fetching of keys for the type of media being
|
||||||
|
/// demuxed.
|
||||||
|
void SetKeySource(scoped_ptr<KeySource> key_source);
|
||||||
|
|
||||||
/// Initialize the Demuxer. Calling other public methods of this class
|
/// Initialize the Demuxer. Calling other public methods of this class
|
||||||
/// without this method returning OK, results in an undefined behavior.
|
/// without this method returning OK, results in an undefined behavior.
|
||||||
/// This method primes the demuxer by parsing portions of the media file to
|
/// This method primes the demuxer by parsing portions of the media file to
|
||||||
|
@ -61,17 +65,14 @@ class Demuxer {
|
||||||
void ParserInitEvent(const std::vector<scoped_refptr<StreamInfo> >& streams);
|
void ParserInitEvent(const std::vector<scoped_refptr<StreamInfo> >& streams);
|
||||||
bool NewSampleEvent(uint32 track_id,
|
bool NewSampleEvent(uint32 track_id,
|
||||||
const scoped_refptr<MediaSample>& sample);
|
const scoped_refptr<MediaSample>& sample);
|
||||||
void KeyNeededEvent(MediaContainerName container,
|
|
||||||
scoped_ptr<uint8[]> init_data,
|
|
||||||
int init_data_size);
|
|
||||||
|
|
||||||
DecryptorSource* decryptor_source_;
|
|
||||||
std::string file_name_;
|
std::string file_name_;
|
||||||
File* media_file_;
|
File* media_file_;
|
||||||
bool init_event_received_;
|
bool init_event_received_;
|
||||||
scoped_ptr<MediaParser> parser_;
|
scoped_ptr<MediaParser> parser_;
|
||||||
std::vector<MediaStream*> streams_;
|
std::vector<MediaStream*> streams_;
|
||||||
scoped_ptr<uint8[]> buffer_;
|
scoped_ptr<uint8[]> buffer_;
|
||||||
|
scoped_ptr<KeySource> key_source_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(Demuxer);
|
DISALLOW_COPY_AND_ASSIGN(Demuxer);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,12 +25,12 @@ KeySource::~KeySource() {}
|
||||||
|
|
||||||
Status KeySource::FetchKeys(const std::vector<uint8>& content_id,
|
Status KeySource::FetchKeys(const std::vector<uint8>& content_id,
|
||||||
const std::string& policy) {
|
const std::string& policy) {
|
||||||
NOTREACHED();
|
// Do nothing for fixed key decryption.
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status KeySource::FetchKeys(const std::vector<uint8>& pssh_data) {
|
Status KeySource::FetchKeys(const std::vector<uint8>& pssh_data) {
|
||||||
NOTREACHED();
|
// Do nothing for fixed key decryption.
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,11 @@ Status KeySource::GetKey(const std::vector<uint8>& key_id,
|
||||||
EncryptionKey* key) {
|
EncryptionKey* key) {
|
||||||
DCHECK(key);
|
DCHECK(key);
|
||||||
DCHECK(encryption_key_);
|
DCHECK(encryption_key_);
|
||||||
|
if (key_id != encryption_key_->key_id) {
|
||||||
|
return Status(error::NOT_FOUND, std::string("Key for key ID ") +
|
||||||
|
base::HexEncode(&key_id[0], key_id.size()) +
|
||||||
|
" was not found.");
|
||||||
|
}
|
||||||
*key = *encryption_key_;
|
*key = *encryption_key_;
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
|
class KeySource;
|
||||||
class MediaSample;
|
class MediaSample;
|
||||||
class StreamInfo;
|
class StreamInfo;
|
||||||
|
|
||||||
|
@ -40,20 +41,16 @@ class MediaParser {
|
||||||
bool(uint32 track_id, const scoped_refptr<MediaSample>& media_sample)>
|
bool(uint32 track_id, const scoped_refptr<MediaSample>& media_sample)>
|
||||||
NewSampleCB;
|
NewSampleCB;
|
||||||
|
|
||||||
/// Called when a new potentially encrypted stream has been parsed.
|
|
||||||
/// @param init_data is the initialization data associated with the stream.
|
|
||||||
/// @param init_data_size is the number of bytes of the initialization data.
|
|
||||||
typedef base::Callback<void(MediaContainerName container_name,
|
|
||||||
scoped_ptr<uint8[]> init_data,
|
|
||||||
int init_data_size)> NeedKeyCB;
|
|
||||||
|
|
||||||
/// Initialize the parser with necessary callbacks. Must be called before any
|
/// Initialize the parser with necessary callbacks. Must be called before any
|
||||||
/// data is passed to Parse().
|
/// data is passed to Parse().
|
||||||
/// @param init_cb will be called once enough data has been parsed to
|
/// @param init_cb will be called once enough data has been parsed to
|
||||||
/// determine the initial stream configurations.
|
/// determine the initial stream configurations.
|
||||||
|
/// @param new_sample_cb will be called each time a new media sample is
|
||||||
|
/// available from the parser. May be NULL, and caller retains
|
||||||
|
/// ownership.
|
||||||
virtual void Init(const InitCB& init_cb,
|
virtual void Init(const InitCB& init_cb,
|
||||||
const NewSampleCB& new_sample_cb,
|
const NewSampleCB& new_sample_cb,
|
||||||
const NeedKeyCB& need_key_cb) = 0;
|
KeySource* decryption_key_source) = 0;
|
||||||
|
|
||||||
/// Flush data currently in the parser and put the parser in a state where it
|
/// Flush data currently in the parser and put the parser in a state where it
|
||||||
/// can receive data for a new seek point.
|
/// can receive data for a new seek point.
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "base/strings/stringprintf.h"
|
#include "base/strings/stringprintf.h"
|
||||||
#include "media/base/decrypt_config.h"
|
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
|
@ -65,15 +64,13 @@ std::string MediaSample::ToString() const {
|
||||||
return "End of stream sample\n";
|
return "End of stream sample\n";
|
||||||
return base::StringPrintf(
|
return base::StringPrintf(
|
||||||
"dts: %" PRId64 "\n pts: %" PRId64 "\n duration: %" PRId64 "\n "
|
"dts: %" PRId64 "\n pts: %" PRId64 "\n duration: %" PRId64 "\n "
|
||||||
"is_key_frame: %s\n size: %zu\n side_data_size: %zu\n "
|
"is_key_frame: %s\n size: %zu\n side_data_size: %zu\n",
|
||||||
"is_encrypted: %s\n",
|
|
||||||
dts_,
|
dts_,
|
||||||
pts_,
|
pts_,
|
||||||
duration_,
|
duration_,
|
||||||
is_key_frame_ ? "true" : "false",
|
is_key_frame_ ? "true" : "false",
|
||||||
data_.size(),
|
data_.size(),
|
||||||
side_data_.size(),
|
side_data_.size());
|
||||||
decrypt_config_ ? "true" : "false");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -14,12 +14,9 @@
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "base/memory/ref_counted.h"
|
#include "base/memory/ref_counted.h"
|
||||||
#include "base/memory/scoped_ptr.h"
|
#include "base/memory/scoped_ptr.h"
|
||||||
#include "media/base/decrypt_config.h"
|
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
class DecryptConfig;
|
|
||||||
|
|
||||||
/// Class to hold a media sample.
|
/// Class to hold a media sample.
|
||||||
class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
|
class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
|
||||||
public:
|
public:
|
||||||
|
@ -113,16 +110,6 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
|
||||||
return side_data_.size();
|
return side_data_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
const DecryptConfig* decrypt_config() const {
|
|
||||||
DCHECK(!end_of_stream());
|
|
||||||
return decrypt_config_.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_decrypt_config(scoped_ptr<DecryptConfig> decrypt_config) {
|
|
||||||
DCHECK(!end_of_stream());
|
|
||||||
decrypt_config_ = decrypt_config.Pass();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's no data in this buffer, it represents end of stream.
|
// If there's no data in this buffer, it represents end of stream.
|
||||||
bool end_of_stream() const { return data_.size() == 0; }
|
bool end_of_stream() const { return data_.size() == 0; }
|
||||||
|
|
||||||
|
@ -155,7 +142,6 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
|
||||||
// http://www.matroska.org/technical/specs/index.html BlockAdditional[A5].
|
// http://www.matroska.org/technical/specs/index.html BlockAdditional[A5].
|
||||||
// Not used by mp4 and other containers.
|
// Not used by mp4 and other containers.
|
||||||
std::vector<uint8> side_data_;
|
std::vector<uint8> side_data_;
|
||||||
scoped_ptr<DecryptConfig> decrypt_config_;
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(MediaSample);
|
DISALLOW_COPY_AND_ASSIGN(MediaSample);
|
||||||
};
|
};
|
||||||
|
|
|
@ -65,6 +65,9 @@ enum Code {
|
||||||
|
|
||||||
// The operation timed out.
|
// The operation timed out.
|
||||||
TIME_OUT,
|
TIME_OUT,
|
||||||
|
|
||||||
|
// Value was not found.
|
||||||
|
NOT_FOUND,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace error
|
} // namespace error
|
||||||
|
|
|
@ -155,16 +155,14 @@ Mp2tMediaParser::~Mp2tMediaParser() {
|
||||||
void Mp2tMediaParser::Init(
|
void Mp2tMediaParser::Init(
|
||||||
const InitCB& init_cb,
|
const InitCB& init_cb,
|
||||||
const NewSampleCB& new_sample_cb,
|
const NewSampleCB& new_sample_cb,
|
||||||
const NeedKeyCB& need_key_cb) {
|
KeySource* decryption_key_source) {
|
||||||
DCHECK(!is_initialized_);
|
DCHECK(!is_initialized_);
|
||||||
DCHECK(init_cb_.is_null());
|
DCHECK(init_cb_.is_null());
|
||||||
DCHECK(!init_cb.is_null());
|
DCHECK(!init_cb.is_null());
|
||||||
DCHECK(!new_sample_cb.is_null());
|
DCHECK(!new_sample_cb.is_null());
|
||||||
DCHECK(!need_key_cb.is_null());
|
|
||||||
|
|
||||||
init_cb_ = init_cb;
|
init_cb_ = init_cb;
|
||||||
new_sample_cb_ = new_sample_cb;
|
new_sample_cb_ = new_sample_cb;
|
||||||
need_key_cb_ = need_key_cb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mp2tMediaParser::Flush() {
|
void Mp2tMediaParser::Flush() {
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Mp2tMediaParser : public MediaParser {
|
||||||
// MediaParser implementation overrides.
|
// MediaParser implementation overrides.
|
||||||
virtual void Init(const InitCB& init_cb,
|
virtual void Init(const InitCB& init_cb,
|
||||||
const NewSampleCB& new_sample_cb,
|
const NewSampleCB& new_sample_cb,
|
||||||
const NeedKeyCB& need_key_cb) OVERRIDE;
|
KeySource* decryption_key_source) OVERRIDE;
|
||||||
|
|
||||||
virtual void Flush() OVERRIDE;
|
virtual void Flush() OVERRIDE;
|
||||||
|
|
||||||
|
@ -74,7 +74,6 @@ class Mp2tMediaParser : public MediaParser {
|
||||||
// List of callbacks.
|
// List of callbacks.
|
||||||
InitCB init_cb_;
|
InitCB init_cb_;
|
||||||
NewSampleCB new_sample_cb_;
|
NewSampleCB new_sample_cb_;
|
||||||
NeedKeyCB need_key_cb_;
|
|
||||||
|
|
||||||
bool sbr_in_mimetype_;
|
bool sbr_in_mimetype_;
|
||||||
|
|
||||||
|
|
|
@ -97,20 +97,13 @@ class Mp2tMediaParserTest : public testing::Test {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnKeyNeeded(MediaContainerName container_name,
|
|
||||||
scoped_ptr<uint8[]> init_data,
|
|
||||||
int init_data_size) {
|
|
||||||
DVLOG(1) << "OnKeyNeeded: " << init_data_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeParser() {
|
void InitializeParser() {
|
||||||
parser_->Init(
|
parser_->Init(
|
||||||
base::Bind(&Mp2tMediaParserTest::OnInit,
|
base::Bind(&Mp2tMediaParserTest::OnInit,
|
||||||
base::Unretained(this)),
|
base::Unretained(this)),
|
||||||
base::Bind(&Mp2tMediaParserTest::OnNewSample,
|
base::Bind(&Mp2tMediaParserTest::OnNewSample,
|
||||||
base::Unretained(this)),
|
base::Unretained(this)),
|
||||||
base::Bind(&Mp2tMediaParserTest::OnKeyNeeded,
|
NULL);
|
||||||
base::Unretained(this)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ParseMpeg2TsFile(const std::string& filename, int append_bytes) {
|
bool ParseMpeg2TsFile(const std::string& filename, int append_bytes) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace {
|
||||||
// 64-bit (8-byte) or 128-bit (16-byte).
|
// 64-bit (8-byte) or 128-bit (16-byte).
|
||||||
bool IsIvSizeValid(size_t iv_size) { return iv_size == 8 || iv_size == 16; }
|
bool IsIvSizeValid(size_t iv_size) { return iv_size == 8 || iv_size == 16; }
|
||||||
|
|
||||||
// 16-bit |clear_bytes| and 32-bit |cypher_bytes|.
|
// 16-bit |clear_bytes| and 32-bit |cipher_bytes|.
|
||||||
const size_t kSubsampleEntrySize = sizeof(uint16) + sizeof(uint32);
|
const size_t kSubsampleEntrySize = sizeof(uint16) + sizeof(uint32);
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -44,11 +44,11 @@ bool FrameCENCInfo::Parse(uint8 iv_size, BufferReader* reader) {
|
||||||
subsamples_.resize(subsample_count);
|
subsamples_.resize(subsample_count);
|
||||||
for (uint16 i = 0; i < subsample_count; ++i) {
|
for (uint16 i = 0; i < subsample_count; ++i) {
|
||||||
uint16 clear_bytes;
|
uint16 clear_bytes;
|
||||||
uint32 cypher_bytes;
|
uint32 cipher_bytes;
|
||||||
RCHECK(reader->Read2(&clear_bytes) &&
|
RCHECK(reader->Read2(&clear_bytes) &&
|
||||||
reader->Read4(&cypher_bytes));
|
reader->Read4(&cipher_bytes));
|
||||||
subsamples_[i].clear_bytes = clear_bytes;
|
subsamples_[i].clear_bytes = clear_bytes;
|
||||||
subsamples_[i].cypher_bytes = cypher_bytes;
|
subsamples_[i].cipher_bytes = cipher_bytes;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ void FrameCENCInfo::Write(BufferWriter* writer) const {
|
||||||
|
|
||||||
for (uint16 i = 0; i < subsample_count; ++i) {
|
for (uint16 i = 0; i < subsample_count; ++i) {
|
||||||
writer->AppendInt(subsamples_[i].clear_bytes);
|
writer->AppendInt(subsamples_[i].clear_bytes);
|
||||||
writer->AppendInt(subsamples_[i].cypher_bytes);
|
writer->AppendInt(subsamples_[i].cipher_bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ size_t FrameCENCInfo::ComputeSize() const {
|
||||||
size_t FrameCENCInfo::GetTotalSizeOfSubsamples() const {
|
size_t FrameCENCInfo::GetTotalSizeOfSubsamples() const {
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
for (size_t i = 0; i < subsamples_.size(); ++i) {
|
for (size_t i = 0; i < subsamples_.size(); ++i) {
|
||||||
size += subsamples_[i].clear_bytes + subsamples_[i].cypher_bytes;
|
size += subsamples_[i].clear_bytes + subsamples_[i].cipher_bytes;
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,13 +144,13 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
|
||||||
|
|
||||||
SubsampleEntry subsample;
|
SubsampleEntry subsample;
|
||||||
subsample.clear_bytes = nalu_length_size_ + 1;
|
subsample.clear_bytes = nalu_length_size_ + 1;
|
||||||
subsample.cypher_bytes = nalu_length - 1;
|
subsample.cipher_bytes = nalu_length - 1;
|
||||||
if (!reader.SkipBytes(nalu_length)) {
|
if (!reader.SkipBytes(nalu_length)) {
|
||||||
return Status(error::MUXER_FAILURE,
|
return Status(error::MUXER_FAILURE,
|
||||||
"Sample size does not match nalu_length.");
|
"Sample size does not match nalu_length.");
|
||||||
}
|
}
|
||||||
|
|
||||||
EncryptBytes(data + subsample.clear_bytes, subsample.cypher_bytes);
|
EncryptBytes(data + subsample.clear_bytes, subsample.cipher_bytes);
|
||||||
cenc_info.AddSubsample(subsample);
|
cenc_info.AddSubsample(subsample);
|
||||||
data += nalu_length_size_ + nalu_length;
|
data += nalu_length_size_ + nalu_length;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,11 @@
|
||||||
#include "base/callback_helpers.h"
|
#include "base/callback_helpers.h"
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "base/memory/ref_counted.h"
|
#include "base/memory/ref_counted.h"
|
||||||
|
#include "base/strings/string_number_conversions.h"
|
||||||
|
#include "media/base/aes_encryptor.h"
|
||||||
#include "media/base/audio_stream_info.h"
|
#include "media/base/audio_stream_info.h"
|
||||||
|
#include "media/base/decrypt_config.h"
|
||||||
|
#include "media/base/key_source.h"
|
||||||
#include "media/base/media_sample.h"
|
#include "media/base/media_sample.h"
|
||||||
#include "media/base/video_stream_info.h"
|
#include "media/base/video_stream_info.h"
|
||||||
#include "media/formats/mp4/box_definitions.h"
|
#include "media/formats/mp4/box_definitions.h"
|
||||||
|
@ -23,6 +27,9 @@ uint64 Rescale(uint64 time_in_old_scale, uint32 old_scale, uint32 new_scale) {
|
||||||
return (static_cast<double>(time_in_old_scale) / old_scale) * new_scale;
|
return (static_cast<double>(time_in_old_scale) / old_scale) * new_scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char kWidevineKeySystemId[] = "edef8ba979d64acea3c827dcd51d21ed";
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
@ -31,21 +38,22 @@ namespace mp4 {
|
||||||
MP4MediaParser::MP4MediaParser()
|
MP4MediaParser::MP4MediaParser()
|
||||||
: state_(kWaitingForInit), moof_head_(0), mdat_tail_(0) {}
|
: state_(kWaitingForInit), 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,
|
||||||
const NeedKeyCB& need_key_cb) {
|
KeySource* decryption_key_source) {
|
||||||
DCHECK_EQ(state_, kWaitingForInit);
|
DCHECK_EQ(state_, kWaitingForInit);
|
||||||
DCHECK(init_cb_.is_null());
|
DCHECK(init_cb_.is_null());
|
||||||
DCHECK(!init_cb.is_null());
|
DCHECK(!init_cb.is_null());
|
||||||
DCHECK(!new_sample_cb.is_null());
|
DCHECK(!new_sample_cb.is_null());
|
||||||
DCHECK(!need_key_cb.is_null());
|
|
||||||
|
|
||||||
ChangeState(kParsingBoxes);
|
ChangeState(kParsingBoxes);
|
||||||
init_cb_ = init_cb;
|
init_cb_ = init_cb;
|
||||||
new_sample_cb_ = new_sample_cb;
|
new_sample_cb_ = new_sample_cb;
|
||||||
need_key_cb_ = need_key_cb;
|
decryption_key_source_ = decryption_key_source;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4MediaParser::Reset() {
|
void MP4MediaParser::Reset() {
|
||||||
|
@ -292,7 +300,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
||||||
}
|
}
|
||||||
|
|
||||||
init_cb_.Run(streams);
|
init_cb_.Run(streams);
|
||||||
EmitNeedKeyIfNecessary(moov_->pssh);
|
if (!FetchKeysIfNecessary(moov_->pssh))
|
||||||
|
return false;
|
||||||
runs_.reset(new TrackRunIterator(moov_.get()));
|
runs_.reset(new TrackRunIterator(moov_.get()));
|
||||||
RCHECK(runs_->Init());
|
RCHECK(runs_->Init());
|
||||||
ChangeState(kEmittingSamples);
|
ChangeState(kEmittingSamples);
|
||||||
|
@ -307,29 +316,40 @@ bool MP4MediaParser::ParseMoof(BoxReader* reader) {
|
||||||
if (!runs_)
|
if (!runs_)
|
||||||
runs_.reset(new TrackRunIterator(moov_.get()));
|
runs_.reset(new TrackRunIterator(moov_.get()));
|
||||||
RCHECK(runs_->Init(moof));
|
RCHECK(runs_->Init(moof));
|
||||||
EmitNeedKeyIfNecessary(moof.pssh);
|
if (!FetchKeysIfNecessary(moof.pssh))
|
||||||
|
return false;
|
||||||
ChangeState(kEmittingSamples);
|
ChangeState(kEmittingSamples);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4MediaParser::EmitNeedKeyIfNecessary(
|
bool MP4MediaParser::FetchKeysIfNecessary(
|
||||||
const std::vector<ProtectionSystemSpecificHeader>& headers) {
|
const std::vector<ProtectionSystemSpecificHeader>& headers) {
|
||||||
if (headers.empty())
|
if (headers.empty())
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
size_t total_size = 0;
|
if (!decryption_key_source_) {
|
||||||
for (size_t i = 0; i < headers.size(); i++)
|
LOG(ERROR) << "Content is encrypted, but content decryption not enabled.";
|
||||||
total_size += headers[i].raw_box.size();
|
return false;
|
||||||
|
|
||||||
scoped_ptr<uint8[]> init_data(new uint8[total_size]);
|
|
||||||
size_t pos = 0;
|
|
||||||
for (size_t i = 0; i < headers.size(); i++) {
|
|
||||||
memcpy(&init_data.get()[pos],
|
|
||||||
&headers[i].raw_box[0],
|
|
||||||
headers[i].raw_box.size());
|
|
||||||
pos += headers[i].raw_box.size();
|
|
||||||
}
|
}
|
||||||
need_key_cb_.Run(CONTAINER_MOV, init_data.Pass(), total_size);
|
|
||||||
|
// TODO(tinskip): Pass in raw 'pssh' boxes to FetchKeys. This will allow
|
||||||
|
// supporting multiple keysystems. Move this to KeySource.
|
||||||
|
std::vector<uint8> widevine_system_id;
|
||||||
|
base::HexStringToBytes(kWidevineKeySystemId, &widevine_system_id);
|
||||||
|
for (std::vector<ProtectionSystemSpecificHeader>::const_iterator iter =
|
||||||
|
headers.begin(); iter != headers.end(); ++iter) {
|
||||||
|
if (iter->system_id == widevine_system_id) {
|
||||||
|
Status status = decryption_key_source_->FetchKeys(iter->data);
|
||||||
|
if (!status.ok()) {
|
||||||
|
LOG(ERROR) << "Error fetching decryption keys: " << status;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(ERROR) << "No viable 'pssh' box found for content decryption.";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MP4MediaParser::EnqueueSample(bool* err) {
|
bool MP4MediaParser::EnqueueSample(bool* err) {
|
||||||
|
@ -379,35 +399,20 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
||||||
if (buf_size < runs_->sample_size())
|
if (buf_size < runs_->sample_size())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
scoped_ptr<DecryptConfig> decrypt_config;
|
scoped_refptr<MediaSample> stream_sample(MediaSample::CopyFrom(
|
||||||
std::vector<SubsampleEntry> subsamples;
|
buf, runs_->sample_size(), runs_->is_keyframe()));
|
||||||
if (runs_->is_encrypted()) {
|
if (runs_->is_encrypted()) {
|
||||||
decrypt_config = runs_->GetDecryptConfig();
|
scoped_ptr<DecryptConfig> decrypt_config = runs_->GetDecryptConfig();
|
||||||
if (!decrypt_config) {
|
if (!decrypt_config) {
|
||||||
*err = true;
|
*err = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
subsamples = decrypt_config->subsamples();
|
if (!DecryptSampleBuffer(decrypt_config.get(),
|
||||||
|
stream_sample->writable_data(),
|
||||||
|
stream_sample->data_size()))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decrypt_config) {
|
|
||||||
if (!subsamples.empty()) {
|
|
||||||
// Create a new config with the updated subsamples.
|
|
||||||
decrypt_config.reset(new DecryptConfig(decrypt_config->key_id(),
|
|
||||||
decrypt_config->iv(),
|
|
||||||
decrypt_config->data_offset(),
|
|
||||||
subsamples));
|
|
||||||
}
|
|
||||||
// else, use the existing config.
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8> frame_buf(buf, buf + runs_->sample_size());
|
|
||||||
scoped_refptr<MediaSample> stream_sample = MediaSample::CopyFrom(
|
|
||||||
&frame_buf[0], frame_buf.size(), runs_->is_keyframe());
|
|
||||||
|
|
||||||
if (decrypt_config)
|
|
||||||
stream_sample->set_decrypt_config(decrypt_config.Pass());
|
|
||||||
|
|
||||||
stream_sample->set_dts(runs_->dts());
|
stream_sample->set_dts(runs_->dts());
|
||||||
stream_sample->set_pts(runs_->cts());
|
stream_sample->set_pts(runs_->cts());
|
||||||
stream_sample->set_duration(runs_->duration());
|
stream_sample->set_duration(runs_->duration());
|
||||||
|
@ -425,6 +430,80 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MP4MediaParser::DecryptSampleBuffer(const DecryptConfig* decrypt_config,
|
||||||
|
uint8* 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* current_ptr = buffer;
|
||||||
|
const uint8* 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 offset) {
|
bool MP4MediaParser::ReadAndDiscardMDATsUntil(const int64 offset) {
|
||||||
bool err = false;
|
bool err = false;
|
||||||
while (mdat_tail_ < offset) {
|
while (mdat_tail_ < offset) {
|
||||||
|
|
|
@ -7,17 +7,23 @@
|
||||||
#ifndef MEDIA_FORMATS_MP4_MP4_MEDIA_PARSER_H_
|
#ifndef MEDIA_FORMATS_MP4_MP4_MEDIA_PARSER_H_
|
||||||
#define MEDIA_FORMATS_MP4_MP4_MEDIA_PARSER_H_
|
#define MEDIA_FORMATS_MP4_MP4_MEDIA_PARSER_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "base/basictypes.h"
|
#include "base/basictypes.h"
|
||||||
#include "base/callback_forward.h"
|
#include "base/callback_forward.h"
|
||||||
#include "base/compiler_specific.h"
|
#include "base/compiler_specific.h"
|
||||||
#include "base/memory/scoped_ptr.h"
|
#include "base/memory/scoped_ptr.h"
|
||||||
|
#include "base/memory/ref_counted.h"
|
||||||
|
#include "base/memory/scoped_ptr.h"
|
||||||
#include "media/base/media_parser.h"
|
#include "media/base/media_parser.h"
|
||||||
#include "media/base/offset_byte_queue.h"
|
#include "media/base/offset_byte_queue.h"
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
|
class AesCtrEncryptor;
|
||||||
|
class DecryptConfig;
|
||||||
|
|
||||||
namespace mp4 {
|
namespace mp4 {
|
||||||
|
|
||||||
class BoxReader;
|
class BoxReader;
|
||||||
|
@ -34,7 +40,7 @@ class MP4MediaParser : public MediaParser {
|
||||||
/// @{
|
/// @{
|
||||||
virtual void Init(const InitCB& init_cb,
|
virtual void Init(const InitCB& init_cb,
|
||||||
const NewSampleCB& new_sample_cb,
|
const NewSampleCB& new_sample_cb,
|
||||||
const NeedKeyCB& need_key_cb) OVERRIDE;
|
KeySource* decryption_key_source) OVERRIDE;
|
||||||
virtual void Flush() OVERRIDE;
|
virtual void Flush() OVERRIDE;
|
||||||
virtual bool Parse(const uint8* buf, int size) OVERRIDE;
|
virtual bool Parse(const uint8* buf, int size) OVERRIDE;
|
||||||
/// @}
|
/// @}
|
||||||
|
@ -51,9 +57,13 @@ class MP4MediaParser : public MediaParser {
|
||||||
bool ParseMoov(mp4::BoxReader* reader);
|
bool ParseMoov(mp4::BoxReader* reader);
|
||||||
bool ParseMoof(mp4::BoxReader* reader);
|
bool ParseMoof(mp4::BoxReader* reader);
|
||||||
|
|
||||||
void EmitNeedKeyIfNecessary(
|
bool FetchKeysIfNecessary(
|
||||||
const std::vector<ProtectionSystemSpecificHeader>& headers);
|
const std::vector<ProtectionSystemSpecificHeader>& headers);
|
||||||
|
|
||||||
|
bool DecryptSampleBuffer(const DecryptConfig* decrypt_config,
|
||||||
|
uint8* buffer,
|
||||||
|
size_t buffer_size);
|
||||||
|
|
||||||
// To retain proper framing, each 'mdat' atom must be read; to limit memory
|
// To retain proper framing, each 'mdat' atom must be read; to limit memory
|
||||||
// usage, the atom's data needs to be discarded incrementally as frames are
|
// usage, the atom'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
|
||||||
|
@ -73,7 +83,7 @@ class MP4MediaParser : public MediaParser {
|
||||||
State state_;
|
State state_;
|
||||||
InitCB init_cb_;
|
InitCB init_cb_;
|
||||||
NewSampleCB new_sample_cb_;
|
NewSampleCB new_sample_cb_;
|
||||||
NeedKeyCB need_key_cb_;
|
KeySource* decryption_key_source_;
|
||||||
|
|
||||||
OffsetByteQueue queue_;
|
OffsetByteQueue queue_;
|
||||||
|
|
||||||
|
@ -90,6 +100,9 @@ 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>, AesCtrEncryptor*> DecryptorMap;
|
||||||
|
DecryptorMap decryptor_map_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(MP4MediaParser);
|
DISALLOW_COPY_AND_ASSIGN(MP4MediaParser);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -53,19 +53,11 @@ class MP4MediaParserTest : public testing::Test {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyNeededF(MediaContainerName container,
|
|
||||||
scoped_ptr<uint8[]> init_data,
|
|
||||||
int init_data_size) {
|
|
||||||
DVLOG(1) << "KeyNeededF: " << init_data_size;
|
|
||||||
EXPECT_TRUE(init_data.get());
|
|
||||||
EXPECT_GT(init_data_size, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeParser() {
|
void InitializeParser() {
|
||||||
parser_->Init(
|
parser_->Init(
|
||||||
base::Bind(&MP4MediaParserTest::InitF, base::Unretained(this)),
|
base::Bind(&MP4MediaParserTest::InitF, base::Unretained(this)),
|
||||||
base::Bind(&MP4MediaParserTest::NewSampleF, base::Unretained(this)),
|
base::Bind(&MP4MediaParserTest::NewSampleF, base::Unretained(this)),
|
||||||
base::Bind(&MP4MediaParserTest::KeyNeededF, base::Unretained(this)));
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ParseMP4File(const std::string& filename, int append_bytes) {
|
bool ParseMP4File(const std::string& filename, int append_bytes) {
|
||||||
|
|
|
@ -541,10 +541,9 @@ scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
|
||||||
return scoped_ptr<DecryptConfig>();
|
return scoped_ptr<DecryptConfig>();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<uint8>& kid = track_encryption().default_kid;
|
|
||||||
return scoped_ptr<DecryptConfig>(new DecryptConfig(
|
return scoped_ptr<DecryptConfig>(new DecryptConfig(
|
||||||
std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()),
|
track_encryption().default_kid,
|
||||||
std::string(cenc_info.iv().begin(), cenc_info.iv().end()),
|
cenc_info.iv(),
|
||||||
0, // No offset to start of media data in MP4 using CENC.
|
0, // No offset to start of media data in MP4 using CENC.
|
||||||
cenc_info.subsamples()));
|
cenc_info.subsamples()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,7 +336,7 @@ TEST_F(TrackRunIteratorTest, DecryptConfigTest) {
|
||||||
config = iter_->GetDecryptConfig();
|
config = iter_->GetDecryptConfig();
|
||||||
EXPECT_EQ(config->subsamples().size(), 2u);
|
EXPECT_EQ(config->subsamples().size(), 2u);
|
||||||
EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u);
|
EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u);
|
||||||
EXPECT_EQ(config->subsamples()[1].cypher_bytes, 4u);
|
EXPECT_EQ(config->subsamples()[1].cipher_bytes, 4u);
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is legal for aux info blocks to be shared among multiple formats.
|
// It is legal for aux info blocks to be shared among multiple formats.
|
||||||
|
|
|
@ -84,7 +84,7 @@ class FakeClock : public base::Clock {
|
||||||
|
|
||||||
class PackagerTestBasic : public ::testing::TestWithParam<const char*> {
|
class PackagerTestBasic : public ::testing::TestWithParam<const char*> {
|
||||||
public:
|
public:
|
||||||
PackagerTestBasic() : decryptor_source_(NULL) {}
|
PackagerTestBasic() {}
|
||||||
|
|
||||||
virtual void SetUp() OVERRIDE {
|
virtual void SetUp() OVERRIDE {
|
||||||
// Create a test directory for testing, will be deleted after test.
|
// Create a test directory for testing, will be deleted after test.
|
||||||
|
@ -108,9 +108,12 @@ class PackagerTestBasic : public ::testing::TestWithParam<const char*> {
|
||||||
bool single_segment,
|
bool single_segment,
|
||||||
bool enable_encryption);
|
bool enable_encryption);
|
||||||
|
|
||||||
|
void Decrypt(const std::string& input,
|
||||||
|
const std::string& video_output,
|
||||||
|
const std::string& audio_output);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
base::FilePath test_directory_;
|
base::FilePath test_directory_;
|
||||||
DecryptorSource* decryptor_source_;
|
|
||||||
FakeClock fake_clock_;
|
FakeClock fake_clock_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -148,7 +151,7 @@ void PackagerTestBasic::Remux(const std::string& input,
|
||||||
bool enable_encryption) {
|
bool enable_encryption) {
|
||||||
CHECK(!video_output.empty() || !audio_output.empty());
|
CHECK(!video_output.empty() || !audio_output.empty());
|
||||||
|
|
||||||
Demuxer demuxer(GetFullPath(input), decryptor_source_);
|
Demuxer demuxer(GetFullPath(input));
|
||||||
ASSERT_OK(demuxer.Initialize());
|
ASSERT_OK(demuxer.Initialize());
|
||||||
|
|
||||||
scoped_ptr<KeySource> encryption_key_source(
|
scoped_ptr<KeySource> encryption_key_source(
|
||||||
|
@ -182,9 +185,9 @@ void PackagerTestBasic::Remux(const std::string& input,
|
||||||
|
|
||||||
if (enable_encryption) {
|
if (enable_encryption) {
|
||||||
muxer_audio->SetKeySource(encryption_key_source.get(),
|
muxer_audio->SetKeySource(encryption_key_source.get(),
|
||||||
KeySource::TRACK_TYPE_SD,
|
KeySource::TRACK_TYPE_SD,
|
||||||
kClearLeadInSeconds,
|
kClearLeadInSeconds,
|
||||||
kCryptoDurationInSeconds);
|
kCryptoDurationInSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,6 +195,39 @@ void PackagerTestBasic::Remux(const std::string& input,
|
||||||
ASSERT_OK(demuxer.Run());
|
ASSERT_OK(demuxer.Run());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PackagerTestBasic::Decrypt(const std::string& input,
|
||||||
|
const std::string& video_output,
|
||||||
|
const std::string& audio_output) {
|
||||||
|
CHECK(!video_output.empty() || !audio_output.empty());
|
||||||
|
|
||||||
|
Demuxer demuxer(GetFullPath(input));
|
||||||
|
scoped_ptr<KeySource> decryption_key_source(
|
||||||
|
KeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", ""));
|
||||||
|
DCHECK(decryption_key_source);
|
||||||
|
demuxer.SetKeySource(decryption_key_source.Pass());
|
||||||
|
ASSERT_OK(demuxer.Initialize());
|
||||||
|
|
||||||
|
scoped_ptr<Muxer> muxer;
|
||||||
|
MediaStream* stream(NULL);
|
||||||
|
if (!video_output.empty()) {
|
||||||
|
muxer.reset(
|
||||||
|
new mp4::MP4Muxer(SetupOptions(video_output, true)));
|
||||||
|
stream = FindFirstVideoStream(demuxer.streams());
|
||||||
|
}
|
||||||
|
if (!audio_output.empty()) {
|
||||||
|
muxer.reset(
|
||||||
|
new mp4::MP4Muxer(SetupOptions(audio_output, true)));
|
||||||
|
stream = FindFirstAudioStream(demuxer.streams());
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(muxer);
|
||||||
|
ASSERT_TRUE(stream != NULL);
|
||||||
|
ASSERT_TRUE(stream->info()->is_encrypted());
|
||||||
|
muxer->set_clock(&fake_clock_);
|
||||||
|
muxer->AddStream(stream);
|
||||||
|
|
||||||
|
ASSERT_OK(demuxer.Run());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedVideo) {
|
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedVideo) {
|
||||||
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
|
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
|
||||||
kOutputVideo,
|
kOutputVideo,
|
||||||
|
@ -215,11 +251,9 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedVideo) {
|
||||||
kSingleSegment,
|
kSingleSegment,
|
||||||
kEnableEncryption));
|
kEnableEncryption));
|
||||||
|
|
||||||
// Expect the output to be encrypted.
|
ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputVideo,
|
||||||
Demuxer demuxer(GetFullPath(kOutputVideo), decryptor_source_);
|
kOutputVideo2,
|
||||||
ASSERT_OK(demuxer.Initialize());
|
kOutputNone));
|
||||||
ASSERT_EQ(1u, demuxer.streams().size());
|
|
||||||
EXPECT_TRUE(demuxer.streams()[0]->info()->is_encrypted());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedAudio) {
|
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedAudio) {
|
||||||
|
@ -229,11 +263,9 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedAudio) {
|
||||||
kSingleSegment,
|
kSingleSegment,
|
||||||
kEnableEncryption));
|
kEnableEncryption));
|
||||||
|
|
||||||
// Expect the output to be encrypted.
|
ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputAudio,
|
||||||
Demuxer demuxer(GetFullPath(kOutputAudio), decryptor_source_);
|
kOutputNone,
|
||||||
ASSERT_OK(demuxer.Initialize());
|
kOutputAudio2));
|
||||||
ASSERT_EQ(1u, demuxer.streams().size());
|
|
||||||
EXPECT_TRUE(demuxer.streams()[0]->info()->is_encrypted());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PackagerTest : public PackagerTestBasic {
|
class PackagerTest : public PackagerTestBasic {
|
||||||
|
|
Loading…
Reference in New Issue