| 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 553f0c46ba37e9a2df04e8c1ea5f20bc6a4bc941..a3b3d8fc1cd8128e13f5edf3ed5abe247f50b9cd 100644
|
| --- a/media/formats/mp2t/mp2t_stream_parser_unittest.cc
|
| +++ b/media/formats/mp2t/mp2t_stream_parser_unittest.cc
|
| @@ -12,6 +12,7 @@
|
| #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"
|
| #include "media/base/audio_decoder_config.h"
|
| #include "media/base/decoder_buffer.h"
|
| @@ -22,8 +23,15 @@
|
| #include "media/base/text_track_config.h"
|
| #include "media/base/video_decoder_config.h"
|
| #include "media/formats/mp2t/mp2t_stream_parser.h"
|
| +#include "media/media_features.h"
|
| #include "testing/gtest/include/gtest/gtest.h"
|
|
|
| +#if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
|
| +#include <openssl/aes.h>
|
| +#include <openssl/evp.h>
|
| +#include "crypto/openssl_util.h"
|
| +#endif
|
| +
|
| namespace media {
|
| namespace mp2t {
|
|
|
| @@ -48,6 +56,94 @@ bool IsAlmostEqual(DecodeTimestamp t0, DecodeTimestamp t1) {
|
| return (diff >= -kMaxDeviation && diff <= kMaxDeviation);
|
| }
|
|
|
| +#if BUILDFLAG(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_t* input,
|
| + int input_size,
|
| + bool has_pattern) {
|
| + DCHECK(input);
|
| + 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_t*>(key.data()),
|
| + reinterpret_cast<const uint8_t*>(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_t* in_ptr = const_cast<uint8_t*>(input);
|
| + uint8_t* out_ptr = reinterpret_cast<uint8_t*>(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() == EncryptionScheme::CIPHER_MODE_AES_CBC);
|
| + bool has_pattern = scheme.pattern().IsInEffect();
|
| + EXPECT_TRUE(!has_pattern ||
|
| + scheme.pattern().Matches(EncryptionScheme::Pattern(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_t* in_ptr = const_cast<uint8_t*>(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 {
|
| @@ -61,7 +157,10 @@ 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_(),
|
| + capture_buffers(false) {
|
| bool has_sbr = false;
|
| parser_.reset(new Mp2tStreamParser(has_sbr));
|
| }
|
| @@ -78,6 +177,12 @@ class Mp2tStreamParserTest : public testing::Test {
|
| DecodeTimestamp video_min_dts_;
|
| DecodeTimestamp video_max_dts_;
|
|
|
| + AudioDecoderConfig current_audio_config_;
|
| + VideoDecoderConfig current_video_config_;
|
| + std::vector<scoped_refptr<StreamParserBuffer>> audio_buffer_capture_;
|
| + std::vector<scoped_refptr<StreamParserBuffer>> video_buffer_capture_;
|
| + bool capture_buffers;
|
| +
|
| void ResetStats() {
|
| segment_count_ = 0;
|
| config_count_ = 0;
|
| @@ -122,10 +227,23 @@ 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 CaptureVideoBuffers(const StreamParser::BufferQueue& video_buffers) {
|
| + for (const auto& buffer : video_buffers) {
|
| + video_buffer_capture_.push_back(buffer);
|
| + }
|
| + }
|
| +
|
| + void CaptureAudioBuffers(const StreamParser::BufferQueue& audio_buffers) {
|
| + for (const auto& buffer : audio_buffers) {
|
| + audio_buffer_capture_.push_back(buffer);
|
| + }
|
| + }
|
|
|
| void DumpBuffers(const std::string& label,
|
| const StreamParser::BufferQueue& buffers) {
|
| @@ -144,6 +262,10 @@ class Mp2tStreamParserTest : public testing::Test {
|
| EXPECT_GT(config_count_, 0);
|
| DumpBuffers("audio_buffers", audio_buffers);
|
| DumpBuffers("video_buffers", video_buffers);
|
| + if (capture_buffers) {
|
| + CaptureVideoBuffers(video_buffers);
|
| + CaptureAudioBuffers(audio_buffers);
|
| + }
|
|
|
| // TODO(wolenetz/acolwell): Add text track support to more MSE parsers. See
|
| // http://crbug.com/336926.
|
| @@ -182,7 +304,6 @@ class Mp2tStreamParserTest : public testing::Test {
|
|
|
| void OnKeyNeeded(EmeInitDataType type,
|
| const std::vector<uint8_t>& init_data) {
|
| - NOTREACHED() << "OnKeyNeeded not expected in the Mpeg2 TS parser";
|
| }
|
|
|
| void OnNewSegment() {
|
| @@ -299,5 +420,57 @@ TEST_F(Mp2tStreamParserTest, AudioInPrivateStream1) {
|
| EXPECT_EQ(segment_count_, 1);
|
| }
|
|
|
| +#if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
|
| +TEST_F(Mp2tStreamParserTest, HLSSampleAES) {
|
| + std::vector<std::string> decrypted_video_buffers;
|
| + std::vector<std::string> decrypted_audio_buffers;
|
| + InitializeParser();
|
| + capture_buffers = true;
|
| + ParseMpeg2TsFile("bear-1280x720-hls-sample-aes.ts", 2048);
|
| + parser_->Flush();
|
| + EncryptionScheme video_encryption_scheme =
|
| + current_video_config_.encryption_scheme();
|
| + EXPECT_TRUE(video_encryption_scheme.is_encrypted());
|
| + for (const auto& buffer : video_buffer_capture_) {
|
| + std::string decrypted_video_buffer =
|
| + DecryptBuffer(*buffer.get(), video_encryption_scheme);
|
| + decrypted_video_buffers.push_back(decrypted_video_buffer);
|
| + }
|
| + EncryptionScheme audio_encryption_scheme =
|
| + current_audio_config_.encryption_scheme();
|
| + EXPECT_TRUE(audio_encryption_scheme.is_encrypted());
|
| + for (const auto& buffer : audio_buffer_capture_) {
|
| + std::string decrypted_audio_buffer =
|
| + DecryptBuffer(*buffer.get(), audio_encryption_scheme);
|
| + decrypted_audio_buffers.push_back(decrypted_audio_buffer);
|
| + }
|
| +
|
| + parser_.reset(new Mp2tStreamParser(false));
|
| + ResetStats();
|
| + InitializeParser();
|
| + video_buffer_capture_.clear();
|
| + audio_buffer_capture_.clear();
|
| + ParseMpeg2TsFile("bear-1280x720-hls.ts", 2048);
|
| + parser_->Flush();
|
| + video_encryption_scheme = current_video_config_.encryption_scheme();
|
| + EXPECT_FALSE(video_encryption_scheme.is_encrypted());
|
| + // Skip the last buffer, which may be truncated.
|
| + for (size_t i = 0; i + 1 < video_buffer_capture_.size(); i++) {
|
| + const auto& buffer = video_buffer_capture_[i];
|
| + std::string unencrypted_video_buffer(
|
| + reinterpret_cast<const char*>(buffer->data()), buffer->data_size());
|
| + EXPECT_EQ(decrypted_video_buffers[i], unencrypted_video_buffer);
|
| + }
|
| + audio_encryption_scheme = current_audio_config_.encryption_scheme();
|
| + EXPECT_FALSE(audio_encryption_scheme.is_encrypted());
|
| + for (size_t i = 0; i + 1 < audio_buffer_capture_.size(); i++) {
|
| + const auto& buffer = audio_buffer_capture_[i];
|
| + std::string unencrypted_audio_buffer(
|
| + reinterpret_cast<const char*>(buffer->data()), buffer->data_size());
|
| + EXPECT_EQ(decrypted_audio_buffers[i], unencrypted_audio_buffer);
|
| + }
|
| +}
|
| +#endif
|
| +
|
| } // namespace mp2t
|
| } // namespace media
|
|
|