Allow query stream info without decryption
Also fixes packager_test script due to path change and adding unittests. Bug: 17977484 Change-Id: I57d1eb9f8ae8039dc5320ffa4141299d42b0e603
This commit is contained in:
parent
dd2ada2026
commit
b270e9fe0c
|
@ -23,7 +23,7 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.packager = packager_app.PackagerApp()
|
self.packager = packager_app.PackagerApp()
|
||||||
self.input = os.path.join(
|
self.input = os.path.join(
|
||||||
test_env.SRC_DIR, 'media', 'test', 'data', 'bear-1280x720.mp4')
|
test_env.SRC_DIR, 'packager', 'media', 'test', 'data', 'bear-1280x720.mp4')
|
||||||
self.tmpdir = tempfile.mkdtemp()
|
self.tmpdir = tempfile.mkdtemp()
|
||||||
fd, self.output = tempfile.mkstemp(dir=self.tmpdir)
|
fd, self.output = tempfile.mkstemp(dir=self.tmpdir)
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
|
|
|
@ -20,7 +20,7 @@ import sys
|
||||||
|
|
||||||
|
|
||||||
# Define static global objects and attributes.
|
# Define static global objects and attributes.
|
||||||
SRC_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../..')
|
SRC_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../..')
|
||||||
|
|
||||||
|
|
||||||
# Parse arguments and calculate dynamic global objects and attributes.
|
# Parse arguments and calculate dynamic global objects and attributes.
|
||||||
|
|
|
@ -337,7 +337,7 @@ void AesCbcCtsEncryptor::Encrypt(const std::vector<uint8_t>& plaintext,
|
||||||
if (plaintext.empty())
|
if (plaintext.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
return Encrypt(plaintext.data(), plaintext.size(), &(*ciphertext)[0]);
|
Encrypt(plaintext.data(), plaintext.size(), &(*ciphertext)[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AesCbcCtsEncryptor::SetIv(const std::vector<uint8_t>& iv) {
|
bool AesCbcCtsEncryptor::SetIv(const std::vector<uint8_t>& iv) {
|
||||||
|
@ -461,7 +461,7 @@ void AesCbcCtsDecryptor::Decrypt(const std::vector<uint8_t>& ciphertext,
|
||||||
if (ciphertext.empty())
|
if (ciphertext.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
return Decrypt(ciphertext.data(), ciphertext.size(), &(*plaintext)[0]);
|
Decrypt(ciphertext.data(), ciphertext.size(), &(*plaintext)[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AesCbcCtsDecryptor::SetIv(const std::vector<uint8_t>& iv) {
|
bool AesCbcCtsDecryptor::SetIv(const std::vector<uint8_t>& iv) {
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
#include "packager/media/formats/wvm/wvm_media_parser.h"
|
#include "packager/media/formats/wvm/wvm_media_parser.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
const size_t kInitBufSize = 0x4000; // 16KB, sufficient to determine the
|
||||||
|
// container and likely all init data.
|
||||||
const size_t kBufSize = 0x40000; // 256KB.
|
const size_t kBufSize = 0x40000; // 256KB.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +57,7 @@ Status Demuxer::Initialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine media container.
|
// Determine media container.
|
||||||
int64_t bytes_read = media_file_->Read(buffer_.get(), kBufSize);
|
int64_t bytes_read = media_file_->Read(buffer_.get(), kInitBufSize);
|
||||||
if (bytes_read <= 0)
|
if (bytes_read <= 0)
|
||||||
return Status(error::FILE_FAILURE, "Cannot read file " + file_name_);
|
return Status(error::FILE_FAILURE, "Cannot read file " + file_name_);
|
||||||
MediaContainerName container = DetermineContainer(buffer_.get(), bytes_read);
|
MediaContainerName container = DetermineContainer(buffer_.get(), bytes_read);
|
||||||
|
@ -80,16 +82,16 @@ Status Demuxer::Initialize() {
|
||||||
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
|
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
|
||||||
key_source_.get());
|
key_source_.get());
|
||||||
|
|
||||||
if (!parser_->Parse(buffer_.get(), bytes_read))
|
if (!parser_->Parse(buffer_.get(), bytes_read)) {
|
||||||
return Status(error::PARSER_FAILURE,
|
init_parsing_status_ =
|
||||||
"Cannot parse media file " + file_name_);
|
Status(error::PARSER_FAILURE, "Cannot parse media file " + file_name_);
|
||||||
|
|
||||||
Status status;
|
|
||||||
while (!init_event_received_) {
|
|
||||||
if (!(status = Parse()).ok())
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return status;
|
|
||||||
|
// Parse until init event received or on error.
|
||||||
|
while (!init_event_received_ && init_parsing_status_.ok())
|
||||||
|
init_parsing_status_ = Parse();
|
||||||
|
// Defer error reporting if init completed successfully.
|
||||||
|
return init_event_received_ ? Status::OK : init_parsing_status_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Demuxer::ParserInitEvent(
|
void Demuxer::ParserInitEvent(
|
||||||
|
@ -147,6 +149,11 @@ Status Demuxer::Parse() {
|
||||||
DCHECK(parser_);
|
DCHECK(parser_);
|
||||||
DCHECK(buffer_);
|
DCHECK(buffer_);
|
||||||
|
|
||||||
|
// Return early and avoid call Parse(...) again if it has already failed at
|
||||||
|
// the initialization.
|
||||||
|
if (!init_parsing_status_.ok())
|
||||||
|
return init_parsing_status_;
|
||||||
|
|
||||||
int64_t bytes_read = media_file_->Read(buffer_.get(), kBufSize);
|
int64_t bytes_read = media_file_->Read(buffer_.get(), kBufSize);
|
||||||
if (bytes_read <= 0) {
|
if (bytes_read <= 0) {
|
||||||
if (media_file_->Eof()) {
|
if (media_file_->Eof()) {
|
||||||
|
|
|
@ -70,6 +70,7 @@ class Demuxer {
|
||||||
std::string file_name_;
|
std::string file_name_;
|
||||||
File* media_file_;
|
File* media_file_;
|
||||||
bool init_event_received_;
|
bool init_event_received_;
|
||||||
|
Status init_parsing_status_;
|
||||||
scoped_ptr<MediaParser> parser_;
|
scoped_ptr<MediaParser> parser_;
|
||||||
std::vector<MediaStream*> streams_;
|
std::vector<MediaStream*> streams_;
|
||||||
scoped_ptr<uint8_t[]> buffer_;
|
scoped_ptr<uint8_t[]> buffer_;
|
||||||
|
|
|
@ -332,10 +332,9 @@ bool MP4MediaParser::FetchKeysIfNecessary(
|
||||||
if (headers.empty())
|
if (headers.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!decryption_key_source_) {
|
// An error will be returned later if the samples need to be decrypted.
|
||||||
LOG(ERROR) << "Content is encrypted, but content decryption not enabled.";
|
if (!decryption_key_source_)
|
||||||
return false;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(tinskip): Pass in raw 'pssh' boxes to FetchKeys. This will allow
|
// TODO(tinskip): Pass in raw 'pssh' boxes to FetchKeys. This will allow
|
||||||
// supporting multiple keysystems. Move this to KeySource.
|
// supporting multiple keysystems. Move this to KeySource.
|
||||||
|
@ -408,14 +407,14 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
||||||
buf, runs_->sample_size(), runs_->is_keyframe()));
|
buf, runs_->sample_size(), runs_->is_keyframe()));
|
||||||
if (runs_->is_encrypted()) {
|
if (runs_->is_encrypted()) {
|
||||||
scoped_ptr<DecryptConfig> decrypt_config = runs_->GetDecryptConfig();
|
scoped_ptr<DecryptConfig> decrypt_config = runs_->GetDecryptConfig();
|
||||||
if (!decrypt_config) {
|
if (!decrypt_config ||
|
||||||
|
!DecryptSampleBuffer(decrypt_config.get(),
|
||||||
|
stream_sample->writable_data(),
|
||||||
|
stream_sample->data_size())) {
|
||||||
*err = true;
|
*err = true;
|
||||||
|
LOG(ERROR) << "Cannot decrypt samples.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!DecryptSampleBuffer(decrypt_config.get(),
|
|
||||||
stream_sample->writable_data(),
|
|
||||||
stream_sample->data_size()))
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stream_sample->set_dts(runs_->dts());
|
stream_sample->set_dts(runs_->dts());
|
||||||
|
|
|
@ -4,27 +4,46 @@
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/base/bind.h"
|
#include "packager/base/bind.h"
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
|
#include "packager/media/base/key_source.h"
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
|
#include "packager/media/base/stream_info.h"
|
||||||
#include "packager/media/formats/mp4/mp4_media_parser.h"
|
#include "packager/media/formats/mp4/mp4_media_parser.h"
|
||||||
#include "packager/media/test/test_data_util.h"
|
#include "packager/media/test/test_data_util.h"
|
||||||
|
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::DoAll;
|
||||||
|
using ::testing::Return;
|
||||||
|
using ::testing::SetArgPointee;
|
||||||
|
|
||||||
namespace edash_packager {
|
namespace edash_packager {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class MockKeySource : public KeySource {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD1(FetchKeys, Status(const std::vector<uint8_t>& pssh_data));
|
||||||
|
MOCK_METHOD2(GetKey,
|
||||||
|
Status(const std::vector<uint8_t>& key_id, EncryptionKey* key));
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace mp4 {
|
namespace mp4 {
|
||||||
|
|
||||||
class MP4MediaParserTest : public testing::Test {
|
class MP4MediaParserTest : public testing::Test {
|
||||||
public:
|
public:
|
||||||
MP4MediaParserTest() : configs_received_(false) {
|
MP4MediaParserTest() : num_streams_(0), num_samples_(0) {
|
||||||
parser_.reset(new MP4MediaParser());
|
parser_.reset(new MP4MediaParser());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
scoped_ptr<MP4MediaParser> parser_;
|
scoped_ptr<MP4MediaParser> parser_;
|
||||||
bool configs_received_;
|
size_t num_streams_;
|
||||||
|
size_t num_samples_;
|
||||||
|
|
||||||
bool AppendData(const uint8_t* data, size_t length) {
|
bool AppendData(const uint8_t* data, size_t length) {
|
||||||
return parser_->Parse(data, length);
|
return parser_->Parse(data, length);
|
||||||
|
@ -46,76 +65,92 @@ class MP4MediaParserTest : public testing::Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitF(const std::vector<scoped_refptr<StreamInfo> >& streams) {
|
void InitF(const std::vector<scoped_refptr<StreamInfo> >& streams) {
|
||||||
if (streams.size() > 0)
|
for (std::vector<scoped_refptr<StreamInfo> >::const_iterator iter =
|
||||||
configs_received_ = true;
|
streams.begin();
|
||||||
|
iter != streams.end();
|
||||||
|
++iter) {
|
||||||
|
DVLOG(2) << (*iter)->ToString();
|
||||||
|
}
|
||||||
|
num_streams_ = streams.size();
|
||||||
|
num_samples_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NewSampleF(uint32_t track_id, const scoped_refptr<MediaSample>& sample) {
|
bool NewSampleF(uint32_t track_id, const scoped_refptr<MediaSample>& sample) {
|
||||||
DVLOG(2) << "Track Id: " << track_id << " "
|
DVLOG(2) << "Track Id: " << track_id << " "
|
||||||
<< sample->ToString();
|
<< sample->ToString();
|
||||||
|
++num_samples_;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitializeParser() {
|
void InitializeParser(KeySource* decryption_key_source) {
|
||||||
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)),
|
||||||
NULL);
|
decryption_key_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ParseMP4File(const std::string& filename, int append_bytes) {
|
bool ParseMP4File(const std::string& filename, int append_bytes) {
|
||||||
InitializeParser();
|
InitializeParser(NULL);
|
||||||
|
|
||||||
std::vector<uint8_t> buffer = ReadTestDataFile(filename);
|
std::vector<uint8_t> buffer = ReadTestDataFile(filename);
|
||||||
EXPECT_TRUE(AppendDataInPieces(buffer.data(),
|
return AppendDataInPieces(buffer.data(), buffer.size(), append_bytes);
|
||||||
buffer.size(),
|
|
||||||
append_bytes));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(MP4MediaParserTest, UnalignedAppend) {
|
TEST_F(MP4MediaParserTest, UnalignedAppend) {
|
||||||
// Test small, non-segment-aligned appends (small enough to exercise
|
// Test small, non-segment-aligned appends (small enough to exercise
|
||||||
// incremental append system)
|
// incremental append system)
|
||||||
ParseMP4File("bear-1280x720-av_frag.mp4", 512);
|
EXPECT_TRUE(ParseMP4File("bear-1280x720-av_frag.mp4", 512));
|
||||||
|
EXPECT_EQ(2u, num_streams_);
|
||||||
|
EXPECT_EQ(201u, num_samples_);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MP4MediaParserTest, BytewiseAppend) {
|
TEST_F(MP4MediaParserTest, BytewiseAppend) {
|
||||||
// Ensure no incremental errors occur when parsing
|
// Ensure no incremental errors occur when parsing
|
||||||
ParseMP4File("bear-1280x720-av_frag.mp4", 1);
|
EXPECT_TRUE(ParseMP4File("bear-1280x720-av_frag.mp4", 1));
|
||||||
|
EXPECT_EQ(2u, num_streams_);
|
||||||
|
EXPECT_EQ(201u, num_samples_);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MP4MediaParserTest, MultiFragmentAppend) {
|
TEST_F(MP4MediaParserTest, MultiFragmentAppend) {
|
||||||
// Large size ensures multiple fragments are appended in one call (size is
|
// Large size ensures multiple fragments are appended in one call (size is
|
||||||
// larger than this particular test file)
|
// larger than this particular test file)
|
||||||
ParseMP4File("bear-1280x720-av_frag.mp4", 768432);
|
EXPECT_TRUE(ParseMP4File("bear-1280x720-av_frag.mp4", 768432));
|
||||||
|
EXPECT_EQ(2u, num_streams_);
|
||||||
|
EXPECT_EQ(201u, num_samples_);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MP4MediaParserTest, Flush) {
|
TEST_F(MP4MediaParserTest, Flush) {
|
||||||
// Flush while reading sample data, then start a new stream.
|
// Flush while reading sample data, then start a new stream.
|
||||||
InitializeParser();
|
InitializeParser(NULL);
|
||||||
|
|
||||||
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
|
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
|
||||||
EXPECT_TRUE(AppendDataInPieces(buffer.data(), 65536, 512));
|
EXPECT_TRUE(AppendDataInPieces(buffer.data(), 65536, 512));
|
||||||
parser_->Flush();
|
parser_->Flush();
|
||||||
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
|
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
|
||||||
|
EXPECT_EQ(2u, num_streams_);
|
||||||
|
EXPECT_EQ(201u, num_samples_);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MP4MediaParserTest, Reinitialization) {
|
TEST_F(MP4MediaParserTest, Reinitialization) {
|
||||||
InitializeParser();
|
InitializeParser(NULL);
|
||||||
|
|
||||||
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
|
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
|
||||||
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
|
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
|
||||||
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
|
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
|
||||||
|
EXPECT_EQ(2u, num_streams_);
|
||||||
|
EXPECT_EQ(201u, num_samples_);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MP4MediaParserTest, MPEG2_AAC_LC) {
|
TEST_F(MP4MediaParserTest, MPEG2_AAC_LC) {
|
||||||
ParseMP4File("bear-mpeg2-aac-only_frag.mp4", 512);
|
EXPECT_TRUE(ParseMP4File("bear-mpeg2-aac-only_frag.mp4", 512));
|
||||||
|
EXPECT_EQ(1u, num_streams_);
|
||||||
|
EXPECT_EQ(119u, num_samples_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that a moov box is not always required after Flush() is called.
|
// Test that a moov box is not always required after Flush() is called.
|
||||||
TEST_F(MP4MediaParserTest, NoMoovAfterFlush) {
|
TEST_F(MP4MediaParserTest, NoMoovAfterFlush) {
|
||||||
InitializeParser();
|
InitializeParser(NULL);
|
||||||
|
|
||||||
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
|
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
|
||||||
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
|
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
|
||||||
|
@ -127,7 +162,48 @@ TEST_F(MP4MediaParserTest, NoMoovAfterFlush) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MP4MediaParserTest, NON_FRAGMENTED_MP4) {
|
TEST_F(MP4MediaParserTest, NON_FRAGMENTED_MP4) {
|
||||||
ParseMP4File("bear-1280x720.mp4", 512);
|
EXPECT_TRUE(ParseMP4File("bear-1280x720.mp4", 512));
|
||||||
|
EXPECT_EQ(2u, num_streams_);
|
||||||
|
EXPECT_EQ(201u, num_samples_);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MP4MediaParserTest, CencWithoutDecryptionSource) {
|
||||||
|
// Parsing should fail but it will get the streams successfully.
|
||||||
|
EXPECT_FALSE(ParseMP4File("bear-1280x720-v_frag-cenc.mp4", 512));
|
||||||
|
EXPECT_EQ(1u, num_streams_);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MP4MediaParserTest, CencInitWithoutDecryptionSource) {
|
||||||
|
InitializeParser(NULL);
|
||||||
|
|
||||||
|
std::vector<uint8_t> buffer =
|
||||||
|
ReadTestDataFile("bear-1280x720-v_frag-cenc.mp4");
|
||||||
|
const int kFirstMoofOffset = 1646;
|
||||||
|
EXPECT_TRUE(AppendDataInPieces(buffer.data(), kFirstMoofOffset, 512));
|
||||||
|
EXPECT_EQ(1u, num_streams_);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MP4MediaParserTest, CencWithDecryptionSource) {
|
||||||
|
MockKeySource mock_key_source;
|
||||||
|
EXPECT_CALL(mock_key_source, FetchKeys(_)).WillOnce(Return(Status::OK));
|
||||||
|
|
||||||
|
const char kKey[] =
|
||||||
|
"\xeb\xdd\x62\xf1\x68\x14\xd2\x7b\x68\xef\x12\x2a\xfc\xe4\xae\x3c";
|
||||||
|
const char kKeyId[] = "0123456789012345";
|
||||||
|
|
||||||
|
EncryptionKey encryption_key;
|
||||||
|
encryption_key.key.assign(kKey, kKey + strlen(kKey));
|
||||||
|
EXPECT_CALL(mock_key_source,
|
||||||
|
GetKey(std::vector<uint8_t>(kKeyId, kKeyId + strlen(kKeyId)), _))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
|
||||||
|
|
||||||
|
InitializeParser(&mock_key_source);
|
||||||
|
|
||||||
|
std::vector<uint8_t> buffer =
|
||||||
|
ReadTestDataFile("bear-1280x720-v_frag-cenc.mp4");
|
||||||
|
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
|
||||||
|
EXPECT_EQ(1u, num_streams_);
|
||||||
|
EXPECT_EQ(82u, num_samples_);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mp4
|
} // namespace mp4
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#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/key_source.h"
|
#include "packager/media/base/key_source.h"
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
|
@ -749,7 +750,11 @@ void WvmMediaParser::StartMediaSampleDemux(uint8_t* read_ptr) {
|
||||||
bool WvmMediaParser::Output() {
|
bool WvmMediaParser::Output() {
|
||||||
// Check decrypted sample data.
|
// Check decrypted sample data.
|
||||||
if (prev_pes_flags_1_ & kScramblingBitsMask) {
|
if (prev_pes_flags_1_ & kScramblingBitsMask) {
|
||||||
content_decryptor_.Decrypt(sample_data_, &sample_data_);
|
if (!content_decryptor_) {
|
||||||
|
LOG(ERROR) << "Source content is encrypted, but decryption not enabled";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
content_decryptor_->Decrypt(sample_data_, &sample_data_);
|
||||||
}
|
}
|
||||||
if ((prev_pes_stream_id_ & kPesStreamIdVideoMask) == kPesStreamIdVideo) {
|
if ((prev_pes_stream_id_ & kPesStreamIdVideoMask) == kPesStreamIdVideo) {
|
||||||
// Set data on the video stream from the NalUnitStream.
|
// Set data on the video stream from the NalUnitStream.
|
||||||
|
@ -911,10 +916,7 @@ void WvmMediaParser::EmitSample(uint32_t parsed_audio_or_video_stream_id,
|
||||||
|
|
||||||
bool WvmMediaParser::GetAssetKey(const uint32_t asset_id,
|
bool WvmMediaParser::GetAssetKey(const uint32_t asset_id,
|
||||||
EncryptionKey* encryption_key) {
|
EncryptionKey* encryption_key) {
|
||||||
if (decryption_key_source_ == NULL) {
|
DCHECK(decryption_key_source_);
|
||||||
LOG(ERROR) << "Source content is encrypted, but decryption not enabled";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Status status = decryption_key_source_->FetchKeys(asset_id);
|
Status status = decryption_key_source_->FetchKeys(asset_id);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
LOG(ERROR) << "Fetch Key(s) failed for AssetID = " << asset_id
|
LOG(ERROR) << "Fetch Key(s) failed for AssetID = " << asset_id
|
||||||
|
@ -934,6 +936,10 @@ bool WvmMediaParser::GetAssetKey(const uint32_t asset_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WvmMediaParser::ProcessEcm() {
|
bool WvmMediaParser::ProcessEcm() {
|
||||||
|
// An error will be returned later if the samples need to be decrypted.
|
||||||
|
if (!decryption_key_source_)
|
||||||
|
return true;
|
||||||
|
|
||||||
if (current_program_id_ > 0) {
|
if (current_program_id_ > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -959,26 +965,29 @@ bool WvmMediaParser::ProcessEcm() {
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> iv(kInitializationVectorSizeBytes);
|
std::vector<uint8_t> iv(kInitializationVectorSizeBytes);
|
||||||
AesCbcCtsDecryptor asset_decryptor;
|
AesCbcCtsDecryptor asset_decryptor;
|
||||||
asset_decryptor.InitializeWithIv(encryption_key.key, iv);
|
if (!asset_decryptor.InitializeWithIv(encryption_key.key, iv)) {
|
||||||
|
LOG(ERROR) << "Failed to initialize asset_decryptor.";
|
||||||
std::vector<uint8_t> content_key_buffer; // flags + contentKey + padding.
|
|
||||||
content_key_buffer.resize(
|
|
||||||
kEcmFlagsSizeBytes + kEcmContentKeySizeBytes + kEcmPaddingSizeBytes);
|
|
||||||
// Get content key + padding from ECM.
|
|
||||||
memcpy(&content_key_buffer[0], ecm_data,
|
|
||||||
kEcmFlagsSizeBytes + kEcmContentKeySizeBytes + kEcmPaddingSizeBytes);
|
|
||||||
asset_decryptor.Decrypt(content_key_buffer, &content_key_buffer);
|
|
||||||
if (content_key_buffer.empty()) {
|
|
||||||
LOG(ERROR) << "Decryption of content key failed for asset id = "
|
|
||||||
<< asset_id;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const size_t content_key_buffer_size =
|
||||||
|
kEcmFlagsSizeBytes + kEcmContentKeySizeBytes +
|
||||||
|
kEcmPaddingSizeBytes; // flags + contentKey + padding.
|
||||||
|
std::vector<uint8_t> content_key_buffer(content_key_buffer_size);
|
||||||
|
asset_decryptor.Decrypt(
|
||||||
|
ecm_data, content_key_buffer_size, &content_key_buffer[0]);
|
||||||
|
|
||||||
std::vector<uint8_t> decrypted_content_key_vec(
|
std::vector<uint8_t> decrypted_content_key_vec(
|
||||||
content_key_buffer.begin() + 4,
|
content_key_buffer.begin() + 4,
|
||||||
content_key_buffer.begin() + 20);
|
content_key_buffer.begin() + 20);
|
||||||
content_decryptor_.InitializeWithIv(decrypted_content_key_vec, iv);
|
scoped_ptr<AesCbcCtsDecryptor> content_decryptor(new AesCbcCtsDecryptor);
|
||||||
return(true);
|
if (!content_decryptor->InitializeWithIv(decrypted_content_key_vec, iv)) {
|
||||||
|
LOG(ERROR) << "Failed to initialize content decryptor.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
content_decryptor_ = content_decryptor.Pass();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
DemuxStreamIdMediaSample::DemuxStreamIdMediaSample() :
|
DemuxStreamIdMediaSample::DemuxStreamIdMediaSample() :
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "packager/base/memory/scoped_ptr.h"
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
#include "packager/media/base/aes_encryptor.h"
|
|
||||||
#include "packager/media/base/media_parser.h"
|
#include "packager/media/base/media_parser.h"
|
||||||
#include "packager/media/base/network_util.h"
|
#include "packager/media/base/network_util.h"
|
||||||
#include "packager/media/filters/h264_byte_to_unit_stream_converter.h"
|
#include "packager/media/filters/h264_byte_to_unit_stream_converter.h"
|
||||||
|
@ -20,6 +19,7 @@
|
||||||
namespace edash_packager {
|
namespace edash_packager {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
|
class AesCbcCtsDecryptor;
|
||||||
class KeySource;
|
class KeySource;
|
||||||
struct EncryptionKey;
|
struct EncryptionKey;
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ class WvmMediaParser : public MediaParser {
|
||||||
std::deque<DemuxStreamIdMediaSample> media_sample_queue_;
|
std::deque<DemuxStreamIdMediaSample> media_sample_queue_;
|
||||||
std::vector<uint8_t> sample_data_;
|
std::vector<uint8_t> sample_data_;
|
||||||
KeySource* decryption_key_source_;
|
KeySource* decryption_key_source_;
|
||||||
AesCbcCtsDecryptor content_decryptor_;
|
scoped_ptr<AesCbcCtsDecryptor> content_decryptor_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(WvmMediaParser);
|
DISALLOW_COPY_AND_ASSIGN(WvmMediaParser);
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,6 +30,7 @@ const int kExpectedVideoFrameCount = 6665;
|
||||||
const int kExpectedAudioFrameCount = 11964;
|
const int kExpectedAudioFrameCount = 11964;
|
||||||
const uint8_t kExpectedAssetKey[] =
|
const uint8_t kExpectedAssetKey[] =
|
||||||
"\x06\x81\x7f\x48\x6b\xf2\x7f\x3e\xc7\x39\xa8\x3f\x12\x0a\xd2\xfc";
|
"\x06\x81\x7f\x48\x6b\xf2\x7f\x3e\xc7\x39\xa8\x3f\x12\x0a\xd2\xfc";
|
||||||
|
const size_t kInitDataSize = 0x4000;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
|
@ -141,6 +142,23 @@ class WvmMediaParserTest : public testing::Test {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TEST_F(WvmMediaParserTest, ParseWvmWithoutKeySource) {
|
||||||
|
// Parsing should fail but it will get the streams successfully.
|
||||||
|
key_source_.reset();
|
||||||
|
InitializeParser();
|
||||||
|
std::vector<uint8_t> buffer = ReadTestDataFile(kWvmFile);
|
||||||
|
EXPECT_FALSE(parser_->Parse(buffer.data(), buffer.size()));
|
||||||
|
EXPECT_EQ(kExpectedStreams, stream_map_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WvmMediaParserTest, ParseWvmInitWithoutKeySource) {
|
||||||
|
key_source_.reset();
|
||||||
|
InitializeParser();
|
||||||
|
std::vector<uint8_t> buffer = ReadTestDataFile(kWvmFile);
|
||||||
|
EXPECT_TRUE(parser_->Parse(buffer.data(), kInitDataSize));
|
||||||
|
EXPECT_EQ(kExpectedStreams, stream_map_.size());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(WvmMediaParserTest, ParseWvm) {
|
TEST_F(WvmMediaParserTest, ParseWvm) {
|
||||||
EXPECT_CALL(*key_source_, FetchKeys(_)).WillOnce(Return(Status::OK));
|
EXPECT_CALL(*key_source_, FetchKeys(_)).WillOnce(Return(Status::OK));
|
||||||
EXPECT_CALL(*key_source_, GetKey(_, _))
|
EXPECT_CALL(*key_source_, GetKey(_, _))
|
||||||
|
|
Loading…
Reference in New Issue