VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp@ 89502

Last change on this file since 89502 was 89502, checked in by vboxsync, 3 years ago

Audio: Changed PDMAUDIOHOSTDEV::szName into a pszName so we don't have to truncate anything. Made PDMAudioHostDevAlloc capable of allocating both pszName and pszId as part of the device allocation. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 114.2 KB
Line 
1/* $Id: DrvHostAudioCoreAudio.cpp 89502 2021-06-04 10:35:52Z vboxsync $ */
2/** @file
3 * Host audio driver - Mac OS X CoreAudio.
4 *
5 * For relevant Apple documentation, here are some starters:
6 * - Core Audio Essentials
7 * https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html
8 * - TN2097: Playing a sound file using the Default Output Audio Unit
9 * https://developer.apple.com/library/archive/technotes/tn2097/
10 * - TN2091: Device input using the HAL Output Audio Unit
11 * https://developer.apple.com/library/archive/technotes/tn2091/
12 * - Audio Component Services
13 * https://developer.apple.com/documentation/audiounit/audio_component_services?language=objc
14 * - QA1533: How to handle kAudioUnitProperty_MaximumFramesPerSlice
15 * https://developer.apple.com/library/archive/qa/qa1533/
16 * - QA1317: Signaling the end of data when using AudioConverterFillComplexBuffer
17 * https://developer.apple.com/library/archive/qa/qa1317/
18 */
19
20/*
21 * Copyright (C) 2010-2020 Oracle Corporation
22 *
23 * This file is part of VirtualBox Open Source Edition (OSE), as
24 * available from http://www.virtualbox.org. This file is free software;
25 * you can redistribute it and/or modify it under the terms of the GNU
26 * General Public License (GPL) as published by the Free Software
27 * Foundation, in version 2 as it comes in the "COPYING" file of the
28 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
29 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
30 */
31
32
33/*********************************************************************************************************************************
34* Header Files *
35*********************************************************************************************************************************/
36#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
37#include <VBox/log.h>
38#include <VBox/vmm/pdmaudioinline.h>
39#include <VBox/vmm/pdmaudiohostenuminline.h>
40
41#include "VBoxDD.h"
42
43#include <iprt/asm.h>
44#include <iprt/cdefs.h>
45#include <iprt/circbuf.h>
46#include <iprt/mem.h>
47#include <iprt/uuid.h>
48#include <iprt/timer.h>
49
50#include <CoreAudio/CoreAudio.h>
51#include <CoreServices/CoreServices.h>
52#include <AudioToolbox/AudioQueue.h>
53#include <AudioUnit/AudioUnit.h>
54
55#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090 /* possibly 1080 */
56# define kAudioHardwarePropertyTranslateUIDToDevice (AudioObjectPropertySelector)'uidd'
57#endif
58
59
60/*********************************************************************************************************************************
61* Defined Constants And Macros *
62*********************************************************************************************************************************/
63/** The max number of queue buffers we'll use. */
64#define COREAUDIO_MAX_BUFFERS 1024
65/** The minimum number of queue buffers. */
66#define COREAUDIO_MIN_BUFFERS 4
67
68/** Enables the worker thread.
69 * This saves CoreAudio from creating an additional thread upon queue
70 * creation. (It does not help with the slow AudioQueueDispose fun.) */
71#define CORE_AUDIO_WITH_WORKER_THREAD
72#if 0
73/** Enables the AudioQueueDispose breakpoint timer (debugging help). */
74# define CORE_AUDIO_WITH_BREAKPOINT_TIMER
75#endif
76
77
78/*********************************************************************************************************************************
79* Structures and Typedefs *
80*********************************************************************************************************************************/
81/** Pointer to the instance data for a Core Audio driver instance. */
82typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO;
83/** Pointer to the Core Audio specific backend data for an audio stream. */
84typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM;
85
86/**
87 * Core Audio device entry (enumeration).
88 *
89 * @note This is definitely not safe to just copy!
90 */
91typedef struct COREAUDIODEVICEDATA
92{
93 /** The core PDM structure. */
94 PDMAUDIOHOSTDEV Core;
95
96 /** The audio device ID of the currently used device (UInt32 typedef). */
97 AudioDeviceID idDevice;
98} COREAUDIODEVICEDATA;
99/** Pointer to a Core Audio device entry (enumeration). */
100typedef COREAUDIODEVICEDATA *PCOREAUDIODEVICEDATA;
101
102
103/**
104 * Audio device information.
105 *
106 * We do not use COREAUDIODEVICEDATA here as it contains lots more than what we
107 * need and care to query. We also don't want to depend on DrvAudio making
108 * PDMIHOSTAUDIO::pfnGetDevices callbacks to keep this information up to date.
109 */
110typedef struct DRVHSTAUDCADEVICE
111{
112 /** The audio device ID. kAudioDeviceUnknown if not available. */
113 AudioObjectID idDevice;
114 /** Indicates whether we've registered device change listener. */
115 bool fRegisteredListeners;
116 /** The UID string (must release). NULL if not available. */
117 CFStringRef hStrUid;
118 /** The UID string for a specific device, NULL if we're using the default device. */
119 char *pszSpecific;
120} DRVHSTAUDCADEVICE;
121/** Pointer to info about a default device. */
122typedef DRVHSTAUDCADEVICE *PDRVHSTAUDCADEVICE;
123
124
125/**
126 * Core Audio stream state.
127 */
128typedef enum COREAUDIOINITSTATE
129{
130 /** The device is uninitialized. */
131 COREAUDIOINITSTATE_UNINIT = 0,
132 /** The device is currently initializing. */
133 COREAUDIOINITSTATE_IN_INIT,
134 /** The device is initialized. */
135 COREAUDIOINITSTATE_INIT,
136 /** The device is currently uninitializing. */
137 COREAUDIOINITSTATE_IN_UNINIT,
138 /** The usual 32-bit hack. */
139 COREAUDIOINITSTATE_32BIT_HACK = 0x7fffffff
140} COREAUDIOINITSTATE;
141
142
143/**
144 * Core audio buffer tracker.
145 *
146 * For output buffer we'll be using AudioQueueBuffer::mAudioDataByteSize to
147 * track how much we've written. When a buffer is full, or if we run low on
148 * queued bufferes, it will be queued.
149 *
150 * For input buffer we'll be using offRead to track how much we've read.
151 *
152 * The queued/not-queued state is stored in the first bit of
153 * AudioQueueBuffer::mUserData. While bits 8 and up holds the index into
154 * COREAUDIOSTREAM::paBuffers.
155 */
156typedef struct COREAUDIOBUF
157{
158 /** The buffer. */
159 AudioQueueBufferRef pBuf;
160 /** The buffer read offset (input only). */
161 uint32_t offRead;
162} COREAUDIOBUF;
163/** Pointer to a core audio buffer tracker. */
164typedef COREAUDIOBUF *PCOREAUDIOBUF;
165
166
167/**
168 * Core Audio specific data for an audio stream.
169 */
170typedef struct COREAUDIOSTREAM
171{
172 /** Common part. */
173 PDMAUDIOBACKENDSTREAM Core;
174
175 /** The stream's acquired configuration. */
176 PDMAUDIOSTREAMCFG Cfg;
177 /** List node for the device's stream list. */
178 RTLISTNODE Node;
179 /** The acquired (final) audio format for this stream.
180 * @note This what the device requests, we don't alter anything. */
181 AudioStreamBasicDescription BasicStreamDesc;
182 /** The actual audio queue being used. */
183 AudioQueueRef hAudioQueue;
184
185 /** Number of buffers. */
186 uint32_t cBuffers;
187 /** The array of buffer. */
188 PCOREAUDIOBUF paBuffers;
189
190 /** Initialization status tracker, actually COREAUDIOINITSTATE.
191 * Used when some of the device parameters or the device itself is changed
192 * during the runtime. */
193 volatile uint32_t enmInitState;
194 /** The current buffer being written to / read from. */
195 uint32_t idxBuffer;
196 /** Set if the stream is enabled. */
197 bool fEnabled;
198 /** Set if the stream is started (playing/capturing). */
199 bool fStarted;
200 /** Set if the stream is draining (output only). */
201 bool fDraining;
202 /** Set if we should restart the stream on resume (saved pause state). */
203 bool fRestartOnResume;
204// /** Set if we're switching to a new output/input device. */
205// bool fSwitchingDevice;
206 /** Internal stream offset (bytes). */
207 uint64_t offInternal;
208 /** The RTTimeMilliTS() at the end of the last transfer. */
209 uint64_t msLastTransfer;
210
211 /** Critical section for serializing access between thread + callbacks. */
212 RTCRITSECT CritSect;
213 /** Buffer that drvHstAudCaStreamStatusString uses. */
214 char szStatus[64];
215} COREAUDIOSTREAM;
216
217
218/**
219 * Instance data for a Core Audio host audio driver.
220 *
221 * @implements PDMIAUDIOCONNECTOR
222 */
223typedef struct DRVHOSTCOREAUDIO
224{
225 /** Pointer to the driver instance structure. */
226 PPDMDRVINS pDrvIns;
227 /** Pointer to host audio interface. */
228 PDMIHOSTAUDIO IHostAudio;
229 /** The input device. */
230 DRVHSTAUDCADEVICE InputDevice;
231 /** The output device. */
232 DRVHSTAUDCADEVICE OutputDevice;
233 /** Upwards notification interface. */
234 PPDMIHOSTAUDIOPORT pIHostAudioPort;
235 /** Indicates whether we've registered default input device change listener. */
236 bool fRegisteredDefaultInputListener;
237 /** Indicates whether we've registered default output device change listener. */
238 bool fRegisteredDefaultOutputListener;
239
240#ifdef CORE_AUDIO_WITH_WORKER_THREAD
241 /** @name Worker Thread For Queue callbacks and stuff.
242 * @{ */
243 /** The worker thread. */
244 RTTHREAD hThread;
245 /** The runloop of the worker thread. */
246 CFRunLoopRef hThreadRunLoop;
247 /** The message port we use to talk to the thread.
248 * @note While we don't currently use the port, it is necessary to prevent
249 * the thread from spinning or stopping prematurely because of
250 * CFRunLoopRunInMode returning kCFRunLoopRunFinished. */
251 CFMachPortRef hThreadPort;
252 /** Runloop source for hThreadPort. */
253 CFRunLoopSourceRef hThreadPortSrc;
254 /** @} */
255#endif
256
257 /** Critical section to serialize access. */
258 RTCRITSECT CritSect;
259#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
260 /** Timder for debugging AudioQueueDispose slowness. */
261 RTTIMERLR hBreakpointTimer;
262#endif
263} DRVHOSTCOREAUDIO;
264
265
266/*********************************************************************************************************************************
267* Internal Functions *
268*********************************************************************************************************************************/
269static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify);
270
271/* DrvHostAudioCoreAudioAuth.mm: */
272DECLHIDDEN(int) coreAudioInputPermissionCheck(void);
273
274
275#ifdef LOG_ENABLED
276/**
277 * Gets the stream status.
278 *
279 * @returns Pointer to stream status string.
280 * @param pStreamCA The stream to get the status for.
281 */
282static const char *drvHstAudCaStreamStatusString(PCOREAUDIOSTREAM pStreamCA)
283{
284 static RTSTRTUPLE const s_aInitState[5] =
285 {
286 { RT_STR_TUPLE("UNINIT") },
287 { RT_STR_TUPLE("IN_INIT") },
288 { RT_STR_TUPLE("INIT") },
289 { RT_STR_TUPLE("IN_UNINIT") },
290 { RT_STR_TUPLE("BAD") },
291 };
292 uint32_t enmInitState = pStreamCA->enmInitState;
293 PCRTSTRTUPLE pTuple = &s_aInitState[RT_MIN(enmInitState, RT_ELEMENTS(s_aInitState) - 1)];
294 memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch);
295 size_t off = pTuple->cch;
296
297 static RTSTRTUPLE const s_aEnable[2] =
298 {
299 { RT_STR_TUPLE("DISABLED") },
300 { RT_STR_TUPLE("ENABLED ") },
301 };
302 pTuple = &s_aEnable[pStreamCA->fEnabled];
303 memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch);
304 off += pTuple->cch;
305
306 static RTSTRTUPLE const s_aStarted[2] =
307 {
308 { RT_STR_TUPLE(" STOPPED") },
309 { RT_STR_TUPLE(" STARTED") },
310 };
311 pTuple = &s_aStarted[pStreamCA->fStarted];
312 memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch);
313 off += pTuple->cch;
314
315 static RTSTRTUPLE const s_aDraining[2] =
316 {
317 { RT_STR_TUPLE("") },
318 { RT_STR_TUPLE(" DRAINING") },
319 };
320 pTuple = &s_aDraining[pStreamCA->fDraining];
321 memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch);
322 off += pTuple->cch;
323
324 Assert(off < sizeof(pStreamCA->szStatus));
325 pStreamCA->szStatus[off] = '\0';
326 return pStreamCA->szStatus;
327}
328#endif /*LOG_ENABLED*/
329
330
331
332
333#if 0 /* unused */
334static int drvHstAudCaCFStringToCString(const CFStringRef pCFString, char **ppszString)
335{
336 CFIndex cLen = CFStringGetLength(pCFString) + 1;
337 char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char));
338 if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8))
339 {
340 RTMemFree(pszResult);
341 return VERR_NOT_FOUND;
342 }
343
344 *ppszString = pszResult;
345 return VINF_SUCCESS;
346}
347
348static AudioDeviceID drvHstAudCaDeviceUIDtoID(const char* pszUID)
349{
350 /* Create a CFString out of our CString. */
351 CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman);
352
353 /* Fill the translation structure. */
354 AudioDeviceID deviceID;
355
356 AudioValueTranslation translation;
357 translation.mInputData = &strUID;
358 translation.mInputDataSize = sizeof(CFStringRef);
359 translation.mOutputData = &deviceID;
360 translation.mOutputDataSize = sizeof(AudioDeviceID);
361
362 /* Fetch the translation from the UID to the device ID. */
363 AudioObjectPropertyAddress PropAddr =
364 {
365 kAudioHardwarePropertyDeviceForUID,
366 kAudioObjectPropertyScopeGlobal,
367 kAudioObjectPropertyElementMaster
368 };
369
370 UInt32 uSize = sizeof(AudioValueTranslation);
371 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr, 0, NULL, &uSize, &translation);
372
373 /* Release the temporary CFString */
374 CFRelease(strUID);
375
376 if (RT_LIKELY(err == noErr))
377 return deviceID;
378
379 /* Return the unknown device on error. */
380 return kAudioDeviceUnknown;
381}
382#endif /* unused */
383
384
385/**
386 * Wrapper around AudioObjectGetPropertyData and AudioObjectGetPropertyDataSize.
387 *
388 * @returns Pointer to temp heap allocation with the data on success, free using
389 * RTMemTmpFree. NULL on failure, fully logged.
390 */
391static void *drvHstAudCaGetPropertyDataEx(AudioObjectID idObject, AudioObjectPropertySelector enmSelector,
392 AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement,
393 const char *pszWhat, UInt32 *pcb)
394{
395 AudioObjectPropertyAddress const PropAddr =
396 {
397 /*.mSelector = */ enmSelector,
398 /*.mScope = */ enmScope,
399 /*.mElement = */ enmElement
400 };
401
402 /*
403 * Have to retry here in case the size isn't stable (like if a new device/whatever is added).
404 */
405 for (uint32_t iTry = 0; ; iTry++)
406 {
407 UInt32 cb = 0;
408 OSStatus orc = AudioObjectGetPropertyDataSize(idObject, &PropAddr, 0, NULL, &cb);
409 if (orc == noErr)
410 {
411 cb = RT_MAX(cb, 1); /* we're allergic to zero allocations. */
412 void *pv = RTMemTmpAllocZ(cb);
413 if (pv)
414 {
415 orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv);
416 if (orc == noErr)
417 {
418 Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n",
419 idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb));
420 if (pcb)
421 *pcb = cb;
422 return pv;
423 }
424
425 RTMemTmpFree(pv);
426 LogFunc(("AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) -> %#x, iTry=%d\n",
427 idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc, iTry));
428 if (iTry < 3)
429 continue;
430 LogRelMax(32, ("CoreAudio: AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) failed: %#x\n",
431 idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc));
432 }
433 else
434 LogRelMax(32, ("CoreAudio: Failed to allocate %#x bytes (to get %s for %s).\n", cb, pszWhat, idObject));
435 }
436 else
437 LogRelMax(32, ("CoreAudio: Failed to get %s for %u: %#x\n", pszWhat, idObject, orc));
438 if (pcb)
439 *pcb = 0;
440 return NULL;
441 }
442}
443
444
445/**
446 * Wrapper around AudioObjectGetPropertyData.
447 *
448 * @returns Success indicator. Failures (@c false) are fully logged.
449 */
450static bool drvHstAudCaGetPropertyData(AudioObjectID idObject, AudioObjectPropertySelector enmSelector,
451 AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement,
452 const char *pszWhat, void *pv, UInt32 cb)
453{
454 AudioObjectPropertyAddress const PropAddr =
455 {
456 /*.mSelector = */ enmSelector,
457 /*.mScope = */ enmScope,
458 /*.mElement = */ enmElement
459 };
460
461 OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv);
462 if (orc == noErr)
463 {
464 Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n", idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb));
465 return true;
466 }
467 LogRelMax(64, ("CoreAudio: Failed to query %s (%u/%#x/%#x/%x, cb=%#x): %#x\n",
468 pszWhat, idObject, enmSelector, enmScope, enmElement, cb, orc));
469 return false;
470}
471
472
473/**
474 * Count the number of channels in one direction.
475 *
476 * @returns Channel count.
477 */
478static uint32_t drvHstAudCaEnumCountChannels(AudioObjectID idObject, AudioObjectPropertyScope enmScope)
479{
480 uint32_t cChannels = 0;
481
482 AudioBufferList *pBufs
483 = (AudioBufferList *)drvHstAudCaGetPropertyDataEx(idObject, kAudioDevicePropertyStreamConfiguration,
484 enmScope, kAudioObjectPropertyElementMaster, "stream config", NULL);
485 if (pBufs)
486 {
487 UInt32 idxBuf = pBufs->mNumberBuffers;
488 while (idxBuf-- > 0)
489 {
490 Log9Func(("%u/%#x[%u]: %u\n", idObject, enmScope, idxBuf, pBufs->mBuffers[idxBuf].mNumberChannels));
491 cChannels += pBufs->mBuffers[idxBuf].mNumberChannels;
492 }
493
494 RTMemTmpFree(pBufs);
495 }
496
497 return cChannels;
498}
499
500
501/**
502 * Translates a UID to an audio device ID.
503 *
504 * @returns Audio device ID on success, kAudioDeviceUnknown on failure.
505 * @param hStrUid The UID string to convert.
506 * @param pszUid The C-string vresion of @a hStrUid.
507 * @param pszWhat What we're converting (for logging).
508 */
509static AudioObjectID drvHstAudCaDeviceUidToId(CFStringRef hStrUid, const char *pszUid, const char *pszWhat)
510{
511 AudioObjectPropertyAddress const PropAddr =
512 {
513 /*.mSelector = */ kAudioHardwarePropertyTranslateUIDToDevice,
514 /*.mScope = */ kAudioObjectPropertyScopeGlobal,
515 /*.mElement = */ kAudioObjectPropertyElementMaster
516 };
517 AudioObjectID idDevice = 0;
518 UInt32 cb = sizeof(idDevice);
519 OSStatus orc = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr,
520 sizeof(hStrUid), &hStrUid, &cb, &idDevice);
521 if (orc == noErr)
522 {
523 Log9Func(("%s device UID '%s' -> %RU32\n", pszWhat, pszUid, idDevice));
524 return idDevice;
525 }
526 /** @todo test on < 10.9, see which status code and do a fallback using the
527 * enumeration code. */
528 LogRelMax(64, ("CoreAudio: Failed to translate %s device UID '%s' to audio device ID: %#x\n", pszWhat, pszUid, orc));
529 return kAudioDeviceUnknown;
530}
531
532
533/**
534 * Copies a CFString to a buffer (UTF-8).
535 *
536 * @returns VBox status code. In the case of a buffer overflow, the buffer will
537 * contain data and be correctly terminated (provided @a cbDst is not
538 * zero.)
539 */
540static int drvHstAudCaCFStringToBuf(CFStringRef hStr, char *pszDst, size_t cbDst)
541{
542 AssertReturn(cbDst > 0, VERR_BUFFER_OVERFLOW);
543
544 if (CFStringGetCString(hStr, pszDst, cbDst, kCFStringEncodingUTF8))
545 return VINF_SUCCESS;
546
547 /* First fallback: */
548 const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8);
549 if (pszSrc)
550 return RTStrCopy(pszDst, cbDst, pszSrc);
551
552 /* Second fallback: */
553 CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1;
554 AssertReturn(cbMax > 0, VERR_INVALID_UTF8_ENCODING);
555 AssertReturn(cbMax < (CFIndex)_16M, VERR_OUT_OF_RANGE);
556
557 char *pszTmp = (char *)RTMemTmpAlloc(cbMax);
558 AssertReturn(pszTmp, VERR_NO_TMP_MEMORY);
559
560 int rc;
561 if (CFStringGetCString(hStr, pszTmp, cbMax, kCFStringEncodingUTF8))
562 rc = RTStrCopy(pszDst, cbDst, pszTmp);
563 else
564 {
565 *pszDst = '\0';
566 rc = VERR_INVALID_UTF8_ENCODING;
567 }
568
569 RTMemTmpFree(pszTmp);
570 return rc;
571}
572
573
574/**
575 * Copies a CFString to a heap buffer (UTF-8).
576 *
577 * @returns Pointer to the heap buffer on success, NULL if out of heap or some
578 * conversion/extraction problem
579 */
580static char *drvHstAudCaCFStringToHeap(CFStringRef hStr)
581{
582 const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8);
583 if (pszSrc)
584 return RTStrDup(pszSrc);
585
586 /* Fallback: */
587 CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1;
588 AssertReturn(cbMax > 0, NULL);
589 AssertReturn(cbMax < (CFIndex)_16M, NULL);
590
591 char *pszDst = RTStrAlloc(cbMax);
592 if (pszDst)
593 {
594 AssertReturnStmt(CFStringGetCString(hStr, pszDst, cbMax, kCFStringEncodingUTF8), RTStrFree(pszDst), NULL);
595 size_t const cchDst = strlen(pszDst);
596 if (cbMax - cchDst > 32)
597 RTStrRealloc(&pszDst, cchDst + 1);
598 }
599 return pszDst;
600}
601
602
603/*********************************************************************************************************************************
604* Device Change Notification Callbacks *
605*********************************************************************************************************************************/
606
607#ifdef LOG_ENABLED
608/**
609 * Called when the kAudioDevicePropertyNominalSampleRate or
610 * kAudioDeviceProcessorOverload properties changes on a default device.
611 *
612 * Registered on default devices after device enumeration.
613 * Not sure on which thread/runloop this runs.
614 *
615 * (See AudioObjectPropertyListenerProc in the SDK headers.)
616 */
617static OSStatus drvHstAudCaDevicePropertyChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
618 const AudioObjectPropertyAddress paAddresses[], void *pvUser)
619{
620 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u pvUser=%p\n", idObject, idObject, cAddresses, pvUser));
621 for (UInt32 idx = 0; idx < cAddresses; idx++)
622 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
623 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
624
625/** @todo r=bird: What's the plan here exactly? I've changed it to
626 * LOG_ENABLED only for now, as this has no other purpose. */
627 switch (idObject)
628 {
629 case kAudioDeviceProcessorOverload:
630 LogFunc(("Processor overload detected!\n"));
631 break;
632 case kAudioDevicePropertyNominalSampleRate:
633 LogFunc(("kAudioDevicePropertyNominalSampleRate!\n"));
634 break;
635 default:
636 /* Just skip. */
637 break;
638 }
639
640 return noErr;
641}
642#endif /* LOG_ENABLED */
643
644
645/**
646 * Called when the kAudioDevicePropertyDeviceIsAlive property changes on a
647 * default device.
648 *
649 * The purpose is mainly to log the event. There isn't much we can do about
650 * active streams or future one, other than waiting for a default device change
651 * notification callback. In the mean time, active streams should start failing
652 * to work and new ones fail on creation. This is the same for when we're
653 * configure to use specific devices, only we don't get any device change
654 * callback like for default ones.
655 *
656 * Not sure on which thread/runloop this runs.
657 *
658 * (See AudioObjectPropertyListenerProc in the SDK headers.)
659 */
660static OSStatus drvHstAudCaDeviceIsAliveChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
661 const AudioObjectPropertyAddress paAddresses[], void *pvUser)
662{
663 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
664 AssertPtr(pThis);
665 RT_NOREF(cAddresses, paAddresses);
666
667 /*
668 * Log everything.
669 */
670 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses));
671 for (UInt32 idx = 0; idx < cAddresses; idx++)
672 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
673 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
674
675 /*
676 * Check which devices are affected.
677 */
678 int rc = RTCritSectEnter(&pThis->CritSect);
679 AssertRCReturn(rc, noErr); /* could be a destruction race */
680
681 for (unsigned i = 0; i < 2; i++)
682 {
683 if (idObject == (i == 0 ? pThis->InputDevice.idDevice : pThis->OutputDevice.idDevice))
684 {
685 AudioObjectPropertyAddress const PropAddr =
686 {
687 kAudioDevicePropertyDeviceIsAlive,
688 i == 0 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
689 kAudioObjectPropertyElementMaster
690 };
691 UInt32 fAlive = 0;
692 UInt32 cb = sizeof(fAlive);
693 OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, &fAlive);
694 if ( orc == kAudioHardwareBadDeviceError
695 || (orc == noErr && !fAlive))
696 {
697 LogRel(("CoreAudio: The default %s device (%u) stopped functioning.\n", idObject, i == 0 ? "input" : "output"));
698#if 0 /* This will only cause an extra re-init (in addition to the default device change) and likely do no good even if that
699 default device change callback doesn't arrive. So, don't do it! (bird) */
700 PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
701 if (pIHostAudioPort)
702 {
703 RTCritSectLeave(&pThis->CritSect);
704
705 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, i == 0 ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL);
706
707 rc = RTCritSectEnter(&pThis->CritSect);
708 AssertRCReturn(rc, noErr); /* could be a destruction race */
709 }
710#endif
711 }
712 }
713 }
714
715 RTCritSectLeave(&pThis->CritSect);
716 return noErr;
717}
718
719
720/**
721 * Called when the default recording or playback device has changed.
722 *
723 * Registered by the constructor. Not sure on which thread/runloop this runs.
724 *
725 * (See AudioObjectPropertyListenerProc in the SDK headers.)
726 */
727static OSStatus drvHstAudCaDefaultDeviceChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
728 const AudioObjectPropertyAddress *paAddresses, void *pvUser)
729
730{
731 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
732 AssertPtr(pThis);
733 RT_NOREF(idObject, cAddresses, paAddresses);
734
735 /*
736 * Log everything.
737 */
738 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses));
739 for (UInt32 idx = 0; idx < cAddresses; idx++)
740 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
741 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
742
743 /*
744 * Update the default devices and notify parent driver if anything actually changed.
745 */
746 drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/);
747 drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/);
748
749 return noErr;
750}
751
752
753/**
754 * Registers callbacks for a specific Core Audio device.
755 *
756 * @returns true if idDevice isn't kAudioDeviceUnknown and callbacks were
757 * registered, otherwise false.
758 * @param pThis The core audio driver instance data.
759 * @param idDevice The device ID to deregister callbacks for.
760 */
761static bool drvHstAudCaDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice)
762{
763 if (idDevice != kAudioDeviceUnknown)
764 {
765 LogFunc(("idDevice=%RU32\n", idDevice));
766 AudioObjectPropertyAddress PropAddr =
767 {
768 kAudioDevicePropertyDeviceIsAlive,
769 kAudioObjectPropertyScopeGlobal,
770 kAudioObjectPropertyElementMaster
771 };
772 OSStatus orc;
773 orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis);
774 unsigned cRegistrations = orc == noErr;
775 if ( orc != noErr
776 && orc != kAudioHardwareIllegalOperationError)
777 LogRel(("CoreAudio: Failed to add the recording device state changed listener (%#x)\n", orc));
778
779#ifdef LOG_ENABLED
780 PropAddr.mSelector = kAudioDeviceProcessorOverload;
781 PropAddr.mScope = kAudioUnitScope_Global;
782 orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
783 cRegistrations += orc == noErr;
784 if (orc != noErr)
785 LogRel(("CoreAudio: Failed to register processor overload listener (%#x)\n", orc));
786
787 PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
788 PropAddr.mScope = kAudioUnitScope_Global;
789 orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
790 cRegistrations += orc == noErr;
791 if (orc != noErr)
792 LogRel(("CoreAudio: Failed to register sample rate changed listener (%#x)\n", orc));
793#endif
794 return cRegistrations > 0;
795 }
796 return false;
797}
798
799
800/**
801 * Undoes what drvHstAudCaDeviceRegisterCallbacks() did.
802 *
803 * @param pThis The core audio driver instance data.
804 * @param idDevice The device ID to deregister callbacks for.
805 */
806static void drvHstAudCaDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice)
807{
808 if (idDevice != kAudioDeviceUnknown)
809 {
810 LogFunc(("idDevice=%RU32\n", idDevice));
811 AudioObjectPropertyAddress PropAddr =
812 {
813 kAudioDevicePropertyDeviceIsAlive,
814 kAudioObjectPropertyScopeGlobal,
815 kAudioObjectPropertyElementMaster
816 };
817 OSStatus orc;
818 orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis);
819 if ( orc != noErr
820 && orc != kAudioHardwareBadObjectError)
821 LogRel(("CoreAudio: Failed to remove the device alive listener (%#x)\n", orc));
822
823#ifdef LOG_ENABLED
824 PropAddr.mSelector = kAudioDeviceProcessorOverload;
825 orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
826 if ( orc != noErr
827 && orc != kAudioHardwareBadObjectError)
828 LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%#x)\n", orc));
829
830 PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
831 orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
832 if ( orc != noErr
833 && orc != kAudioHardwareBadObjectError)
834 LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%#x)\n", orc));
835#endif
836 }
837}
838
839
840/**
841 * Updates the default device for one direction.
842 *
843 * @param pThis The core audio driver instance data.
844 * @param pDevice The device information to update.
845 * @param fInput Set if input device, clear if output.
846 * @param fNotify Whether to notify the parent driver if something
847 * changed.
848 */
849static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify)
850{
851 /*
852 * Skip if there is a specific device we should use for this direction.
853 */
854 if (pDevice->pszSpecific)
855 return;
856
857 /*
858 * Get the information before we enter the critical section.
859 *
860 * (Yeah, this may make us get things wrong if the defaults changes really
861 * fast and we get notifications in parallel on multiple threads. However,
862 * the first is a don't-do-that situation and the latter is unlikely.)
863 */
864 AudioDeviceID idDefaultDev = kAudioDeviceUnknown;
865 if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject,
866 fInput ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice,
867 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
868 fInput ? "default input device" : "default output device",
869 &idDefaultDev, sizeof(idDefaultDev)))
870 idDefaultDev = kAudioDeviceUnknown;
871
872 CFStringRef hStrUid = NULL;
873 if (idDefaultDev != kAudioDeviceUnknown)
874 {
875 if (!drvHstAudCaGetPropertyData(idDefaultDev, kAudioDevicePropertyDeviceUID,
876 fInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
877 kAudioObjectPropertyElementMaster,
878 fInput ? "default input device UID" : "default output device UID",
879 &hStrUid, sizeof(hStrUid)))
880 hStrUid = NULL;
881 }
882 char szUid[128];
883 if (hStrUid)
884 drvHstAudCaCFStringToBuf(hStrUid, szUid, sizeof(szUid));
885 else
886 szUid[0] = '\0';
887
888 /*
889 * Grab the lock and do the updating.
890 *
891 * We're a little paranoid wrt the locking in case there turn out to be some kind
892 * of race around destruction (there really can't be, but better play safe).
893 */
894 PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL;
895
896 int rc = RTCritSectEnter(&pThis->CritSect);
897 AssertRC(rc);
898 if (RT_SUCCESS(rc))
899 {
900 if (idDefaultDev != pDevice->idDevice)
901 {
902 if (idDefaultDev != kAudioDeviceUnknown)
903 {
904 LogRel(("CoreAudio: Default %s device: %u (was %u), ID '%s'\n",
905 fInput ? "input" : "output", idDefaultDev, pDevice->idDevice, szUid));
906 pIHostAudioPort = fNotify ? pThis->pIHostAudioPort : NULL; /* (only if there is a new device) */
907 }
908 else
909 LogRel(("CoreAudio: Default %s device is gone (was %u)\n", fInput ? "input" : "output", pDevice->idDevice));
910
911 if (pDevice->hStrUid)
912 CFRelease(pDevice->hStrUid);
913 if (pDevice->fRegisteredListeners)
914 drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice);
915 pDevice->hStrUid = hStrUid;
916 pDevice->idDevice = idDefaultDev;
917 pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice);
918 hStrUid = NULL;
919 }
920 RTCritSectLeave(&pThis->CritSect);
921 }
922
923 if (hStrUid != NULL)
924 CFRelease(hStrUid);
925
926 /*
927 * Notify parent driver to trigger a re-init of any associated streams.
928 */
929 if (pIHostAudioPort)
930 {
931 LogFlowFunc(("Notifying parent driver about %s default device change...\n", fInput ? "input" : "output"));
932 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/);
933 }
934}
935
936
937/**
938 * Sets the device to use in one or the other direction (@a fInput).
939 *
940 * @returns VBox status code.
941 * @param pThis The core audio driver instance data.
942 * @param pDevice The device info structure to update.
943 * @param fInput Set if input, clear if output.
944 * @param fNotify Whether to notify the parent driver if something
945 * changed.
946 * @param pszUid The UID string for the device to use. NULL or empty
947 * string if default should be used.
948 */
949static int drvHstAudCaSetDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify,
950 const char *pszUid)
951{
952 if (!pszUid || !*pszUid)
953 {
954 /*
955 * Use default. Always refresh the given default device.
956 */
957 int rc = RTCritSectEnter(&pThis->CritSect);
958 AssertRCReturn(rc, rc);
959
960 if (pDevice->pszSpecific)
961 {
962 LogRel(("CoreAudio: Changing %s device from '%s' to default.\n", fInput ? "input" : "output", pDevice->pszSpecific));
963 RTStrFree(pDevice->pszSpecific);
964 pDevice->pszSpecific = NULL;
965 }
966
967 RTCritSectLeave(&pThis->CritSect);
968
969 drvHstAudCaUpdateOneDefaultDevice(pThis, pDevice, fInput, fNotify);
970 }
971 else
972 {
973 /*
974 * Use device specified by pszUid. If not change, search for the device
975 * again if idDevice is unknown.
976 */
977 int rc = RTCritSectEnter(&pThis->CritSect);
978 AssertRCReturn(rc, rc);
979
980 bool fSkip = false;
981 bool fSame = false;
982 if (pDevice->pszSpecific)
983 {
984 if (strcmp(pszUid, pDevice->pszSpecific) != 0)
985 {
986 LogRel(("CoreAudio: Changing %s device from '%s' to '%s'.\n",
987 fInput ? "input" : "output", pDevice->pszSpecific, pszUid));
988 RTStrFree(pDevice->pszSpecific);
989 pDevice->pszSpecific = NULL;
990 }
991 else
992 {
993 fSkip = pDevice->idDevice != kAudioDeviceUnknown;
994 fSame = true;
995 }
996 }
997 else
998 LogRel(("CoreAudio: Changing %s device from default to '%s'.\n", fInput ? "input" : "output", pszUid));
999
1000 /*
1001 * Allocate and swap the strings. This is the bit that might fail.
1002 */
1003 if (!fSame)
1004 {
1005 CFStringRef hStrUid = CFStringCreateWithBytes(NULL /*allocator*/, (UInt8 const *)pszUid, (CFIndex)strlen(pszUid),
1006 kCFStringEncodingUTF8, false /*isExternalRepresentation*/);
1007 char *pszSpecific = RTStrDup(pszUid);
1008 if (hStrUid && pszSpecific)
1009 {
1010 if (pDevice->hStrUid)
1011 CFRelease(pDevice->hStrUid);
1012 pDevice->hStrUid = hStrUid;
1013 RTStrFree(pDevice->pszSpecific);
1014 pDevice->pszSpecific = pszSpecific;
1015 }
1016 else
1017 {
1018 RTCritSectLeave(&pThis->CritSect);
1019
1020 LogFunc(("returns VERR_NO_STR_MEMORY!\n"));
1021 if (hStrUid)
1022 CFRelease(hStrUid);
1023 RTStrFree(pszSpecific);
1024 return VERR_NO_STR_MEMORY;
1025 }
1026
1027 if (pDevice->fRegisteredListeners)
1028 {
1029 drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice);
1030 pDevice->fRegisteredListeners = false;
1031 }
1032 }
1033
1034 /*
1035 * Locate the device ID corresponding to the UID string.
1036 */
1037 if (!fSkip)
1038 {
1039 pDevice->idDevice = drvHstAudCaDeviceUidToId(pDevice->hStrUid, pszUid, fInput ? "input" : "output");
1040 pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice);
1041 }
1042
1043 PPDMIHOSTAUDIOPORT pIHostAudioPort = fNotify && !fSame ? pThis->pIHostAudioPort : NULL;
1044 RTCritSectLeave(&pThis->CritSect);
1045
1046 /*
1047 * Notify parent driver to trigger a re-init of any associated streams.
1048 */
1049 if (pIHostAudioPort)
1050 {
1051 LogFlowFunc(("Notifying parent driver about %s device change...\n", fInput ? "input" : "output"));
1052 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/);
1053 }
1054 }
1055 return VINF_SUCCESS;
1056}
1057
1058
1059/*********************************************************************************************************************************
1060* Worker Thread *
1061*********************************************************************************************************************************/
1062#ifdef CORE_AUDIO_WITH_WORKER_THREAD
1063
1064/**
1065 * Message handling callback for CFMachPort.
1066 */
1067static void drvHstAudCaThreadPortCallback(CFMachPortRef hPort, void *pvMsg, CFIndex cbMsg, void *pvUser)
1068{
1069 RT_NOREF(hPort, pvMsg, cbMsg, pvUser);
1070 LogFunc(("hPort=%p pvMsg=%p cbMsg=%#x pvUser=%p\n", hPort, pvMsg, cbMsg, pvUser));
1071}
1072
1073
1074/**
1075 * @callback_method_impl{FNRTTHREAD, Worker thread for buffer callbacks.}
1076 */
1077static DECLCALLBACK(int) drvHstAudCaThread(RTTHREAD hThreadSelf, void *pvUser)
1078{
1079 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
1080
1081 /*
1082 * Get the runloop, add the mach port to it and signal the constructor thread that we're ready.
1083 */
1084 pThis->hThreadRunLoop = CFRunLoopGetCurrent();
1085 CFRetain(pThis->hThreadRunLoop);
1086
1087 CFRunLoopAddSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode);
1088
1089 int rc = RTThreadUserSignal(hThreadSelf);
1090 AssertRCReturn(rc, rc);
1091
1092 /*
1093 * Do work.
1094 */
1095 for (;;)
1096 {
1097 SInt32 rcRunLoop = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 30.0, TRUE);
1098 Log8Func(("CFRunLoopRunInMode -> %d\n", rcRunLoop));
1099 Assert(rcRunLoop != kCFRunLoopRunFinished);
1100 if (rcRunLoop != kCFRunLoopRunStopped && rcRunLoop != kCFRunLoopRunFinished)
1101 { /* likely */ }
1102 else
1103 break;
1104 }
1105
1106 /*
1107 * Clean up.
1108 */
1109 CFRunLoopRemoveSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode);
1110 LogFunc(("The thread quits!\n"));
1111 return VINF_SUCCESS;
1112}
1113
1114#endif /* CORE_AUDIO_WITH_WORKER_THREAD */
1115
1116
1117
1118/*********************************************************************************************************************************
1119* PDMIHOSTAUDIO *
1120*********************************************************************************************************************************/
1121
1122/**
1123 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
1124 */
1125static DECLCALLBACK(int) drvHstAudCaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
1126{
1127 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1128 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
1129
1130 /*
1131 * Fill in the config structure.
1132 */
1133 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio");
1134 pBackendCfg->cbStream = sizeof(COREAUDIOSTREAM);
1135 pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY;
1136
1137 RTCritSectEnter(&pThis->CritSect);
1138#if 0 /** @todo r=bird: This looks like complete utter non-sense to me. */
1139 /* For Core Audio we provide one stream per device for now. */
1140 pBackendCfg->cMaxStreamsIn = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_IN);
1141 pBackendCfg->cMaxStreamsOut = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_OUT);
1142#else
1143 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
1144 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
1145#endif
1146 RTCritSectLeave(&pThis->CritSect);
1147
1148 LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS));
1149 return VINF_SUCCESS;
1150}
1151
1152
1153/**
1154 * Creates an enumeration of the host's playback and capture devices.
1155 *
1156 * @returns VBox status code.
1157 * @param pDevEnm Where to store the enumerated devices. Caller is
1158 * expected to clean this up on failure, if so desired.
1159 *
1160 * @note Handling of out-of-memory conditions isn't perhaps as good as it
1161 * could be, but it was done so to make the drvHstAudCaGetPropertyData*
1162 * functions as uncomplicated as possible.
1163 */
1164static int drvHstAudCaDevicesEnumerateAll(PPDMAUDIOHOSTENUM pDevEnm)
1165{
1166 AssertPtr(pDevEnm);
1167
1168 /*
1169 * First get the UIDs for the default devices.
1170 */
1171 AudioDeviceID idDefaultDevIn = kAudioDeviceUnknown;
1172 if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
1173 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
1174 "default input device", &idDefaultDevIn, sizeof(idDefaultDevIn)))
1175 idDefaultDevIn = kAudioDeviceUnknown;
1176 if (idDefaultDevIn == kAudioDeviceUnknown)
1177 LogFunc(("No default input device\n"));
1178
1179 AudioDeviceID idDefaultDevOut = kAudioDeviceUnknown;
1180 if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
1181 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
1182 "default output device", &idDefaultDevOut, sizeof(idDefaultDevOut)))
1183 idDefaultDevOut = kAudioDeviceUnknown;
1184 if (idDefaultDevOut == kAudioDeviceUnknown)
1185 LogFunc(("No default output device\n"));
1186
1187 /*
1188 * Get a list of all audio devices.
1189 * (We have to retry as the we may race new devices being inserted.)
1190 */
1191 UInt32 cDevices = 0;
1192 AudioDeviceID *paidDevices
1193 = (AudioDeviceID *)drvHstAudCaGetPropertyDataEx(kAudioObjectSystemObject, kAudioHardwarePropertyDevices,
1194 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
1195 "devices", &cDevices);
1196 cDevices /= sizeof(paidDevices[0]);
1197
1198 /*
1199 * Try get details on each device and try add them to the enumeration result.
1200 */
1201 for (uint32_t i = 0; i < cDevices; i++)
1202 {
1203 AudioDeviceID const idDevice = paidDevices[i];
1204
1205 /*
1206 * Allocate a new device entry and populate it.
1207 *
1208 * The only relevant information here is channel counts and the UID(s),
1209 * everything else is just extras we can live without.
1210 */
1211 PCOREAUDIODEVICEDATA pDevEntry = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDevEntry), 0, 0);
1212 AssertReturnStmt(pDevEntry, RTMemTmpFree(paidDevices), VERR_NO_MEMORY);
1213
1214 pDevEntry->idDevice = idDevice;
1215 if (idDevice != kAudioDeviceUnknown)
1216 {
1217 if (idDevice == idDefaultDevIn)
1218 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_IN;
1219 if (idDevice == idDefaultDevOut)
1220 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_OUT;
1221 }
1222
1223 /* Count channels and determin the usage. */
1224 pDevEntry->Core.cMaxInputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeInput);
1225 pDevEntry->Core.cMaxOutputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeOutput);
1226 if (pDevEntry->Core.cMaxInputChannels > 0 && pDevEntry->Core.cMaxOutputChannels > 0)
1227 pDevEntry->Core.enmUsage = PDMAUDIODIR_DUPLEX;
1228 else if (pDevEntry->Core.cMaxInputChannels > 0)
1229 pDevEntry->Core.enmUsage = PDMAUDIODIR_IN;
1230 else if (pDevEntry->Core.cMaxOutputChannels > 0)
1231 pDevEntry->Core.enmUsage = PDMAUDIODIR_OUT;
1232 else
1233 {
1234 pDevEntry->Core.enmUsage = PDMAUDIODIR_UNKNOWN;
1235 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE;
1236 /** @todo drop & skip? */
1237 }
1238
1239 /* Get the device UID. (We ASSUME this is the same for both input and
1240 output sides of the device.) */
1241 CFStringRef hStrUid;
1242 if (!drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceUID, kAudioDevicePropertyDeviceUID,
1243 kAudioObjectPropertyElementMaster,
1244 "device UID", &hStrUid, sizeof(hStrUid)))
1245 hStrUid = NULL;
1246
1247 if (hStrUid)
1248 {
1249 pDevEntry->Core.pszId = drvHstAudCaCFStringToHeap(hStrUid);
1250 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_ID_ALLOC;
1251 }
1252 else
1253 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE;
1254
1255 /* Get the device name (ignore failures). */
1256 CFStringRef hStrName = NULL;
1257 if (drvHstAudCaGetPropertyData(idDevice, kAudioObjectPropertyName,
1258 pDevEntry->Core.enmUsage == PDMAUDIODIR_IN
1259 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
1260 kAudioObjectPropertyElementMaster, "device name", &hStrName, sizeof(hStrName)))
1261 {
1262 pDevEntry->Core.pszName = drvHstAudCaCFStringToHeap(hStrName);
1263 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_NAME_ALLOC;
1264 CFRelease(hStrName);
1265 }
1266
1267 /* Check if the device is alive for the intended usage. For duplex
1268 devices we'll flag it as dead if either of the directions are dead,
1269 as there is no convenient way of saying otherwise. It's acadmic as
1270 nobody currently 2021-05-22) uses the flag for anything. */
1271 UInt32 fAlive = 0;
1272 if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive,
1273 pDevEntry->Core.enmUsage == PDMAUDIODIR_IN
1274 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
1275 kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive)))
1276 if (!fAlive)
1277 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD;
1278 fAlive = 0;
1279 if ( pDevEntry->Core.enmUsage == PDMAUDIODIR_DUPLEX
1280 && !(pDevEntry->Core.fFlags == PDMAUDIOHOSTDEV_F_DEAD)
1281 && drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive, kAudioDevicePropertyScopeInput,
1282 kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive)))
1283 if (!fAlive)
1284 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD;
1285
1286 /* Check if the device is being hogged by someone else. */
1287 pid_t pidHogger = -2;
1288 if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyHogMode, kAudioObjectPropertyScopeGlobal,
1289 kAudioObjectPropertyElementMaster, "hog-mode", &pidHogger, sizeof(pidHogger)))
1290 if (pidHogger >= 0)
1291 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_LOCKED;
1292
1293 /*
1294 * Try make sure we've got a name... Only add it to the enumeration if we have one.
1295 */
1296 if (!pDevEntry->Core.pszName)
1297 {
1298 pDevEntry->Core.pszName = pDevEntry->Core.pszId;
1299 pDevEntry->Core.fFlags &= ~PDMAUDIOHOSTDEV_F_NAME_ALLOC;
1300 }
1301
1302 if (pDevEntry->Core.pszName)
1303 PDMAudioHostEnumAppend(pDevEnm, &pDevEntry->Core);
1304 else
1305 PDMAudioHostDevFree(&pDevEntry->Core);
1306 }
1307
1308 RTMemTmpFree(paidDevices);
1309
1310 LogFunc(("Returning %u devices\n", pDevEnm->cDevices));
1311 PDMAudioHostEnumLog(pDevEnm, "Core Audio");
1312 return VINF_SUCCESS;
1313}
1314
1315
1316/**
1317 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
1318 */
1319static DECLCALLBACK(int) drvHstAudCaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
1320{
1321 RT_NOREF(pInterface);
1322 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
1323
1324 PDMAudioHostEnumInit(pDeviceEnum);
1325 int rc = drvHstAudCaDevicesEnumerateAll(pDeviceEnum);
1326 if (RT_FAILURE(rc))
1327 PDMAudioHostEnumDelete(pDeviceEnum);
1328
1329 LogFlowFunc(("returns %Rrc\n", rc));
1330 return rc;
1331}
1332
1333
1334/**
1335 * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
1336 */
1337static DECLCALLBACK(int) drvHstAudCaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
1338{
1339 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1340 AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
1341 if (pszId && !*pszId)
1342 pszId = NULL;
1343 AssertMsgReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX,
1344 ("enmDir=%d\n", enmDir, pszId), VERR_INVALID_PARAMETER);
1345
1346 /*
1347 * Make the change.
1348 */
1349 int rc = VINF_SUCCESS;
1350 if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
1351 rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/, pszId);
1352 if (enmDir == PDMAUDIODIR_OUT || (enmDir == PDMAUDIODIR_DUPLEX && RT_SUCCESS(rc)))
1353 rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/, pszId);
1354
1355 LogFlowFunc(("returns %Rrc\n", rc));
1356 return rc;
1357}
1358
1359
1360/**
1361 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1362 */
1363static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudCaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1364{
1365 RT_NOREF(pInterface, enmDir);
1366 return PDMAUDIOBACKENDSTS_RUNNING;
1367}
1368
1369
1370/**
1371 * Marks the given buffer as queued or not-queued.
1372 *
1373 * @returns Old queued value.
1374 * @param pAudioBuffer The buffer.
1375 * @param fQueued The new queued state.
1376 */
1377DECLINLINE(bool) drvHstAudCaSetBufferQueued(AudioQueueBufferRef pAudioBuffer, bool fQueued)
1378{
1379 if (fQueued)
1380 return ASMAtomicBitTestAndSet(&pAudioBuffer->mUserData, 0);
1381 return ASMAtomicBitTestAndClear(&pAudioBuffer->mUserData, 0);
1382}
1383
1384
1385/**
1386 * Gets the queued state of the buffer.
1387 * @returns true if queued, false if not.
1388 * @param pAudioBuffer The buffer.
1389 */
1390DECLINLINE(bool) drvHstAudCaIsBufferQueued(AudioQueueBufferRef pAudioBuffer)
1391{
1392 return ((uintptr_t)pAudioBuffer->mUserData & 1) == 1;
1393}
1394
1395
1396/**
1397 * Output audio queue buffer callback.
1398 *
1399 * Called whenever an audio queue is done processing a buffer. This routine
1400 * will set the data fill size to zero and mark it as unqueued so that
1401 * drvHstAudCaHA_StreamPlay knowns it can use it.
1402 *
1403 * @param pvUser User argument.
1404 * @param hAudioQueue Audio queue to process output data for.
1405 * @param pAudioBuffer Audio buffer to store output data in.
1406 *
1407 * @thread queue thread.
1408 */
1409static void drvHstAudCaOutputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue, AudioQueueBufferRef pAudioBuffer)
1410{
1411#if defined(VBOX_STRICT) || defined(LOG_ENABLED)
1412 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
1413 AssertPtr(pStreamCA);
1414 Assert(pStreamCA->hAudioQueue == hAudioQueue);
1415
1416 uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8;
1417 Log4Func(("Got back buffer #%zu (%p)\n", idxBuf, pAudioBuffer));
1418 AssertReturnVoid( idxBuf < pStreamCA->cBuffers
1419 && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer);
1420#endif
1421
1422 pAudioBuffer->mAudioDataByteSize = 0;
1423 bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/);
1424 Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer));
1425 Assert(fWasQueued); RT_NOREF(fWasQueued);
1426
1427 RT_NOREF(pvUser, hAudioQueue);
1428}
1429
1430
1431/**
1432 * Input audio queue buffer callback.
1433 *
1434 * Called whenever input data from the audio queue becomes available. This
1435 * routine will mark the buffer unqueued so that drvHstAudCaHA_StreamCapture can
1436 * read the data from it.
1437 *
1438 * @param pvUser User argument.
1439 * @param hAudioQueue Audio queue to process input data from.
1440 * @param pAudioBuffer Audio buffer to process input data from.
1441 * @param pAudioTS Audio timestamp.
1442 * @param cPacketDesc Number of packet descriptors.
1443 * @param paPacketDesc Array of packet descriptors.
1444 */
1445static void drvHstAudCaInputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue,
1446 AudioQueueBufferRef pAudioBuffer, const AudioTimeStamp *pAudioTS,
1447 UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc)
1448{
1449#if defined(VBOX_STRICT) || defined(LOG_ENABLED)
1450 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
1451 AssertPtr(pStreamCA);
1452 Assert(pStreamCA->hAudioQueue == hAudioQueue);
1453
1454 uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8;
1455 Log4Func(("Got back buffer #%zu (%p) with %#x bytes\n", idxBuf, pAudioBuffer, pAudioBuffer->mAudioDataByteSize));
1456 AssertReturnVoid( idxBuf < pStreamCA->cBuffers
1457 && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer);
1458#endif
1459
1460 bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/);
1461 Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer));
1462 Assert(fWasQueued); RT_NOREF(fWasQueued);
1463
1464 RT_NOREF(pvUser, hAudioQueue, pAudioTS, cPacketDesc, paPacketDesc);
1465}
1466
1467
1468static void drvHstAudCaLogAsbd(const char *pszDesc, const AudioStreamBasicDescription *pASBD)
1469{
1470 LogRel2(("CoreAudio: %s description:\n", pszDesc));
1471 LogRel2(("CoreAudio: Format ID: %#RX32 (%c%c%c%c)\n", pASBD->mFormatID,
1472 RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID),
1473 RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID)));
1474 LogRel2(("CoreAudio: Flags: %#RX32", pASBD->mFormatFlags));
1475 if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat)
1476 LogRel2((" Float"));
1477 if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian)
1478 LogRel2((" BigEndian"));
1479 if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger)
1480 LogRel2((" SignedInteger"));
1481 if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked)
1482 LogRel2((" Packed"));
1483 if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh)
1484 LogRel2((" AlignedHigh"));
1485 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved)
1486 LogRel2((" NonInterleaved"));
1487 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable)
1488 LogRel2((" NonMixable"));
1489 if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear)
1490 LogRel2((" AllClear"));
1491 LogRel2(("\n"));
1492 LogRel2(("CoreAudio: SampleRate : %RU64.%02u Hz\n",
1493 (uint64_t)pASBD->mSampleRate, (unsigned)(pASBD->mSampleRate * 100) % 100));
1494 LogRel2(("CoreAudio: ChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame));
1495 LogRel2(("CoreAudio: FramesPerPacket : %RU32\n", pASBD->mFramesPerPacket));
1496 LogRel2(("CoreAudio: BitsPerChannel : %RU32\n", pASBD->mBitsPerChannel));
1497 LogRel2(("CoreAudio: BytesPerFrame : %RU32\n", pASBD->mBytesPerFrame));
1498 LogRel2(("CoreAudio: BytesPerPacket : %RU32\n", pASBD->mBytesPerPacket));
1499}
1500
1501
1502static void drvHstAudCaPropsToAsbd(PCPDMAUDIOPCMPROPS pProps, AudioStreamBasicDescription *pASBD)
1503{
1504 AssertPtrReturnVoid(pProps);
1505 AssertPtrReturnVoid(pASBD);
1506
1507 RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription));
1508
1509 pASBD->mFormatID = kAudioFormatLinearPCM;
1510 pASBD->mFormatFlags = kAudioFormatFlagIsPacked;
1511 if (pProps->fSigned)
1512 pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
1513 if (PDMAudioPropsIsBigEndian(pProps))
1514 pASBD->mFormatFlags |= kAudioFormatFlagIsBigEndian;
1515 pASBD->mSampleRate = PDMAudioPropsHz(pProps);
1516 pASBD->mChannelsPerFrame = PDMAudioPropsChannels(pProps);
1517 pASBD->mBitsPerChannel = PDMAudioPropsSampleBits(pProps);
1518 pASBD->mBytesPerFrame = PDMAudioPropsFrameSize(pProps);
1519 pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */
1520 pASBD->mBytesPerPacket = PDMAudioPropsFrameSize(pProps) * pASBD->mFramesPerPacket;
1521}
1522
1523
1524/**
1525 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1526 */
1527static DECLCALLBACK(int) drvHstAudCaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1528 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1529{
1530 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1531 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1532 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1533 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1534 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1535 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1536 int rc;
1537
1538 /** @todo This takes too long. Stats indicates it may take up to 200 ms.
1539 * Knoppix guest resets the stream and we hear nada because the
1540 * draining is aborted when the stream is destroyed. Should try use
1541 * async init for parts (much) of this. */
1542
1543 /*
1544 * Permission check for input devices before we start.
1545 */
1546 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1547 {
1548 rc = coreAudioInputPermissionCheck();
1549 if (RT_FAILURE(rc))
1550 return rc;
1551 }
1552
1553 /*
1554 * Do we have a device for the requested stream direction?
1555 */
1556 RTCritSectEnter(&pThis->CritSect);
1557 CFStringRef hDevUidStr = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->InputDevice.hStrUid : pThis->OutputDevice.hStrUid;
1558 if (hDevUidStr)
1559 CFRetain(hDevUidStr);
1560 RTCritSectLeave(&pThis->CritSect);
1561
1562#ifdef LOG_ENABLED
1563 char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
1564#endif
1565 LogFunc(("hDevUidStr=%p *pCfgReq: %s\n", hDevUidStr, PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp)) ));
1566 if (hDevUidStr)
1567 {
1568 /*
1569 * Basic structure init.
1570 */
1571 pStreamCA->fEnabled = false;
1572 pStreamCA->fStarted = false;
1573 pStreamCA->fDraining = false;
1574 pStreamCA->fRestartOnResume = false;
1575 pStreamCA->offInternal = 0;
1576 pStreamCA->idxBuffer = 0;
1577 pStreamCA->enmInitState = COREAUDIOINITSTATE_IN_INIT;
1578
1579 rc = RTCritSectInit(&pStreamCA->CritSect);
1580 if (RT_SUCCESS(rc))
1581 {
1582 /*
1583 * Do format conversion and create the circular buffer we use to shuffle
1584 * data to/from the queue thread.
1585 */
1586 PDMAudioStrmCfgCopy(&pStreamCA->Cfg, pCfgReq);
1587 drvHstAudCaPropsToAsbd(&pCfgReq->Props, &pStreamCA->BasicStreamDesc);
1588 /** @todo Do some validation? */
1589 drvHstAudCaLogAsbd(pCfgReq->enmDir == PDMAUDIODIR_IN ? "Capturing queue format" : "Playback queue format",
1590 &pStreamCA->BasicStreamDesc);
1591 /*
1592 * Create audio queue.
1593 *
1594 * Documentation says the callbacks will be run on some core audio
1595 * related thread if we don't specify a runloop here. That's simpler.
1596 */
1597#ifdef CORE_AUDIO_WITH_WORKER_THREAD
1598 CFRunLoopRef const hRunLoop = pThis->hThreadRunLoop;
1599 CFStringRef const hRunLoopMode = kCFRunLoopDefaultMode;
1600#else
1601 CFRunLoopRef const hRunLoop = NULL;
1602 CFStringRef const hRunLoopMode = NULL;
1603#endif
1604 OSStatus orc;
1605 if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
1606 orc = AudioQueueNewOutput(&pStreamCA->BasicStreamDesc, drvHstAudCaOutputQueueBufferCallback, pStreamCA,
1607 hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue);
1608 else
1609 orc = AudioQueueNewInput(&pStreamCA->BasicStreamDesc, drvHstAudCaInputQueueBufferCallback, pStreamCA,
1610 hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue);
1611 LogFlowFunc(("AudioQueueNew%s -> %#x\n", pCfgReq->enmDir == PDMAUDIODIR_OUT ? "Output" : "Input", orc));
1612 if (orc == noErr)
1613 {
1614 /*
1615 * Assign device to the queue.
1616 */
1617 UInt32 uSize = sizeof(hDevUidStr);
1618 orc = AudioQueueSetProperty(pStreamCA->hAudioQueue, kAudioQueueProperty_CurrentDevice, &hDevUidStr, uSize);
1619 LogFlowFunc(("AudioQueueSetProperty -> %#x\n", orc));
1620 if (orc == noErr)
1621 {
1622 /*
1623 * Sanity-adjust the requested buffer size.
1624 */
1625 uint32_t cFramesBufferSizeMax = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 2 * RT_MS_1SEC);
1626 uint32_t cFramesBufferSize = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 32 /*ms*/);
1627 cFramesBufferSize = RT_MAX(cFramesBufferSize, pCfgReq->Backend.cFramesBufferSize);
1628 cFramesBufferSize = RT_MIN(cFramesBufferSize, cFramesBufferSizeMax);
1629
1630 /*
1631 * The queue buffers size is based on cMsSchedulingHint so that we're likely to
1632 * have a new one ready/done after each guest DMA transfer. We must however
1633 * make sure we don't end up with too may or too few.
1634 */
1635 Assert(pCfgReq->Device.cMsSchedulingHint > 0);
1636 uint32_t cFramesQueueBuffer = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props,
1637 pCfgReq->Device.cMsSchedulingHint > 0
1638 ? pCfgReq->Device.cMsSchedulingHint : 10);
1639 uint32_t cQueueBuffers;
1640 if (cFramesQueueBuffer * COREAUDIO_MIN_BUFFERS <= cFramesBufferSize)
1641 {
1642 cQueueBuffers = cFramesBufferSize / cFramesQueueBuffer;
1643 if (cQueueBuffers > COREAUDIO_MAX_BUFFERS)
1644 {
1645 cQueueBuffers = COREAUDIO_MAX_BUFFERS;
1646 cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MAX_BUFFERS;
1647 }
1648 }
1649 else
1650 {
1651 cQueueBuffers = COREAUDIO_MIN_BUFFERS;
1652 cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MIN_BUFFERS;
1653 }
1654
1655 cFramesBufferSize = cQueueBuffers * cFramesBufferSize;
1656
1657 /*
1658 * Allocate the audio queue buffers.
1659 */
1660 pStreamCA->paBuffers = (PCOREAUDIOBUF)RTMemAllocZ(sizeof(pStreamCA->paBuffers[0]) * cQueueBuffers);
1661 if (pStreamCA->paBuffers != NULL)
1662 {
1663 pStreamCA->cBuffers = cQueueBuffers;
1664
1665 const size_t cbQueueBuffer = PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, cFramesQueueBuffer);
1666 LogFlowFunc(("Allocating %u, each %#x bytes / %u frames\n", cQueueBuffers, cbQueueBuffer, cFramesQueueBuffer));
1667 cFramesBufferSize = 0;
1668 for (uint32_t iBuf = 0; iBuf < cQueueBuffers; iBuf++)
1669 {
1670 AudioQueueBufferRef pBuf = NULL;
1671 orc = AudioQueueAllocateBuffer(pStreamCA->hAudioQueue, cbQueueBuffer, &pBuf);
1672 if (RT_LIKELY(orc == noErr))
1673 {
1674 pBuf->mUserData = (void *)(uintptr_t)(iBuf << 8); /* bit zero is the queued-indicator. */
1675 pStreamCA->paBuffers[iBuf].pBuf = pBuf;
1676 cFramesBufferSize += PDMAudioPropsBytesToFrames(&pStreamCA->Cfg.Props,
1677 pBuf->mAudioDataBytesCapacity);
1678 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, pBuf->mAudioDataBytesCapacity));
1679 }
1680 else
1681 {
1682 LogRel(("CoreAudio: Out of memory (buffer %#x out of %#x, %#x bytes)\n",
1683 iBuf, cQueueBuffers, cbQueueBuffer));
1684 while (iBuf-- > 0)
1685 {
1686 AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
1687 pStreamCA->paBuffers[iBuf].pBuf = NULL;
1688 }
1689 break;
1690 }
1691 }
1692 if (orc == noErr)
1693 {
1694 /*
1695 * Update the stream config.
1696 */
1697 pStreamCA->Cfg.Backend.cFramesBufferSize = cFramesBufferSize;
1698 pStreamCA->Cfg.Backend.cFramesPeriod = cFramesQueueBuffer; /* whatever */
1699 pStreamCA->Cfg.Backend.cFramesPreBuffering = pStreamCA->Cfg.Backend.cFramesPreBuffering
1700 * pStreamCA->Cfg.Backend.cFramesBufferSize
1701 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
1702
1703 PDMAudioStrmCfgCopy(pCfgAcq, &pStreamCA->Cfg);
1704
1705 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_INIT);
1706
1707 LogFunc(("returns VINF_SUCCESS\n"));
1708 CFRelease(hDevUidStr);
1709 return VINF_SUCCESS;
1710 }
1711
1712 RTMemFree(pStreamCA->paBuffers);
1713 }
1714 else
1715 rc = VERR_NO_MEMORY;
1716 }
1717 else
1718 LogRelMax(64, ("CoreAudio: Failed to associate device with queue: %#x (%d)\n", orc, orc));
1719 AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
1720 }
1721 else
1722 LogRelMax(64, ("CoreAudio: Failed to create audio queue: %#x (%d)\n", orc, orc));
1723 RTCritSectDelete(&pStreamCA->CritSect);
1724 }
1725 else
1726 LogRel(("CoreAudio: Failed to initialize critical section for stream: %Rrc\n", rc));
1727 CFRelease(hDevUidStr);
1728 }
1729 else
1730 {
1731 LogRelMax(64, ("CoreAudio: No device for %s stream.\n", PDMAudioDirGetName(pCfgReq->enmDir)));
1732 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1733 }
1734
1735 LogFunc(("returns %Rrc\n", rc));
1736 return rc;
1737}
1738
1739
1740/**
1741 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1742 */
1743static DECLCALLBACK(int) drvHstAudCaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
1744{
1745 RT_NOREF(pInterface);
1746 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1747 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1748 LogFunc(("%p: %s fImmediate=%RTbool\n", pStreamCA, pStreamCA->Cfg.szName, fImmediate));
1749#ifdef LOG_ENABLED
1750 uint64_t const nsStart = RTTimeNanoTS();
1751#endif
1752
1753 /*
1754 * Never mind if the status isn't INIT (it should always be, though).
1755 */
1756 COREAUDIOINITSTATE const enmInitState = (COREAUDIOINITSTATE)ASMAtomicReadU32(&pStreamCA->enmInitState);
1757 AssertMsg(enmInitState == COREAUDIOINITSTATE_INIT, ("%d\n", enmInitState));
1758 if (enmInitState == COREAUDIOINITSTATE_INIT)
1759 {
1760 Assert(RTCritSectIsInitialized(&pStreamCA->CritSect));
1761
1762 /*
1763 * Change the stream state and stop the stream (just to be sure).
1764 */
1765 OSStatus orc;
1766 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_IN_UNINIT);
1767 if (pStreamCA->hAudioQueue)
1768 {
1769 orc = AudioQueueStop(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/);
1770 LogFlowFunc(("AudioQueueStop -> %#x\n", orc));
1771 }
1772
1773 /*
1774 * Enter and leave the critsect afterwards for paranoid reasons.
1775 */
1776 RTCritSectEnter(&pStreamCA->CritSect);
1777 RTCritSectLeave(&pStreamCA->CritSect);
1778
1779 /*
1780 * Free the queue buffers and the queue.
1781 *
1782 * This may take a while. The AudioQueueReset call seems to helps
1783 * reducing time stuck in AudioQueueDispose.
1784 */
1785#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1786 LogRel(("Queue-destruction timer starting...\n"));
1787 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1788 RTTimerLRStart(pThis->hBreakpointTimer, RT_NS_100MS);
1789 uint64_t nsStart = RTTimeNanoTS();
1790#endif
1791
1792#if 0 /* This seems to work even when doing a non-immediate stop&dispose. However, it doesn't make sense conceptually. */
1793 if (pStreamCA->hAudioQueue /*&& fImmediate*/)
1794 {
1795 LogFlowFunc(("Calling AudioQueueReset ...\n"));
1796 orc = AudioQueueReset(pStreamCA->hAudioQueue);
1797 LogFlowFunc(("AudioQueueReset -> %#x\n", orc));
1798 }
1799#endif
1800
1801 if (pStreamCA->paBuffers && fImmediate)
1802 {
1803 LogFlowFunc(("Freeing %u buffers ...\n", pStreamCA->cBuffers));
1804 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1805 {
1806 orc = AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
1807 AssertMsg(orc == noErr, ("AudioQueueFreeBuffer(#%u) -> orc=%#x\n", iBuf, orc));
1808 pStreamCA->paBuffers[iBuf].pBuf = NULL;
1809 }
1810 }
1811
1812 if (pStreamCA->hAudioQueue)
1813 {
1814 LogFlowFunc(("Disposing of the queue ...\n"));
1815 orc = AudioQueueDispose(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/); /* may take some time */
1816 LogFlowFunc(("AudioQueueDispose -> %#x (%d)\n", orc, orc));
1817 AssertMsg(orc == noErr, ("AudioQueueDispose -> orc=%#x\n", orc));
1818 pStreamCA->hAudioQueue = NULL;
1819 }
1820
1821 /* We should get no further buffer callbacks at this point according to the docs. */
1822 if (pStreamCA->paBuffers)
1823 {
1824 RTMemFree(pStreamCA->paBuffers);
1825 pStreamCA->paBuffers = NULL;
1826 }
1827 pStreamCA->cBuffers = 0;
1828
1829#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1830 RTTimerLRStop(pThis->hBreakpointTimer);
1831 LogRel(("Queue-destruction: %'RU64\n", RTTimeNanoTS() - nsStart));
1832#endif
1833
1834 /*
1835 * Delete the critsect and we're done.
1836 */
1837 RTCritSectDelete(&pStreamCA->CritSect);
1838
1839 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_UNINIT);
1840 }
1841 else
1842 LogFunc(("Wrong stream init state for %p: %d - leaking it\n", pStream, enmInitState));
1843
1844 LogFunc(("returns (took %'RU64 ns)\n", RTTimeNanoTS() - nsStart));
1845 return VINF_SUCCESS;
1846}
1847
1848
1849#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1850/** @callback_method_impl{FNRTTIMERLR, For debugging things that takes too long.} */
1851static DECLCALLBACK(void) drvHstAudCaBreakpointTimer(RTTIMERLR hTimer, void *pvUser, uint64_t iTick)
1852{
1853 LogFlowFunc(("Queue-destruction timeout! iTick=%RU64\n", iTick));
1854 RT_NOREF(hTimer, pvUser, iTick);
1855 RTLogFlush(NULL);
1856 RT_BREAKPOINT();
1857}
1858#endif
1859
1860
1861/**
1862 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
1863 */
1864static DECLCALLBACK(int) drvHstAudCaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1865{
1866 RT_NOREF(pInterface);
1867 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1868 LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA)));
1869 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1870 RTCritSectEnter(&pStreamCA->CritSect);
1871
1872 Assert(!pStreamCA->fEnabled);
1873 Assert(!pStreamCA->fStarted);
1874
1875 /*
1876 * We always reset the buffer before enabling the stream (normally never necessary).
1877 */
1878 OSStatus orc = AudioQueueReset(pStreamCA->hAudioQueue);
1879 if (orc != noErr)
1880 LogRelMax(64, ("CoreAudio: Stream reset failed when enabling '%s': %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1881 Assert(orc == noErr);
1882 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1883 Assert(!drvHstAudCaIsBufferQueued(pStreamCA->paBuffers[iBuf].pBuf));
1884
1885 pStreamCA->offInternal = 0;
1886 pStreamCA->fDraining = false;
1887 pStreamCA->fEnabled = true;
1888 pStreamCA->fRestartOnResume = false;
1889 pStreamCA->idxBuffer = 0;
1890
1891 /*
1892 * Input streams will start capturing, while output streams will only start
1893 * playing once we get some audio data to play (see drvHstAudCaHA_StreamPlay).
1894 */
1895 int rc = VINF_SUCCESS;
1896 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
1897 {
1898 /* Zero (probably not needed) and submit all the buffers first. */
1899 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1900 {
1901 AudioQueueBufferRef pBuf = pStreamCA->paBuffers[iBuf].pBuf;
1902
1903 RT_BZERO(pBuf->mAudioData, pBuf->mAudioDataBytesCapacity);
1904 pBuf->mAudioDataByteSize = 0;
1905 drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/);
1906
1907 orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDescs*/, NULL /*inPacketDescs*/);
1908 AssertLogRelMsgBreakStmt(orc == noErr, ("CoreAudio: AudioQueueEnqueueBuffer(#%u) -> %#x (%d) - stream '%s'\n",
1909 iBuf, orc, orc, pStreamCA->Cfg.szName),
1910 drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/));
1911 }
1912
1913 /* Start the stream. */
1914 if (orc == noErr)
1915 {
1916 LogFlowFunc(("Start input stream '%s'...\n", pStreamCA->Cfg.szName));
1917 orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
1918 AssertLogRelMsgStmt(orc == noErr, ("CoreAudio: AudioQueueStart(%s) -> %#x (%d) \n", pStreamCA->Cfg.szName, orc, orc),
1919 rc = VERR_AUDIO_STREAM_NOT_READY);
1920 pStreamCA->fStarted = orc == noErr;
1921 }
1922 else
1923 rc = VERR_AUDIO_STREAM_NOT_READY;
1924 }
1925 else
1926 Assert(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
1927
1928 RTCritSectLeave(&pStreamCA->CritSect);
1929 LogFlowFunc(("returns %Rrc\n", rc));
1930 return rc;
1931}
1932
1933
1934/**
1935 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
1936 */
1937static DECLCALLBACK(int) drvHstAudCaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1938{
1939 RT_NOREF(pInterface);
1940 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1941 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1942 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
1943 pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
1944 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1945 RTCritSectEnter(&pStreamCA->CritSect);
1946
1947 /*
1948 * Always stop it (draining or no).
1949 */
1950 pStreamCA->fEnabled = false;
1951 pStreamCA->fRestartOnResume = false;
1952 Assert(!pStreamCA->fDraining || pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
1953
1954 int rc = VINF_SUCCESS;
1955 if (pStreamCA->fStarted)
1956 {
1957#if 0
1958 OSStatus orc2 = AudioQueueReset(pStreamCA->hAudioQueue);
1959 LogFlowFunc(("AudioQueueReset(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2);
1960 orc2 = AudioQueueFlush(pStreamCA->hAudioQueue);
1961 LogFlowFunc(("AudioQueueFlush(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2);
1962#endif
1963
1964 OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
1965 LogFlowFunc(("AudioQueueStop(%s,TRUE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1966 if (orc != noErr)
1967 {
1968 LogRelMax(64, ("CoreAudio: Stopping '%s' failed (disable): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1969 rc = VERR_GENERAL_FAILURE;
1970 }
1971 pStreamCA->fStarted = false;
1972 pStreamCA->fDraining = false;
1973 }
1974
1975 RTCritSectLeave(&pStreamCA->CritSect);
1976 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
1977 return rc;
1978}
1979
1980
1981/**
1982 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
1983 */
1984static DECLCALLBACK(int) drvHstAudCaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1985{
1986 RT_NOREF(pInterface);
1987 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1988 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1989 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
1990 pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
1991 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1992 RTCritSectEnter(&pStreamCA->CritSect);
1993
1994 /*
1995 * Unless we're draining the stream, pause it if it has started.
1996 */
1997 int rc = VINF_SUCCESS;
1998 if (pStreamCA->fStarted && !pStreamCA->fDraining)
1999 {
2000 pStreamCA->fRestartOnResume = true;
2001
2002 OSStatus orc = AudioQueuePause(pStreamCA->hAudioQueue);
2003 LogFlowFunc(("AudioQueuePause(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2004 if (orc != noErr)
2005 {
2006 LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2007 rc = VERR_GENERAL_FAILURE;
2008 }
2009 pStreamCA->fStarted = false;
2010 }
2011 else
2012 {
2013 pStreamCA->fRestartOnResume = false;
2014 if (pStreamCA->fDraining)
2015 {
2016 LogFunc(("Stream '%s' is draining\n", pStreamCA->Cfg.szName));
2017 Assert(pStreamCA->fStarted);
2018 }
2019 }
2020
2021 RTCritSectLeave(&pStreamCA->CritSect);
2022 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
2023 return rc;
2024}
2025
2026
2027/**
2028 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
2029 */
2030static DECLCALLBACK(int) drvHstAudCaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2031{
2032 RT_NOREF(pInterface);
2033 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2034 LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA)));
2035 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
2036 RTCritSectEnter(&pStreamCA->CritSect);
2037
2038 /*
2039 * Resume according to state saved by drvHstAudCaHA_StreamPause.
2040 */
2041 int rc = VINF_SUCCESS;
2042 if (pStreamCA->fRestartOnResume)
2043 {
2044 OSStatus orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
2045 LogFlowFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2046 if (orc != noErr)
2047 {
2048 LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2049 rc = VERR_AUDIO_STREAM_NOT_READY;
2050 }
2051 }
2052 pStreamCA->fRestartOnResume = false;
2053
2054 RTCritSectLeave(&pStreamCA->CritSect);
2055 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
2056 return rc;
2057}
2058
2059
2060/**
2061 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
2062 */
2063static DECLCALLBACK(int) drvHstAudCaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2064{
2065 RT_NOREF(pInterface);
2066 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2067 AssertReturn(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
2068 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2069 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
2070 pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
2071 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
2072 RTCritSectEnter(&pStreamCA->CritSect);
2073
2074 /*
2075 * The AudioQueueStop function has both an immediate and a drain mode,
2076 * so we'll obviously use the latter here. For checking draining progress,
2077 * we will just check if all buffers have been returned or not.
2078 */
2079 int rc = VINF_SUCCESS;
2080 if (pStreamCA->fStarted)
2081 {
2082 if (!pStreamCA->fDraining)
2083 {
2084 OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, FALSE /*inImmediate*/);
2085 LogFlowFunc(("AudioQueueStop(%s, FALSE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2086 if (orc == noErr)
2087 pStreamCA->fDraining = true;
2088 else
2089 {
2090 LogRelMax(64, ("CoreAudio: Stopping '%s' failed (drain): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2091 rc = VERR_GENERAL_FAILURE;
2092 }
2093 }
2094 else
2095 LogFlowFunc(("Already draining '%s' ...\n", pStreamCA->Cfg.szName));
2096 }
2097 else
2098 {
2099 LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamCA->Cfg.szName));
2100 AssertStmt(!pStreamCA->fDraining, pStreamCA->fDraining = false);
2101 }
2102
2103 RTCritSectLeave(&pStreamCA->CritSect);
2104 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
2105 return rc;
2106}
2107
2108
2109/**
2110 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
2111 */
2112static DECLCALLBACK(int) drvHstAudCaHA_StreamControl(PPDMIHOSTAUDIO pInterface,
2113 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2114{
2115 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
2116 * replacing it with individual StreamXxxx methods. That would save us
2117 * potentally huge switches and more easily see which drivers implement
2118 * which operations (grep for pfnStreamXxxx). */
2119 switch (enmStreamCmd)
2120 {
2121 case PDMAUDIOSTREAMCMD_ENABLE:
2122 return drvHstAudCaHA_StreamEnable(pInterface, pStream);
2123 case PDMAUDIOSTREAMCMD_DISABLE:
2124 return drvHstAudCaHA_StreamDisable(pInterface, pStream);
2125 case PDMAUDIOSTREAMCMD_PAUSE:
2126 return drvHstAudCaHA_StreamPause(pInterface, pStream);
2127 case PDMAUDIOSTREAMCMD_RESUME:
2128 return drvHstAudCaHA_StreamResume(pInterface, pStream);
2129 case PDMAUDIOSTREAMCMD_DRAIN:
2130 return drvHstAudCaHA_StreamDrain(pInterface, pStream);
2131
2132 case PDMAUDIOSTREAMCMD_END:
2133 case PDMAUDIOSTREAMCMD_32BIT_HACK:
2134 case PDMAUDIOSTREAMCMD_INVALID:
2135 /* no default*/
2136 break;
2137 }
2138 return VERR_NOT_SUPPORTED;
2139}
2140
2141
2142/**
2143 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2144 */
2145static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2146{
2147 RT_NOREF(pInterface);
2148 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2149 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2150 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
2151
2152 uint32_t cbReadable = 0;
2153 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
2154 {
2155 RTCritSectEnter(&pStreamCA->CritSect);
2156 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2157 uint32_t const cBuffers = pStreamCA->cBuffers;
2158 uint32_t const idxStart = pStreamCA->idxBuffer;
2159 uint32_t idxBuffer = idxStart;
2160 AudioQueueBufferRef pBuf;
2161
2162 if ( cBuffers > 0
2163 && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf))
2164 {
2165 do
2166 {
2167 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2168 uint32_t cbFill = pBuf->mAudioDataByteSize;
2169 AssertStmt(cbFill <= cbTotal, cbFill = cbTotal);
2170 uint32_t off = paBuffers[idxBuffer].offRead;
2171 AssertStmt(off < cbFill, off = cbFill);
2172
2173 cbReadable += cbFill - off;
2174
2175 /* Advance. */
2176 idxBuffer++;
2177 if (idxBuffer < cBuffers)
2178 { /* likely */ }
2179 else
2180 idxBuffer = 0;
2181 } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf));
2182 }
2183
2184 RTCritSectLeave(&pStreamCA->CritSect);
2185 }
2186 Log2Func(("returns %#x for '%s'\n", cbReadable, pStreamCA->Cfg.szName));
2187 return cbReadable;
2188}
2189
2190
2191/**
2192 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2193 */
2194static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2195{
2196 RT_NOREF(pInterface);
2197 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2198 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2199 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
2200
2201 uint32_t cbWritable = 0;
2202 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT)
2203 {
2204 RTCritSectEnter(&pStreamCA->CritSect);
2205 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2206 uint32_t const cBuffers = pStreamCA->cBuffers;
2207 uint32_t const idxStart = pStreamCA->idxBuffer;
2208 uint32_t idxBuffer = idxStart;
2209 AudioQueueBufferRef pBuf;
2210
2211 if ( cBuffers > 0
2212 && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf))
2213 {
2214 do
2215 {
2216 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2217 uint32_t cbUsed = pBuf->mAudioDataByteSize;
2218 AssertStmt(cbUsed <= cbTotal, paBuffers[idxBuffer].pBuf->mAudioDataByteSize = cbUsed = cbTotal);
2219
2220 cbWritable += cbTotal - cbUsed;
2221
2222 /* Advance. */
2223 idxBuffer++;
2224 if (idxBuffer < cBuffers)
2225 { /* likely */ }
2226 else
2227 idxBuffer = 0;
2228 } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf));
2229 }
2230
2231 RTCritSectLeave(&pStreamCA->CritSect);
2232 }
2233 Log2Func(("returns %#x for '%s'\n", cbWritable, pStreamCA->Cfg.szName));
2234 return cbWritable;
2235}
2236
2237
2238/**
2239 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
2240 */
2241static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudCaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
2242 PPDMAUDIOBACKENDSTREAM pStream)
2243{
2244 RT_NOREF(pInterface);
2245 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2246 AssertPtrReturn(pStreamCA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
2247
2248 if (ASMAtomicReadU32(&pStreamCA->enmInitState) == COREAUDIOINITSTATE_INIT)
2249 {
2250 if (!pStreamCA->fDraining)
2251 { /* likely */ }
2252 else
2253 {
2254 /*
2255 * If we're draining, we're done when we've got all the buffers back.
2256 */
2257 RTCritSectEnter(&pStreamCA->CritSect);
2258 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2259 uintptr_t idxBuffer = pStreamCA->cBuffers;
2260 while (idxBuffer-- > 0)
2261 if (!drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf))
2262 { /* likely */ }
2263 else
2264 {
2265#ifdef LOG_ENABLED
2266 uint32_t cQueued = 1;
2267 while (idxBuffer-- > 0)
2268 cQueued += drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf);
2269 LogFunc(("Still done draining '%s': %u queued buffers\n", pStreamCA->Cfg.szName, cQueued));
2270#endif
2271 RTCritSectLeave(&pStreamCA->CritSect);
2272 return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
2273 }
2274
2275 LogFunc(("Done draining '%s'\n", pStreamCA->Cfg.szName));
2276 pStreamCA->fDraining = false;
2277 pStreamCA->fEnabled = false;
2278 pStreamCA->fStarted = false;
2279 RTCritSectLeave(&pStreamCA->CritSect);
2280 }
2281
2282 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
2283 }
2284 return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; /** @todo ?? */
2285}
2286
2287/**
2288 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2289 */
2290static DECLCALLBACK(int) drvHstAudCaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2291 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2292{
2293 RT_NOREF(pInterface);
2294 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2295 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2296 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
2297 if (cbBuf)
2298 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2299 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
2300 AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbWritten = 0, VERR_AUDIO_STREAM_NOT_READY);
2301
2302 RTCritSectEnter(&pStreamCA->CritSect);
2303 if (pStreamCA->fEnabled)
2304 { /* likely */ }
2305 else
2306 {
2307 RTCritSectLeave(&pStreamCA->CritSect);
2308 *pcbWritten = 0;
2309 LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2310 return VINF_SUCCESS;
2311 }
2312 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
2313
2314 /*
2315 * Transfer loop.
2316 */
2317 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2318 uint32_t const cBuffers = pStreamCA->cBuffers;
2319 AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
2320 RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
2321
2322 uint32_t idxBuffer = pStreamCA->idxBuffer;
2323 AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
2324
2325 int rc = VINF_SUCCESS;
2326 uint32_t cbWritten = 0;
2327 while (cbBuf > 0)
2328 {
2329 AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
2330
2331 /*
2332 * Check out how much we can put into the current buffer.
2333 */
2334 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2335 if (!drvHstAudCaIsBufferQueued(pBuf))
2336 { /* likely */ }
2337 else
2338 {
2339 LogFunc(("@%#RX64: Warning! Out of buffer space! (%#x bytes unwritten)\n", pStreamCA->offInternal, cbBuf));
2340 /** @todo stats */
2341 break;
2342 }
2343
2344 AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
2345 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2346 uint32_t cbUsed = pBuf->mAudioDataByteSize;
2347 AssertStmt(cbUsed < cbTotal, cbUsed = cbTotal);
2348 uint32_t const cbAvail = cbTotal - cbUsed;
2349
2350 /*
2351 * Copy over the data.
2352 */
2353 if (cbBuf < cbAvail)
2354 {
2355 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x only - leaving unqueued {%s}\n",
2356 pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2357 memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbBuf);
2358 pBuf->mAudioDataByteSize = cbUsed + cbBuf;
2359 cbWritten += cbBuf;
2360 pStreamCA->offInternal += cbBuf;
2361 /** @todo Maybe queue it anyway if it's almost full or we haven't got a lot of
2362 * buffers queued. */
2363 break;
2364 }
2365
2366 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x - will queue {%s}\n",
2367 pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2368 memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbAvail);
2369 pBuf->mAudioDataByteSize = cbTotal;
2370 cbWritten += cbAvail;
2371 pStreamCA->offInternal += cbAvail;
2372 drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/);
2373
2374 OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
2375 if (orc == noErr)
2376 { /* likely */ }
2377 else
2378 {
2379 LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
2380 pStreamCA->Cfg.szName, idxBuffer, orc, orc));
2381 drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/);
2382 pBuf->mAudioDataByteSize -= PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, 1); /* avoid assertions above */
2383 rc = VERR_AUDIO_STREAM_NOT_READY;
2384 break;
2385 }
2386
2387 /*
2388 * Advance.
2389 */
2390 idxBuffer += 1;
2391 if (idxBuffer < cBuffers)
2392 { /* likely */ }
2393 else
2394 idxBuffer = 0;
2395 pStreamCA->idxBuffer = idxBuffer;
2396
2397 pvBuf = (const uint8_t *)pvBuf + cbAvail;
2398 cbBuf -= cbAvail;
2399 }
2400
2401 /*
2402 * Start the stream if we haven't do so yet.
2403 */
2404 if ( pStreamCA->fStarted
2405 || cbWritten == 0
2406 || RT_FAILURE_NP(rc))
2407 { /* likely */ }
2408 else
2409 {
2410 UInt32 cFramesPrepared = 0;
2411#if 0 /* taking too long? */
2412 OSStatus orc = AudioQueuePrime(pStreamCA->hAudioQueue, 0 /*inNumberOfFramesToPrepare*/, &cFramesPrepared);
2413 LogFlowFunc(("AudioQueuePrime(%s, 0,) returns %#x (%d) and cFramesPrepared=%u (offInternal=%#RX64)\n",
2414 pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
2415 AssertMsg(orc == noErr, ("%#x (%d)\n", orc, orc));
2416#else
2417 OSStatus orc;
2418#endif
2419 orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
2420 LogFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2421 if (orc == noErr)
2422 pStreamCA->fStarted = true;
2423 else
2424 {
2425 LogRelMax(128, ("CoreAudio: Starting '%s' failed: %#x (%d) - %u frames primed, %#x bytes queued\n",
2426 pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
2427 rc = VERR_AUDIO_STREAM_NOT_READY;
2428 }
2429 }
2430
2431 /*
2432 * Done.
2433 */
2434#ifdef LOG_ENABLED
2435 uint64_t const msPrev = pStreamCA->msLastTransfer;
2436#endif
2437 uint64_t const msNow = RTTimeMilliTS();
2438 if (cbWritten)
2439 pStreamCA->msLastTransfer = msNow;
2440
2441 RTCritSectLeave(&pStreamCA->CritSect);
2442
2443 *pcbWritten = cbWritten;
2444 if (RT_SUCCESS(rc) || !cbWritten)
2445 { }
2446 else
2447 {
2448 LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten));
2449 rc = VINF_SUCCESS;
2450 }
2451 LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbWritten,
2452 msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) ));
2453 return rc;
2454}
2455
2456
2457/**
2458 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2459 */
2460static DECLCALLBACK(int) drvHstAudCaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2461 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2462{
2463 RT_NOREF(pInterface);
2464 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2465 AssertPtrReturn(pStreamCA, 0);
2466 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2467 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2468 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2469 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
2470 AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbRead = 0, VERR_AUDIO_STREAM_NOT_READY);
2471
2472 RTCritSectEnter(&pStreamCA->CritSect);
2473 if (pStreamCA->fEnabled)
2474 { /* likely */ }
2475 else
2476 {
2477 RTCritSectLeave(&pStreamCA->CritSect);
2478 *pcbRead = 0;
2479 LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA)));
2480 return VINF_SUCCESS;
2481 }
2482 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
2483
2484
2485 /*
2486 * Transfer loop.
2487 */
2488 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamCA->Cfg.Props);
2489 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2490 uint32_t const cBuffers = pStreamCA->cBuffers;
2491 AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
2492 RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
2493
2494 uint32_t idxBuffer = pStreamCA->idxBuffer;
2495 AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
2496
2497 int rc = VINF_SUCCESS;
2498 uint32_t cbRead = 0;
2499 while (cbBuf > cbFrame)
2500 {
2501 AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
2502
2503 /*
2504 * Check out how much we can read from the current buffer (if anything at all).
2505 */
2506 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2507 if (!drvHstAudCaIsBufferQueued(pBuf))
2508 { /* likely */ }
2509 else
2510 {
2511 LogFunc(("@%#RX64: Warning! Underrun! (%#x bytes unread)\n", pStreamCA->offInternal, cbBuf));
2512 /** @todo stats */
2513 break;
2514 }
2515
2516 AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
2517 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2518 uint32_t cbValid = pBuf->mAudioDataByteSize;
2519 AssertStmt(cbValid < cbTotal, cbValid = cbTotal);
2520 uint32_t offRead = paBuffers[idxBuffer].offRead;
2521 uint32_t const cbLeft = cbValid - offRead;
2522
2523 /*
2524 * Copy over the data.
2525 */
2526 if (cbBuf < cbLeft)
2527 {
2528 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want %#x - leaving unqueued {%s}\n",
2529 pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2530 memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbBuf);
2531 paBuffers[idxBuffer].offRead = offRead + cbBuf;
2532 cbRead += cbBuf;
2533 pStreamCA->offInternal += cbBuf;
2534 break;
2535 }
2536
2537 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want all (%#x) - will queue {%s}\n",
2538 pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2539 memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbLeft);
2540 cbRead += cbLeft;
2541 pStreamCA->offInternal += cbLeft;
2542
2543 RT_BZERO(pBuf->mAudioData, cbTotal); /* paranoia */
2544 paBuffers[idxBuffer].offRead = 0;
2545 pBuf->mAudioDataByteSize = 0;
2546 drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/);
2547
2548 OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
2549 if (orc == noErr)
2550 { /* likely */ }
2551 else
2552 {
2553 LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
2554 pStreamCA->Cfg.szName, idxBuffer, orc, orc));
2555 drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/);
2556 rc = VERR_AUDIO_STREAM_NOT_READY;
2557 break;
2558 }
2559
2560 /*
2561 * Advance.
2562 */
2563 idxBuffer += 1;
2564 if (idxBuffer < cBuffers)
2565 { /* likely */ }
2566 else
2567 idxBuffer = 0;
2568 pStreamCA->idxBuffer = idxBuffer;
2569
2570 pvBuf = (uint8_t *)pvBuf + cbLeft;
2571 cbBuf -= cbLeft;
2572 }
2573
2574 /*
2575 * Done.
2576 */
2577#ifdef LOG_ENABLED
2578 uint64_t const msPrev = pStreamCA->msLastTransfer;
2579#endif
2580 uint64_t const msNow = RTTimeMilliTS();
2581 if (cbRead)
2582 pStreamCA->msLastTransfer = msNow;
2583
2584 RTCritSectLeave(&pStreamCA->CritSect);
2585
2586 *pcbRead = cbRead;
2587 if (RT_SUCCESS(rc) || !cbRead)
2588 { }
2589 else
2590 {
2591 LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead));
2592 rc = VINF_SUCCESS;
2593 }
2594 LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbRead,
2595 msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) ));
2596 return rc;
2597}
2598
2599
2600/*********************************************************************************************************************************
2601* PDMIBASE *
2602*********************************************************************************************************************************/
2603
2604/**
2605 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2606 */
2607static DECLCALLBACK(void *) drvHstAudCaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2608{
2609 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2610 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2611
2612 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2613 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2614
2615 return NULL;
2616}
2617
2618
2619/*********************************************************************************************************************************
2620* PDMDRVREG *
2621*********************************************************************************************************************************/
2622
2623/**
2624 * Worker for the power off and destructor callbacks.
2625 */
2626static void drvHstAudCaRemoveDefaultDeviceListners(PDRVHOSTCOREAUDIO pThis)
2627{
2628 /*
2629 * Unregister system callbacks.
2630 */
2631 AudioObjectPropertyAddress PropAddr =
2632 {
2633 kAudioHardwarePropertyDefaultInputDevice,
2634 kAudioObjectPropertyScopeGlobal,
2635 kAudioObjectPropertyElementMaster
2636 };
2637
2638 OSStatus orc;
2639 if (pThis->fRegisteredDefaultInputListener)
2640 {
2641 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr,
2642 drvHstAudCaDefaultDeviceChangedCallback, pThis);
2643 if ( orc != noErr
2644 && orc != kAudioHardwareBadObjectError)
2645 LogRel(("CoreAudio: Failed to remove the default input device changed listener: %d (%#x))\n", orc, orc));
2646 pThis->fRegisteredDefaultInputListener = false;
2647 }
2648
2649 if (pThis->fRegisteredDefaultOutputListener)
2650 {
2651 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2652 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr,
2653 drvHstAudCaDefaultDeviceChangedCallback, pThis);
2654 if ( orc != noErr
2655 && orc != kAudioHardwareBadObjectError)
2656 LogRel(("CoreAudio: Failed to remove the default output device changed listener: %d (%#x))\n", orc, orc));
2657 pThis->fRegisteredDefaultOutputListener = false;
2658 }
2659
2660 /*
2661 * Unregister device callbacks.
2662 */
2663 RTCritSectEnter(&pThis->CritSect);
2664
2665 drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->InputDevice.idDevice);
2666 pThis->InputDevice.idDevice = kAudioDeviceUnknown;
2667
2668 drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->OutputDevice.idDevice);
2669 pThis->OutputDevice.idDevice = kAudioDeviceUnknown;
2670
2671 RTCritSectLeave(&pThis->CritSect);
2672
2673 LogFlowFuncEnter();
2674}
2675
2676
2677/**
2678 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
2679 */
2680static DECLCALLBACK(void) drvHstAudCaPowerOff(PPDMDRVINS pDrvIns)
2681{
2682 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2683 drvHstAudCaRemoveDefaultDeviceListners(pThis);
2684}
2685
2686
2687/**
2688 * @callback_method_impl{FNPDMDRVDESTRUCT}
2689 */
2690static DECLCALLBACK(void) drvHstAudCaDestruct(PPDMDRVINS pDrvIns)
2691{
2692 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2693 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2694
2695 if (RTCritSectIsInitialized(&pThis->CritSect))
2696 drvHstAudCaRemoveDefaultDeviceListners(pThis);
2697
2698#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2699 if (pThis->hThread != NIL_RTTHREAD)
2700 {
2701 for (unsigned iLoop = 0; iLoop < 60; iLoop++)
2702 {
2703 if (pThis->hThreadRunLoop)
2704 CFRunLoopStop(pThis->hThreadRunLoop);
2705 if (iLoop > 10)
2706 RTThreadPoke(pThis->hThread);
2707 int rc = RTThreadWait(pThis->hThread, 500 /*ms*/, NULL /*prcThread*/);
2708 if (RT_SUCCESS(rc))
2709 break;
2710 AssertMsgBreak(rc == VERR_TIMEOUT, ("RTThreadWait -> %Rrc\n",rc));
2711 }
2712 pThis->hThread = NIL_RTTHREAD;
2713 }
2714 if (pThis->hThreadPortSrc)
2715 {
2716 CFRelease(pThis->hThreadPortSrc);
2717 pThis->hThreadPortSrc = NULL;
2718 }
2719 if (pThis->hThreadPort)
2720 {
2721 CFMachPortInvalidate(pThis->hThreadPort);
2722 CFRelease(pThis->hThreadPort);
2723 pThis->hThreadPort = NULL;
2724 }
2725 if (pThis->hThreadRunLoop)
2726 {
2727 CFRelease(pThis->hThreadRunLoop);
2728 pThis->hThreadRunLoop = NULL;
2729 }
2730#endif
2731
2732#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2733 if (pThis->hBreakpointTimer != NIL_RTTIMERLR)
2734 {
2735 RTTimerLRDestroy(pThis->hBreakpointTimer);
2736 pThis->hBreakpointTimer = NIL_RTTIMERLR;
2737 }
2738#endif
2739
2740 if (RTCritSectIsInitialized(&pThis->CritSect))
2741 {
2742 int rc2 = RTCritSectDelete(&pThis->CritSect);
2743 AssertRC(rc2);
2744 }
2745
2746 LogFlowFuncLeave();
2747}
2748
2749
2750/**
2751 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2752 * Construct a Core Audio driver instance.}
2753 */
2754static DECLCALLBACK(int) drvHstAudCaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2755{
2756 RT_NOREF(pCfg, fFlags);
2757 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2758 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2759 LogRel(("Audio: Initializing Core Audio driver\n"));
2760
2761 /*
2762 * Init the static parts.
2763 */
2764 pThis->pDrvIns = pDrvIns;
2765#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2766 pThis->hThread = NIL_RTTHREAD;
2767#endif
2768#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2769 pThis->hBreakpointTimer = NIL_RTTIMERLR;
2770#endif
2771 /* IBase */
2772 pDrvIns->IBase.pfnQueryInterface = drvHstAudCaQueryInterface;
2773 /* IHostAudio */
2774 pThis->IHostAudio.pfnGetConfig = drvHstAudCaHA_GetConfig;
2775 pThis->IHostAudio.pfnGetDevices = drvHstAudCaHA_GetDevices;
2776 pThis->IHostAudio.pfnSetDevice = drvHstAudCaHA_SetDevice;
2777 pThis->IHostAudio.pfnGetStatus = drvHstAudCaHA_GetStatus;
2778 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
2779 pThis->IHostAudio.pfnStreamConfigHint = NULL;
2780 pThis->IHostAudio.pfnStreamCreate = drvHstAudCaHA_StreamCreate;
2781 pThis->IHostAudio.pfnStreamInitAsync = NULL;
2782 pThis->IHostAudio.pfnStreamDestroy = drvHstAudCaHA_StreamDestroy;
2783 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
2784 pThis->IHostAudio.pfnStreamControl = drvHstAudCaHA_StreamControl;
2785 pThis->IHostAudio.pfnStreamGetReadable = drvHstAudCaHA_StreamGetReadable;
2786 pThis->IHostAudio.pfnStreamGetWritable = drvHstAudCaHA_StreamGetWritable;
2787 pThis->IHostAudio.pfnStreamGetPending = NULL;
2788 pThis->IHostAudio.pfnStreamGetState = drvHstAudCaHA_StreamGetState;
2789 pThis->IHostAudio.pfnStreamPlay = drvHstAudCaHA_StreamPlay;
2790 pThis->IHostAudio.pfnStreamCapture = drvHstAudCaHA_StreamCapture;
2791
2792 int rc = RTCritSectInit(&pThis->CritSect);
2793 AssertRCReturn(rc, rc);
2794
2795 /*
2796 * Validate and read configuration.
2797 */
2798 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "InputDeviceID|OutputDeviceID", "");
2799
2800 char *pszTmp = NULL;
2801 rc = CFGMR3QueryStringAlloc(pCfg, "InputDeviceID", &pszTmp);
2802 if (RT_SUCCESS(rc))
2803 {
2804 rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotify*/, pszTmp);
2805 MMR3HeapFree(pszTmp);
2806 }
2807 else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
2808 return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'InputDeviceID'");
2809
2810 rc = CFGMR3QueryStringAlloc(pCfg, "OutputDeviceID", &pszTmp);
2811 if (RT_SUCCESS(rc))
2812 {
2813 rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotify*/, pszTmp);
2814 MMR3HeapFree(pszTmp);
2815 }
2816 else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
2817 return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'OutputDeviceID'");
2818
2819 /*
2820 * Query the notification interface from the driver/device above us.
2821 */
2822 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
2823 AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
2824
2825#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2826 /*
2827 * Create worker thread for running callbacks on.
2828 */
2829 CFMachPortContext PortCtx;
2830 PortCtx.version = 0;
2831 PortCtx.info = pThis;
2832 PortCtx.retain = NULL;
2833 PortCtx.release = NULL;
2834 PortCtx.copyDescription = NULL;
2835 pThis->hThreadPort = CFMachPortCreate(NULL /*allocator*/, drvHstAudCaThreadPortCallback, &PortCtx, NULL);
2836 AssertLogRelReturn(pThis->hThreadPort != NULL, VERR_NO_MEMORY);
2837
2838 pThis->hThreadPortSrc = CFMachPortCreateRunLoopSource(NULL, pThis->hThreadPort, 0 /*order*/);
2839 AssertLogRelReturn(pThis->hThreadPortSrc != NULL, VERR_NO_MEMORY);
2840
2841 rc = RTThreadCreateF(&pThis->hThread, drvHstAudCaThread, pThis, 0, RTTHREADTYPE_IO,
2842 RTTHREADFLAGS_WAITABLE, "CaAud-%u", pDrvIns->iInstance);
2843 AssertLogRelMsgReturn(RT_SUCCESS(rc), ("RTThreadCreateF failed: %Rrc\n", rc), rc);
2844
2845 RTThreadUserWait(pThis->hThread, RT_MS_10SEC);
2846 AssertLogRel(pThis->hThreadRunLoop);
2847#endif
2848
2849#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2850 /*
2851 * Create a IPRT timer. The TM timers won't necessarily work as EMT is probably busy.
2852 */
2853 rc = RTTimerLRCreateEx(&pThis->hBreakpointTimer, 0 /*no interval*/, 0, drvHstAudCaBreakpointTimer, pThis);
2854 AssertRCReturn(rc, rc);
2855#endif
2856
2857 /*
2858 * Determin the default devices.
2859 */
2860 drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotifty*/);
2861 drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotifty*/);
2862
2863 /*
2864 * Register callbacks for default device input and output changes.
2865 * (We just ignore failures here as there isn't much we can do about it,
2866 * and it isn't 100% critical.)
2867 */
2868 AudioObjectPropertyAddress PropAddr =
2869 {
2870 /* .mSelector = */ kAudioHardwarePropertyDefaultInputDevice,
2871 /* .mScope = */ kAudioObjectPropertyScopeGlobal,
2872 /* .mElement = */ kAudioObjectPropertyElementMaster
2873 };
2874
2875 OSStatus orc;
2876 orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis);
2877 pThis->fRegisteredDefaultInputListener = orc == noErr;
2878 if ( orc != noErr
2879 && orc != kAudioHardwareIllegalOperationError)
2880 LogRel(("CoreAudio: Failed to add the input default device changed listener: %d (%#x)\n", orc, orc));
2881
2882 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2883 orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis);
2884 pThis->fRegisteredDefaultOutputListener = orc == noErr;
2885 if ( orc != noErr
2886 && orc != kAudioHardwareIllegalOperationError)
2887 LogRel(("CoreAudio: Failed to add the output default device changed listener: %d (%#x)\n", orc, orc));
2888
2889 /*
2890 * Cleanup debug dumps from previous run.
2891 */
2892#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH
2893 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm");
2894 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm");
2895#endif
2896
2897 LogFlowFuncLeaveRC(rc);
2898 return rc;
2899}
2900
2901
2902/**
2903 * Char driver registration record.
2904 */
2905const PDMDRVREG g_DrvHostCoreAudio =
2906{
2907 /* u32Version */
2908 PDM_DRVREG_VERSION,
2909 /* szName */
2910 "CoreAudio",
2911 /* szRCMod */
2912 "",
2913 /* szR0Mod */
2914 "",
2915 /* pszDescription */
2916 "Core Audio host driver",
2917 /* fFlags */
2918 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2919 /* fClass. */
2920 PDM_DRVREG_CLASS_AUDIO,
2921 /* cMaxInstances */
2922 ~0U,
2923 /* cbInstance */
2924 sizeof(DRVHOSTCOREAUDIO),
2925 /* pfnConstruct */
2926 drvHstAudCaConstruct,
2927 /* pfnDestruct */
2928 drvHstAudCaDestruct,
2929 /* pfnRelocate */
2930 NULL,
2931 /* pfnIOCtl */
2932 NULL,
2933 /* pfnPowerOn */
2934 NULL,
2935 /* pfnReset */
2936 NULL,
2937 /* pfnSuspend */
2938 NULL,
2939 /* pfnResume */
2940 NULL,
2941 /* pfnAttach */
2942 NULL,
2943 /* pfnDetach */
2944 NULL,
2945 /* pfnPowerOff */
2946 drvHstAudCaPowerOff,
2947 /* pfnSoftReset */
2948 NULL,
2949 /* u32EndVersion */
2950 PDM_DRVREG_VERSION
2951};
2952
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use