VirtualBox

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

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

Audio: Logging tweaks (driver names); no functional changes.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use