1 // Copyright (c) 2012 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 "media/filters/pipeline_integration_test_base.h"
6
7 #include "base/bind.h"
8 #include "base/memory/scoped_vector.h"
9 #include "media/base/clock.h"
10 #include "media/base/media_log.h"
11 #include "media/filters/audio_renderer_impl.h"
12 #include "media/filters/chunk_demuxer.h"
13 #include "media/filters/ffmpeg_audio_decoder.h"
14 #include "media/filters/ffmpeg_demuxer.h"
15 #include "media/filters/ffmpeg_video_decoder.h"
16 #include "media/filters/file_data_source.h"
17 #include "media/filters/opus_audio_decoder.h"
18 #include "media/filters/vpx_video_decoder.h"
19
20 using ::testing::AnyNumber;
21 using ::testing::AtMost;
22
23 namespace media {
24
25 const char kNullVideoHash[] = "d41d8cd98f00b204e9800998ecf8427e";
26 const char kNullAudioHash[] = "0.00,0.00,0.00,0.00,0.00,0.00,";
27
PipelineIntegrationTestBase()28 PipelineIntegrationTestBase::PipelineIntegrationTestBase()
29 : hashing_enabled_(false),
30 clockless_playback_(false),
31 pipeline_(new Pipeline(message_loop_.message_loop_proxy(),
32 new MediaLog())),
33 ended_(false),
34 pipeline_status_(PIPELINE_OK),
35 last_video_frame_format_(VideoFrame::UNKNOWN) {
36 base::MD5Init(&md5_context_);
37 EXPECT_CALL(*this, OnSetOpaque(true)).Times(AnyNumber());
38 }
39
~PipelineIntegrationTestBase()40 PipelineIntegrationTestBase::~PipelineIntegrationTestBase() {
41 if (!pipeline_->IsRunning())
42 return;
43
44 Stop();
45 }
46
OnStatusCallback(PipelineStatus status)47 void PipelineIntegrationTestBase::OnStatusCallback(
48 PipelineStatus status) {
49 pipeline_status_ = status;
50 message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
51 }
52
OnStatusCallbackChecked(PipelineStatus expected_status,PipelineStatus status)53 void PipelineIntegrationTestBase::OnStatusCallbackChecked(
54 PipelineStatus expected_status,
55 PipelineStatus status) {
56 EXPECT_EQ(expected_status, status);
57 OnStatusCallback(status);
58 }
59
QuitOnStatusCB(PipelineStatus expected_status)60 PipelineStatusCB PipelineIntegrationTestBase::QuitOnStatusCB(
61 PipelineStatus expected_status) {
62 return base::Bind(&PipelineIntegrationTestBase::OnStatusCallbackChecked,
63 base::Unretained(this),
64 expected_status);
65 }
66
DemuxerNeedKeyCB(const std::string & type,const std::vector<uint8> & init_data)67 void PipelineIntegrationTestBase::DemuxerNeedKeyCB(
68 const std::string& type,
69 const std::vector<uint8>& init_data) {
70 DCHECK(!init_data.empty());
71 CHECK(!need_key_cb_.is_null());
72 need_key_cb_.Run(type, init_data);
73 }
74
OnEnded()75 void PipelineIntegrationTestBase::OnEnded() {
76 DCHECK(!ended_);
77 ended_ = true;
78 pipeline_status_ = PIPELINE_OK;
79 message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
80 }
81
WaitUntilOnEnded()82 bool PipelineIntegrationTestBase::WaitUntilOnEnded() {
83 if (ended_)
84 return (pipeline_status_ == PIPELINE_OK);
85 message_loop_.Run();
86 EXPECT_TRUE(ended_);
87 return ended_ && (pipeline_status_ == PIPELINE_OK);
88 }
89
WaitUntilEndedOrError()90 PipelineStatus PipelineIntegrationTestBase::WaitUntilEndedOrError() {
91 if (ended_ || pipeline_status_ != PIPELINE_OK)
92 return pipeline_status_;
93 message_loop_.Run();
94 return pipeline_status_;
95 }
96
OnError(PipelineStatus status)97 void PipelineIntegrationTestBase::OnError(PipelineStatus status) {
98 DCHECK_NE(status, PIPELINE_OK);
99 pipeline_status_ = status;
100 message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
101 }
102
Start(const base::FilePath & file_path,PipelineStatus expected_status)103 bool PipelineIntegrationTestBase::Start(const base::FilePath& file_path,
104 PipelineStatus expected_status) {
105 EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata))
106 .Times(AtMost(1));
107 EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
108 .Times(AtMost(1));
109 pipeline_->Start(
110 CreateFilterCollection(file_path, NULL),
111 base::Bind(&PipelineIntegrationTestBase::OnEnded, base::Unretained(this)),
112 base::Bind(&PipelineIntegrationTestBase::OnError, base::Unretained(this)),
113 QuitOnStatusCB(expected_status),
114 base::Bind(&PipelineIntegrationTestBase::OnBufferingState,
115 base::Unretained(this)),
116 base::Closure());
117 message_loop_.Run();
118 return (pipeline_status_ == PIPELINE_OK);
119 }
120
Start(const base::FilePath & file_path,PipelineStatus expected_status,kTestType test_type)121 bool PipelineIntegrationTestBase::Start(const base::FilePath& file_path,
122 PipelineStatus expected_status,
123 kTestType test_type) {
124 hashing_enabled_ = test_type == kHashed;
125 clockless_playback_ = test_type == kClockless;
126 if (clockless_playback_) {
127 pipeline_->SetClockForTesting(new Clock(&dummy_clock_));
128 }
129 return Start(file_path, expected_status);
130 }
131
Start(const base::FilePath & file_path)132 bool PipelineIntegrationTestBase::Start(const base::FilePath& file_path) {
133 return Start(file_path, NULL);
134 }
135
Start(const base::FilePath & file_path,Decryptor * decryptor)136 bool PipelineIntegrationTestBase::Start(const base::FilePath& file_path,
137 Decryptor* decryptor) {
138 EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata))
139 .Times(AtMost(1));
140 EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
141 .Times(AtMost(1));
142 pipeline_->Start(
143 CreateFilterCollection(file_path, decryptor),
144 base::Bind(&PipelineIntegrationTestBase::OnEnded, base::Unretained(this)),
145 base::Bind(&PipelineIntegrationTestBase::OnError, base::Unretained(this)),
146 base::Bind(&PipelineIntegrationTestBase::OnStatusCallback,
147 base::Unretained(this)),
148 base::Bind(&PipelineIntegrationTestBase::OnBufferingState,
149 base::Unretained(this)),
150 base::Closure());
151 message_loop_.Run();
152 return (pipeline_status_ == PIPELINE_OK);
153 }
154
Play()155 void PipelineIntegrationTestBase::Play() {
156 pipeline_->SetPlaybackRate(1);
157 }
158
Pause()159 void PipelineIntegrationTestBase::Pause() {
160 pipeline_->SetPlaybackRate(0);
161 }
162
Seek(base::TimeDelta seek_time)163 bool PipelineIntegrationTestBase::Seek(base::TimeDelta seek_time) {
164 ended_ = false;
165
166 EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted));
167 pipeline_->Seek(seek_time, QuitOnStatusCB(PIPELINE_OK));
168 message_loop_.Run();
169 return (pipeline_status_ == PIPELINE_OK);
170 }
171
Stop()172 void PipelineIntegrationTestBase::Stop() {
173 DCHECK(pipeline_->IsRunning());
174 pipeline_->Stop(base::MessageLoop::QuitClosure());
175 message_loop_.Run();
176 }
177
QuitAfterCurrentTimeTask(const base::TimeDelta & quit_time)178 void PipelineIntegrationTestBase::QuitAfterCurrentTimeTask(
179 const base::TimeDelta& quit_time) {
180 if (pipeline_->GetMediaTime() >= quit_time ||
181 pipeline_status_ != PIPELINE_OK) {
182 message_loop_.Quit();
183 return;
184 }
185
186 message_loop_.PostDelayedTask(
187 FROM_HERE,
188 base::Bind(&PipelineIntegrationTestBase::QuitAfterCurrentTimeTask,
189 base::Unretained(this), quit_time),
190 base::TimeDelta::FromMilliseconds(10));
191 }
192
WaitUntilCurrentTimeIsAfter(const base::TimeDelta & wait_time)193 bool PipelineIntegrationTestBase::WaitUntilCurrentTimeIsAfter(
194 const base::TimeDelta& wait_time) {
195 DCHECK(pipeline_->IsRunning());
196 DCHECK_GT(pipeline_->GetPlaybackRate(), 0);
197 DCHECK(wait_time <= pipeline_->GetMediaDuration());
198
199 message_loop_.PostDelayedTask(
200 FROM_HERE,
201 base::Bind(&PipelineIntegrationTestBase::QuitAfterCurrentTimeTask,
202 base::Unretained(this),
203 wait_time),
204 base::TimeDelta::FromMilliseconds(10));
205 message_loop_.Run();
206 return (pipeline_status_ == PIPELINE_OK);
207 }
208
209 scoped_ptr<FilterCollection>
CreateFilterCollection(const base::FilePath & file_path,Decryptor * decryptor)210 PipelineIntegrationTestBase::CreateFilterCollection(
211 const base::FilePath& file_path,
212 Decryptor* decryptor) {
213 FileDataSource* file_data_source = new FileDataSource();
214 CHECK(file_data_source->Initialize(file_path));
215 data_source_.reset(file_data_source);
216
217 Demuxer::NeedKeyCB need_key_cb = base::Bind(
218 &PipelineIntegrationTestBase::DemuxerNeedKeyCB, base::Unretained(this));
219 scoped_ptr<Demuxer> demuxer(
220 new FFmpegDemuxer(message_loop_.message_loop_proxy(),
221 data_source_.get(),
222 need_key_cb,
223 new MediaLog()));
224 return CreateFilterCollection(demuxer.Pass(), decryptor);
225 }
226
227 scoped_ptr<FilterCollection>
CreateFilterCollection(scoped_ptr<Demuxer> demuxer,Decryptor * decryptor)228 PipelineIntegrationTestBase::CreateFilterCollection(
229 scoped_ptr<Demuxer> demuxer,
230 Decryptor* decryptor) {
231 demuxer_ = demuxer.Pass();
232
233 scoped_ptr<FilterCollection> collection(new FilterCollection());
234 collection->SetDemuxer(demuxer_.get());
235
236 ScopedVector<VideoDecoder> video_decoders;
237 video_decoders.push_back(
238 new VpxVideoDecoder(message_loop_.message_loop_proxy()));
239 video_decoders.push_back(
240 new FFmpegVideoDecoder(message_loop_.message_loop_proxy()));
241
242 // Disable frame dropping if hashing is enabled.
243 scoped_ptr<VideoRenderer> renderer(new VideoRendererImpl(
244 message_loop_.message_loop_proxy(),
245 video_decoders.Pass(),
246 base::Bind(&PipelineIntegrationTestBase::SetDecryptor,
247 base::Unretained(this),
248 decryptor),
249 base::Bind(&PipelineIntegrationTestBase::OnVideoRendererPaint,
250 base::Unretained(this)),
251 base::Bind(&PipelineIntegrationTestBase::OnSetOpaque,
252 base::Unretained(this)),
253 false));
254 collection->SetVideoRenderer(renderer.Pass());
255
256 if (!clockless_playback_) {
257 audio_sink_ = new NullAudioSink(message_loop_.message_loop_proxy());
258 } else {
259 clockless_audio_sink_ = new ClocklessAudioSink();
260 }
261
262 ScopedVector<AudioDecoder> audio_decoders;
263 audio_decoders.push_back(
264 new FFmpegAudioDecoder(message_loop_.message_loop_proxy()));
265 audio_decoders.push_back(
266 new OpusAudioDecoder(message_loop_.message_loop_proxy()));
267
268 AudioRendererImpl* audio_renderer_impl = new AudioRendererImpl(
269 message_loop_.message_loop_proxy(),
270 (clockless_playback_)
271 ? static_cast<AudioRendererSink*>(clockless_audio_sink_.get())
272 : audio_sink_.get(),
273 audio_decoders.Pass(),
274 base::Bind(&PipelineIntegrationTestBase::SetDecryptor,
275 base::Unretained(this),
276 decryptor));
277 // Disable underflow if hashing is enabled.
278 if (hashing_enabled_) {
279 audio_sink_->StartAudioHashForTesting();
280 audio_renderer_impl->DisableUnderflowForTesting();
281 }
282 scoped_ptr<AudioRenderer> audio_renderer(audio_renderer_impl);
283 collection->SetAudioRenderer(audio_renderer.Pass());
284
285 return collection.Pass();
286 }
287
SetDecryptor(Decryptor * decryptor,const DecryptorReadyCB & decryptor_ready_cb)288 void PipelineIntegrationTestBase::SetDecryptor(
289 Decryptor* decryptor,
290 const DecryptorReadyCB& decryptor_ready_cb) {
291 decryptor_ready_cb.Run(decryptor);
292 }
293
OnVideoRendererPaint(const scoped_refptr<VideoFrame> & frame)294 void PipelineIntegrationTestBase::OnVideoRendererPaint(
295 const scoped_refptr<VideoFrame>& frame) {
296 last_video_frame_format_ = frame->format();
297 if (!hashing_enabled_)
298 return;
299 frame->HashFrameForTesting(&md5_context_);
300 }
301
GetVideoHash()302 std::string PipelineIntegrationTestBase::GetVideoHash() {
303 DCHECK(hashing_enabled_);
304 base::MD5Digest digest;
305 base::MD5Final(&digest, &md5_context_);
306 return base::MD5DigestToBase16(digest);
307 }
308
GetAudioHash()309 std::string PipelineIntegrationTestBase::GetAudioHash() {
310 DCHECK(hashing_enabled_);
311 return audio_sink_->GetAudioHashForTesting();
312 }
313
GetAudioTime()314 base::TimeDelta PipelineIntegrationTestBase::GetAudioTime() {
315 DCHECK(clockless_playback_);
316 return clockless_audio_sink_->render_time();
317 }
318
NowTicks()319 base::TimeTicks DummyTickClock::NowTicks() {
320 now_ += base::TimeDelta::FromSeconds(60);
321 return now_;
322 }
323
324 } // namespace media
325