Modify WVM media parser to support encrypted media sample.

Change-Id: I8e696527a09fcec22b6c9713e0d1d3096720ce9c
This commit is contained in:
Ramji Chandramouli 2015-01-12 13:17:10 -08:00 committed by Gerrit Code Review
parent 50c3f3a52e
commit a99af5a015
6 changed files with 111 additions and 75 deletions

View File

@ -19,7 +19,11 @@ MediaSample::MediaSample(const uint8_t* data,
const uint8_t* side_data, const uint8_t* side_data,
size_t side_data_size, size_t side_data_size,
bool is_key_frame) bool is_key_frame)
: dts_(0), pts_(0), duration_(0), is_key_frame_(is_key_frame) { : dts_(0),
pts_(0),
duration_(0),
is_key_frame_(is_key_frame),
is_encrypted_(false) {
if (!data) { if (!data) {
CHECK_EQ(size, 0u); CHECK_EQ(size, 0u);
CHECK(!side_data); CHECK(!side_data);
@ -31,9 +35,11 @@ MediaSample::MediaSample(const uint8_t* data,
side_data_.assign(side_data, side_data + side_data_size); side_data_.assign(side_data, side_data + side_data_size);
} }
MediaSample::MediaSample() : dts_(0), pts_(0), MediaSample::MediaSample() : dts_(0),
pts_(0),
duration_(0), duration_(0),
is_key_frame_(false) {} is_key_frame_(false),
is_encrypted_(false) {}
MediaSample::~MediaSample() {} MediaSample::~MediaSample() {}

View File

@ -83,6 +83,10 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
return is_key_frame_; return is_key_frame_;
} }
bool is_encrypted() const {
DCHECK(!end_of_stream());
return is_encrypted_;
}
const uint8_t* data() const { const uint8_t* data() const {
DCHECK(!end_of_stream()); DCHECK(!end_of_stream());
return &data_[0]; return &data_[0];
@ -116,6 +120,10 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
is_key_frame_ = value; is_key_frame_ = value;
} }
void set_is_encrypted(bool value) {
is_encrypted_ = value;
}
// 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; }
@ -142,6 +150,8 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
int64_t pts_; int64_t pts_;
int64_t duration_; int64_t duration_;
bool is_key_frame_; bool is_key_frame_;
// is sample encrypted ?
bool is_encrypted_;
// Main buffer data. // Main buffer data.
std::vector<uint8_t> data_; std::vector<uint8_t> data_;

View File

@ -91,6 +91,9 @@ Status Muxer::AddSample(const MediaStream* stream,
// to Muxer. In this case, there should be only one stream in Muxer. // to Muxer. In this case, there should be only one stream in Muxer.
DCHECK_EQ(1u, streams_.size()); DCHECK_EQ(1u, streams_.size());
return Finalize(); return Finalize();
} else if (sample->is_encrypted()) {
LOG(ERROR) << "Unable to multiplex encrypted media sample";
return Status(error::INTERNAL_ERROR, "Encrypted media sample.");
} }
return DoAddSample(stream, sample); return DoAddSample(stream, sample);
} }

View File

@ -373,7 +373,7 @@ bool WvmMediaParser::Parse(const uint8_t* buf, int size) {
parse_state_ = IndexPayload; parse_state_ = IndexPayload;
continue; continue;
default: default:
if (!DemuxNextPes(read_ptr, false)) { if (!DemuxNextPes(false)) {
return false; return false;
} }
parse_state_ = EsPayload; parse_state_ = EsPayload;
@ -460,7 +460,7 @@ bool WvmMediaParser::Parse(const uint8_t* buf, int size) {
case ProgramEnd: case ProgramEnd:
parse_state_ = StartCode1; parse_state_ = StartCode1;
metadata_is_complete_ = true; metadata_is_complete_ = true;
if (!DemuxNextPes(read_ptr, true)) { if (!DemuxNextPes(true)) {
return false; return false;
} }
Flush(); Flush();
@ -728,33 +728,34 @@ bool WvmMediaParser::ParseIndexEntry() {
return true; return true;
} }
bool WvmMediaParser::DemuxNextPes(uint8_t* read_ptr, bool is_program_end) { bool WvmMediaParser::DemuxNextPes(bool is_program_end) {
bool output_encrypted_sample = false;
if (!sample_data_.empty() && (prev_pes_flags_1_ & kScramblingBitsMask)) { if (!sample_data_.empty() && (prev_pes_flags_1_ & kScramblingBitsMask)) {
// Decrypt crypto unit. // Decrypt crypto unit.
if (!content_decryptor_) { if (!content_decryptor_) {
LOG(ERROR) << "Source content is encrypted, but decryption not enabled"; output_encrypted_sample = true;
return false; } else {
content_decryptor_->Decrypt(&sample_data_[crypto_unit_start_pos_],
sample_data_.size() - crypto_unit_start_pos_,
&sample_data_[crypto_unit_start_pos_]);
} }
content_decryptor_->Decrypt(&sample_data_[crypto_unit_start_pos_],
sample_data_.size() - crypto_unit_start_pos_,
&sample_data_[crypto_unit_start_pos_]);
} }
// Demux media sample if we are at program end or if we are not at a // Demux media sample if we are at program end or if we are not at a
// continuation PES. // continuation PES.
if ((pes_flags_2_ & kPesOptPts) || is_program_end) { if ((pes_flags_2_ & kPesOptPts) || is_program_end) {
if (!sample_data_.empty()) { if (!sample_data_.empty()) {
if (!Output()) { if (!Output(output_encrypted_sample)) {
return false; return false;
} }
} }
StartMediaSampleDemux(read_ptr); StartMediaSampleDemux();
} }
crypto_unit_start_pos_ = sample_data_.size(); crypto_unit_start_pos_ = sample_data_.size();
return true; return true;
} }
void WvmMediaParser::StartMediaSampleDemux(uint8_t* read_ptr) { void WvmMediaParser::StartMediaSampleDemux() {
bool is_key_frame = ((pes_flags_1_ & kPesOptAlign) != 0); bool is_key_frame = ((pes_flags_1_ & kPesOptAlign) != 0);
media_sample_ = MediaSample::CreateEmptyMediaSample(); media_sample_ = MediaSample::CreateEmptyMediaSample();
media_sample_->set_dts(dts_); media_sample_->set_dts(dts_);
@ -764,64 +765,69 @@ void WvmMediaParser::StartMediaSampleDemux(uint8_t* read_ptr) {
sample_data_.clear(); sample_data_.clear();
} }
bool WvmMediaParser::Output() { bool WvmMediaParser::Output(bool output_encrypted_sample) {
if ((prev_pes_stream_id_ & kPesStreamIdVideoMask) == kPesStreamIdVideo) { if (output_encrypted_sample) {
// Set data on the video stream from the NalUnitStream. media_sample_->set_data(&sample_data_[0], sample_data_.size());
std::vector<uint8_t> nal_unit_stream; media_sample_->set_is_encrypted(true);
if (!byte_to_unit_stream_converter_.ConvertByteStreamToNalUnitStream( } else {
&sample_data_[0], sample_data_.size(), &nal_unit_stream)) { if ((prev_pes_stream_id_ & kPesStreamIdVideoMask) == kPesStreamIdVideo) {
LOG(ERROR) << "Could not convert h.264 byte stream sample"; // Set data on the video stream from the NalUnitStream.
return false; std::vector<uint8_t> nal_unit_stream;
} if (!byte_to_unit_stream_converter_.ConvertByteStreamToNalUnitStream(
media_sample_->set_data(nal_unit_stream.data(), nal_unit_stream.size()); &sample_data_[0], sample_data_.size(), &nal_unit_stream)) {
if (!is_initialized_) { LOG(ERROR) << "Could not convert h.264 byte stream sample";
// Set extra data for video stream from AVC Decoder Config Record. return false;
// Also, set codec string from the AVC Decoder Config Record. }
std::vector<uint8_t> decoder_config_record; media_sample_->set_data(nal_unit_stream.data(), nal_unit_stream.size());
byte_to_unit_stream_converter_.GetAVCDecoderConfigurationRecord( if (!is_initialized_) {
&decoder_config_record); // Set extra data for video stream from AVC Decoder Config Record.
for (uint32_t i = 0; i < stream_infos_.size(); i++) { // Also, set codec string from the AVC Decoder Config Record.
if (stream_infos_[i]->stream_type() == media::kStreamVideo && std::vector<uint8_t> decoder_config_record;
stream_infos_[i]->extra_data().empty()) { byte_to_unit_stream_converter_.GetAVCDecoderConfigurationRecord(
stream_infos_[i]->set_extra_data(decoder_config_record); &decoder_config_record);
stream_infos_[i]->set_codec_string(VideoStreamInfo::GetCodecString( for (uint32_t i = 0; i < stream_infos_.size(); i++) {
kCodecH264, decoder_config_record[1], decoder_config_record[2], if (stream_infos_[i]->stream_type() == media::kStreamVideo &&
decoder_config_record[3])); stream_infos_[i]->extra_data().empty()) {
stream_infos_[i]->set_extra_data(decoder_config_record);
stream_infos_[i]->set_codec_string(VideoStreamInfo::GetCodecString(
kCodecH264, decoder_config_record[1], decoder_config_record[2],
decoder_config_record[3]));
}
} }
} }
} } else if ((prev_pes_stream_id_ & kPesStreamIdAudioMask) ==
} else if ((prev_pes_stream_id_ & kPesStreamIdAudioMask) == kPesStreamIdAudio) {
kPesStreamIdAudio) { // Set data on the audio stream from AdtsHeader.
// Set data on the audio stream from AdtsHeader. int frame_size = media::mp2t::AdtsHeader::GetAdtsFrameSize(
int frame_size = media::mp2t::AdtsHeader::GetAdtsFrameSize( &sample_data_[0], kAdtsHeaderMinSize);
&sample_data_[0], kAdtsHeaderMinSize); media::mp2t::AdtsHeader adts_header;
media::mp2t::AdtsHeader adts_header; const uint8_t* frame_ptr = &sample_data_[0];
const uint8_t* frame_ptr = &sample_data_[0]; std::vector<uint8_t> extra_data;
std::vector<uint8_t> extra_data; if (!adts_header.Parse(frame_ptr, frame_size) ||
if (!adts_header.Parse(frame_ptr, frame_size) || !adts_header.GetAudioSpecificConfig(&extra_data)) {
!adts_header.GetAudioSpecificConfig(&extra_data)) { LOG(ERROR) << "Could not parse ADTS header";
LOG(ERROR) << "Could not parse ADTS header"; return false;
return false; }
} size_t header_size = adts_header.GetAdtsHeaderSize(frame_ptr,
size_t header_size = adts_header.GetAdtsHeaderSize(frame_ptr, frame_size);
frame_size); media_sample_->set_data(frame_ptr + header_size,
media_sample_->set_data(frame_ptr + header_size, frame_size - header_size);
frame_size - header_size); if (!is_initialized_) {
if (!is_initialized_) { for (uint32_t i = 0; i < stream_infos_.size(); i++) {
for (uint32_t i = 0; i < stream_infos_.size(); i++) { if (stream_infos_[i]->stream_type() == media::kStreamAudio &&
if (stream_infos_[i]->stream_type() == media::kStreamAudio && stream_infos_[i]->extra_data().empty()) {
stream_infos_[i]->extra_data().empty()) { // Set AudioStreamInfo fields using information from the ADTS
// Set AudioStreamInfo fields using information from the ADTS // header.
// header. AudioStreamInfo* audio_stream_info =
AudioStreamInfo* audio_stream_info = reinterpret_cast<AudioStreamInfo*>(
reinterpret_cast<AudioStreamInfo*>( stream_infos_[i].get());
stream_infos_[i].get()); audio_stream_info->set_sampling_frequency(
audio_stream_info->set_sampling_frequency( adts_header.GetSamplingFrequency());
adts_header.GetSamplingFrequency()); audio_stream_info->set_extra_data(extra_data);
audio_stream_info->set_extra_data(extra_data); audio_stream_info->set_codec_string(
audio_stream_info->set_codec_string( AudioStreamInfo::GetCodecString(
AudioStreamInfo::GetCodecString( kCodecAAC, adts_header.GetObjectType()));
kCodecAAC, adts_header.GetObjectType())); }
} }
} }
} }

View File

@ -164,9 +164,9 @@ class WvmMediaParser : public MediaParser {
// Index denotes 'search index' in the WVM content. // Index denotes 'search index' in the WVM content.
bool ParseIndexEntry(); bool ParseIndexEntry();
bool DemuxNextPes(uint8_t* start, bool is_program_end); bool DemuxNextPes(bool is_program_end);
void StartMediaSampleDemux(uint8_t* start); void StartMediaSampleDemux();
template <typename T> template <typename T>
Tag GetTag(const uint8_t& tag, Tag GetTag(const uint8_t& tag,
@ -195,7 +195,9 @@ class WvmMediaParser : public MediaParser {
return Tag(tag); return Tag(tag);
} }
bool Output(); // |must_process_encrypted| setting determines if Output() should attempt
// to ouput media sample as encrypted.
bool Output(bool must_process_encrypted);
bool GetAssetKey(const uint32_t asset_id, EncryptionKey* encryption_key); bool GetAssetKey(const uint32_t asset_id, EncryptionKey* encryption_key);

View File

@ -28,6 +28,7 @@ const char kWvmFile[] = "hb2_4stream_encrypted.wvm";
const uint32_t kExpectedStreams = 4; const uint32_t kExpectedStreams = 4;
const int kExpectedVideoFrameCount = 6665; const int kExpectedVideoFrameCount = 6665;
const int kExpectedAudioFrameCount = 11964; const int kExpectedAudioFrameCount = 11964;
const int kExpectedEncryptedSampleCount = 17287;
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 uint8_t k64ByteAssetKey[] = const uint8_t k64ByteAssetKey[] =
@ -66,6 +67,7 @@ class WvmMediaParserTest : public testing::Test {
WvmMediaParserTest() WvmMediaParserTest()
: audio_frame_count_(0), : audio_frame_count_(0),
video_frame_count_(0), video_frame_count_(0),
encrypted_sample_count_(0),
video_max_dts_(kNoTimestamp), video_max_dts_(kNoTimestamp),
current_track_id_(-1) { current_track_id_(-1) {
parser_.reset(new WvmMediaParser()); parser_.reset(new WvmMediaParser());
@ -82,6 +84,7 @@ class WvmMediaParserTest : public testing::Test {
StreamMap stream_map_; StreamMap stream_map_;
int audio_frame_count_; int audio_frame_count_;
int video_frame_count_; int video_frame_count_;
int encrypted_sample_count_;
int64_t video_max_dts_; int64_t video_max_dts_;
uint32_t current_track_id_; uint32_t current_track_id_;
EncryptionKey encryption_key_; EncryptionKey encryption_key_;
@ -128,6 +131,9 @@ class WvmMediaParserTest : public testing::Test {
} }
} }
if (sample->is_encrypted()) {
++encrypted_sample_count_;
}
return true; return true;
} }
@ -149,12 +155,14 @@ class WvmMediaParserTest : public testing::Test {
}; };
TEST_F(WvmMediaParserTest, ParseWvmWithoutKeySource) { TEST_F(WvmMediaParserTest, ParseWvmWithoutKeySource) {
// Parsing should fail but it will get the streams successfully.
key_source_.reset(); key_source_.reset();
InitializeParser(); InitializeParser();
std::vector<uint8_t> buffer = ReadTestDataFile(kWvmFile); std::vector<uint8_t> buffer = ReadTestDataFile(kWvmFile);
EXPECT_FALSE(parser_->Parse(buffer.data(), buffer.size())); EXPECT_TRUE(parser_->Parse(buffer.data(), buffer.size()));
EXPECT_EQ(kExpectedStreams, stream_map_.size()); EXPECT_EQ(kExpectedStreams, stream_map_.size());
EXPECT_EQ(kExpectedVideoFrameCount, video_frame_count_);
EXPECT_EQ(kExpectedAudioFrameCount, audio_frame_count_);
EXPECT_EQ(kExpectedEncryptedSampleCount, encrypted_sample_count_);
} }
TEST_F(WvmMediaParserTest, ParseWvmInitWithoutKeySource) { TEST_F(WvmMediaParserTest, ParseWvmInitWithoutKeySource) {
@ -173,6 +181,7 @@ TEST_F(WvmMediaParserTest, ParseWvm) {
EXPECT_EQ(kExpectedStreams, stream_map_.size()); EXPECT_EQ(kExpectedStreams, stream_map_.size());
EXPECT_EQ(kExpectedVideoFrameCount, video_frame_count_); EXPECT_EQ(kExpectedVideoFrameCount, video_frame_count_);
EXPECT_EQ(kExpectedAudioFrameCount, audio_frame_count_); EXPECT_EQ(kExpectedAudioFrameCount, audio_frame_count_);
EXPECT_EQ(0, encrypted_sample_count_);
} }
TEST_F(WvmMediaParserTest, ParseWvmWith64ByteAssetKey) { TEST_F(WvmMediaParserTest, ParseWvmWith64ByteAssetKey) {