Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(178)

Side by Side Diff: media/formats/mp2t/mp2t_stream_parser_unittest.cc

Issue 1517473002: Support HLS MPEG2 TS with SAMPLE-AES encryption. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@encryption_scheme
Patch Set: move some gn defs Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <stddef.h> 5 #include <stddef.h>
6 #include <stdint.h> 6 #include <stdint.h>
7 7
8 #include <algorithm> 8 #include <algorithm>
9 #include <string> 9 #include <string>
10 10
11 #include "base/bind.h" 11 #include "base/bind.h"
12 #include "base/bind_helpers.h" 12 #include "base/bind_helpers.h"
13 #include "base/logging.h" 13 #include "base/logging.h"
14 #include "base/memory/ref_counted.h" 14 #include "base/memory/ref_counted.h"
15 #include "base/strings/string_number_conversions.h"
15 #include "base/time/time.h" 16 #include "base/time/time.h"
16 #include "media/base/audio_decoder_config.h" 17 #include "media/base/audio_decoder_config.h"
17 #include "media/base/decoder_buffer.h" 18 #include "media/base/decoder_buffer.h"
18 #include "media/base/media_track.h" 19 #include "media/base/media_track.h"
19 #include "media/base/media_tracks.h" 20 #include "media/base/media_tracks.h"
20 #include "media/base/stream_parser_buffer.h" 21 #include "media/base/stream_parser_buffer.h"
21 #include "media/base/test_data_util.h" 22 #include "media/base/test_data_util.h"
22 #include "media/base/text_track_config.h" 23 #include "media/base/text_track_config.h"
23 #include "media/base/video_decoder_config.h" 24 #include "media/base/video_decoder_config.h"
24 #include "media/formats/mp2t/mp2t_stream_parser.h" 25 #include "media/formats/mp2t/mp2t_stream_parser.h"
26 #include "media/media_features.h"
25 #include "testing/gtest/include/gtest/gtest.h" 27 #include "testing/gtest/include/gtest/gtest.h"
26 28
29 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
30 #include <openssl/aes.h>
31 #include <openssl/evp.h>
32 #include "crypto/openssl_util.h"
33 #endif
34
27 namespace media { 35 namespace media {
28 namespace mp2t { 36 namespace mp2t {
29 37
30 namespace { 38 namespace {
31 39
32 bool IsMonotonic(const StreamParser::BufferQueue& buffers) { 40 bool IsMonotonic(const StreamParser::BufferQueue& buffers) {
33 if (buffers.empty()) 41 if (buffers.empty())
34 return true; 42 return true;
35 43
36 StreamParser::BufferQueue::const_iterator it1 = buffers.begin(); 44 StreamParser::BufferQueue::const_iterator it1 = buffers.begin();
37 StreamParser::BufferQueue::const_iterator it2 = ++it1; 45 StreamParser::BufferQueue::const_iterator it2 = ++it1;
38 for ( ; it2 != buffers.end(); ++it1, ++it2) { 46 for ( ; it2 != buffers.end(); ++it1, ++it2) {
39 if ((*it2)->GetDecodeTimestamp() < (*it1)->GetDecodeTimestamp()) 47 if ((*it2)->GetDecodeTimestamp() < (*it1)->GetDecodeTimestamp())
40 return false; 48 return false;
41 } 49 }
42 return true; 50 return true;
43 } 51 }
44 52
45 bool IsAlmostEqual(DecodeTimestamp t0, DecodeTimestamp t1) { 53 bool IsAlmostEqual(DecodeTimestamp t0, DecodeTimestamp t1) {
46 base::TimeDelta kMaxDeviation = base::TimeDelta::FromMilliseconds(5); 54 base::TimeDelta kMaxDeviation = base::TimeDelta::FromMilliseconds(5);
47 base::TimeDelta diff = t1 - t0; 55 base::TimeDelta diff = t1 - t0;
48 return (diff >= -kMaxDeviation && diff <= kMaxDeviation); 56 return (diff >= -kMaxDeviation && diff <= kMaxDeviation);
49 } 57 }
50 58
59 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
60 class ScopedCipherCTX {
61 public:
62 explicit ScopedCipherCTX() { EVP_CIPHER_CTX_init(&ctx_); }
63 ~ScopedCipherCTX() {
64 EVP_CIPHER_CTX_cleanup(&ctx_);
65 crypto::ClearOpenSSLERRStack(FROM_HERE);
66 }
67 EVP_CIPHER_CTX* get() { return &ctx_; }
68
69 private:
70 EVP_CIPHER_CTX ctx_;
71 };
72
73 std::string DecryptSampleAES(const std::string& key,
74 const std::string& iv,
75 const uint8_t* input,
76 int input_size,
77 bool has_pattern) {
78 DCHECK(input);
79 EXPECT_EQ(input_size % 16, 0);
80 crypto::EnsureOpenSSLInit();
81 std::string result;
82 const EVP_CIPHER* cipher = EVP_aes_128_cbc();
83 ScopedCipherCTX ctx;
84 EXPECT_EQ(EVP_CipherInit_ex(ctx.get(), cipher, NULL,
85 reinterpret_cast<const uint8_t*>(key.data()),
86 reinterpret_cast<const uint8_t*>(iv.data()), 0),
87 1);
88 EVP_CIPHER_CTX_set_padding(ctx.get(), 0);
89 const size_t output_size = input_size;
90 scoped_ptr<char[]> output(new char[output_size]);
91 uint8_t* in_ptr = const_cast<uint8_t*>(input);
92 uint8_t* out_ptr = reinterpret_cast<uint8_t*>(output.get());
93 size_t bytes_remaining = output_size;
94
95 while (bytes_remaining) {
96 int unused;
97 size_t amount_to_decrypt = has_pattern ? 16UL : bytes_remaining;
98 EXPECT_EQ(EVP_CipherUpdate(ctx.get(), out_ptr, &unused, in_ptr,
99 amount_to_decrypt),
100 1);
101 bytes_remaining -= amount_to_decrypt;
102 if (bytes_remaining) {
103 out_ptr += amount_to_decrypt;
104 in_ptr += amount_to_decrypt;
105 size_t amount_to_skip = 144UL;
106 if (amount_to_skip > bytes_remaining)
107 amount_to_skip = bytes_remaining;
108 memcpy(out_ptr, in_ptr, amount_to_skip);
109 out_ptr += amount_to_skip;
110 in_ptr += amount_to_skip;
111 bytes_remaining -= amount_to_skip;
112 }
113 }
114
115 result.assign(output.get(), output_size);
116 return result;
117 }
118
119 // We only support AES-CBC at this time.
120 // For the purpose of these tests, the key id is also used as the actual key.
121 std::string DecryptBuffer(const StreamParserBuffer& buffer,
122 const EncryptionScheme& scheme) {
123 EXPECT_TRUE(scheme.is_encrypted());
124 EXPECT_TRUE(scheme.mode() == EncryptionScheme::CIPHER_MODE_AES_CBC);
125 bool has_pattern = scheme.pattern().IsInEffect();
126 EXPECT_TRUE(!has_pattern ||
127 scheme.pattern().Matches(EncryptionScheme::Pattern(1, 9)));
128
129 std::string key;
130 EXPECT_TRUE(LookupTestKey(buffer.decrypt_config()->key_id(), &key, false));
131 std::string iv = buffer.decrypt_config()->iv();
132 EXPECT_EQ(key.size(), 16UL);
133 EXPECT_EQ(iv.size(), 16UL);
134 std::string result;
135 uint8_t* in_ptr = const_cast<uint8_t*>(buffer.data());
136 const DecryptConfig* decrypt_config = buffer.decrypt_config();
137 for (const auto& subsample : decrypt_config->subsamples()) {
138 std::string clear(reinterpret_cast<char*>(in_ptr), subsample.clear_bytes);
139 result += clear;
140 in_ptr += subsample.clear_bytes;
141 result +=
142 DecryptSampleAES(key, iv, in_ptr, subsample.cypher_bytes, has_pattern);
143 in_ptr += subsample.cypher_bytes;
144 }
145 return result;
146 }
147 #endif
148
51 } // namespace 149 } // namespace
52 150
53 class Mp2tStreamParserTest : public testing::Test { 151 class Mp2tStreamParserTest : public testing::Test {
54 public: 152 public:
55 Mp2tStreamParserTest() 153 Mp2tStreamParserTest()
56 : segment_count_(0), 154 : segment_count_(0),
57 config_count_(0), 155 config_count_(0),
58 audio_frame_count_(0), 156 audio_frame_count_(0),
59 video_frame_count_(0), 157 video_frame_count_(0),
60 has_video_(true), 158 has_video_(true),
61 audio_min_dts_(kNoDecodeTimestamp()), 159 audio_min_dts_(kNoDecodeTimestamp()),
62 audio_max_dts_(kNoDecodeTimestamp()), 160 audio_max_dts_(kNoDecodeTimestamp()),
63 video_min_dts_(kNoDecodeTimestamp()), 161 video_min_dts_(kNoDecodeTimestamp()),
64 video_max_dts_(kNoDecodeTimestamp()) { 162 video_max_dts_(kNoDecodeTimestamp()),
163 current_audio_config_(),
164 current_video_config_(),
165 capture_buffers(false) {
65 bool has_sbr = false; 166 bool has_sbr = false;
66 parser_.reset(new Mp2tStreamParser(has_sbr)); 167 parser_.reset(new Mp2tStreamParser(has_sbr));
67 } 168 }
68 169
69 protected: 170 protected:
70 scoped_ptr<Mp2tStreamParser> parser_; 171 scoped_ptr<Mp2tStreamParser> parser_;
71 int segment_count_; 172 int segment_count_;
72 int config_count_; 173 int config_count_;
73 int audio_frame_count_; 174 int audio_frame_count_;
74 int video_frame_count_; 175 int video_frame_count_;
75 bool has_video_; 176 bool has_video_;
76 DecodeTimestamp audio_min_dts_; 177 DecodeTimestamp audio_min_dts_;
77 DecodeTimestamp audio_max_dts_; 178 DecodeTimestamp audio_max_dts_;
78 DecodeTimestamp video_min_dts_; 179 DecodeTimestamp video_min_dts_;
79 DecodeTimestamp video_max_dts_; 180 DecodeTimestamp video_max_dts_;
80 181
182 AudioDecoderConfig current_audio_config_;
183 VideoDecoderConfig current_video_config_;
184 std::vector<scoped_refptr<StreamParserBuffer>> audio_buffer_capture_;
185 std::vector<scoped_refptr<StreamParserBuffer>> video_buffer_capture_;
186 bool capture_buffers;
187
81 void ResetStats() { 188 void ResetStats() {
82 segment_count_ = 0; 189 segment_count_ = 0;
83 config_count_ = 0; 190 config_count_ = 0;
84 audio_frame_count_ = 0; 191 audio_frame_count_ = 0;
85 video_frame_count_ = 0; 192 video_frame_count_ = 0;
86 audio_min_dts_ = kNoDecodeTimestamp(); 193 audio_min_dts_ = kNoDecodeTimestamp();
87 audio_max_dts_ = kNoDecodeTimestamp(); 194 audio_max_dts_ = kNoDecodeTimestamp();
88 video_min_dts_ = kNoDecodeTimestamp(); 195 video_min_dts_ = kNoDecodeTimestamp();
89 video_max_dts_ = kNoDecodeTimestamp(); 196 video_max_dts_ = kNoDecodeTimestamp();
90 } 197 }
(...skipping 24 matching lines...) Expand all
115 222
116 bool OnNewConfig(scoped_ptr<MediaTracks> tracks, 223 bool OnNewConfig(scoped_ptr<MediaTracks> tracks,
117 const StreamParser::TextTrackConfigMap& tc) { 224 const StreamParser::TextTrackConfigMap& tc) {
118 const AudioDecoderConfig& ac = tracks->getFirstAudioConfig(); 225 const AudioDecoderConfig& ac = tracks->getFirstAudioConfig();
119 const VideoDecoderConfig& vc = tracks->getFirstVideoConfig(); 226 const VideoDecoderConfig& vc = tracks->getFirstVideoConfig();
120 DVLOG(1) << "OnNewConfig: media tracks count=" << tracks->tracks().size() 227 DVLOG(1) << "OnNewConfig: media tracks count=" << tracks->tracks().size()
121 << ", audio=" << ac.IsValidConfig() 228 << ", audio=" << ac.IsValidConfig()
122 << ", video=" << vc.IsValidConfig(); 229 << ", video=" << vc.IsValidConfig();
123 config_count_++; 230 config_count_++;
124 EXPECT_TRUE(ac.IsValidConfig()); 231 EXPECT_TRUE(ac.IsValidConfig());
232 current_audio_config_ = ac;
125 EXPECT_EQ(vc.IsValidConfig(), has_video_); 233 EXPECT_EQ(vc.IsValidConfig(), has_video_);
234 current_video_config_ = vc;
126 return true; 235 return true;
127 } 236 }
128 237
238 void CaptureVideoBuffers(const StreamParser::BufferQueue& video_buffers) {
239 for (const auto& buffer : video_buffers) {
240 video_buffer_capture_.push_back(buffer);
241 }
242 }
243
244 void CaptureAudioBuffers(const StreamParser::BufferQueue& audio_buffers) {
245 for (const auto& buffer : audio_buffers) {
246 audio_buffer_capture_.push_back(buffer);
247 }
248 }
129 249
130 void DumpBuffers(const std::string& label, 250 void DumpBuffers(const std::string& label,
131 const StreamParser::BufferQueue& buffers) { 251 const StreamParser::BufferQueue& buffers) {
132 DVLOG(2) << "DumpBuffers: " << label << " size " << buffers.size(); 252 DVLOG(2) << "DumpBuffers: " << label << " size " << buffers.size();
133 for (StreamParser::BufferQueue::const_iterator buf = buffers.begin(); 253 for (StreamParser::BufferQueue::const_iterator buf = buffers.begin();
134 buf != buffers.end(); buf++) { 254 buf != buffers.end(); buf++) {
135 DVLOG(3) << " n=" << buf - buffers.begin() 255 DVLOG(3) << " n=" << buf - buffers.begin()
136 << ", size=" << (*buf)->data_size() 256 << ", size=" << (*buf)->data_size()
137 << ", dur=" << (*buf)->duration().InMilliseconds(); 257 << ", dur=" << (*buf)->duration().InMilliseconds();
138 } 258 }
139 } 259 }
140 260
141 bool OnNewBuffers(const StreamParser::BufferQueue& audio_buffers, 261 bool OnNewBuffers(const StreamParser::BufferQueue& audio_buffers,
142 const StreamParser::BufferQueue& video_buffers, 262 const StreamParser::BufferQueue& video_buffers,
143 const StreamParser::TextBufferQueueMap& text_map) { 263 const StreamParser::TextBufferQueueMap& text_map) {
144 EXPECT_GT(config_count_, 0); 264 EXPECT_GT(config_count_, 0);
145 DumpBuffers("audio_buffers", audio_buffers); 265 DumpBuffers("audio_buffers", audio_buffers);
146 DumpBuffers("video_buffers", video_buffers); 266 DumpBuffers("video_buffers", video_buffers);
267 if (capture_buffers) {
268 CaptureVideoBuffers(video_buffers);
269 CaptureAudioBuffers(audio_buffers);
270 }
147 271
148 // TODO(wolenetz/acolwell): Add text track support to more MSE parsers. See 272 // TODO(wolenetz/acolwell): Add text track support to more MSE parsers. See
149 // http://crbug.com/336926. 273 // http://crbug.com/336926.
150 if (!text_map.empty()) 274 if (!text_map.empty())
151 return false; 275 return false;
152 276
153 // Verify monotonicity. 277 // Verify monotonicity.
154 if (!IsMonotonic(video_buffers)) 278 if (!IsMonotonic(video_buffers))
155 return false; 279 return false;
156 if (!IsMonotonic(audio_buffers)) 280 if (!IsMonotonic(audio_buffers))
(...skipping 17 matching lines...) Expand all
174 audio_min_dts_ = first_dts; 298 audio_min_dts_ = first_dts;
175 audio_max_dts_ = last_dts; 299 audio_max_dts_ = last_dts;
176 } 300 }
177 301
178 audio_frame_count_ += audio_buffers.size(); 302 audio_frame_count_ += audio_buffers.size();
179 video_frame_count_ += video_buffers.size(); 303 video_frame_count_ += video_buffers.size();
180 return true; 304 return true;
181 } 305 }
182 306
183 void OnKeyNeeded(EmeInitDataType type, 307 void OnKeyNeeded(EmeInitDataType type,
184 const std::vector<uint8_t>& init_data) { 308 const std::vector<uint8_t>& init_data) {
ddorwin 2016/04/12 00:40:48 Should the NOTREACHED be #if !BUILDFLAG() instead
dougsteed 2016/05/08 23:18:44 Done.
185 NOTREACHED() << "OnKeyNeeded not expected in the Mpeg2 TS parser";
186 } 309 }
187 310
188 void OnNewSegment() { 311 void OnNewSegment() {
189 DVLOG(1) << "OnNewSegment"; 312 DVLOG(1) << "OnNewSegment";
190 segment_count_++; 313 segment_count_++;
191 } 314 }
192 315
193 void OnEndOfSegment() { 316 void OnEndOfSegment() {
194 NOTREACHED() << "OnEndOfSegment not expected in the Mpeg2 TS parser"; 317 NOTREACHED() << "OnEndOfSegment not expected in the Mpeg2 TS parser";
195 } 318 }
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
292 has_video_ = false; 415 has_video_ = false;
293 ParseMpeg2TsFile("bear_adts_in_private_stream_1.ts", 512); 416 ParseMpeg2TsFile("bear_adts_in_private_stream_1.ts", 512);
294 parser_->Flush(); 417 parser_->Flush();
295 EXPECT_EQ(audio_frame_count_, 40); 418 EXPECT_EQ(audio_frame_count_, 40);
296 EXPECT_EQ(video_frame_count_, 0); 419 EXPECT_EQ(video_frame_count_, 0);
297 // This stream has no mid-stream configuration change. 420 // This stream has no mid-stream configuration change.
298 EXPECT_EQ(config_count_, 1); 421 EXPECT_EQ(config_count_, 1);
299 EXPECT_EQ(segment_count_, 1); 422 EXPECT_EQ(segment_count_, 1);
300 } 423 }
301 424
425 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
426 TEST_F(Mp2tStreamParserTest, HLSSampleAES) {
427 std::vector<std::string> decrypted_video_buffers;
428 std::vector<std::string> decrypted_audio_buffers;
429 InitializeParser();
430 capture_buffers = true;
431 ParseMpeg2TsFile("bear-1280x720-hls-sample-aes.ts", 2048);
432 parser_->Flush();
433 EncryptionScheme video_encryption_scheme =
434 current_video_config_.encryption_scheme();
435 EXPECT_TRUE(video_encryption_scheme.is_encrypted());
436 for (const auto& buffer : video_buffer_capture_) {
437 std::string decrypted_video_buffer =
438 DecryptBuffer(*buffer.get(), video_encryption_scheme);
439 decrypted_video_buffers.push_back(decrypted_video_buffer);
440 }
441 EncryptionScheme audio_encryption_scheme =
442 current_audio_config_.encryption_scheme();
443 EXPECT_TRUE(audio_encryption_scheme.is_encrypted());
444 for (const auto& buffer : audio_buffer_capture_) {
445 std::string decrypted_audio_buffer =
446 DecryptBuffer(*buffer.get(), audio_encryption_scheme);
447 decrypted_audio_buffers.push_back(decrypted_audio_buffer);
448 }
449
450 parser_.reset(new Mp2tStreamParser(false));
451 ResetStats();
452 InitializeParser();
453 video_buffer_capture_.clear();
454 audio_buffer_capture_.clear();
455 ParseMpeg2TsFile("bear-1280x720-hls.ts", 2048);
456 parser_->Flush();
457 video_encryption_scheme = current_video_config_.encryption_scheme();
458 EXPECT_FALSE(video_encryption_scheme.is_encrypted());
459 // Skip the last buffer, which may be truncated.
460 for (size_t i = 0; i + 1 < video_buffer_capture_.size(); i++) {
461 const auto& buffer = video_buffer_capture_[i];
462 std::string unencrypted_video_buffer(
463 reinterpret_cast<const char*>(buffer->data()), buffer->data_size());
464 EXPECT_EQ(decrypted_video_buffers[i], unencrypted_video_buffer);
465 }
466 audio_encryption_scheme = current_audio_config_.encryption_scheme();
467 EXPECT_FALSE(audio_encryption_scheme.is_encrypted());
468 for (size_t i = 0; i + 1 < audio_buffer_capture_.size(); i++) {
469 const auto& buffer = audio_buffer_capture_[i];
470 std::string unencrypted_audio_buffer(
471 reinterpret_cast<const char*>(buffer->data()), buffer->data_size());
472 EXPECT_EQ(decrypted_audio_buffers[i], unencrypted_audio_buffer);
473 }
474 }
475 #endif
476
302 } // namespace mp2t 477 } // namespace mp2t
303 } // namespace media 478 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698