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

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 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 "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_log.h" 22 #include "media/base/media_log.h"
22 #include "media/base/media_track.h" 23 #include "media/base/media_track.h"
23 #include "media/base/media_tracks.h" 24 #include "media/base/media_tracks.h"
24 #include "media/base/stream_parser_buffer.h" 25 #include "media/base/stream_parser_buffer.h"
25 #include "media/base/test_data_util.h" 26 #include "media/base/test_data_util.h"
26 #include "media/base/text_track_config.h" 27 #include "media/base/text_track_config.h"
27 #include "media/base/video_decoder_config.h" 28 #include "media/base/video_decoder_config.h"
29 #include "media/media_features.h"
28 #include "testing/gtest/include/gtest/gtest.h" 30 #include "testing/gtest/include/gtest/gtest.h"
29 31
32 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
33 #include <openssl/aes.h>
34 #include <openssl/evp.h>
35 #include "crypto/openssl_util.h"
36 #endif
37
30 namespace media { 38 namespace media {
31 namespace mp2t { 39 namespace mp2t {
32 40
33 namespace { 41 namespace {
34 42
35 bool IsMonotonic(const StreamParser::BufferQueue& buffers) { 43 bool IsMonotonic(const StreamParser::BufferQueue& buffers) {
36 if (buffers.empty()) 44 if (buffers.empty())
37 return true; 45 return true;
38 46
39 StreamParser::BufferQueue::const_iterator it1 = buffers.begin(); 47 StreamParser::BufferQueue::const_iterator it1 = buffers.begin();
40 StreamParser::BufferQueue::const_iterator it2 = ++it1; 48 StreamParser::BufferQueue::const_iterator it2 = ++it1;
41 for ( ; it2 != buffers.end(); ++it1, ++it2) { 49 for ( ; it2 != buffers.end(); ++it1, ++it2) {
42 if ((*it2)->GetDecodeTimestamp() < (*it1)->GetDecodeTimestamp()) 50 if ((*it2)->GetDecodeTimestamp() < (*it1)->GetDecodeTimestamp())
43 return false; 51 return false;
44 } 52 }
45 return true; 53 return true;
46 } 54 }
47 55
48 bool IsAlmostEqual(DecodeTimestamp t0, DecodeTimestamp t1) { 56 bool IsAlmostEqual(DecodeTimestamp t0, DecodeTimestamp t1) {
49 base::TimeDelta kMaxDeviation = base::TimeDelta::FromMilliseconds(5); 57 base::TimeDelta kMaxDeviation = base::TimeDelta::FromMilliseconds(5);
50 base::TimeDelta diff = t1 - t0; 58 base::TimeDelta diff = t1 - t0;
51 return (diff >= -kMaxDeviation && diff <= kMaxDeviation); 59 return (diff >= -kMaxDeviation && diff <= kMaxDeviation);
52 } 60 }
53 61
62 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
63 class ScopedCipherCTX {
64 public:
65 explicit ScopedCipherCTX() { EVP_CIPHER_CTX_init(&ctx_); }
66 ~ScopedCipherCTX() {
67 EVP_CIPHER_CTX_cleanup(&ctx_);
68 crypto::ClearOpenSSLERRStack(FROM_HERE);
69 }
70 EVP_CIPHER_CTX* get() { return &ctx_; }
71
72 private:
73 EVP_CIPHER_CTX ctx_;
74 };
75
76 std::string DecryptSampleAES(const std::string& key,
77 const std::string& iv,
78 const uint8_t* input,
79 int input_size,
80 bool has_pattern) {
81 DCHECK(input);
82 EXPECT_EQ(input_size % 16, 0);
83 crypto::EnsureOpenSSLInit();
84 std::string result;
85 const EVP_CIPHER* cipher = EVP_aes_128_cbc();
86 ScopedCipherCTX ctx;
87 EXPECT_EQ(EVP_CipherInit_ex(ctx.get(), cipher, NULL,
88 reinterpret_cast<const uint8_t*>(key.data()),
89 reinterpret_cast<const uint8_t*>(iv.data()), 0),
90 1);
91 EVP_CIPHER_CTX_set_padding(ctx.get(), 0);
92 const size_t output_size = input_size;
93 std::unique_ptr<char[]> output(new char[output_size]);
94 uint8_t* in_ptr = const_cast<uint8_t*>(input);
95 uint8_t* out_ptr = reinterpret_cast<uint8_t*>(output.get());
96 size_t bytes_remaining = output_size;
97
98 while (bytes_remaining) {
99 int unused;
100 size_t amount_to_decrypt = has_pattern ? 16UL : bytes_remaining;
101 EXPECT_EQ(EVP_CipherUpdate(ctx.get(), out_ptr, &unused, in_ptr,
102 amount_to_decrypt),
103 1);
104 bytes_remaining -= amount_to_decrypt;
105 if (bytes_remaining) {
106 out_ptr += amount_to_decrypt;
107 in_ptr += amount_to_decrypt;
108 size_t amount_to_skip = 144UL;
109 if (amount_to_skip > bytes_remaining)
110 amount_to_skip = bytes_remaining;
111 memcpy(out_ptr, in_ptr, amount_to_skip);
112 out_ptr += amount_to_skip;
113 in_ptr += amount_to_skip;
114 bytes_remaining -= amount_to_skip;
115 }
116 }
117
118 result.assign(output.get(), output_size);
119 return result;
120 }
121
122 // We only support AES-CBC at this time.
123 // For the purpose of these tests, the key id is also used as the actual key.
124 std::string DecryptBuffer(const StreamParserBuffer& buffer,
125 const EncryptionScheme& scheme) {
126 EXPECT_TRUE(scheme.is_encrypted());
127 EXPECT_TRUE(scheme.mode() == EncryptionScheme::CIPHER_MODE_AES_CBC);
128 bool has_pattern = scheme.pattern().IsInEffect();
129 EXPECT_TRUE(!has_pattern ||
130 scheme.pattern().Matches(EncryptionScheme::Pattern(1, 9)));
131
132 std::string key;
133 EXPECT_TRUE(
134 LookupTestKeyString(buffer.decrypt_config()->key_id(), false, &key));
135 std::string iv = buffer.decrypt_config()->iv();
136 EXPECT_EQ(key.size(), 16UL);
137 EXPECT_EQ(iv.size(), 16UL);
138 std::string result;
139 uint8_t* in_ptr = const_cast<uint8_t*>(buffer.data());
140 const DecryptConfig* decrypt_config = buffer.decrypt_config();
141 for (const auto& subsample : decrypt_config->subsamples()) {
142 std::string clear(reinterpret_cast<char*>(in_ptr), subsample.clear_bytes);
143 result += clear;
144 in_ptr += subsample.clear_bytes;
145 result +=
146 DecryptSampleAES(key, iv, in_ptr, subsample.cypher_bytes, has_pattern);
147 in_ptr += subsample.cypher_bytes;
148 }
149 return result;
150 }
151 #endif
152
54 } // namespace 153 } // namespace
55 154
56 class Mp2tStreamParserTest : public testing::Test { 155 class Mp2tStreamParserTest : public testing::Test {
57 public: 156 public:
58 Mp2tStreamParserTest() 157 Mp2tStreamParserTest()
59 : segment_count_(0), 158 : segment_count_(0),
60 config_count_(0), 159 config_count_(0),
61 audio_frame_count_(0), 160 audio_frame_count_(0),
62 video_frame_count_(0), 161 video_frame_count_(0),
63 has_video_(true), 162 has_video_(true),
64 audio_min_dts_(kNoDecodeTimestamp()), 163 audio_min_dts_(kNoDecodeTimestamp()),
65 audio_max_dts_(kNoDecodeTimestamp()), 164 audio_max_dts_(kNoDecodeTimestamp()),
66 video_min_dts_(kNoDecodeTimestamp()), 165 video_min_dts_(kNoDecodeTimestamp()),
67 video_max_dts_(kNoDecodeTimestamp()), 166 video_max_dts_(kNoDecodeTimestamp()),
68 audio_track_id_(0), 167 audio_track_id_(0),
69 video_track_id_(0) { 168 video_track_id_(0),
169 current_audio_config_(),
170 current_video_config_(),
171 capture_buffers(false) {
70 bool has_sbr = false; 172 bool has_sbr = false;
71 parser_.reset(new Mp2tStreamParser(has_sbr)); 173 parser_.reset(new Mp2tStreamParser(has_sbr));
72 } 174 }
73 175
74 protected: 176 protected:
75 std::unique_ptr<Mp2tStreamParser> parser_; 177 std::unique_ptr<Mp2tStreamParser> parser_;
76 int segment_count_; 178 int segment_count_;
77 int config_count_; 179 int config_count_;
78 int audio_frame_count_; 180 int audio_frame_count_;
79 int video_frame_count_; 181 int video_frame_count_;
80 bool has_video_; 182 bool has_video_;
81 DecodeTimestamp audio_min_dts_; 183 DecodeTimestamp audio_min_dts_;
82 DecodeTimestamp audio_max_dts_; 184 DecodeTimestamp audio_max_dts_;
83 DecodeTimestamp video_min_dts_; 185 DecodeTimestamp video_min_dts_;
84 DecodeTimestamp video_max_dts_; 186 DecodeTimestamp video_max_dts_;
85 StreamParser::TrackId audio_track_id_; 187 StreamParser::TrackId audio_track_id_;
86 StreamParser::TrackId video_track_id_; 188 StreamParser::TrackId video_track_id_;
87 189
190 AudioDecoderConfig current_audio_config_;
191 VideoDecoderConfig current_video_config_;
192 std::vector<scoped_refptr<StreamParserBuffer>> audio_buffer_capture_;
193 std::vector<scoped_refptr<StreamParserBuffer>> video_buffer_capture_;
194 bool capture_buffers;
195
88 void ResetStats() { 196 void ResetStats() {
89 segment_count_ = 0; 197 segment_count_ = 0;
90 config_count_ = 0; 198 config_count_ = 0;
91 audio_frame_count_ = 0; 199 audio_frame_count_ = 0;
92 video_frame_count_ = 0; 200 video_frame_count_ = 0;
93 audio_min_dts_ = kNoDecodeTimestamp(); 201 audio_min_dts_ = kNoDecodeTimestamp();
94 audio_max_dts_ = kNoDecodeTimestamp(); 202 audio_max_dts_ = kNoDecodeTimestamp();
95 video_min_dts_ = kNoDecodeTimestamp(); 203 video_min_dts_ = kNoDecodeTimestamp();
96 video_max_dts_ = kNoDecodeTimestamp(); 204 video_max_dts_ = kNoDecodeTimestamp();
97 } 205 }
(...skipping 26 matching lines...) Expand all
124 const StreamParser::TextTrackConfigMap& tc) { 232 const StreamParser::TextTrackConfigMap& tc) {
125 DVLOG(1) << "OnNewConfig: got " << tracks->tracks().size() << " tracks"; 233 DVLOG(1) << "OnNewConfig: got " << tracks->tracks().size() << " tracks";
126 bool found_audio_track = false; 234 bool found_audio_track = false;
127 bool found_video_track = false; 235 bool found_video_track = false;
128 for (const auto& track : tracks->tracks()) { 236 for (const auto& track : tracks->tracks()) {
129 const auto& track_id = track->bytestream_track_id(); 237 const auto& track_id = track->bytestream_track_id();
130 if (track->type() == MediaTrack::Audio) { 238 if (track->type() == MediaTrack::Audio) {
131 audio_track_id_ = track_id; 239 audio_track_id_ = track_id;
132 found_audio_track = true; 240 found_audio_track = true;
133 EXPECT_TRUE(tracks->getAudioConfig(track_id).IsValidConfig()); 241 EXPECT_TRUE(tracks->getAudioConfig(track_id).IsValidConfig());
242 current_audio_config_ = tracks->getAudioConfig(track_id);
134 } else if (track->type() == MediaTrack::Video) { 243 } else if (track->type() == MediaTrack::Video) {
135 video_track_id_ = track_id; 244 video_track_id_ = track_id;
136 found_video_track = true; 245 found_video_track = true;
137 EXPECT_TRUE(tracks->getVideoConfig(track_id).IsValidConfig()); 246 EXPECT_TRUE(tracks->getVideoConfig(track_id).IsValidConfig());
247 current_video_config_ = tracks->getVideoConfig(track_id);
138 } else { 248 } else {
139 // Unexpected track type. 249 // Unexpected track type.
140 LOG(ERROR) << "Unexpected track type " << track->type(); 250 LOG(ERROR) << "Unexpected track type " << track->type();
141 EXPECT_TRUE(false); 251 EXPECT_TRUE(false);
142 } 252 }
143 } 253 }
144 EXPECT_TRUE(found_audio_track); 254 EXPECT_TRUE(found_audio_track);
145 EXPECT_EQ(has_video_, found_video_track); 255 EXPECT_EQ(has_video_, found_video_track);
146 config_count_++; 256 config_count_++;
147 return true; 257 return true;
148 } 258 }
149 259
260 void CaptureVideoBuffers(const StreamParser::BufferQueue& video_buffers) {
261 for (const auto& buffer : video_buffers) {
262 video_buffer_capture_.push_back(buffer);
263 }
264 }
265
266 void CaptureAudioBuffers(const StreamParser::BufferQueue& audio_buffers) {
267 for (const auto& buffer : audio_buffers) {
268 audio_buffer_capture_.push_back(buffer);
269 }
270 }
271
150 bool OnNewBuffers(const StreamParser::BufferQueueMap& buffer_queue_map) { 272 bool OnNewBuffers(const StreamParser::BufferQueueMap& buffer_queue_map) {
151 EXPECT_GT(config_count_, 0); 273 EXPECT_GT(config_count_, 0);
152 // Ensure that track ids are properly assigned on all emitted buffers. 274 // Ensure that track ids are properly assigned on all emitted buffers.
153 for (const auto& it : buffer_queue_map) { 275 for (const auto& it : buffer_queue_map) {
154 DVLOG(3) << "Buffers for track_id=" << it.first; 276 DVLOG(3) << "Buffers for track_id=" << it.first;
155 for (const auto& buf : it.second) { 277 for (const auto& buf : it.second) {
156 DVLOG(3) << " track_id=" << buf->track_id() 278 DVLOG(3) << " track_id=" << buf->track_id()
157 << ", size=" << buf->data_size() 279 << ", size=" << buf->data_size()
158 << ", pts=" << buf->timestamp().InSecondsF() 280 << ", pts=" << buf->timestamp().InSecondsF()
159 << ", dts=" << buf->GetDecodeTimestamp().InSecondsF() 281 << ", dts=" << buf->GetDecodeTimestamp().InSecondsF()
160 << ", dur=" << buf->duration().InSecondsF(); 282 << ", dur=" << buf->duration().InSecondsF();
161 EXPECT_EQ(it.first, buf->track_id()); 283 EXPECT_EQ(it.first, buf->track_id());
162 } 284 }
163 } 285 }
164 286
165 const StreamParser::BufferQueue empty_buffers; 287 const StreamParser::BufferQueue empty_buffers;
166 const auto& itr_audio = buffer_queue_map.find(audio_track_id_); 288 const auto& itr_audio = buffer_queue_map.find(audio_track_id_);
167 const StreamParser::BufferQueue& audio_buffers = 289 const StreamParser::BufferQueue& audio_buffers =
168 (itr_audio == buffer_queue_map.end()) ? empty_buffers 290 (itr_audio == buffer_queue_map.end()) ? empty_buffers
169 : itr_audio->second; 291 : itr_audio->second;
170 292
171 const auto& itr_video = buffer_queue_map.find(video_track_id_); 293 const auto& itr_video = buffer_queue_map.find(video_track_id_);
172 const StreamParser::BufferQueue& video_buffers = 294 const StreamParser::BufferQueue& video_buffers =
173 (itr_video == buffer_queue_map.end()) ? empty_buffers 295 (itr_video == buffer_queue_map.end()) ? empty_buffers
174 : itr_video->second; 296 : itr_video->second;
175 297
298 if (capture_buffers) {
299 CaptureVideoBuffers(video_buffers);
300 CaptureAudioBuffers(audio_buffers);
301 }
302
176 // Verify monotonicity. 303 // Verify monotonicity.
177 if (!IsMonotonic(video_buffers)) 304 if (!IsMonotonic(video_buffers))
178 return false; 305 return false;
179 if (!IsMonotonic(audio_buffers)) 306 if (!IsMonotonic(audio_buffers))
180 return false; 307 return false;
181 308
182 if (!video_buffers.empty()) { 309 if (!video_buffers.empty()) {
183 DecodeTimestamp first_dts = video_buffers.front()->GetDecodeTimestamp(); 310 DecodeTimestamp first_dts = video_buffers.front()->GetDecodeTimestamp();
184 DecodeTimestamp last_dts = video_buffers.back()->GetDecodeTimestamp(); 311 DecodeTimestamp last_dts = video_buffers.back()->GetDecodeTimestamp();
185 if (video_max_dts_ != kNoDecodeTimestamp() && first_dts < video_max_dts_) 312 if (video_max_dts_ != kNoDecodeTimestamp() && first_dts < video_max_dts_)
(...skipping 12 matching lines...) Expand all
198 audio_max_dts_ = last_dts; 325 audio_max_dts_ = last_dts;
199 } 326 }
200 327
201 audio_frame_count_ += audio_buffers.size(); 328 audio_frame_count_ += audio_buffers.size();
202 video_frame_count_ += video_buffers.size(); 329 video_frame_count_ += video_buffers.size();
203 return true; 330 return true;
204 } 331 }
205 332
206 void OnKeyNeeded(EmeInitDataType type, 333 void OnKeyNeeded(EmeInitDataType type,
207 const std::vector<uint8_t>& init_data) { 334 const std::vector<uint8_t>& init_data) {
335 #if !BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
208 LOG(ERROR) << "OnKeyNeeded not expected in the Mpeg2 TS parser"; 336 LOG(ERROR) << "OnKeyNeeded not expected in the Mpeg2 TS parser";
209 EXPECT_TRUE(false); 337 EXPECT_TRUE(false);
338 #endif
210 } 339 }
211 340
212 void OnNewSegment() { 341 void OnNewSegment() {
213 DVLOG(1) << "OnNewSegment"; 342 DVLOG(1) << "OnNewSegment";
214 segment_count_++; 343 segment_count_++;
215 } 344 }
216 345
217 void OnEndOfSegment() { 346 void OnEndOfSegment() {
218 LOG(ERROR) << "OnEndOfSegment not expected in the Mpeg2 TS parser"; 347 LOG(ERROR) << "OnEndOfSegment not expected in the Mpeg2 TS parser";
219 EXPECT_TRUE(false); 348 EXPECT_TRUE(false);
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
317 has_video_ = false; 446 has_video_ = false;
318 ParseMpeg2TsFile("bear_adts_in_private_stream_1.ts", 512); 447 ParseMpeg2TsFile("bear_adts_in_private_stream_1.ts", 512);
319 parser_->Flush(); 448 parser_->Flush();
320 EXPECT_EQ(audio_frame_count_, 40); 449 EXPECT_EQ(audio_frame_count_, 40);
321 EXPECT_EQ(video_frame_count_, 0); 450 EXPECT_EQ(video_frame_count_, 0);
322 // This stream has no mid-stream configuration change. 451 // This stream has no mid-stream configuration change.
323 EXPECT_EQ(config_count_, 1); 452 EXPECT_EQ(config_count_, 1);
324 EXPECT_EQ(segment_count_, 1); 453 EXPECT_EQ(segment_count_, 1);
325 } 454 }
326 455
456 #if BUILDFLAG(ENABLE_HLS_SAMPLE_AES)
457 TEST_F(Mp2tStreamParserTest, HLSSampleAES) {
458 std::vector<std::string> decrypted_video_buffers;
459 std::vector<std::string> decrypted_audio_buffers;
460 InitializeParser();
461 capture_buffers = true;
462 ParseMpeg2TsFile("bear-1280x720-hls-sample-aes.ts", 2048);
463 parser_->Flush();
464 EncryptionScheme video_encryption_scheme =
465 current_video_config_.encryption_scheme();
466 EXPECT_TRUE(video_encryption_scheme.is_encrypted());
467 for (const auto& buffer : video_buffer_capture_) {
468 std::string decrypted_video_buffer =
469 DecryptBuffer(*buffer.get(), video_encryption_scheme);
470 decrypted_video_buffers.push_back(decrypted_video_buffer);
471 }
472 EncryptionScheme audio_encryption_scheme =
473 current_audio_config_.encryption_scheme();
474 EXPECT_TRUE(audio_encryption_scheme.is_encrypted());
475 for (const auto& buffer : audio_buffer_capture_) {
476 std::string decrypted_audio_buffer =
477 DecryptBuffer(*buffer.get(), audio_encryption_scheme);
478 decrypted_audio_buffers.push_back(decrypted_audio_buffer);
479 }
480
481 parser_.reset(new Mp2tStreamParser(false));
482 ResetStats();
483 InitializeParser();
484 video_buffer_capture_.clear();
485 audio_buffer_capture_.clear();
486 ParseMpeg2TsFile("bear-1280x720-hls.ts", 2048);
487 parser_->Flush();
488 video_encryption_scheme = current_video_config_.encryption_scheme();
489 EXPECT_FALSE(video_encryption_scheme.is_encrypted());
490 // Skip the last buffer, which may be truncated.
491 for (size_t i = 0; i + 1 < video_buffer_capture_.size(); i++) {
492 const auto& buffer = video_buffer_capture_[i];
493 std::string unencrypted_video_buffer(
494 reinterpret_cast<const char*>(buffer->data()), buffer->data_size());
495 EXPECT_EQ(decrypted_video_buffers[i], unencrypted_video_buffer);
496 }
497 audio_encryption_scheme = current_audio_config_.encryption_scheme();
498 EXPECT_FALSE(audio_encryption_scheme.is_encrypted());
499 for (size_t i = 0; i + 1 < audio_buffer_capture_.size(); i++) {
500 const auto& buffer = audio_buffer_capture_[i];
501 std::string unencrypted_audio_buffer(
502 reinterpret_cast<const char*>(buffer->data()), buffer->data_size());
503 EXPECT_EQ(decrypted_audio_buffers[i], unencrypted_audio_buffer);
504 }
505 }
506 #endif
507
327 } // namespace mp2t 508 } // namespace mp2t
328 } // namespace media 509 } // 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