OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "base/bind.h" | |
6 #include "base/logging.h" | |
7 #include "base/thread_task_runner_handle.h" | |
8 #include "base/timer/timer.h" | |
9 #include "media/base/android/media_codec_audio_decoder.h" | |
10 #include "media/base/android/media_codec_bridge.h" | |
11 #include "media/base/android/media_codec_video_decoder.h" | |
12 #include "media/base/android/test_data_factory.h" | |
13 #include "testing/gtest/include/gtest/gtest.h" | |
14 #include "ui/gl/android/surface_texture.h" | |
15 | |
16 namespace media { | |
17 | |
18 // Helper macro to skip the test if MediaCodecBridge isn't available. | |
19 #define SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE() \ | |
20 do { \ | |
21 if (!MediaCodecBridge::IsAvailable()) { \ | |
22 VLOG(0) << "Could not run test - not supported on device."; \ | |
23 return; \ | |
24 } \ | |
25 } while (0) | |
26 | |
27 namespace { | |
28 | |
29 const base::TimeDelta kDefaultTimeout = base::TimeDelta::FromMilliseconds(200); | |
30 const base::TimeDelta kAudioFramePeriod = base::TimeDelta::FromMilliseconds(20); | |
31 const base::TimeDelta kVideoFramePeriod = base::TimeDelta::FromMilliseconds(20); | |
32 | |
33 class AudioFactory : public TestDataFactory { | |
34 public: | |
35 AudioFactory(const base::TimeDelta& duration); | |
36 DemuxerConfigs GetConfigs() override; | |
37 | |
38 protected: | |
39 void ModifyAccessUnit(int index_in_chunk, AccessUnit* unit) override; | |
40 }; | |
41 | |
42 class VideoFactory : public TestDataFactory { | |
43 public: | |
44 VideoFactory(const base::TimeDelta& duration); | |
45 DemuxerConfigs GetConfigs() override; | |
46 | |
47 protected: | |
48 void ModifyAccessUnit(int index_in_chunk, AccessUnit* unit) override; | |
49 }; | |
50 | |
51 AudioFactory::AudioFactory(const base::TimeDelta& duration) | |
52 : TestDataFactory("vorbis-packet-%d", duration, kAudioFramePeriod) { | |
53 } | |
54 | |
55 DemuxerConfigs AudioFactory::GetConfigs() { | |
56 return TestDataFactory::CreateAudioConfigs(kCodecVorbis, duration_); | |
57 } | |
58 | |
59 void AudioFactory::ModifyAccessUnit(int index_in_chunk, AccessUnit* unit) { | |
60 // Vorbis needs 4 extra bytes padding on Android to decode properly. Check | |
61 // NuMediaExtractor.cpp in Android source code. | |
62 uint8 padding[4] = {0xff, 0xff, 0xff, 0xff}; | |
63 unit->data.insert(unit->data.end(), padding, padding + 4); | |
64 } | |
65 | |
66 VideoFactory::VideoFactory(const base::TimeDelta& duration) | |
67 : TestDataFactory("h264-320x180-frame-%d", duration, kVideoFramePeriod) { | |
68 } | |
69 | |
70 DemuxerConfigs VideoFactory::GetConfigs() { | |
71 return TestDataFactory::CreateVideoConfigs(kCodecH264, duration_, | |
72 gfx::Size(320, 180)); | |
73 } | |
74 | |
75 void VideoFactory::ModifyAccessUnit(int index_in_chunk, AccessUnit* unit) { | |
76 // The frames are taken from High profile and some are B-frames. | |
77 // The first 4 frames appear in the file in the following order: | |
78 // | |
79 // Frames: I P B P | |
80 // Decoding order: 0 1 2 3 | |
81 // Presentation order: 0 2 1 4(3) | |
82 // | |
83 // I keep the last PTS to be 3 for simplicity. | |
84 | |
85 // Swap pts for second and third frames. | |
86 if (index_in_chunk == 1) // second frame | |
87 unit->timestamp += frame_period_; | |
88 if (index_in_chunk == 2) // third frame | |
89 unit->timestamp -= frame_period_; | |
90 | |
91 if (index_in_chunk == 0) | |
92 unit->is_key_frame = true; | |
93 } | |
94 | |
95 // Class that computes statistics: number of calls, minimum and maximum values. | |
96 // It is used for PTS statistics to verify that playback did actually happen. | |
97 | |
98 template <typename T> | |
99 class Minimax { | |
100 public: | |
101 Minimax() : num_values_(0) {} | |
102 ~Minimax() {} | |
103 | |
104 void AddValue(const T& value) { | |
105 ++num_values_; | |
106 if (value < min_) | |
107 min_ = value; | |
108 else if (max_ < value) | |
109 max_ = value; | |
110 } | |
111 | |
112 const T& min() const { return min_; } | |
113 const T& max() const { return max_; } | |
114 int num_values() const { return num_values_; } | |
115 | |
116 private: | |
117 T min_; | |
118 T max_; | |
119 int num_values_; | |
120 }; | |
121 | |
122 } // namespace (anonymous) | |
123 | |
124 // The test fixture for MediaCodecDecoder | |
125 | |
126 class MediaCodecDecoderTest : public testing::Test { | |
127 public: | |
128 MediaCodecDecoderTest(); | |
129 ~MediaCodecDecoderTest() override; | |
130 | |
131 // Conditions we wait for. | |
132 bool is_prefetched() const { return is_prefetched_; } | |
133 bool is_stopped() const { return is_stopped_; } | |
134 bool is_starved() const { return is_starved_; } | |
135 | |
136 // Prefetch callback has to be public. | |
137 void SetPrefetched() { is_prefetched_ = true; } | |
138 | |
139 protected: | |
140 typedef base::Callback<bool()> Predicate; | |
141 | |
142 typedef base::Callback<void(const DemuxerData&)> DataAvailableCallback; | |
143 | |
144 // Waits for condition to become true or for timeout to expire. | |
145 // Returns true if the condition becomes true. | |
146 bool WaitForCondition(const Predicate& condition, | |
147 const base::TimeDelta& timeout = kDefaultTimeout); | |
148 | |
149 void SetDataFactory(scoped_ptr<TestDataFactory> factory) { | |
150 data_factory_ = factory.Pass(); | |
151 } | |
152 | |
153 DemuxerConfigs GetConfigs() { | |
154 // ASSERT_NE does not compile here because it expects void return value. | |
155 EXPECT_NE(nullptr, data_factory_.get()); | |
156 return data_factory_->GetConfigs(); | |
157 } | |
158 | |
159 void CreateAudioDecoder(); | |
160 void CreateVideoDecoder(); | |
161 void SetVideoSurface(); | |
162 | |
163 // Decoder callbacks. | |
164 void OnDataRequested(); | |
165 void OnStarvation() { is_starved_ = true; } | |
166 void OnStopDone() { is_stopped_ = true; } | |
167 void OnError() {} | |
168 void OnUpdateCurrentTime(base::TimeDelta now_playing, | |
169 base::TimeDelta last_buffered) { | |
170 pts_stat_.AddValue(now_playing); | |
171 } | |
172 void OnVideoSizeChanged(const gfx::Size& video_size) {} | |
173 void OnVideoCodecCreated() {} | |
174 | |
175 scoped_ptr<MediaCodecDecoder> decoder_; | |
176 scoped_ptr<TestDataFactory> data_factory_; | |
177 Minimax<base::TimeDelta> pts_stat_; | |
178 | |
179 private: | |
180 bool is_timeout_expired() const { return is_timeout_expired_; } | |
181 void SetTimeoutExpired(bool value) { is_timeout_expired_ = value; } | |
182 | |
183 base::MessageLoop message_loop_; | |
184 bool is_timeout_expired_; | |
185 | |
186 bool is_prefetched_; | |
187 bool is_stopped_; | |
188 bool is_starved_; | |
189 | |
190 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; | |
191 DataAvailableCallback data_available_cb_; | |
192 scoped_refptr<gfx::SurfaceTexture> surface_texture_; | |
193 | |
194 DISALLOW_COPY_AND_ASSIGN(MediaCodecDecoderTest); | |
195 }; | |
196 | |
197 MediaCodecDecoderTest::MediaCodecDecoderTest() | |
198 : is_timeout_expired_(false), | |
199 is_prefetched_(false), | |
200 is_stopped_(false), | |
201 is_starved_(false), | |
202 task_runner_(base::ThreadTaskRunnerHandle::Get()) { | |
203 } | |
204 | |
205 MediaCodecDecoderTest::~MediaCodecDecoderTest() {} | |
206 | |
207 bool MediaCodecDecoderTest::WaitForCondition(const Predicate& condition, | |
208 const base::TimeDelta& timeout) { | |
209 // Let the message_loop_ process events. | |
210 // We start the timer and RunUntilIdle() until it signals. | |
211 | |
212 SetTimeoutExpired(false); | |
213 | |
214 base::Timer timer(false, false); | |
215 timer.Start(FROM_HERE, timeout, | |
216 base::Bind(&MediaCodecDecoderTest::SetTimeoutExpired, | |
217 base::Unretained(this), true)); | |
218 | |
219 do { | |
220 if (condition.Run()) { | |
221 timer.Stop(); | |
222 return true; | |
223 } | |
224 message_loop_.RunUntilIdle(); | |
225 } while (!is_timeout_expired()); | |
226 | |
227 DCHECK(!timer.IsRunning()); | |
228 return false; | |
229 } | |
230 | |
231 void MediaCodecDecoderTest::CreateAudioDecoder() { | |
232 decoder_ = scoped_ptr<MediaCodecDecoder>(new MediaCodecAudioDecoder( | |
233 task_runner_, base::Bind(&MediaCodecDecoderTest::OnDataRequested, | |
234 base::Unretained(this)), | |
235 base::Bind(&MediaCodecDecoderTest::OnStarvation, base::Unretained(this)), | |
236 base::Bind(&MediaCodecDecoderTest::OnStopDone, base::Unretained(this)), | |
237 base::Bind(&MediaCodecDecoderTest::OnError, base::Unretained(this)), | |
238 base::Bind(&MediaCodecDecoderTest::OnUpdateCurrentTime, | |
239 base::Unretained(this)))); | |
240 | |
241 data_available_cb_ = base::Bind(&MediaCodecDecoder::OnDemuxerDataAvailable, | |
242 base::Unretained(decoder_.get())); | |
243 } | |
244 | |
245 void MediaCodecDecoderTest::CreateVideoDecoder() { | |
246 decoder_ = scoped_ptr<MediaCodecDecoder>(new MediaCodecVideoDecoder( | |
247 task_runner_, base::Bind(&MediaCodecDecoderTest::OnDataRequested, | |
248 base::Unretained(this)), | |
249 base::Bind(&MediaCodecDecoderTest::OnStarvation, base::Unretained(this)), | |
250 base::Bind(&MediaCodecDecoderTest::OnStopDone, base::Unretained(this)), | |
251 base::Bind(&MediaCodecDecoderTest::OnError, base::Unretained(this)), | |
252 base::Bind(&MediaCodecDecoderTest::OnUpdateCurrentTime, | |
253 base::Unretained(this)), | |
254 base::Bind(&MediaCodecDecoderTest::OnVideoSizeChanged, | |
255 base::Unretained(this)), | |
256 base::Bind(&MediaCodecDecoderTest::OnVideoCodecCreated, | |
257 base::Unretained(this)))); | |
258 | |
259 data_available_cb_ = base::Bind(&MediaCodecDecoder::OnDemuxerDataAvailable, | |
260 base::Unretained(decoder_.get())); | |
261 } | |
262 | |
263 void MediaCodecDecoderTest::OnDataRequested() { | |
264 if (!data_factory_) | |
265 return; | |
266 | |
267 DemuxerData data; | |
268 base::TimeDelta delay; | |
269 data_factory_->CreateChunk(&data, &delay); | |
270 | |
271 task_runner_->PostDelayedTask(FROM_HERE, base::Bind(data_available_cb_, data), | |
272 delay); | |
273 } | |
274 | |
275 void MediaCodecDecoderTest::SetVideoSurface() { | |
276 surface_texture_ = gfx::SurfaceTexture::Create(0); | |
277 gfx::ScopedJavaSurface surface(surface_texture_.get()); | |
278 ASSERT_NE(nullptr, decoder_.get()); | |
279 MediaCodecVideoDecoder* video_decoder = | |
280 static_cast<MediaCodecVideoDecoder*>(decoder_.get()); | |
281 video_decoder->SetPendingSurface(surface.Pass()); | |
282 } | |
283 | |
284 TEST_F(MediaCodecDecoderTest, AudioPrefetch) { | |
285 CreateAudioDecoder(); | |
286 | |
287 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); | |
288 SetDataFactory(scoped_ptr<TestDataFactory>(new AudioFactory(duration))); | |
289 | |
290 decoder_->Prefetch(base::Bind(&MediaCodecDecoderTest::SetPrefetched, | |
291 base::Unretained(this))); | |
292 | |
293 EXPECT_TRUE(WaitForCondition(base::Bind(&MediaCodecDecoderTest::is_prefetched, | |
294 base::Unretained(this)))); | |
295 } | |
296 | |
297 TEST_F(MediaCodecDecoderTest, VideoPrefetch) { | |
298 CreateVideoDecoder(); | |
299 | |
300 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); | |
301 SetDataFactory(scoped_ptr<VideoFactory>(new VideoFactory(duration))); | |
302 | |
303 decoder_->Prefetch(base::Bind(&MediaCodecDecoderTest::SetPrefetched, | |
304 base::Unretained(this))); | |
305 | |
306 EXPECT_TRUE(WaitForCondition(base::Bind(&MediaCodecDecoderTest::is_prefetched, | |
307 base::Unretained(this)))); | |
308 } | |
309 | |
310 TEST_F(MediaCodecDecoderTest, AudioConfigureNoParams) { | |
311 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); | |
312 | |
313 CreateAudioDecoder(); | |
314 | |
315 // Cannot configure without config parameters. | |
316 EXPECT_EQ(MediaCodecDecoder::CONFIG_FAILURE, decoder_->Configure()); | |
317 } | |
318 | |
319 TEST_F(MediaCodecDecoderTest, AudioConfigureValidParams) { | |
320 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); | |
321 | |
322 CreateAudioDecoder(); | |
323 | |
324 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); | |
325 scoped_ptr<AudioFactory> factory(new AudioFactory(duration)); | |
326 decoder_->SetDemuxerConfigs(factory->GetConfigs()); | |
327 | |
328 EXPECT_EQ(MediaCodecDecoder::CONFIG_OK, decoder_->Configure()); | |
329 } | |
330 | |
331 TEST_F(MediaCodecDecoderTest, VideoConfigureNoParams) { | |
332 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); | |
333 | |
334 CreateVideoDecoder(); | |
335 | |
336 // Cannot configure without config parameters. | |
337 EXPECT_EQ(MediaCodecDecoder::CONFIG_FAILURE, decoder_->Configure()); | |
338 } | |
339 | |
340 TEST_F(MediaCodecDecoderTest, VideoConfigureNoSurface) { | |
341 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); | |
342 | |
343 CreateVideoDecoder(); | |
344 | |
345 // decoder_->Configure() searches back for the key frame. | |
346 // We have to prefetch decoder. | |
347 | |
348 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); | |
349 SetDataFactory(scoped_ptr<VideoFactory>(new VideoFactory(duration))); | |
350 | |
351 decoder_->Prefetch(base::Bind(&MediaCodecDecoderTest::SetPrefetched, | |
352 base::Unretained(this))); | |
353 | |
354 EXPECT_TRUE(WaitForCondition(base::Bind(&MediaCodecDecoderTest::is_prefetched, | |
355 base::Unretained(this)))); | |
356 | |
357 decoder_->SetDemuxerConfigs(GetConfigs()); | |
358 | |
359 // Surface is not set, Configure() should fail. | |
360 | |
361 EXPECT_EQ(MediaCodecDecoder::CONFIG_FAILURE, decoder_->Configure()); | |
362 } | |
363 | |
364 TEST_F(MediaCodecDecoderTest, VideoConfigureInvalidSurface) { | |
365 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); | |
366 | |
367 CreateVideoDecoder(); | |
368 | |
369 // decoder_->Configure() searches back for the key frame. | |
370 // We have to prefetch decoder. | |
371 | |
372 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); | |
373 SetDataFactory(scoped_ptr<VideoFactory>(new VideoFactory(duration))); | |
374 | |
375 decoder_->Prefetch(base::Bind(&MediaCodecDecoderTest::SetPrefetched, | |
376 base::Unretained(this))); | |
377 | |
378 EXPECT_TRUE(WaitForCondition(base::Bind(&MediaCodecDecoderTest::is_prefetched, | |
379 base::Unretained(this)))); | |
380 | |
381 decoder_->SetDemuxerConfigs(GetConfigs()); | |
382 | |
383 // Prepare the surface. | |
384 scoped_refptr<gfx::SurfaceTexture> surface_texture( | |
385 gfx::SurfaceTexture::Create(0)); | |
386 gfx::ScopedJavaSurface surface(surface_texture.get()); | |
387 | |
388 // Release the surface texture. | |
389 surface_texture = NULL; | |
390 | |
391 MediaCodecVideoDecoder* video_decoder = | |
392 static_cast<MediaCodecVideoDecoder*>(decoder_.get()); | |
393 video_decoder->SetPendingSurface(surface.Pass()); | |
394 | |
395 EXPECT_EQ(MediaCodecDecoder::CONFIG_FAILURE, decoder_->Configure()); | |
396 } | |
397 | |
398 TEST_F(MediaCodecDecoderTest, VideoConfigureValidParams) { | |
399 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); | |
400 | |
401 CreateVideoDecoder(); | |
402 | |
403 // decoder_->Configure() searches back for the key frame. | |
404 // We have to prefetch decoder. | |
405 | |
406 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); | |
407 SetDataFactory(scoped_ptr<VideoFactory>(new VideoFactory(duration))); | |
408 | |
409 decoder_->Prefetch(base::Bind(&MediaCodecDecoderTest::SetPrefetched, | |
410 base::Unretained(this))); | |
411 | |
412 EXPECT_TRUE(WaitForCondition(base::Bind(&MediaCodecDecoderTest::is_prefetched, | |
413 base::Unretained(this)))); | |
414 | |
415 decoder_->SetDemuxerConfigs(GetConfigs()); | |
416 | |
417 SetVideoSurface(); | |
418 | |
419 // Now we can expect Configure() to succeed. | |
420 | |
421 EXPECT_EQ(MediaCodecDecoder::CONFIG_OK, decoder_->Configure()); | |
422 } | |
423 | |
424 TEST_F(MediaCodecDecoderTest, AudioStartWithoutConfigure) { | |
425 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); | |
426 | |
427 CreateAudioDecoder(); | |
428 | |
429 // Decoder has to be prefetched and configured before the start. | |
430 | |
431 // Wrong state: not prefetched | |
432 EXPECT_FALSE(decoder_->Start(base::TimeDelta::FromMilliseconds(0))); | |
433 | |
434 // Do the prefetch. | |
435 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); | |
436 SetDataFactory(scoped_ptr<AudioFactory>(new AudioFactory(duration))); | |
437 | |
438 // Prefetch to avoid starvation at the beginning of playback. | |
439 decoder_->Prefetch(base::Bind(&MediaCodecDecoderTest::SetPrefetched, | |
440 base::Unretained(this))); | |
441 | |
442 EXPECT_TRUE(WaitForCondition(base::Bind(&MediaCodecDecoderTest::is_prefetched, | |
443 base::Unretained(this)))); | |
444 | |
445 // Still, decoder is not configured. | |
446 EXPECT_FALSE(decoder_->Start(base::TimeDelta::FromMilliseconds(0))); | |
447 } | |
448 | |
449 TEST_F(MediaCodecDecoderTest, AudioPlayTillCompletion) { | |
450 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); | |
451 | |
452 CreateAudioDecoder(); | |
453 | |
454 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); | |
455 base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(600); | |
456 | |
457 SetDataFactory(scoped_ptr<AudioFactory>(new AudioFactory(duration))); | |
458 | |
459 // Prefetch to avoid starvation at the beginning of playback. | |
460 decoder_->Prefetch(base::Bind(&MediaCodecDecoderTest::SetPrefetched, | |
461 base::Unretained(this))); | |
462 | |
463 EXPECT_TRUE(WaitForCondition(base::Bind(&MediaCodecDecoderTest::is_prefetched, | |
464 base::Unretained(this)))); | |
465 | |
466 decoder_->SetDemuxerConfigs(GetConfigs()); | |
467 | |
468 EXPECT_EQ(MediaCodecDecoder::CONFIG_OK, decoder_->Configure()); | |
469 | |
470 EXPECT_TRUE(decoder_->Start(base::TimeDelta::FromMilliseconds(0))); | |
471 | |
472 EXPECT_TRUE(WaitForCondition( | |
473 base::Bind(&MediaCodecDecoderTest::is_stopped, base::Unretained(this)), | |
474 timeout)); | |
475 | |
476 EXPECT_TRUE(decoder_->IsStopped()); | |
477 EXPECT_TRUE(decoder_->IsCompleted()); | |
478 | |
479 // It is hard to properly estimate minimum and maximum values because | |
480 // reported times are different from PTS. | |
481 EXPECT_EQ(25, pts_stat_.num_values()); | |
482 } | |
483 | |
484 TEST_F(MediaCodecDecoderTest, VideoPlayTillCompletion) { | |
485 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); | |
486 | |
487 CreateVideoDecoder(); | |
488 | |
489 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); | |
490 base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(600); | |
491 SetDataFactory(scoped_ptr<VideoFactory>(new VideoFactory(duration))); | |
492 | |
493 // Prefetch | |
494 decoder_->Prefetch(base::Bind(&MediaCodecDecoderTest::SetPrefetched, | |
495 base::Unretained(this))); | |
496 | |
497 EXPECT_TRUE(WaitForCondition(base::Bind(&MediaCodecDecoderTest::is_prefetched, | |
498 base::Unretained(this)))); | |
499 | |
500 decoder_->SetDemuxerConfigs(GetConfigs()); | |
501 | |
502 SetVideoSurface(); | |
503 | |
504 EXPECT_EQ(MediaCodecDecoder::CONFIG_OK, decoder_->Configure()); | |
505 | |
506 EXPECT_TRUE(decoder_->Start(base::TimeDelta::FromMilliseconds(0))); | |
507 | |
508 EXPECT_TRUE(WaitForCondition( | |
509 base::Bind(&MediaCodecDecoderTest::is_stopped, base::Unretained(this)), | |
510 timeout)); | |
511 | |
512 EXPECT_TRUE(decoder_->IsStopped()); | |
513 EXPECT_TRUE(decoder_->IsCompleted()); | |
514 | |
515 EXPECT_EQ(26, pts_stat_.num_values()); | |
516 EXPECT_EQ(data_factory_->last_pts(), pts_stat_.max()); | |
517 } | |
518 | |
519 } // namespace media | |
OLD | NEW |