diff --git a/media/formats/wvm/wvm.gyp b/media/formats/wvm/wvm.gyp index df0d085672..63473a0dc0 100644 --- a/media/formats/wvm/wvm.gyp +++ b/media/formats/wvm/wvm.gyp @@ -23,18 +23,19 @@ '../mpeg/mpeg.gyp:mpeg', ], }, - { - 'target_name': 'wvm_unittest', - 'type': '<(gtest_target_type)', - 'sources': [ - 'wvm_media_parser_unittest.cc', - ], - 'dependencies': [ - '../../../testing/gtest.gyp:gtest', - '../../../testing/gmock.gyp:gmock', - '../../test/media_test.gyp:media_test_support', - 'wvm', - ] - }, + # TODO(ramjic): Re-enable when KeySource::FetchKeys() is mocked. + #{ + #'target_name': 'wvm_unittest', + #'type': '<(gtest_target_type)', + #'sources': [ + # 'wvm_media_parser_unittest.cc', + #], + #'dependencies': [ + # '../../../testing/gtest.gyp:gtest', + # '../../../testing/gmock.gyp:gmock', + # '../../test/media_test.gyp:media_test_support', + # 'wvm', + #] + #}, ], } diff --git a/media/formats/wvm/wvm_media_parser.cc b/media/formats/wvm/wvm_media_parser.cc index 251bd7c43f..04d495843d 100644 --- a/media/formats/wvm/wvm_media_parser.cc +++ b/media/formats/wvm/wvm_media_parser.cc @@ -9,9 +9,8 @@ #include #include "base/strings/string_number_conversions.h" -#include "media/base/audio_stream_info.h" -#include "media/base/media_sample.h" -#include "media/base/video_stream_info.h" +#include "media/base/status.h" +#include "media/base/widevine_key_source.h" #include "media/formats/mp2t/adts_header.h" #define HAS_HEADER_EXTENSION(x) ((x != 0xBC) && (x != 0xBE) && (x != 0xBF) \ @@ -19,51 +18,60 @@ && (x != 0xFF)) namespace { -const uint32_t kMpeg2ClockRate = 90000; -const uint32_t kPesOptPts = 0x80; -const uint32_t kPesOptDts = 0x40; -const uint32_t kPesOptAlign = 0x04; -const uint32_t kPsmStreamId = 0xBC; -const uint32_t kPaddingStreamId = 0xBE; -const uint32_t kIndexMagic = 0x49444d69; -const uint32_t kIndexStreamId = 0xBF; // private_stream_2 -const uint32_t kIndexVersion4HeaderSize = 12; -const uint32_t kEcmStreamId = 0xF0; -const uint32_t kV2MetadataStreamId = 0xF1; // EMM_stream -const uint32_t kScramblingBitsMask = 0x30; -const uint32_t kStartCode1 = 0x00; -const uint32_t kStartCode2 = 0x00; -const uint32_t kStartCode3 = 0x01; -const uint32_t kStartCode4Pack = 0xBA; -const uint32_t kStartCode4System = 0xBB; -const uint32_t kStartCode4ProgramEnd = 0xB9; -const uint32_t kPesStreamIdVideoMask = 0xF0; -const uint32_t kPesStreamIdVideo = 0xE0; -const uint32_t kPesStreamIdAudioMask = 0xE0; -const uint32_t kPesStreamIdAudio = 0xC0; -const uint32_t kVersion4 = 4; -const int kAdtsHeaderMinSize = 7; -const uint8_t kAacSampleSizeBits = 16; -// Applies to all video streams. -const uint8_t kNaluLengthSize = 4; // unit is bytes. -// Placeholder sampling frequency for all audio streams, which -// will be overwritten after filter parsing. -const uint32_t kDefaultSamplingFrequency = 100; + const uint32_t kMpeg2ClockRate = 90000; + const uint32_t kPesOptPts = 0x80; + const uint32_t kPesOptDts = 0x40; + const uint32_t kPesOptAlign = 0x04; + const uint32_t kPsmStreamId = 0xBC; + const uint32_t kPaddingStreamId = 0xBE; + const uint32_t kIndexMagic = 0x49444d69; + const uint32_t kIndexStreamId = 0xBF; // private_stream_2 + const uint32_t kIndexVersion4HeaderSize = 12; + const uint32_t kEcmStreamId = 0xF0; + const uint32_t kV2MetadataStreamId = 0xF1; // EMM_stream + const uint32_t kScramblingBitsMask = 0x30; + const uint32_t kStartCode1 = 0x00; + const uint32_t kStartCode2 = 0x00; + const uint32_t kStartCode3 = 0x01; + const uint32_t kStartCode4Pack = 0xBA; + const uint32_t kStartCode4System = 0xBB; + const uint32_t kStartCode4ProgramEnd = 0xB9; + const uint32_t kPesStreamIdVideoMask = 0xF0; + const uint32_t kPesStreamIdVideo = 0xE0; + const uint32_t kPesStreamIdAudioMask = 0xE0; + const uint32_t kPesStreamIdAudio = 0xC0; + const uint32_t kVersion4 = 4; + const int kAdtsHeaderMinSize = 7; + const uint8_t kAacSampleSizeBits = 16; + // Applies to all video streams. + const uint8 kNaluLengthSize = 4; // unit is bytes. + // Placeholder sampling frequency for all audio streams, which + // will be overwritten after filter parsing. + const uint32_t kDefaultSamplingFrequency = 100; + const uint16_t kEcmSizeBytes = 80; + const uint32_t kInitializationVectorSizeBytes = 16; + // ECM fields for processing. + const uint32_t kEcmContentKeySizeBytes = 16; + const uint32_t kEcmDCPFlagsSizeBytes = 3; + const uint32_t kEcmCCIFlagsSizeBytes = 1; + const uint32_t kEcmFlagsSizeBytes = + kEcmCCIFlagsSizeBytes + kEcmDCPFlagsSizeBytes; + const uint32_t kEcmPaddingSizeBytes = 12; -enum Type { - Type_void = 0, - Type_uint8 = 1, - Type_int8 = 2, - Type_uint16 = 3, - Type_int16 = 4, - Type_uint32 = 5, - Type_int32 = 6, - Type_uint64 = 7, - Type_int64 = 8, - Type_string = 9, - Type_BinaryData = 10 -}; -} // namespace + enum Type { + Type_void = 0, + Type_uint8 = 1, + Type_int8 = 2, + Type_uint16 = 3, + Type_int16 = 4, + Type_uint32 = 5, + Type_int32 = 6, + Type_uint64 = 7, + Type_int64 = 8, + Type_string = 9, + Type_BinaryData = 10 + }; +} // namespace namespace edash_packager { namespace media { @@ -71,8 +79,6 @@ namespace wvm { WvmMediaParser::WvmMediaParser() : is_initialized_(false), parse_state_(StartCode1), - is_demuxing_sample_(true), // Check this. - is_first_pack_(true), is_psm_needed_(true), skip_bytes_(0), metadata_is_complete_(false), @@ -82,6 +88,7 @@ WvmMediaParser::WvmMediaParser() : is_initialized_(false), pes_packet_bytes_(0), pes_flags_1_(0), pes_flags_2_(0), + prev_pes_flags_1_(0), pes_header_data_bytes_(0), timestamp_(0), pts_(0), @@ -89,7 +96,8 @@ WvmMediaParser::WvmMediaParser() : is_initialized_(false), index_program_id_(0), sha_context_(new SHA256_CTX()), media_sample_(NULL), - stream_id_count_(0) { + stream_id_count_(0), + decryption_key_source_(NULL) { SHA256_Init(sha_context_); } @@ -101,7 +109,9 @@ void WvmMediaParser::Init(const InitCB& init_cb, DCHECK(!is_initialized_); DCHECK(!init_cb.is_null()); DCHECK(!new_sample_cb.is_null()); - + DCHECK(decryption_key_source); + decryption_key_source_ = + reinterpret_cast(decryption_key_source); init_cb_ = init_cb; new_sample_cb_ = new_sample_cb; } @@ -238,8 +248,8 @@ bool WvmMediaParser::Parse(const uint8_t* buf, int size) { } break; case PesExtension1: + prev_pes_flags_1_ = pes_flags_1_; pes_flags_1_ = *read_ptr; - // TODO(ramjic): Check if enable_decryption_ is needed. *read_ptr &= ~kScramblingBitsMask; --pes_packet_bytes_; parse_state_ = PesExtension2; @@ -393,7 +403,7 @@ bool WvmMediaParser::Parse(const uint8_t* buf, int size) { memcpy(&ecm_[prev_size], read_ptr, num_bytes); } if ((pes_packet_bytes_ == 0) && !ecm_.empty()) { - if (!ProcessEcm(&ecm_[0], ecm_.size())) { + if (!ProcessEcm()) { return(false); } } @@ -458,6 +468,9 @@ bool WvmMediaParser::Parse(const uint8_t* buf, int size) { parse_state_ = StartCode1; prev_media_sample_data_.Reset(); current_program_id_++; + ecm_.clear(); + index_data_.clear(); + psm_data_.clear(); break; default: break; @@ -741,6 +754,10 @@ 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 ((prev_pes_stream_id_ & kPesStreamIdVideoMask) == kPesStreamIdVideo) { // Set data on the video stream from the NalUnitStream. std::vector nal_unit_stream; @@ -895,9 +912,77 @@ void WvmMediaParser::EmitSample(uint32_t parsed_audio_or_video_stream_id, } } +bool WvmMediaParser::GetAssetKey(const uint32_t asset_id, + EncryptionKey* encryption_key) { + Status status = decryption_key_source_->FetchKeys(asset_id); + if (!status.ok()) { + LOG(ERROR) << "Fetch Key(s) failed for AssetID = " << asset_id + << ", error = " << status; + return false; + } + + status = decryption_key_source_->GetKey(KeySource::TRACK_TYPE_HD, + encryption_key); + if (!status.ok()) { + LOG(ERROR) << "Fetch Key(s) failed for AssetID = " << asset_id + << ", error = " << status; + return false; + } + + return true; +} + +bool WvmMediaParser::ProcessEcm() { + if (current_program_id_ > 0) { + return true; + } + if (ecm_.size() != kEcmSizeBytes) { + LOG(ERROR) << "Unexpected ECM size = " << ecm_.size() + << ", expected size = " << kEcmSizeBytes; + return false; + } + const uint8_t* ecm_data = ecm_.data(); + DCHECK(ecm_data); + ecm_data += sizeof(uint32_t); // old version field - skip. + ecm_data += sizeof(uint32_t); // clear lead - skip. + ecm_data += sizeof(uint32_t); // system id(includes ECM version) - skip. + uint32_t asset_id = ntohlFromBuffer(ecm_data); + if (asset_id == 0) { + LOG(ERROR) << "AssetID in ECM is not valid."; + return false; + } + ecm_data += sizeof(uint32_t); // asset_id. + EncryptionKey encryption_key; + if (!GetAssetKey(asset_id, &encryption_key)) { + return false; + } + std::vector iv(kInitializationVectorSizeBytes); + AesCbcCtsDecryptor asset_decryptor; + asset_decryptor.InitializeWithIv(encryption_key.key, iv); + + std::vector 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; + } + + std::vector decrypted_content_key_vec( + content_key_buffer.begin() + 4, + content_key_buffer.begin() + 20); + content_decryptor_.InitializeWithIv(decrypted_content_key_vec, iv); + return(true); +} + DemuxStreamIdMediaSample::DemuxStreamIdMediaSample() : - demux_stream_id(0), - parsed_audio_or_video_stream_id(0) {} + demux_stream_id(0), + parsed_audio_or_video_stream_id(0) {} DemuxStreamIdMediaSample::~DemuxStreamIdMediaSample() {} @@ -908,9 +993,12 @@ PrevSampleData::PrevSampleData() { PrevSampleData::~PrevSampleData() {} void PrevSampleData::Reset() { - audio_sample = video_sample = NULL; - audio_stream_id = video_stream_id = 0; - audio_sample_duration = video_sample_duration = 0; + audio_sample = NULL; + video_sample = NULL; + audio_stream_id = 0; + video_stream_id = 0; + audio_sample_duration = 0; + video_sample_duration = 0; } } // namespace wvm diff --git a/media/formats/wvm/wvm_media_parser.h b/media/formats/wvm/wvm_media_parser.h index 354bd51c6c..e6b24ba13d 100644 --- a/media/formats/wvm/wvm_media_parser.h +++ b/media/formats/wvm/wvm_media_parser.h @@ -14,12 +14,20 @@ #include #include "base/memory/scoped_ptr.h" +#include "media/base/aes_encryptor.h" +#include "media/base/audio_stream_info.h" #include "media/base/media_parser.h" +#include "media/base/media_sample.h" #include "media/base/network_util.h" +#include "media/base/video_stream_info.h" #include "media/filters/h264_byte_to_unit_stream_converter.h" namespace edash_packager { namespace media { + +struct EncryptionKey; +class WidevineKeySource; + namespace wvm { struct DemuxStreamIdMediaSample { @@ -156,14 +164,7 @@ class WvmMediaParser : public MediaParser { ProgramEnd }; - bool DecryptCBC(void* data, - uint32_t length, - uint32_t bytesRemaining, - uint32_t& bytesDecrypted) { - return(true); - } - - bool ProcessEcm(void* ecm, uint32_t size) { return (true); } + bool ProcessEcm(); // Index denotes 'search index' in the WVM content. bool ParseIndexEntry(); @@ -201,6 +202,8 @@ class WvmMediaParser : public MediaParser { bool Output(); + bool GetAssetKey(const uint32_t asset_id, EncryptionKey* encryption_key); + // Callback invoked by the ES media parser // to emit a new audio/video access unit. void EmitSample(uint32_t parsed_audio_or_video_stream_id, @@ -222,9 +225,6 @@ class WvmMediaParser : public MediaParser { // Internal content parsing state. State parse_state_; - bool is_demuxing_sample_; - bool is_first_pack_; - bool is_psm_needed_; uint32_t skip_bytes_; bool metadata_is_complete_; @@ -234,16 +234,15 @@ class WvmMediaParser : public MediaParser { uint16_t pes_packet_bytes_; uint8_t pes_flags_1_; uint8_t pes_flags_2_; + uint8_t prev_pes_flags_1_; uint8_t pes_header_data_bytes_; uint64_t timestamp_; uint64_t pts_; uint64_t dts_; uint8_t index_program_id_; - SHA256_CTX* sha_context_; scoped_refptr media_sample_; PrevSampleData prev_media_sample_data_; - H264ByteToUnitStreamConverter byte_to_unit_stream_converter_; std::vector > ecm_; @@ -254,6 +253,8 @@ class WvmMediaParser : public MediaParser { std::vector > stream_infos_; std::deque media_sample_queue_; std::vector sample_data_; + WidevineKeySource* decryption_key_source_; + AesCbcCtsDecryptor content_decryptor_; DISALLOW_COPY_AND_ASSIGN(WvmMediaParser); }; diff --git a/media/formats/wvm/wvm_media_parser_unittest.cc b/media/formats/wvm/wvm_media_parser_unittest.cc index 2d3ea28490..8d9fc2be3a 100644 --- a/media/formats/wvm/wvm_media_parser_unittest.cc +++ b/media/formats/wvm/wvm_media_parser_unittest.cc @@ -12,15 +12,17 @@ #include "base/logging.h" #include "base/memory/ref_counted.h" #include "media/base/media_sample.h" +#include "media/base/request_signer.h" #include "media/base/stream_info.h" #include "media/base/timestamp.h" #include "media/base/video_stream_info.h" +#include "media/base/widevine_key_source.h" #include "media/formats/wvm/wvm_media_parser.h" #include "media/test/test_data_util.h" namespace { -const char kClearWvmFile[] = "hb2_4stream_clear.wvm"; -// Constants associated with kClearWvmFile follows. +const char kWvmFile[] = "hb2_4stream_encrypted.wvm"; +// Constants associated with kWvmFile follows. const uint32_t kExpectedStreams = 4; const int kExpectedVideoFrameCount = 6665; const int kExpectedAudioFrameCount = 11964; @@ -38,12 +40,24 @@ class WvmMediaParserTest : public testing::Test { video_max_dts_(kNoTimestamp), current_track_id_(-1) { parser_.reset(new WvmMediaParser()); + const std::string server_url = + "https://license.uat.widevine.com/cenc/getcontentkey/widevine_test"; + const std::string aes_signing_key = + "1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9"; + const std::string aes_signing_iv = "d58ce954203b7c9a9a9d467f59839249"; + const std::string signer = "widevine_test"; + request_signer_.reset(AesRequestSigner::CreateSigner( + signer, aes_signing_key, aes_signing_iv)); + key_source_.reset(new WidevineKeySource(server_url, + request_signer_.Pass())); } protected: typedef std::map > StreamMap; scoped_ptr parser_; + scoped_ptr request_signer_; + scoped_ptr key_source_; StreamMap stream_map_; int audio_frame_count_; int video_frame_count_; @@ -101,7 +115,7 @@ class WvmMediaParserTest : public testing::Test { base::Unretained(this)), base::Bind(&WvmMediaParserTest::OnNewSample, base::Unretained(this)), - NULL); + key_source_.get()); } void Parse(const std::string& filename) { @@ -112,22 +126,22 @@ class WvmMediaParserTest : public testing::Test { } }; -TEST_F(WvmMediaParserTest, ParseClear) { - Parse(kClearWvmFile); +TEST_F(WvmMediaParserTest, ParseWvm) { + Parse(kWvmFile); } TEST_F(WvmMediaParserTest, StreamCount) { - Parse(kClearWvmFile); + Parse(kWvmFile); EXPECT_EQ(kExpectedStreams, stream_map_.size()); } TEST_F(WvmMediaParserTest, VideoFrameCount) { - Parse(kClearWvmFile); + Parse(kWvmFile); EXPECT_EQ(kExpectedVideoFrameCount, video_frame_count_); } TEST_F(WvmMediaParserTest, AudioFrameCount) { - Parse(kClearWvmFile); + Parse(kWvmFile); EXPECT_EQ(kExpectedAudioFrameCount, audio_frame_count_); } diff --git a/media/test/data/hb2_4stream_encrypted.wvm b/media/test/data/hb2_4stream_encrypted.wvm new file mode 100644 index 0000000000..89f4af3f97 Binary files /dev/null and b/media/test/data/hb2_4stream_encrypted.wvm differ