VirtualBox

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

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

Audio: Preparing to move some of the DrvAudio.h stuff into PDM. bugref:9890

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

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