VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp@ 88031

Last change on this file since 88031 was 88031, checked in by vboxsync, 4 years ago

Audio: Preparing for moving the enumeration helpers to PDM. Simplify allocation. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 93.4 KB
Line 
1/* $Id: DrvHostCoreAudio.cpp 88031 2021-03-09 00:13:46Z vboxsync $ */
2/** @file
3 * VBox audio devices - Mac OS X CoreAudio audio driver.
4 */
5
6/*
7 * Copyright (C) 2010-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
23#include <VBox/log.h>
24#include <VBox/vmm/pdmaudioinline.h>
25
26#include "DrvAudio.h"
27#include "VBoxDD.h"
28
29#include <iprt/asm.h>
30#include <iprt/cdefs.h>
31#include <iprt/circbuf.h>
32#include <iprt/mem.h>
33
34#include <iprt/uuid.h>
35
36#include <CoreAudio/CoreAudio.h>
37#include <CoreServices/CoreServices.h>
38#include <AudioUnit/AudioUnit.h>
39#include <AudioToolbox/AudioConverter.h>
40#include <AudioToolbox/AudioToolbox.h>
41
42
43
44/* Enables utilizing the Core Audio converter unit for converting
45 * input / output from/to our requested formats. That might be more
46 * performant than using our own routines later down the road. */
47/** @todo Needs more investigation and testing first before enabling. */
48//# define VBOX_WITH_AUDIO_CA_CONVERTER
49
50/** @todo
51 * - Maybe make sure the threads are immediately stopped if playing/recording stops.
52 */
53
54/*
55 * Most of this is based on:
56 * http://developer.apple.com/mac/library/technotes/tn2004/tn2097.html
57 * http://developer.apple.com/mac/library/technotes/tn2002/tn2091.html
58 * http://developer.apple.com/mac/library/qa/qa2007/qa1533.html
59 * http://developer.apple.com/mac/library/qa/qa2001/qa1317.html
60 * http://developer.apple.com/mac/library/documentation/AudioUnit/Reference/AUComponentServicesReference/Reference/reference.html
61 */
62
63/* Prototypes needed for COREAUDIODEVICE. */
64struct DRVHOSTCOREAUDIO;
65typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO;
66
67/**
68 * Structure for holding Core Audio-specific device data.
69 * This data then lives in the pvData part of the PDMAUDIODEVICE struct.
70 */
71typedef struct COREAUDIODEVICEDATA
72{
73 /** Pointer to driver instance this device is bound to. */
74 PDRVHOSTCOREAUDIO pDrv;
75 /** The audio device ID of the currently used device (UInt32 typedef). */
76 AudioDeviceID deviceID;
77 /** The device' UUID. */
78 CFStringRef UUID;
79 /** List of attached (native) Core Audio streams attached to this device. */
80 RTLISTANCHOR lstStreams;
81} COREAUDIODEVICEDATA, *PCOREAUDIODEVICEDATA;
82
83/**
84 * Host Coreaudio driver instance data.
85 * @implements PDMIAUDIOCONNECTOR
86 */
87typedef struct DRVHOSTCOREAUDIO
88{
89 /** Pointer to the driver instance structure. */
90 PPDMDRVINS pDrvIns;
91 /** Pointer to host audio interface. */
92 PDMIHOSTAUDIO IHostAudio;
93 /** Critical section to serialize access. */
94 RTCRITSECT CritSect;
95 /** Current (last reported) device enumeration. */
96 PDMAUDIODEVICEENUM Devices;
97 /** Pointer to the currently used input device in the device enumeration.
98 * Can be NULL if none assigned. */
99 PPDMAUDIODEVICE pDefaultDevIn;
100 /** Pointer to the currently used output device in the device enumeration.
101 * Can be NULL if none assigned. */
102 PPDMAUDIODEVICE pDefaultDevOut;
103#ifdef VBOX_WITH_AUDIO_CALLBACKS
104 /** Callback function to the upper driver.
105 * Can be NULL if not being used / registered. */
106 PFNPDMHOSTAUDIOCALLBACK pfnCallback;
107#endif
108} DRVHOSTCOREAUDIO, *PDRVHOSTCOREAUDIO;
109
110/** Converts a pointer to DRVHOSTCOREAUDIO::IHostAudio to a PDRVHOSTCOREAUDIO. */
111#define PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface) RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio)
112
113/**
114 * Structure for holding a Core Audio unit
115 * and its data.
116 */
117typedef struct COREAUDIOUNIT
118{
119 /** Pointer to the device this audio unit is bound to.
120 * Can be NULL if not bound to a device (anymore). */
121 PPDMAUDIODEVICE pDevice;
122 /** The actual audio unit object. */
123 AudioUnit audioUnit;
124 /** Stream description for using with VBox:
125 * - When using this audio unit for input (capturing), this format states
126 * the unit's output format.
127 * - When using this audio unit for output (playback), this format states
128 * the unit's input format. */
129 AudioStreamBasicDescription streamFmt;
130} COREAUDIOUNIT, *PCOREAUDIOUNIT;
131
132
133DECLHIDDEN(int) coreAudioInputPermissionCheck(void);
134
135/*******************************************************************************
136 *
137 * Helper function section
138 *
139 ******************************************************************************/
140
141/* Move these down below the internal function prototypes... */
142
143static void coreAudioPrintASBD(const char *pszDesc, const AudioStreamBasicDescription *pASBD)
144{
145 char pszSampleRate[32];
146 LogRel2(("CoreAudio: %s description:\n", pszDesc));
147 LogRel2(("CoreAudio:\tFormat ID: %RU32 (%c%c%c%c)\n", pASBD->mFormatID,
148 RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID),
149 RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID)));
150 LogRel2(("CoreAudio:\tFlags: %RU32", pASBD->mFormatFlags));
151 if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat)
152 LogRel2((" Float"));
153 if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian)
154 LogRel2((" BigEndian"));
155 if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger)
156 LogRel2((" SignedInteger"));
157 if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked)
158 LogRel2((" Packed"));
159 if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh)
160 LogRel2((" AlignedHigh"));
161 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved)
162 LogRel2((" NonInterleaved"));
163 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable)
164 LogRel2((" NonMixable"));
165 if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear)
166 LogRel2((" AllClear"));
167 LogRel2(("\n"));
168 snprintf(pszSampleRate, 32, "%.2f", (float)pASBD->mSampleRate); /** @todo r=andy Use RTStrPrint*. */
169 LogRel2(("CoreAudio:\tSampleRate : %s\n", pszSampleRate));
170 LogRel2(("CoreAudio:\tChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame));
171 LogRel2(("CoreAudio:\tFramesPerPacket : %RU32\n", pASBD->mFramesPerPacket));
172 LogRel2(("CoreAudio:\tBitsPerChannel : %RU32\n", pASBD->mBitsPerChannel));
173 LogRel2(("CoreAudio:\tBytesPerFrame : %RU32\n", pASBD->mBytesPerFrame));
174 LogRel2(("CoreAudio:\tBytesPerPacket : %RU32\n", pASBD->mBytesPerPacket));
175}
176
177static void coreAudioPCMPropsToASBD(PDMAUDIOPCMPROPS *pPCMProps, AudioStreamBasicDescription *pASBD)
178{
179 AssertPtrReturnVoid(pPCMProps);
180 AssertPtrReturnVoid(pASBD);
181
182 RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription));
183
184 pASBD->mFormatID = kAudioFormatLinearPCM;
185 pASBD->mFormatFlags = kAudioFormatFlagIsPacked;
186 pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */
187 pASBD->mSampleRate = (Float64)pPCMProps->uHz;
188 pASBD->mChannelsPerFrame = pPCMProps->cChannels;
189 pASBD->mBitsPerChannel = pPCMProps->cbSample * 8;
190 if (pPCMProps->fSigned)
191 pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
192 pASBD->mBytesPerFrame = pASBD->mChannelsPerFrame * (pASBD->mBitsPerChannel / 8);
193 pASBD->mBytesPerPacket = pASBD->mFramesPerPacket * pASBD->mBytesPerFrame;
194}
195
196#ifndef VBOX_WITH_AUDIO_CALLBACKS
197static int coreAudioASBDToStreamCfg(AudioStreamBasicDescription *pASBD, PPDMAUDIOSTREAMCFG pCfg)
198{
199 AssertPtrReturn(pASBD, VERR_INVALID_PARAMETER);
200 AssertPtrReturn(pCfg, VERR_INVALID_PARAMETER);
201
202 pCfg->Props.cChannels = pASBD->mChannelsPerFrame;
203 pCfg->Props.uHz = (uint32_t)pASBD->mSampleRate;
204 AssertMsg(!(pASBD->mBitsPerChannel & 7), ("%u\n", pASBD->mBitsPerChannel));
205 pCfg->Props.cbSample = pASBD->mBitsPerChannel / 8;
206 pCfg->Props.fSigned = RT_BOOL(pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger);
207 pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cbSample, pCfg->Props.cChannels);
208 /** @todo r=bird: pCfg->Props.fSwapEndian is not initialized here! */
209
210 return VINF_SUCCESS;
211}
212#endif /* !VBOX_WITH_AUDIO_CALLBACKS */
213
214#if 0 /* unused */
215static int coreAudioCFStringToCString(const CFStringRef pCFString, char **ppszString)
216{
217 CFIndex cLen = CFStringGetLength(pCFString) + 1;
218 char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char));
219 if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8))
220 {
221 RTMemFree(pszResult);
222 return VERR_NOT_FOUND;
223 }
224
225 *ppszString = pszResult;
226 return VINF_SUCCESS;
227}
228
229static AudioDeviceID coreAudioDeviceUIDtoID(const char* pszUID)
230{
231 /* Create a CFString out of our CString. */
232 CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman);
233
234 /* Fill the translation structure. */
235 AudioDeviceID deviceID;
236
237 AudioValueTranslation translation;
238 translation.mInputData = &strUID;
239 translation.mInputDataSize = sizeof(CFStringRef);
240 translation.mOutputData = &deviceID;
241 translation.mOutputDataSize = sizeof(AudioDeviceID);
242
243 /* Fetch the translation from the UID to the device ID. */
244 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDeviceForUID, kAudioObjectPropertyScopeGlobal,
245 kAudioObjectPropertyElementMaster };
246
247 UInt32 uSize = sizeof(AudioValueTranslation);
248 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdr, 0, NULL, &uSize, &translation);
249
250 /* Release the temporary CFString */
251 CFRelease(strUID);
252
253 if (RT_LIKELY(err == noErr))
254 return deviceID;
255
256 /* Return the unknown device on error. */
257 return kAudioDeviceUnknown;
258}
259#endif /* unused */
260
261
262/*********************************************************************************************************************************
263* Defined Constants And Macros *
264*********************************************************************************************************************************/
265
266/** @name Initialization status indicator used for the recreation of the AudioUnits.
267 *
268 * Global structures section
269 *
270 ******************************************************************************/
271
272/**
273 * Enumeration for a Core Audio stream status.
274 */
275typedef enum COREAUDIOSTATUS
276{
277 /** The device is uninitialized. */
278 COREAUDIOSTATUS_UNINIT = 0,
279 /** The device is currently initializing. */
280 COREAUDIOSTATUS_IN_INIT,
281 /** The device is initialized. */
282 COREAUDIOSTATUS_INIT,
283 /** The device is currently uninitializing. */
284 COREAUDIOSTATUS_IN_UNINIT,
285#ifndef VBOX_WITH_AUDIO_CALLBACKS
286 /** The device has to be reinitialized.
287 * Note: Only needed if VBOX_WITH_AUDIO_CALLBACKS is not defined, as otherwise
288 * the Audio Connector will take care of this as soon as this backend
289 * tells it to do so via the provided audio callback. */
290 COREAUDIOSTATUS_REINIT,
291#endif
292 /** The usual 32-bit hack. */
293 COREAUDIOSTATUS_32BIT_HACK = 0x7fffffff
294} COREAUDIOSTATUS, *PCOREAUDIOSTATUS;
295
296#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
297 /* Error code which indicates "End of data" */
298 static const OSStatus caConverterEOFDErr = 0x656F6664; /* 'eofd' */
299#endif
300
301/* Prototypes needed for COREAUDIOSTREAMCBCTX. */
302struct COREAUDIOSTREAM;
303typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM;
304
305/**
306 * Structure for keeping a conversion callback context.
307 * This is needed when using an audio converter during input/output processing.
308 */
309typedef struct COREAUDIOCONVCBCTX
310{
311 /** Pointer to the stream this context is bound to. */
312 PCOREAUDIOSTREAM pStream;
313 /** Source stream description. */
314 AudioStreamBasicDescription asbdSrc;
315 /** Destination stream description. */
316 AudioStreamBasicDescription asbdDst;
317 /** Pointer to native buffer list used for rendering the source audio data into. */
318 AudioBufferList *pBufLstSrc;
319 /** Total packet conversion count. */
320 UInt32 uPacketCnt;
321 /** Current packet conversion index. */
322 UInt32 uPacketIdx;
323 /** Error count, for limiting the logging. */
324 UInt32 cErrors;
325} COREAUDIOCONVCBCTX, *PCOREAUDIOCONVCBCTX;
326
327/**
328 * Structure for keeping the input stream specifics.
329 */
330typedef struct COREAUDIOSTREAMIN
331{
332#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
333 /** The audio converter if necessary. NULL if no converter is being used. */
334 AudioConverterRef ConverterRef;
335 /** Callback context for the audio converter. */
336 COREAUDIOCONVCBCTX convCbCtx;
337#endif
338 /** The ratio between the device & the stream sample rate. */
339 Float64 sampleRatio;
340} COREAUDIOSTREAMIN, *PCOREAUDIOSTREAMIN;
341
342/**
343 * Structure for keeping the output stream specifics.
344 */
345typedef struct COREAUDIOSTREAMOUT
346{
347 /** Nothing here yet. */
348} COREAUDIOSTREAMOUT, *PCOREAUDIOSTREAMOUT;
349
350/**
351 * Structure for maintaining a Core Audio stream.
352 */
353typedef struct COREAUDIOSTREAM
354{
355 /** The stream's acquired configuration. */
356 PPDMAUDIOSTREAMCFG pCfg;
357 /** Stream-specific data, depending on the stream type. */
358 union
359 {
360 COREAUDIOSTREAMIN In;
361 COREAUDIOSTREAMOUT Out;
362 };
363 /** List node for the device's stream list. */
364 RTLISTNODE Node;
365 /** Pointer to driver instance this stream is bound to. */
366 PDRVHOSTCOREAUDIO pDrv;
367 /** The stream's thread handle for maintaining the audio queue. */
368 RTTHREAD hThread;
369 /** Flag indicating to start a stream's data processing. */
370 bool fRun;
371 /** Whether the stream is in a running (active) state or not.
372 * For playback streams this means that audio data can be (or is being) played,
373 * for capturing streams this means that audio data is being captured (if available). */
374 bool fIsRunning;
375 /** Thread shutdown indicator. */
376 bool fShutdown;
377 /** Critical section for serializing access between thread + callbacks. */
378 RTCRITSECT CritSect;
379 /** The actual audio queue being used. */
380 AudioQueueRef audioQueue;
381 /** The audio buffers which are used with the above audio queue. */
382 AudioQueueBufferRef audioBuffer[2];
383 /** The acquired (final) audio format for this stream. */
384 AudioStreamBasicDescription asbdStream;
385 /** The audio unit for this stream. */
386 COREAUDIOUNIT Unit;
387 /** Initialization status tracker, actually COREAUDIOSTATUS.
388 * Used when some of the device parameters or the device itself is changed
389 * during the runtime. */
390 volatile uint32_t enmStatus;
391 /** An internal ring buffer for transferring data from/to the rendering callbacks. */
392 PRTCIRCBUF pCircBuf;
393} COREAUDIOSTREAM, *PCOREAUDIOSTREAM;
394
395static int coreAudioStreamInit(PCOREAUDIOSTREAM pCAStream, PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev);
396#ifndef VBOX_WITH_AUDIO_CALLBACKS
397static int coreAudioStreamReinit(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev);
398#endif
399static int coreAudioStreamUninit(PCOREAUDIOSTREAM pCAStream);
400
401static int coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd);
402
403static int coreAudioDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev);
404static int coreAudioDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev);
405static void coreAudioDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID, bool fIsInput, PDRVHOSTCOREAUDIO pDrv);
406
407static DECLCALLBACK(OSStatus) coreAudioDevPropChgCb(AudioObjectID propertyID, UInt32 nAddresses,
408 const AudioObjectPropertyAddress properties[], void *pvUser);
409
410static int coreAudioStreamInitQueue(PCOREAUDIOSTREAM pCAStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq);
411static DECLCALLBACK(void) coreAudioInputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer,
412 const AudioTimeStamp *pAudioTS, UInt32 cPacketDesc,
413 const AudioStreamPacketDescription *paPacketDesc);
414static DECLCALLBACK(void) coreAudioOutputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer);
415
416#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
417/**
418 * Initializes a conversion callback context.
419 *
420 * @return IPRT status code.
421 * @param pConvCbCtx Conversion callback context to initialize.
422 * @param pStream Pointer to stream to use.
423 * @param pASBDSrc Input (source) stream description to use.
424 * @param pASBDDst Output (destination) stream description to use.
425 */
426static int coreAudioInitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx, PCOREAUDIOSTREAM pStream,
427 AudioStreamBasicDescription *pASBDSrc, AudioStreamBasicDescription *pASBDDst)
428{
429 AssertPtrReturn(pConvCbCtx, VERR_INVALID_POINTER);
430 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
431 AssertPtrReturn(pASBDSrc, VERR_INVALID_POINTER);
432 AssertPtrReturn(pASBDDst, VERR_INVALID_POINTER);
433
434#ifdef DEBUG
435 coreAudioPrintASBD("CbCtx: Src", pASBDSrc);
436 coreAudioPrintASBD("CbCtx: Dst", pASBDDst);
437#endif
438
439 pConvCbCtx->pStream = pStream;
440
441 memcpy(&pConvCbCtx->asbdSrc, pASBDSrc, sizeof(AudioStreamBasicDescription));
442 memcpy(&pConvCbCtx->asbdDst, pASBDDst, sizeof(AudioStreamBasicDescription));
443
444 pConvCbCtx->pBufLstSrc = NULL;
445 pConvCbCtx->cErrors = 0;
446
447 return VINF_SUCCESS;
448}
449
450
451/**
452 * Uninitializes a conversion callback context.
453 *
454 * @return IPRT status code.
455 * @param pConvCbCtx Conversion callback context to uninitialize.
456 */
457static void coreAudioUninitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx)
458{
459 AssertPtrReturnVoid(pConvCbCtx);
460
461 pConvCbCtx->pStream = NULL;
462
463 RT_ZERO(pConvCbCtx->asbdSrc);
464 RT_ZERO(pConvCbCtx->asbdDst);
465
466 pConvCbCtx->pBufLstSrc = NULL;
467 pConvCbCtx->cErrors = 0;
468}
469#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */
470
471
472/**
473 * Does a (re-)enumeration of the host's playback + recording devices.
474 *
475 * @return IPRT status code.
476 * @param pThis Host audio driver instance.
477 * @param enmUsage Which devices to enumerate.
478 * @param pDevEnm Where to store the enumerated devices.
479 */
480static int coreAudioDevicesEnumerate(PDRVHOSTCOREAUDIO pThis, PDMAUDIODIR enmUsage, PPDMAUDIODEVICEENUM pDevEnm)
481{
482 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
483 AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER);
484
485 int rc = VINF_SUCCESS;
486
487 do
488 {
489 AudioDeviceID defaultDeviceID = kAudioDeviceUnknown;
490
491 /* Fetch the default audio device currently in use. */
492 AudioObjectPropertyAddress propAdrDefaultDev = { enmUsage == PDMAUDIODIR_IN
493 ? kAudioHardwarePropertyDefaultInputDevice
494 : kAudioHardwarePropertyDefaultOutputDevice,
495 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
496 UInt32 uSize = sizeof(defaultDeviceID);
497 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdrDefaultDev, 0, NULL, &uSize, &defaultDeviceID);
498 if (err != noErr)
499 {
500 LogRel(("CoreAudio: Unable to determine default %s device (%RI32)\n",
501 enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback", err));
502 return VERR_NOT_FOUND;
503 }
504
505 if (defaultDeviceID == kAudioDeviceUnknown)
506 {
507 LogFunc(("No default %s device found\n", enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback"));
508 /* Keep going. */
509 }
510
511 AudioObjectPropertyAddress propAdrDevList = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
512 kAudioObjectPropertyElementMaster };
513
514 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propAdrDevList, 0, NULL, &uSize);
515 if (err != kAudioHardwareNoError)
516 break;
517
518 AudioDeviceID *pDevIDs = (AudioDeviceID *)alloca(uSize);
519 if (pDevIDs == NULL)
520 break;
521
522 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdrDevList, 0, NULL, &uSize, pDevIDs);
523 if (err != kAudioHardwareNoError)
524 break;
525
526 rc = DrvAudioHlpDeviceEnumInit(pDevEnm);
527 if (RT_FAILURE(rc))
528 break;
529
530 UInt16 cDevices = uSize / sizeof(AudioDeviceID);
531
532 PPDMAUDIODEVICE pDev = NULL;
533 for (UInt16 i = 0; i < cDevices; i++)
534 {
535 if (pDev) /* Some (skipped) device to clean up first? */
536 PDMAudioDeviceFree(pDev);
537
538 pDev = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA));
539 if (!pDev)
540 {
541 rc = VERR_NO_MEMORY;
542 break;
543 }
544
545 /* Set usage. */
546 pDev->enmUsage = enmUsage;
547
548 /* Init backend-specific device data. */
549 PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pDev->pvData;
550 AssertPtr(pDevData);
551 coreAudioDeviceDataInit(pDevData, pDevIDs[i], enmUsage == PDMAUDIODIR_IN, pThis);
552
553 /* Check if the device is valid. */
554 AudioDeviceID curDevID = pDevData->deviceID;
555
556 /* Is the device the default device? */
557 if (curDevID == defaultDeviceID)
558 pDev->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT;
559
560 AudioObjectPropertyAddress propAddrCfg = { kAudioDevicePropertyStreamConfiguration,
561 enmUsage == PDMAUDIODIR_IN
562 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
563 kAudioObjectPropertyElementMaster };
564
565 err = AudioObjectGetPropertyDataSize(curDevID, &propAddrCfg, 0, NULL, &uSize);
566 if (err != noErr)
567 continue;
568
569 AudioBufferList *pBufList = (AudioBufferList *)RTMemAlloc(uSize);
570 if (!pBufList)
571 continue;
572
573 err = AudioObjectGetPropertyData(curDevID, &propAddrCfg, 0, NULL, &uSize, pBufList);
574 if (err == noErr)
575 {
576 for (UInt32 a = 0; a < pBufList->mNumberBuffers; a++)
577 {
578 if (enmUsage == PDMAUDIODIR_IN)
579 pDev->cMaxInputChannels += pBufList->mBuffers[a].mNumberChannels;
580 else if (enmUsage == PDMAUDIODIR_OUT)
581 pDev->cMaxOutputChannels += pBufList->mBuffers[a].mNumberChannels;
582 }
583 }
584
585 if (pBufList)
586 {
587 RTMemFree(pBufList);
588 pBufList = NULL;
589 }
590
591 /* Check if the device is valid, e.g. has any input/output channels according to its usage. */
592 if ( enmUsage == PDMAUDIODIR_IN
593 && !pDev->cMaxInputChannels)
594 continue;
595 if ( enmUsage == PDMAUDIODIR_OUT
596 && !pDev->cMaxOutputChannels)
597 continue;
598
599 /* Resolve the device's name. */
600 AudioObjectPropertyAddress propAddrName = { kAudioObjectPropertyName,
601 enmUsage == PDMAUDIODIR_IN
602 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
603 kAudioObjectPropertyElementMaster };
604 uSize = sizeof(CFStringRef);
605 CFStringRef pcfstrName = NULL;
606
607 err = AudioObjectGetPropertyData(curDevID, &propAddrName, 0, NULL, &uSize, &pcfstrName);
608 if (err != kAudioHardwareNoError)
609 continue;
610
611 CFIndex cbName = CFStringGetMaximumSizeForEncoding(CFStringGetLength(pcfstrName), kCFStringEncodingUTF8) + 1;
612 if (cbName)
613 {
614 char *pszName = (char *)RTStrAlloc(cbName);
615 if ( pszName
616 && CFStringGetCString(pcfstrName, pszName, cbName, kCFStringEncodingUTF8))
617 RTStrCopy(pDev->szName, sizeof(pDev->szName), pszName);
618
619 LogFunc(("Device '%s': %RU32\n", pszName, curDevID));
620
621 if (pszName)
622 {
623 RTStrFree(pszName);
624 pszName = NULL;
625 }
626 }
627
628 CFRelease(pcfstrName);
629
630 /* Check if the device is alive for the intended usage. */
631 AudioObjectPropertyAddress propAddrAlive = { kAudioDevicePropertyDeviceIsAlive,
632 enmUsage == PDMAUDIODIR_IN
633 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
634 kAudioObjectPropertyElementMaster };
635
636 UInt32 uAlive = 0;
637 uSize = sizeof(uAlive);
638
639 err = AudioObjectGetPropertyData(curDevID, &propAddrAlive, 0, NULL, &uSize, &uAlive);
640 if ( (err == noErr)
641 && !uAlive)
642 {
643 pDev->fFlags |= PDMAUDIODEV_FLAGS_DEAD;
644 }
645
646 /* Check if the device is being hogged by someone else. */
647 AudioObjectPropertyAddress propAddrHogged = { kAudioDevicePropertyHogMode,
648 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
649
650 pid_t pid = 0;
651 uSize = sizeof(pid);
652
653 err = AudioObjectGetPropertyData(curDevID, &propAddrHogged, 0, NULL, &uSize, &pid);
654 if ( (err == noErr)
655 && (pid != -1))
656 {
657 pDev->fFlags |= PDMAUDIODEV_FLAGS_LOCKED;
658 }
659
660 /* Add the device to the enumeration. */
661 rc = DrvAudioHlpDeviceEnumAdd(pDevEnm, pDev);
662 if (RT_FAILURE(rc))
663 break;
664
665 /* NULL device pointer because it's now part of the device enumeration. */
666 pDev = NULL;
667 }
668
669 if (RT_FAILURE(rc))
670 {
671 PDMAudioDeviceFree(pDev);
672 pDev = NULL;
673 }
674
675 } while (0);
676
677 if (RT_SUCCESS(rc))
678 {
679#ifdef DEBUG
680 LogFunc(("Devices for pDevEnm=%p, enmUsage=%RU32:\n", pDevEnm, enmUsage));
681 DrvAudioHlpDeviceEnumPrint("Core Audio", pDevEnm);
682#endif
683 }
684 else
685 DrvAudioHlpDeviceEnumFree(pDevEnm);
686
687 LogFlowFuncLeaveRC(rc);
688 return rc;
689}
690
691
692/**
693 * Checks if an audio device with a specific device ID is in the given device
694 * enumeration or not.
695 *
696 * @retval true if the node is the last element in the list.
697 * @retval false otherwise.
698 *
699 * @param pEnmSrc Device enumeration to search device ID in.
700 * @param deviceID Device ID to search.
701 */
702bool coreAudioDevicesHasDevice(PPDMAUDIODEVICEENUM pEnmSrc, AudioDeviceID deviceID)
703{
704 PPDMAUDIODEVICE pDevSrc;
705 RTListForEach(&pEnmSrc->LstDevices, pDevSrc, PDMAUDIODEVICE, Node)
706 {
707 PCOREAUDIODEVICEDATA pDevSrcData = (PCOREAUDIODEVICEDATA)pDevSrc->pvData;
708 AssertPtr(pDevSrcData);
709
710 if (pDevSrcData->deviceID == deviceID)
711 return true;
712 }
713
714 return false;
715}
716
717
718/**
719 * Enumerates all host devices and builds a final device enumeration list, consisting
720 * of (duplex) input and output devices.
721 *
722 * @return IPRT status code.
723 * @param pThis Host audio driver instance.
724 * @param pEnmDst Where to store the device enumeration list.
725 */
726int coreAudioDevicesEnumerateAll(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICEENUM pEnmDst)
727{
728 PDMAUDIODEVICEENUM devEnmIn;
729 int rc = coreAudioDevicesEnumerate(pThis, PDMAUDIODIR_IN, &devEnmIn);
730 if (RT_SUCCESS(rc))
731 {
732 PDMAUDIODEVICEENUM devEnmOut;
733 rc = coreAudioDevicesEnumerate(pThis, PDMAUDIODIR_OUT, &devEnmOut);
734 if (RT_SUCCESS(rc))
735 {
736 /*
737 * Build up the final device enumeration, based on the input and output device lists
738 * just enumerated.
739 *
740 * Also make sure to handle duplex devices, that is, devices which act as input and output
741 * at the same time.
742 */
743
744 rc = DrvAudioHlpDeviceEnumInit(pEnmDst);
745 if (RT_SUCCESS(rc))
746 {
747 PPDMAUDIODEVICE pDevSrcIn;
748 RTListForEach(&devEnmIn.LstDevices, pDevSrcIn, PDMAUDIODEVICE, Node)
749 {
750 PCOREAUDIODEVICEDATA pDevSrcInData = (PCOREAUDIODEVICEDATA)pDevSrcIn->pvData;
751 AssertPtr(pDevSrcInData);
752
753 PPDMAUDIODEVICE pDevDst = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA));
754 if (!pDevDst)
755 {
756 rc = VERR_NO_MEMORY;
757 break;
758 }
759
760 PCOREAUDIODEVICEDATA pDevDstData = (PCOREAUDIODEVICEDATA)pDevDst->pvData;
761 AssertPtr(pDevDstData);
762 coreAudioDeviceDataInit(pDevDstData, pDevSrcInData->deviceID, true /* fIsInput */, pThis);
763
764 RTStrCopy(pDevDst->szName, sizeof(pDevDst->szName), pDevSrcIn->szName);
765
766 pDevDst->enmUsage = PDMAUDIODIR_IN; /* Input device by default (simplex). */
767 pDevDst->cMaxInputChannels = pDevSrcIn->cMaxInputChannels;
768
769 /* Handle flags. */
770 if (pDevSrcIn->fFlags & PDMAUDIODEV_FLAGS_DEFAULT)
771 pDevDst->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT;
772 /** @todo Handle hot plugging? */
773
774 /*
775 * Now search through the list of all found output devices and check if we found
776 * an output device with the same device ID as the currently handled input device.
777 *
778 * If found, this means we have to treat that device as a duplex device then.
779 */
780 PPDMAUDIODEVICE pDevSrcOut;
781 RTListForEach(&devEnmOut.LstDevices, pDevSrcOut, PDMAUDIODEVICE, Node)
782 {
783 PCOREAUDIODEVICEDATA pDevSrcOutData = (PCOREAUDIODEVICEDATA)pDevSrcOut->pvData;
784 AssertPtr(pDevSrcOutData);
785
786 if (pDevSrcInData->deviceID == pDevSrcOutData->deviceID)
787 {
788 pDevDst->enmUsage = PDMAUDIODIR_DUPLEX;
789 pDevDst->cMaxOutputChannels = pDevSrcOut->cMaxOutputChannels;
790
791 if (pDevSrcOut->fFlags & PDMAUDIODEV_FLAGS_DEFAULT)
792 pDevDst->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT;
793 break;
794 }
795 }
796
797 if (RT_SUCCESS(rc))
798 {
799 rc = DrvAudioHlpDeviceEnumAdd(pEnmDst, pDevDst);
800 }
801 else
802 {
803 PDMAudioDeviceFree(pDevDst);
804 pDevDst = NULL;
805 }
806 }
807
808 if (RT_SUCCESS(rc))
809 {
810 /*
811 * As a last step, add all remaining output devices which have not been handled in the loop above,
812 * that is, all output devices which operate in simplex mode.
813 */
814 PPDMAUDIODEVICE pDevSrcOut;
815 RTListForEach(&devEnmOut.LstDevices, pDevSrcOut, PDMAUDIODEVICE, Node)
816 {
817 PCOREAUDIODEVICEDATA pDevSrcOutData = (PCOREAUDIODEVICEDATA)pDevSrcOut->pvData;
818 AssertPtr(pDevSrcOutData);
819
820 if (coreAudioDevicesHasDevice(pEnmDst, pDevSrcOutData->deviceID))
821 continue; /* Already in our list, skip. */
822
823 PPDMAUDIODEVICE pDevDst = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA));
824 if (!pDevDst)
825 {
826 rc = VERR_NO_MEMORY;
827 break;
828 }
829
830 PCOREAUDIODEVICEDATA pDevDstData = (PCOREAUDIODEVICEDATA)pDevDst->pvData;
831 AssertPtr(pDevDstData);
832 coreAudioDeviceDataInit(pDevDstData, pDevSrcOutData->deviceID, false /* fIsInput */, pThis);
833
834 RTStrCopy(pDevDst->szName, sizeof(pDevDst->szName), pDevSrcOut->szName);
835
836 pDevDst->enmUsage = PDMAUDIODIR_OUT;
837 pDevDst->cMaxOutputChannels = pDevSrcOut->cMaxOutputChannels;
838
839 pDevDstData->deviceID = pDevSrcOutData->deviceID;
840
841 /* Handle flags. */
842 if (pDevSrcOut->fFlags & PDMAUDIODEV_FLAGS_DEFAULT)
843 pDevDst->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT;
844 /** @todo Handle hot plugging? */
845
846 rc = DrvAudioHlpDeviceEnumAdd(pEnmDst, pDevDst);
847 if (RT_FAILURE(rc))
848 {
849 PDMAudioDeviceFree(pDevDst);
850 break;
851 }
852 }
853 }
854
855 if (RT_FAILURE(rc))
856 DrvAudioHlpDeviceEnumFree(pEnmDst);
857 }
858
859 DrvAudioHlpDeviceEnumFree(&devEnmOut);
860 }
861
862 DrvAudioHlpDeviceEnumFree(&devEnmIn);
863 }
864
865#ifdef DEBUG
866 if (RT_SUCCESS(rc))
867 DrvAudioHlpDeviceEnumPrint("Core Audio (Final)", pEnmDst);
868#endif
869
870 LogFlowFuncLeaveRC(rc);
871 return rc;
872}
873
874
875/**
876 * Initializes a Core Audio-specific device data structure.
877 *
878 * @returns IPRT status code.
879 * @param pDevData Device data structure to initialize.
880 * @param deviceID Core Audio device ID to assign this structure to.
881 * @param fIsInput Whether this is an input device or not.
882 * @param pDrv Driver instance to use.
883 */
884static void coreAudioDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID, bool fIsInput, PDRVHOSTCOREAUDIO pDrv)
885{
886 AssertPtrReturnVoid(pDevData);
887 AssertPtrReturnVoid(pDrv);
888
889 pDevData->deviceID = deviceID;
890 pDevData->pDrv = pDrv;
891
892 /* Get the device UUID. */
893 AudioObjectPropertyAddress propAdrDevUUID = { kAudioDevicePropertyDeviceUID,
894 fIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
895 kAudioObjectPropertyElementMaster };
896 UInt32 uSize = sizeof(pDevData->UUID);
897 OSStatus err = AudioObjectGetPropertyData(pDevData->deviceID, &propAdrDevUUID, 0, NULL, &uSize, &pDevData->UUID);
898 if (err != noErr)
899 LogRel(("CoreAudio: Failed to retrieve device UUID for device %RU32 (%RI32)\n", deviceID, err));
900
901 RTListInit(&pDevData->lstStreams);
902}
903
904
905/**
906 * Propagates an audio device status to all its connected Core Audio streams.
907 *
908 * @return IPRT status code.
909 * @param pDev Audio device to propagate status for.
910 * @param enmSts Status to propagate.
911 */
912static int coreAudioDevicePropagateStatus(PPDMAUDIODEVICE pDev, COREAUDIOSTATUS enmSts)
913{
914 AssertPtrReturn(pDev, VERR_INVALID_POINTER);
915
916 PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pDev->pvData;
917 AssertPtrReturn(pDevData, VERR_INVALID_POINTER);
918
919 /* Sanity. */
920 AssertPtr(pDevData->pDrv);
921
922 LogFlowFunc(("pDev=%p, pDevData=%p, enmSts=%RU32\n", pDev, pDevData, enmSts));
923
924 PCOREAUDIOSTREAM pCAStream;
925 RTListForEach(&pDevData->lstStreams, pCAStream, COREAUDIOSTREAM, Node)
926 {
927 LogFlowFunc(("pCAStream=%p\n", pCAStream));
928
929 /* We move the reinitialization to the next output event.
930 * This make sure this thread isn't blocked and the
931 * reinitialization is done when necessary only. */
932 ASMAtomicXchgU32(&pCAStream->enmStatus, enmSts);
933 }
934
935 return VINF_SUCCESS;
936}
937
938
939static DECLCALLBACK(OSStatus) coreAudioDeviceStateChangedCb(AudioObjectID propertyID,
940 UInt32 nAddresses,
941 const AudioObjectPropertyAddress properties[],
942 void *pvUser)
943{
944 RT_NOREF(propertyID, nAddresses, properties);
945
946 LogFlowFunc(("propertyID=%u, nAddresses=%u, pvUser=%p\n", propertyID, nAddresses, pvUser));
947
948 PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)pvUser;
949 AssertPtr(pDev);
950
951 PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
952 AssertPtrReturn(pData, VERR_INVALID_POINTER);
953
954 PDRVHOSTCOREAUDIO pThis = pData->pDrv;
955 AssertPtr(pThis);
956
957 int rc2 = RTCritSectEnter(&pThis->CritSect);
958 AssertRC(rc2);
959
960 UInt32 uAlive = 1;
961 UInt32 uSize = sizeof(UInt32);
962
963 AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
964 kAudioObjectPropertyElementMaster };
965
966 AudioDeviceID deviceID = pData->deviceID;
967
968 OSStatus err = AudioObjectGetPropertyData(deviceID, &propAdr, 0, NULL, &uSize, &uAlive);
969
970 bool fIsDead = false;
971
972 if (err == kAudioHardwareBadDeviceError)
973 fIsDead = true; /* Unplugged. */
974 else if ((err == kAudioHardwareNoError) && (!RT_BOOL(uAlive)))
975 fIsDead = true; /* Something else happened. */
976
977 if (fIsDead)
978 {
979 LogRel2(("CoreAudio: Device '%s' stopped functioning\n", pDev->szName));
980
981 /* Mark device as dead. */
982 rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_UNINIT);
983 AssertRC(rc2);
984 }
985
986 rc2 = RTCritSectLeave(&pThis->CritSect);
987 AssertRC(rc2);
988
989 return noErr;
990}
991
992/* Callback for getting notified when the default recording/playback device has been changed. */
993static DECLCALLBACK(OSStatus) coreAudioDefaultDeviceChangedCb(AudioObjectID propertyID,
994 UInt32 nAddresses,
995 const AudioObjectPropertyAddress properties[],
996 void *pvUser)
997{
998 RT_NOREF(propertyID, nAddresses);
999
1000 LogFlowFunc(("propertyID=%u, nAddresses=%u, pvUser=%p\n", propertyID, nAddresses, pvUser));
1001
1002 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
1003 AssertPtr(pThis);
1004
1005 int rc2 = RTCritSectEnter(&pThis->CritSect);
1006 AssertRC(rc2);
1007
1008 for (UInt32 idxAddress = 0; idxAddress < nAddresses; idxAddress++)
1009 {
1010 PPDMAUDIODEVICE pDev = NULL;
1011
1012 /*
1013 * Check if the default input / output device has been changed.
1014 */
1015 const AudioObjectPropertyAddress *pProperty = &properties[idxAddress];
1016 switch (pProperty->mSelector)
1017 {
1018 case kAudioHardwarePropertyDefaultInputDevice:
1019 LogFlowFunc(("kAudioHardwarePropertyDefaultInputDevice\n"));
1020 pDev = pThis->pDefaultDevIn;
1021 break;
1022
1023 case kAudioHardwarePropertyDefaultOutputDevice:
1024 LogFlowFunc(("kAudioHardwarePropertyDefaultOutputDevice\n"));
1025 pDev = pThis->pDefaultDevOut;
1026 break;
1027
1028 default:
1029 /* Skip others. */
1030 break;
1031 }
1032
1033 LogFlowFunc(("pDev=%p\n", pDev));
1034
1035#ifndef VBOX_WITH_AUDIO_CALLBACKS
1036 if (pDev)
1037 {
1038 PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
1039 AssertPtr(pData);
1040
1041 /* This listener is called on every change of the hardware
1042 * device. So check if the default device has really changed. */
1043 UInt32 uSize = sizeof(AudioDeviceID);
1044 UInt32 uResp = 0;
1045
1046 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, pProperty, 0, NULL, &uSize, &uResp);
1047 if (err == noErr)
1048 {
1049 if (pData->deviceID != uResp) /* Has the device ID changed? */
1050 {
1051 rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_REINIT);
1052 AssertRC(rc2);
1053 }
1054 }
1055 }
1056#endif /* VBOX_WITH_AUDIO_CALLBACKS */
1057 }
1058
1059#ifdef VBOX_WITH_AUDIO_CALLBACKS
1060 PFNPDMHOSTAUDIOCALLBACK pfnCallback = pThis->pfnCallback;
1061#endif
1062
1063 /* Make sure to leave the critical section before calling the callback. */
1064 rc2 = RTCritSectLeave(&pThis->CritSect);
1065 AssertRC(rc2);
1066
1067#ifdef VBOX_WITH_AUDIO_CALLBACKS
1068 if (pfnCallback)
1069 /* Ignore rc */ pfnCallback(pThis->pDrvIns, PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED, NULL, 0);
1070#endif
1071
1072 return noErr;
1073}
1074
1075#ifndef VBOX_WITH_AUDIO_CALLBACKS
1076/**
1077 * Re-initializes a Core Audio stream with a specific audio device and stream configuration.
1078 *
1079 * @return IPRT status code.
1080 * @param pThis Driver instance.
1081 * @param pCAStream Audio stream to re-initialize.
1082 * @param pDev Audio device to use for re-initialization.
1083 * @param pCfg Stream configuration to use for re-initialization.
1084 */
1085static int coreAudioStreamReinitEx(PDRVHOSTCOREAUDIO pThis,
1086 PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev, PPDMAUDIOSTREAMCFG pCfg)
1087{
1088 LogFunc(("pCAStream=%p\n", pCAStream));
1089
1090 int rc = coreAudioStreamUninit(pCAStream);
1091 if (RT_SUCCESS(rc))
1092 {
1093 rc = coreAudioStreamInit(pCAStream, pThis, pDev);
1094 if (RT_SUCCESS(rc))
1095 {
1096 rc = coreAudioStreamInitQueue(pCAStream, pCfg /* pCfgReq */, NULL /* pCfgAcq */);
1097 if (RT_SUCCESS(rc))
1098 rc = coreAudioStreamControl(pCAStream->pDrv, pCAStream, PDMAUDIOSTREAMCMD_ENABLE);
1099
1100 if (RT_FAILURE(rc))
1101 {
1102 int rc2 = coreAudioStreamUninit(pCAStream);
1103 AssertRC(rc2);
1104 }
1105 }
1106 }
1107
1108 if (RT_FAILURE(rc))
1109 LogRel(("CoreAudio: Unable to re-init stream: %Rrc\n", rc));
1110
1111 return rc;
1112}
1113
1114/**
1115 * Re-initializes a Core Audio stream with a specific audio device.
1116 *
1117 * @return IPRT status code.
1118 * @param pThis Driver instance.
1119 * @param pCAStream Audio stream to re-initialize.
1120 * @param pDev Audio device to use for re-initialization.
1121 */
1122static int coreAudioStreamReinit(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev)
1123{
1124 int rc = coreAudioStreamUninit(pCAStream);
1125 if (RT_SUCCESS(rc))
1126 {
1127 /* Use the acquired stream configuration from the former initialization to
1128 * re-initialize the stream. */
1129 PDMAUDIOSTREAMCFG CfgAcq;
1130 rc = coreAudioASBDToStreamCfg(&pCAStream->Unit.streamFmt, &CfgAcq);
1131 if (RT_SUCCESS(rc))
1132 rc = coreAudioStreamReinitEx(pThis, pCAStream, pDev, &CfgAcq);
1133 }
1134
1135 return rc;
1136}
1137#endif /* !VBOX_WITH_AUDIO_CALLBACKS */
1138
1139#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
1140/* Callback to convert audio input data from one format to another. */
1141static DECLCALLBACK(OSStatus) coreAudioConverterCb(AudioConverterRef inAudioConverter,
1142 UInt32 *ioNumberDataPackets,
1143 AudioBufferList *ioData,
1144 AudioStreamPacketDescription **ppASPD,
1145 void *pvUser)
1146{
1147 RT_NOREF(inAudioConverter);
1148
1149 AssertPtrReturn(ioNumberDataPackets, caConverterEOFDErr);
1150 AssertPtrReturn(ioData, caConverterEOFDErr);
1151
1152 PCOREAUDIOCONVCBCTX pConvCbCtx = (PCOREAUDIOCONVCBCTX)pvUser;
1153 AssertPtr(pConvCbCtx);
1154
1155 /* Initialize values. */
1156 ioData->mBuffers[0].mNumberChannels = 0;
1157 ioData->mBuffers[0].mDataByteSize = 0;
1158 ioData->mBuffers[0].mData = NULL;
1159
1160 if (ppASPD)
1161 {
1162 Log3Func(("Handling packet description not implemented\n"));
1163 }
1164 else
1165 {
1166 /** @todo Check converter ID? */
1167
1168 /** @todo Handled non-interleaved data by going through the full buffer list,
1169 * not only through the first buffer like we do now. */
1170 Log3Func(("ioNumberDataPackets=%RU32\n", *ioNumberDataPackets));
1171
1172 UInt32 cNumberDataPackets = *ioNumberDataPackets;
1173 Assert(pConvCbCtx->uPacketIdx + cNumberDataPackets <= pConvCbCtx->uPacketCnt);
1174
1175 if (cNumberDataPackets)
1176 {
1177 AssertPtr(pConvCbCtx->pBufLstSrc);
1178 Assert(pConvCbCtx->pBufLstSrc->mNumberBuffers == 1); /* Only one buffer for the source supported atm. */
1179
1180 AudioStreamBasicDescription *pSrcASBD = &pConvCbCtx->asbdSrc;
1181 AudioBuffer *pSrcBuf = &pConvCbCtx->pBufLstSrc->mBuffers[0];
1182
1183 size_t cbOff = pConvCbCtx->uPacketIdx * pSrcASBD->mBytesPerPacket;
1184
1185 cNumberDataPackets = RT_MIN((pSrcBuf->mDataByteSize - cbOff) / pSrcASBD->mBytesPerPacket,
1186 cNumberDataPackets);
1187
1188 void *pvAvail = (uint8_t *)pSrcBuf->mData + cbOff;
1189 size_t cbAvail = RT_MIN(pSrcBuf->mDataByteSize - cbOff, cNumberDataPackets * pSrcASBD->mBytesPerPacket);
1190
1191 Log3Func(("cNumberDataPackets=%RU32, cbOff=%zu, cbAvail=%zu\n", cNumberDataPackets, cbOff, cbAvail));
1192
1193 /* Set input data for the converter to use.
1194 * Note: For VBR (Variable Bit Rates) or interleaved data handling we need multiple buffers here. */
1195 ioData->mNumberBuffers = 1;
1196
1197 ioData->mBuffers[0].mNumberChannels = pSrcBuf->mNumberChannels;
1198 ioData->mBuffers[0].mDataByteSize = cbAvail;
1199 ioData->mBuffers[0].mData = pvAvail;
1200
1201#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
1202 RTFILE fh;
1203 int rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm",
1204 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
1205 if (RT_SUCCESS(rc))
1206 {
1207 RTFileWrite(fh, pvAvail, cbAvail, NULL);
1208 RTFileClose(fh);
1209 }
1210 else
1211 AssertFailed();
1212#endif
1213 pConvCbCtx->uPacketIdx += cNumberDataPackets;
1214 Assert(pConvCbCtx->uPacketIdx <= pConvCbCtx->uPacketCnt);
1215
1216 *ioNumberDataPackets = cNumberDataPackets;
1217 }
1218 }
1219
1220 Log3Func(("%RU32 / %RU32 -> ioNumberDataPackets=%RU32\n",
1221 pConvCbCtx->uPacketIdx, pConvCbCtx->uPacketCnt, *ioNumberDataPackets));
1222
1223 return noErr;
1224}
1225#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */
1226
1227
1228/**
1229 * Initializes a Core Audio stream.
1230 *
1231 * @return IPRT status code.
1232 * @param pThis Driver instance.
1233 * @param pCAStream Stream to initialize.
1234 * @param pDev Audio device to use for this stream.
1235 */
1236static int coreAudioStreamInit(PCOREAUDIOSTREAM pCAStream, PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev)
1237{
1238 AssertPtrReturn(pCAStream, VERR_INVALID_POINTER);
1239 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1240 AssertPtrReturn(pDev, VERR_INVALID_POINTER);
1241
1242 Assert(pCAStream->Unit.pDevice == NULL); /* Make sure no device is assigned yet. */
1243 AssertPtr(pDev->pvData);
1244 Assert(pDev->cbData == sizeof(COREAUDIODEVICEDATA));
1245
1246#ifdef DEBUG
1247 PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
1248 LogFunc(("pCAStream=%p, pDev=%p ('%s', ID=%RU32)\n", pCAStream, pDev, pDev->szName, pData->deviceID));
1249#endif
1250
1251 pCAStream->Unit.pDevice = pDev;
1252 pCAStream->pDrv = pThis;
1253
1254 return VINF_SUCCESS;
1255}
1256
1257# define CA_BREAK_STMT(stmt) \
1258 stmt; \
1259 break;
1260
1261/**
1262 * Thread for a Core Audio stream's audio queue handling.
1263 *
1264 * This thread is required per audio queue to pump data to/from the Core Audio
1265 * stream and handling its callbacks.
1266 *
1267 * @returns IPRT status code.
1268 * @param hThreadSelf Thread handle.
1269 * @param pvUser User argument.
1270 */
1271static DECLCALLBACK(int) coreAudioQueueThread(RTTHREAD hThreadSelf, void *pvUser)
1272{
1273 RT_NOREF(hThreadSelf);
1274
1275 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser;
1276 AssertPtr(pCAStream);
1277 AssertPtr(pCAStream->pCfg);
1278
1279 const bool fIn = pCAStream->pCfg->enmDir == PDMAUDIODIR_IN;
1280
1281 LogFunc(("Thread started for pCAStream=%p, fIn=%RTbool\n", pCAStream, fIn));
1282
1283 /*
1284 * Create audio queue.
1285 */
1286 OSStatus err;
1287 if (fIn)
1288 err = AudioQueueNewInput(&pCAStream->asbdStream, coreAudioInputQueueCb, pCAStream /* pvData */,
1289 CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pCAStream->audioQueue);
1290 else
1291 err = AudioQueueNewOutput(&pCAStream->asbdStream, coreAudioOutputQueueCb, pCAStream /* pvData */,
1292 CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pCAStream->audioQueue);
1293
1294 if (err != noErr)
1295 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1296
1297 /*
1298 * Assign device to queue.
1299 */
1300 PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pCAStream->Unit.pDevice->pvData;
1301 AssertPtr(pData);
1302
1303 UInt32 uSize = sizeof(pData->UUID);
1304 err = AudioQueueSetProperty(pCAStream->audioQueue, kAudioQueueProperty_CurrentDevice, &pData->UUID, uSize);
1305 if (err != noErr)
1306 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1307
1308 const size_t cbBufSize = PDMAudioPropsFramesToBytes(&pCAStream->pCfg->Props, pCAStream->pCfg->Backend.cFramesPeriod);
1309
1310 /*
1311 * Allocate audio buffers.
1312 */
1313 for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++)
1314 {
1315 err = AudioQueueAllocateBuffer(pCAStream->audioQueue, cbBufSize, &pCAStream->audioBuffer[i]);
1316 if (err != noErr)
1317 break;
1318 }
1319
1320 if (err != noErr)
1321 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1322
1323 /* Signal the main thread before entering the main loop. */
1324 RTThreadUserSignal(RTThreadSelf());
1325
1326 /*
1327 * Enter the main loop.
1328 */
1329 while (!ASMAtomicReadBool(&pCAStream->fShutdown))
1330 {
1331 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
1332 }
1333
1334 /*
1335 * Cleanup.
1336 */
1337 if (fIn)
1338 {
1339 AudioQueueStop(pCAStream->audioQueue, 1);
1340 }
1341 else
1342 {
1343 AudioQueueStop(pCAStream->audioQueue, 0);
1344 }
1345
1346 for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++)
1347 {
1348 if (pCAStream->audioBuffer[i])
1349 AudioQueueFreeBuffer(pCAStream->audioQueue, pCAStream->audioBuffer[i]);
1350 }
1351
1352 AudioQueueDispose(pCAStream->audioQueue, 1);
1353
1354 LogFunc(("Thread ended for pCAStream=%p, fIn=%RTbool\n", pCAStream, fIn));
1355 return VINF_SUCCESS;
1356}
1357
1358/**
1359 * Processes input data of an audio queue buffer and stores it into a Core Audio stream.
1360 *
1361 * @returns IPRT status code.
1362 * @param pCAStream Core Audio stream to store input data into.
1363 * @param audioBuffer Audio buffer to process input data from.
1364 */
1365int coreAudioInputQueueProcBuffer(PCOREAUDIOSTREAM pCAStream, AudioQueueBufferRef audioBuffer)
1366{
1367 PRTCIRCBUF pCircBuf = pCAStream->pCircBuf;
1368 AssertPtr(pCircBuf);
1369
1370 UInt8 *pvSrc = (UInt8 *)audioBuffer->mAudioData;
1371 UInt8 *pvDst = NULL;
1372
1373 size_t cbWritten = 0;
1374
1375 size_t cbToWrite = audioBuffer->mAudioDataByteSize;
1376 size_t cbLeft = RT_MIN(cbToWrite, RTCircBufFree(pCircBuf));
1377
1378 while (cbLeft)
1379 {
1380 /* Try to acquire the necessary block from the ring buffer. */
1381 RTCircBufAcquireWriteBlock(pCircBuf, cbLeft, (void **)&pvDst, &cbToWrite);
1382
1383 if (!cbToWrite)
1384 break;
1385
1386 /* Copy the data from our ring buffer to the core audio buffer. */
1387 memcpy((UInt8 *)pvDst, pvSrc + cbWritten, cbToWrite);
1388
1389 /* Release the read buffer, so it could be used for new data. */
1390 RTCircBufReleaseWriteBlock(pCircBuf, cbToWrite);
1391
1392 cbWritten += cbToWrite;
1393
1394 Assert(cbLeft >= cbToWrite);
1395 cbLeft -= cbToWrite;
1396 }
1397
1398 Log3Func(("pCAStream=%p, cbBuffer=%RU32/%zu, cbWritten=%zu\n",
1399 pCAStream, audioBuffer->mAudioDataByteSize, audioBuffer->mAudioDataBytesCapacity, cbWritten));
1400
1401 return VINF_SUCCESS;
1402}
1403
1404/**
1405 * Input audio queue callback. Called whenever input data from the audio queue becomes available.
1406 *
1407 * @param pvUser User argument.
1408 * @param audioQueue Audio queue to process input data from.
1409 * @param audioBuffer Audio buffer to process input data from. Must be part of audio queue.
1410 * @param pAudioTS Audio timestamp.
1411 * @param cPacketDesc Number of packet descriptors.
1412 * @param paPacketDesc Array of packet descriptors.
1413 */
1414static DECLCALLBACK(void) coreAudioInputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer,
1415 const AudioTimeStamp *pAudioTS,
1416 UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc)
1417{
1418 RT_NOREF(audioQueue, pAudioTS, cPacketDesc, paPacketDesc);
1419
1420 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser;
1421 AssertPtr(pCAStream);
1422
1423 int rc = RTCritSectEnter(&pCAStream->CritSect);
1424 AssertRC(rc);
1425
1426 rc = coreAudioInputQueueProcBuffer(pCAStream, audioBuffer);
1427 if (RT_SUCCESS(rc))
1428 AudioQueueEnqueueBuffer(audioQueue, audioBuffer, 0, NULL);
1429
1430 rc = RTCritSectLeave(&pCAStream->CritSect);
1431 AssertRC(rc);
1432}
1433
1434/**
1435 * Processes output data of a Core Audio stream into an audio queue buffer.
1436 *
1437 * @returns IPRT status code.
1438 * @param pCAStream Core Audio stream to process output data for.
1439 * @param audioBuffer Audio buffer to store data into.
1440 */
1441int coreAudioOutputQueueProcBuffer(PCOREAUDIOSTREAM pCAStream, AudioQueueBufferRef audioBuffer)
1442{
1443 AssertPtr(pCAStream);
1444
1445 PRTCIRCBUF pCircBuf = pCAStream->pCircBuf;
1446 AssertPtr(pCircBuf);
1447
1448 size_t cbRead = 0;
1449
1450 UInt8 *pvSrc = NULL;
1451 UInt8 *pvDst = (UInt8 *)audioBuffer->mAudioData;
1452
1453 size_t cbToRead = RT_MIN(RTCircBufUsed(pCircBuf), audioBuffer->mAudioDataBytesCapacity);
1454 size_t cbLeft = cbToRead;
1455
1456 while (cbLeft)
1457 {
1458 /* Try to acquire the necessary block from the ring buffer. */
1459 RTCircBufAcquireReadBlock(pCircBuf, cbLeft, (void **)&pvSrc, &cbToRead);
1460
1461 if (cbToRead)
1462 {
1463 /* Copy the data from our ring buffer to the core audio buffer. */
1464 memcpy((UInt8 *)pvDst + cbRead, pvSrc, cbToRead);
1465 }
1466
1467 /* Release the read buffer, so it could be used for new data. */
1468 RTCircBufReleaseReadBlock(pCircBuf, cbToRead);
1469
1470 if (!cbToRead)
1471 break;
1472
1473 /* Move offset. */
1474 cbRead += cbToRead;
1475 Assert(cbRead <= audioBuffer->mAudioDataBytesCapacity);
1476
1477 Assert(cbToRead <= cbLeft);
1478 cbLeft -= cbToRead;
1479 }
1480
1481 audioBuffer->mAudioDataByteSize = cbRead;
1482
1483 if (audioBuffer->mAudioDataByteSize < audioBuffer->mAudioDataBytesCapacity)
1484 {
1485 RT_BZERO((UInt8 *)audioBuffer->mAudioData + audioBuffer->mAudioDataByteSize,
1486 audioBuffer->mAudioDataBytesCapacity - audioBuffer->mAudioDataByteSize);
1487
1488 audioBuffer->mAudioDataByteSize = audioBuffer->mAudioDataBytesCapacity;
1489 }
1490
1491 Log3Func(("pCAStream=%p, cbCapacity=%RU32, cbRead=%zu\n",
1492 pCAStream, audioBuffer->mAudioDataBytesCapacity, cbRead));
1493
1494 return VINF_SUCCESS;
1495}
1496
1497/**
1498 * Output audio queue callback. Called whenever an audio queue is ready to process more output data.
1499 *
1500 * @param pvUser User argument.
1501 * @param audioQueue Audio queue to process output data for.
1502 * @param audioBuffer Audio buffer to store output data in. Must be part of audio queue.
1503 */
1504static DECLCALLBACK(void) coreAudioOutputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer)
1505{
1506 RT_NOREF(audioQueue);
1507
1508 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser;
1509 AssertPtr(pCAStream);
1510
1511 int rc = RTCritSectEnter(&pCAStream->CritSect);
1512 AssertRC(rc);
1513
1514 rc = coreAudioOutputQueueProcBuffer(pCAStream, audioBuffer);
1515 if (RT_SUCCESS(rc))
1516 AudioQueueEnqueueBuffer(audioQueue, audioBuffer, 0, NULL);
1517
1518 rc = RTCritSectLeave(&pCAStream->CritSect);
1519 AssertRC(rc);
1520}
1521
1522/**
1523 * Invalidates a Core Audio stream's audio queue.
1524 *
1525 * @returns IPRT status code.
1526 * @param pCAStream Core Audio stream to invalidate its queue for.
1527 */
1528static int coreAudioStreamInvalidateQueue(PCOREAUDIOSTREAM pCAStream)
1529{
1530 int rc = VINF_SUCCESS;
1531
1532 Log3Func(("pCAStream=%p\n", pCAStream));
1533
1534 for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++)
1535 {
1536 AudioQueueBufferRef pBuf = pCAStream->audioBuffer[i];
1537
1538 if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN)
1539 {
1540 int rc2 = coreAudioInputQueueProcBuffer(pCAStream, pBuf);
1541 if (RT_SUCCESS(rc2))
1542 {
1543 AudioQueueEnqueueBuffer(pCAStream->audioQueue, pBuf, 0, NULL);
1544 }
1545 }
1546 else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT)
1547 {
1548 int rc2 = coreAudioOutputQueueProcBuffer(pCAStream, pBuf);
1549 if ( RT_SUCCESS(rc2)
1550 && pBuf->mAudioDataByteSize)
1551 {
1552 AudioQueueEnqueueBuffer(pCAStream->audioQueue, pBuf, 0, NULL);
1553 }
1554
1555 if (RT_SUCCESS(rc))
1556 rc = rc2;
1557 }
1558 else
1559 AssertFailed();
1560 }
1561
1562 return rc;
1563}
1564
1565/**
1566 * Initializes a Core Audio stream's audio queue.
1567 *
1568 * @returns IPRT status code.
1569 * @param pCAStream Core Audio stream to initialize audio queue for.
1570 * @param pCfgReq Requested stream configuration.
1571 * @param pCfgAcq Acquired stream configuration on success.
1572 */
1573static int coreAudioStreamInitQueue(PCOREAUDIOSTREAM pCAStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1574{
1575 RT_NOREF(pCfgAcq);
1576
1577 LogFunc(("pCAStream=%p, pCfgReq=%p, pCfgAcq=%p\n", pCAStream, pCfgReq, pCfgAcq));
1578
1579 /* No device assigned? Bail out early. */
1580 if (pCAStream->Unit.pDevice == NULL)
1581 return VERR_NOT_AVAILABLE;
1582
1583 const bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN;
1584
1585 int rc = VINF_SUCCESS;
1586
1587 if (fIn)
1588 {
1589 rc = coreAudioInputPermissionCheck();
1590 if (RT_FAILURE(rc))
1591 return rc;
1592 }
1593
1594 /* Create the recording device's out format based on our required audio settings. */
1595 Assert(pCAStream->pCfg == NULL);
1596 pCAStream->pCfg = PDMAudioStrmCfgDup(pCfgReq);
1597 if (!pCAStream->pCfg)
1598 rc = VERR_NO_MEMORY;
1599
1600 coreAudioPCMPropsToASBD(&pCfgReq->Props, &pCAStream->asbdStream);
1601 /** @todo Do some validation? */
1602
1603 coreAudioPrintASBD( fIn
1604 ? "Capturing queue format"
1605 : "Playback queue format", &pCAStream->asbdStream);
1606
1607 if (RT_FAILURE(rc))
1608 {
1609 LogRel(("CoreAudio: Failed to convert requested %s format to native format (%Rrc)\n", fIn ? "input" : "output", rc));
1610 return rc;
1611 }
1612
1613 rc = RTCircBufCreate(&pCAStream->pCircBuf, PDMAUDIOSTREAMCFG_F2B(pCfgReq, pCfgReq->Backend.cFramesBufferSize));
1614 if (RT_FAILURE(rc))
1615 return rc;
1616
1617 /*
1618 * Start the thread.
1619 */
1620 rc = RTThreadCreate(&pCAStream->hThread, coreAudioQueueThread,
1621 pCAStream /* pvUser */, 0 /* Default stack size */,
1622 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CAQUEUE");
1623 if (RT_SUCCESS(rc))
1624 rc = RTThreadUserWait(pCAStream->hThread, 10 * 1000 /* 10s timeout */);
1625
1626 LogFunc(("Returning %Rrc\n", rc));
1627 return rc;
1628}
1629
1630/**
1631 * Unitializes a Core Audio stream's audio queue.
1632 *
1633 * @returns IPRT status code.
1634 * @param pCAStream Core Audio stream to unitialize audio queue for.
1635 */
1636static int coreAudioStreamUninitQueue(PCOREAUDIOSTREAM pCAStream)
1637{
1638 LogFunc(("pCAStream=%p\n", pCAStream));
1639
1640 if (pCAStream->hThread != NIL_RTTHREAD)
1641 {
1642 LogFunc(("Waiting for thread ...\n"));
1643
1644 ASMAtomicXchgBool(&pCAStream->fShutdown, true);
1645
1646 int rcThread;
1647 int rc = RTThreadWait(pCAStream->hThread, 30 * 1000, &rcThread);
1648 if (RT_FAILURE(rc))
1649 return rc;
1650
1651 RT_NOREF(rcThread);
1652 LogFunc(("Thread stopped with %Rrc\n", rcThread));
1653
1654 pCAStream->hThread = NIL_RTTHREAD;
1655 }
1656
1657 if (pCAStream->pCfg)
1658 {
1659 PDMAudioStrmCfgFree(pCAStream->pCfg);
1660 pCAStream->pCfg = NULL;
1661 }
1662
1663 if (pCAStream->pCircBuf)
1664 {
1665 RTCircBufDestroy(pCAStream->pCircBuf);
1666 pCAStream->pCircBuf = NULL;
1667 }
1668
1669 LogFunc(("Returning\n"));
1670 return VINF_SUCCESS;
1671}
1672
1673/**
1674 * Unitializes a Core Audio stream.
1675 *
1676 * @returns IPRT status code.
1677 * @param pCAStream Core Audio stream to uninitialize.
1678 */
1679static int coreAudioStreamUninit(PCOREAUDIOSTREAM pCAStream)
1680{
1681 LogFunc(("pCAStream=%p\n", pCAStream));
1682
1683 int rc = coreAudioStreamUninitQueue(pCAStream);
1684 if (RT_SUCCESS(rc))
1685 {
1686 pCAStream->Unit.pDevice = NULL;
1687 pCAStream->pDrv = NULL;
1688 }
1689
1690 return rc;
1691}
1692
1693/**
1694 * Registers callbacks for a specific Core Audio device.
1695 *
1696 * @return IPRT status code.
1697 * @param pThis Host audio driver instance.
1698 * @param pDev Audio device to use for the registered callbacks.
1699 */
1700static int coreAudioDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev)
1701{
1702 RT_NOREF(pThis);
1703
1704 AudioDeviceID deviceID = kAudioDeviceUnknown;
1705
1706 PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
1707 if (pData)
1708 deviceID = pData->deviceID;
1709
1710 if (deviceID != kAudioDeviceUnknown)
1711 {
1712 LogFunc(("deviceID=%RU32\n", deviceID));
1713
1714 /*
1715 * Register device callbacks.
1716 */
1717 AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
1718 kAudioObjectPropertyElementMaster };
1719 OSStatus err = AudioObjectAddPropertyListener(deviceID, &propAdr,
1720 coreAudioDeviceStateChangedCb, pDev /* pvUser */);
1721 if ( err != noErr
1722 && err != kAudioHardwareIllegalOperationError)
1723 {
1724 LogRel(("CoreAudio: Failed to add the recording device state changed listener (%RI32)\n", err));
1725 }
1726
1727 propAdr.mSelector = kAudioDeviceProcessorOverload;
1728 propAdr.mScope = kAudioUnitScope_Global;
1729 err = AudioObjectAddPropertyListener(deviceID, &propAdr,
1730 coreAudioDevPropChgCb, pDev /* pvUser */);
1731 if (err != noErr)
1732 LogRel(("CoreAudio: Failed to register processor overload listener (%RI32)\n", err));
1733
1734 propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
1735 propAdr.mScope = kAudioUnitScope_Global;
1736 err = AudioObjectAddPropertyListener(deviceID, &propAdr,
1737 coreAudioDevPropChgCb, pDev /* pvUser */);
1738 if (err != noErr)
1739 LogRel(("CoreAudio: Failed to register sample rate changed listener (%RI32)\n", err));
1740 }
1741
1742 return VINF_SUCCESS;
1743}
1744
1745/**
1746 * Unregisters all formerly registered callbacks of a Core Audio device again.
1747 *
1748 * @return IPRT status code.
1749 * @param pThis Host audio driver instance.
1750 * @param pDev Audio device to use for the registered callbacks.
1751 */
1752static int coreAudioDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev)
1753{
1754 RT_NOREF(pThis);
1755
1756 AudioDeviceID deviceID = kAudioDeviceUnknown;
1757
1758 if (pDev)
1759 {
1760 PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
1761 if (pData)
1762 deviceID = pData->deviceID;
1763 }
1764
1765 if (deviceID != kAudioDeviceUnknown)
1766 {
1767 LogFunc(("deviceID=%RU32\n", deviceID));
1768
1769 /*
1770 * Unregister per-device callbacks.
1771 */
1772 AudioObjectPropertyAddress propAdr = { kAudioDeviceProcessorOverload, kAudioObjectPropertyScopeGlobal,
1773 kAudioObjectPropertyElementMaster };
1774 OSStatus err = AudioObjectRemovePropertyListener(deviceID, &propAdr,
1775 coreAudioDevPropChgCb, pDev /* pvUser */);
1776 if ( err != noErr
1777 && err != kAudioHardwareBadObjectError)
1778 {
1779 LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%RI32)\n", err));
1780 }
1781
1782 propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
1783 err = AudioObjectRemovePropertyListener(deviceID, &propAdr,
1784 coreAudioDevPropChgCb, pDev /* pvUser */);
1785 if ( err != noErr
1786 && err != kAudioHardwareBadObjectError)
1787 {
1788 LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%RI32)\n", err));
1789 }
1790
1791 propAdr.mSelector = kAudioDevicePropertyDeviceIsAlive;
1792 err = AudioObjectRemovePropertyListener(deviceID, &propAdr,
1793 coreAudioDeviceStateChangedCb, pDev /* pvUser */);
1794 if ( err != noErr
1795 && err != kAudioHardwareBadObjectError)
1796 {
1797 LogRel(("CoreAudio: Failed to remove the device alive listener (%RI32)\n", err));
1798 }
1799 }
1800
1801 return VINF_SUCCESS;
1802}
1803
1804/* Callback for getting notified when some of the properties of an audio device have changed. */
1805static DECLCALLBACK(OSStatus) coreAudioDevPropChgCb(AudioObjectID propertyID,
1806 UInt32 cAddresses,
1807 const AudioObjectPropertyAddress properties[],
1808 void *pvUser)
1809{
1810 RT_NOREF(cAddresses, properties, pvUser);
1811
1812 PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)pvUser;
1813 AssertPtr(pDev);
1814
1815 LogFlowFunc(("propertyID=%u, nAddresses=%u, pDev=%p\n", propertyID, cAddresses, pDev));
1816
1817 switch (propertyID)
1818 {
1819#ifdef DEBUG
1820 case kAudioDeviceProcessorOverload:
1821 {
1822 LogFunc(("Processor overload detected!\n"));
1823 break;
1824 }
1825#endif /* DEBUG */
1826 case kAudioDevicePropertyNominalSampleRate:
1827 {
1828#ifndef VBOX_WITH_AUDIO_CALLBACKS
1829 int rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_REINIT);
1830 AssertRC(rc2);
1831#else
1832 RT_NOREF(pDev);
1833#endif
1834 break;
1835 }
1836
1837 default:
1838 /* Just skip. */
1839 break;
1840 }
1841
1842 return noErr;
1843}
1844
1845/**
1846 * Enumerates all available host audio devices internally.
1847 *
1848 * @returns IPRT status code.
1849 * @param pThis Host audio driver instance.
1850 */
1851static int coreAudioEnumerateDevices(PDRVHOSTCOREAUDIO pThis)
1852{
1853 LogFlowFuncEnter();
1854
1855 /*
1856 * Unregister old default devices, if any.
1857 */
1858 if (pThis->pDefaultDevIn)
1859 {
1860 coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevIn);
1861 pThis->pDefaultDevIn = NULL;
1862 }
1863
1864 if (pThis->pDefaultDevOut)
1865 {
1866 coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevOut);
1867 pThis->pDefaultDevOut = NULL;
1868 }
1869
1870 /* Remove old / stale device entries. */
1871 DrvAudioHlpDeviceEnumFree(&pThis->Devices);
1872
1873 /* Enumerate all devices internally. */
1874 int rc = coreAudioDevicesEnumerateAll(pThis, &pThis->Devices);
1875 if (RT_SUCCESS(rc))
1876 {
1877 /*
1878 * Default input device.
1879 */
1880 pThis->pDefaultDevIn = DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_IN);
1881 if (pThis->pDefaultDevIn)
1882 {
1883 LogRel2(("CoreAudio: Default capturing device is '%s'\n", pThis->pDefaultDevIn->szName));
1884
1885#ifdef DEBUG
1886 PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pThis->pDefaultDevIn->pvData;
1887 AssertPtr(pDevData);
1888 LogFunc(("pDefaultDevIn=%p, ID=%RU32\n", pThis->pDefaultDevIn, pDevData->deviceID));
1889#endif
1890 rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevIn);
1891 }
1892 else
1893 LogRel2(("CoreAudio: No default capturing device found\n"));
1894
1895 /*
1896 * Default output device.
1897 */
1898 pThis->pDefaultDevOut = DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_OUT);
1899 if (pThis->pDefaultDevOut)
1900 {
1901 LogRel2(("CoreAudio: Default playback device is '%s'\n", pThis->pDefaultDevOut->szName));
1902
1903#ifdef DEBUG
1904 PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pThis->pDefaultDevOut->pvData;
1905 AssertPtr(pDevData);
1906 LogFunc(("pDefaultDevOut=%p, ID=%RU32\n", pThis->pDefaultDevOut, pDevData->deviceID));
1907#endif
1908 rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevOut);
1909 }
1910 else
1911 LogRel2(("CoreAudio: No default playback device found\n"));
1912 }
1913
1914 LogFunc(("Returning %Rrc\n", rc));
1915 return rc;
1916}
1917
1918/**
1919 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1920 */
1921static DECLCALLBACK(int) drvHostCoreAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1922 void *pvBuf, uint32_t uBufSize, uint32_t *puRead)
1923{
1924 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1925 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1926 /* puRead is optional. */
1927
1928 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
1929 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
1930
1931#ifndef VBOX_WITH_AUDIO_CALLBACKS
1932 /* Check if the audio device should be reinitialized. If so do it. */
1933 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT)
1934 {
1935 /* For now re just re-initialize with the current input device. */
1936 if (pThis->pDefaultDevIn)
1937 {
1938 int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevIn);
1939 if (RT_FAILURE(rc2))
1940 return VERR_NOT_AVAILABLE;
1941 }
1942 else
1943 return VERR_NOT_AVAILABLE;
1944 }
1945#else
1946 RT_NOREF(pThis);
1947#endif
1948
1949 if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
1950 {
1951 if (puRead)
1952 *puRead = 0;
1953 return VINF_SUCCESS;
1954 }
1955
1956 int rc = VINF_SUCCESS;
1957
1958 uint32_t cbReadTotal = 0;
1959
1960 rc = RTCritSectEnter(&pCAStream->CritSect);
1961 AssertRC(rc);
1962
1963 do
1964 {
1965 size_t cbToWrite = RT_MIN(uBufSize, RTCircBufUsed(pCAStream->pCircBuf));
1966
1967 uint8_t *pvChunk;
1968 size_t cbChunk;
1969
1970 Log3Func(("cbToWrite=%zu/%zu\n", cbToWrite, RTCircBufSize(pCAStream->pCircBuf)));
1971
1972 while (cbToWrite)
1973 {
1974 /* Try to acquire the necessary block from the ring buffer. */
1975 RTCircBufAcquireReadBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk);
1976 if (cbChunk)
1977 memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk);
1978
1979 /* Release the read buffer, so it could be used for new data. */
1980 RTCircBufReleaseReadBlock(pCAStream->pCircBuf, cbChunk);
1981
1982 if (RT_FAILURE(rc))
1983 break;
1984
1985 Assert(cbToWrite >= cbChunk);
1986 cbToWrite -= cbChunk;
1987
1988 cbReadTotal += cbChunk;
1989 }
1990 }
1991 while (0);
1992
1993 int rc2 = RTCritSectLeave(&pCAStream->CritSect);
1994 AssertRC(rc2);
1995
1996 if (RT_SUCCESS(rc))
1997 {
1998 if (puRead)
1999 *puRead = cbReadTotal;
2000 }
2001
2002 return rc;
2003}
2004
2005/**
2006 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2007 */
2008static DECLCALLBACK(int) drvHostCoreAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2009 const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten)
2010{
2011 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2012 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2013
2014#ifndef VBOX_WITH_AUDIO_CALLBACKS
2015 /* Check if the audio device should be reinitialized. If so do it. */
2016 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT)
2017 {
2018 if (pThis->pDefaultDevOut)
2019 {
2020 /* For now re just re-initialize with the current output device. */
2021 int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevOut);
2022 if (RT_FAILURE(rc2))
2023 return VERR_NOT_AVAILABLE;
2024 }
2025 else
2026 return VERR_NOT_AVAILABLE;
2027 }
2028#else
2029 RT_NOREF(pThis);
2030#endif
2031
2032 if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
2033 {
2034 if (puWritten)
2035 *puWritten = 0;
2036 return VINF_SUCCESS;
2037 }
2038
2039 uint32_t cbWrittenTotal = 0;
2040
2041 int rc = VINF_SUCCESS;
2042
2043 rc = RTCritSectEnter(&pCAStream->CritSect);
2044 AssertRC(rc);
2045
2046 size_t cbToWrite = RT_MIN(uBufSize, RTCircBufFree(pCAStream->pCircBuf));
2047 Log3Func(("cbToWrite=%zu\n", cbToWrite));
2048
2049 uint8_t *pvChunk;
2050 size_t cbChunk;
2051
2052 while (cbToWrite)
2053 {
2054 /* Try to acquire the necessary space from the ring buffer. */
2055 RTCircBufAcquireWriteBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk);
2056 if (!cbChunk)
2057 {
2058 RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk);
2059 break;
2060 }
2061
2062 Assert(cbChunk <= cbToWrite);
2063 Assert(cbWrittenTotal + cbChunk <= uBufSize);
2064
2065 memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk);
2066
2067#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2068 RTFILE fh;
2069 rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm",
2070 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
2071 if (RT_SUCCESS(rc))
2072 {
2073 RTFileWrite(fh, pvChunk, cbChunk, NULL);
2074 RTFileClose(fh);
2075 }
2076 else
2077 AssertFailed();
2078#endif
2079
2080 /* Release the ring buffer, so the read thread could start reading this data. */
2081 RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk);
2082
2083 if (RT_FAILURE(rc))
2084 break;
2085
2086 Assert(cbToWrite >= cbChunk);
2087 cbToWrite -= cbChunk;
2088
2089 cbWrittenTotal += cbChunk;
2090 }
2091
2092 if ( RT_SUCCESS(rc)
2093 && pCAStream->fRun
2094 && !pCAStream->fIsRunning)
2095 {
2096 rc = coreAudioStreamInvalidateQueue(pCAStream);
2097 if (RT_SUCCESS(rc))
2098 {
2099 AudioQueueStart(pCAStream->audioQueue, NULL);
2100 pCAStream->fRun = false;
2101 pCAStream->fIsRunning = true;
2102 }
2103 }
2104
2105 int rc2 = RTCritSectLeave(&pCAStream->CritSect);
2106 AssertRC(rc2);
2107
2108 if (RT_SUCCESS(rc))
2109 {
2110 if (puWritten)
2111 *puWritten = cbWrittenTotal;
2112 }
2113
2114 return rc;
2115}
2116
2117static int coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2118{
2119 RT_NOREF(pThis);
2120
2121 uint32_t enmStatus = ASMAtomicReadU32(&pCAStream->enmStatus);
2122
2123 LogFlowFunc(("enmStreamCmd=%RU32, enmStatus=%RU32\n", enmStreamCmd, enmStatus));
2124
2125 if (!( enmStatus == COREAUDIOSTATUS_INIT
2126#ifndef VBOX_WITH_AUDIO_CALLBACKS
2127 || enmStatus == COREAUDIOSTATUS_REINIT
2128#endif
2129 ))
2130 {
2131 return VINF_SUCCESS;
2132 }
2133
2134 if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */
2135 return VINF_SUCCESS;
2136
2137 int rc = VINF_SUCCESS;
2138
2139 switch (enmStreamCmd)
2140 {
2141 case PDMAUDIOSTREAMCMD_ENABLE:
2142 case PDMAUDIOSTREAMCMD_RESUME:
2143 {
2144 LogFunc(("Queue enable\n"));
2145 if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN)
2146 {
2147 rc = coreAudioStreamInvalidateQueue(pCAStream);
2148 if (RT_SUCCESS(rc))
2149 {
2150 /* Start the audio queue immediately. */
2151 AudioQueueStart(pCAStream->audioQueue, NULL);
2152 }
2153 }
2154 else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT)
2155 {
2156 /* Touch the run flag to start the audio queue as soon as
2157 * we have anough data to actually play something. */
2158 ASMAtomicXchgBool(&pCAStream->fRun, true);
2159 }
2160 break;
2161 }
2162
2163 case PDMAUDIOSTREAMCMD_DISABLE:
2164 {
2165 LogFunc(("Queue disable\n"));
2166 AudioQueueStop(pCAStream->audioQueue, 1 /* Immediately */);
2167 ASMAtomicXchgBool(&pCAStream->fRun, false);
2168 ASMAtomicXchgBool(&pCAStream->fIsRunning, false);
2169 break;
2170 }
2171 case PDMAUDIOSTREAMCMD_PAUSE:
2172 {
2173 LogFunc(("Queue pause\n"));
2174 AudioQueuePause(pCAStream->audioQueue);
2175 ASMAtomicXchgBool(&pCAStream->fIsRunning, false);
2176 break;
2177 }
2178
2179 default:
2180 rc = VERR_NOT_SUPPORTED;
2181 break;
2182 }
2183
2184 LogFlowFuncLeaveRC(rc);
2185 return rc;
2186}
2187
2188
2189/**
2190 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
2191 */
2192static DECLCALLBACK(int) drvHostCoreAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
2193{
2194 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2195 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
2196
2197 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2198
2199 RT_BZERO(pBackendCfg, sizeof(PDMAUDIOBACKENDCFG));
2200
2201 RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio");
2202
2203 pBackendCfg->cbStreamIn = sizeof(COREAUDIOSTREAM);
2204 pBackendCfg->cbStreamOut = sizeof(COREAUDIOSTREAM);
2205
2206 /* For Core Audio we provide one stream per device for now. */
2207 pBackendCfg->cMaxStreamsIn = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_IN);
2208 pBackendCfg->cMaxStreamsOut = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_OUT);
2209
2210 LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS));
2211 return VINF_SUCCESS;
2212}
2213
2214
2215/**
2216 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
2217 */
2218static DECLCALLBACK(int) drvHostCoreAudioHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIODEVICEENUM pDeviceEnum)
2219{
2220 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2221 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
2222
2223 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2224
2225 int rc = RTCritSectEnter(&pThis->CritSect);
2226 if (RT_SUCCESS(rc))
2227 {
2228 rc = coreAudioEnumerateDevices(pThis);
2229 if (RT_SUCCESS(rc))
2230 {
2231 if (pDeviceEnum)
2232 {
2233 rc = DrvAudioHlpDeviceEnumInit(pDeviceEnum);
2234 if (RT_SUCCESS(rc))
2235 rc = DrvAudioHlpDeviceEnumCopy(pDeviceEnum, &pThis->Devices);
2236
2237 if (RT_FAILURE(rc))
2238 DrvAudioHlpDeviceEnumFree(pDeviceEnum);
2239 }
2240 }
2241
2242 int rc2 = RTCritSectLeave(&pThis->CritSect);
2243 AssertRC(rc2);
2244 }
2245
2246 LogFlowFunc(("Returning %Rrc\n", rc));
2247 return rc;
2248}
2249
2250
2251#ifdef VBOX_WITH_AUDIO_CALLBACKS
2252/**
2253 * @interface_method_impl{PDMIHOSTAUDIO,pfnSetCallback}
2254 */
2255static DECLCALLBACK(int) drvHostCoreAudioHA_SetCallback(PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback)
2256{
2257 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2258 /* pfnCallback will be handled below. */
2259
2260 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2261
2262 int rc = RTCritSectEnter(&pThis->CritSect);
2263 if (RT_SUCCESS(rc))
2264 {
2265 LogFunc(("pfnCallback=%p\n", pfnCallback));
2266
2267 if (pfnCallback) /* Register. */
2268 {
2269 Assert(pThis->pfnCallback == NULL);
2270 pThis->pfnCallback = pfnCallback;
2271 }
2272 else /* Unregister. */
2273 {
2274 if (pThis->pfnCallback)
2275 pThis->pfnCallback = NULL;
2276 }
2277
2278 int rc2 = RTCritSectLeave(&pThis->CritSect);
2279 AssertRC(rc2);
2280 }
2281
2282 return rc;
2283}
2284#endif
2285
2286
2287/**
2288 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
2289 */
2290static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostCoreAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
2291{
2292 RT_NOREF(pInterface, enmDir);
2293 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
2294
2295 return PDMAUDIOBACKENDSTS_RUNNING;
2296}
2297
2298
2299/**
2300 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
2301 */
2302static DECLCALLBACK(int) drvHostCoreAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2303 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
2304{
2305 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2306 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2307 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
2308 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
2309
2310 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2311 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2312
2313 int rc = RTCritSectInit(&pCAStream->CritSect);
2314 if (RT_FAILURE(rc))
2315 return rc;
2316
2317 pCAStream->hThread = NIL_RTTHREAD;
2318 pCAStream->fRun = false;
2319 pCAStream->fIsRunning = false;
2320 pCAStream->fShutdown = false;
2321
2322 /* Input or output device? */
2323 bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN;
2324
2325 /* For now, just use the default device available. */
2326 PPDMAUDIODEVICE pDev = fIn ? pThis->pDefaultDevIn : pThis->pDefaultDevOut;
2327
2328 LogFunc(("pStream=%p, pCfgReq=%p, pCfgAcq=%p, fIn=%RTbool, pDev=%p\n", pStream, pCfgReq, pCfgAcq, fIn, pDev));
2329
2330 if (pDev) /* (Default) device available? */
2331 {
2332 /* Sanity. */
2333 AssertPtr(pDev->pvData);
2334 Assert(pDev->cbData);
2335
2336 /* Init the Core Audio stream. */
2337 rc = coreAudioStreamInit(pCAStream, pThis, pDev);
2338 if (RT_SUCCESS(rc))
2339 {
2340 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_INIT);
2341
2342 rc = coreAudioStreamInitQueue(pCAStream, pCfgReq, pCfgAcq);
2343 if (RT_SUCCESS(rc))
2344 {
2345 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_INIT);
2346 }
2347 else
2348 {
2349 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT);
2350
2351 int rc2 = coreAudioStreamUninit(pCAStream);
2352 AssertRC(rc2);
2353
2354 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT);
2355 }
2356 }
2357 }
2358 else
2359 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
2360
2361 LogFunc(("Returning %Rrc\n", rc));
2362 return rc;
2363}
2364
2365
2366/**
2367 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
2368 */
2369static DECLCALLBACK(int) drvHostCoreAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2370{
2371 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2372 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2373
2374 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2375
2376 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2377
2378 uint32_t status = ASMAtomicReadU32(&pCAStream->enmStatus);
2379 if (!( status == COREAUDIOSTATUS_INIT
2380#ifndef VBOX_WITH_AUDIO_CALLBACKS
2381 || status == COREAUDIOSTATUS_REINIT
2382#endif
2383 ))
2384 {
2385 return VINF_SUCCESS;
2386 }
2387
2388 if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */
2389 return VINF_SUCCESS;
2390
2391 int rc = coreAudioStreamControl(pThis, pCAStream, PDMAUDIOSTREAMCMD_DISABLE);
2392 if (RT_SUCCESS(rc))
2393 {
2394 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT);
2395
2396 rc = coreAudioStreamUninit(pCAStream);
2397
2398 if (RT_SUCCESS(rc))
2399 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT);
2400 }
2401
2402 if (RT_SUCCESS(rc))
2403 {
2404 if (RTCritSectIsInitialized(&pCAStream->CritSect))
2405 RTCritSectDelete(&pCAStream->CritSect);
2406 }
2407
2408 LogFunc(("rc=%Rrc\n", rc));
2409 return rc;
2410}
2411
2412
2413/**
2414 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
2415 */
2416static DECLCALLBACK(int) drvHostCoreAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface,
2417 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2418{
2419 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2420 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2421
2422 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2423 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2424
2425 return coreAudioStreamControl(pThis, pCAStream, enmStreamCmd);
2426}
2427
2428
2429/**
2430 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2431 */
2432static DECLCALLBACK(uint32_t) drvHostCoreAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2433{
2434 RT_NOREF(pInterface);
2435 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2436
2437 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2438
2439 if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
2440 return 0;
2441
2442 AssertPtr(pCAStream->pCfg);
2443 AssertPtr(pCAStream->pCircBuf);
2444
2445 switch (pCAStream->pCfg->enmDir)
2446 {
2447 case PDMAUDIODIR_IN:
2448 return (uint32_t)RTCircBufUsed(pCAStream->pCircBuf);
2449
2450 case PDMAUDIODIR_OUT:
2451 default:
2452 AssertFailed();
2453 break;
2454 }
2455
2456 return 0;
2457}
2458
2459
2460/**
2461 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2462 */
2463static DECLCALLBACK(uint32_t) drvHostCoreAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2464{
2465 RT_NOREF(pInterface);
2466 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2467
2468 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2469
2470 uint32_t cbWritable = 0;
2471
2472 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT)
2473 {
2474 AssertPtr(pCAStream->pCfg);
2475 AssertPtr(pCAStream->pCircBuf);
2476
2477 switch (pCAStream->pCfg->enmDir)
2478 {
2479 case PDMAUDIODIR_OUT:
2480 cbWritable = (uint32_t)RTCircBufFree(pCAStream->pCircBuf);
2481 break;
2482
2483 default:
2484 break;
2485 }
2486 }
2487
2488 LogFlowFunc(("cbWritable=%RU32\n", cbWritable));
2489 return cbWritable;
2490}
2491
2492
2493/**
2494 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
2495 */
2496static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostCoreAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2497{
2498 RT_NOREF(pInterface);
2499 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2500
2501 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2502
2503 PDMAUDIOSTREAMSTS fStrmStatus = PDMAUDIOSTREAMSTS_FLAGS_NONE;
2504
2505 if (pCAStream->pCfg) /* Configured? */
2506 {
2507 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT)
2508 fStrmStatus |= PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED;
2509 }
2510
2511 return fStrmStatus;
2512}
2513
2514
2515/**
2516 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
2517 */
2518static DECLCALLBACK(int) drvHostCoreAudioHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2519{
2520 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2521 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2522
2523 RT_NOREF(pInterface, pStream);
2524
2525 /* Nothing to do here for Core Audio. */
2526 return VINF_SUCCESS;
2527}
2528
2529
2530/**
2531 * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
2532 */
2533static DECLCALLBACK(int) drvHostCoreAudioHA_Init(PPDMIHOSTAUDIO pInterface)
2534{
2535 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2536
2537 int rc = DrvAudioHlpDeviceEnumInit(&pThis->Devices);
2538 if (RT_SUCCESS(rc))
2539 {
2540 /* Do the first (initial) internal device enumeration. */
2541 rc = coreAudioEnumerateDevices(pThis);
2542 }
2543
2544 if (RT_SUCCESS(rc))
2545 {
2546 /* Register system callbacks. */
2547 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
2548 kAudioObjectPropertyElementMaster };
2549
2550 OSStatus err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr,
2551 coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
2552 if ( err != noErr
2553 && err != kAudioHardwareIllegalOperationError)
2554 {
2555 LogRel(("CoreAudio: Failed to add the input default device changed listener (%RI32)\n", err));
2556 }
2557
2558 propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2559 err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr,
2560 coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
2561 if ( err != noErr
2562 && err != kAudioHardwareIllegalOperationError)
2563 {
2564 LogRel(("CoreAudio: Failed to add the output default device changed listener (%RI32)\n", err));
2565 }
2566 }
2567
2568 LogFlowFunc(("Returning %Rrc\n", rc));
2569 return rc;
2570}
2571
2572
2573/**
2574 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
2575 */
2576static DECLCALLBACK(void) drvHostCoreAudioHA_Shutdown(PPDMIHOSTAUDIO pInterface)
2577{
2578 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2579
2580 /*
2581 * Unregister system callbacks.
2582 */
2583 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
2584 kAudioObjectPropertyElementMaster };
2585
2586 OSStatus err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr,
2587 coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
2588 if ( err != noErr
2589 && err != kAudioHardwareBadObjectError)
2590 {
2591 LogRel(("CoreAudio: Failed to remove the default input device changed listener (%RI32)\n", err));
2592 }
2593
2594 propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2595 err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr,
2596 coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
2597 if ( err != noErr
2598 && err != kAudioHardwareBadObjectError)
2599 {
2600 LogRel(("CoreAudio: Failed to remove the default output device changed listener (%RI32)\n", err));
2601 }
2602
2603 LogFlowFuncEnter();
2604}
2605
2606
2607/**
2608 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2609 */
2610static DECLCALLBACK(void *) drvHostCoreAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2611{
2612 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2613 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2614
2615 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2616 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2617
2618 return NULL;
2619}
2620
2621
2622/**
2623 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2624 * Construct a Core Audio driver instance.}
2625 */
2626static DECLCALLBACK(int) drvHostCoreAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2627{
2628 RT_NOREF(pCfg, fFlags);
2629 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2630 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2631 LogRel(("Audio: Initializing Core Audio driver\n"));
2632
2633 /*
2634 * Init the static parts.
2635 */
2636 pThis->pDrvIns = pDrvIns;
2637 /* IBase */
2638 pDrvIns->IBase.pfnQueryInterface = drvHostCoreAudioQueryInterface;
2639 /* IHostAudio */
2640 pThis->IHostAudio.pfnInit = drvHostCoreAudioHA_Init;
2641 pThis->IHostAudio.pfnShutdown = drvHostCoreAudioHA_Shutdown;
2642 pThis->IHostAudio.pfnGetConfig = drvHostCoreAudioHA_GetConfig;
2643 pThis->IHostAudio.pfnGetStatus = drvHostCoreAudioHA_GetStatus;
2644 pThis->IHostAudio.pfnStreamCreate = drvHostCoreAudioHA_StreamCreate;
2645 pThis->IHostAudio.pfnStreamDestroy = drvHostCoreAudioHA_StreamDestroy;
2646 pThis->IHostAudio.pfnStreamControl = drvHostCoreAudioHA_StreamControl;
2647 pThis->IHostAudio.pfnStreamGetReadable = drvHostCoreAudioHA_StreamGetReadable;
2648 pThis->IHostAudio.pfnStreamGetWritable = drvHostCoreAudioHA_StreamGetWritable;
2649 pThis->IHostAudio.pfnStreamGetStatus = drvHostCoreAudioHA_StreamGetStatus;
2650 pThis->IHostAudio.pfnStreamIterate = drvHostCoreAudioHA_StreamIterate;
2651 pThis->IHostAudio.pfnStreamPlay = drvHostCoreAudioHA_StreamPlay;
2652 pThis->IHostAudio.pfnStreamCapture = drvHostCoreAudioHA_StreamCapture;
2653#ifdef VBOX_WITH_AUDIO_CALLBACKS
2654 pThis->IHostAudio.pfnSetCallback = drvHostCoreAudioHA_SetCallback;
2655 pThis->pfnCallback = NULL;
2656#else
2657 pThis->IHostAudio.pfnSetCallback = NULL;
2658#endif
2659 pThis->IHostAudio.pfnGetDevices = drvHostCoreAudioHA_GetDevices;
2660 pThis->IHostAudio.pfnStreamGetPending = NULL;
2661 pThis->IHostAudio.pfnStreamPlayBegin = NULL;
2662 pThis->IHostAudio.pfnStreamPlayEnd = NULL;
2663 pThis->IHostAudio.pfnStreamCaptureBegin = NULL;
2664 pThis->IHostAudio.pfnStreamCaptureEnd = NULL;
2665
2666 int rc = RTCritSectInit(&pThis->CritSect);
2667 AssertRCReturn(rc, rc);
2668
2669#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2670 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm");
2671 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm");
2672#endif
2673
2674 LogFlowFuncLeaveRC(rc);
2675 return rc;
2676}
2677
2678
2679/**
2680 * @callback_method_impl{FNPDMDRVDESTRUCT}
2681 */
2682static DECLCALLBACK(void) drvHostCoreAudioDestruct(PPDMDRVINS pDrvIns)
2683{
2684 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2685 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2686
2687 int rc2 = RTCritSectDelete(&pThis->CritSect);
2688 AssertRC(rc2);
2689
2690 LogFlowFuncLeaveRC(rc2);
2691}
2692
2693
2694/**
2695 * Char driver registration record.
2696 */
2697const PDMDRVREG g_DrvHostCoreAudio =
2698{
2699 /* u32Version */
2700 PDM_DRVREG_VERSION,
2701 /* szName */
2702 "CoreAudio",
2703 /* szRCMod */
2704 "",
2705 /* szR0Mod */
2706 "",
2707 /* pszDescription */
2708 "Core Audio host driver",
2709 /* fFlags */
2710 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2711 /* fClass. */
2712 PDM_DRVREG_CLASS_AUDIO,
2713 /* cMaxInstances */
2714 ~0U,
2715 /* cbInstance */
2716 sizeof(DRVHOSTCOREAUDIO),
2717 /* pfnConstruct */
2718 drvHostCoreAudioConstruct,
2719 /* pfnDestruct */
2720 drvHostCoreAudioDestruct,
2721 /* pfnRelocate */
2722 NULL,
2723 /* pfnIOCtl */
2724 NULL,
2725 /* pfnPowerOn */
2726 NULL,
2727 /* pfnReset */
2728 NULL,
2729 /* pfnSuspend */
2730 NULL,
2731 /* pfnResume */
2732 NULL,
2733 /* pfnAttach */
2734 NULL,
2735 /* pfnDetach */
2736 NULL,
2737 /* pfnPowerOff */
2738 NULL,
2739 /* pfnSoftReset */
2740 NULL,
2741 /* u32EndVersion */
2742 PDM_DRVREG_VERSION
2743};
2744
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette