VirtualBox

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

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

DrvHostAudioCoreAudio: Updated drvHostCoreAudioHA_StreamPlay parameter list to match the doxygen docs. bugref:9890

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