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 0989b74c935669a1dc6b2b51a3d8aeee5ed4f9a7..933a5de626f65c0218725fada0a8c6f7469e7662 100644 |
--- a/media/formats/mp2t/mp2t_stream_parser_unittest.cc |
+++ b/media/formats/mp2t/mp2t_stream_parser_unittest.cc |
@@ -15,6 +15,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" |
@@ -25,8 +26,15 @@ |
#include "media/base/test_data_util.h" |
#include "media/base/text_track_config.h" |
#include "media/base/video_decoder_config.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 { |
@@ -51,6 +59,97 @@ 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; |
+ std::unique_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; |
+ EXPECT_TRUE( |
+ LookupTestKeyString(buffer.decrypt_config()->key_id(), false, &key)); |
+ 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 { |
@@ -66,7 +165,10 @@ class Mp2tStreamParserTest : public testing::Test { |
video_min_dts_(kNoDecodeTimestamp()), |
video_max_dts_(kNoDecodeTimestamp()), |
audio_track_id_(0), |
- video_track_id_(0) { |
+ video_track_id_(0), |
+ current_audio_config_(), |
+ current_video_config_(), |
+ capture_buffers(false) { |
bool has_sbr = false; |
parser_.reset(new Mp2tStreamParser(has_sbr)); |
} |
@@ -85,6 +187,12 @@ class Mp2tStreamParserTest : public testing::Test { |
StreamParser::TrackId audio_track_id_; |
StreamParser::TrackId video_track_id_; |
+ 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; |
@@ -131,10 +239,12 @@ class Mp2tStreamParserTest : public testing::Test { |
audio_track_id_ = track_id; |
found_audio_track = true; |
EXPECT_TRUE(tracks->getAudioConfig(track_id).IsValidConfig()); |
+ current_audio_config_ = tracks->getAudioConfig(track_id); |
} else if (track->type() == MediaTrack::Video) { |
video_track_id_ = track_id; |
found_video_track = true; |
EXPECT_TRUE(tracks->getVideoConfig(track_id).IsValidConfig()); |
+ current_video_config_ = tracks->getVideoConfig(track_id); |
} else { |
// Unexpected track type. |
LOG(ERROR) << "Unexpected track type " << track->type(); |
@@ -147,6 +257,18 @@ class Mp2tStreamParserTest : public testing::Test { |
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); |
+ } |
+ } |
+ |
bool OnNewBuffers(const StreamParser::BufferQueueMap& buffer_queue_map) { |
EXPECT_GT(config_count_, 0); |
// Ensure that track ids are properly assigned on all emitted buffers. |
@@ -173,6 +295,11 @@ class Mp2tStreamParserTest : public testing::Test { |
(itr_video == buffer_queue_map.end()) ? empty_buffers |
: itr_video->second; |
+ if (capture_buffers) { |
+ CaptureVideoBuffers(video_buffers); |
+ CaptureAudioBuffers(audio_buffers); |
+ } |
+ |
// Verify monotonicity. |
if (!IsMonotonic(video_buffers)) |
return false; |
@@ -205,8 +332,10 @@ class Mp2tStreamParserTest : public testing::Test { |
void OnKeyNeeded(EmeInitDataType type, |
const std::vector<uint8_t>& init_data) { |
+#if !BUILDFLAG(ENABLE_HLS_SAMPLE_AES) |
LOG(ERROR) << "OnKeyNeeded not expected in the Mpeg2 TS parser"; |
EXPECT_TRUE(false); |
+#endif |
} |
void OnNewSegment() { |
@@ -324,5 +453,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 |