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

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

Powered by Google App Engine
This is Rietveld 408576698