VirtualBox

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

Last change on this file since 74058 was 74036, checked in by vboxsync, 6 years ago

Audio/DrvHostCoreAudio: Return the correct available input/output stream count in drvHostCoreAudioGetConfig(). For now we provide on stream per device (function) on Core Audio.

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

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