VirtualBox

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

Last change on this file since 88788 was 88761, checked in by vboxsync, 4 years ago

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