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:
KongQun Yang 2014-10-13 19:34:29 -07:00
parent dd2ada2026
commit b270e9fe0c
10 changed files with 173 additions and 63 deletions

View File

@ -23,7 +23,7 @@ class PackagerAppTest(unittest.TestCase):
def setUp(self):
self.packager = packager_app.PackagerApp()
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()
fd, self.output = tempfile.mkstemp(dir=self.tmpdir)
os.close(fd)

View File

@ -20,7 +20,7 @@ import sys
# 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.

View File

@ -337,7 +337,7 @@ void AesCbcCtsEncryptor::Encrypt(const std::vector<uint8_t>& plaintext,
if (plaintext.empty())
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) {
@ -461,7 +461,7 @@ void AesCbcCtsDecryptor::Decrypt(const std::vector<uint8_t>& ciphertext,
if (ciphertext.empty())
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) {

View File

@ -21,6 +21,8 @@
#include "packager/media/formats/wvm/wvm_media_parser.h"
namespace {
const size_t kInitBufSize = 0x4000; // 16KB, sufficient to determine the
// container and likely all init data.
const size_t kBufSize = 0x40000; // 256KB.
}
@ -55,7 +57,7 @@ Status Demuxer::Initialize() {
}
// 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)
return Status(error::FILE_FAILURE, "Cannot read file " + file_name_);
MediaContainerName container = DetermineContainer(buffer_.get(), bytes_read);
@ -80,16 +82,16 @@ Status Demuxer::Initialize() {
base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)),
key_source_.get());
if (!parser_->Parse(buffer_.get(), bytes_read))
return Status(error::PARSER_FAILURE,
"Cannot parse media file " + file_name_);
Status status;
while (!init_event_received_) {
if (!(status = Parse()).ok())
break;
if (!parser_->Parse(buffer_.get(), bytes_read)) {
init_parsing_status_ =
Status(error::PARSER_FAILURE, "Cannot parse media file " + file_name_);
}
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(
@ -147,6 +149,11 @@ Status Demuxer::Parse() {
DCHECK(parser_);
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);
if (bytes_read <= 0) {
if (media_file_->Eof()) {

View File

@ -70,6 +70,7 @@ class Demuxer {
std::string file_name_;
File* media_file_;
bool init_event_received_;
Status init_parsing_status_;
scoped_ptr<MediaParser> parser_;
std::vector<MediaStream*> streams_;
scoped_ptr<uint8_t[]> buffer_;

View File

@ -332,10 +332,9 @@ bool MP4MediaParser::FetchKeysIfNecessary(
if (headers.empty())
return true;
if (!decryption_key_source_) {
LOG(ERROR) << "Content is encrypted, but content decryption not enabled.";
return false;
}
// An error will be returned later if the samples need to be decrypted.
if (!decryption_key_source_)
return true;
// TODO(tinskip): Pass in raw 'pssh' boxes to FetchKeys. This will allow
// supporting multiple keysystems. Move this to KeySource.
@ -408,14 +407,14 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
buf, runs_->sample_size(), runs_->is_keyframe()));
if (runs_->is_encrypted()) {
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;
LOG(ERROR) << "Cannot decrypt samples.";
return false;
}
if (!DecryptSampleBuffer(decrypt_config.get(),
stream_sample->writable_data(),
stream_sample->data_size()))
return false;
}
stream_sample->set_dts(runs_->dts());

View File

@ -4,27 +4,46 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "packager/base/bind.h"
#include "packager/base/logging.h"
#include "packager/media/base/key_source.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/test/test_data_util.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace edash_packager {
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 {
class MP4MediaParserTest : public testing::Test {
public:
MP4MediaParserTest() : configs_received_(false) {
MP4MediaParserTest() : num_streams_(0), num_samples_(0) {
parser_.reset(new MP4MediaParser());
}
protected:
scoped_ptr<MP4MediaParser> parser_;
bool configs_received_;
size_t num_streams_;
size_t num_samples_;
bool AppendData(const uint8_t* data, size_t length) {
return parser_->Parse(data, length);
@ -46,76 +65,92 @@ class MP4MediaParserTest : public testing::Test {
}
void InitF(const std::vector<scoped_refptr<StreamInfo> >& streams) {
if (streams.size() > 0)
configs_received_ = true;
for (std::vector<scoped_refptr<StreamInfo> >::const_iterator iter =
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) {
DVLOG(2) << "Track Id: " << track_id << " "
<< sample->ToString();
++num_samples_;
return true;
}
void InitializeParser() {
void InitializeParser(KeySource* decryption_key_source) {
parser_->Init(
base::Bind(&MP4MediaParserTest::InitF, base::Unretained(this)),
base::Bind(&MP4MediaParserTest::NewSampleF, base::Unretained(this)),
NULL);
decryption_key_source);
}
bool ParseMP4File(const std::string& filename, int append_bytes) {
InitializeParser();
InitializeParser(NULL);
std::vector<uint8_t> buffer = ReadTestDataFile(filename);
EXPECT_TRUE(AppendDataInPieces(buffer.data(),
buffer.size(),
append_bytes));
return true;
return AppendDataInPieces(buffer.data(), buffer.size(), append_bytes);
}
};
TEST_F(MP4MediaParserTest, UnalignedAppend) {
// Test small, non-segment-aligned appends (small enough to exercise
// 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) {
// 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) {
// Large size ensures multiple fragments are appended in one call (size is
// 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) {
// Flush while reading sample data, then start a new stream.
InitializeParser();
InitializeParser(NULL);
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
EXPECT_TRUE(AppendDataInPieces(buffer.data(), 65536, 512));
parser_->Flush();
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
EXPECT_EQ(2u, num_streams_);
EXPECT_EQ(201u, num_samples_);
}
TEST_F(MP4MediaParserTest, Reinitialization) {
InitializeParser();
InitializeParser(NULL);
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_EQ(2u, num_streams_);
EXPECT_EQ(201u, num_samples_);
}
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_F(MP4MediaParserTest, NoMoovAfterFlush) {
InitializeParser();
InitializeParser(NULL);
std::vector<uint8_t> buffer = ReadTestDataFile("bear-1280x720-av_frag.mp4");
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
@ -127,7 +162,48 @@ TEST_F(MP4MediaParserTest, NoMoovAfterFlush) {
}
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

View File

@ -9,6 +9,7 @@
#include <vector>
#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/key_source.h"
#include "packager/media/base/media_sample.h"
@ -749,7 +750,11 @@ void WvmMediaParser::StartMediaSampleDemux(uint8_t* read_ptr) {
bool WvmMediaParser::Output() {
// Check decrypted sample data.
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) {
// 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,
EncryptionKey* encryption_key) {
if (decryption_key_source_ == NULL) {
LOG(ERROR) << "Source content is encrypted, but decryption not enabled";
return false;
}
DCHECK(decryption_key_source_);
Status status = decryption_key_source_->FetchKeys(asset_id);
if (!status.ok()) {
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() {
// An error will be returned later if the samples need to be decrypted.
if (!decryption_key_source_)
return true;
if (current_program_id_ > 0) {
return true;
}
@ -959,26 +965,29 @@ bool WvmMediaParser::ProcessEcm() {
}
std::vector<uint8_t> iv(kInitializationVectorSizeBytes);
AesCbcCtsDecryptor asset_decryptor;
asset_decryptor.InitializeWithIv(encryption_key.key, iv);
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;
if (!asset_decryptor.InitializeWithIv(encryption_key.key, iv)) {
LOG(ERROR) << "Failed to initialize asset_decryptor.";
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(
content_key_buffer.begin() + 4,
content_key_buffer.begin() + 20);
content_decryptor_.InitializeWithIv(decrypted_content_key_vec, iv);
return(true);
scoped_ptr<AesCbcCtsDecryptor> content_decryptor(new AesCbcCtsDecryptor);
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() :

View File

@ -12,7 +12,6 @@
#include <vector>
#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/network_util.h"
#include "packager/media/filters/h264_byte_to_unit_stream_converter.h"
@ -20,6 +19,7 @@
namespace edash_packager {
namespace media {
class AesCbcCtsDecryptor;
class KeySource;
struct EncryptionKey;
@ -248,7 +248,7 @@ class WvmMediaParser : public MediaParser {
std::deque<DemuxStreamIdMediaSample> media_sample_queue_;
std::vector<uint8_t> sample_data_;
KeySource* decryption_key_source_;
AesCbcCtsDecryptor content_decryptor_;
scoped_ptr<AesCbcCtsDecryptor> content_decryptor_;
DISALLOW_COPY_AND_ASSIGN(WvmMediaParser);
};

View File

@ -30,6 +30,7 @@ const int kExpectedVideoFrameCount = 6665;
const int kExpectedAudioFrameCount = 11964;
const uint8_t kExpectedAssetKey[] =
"\x06\x81\x7f\x48\x6b\xf2\x7f\x3e\xc7\x39\xa8\x3f\x12\x0a\xd2\xfc";
const size_t kInitDataSize = 0x4000;
} // namespace
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) {
EXPECT_CALL(*key_source_, FetchKeys(_)).WillOnce(Return(Status::OK));
EXPECT_CALL(*key_source_, GetKey(_, _))