VirtualBox

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

Last change on this file since 89082 was 89055, checked in by vboxsync, 4 years ago

ValKit/Audio: Eliminated VBoxDDVKAT.h. Made some progress getting the PulseAudio driver to work (integer overflow now due to invalid config). bugref:10008

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