VirtualBox

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

Last change on this file since 88568 was 88534, checked in by vboxsync, 4 years ago

Audio: Merged the cbStreamOut and cbStreamIn fields in PDMAUDIOBACKENDCFG (into cbStream). Added a fFlags member to PDMAUDIOBACKENDCFG, currently must-be-zero. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 90.2 KB
Line 
1/* $Id: DrvHostAudioCoreAudio.cpp 88534 2021-04-15 12:16:56Z 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 cbBuf, uint32_t *pcbRead)
1901{
1902 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1903 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1904 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
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 *pcbRead = 0;
1930 return VINF_SUCCESS;
1931 }
1932
1933 int rc = VINF_SUCCESS;
1934
1935 uint32_t cbReadTotal = 0;
1936
1937 rc = RTCritSectEnter(&pCAStream->CritSect);
1938 AssertRC(rc);
1939
1940 do
1941 {
1942 size_t cbToWrite = RT_MIN(cbBuf, RTCircBufUsed(pCAStream->pCircBuf));
1943
1944 uint8_t *pvChunk;
1945 size_t cbChunk;
1946
1947 Log3Func(("cbToWrite=%zu/%zu\n", cbToWrite, RTCircBufSize(pCAStream->pCircBuf)));
1948
1949 while (cbToWrite)
1950 {
1951 /* Try to acquire the necessary block from the ring buffer. */
1952 RTCircBufAcquireReadBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk);
1953 if (cbChunk)
1954 memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk);
1955
1956 /* Release the read buffer, so it could be used for new data. */
1957 RTCircBufReleaseReadBlock(pCAStream->pCircBuf, cbChunk);
1958
1959 if (RT_FAILURE(rc))
1960 break;
1961
1962 Assert(cbToWrite >= cbChunk);
1963 cbToWrite -= cbChunk;
1964
1965 cbReadTotal += cbChunk;
1966 }
1967 }
1968 while (0);
1969
1970 int rc2 = RTCritSectLeave(&pCAStream->CritSect);
1971 AssertRC(rc2);
1972
1973 if (RT_SUCCESS(rc))
1974 *pcbRead = cbReadTotal;
1975
1976 return rc;
1977}
1978
1979/**
1980 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1981 */
1982static DECLCALLBACK(int) drvHostCoreAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1983 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1984{
1985 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
1986 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
1987
1988#ifndef VBOX_WITH_AUDIO_CALLBACKS
1989 /* Check if the audio device should be reinitialized. If so do it. */
1990 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT)
1991 {
1992 if (pThis->pDefaultDevOut)
1993 {
1994 /* For now re just re-initialize with the current output device. */
1995 int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevOut);
1996 if (RT_FAILURE(rc2))
1997 return VERR_NOT_AVAILABLE;
1998 }
1999 else
2000 return VERR_NOT_AVAILABLE;
2001 }
2002#else
2003 RT_NOREF(pThis);
2004#endif
2005
2006 if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
2007 {
2008 *pcbWritten = 0;
2009 return VINF_SUCCESS;
2010 }
2011
2012 uint32_t cbWrittenTotal = 0;
2013
2014 int rc = VINF_SUCCESS;
2015
2016 rc = RTCritSectEnter(&pCAStream->CritSect);
2017 AssertRC(rc);
2018
2019 size_t cbToWrite = RT_MIN(cbBuf, RTCircBufFree(pCAStream->pCircBuf));
2020 Log3Func(("cbToWrite=%zu\n", cbToWrite));
2021
2022 uint8_t *pvChunk;
2023 size_t cbChunk;
2024
2025 while (cbToWrite)
2026 {
2027 /* Try to acquire the necessary space from the ring buffer. */
2028 RTCircBufAcquireWriteBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk);
2029 if (!cbChunk)
2030 {
2031 RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk);
2032 break;
2033 }
2034
2035 Assert(cbChunk <= cbToWrite);
2036 Assert(cbWrittenTotal + cbChunk <= cbBuf);
2037
2038 memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk);
2039
2040#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2041 RTFILE fh;
2042 rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm",
2043 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
2044 if (RT_SUCCESS(rc))
2045 {
2046 RTFileWrite(fh, pvChunk, cbChunk, NULL);
2047 RTFileClose(fh);
2048 }
2049 else
2050 AssertFailed();
2051#endif
2052
2053 /* Release the ring buffer, so the read thread could start reading this data. */
2054 RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk);
2055
2056 if (RT_FAILURE(rc))
2057 break;
2058
2059 Assert(cbToWrite >= cbChunk);
2060 cbToWrite -= cbChunk;
2061
2062 cbWrittenTotal += cbChunk;
2063 }
2064
2065 if ( RT_SUCCESS(rc)
2066 && pCAStream->fRun
2067 && !pCAStream->fIsRunning)
2068 {
2069 rc = coreAudioStreamInvalidateQueue(pCAStream);
2070 if (RT_SUCCESS(rc))
2071 {
2072 AudioQueueStart(pCAStream->audioQueue, NULL);
2073 pCAStream->fRun = false;
2074 pCAStream->fIsRunning = true;
2075 }
2076 }
2077
2078 int rc2 = RTCritSectLeave(&pCAStream->CritSect);
2079 AssertRC(rc2);
2080
2081 if (RT_SUCCESS(rc))
2082 *pcbWritten = cbWrittenTotal;
2083
2084 return rc;
2085}
2086
2087static int coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2088{
2089 RT_NOREF(pThis);
2090
2091 uint32_t enmStatus = ASMAtomicReadU32(&pCAStream->enmStatus);
2092
2093 LogFlowFunc(("enmStreamCmd=%RU32, enmStatus=%RU32\n", enmStreamCmd, enmStatus));
2094
2095 if (!( enmStatus == COREAUDIOSTATUS_INIT
2096#ifndef VBOX_WITH_AUDIO_CALLBACKS
2097 || enmStatus == COREAUDIOSTATUS_REINIT
2098#endif
2099 ))
2100 {
2101 return VINF_SUCCESS;
2102 }
2103
2104 if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */
2105 return VINF_SUCCESS;
2106
2107 int rc = VINF_SUCCESS;
2108
2109 switch (enmStreamCmd)
2110 {
2111 case PDMAUDIOSTREAMCMD_ENABLE:
2112 case PDMAUDIOSTREAMCMD_RESUME:
2113 {
2114 LogFunc(("Queue enable\n"));
2115 if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN)
2116 {
2117 rc = coreAudioStreamInvalidateQueue(pCAStream);
2118 if (RT_SUCCESS(rc))
2119 {
2120 /* Start the audio queue immediately. */
2121 AudioQueueStart(pCAStream->audioQueue, NULL);
2122 }
2123 }
2124 else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT)
2125 {
2126 /* Touch the run flag to start the audio queue as soon as
2127 * we have anough data to actually play something. */
2128 ASMAtomicXchgBool(&pCAStream->fRun, true);
2129 }
2130 break;
2131 }
2132
2133 case PDMAUDIOSTREAMCMD_DISABLE:
2134 {
2135 LogFunc(("Queue disable\n"));
2136 AudioQueueStop(pCAStream->audioQueue, 1 /* Immediately */);
2137 ASMAtomicXchgBool(&pCAStream->fRun, false);
2138 ASMAtomicXchgBool(&pCAStream->fIsRunning, false);
2139 break;
2140 }
2141 case PDMAUDIOSTREAMCMD_PAUSE:
2142 {
2143 LogFunc(("Queue pause\n"));
2144 AudioQueuePause(pCAStream->audioQueue);
2145 ASMAtomicXchgBool(&pCAStream->fIsRunning, false);
2146 break;
2147 }
2148
2149 default:
2150 rc = VERR_NOT_SUPPORTED;
2151 break;
2152 }
2153
2154 LogFlowFuncLeaveRC(rc);
2155 return rc;
2156}
2157
2158
2159/**
2160 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
2161 */
2162static DECLCALLBACK(int) drvHostCoreAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
2163{
2164 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2165 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
2166
2167 /*
2168 * Fill in the config structure.
2169 */
2170 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio");
2171 pBackendCfg->cbStream = sizeof(COREAUDIOSTREAM);
2172 pBackendCfg->fFlags = 0;
2173 /* For Core Audio we provide one stream per device for now. */
2174 pBackendCfg->cMaxStreamsIn = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_IN);
2175 pBackendCfg->cMaxStreamsOut = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_OUT);
2176
2177 LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS));
2178 return VINF_SUCCESS;
2179}
2180
2181
2182/**
2183 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
2184 */
2185static DECLCALLBACK(int) drvHostCoreAudioHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
2186{
2187 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2188 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
2189
2190 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2191
2192 int rc = RTCritSectEnter(&pThis->CritSect);
2193 if (RT_SUCCESS(rc))
2194 {
2195 rc = coreAudioEnumerateDevices(pThis);
2196 if (RT_SUCCESS(rc))
2197 {
2198 if (pDeviceEnum)
2199 {
2200 /* Return a copy with only PDMAUDIOHOSTDEV, none of the extra bits in COREAUDIODEVICEDATA. */
2201 PDMAudioHostEnumInit(pDeviceEnum);
2202 rc = PDMAudioHostEnumCopy(pDeviceEnum, &pThis->Devices, PDMAUDIODIR_INVALID /*all*/, true /*fOnlyCoreData*/);
2203 if (RT_FAILURE(rc))
2204 PDMAudioHostEnumDelete(pDeviceEnum);
2205 }
2206 }
2207
2208 int rc2 = RTCritSectLeave(&pThis->CritSect);
2209 AssertRC(rc2);
2210 }
2211
2212 LogFlowFunc(("Returning %Rrc\n", rc));
2213 return rc;
2214}
2215
2216
2217/**
2218 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
2219 */
2220static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostCoreAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
2221{
2222 RT_NOREF(pInterface, enmDir);
2223 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
2224
2225 return PDMAUDIOBACKENDSTS_RUNNING;
2226}
2227
2228
2229/**
2230 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
2231 */
2232static DECLCALLBACK(int) drvHostCoreAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2233 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
2234{
2235 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2236 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2237 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
2238 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
2239
2240 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2241 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2242
2243 int rc = RTCritSectInit(&pCAStream->CritSect);
2244 if (RT_FAILURE(rc))
2245 return rc;
2246
2247 pCAStream->hThread = NIL_RTTHREAD;
2248 pCAStream->fRun = false;
2249 pCAStream->fIsRunning = false;
2250 pCAStream->fShutdown = false;
2251
2252 /* Input or output device? */
2253 bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN;
2254
2255 /* For now, just use the default device available. */
2256 PCOREAUDIODEVICEDATA pDev = fIn ? pThis->pDefaultDevIn : pThis->pDefaultDevOut;
2257
2258 LogFunc(("pStream=%p, pCfgReq=%p, pCfgAcq=%p, fIn=%RTbool, pDev=%p\n", pStream, pCfgReq, pCfgAcq, fIn, pDev));
2259
2260 if (pDev) /* (Default) device available? */
2261 {
2262 /* Sanity. */
2263 Assert(pDev->Core.cbSelf == sizeof(*pDev));
2264
2265 /* Init the Core Audio stream. */
2266 rc = coreAudioStreamInit(pCAStream, pThis, pDev);
2267 if (RT_SUCCESS(rc))
2268 {
2269 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_INIT);
2270
2271 rc = coreAudioStreamInitQueue(pCAStream, pCfgReq, pCfgAcq);
2272 if (RT_SUCCESS(rc))
2273 {
2274 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_INIT);
2275 }
2276 else
2277 {
2278 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT);
2279
2280 int rc2 = coreAudioStreamUninit(pCAStream);
2281 AssertRC(rc2);
2282
2283 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT);
2284 }
2285 }
2286 }
2287 else
2288 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
2289
2290 LogFunc(("Returning %Rrc\n", rc));
2291 return rc;
2292}
2293
2294
2295/**
2296 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
2297 */
2298static DECLCALLBACK(int) drvHostCoreAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2299{
2300 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2301 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2302
2303 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2304
2305 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2306
2307 uint32_t status = ASMAtomicReadU32(&pCAStream->enmStatus);
2308 if (!( status == COREAUDIOSTATUS_INIT
2309#ifndef VBOX_WITH_AUDIO_CALLBACKS
2310 || status == COREAUDIOSTATUS_REINIT
2311#endif
2312 ))
2313 {
2314 return VINF_SUCCESS;
2315 }
2316
2317 if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */
2318 return VINF_SUCCESS;
2319
2320 int rc = coreAudioStreamControl(pThis, pCAStream, PDMAUDIOSTREAMCMD_DISABLE);
2321 if (RT_SUCCESS(rc))
2322 {
2323 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT);
2324
2325 rc = coreAudioStreamUninit(pCAStream);
2326
2327 if (RT_SUCCESS(rc))
2328 ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT);
2329 }
2330
2331 if (RT_SUCCESS(rc))
2332 {
2333 if (RTCritSectIsInitialized(&pCAStream->CritSect))
2334 RTCritSectDelete(&pCAStream->CritSect);
2335 }
2336
2337 LogFunc(("rc=%Rrc\n", rc));
2338 return rc;
2339}
2340
2341
2342/**
2343 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
2344 */
2345static DECLCALLBACK(int) drvHostCoreAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface,
2346 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2347{
2348 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2349 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2350
2351 PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
2352 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2353
2354 return coreAudioStreamControl(pThis, pCAStream, enmStreamCmd);
2355}
2356
2357
2358/**
2359 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2360 */
2361static DECLCALLBACK(uint32_t) drvHostCoreAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2362{
2363 RT_NOREF(pInterface);
2364 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2365
2366 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2367
2368 if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
2369 return 0;
2370
2371 AssertPtr(pCAStream->pCfg);
2372 AssertPtr(pCAStream->pCircBuf);
2373
2374 switch (pCAStream->pCfg->enmDir)
2375 {
2376 case PDMAUDIODIR_IN:
2377 return (uint32_t)RTCircBufUsed(pCAStream->pCircBuf);
2378
2379 case PDMAUDIODIR_OUT:
2380 default:
2381 AssertFailed();
2382 break;
2383 }
2384
2385 return 0;
2386}
2387
2388
2389/**
2390 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2391 */
2392static DECLCALLBACK(uint32_t) drvHostCoreAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2393{
2394 RT_NOREF(pInterface);
2395 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2396
2397 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2398
2399 uint32_t cbWritable = 0;
2400
2401 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT)
2402 {
2403 AssertPtr(pCAStream->pCfg);
2404 AssertPtr(pCAStream->pCircBuf);
2405
2406 switch (pCAStream->pCfg->enmDir)
2407 {
2408 case PDMAUDIODIR_OUT:
2409 cbWritable = (uint32_t)RTCircBufFree(pCAStream->pCircBuf);
2410 break;
2411
2412 default:
2413 break;
2414 }
2415 }
2416
2417 LogFlowFunc(("cbWritable=%RU32\n", cbWritable));
2418 return cbWritable;
2419}
2420
2421
2422/**
2423 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
2424 */
2425static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostCoreAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2426{
2427 RT_NOREF(pInterface);
2428 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2429
2430 PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
2431
2432 PDMAUDIOSTREAMSTS fStrmStatus = PDMAUDIOSTREAMSTS_FLAGS_NONE;
2433
2434 if (pCAStream->pCfg) /* Configured? */
2435 {
2436 if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT)
2437 fStrmStatus |= PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED;
2438 }
2439
2440 return fStrmStatus;
2441}
2442
2443
2444/**
2445 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2446 */
2447static DECLCALLBACK(void *) drvHostCoreAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2448{
2449 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2450 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2451
2452 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2453 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2454
2455 return NULL;
2456}
2457
2458/**
2459 * Worker for the power off and destructor callbacks.
2460 */
2461static void drvHostCoreAudioRemoveDefaultDeviceListners(PDRVHOSTCOREAUDIO pThis)
2462{
2463 /*
2464 * Unregister system callbacks.
2465 */
2466 AudioObjectPropertyAddress PropAddr =
2467 {
2468 kAudioHardwarePropertyDefaultInputDevice,
2469 kAudioObjectPropertyScopeGlobal,
2470 kAudioObjectPropertyElementMaster
2471 };
2472
2473 OSStatus orc;
2474 if (pThis->fRegisteredDefaultInputListener)
2475 {
2476 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, coreAudioDefaultDeviceChangedCb, pThis);
2477 if ( orc != noErr
2478 && orc != kAudioHardwareBadObjectError)
2479 LogRel(("CoreAudio: Failed to remove the default input device changed listener: %d (%#x))\n", orc, orc));
2480 pThis->fRegisteredDefaultInputListener = false;
2481 }
2482
2483 if (pThis->fRegisteredDefaultOutputListener)
2484 {
2485
2486 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2487 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, coreAudioDefaultDeviceChangedCb, pThis);
2488 if ( orc != noErr
2489 && orc != kAudioHardwareBadObjectError)
2490 LogRel(("CoreAudio: Failed to remove the default output device changed listener: %d (%#x))\n", orc, orc));
2491 pThis->fRegisteredDefaultOutputListener = false;
2492 }
2493
2494 LogFlowFuncEnter();
2495}
2496
2497
2498/**
2499 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
2500 */
2501static DECLCALLBACK(void) drvHostCoreAudioPowerOff(PPDMDRVINS pDrvIns)
2502{
2503 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2504 drvHostCoreAudioRemoveDefaultDeviceListners(pThis);
2505}
2506
2507
2508/**
2509 * @callback_method_impl{FNPDMDRVDESTRUCT}
2510 */
2511static DECLCALLBACK(void) drvHostCoreAudioDestruct(PPDMDRVINS pDrvIns)
2512{
2513 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2514 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2515
2516 drvHostCoreAudioRemoveDefaultDeviceListners(pThis);
2517
2518 int rc2 = RTCritSectDelete(&pThis->CritSect);
2519 AssertRC(rc2);
2520
2521 LogFlowFuncLeaveRC(rc2);
2522}
2523
2524
2525/**
2526 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2527 * Construct a Core Audio driver instance.}
2528 */
2529static DECLCALLBACK(int) drvHostCoreAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2530{
2531 RT_NOREF(pCfg, fFlags);
2532 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2533 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2534 LogRel(("Audio: Initializing Core Audio driver\n"));
2535
2536 /*
2537 * Init the static parts.
2538 */
2539 pThis->pDrvIns = pDrvIns;
2540 PDMAudioHostEnumInit(&pThis->Devices);
2541 /* IBase */
2542 pDrvIns->IBase.pfnQueryInterface = drvHostCoreAudioQueryInterface;
2543 /* IHostAudio */
2544 pThis->IHostAudio.pfnGetConfig = drvHostCoreAudioHA_GetConfig;
2545 pThis->IHostAudio.pfnGetStatus = drvHostCoreAudioHA_GetStatus;
2546 pThis->IHostAudio.pfnStreamCreate = drvHostCoreAudioHA_StreamCreate;
2547 pThis->IHostAudio.pfnStreamDestroy = drvHostCoreAudioHA_StreamDestroy;
2548 pThis->IHostAudio.pfnStreamControl = drvHostCoreAudioHA_StreamControl;
2549 pThis->IHostAudio.pfnStreamGetReadable = drvHostCoreAudioHA_StreamGetReadable;
2550 pThis->IHostAudio.pfnStreamGetWritable = drvHostCoreAudioHA_StreamGetWritable;
2551 pThis->IHostAudio.pfnStreamGetStatus = drvHostCoreAudioHA_StreamGetStatus;
2552 pThis->IHostAudio.pfnStreamPlay = drvHostCoreAudioHA_StreamPlay;
2553 pThis->IHostAudio.pfnStreamCapture = drvHostCoreAudioHA_StreamCapture;
2554 pThis->IHostAudio.pfnGetDevices = drvHostCoreAudioHA_GetDevices;
2555 pThis->IHostAudio.pfnStreamGetPending = NULL;
2556
2557 int rc = RTCritSectInit(&pThis->CritSect);
2558 AssertRCReturn(rc, rc);
2559
2560 /*
2561 * Enumerate audio devices.
2562 */
2563 rc = coreAudioEnumerateDevices(pThis);
2564 AssertRCReturn(rc, rc);
2565
2566 /*
2567 * Register callbacks for default device input and output changes.
2568 * We just ignore errors here it seems.
2569 */
2570 AudioObjectPropertyAddress PropAddr =
2571 {
2572 /* .mSelector = */ kAudioHardwarePropertyDefaultInputDevice,
2573 /* .mScope = */ kAudioObjectPropertyScopeGlobal,
2574 /* .mElement = */ kAudioObjectPropertyElementMaster
2575 };
2576
2577 OSStatus orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, coreAudioDefaultDeviceChangedCb, pThis);
2578 pThis->fRegisteredDefaultInputListener = orc == noErr;
2579 if ( orc != noErr
2580 && orc != kAudioHardwareIllegalOperationError)
2581 LogRel(("CoreAudio: Failed to add the input default device changed listener: %d (%#x)\n", orc, orc));
2582
2583 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2584 orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, coreAudioDefaultDeviceChangedCb, pThis);
2585 pThis->fRegisteredDefaultOutputListener = orc == noErr;
2586 if ( orc != noErr
2587 && orc != kAudioHardwareIllegalOperationError)
2588 LogRel(("CoreAudio: Failed to add the output default device changed listener: %d (%#x)\n", orc, orc));
2589
2590#ifdef VBOX_WITH_AUDIO_CALLBACKS
2591 /*
2592 * Query the notification interface from the driver/device above us.
2593 */
2594 pThis->pIAudioNotifyFromHost = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIONOTIFYFROMHOST);
2595 Assert(pThis->pIAudioNotifyFromHost);
2596#endif
2597
2598#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2599 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm");
2600 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm");
2601#endif
2602
2603 LogFlowFuncLeaveRC(rc);
2604 return rc;
2605}
2606
2607
2608/**
2609 * Char driver registration record.
2610 */
2611const PDMDRVREG g_DrvHostCoreAudio =
2612{
2613 /* u32Version */
2614 PDM_DRVREG_VERSION,
2615 /* szName */
2616 "CoreAudio",
2617 /* szRCMod */
2618 "",
2619 /* szR0Mod */
2620 "",
2621 /* pszDescription */
2622 "Core Audio host driver",
2623 /* fFlags */
2624 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2625 /* fClass. */
2626 PDM_DRVREG_CLASS_AUDIO,
2627 /* cMaxInstances */
2628 ~0U,
2629 /* cbInstance */
2630 sizeof(DRVHOSTCOREAUDIO),
2631 /* pfnConstruct */
2632 drvHostCoreAudioConstruct,
2633 /* pfnDestruct */
2634 drvHostCoreAudioDestruct,
2635 /* pfnRelocate */
2636 NULL,
2637 /* pfnIOCtl */
2638 NULL,
2639 /* pfnPowerOn */
2640 NULL,
2641 /* pfnReset */
2642 NULL,
2643 /* pfnSuspend */
2644 NULL,
2645 /* pfnResume */
2646 NULL,
2647 /* pfnAttach */
2648 NULL,
2649 /* pfnDetach */
2650 NULL,
2651 /* pfnPowerOff */
2652 drvHostCoreAudioPowerOff,
2653 /* pfnSoftReset */
2654 NULL,
2655 /* u32EndVersion */
2656 PDM_DRVREG_VERSION
2657};
2658
Note: See TracBrowser for help on using the repository browser.

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