VirtualBox

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

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

Audio: More prep work to move the audio device enumeration code to PDM. bugref:9890

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