VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp@ 88356

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

Audio: mac os build fix. bugref:9890

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

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