1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
media / test / pipeline_integration_test_base.h [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_TEST_PIPELINE_INTEGRATION_TEST_BASE_H_
#define MEDIA_TEST_PIPELINE_INTEGRATION_TEST_BASE_H_
#include <stdint.h>
#include <memory>
#include "base/functional/callback_forward.h"
#include "base/hash/md5.h"
#include "base/run_loop.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "media/audio/clockless_audio_sink.h"
#include "media/audio/null_audio_sink.h"
#include "media/base/demuxer.h"
#include "media/base/media_switches.h"
#include "media/base/mock_media_log.h"
#include "media/base/null_video_sink.h"
#include "media/base/pipeline_impl.h"
#include "media/base/pipeline_status.h"
#include "media/base/video_frame.h"
#include "media/renderers/audio_renderer_impl.h"
#include "media/renderers/video_renderer_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_com_initializer.h"
#endif // BUILDFLAG(IS_WIN)
using ::testing::NiceMock;
namespace base {
class RunLoop;
}
namespace media {
class FakeEncryptedMedia;
class TestMediaSource;
// Empty MD5 hash string. Used to verify empty video tracks.
extern const char kNullVideoHash[];
// Empty hash string. Used to verify empty audio tracks.
extern const char kNullAudioHash[];
// Integration tests for Pipeline. Real demuxers, real decoders, and
// base renderer implementations are used to verify pipeline functionality. The
// renderers used in these tests rely heavily on the AudioRendererBase &
// VideoRendererImpl implementations which contain a majority of the code used
// in the real AudioRendererImpl & PaintCanvasVideoRenderer implementations used
// in the browser. The renderers in this test don't actually write data to a
// display or audio device. Both of these devices are simulated since they have
// little effect on verifying pipeline behavior and allow tests to run faster
// than real-time.
class PipelineIntegrationTestBase : public Pipeline::Client {
public:
PipelineIntegrationTestBase();
PipelineIntegrationTestBase(const PipelineIntegrationTestBase&) = delete;
PipelineIntegrationTestBase& operator=(const PipelineIntegrationTestBase&) =
delete;
virtual ~PipelineIntegrationTestBase();
// Test types for advanced testing and benchmarking (e.g., underflow is
// disabled to ensure consistent hashes). May be combined using the bitwise
// or operator (and as such must have values that are powers of two).
enum TestTypeFlags {
kNormal = 0,
kHashed = 1,
kNoClockless = 2,
kExpectDemuxerFailure = 4,
kUnreliableDuration = 8,
kWebAudio = 16,
kMonoOutput = 32,
kFuzzing = 64,
};
// Setup method to initialize various state according to flags.
void ParseTestTypeFlags(uint8_t flags);
// Starts the pipeline with a file specified by |filename|, optionally with a
// CdmContext or a |test_type|, returning the final status code after it has
// started. |filename| points at a test file located under media/test/data/.
PipelineStatus Start(const std::string& filename);
PipelineStatus Start(const std::string& filename, CdmContext* cdm_context);
PipelineStatus Start(
const std::string& filename,
uint8_t test_type,
CreateVideoDecodersCB prepend_video_decoders_cb = CreateVideoDecodersCB(),
CreateAudioDecodersCB prepend_audio_decoders_cb =
CreateAudioDecodersCB());
// Starts the pipeline with |data| (with |size| bytes). The |data| will be
// valid throughtout the lifetime of this test.
PipelineStatus Start(const uint8_t* data, size_t size, uint8_t test_type);
void Play();
void Pause();
bool Seek(base::TimeDelta seek_time);
bool Suspend();
bool Resume(base::TimeDelta seek_time);
void Stop();
// Fails the test with |status|.
void FailTest(PipelineStatus status);
bool WaitUntilCurrentTimeIsAfter(const base::TimeDelta& wait_time);
bool WaitUntilOnEnded();
PipelineStatus WaitUntilEndedOrError();
// Returns the MD5 hash of all video frames seen. Should only be called once
// after playback completes. First time hashes should be generated with
// --video-threads=1 to ensure correctness. Pipeline must have been started
// with hashing enabled.
std::string GetVideoHash();
// Returns the hash of all audio frames seen. Should only be called once
// after playback completes. Pipeline must have been started with hashing
// enabled.
const AudioHash& GetAudioHash() const;
// Reset video hash to restart hashing from scratch (e.g. after a seek or
// after disabling a media track).
void ResetVideoHash();
// Returns the time taken to render the complete audio file.
// Pipeline must have been started with clockless playback enabled.
base::TimeDelta GetAudioTime();
// Sets a callback to handle EME "encrypted" event. Must be called to test
// potentially encrypted media.
void set_encrypted_media_init_data_cb(
const Demuxer::EncryptedMediaInitDataCB& encrypted_media_init_data_cb) {
encrypted_media_init_data_cb_ = encrypted_media_init_data_cb;
}
// Saves a test callback, ownership of which will be transferred to the next
// AudioRendererImpl created by CreateRendererImpl().
void set_audio_play_delay_cb(AudioRendererImpl::PlayDelayCBForTesting cb) {
audio_play_delay_cb_ = std::move(cb);
}
std::unique_ptr<Renderer> CreateRenderer(
std::optional<RendererType> renderer_type);
protected:
NiceMock<MockMediaLog> media_log_;
base::test::TaskEnvironment task_environment_;
base::MD5Context md5_context_;
bool hashing_enabled_;
bool clockless_playback_;
bool webaudio_attached_;
bool mono_output_;
bool fuzzing_;
#if defined(ADDRESS_SANITIZER) || defined(UNDEFINED_SANITIZER)
// TODO(crbug.com/40610469): ASAN causes Run() timeouts to be reached.
const base::test::ScopedDisableRunLoopTimeout disable_run_timeout_;
#endif
std::unique_ptr<Demuxer> demuxer_;
std::unique_ptr<DataSource> data_source_;
std::unique_ptr<PipelineImpl> pipeline_;
scoped_refptr<NullAudioSink> audio_sink_;
scoped_refptr<ClocklessAudioSink> clockless_audio_sink_;
std::unique_ptr<NullVideoSink> video_sink_;
bool ended_;
PipelineStatus pipeline_status_;
Demuxer::EncryptedMediaInitDataCB encrypted_media_init_data_cb_;
VideoPixelFormat last_video_frame_format_;
gfx::ColorSpace last_video_frame_color_space_;
PipelineMetadata metadata_;
scoped_refptr<VideoFrame> last_frame_;
base::TimeDelta current_duration_;
AudioRendererImpl::PlayDelayCBForTesting audio_play_delay_cb_;
// By default RendererImpl will be created using CreateRendererImpl(). But
// if |create_renderer_cb_| is set, it'll be used to create the Renderer
// instead.
using CreateRendererCB = base::RepeatingCallback<std::unique_ptr<Renderer>(
std::optional<RendererType> renderer_type)>;
CreateRendererCB create_renderer_cb_;
std::unique_ptr<Renderer> CreateRendererImpl(
std::optional<RendererType> renderer_type);
// Sets |create_renderer_cb_| which will be used to wrap the Renderer created
// by CreateRendererImpl().
void SetCreateRendererCB(CreateRendererCB create_renderer_cb);
PipelineStatus StartInternal(
std::unique_ptr<DataSource> data_source,
CdmContext* cdm_context,
uint8_t test_type,
CreateVideoDecodersCB prepend_video_decoders_cb = CreateVideoDecodersCB(),
CreateAudioDecodersCB prepend_audio_decoders_cb =
CreateAudioDecodersCB());
PipelineStatus StartWithFile(
const std::string& filename,
CdmContext* cdm_context,
uint8_t test_type,
CreateVideoDecodersCB prepend_video_decoders_cb = CreateVideoDecodersCB(),
CreateAudioDecodersCB prepend_audio_decoders_cb =
CreateAudioDecodersCB());
PipelineStatus StartPipelineWithMediaSource(TestMediaSource* source);
PipelineStatus StartPipelineWithEncryptedMedia(
TestMediaSource* source,
FakeEncryptedMedia* encrypted_media);
PipelineStatus StartPipelineWithMediaSource(
TestMediaSource* source,
uint8_t test_type,
FakeEncryptedMedia* encrypted_media);
PipelineStatus StartPipelineWithMediaSource(
TestMediaSource* source,
uint8_t test_type,
CreateAudioDecodersCB prepend_audio_decoders_cb);
#if BUILDFLAG(ENABLE_HLS_DEMUXER)
PipelineStatus StartPipelineWithHlsManifest(const std::string& filename);
#endif // BUILDFLAG(ENABLE_HLS_DEMUXER)
void OnSeeked(base::TimeDelta seek_time, PipelineStatus status);
void OnStatusCallback(const base::RepeatingClosure& quit_run_loop_closure,
PipelineStatus status);
void DemuxerEncryptedMediaInitDataCB(EmeInitDataType type,
const std::vector<uint8_t>& init_data);
void DemuxerMediaTracksUpdatedCB(std::unique_ptr<MediaTracks> tracks);
void QuitAfterCurrentTimeTask(base::TimeDelta quit_time,
base::OnceClosure quit_closure);
// Creates Demuxer and sets |demuxer_|.
void CreateDemuxer(std::unique_ptr<DataSource> data_source);
void OnVideoFramePaint(scoped_refptr<VideoFrame> frame);
void CheckDuration();
// Return the media start time from |demuxer_|.
base::TimeDelta GetStartTime();
MOCK_METHOD1(DecryptorAttached, void(bool));
// Pipeline::Client overrides.
void OnError(PipelineStatus status) override;
void OnFallback(PipelineStatus status) override;
void OnEnded() override;
MOCK_METHOD1(OnMetadata, void(const PipelineMetadata&));
MOCK_METHOD2(OnBufferingStateChange,
void(BufferingState, BufferingStateChangeReason));
MOCK_METHOD0(OnDurationChange, void());
MOCK_METHOD1(OnWaiting, void(WaitingReason));
MOCK_METHOD1(OnVideoNaturalSizeChange, void(const gfx::Size&));
MOCK_METHOD1(OnVideoConfigChange, void(const VideoDecoderConfig&));
MOCK_METHOD1(OnAudioConfigChange, void(const AudioDecoderConfig&));
MOCK_METHOD1(OnVideoOpacityChange, void(bool));
MOCK_METHOD1(OnVideoFrameRateChange, void(std::optional<int>));
MOCK_METHOD0(OnVideoAverageKeyframeDistanceUpdate, void());
MOCK_METHOD1(OnAudioPipelineInfoChange, void(const AudioPipelineInfo&));
MOCK_METHOD1(OnVideoPipelineInfoChange, void(const VideoPipelineInfo&));
MOCK_METHOD1(OnRemotePlayStateChange, void(MediaStatus::State state));
private:
// Runs |run_loop| until it is explicitly Quit() by some part of the calling
// test fixture or when an error occurs (by setting |on_error_closure_|). The
// |task_environment_| is RunUntilIdle() after the RunLoop finishes
// running, before returning to the caller.
void RunUntilQuitOrError(base::RunLoop* run_loop);
// Configures |on_ended_closure_| to quit |run_loop| and then calls
// RunUntilQuitOrError() on it.
void RunUntilQuitOrEndedOrError(base::RunLoop* run_loop);
// Implementation of `Pipeline::Client::OnBufferingStateChange()` used during
// seeks. This handles failed seeks as well as successful ones, which have
// different behavior around exiting the seek.
void OnBufferingStateChangeForSeek(BufferingState state,
BufferingStateChangeReason reason);
#if BUILDFLAG(IS_WIN)
// MediaFoundationAudioDecoder calls CoInitialize() when creating the decoder.
base::win::ScopedCOMInitializer com_initializer_;
#endif // BUILDFLAG(IS_WIN)
CreateVideoDecodersCB prepend_video_decoders_cb_;
CreateAudioDecodersCB prepend_audio_decoders_cb_;
// First buffering state we get from the pipeline.
std::optional<BufferingState> buffering_state_;
base::OnceClosure on_ended_closure_;
base::OnceClosure on_error_closure_;
};
} // namespace media
#endif // MEDIA_TEST_PIPELINE_INTEGRATION_TEST_BASE_H_