VirtualBox

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

Last change on this file since 89032 was 89027, checked in by vboxsync, 4 years ago

DrvHostAudioCoreAudio: More cleanups in the input/output buffer callback area. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 87.9 KB
Line 
1/* $Id: DrvHostAudioCoreAudio.cpp 89027 2021-05-13 01:53:41Z vboxsync $ */
2/** @file
3 * Host audio driver - Mac OS X CoreAudio.
4 *
5 * For relevant Apple documentation, here are some starters:
6 * - Core Audio Essentials
7 * https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html
8 * - TN2097: Playing a sound file using the Default Output Audio Unit
9 * https://developer.apple.com/library/archive/technotes/tn2097/
10 * - TN2091: Device input using the HAL Output Audio Unit
11 * https://developer.apple.com/library/archive/technotes/tn2091/
12 * - Audio Component Services
13 * https://developer.apple.com/documentation/audiounit/audio_component_services?language=objc
14 * - QA1533: How to handle kAudioUnitProperty_MaximumFramesPerSlice
15 * https://developer.apple.com/library/archive/qa/qa1533/
16 * - QA1317: Signaling the end of data when using AudioConverterFillComplexBuffer
17 * https://developer.apple.com/library/archive/qa/qa1317/
18 */
19
20/*
21 * Copyright (C) 2010-2020 Oracle Corporation
22 *
23 * This file is part of VirtualBox Open Source Edition (OSE), as
24 * available from http://www.virtualbox.org. This file is free software;
25 * you can redistribute it and/or modify it under the terms of the GNU
26 * General Public License (GPL) as published by the Free Software
27 * Foundation, in version 2 as it comes in the "COPYING" file of the
28 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
29 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
30 */
31
32
33/*********************************************************************************************************************************
34* Header Files *
35*********************************************************************************************************************************/
36#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
37#include <VBox/log.h>
38#include <VBox/vmm/pdmaudioinline.h>
39#include <VBox/vmm/pdmaudiohostenuminline.h>
40
41#ifdef VBOX_AUDIO_VKAT
42# include "VBoxDDVKAT.h"
43#else
44# include "VBoxDD.h"
45#endif
46
47#include <iprt/asm.h>
48#include <iprt/cdefs.h>
49#include <iprt/circbuf.h>
50#include <iprt/mem.h>
51
52#include <iprt/uuid.h>
53
54#include <CoreAudio/CoreAudio.h>
55#include <CoreServices/CoreServices.h>
56#include <AudioUnit/AudioUnit.h>
57#include <AudioToolbox/AudioConverter.h>
58#include <AudioToolbox/AudioToolbox.h>
59
60
61
62/* Enables utilizing the Core Audio converter unit for converting
63 * input / output from/to our requested formats. That might be more
64 * performant than using our own routines later down the road. */
65/** @todo Needs more investigation and testing first before enabling. */
66//# define VBOX_WITH_AUDIO_CA_CONVERTER
67
68/** @todo
69 * - Maybe make sure the threads are immediately stopped if playing/recording stops.
70 */
71
72
73/*********************************************************************************************************************************
74* Structures and Typedefs *
75*********************************************************************************************************************************/
76/** Pointer to the instance data for a Core Audio driver instance. */
77typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO;
78/** Pointer to the Core Audio specific backend data for an audio stream. */
79typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM;
80
81/**
82 * Core Audio device entry (enumeration).
83 *
84 * @note This is definitely not safe to just copy!
85 */
86typedef struct COREAUDIODEVICEDATA
87{
88 /** The core PDM structure. */
89 PDMAUDIOHOSTDEV Core;
90
91 /** Pointer to driver instance this device is bound to. */
92 PDRVHOSTCOREAUDIO pDrv;
93 /** The audio device ID of the currently used device (UInt32 typedef). */
94 AudioDeviceID deviceID;
95 /** The device' "UUID".
96 * @todo r=bird: We leak this. Header say we must CFRelease it. */
97 CFStringRef UUID;
98 /** List of attached (native) Core Audio streams attached to this device. */
99 RTLISTANCHOR lstStreams;
100} COREAUDIODEVICEDATA;
101/** Pointer to a Core Audio device entry (enumeration). */
102typedef COREAUDIODEVICEDATA *PCOREAUDIODEVICEDATA;
103
104
105/**
106 * Core Audio stream state.
107 */
108typedef enum COREAUDIOINITSTATE
109{
110 /** The device is uninitialized. */
111 COREAUDIOINITSTATE_UNINIT = 0,
112 /** The device is currently initializing. */
113 COREAUDIOINITSTATE_IN_INIT,
114 /** The device is initialized. */
115 COREAUDIOINITSTATE_INIT,
116 /** The device is currently uninitializing. */
117 COREAUDIOINITSTATE_IN_UNINIT,
118 /** The usual 32-bit hack. */
119 COREAUDIOINITSTATE_32BIT_HACK = 0x7fffffff
120} COREAUDIOINITSTATE;
121
122
123#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
124/**
125 * Context data for the audio format converter.
126 */
127typedef struct COREAUDIOCONVCBCTX
128{
129 /** Pointer to the stream this context is bound to. */
130 PCOREAUDIOSTREAM pStream; /**< @todo r=bird: It's part of the COREAUDIOSTREAM structure! You don't need this. */
131 /** Source stream description. */
132 AudioStreamBasicDescription asbdSrc;
133 /** Destination stream description. */
134 AudioStreamBasicDescription asbdDst;
135 /** Pointer to native buffer list used for rendering the source audio data into. */
136 AudioBufferList *pBufLstSrc;
137 /** Total packet conversion count. */
138 UInt32 uPacketCnt;
139 /** Current packet conversion index. */
140 UInt32 uPacketIdx;
141 /** Error count, for limiting the logging. */
142 UInt32 cErrors;
143} COREAUDIOCONVCBCTX;
144/** Pointer to the context of a conversion callback. */
145typedef COREAUDIOCONVCBCTX *PCOREAUDIOCONVCBCTX;
146#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */
147
148
149/**
150 * Core Audio specific data for an audio stream.
151 */
152typedef struct COREAUDIOSTREAM
153{
154 /** Common part. */
155 PDMAUDIOBACKENDSTREAM Core;
156
157 /** The stream's acquired configuration. */
158 PDMAUDIOSTREAMCFG Cfg;
159 /** Direction specific data. */
160 union
161 {
162 struct
163 {
164#if 0 /* Unused */
165 /** The ratio between the device & the stream sample rate. */
166 Float64 sampleRatio;
167#endif
168#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
169 /** The audio converter if necessary. NULL if no converter is being used. */
170 AudioConverterRef ConverterRef;
171 /** Callback context for the audio converter. */
172 COREAUDIOCONVCBCTX convCbCtx;
173#endif
174 } In;
175 //struct {} Out;
176 };
177 /** List node for the device's stream list. */
178 RTLISTNODE Node;
179 /** The stream's thread handle for maintaining the audio queue. */
180 RTTHREAD hThread;
181 /** The runloop of the queue thread. */
182 CFRunLoopRef hRunLoop;
183 /** Flag indicating to start a stream's data processing. */
184 bool fRun;
185 /** Whether the stream is in a running (active) state or not.
186 * For playback streams this means that audio data can be (or is being) played,
187 * for capturing streams this means that audio data is being captured (if available). */
188 bool fIsRunning;
189 /** Thread shutdown indicator. */
190 bool volatile fShutdown;
191 /** The actual audio queue being used. */
192 AudioQueueRef hAudioQueue;
193 /** The audio buffers which are used with the above audio queue.
194 * @todo r=bird: Two buffers is a bit to granular, isn't it? I'd think we'd be
195 * better off with a variable buffer count using the device's interval
196 * hint for the size (though we should at least have two buffers ofc). */
197 AudioQueueBufferRef apAudioBuffers[2];
198 /** The acquired (final) audio format for this stream.
199 * @note This what the device requests, we don't alter anything. */
200 AudioStreamBasicDescription BasicStreamDesc;
201 /** The audio unit for this stream. */
202 struct
203 {
204 /** Pointer to the device this audio unit is bound to.
205 * Can be NULL if not bound to a device (anymore). */
206 PCOREAUDIODEVICEDATA pDevice;
207#if 0 /* not used */
208 /** The actual audio unit object. */
209 AudioUnit hAudioUnit;
210 /** Stream description for using with VBox:
211 * - When using this audio unit for input (capturing), this format states
212 * the unit's output format.
213 * - When using this audio unit for output (playback), this format states
214 * the unit's input format. */
215 AudioStreamBasicDescription StreamFmt;
216#endif
217 } Unit;
218 /** Initialization status tracker, actually COREAUDIOINITSTATE.
219 * Used when some of the device parameters or the device itself is changed
220 * during the runtime. */
221 volatile uint32_t enmInitState;
222 /** An internal ring buffer for transferring data from/to the rendering callbacks. */
223 PRTCIRCBUF pCircBuf;
224 /** Critical section for serializing access between thread + callbacks. */
225 RTCRITSECT CritSect;
226} COREAUDIOSTREAM;
227
228
229/**
230 * Instance data for a Core Audio host audio driver.
231 *
232 * @implements PDMIAUDIOCONNECTOR
233 */
234typedef struct DRVHOSTCOREAUDIO
235{
236 /** Pointer to the driver instance structure. */
237 PPDMDRVINS pDrvIns;
238 /** Pointer to host audio interface. */
239 PDMIHOSTAUDIO IHostAudio;
240 /** Current (last reported) device enumeration. */
241 PDMAUDIOHOSTENUM Devices;
242 /** Pointer to the currently used input device in the device enumeration.
243 * Can be NULL if none assigned. */
244 PCOREAUDIODEVICEDATA pDefaultDevIn;
245 /** Pointer to the currently used output device in the device enumeration.
246 * Can be NULL if none assigned. */
247 PCOREAUDIODEVICEDATA pDefaultDevOut;
248 /** Upwards notification interface. */
249 PPDMIHOSTAUDIOPORT pIHostAudioPort;
250 /** Indicates whether we've registered default input device change listener. */
251 bool fRegisteredDefaultInputListener;
252 /** Indicates whether we've registered default output device change listener. */
253 bool fRegisteredDefaultOutputListener;
254 /** Critical section to serialize access. */
255 RTCRITSECT CritSect;
256} DRVHOSTCOREAUDIO;
257
258
259/*********************************************************************************************************************************
260* Global Variables *
261*********************************************************************************************************************************/
262#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
263/** Error code which indicates "End of data" */
264static const OSStatus g_rcCoreAudioConverterEOFDErr = 0x656F6664; /* 'eofd' */
265#endif
266
267
268/*********************************************************************************************************************************
269* Internal Functions *
270*********************************************************************************************************************************/
271static int drvHostAudioCaStreamControlInternal(PCOREAUDIOSTREAM pStreamCA, PDMAUDIOSTREAMCMD enmStreamCmd);
272
273/* DrvHostAudioCoreAudioAuth.mm: */
274DECLHIDDEN(int) coreAudioInputPermissionCheck(void);
275
276
277
278
279static void drvHostAudioCaPrintASBD(const char *pszDesc, const AudioStreamBasicDescription *pASBD)
280{
281 LogRel2(("CoreAudio: %s description:\n", pszDesc));
282 LogRel2(("CoreAudio: Format ID: %RU32 (%c%c%c%c)\n", pASBD->mFormatID,
283 RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID),
284 RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID)));
285 LogRel2(("CoreAudio: Flags: %RU32", pASBD->mFormatFlags));
286 if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat)
287 LogRel2((" Float"));
288 if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian)
289 LogRel2((" BigEndian"));
290 if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger)
291 LogRel2((" SignedInteger"));
292 if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked)
293 LogRel2((" Packed"));
294 if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh)
295 LogRel2((" AlignedHigh"));
296 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved)
297 LogRel2((" NonInterleaved"));
298 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable)
299 LogRel2((" NonMixable"));
300 if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear)
301 LogRel2((" AllClear"));
302 LogRel2(("\n"));
303 LogRel2(("CoreAudio: SampleRate : %RU64.%02u Hz\n",
304 (uint64_t)pASBD->mSampleRate, (unsigned)(pASBD->mSampleRate * 100) % 100));
305 LogRel2(("CoreAudio: ChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame));
306 LogRel2(("CoreAudio: FramesPerPacket : %RU32\n", pASBD->mFramesPerPacket));
307 LogRel2(("CoreAudio: BitsPerChannel : %RU32\n", pASBD->mBitsPerChannel));
308 LogRel2(("CoreAudio: BytesPerFrame : %RU32\n", pASBD->mBytesPerFrame));
309 LogRel2(("CoreAudio: BytesPerPacket : %RU32\n", pASBD->mBytesPerPacket));
310}
311
312
313static void drvHostAudioCaPCMPropsToASBD(PCPDMAUDIOPCMPROPS pProps, AudioStreamBasicDescription *pASBD)
314{
315 AssertPtrReturnVoid(pProps);
316 AssertPtrReturnVoid(pASBD);
317
318 RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription));
319
320 pASBD->mFormatID = kAudioFormatLinearPCM;
321 pASBD->mFormatFlags = kAudioFormatFlagIsPacked;
322 if (pProps->fSigned)
323 pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
324 if (PDMAudioPropsIsBigEndian(pProps))
325 pASBD->mFormatFlags |= kAudioFormatFlagIsBigEndian;
326 pASBD->mSampleRate = PDMAudioPropsHz(pProps);
327 pASBD->mChannelsPerFrame = PDMAudioPropsChannels(pProps);
328 pASBD->mBitsPerChannel = PDMAudioPropsSampleBits(pProps);
329 pASBD->mBytesPerFrame = PDMAudioPropsFrameSize(pProps);
330 pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */
331 pASBD->mBytesPerPacket = PDMAudioPropsFrameSize(pProps) * pASBD->mFramesPerPacket;
332}
333
334
335#if 0 /* unused */
336static int drvHostAudioCaCFStringToCString(const CFStringRef pCFString, char **ppszString)
337{
338 CFIndex cLen = CFStringGetLength(pCFString) + 1;
339 char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char));
340 if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8))
341 {
342 RTMemFree(pszResult);
343 return VERR_NOT_FOUND;
344 }
345
346 *ppszString = pszResult;
347 return VINF_SUCCESS;
348}
349
350static AudioDeviceID drvHostAudioCaDeviceUIDtoID(const char* pszUID)
351{
352 /* Create a CFString out of our CString. */
353 CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman);
354
355 /* Fill the translation structure. */
356 AudioDeviceID deviceID;
357
358 AudioValueTranslation translation;
359 translation.mInputData = &strUID;
360 translation.mInputDataSize = sizeof(CFStringRef);
361 translation.mOutputData = &deviceID;
362 translation.mOutputDataSize = sizeof(AudioDeviceID);
363
364 /* Fetch the translation from the UID to the device ID. */
365 AudioObjectPropertyAddress PropAddr =
366 {
367 kAudioHardwarePropertyDeviceForUID,
368 kAudioObjectPropertyScopeGlobal,
369 kAudioObjectPropertyElementMaster
370 };
371
372 UInt32 uSize = sizeof(AudioValueTranslation);
373 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr, 0, NULL, &uSize, &translation);
374
375 /* Release the temporary CFString */
376 CFRelease(strUID);
377
378 if (RT_LIKELY(err == noErr))
379 return deviceID;
380
381 /* Return the unknown device on error. */
382 return kAudioDeviceUnknown;
383}
384#endif /* unused */
385
386
387#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
388
389/**
390 * Initializes a conversion callback context.
391 *
392 * @return IPRT status code.
393 * @param pConvCbCtx Conversion callback context to initialize.
394 * @param pStream Pointer to stream to use.
395 * @param pASBDSrc Input (source) stream description to use.
396 * @param pASBDDst Output (destination) stream description to use.
397 */
398static int drvHostAudioCaInitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx, PCOREAUDIOSTREAM pStream,
399 AudioStreamBasicDescription *pASBDSrc, AudioStreamBasicDescription *pASBDDst)
400{
401 AssertPtrReturn(pConvCbCtx, VERR_INVALID_POINTER);
402 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
403 AssertPtrReturn(pASBDSrc, VERR_INVALID_POINTER);
404 AssertPtrReturn(pASBDDst, VERR_INVALID_POINTER);
405
406# ifdef DEBUG
407 drvHostAudioCaPrintASBD("CbCtx: Src", pASBDSrc);
408 drvHostAudioCaPrintASBD("CbCtx: Dst", pASBDDst);
409# endif
410
411 pConvCbCtx->pStream = pStream;
412
413 memcpy(&pConvCbCtx->asbdSrc, pASBDSrc, sizeof(AudioStreamBasicDescription));
414 memcpy(&pConvCbCtx->asbdDst, pASBDDst, sizeof(AudioStreamBasicDescription));
415
416 pConvCbCtx->pBufLstSrc = NULL;
417 pConvCbCtx->cErrors = 0;
418
419 return VINF_SUCCESS;
420}
421
422
423/**
424 * Uninitializes a conversion callback context.
425 *
426 * @return IPRT status code.
427 * @param pConvCbCtx Conversion callback context to uninitialize.
428 */
429static void drvHostAudioCaUninitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx)
430{
431 AssertPtrReturnVoid(pConvCbCtx);
432
433 pConvCbCtx->pStream = NULL;
434
435 RT_ZERO(pConvCbCtx->asbdSrc);
436 RT_ZERO(pConvCbCtx->asbdDst);
437
438 pConvCbCtx->pBufLstSrc = NULL;
439 pConvCbCtx->cErrors = 0;
440}
441
442/* Callback to convert audio input data from one format to another. */
443static OSStatus drvHostAudioCaConverterCb(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets,
444 AudioBufferList *ioData, AudioStreamPacketDescription **ppASPD, void *pvUser)
445{
446 RT_NOREF(inAudioConverter);
447
448 AssertPtrReturn(ioNumberDataPackets, g_rcCoreAudioConverterEOFDErr);
449 AssertPtrReturn(ioData, g_rcCoreAudioConverterEOFDErr);
450
451 PCOREAUDIOCONVCBCTX pConvCbCtx = (PCOREAUDIOCONVCBCTX)pvUser;
452 AssertPtr(pConvCbCtx);
453
454 /* Initialize values. */
455 ioData->mBuffers[0].mNumberChannels = 0;
456 ioData->mBuffers[0].mDataByteSize = 0;
457 ioData->mBuffers[0].mData = NULL;
458
459 if (ppASPD)
460 {
461 Log3Func(("Handling packet description not implemented\n"));
462 }
463 else
464 {
465 /** @todo Check converter ID? */
466
467 /** @todo Handled non-interleaved data by going through the full buffer list,
468 * not only through the first buffer like we do now. */
469 Log3Func(("ioNumberDataPackets=%RU32\n", *ioNumberDataPackets));
470
471 UInt32 cNumberDataPackets = *ioNumberDataPackets;
472 Assert(pConvCbCtx->uPacketIdx + cNumberDataPackets <= pConvCbCtx->uPacketCnt);
473
474 if (cNumberDataPackets)
475 {
476 AssertPtr(pConvCbCtx->pBufLstSrc);
477 Assert(pConvCbCtx->pBufLstSrc->mNumberBuffers == 1); /* Only one buffer for the source supported atm. */
478
479 AudioStreamBasicDescription *pSrcASBD = &pConvCbCtx->asbdSrc;
480 AudioBuffer *pSrcBuf = &pConvCbCtx->pBufLstSrc->mBuffers[0];
481
482 size_t cbOff = pConvCbCtx->uPacketIdx * pSrcASBD->mBytesPerPacket;
483
484 cNumberDataPackets = RT_MIN((pSrcBuf->mDataByteSize - cbOff) / pSrcASBD->mBytesPerPacket,
485 cNumberDataPackets);
486
487 void *pvAvail = (uint8_t *)pSrcBuf->mData + cbOff;
488 size_t cbAvail = RT_MIN(pSrcBuf->mDataByteSize - cbOff, cNumberDataPackets * pSrcASBD->mBytesPerPacket);
489
490 Log3Func(("cNumberDataPackets=%RU32, cbOff=%zu, cbAvail=%zu\n", cNumberDataPackets, cbOff, cbAvail));
491
492 /* Set input data for the converter to use.
493 * Note: For VBR (Variable Bit Rates) or interleaved data handling we need multiple buffers here. */
494 ioData->mNumberBuffers = 1;
495
496 ioData->mBuffers[0].mNumberChannels = pSrcBuf->mNumberChannels;
497 ioData->mBuffers[0].mDataByteSize = cbAvail;
498 ioData->mBuffers[0].mData = pvAvail;
499
500# ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
501 RTFILE fh;
502 int rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm",
503 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
504 if (RT_SUCCESS(rc))
505 {
506 RTFileWrite(fh, pvAvail, cbAvail, NULL);
507 RTFileClose(fh);
508 }
509 else
510 AssertFailed();
511# endif
512 pConvCbCtx->uPacketIdx += cNumberDataPackets;
513 Assert(pConvCbCtx->uPacketIdx <= pConvCbCtx->uPacketCnt);
514
515 *ioNumberDataPackets = cNumberDataPackets;
516 }
517 }
518
519 Log3Func(("%RU32 / %RU32 -> ioNumberDataPackets=%RU32\n",
520 pConvCbCtx->uPacketIdx, pConvCbCtx->uPacketCnt, *ioNumberDataPackets));
521
522 return noErr;
523}
524
525#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */
526
527
528/*********************************************************************************************************************************
529* Device Change Notification Callbacks *
530*********************************************************************************************************************************/
531
532/**
533 * Called when the kAudioDevicePropertyNominalSampleRate or
534 * kAudioDeviceProcessorOverload properties changes on a default device.
535 *
536 * Registered on default devices after device enumeration.
537 * Not sure on which thread/runloop this runs.
538 *
539 * (See AudioObjectPropertyListenerProc in the SDK headers.)
540 */
541static OSStatus drvHostAudioCaDevicePropertyChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
542 const AudioObjectPropertyAddress paAddresses[], void *pvUser)
543{
544 PCOREAUDIODEVICEDATA pDev = (PCOREAUDIODEVICEDATA)pvUser;
545 AssertPtr(pDev);
546 RT_NOREF(pDev, cAddresses, paAddresses);
547
548 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u pDev=%p\n", idObject, idObject, cAddresses, pDev));
549 for (UInt32 idx = 0; idx < cAddresses; idx++)
550 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
551 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
552
553/** @todo r=bird: What's the plan here exactly? */
554 switch (idObject)
555 {
556 case kAudioDeviceProcessorOverload:
557 LogFunc(("Processor overload detected!\n"));
558 break;
559 case kAudioDevicePropertyNominalSampleRate:
560 LogFunc(("kAudioDevicePropertyNominalSampleRate!\n"));
561 break;
562 default:
563 /* Just skip. */
564 break;
565 }
566
567 return noErr;
568}
569
570
571/**
572 * Propagates an audio device status to all its Core Audio streams.
573 *
574 * @param pDev Audio device to propagate status for.
575 * @param enmSts Status to propagate.
576 */
577static void drvHostAudioCaDevicePropagateStatus(PCOREAUDIODEVICEDATA pDev, COREAUDIOINITSTATE enmSts)
578{
579 /* Sanity. */
580 AssertPtr(pDev);
581 AssertPtr(pDev->pDrv);
582
583 LogFlowFunc(("pDev=%p enmSts=%RU32\n", pDev, enmSts));
584
585 PCOREAUDIOSTREAM pStreamCA;
586 RTListForEach(&pDev->lstStreams, pStreamCA, COREAUDIOSTREAM, Node)
587 {
588 LogFlowFunc(("pStreamCA=%p\n", pStreamCA));
589
590 /* We move the reinitialization to the next output event.
591 * This make sure this thread isn't blocked and the
592 * reinitialization is done when necessary only. */
593/** @todo r=bird: This is now extremely bogus, see comment in caller. */
594 ASMAtomicWriteU32(&pStreamCA->enmInitState, enmSts);
595 }
596}
597
598
599/**
600 * Called when the kAudioDevicePropertyDeviceIsAlive property changes on a
601 * default device.
602 *
603 * Registered on default devices after device enumeration.
604 * Not sure on which thread/runloop this runs.
605 *
606 * (See AudioObjectPropertyListenerProc in the SDK headers.)
607 */
608static OSStatus drvHostAudioCaDeviceIsAliveChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
609 const AudioObjectPropertyAddress paAddresses[], void *pvUser)
610{
611 PCOREAUDIODEVICEDATA pDev = (PCOREAUDIODEVICEDATA)pvUser;
612 AssertPtr(pDev);
613 PDRVHOSTCOREAUDIO pThis = pDev->pDrv;
614 AssertPtr(pThis);
615 RT_NOREF(idObject, cAddresses, paAddresses);
616
617 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u pDev=%p\n", idObject, idObject, cAddresses, pDev));
618 for (UInt32 idx = 0; idx < cAddresses; idx++)
619 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
620 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
621
622 int rc = RTCritSectEnter(&pThis->CritSect);
623 AssertRC(rc);
624
625 UInt32 uAlive = 1;
626 UInt32 uSize = sizeof(UInt32);
627
628 AudioObjectPropertyAddress PropAddr =
629 {
630 kAudioDevicePropertyDeviceIsAlive,
631 kAudioObjectPropertyScopeGlobal,
632 kAudioObjectPropertyElementMaster
633 };
634
635 OSStatus err = AudioObjectGetPropertyData(pDev->deviceID, &PropAddr, 0, NULL, &uSize, &uAlive);
636
637 bool fIsDead = false;
638 if (err == kAudioHardwareBadDeviceError)
639 fIsDead = true; /* Unplugged. */
640 else if (err == kAudioHardwareNoError && !RT_BOOL(uAlive))
641 fIsDead = true; /* Something else happened. */
642
643 if (fIsDead)
644 {
645 LogRel2(("CoreAudio: Device '%s' stopped functioning\n", pDev->Core.szName));
646
647 /* Mark device as dead. */
648/** @todo r=bird: This is certifiably insane given how StreamDestroy does absolutely _nothing_ unless the init state is INIT.
649 * The queue thread will be running and trashing random heap if it tries to modify anything in the stream structure. */
650 drvHostAudioCaDevicePropagateStatus(pDev, COREAUDIOINITSTATE_UNINIT);
651 }
652
653 RTCritSectLeave(&pThis->CritSect);
654 return noErr;
655}
656
657
658/**
659 * Called when the default recording or playback device has changed.
660 *
661 * Registered by the constructor. Not sure on which thread/runloop this runs.
662 *
663 * (See AudioObjectPropertyListenerProc in the SDK headers.)
664 */
665static OSStatus drvHostAudioCaDefaultDeviceChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
666 const AudioObjectPropertyAddress *paAddresses, void *pvUser)
667
668{
669 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
670 AssertPtr(pThis);
671 LogFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses));
672 RT_NOREF(idObject);
673
674 //int rc2 = RTCritSectEnter(&pThis->CritSect);
675 //AssertRC(rc2);
676
677 for (UInt32 idxAddress = 0; idxAddress < cAddresses; idxAddress++)
678 {
679 /// @todo r=bird: what's the plan here? PCOREAUDIODEVICEDATA pDev = NULL;
680
681 /*
682 * Check if the default input / output device has been changed.
683 */
684 const AudioObjectPropertyAddress *pProperty = &paAddresses[idxAddress];
685 switch (pProperty->mSelector)
686 {
687 case kAudioHardwarePropertyDefaultInputDevice:
688 LogFlowFunc(("#%u: sel=kAudioHardwarePropertyDefaultInputDevice scope=%#x element=%#x\n",
689 idxAddress, pProperty->mScope, pProperty->mElement));
690 //pDev = pThis->pDefaultDevIn;
691 break;
692
693 case kAudioHardwarePropertyDefaultOutputDevice:
694 LogFlowFunc(("#%u: sel=kAudioHardwarePropertyDefaultOutputDevice scope=%#x element=%#x\n",
695 idxAddress, pProperty->mScope, pProperty->mElement));
696 //pDev = pThis->pDefaultDevOut;
697 break;
698
699 default:
700 LogFlowFunc(("#%u: sel=%#x scope=%#x element=%#x\n",
701 idxAddress, pProperty->mSelector, pProperty->mScope, pProperty->mElement));
702 break;
703 }
704 }
705
706 /* Make sure to leave the critical section before notify higher drivers/devices. */
707 //rc2 = RTCritSectLeave(&pThis->CritSect);
708 //AssertRC(rc2);
709
710 /*
711 * Notify the driver/device above us about possible changes in devices.
712 */
713 if (pThis->pIHostAudioPort)
714 pThis->pIHostAudioPort->pfnNotifyDevicesChanged(pThis->pIHostAudioPort);
715
716 return noErr;
717}
718
719
720/*********************************************************************************************************************************
721* Queue Thread *
722*********************************************************************************************************************************/
723
724/**
725 * Processes output data of a Core Audio stream into an audio queue buffer.
726 *
727 * @param pStreamCA Core Audio stream to process output data for.
728 * @param pAudioBuffer Audio buffer to store data into.
729 */
730static void drvHostAudioCaOutputQueueFillBuffer(PCOREAUDIOSTREAM pStreamCA, AudioQueueBufferRef pAudioBuffer)
731{
732 PRTCIRCBUF pCircBuf = pStreamCA->pCircBuf;
733 AssertPtr(pCircBuf);
734
735 /*
736 * Copy out the data from the circular buffer..
737 */
738 size_t offDst = 0;
739 size_t cbLeft = RTCircBufUsed(pCircBuf);
740 cbLeft = RT_MIN(cbLeft, pAudioBuffer->mAudioDataBytesCapacity);
741 while (cbLeft > 0)
742 {
743 /* Get the next ring buffer block and copy the data from it. */
744 void *pvSrc = NULL;
745 size_t cbSrc = 0;
746 RTCircBufAcquireReadBlock(pCircBuf, cbLeft, &pvSrc, &cbSrc);
747 AssertBreakStmt(cbSrc > 0, RTCircBufReleaseReadBlock(pCircBuf, 0));
748 Assert(cbSrc <= cbLeft);
749
750 memcpy((uint8_t *)pAudioBuffer->mAudioData + offDst, pvSrc, cbSrc);
751
752 /* Advance. */
753 RTCircBufReleaseReadBlock(pCircBuf, cbSrc);
754 offDst += cbSrc;
755 cbLeft -= cbSrc;
756 }
757
758 /*
759 * Zero any remaining buffer space.
760 */
761 if (offDst >= pAudioBuffer->mAudioDataBytesCapacity)
762 {
763 pAudioBuffer->mAudioDataByteSize = offDst;
764 Log3Func(("pStreamCA=%p RTCircBufUsed=%#zx pAudioBuffer=%p cbCapacity=%#zx full\n",
765 pStreamCA, RTCircBufUsed(pCircBuf), offDst));
766 }
767 else
768 {
769 RT_BZERO((uint8_t *)pAudioBuffer->mAudioData + offDst, pAudioBuffer->mAudioDataBytesCapacity - offDst);
770 pAudioBuffer->mAudioDataByteSize = pAudioBuffer->mAudioDataBytesCapacity;
771 LogFunc(("pStreamCA=%p RTCircBufUsed=%#zx pAudioBuffer=%p cbCapacity=%#zx offDst=%#zx (zeroed %#zx bytes)\n", pStreamCA,
772 RTCircBufUsed(pCircBuf), pAudioBuffer->mAudioDataBytesCapacity, offDst, pAudioBuffer->mAudioDataBytesCapacity - offDst));
773 /** @todo overflow statistics */
774 /** @todo do we really need to operate this way here? */
775 }
776}
777
778
779/**
780 * Output audio queue callback.
781 *
782 * Called whenever an audio queue is ready to process more output data.
783 *
784 * @param pvUser User argument.
785 * @param hAudioQueue Audio queue to process output data for.
786 * @param pAudioBuffer Audio buffer to store output data in.
787 *
788 * @thread queue thread.
789 */
790static void drvHostAudioCaOutputQueueCallback(void *pvUser, AudioQueueRef hAudioQueue, AudioQueueBufferRef pAudioBuffer)
791{
792 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
793 AssertPtr(pStreamCA);
794
795 /** @todo r=bird: The locking is probably not really necessary. The
796 * circular buffer can deal with concurrent access. Might help with
797 * some life-cycle issues, but that should be serialized by the
798 * thread destruction. Only would be concurrent calls to
799 * drvHostAudioCaOutputQueueFillBuffer on different threads. */
800 int rc = RTCritSectEnter(&pStreamCA->CritSect);
801 AssertRC(rc);
802
803 drvHostAudioCaOutputQueueFillBuffer(pStreamCA, pAudioBuffer);
804 OSStatus orc = AudioQueueEnqueueBuffer(hAudioQueue, pAudioBuffer, 0, NULL);
805 AssertMsg(orc == noErr, ("%#x (%d)\n", orc, orc)); NOREF(orc);
806
807 rc = RTCritSectLeave(&pStreamCA->CritSect);
808 AssertRC(rc);
809}
810
811
812/**
813 * Processes input data of an audio queue buffer and stores it into a Core Audio stream.
814 *
815 * @param pStreamCA Core Audio stream to store input data into.
816 * @param pAudioBuffer Audio buffer to process input data from.
817 */
818static void drvHostAudioCaInputQueueReadBuffer(PCOREAUDIOSTREAM pStreamCA, AudioQueueBufferRef pAudioBuffer)
819{
820 PRTCIRCBUF pCircBuf = pStreamCA->pCircBuf;
821 AssertPtr(pCircBuf);
822
823 /*
824 * Copy data out of the buffer.
825 */
826 size_t offSrc = 0;
827 size_t cbLeft = RTCircBufFree(pCircBuf);
828 cbLeft = RT_MIN(cbLeft, pAudioBuffer->mAudioDataByteSize);
829 while (cbLeft > 0)
830 {
831 /* Get the next ring buffer block and copy the data into it. */
832 void *pvDst = NULL;
833 size_t cbDst = 0;
834 RTCircBufAcquireWriteBlock(pCircBuf, cbLeft, &pvDst, &cbDst);
835 AssertBreakStmt(cbDst > 0, RTCircBufReleaseWriteBlock(pCircBuf, 0));
836 Assert(cbDst <= cbLeft);
837
838 memcpy(pvDst, (uint8_t const *)pAudioBuffer->mAudioData + offSrc, cbDst);
839
840 /* Advance. */
841 RTCircBufReleaseWriteBlock(pCircBuf, cbDst);
842 offSrc += cbDst;
843 cbLeft -= cbDst;
844 }
845
846 /*
847 * Log and count overflows.
848 */
849 if (offSrc >= pAudioBuffer->mAudioDataByteSize)
850 Log3Func(("pStreamCA=%p RTCircBufUsed=%#zx pAudioBuffer=%p cbData=%#zx/%#zx all\n",
851 pStreamCA, RTCircBufUsed(pCircBuf), pAudioBuffer, offSrc, pAudioBuffer->mAudioDataBytesCapacity));
852 else
853 {
854 LogFunc(("pStreamCA=%p RTCircBufUsed=%#zx pAudioBuffer=%p cbData=%#zx/%#zx overflow copyied %#zx (%#zx left)!\n",
855 pStreamCA, RTCircBufUsed(pCircBuf), pAudioBuffer, pAudioBuffer->mAudioDataByteSize,
856 pAudioBuffer->mAudioDataBytesCapacity, offSrc, pAudioBuffer->mAudioDataBytesCapacity - offSrc));
857 /** @todo statistics counter for overflows */
858 }
859}
860
861
862/**
863 * Input audio queue callback.
864 *
865 * Called whenever input data from the audio queue becomes available.
866 *
867 * @param pvUser User argument.
868 * @param hAudioQueue Audio queue to process input data from.
869 * @param pAudioBuffer Audio buffer to process input data from.
870 * @param pAudioTS Audio timestamp.
871 * @param cPacketDesc Number of packet descriptors.
872 * @param paPacketDesc Array of packet descriptors.
873 */
874static void drvHostAudioCaInputQueueCallback(void *pvUser, AudioQueueRef hAudioQueue, AudioQueueBufferRef pAudioBuffer,
875 const AudioTimeStamp *pAudioTS,
876 UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc)
877{
878 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
879 RT_NOREF(pAudioTS, cPacketDesc, paPacketDesc);
880 AssertPtr(pStreamCA);
881
882 int rc = RTCritSectEnter(&pStreamCA->CritSect);
883 AssertRC(rc);
884
885 drvHostAudioCaInputQueueReadBuffer(pStreamCA, pAudioBuffer);
886 OSStatus orc = AudioQueueEnqueueBuffer(hAudioQueue, pAudioBuffer, 0, NULL);
887 AssertMsg(orc == noErr, ("%#x (%d)\n", orc, orc)); NOREF(orc);
888
889 rc = RTCritSectLeave(&pStreamCA->CritSect);
890 AssertRC(rc);
891}
892
893
894/**
895 * @callback_method_impl{FNRTTHREAD,
896 * Thread for a Core Audio stream's audio queue handling.}
897 *
898 * This thread is required per audio queue to pump data to/from the Core Audio
899 * stream and handling its callbacks.
900 */
901static DECLCALLBACK(int) drvHostAudioCaQueueThread(RTTHREAD hThreadSelf, void *pvUser)
902{
903 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
904 AssertPtr(pStreamCA);
905 const bool fIn = pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN;
906 PCOREAUDIODEVICEDATA const pDev = (PCOREAUDIODEVICEDATA)pStreamCA->Unit.pDevice;
907 CFRunLoopRef const hRunLoop = CFRunLoopGetCurrent();
908 AssertPtr(pDev);
909
910 LogFunc(("Thread started for pStreamCA=%p fIn=%RTbool\n", pStreamCA, fIn));
911
912 /*
913 * Create audio queue.
914 */
915 int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
916 OSStatus orc;
917 if (fIn)
918 orc = AudioQueueNewInput(&pStreamCA->BasicStreamDesc, drvHostAudioCaInputQueueCallback, pStreamCA /* pvData */,
919 CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pStreamCA->hAudioQueue);
920 else
921 orc = AudioQueueNewOutput(&pStreamCA->BasicStreamDesc, drvHostAudioCaOutputQueueCallback, pStreamCA /* pvData */,
922 CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pStreamCA->hAudioQueue);
923 if (orc == noErr)
924 {
925 /*
926 * Assign device to the queue.
927 */
928 UInt32 uSize = sizeof(pDev->UUID);
929 orc = AudioQueueSetProperty(pStreamCA->hAudioQueue, kAudioQueueProperty_CurrentDevice, &pDev->UUID, uSize);
930 if (orc == noErr)
931 {
932 /*
933 * Allocate audio buffers.
934 */
935 const size_t cbBuf = PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, pStreamCA->Cfg.Backend.cFramesPeriod);
936 size_t iBuf;
937 for (iBuf = 0; orc == noErr && iBuf < RT_ELEMENTS(pStreamCA->apAudioBuffers); iBuf++)
938 orc = AudioQueueAllocateBuffer(pStreamCA->hAudioQueue, cbBuf, &pStreamCA->apAudioBuffers[iBuf]);
939 if (orc == noErr)
940 {
941 /*
942 * Get a reference to our runloop so it can be stopped then signal
943 * our creator to say that we're done. The runloop reference is the
944 * success indicator.
945 */
946 pStreamCA->hRunLoop = hRunLoop;
947 CFRetain(hRunLoop);
948 RTThreadUserSignal(hThreadSelf);
949
950 /*
951 * The main loop.
952 */
953 while (!ASMAtomicReadBool(&pStreamCA->fShutdown))
954 {
955 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 30.0 /*sec*/, 1);
956 }
957
958 AudioQueueStop(pStreamCA->hAudioQueue, fIn ? 1 : 0);
959 rc = VINF_SUCCESS;
960 }
961 else
962 LogRel(("CoreAudio: Failed to allocate %#x byte queue buffer #%u: %#x (%d)\n", cbBuf, iBuf, orc, orc));
963
964 while (iBuf-- > 0)
965 {
966 AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->apAudioBuffers[iBuf]);
967 pStreamCA->apAudioBuffers[iBuf] = NULL;
968 }
969 }
970 else
971 LogRel(("CoreAudio: Failed to associate device with queue: %#x (%d)\n", orc, orc));
972
973 AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
974 }
975 else
976 LogRel(("CoreAudio: Failed to create audio queue: %#x (%d)\n", orc, orc));
977
978
979 RTThreadUserSignal(hThreadSelf);
980 LogFunc(("Thread ended for pStreamCA=%p fIn=%RTbool: rc=%Rrc (orc=%#x/%d)\n", pStreamCA, fIn, rc, orc, orc));
981 return rc;
982}
983
984
985/**
986 * Invalidates a Core Audio stream's audio queue.
987 *
988 * @returns IPRT status code.
989 * @param pStreamCA Core Audio stream to invalidate its queue for.
990 *
991 * @todo r=bird: Which use of the word 'invalidate' is this?
992 */
993static int drvHostAudioCaStreamInvalidateQueue(PCOREAUDIOSTREAM pStreamCA)
994{
995 int rc = VINF_SUCCESS;
996
997 Log3Func(("pStreamCA=%p\n", pStreamCA));
998
999 for (size_t i = 0; i < RT_ELEMENTS(pStreamCA->apAudioBuffers); i++)
1000 {
1001 AudioQueueBufferRef pBuf = pStreamCA->apAudioBuffers[i];
1002 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN) /** @todo to this outside. */
1003 {
1004 drvHostAudioCaInputQueueReadBuffer(pStreamCA, pBuf);
1005 AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDescs*/, NULL /*inPacketDescs*/);
1006 }
1007 else
1008 {
1009 Assert(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
1010 drvHostAudioCaOutputQueueFillBuffer(pStreamCA, pBuf);
1011 if (pBuf->mAudioDataByteSize) /** @todo r=bird: pointless. Always set to the capacity. */
1012 AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDescs*/, NULL /*inPacketDescs*/);
1013 }
1014 }
1015
1016 return rc;
1017}
1018
1019
1020/*********************************************************************************************************************************
1021* PDMIHOSTAUDIO *
1022*********************************************************************************************************************************/
1023
1024/**
1025 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
1026 */
1027static DECLCALLBACK(int) drvHostAudioCaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
1028{
1029 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1030 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
1031
1032 /*
1033 * Fill in the config structure.
1034 */
1035 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio");
1036 pBackendCfg->cbStream = sizeof(COREAUDIOSTREAM);
1037 pBackendCfg->fFlags = 0;
1038 /* For Core Audio we provide one stream per device for now. */
1039 pBackendCfg->cMaxStreamsIn = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_IN);
1040 pBackendCfg->cMaxStreamsOut = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_OUT);
1041
1042 LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS));
1043 return VINF_SUCCESS;
1044}
1045
1046
1047/**
1048 * Initializes a Core Audio-specific device data structure.
1049 *
1050 * @returns IPRT status code.
1051 * @param pDevData Device data structure to initialize.
1052 * @param deviceID Core Audio device ID to assign this structure to.
1053 * @param fIsInput Whether this is an input device or not.
1054 * @param pDrv Driver instance to use.
1055 */
1056static void drvHostAudioCaDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID,
1057 bool fIsInput, PDRVHOSTCOREAUDIO pDrv)
1058{
1059 AssertPtrReturnVoid(pDevData);
1060 AssertPtrReturnVoid(pDrv);
1061
1062 pDevData->deviceID = deviceID;
1063 pDevData->pDrv = pDrv;
1064
1065 /* Get the device UUID. */
1066 AudioObjectPropertyAddress PropAddrDevUUID =
1067 {
1068 kAudioDevicePropertyDeviceUID,
1069 fIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
1070 kAudioObjectPropertyElementMaster
1071 };
1072 UInt32 uSize = sizeof(pDevData->UUID);
1073 OSStatus err = AudioObjectGetPropertyData(pDevData->deviceID, &PropAddrDevUUID, 0, NULL, &uSize, &pDevData->UUID);
1074 if (err != noErr)
1075 LogRel(("CoreAudio: Failed to retrieve device UUID for device %RU32 (%RI32)\n", deviceID, err));
1076
1077 RTListInit(&pDevData->lstStreams);
1078}
1079
1080
1081/**
1082 * Does a (re-)enumeration of the host's playback + recording devices.
1083 *
1084 * @todo No, it doesn't do playback & recording, it does only what @a enmUsage
1085 * says.
1086 *
1087 * @return IPRT status code.
1088 * @param pThis Host audio driver instance.
1089 * @param enmUsage Which devices to enumerate.
1090 * @param pDevEnm Where to store the enumerated devices.
1091 */
1092static int drvHostAudioCaDevicesEnumerate(PDRVHOSTCOREAUDIO pThis, PDMAUDIODIR enmUsage, PPDMAUDIOHOSTENUM pDevEnm)
1093{
1094 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1095 AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER);
1096
1097 int rc = VINF_SUCCESS;
1098
1099 do /* (this is not a loop, just a device for avoid gotos while trying not to shoot oneself in the foot too badly.) */
1100 {
1101 /*
1102 * First get the device ID of the default device.
1103 */
1104 AudioDeviceID defaultDeviceID = kAudioDeviceUnknown;
1105 AudioObjectPropertyAddress PropAddrDefaultDev =
1106 {
1107 enmUsage == PDMAUDIODIR_IN ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice,
1108 kAudioObjectPropertyScopeGlobal,
1109 kAudioObjectPropertyElementMaster
1110 };
1111 UInt32 uSize = sizeof(defaultDeviceID);
1112 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddrDefaultDev, 0, NULL, &uSize, &defaultDeviceID);
1113 if (err != noErr)
1114 {
1115 LogRel(("CoreAudio: Unable to determine default %s device (%RI32)\n",
1116 enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback", err));
1117 return VERR_NOT_FOUND;
1118 }
1119
1120 if (defaultDeviceID == kAudioDeviceUnknown)
1121 {
1122 LogFunc(("No default %s device found\n", enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback"));
1123 /* Keep going. */
1124 }
1125
1126 /*
1127 * Get a list of all audio devices.
1128 */
1129 AudioObjectPropertyAddress PropAddrDevList =
1130 {
1131 kAudioHardwarePropertyDevices,
1132 kAudioObjectPropertyScopeGlobal,
1133 kAudioObjectPropertyElementMaster
1134 };
1135
1136 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &PropAddrDevList, 0, NULL, &uSize);
1137 if (err != kAudioHardwareNoError)
1138 break;
1139
1140 AudioDeviceID *pDevIDs = (AudioDeviceID *)alloca(uSize);
1141 if (pDevIDs == NULL)
1142 break;
1143
1144 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddrDevList, 0, NULL, &uSize, pDevIDs);
1145 if (err != kAudioHardwareNoError)
1146 break;
1147
1148 PDMAudioHostEnumInit(pDevEnm);
1149
1150 UInt16 cDevices = uSize / sizeof(AudioDeviceID);
1151
1152 /*
1153 * Try get details on each device and try add them to the enumeration result.
1154 */
1155 PCOREAUDIODEVICEDATA pDev = NULL;
1156 for (UInt16 i = 0; i < cDevices; i++)
1157 {
1158 if (pDev) /* Some (skipped) device to clean up first? */
1159 PDMAudioHostDevFree(&pDev->Core);
1160
1161 pDev = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDev));
1162 if (!pDev)
1163 {
1164 rc = VERR_NO_MEMORY;
1165 break;
1166 }
1167
1168 /* Set usage. */
1169 pDev->Core.enmUsage = enmUsage;
1170
1171 /* Init backend-specific device data. */
1172 drvHostAudioCaDeviceDataInit(pDev, pDevIDs[i], enmUsage == PDMAUDIODIR_IN, pThis);
1173
1174 /* Check if the device is valid. */
1175 AudioDeviceID curDevID = pDev->deviceID;
1176
1177 /* Is the device the default device? */
1178 if (curDevID == defaultDeviceID)
1179 pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT;
1180
1181 AudioObjectPropertyAddress PropAddrCfg =
1182 {
1183 kAudioDevicePropertyStreamConfiguration,
1184 enmUsage == PDMAUDIODIR_IN ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
1185 kAudioObjectPropertyElementMaster
1186 };
1187 err = AudioObjectGetPropertyDataSize(curDevID, &PropAddrCfg, 0, NULL, &uSize);
1188 if (err != noErr)
1189 continue;
1190
1191 AudioBufferList *pBufList = (AudioBufferList *)RTMemAlloc(uSize);
1192 if (!pBufList)
1193 continue;
1194
1195 err = AudioObjectGetPropertyData(curDevID, &PropAddrCfg, 0, NULL, &uSize, pBufList);
1196 if (err == noErr)
1197 {
1198 for (UInt32 a = 0; a < pBufList->mNumberBuffers; a++)
1199 {
1200 if (enmUsage == PDMAUDIODIR_IN)
1201 pDev->Core.cMaxInputChannels += pBufList->mBuffers[a].mNumberChannels;
1202 else if (enmUsage == PDMAUDIODIR_OUT)
1203 pDev->Core.cMaxOutputChannels += pBufList->mBuffers[a].mNumberChannels;
1204 }
1205 }
1206
1207 RTMemFree(pBufList);
1208 pBufList = NULL;
1209
1210 /* Check if the device is valid, e.g. has any input/output channels according to its usage. */
1211 if ( enmUsage == PDMAUDIODIR_IN
1212 && !pDev->Core.cMaxInputChannels)
1213 continue;
1214 if ( enmUsage == PDMAUDIODIR_OUT
1215 && !pDev->Core.cMaxOutputChannels)
1216 continue;
1217
1218 /* Resolve the device's name. */
1219 AudioObjectPropertyAddress PropAddrName =
1220 {
1221 kAudioObjectPropertyName,
1222 enmUsage == PDMAUDIODIR_IN ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
1223 kAudioObjectPropertyElementMaster
1224 };
1225 uSize = sizeof(CFStringRef);
1226 CFStringRef pcfstrName = NULL;
1227
1228 err = AudioObjectGetPropertyData(curDevID, &PropAddrName, 0, NULL, &uSize, &pcfstrName);
1229 if (err != kAudioHardwareNoError)
1230 continue;
1231
1232 CFIndex cbName = CFStringGetMaximumSizeForEncoding(CFStringGetLength(pcfstrName), kCFStringEncodingUTF8) + 1;
1233 if (cbName)
1234 {
1235 char *pszName = (char *)RTStrAlloc(cbName);
1236 if ( pszName
1237 && CFStringGetCString(pcfstrName, pszName, cbName, kCFStringEncodingUTF8))
1238 RTStrCopy(pDev->Core.szName, sizeof(pDev->Core.szName), pszName);
1239
1240 LogFunc(("Device '%s': %RU32\n", pszName, curDevID));
1241
1242 if (pszName)
1243 {
1244 RTStrFree(pszName);
1245 pszName = NULL;
1246 }
1247 }
1248
1249 CFRelease(pcfstrName);
1250
1251 /* Check if the device is alive for the intended usage. */
1252 AudioObjectPropertyAddress PropAddrAlive =
1253 {
1254 kAudioDevicePropertyDeviceIsAlive,
1255 enmUsage == PDMAUDIODIR_IN ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
1256 kAudioObjectPropertyElementMaster
1257 };
1258
1259 UInt32 uAlive = 0;
1260 uSize = sizeof(uAlive);
1261
1262 err = AudioObjectGetPropertyData(curDevID, &PropAddrAlive, 0, NULL, &uSize, &uAlive);
1263 if ( (err == noErr)
1264 && !uAlive)
1265 {
1266 pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD;
1267 }
1268
1269 /* Check if the device is being hogged by someone else. */
1270 AudioObjectPropertyAddress PropAddrHogged =
1271 {
1272 kAudioDevicePropertyHogMode,
1273 kAudioObjectPropertyScopeGlobal,
1274 kAudioObjectPropertyElementMaster
1275 };
1276
1277 pid_t pid = 0;
1278 uSize = sizeof(pid);
1279
1280 err = AudioObjectGetPropertyData(curDevID, &PropAddrHogged, 0, NULL, &uSize, &pid);
1281 if ( (err == noErr)
1282 && (pid != -1))
1283 {
1284 pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_LOCKED;
1285 }
1286
1287 /* Add the device to the enumeration. */
1288 PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
1289
1290 /* NULL device pointer because it's now part of the device enumeration. */
1291 pDev = NULL;
1292 }
1293
1294 if (RT_FAILURE(rc))
1295 {
1296 PDMAudioHostDevFree(&pDev->Core);
1297 pDev = NULL;
1298 }
1299
1300 } while (0);
1301
1302 if (RT_SUCCESS(rc))
1303 {
1304#ifdef LOG_ENABLED
1305 LogFunc(("Devices for pDevEnm=%p, enmUsage=%RU32:\n", pDevEnm, enmUsage));
1306 PDMAudioHostEnumLog(pDevEnm, "Core Audio");
1307#endif
1308 }
1309 else
1310 PDMAudioHostEnumDelete(pDevEnm);
1311
1312 LogFlowFuncLeaveRC(rc);
1313 return rc;
1314}
1315
1316
1317/**
1318 * Checks if an audio device with a specific device ID is in the given device
1319 * enumeration or not.
1320 *
1321 * @retval true if the node is the last element in the list.
1322 * @retval false otherwise.
1323 *
1324 * @param pEnmSrc Device enumeration to search device ID in.
1325 * @param deviceID Device ID to search.
1326 */
1327static bool drvHostAudioCaDevicesHasDevice(PPDMAUDIOHOSTENUM pEnmSrc, AudioDeviceID deviceID)
1328{
1329 PCOREAUDIODEVICEDATA pDevSrc;
1330 RTListForEach(&pEnmSrc->LstDevices, pDevSrc, COREAUDIODEVICEDATA, Core.ListEntry)
1331 {
1332 if (pDevSrc->deviceID == deviceID)
1333 return true;
1334 }
1335
1336 return false;
1337}
1338
1339
1340/**
1341 * Enumerates all host devices and builds a final device enumeration list, consisting
1342 * of (duplex) input and output devices.
1343 *
1344 * @return IPRT status code.
1345 * @param pThis Host audio driver instance.
1346 * @param pEnmDst Where to store the device enumeration list.
1347 */
1348static int drvHostAudioCaDevicesEnumerateAll(PDRVHOSTCOREAUDIO pThis, PPDMAUDIOHOSTENUM pEnmDst)
1349{
1350 PDMAUDIOHOSTENUM devEnmIn;
1351 int rc = drvHostAudioCaDevicesEnumerate(pThis, PDMAUDIODIR_IN, &devEnmIn);
1352 if (RT_SUCCESS(rc))
1353 {
1354 PDMAUDIOHOSTENUM devEnmOut;
1355 rc = drvHostAudioCaDevicesEnumerate(pThis, PDMAUDIODIR_OUT, &devEnmOut);
1356 if (RT_SUCCESS(rc))
1357 {
1358
1359/** @todo r=bird: This is an awfully complicated and inefficient way of doing
1360 * it. Here you could just merge the two list (walk one, remove duplicates
1361 * from the other one) and skip all that duplication.
1362 *
1363 * Howerver, drvHostAudioCaDevicesEnumerate gets the device list twice, which is
1364 * a complete waste of time. You could easily do all the work in
1365 * drvHostAudioCaDevicesEnumerate by just querying the IDs of both default
1366 * devices we're interested in, saving the merging extra allocations and
1367 * extra allocation. */
1368
1369 /*
1370 * Build up the final device enumeration, based on the input and output device lists
1371 * just enumerated.
1372 *
1373 * Also make sure to handle duplex devices, that is, devices which act as input and output
1374 * at the same time.
1375 */
1376 PDMAudioHostEnumInit(pEnmDst);
1377 PCOREAUDIODEVICEDATA pDevSrcIn;
1378 RTListForEach(&devEnmIn.LstDevices, pDevSrcIn, COREAUDIODEVICEDATA, Core.ListEntry)
1379 {
1380 PCOREAUDIODEVICEDATA pDevDst = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDevDst));
1381 if (!pDevDst)
1382 {
1383 rc = VERR_NO_MEMORY;
1384 break;
1385 }
1386
1387 drvHostAudioCaDeviceDataInit(pDevDst, pDevSrcIn->deviceID, true /* fIsInput */, pThis);
1388
1389 RTStrCopy(pDevDst->Core.szName, sizeof(pDevDst->Core.szName), pDevSrcIn->Core.szName);
1390
1391 pDevDst->Core.enmUsage = PDMAUDIODIR_IN; /* Input device by default (simplex). */
1392 pDevDst->Core.cMaxInputChannels = pDevSrcIn->Core.cMaxInputChannels;
1393
1394 /* Handle flags. */
1395 if (pDevSrcIn->Core.fFlags & PDMAUDIOHOSTDEV_F_DEFAULT)
1396 pDevDst->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT;
1397 /** @todo Handle hot plugging? */
1398
1399 /*
1400 * Now search through the list of all found output devices and check if we found
1401 * an output device with the same device ID as the currently handled input device.
1402 *
1403 * If found, this means we have to treat that device as a duplex device then.
1404 */
1405 PCOREAUDIODEVICEDATA pDevSrcOut;
1406 RTListForEach(&devEnmOut.LstDevices, pDevSrcOut, COREAUDIODEVICEDATA, Core.ListEntry)
1407 {
1408 if (pDevSrcIn->deviceID == pDevSrcOut->deviceID)
1409 {
1410 pDevDst->Core.enmUsage = PDMAUDIODIR_DUPLEX;
1411 pDevDst->Core.cMaxOutputChannels = pDevSrcOut->Core.cMaxOutputChannels;
1412
1413 if (pDevSrcOut->Core.fFlags & PDMAUDIOHOSTDEV_F_DEFAULT)
1414 pDevDst->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT;
1415 break;
1416 }
1417 }
1418
1419 if (RT_SUCCESS(rc))
1420 PDMAudioHostEnumAppend(pEnmDst, &pDevDst->Core);
1421 else
1422 {
1423 PDMAudioHostDevFree(&pDevDst->Core);
1424 pDevDst = NULL;
1425 }
1426 }
1427
1428 if (RT_SUCCESS(rc))
1429 {
1430 /*
1431 * As a last step, add all remaining output devices which have not been handled in the loop above,
1432 * that is, all output devices which operate in simplex mode.
1433 */
1434 PCOREAUDIODEVICEDATA pDevSrcOut;
1435 RTListForEach(&devEnmOut.LstDevices, pDevSrcOut, COREAUDIODEVICEDATA, Core.ListEntry)
1436 {
1437 if (drvHostAudioCaDevicesHasDevice(pEnmDst, pDevSrcOut->deviceID))
1438 continue; /* Already in our list, skip. */
1439
1440 PCOREAUDIODEVICEDATA pDevDst = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDevDst));
1441 if (!pDevDst)
1442 {
1443 rc = VERR_NO_MEMORY;
1444 break;
1445 }
1446
1447 drvHostAudioCaDeviceDataInit(pDevDst, pDevSrcOut->deviceID, false /* fIsInput */, pThis);
1448
1449 RTStrCopy(pDevDst->Core.szName, sizeof(pDevDst->Core.szName), pDevSrcOut->Core.szName);
1450
1451 pDevDst->Core.enmUsage = PDMAUDIODIR_OUT;
1452 pDevDst->Core.cMaxOutputChannels = pDevSrcOut->Core.cMaxOutputChannels;
1453
1454 pDevDst->deviceID = pDevSrcOut->deviceID;
1455
1456 /* Handle flags. */
1457 if (pDevSrcOut->Core.fFlags & PDMAUDIOHOSTDEV_F_DEFAULT)
1458 pDevDst->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT;
1459 /** @todo Handle hot plugging? */
1460
1461 PDMAudioHostEnumAppend(pEnmDst, &pDevDst->Core);
1462 }
1463 }
1464
1465 if (RT_FAILURE(rc))
1466 PDMAudioHostEnumDelete(pEnmDst);
1467
1468 PDMAudioHostEnumDelete(&devEnmOut);
1469 }
1470
1471 PDMAudioHostEnumDelete(&devEnmIn);
1472 }
1473
1474#ifdef LOG_ENABLED
1475 if (RT_SUCCESS(rc))
1476 PDMAudioHostEnumLog(pEnmDst, "Core Audio (Final)");
1477#endif
1478
1479 LogFlowFuncLeaveRC(rc);
1480 return rc;
1481}
1482
1483
1484/**
1485 * Registers callbacks for a specific Core Audio device.
1486 *
1487 * @return IPRT status code.
1488 * @param pThis Host audio driver instance.
1489 * @param pDev Audio device to use for the registered callbacks.
1490 */
1491static int drvHostAudioCaDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PCOREAUDIODEVICEDATA pDev)
1492{
1493 RT_NOREF(pThis);
1494
1495 AudioDeviceID deviceID = kAudioDeviceUnknown;
1496 Assert(pDev && pDev->Core.cbSelf == sizeof(*pDev));
1497 if (pDev && pDev->Core.cbSelf == sizeof(*pDev)) /* paranoia or actually needed? */
1498 deviceID = pDev->deviceID;
1499
1500 if (deviceID != kAudioDeviceUnknown)
1501 {
1502 LogFunc(("deviceID=%RU32\n", deviceID));
1503
1504 /*
1505 * Register device callbacks.
1506 */
1507 AudioObjectPropertyAddress PropAddr =
1508 {
1509 kAudioDevicePropertyDeviceIsAlive,
1510 kAudioObjectPropertyScopeGlobal,
1511 kAudioObjectPropertyElementMaster
1512 };
1513 OSStatus err = AudioObjectAddPropertyListener(deviceID, &PropAddr,
1514 drvHostAudioCaDeviceIsAliveChangedCallback, pDev /*pvUser*/);
1515 if ( err != noErr
1516 && err != kAudioHardwareIllegalOperationError)
1517 LogRel(("CoreAudio: Failed to add the recording device state changed listener (%RI32)\n", err));
1518
1519 PropAddr.mSelector = kAudioDeviceProcessorOverload;
1520 PropAddr.mScope = kAudioUnitScope_Global;
1521 err = AudioObjectAddPropertyListener(deviceID, &PropAddr, drvHostAudioCaDevicePropertyChangedCallback, pDev /* pvUser */);
1522 if (err != noErr)
1523 LogRel(("CoreAudio: Failed to register processor overload listener (%RI32)\n", err));
1524
1525 PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
1526 PropAddr.mScope = kAudioUnitScope_Global;
1527 err = AudioObjectAddPropertyListener(deviceID, &PropAddr, drvHostAudioCaDevicePropertyChangedCallback, pDev /* pvUser */);
1528 if (err != noErr)
1529 LogRel(("CoreAudio: Failed to register sample rate changed listener (%RI32)\n", err));
1530 }
1531
1532 return VINF_SUCCESS;
1533}
1534
1535
1536/**
1537 * Unregisters all formerly registered callbacks of a Core Audio device again.
1538 *
1539 * @return IPRT status code.
1540 * @param pThis Host audio driver instance.
1541 * @param pDev Audio device to use for the registered callbacks.
1542 */
1543static int drvHostAudioCaDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PCOREAUDIODEVICEDATA pDev)
1544{
1545 RT_NOREF(pThis);
1546
1547 AudioDeviceID deviceID = kAudioDeviceUnknown;
1548 Assert(pDev && pDev->Core.cbSelf == sizeof(*pDev));
1549 if (pDev && pDev->Core.cbSelf == sizeof(*pDev)) /* paranoia or actually needed? */
1550 deviceID = pDev->deviceID;
1551
1552 if (deviceID != kAudioDeviceUnknown)
1553 {
1554 LogFunc(("deviceID=%RU32\n", deviceID));
1555
1556 /*
1557 * Unregister per-device callbacks.
1558 */
1559 AudioObjectPropertyAddress PropAddr =
1560 {
1561 kAudioDeviceProcessorOverload,
1562 kAudioObjectPropertyScopeGlobal,
1563 kAudioObjectPropertyElementMaster
1564 };
1565 OSStatus err = AudioObjectRemovePropertyListener(deviceID, &PropAddr, drvHostAudioCaDevicePropertyChangedCallback, pDev /* pvUser */);
1566 if ( err != noErr
1567 && err != kAudioHardwareBadObjectError)
1568 LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%RI32)\n", err));
1569
1570 PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
1571 err = AudioObjectRemovePropertyListener(deviceID, &PropAddr, drvHostAudioCaDevicePropertyChangedCallback, pDev /* pvUser */);
1572 if ( err != noErr
1573 && err != kAudioHardwareBadObjectError)
1574 LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%RI32)\n", err));
1575
1576 PropAddr.mSelector = kAudioDevicePropertyDeviceIsAlive;
1577 err = AudioObjectRemovePropertyListener(deviceID, &PropAddr, drvHostAudioCaDeviceIsAliveChangedCallback, pDev /* pvUser */);
1578 if ( err != noErr
1579 && err != kAudioHardwareBadObjectError)
1580 LogRel(("CoreAudio: Failed to remove the device alive listener (%RI32)\n", err));
1581 }
1582
1583 return VINF_SUCCESS;
1584}
1585
1586
1587/**
1588 * Enumerates all available host audio devices internally.
1589 *
1590 * @returns IPRT status code.
1591 * @param pThis Host audio driver instance.
1592 */
1593static int drvHostAudioCaEnumerateDevices(PDRVHOSTCOREAUDIO pThis)
1594{
1595 LogFlowFuncEnter();
1596
1597 /*
1598 * Unregister old default devices, if any.
1599 */
1600 if (pThis->pDefaultDevIn)
1601 {
1602 drvHostAudioCaDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevIn);
1603 pThis->pDefaultDevIn = NULL;
1604 }
1605
1606 if (pThis->pDefaultDevOut)
1607 {
1608 drvHostAudioCaDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevOut);
1609 pThis->pDefaultDevOut = NULL;
1610 }
1611
1612 /* Remove old / stale device entries. */
1613 PDMAudioHostEnumDelete(&pThis->Devices);
1614
1615 /* Enumerate all devices internally. */
1616 int rc = drvHostAudioCaDevicesEnumerateAll(pThis, &pThis->Devices);
1617 if (RT_SUCCESS(rc))
1618 {
1619 /*
1620 * Default input device.
1621 */
1622 pThis->pDefaultDevIn = (PCOREAUDIODEVICEDATA)PDMAudioHostEnumGetDefault(&pThis->Devices, PDMAUDIODIR_IN);
1623 if (pThis->pDefaultDevIn)
1624 {
1625 LogRel2(("CoreAudio: Default capturing device is '%s'\n", pThis->pDefaultDevIn->Core.szName));
1626 LogFunc(("pDefaultDevIn=%p, ID=%RU32\n", pThis->pDefaultDevIn, pThis->pDefaultDevIn->deviceID));
1627 rc = drvHostAudioCaDeviceRegisterCallbacks(pThis, pThis->pDefaultDevIn);
1628 }
1629 else
1630 LogRel2(("CoreAudio: No default capturing device found\n"));
1631
1632 /*
1633 * Default output device.
1634 */
1635 pThis->pDefaultDevOut = (PCOREAUDIODEVICEDATA)PDMAudioHostEnumGetDefault(&pThis->Devices, PDMAUDIODIR_OUT);
1636 if (pThis->pDefaultDevOut)
1637 {
1638 LogRel2(("CoreAudio: Default playback device is '%s'\n", pThis->pDefaultDevOut->Core.szName));
1639 LogFunc(("pDefaultDevOut=%p, ID=%RU32\n", pThis->pDefaultDevOut, pThis->pDefaultDevOut->deviceID));
1640 rc = drvHostAudioCaDeviceRegisterCallbacks(pThis, pThis->pDefaultDevOut);
1641 }
1642 else
1643 LogRel2(("CoreAudio: No default playback device found\n"));
1644 }
1645
1646 LogFunc(("Returning %Rrc\n", rc));
1647 return rc;
1648}
1649
1650
1651/**
1652 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
1653 */
1654static DECLCALLBACK(int) drvHostAudioCaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
1655{
1656 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1657 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
1658
1659 PDMAudioHostEnumInit(pDeviceEnum);
1660
1661 /*
1662 * We update the enumeration associated with pThis.
1663 */
1664 int rc = RTCritSectEnter(&pThis->CritSect);
1665 if (RT_SUCCESS(rc))
1666 {
1667 rc = drvHostAudioCaEnumerateDevices(pThis);
1668 if (RT_SUCCESS(rc))
1669 {
1670 /*
1671 * Return a copy with only PDMAUDIOHOSTDEV and none of the extra
1672 * bits in COREAUDIODEVICEDATA.
1673 */
1674 rc = PDMAudioHostEnumCopy(pDeviceEnum, &pThis->Devices, PDMAUDIODIR_INVALID /*all*/, true /*fOnlyCoreData*/);
1675 if (RT_FAILURE(rc))
1676 PDMAudioHostEnumDelete(pDeviceEnum);
1677 }
1678
1679 RTCritSectLeave(&pThis->CritSect);
1680 }
1681
1682 LogFlowFunc(("returns %Rrc\n", rc));
1683 return rc;
1684}
1685
1686
1687/**
1688 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1689 */
1690static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAudioCaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1691{
1692 RT_NOREF(pInterface, enmDir);
1693 return PDMAUDIOBACKENDSTS_RUNNING;
1694}
1695
1696
1697/**
1698 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1699 */
1700static DECLCALLBACK(int) drvHostAudioCaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1701 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1702{
1703 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1704 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1705 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1706 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1707 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1708 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1709 int rc;
1710
1711 /*
1712 * Permission check for input devices before we start.
1713 */
1714 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1715 {
1716 rc = coreAudioInputPermissionCheck();
1717 if (RT_FAILURE(rc))
1718 return rc;
1719 }
1720
1721 /*
1722 * Do we have a device for the requested stream direction?
1723 */
1724 PCOREAUDIODEVICEDATA pDev = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pDefaultDevIn : pThis->pDefaultDevOut;
1725#ifdef LOG_ENABLED
1726 char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
1727#endif
1728 LogFunc(("pDev=%p *pCfgReq: %s\n", pDev, PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp)) ));
1729 if (pDev)
1730 {
1731 Assert(pDev->Core.cbSelf == sizeof(*pDev));
1732
1733 /*
1734 * Basic structure init.
1735 */
1736 pStreamCA->hThread = NIL_RTTHREAD;
1737 pStreamCA->fRun = false;
1738 pStreamCA->fIsRunning = false;
1739 pStreamCA->fShutdown = false;
1740 pStreamCA->Unit.pDevice = pDev; /** @todo r=bird: How do we protect this against enumeration releasing pDefaultDevOut/In. */
1741 pStreamCA->enmInitState = COREAUDIOINITSTATE_IN_INIT;
1742
1743 rc = RTCritSectInit(&pStreamCA->CritSect);
1744 if (RT_SUCCESS(rc))
1745 {
1746 /*
1747 * Do format conversion and create the circular buffer we use to shuffle
1748 * data to/from the queue thread.
1749 */
1750 PDMAudioStrmCfgCopy(&pStreamCA->Cfg, pCfgReq);
1751 drvHostAudioCaPCMPropsToASBD(&pCfgReq->Props, &pStreamCA->BasicStreamDesc);
1752 /** @todo Do some validation? */
1753 drvHostAudioCaPrintASBD( pCfgReq->enmDir == PDMAUDIODIR_IN
1754 ? "Capturing queue format"
1755 : "Playback queue format", &pStreamCA->BasicStreamDesc);
1756
1757 rc = RTCircBufCreate(&pStreamCA->pCircBuf,
1758 PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize));
1759 if (RT_SUCCESS(rc))
1760 {
1761 /*
1762 * Start the thread.
1763 */
1764 static uint32_t volatile s_idxThread = 0;
1765 uint32_t idxThread = ASMAtomicIncU32(&s_idxThread);
1766
1767 rc = RTThreadCreateF(&pStreamCA->hThread, drvHostAudioCaQueueThread, pStreamCA, 0 /*cbStack*/,
1768 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "CaQue%u", idxThread);
1769 if (RT_SUCCESS(rc))
1770 {
1771 rc = RTThreadUserWait(pStreamCA->hThread, RT_MS_10SEC);
1772 AssertRC(rc);
1773 if (RT_SUCCESS(rc) && pStreamCA->hRunLoop != NULL)
1774 {
1775 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_INIT);
1776
1777 LogFunc(("returns VINF_SUCCESS\n"));
1778 return VINF_SUCCESS;
1779 }
1780
1781 /*
1782 * Failed, clean up.
1783 */
1784 LogRel(("CoreAudio: Thread failed to initialize in a timely manner (%Rrc).\n", rc));
1785
1786 ASMAtomicWriteBool(&pStreamCA->fShutdown, true);
1787 RTThreadPoke(pStreamCA->hThread);
1788 int rcThread = 0;
1789 rc = RTThreadWait(pStreamCA->hThread, RT_MS_15SEC, NULL);
1790 AssertLogRelRC(rc);
1791 LogRel(("CoreAudio: Thread exit code: %Rrc / %Rrc.\n", rc, rcThread));
1792 pStreamCA->hThread = NIL_RTTHREAD;
1793 }
1794 else
1795 LogRel(("CoreAudio: Failed to create queue thread for stream: %Rrc\n", rc));
1796 RTCircBufDestroy(pStreamCA->pCircBuf);
1797 pStreamCA->pCircBuf = NULL;
1798 }
1799 else
1800 LogRel(("CoreAudio: Failed to allocate stream buffer: %Rrc\n", rc));
1801 RTCritSectDelete(&pStreamCA->CritSect);
1802 }
1803 else
1804 LogRel(("CoreAudio: Failed to initialize critical section for stream: %Rrc\n", rc));
1805 }
1806 else
1807 {
1808 LogFunc(("No device for stream.\n"));
1809 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1810 }
1811
1812 LogFunc(("returns %Rrc\n", rc));
1813 return rc;
1814}
1815
1816
1817/**
1818 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1819 */
1820static DECLCALLBACK(int) drvHostAudioCaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1821{
1822 RT_NOREF(pInterface);
1823 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1824 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1825
1826 /*
1827 * Never mind if the status isn't INIT (it should always be, though).
1828 */
1829 COREAUDIOINITSTATE const enmInitState = (COREAUDIOINITSTATE)ASMAtomicReadU32(&pStreamCA->enmInitState);
1830 AssertMsg(enmInitState == COREAUDIOINITSTATE_INIT, ("%d\n", enmInitState));
1831 if (enmInitState == COREAUDIOINITSTATE_INIT)
1832 {
1833 Assert(RTCritSectIsInitialized(&pStreamCA->CritSect));
1834
1835 /*
1836 * Disable (stop) the stream just in case it's running.
1837 */
1838 /** @todo this isn't paranoid enough, the pStreamCA->hAudioQueue is
1839 * owned+released by the queue thread. */
1840 drvHostAudioCaStreamControlInternal(pStreamCA, PDMAUDIOSTREAMCMD_DISABLE);
1841
1842 /*
1843 * Change the state (cannot do before the stop).
1844 * Enter and leave the critsect afterwards for paranoid reasons.
1845 */
1846 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_IN_UNINIT);
1847 RTCritSectEnter(&pStreamCA->CritSect);
1848 RTCritSectLeave(&pStreamCA->CritSect);
1849
1850 /*
1851 * Bring down the queue thread.
1852 */
1853 if (pStreamCA->hThread != NIL_RTTHREAD)
1854 {
1855 LogFunc(("Waiting for thread ...\n"));
1856 ASMAtomicXchgBool(&pStreamCA->fShutdown, true);
1857 int rcThread = VERR_IPE_UNINITIALIZED_STATUS;
1858 int rc = VERR_TIMEOUT;
1859 for (uint32_t iWait = 0; rc == VERR_TIMEOUT && iWait < 60; iWait++)
1860 {
1861 LogFunc(("%u ...\n", iWait));
1862 if (pStreamCA->hRunLoop != NULL)
1863 CFRunLoopStop(pStreamCA->hRunLoop);
1864 if (iWait >= 10)
1865 RTThreadPoke(pStreamCA->hThread);
1866
1867 rcThread = VERR_IPE_UNINITIALIZED_STATUS;
1868 rc = RTThreadWait(pStreamCA->hThread, RT_MS_1SEC / 2, &rcThread);
1869 }
1870 AssertLogRelRC(rc);
1871 LogFunc(("Thread stopped with: %Rrc/%Rrc\n", rc, rcThread));
1872 pStreamCA->hThread = NIL_RTTHREAD;
1873 }
1874
1875 if (pStreamCA->hRunLoop != NULL)
1876 {
1877 CFRelease(pStreamCA->hRunLoop);
1878 pStreamCA->hRunLoop = NULL;
1879 }
1880
1881 /*
1882 * Kill the circular buffer and NULL essential variable.
1883 */
1884 if (pStreamCA->pCircBuf)
1885 {
1886 RTCircBufDestroy(pStreamCA->pCircBuf);
1887 pStreamCA->pCircBuf = NULL;
1888 }
1889
1890 pStreamCA->Unit.pDevice = NULL;
1891
1892 RTCritSectDelete(&pStreamCA->CritSect);
1893
1894 /*
1895 * Done.
1896 */
1897 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_UNINIT);
1898 }
1899
1900 LogFunc(("returns\n"));
1901 return VINF_SUCCESS;
1902}
1903
1904
1905static int drvHostAudioCaStreamControlInternal(PCOREAUDIOSTREAM pStreamCA, PDMAUDIOSTREAMCMD enmStreamCmd)
1906{
1907 uint32_t enmInitState = ASMAtomicReadU32(&pStreamCA->enmInitState);
1908
1909 LogFlowFunc(("enmStreamCmd=%RU32, enmInitState=%RU32\n", enmStreamCmd, enmInitState));
1910
1911 if (enmInitState != COREAUDIOINITSTATE_INIT)
1912 {
1913 return VINF_SUCCESS;
1914 }
1915
1916 int rc = VINF_SUCCESS;
1917 switch (enmStreamCmd)
1918 {
1919 case PDMAUDIOSTREAMCMD_ENABLE:
1920 case PDMAUDIOSTREAMCMD_RESUME:
1921 {
1922 LogFunc(("Queue enable\n"));
1923 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
1924 {
1925 rc = drvHostAudioCaStreamInvalidateQueue(pStreamCA);
1926 if (RT_SUCCESS(rc))
1927 {
1928 /* Start the audio queue immediately. */
1929 AudioQueueStart(pStreamCA->hAudioQueue, NULL);
1930 }
1931 }
1932 else if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT)
1933 {
1934 /* Touch the run flag to start the audio queue as soon as
1935 * we have anough data to actually play something. */
1936 ASMAtomicXchgBool(&pStreamCA->fRun, true);
1937 }
1938 break;
1939 }
1940
1941 case PDMAUDIOSTREAMCMD_DISABLE:
1942 {
1943 LogFunc(("Queue disable\n"));
1944 AudioQueueStop(pStreamCA->hAudioQueue, 1 /* Immediately */);
1945 ASMAtomicXchgBool(&pStreamCA->fRun, false);
1946 ASMAtomicXchgBool(&pStreamCA->fIsRunning, false);
1947 break;
1948 }
1949 case PDMAUDIOSTREAMCMD_PAUSE:
1950 {
1951 LogFunc(("Queue pause\n"));
1952 AudioQueuePause(pStreamCA->hAudioQueue);
1953 ASMAtomicXchgBool(&pStreamCA->fIsRunning, false);
1954 break;
1955 }
1956
1957 default:
1958 rc = VERR_NOT_SUPPORTED;
1959 break;
1960 }
1961
1962 LogFlowFuncLeaveRC(rc);
1963 return rc;
1964}
1965
1966
1967/**
1968 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
1969 */
1970static DECLCALLBACK(int) drvHostAudioCaHA_StreamControl(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1971 PDMAUDIOSTREAMCMD enmStreamCmd)
1972{
1973 RT_NOREF(pInterface);
1974 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1975 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1976
1977 return drvHostAudioCaStreamControlInternal(pStreamCA, enmStreamCmd);
1978}
1979
1980
1981/**
1982 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1983 */
1984static DECLCALLBACK(uint32_t) drvHostAudioCaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1985{
1986 RT_NOREF(pInterface);
1987 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1988 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1989
1990 if (ASMAtomicReadU32(&pStreamCA->enmInitState) != COREAUDIOINITSTATE_INIT)
1991 return 0;
1992
1993 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
1994 {
1995 AssertPtr(pStreamCA->pCircBuf);
1996 return (uint32_t)RTCircBufUsed(pStreamCA->pCircBuf);
1997 }
1998 AssertFailed();
1999 return 0;
2000}
2001
2002
2003/**
2004 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2005 */
2006static DECLCALLBACK(uint32_t) drvHostAudioCaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2007{
2008 RT_NOREF(pInterface);
2009 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2010 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2011
2012
2013 uint32_t cbWritable = 0;
2014 if (ASMAtomicReadU32(&pStreamCA->enmInitState) == COREAUDIOINITSTATE_INIT)
2015 {
2016 AssertPtr(pStreamCA->pCircBuf);
2017
2018 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT)
2019 cbWritable = (uint32_t)RTCircBufFree(pStreamCA->pCircBuf);
2020 }
2021
2022 LogFlowFunc(("cbWritable=%RU32\n", cbWritable));
2023 return cbWritable;
2024}
2025
2026
2027/**
2028 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
2029 */
2030static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAudioCaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
2031 PPDMAUDIOBACKENDSTREAM pStream)
2032{
2033 RT_NOREF(pInterface);
2034 PCOREAUDIOSTREAM pStreamCa = (PCOREAUDIOSTREAM)pStream;
2035 AssertPtrReturn(pStreamCa, PDMHOSTAUDIOSTREAMSTATE_INVALID);
2036
2037 if (ASMAtomicReadU32(&pStreamCa->enmInitState) == COREAUDIOINITSTATE_INIT)
2038 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
2039 return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; /** @todo ?? */
2040}
2041
2042/**
2043 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2044 */
2045static DECLCALLBACK(int) drvHostAudioCaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2046 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2047{
2048 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
2049 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2050
2051 RT_NOREF(pThis);
2052
2053 if (ASMAtomicReadU32(&pStreamCA->enmInitState) != COREAUDIOINITSTATE_INIT)
2054 {
2055 *pcbWritten = 0;
2056 return VINF_SUCCESS;
2057 }
2058
2059 int rc = RTCritSectEnter(&pStreamCA->CritSect);
2060 AssertRCReturn(rc, rc);
2061
2062 size_t cbToWrite = RT_MIN(cbBuf, RTCircBufFree(pStreamCA->pCircBuf));
2063 Log3Func(("cbToWrite=%zu\n", cbToWrite));
2064
2065 uint32_t cbWrittenTotal = 0;
2066 while (cbToWrite > 0)
2067 {
2068 /* Try to acquire the necessary space from the ring buffer. */
2069 void *pvChunk = NULL;
2070 size_t cbChunk = 0;
2071 RTCircBufAcquireWriteBlock(pStreamCA->pCircBuf, cbToWrite, &pvChunk, &cbChunk);
2072 AssertBreakStmt(cbChunk > 0, RTCircBufReleaseWriteBlock(pStreamCA->pCircBuf, cbChunk));
2073
2074 Assert(cbChunk <= cbToWrite);
2075 Assert(cbWrittenTotal + cbChunk <= cbBuf);
2076
2077 memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk);
2078
2079#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2080 RTFILE fh;
2081 rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm",
2082 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
2083 if (RT_SUCCESS(rc))
2084 {
2085 RTFileWrite(fh, pvChunk, cbChunk, NULL);
2086 RTFileClose(fh);
2087 }
2088 else
2089 AssertFailed();
2090#endif
2091
2092 /* Release the ring buffer, so the read thread could start reading this data. */
2093 RTCircBufReleaseWriteBlock(pStreamCA->pCircBuf, cbChunk);
2094
2095 if (RT_FAILURE(rc))
2096 break;
2097
2098 Assert(cbToWrite >= cbChunk);
2099 cbToWrite -= cbChunk;
2100
2101 cbWrittenTotal += cbChunk;
2102 }
2103
2104 if ( RT_SUCCESS(rc)
2105 && pStreamCA->fRun
2106 && !pStreamCA->fIsRunning)
2107 {
2108 rc = drvHostAudioCaStreamInvalidateQueue(pStreamCA);
2109 if (RT_SUCCESS(rc))
2110 {
2111 AudioQueueStart(pStreamCA->hAudioQueue, NULL);
2112 pStreamCA->fRun = false;
2113 pStreamCA->fIsRunning = true;
2114 }
2115 }
2116
2117 int rc2 = RTCritSectLeave(&pStreamCA->CritSect);
2118 AssertRC(rc2);
2119
2120 *pcbWritten = cbWrittenTotal;
2121 return rc;
2122}
2123
2124
2125/**
2126 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2127 */
2128static DECLCALLBACK(int) drvHostAudioCaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2129 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2130{
2131 RT_NOREF(pInterface);
2132 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2133 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2134 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2135
2136
2137 if (ASMAtomicReadU32(&pStreamCA->enmInitState) != COREAUDIOINITSTATE_INIT)
2138 {
2139 *pcbRead = 0;
2140 return VINF_SUCCESS;
2141 }
2142
2143 int rc = RTCritSectEnter(&pStreamCA->CritSect);
2144 AssertRCReturn(rc, rc);
2145
2146 size_t cbToWrite = RT_MIN(cbBuf, RTCircBufUsed(pStreamCA->pCircBuf));
2147 Log3Func(("cbToWrite=%zu/%zu\n", cbToWrite, RTCircBufSize(pStreamCA->pCircBuf)));
2148
2149 uint32_t cbReadTotal = 0;
2150 while (cbToWrite > 0)
2151 {
2152 void *pvChunk = NULL;
2153 size_t cbChunk = 0;
2154 RTCircBufAcquireReadBlock(pStreamCA->pCircBuf, cbToWrite, &pvChunk, &cbChunk);
2155
2156 AssertStmt(cbChunk <= cbToWrite, cbChunk = cbToWrite);
2157 memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk);
2158
2159 RTCircBufReleaseReadBlock(pStreamCA->pCircBuf, cbChunk);
2160
2161 cbToWrite -= cbChunk;
2162 cbReadTotal += cbChunk;
2163 }
2164
2165 *pcbRead = cbReadTotal;
2166
2167 RTCritSectLeave(&pStreamCA->CritSect);
2168 return VINF_SUCCESS;
2169}
2170
2171
2172/*********************************************************************************************************************************
2173* PDMIBASE *
2174*********************************************************************************************************************************/
2175
2176/**
2177 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2178 */
2179static DECLCALLBACK(void *) drvHostAudioCaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2180{
2181 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2182 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2183
2184 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2185 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2186
2187 return NULL;
2188}
2189
2190
2191/*********************************************************************************************************************************
2192* PDMDRVREG *
2193*********************************************************************************************************************************/
2194
2195/**
2196 * Worker for the power off and destructor callbacks.
2197 */
2198static void drvHostAudioCaRemoveDefaultDeviceListners(PDRVHOSTCOREAUDIO pThis)
2199{
2200 /*
2201 * Unregister system callbacks.
2202 */
2203 AudioObjectPropertyAddress PropAddr =
2204 {
2205 kAudioHardwarePropertyDefaultInputDevice,
2206 kAudioObjectPropertyScopeGlobal,
2207 kAudioObjectPropertyElementMaster
2208 };
2209
2210 OSStatus orc;
2211 if (pThis->fRegisteredDefaultInputListener)
2212 {
2213 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2214 if ( orc != noErr
2215 && orc != kAudioHardwareBadObjectError)
2216 LogRel(("CoreAudio: Failed to remove the default input device changed listener: %d (%#x))\n", orc, orc));
2217 pThis->fRegisteredDefaultInputListener = false;
2218 }
2219
2220 if (pThis->fRegisteredDefaultOutputListener)
2221 {
2222
2223 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2224 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2225 if ( orc != noErr
2226 && orc != kAudioHardwareBadObjectError)
2227 LogRel(("CoreAudio: Failed to remove the default output device changed listener: %d (%#x))\n", orc, orc));
2228 pThis->fRegisteredDefaultOutputListener = false;
2229 }
2230
2231 LogFlowFuncEnter();
2232}
2233
2234
2235/**
2236 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
2237 */
2238static DECLCALLBACK(void) drvHostAudioCaPowerOff(PPDMDRVINS pDrvIns)
2239{
2240 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2241 drvHostAudioCaRemoveDefaultDeviceListners(pThis);
2242}
2243
2244
2245/**
2246 * @callback_method_impl{FNPDMDRVDESTRUCT}
2247 */
2248static DECLCALLBACK(void) drvHostAudioCaDestruct(PPDMDRVINS pDrvIns)
2249{
2250 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2251 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2252
2253 drvHostAudioCaRemoveDefaultDeviceListners(pThis);
2254
2255 int rc2 = RTCritSectDelete(&pThis->CritSect);
2256 AssertRC(rc2);
2257
2258 LogFlowFuncLeaveRC(rc2);
2259}
2260
2261
2262/**
2263 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2264 * Construct a Core Audio driver instance.}
2265 */
2266static DECLCALLBACK(int) drvHostAudioCaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2267{
2268 RT_NOREF(pCfg, fFlags);
2269 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2270 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2271 LogRel(("Audio: Initializing Core Audio driver\n"));
2272
2273 /*
2274 * Init the static parts.
2275 */
2276 pThis->pDrvIns = pDrvIns;
2277 PDMAudioHostEnumInit(&pThis->Devices);
2278 /* IBase */
2279 pDrvIns->IBase.pfnQueryInterface = drvHostAudioCaQueryInterface;
2280 /* IHostAudio */
2281 pThis->IHostAudio.pfnGetConfig = drvHostAudioCaHA_GetConfig;
2282 pThis->IHostAudio.pfnGetDevices = drvHostAudioCaHA_GetDevices;
2283 pThis->IHostAudio.pfnGetStatus = drvHostAudioCaHA_GetStatus;
2284 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
2285 pThis->IHostAudio.pfnStreamConfigHint = NULL;
2286 pThis->IHostAudio.pfnStreamCreate = drvHostAudioCaHA_StreamCreate;
2287 pThis->IHostAudio.pfnStreamInitAsync = NULL;
2288 pThis->IHostAudio.pfnStreamDestroy = drvHostAudioCaHA_StreamDestroy;
2289 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
2290 pThis->IHostAudio.pfnStreamControl = drvHostAudioCaHA_StreamControl;
2291 pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioCaHA_StreamGetReadable;
2292 pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioCaHA_StreamGetWritable;
2293 pThis->IHostAudio.pfnStreamGetPending = NULL;
2294 pThis->IHostAudio.pfnStreamGetState = drvHostAudioCaHA_StreamGetState;
2295 pThis->IHostAudio.pfnStreamPlay = drvHostAudioCaHA_StreamPlay;
2296 pThis->IHostAudio.pfnStreamCapture = drvHostAudioCaHA_StreamCapture;
2297
2298 int rc = RTCritSectInit(&pThis->CritSect);
2299 AssertRCReturn(rc, rc);
2300
2301 /*
2302 * Enumerate audio devices.
2303 */
2304 rc = drvHostAudioCaEnumerateDevices(pThis);
2305 AssertRCReturn(rc, rc);
2306
2307 /*
2308 * Register callbacks for default device input and output changes.
2309 * We just ignore errors here it seems.
2310 */
2311 AudioObjectPropertyAddress PropAddr =
2312 {
2313 /* .mSelector = */ kAudioHardwarePropertyDefaultInputDevice,
2314 /* .mScope = */ kAudioObjectPropertyScopeGlobal,
2315 /* .mElement = */ kAudioObjectPropertyElementMaster
2316 };
2317
2318 OSStatus orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2319 pThis->fRegisteredDefaultInputListener = orc == noErr;
2320 if ( orc != noErr
2321 && orc != kAudioHardwareIllegalOperationError)
2322 LogRel(("CoreAudio: Failed to add the input default device changed listener: %d (%#x)\n", orc, orc));
2323
2324 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2325 orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2326 pThis->fRegisteredDefaultOutputListener = orc == noErr;
2327 if ( orc != noErr
2328 && orc != kAudioHardwareIllegalOperationError)
2329 LogRel(("CoreAudio: Failed to add the output default device changed listener: %d (%#x)\n", orc, orc));
2330
2331 /*
2332 * Query the notification interface from the driver/device above us.
2333 */
2334 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
2335 AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
2336
2337#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2338 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm");
2339 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm");
2340#endif
2341
2342 LogFlowFuncLeaveRC(rc);
2343 return rc;
2344}
2345
2346
2347/**
2348 * Char driver registration record.
2349 */
2350const PDMDRVREG g_DrvHostCoreAudio =
2351{
2352 /* u32Version */
2353 PDM_DRVREG_VERSION,
2354 /* szName */
2355 "CoreAudio",
2356 /* szRCMod */
2357 "",
2358 /* szR0Mod */
2359 "",
2360 /* pszDescription */
2361 "Core Audio host driver",
2362 /* fFlags */
2363 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2364 /* fClass. */
2365 PDM_DRVREG_CLASS_AUDIO,
2366 /* cMaxInstances */
2367 ~0U,
2368 /* cbInstance */
2369 sizeof(DRVHOSTCOREAUDIO),
2370 /* pfnConstruct */
2371 drvHostAudioCaConstruct,
2372 /* pfnDestruct */
2373 drvHostAudioCaDestruct,
2374 /* pfnRelocate */
2375 NULL,
2376 /* pfnIOCtl */
2377 NULL,
2378 /* pfnPowerOn */
2379 NULL,
2380 /* pfnReset */
2381 NULL,
2382 /* pfnSuspend */
2383 NULL,
2384 /* pfnResume */
2385 NULL,
2386 /* pfnAttach */
2387 NULL,
2388 /* pfnDetach */
2389 NULL,
2390 /* pfnPowerOff */
2391 drvHostAudioCaPowerOff,
2392 /* pfnSoftReset */
2393 NULL,
2394 /* u32EndVersion */
2395 PDM_DRVREG_VERSION
2396};
2397
Note: See TracBrowser for help on using the repository browser.

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