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/. */
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"
21 const uint32_t MAX_OUTPUT_CHANNELS
= 2;
22 const uint32_t MAX_INPUT_CHANNELS
= 2;
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
)(
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
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
,
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(
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
= {
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.
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
);
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
);
180 void ForceDeviceChanged();
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
191 nsTArray
<AudioDataValue
>&& TakeRecordedOutput();
192 // Get the recorded input from this stream. This doesn't copy, and therefore
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();
208 const bool mHasInput
;
209 const bool mHasOutput
;
210 SmartMockCubebStream
* const mSelf
;
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.
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;
241 cubeb_stream_params mOutputParams
= {};
242 cubeb_stream_params mInputParams
= {};
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.
265 nsTArray
<AudioDataValue
> mRecordedOutput
;
266 // The recorded data, copied from the input buffer of the callback.
268 nsTArray
<AudioDataValue
> mRecordedInput
;
271 class SmartMockCubebStream
272 : public MockCubebStream
,
273 public SupportsThreadSafeWeakPtr
<SmartMockCubebStream
> {
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
,
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.
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
);
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
);
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
);
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
)
353 explicit AudioThreadAutoUnforcer(MockCubeb
* aContext
)
354 : mContext(aContext
) {}
357 virtual ~AudioThreadAutoUnforcer() { mContext
->UnforceAudioThread(); }
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
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
);
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();
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();
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)
493 #elif defined(XP_WIN)
495 #elif defined(ANDROID)
497 #elif defined(__OpenBSD__)
504 static int cubeb_mock_stream_set_volume(cubeb_stream
* stream
, float volume
) {
508 static int cubeb_mock_stream_set_name(cubeb_stream
* stream
,
509 char const* stream_name
) {
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
) {
526 int cubeb_mock_get_preferred_sample_rate(cubeb
* context
, uint32_t* rate
) {
531 int cubeb_mock_get_max_channel_count(cubeb
* context
, uint32_t* max_channels
) {
532 *max_channels
= MAX_OUTPUT_CHANNELS
;
536 void PrintDevice(cubeb_device_info aInfo
);
538 void PrintDevice(AudioDeviceInfo
* aInfo
);
540 cubeb_device_info
DeviceTemplate(cubeb_devid aId
, cubeb_device_type aType
,
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_