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

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, 9 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
« no previous file with comments | « media/formats/mp2t/mp2t_stream_parser.cc ('k') | media/formats/mp2t/ts_section_cat.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 std::string key = buffer.decrypt_config()->key_id();
ddorwin 2016/03/24 22:44:52 Related to my other comment, I suggest actually do
dougsteed 2016/03/31 19:09:50 Done.
129 std::string iv = buffer.decrypt_config()->iv();
130 EXPECT_EQ(key.size(), 16UL);
131 EXPECT_EQ(iv.size(), 16UL);
132 std::string result;
133 uint8_t* in_ptr = const_cast<uint8_t*>(buffer.data());
134 const DecryptConfig* decrypt_config = buffer.decrypt_config();
135 for (const auto& subsample : decrypt_config->subsamples()) {
136 std::string clear(reinterpret_cast<char*>(in_ptr), subsample.clear_bytes);
137 result += clear;
138 in_ptr += subsample.clear_bytes;
139 result +=
140 DecryptSampleAES(key, iv, in_ptr, subsample.cypher_bytes, has_pattern);
141 in_ptr += subsample.cypher_bytes;
142 }
143 return result;
144 }
145 #endif
146
51 } // namespace 147 } // namespace
52 148
53 class Mp2tStreamParserTest : public testing::Test { 149 class Mp2tStreamParserTest : public testing::Test {
54 public: 150 public:
55 Mp2tStreamParserTest() 151 Mp2tStreamParserTest()
56 : segment_count_(0), 152 : segment_count_(0),
57 config_count_(0), 153 config_count_(0),
58 audio_frame_count_(0), 154 audio_frame_count_(0),
59 video_frame_count_(0), 155 video_frame_count_(0),
60 has_video_(true), 156 has_video_(true),
61 audio_min_dts_(kNoDecodeTimestamp()), 157 audio_min_dts_(kNoDecodeTimestamp()),
62 audio_max_dts_(kNoDecodeTimestamp()), 158 audio_max_dts_(kNoDecodeTimestamp()),
63 video_min_dts_(kNoDecodeTimestamp()), 159 video_min_dts_(kNoDecodeTimestamp()),
64 video_max_dts_(kNoDecodeTimestamp()) { 160 video_max_dts_(kNoDecodeTimestamp()),
161 current_audio_config_(),
162 current_video_config_(),
163 capture_buffers(false) {
65 bool has_sbr = false; 164 bool has_sbr = false;
66 parser_.reset(new Mp2tStreamParser(has_sbr)); 165 parser_.reset(new Mp2tStreamParser(has_sbr));
67 } 166 }
68 167
69 protected: 168 protected:
70 scoped_ptr<Mp2tStreamParser> parser_; 169 scoped_ptr<Mp2tStreamParser> parser_;
71 int segment_count_; 170 int segment_count_;
72 int config_count_; 171 int config_count_;
73 int audio_frame_count_; 172 int audio_frame_count_;
74 int video_frame_count_; 173 int video_frame_count_;
75 bool has_video_; 174 bool has_video_;
76 DecodeTimestamp audio_min_dts_; 175 DecodeTimestamp audio_min_dts_;
77 DecodeTimestamp audio_max_dts_; 176 DecodeTimestamp audio_max_dts_;
78 DecodeTimestamp video_min_dts_; 177 DecodeTimestamp video_min_dts_;
79 DecodeTimestamp video_max_dts_; 178 DecodeTimestamp video_max_dts_;
80 179
180 AudioDecoderConfig current_audio_config_;
181 VideoDecoderConfig current_video_config_;
182 std::vector<scoped_refptr<StreamParserBuffer>> audio_buffer_capture_;
183 std::vector<scoped_refptr<StreamParserBuffer>> video_buffer_capture_;
184 bool capture_buffers;
185
81 void ResetStats() { 186 void ResetStats() {
82 segment_count_ = 0; 187 segment_count_ = 0;
83 config_count_ = 0; 188 config_count_ = 0;
84 audio_frame_count_ = 0; 189 audio_frame_count_ = 0;
85 video_frame_count_ = 0; 190 video_frame_count_ = 0;
86 audio_min_dts_ = kNoDecodeTimestamp(); 191 audio_min_dts_ = kNoDecodeTimestamp();
87 audio_max_dts_ = kNoDecodeTimestamp(); 192 audio_max_dts_ = kNoDecodeTimestamp();
88 video_min_dts_ = kNoDecodeTimestamp(); 193 video_min_dts_ = kNoDecodeTimestamp();
89 video_max_dts_ = kNoDecodeTimestamp(); 194 video_max_dts_ = kNoDecodeTimestamp();
90 } 195 }
(...skipping 24 matching lines...) Expand all
115 220
116 bool OnNewConfig(scoped_ptr<MediaTracks> tracks, 221 bool OnNewConfig(scoped_ptr<MediaTracks> tracks,
117 const StreamParser::TextTrackConfigMap& tc) { 222 const StreamParser::TextTrackConfigMap& tc) {
118 const AudioDecoderConfig& ac = tracks->getFirstAudioConfig(); 223 const AudioDecoderConfig& ac = tracks->getFirstAudioConfig();
119 const VideoDecoderConfig& vc = tracks->getFirstVideoConfig(); 224 const VideoDecoderConfig& vc = tracks->getFirstVideoConfig();
120 DVLOG(1) << "OnNewConfig: media tracks count=" << tracks->tracks().size() 225 DVLOG(1) << "OnNewConfig: media tracks count=" << tracks->tracks().size()
121 << ", audio=" << ac.IsValidConfig() 226 << ", audio=" << ac.IsValidConfig()
122 << ", video=" << vc.IsValidConfig(); 227 << ", video=" << vc.IsValidConfig();
123 config_count_++; 228 config_count_++;
124 EXPECT_TRUE(ac.IsValidConfig()); 229 EXPECT_TRUE(ac.IsValidConfig());
230 current_audio_config_ = ac;
125 EXPECT_EQ(vc.IsValidConfig(), has_video_); 231 EXPECT_EQ(vc.IsValidConfig(), has_video_);
232 current_video_config_ = vc;
126 return true; 233 return true;
127 } 234 }
128 235
236 void CaptureVideoBuffers(const StreamParser::BufferQueue& video_buffers) {
237 for (const auto& buffer : video_buffers) {
238 video_buffer_capture_.push_back(buffer);
239 }
240 }
241
242 void CaptureAudioBuffers(const StreamParser::BufferQueue& audio_buffers) {
243 for (const auto& buffer : audio_buffers) {
244 audio_buffer_capture_.push_back(buffer);
245 }
246 }
129 247
130 void DumpBuffers(const std::string& label, 248 void DumpBuffers(const std::string& label,
131 const StreamParser::BufferQueue& buffers) { 249 const StreamParser::BufferQueue& buffers) {
132 DVLOG(2) << "DumpBuffers: " << label << " size " << buffers.size(); 250 DVLOG(2) << "DumpBuffers: " << label << " size " << buffers.size();
133 for (StreamParser::BufferQueue::const_iterator buf = buffers.begin(); 251 for (StreamParser::BufferQueue::const_iterator buf = buffers.begin();
134 buf != buffers.end(); buf++) { 252 buf != buffers.end(); buf++) {
135 DVLOG(3) << " n=" << buf - buffers.begin() 253 DVLOG(3) << " n=" << buf - buffers.begin()
136 << ", size=" << (*buf)->data_size() 254 << ", size=" << (*buf)->data_size()
137 << ", dur=" << (*buf)->duration().InMilliseconds(); 255 << ", dur=" << (*buf)->duration().InMilliseconds();
138 } 256 }
139 } 257 }
140 258
141 bool OnNewBuffers(const StreamParser::BufferQueue& audio_buffers, 259 bool OnNewBuffers(const StreamParser::BufferQueue& audio_buffers,
142 const StreamParser::BufferQueue& video_buffers, 260 const StreamParser::BufferQueue& video_buffers,
143 const StreamParser::TextBufferQueueMap& text_map) { 261 const StreamParser::TextBufferQueueMap& text_map) {
144 EXPECT_GT(config_count_, 0); 262 EXPECT_GT(config_count_, 0);
145 DumpBuffers("audio_buffers", audio_buffers); 263 DumpBuffers("audio_buffers", audio_buffers);
146 DumpBuffers("video_buffers", video_buffers); 264 DumpBuffers("video_buffers", video_buffers);
265 if (capture_buffers) {
266 CaptureVideoBuffers(video_buffers);
267 CaptureAudioBuffers(audio_buffers);
268 }
147 269
148 // TODO(wolenetz/acolwell): Add text track support to more MSE parsers. See 270 // TODO(wolenetz/acolwell): Add text track support to more MSE parsers. See
149 // http://crbug.com/336926. 271 // http://crbug.com/336926.
150 if (!text_map.empty()) 272 if (!text_map.empty())
151 return false; 273 return false;
152 274
153 // Verify monotonicity. 275 // Verify monotonicity.
154 if (!IsMonotonic(video_buffers)) 276 if (!IsMonotonic(video_buffers))
155 return false; 277 return false;
156 if (!IsMonotonic(audio_buffers)) 278 if (!IsMonotonic(audio_buffers))
(...skipping 18 matching lines...) Expand all
175 audio_max_dts_ = last_dts; 297 audio_max_dts_ = last_dts;
176 } 298 }
177 299
178 audio_frame_count_ += audio_buffers.size(); 300 audio_frame_count_ += audio_buffers.size();
179 video_frame_count_ += video_buffers.size(); 301 video_frame_count_ += video_buffers.size();
180 return true; 302 return true;
181 } 303 }
182 304
183 void OnKeyNeeded(EmeInitDataType type, 305 void OnKeyNeeded(EmeInitDataType type,
184 const std::vector<uint8_t>& init_data) { 306 const std::vector<uint8_t>& init_data) {
185 NOTREACHED() << "OnKeyNeeded not expected in the Mpeg2 TS parser";
186 } 307 }
187 308
188 void OnNewSegment() { 309 void OnNewSegment() {
189 DVLOG(1) << "OnNewSegment"; 310 DVLOG(1) << "OnNewSegment";
190 segment_count_++; 311 segment_count_++;
191 } 312 }
192 313
193 void OnEndOfSegment() { 314 void OnEndOfSegment() {
194 NOTREACHED() << "OnEndOfSegment not expected in the Mpeg2 TS parser"; 315 NOTREACHED() << "OnEndOfSegment not expected in the Mpeg2 TS parser";
195 } 316 }
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
292 has_video_ = false; 413 has_video_ = false;
293 ParseMpeg2TsFile("bear_adts_in_private_stream_1.ts", 512); 414 ParseMpeg2TsFile("bear_adts_in_private_stream_1.ts", 512);
294 parser_->Flush(); 415 parser_->Flush();
295 EXPECT_EQ(audio_frame_count_, 40); 416 EXPECT_EQ(audio_frame_count_, 40);
296 EXPECT_EQ(video_frame_count_, 0); 417 EXPECT_EQ(video_frame_count_, 0);
297 // This stream has no mid-stream configuration change. 418 // This stream has no mid-stream configuration change.
298 EXPECT_EQ(config_count_, 1); 419 EXPECT_EQ(config_count_, 1);
299 EXPECT_EQ(segment_count_, 1); 420 EXPECT_EQ(segment_count_, 1);
300 } 421 }
301 422
423 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
424 TEST_F(Mp2tStreamParserTest, HLSSampleAES) {
425 std::vector<std::string> decrypted_video_buffers;
426 std::vector<std::string> decrypted_audio_buffers;
427 InitializeParser();
428 capture_buffers = true;
429 ParseMpeg2TsFile("bear-1280x720-hls-sample-aes.ts", 2048);
430 parser_->Flush();
431 EncryptionScheme video_encryption_scheme =
432 current_video_config_.encryption_scheme();
433 EXPECT_TRUE(video_encryption_scheme.is_encrypted());
434 for (const auto& buffer : video_buffer_capture_) {
435 std::string decrypted_video_buffer =
436 DecryptBuffer(*buffer.get(), video_encryption_scheme);
437 decrypted_video_buffers.push_back(decrypted_video_buffer);
438 }
439 EncryptionScheme audio_encryption_scheme =
440 current_audio_config_.encryption_scheme();
441 EXPECT_TRUE(audio_encryption_scheme.is_encrypted());
442 for (const auto& buffer : audio_buffer_capture_) {
443 std::string decrypted_audio_buffer =
444 DecryptBuffer(*buffer.get(), audio_encryption_scheme);
445 decrypted_audio_buffers.push_back(decrypted_audio_buffer);
446 }
447
448 parser_.reset(new Mp2tStreamParser(false));
449 ResetStats();
450 InitializeParser();
451 video_buffer_capture_.clear();
452 audio_buffer_capture_.clear();
453 ParseMpeg2TsFile("bear-1280x720-hls.ts", 2048);
454 parser_->Flush();
455 video_encryption_scheme = current_video_config_.encryption_scheme();
456 EXPECT_FALSE(video_encryption_scheme.is_encrypted());
457 // Skip the last buffer, which may be truncated.
458 for (size_t i = 0; i + 1 < video_buffer_capture_.size(); i++) {
459 const auto& buffer = video_buffer_capture_[i];
460 std::string unencrypted_video_buffer(
461 reinterpret_cast<const char*>(buffer->data()), buffer->data_size());
462 EXPECT_EQ(decrypted_video_buffers[i], unencrypted_video_buffer);
463 }
464 audio_encryption_scheme = current_audio_config_.encryption_scheme();
465 EXPECT_FALSE(audio_encryption_scheme.is_encrypted());
466 for (size_t i = 0; i + 1 < audio_buffer_capture_.size(); i++) {
467 const auto& buffer = audio_buffer_capture_[i];
468 std::string unencrypted_audio_buffer(
469 reinterpret_cast<const char*>(buffer->data()), buffer->data_size());
470 EXPECT_EQ(decrypted_audio_buffers[i], unencrypted_audio_buffer);
471 }
472 }
473 #endif
474
302 } // namespace mp2t 475 } // namespace mp2t
303 } // namespace media 476 } // namespace media
OLDNEW
« no previous file with comments | « media/formats/mp2t/mp2t_stream_parser.cc ('k') | media/formats/mp2t/ts_section_cat.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698