Bug 1846191 ref-count MockCubeb to ensure that it lives as long as the audio thread...
[gecko.git] / dom / media / gtest / MockCubeb.h
blobd84ed46a87977eae67c5a3ac10843de564eb54ca
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #ifndef MOCKCUBEB_H_
6 #define MOCKCUBEB_H_
8 #include "AudioDeviceInfo.h"
9 #include "AudioGenerator.h"
10 #include "AudioVerifier.h"
11 #include "MediaEventSource.h"
12 #include "mozilla/DataMutex.h"
13 #include "mozilla/ThreadSafeWeakPtr.h"
14 #include "nsTArray.h"
16 #include <thread>
17 #include <atomic>
18 #include <chrono>
20 namespace mozilla {
21 const uint32_t MAX_OUTPUT_CHANNELS = 2;
22 const uint32_t MAX_INPUT_CHANNELS = 2;
24 struct cubeb_ops {
25 int (*init)(cubeb** context, char const* context_name);
26 char const* (*get_backend_id)(cubeb* context);
27 int (*get_max_channel_count)(cubeb* context, uint32_t* max_channels);
28 int (*get_min_latency)(cubeb* context, cubeb_stream_params params,
29 uint32_t* latency_ms);
30 int (*get_preferred_sample_rate)(cubeb* context, uint32_t* rate);
31 int (*enumerate_devices)(cubeb* context, cubeb_device_type type,
32 cubeb_device_collection* collection);
33 int (*device_collection_destroy)(cubeb* context,
34 cubeb_device_collection* collection);
35 void (*destroy)(cubeb* context);
36 int (*stream_init)(cubeb* context, cubeb_stream** stream,
37 char const* stream_name, cubeb_devid input_device,
38 cubeb_stream_params* input_stream_params,
39 cubeb_devid output_device,
40 cubeb_stream_params* output_stream_params,
41 unsigned int latency, cubeb_data_callback data_callback,
42 cubeb_state_callback state_callback, void* user_ptr);
43 void (*stream_destroy)(cubeb_stream* stream);
44 int (*stream_start)(cubeb_stream* stream);
45 int (*stream_stop)(cubeb_stream* stream);
46 int (*stream_get_position)(cubeb_stream* stream, uint64_t* position);
47 int (*stream_get_latency)(cubeb_stream* stream, uint32_t* latency);
48 int (*stream_get_input_latency)(cubeb_stream* stream, uint32_t* latency);
49 int (*stream_set_volume)(cubeb_stream* stream, float volumes);
50 int (*stream_set_name)(cubeb_stream* stream, char const* stream_name);
51 int (*stream_get_current_device)(cubeb_stream* stream,
52 cubeb_device** const device);
53 int (*stream_device_destroy)(cubeb_stream* stream, cubeb_device* device);
54 int (*stream_register_device_changed_callback)(
55 cubeb_stream* stream,
56 cubeb_device_changed_callback device_changed_callback);
57 int (*register_device_collection_changed)(
58 cubeb* context, cubeb_device_type devtype,
59 cubeb_device_collection_changed_callback callback, void* user_ptr);
62 // Keep those and the struct definition in sync with cubeb.h and
63 // cubeb-internal.h
64 void cubeb_mock_destroy(cubeb* context);
65 static int cubeb_mock_enumerate_devices(cubeb* context, cubeb_device_type type,
66 cubeb_device_collection* out);
68 static int cubeb_mock_device_collection_destroy(
69 cubeb* context, cubeb_device_collection* collection);
71 static int cubeb_mock_register_device_collection_changed(
72 cubeb* context, cubeb_device_type devtype,
73 cubeb_device_collection_changed_callback callback, void* user_ptr);
75 static int cubeb_mock_stream_init(
76 cubeb* context, cubeb_stream** stream, char const* stream_name,
77 cubeb_devid input_device, cubeb_stream_params* input_stream_params,
78 cubeb_devid output_device, cubeb_stream_params* output_stream_params,
79 unsigned int latency, cubeb_data_callback data_callback,
80 cubeb_state_callback state_callback, void* user_ptr);
82 static int cubeb_mock_stream_start(cubeb_stream* stream);
84 static int cubeb_mock_stream_stop(cubeb_stream* stream);
86 static int cubeb_mock_stream_get_position(cubeb_stream* stream,
87 uint64_t* position);
89 static void cubeb_mock_stream_destroy(cubeb_stream* stream);
91 static char const* cubeb_mock_get_backend_id(cubeb* context);
93 static int cubeb_mock_stream_set_volume(cubeb_stream* stream, float volume);
95 static int cubeb_mock_stream_set_name(cubeb_stream* stream,
96 char const* stream_name);
98 static int cubeb_mock_stream_register_device_changed_callback(
99 cubeb_stream* stream,
100 cubeb_device_changed_callback device_changed_callback);
102 static int cubeb_mock_get_min_latency(cubeb* context,
103 cubeb_stream_params params,
104 uint32_t* latency_ms);
106 static int cubeb_mock_get_preferred_sample_rate(cubeb* context, uint32_t* rate);
108 static int cubeb_mock_get_max_channel_count(cubeb* context,
109 uint32_t* max_channels);
111 // Mock cubeb impl, only supports device enumeration for now.
112 cubeb_ops const mock_ops = {
113 /*.init =*/NULL,
114 /*.get_backend_id =*/cubeb_mock_get_backend_id,
115 /*.get_max_channel_count =*/cubeb_mock_get_max_channel_count,
116 /*.get_min_latency =*/cubeb_mock_get_min_latency,
117 /*.get_preferred_sample_rate =*/cubeb_mock_get_preferred_sample_rate,
118 /*.enumerate_devices =*/cubeb_mock_enumerate_devices,
119 /*.device_collection_destroy =*/cubeb_mock_device_collection_destroy,
120 /*.destroy =*/cubeb_mock_destroy,
121 /*.stream_init =*/cubeb_mock_stream_init,
122 /*.stream_destroy =*/cubeb_mock_stream_destroy,
123 /*.stream_start =*/cubeb_mock_stream_start,
124 /*.stream_stop =*/cubeb_mock_stream_stop,
125 /*.stream_get_position =*/cubeb_mock_stream_get_position,
126 /*.stream_get_latency =*/NULL,
127 /*.stream_get_input_latency =*/NULL,
128 /*.stream_set_volume =*/cubeb_mock_stream_set_volume,
129 /*.stream_set_name =*/cubeb_mock_stream_set_name,
130 /*.stream_get_current_device =*/NULL,
131 /*.stream_device_destroy =*/NULL,
132 /*.stream_register_device_changed_callback =*/
133 cubeb_mock_stream_register_device_changed_callback,
134 /*.register_device_collection_changed =*/
136 cubeb_mock_register_device_collection_changed};
138 class SmartMockCubebStream;
140 // Represents the fake cubeb_stream. The context instance is needed to
141 // provide access on cubeb_ops struct.
142 class MockCubebStream {
143 // These members need to have the exact same memory layout as a real
144 // cubeb_stream, so that AsMock() returns a pointer to this that can be used
145 // as a cubeb_stream.
146 cubeb* context;
147 void* mUserPtr;
149 public:
150 MockCubebStream(cubeb* aContext, cubeb_devid aInputDevice,
151 cubeb_stream_params* aInputStreamParams,
152 cubeb_devid aOutputDevice,
153 cubeb_stream_params* aOutputStreamParams,
154 cubeb_data_callback aDataCallback,
155 cubeb_state_callback aStateCallback, void* aUserPtr,
156 SmartMockCubebStream* aSelf, bool aFrozenStart);
158 ~MockCubebStream();
160 int Start();
161 int Stop();
162 uint64_t Position();
163 void Destroy();
164 int RegisterDeviceChangedCallback(
165 cubeb_device_changed_callback aDeviceChangedCallback);
167 cubeb_stream* AsCubebStream();
168 static MockCubebStream* AsMock(cubeb_stream* aStream);
170 cubeb_devid GetInputDeviceID() const;
171 cubeb_devid GetOutputDeviceID() const;
173 uint32_t InputChannels() const;
174 uint32_t OutputChannels() const;
175 uint32_t InputSampleRate() const;
176 uint32_t InputFrequency() const;
178 void SetDriftFactor(float aDriftFactor);
179 void ForceError();
180 void ForceDeviceChanged();
181 void Thaw();
183 // Enable input recording for this driver. This is best called before
184 // the thread is running, but is safe to call whenever.
185 void SetOutputRecordingEnabled(bool aEnabled);
186 // Enable input recording for this driver. This is best called before
187 // the thread is running, but is safe to call whenever.
188 void SetInputRecordingEnabled(bool aEnabled);
189 // Get the recorded output from this stream. This doesn't copy, and therefore
190 // only works once.
191 nsTArray<AudioDataValue>&& TakeRecordedOutput();
192 // Get the recorded input from this stream. This doesn't copy, and therefore
193 // only works once.
194 nsTArray<AudioDataValue>&& TakeRecordedInput();
196 MediaEventSource<cubeb_state>& StateEvent();
197 MediaEventSource<uint32_t>& FramesProcessedEvent();
198 MediaEventSource<uint32_t>& FramesVerifiedEvent();
199 MediaEventSource<std::tuple<uint64_t, float, uint32_t>>&
200 OutputVerificationEvent();
201 MediaEventSource<void>& ErrorForcedEvent();
202 MediaEventSource<void>& DeviceChangeForcedEvent();
204 enum class KeepProcessing { No, Yes };
205 KeepProcessing Process10Ms();
207 public:
208 const bool mHasInput;
209 const bool mHasOutput;
210 SmartMockCubebStream* const mSelf;
212 private:
213 void NotifyState(cubeb_state aState);
215 // Monitor used to block start until mFrozenStart is false.
216 Monitor mFrozenStartMonitor MOZ_UNANNOTATED;
217 // Whether this stream should wait for an explicit start request before
218 // starting. Protected by FrozenStartMonitor.
219 bool mFrozenStart;
220 // Used to abort a frozen start if cubeb_stream_start() is called currently
221 // with a blocked cubeb_stream_start() call.
222 std::atomic_bool mStreamStop{true};
223 // Whether or not the output-side of this stream (what is written from the
224 // callback output buffer) is recorded in an internal buffer. The data is then
225 // available via `GetRecordedOutput`.
226 std::atomic_bool mOutputRecordingEnabled{false};
227 // Whether or not the input-side of this stream (what is written from the
228 // callback input buffer) is recorded in an internal buffer. The data is then
229 // available via `TakeRecordedInput`.
230 std::atomic_bool mInputRecordingEnabled{false};
231 // The audio buffer used on data callback.
232 AudioDataValue mOutputBuffer[MAX_OUTPUT_CHANNELS * 1920] = {};
233 AudioDataValue mInputBuffer[MAX_INPUT_CHANNELS * 1920] = {};
234 // The audio callback
235 cubeb_data_callback mDataCallback = nullptr;
236 // The stream state callback
237 cubeb_state_callback mStateCallback = nullptr;
238 // The device changed callback
239 cubeb_device_changed_callback mDeviceChangedCallback = nullptr;
240 // The stream params
241 cubeb_stream_params mOutputParams = {};
242 cubeb_stream_params mInputParams = {};
243 /* Device IDs */
244 cubeb_devid mInputDeviceID;
245 cubeb_devid mOutputDeviceID;
247 std::atomic<float> mDriftFactor{1.0};
248 std::atomic_bool mFastMode{false};
249 std::atomic_bool mForceErrorState{false};
250 std::atomic_bool mForceDeviceChanged{false};
251 std::atomic_bool mDestroyed{false};
252 std::atomic<uint64_t> mPosition{0};
253 AudioGenerator<AudioDataValue> mAudioGenerator;
254 AudioVerifier<AudioDataValue> mAudioVerifier;
256 MediaEventProducer<cubeb_state> mStateEvent;
257 MediaEventProducer<uint32_t> mFramesProcessedEvent;
258 MediaEventProducer<uint32_t> mFramesVerifiedEvent;
259 MediaEventProducer<std::tuple<uint64_t, float, uint32_t>>
260 mOutputVerificationEvent;
261 MediaEventProducer<void> mErrorForcedEvent;
262 MediaEventProducer<void> mDeviceChangedForcedEvent;
263 // The recorded data, copied from the output_buffer of the callback.
264 // Interleaved.
265 nsTArray<AudioDataValue> mRecordedOutput;
266 // The recorded data, copied from the input buffer of the callback.
267 // Interleaved.
268 nsTArray<AudioDataValue> mRecordedInput;
271 class SmartMockCubebStream
272 : public MockCubebStream,
273 public SupportsThreadSafeWeakPtr<SmartMockCubebStream> {
274 public:
275 MOZ_DECLARE_REFCOUNTED_TYPENAME(SmartMockCubebStream)
276 SmartMockCubebStream(cubeb* aContext, cubeb_devid aInputDevice,
277 cubeb_stream_params* aInputStreamParams,
278 cubeb_devid aOutputDevice,
279 cubeb_stream_params* aOutputStreamParams,
280 cubeb_data_callback aDataCallback,
281 cubeb_state_callback aStateCallback, void* aUserPtr,
282 bool aFrozenStart)
283 : MockCubebStream(aContext, aInputDevice, aInputStreamParams,
284 aOutputDevice, aOutputStreamParams, aDataCallback,
285 aStateCallback, aUserPtr, this, aFrozenStart) {}
288 // This class has two facets: it is both a fake cubeb backend that is intended
289 // to be used for testing, and passed to Gecko code that expects a normal
290 // backend, but is also controllable by the test code to decide what the backend
291 // should do, depending on what is being tested.
292 class MockCubeb {
293 // This needs to have the exact same memory layout as a real cubeb backend.
294 // It's very important for the `ops` member to be the very first member of
295 // the class, and for MockCubeb to not have any virtual members (to avoid
296 // having a vtable), so that AsMock() returns a pointer to this that can be
297 // used as a cubeb backend.
298 const cubeb_ops* ops;
299 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockCubeb);
301 public:
302 MockCubeb();
303 // Cubeb backend implementation
304 // This allows passing this class as a cubeb* instance.
305 // cubeb_destroy(context) should eventually be called on the return value
306 // iff this method is called.
307 cubeb* AsCubebContext();
308 static MockCubeb* AsMock(cubeb* aContext);
309 void Destroy();
310 // Fill in the collection parameter with all devices of aType.
311 int EnumerateDevices(cubeb_device_type aType,
312 cubeb_device_collection* aCollection);
313 // Clear the collection parameter and deallocate its related memory space.
314 int DestroyDeviceCollection(cubeb_device_collection* aCollection);
316 // For a given device type, add a callback, called with a user pointer, when
317 // the device collection for this backend changes (i.e. a device has been
318 // removed or added).
319 int RegisterDeviceCollectionChangeCallback(
320 cubeb_device_type aDevType,
321 cubeb_device_collection_changed_callback aCallback, void* aUserPtr);
323 // Control API
325 // Add an input or output device to this backend. This calls the device
326 // collection invalidation callback if needed.
327 void AddDevice(cubeb_device_info aDevice);
328 // Remove a specific input or output device to this backend, returns true if
329 // a device was removed. This calls the device collection invalidation
330 // callback if needed.
331 bool RemoveDevice(cubeb_devid aId);
332 // Remove all input or output devices from this backend, without calling the
333 // callback. This is meant to clean up in between tests.
334 void ClearDevices(cubeb_device_type aType);
336 // This allows simulating a backend that does not support setting a device
337 // collection invalidation callback, to be able to test the fallback path.
338 void SetSupportDeviceChangeCallback(bool aSupports);
340 // This causes the next stream init with this context to return failure;
341 void ForceStreamInitError();
343 // Makes MockCubebStreams starting after this point wait for AllowStart().
344 // Callers must ensure they get a hold of the stream through StreamInitEvent
345 // to be able to start them.
346 void SetStreamStartFreezeEnabled(bool aEnabled);
348 // Helper class that automatically unforces a forced audio thread on release.
349 class AudioThreadAutoUnforcer {
350 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioThreadAutoUnforcer)
352 public:
353 explicit AudioThreadAutoUnforcer(MockCubeb* aContext)
354 : mContext(aContext) {}
356 protected:
357 virtual ~AudioThreadAutoUnforcer() { mContext->UnforceAudioThread(); }
358 MockCubeb* mContext;
361 // Creates the audio thread if one is not available. The audio thread remains
362 // forced until UnforceAudioThread is called. The returned promise is resolved
363 // when the audio thread is running. With this, a test can ensure starting
364 // audio streams is deterministically fast across platforms for more accurate
365 // results.
366 using ForcedAudioThreadPromise =
367 MozPromise<RefPtr<AudioThreadAutoUnforcer>, nsresult, false>;
368 RefPtr<ForcedAudioThreadPromise> ForceAudioThread();
370 // Allows a forced audio thread to stop.
371 void UnforceAudioThread();
373 int StreamInit(cubeb* aContext, cubeb_stream** aStream,
374 cubeb_devid aInputDevice,
375 cubeb_stream_params* aInputStreamParams,
376 cubeb_devid aOutputDevice,
377 cubeb_stream_params* aOutputStreamParams,
378 cubeb_data_callback aDataCallback,
379 cubeb_state_callback aStateCallback, void* aUserPtr);
381 void StreamDestroy(MockCubebStream* aStream);
383 void GoFaster();
384 void DontGoFaster();
386 MediaEventSource<RefPtr<SmartMockCubebStream>>& StreamInitEvent();
387 MediaEventSource<RefPtr<SmartMockCubebStream>>& StreamDestroyEvent();
389 // MockCubeb specific API
390 void StartStream(MockCubebStream* aStream);
391 void StopStream(MockCubebStream* aStream);
393 // Simulates the audio thread. The thread is created at Start and destroyed
394 // at Stop. At next StreamStart a new thread is created.
395 static void ThreadFunction_s(MockCubeb* aContext) {
396 aContext->ThreadFunction();
399 void ThreadFunction();
401 private:
402 ~MockCubeb();
403 // The callback to call when the device list has been changed.
404 cubeb_device_collection_changed_callback
405 mInputDeviceCollectionChangeCallback = nullptr;
406 cubeb_device_collection_changed_callback
407 mOutputDeviceCollectionChangeCallback = nullptr;
408 // The pointer to pass in the callback.
409 void* mInputDeviceCollectionChangeUserPtr = nullptr;
410 void* mOutputDeviceCollectionChangeUserPtr = nullptr;
411 void* mUserPtr = nullptr;
412 // Whether or not this backend supports device collection change
413 // notification via a system callback. If not, Gecko is expected to re-query
414 // the list every time.
415 bool mSupportsDeviceCollectionChangedCallback = true;
416 Atomic<bool> mStreamInitErrorState;
417 // Whether new MockCubebStreams should be frozen on start.
418 Atomic<bool> mStreamStartFreezeEnabled{false};
419 // Whether the audio thread is forced, i.e., whether it remains active even
420 // with no live streams.
421 Atomic<bool> mForcedAudioThread{false};
422 Atomic<bool> mHasCubebContext{false};
423 Atomic<bool> mDestroyed{false};
424 MozPromiseHolder<ForcedAudioThreadPromise> mForcedAudioThreadPromise;
425 // Our input and output devices.
426 nsTArray<cubeb_device_info> mInputDevices;
427 nsTArray<cubeb_device_info> mOutputDevices;
429 // The streams that are currently running.
430 DataMutex<nsTArray<RefPtr<SmartMockCubebStream>>> mLiveStreams{
431 "MockCubeb::mLiveStreams"};
432 // Thread that simulates the audio thread, shared across MockCubebStreams to
433 // avoid unintended drift. This is set together with mLiveStreams, under the
434 // mLiveStreams DataMutex.
435 UniquePtr<std::thread> mFakeAudioThread;
436 // Whether to run the fake audio thread in fast mode, not caring about wall
437 // clock time. false is default and means data is processed every 10ms. When
438 // true we sleep(0) between iterations instead of 10ms.
439 std::atomic<bool> mFastMode{false};
441 MediaEventProducer<RefPtr<SmartMockCubebStream>> mStreamInitEvent;
442 MediaEventProducer<RefPtr<SmartMockCubebStream>> mStreamDestroyEvent;
445 int cubeb_mock_enumerate_devices(cubeb* context, cubeb_device_type type,
446 cubeb_device_collection* out) {
447 return MockCubeb::AsMock(context)->EnumerateDevices(type, out);
450 int cubeb_mock_device_collection_destroy(cubeb* context,
451 cubeb_device_collection* collection) {
452 return MockCubeb::AsMock(context)->DestroyDeviceCollection(collection);
455 int cubeb_mock_register_device_collection_changed(
456 cubeb* context, cubeb_device_type devtype,
457 cubeb_device_collection_changed_callback callback, void* user_ptr) {
458 return MockCubeb::AsMock(context)->RegisterDeviceCollectionChangeCallback(
459 devtype, callback, user_ptr);
462 int cubeb_mock_stream_init(
463 cubeb* context, cubeb_stream** stream, char const* stream_name,
464 cubeb_devid input_device, cubeb_stream_params* input_stream_params,
465 cubeb_devid output_device, cubeb_stream_params* output_stream_params,
466 unsigned int latency, cubeb_data_callback data_callback,
467 cubeb_state_callback state_callback, void* user_ptr) {
468 return MockCubeb::AsMock(context)->StreamInit(
469 context, stream, input_device, input_stream_params, output_device,
470 output_stream_params, data_callback, state_callback, user_ptr);
473 int cubeb_mock_stream_start(cubeb_stream* stream) {
474 return MockCubebStream::AsMock(stream)->Start();
477 int cubeb_mock_stream_stop(cubeb_stream* stream) {
478 return MockCubebStream::AsMock(stream)->Stop();
481 int cubeb_mock_stream_get_position(cubeb_stream* stream, uint64_t* position) {
482 *position = MockCubebStream::AsMock(stream)->Position();
483 return CUBEB_OK;
486 void cubeb_mock_stream_destroy(cubeb_stream* stream) {
487 MockCubebStream::AsMock(stream)->Destroy();
490 static char const* cubeb_mock_get_backend_id(cubeb* context) {
491 #if defined(XP_MACOSX)
492 return "audiounit";
493 #elif defined(XP_WIN)
494 return "wasapi";
495 #elif defined(ANDROID)
496 return "opensl";
497 #elif defined(__OpenBSD__)
498 return "sndio";
499 #else
500 return "pulse";
501 #endif
504 static int cubeb_mock_stream_set_volume(cubeb_stream* stream, float volume) {
505 return CUBEB_OK;
508 static int cubeb_mock_stream_set_name(cubeb_stream* stream,
509 char const* stream_name) {
510 return CUBEB_OK;
513 int cubeb_mock_stream_register_device_changed_callback(
514 cubeb_stream* stream,
515 cubeb_device_changed_callback device_changed_callback) {
516 return MockCubebStream::AsMock(stream)->RegisterDeviceChangedCallback(
517 device_changed_callback);
520 int cubeb_mock_get_min_latency(cubeb* context, cubeb_stream_params params,
521 uint32_t* latency_ms) {
522 *latency_ms = 10;
523 return CUBEB_OK;
526 int cubeb_mock_get_preferred_sample_rate(cubeb* context, uint32_t* rate) {
527 *rate = 44100;
528 return CUBEB_OK;
531 int cubeb_mock_get_max_channel_count(cubeb* context, uint32_t* max_channels) {
532 *max_channels = MAX_OUTPUT_CHANNELS;
533 return CUBEB_OK;
536 void PrintDevice(cubeb_device_info aInfo);
538 void PrintDevice(AudioDeviceInfo* aInfo);
540 cubeb_device_info DeviceTemplate(cubeb_devid aId, cubeb_device_type aType,
541 const char* name);
543 cubeb_device_info DeviceTemplate(cubeb_devid aId, cubeb_device_type aType);
545 void AddDevices(MockCubeb* mock, uint32_t device_count,
546 cubeb_device_type deviceType);
548 } // namespace mozilla
550 #endif // MOCKCUBEB_H_