/src/mozilla-central/dom/media/gtest/TestAudioDeviceEnumerator.cpp
Line | Count | Source (jump to first uncovered line) |
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 |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "gtest/gtest.h" |
7 | | #include "mozilla/UniquePtr.h" |
8 | | #include "mozilla/Attributes.h" |
9 | | #include "nsTArray.h" |
10 | | #define ENABLE_SET_CUBEB_BACKEND 1 |
11 | | #include "CubebUtils.h" |
12 | | #include "MediaEngineWebRTC.h" |
13 | | |
14 | | using namespace mozilla; |
15 | | |
16 | | const bool DEBUG_PRINTS = false; |
17 | | |
18 | | // Keep those and the struct definition in sync with cubeb.h and |
19 | | // cubeb-internal.h |
20 | | void |
21 | | cubeb_mock_destroy(cubeb* context); |
22 | | static int |
23 | | cubeb_mock_enumerate_devices(cubeb* context, |
24 | | cubeb_device_type type, |
25 | | cubeb_device_collection* out); |
26 | | |
27 | | static int |
28 | | cubeb_mock_device_collection_destroy(cubeb* context, |
29 | | cubeb_device_collection* collection); |
30 | | |
31 | | static int |
32 | | cubeb_mock_register_device_collection_changed( |
33 | | cubeb* context, |
34 | | cubeb_device_type devtype, |
35 | | cubeb_device_collection_changed_callback callback, |
36 | | void* user_ptr); |
37 | | |
38 | | struct cubeb_ops |
39 | | { |
40 | | int (*init)(cubeb** context, char const* context_name); |
41 | | char const* (*get_backend_id)(cubeb* context); |
42 | | int (*get_max_channel_count)(cubeb* context, uint32_t* max_channels); |
43 | | int (*get_min_latency)(cubeb* context, |
44 | | cubeb_stream_params params, |
45 | | uint32_t* latency_ms); |
46 | | int (*get_preferred_sample_rate)(cubeb* context, uint32_t* rate); |
47 | | int (*enumerate_devices)(cubeb* context, |
48 | | cubeb_device_type type, |
49 | | cubeb_device_collection* collection); |
50 | | int (*device_collection_destroy)(cubeb* context, |
51 | | cubeb_device_collection* collection); |
52 | | void (*destroy)(cubeb* context); |
53 | | int (*stream_init)(cubeb* context, |
54 | | cubeb_stream** stream, |
55 | | char const* stream_name, |
56 | | cubeb_devid input_device, |
57 | | cubeb_stream_params* input_stream_params, |
58 | | cubeb_devid output_device, |
59 | | cubeb_stream_params* output_stream_params, |
60 | | unsigned int latency, |
61 | | cubeb_data_callback data_callback, |
62 | | cubeb_state_callback state_callback, |
63 | | void* user_ptr); |
64 | | void (*stream_destroy)(cubeb_stream* stream); |
65 | | int (*stream_start)(cubeb_stream* stream); |
66 | | int (*stream_stop)(cubeb_stream* stream); |
67 | | int (*stream_reset_default_device)(cubeb_stream* stream); |
68 | | int (*stream_get_position)(cubeb_stream* stream, uint64_t* position); |
69 | | int (*stream_get_latency)(cubeb_stream* stream, uint32_t* latency); |
70 | | int (*stream_set_volume)(cubeb_stream* stream, float volumes); |
71 | | int (*stream_set_panning)(cubeb_stream* stream, float panning); |
72 | | int (*stream_get_current_device)(cubeb_stream* stream, |
73 | | cubeb_device** const device); |
74 | | int (*stream_device_destroy)(cubeb_stream* stream, cubeb_device* device); |
75 | | int (*stream_register_device_changed_callback)( |
76 | | cubeb_stream* stream, |
77 | | cubeb_device_changed_callback device_changed_callback); |
78 | | int (*register_device_collection_changed)( |
79 | | cubeb* context, |
80 | | cubeb_device_type devtype, |
81 | | cubeb_device_collection_changed_callback callback, |
82 | | void* user_ptr); |
83 | | }; |
84 | | |
85 | | // Mock cubeb impl, only supports device enumeration for now. |
86 | | cubeb_ops const mock_ops = { |
87 | | /*.init =*/NULL, |
88 | | /*.get_backend_id =*/NULL, |
89 | | /*.get_max_channel_count =*/NULL, |
90 | | /*.get_min_latency =*/NULL, |
91 | | /*.get_preferred_sample_rate =*/NULL, |
92 | | /*.enumerate_devices =*/cubeb_mock_enumerate_devices, |
93 | | /*.device_collection_destroy =*/cubeb_mock_device_collection_destroy, |
94 | | /*.destroy =*/cubeb_mock_destroy, |
95 | | /*.stream_init =*/NULL, |
96 | | /*.stream_destroy =*/NULL, |
97 | | /*.stream_start =*/NULL, |
98 | | /*.stream_stop =*/NULL, |
99 | | /*.stream_reset_default_device =*/NULL, |
100 | | /*.stream_get_position =*/NULL, |
101 | | /*.stream_get_latency =*/NULL, |
102 | | /*.stream_set_volume =*/NULL, |
103 | | /*.stream_set_panning =*/NULL, |
104 | | /*.stream_get_current_device =*/NULL, |
105 | | /*.stream_device_destroy =*/NULL, |
106 | | /*.stream_register_device_changed_callback =*/NULL, |
107 | | /*.register_device_collection_changed =*/ |
108 | | cubeb_mock_register_device_collection_changed |
109 | | }; |
110 | | |
111 | | // This class has two facets: it is both a fake cubeb backend that is intended |
112 | | // to be used for testing, and passed to Gecko code that expects a normal |
113 | | // backend, but is also controllable by the test code to decide what the backend |
114 | | // should do, depending on what is being tested. |
115 | | class MockCubeb |
116 | | { |
117 | | public: |
118 | | MockCubeb() |
119 | | : ops(&mock_ops) |
120 | | , mDeviceCollectionChangeCallback(nullptr) |
121 | | , mDeviceCollectionChangeType(CUBEB_DEVICE_TYPE_UNKNOWN) |
122 | | , mDeviceCollectionChangeUserPtr(nullptr) |
123 | | , mSupportsDeviceCollectionChangedCallback(true) |
124 | 0 | { |
125 | 0 | } |
126 | | // Cubeb backend implementation |
127 | | // This allows passing this class as a cubeb* instance. |
128 | 0 | cubeb* AsCubebContext() { return reinterpret_cast<cubeb*>(this); } |
129 | | // Fill in the collection parameter with all devices of aType. |
130 | | int EnumerateDevices(cubeb_device_type aType, |
131 | | cubeb_device_collection* collection) |
132 | 0 | { |
133 | | #ifdef ANDROID |
134 | | EXPECT_TRUE(false) << "This is not to be called on Android."; |
135 | | #endif |
136 | | size_t count = 0; |
137 | 0 | if (aType & CUBEB_DEVICE_TYPE_INPUT) { |
138 | 0 | count += mInputDevices.Length(); |
139 | 0 | } |
140 | 0 | if (aType & CUBEB_DEVICE_TYPE_OUTPUT) { |
141 | 0 | count += mOutputDevices.Length(); |
142 | 0 | } |
143 | 0 | collection->device = new cubeb_device_info[count]; |
144 | 0 | collection->count = count; |
145 | 0 |
|
146 | 0 | uint32_t collection_index = 0; |
147 | 0 | if (aType & CUBEB_DEVICE_TYPE_INPUT) { |
148 | 0 | for (auto& device : mInputDevices) { |
149 | 0 | collection->device[collection_index] = device; |
150 | 0 | collection_index++; |
151 | 0 | } |
152 | 0 | } |
153 | 0 | if (aType & CUBEB_DEVICE_TYPE_OUTPUT) { |
154 | 0 | for (auto& device : mOutputDevices) { |
155 | 0 | collection->device[collection_index] = device; |
156 | 0 | collection_index++; |
157 | 0 | } |
158 | 0 | } |
159 | 0 |
|
160 | 0 | return CUBEB_OK; |
161 | 0 | } |
162 | | |
163 | | // For a given device type, add a callback, called with a user pointer, when |
164 | | // the device collection for this backend changes (i.e. a device has been |
165 | | // removed or added). |
166 | | int RegisterDeviceCollectionChangeCallback( |
167 | | cubeb_device_type aDevType, |
168 | | cubeb_device_collection_changed_callback aCallback, |
169 | | void* aUserPtr) |
170 | 0 | { |
171 | 0 | if (!mSupportsDeviceCollectionChangedCallback) { |
172 | 0 | return CUBEB_ERROR; |
173 | 0 | } |
174 | 0 | |
175 | 0 | mDeviceCollectionChangeType = aDevType; |
176 | 0 | mDeviceCollectionChangeCallback = aCallback; |
177 | 0 | mDeviceCollectionChangeUserPtr = aUserPtr; |
178 | 0 |
|
179 | 0 | return CUBEB_OK; |
180 | 0 | } |
181 | | |
182 | | // Control API |
183 | | |
184 | | // Add an input or output device to this backend. This calls the device |
185 | | // collection invalidation callback if needed. |
186 | | void AddDevice(cubeb_device_info aDevice) |
187 | 0 | { |
188 | 0 | bool needToCall = false; |
189 | 0 |
|
190 | 0 | if (aDevice.type == CUBEB_DEVICE_TYPE_INPUT) { |
191 | 0 | mInputDevices.AppendElement(aDevice); |
192 | 0 | } else if (aDevice.type == CUBEB_DEVICE_TYPE_OUTPUT) { |
193 | 0 | mOutputDevices.AppendElement(aDevice); |
194 | 0 | } else { |
195 | 0 | MOZ_CRASH("bad device type when adding a device in mock cubeb backend"); |
196 | 0 | } |
197 | 0 |
|
198 | 0 | bool isInput = aDevice.type & CUBEB_DEVICE_TYPE_INPUT; |
199 | 0 |
|
200 | 0 | needToCall |= |
201 | 0 | isInput && mDeviceCollectionChangeType & CUBEB_DEVICE_TYPE_INPUT; |
202 | 0 | needToCall |= |
203 | 0 | !isInput && mDeviceCollectionChangeType & CUBEB_DEVICE_TYPE_OUTPUT; |
204 | 0 |
|
205 | 0 | if (needToCall && mDeviceCollectionChangeCallback) { |
206 | 0 | mDeviceCollectionChangeCallback(AsCubebContext(), |
207 | 0 | mDeviceCollectionChangeUserPtr); |
208 | 0 | } |
209 | 0 | } |
210 | | // Remove a specific input or output device to this backend, returns true if |
211 | | // a device was removed. This calls the device collection invalidation |
212 | | // callback if needed. |
213 | | bool RemoveDevice(cubeb_devid aId) |
214 | 0 | { |
215 | 0 | bool foundInput = false; |
216 | 0 | bool foundOutput = false; |
217 | 0 | mInputDevices.RemoveElementsBy( |
218 | 0 | [aId, &foundInput](cubeb_device_info& aDeviceInfo) { |
219 | 0 | bool foundThisTime = aDeviceInfo.devid == aId; |
220 | 0 | foundInput |= foundThisTime; |
221 | 0 | return foundThisTime; |
222 | 0 | }); |
223 | 0 | mOutputDevices.RemoveElementsBy( |
224 | 0 | [aId, &foundOutput](cubeb_device_info& aDeviceInfo) { |
225 | 0 | bool foundThisTime = aDeviceInfo.devid == aId; |
226 | 0 | foundOutput |= foundThisTime; |
227 | 0 | return foundThisTime; |
228 | 0 | }); |
229 | 0 |
|
230 | 0 | bool needToCall = false; |
231 | 0 |
|
232 | 0 | needToCall |= |
233 | 0 | foundInput && mDeviceCollectionChangeType & CUBEB_DEVICE_TYPE_INPUT; |
234 | 0 | needToCall |= |
235 | 0 | foundOutput && mDeviceCollectionChangeType & CUBEB_DEVICE_TYPE_OUTPUT; |
236 | 0 |
|
237 | 0 | if (needToCall && mDeviceCollectionChangeCallback) { |
238 | 0 | mDeviceCollectionChangeCallback(AsCubebContext(), |
239 | 0 | mDeviceCollectionChangeUserPtr); |
240 | 0 | } |
241 | 0 |
|
242 | 0 | // If the device removed was a default device, set another device as the |
243 | 0 | // default, if there are still devices available. |
244 | 0 | bool foundDefault = false; |
245 | 0 | for (uint32_t i = 0; i < mInputDevices.Length(); i++) { |
246 | 0 | foundDefault |= mInputDevices[i].preferred != CUBEB_DEVICE_PREF_NONE; |
247 | 0 | } |
248 | 0 |
|
249 | 0 | if (!foundDefault) { |
250 | 0 | if (!mInputDevices.IsEmpty()) { |
251 | 0 | mInputDevices[mInputDevices.Length() - 1].preferred = |
252 | 0 | CUBEB_DEVICE_PREF_ALL; |
253 | 0 | } |
254 | 0 | } |
255 | 0 |
|
256 | 0 | foundDefault = false; |
257 | 0 | for (uint32_t i = 0; i < mOutputDevices.Length(); i++) { |
258 | 0 | foundDefault |= mOutputDevices[i].preferred != CUBEB_DEVICE_PREF_NONE; |
259 | 0 | } |
260 | 0 |
|
261 | 0 | if (!foundDefault) { |
262 | 0 | if (!mOutputDevices.IsEmpty()) { |
263 | 0 | mOutputDevices[mOutputDevices.Length() - 1].preferred = |
264 | 0 | CUBEB_DEVICE_PREF_ALL; |
265 | 0 | } |
266 | 0 | } |
267 | 0 |
|
268 | 0 | return foundInput | foundOutput; |
269 | 0 | } |
270 | | // Remove all input or output devices from this backend, without calling the |
271 | | // callback. This is meant to clean up in between tests. |
272 | | void ClearDevices(cubeb_device_type aType) |
273 | 0 | { |
274 | 0 | mInputDevices.Clear(); |
275 | 0 | mOutputDevices.Clear(); |
276 | 0 | } |
277 | | |
278 | | // This allows simulating a backend that does not support setting a device |
279 | | // collection invalidation callback, to be able to test the fallback path. |
280 | | void SetSupportDeviceChangeCallback(bool aSupports) |
281 | 0 | { |
282 | 0 | mSupportsDeviceCollectionChangedCallback = aSupports; |
283 | 0 | } |
284 | | |
285 | | private: |
286 | | // This needs to have the exact same memory layout as a real cubeb backend. |
287 | | // It's very important for this `ops` member to be the very first member of |
288 | | // the class, and to not have any virtual members (to avoid having a vtable). |
289 | | const cubeb_ops* ops; |
290 | | // The callback to call when the device list has been changed. |
291 | | cubeb_device_collection_changed_callback mDeviceCollectionChangeCallback; |
292 | | // For which device type to call the callback. |
293 | | cubeb_device_type mDeviceCollectionChangeType; |
294 | | // The pointer to pass in the callback. |
295 | | void* mDeviceCollectionChangeUserPtr; |
296 | | // Whether or not this backed supports device collection change notification |
297 | | // via a system callback. If not, Gecko is expected to re-query the list every |
298 | | // time. |
299 | | bool mSupportsDeviceCollectionChangedCallback; |
300 | | // Our input and output devices. |
301 | | nsTArray<cubeb_device_info> mInputDevices; |
302 | | nsTArray<cubeb_device_info> mOutputDevices; |
303 | | }; |
304 | | |
305 | | void |
306 | | cubeb_mock_destroy(cubeb* context) |
307 | 0 | { |
308 | 0 | delete reinterpret_cast<MockCubeb*>(context); |
309 | 0 | } |
310 | | |
311 | | static int |
312 | | cubeb_mock_enumerate_devices(cubeb* context, |
313 | | cubeb_device_type type, |
314 | | cubeb_device_collection* out) |
315 | 0 | { |
316 | 0 | MockCubeb* mock = reinterpret_cast<MockCubeb*>(context); |
317 | 0 | return mock->EnumerateDevices(type, out); |
318 | 0 | } |
319 | | |
320 | | int |
321 | | cubeb_mock_device_collection_destroy(cubeb* context, |
322 | | cubeb_device_collection* collection) |
323 | 0 | { |
324 | 0 | delete[] collection->device; |
325 | 0 | return CUBEB_OK; |
326 | 0 | } |
327 | | |
328 | | int |
329 | | cubeb_mock_register_device_collection_changed( |
330 | | cubeb* context, |
331 | | cubeb_device_type devtype, |
332 | | cubeb_device_collection_changed_callback callback, |
333 | | void* user_ptr) |
334 | 0 | { |
335 | 0 | MockCubeb* mock = reinterpret_cast<MockCubeb*>(context); |
336 | 0 | return mock->RegisterDeviceCollectionChangeCallback( |
337 | 0 | devtype, callback, user_ptr); |
338 | 0 | return CUBEB_OK; |
339 | 0 | } |
340 | | |
341 | | void |
342 | | PrintDevice(cubeb_device_info aInfo) |
343 | 0 | { |
344 | 0 | printf("id: %zu\n" |
345 | 0 | "device_id: %s\n" |
346 | 0 | "friendly_name: %s\n" |
347 | 0 | "group_id: %s\n" |
348 | 0 | "vendor_name: %s\n" |
349 | 0 | "type: %d\n" |
350 | 0 | "state: %d\n" |
351 | 0 | "preferred: %d\n" |
352 | 0 | "format: %d\n" |
353 | 0 | "default_format: %d\n" |
354 | 0 | "max_channels: %d\n" |
355 | 0 | "default_rate: %d\n" |
356 | 0 | "max_rate: %d\n" |
357 | 0 | "min_rate: %d\n" |
358 | 0 | "latency_lo: %d\n" |
359 | 0 | "latency_hi: %d\n", |
360 | 0 | reinterpret_cast<uintptr_t>(aInfo.devid), |
361 | 0 | aInfo.device_id, |
362 | 0 | aInfo.friendly_name, |
363 | 0 | aInfo.group_id, |
364 | 0 | aInfo.vendor_name, |
365 | 0 | aInfo.type, |
366 | 0 | aInfo.state, |
367 | 0 | aInfo.preferred, |
368 | 0 | aInfo.format, |
369 | 0 | aInfo.default_format, |
370 | 0 | aInfo.max_channels, |
371 | 0 | aInfo.default_rate, |
372 | 0 | aInfo.max_rate, |
373 | 0 | aInfo.min_rate, |
374 | 0 | aInfo.latency_lo, |
375 | 0 | aInfo.latency_hi); |
376 | 0 | } |
377 | | |
378 | | void |
379 | | PrintDevice(AudioDeviceInfo* aInfo) |
380 | 0 | { |
381 | 0 | cubeb_devid id; |
382 | 0 | nsString name; |
383 | 0 | nsString groupid; |
384 | 0 | nsString vendor; |
385 | 0 | uint16_t type; |
386 | 0 | uint16_t state; |
387 | 0 | uint16_t preferred; |
388 | 0 | uint16_t supportedFormat; |
389 | 0 | uint16_t defaultFormat; |
390 | 0 | uint32_t maxChannels; |
391 | 0 | uint32_t defaultRate; |
392 | 0 | uint32_t maxRate; |
393 | 0 | uint32_t minRate; |
394 | 0 | uint32_t maxLatency; |
395 | 0 | uint32_t minLatency; |
396 | 0 |
|
397 | 0 | id = aInfo->DeviceID(); |
398 | 0 | aInfo->GetName(name); |
399 | 0 | aInfo->GetGroupId(groupid); |
400 | 0 | aInfo->GetVendor(vendor); |
401 | 0 | aInfo->GetType(&type); |
402 | 0 | aInfo->GetState(&state); |
403 | 0 | aInfo->GetPreferred(&preferred); |
404 | 0 | aInfo->GetSupportedFormat(&supportedFormat); |
405 | 0 | aInfo->GetDefaultFormat(&defaultFormat); |
406 | 0 | aInfo->GetMaxChannels(&maxChannels); |
407 | 0 | aInfo->GetDefaultRate(&defaultRate); |
408 | 0 | aInfo->GetMaxRate(&maxRate); |
409 | 0 | aInfo->GetMinRate(&minRate); |
410 | 0 | aInfo->GetMinLatency(&minLatency); |
411 | 0 | aInfo->GetMaxLatency(&maxLatency); |
412 | 0 |
|
413 | 0 | printf("device id: %zu\n" |
414 | 0 | "friendly_name: %s\n" |
415 | 0 | "group_id: %s\n" |
416 | 0 | "vendor_name: %s\n" |
417 | 0 | "type: %d\n" |
418 | 0 | "state: %d\n" |
419 | 0 | "preferred: %d\n" |
420 | 0 | "format: %d\n" |
421 | 0 | "default_format: %d\n" |
422 | 0 | "max_channels: %d\n" |
423 | 0 | "default_rate: %d\n" |
424 | 0 | "max_rate: %d\n" |
425 | 0 | "min_rate: %d\n" |
426 | 0 | "latency_lo: %d\n" |
427 | 0 | "latency_hi: %d\n", |
428 | 0 | reinterpret_cast<uintptr_t>(id), |
429 | 0 | NS_LossyConvertUTF16toASCII(name).get(), |
430 | 0 | NS_LossyConvertUTF16toASCII(groupid).get(), |
431 | 0 | NS_LossyConvertUTF16toASCII(vendor).get(), |
432 | 0 | type, |
433 | 0 | state, |
434 | 0 | preferred, |
435 | 0 | supportedFormat, |
436 | 0 | defaultFormat, |
437 | 0 | maxChannels, |
438 | 0 | defaultRate, |
439 | 0 | maxRate, |
440 | 0 | minRate, |
441 | 0 | minLatency, |
442 | 0 | maxLatency); |
443 | 0 | } |
444 | | |
445 | | cubeb_device_info |
446 | | InputDeviceTemplate(cubeb_devid aId) |
447 | 0 | { |
448 | 0 | // A fake input device |
449 | 0 | cubeb_device_info device; |
450 | 0 | device.devid = aId; |
451 | 0 | device.device_id = "nice name"; |
452 | 0 | device.friendly_name = "an even nicer name"; |
453 | 0 | device.group_id = "the physical device"; |
454 | 0 | device.vendor_name = "mozilla"; |
455 | 0 | device.type = CUBEB_DEVICE_TYPE_INPUT; |
456 | 0 | device.state = CUBEB_DEVICE_STATE_ENABLED; |
457 | 0 | device.preferred = CUBEB_DEVICE_PREF_NONE; |
458 | 0 | device.format = CUBEB_DEVICE_FMT_F32NE; |
459 | 0 | device.default_format = CUBEB_DEVICE_FMT_F32NE; |
460 | 0 | device.max_channels = 2; |
461 | 0 | device.default_rate = 44100; |
462 | 0 | device.max_rate = 44100; |
463 | 0 | device.min_rate = 16000; |
464 | 0 | device.latency_lo = 256; |
465 | 0 | device.latency_hi = 1024; |
466 | 0 |
|
467 | 0 | return device; |
468 | 0 | } |
469 | | |
470 | | enum DeviceOperation |
471 | | { |
472 | | ADD, |
473 | | REMOVE |
474 | | }; |
475 | | |
476 | | void |
477 | | TestEnumeration(MockCubeb* aMock, |
478 | | uint32_t aExpectedDeviceCount, |
479 | | DeviceOperation aOperation) |
480 | 0 | { |
481 | 0 | CubebDeviceEnumerator enumerator; |
482 | 0 |
|
483 | 0 | nsTArray<RefPtr<AudioDeviceInfo>> inputDevices; |
484 | 0 |
|
485 | 0 | enumerator.EnumerateAudioInputDevices(inputDevices); |
486 | 0 |
|
487 | 0 | EXPECT_EQ(inputDevices.Length(), aExpectedDeviceCount) |
488 | 0 | << "Device count is correct when enumerating"; |
489 | 0 |
|
490 | 0 | if (DEBUG_PRINTS) { |
491 | 0 | for (uint32_t i = 0; i < inputDevices.Length(); i++) { |
492 | 0 | printf("=== Before removal\n"); |
493 | 0 | PrintDevice(inputDevices[i]); |
494 | 0 | } |
495 | 0 | } |
496 | 0 |
|
497 | 0 | if (aOperation == DeviceOperation::REMOVE) { |
498 | 0 | aMock->RemoveDevice(reinterpret_cast<cubeb_devid>(1)); |
499 | 0 | } else { |
500 | 0 | aMock->AddDevice(InputDeviceTemplate(reinterpret_cast<cubeb_devid>(123))); |
501 | 0 | } |
502 | 0 |
|
503 | 0 | enumerator.EnumerateAudioInputDevices(inputDevices); |
504 | 0 |
|
505 | 0 | uint32_t newExpectedDeviceCount = aOperation == DeviceOperation::REMOVE |
506 | 0 | ? aExpectedDeviceCount - 1 |
507 | 0 | : aExpectedDeviceCount + 1; |
508 | 0 |
|
509 | 0 | EXPECT_EQ(inputDevices.Length(), newExpectedDeviceCount) |
510 | 0 | << "Device count is correct when enumerating after operation"; |
511 | 0 |
|
512 | 0 | if (DEBUG_PRINTS) { |
513 | 0 | for (uint32_t i = 0; i < inputDevices.Length(); i++) { |
514 | 0 | printf("=== After removal\n"); |
515 | 0 | PrintDevice(inputDevices[i]); |
516 | 0 | } |
517 | 0 | } |
518 | 0 | } |
519 | | |
520 | | #ifndef ANDROID |
521 | | TEST(CubebDeviceEnumerator, EnumerateSimple) |
522 | 0 | { |
523 | 0 | // It looks like we're leaking this object, but in fact it will be freed by |
524 | 0 | // gecko sometime later: `cubeb_destroy` is called when layout statics are |
525 | 0 | // shutdown and we cast back to a MockCubeb* and call the dtor. |
526 | 0 | MockCubeb* mock = new MockCubeb(); |
527 | 0 | mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext()); |
528 | 0 |
|
529 | 0 | // We want to test whether CubebDeviceEnumerator works with and without a |
530 | 0 | // backend that can notify of a device collection change via callback. |
531 | 0 | // Additionally, we're testing that both adding and removing a device |
532 | 0 | // invalidates the list correctly. |
533 | 0 | bool supportsDeviceChangeCallback[2] = { true, false }; |
534 | 0 | DeviceOperation operations[2] = { DeviceOperation::ADD, |
535 | 0 | DeviceOperation::REMOVE }; |
536 | 0 |
|
537 | 0 | for (DeviceOperation op : operations) { |
538 | 0 | for (bool supports : supportsDeviceChangeCallback) { |
539 | 0 | mock->ClearDevices(CUBEB_DEVICE_TYPE_INPUT); |
540 | 0 | // Add a few input devices (almost all the same but it does not really |
541 | 0 | // matter as long as they have distinct IDs and only one is the default |
542 | 0 | // devices) |
543 | 0 | uint32_t device_count = 4; |
544 | 0 | for (uintptr_t i = 0; i < device_count; i++) { |
545 | 0 | cubeb_device_info device = |
546 | 0 | InputDeviceTemplate(reinterpret_cast<void*>(i + 1)); |
547 | 0 | // Make it so that the last device is the default input device. |
548 | 0 | if (i == device_count - 1) { |
549 | 0 | device.preferred = CUBEB_DEVICE_PREF_ALL; |
550 | 0 | } |
551 | 0 | mock->AddDevice(device); |
552 | 0 | } |
553 | 0 |
|
554 | 0 | mock->SetSupportDeviceChangeCallback(supports); |
555 | 0 | TestEnumeration(mock, device_count, op); |
556 | 0 | } |
557 | 0 | } |
558 | 0 | } |
559 | | #else // building for Android, which has no device enumeration support |
560 | | TEST(CubebDeviceEnumerator, EnumerateAndroid) |
561 | | { |
562 | | MockCubeb* mock = new MockCubeb(); |
563 | | mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext()); |
564 | | |
565 | | CubebDeviceEnumerator enumerator; |
566 | | |
567 | | nsTArray<RefPtr<AudioDeviceInfo>> inputDevices; |
568 | | enumerator.EnumerateAudioInputDevices(inputDevices); |
569 | | EXPECT_EQ(inputDevices.Length(), 1u) << "Android always exposes a single input device."; |
570 | | EXPECT_EQ(inputDevices[0]->MaxChannels(), 1u) << "With a single channel."; |
571 | | EXPECT_EQ(inputDevices[0]->DeviceID(), nullptr) << "It's always the default device."; |
572 | | EXPECT_TRUE(inputDevices[0]->Preferred()) << "it's always the prefered device."; |
573 | | } |
574 | | #endif |
575 | | |
576 | | TEST(CubebDeviceEnumerator, ForceNullCubebContext) |
577 | 0 | { |
578 | 0 | mozilla::CubebUtils::ForceSetCubebContext(nullptr); |
579 | 0 | CubebDeviceEnumerator enumerator; |
580 | 0 | nsTArray<RefPtr<AudioDeviceInfo>> inputDevices; |
581 | 0 | enumerator.EnumerateAudioInputDevices(inputDevices); |
582 | 0 | EXPECT_EQ(inputDevices.Length(), 0u) << "Enumeration must fail device list must be empty."; |
583 | 0 | } |
584 | | |