VirtualBox

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

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

Audio: Converted the core audio code to extending PDMAUDIODEVICE rather than the pvData scheme. Eliminated PDMAUDIODEVICE::pvData and added a magic. Untested. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 92.6 KB
Line 
1/* $Id: DrvHostCoreAudio.cpp 88033 2021-03-09 01:31:33Z 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 DEBUG
684 LogFunc(("Devices for pDevEnm=%p, enmUsage=%RU32:\n", pDevEnm, enmUsage));
685 DrvAudioHlpDeviceEnumPrint("Core Audio", pDevEnm);
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 DEBUG
852 if (RT_SUCCESS(rc))
853 DrvAudioHlpDeviceEnumPrint("Core Audio (Final)", pEnmDst);
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.cbData == 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 if (pDev && pDev->Core.cbData) /* paranoia or actually needed? */
1683 deviceID = pDev->deviceID;
1684
1685 if (deviceID != kAudioDeviceUnknown)
1686 {
1687 LogFunc(("deviceID=%RU32\n", deviceID));
1688
1689 /*
1690 * Register device callbacks.
1691 */
1692 AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
1693 kAudioObjectPropertyElementMaster };
1694 OSStatus err = AudioObjectAddPropertyListener(deviceID, &propAdr,
1695 coreAudioDeviceStateChangedCb, pDev /* pvUser */);
1696 if ( err != noErr
1697 && err != kAudioHardwareIllegalOperationError)
1698 {
1699 LogRel(("CoreAudio: Failed to add the recording device state changed listener (%RI32)\n", err));
1700 }
1701
1702 propAdr.mSelector = kAudioDeviceProcessorOverload;
1703 propAdr.mScope = kAudioUnitScope_Global;
1704 err = AudioObjectAddPropertyListener(deviceID, &propAdr,
1705 coreAudioDevPropChgCb, pDev /* pvUser */);
1706 if (err != noErr)
1707 LogRel(("CoreAudio: Failed to register processor overload listener (%RI32)\n", err));
1708
1709 propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
1710 propAdr.mScope = kAudioUnitScope_Global;
1711 err = AudioObjectAddPropertyListener(deviceID, &propAdr,
1712 coreAudioDevPropChgCb, pDev /* pvUser */);
1713 if (err != noErr)
1714 LogRel(("CoreAudio: Failed to register sample rate changed listener (%RI32)\n", err));
1715 }
1716
1717 return VINF_SUCCESS;
1718}
1719
1720/**
1721 * Unregisters all formerly registered callbacks of a Core Audio device again.
1722 *
1723 * @return IPRT status code.
1724 * @param pThis Host audio driver instance.
1725 * @param pDev Audio device to use for the registered callbacks.
1726 */
1727static int coreAudioDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PCOREAUDIODEVICEDATA pDev)
1728{
1729 RT_NOREF(pThis);
1730
1731 AudioDeviceID deviceID = kAudioDeviceUnknown;
1732 if (pDev && pDev->Core.cbData) /* paranoia or actually needed? */
1733 deviceID = pDev->deviceID;
1734
1735 if (deviceID != kAudioDeviceUnknown)
1736 {
1737 LogFunc(("deviceID=%RU32\n", deviceID));
1738
1739 /*
1740 * Unregister per-device callbacks.
1741 */
1742 AudioObjectPropertyAddress propAdr = { kAudioDeviceProcessorOverload, kAudioObjectPropertyScopeGlobal,
1743 kAudioObjectPropertyElementMaster };
1744 OSStatus err = AudioObjectRemovePropertyListener(deviceID, &propAdr,
1745 coreAudioDevPropChgCb, pDev /* pvUser */);
1746 if ( err != noErr
1747 && err != kAudioHardwareBadObjectError)
1748 {
1749 LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%RI32)\n", err));
1750 }
1751
1752 propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
1753 err = AudioObjectRemovePropertyListener(deviceID, &propAdr,
1754 coreAudioDevPropChgCb, pDev /* pvUser */);
1755 if ( err != noErr
1756 && err != kAudioHardwareBadObjectError)
1757 {
1758 LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%RI32)\n", err));
1759 }
1760
1761 propAdr.mSelector = kAudioDevicePropertyDeviceIsAlive;
1762 err = AudioObjectRemovePropertyListener(deviceID, &propAdr,
1763 coreAudioDeviceStateChangedCb, pDev /* pvUser */);
1764 if ( err != noErr
1765 && err != kAudioHardwareBadObjectError)
1766 {
1767 LogRel(("CoreAudio: Failed to remove the device alive listener (%RI32)\n", err));
1768 }
1769 }
1770
1771 return VINF_SUCCESS;
1772}
1773
1774/* Callback for getting notified when some of the properties of an audio device have changed. */
1775static DECLCALLBACK(OSStatus) coreAudioDevPropChgCb(AudioObjectID propertyID,
1776 UInt32 cAddresses,
1777 const AudioObjectPropertyAddress properties[],
1778 void *pvUser)
1779{
1780 RT_NOREF(cAddresses, properties, pvUser);
1781
1782 PCOREAUDIODEVICEDATA pDev = (PCOREAUDIODEVICEDATA)pvUser;
1783 AssertPtr(pDev);
1784
1785 LogFlowFunc(("propertyID=%u, nAddresses=%u, pDev=%p\n", propertyID, cAddresses, pDev));
1786
1787 switch (propertyID)
1788 {
1789#ifdef DEBUG
1790 case kAudioDeviceProcessorOverload:
1791 {
1792 LogFunc(("Processor overload detected!\n"));
1793 break;
1794 }
1795#endif /* DEBUG */
1796 case kAudioDevicePropertyNominalSampleRate:
1797 {
1798#ifndef VBOX_WITH_AUDIO_CALLBACKS
1799 int rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_REINIT);
1800 AssertRC(rc2);
1801#else
1802 RT_NOREF(pDev);
1803#endif
1804 break;
1805 }
1806
1807 default:
1808 /* Just skip. */
1809 break;
1810 }
1811
1812 return noErr;
1813}
1814
1815/**
1816 * Enumerates all available host audio devices internally.
1817 *
1818 * @returns IPRT status code.
1819 * @param pThis Host audio driver instance.
1820 */
1821static int coreAudioEnumerateDevices(PDRVHOSTCOREAUDIO pThis)
1822{
1823 LogFlowFuncEnter();
1824
1825 /*
1826 * Unregister old default devices, if any.
1827 */
1828 if (pThis->pDefaultDevIn)
1829 {
1830 coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevIn);
1831 pThis->pDefaultDevIn = NULL;
1832 }
1833
1834 if (pThis->pDefaultDevOut)
1835 {
1836 coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevOut);
1837 pThis->pDefaultDevOut = NULL;
1838 }
1839
1840 /* Remove old / stale device entries. */
1841 DrvAudioHlpDeviceEnumFree(&pThis->Devices);
1842
1843 /* Enumerate all devices internally. */
1844 int rc = coreAudioDevicesEnumerateAll(pThis, &pThis->Devices);
1845 if (RT_SUCCESS(rc))
1846 {
1847 /*
1848 * Default input device.
1849 */
1850 pThis->pDefaultDevIn = (PCOREAUDIODEVICEDATA)DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_IN);
1851 if (pThis->pDefaultDevIn)
1852 {
1853 LogRel2(("CoreAudio: Default capturing device is '%s'\n", pThis->pDefaultDevIn->Core.szName));
1854 LogFunc(("pDefaultDevIn=%p, ID=%RU32\n", pThis->pDefaultDevIn, pThis->pDefaultDevIn->deviceID));
1855 rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevIn);
1856 }
1857 else
1858 LogRel2(("CoreAudio: No default capturing device found\n"));
1859
1860 /*
1861 * Default output device.
1862 */
1863 pThis->pDefaultDevOut = (PCOREAUDIODEVICEDATA)DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_OUT);
1864 if (pThis->pDefaultDevOut)
1865 {
1866 LogRel2(("CoreAudio: Default playback device is '%s'\n", pThis->pDefaultDevOut->Core.szName));
1867 LogFunc(("pDefaultDevOut=%p, ID=%RU32\n", pThis->pDefaultDevOut, pThis->pDefaultDevOut->deviceID));
1868 rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevOut);
1869 }
1870 else
1871 LogRel2(("CoreAudio: No default playback device found\n"));
1872 }
1873
1874 LogFunc(("Returning %Rrc\n", rc));
1875 return rc;
1876}
1877
1878/**
1879 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1880 */
1881static DECLCALLBACK(int) drvHostCoreAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1882 void *pvBuf, uint32_t uBufSize, uint32_t *puRead)
1883{
1884 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1885 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1886 /* puRead is optional. */
1887
1888 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
1889 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
1890
1891#ifndef VBOX_WITH_AUDIO_CALLBACKS
1892 /* Check if the audio device should be reinitialized. If so do it. */
1893 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT)
1894 {
1895 /* For now re just re-initialize with the current input device. */
1896 if (pThis->pDefaultDevIn)
1897 {
1898 int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevIn);
1899 if (RT_FAILURE(rc2))
1900 return VERR_NOT_AVAILABLE;
1901 }
1902 else
1903 return VERR_NOT_AVAILABLE;
1904 }
1905#else
1906 RT_NOREF(pThis);
1907#endif
1908
1909 if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
1910 {
1911 if (puRead)
1912 *puRead = 0;
1913 return VINF_SUCCESS;
1914 }
1915
1916 int rc = VINF_SUCCESS;
1917
1918 uint32_t cbReadTotal = 0;
1919
1920 rc = RTCritSectEnter(&pCAStream->CritSect);
1921 AssertRC(rc);
1922
1923 do
1924 {
1925 size_t cbToWrite = RT_MIN(uBufSize, RTCircBufUsed(pCAStream->pCircBuf));
1926
1927 uint8_t *pvChunk;
1928 size_t cbChunk;
1929
1930 Log3Func(("cbToWrite=%zu/%zu\n", cbToWrite, RTCircBufSize(pCAStream->pCircBuf)));
1931
1932 while (cbToWrite)
1933 {
1934 /* Try to acquire the necessary block from the ring buffer. */
1935 RTCircBufAcquireReadBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk);
1936 if (cbChunk)
1937 memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk);
1938
1939 /* Release the read buffer, so it could be used for new data. */
1940 RTCircBufReleaseReadBlock(pCAStream->pCircBuf, cbChunk);
1941
1942 if (RT_FAILURE(rc))
1943 break;
1944
1945 Assert(cbToWrite >= cbChunk);
1946 cbToWrite -= cbChunk;
1947
1948 cbReadTotal += cbChunk;
1949 }
1950 }
1951 while (0);
1952
1953 int rc2 = RTCritSectLeave(&pCAStream->CritSect);
1954 AssertRC(rc2);
1955
1956 if (RT_SUCCESS(rc))
1957 {
1958 if (puRead)
1959 *puRead = cbReadTotal;
1960 }
1961
1962 return rc;
1963}
1964
1965/**
1966 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1967 */
1968static DECLCALLBACK(int) drvHostCoreAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1969 const void *pvBuf, uint32_t uBufSize, uint32_t *puWritten)
1970{
1971 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
1972 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
1973
1974#ifndef VBOX_WITH_AUDIO_CALLBACKS
1975 /* Check if the audio device should be reinitialized. If so do it. */
1976 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT)
1977 {
1978 if (pThis->pDefaultDevOut)
1979 {
1980 /* For now re just re-initialize with the current output device. */
1981 int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevOut);
1982 if (RT_FAILURE(rc2))
1983 return VERR_NOT_AVAILABLE;
1984 }
1985 else
1986 return VERR_NOT_AVAILABLE;
1987 }
1988#else
1989 RT_NOREF(pThis);
1990#endif
1991
1992 if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
1993 {
1994 if (puWritten)
1995 *puWritten = 0;
1996 return VINF_SUCCESS;
1997 }
1998
1999 uint32_t cbWrittenTotal = 0;
2000
2001 int rc = VINF_SUCCESS;
2002
2003 rc = RTCritSectEnter(&pCAStream->CritSect);
2004 AssertRC(rc);
2005
2006 size_t cbToWrite = RT_MIN(uBufSize, RTCircBufFree(pCAStream->pCircBuf));
2007 Log3Func(("cbToWrite=%zu\n", cbToWrite));
2008
2009 uint8_t *pvChunk;
2010 size_t cbChunk;
2011
2012 while (cbToWrite)
2013 {
2014 /* Try to acquire the necessary space from the ring buffer. */
2015 RTCircBufAcquireWriteBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk);
2016 if (!cbChunk)
2017 {
2018 RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk);
2019 break;
2020 }
2021
2022 Assert(cbChunk <= cbToWrite);
2023 Assert(cbWrittenTotal + cbChunk <= uBufSize);
2024
2025 memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk);
2026
2027#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2028 RTFILE fh;
2029 rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm",
2030 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
2031 if (RT_SUCCESS(rc))
2032 {
2033 RTFileWrite(fh, pvChunk, cbChunk, NULL);
2034 RTFileClose(fh);
2035 }
2036 else
2037 AssertFailed();
2038#endif
2039
2040 /* Release the ring buffer, so the read thread could start reading this data. */
2041 RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk);
2042
2043 if (RT_FAILURE(rc))
2044 break;
2045
2046 Assert(cbToWrite >= cbChunk);
2047 cbToWrite -= cbChunk;
2048
2049 cbWrittenTotal += cbChunk;
2050 }
2051
2052 if ( RT_SUCCESS(rc)
2053 && pCAStream->fRun
2054 && !pCAStream->fIsRunning)
2055 {
2056 rc = coreAudioStreamInvalidateQueue(pCAStream);
2057 if (RT_SUCCESS(rc))
2058 {
2059 AudioQueueStart(pCAStream->audioQueue, NULL);
2060 pCAStream->fRun = false;
2061 pCAStream->fIsRunning = true;
2062 }
2063 }
2064
2065 int rc2 = RTCritSectLeave(&pCAStream->CritSect);
2066 AssertRC(rc2);
2067
2068 if (RT_SUCCESS(rc))
2069 {
2070 if (puWritten)
2071 *puWritten = cbWrittenTotal;
2072 }
2073
2074 return rc;
2075}
2076
2077static int coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2078{
2079 RT_NOREF(pThis);
2080
2081 uint32_t enmStatus = ASMAtomicReadU32(&pCAStream->enmStatus);
2082
2083 LogFlowFunc(("enmStreamCmd=%RU32, enmStatus=%RU32\n", enmStreamCmd, enmStatus));
2084
2085 if (!( enmStatus == COREAUDIOSTATUS_INIT
2086#ifndef VBOX_WITH_AUDIO_CALLBACKS
2087 || enmStatus == COREAUDIOSTATUS_REINIT
2088#endif
2089 ))
2090 {
2091 return VINF_SUCCESS;
2092 }
2093
2094 if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */
2095 return VINF_SUCCESS;
2096
2097 int rc = VINF_SUCCESS;
2098
2099 switch (enmStreamCmd)
2100 {
2101 case PDMAUDIOSTREAMCMD_ENABLE:
2102 case PDMAUDIOSTREAMCMD_RESUME:
2103 {
2104 LogFunc(("Queue enable\n"));
2105 if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN)
2106 {
2107 rc = coreAudioStreamInvalidateQueue(pCAStream);
2108 if (RT_SUCCESS(rc))
2109 {
2110 /* Start the audio queue immediately. */
2111 AudioQueueStart(pCAStream->audioQueue, NULL);
2112 }
2113 }
2114 else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT)
2115 {
2116 /* Touch the run flag to start the audio queue as soon as
2117 * we have anough data to actually play something. */
2118 ASMAtomicXchgBool(&pCAStream->fRun, true);
2119 }
2120 break;
2121 }
2122
2123 case PDMAUDIOSTREAMCMD_DISABLE:
2124 {
2125 LogFunc(("Queue disable\n"));
2126 AudioQueueStop(pCAStream->audioQueue, 1 /* Immediately */);
2127 ASMAtomicXchgBool(&pCAStream->fRun, false);
2128 ASMAtomicXchgBool(&pCAStream->fIsRunning, false);
2129 break;
2130 }
2131 case PDMAUDIOSTREAMCMD_PAUSE:
2132 {
2133 LogFunc(("Queue pause\n"));
2134 AudioQueuePause(pCAStream->audioQueue);
2135 ASMAtomicXchgBool(&pCAStream->fIsRunning, false);
2136 break;
2137 }
2138
2139 default:
2140 rc = VERR_NOT_SUPPORTED;
2141 break;
2142 }
2143
2144 LogFlowFuncLeaveRC(rc);
2145 return rc;
2146}
2147
2148
2149/**
2150 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
2151 */
2152static DECLCALLBACK(int) drvHostCoreAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
2153{
2154 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2155 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
2156
2157 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2158
2159 RT_BZERO(pBackendCfg, sizeof(PDMAUDIOBACKENDCFG));
2160
2161 RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio");
2162
2163 pBackendCfg->cbStreamIn = sizeof(COREAUDIOSTREAM);
2164 pBackendCfg->cbStreamOut = sizeof(COREAUDIOSTREAM);
2165
2166 /* For Core Audio we provide one stream per device for now. */
2167 pBackendCfg->cMaxStreamsIn = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_IN);
2168 pBackendCfg->cMaxStreamsOut = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_OUT);
2169
2170 LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS));
2171 return VINF_SUCCESS;
2172}
2173
2174
2175/**
2176 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
2177 */
2178static DECLCALLBACK(int) drvHostCoreAudioHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIODEVICEENUM pDeviceEnum)
2179{
2180 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2181 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
2182
2183 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2184
2185 int rc = RTCritSectEnter(&pThis->CritSect);
2186 if (RT_SUCCESS(rc))
2187 {
2188 rc = coreAudioEnumerateDevices(pThis);
2189 if (RT_SUCCESS(rc))
2190 {
2191 if (pDeviceEnum)
2192 {
2193 /* Return a copy with only PDMAUDIODEVICE, none of the extra bits in COREAUDIODEVICEDATA. */
2194 rc = DrvAudioHlpDeviceEnumInit(pDeviceEnum);
2195 if (RT_SUCCESS(rc))
2196 rc = DrvAudioHlpDeviceEnumCopy(pDeviceEnum, &pThis->Devices);
2197
2198 if (RT_FAILURE(rc))
2199 DrvAudioHlpDeviceEnumFree(pDeviceEnum);
2200 }
2201 }
2202
2203 int rc2 = RTCritSectLeave(&pThis->CritSect);
2204 AssertRC(rc2);
2205 }
2206
2207 LogFlowFunc(("Returning %Rrc\n", rc));
2208 return rc;
2209}
2210
2211
2212#ifdef VBOX_WITH_AUDIO_CALLBACKS
2213/**
2214 * @interface_method_impl{PDMIHOSTAUDIO,pfnSetCallback}
2215 */
2216static DECLCALLBACK(int) drvHostCoreAudioHA_SetCallback(PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback)
2217{
2218 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2219 /* pfnCallback will be handled below. */
2220
2221 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2222
2223 int rc = RTCritSectEnter(&pThis->CritSect);
2224 if (RT_SUCCESS(rc))
2225 {
2226 LogFunc(("pfnCallback=%p\n", pfnCallback));
2227
2228 if (pfnCallback) /* Register. */
2229 {
2230 Assert(pThis->pfnCallback == NULL);
2231 pThis->pfnCallback = pfnCallback;
2232 }
2233 else /* Unregister. */
2234 {
2235 if (pThis->pfnCallback)
2236 pThis->pfnCallback = NULL;
2237 }
2238
2239 int rc2 = RTCritSectLeave(&pThis->CritSect);
2240 AssertRC(rc2);
2241 }
2242
2243 return rc;
2244}
2245#endif
2246
2247
2248/**
2249 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
2250 */
2251static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostCoreAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
2252{
2253 RT_NOREF(pInterface, enmDir);
2254 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
2255
2256 return PDMAUDIOBACKENDSTS_RUNNING;
2257}
2258
2259
2260/**
2261 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
2262 */
2263static DECLCALLBACK(int) drvHostCoreAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2264 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
2265{
2266 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2267 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2268 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
2269 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
2270
2271 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2272 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2273
2274 int rc = RTCritSectInit(&pCAStream->CritSect);
2275 if (RT_FAILURE(rc))
2276 return rc;
2277
2278 pCAStream->hThread = NIL_RTTHREAD;
2279 pCAStream->fRun = false;
2280 pCAStream->fIsRunning = false;
2281 pCAStream->fShutdown = false;
2282
2283 /* Input or output device? */
2284 bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN;
2285
2286 /* For now, just use the default device available. */
2287 PCOREAUDIODEVICEDATA pDev = fIn ? pThis->pDefaultDevIn : pThis->pDefaultDevOut;
2288
2289 LogFunc(("pStream=%p, pCfgReq=%p, pCfgAcq=%p, fIn=%RTbool, pDev=%p\n", pStream, pCfgReq, pCfgAcq, fIn, pDev));
2290
2291 if (pDev) /* (Default) device available? */
2292 {
2293 /* Sanity. */
2294 Assert(pDev->Core.cbData);
2295
2296 /* Init the Core Audio stream. */
2297 rc = coreAudioStreamInit(pCAStream, pThis, pDev);
2298 if (RT_SUCCESS(rc))
2299 {
2300 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_INIT);
2301
2302 rc = coreAudioStreamInitQueue(pCAStream, pCfgReq, pCfgAcq);
2303 if (RT_SUCCESS(rc))
2304 {
2305 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_INIT);
2306 }
2307 else
2308 {
2309 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT);
2310
2311 int rc2 = coreAudioStreamUninit(pCAStream);
2312 AssertRC(rc2);
2313
2314 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT);
2315 }
2316 }
2317 }
2318 else
2319 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
2320
2321 LogFunc(("Returning %Rrc\n", rc));
2322 return rc;
2323}
2324
2325
2326/**
2327 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
2328 */
2329static DECLCALLBACK(int) drvHostCoreAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2330{
2331 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2332 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2333
2334 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2335
2336 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2337
2338 uint32_t status = ASMAtomicReadU32(&pCAStream->enmStatus);
2339 if (!( status == COREAUDIOSTATUS_INIT
2340#ifndef VBOX_WITH_AUDIO_CALLBACKS
2341 || status == COREAUDIOSTATUS_REINIT
2342#endif
2343 ))
2344 {
2345 return VINF_SUCCESS;
2346 }
2347
2348 if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */
2349 return VINF_SUCCESS;
2350
2351 int rc = coreAudioStreamControl(pThis, pCAStream, PDMAUDIOSTREAMCMD_DISABLE);
2352 if (RT_SUCCESS(rc))
2353 {
2354 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT);
2355
2356 rc = coreAudioStreamUninit(pCAStream);
2357
2358 if (RT_SUCCESS(rc))
2359 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT);
2360 }
2361
2362 if (RT_SUCCESS(rc))
2363 {
2364 if (RTCritSectIsInitialized(&pCAStream->CritSect))
2365 RTCritSectDelete(&pCAStream->CritSect);
2366 }
2367
2368 LogFunc(("rc=%Rrc\n", rc));
2369 return rc;
2370}
2371
2372
2373/**
2374 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
2375 */
2376static DECLCALLBACK(int) drvHostCoreAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface,
2377 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2378{
2379 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2380 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2381
2382 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2383 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2384
2385 return coreAudioStreamControl(pThis, pCAStream, enmStreamCmd);
2386}
2387
2388
2389/**
2390 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2391 */
2392static DECLCALLBACK(uint32_t) drvHostCoreAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2393{
2394 RT_NOREF(pInterface);
2395 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2396
2397 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2398
2399 if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
2400 return 0;
2401
2402 AssertPtr(pCAStream->pCfg);
2403 AssertPtr(pCAStream->pCircBuf);
2404
2405 switch (pCAStream->pCfg->enmDir)
2406 {
2407 case PDMAUDIODIR_IN:
2408 return (uint32_t)RTCircBufUsed(pCAStream->pCircBuf);
2409
2410 case PDMAUDIODIR_OUT:
2411 default:
2412 AssertFailed();
2413 break;
2414 }
2415
2416 return 0;
2417}
2418
2419
2420/**
2421 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2422 */
2423static DECLCALLBACK(uint32_t) drvHostCoreAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2424{
2425 RT_NOREF(pInterface);
2426 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2427
2428 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2429
2430 uint32_t cbWritable = 0;
2431
2432 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT)
2433 {
2434 AssertPtr(pCAStream->pCfg);
2435 AssertPtr(pCAStream->pCircBuf);
2436
2437 switch (pCAStream->pCfg->enmDir)
2438 {
2439 case PDMAUDIODIR_OUT:
2440 cbWritable = (uint32_t)RTCircBufFree(pCAStream->pCircBuf);
2441 break;
2442
2443 default:
2444 break;
2445 }
2446 }
2447
2448 LogFlowFunc(("cbWritable=%RU32\n", cbWritable));
2449 return cbWritable;
2450}
2451
2452
2453/**
2454 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
2455 */
2456static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostCoreAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2457{
2458 RT_NOREF(pInterface);
2459 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2460
2461 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2462
2463 PDMAUDIOSTREAMSTS fStrmStatus = PDMAUDIOSTREAMSTS_FLAGS_NONE;
2464
2465 if (pCAStream->pCfg) /* Configured? */
2466 {
2467 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT)
2468 fStrmStatus |= PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED;
2469 }
2470
2471 return fStrmStatus;
2472}
2473
2474
2475/**
2476 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
2477 */
2478static DECLCALLBACK(int) drvHostCoreAudioHA_StreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2479{
2480 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2481 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2482
2483 RT_NOREF(pInterface, pStream);
2484
2485 /* Nothing to do here for Core Audio. */
2486 return VINF_SUCCESS;
2487}
2488
2489
2490/**
2491 * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
2492 */
2493static DECLCALLBACK(int) drvHostCoreAudioHA_Init(PPDMIHOSTAUDIO pInterface)
2494{
2495 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2496
2497 int rc = DrvAudioHlpDeviceEnumInit(&pThis->Devices);
2498 if (RT_SUCCESS(rc))
2499 {
2500 /* Do the first (initial) internal device enumeration. */
2501 rc = coreAudioEnumerateDevices(pThis);
2502 }
2503
2504 if (RT_SUCCESS(rc))
2505 {
2506 /* Register system callbacks. */
2507 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
2508 kAudioObjectPropertyElementMaster };
2509
2510 OSStatus err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr,
2511 coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
2512 if ( err != noErr
2513 && err != kAudioHardwareIllegalOperationError)
2514 {
2515 LogRel(("CoreAudio: Failed to add the input default device changed listener (%RI32)\n", err));
2516 }
2517
2518 propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2519 err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr,
2520 coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
2521 if ( err != noErr
2522 && err != kAudioHardwareIllegalOperationError)
2523 {
2524 LogRel(("CoreAudio: Failed to add the output default device changed listener (%RI32)\n", err));
2525 }
2526 }
2527
2528 LogFlowFunc(("Returning %Rrc\n", rc));
2529 return rc;
2530}
2531
2532
2533/**
2534 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
2535 */
2536static DECLCALLBACK(void) drvHostCoreAudioHA_Shutdown(PPDMIHOSTAUDIO pInterface)
2537{
2538 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2539
2540 /*
2541 * Unregister system callbacks.
2542 */
2543 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
2544 kAudioObjectPropertyElementMaster };
2545
2546 OSStatus err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr,
2547 coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
2548 if ( err != noErr
2549 && err != kAudioHardwareBadObjectError)
2550 {
2551 LogRel(("CoreAudio: Failed to remove the default input device changed listener (%RI32)\n", err));
2552 }
2553
2554 propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2555 err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr,
2556 coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
2557 if ( err != noErr
2558 && err != kAudioHardwareBadObjectError)
2559 {
2560 LogRel(("CoreAudio: Failed to remove the default output device changed listener (%RI32)\n", err));
2561 }
2562
2563 LogFlowFuncEnter();
2564}
2565
2566
2567/**
2568 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2569 */
2570static DECLCALLBACK(void *) drvHostCoreAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2571{
2572 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2573 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2574
2575 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2576 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2577
2578 return NULL;
2579}
2580
2581
2582/**
2583 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2584 * Construct a Core Audio driver instance.}
2585 */
2586static DECLCALLBACK(int) drvHostCoreAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2587{
2588 RT_NOREF(pCfg, fFlags);
2589 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2590 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2591 LogRel(("Audio: Initializing Core Audio driver\n"));
2592
2593 /*
2594 * Init the static parts.
2595 */
2596 pThis->pDrvIns = pDrvIns;
2597 /* IBase */
2598 pDrvIns->IBase.pfnQueryInterface = drvHostCoreAudioQueryInterface;
2599 /* IHostAudio */
2600 pThis->IHostAudio.pfnInit = drvHostCoreAudioHA_Init;
2601 pThis->IHostAudio.pfnShutdown = drvHostCoreAudioHA_Shutdown;
2602 pThis->IHostAudio.pfnGetConfig = drvHostCoreAudioHA_GetConfig;
2603 pThis->IHostAudio.pfnGetStatus = drvHostCoreAudioHA_GetStatus;
2604 pThis->IHostAudio.pfnStreamCreate = drvHostCoreAudioHA_StreamCreate;
2605 pThis->IHostAudio.pfnStreamDestroy = drvHostCoreAudioHA_StreamDestroy;
2606 pThis->IHostAudio.pfnStreamControl = drvHostCoreAudioHA_StreamControl;
2607 pThis->IHostAudio.pfnStreamGetReadable = drvHostCoreAudioHA_StreamGetReadable;
2608 pThis->IHostAudio.pfnStreamGetWritable = drvHostCoreAudioHA_StreamGetWritable;
2609 pThis->IHostAudio.pfnStreamGetStatus = drvHostCoreAudioHA_StreamGetStatus;
2610 pThis->IHostAudio.pfnStreamIterate = drvHostCoreAudioHA_StreamIterate;
2611 pThis->IHostAudio.pfnStreamPlay = drvHostCoreAudioHA_StreamPlay;
2612 pThis->IHostAudio.pfnStreamCapture = drvHostCoreAudioHA_StreamCapture;
2613#ifdef VBOX_WITH_AUDIO_CALLBACKS
2614 pThis->IHostAudio.pfnSetCallback = drvHostCoreAudioHA_SetCallback;
2615 pThis->pfnCallback = NULL;
2616#else
2617 pThis->IHostAudio.pfnSetCallback = NULL;
2618#endif
2619 pThis->IHostAudio.pfnGetDevices = drvHostCoreAudioHA_GetDevices;
2620 pThis->IHostAudio.pfnStreamGetPending = NULL;
2621 pThis->IHostAudio.pfnStreamPlayBegin = NULL;
2622 pThis->IHostAudio.pfnStreamPlayEnd = NULL;
2623 pThis->IHostAudio.pfnStreamCaptureBegin = NULL;
2624 pThis->IHostAudio.pfnStreamCaptureEnd = NULL;
2625
2626 int rc = RTCritSectInit(&pThis->CritSect);
2627 AssertRCReturn(rc, rc);
2628
2629#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2630 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm");
2631 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm");
2632#endif
2633
2634 LogFlowFuncLeaveRC(rc);
2635 return rc;
2636}
2637
2638
2639/**
2640 * @callback_method_impl{FNPDMDRVDESTRUCT}
2641 */
2642static DECLCALLBACK(void) drvHostCoreAudioDestruct(PPDMDRVINS pDrvIns)
2643{
2644 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2645 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2646
2647 int rc2 = RTCritSectDelete(&pThis->CritSect);
2648 AssertRC(rc2);
2649
2650 LogFlowFuncLeaveRC(rc2);
2651}
2652
2653
2654/**
2655 * Char driver registration record.
2656 */
2657const PDMDRVREG g_DrvHostCoreAudio =
2658{
2659 /* u32Version */
2660 PDM_DRVREG_VERSION,
2661 /* szName */
2662 "CoreAudio",
2663 /* szRCMod */
2664 "",
2665 /* szR0Mod */
2666 "",
2667 /* pszDescription */
2668 "Core Audio host driver",
2669 /* fFlags */
2670 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2671 /* fClass. */
2672 PDM_DRVREG_CLASS_AUDIO,
2673 /* cMaxInstances */
2674 ~0U,
2675 /* cbInstance */
2676 sizeof(DRVHOSTCOREAUDIO),
2677 /* pfnConstruct */
2678 drvHostCoreAudioConstruct,
2679 /* pfnDestruct */
2680 drvHostCoreAudioDestruct,
2681 /* pfnRelocate */
2682 NULL,
2683 /* pfnIOCtl */
2684 NULL,
2685 /* pfnPowerOn */
2686 NULL,
2687 /* pfnReset */
2688 NULL,
2689 /* pfnSuspend */
2690 NULL,
2691 /* pfnResume */
2692 NULL,
2693 /* pfnAttach */
2694 NULL,
2695 /* pfnDetach */
2696 NULL,
2697 /* pfnPowerOff */
2698 NULL,
2699 /* pfnSoftReset */
2700 NULL,
2701 /* u32EndVersion */
2702 PDM_DRVREG_VERSION
2703};
2704
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