Chromium Code Reviews| Index: media/formats/mp2t/mp2t_stream_parser_unittest.cc |
| diff --git a/media/formats/mp2t/mp2t_stream_parser_unittest.cc b/media/formats/mp2t/mp2t_stream_parser_unittest.cc |
| index 6663a054814262dca5aa63809798656d3f6328db..5188683479ea2af03af5132e4a101bfeba05f66a 100644 |
| --- a/media/formats/mp2t/mp2t_stream_parser_unittest.cc |
| +++ b/media/formats/mp2t/mp2t_stream_parser_unittest.cc |
| @@ -5,11 +5,20 @@ |
| #include <algorithm> |
| #include <string> |
| +#ifdef ENABLE_HLS_SAMPLE_AES |
| +#include <openssl/aes.h> |
|
ddorwin
2015/12/10 20:10:59
The Clear Key implementation supports AES via cryp
dougsteed
2015/12/14 22:51:46
See comment on my change to BUILD.gn.
|
| +#include <openssl/evp.h> |
| +#endif |
| + |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| +#include "base/strings/string_number_conversions.h" |
| #include "base/time/time.h" |
| +#ifdef ENABLE_HLS_SAMPLE_AES |
| +#include "crypto/openssl_util.h" |
| +#endif |
| #include "media/base/audio_decoder_config.h" |
| #include "media/base/decoder_buffer.h" |
| #include "media/base/stream_parser_buffer.h" |
| @@ -43,6 +52,93 @@ bool IsAlmostEqual(DecodeTimestamp t0, DecodeTimestamp t1) { |
| return (diff >= -kMaxDeviation && diff <= kMaxDeviation); |
| } |
| +#ifdef ENABLE_HLS_SAMPLE_AES |
| +class ScopedCipherCTX { |
| + public: |
| + explicit ScopedCipherCTX() { EVP_CIPHER_CTX_init(&ctx_); } |
| + ~ScopedCipherCTX() { |
| + EVP_CIPHER_CTX_cleanup(&ctx_); |
| + crypto::ClearOpenSSLERRStack(FROM_HERE); |
| + } |
| + EVP_CIPHER_CTX* get() { return &ctx_; } |
| + |
| + private: |
| + EVP_CIPHER_CTX ctx_; |
| +}; |
| + |
| +std::string DecryptSampleAES(const std::string& key, |
| + const std::string& iv, |
| + const uint8* input, |
| + int input_size, |
| + bool has_pattern) { |
| + EXPECT_EQ(input_size % 16, 0); |
| + crypto::EnsureOpenSSLInit(); |
| + std::string result; |
| + const EVP_CIPHER* cipher = EVP_aes_128_cbc(); |
| + ScopedCipherCTX ctx; |
| + EXPECT_EQ(EVP_CipherInit_ex(ctx.get(), cipher, NULL, |
| + reinterpret_cast<const uint8*>(key.data()), |
| + reinterpret_cast<const uint8*>(iv.data()), 0), |
| + 1); |
| + EVP_CIPHER_CTX_set_padding(ctx.get(), 0); |
| + const size_t output_size = input_size; |
| + scoped_ptr<char[]> output(new char[output_size]); |
| + uint8* in_ptr = const_cast<uint8*>(input); |
| + uint8* out_ptr = reinterpret_cast<uint8*>(output.get()); |
| + size_t bytes_remaining = output_size; |
| + |
| + while (bytes_remaining) { |
| + int unused; |
| + size_t amount_to_decrypt = has_pattern ? 16UL : bytes_remaining; |
| + EXPECT_EQ(EVP_CipherUpdate(ctx.get(), out_ptr, &unused, in_ptr, |
| + amount_to_decrypt), |
| + 1); |
| + bytes_remaining -= amount_to_decrypt; |
| + if (bytes_remaining) { |
| + out_ptr += amount_to_decrypt; |
| + in_ptr += amount_to_decrypt; |
| + size_t amount_to_skip = 144UL; |
| + if (amount_to_skip > bytes_remaining) |
| + amount_to_skip = bytes_remaining; |
| + memcpy(out_ptr, in_ptr, amount_to_skip); |
| + out_ptr += amount_to_skip; |
| + in_ptr += amount_to_skip; |
| + bytes_remaining -= amount_to_skip; |
| + } |
| + } |
| + |
| + result.assign(output.get(), output_size); |
| + return result; |
| +} |
| + |
| +// We only support AES-CBC at this time. |
| +// For the purpose of these tests, the key id is also used as the actual key. |
| +std::string DecryptBuffer(const StreamParserBuffer& buffer, |
| + const EncryptionScheme& scheme) { |
| + EXPECT_TRUE(scheme.is_encrypted()); |
| + EXPECT_TRUE(scheme.mode() == kCipherModeAesCbc); |
| + bool has_pattern = scheme.pattern().in_effect(); |
| + EXPECT_TRUE(!has_pattern || |
| + scheme.pattern().Matches(EncryptionScheme::PatternSpec(1, 9))); |
| + std::string key = buffer.decrypt_config()->key_id(); |
| + std::string iv = buffer.decrypt_config()->iv(); |
| + EXPECT_EQ(key.size(), 16UL); |
| + EXPECT_EQ(iv.size(), 16UL); |
| + std::string result; |
| + uint8* in_ptr = const_cast<uint8*>(buffer.data()); |
| + const DecryptConfig* decrypt_config = buffer.decrypt_config(); |
| + for (const auto& subsample : decrypt_config->subsamples()) { |
| + std::string clear(reinterpret_cast<char*>(in_ptr), subsample.clear_bytes); |
| + result += clear; |
| + in_ptr += subsample.clear_bytes; |
| + result += |
| + DecryptSampleAES(key, iv, in_ptr, subsample.cypher_bytes, has_pattern); |
| + in_ptr += subsample.cypher_bytes; |
| + } |
| + return result; |
| +} |
| +#endif |
| + |
| } // namespace |
| class Mp2tStreamParserTest : public testing::Test { |
| @@ -56,7 +152,11 @@ class Mp2tStreamParserTest : public testing::Test { |
| audio_min_dts_(kNoDecodeTimestamp()), |
| audio_max_dts_(kNoDecodeTimestamp()), |
| video_min_dts_(kNoDecodeTimestamp()), |
| - video_max_dts_(kNoDecodeTimestamp()) { |
| + video_max_dts_(kNoDecodeTimestamp()), |
| + current_audio_config_(), |
| + current_video_config_(), |
| + audio_buffer_capture_index_(-1), |
| + video_buffer_capture_index_(-1) { |
| bool has_sbr = false; |
| parser_.reset(new Mp2tStreamParser(has_sbr)); |
| } |
| @@ -73,6 +173,13 @@ class Mp2tStreamParserTest : public testing::Test { |
| DecodeTimestamp video_min_dts_; |
| DecodeTimestamp video_max_dts_; |
| + AudioDecoderConfig current_audio_config_; |
| + VideoDecoderConfig current_video_config_; |
| + int audio_buffer_capture_index_; |
| + int video_buffer_capture_index_; |
| + scoped_refptr<StreamParserBuffer> audio_buffer_capture_; |
| + scoped_refptr<StreamParserBuffer> video_buffer_capture_; |
| + |
| void ResetStats() { |
| segment_count_ = 0; |
| config_count_ = 0; |
| @@ -113,10 +220,31 @@ class Mp2tStreamParserTest : public testing::Test { |
| << ", video=" << vc.IsValidConfig(); |
| config_count_++; |
| EXPECT_TRUE(ac.IsValidConfig()); |
| + current_audio_config_ = ac; |
| EXPECT_EQ(vc.IsValidConfig(), has_video_); |
| + current_video_config_ = vc; |
| return true; |
| } |
| + void CaptureVideoBuffer(const StreamParser::BufferQueue& video_buffers) { |
| + for (const auto& buffer : video_buffers) { |
| + if (video_buffer_capture_index_ < 0) |
| + return; |
| + else if (video_buffer_capture_index_ == 0) |
| + video_buffer_capture_ = buffer; |
| + video_buffer_capture_index_--; |
| + } |
| + } |
| + |
| + void CaptureAudioBuffer(const StreamParser::BufferQueue& audio_buffers) { |
| + for (const auto& buffer : audio_buffers) { |
| + if (audio_buffer_capture_index_ < 0) |
| + return; |
| + else if (audio_buffer_capture_index_ == 0) |
| + audio_buffer_capture_ = buffer; |
| + audio_buffer_capture_index_--; |
| + } |
| + } |
| void DumpBuffers(const std::string& label, |
| const StreamParser::BufferQueue& buffers) { |
| @@ -135,6 +263,8 @@ class Mp2tStreamParserTest : public testing::Test { |
| EXPECT_GT(config_count_, 0); |
| DumpBuffers("audio_buffers", audio_buffers); |
| DumpBuffers("video_buffers", video_buffers); |
| + CaptureVideoBuffer(video_buffers); |
| + CaptureAudioBuffer(audio_buffers); |
| // TODO(wolenetz/acolwell): Add text track support to more MSE parsers. See |
| // http://crbug.com/336926. |
| @@ -172,7 +302,6 @@ class Mp2tStreamParserTest : public testing::Test { |
| } |
| void OnKeyNeeded(EmeInitDataType type, const std::vector<uint8>& init_data) { |
| - NOTREACHED() << "OnKeyNeeded not expected in the Mpeg2 TS parser"; |
| } |
| void OnNewSegment() { |
| @@ -289,5 +418,46 @@ TEST_F(Mp2tStreamParserTest, AudioInPrivateStream1) { |
| EXPECT_EQ(segment_count_, 1); |
| } |
| +#ifdef ENABLE_HLS_SAMPLE_AES |
| +TEST_F(Mp2tStreamParserTest, HLSSampleAES) { |
| + InitializeParser(); |
| + audio_buffer_capture_index_ = 0; |
| + video_buffer_capture_index_ = 0; |
| + ParseMpeg2TsFile("main.ts", 2048); |
| + parser_->Flush(); |
| + EncryptionScheme video_encryption_scheme = |
| + current_video_config_.encryption_scheme(); |
| + EXPECT_TRUE(video_encryption_scheme.is_encrypted()); |
| + std::string decrypted_video_buffer = |
| + DecryptBuffer(*video_buffer_capture_.get(), video_encryption_scheme); |
| + EncryptionScheme audio_encryption_scheme = |
| + current_audio_config_.encryption_scheme(); |
| + EXPECT_TRUE(audio_encryption_scheme.is_encrypted()); |
| + std::string decrypted_audio_buffer = |
| + DecryptBuffer(*audio_buffer_capture_.get(), audio_encryption_scheme); |
| + |
| + parser_.reset(new Mp2tStreamParser(false)); |
| + ResetStats(); |
| + InitializeParser(); |
| + audio_buffer_capture_index_ = 0; |
| + video_buffer_capture_index_ = 0; |
| + ParseMpeg2TsFile("unencrypted.ts", 2048); |
| + parser_->Flush(); |
| + video_encryption_scheme = current_video_config_.encryption_scheme(); |
| + EXPECT_FALSE(video_encryption_scheme.is_encrypted()); |
| + std::string unencrypted_video_buffer( |
| + reinterpret_cast<const char*>(video_buffer_capture_->data()), |
| + video_buffer_capture_->data_size()); |
| + audio_encryption_scheme = current_audio_config_.encryption_scheme(); |
| + EXPECT_FALSE(audio_encryption_scheme.is_encrypted()); |
| + std::string unencrypted_audio_buffer( |
| + reinterpret_cast<const char*>(audio_buffer_capture_->data()), |
| + audio_buffer_capture_->data_size()); |
| + |
| + EXPECT_EQ(decrypted_video_buffer, unencrypted_video_buffer); |
| + EXPECT_EQ(decrypted_audio_buffer, unencrypted_audio_buffer); |
| +} |
| +#endif |
| + |
| } // namespace mp2t |
| } // namespace media |