VirtualBox

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

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

DrvHostAudioCoreAudio: More logging around creation and destruction of streams. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 107.0 KB
Line 
1/* $Id: DrvHostAudioCoreAudio.cpp 89209 2021-05-20 21:55:29Z 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#include <iprt/uuid.h>
48#include <iprt/timer.h>
49
50#include <CoreAudio/CoreAudio.h>
51#include <CoreServices/CoreServices.h>
52#include <AudioToolbox/AudioQueue.h>
53#include <AudioUnit/AudioUnit.h>
54
55
56/*********************************************************************************************************************************
57* Defined Constants And Macros *
58*********************************************************************************************************************************/
59/** The max number of queue buffers we'll use. */
60#define COREAUDIO_MAX_BUFFERS 1024
61/** The minimum number of queue buffers. */
62#define COREAUDIO_MIN_BUFFERS 4
63
64/** Enables the worker thread.
65 * This saves CoreAudio from creating an additional thread upon queue
66 * creation. (It does not help with the slow AudioQueueDispose fun.) */
67#define CORE_AUDIO_WITH_WORKER_THREAD
68#if 0
69/** Enables the AudioQueueDispose breakpoint timer (debugging help). */
70# define CORE_AUDIO_WITH_BREAKPOINT_TIMER
71#endif
72
73
74/*********************************************************************************************************************************
75* Structures and Typedefs *
76*********************************************************************************************************************************/
77/** Pointer to the instance data for a Core Audio driver instance. */
78typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO;
79/** Pointer to the Core Audio specific backend data for an audio stream. */
80typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM;
81
82/**
83 * Core Audio device entry (enumeration).
84 *
85 * @note This is definitely not safe to just copy!
86 */
87typedef struct COREAUDIODEVICEDATA
88{
89 /** The core PDM structure. */
90 PDMAUDIOHOSTDEV Core;
91
92 /** Pointer to driver instance this device is bound to. */
93 PDRVHOSTCOREAUDIO pDrv;
94 /** The audio device ID of the currently used device (UInt32 typedef). */
95 AudioDeviceID deviceID;
96 /** The device' "UUID".
97 * @todo r=bird: We leak this. Header say we must CFRelease it. */
98 CFStringRef UUID;
99 /** List of attached (native) Core Audio streams attached to this device. */
100 RTLISTANCHOR lstStreams;
101} COREAUDIODEVICEDATA;
102/** Pointer to a Core Audio device entry (enumeration). */
103typedef COREAUDIODEVICEDATA *PCOREAUDIODEVICEDATA;
104
105
106/**
107 * Core Audio stream state.
108 */
109typedef enum COREAUDIOINITSTATE
110{
111 /** The device is uninitialized. */
112 COREAUDIOINITSTATE_UNINIT = 0,
113 /** The device is currently initializing. */
114 COREAUDIOINITSTATE_IN_INIT,
115 /** The device is initialized. */
116 COREAUDIOINITSTATE_INIT,
117 /** The device is currently uninitializing. */
118 COREAUDIOINITSTATE_IN_UNINIT,
119 /** The usual 32-bit hack. */
120 COREAUDIOINITSTATE_32BIT_HACK = 0x7fffffff
121} COREAUDIOINITSTATE;
122
123
124/**
125 * Core audio buffer tracker.
126 *
127 * For output buffer we'll be using AudioQueueBuffer::mAudioDataByteSize to
128 * track how much we've written. When a buffer is full, or if we run low on
129 * queued bufferes, it will be queued.
130 *
131 * For input buffer we'll be using offRead to track how much we've read.
132 */
133typedef struct COREAUDIOBUF
134{
135 /** The buffer. */
136 AudioQueueBufferRef pBuf;
137 /** Set if the buffer is queued. */
138 bool volatile fQueued;
139 /** The buffer read offset (input only). */
140 uint32_t offRead;
141} COREAUDIOBUF;
142/** Pointer to a core audio buffer tracker. */
143typedef COREAUDIOBUF *PCOREAUDIOBUF;
144
145
146/**
147 * Core Audio specific data for an audio stream.
148 */
149typedef struct COREAUDIOSTREAM
150{
151 /** Common part. */
152 PDMAUDIOBACKENDSTREAM Core;
153
154 /** The stream's acquired configuration. */
155 PDMAUDIOSTREAMCFG Cfg;
156 /** List node for the device's stream list. */
157 RTLISTNODE Node;
158 /** The acquired (final) audio format for this stream.
159 * @note This what the device requests, we don't alter anything. */
160 AudioStreamBasicDescription BasicStreamDesc;
161 /** The actual audio queue being used. */
162 AudioQueueRef hAudioQueue;
163
164 /** Number of buffers. */
165 uint32_t cBuffers;
166 /** The array of buffer. */
167 PCOREAUDIOBUF paBuffers;
168
169 /** The audio unit for this stream. */
170 struct
171 {
172 /** Pointer to the device this audio unit is bound to.
173 * Can be NULL if not bound to a device (anymore). */
174 PCOREAUDIODEVICEDATA pDevice;
175#if 0 /* not used */
176 /** The actual audio unit object. */
177 AudioUnit hAudioUnit;
178 /** Stream description for using with VBox:
179 * - When using this audio unit for input (capturing), this format states
180 * the unit's output format.
181 * - When using this audio unit for output (playback), this format states
182 * the unit's input format. */
183 AudioStreamBasicDescription StreamFmt;
184#endif
185 } Unit;
186 /** Initialization status tracker, actually COREAUDIOINITSTATE.
187 * Used when some of the device parameters or the device itself is changed
188 * during the runtime. */
189 volatile uint32_t enmInitState;
190 /** The current buffer being written to / read from. */
191 uint32_t idxBuffer;
192 /** Set if the stream is enabled. */
193 bool fEnabled;
194 /** Set if the stream is started (playing/capturing). */
195 bool fStarted;
196 /** Set if the stream is draining (output only). */
197 bool fDraining;
198 /** Set if we should restart the stream on resume (saved pause state). */
199 bool fRestartOnResume;
200// /** Set if we're switching to a new output/input device. */
201// bool fSwitchingDevice;
202 /** Internal stream offset (bytes). */
203 uint64_t offInternal;
204 /** The RTTimeMilliTS() at the end of the last transfer. */
205 uint64_t msLastTransfer;
206
207 /** Critical section for serializing access between thread + callbacks. */
208 RTCRITSECT CritSect;
209 /** Buffer that drvHostAudioCaStreamStatusString uses. */
210 char szStatus[64];
211} COREAUDIOSTREAM;
212
213
214/**
215 * Instance data for a Core Audio host audio driver.
216 *
217 * @implements PDMIAUDIOCONNECTOR
218 */
219typedef struct DRVHOSTCOREAUDIO
220{
221 /** Pointer to the driver instance structure. */
222 PPDMDRVINS pDrvIns;
223 /** Pointer to host audio interface. */
224 PDMIHOSTAUDIO IHostAudio;
225 /** Current (last reported) device enumeration. */
226 PDMAUDIOHOSTENUM Devices;
227 /** Pointer to the currently used input device in the device enumeration.
228 * Can be NULL if none assigned. */
229 PCOREAUDIODEVICEDATA pDefaultDevIn;
230 /** Pointer to the currently used output device in the device enumeration.
231 * Can be NULL if none assigned. */
232 PCOREAUDIODEVICEDATA pDefaultDevOut;
233 /** Upwards notification interface. */
234 PPDMIHOSTAUDIOPORT pIHostAudioPort;
235 /** Indicates whether we've registered default input device change listener. */
236 bool fRegisteredDefaultInputListener;
237 /** Indicates whether we've registered default output device change listener. */
238 bool fRegisteredDefaultOutputListener;
239
240#ifdef CORE_AUDIO_WITH_WORKER_THREAD
241 /** @name Worker Thread For Queue callbacks and stuff.
242 * @{ */
243 /** The worker thread. */
244 RTTHREAD hThread;
245 /** The runloop of the worker thread. */
246 CFRunLoopRef hThreadRunLoop;
247 /** The message port we use to talk to the thread.
248 * @note While we don't currently use the port, it is necessary to prevent
249 * the thread from spinning or stopping prematurely because of
250 * CFRunLoopRunInMode returning kCFRunLoopRunFinished. */
251 CFMachPortRef hThreadPort;
252 /** Runloop source for hThreadPort. */
253 CFRunLoopSourceRef hThreadPortSrc;
254 /** @} */
255#endif
256
257 /** Critical section to serialize access. */
258 RTCRITSECT CritSect;
259#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
260 /** Timder for debugging AudioQueueDispose slowness. */
261 RTTIMERLR hBreakpointTimer;
262#endif
263} DRVHOSTCOREAUDIO;
264
265
266/*********************************************************************************************************************************
267* Internal Functions *
268*********************************************************************************************************************************/
269/* DrvHostAudioCoreAudioAuth.mm: */
270DECLHIDDEN(int) coreAudioInputPermissionCheck(void);
271
272
273#ifdef LOG_ENABLED
274/**
275 * Gets the stream status.
276 *
277 * @returns Pointer to stream status string.
278 * @param pStreamCA The stream to get the status for.
279 */
280static const char *drvHostAudioCaStreamStatusString(PCOREAUDIOSTREAM pStreamCA)
281{
282 static RTSTRTUPLE const s_aInitState[5] =
283 {
284 { RT_STR_TUPLE("UNINIT") },
285 { RT_STR_TUPLE("IN_INIT") },
286 { RT_STR_TUPLE("INIT") },
287 { RT_STR_TUPLE("IN_UNINIT") },
288 { RT_STR_TUPLE("BAD") },
289 };
290 uint32_t enmInitState = pStreamCA->enmInitState;
291 PCRTSTRTUPLE pTuple = &s_aInitState[RT_MIN(enmInitState, RT_ELEMENTS(s_aInitState) - 1)];
292 memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch);
293 size_t off = pTuple->cch;
294
295 static RTSTRTUPLE const s_aEnable[2] =
296 {
297 { RT_STR_TUPLE("DISABLED") },
298 { RT_STR_TUPLE("ENABLED ") },
299 };
300 pTuple = &s_aEnable[pStreamCA->fEnabled];
301 memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch);
302 off += pTuple->cch;
303
304 static RTSTRTUPLE const s_aStarted[2] =
305 {
306 { RT_STR_TUPLE(" STOPPED") },
307 { RT_STR_TUPLE(" STARTED") },
308 };
309 pTuple = &s_aStarted[pStreamCA->fStarted];
310 memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch);
311 off += pTuple->cch;
312
313 static RTSTRTUPLE const s_aDraining[2] =
314 {
315 { RT_STR_TUPLE("") },
316 { RT_STR_TUPLE(" DRAINING") },
317 };
318 pTuple = &s_aDraining[pStreamCA->fDraining];
319 memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch);
320 off += pTuple->cch;
321
322 Assert(off < sizeof(pStreamCA->szStatus));
323 pStreamCA->szStatus[off] = '\0';
324 return pStreamCA->szStatus;
325}
326#endif /*LOG_ENABLED*/
327
328
329static void drvHostAudioCaPrintASBD(const char *pszDesc, const AudioStreamBasicDescription *pASBD)
330{
331 LogRel2(("CoreAudio: %s description:\n", pszDesc));
332 LogRel2(("CoreAudio: Format ID: %RU32 (%c%c%c%c)\n", pASBD->mFormatID,
333 RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID),
334 RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID)));
335 LogRel2(("CoreAudio: Flags: %RU32", pASBD->mFormatFlags));
336 if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat)
337 LogRel2((" Float"));
338 if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian)
339 LogRel2((" BigEndian"));
340 if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger)
341 LogRel2((" SignedInteger"));
342 if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked)
343 LogRel2((" Packed"));
344 if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh)
345 LogRel2((" AlignedHigh"));
346 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved)
347 LogRel2((" NonInterleaved"));
348 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable)
349 LogRel2((" NonMixable"));
350 if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear)
351 LogRel2((" AllClear"));
352 LogRel2(("\n"));
353 LogRel2(("CoreAudio: SampleRate : %RU64.%02u Hz\n",
354 (uint64_t)pASBD->mSampleRate, (unsigned)(pASBD->mSampleRate * 100) % 100));
355 LogRel2(("CoreAudio: ChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame));
356 LogRel2(("CoreAudio: FramesPerPacket : %RU32\n", pASBD->mFramesPerPacket));
357 LogRel2(("CoreAudio: BitsPerChannel : %RU32\n", pASBD->mBitsPerChannel));
358 LogRel2(("CoreAudio: BytesPerFrame : %RU32\n", pASBD->mBytesPerFrame));
359 LogRel2(("CoreAudio: BytesPerPacket : %RU32\n", pASBD->mBytesPerPacket));
360}
361
362
363static void drvHostAudioCaPCMPropsToASBD(PCPDMAUDIOPCMPROPS pProps, AudioStreamBasicDescription *pASBD)
364{
365 AssertPtrReturnVoid(pProps);
366 AssertPtrReturnVoid(pASBD);
367
368 RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription));
369
370 pASBD->mFormatID = kAudioFormatLinearPCM;
371 pASBD->mFormatFlags = kAudioFormatFlagIsPacked;
372 if (pProps->fSigned)
373 pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
374 if (PDMAudioPropsIsBigEndian(pProps))
375 pASBD->mFormatFlags |= kAudioFormatFlagIsBigEndian;
376 pASBD->mSampleRate = PDMAudioPropsHz(pProps);
377 pASBD->mChannelsPerFrame = PDMAudioPropsChannels(pProps);
378 pASBD->mBitsPerChannel = PDMAudioPropsSampleBits(pProps);
379 pASBD->mBytesPerFrame = PDMAudioPropsFrameSize(pProps);
380 pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */
381 pASBD->mBytesPerPacket = PDMAudioPropsFrameSize(pProps) * pASBD->mFramesPerPacket;
382}
383
384
385#if 0 /* unused */
386static int drvHostAudioCaCFStringToCString(const CFStringRef pCFString, char **ppszString)
387{
388 CFIndex cLen = CFStringGetLength(pCFString) + 1;
389 char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char));
390 if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8))
391 {
392 RTMemFree(pszResult);
393 return VERR_NOT_FOUND;
394 }
395
396 *ppszString = pszResult;
397 return VINF_SUCCESS;
398}
399
400static AudioDeviceID drvHostAudioCaDeviceUIDtoID(const char* pszUID)
401{
402 /* Create a CFString out of our CString. */
403 CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman);
404
405 /* Fill the translation structure. */
406 AudioDeviceID deviceID;
407
408 AudioValueTranslation translation;
409 translation.mInputData = &strUID;
410 translation.mInputDataSize = sizeof(CFStringRef);
411 translation.mOutputData = &deviceID;
412 translation.mOutputDataSize = sizeof(AudioDeviceID);
413
414 /* Fetch the translation from the UID to the device ID. */
415 AudioObjectPropertyAddress PropAddr =
416 {
417 kAudioHardwarePropertyDeviceForUID,
418 kAudioObjectPropertyScopeGlobal,
419 kAudioObjectPropertyElementMaster
420 };
421
422 UInt32 uSize = sizeof(AudioValueTranslation);
423 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr, 0, NULL, &uSize, &translation);
424
425 /* Release the temporary CFString */
426 CFRelease(strUID);
427
428 if (RT_LIKELY(err == noErr))
429 return deviceID;
430
431 /* Return the unknown device on error. */
432 return kAudioDeviceUnknown;
433}
434#endif /* unused */
435
436
437/*********************************************************************************************************************************
438* Device Change Notification Callbacks *
439*********************************************************************************************************************************/
440
441/**
442 * Called when the kAudioDevicePropertyNominalSampleRate or
443 * kAudioDeviceProcessorOverload properties changes on a default device.
444 *
445 * Registered on default devices after device enumeration.
446 * Not sure on which thread/runloop this runs.
447 *
448 * (See AudioObjectPropertyListenerProc in the SDK headers.)
449 */
450static OSStatus drvHostAudioCaDevicePropertyChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
451 const AudioObjectPropertyAddress paAddresses[], void *pvUser)
452{
453 PCOREAUDIODEVICEDATA pDev = (PCOREAUDIODEVICEDATA)pvUser;
454 AssertPtr(pDev);
455 RT_NOREF(pDev, cAddresses, paAddresses);
456
457 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u pDev=%p\n", idObject, idObject, cAddresses, pDev));
458 for (UInt32 idx = 0; idx < cAddresses; idx++)
459 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
460 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
461
462/** @todo r=bird: What's the plan here exactly? */
463 switch (idObject)
464 {
465 case kAudioDeviceProcessorOverload:
466 LogFunc(("Processor overload detected!\n"));
467 break;
468 case kAudioDevicePropertyNominalSampleRate:
469 LogFunc(("kAudioDevicePropertyNominalSampleRate!\n"));
470 break;
471 default:
472 /* Just skip. */
473 break;
474 }
475
476 return noErr;
477}
478
479
480/**
481 * Propagates an audio device status to all its Core Audio streams.
482 *
483 * @param pDev Audio device to propagate status for.
484 * @param enmSts Status to propagate.
485 */
486static void drvHostAudioCaDevicePropagateStatus(PCOREAUDIODEVICEDATA pDev, COREAUDIOINITSTATE enmSts)
487{
488 /* Sanity. */
489 AssertPtr(pDev);
490 AssertPtr(pDev->pDrv);
491
492 LogFlowFunc(("pDev=%p enmSts=%RU32\n", pDev, enmSts));
493
494 PCOREAUDIOSTREAM pStreamCA;
495 RTListForEach(&pDev->lstStreams, pStreamCA, COREAUDIOSTREAM, Node)
496 {
497 LogFlowFunc(("pStreamCA=%p\n", pStreamCA));
498
499 /* We move the reinitialization to the next output event.
500 * This make sure this thread isn't blocked and the
501 * reinitialization is done when necessary only. */
502/** @todo r=bird: This is now extremely bogus, see comment in caller. */
503 ASMAtomicWriteU32(&pStreamCA->enmInitState, enmSts);
504 }
505}
506
507
508/**
509 * Called when the kAudioDevicePropertyDeviceIsAlive property changes on a
510 * default device.
511 *
512 * Registered on default devices after device enumeration.
513 * Not sure on which thread/runloop this runs.
514 *
515 * (See AudioObjectPropertyListenerProc in the SDK headers.)
516 */
517static OSStatus drvHostAudioCaDeviceIsAliveChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
518 const AudioObjectPropertyAddress paAddresses[], void *pvUser)
519{
520 PCOREAUDIODEVICEDATA pDev = (PCOREAUDIODEVICEDATA)pvUser;
521 AssertPtr(pDev);
522 PDRVHOSTCOREAUDIO pThis = pDev->pDrv;
523 AssertPtr(pThis);
524 RT_NOREF(idObject, cAddresses, paAddresses);
525
526 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u pDev=%p\n", idObject, idObject, cAddresses, pDev));
527 for (UInt32 idx = 0; idx < cAddresses; idx++)
528 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
529 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
530
531 int rc = RTCritSectEnter(&pThis->CritSect);
532 AssertRC(rc);
533
534 UInt32 uAlive = 1;
535 UInt32 uSize = sizeof(UInt32);
536
537 AudioObjectPropertyAddress PropAddr =
538 {
539 kAudioDevicePropertyDeviceIsAlive,
540 kAudioObjectPropertyScopeGlobal,
541 kAudioObjectPropertyElementMaster
542 };
543
544 OSStatus err = AudioObjectGetPropertyData(pDev->deviceID, &PropAddr, 0, NULL, &uSize, &uAlive);
545
546 bool fIsDead = false;
547 if (err == kAudioHardwareBadDeviceError)
548 fIsDead = true; /* Unplugged. */
549 else if (err == kAudioHardwareNoError && !RT_BOOL(uAlive))
550 fIsDead = true; /* Something else happened. */
551
552 if (fIsDead)
553 {
554 LogRel2(("CoreAudio: Device '%s' stopped functioning\n", pDev->Core.szName));
555
556 /* Mark device as dead. */
557/** @todo r=bird: This is certifiably insane given how StreamDestroy does absolutely _nothing_ unless the init state is INIT.
558 * The queue thread will be running and trashing random heap if it tries to modify anything in the stream structure. */
559 drvHostAudioCaDevicePropagateStatus(pDev, COREAUDIOINITSTATE_UNINIT);
560 }
561
562 RTCritSectLeave(&pThis->CritSect);
563 return noErr;
564}
565
566
567/**
568 * Called when the default recording or playback device has changed.
569 *
570 * Registered by the constructor. Not sure on which thread/runloop this runs.
571 *
572 * (See AudioObjectPropertyListenerProc in the SDK headers.)
573 */
574static OSStatus drvHostAudioCaDefaultDeviceChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
575 const AudioObjectPropertyAddress *paAddresses, void *pvUser)
576
577{
578 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
579 AssertPtr(pThis);
580 LogFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses));
581 RT_NOREF(idObject);
582
583 //int rc2 = RTCritSectEnter(&pThis->CritSect);
584 //AssertRC(rc2);
585
586 for (UInt32 idxAddress = 0; idxAddress < cAddresses; idxAddress++)
587 {
588 /// @todo r=bird: what's the plan here? PCOREAUDIODEVICEDATA pDev = NULL;
589
590 /*
591 * Check if the default input / output device has been changed.
592 */
593 const AudioObjectPropertyAddress *pProperty = &paAddresses[idxAddress];
594 switch (pProperty->mSelector)
595 {
596 case kAudioHardwarePropertyDefaultInputDevice:
597 LogFlowFunc(("#%u: sel=kAudioHardwarePropertyDefaultInputDevice scope=%#x element=%#x\n",
598 idxAddress, pProperty->mScope, pProperty->mElement));
599 //pDev = pThis->pDefaultDevIn;
600 break;
601
602 case kAudioHardwarePropertyDefaultOutputDevice:
603 LogFlowFunc(("#%u: sel=kAudioHardwarePropertyDefaultOutputDevice scope=%#x element=%#x\n",
604 idxAddress, pProperty->mScope, pProperty->mElement));
605 //pDev = pThis->pDefaultDevOut;
606 break;
607
608 default:
609 LogFlowFunc(("#%u: sel=%#x scope=%#x element=%#x\n",
610 idxAddress, pProperty->mSelector, pProperty->mScope, pProperty->mElement));
611 break;
612 }
613 }
614
615 /* Make sure to leave the critical section before notify higher drivers/devices. */
616 //rc2 = RTCritSectLeave(&pThis->CritSect);
617 //AssertRC(rc2);
618
619 /*
620 * Notify the driver/device above us about possible changes in devices.
621 */
622 if (pThis->pIHostAudioPort)
623 pThis->pIHostAudioPort->pfnNotifyDevicesChanged(pThis->pIHostAudioPort);
624
625 return noErr;
626}
627
628
629/*********************************************************************************************************************************
630* Worker Thread *
631*********************************************************************************************************************************/
632#ifdef CORE_AUDIO_WITH_WORKER_THREAD
633
634/**
635 * Message handling callback for CFMachPort.
636 */
637static void drvHostAudioCaThreadPortCallback(CFMachPortRef hPort, void *pvMsg, CFIndex cbMsg, void *pvUser)
638{
639 RT_NOREF(hPort, pvMsg, cbMsg, pvUser);
640 LogFunc(("hPort=%p pvMsg=%p cbMsg=%#x pvUser=%p\n", hPort, pvMsg, cbMsg, pvUser));
641}
642
643
644/**
645 * @callback_method_impl{FNRTTHREAD, Worker thread for buffer callbacks.}
646 */
647static DECLCALLBACK(int) drvHostAudioCaThread(RTTHREAD hThreadSelf, void *pvUser)
648{
649 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
650
651 /*
652 * Get the runloop, add the mach port to it and signal the constructor thread that we're ready.
653 */
654 pThis->hThreadRunLoop = CFRunLoopGetCurrent();
655 CFRetain(pThis->hThreadRunLoop);
656
657 CFRunLoopAddSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode);
658
659 int rc = RTThreadUserSignal(hThreadSelf);
660 AssertRCReturn(rc, rc);
661
662 /*
663 * Do work.
664 */
665 for (;;)
666 {
667 SInt32 rcRunLoop = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 30.0, TRUE);
668 Log8Func(("CFRunLoopRunInMode -> %d\n", rcRunLoop));
669 Assert(rcRunLoop != kCFRunLoopRunFinished);
670 if (rcRunLoop != kCFRunLoopRunStopped && rcRunLoop != kCFRunLoopRunFinished)
671 { /* likely */ }
672 else
673 break;
674 }
675
676 /*
677 * Clean up.
678 */
679 CFRunLoopRemoveSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode);
680 LogFunc(("The thread quits!\n"));
681 return VINF_SUCCESS;
682}
683
684#endif /* CORE_AUDIO_WITH_WORKER_THREAD */
685
686
687
688/*********************************************************************************************************************************
689* PDMIHOSTAUDIO *
690*********************************************************************************************************************************/
691
692/**
693 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
694 */
695static DECLCALLBACK(int) drvHostAudioCaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
696{
697 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
698 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
699
700 /*
701 * Fill in the config structure.
702 */
703 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio");
704 pBackendCfg->cbStream = sizeof(COREAUDIOSTREAM);
705 pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY;
706 /* For Core Audio we provide one stream per device for now. */
707 pBackendCfg->cMaxStreamsIn = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_IN);
708 pBackendCfg->cMaxStreamsOut = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_OUT);
709
710 LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS));
711 return VINF_SUCCESS;
712}
713
714
715/**
716 * Initializes a Core Audio-specific device data structure.
717 *
718 * @returns IPRT status code.
719 * @param pDevData Device data structure to initialize.
720 * @param deviceID Core Audio device ID to assign this structure to.
721 * @param fIsInput Whether this is an input device or not.
722 * @param pDrv Driver instance to use.
723 */
724static void drvHostAudioCaDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID,
725 bool fIsInput, PDRVHOSTCOREAUDIO pDrv)
726{
727 AssertPtrReturnVoid(pDevData);
728 AssertPtrReturnVoid(pDrv);
729
730 pDevData->deviceID = deviceID;
731 pDevData->pDrv = pDrv;
732
733 /* Get the device UUID. */
734 AudioObjectPropertyAddress PropAddrDevUUID =
735 {
736 kAudioDevicePropertyDeviceUID,
737 fIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
738 kAudioObjectPropertyElementMaster
739 };
740 UInt32 uSize = sizeof(pDevData->UUID);
741 OSStatus err = AudioObjectGetPropertyData(pDevData->deviceID, &PropAddrDevUUID, 0, NULL, &uSize, &pDevData->UUID);
742 if (err != noErr)
743 LogRel(("CoreAudio: Failed to retrieve device UUID for device %RU32 (%RI32)\n", deviceID, err));
744
745 RTListInit(&pDevData->lstStreams);
746}
747
748
749/**
750 * Does a (re-)enumeration of the host's playback + recording devices.
751 *
752 * @todo No, it doesn't do playback & recording, it does only what @a enmUsage
753 * says.
754 *
755 * @return IPRT status code.
756 * @param pThis Host audio driver instance.
757 * @param enmUsage Which devices to enumerate.
758 * @param pDevEnm Where to store the enumerated devices.
759 */
760static int drvHostAudioCaDevicesEnumerate(PDRVHOSTCOREAUDIO pThis, PDMAUDIODIR enmUsage, PPDMAUDIOHOSTENUM pDevEnm)
761{
762 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
763 AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER);
764
765 int rc = VINF_SUCCESS;
766
767 do /* (this is not a loop, just a device for avoid gotos while trying not to shoot oneself in the foot too badly.) */
768 {
769 /*
770 * First get the device ID of the default device.
771 */
772 AudioDeviceID defaultDeviceID = kAudioDeviceUnknown;
773 AudioObjectPropertyAddress PropAddrDefaultDev =
774 {
775 enmUsage == PDMAUDIODIR_IN ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice,
776 kAudioObjectPropertyScopeGlobal,
777 kAudioObjectPropertyElementMaster
778 };
779 UInt32 uSize = sizeof(defaultDeviceID);
780 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddrDefaultDev, 0, NULL, &uSize, &defaultDeviceID);
781 if (err != noErr)
782 {
783 LogRel(("CoreAudio: Unable to determine default %s device (%RI32)\n",
784 enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback", err));
785 return VERR_NOT_FOUND;
786 }
787
788 if (defaultDeviceID == kAudioDeviceUnknown)
789 {
790 LogFunc(("No default %s device found\n", enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback"));
791 /* Keep going. */
792 }
793
794 /*
795 * Get a list of all audio devices.
796 */
797 AudioObjectPropertyAddress PropAddrDevList =
798 {
799 kAudioHardwarePropertyDevices,
800 kAudioObjectPropertyScopeGlobal,
801 kAudioObjectPropertyElementMaster
802 };
803
804 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &PropAddrDevList, 0, NULL, &uSize);
805 if (err != kAudioHardwareNoError)
806 break;
807
808 AudioDeviceID *pDevIDs = (AudioDeviceID *)alloca(uSize);
809 if (pDevIDs == NULL)
810 break;
811
812 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddrDevList, 0, NULL, &uSize, pDevIDs);
813 if (err != kAudioHardwareNoError)
814 break;
815
816 PDMAudioHostEnumInit(pDevEnm);
817
818 UInt16 cDevices = uSize / sizeof(AudioDeviceID);
819
820 /*
821 * Try get details on each device and try add them to the enumeration result.
822 */
823 PCOREAUDIODEVICEDATA pDev = NULL;
824 for (UInt16 i = 0; i < cDevices; i++)
825 {
826 if (pDev) /* Some (skipped) device to clean up first? */
827 PDMAudioHostDevFree(&pDev->Core);
828
829 pDev = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDev));
830 if (!pDev)
831 {
832 rc = VERR_NO_MEMORY;
833 break;
834 }
835
836 /* Set usage. */
837 pDev->Core.enmUsage = enmUsage;
838
839 /* Init backend-specific device data. */
840 drvHostAudioCaDeviceDataInit(pDev, pDevIDs[i], enmUsage == PDMAUDIODIR_IN, pThis);
841
842 /* Check if the device is valid. */
843 AudioDeviceID curDevID = pDev->deviceID;
844
845 /* Is the device the default device? */
846 if (curDevID == defaultDeviceID)
847 pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT;
848
849 AudioObjectPropertyAddress PropAddrCfg =
850 {
851 kAudioDevicePropertyStreamConfiguration,
852 enmUsage == PDMAUDIODIR_IN ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
853 kAudioObjectPropertyElementMaster
854 };
855 err = AudioObjectGetPropertyDataSize(curDevID, &PropAddrCfg, 0, NULL, &uSize);
856 if (err != noErr)
857 continue;
858
859 AudioBufferList *pBufList = (AudioBufferList *)RTMemAlloc(uSize);
860 if (!pBufList)
861 continue;
862
863 err = AudioObjectGetPropertyData(curDevID, &PropAddrCfg, 0, NULL, &uSize, pBufList);
864 if (err == noErr)
865 {
866 for (UInt32 a = 0; a < pBufList->mNumberBuffers; a++)
867 {
868 if (enmUsage == PDMAUDIODIR_IN)
869 pDev->Core.cMaxInputChannels += pBufList->mBuffers[a].mNumberChannels;
870 else if (enmUsage == PDMAUDIODIR_OUT)
871 pDev->Core.cMaxOutputChannels += pBufList->mBuffers[a].mNumberChannels;
872 }
873 }
874
875 RTMemFree(pBufList);
876 pBufList = NULL;
877
878 /* Check if the device is valid, e.g. has any input/output channels according to its usage. */
879 if ( enmUsage == PDMAUDIODIR_IN
880 && !pDev->Core.cMaxInputChannels)
881 continue;
882 if ( enmUsage == PDMAUDIODIR_OUT
883 && !pDev->Core.cMaxOutputChannels)
884 continue;
885
886 /* Resolve the device's name. */
887 AudioObjectPropertyAddress PropAddrName =
888 {
889 kAudioObjectPropertyName,
890 enmUsage == PDMAUDIODIR_IN ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
891 kAudioObjectPropertyElementMaster
892 };
893 uSize = sizeof(CFStringRef);
894 CFStringRef pcfstrName = NULL;
895
896 err = AudioObjectGetPropertyData(curDevID, &PropAddrName, 0, NULL, &uSize, &pcfstrName);
897 if (err != kAudioHardwareNoError)
898 continue;
899
900 CFIndex cbName = CFStringGetMaximumSizeForEncoding(CFStringGetLength(pcfstrName), kCFStringEncodingUTF8) + 1;
901 if (cbName)
902 {
903 char *pszName = (char *)RTStrAlloc(cbName);
904 if ( pszName
905 && CFStringGetCString(pcfstrName, pszName, cbName, kCFStringEncodingUTF8))
906 RTStrCopy(pDev->Core.szName, sizeof(pDev->Core.szName), pszName);
907
908 LogFunc(("Device '%s': %RU32\n", pszName, curDevID));
909
910 if (pszName)
911 {
912 RTStrFree(pszName);
913 pszName = NULL;
914 }
915 }
916
917 CFRelease(pcfstrName);
918
919 /* Check if the device is alive for the intended usage. */
920 AudioObjectPropertyAddress PropAddrAlive =
921 {
922 kAudioDevicePropertyDeviceIsAlive,
923 enmUsage == PDMAUDIODIR_IN ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
924 kAudioObjectPropertyElementMaster
925 };
926
927 UInt32 uAlive = 0;
928 uSize = sizeof(uAlive);
929
930 err = AudioObjectGetPropertyData(curDevID, &PropAddrAlive, 0, NULL, &uSize, &uAlive);
931 if ( (err == noErr)
932 && !uAlive)
933 {
934 pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD;
935 }
936
937 /* Check if the device is being hogged by someone else. */
938 AudioObjectPropertyAddress PropAddrHogged =
939 {
940 kAudioDevicePropertyHogMode,
941 kAudioObjectPropertyScopeGlobal,
942 kAudioObjectPropertyElementMaster
943 };
944
945 pid_t pid = 0;
946 uSize = sizeof(pid);
947
948 err = AudioObjectGetPropertyData(curDevID, &PropAddrHogged, 0, NULL, &uSize, &pid);
949 if ( (err == noErr)
950 && (pid != -1))
951 {
952 pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_LOCKED;
953 }
954
955 /* Add the device to the enumeration. */
956 PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
957
958 /* NULL device pointer because it's now part of the device enumeration. */
959 pDev = NULL;
960 }
961
962 if (RT_FAILURE(rc))
963 {
964 PDMAudioHostDevFree(&pDev->Core);
965 pDev = NULL;
966 }
967
968 } while (0);
969
970 if (RT_SUCCESS(rc))
971 {
972#ifdef LOG_ENABLED
973 LogFunc(("Devices for pDevEnm=%p, enmUsage=%RU32:\n", pDevEnm, enmUsage));
974 PDMAudioHostEnumLog(pDevEnm, "Core Audio");
975#endif
976 }
977 else
978 PDMAudioHostEnumDelete(pDevEnm);
979
980 LogFlowFuncLeaveRC(rc);
981 return rc;
982}
983
984
985/**
986 * Checks if an audio device with a specific device ID is in the given device
987 * enumeration or not.
988 *
989 * @retval true if the node is the last element in the list.
990 * @retval false otherwise.
991 *
992 * @param pEnmSrc Device enumeration to search device ID in.
993 * @param deviceID Device ID to search.
994 */
995static bool drvHostAudioCaDevicesHasDevice(PPDMAUDIOHOSTENUM pEnmSrc, AudioDeviceID deviceID)
996{
997 PCOREAUDIODEVICEDATA pDevSrc;
998 RTListForEach(&pEnmSrc->LstDevices, pDevSrc, COREAUDIODEVICEDATA, Core.ListEntry)
999 {
1000 if (pDevSrc->deviceID == deviceID)
1001 return true;
1002 }
1003
1004 return false;
1005}
1006
1007
1008/**
1009 * Enumerates all host devices and builds a final device enumeration list, consisting
1010 * of (duplex) input and output devices.
1011 *
1012 * @return IPRT status code.
1013 * @param pThis Host audio driver instance.
1014 * @param pEnmDst Where to store the device enumeration list.
1015 */
1016static int drvHostAudioCaDevicesEnumerateAll(PDRVHOSTCOREAUDIO pThis, PPDMAUDIOHOSTENUM pEnmDst)
1017{
1018 PDMAUDIOHOSTENUM devEnmIn;
1019 int rc = drvHostAudioCaDevicesEnumerate(pThis, PDMAUDIODIR_IN, &devEnmIn);
1020 if (RT_SUCCESS(rc))
1021 {
1022 PDMAUDIOHOSTENUM devEnmOut;
1023 rc = drvHostAudioCaDevicesEnumerate(pThis, PDMAUDIODIR_OUT, &devEnmOut);
1024 if (RT_SUCCESS(rc))
1025 {
1026
1027/** @todo r=bird: This is an awfully complicated and inefficient way of doing
1028 * it. Here you could just merge the two list (walk one, remove duplicates
1029 * from the other one) and skip all that duplication.
1030 *
1031 * Howerver, drvHostAudioCaDevicesEnumerate gets the device list twice, which is
1032 * a complete waste of time. You could easily do all the work in
1033 * drvHostAudioCaDevicesEnumerate by just querying the IDs of both default
1034 * devices we're interested in, saving the merging extra allocations and
1035 * extra allocation. */
1036
1037 /*
1038 * Build up the final device enumeration, based on the input and output device lists
1039 * just enumerated.
1040 *
1041 * Also make sure to handle duplex devices, that is, devices which act as input and output
1042 * at the same time.
1043 */
1044 PDMAudioHostEnumInit(pEnmDst);
1045 PCOREAUDIODEVICEDATA pDevSrcIn;
1046 RTListForEach(&devEnmIn.LstDevices, pDevSrcIn, COREAUDIODEVICEDATA, Core.ListEntry)
1047 {
1048 PCOREAUDIODEVICEDATA pDevDst = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDevDst));
1049 if (!pDevDst)
1050 {
1051 rc = VERR_NO_MEMORY;
1052 break;
1053 }
1054
1055 drvHostAudioCaDeviceDataInit(pDevDst, pDevSrcIn->deviceID, true /* fIsInput */, pThis);
1056
1057 RTStrCopy(pDevDst->Core.szName, sizeof(pDevDst->Core.szName), pDevSrcIn->Core.szName);
1058
1059 pDevDst->Core.enmUsage = PDMAUDIODIR_IN; /* Input device by default (simplex). */
1060 pDevDst->Core.cMaxInputChannels = pDevSrcIn->Core.cMaxInputChannels;
1061
1062 /* Handle flags. */
1063 if (pDevSrcIn->Core.fFlags & PDMAUDIOHOSTDEV_F_DEFAULT)
1064 pDevDst->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT;
1065 /** @todo Handle hot plugging? */
1066
1067 /*
1068 * Now search through the list of all found output devices and check if we found
1069 * an output device with the same device ID as the currently handled input device.
1070 *
1071 * If found, this means we have to treat that device as a duplex device then.
1072 */
1073 PCOREAUDIODEVICEDATA pDevSrcOut;
1074 RTListForEach(&devEnmOut.LstDevices, pDevSrcOut, COREAUDIODEVICEDATA, Core.ListEntry)
1075 {
1076 if (pDevSrcIn->deviceID == pDevSrcOut->deviceID)
1077 {
1078 pDevDst->Core.enmUsage = PDMAUDIODIR_DUPLEX;
1079 pDevDst->Core.cMaxOutputChannels = pDevSrcOut->Core.cMaxOutputChannels;
1080
1081 if (pDevSrcOut->Core.fFlags & PDMAUDIOHOSTDEV_F_DEFAULT)
1082 pDevDst->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT;
1083 break;
1084 }
1085 }
1086
1087 if (RT_SUCCESS(rc))
1088 PDMAudioHostEnumAppend(pEnmDst, &pDevDst->Core);
1089 else
1090 {
1091 PDMAudioHostDevFree(&pDevDst->Core);
1092 pDevDst = NULL;
1093 }
1094 }
1095
1096 if (RT_SUCCESS(rc))
1097 {
1098 /*
1099 * As a last step, add all remaining output devices which have not been handled in the loop above,
1100 * that is, all output devices which operate in simplex mode.
1101 */
1102 PCOREAUDIODEVICEDATA pDevSrcOut;
1103 RTListForEach(&devEnmOut.LstDevices, pDevSrcOut, COREAUDIODEVICEDATA, Core.ListEntry)
1104 {
1105 if (drvHostAudioCaDevicesHasDevice(pEnmDst, pDevSrcOut->deviceID))
1106 continue; /* Already in our list, skip. */
1107
1108 PCOREAUDIODEVICEDATA pDevDst = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDevDst));
1109 if (!pDevDst)
1110 {
1111 rc = VERR_NO_MEMORY;
1112 break;
1113 }
1114
1115 drvHostAudioCaDeviceDataInit(pDevDst, pDevSrcOut->deviceID, false /* fIsInput */, pThis);
1116
1117 RTStrCopy(pDevDst->Core.szName, sizeof(pDevDst->Core.szName), pDevSrcOut->Core.szName);
1118
1119 pDevDst->Core.enmUsage = PDMAUDIODIR_OUT;
1120 pDevDst->Core.cMaxOutputChannels = pDevSrcOut->Core.cMaxOutputChannels;
1121
1122 pDevDst->deviceID = pDevSrcOut->deviceID;
1123
1124 /* Handle flags. */
1125 if (pDevSrcOut->Core.fFlags & PDMAUDIOHOSTDEV_F_DEFAULT)
1126 pDevDst->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT;
1127 /** @todo Handle hot plugging? */
1128
1129 PDMAudioHostEnumAppend(pEnmDst, &pDevDst->Core);
1130 }
1131 }
1132
1133 if (RT_FAILURE(rc))
1134 PDMAudioHostEnumDelete(pEnmDst);
1135
1136 PDMAudioHostEnumDelete(&devEnmOut);
1137 }
1138
1139 PDMAudioHostEnumDelete(&devEnmIn);
1140 }
1141
1142#ifdef LOG_ENABLED
1143 if (RT_SUCCESS(rc))
1144 PDMAudioHostEnumLog(pEnmDst, "Core Audio (Final)");
1145#endif
1146
1147 LogFlowFuncLeaveRC(rc);
1148 return rc;
1149}
1150
1151
1152/**
1153 * Registers callbacks for a specific Core Audio device.
1154 *
1155 * @return IPRT status code.
1156 * @param pThis Host audio driver instance.
1157 * @param pDev Audio device to use for the registered callbacks.
1158 */
1159static int drvHostAudioCaDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PCOREAUDIODEVICEDATA pDev)
1160{
1161 RT_NOREF(pThis);
1162
1163 AudioDeviceID deviceID = kAudioDeviceUnknown;
1164 Assert(pDev && pDev->Core.cbSelf == sizeof(*pDev));
1165 if (pDev && pDev->Core.cbSelf == sizeof(*pDev)) /* paranoia or actually needed? */
1166 deviceID = pDev->deviceID;
1167
1168 if (deviceID != kAudioDeviceUnknown)
1169 {
1170 LogFunc(("deviceID=%RU32\n", deviceID));
1171
1172 /*
1173 * Register device callbacks.
1174 */
1175 AudioObjectPropertyAddress PropAddr =
1176 {
1177 kAudioDevicePropertyDeviceIsAlive,
1178 kAudioObjectPropertyScopeGlobal,
1179 kAudioObjectPropertyElementMaster
1180 };
1181 OSStatus err = AudioObjectAddPropertyListener(deviceID, &PropAddr,
1182 drvHostAudioCaDeviceIsAliveChangedCallback, pDev /*pvUser*/);
1183 if ( err != noErr
1184 && err != kAudioHardwareIllegalOperationError)
1185 LogRel(("CoreAudio: Failed to add the recording device state changed listener (%RI32)\n", err));
1186
1187 PropAddr.mSelector = kAudioDeviceProcessorOverload;
1188 PropAddr.mScope = kAudioUnitScope_Global;
1189 err = AudioObjectAddPropertyListener(deviceID, &PropAddr, drvHostAudioCaDevicePropertyChangedCallback, pDev /* pvUser */);
1190 if (err != noErr)
1191 LogRel(("CoreAudio: Failed to register processor overload listener (%RI32)\n", err));
1192
1193 PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
1194 PropAddr.mScope = kAudioUnitScope_Global;
1195 err = AudioObjectAddPropertyListener(deviceID, &PropAddr, drvHostAudioCaDevicePropertyChangedCallback, pDev /* pvUser */);
1196 if (err != noErr)
1197 LogRel(("CoreAudio: Failed to register sample rate changed listener (%RI32)\n", err));
1198 }
1199
1200 return VINF_SUCCESS;
1201}
1202
1203
1204/**
1205 * Unregisters all formerly registered callbacks of a Core Audio device again.
1206 *
1207 * @return IPRT status code.
1208 * @param pThis Host audio driver instance.
1209 * @param pDev Audio device to use for the registered callbacks.
1210 */
1211static int drvHostAudioCaDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PCOREAUDIODEVICEDATA pDev)
1212{
1213 RT_NOREF(pThis);
1214
1215 AudioDeviceID deviceID = kAudioDeviceUnknown;
1216 Assert(pDev && pDev->Core.cbSelf == sizeof(*pDev));
1217 if (pDev && pDev->Core.cbSelf == sizeof(*pDev)) /* paranoia or actually needed? */
1218 deviceID = pDev->deviceID;
1219
1220 if (deviceID != kAudioDeviceUnknown)
1221 {
1222 LogFunc(("deviceID=%RU32\n", deviceID));
1223
1224 /*
1225 * Unregister per-device callbacks.
1226 */
1227 AudioObjectPropertyAddress PropAddr =
1228 {
1229 kAudioDeviceProcessorOverload,
1230 kAudioObjectPropertyScopeGlobal,
1231 kAudioObjectPropertyElementMaster
1232 };
1233 OSStatus err = AudioObjectRemovePropertyListener(deviceID, &PropAddr, drvHostAudioCaDevicePropertyChangedCallback, pDev /* pvUser */);
1234 if ( err != noErr
1235 && err != kAudioHardwareBadObjectError)
1236 LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%RI32)\n", err));
1237
1238 PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
1239 err = AudioObjectRemovePropertyListener(deviceID, &PropAddr, drvHostAudioCaDevicePropertyChangedCallback, pDev /* pvUser */);
1240 if ( err != noErr
1241 && err != kAudioHardwareBadObjectError)
1242 LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%RI32)\n", err));
1243
1244 PropAddr.mSelector = kAudioDevicePropertyDeviceIsAlive;
1245 err = AudioObjectRemovePropertyListener(deviceID, &PropAddr, drvHostAudioCaDeviceIsAliveChangedCallback, pDev /* pvUser */);
1246 if ( err != noErr
1247 && err != kAudioHardwareBadObjectError)
1248 LogRel(("CoreAudio: Failed to remove the device alive listener (%RI32)\n", err));
1249 }
1250
1251 return VINF_SUCCESS;
1252}
1253
1254
1255/**
1256 * Enumerates all available host audio devices internally.
1257 *
1258 * @returns IPRT status code.
1259 * @param pThis Host audio driver instance.
1260 */
1261static int drvHostAudioCaEnumerateDevices(PDRVHOSTCOREAUDIO pThis)
1262{
1263 LogFlowFuncEnter();
1264
1265 /*
1266 * Unregister old default devices, if any.
1267 */
1268 if (pThis->pDefaultDevIn)
1269 {
1270 drvHostAudioCaDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevIn);
1271 pThis->pDefaultDevIn = NULL;
1272 }
1273
1274 if (pThis->pDefaultDevOut)
1275 {
1276 drvHostAudioCaDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevOut);
1277 pThis->pDefaultDevOut = NULL;
1278 }
1279
1280 /* Remove old / stale device entries. */
1281 PDMAudioHostEnumDelete(&pThis->Devices);
1282
1283 /* Enumerate all devices internally. */
1284 int rc = drvHostAudioCaDevicesEnumerateAll(pThis, &pThis->Devices);
1285 if (RT_SUCCESS(rc))
1286 {
1287 /*
1288 * Default input device.
1289 */
1290 pThis->pDefaultDevIn = (PCOREAUDIODEVICEDATA)PDMAudioHostEnumGetDefault(&pThis->Devices, PDMAUDIODIR_IN);
1291 if (pThis->pDefaultDevIn)
1292 {
1293 LogRel2(("CoreAudio: Default capturing device is '%s'\n", pThis->pDefaultDevIn->Core.szName));
1294 LogFunc(("pDefaultDevIn=%p, ID=%RU32\n", pThis->pDefaultDevIn, pThis->pDefaultDevIn->deviceID));
1295 rc = drvHostAudioCaDeviceRegisterCallbacks(pThis, pThis->pDefaultDevIn);
1296 }
1297 else
1298 LogRel2(("CoreAudio: No default capturing device found\n"));
1299
1300 /*
1301 * Default output device.
1302 */
1303 pThis->pDefaultDevOut = (PCOREAUDIODEVICEDATA)PDMAudioHostEnumGetDefault(&pThis->Devices, PDMAUDIODIR_OUT);
1304 if (pThis->pDefaultDevOut)
1305 {
1306 LogRel2(("CoreAudio: Default playback device is '%s'\n", pThis->pDefaultDevOut->Core.szName));
1307 LogFunc(("pDefaultDevOut=%p, ID=%RU32\n", pThis->pDefaultDevOut, pThis->pDefaultDevOut->deviceID));
1308 rc = drvHostAudioCaDeviceRegisterCallbacks(pThis, pThis->pDefaultDevOut);
1309 }
1310 else
1311 LogRel2(("CoreAudio: No default playback device found\n"));
1312 }
1313
1314 LogFunc(("Returning %Rrc\n", rc));
1315 return rc;
1316}
1317
1318
1319/**
1320 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
1321 */
1322static DECLCALLBACK(int) drvHostAudioCaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
1323{
1324 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1325 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
1326
1327 PDMAudioHostEnumInit(pDeviceEnum);
1328
1329 /*
1330 * We update the enumeration associated with pThis.
1331 */
1332 int rc = RTCritSectEnter(&pThis->CritSect);
1333 if (RT_SUCCESS(rc))
1334 {
1335 rc = drvHostAudioCaEnumerateDevices(pThis);
1336 if (RT_SUCCESS(rc))
1337 {
1338 /*
1339 * Return a copy with only PDMAUDIOHOSTDEV and none of the extra
1340 * bits in COREAUDIODEVICEDATA.
1341 */
1342 rc = PDMAudioHostEnumCopy(pDeviceEnum, &pThis->Devices, PDMAUDIODIR_INVALID /*all*/, true /*fOnlyCoreData*/);
1343 if (RT_FAILURE(rc))
1344 PDMAudioHostEnumDelete(pDeviceEnum);
1345 }
1346
1347 RTCritSectLeave(&pThis->CritSect);
1348 }
1349
1350 LogFlowFunc(("returns %Rrc\n", rc));
1351 return rc;
1352}
1353
1354
1355/**
1356 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1357 */
1358static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAudioCaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1359{
1360 RT_NOREF(pInterface, enmDir);
1361 return PDMAUDIOBACKENDSTS_RUNNING;
1362}
1363
1364
1365/**
1366 * Output audio queue buffer callback.
1367 *
1368 * Called whenever an audio queue is done processing a buffer. This routine
1369 * will set the data fill size to zero and mark it as unqueued so that
1370 * drvHostAudioCaHA_StreamPlay knowns it can use it.
1371 *
1372 * @param pvUser User argument.
1373 * @param hAudioQueue Audio queue to process output data for.
1374 * @param pAudioBuffer Audio buffer to store output data in.
1375 *
1376 * @thread queue thread.
1377 */
1378static void drvHostAudioCaOutputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue, AudioQueueBufferRef pAudioBuffer)
1379{
1380 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
1381 AssertPtr(pStreamCA);
1382 Assert(pStreamCA->hAudioQueue == hAudioQueue);
1383 RT_NOREF(hAudioQueue);
1384
1385 uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData;
1386 Log4Func(("Got back buffer #%zu (%p)\n", idxBuf, pAudioBuffer));
1387 AssertReturnVoid( idxBuf < pStreamCA->cBuffers
1388 && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer);
1389
1390 pAudioBuffer->mAudioDataByteSize = 0;
1391 bool fWasQueued = ASMAtomicXchgBool(&pStreamCA->paBuffers[idxBuf].fQueued, false);
1392 Assert(fWasQueued); RT_NOREF(fWasQueued);
1393}
1394
1395
1396/**
1397 * Input audio queue buffer callback.
1398 *
1399 * Called whenever input data from the audio queue becomes available. This
1400 * routine will mark the buffer unqueued so that drvHostAudioCaHA_StreamCapture
1401 * can read the data from it.
1402 *
1403 * @param pvUser User argument.
1404 * @param hAudioQueue Audio queue to process input data from.
1405 * @param pAudioBuffer Audio buffer to process input data from.
1406 * @param pAudioTS Audio timestamp.
1407 * @param cPacketDesc Number of packet descriptors.
1408 * @param paPacketDesc Array of packet descriptors.
1409 */
1410static void drvHostAudioCaInputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue,
1411 AudioQueueBufferRef pAudioBuffer, const AudioTimeStamp *pAudioTS,
1412 UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc)
1413{
1414 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
1415 AssertPtr(pStreamCA);
1416 Assert(pStreamCA->hAudioQueue == hAudioQueue);
1417 RT_NOREF(hAudioQueue, pAudioTS, cPacketDesc, paPacketDesc);
1418
1419 uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData;
1420 Log4Func(("Got back buffer #%zu (%p) with %#x bytes\n", idxBuf, pAudioBuffer, pAudioBuffer->mAudioDataByteSize));
1421 AssertReturnVoid( idxBuf < pStreamCA->cBuffers
1422 && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer);
1423
1424 bool fWasQueued = ASMAtomicXchgBool(&pStreamCA->paBuffers[idxBuf].fQueued, false);
1425 Assert(fWasQueued); RT_NOREF(fWasQueued);
1426}
1427
1428
1429/**
1430 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1431 */
1432static DECLCALLBACK(int) drvHostAudioCaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1433 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1434{
1435 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1436 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1437 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1438 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1439 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1440 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1441 int rc;
1442
1443 /** @todo This takes too long. Stats indicates it may take up to 200 ms.
1444 * Knoppix guest resets the stream and we hear nada because the
1445 * draining is aborted when the stream is destroyed. Should try use
1446 * async init for parts (much) of this. */
1447
1448 /*
1449 * Permission check for input devices before we start.
1450 */
1451 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1452 {
1453 rc = coreAudioInputPermissionCheck();
1454 if (RT_FAILURE(rc))
1455 return rc;
1456 }
1457
1458 /*
1459 * Do we have a device for the requested stream direction?
1460 */
1461 PCOREAUDIODEVICEDATA pDev = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pDefaultDevIn : pThis->pDefaultDevOut;
1462#ifdef LOG_ENABLED
1463 char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
1464#endif
1465 LogFunc(("pDev=%p *pCfgReq: %s\n", pDev, PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp)) ));
1466 if (pDev)
1467 {
1468 Assert(pDev->Core.cbSelf == sizeof(*pDev));
1469
1470 /*
1471 * Basic structure init.
1472 */
1473 pStreamCA->fEnabled = false;
1474 pStreamCA->fStarted = false;
1475 pStreamCA->fDraining = false;
1476 pStreamCA->fRestartOnResume = false;
1477 pStreamCA->offInternal = 0;
1478 pStreamCA->idxBuffer = 0;
1479 pStreamCA->Unit.pDevice = pDev; /** @todo r=bird: How do we protect this against enumeration releasing pDefaultDevOut/In. */
1480 pStreamCA->enmInitState = COREAUDIOINITSTATE_IN_INIT;
1481
1482 rc = RTCritSectInit(&pStreamCA->CritSect);
1483 if (RT_SUCCESS(rc))
1484 {
1485 /*
1486 * Do format conversion and create the circular buffer we use to shuffle
1487 * data to/from the queue thread.
1488 */
1489 PDMAudioStrmCfgCopy(&pStreamCA->Cfg, pCfgReq);
1490 drvHostAudioCaPCMPropsToASBD(&pCfgReq->Props, &pStreamCA->BasicStreamDesc);
1491 /** @todo Do some validation? */
1492 drvHostAudioCaPrintASBD( pCfgReq->enmDir == PDMAUDIODIR_IN
1493 ? "Capturing queue format"
1494 : "Playback queue format", &pStreamCA->BasicStreamDesc);
1495 /*
1496 * Create audio queue.
1497 *
1498 * Documentation says the callbacks will be run on some core audio
1499 * related thread if we don't specify a runloop here. That's simpler.
1500 */
1501#ifdef CORE_AUDIO_WITH_WORKER_THREAD
1502 CFRunLoopRef const hRunLoop = pThis->hThreadRunLoop;
1503 CFStringRef const hRunLoopMode = kCFRunLoopDefaultMode;
1504#else
1505 CFRunLoopRef const hRunLoop = NULL;
1506 CFStringRef const hRunLoopMode = NULL;
1507#endif
1508 OSStatus orc;
1509 if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
1510 orc = AudioQueueNewOutput(&pStreamCA->BasicStreamDesc, drvHostAudioCaOutputQueueBufferCallback, pStreamCA,
1511 hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue);
1512 else
1513 orc = AudioQueueNewInput(&pStreamCA->BasicStreamDesc, drvHostAudioCaInputQueueBufferCallback, pStreamCA,
1514 hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue);
1515 LogFlowFunc(("AudioQueueNew%s -> %#x\n", pCfgReq->enmDir == PDMAUDIODIR_OUT ? "Output" : "Input", orc));
1516 if (orc == noErr)
1517 {
1518 /*
1519 * Assign device to the queue.
1520 */
1521 UInt32 uSize = sizeof(pDev->UUID);
1522 orc = AudioQueueSetProperty(pStreamCA->hAudioQueue, kAudioQueueProperty_CurrentDevice, &pDev->UUID, uSize);
1523 LogFlowFunc(("AudioQueueSetProperty -> %#x\n", orc));
1524 if (orc == noErr)
1525 {
1526 /*
1527 * Sanity-adjust the requested buffer size.
1528 */
1529 uint32_t cFramesBufferSizeMax = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 2 * RT_MS_1SEC);
1530 uint32_t cFramesBufferSize = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 32 /*ms*/);
1531 cFramesBufferSize = RT_MAX(cFramesBufferSize, pCfgReq->Backend.cFramesBufferSize);
1532 cFramesBufferSize = RT_MIN(cFramesBufferSize, cFramesBufferSizeMax);
1533
1534 /*
1535 * The queue buffers size is based on cMsSchedulingHint so that we're likely to
1536 * have a new one ready/done after each guest DMA transfer. We must however
1537 * make sure we don't end up with too may or too few.
1538 */
1539 Assert(pCfgReq->Device.cMsSchedulingHint > 0);
1540 uint32_t cFramesQueueBuffer = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props,
1541 pCfgReq->Device.cMsSchedulingHint > 0
1542 ? pCfgReq->Device.cMsSchedulingHint : 10);
1543 uint32_t cQueueBuffers;
1544 if (cFramesQueueBuffer * COREAUDIO_MIN_BUFFERS <= cFramesBufferSize)
1545 {
1546 cQueueBuffers = cFramesBufferSize / cFramesQueueBuffer;
1547 if (cQueueBuffers > COREAUDIO_MAX_BUFFERS)
1548 {
1549 cQueueBuffers = COREAUDIO_MAX_BUFFERS;
1550 cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MAX_BUFFERS;
1551 }
1552 }
1553 else
1554 {
1555 cQueueBuffers = COREAUDIO_MIN_BUFFERS;
1556 cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MIN_BUFFERS;
1557 }
1558
1559 cFramesBufferSize = cQueueBuffers * cFramesBufferSize;
1560
1561 /*
1562 * Allocate the audio queue buffers.
1563 */
1564 pStreamCA->paBuffers = (PCOREAUDIOBUF)RTMemAllocZ(sizeof(pStreamCA->paBuffers[0]) * cQueueBuffers);
1565 if (pStreamCA->paBuffers != NULL)
1566 {
1567 pStreamCA->cBuffers = cQueueBuffers;
1568
1569 const size_t cbQueueBuffer = PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, cFramesQueueBuffer);
1570 LogFlowFunc(("Allocating %u, each %#x bytes / %u frames\n", cQueueBuffers, cbQueueBuffer, cFramesQueueBuffer));
1571 cFramesBufferSize = 0;
1572 for (uint32_t iBuf = 0; iBuf < cQueueBuffers; iBuf++)
1573 {
1574 AudioQueueBufferRef pBuf = NULL;
1575 orc = AudioQueueAllocateBuffer(pStreamCA->hAudioQueue, cbQueueBuffer, &pBuf);
1576 if (RT_LIKELY(orc == noErr))
1577 {
1578 pBuf->mUserData = (void *)(uintptr_t)iBuf;
1579 pStreamCA->paBuffers[iBuf].pBuf = pBuf;
1580 cFramesBufferSize += PDMAudioPropsBytesToFrames(&pStreamCA->Cfg.Props,
1581 pBuf->mAudioDataBytesCapacity);
1582 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, pBuf->mAudioDataBytesCapacity));
1583 }
1584 else
1585 {
1586 LogRel(("CoreAudio: Out of memory (buffer %#x out of %#x, %#x bytes)\n",
1587 iBuf, cQueueBuffers, cbQueueBuffer));
1588 while (iBuf-- > 0)
1589 {
1590 AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
1591 pStreamCA->paBuffers[iBuf].pBuf = NULL;
1592 }
1593 break;
1594 }
1595 }
1596 if (orc == noErr)
1597 {
1598 /*
1599 * Update the stream config.
1600 */
1601 pStreamCA->Cfg.Backend.cFramesBufferSize = cFramesBufferSize;
1602 pStreamCA->Cfg.Backend.cFramesPeriod = cFramesQueueBuffer; /* whatever */
1603 pStreamCA->Cfg.Backend.cFramesPreBuffering = pStreamCA->Cfg.Backend.cFramesPreBuffering
1604 * pStreamCA->Cfg.Backend.cFramesBufferSize
1605 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
1606
1607 PDMAudioStrmCfgCopy(pCfgAcq, &pStreamCA->Cfg);
1608
1609 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_INIT);
1610
1611 LogFunc(("returns VINF_SUCCESS\n"));
1612 return VINF_SUCCESS;
1613 }
1614 RTMemFree(pStreamCA->paBuffers);
1615 }
1616 else
1617 rc = VERR_NO_MEMORY;
1618 }
1619 else
1620 LogRelMax(64, ("CoreAudio: Failed to associate device with queue: %#x (%d)\n", orc, orc));
1621 AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
1622 }
1623 else
1624 LogRelMax(64, ("CoreAudio: Failed to create audio queue: %#x (%d)\n", orc, orc));
1625 RTCritSectDelete(&pStreamCA->CritSect);
1626 }
1627 else
1628 LogRel(("CoreAudio: Failed to initialize critical section for stream: %Rrc\n", rc));
1629 }
1630 else
1631 {
1632 LogRelMax(64, ("CoreAudio: No device for %s stream.\n", PDMAudioDirGetName(pCfgReq->enmDir)));
1633 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1634 }
1635
1636 LogFunc(("returns %Rrc\n", rc));
1637 return rc;
1638}
1639
1640
1641/**
1642 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1643 */
1644static DECLCALLBACK(int) drvHostAudioCaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1645{
1646 RT_NOREF(pInterface);
1647 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1648 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1649 LogFunc(("%p: %s\n", pStreamCA, pStreamCA->Cfg.szName));
1650#ifdef LOG_ENABLED
1651 uint64_t const nsStart = RTTimeNanoTS();
1652#endif
1653
1654 /*
1655 * Never mind if the status isn't INIT (it should always be, though).
1656 */
1657 COREAUDIOINITSTATE const enmInitState = (COREAUDIOINITSTATE)ASMAtomicReadU32(&pStreamCA->enmInitState);
1658 AssertMsg(enmInitState == COREAUDIOINITSTATE_INIT, ("%d\n", enmInitState));
1659 if (enmInitState == COREAUDIOINITSTATE_INIT)
1660 {
1661 Assert(RTCritSectIsInitialized(&pStreamCA->CritSect));
1662
1663 /*
1664 * Change the stream state and stop the stream (just to be sure).
1665 */
1666 OSStatus orc;
1667 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_IN_UNINIT);
1668 if (pStreamCA->hAudioQueue)
1669 {
1670 orc = AudioQueueStop(pStreamCA->hAudioQueue, TRUE /*inImmediate/synchronously*/);
1671 LogFlowFunc(("AudioQueueStop -> %#x\n", orc));
1672 }
1673
1674 /*
1675 * Enter and leave the critsect afterwards for paranoid reasons.
1676 */
1677 RTCritSectEnter(&pStreamCA->CritSect);
1678 RTCritSectLeave(&pStreamCA->CritSect);
1679
1680 /*
1681 * Free the queue buffers and the queue.
1682 *
1683 * This may take a while. The AudioQueueReset call seems to helps
1684 * reducing time stuck in AudioQueueDispose.
1685 */
1686#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1687 LogRel(("Queue-destruction timer starting...\n"));
1688 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1689 RTTimerLRStart(pThis->hBreakpointTimer, RT_NS_100MS);
1690 uint64_t nsStart = RTTimeNanoTS();
1691#endif
1692
1693 /* Resetting the queue helps prevent AudioQueueDispose from taking a long time. */
1694 if (pStreamCA->hAudioQueue)
1695 {
1696 LogFlowFunc(("Calling AudioQueueReset ...\n"));
1697 orc = AudioQueueReset(pStreamCA->hAudioQueue);
1698 LogFlowFunc(("AudioQueueReset -> %#x\n", orc));
1699 }
1700
1701 if (pStreamCA->paBuffers)
1702 {
1703 LogFlowFunc(("Freeing %u buffers ...\n", pStreamCA->cBuffers));
1704 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1705 {
1706 orc = AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
1707 AssertMsg(orc == noErr, ("AudioQueueFreeBuffer(#%u) -> orc=%#x\n", iBuf, orc));
1708 pStreamCA->paBuffers[iBuf].pBuf = NULL;
1709 }
1710 RTMemFree(pStreamCA->paBuffers);
1711 pStreamCA->paBuffers = NULL;
1712 }
1713 pStreamCA->cBuffers = 0;
1714
1715 if (pStreamCA->hAudioQueue)
1716 {
1717 LogFlowFunc(("Disposing of the queue ...\n"));
1718 orc = AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate/synchronously*/); /* may take some time */
1719 LogFlowFunc(("AudioQueueDispose -> %#x (%d)\n", orc, orc));
1720 AssertMsg(orc == noErr, ("AudioQueueDispose -> orc=%#x\n", orc));
1721 pStreamCA->hAudioQueue = NULL;
1722 }
1723
1724#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1725 RTTimerLRStop(pThis->hBreakpointTimer);
1726 LogRel(("Queue-destruction: %'RU64\n", RTTimeNanoTS() - nsStart));
1727#endif
1728
1729 /*
1730 * Release the device and delete the critsect.
1731 */
1732 pStreamCA->Unit.pDevice = NULL; /** @todo This bugger must be refcounted! */
1733
1734 RTCritSectDelete(&pStreamCA->CritSect);
1735
1736 /*
1737 * Done.
1738 */
1739 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_UNINIT);
1740 }
1741 else
1742 LogFunc(("Wrong stream init state for %p: %d - leaking it\n", pStream, enmInitState));
1743
1744 LogFunc(("returns (took %'RU64 ns)\n", RTTimeNanoTS() - nsStart));
1745 return VINF_SUCCESS;
1746}
1747
1748
1749#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1750/** @callback_method_impl{FNRTTIMERLR, For debugging things that takes too long.} */
1751static DECLCALLBACK(void) drvHostAudioCaBreakpointTimer(RTTIMERLR hTimer, void *pvUser, uint64_t iTick)
1752{
1753 LogFlowFunc(("Queue-destruction timeout! iTick=%RU64\n", iTick));
1754 RT_NOREF(hTimer, pvUser, iTick);
1755 RTLogFlush(NULL);
1756 RT_BREAKPOINT();
1757}
1758#endif
1759
1760
1761/**
1762 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
1763 */
1764static DECLCALLBACK(int) drvHostAudioCaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1765{
1766 RT_NOREF(pInterface);
1767 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1768 LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA)));
1769 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1770 RTCritSectEnter(&pStreamCA->CritSect);
1771
1772 Assert(!pStreamCA->fEnabled);
1773 Assert(!pStreamCA->fStarted);
1774
1775 /*
1776 * We always reset the buffer before enabling the stream (normally never necessary).
1777 */
1778 OSStatus orc = AudioQueueReset(pStreamCA->hAudioQueue);
1779 if (orc != noErr)
1780 LogRelMax(64, ("CoreAudio: Stream reset failed when enabling '%s': %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1781 Assert(orc == noErr);
1782 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1783 Assert(!pStreamCA->paBuffers[iBuf].fQueued);
1784
1785 pStreamCA->offInternal = 0;
1786 pStreamCA->fDraining = false;
1787 pStreamCA->fEnabled = true;
1788 pStreamCA->fRestartOnResume = false;
1789 pStreamCA->idxBuffer = 0;
1790
1791 /*
1792 * Input streams will start capturing, while output streams will only start
1793 * playing once we get some audio data to play (see drvHostAudioCaHA_StreamPlay).
1794 */
1795 int rc = VINF_SUCCESS;
1796 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
1797 {
1798 /* Zero (probably not needed) and submit all the buffers first. */
1799 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1800 {
1801 AudioQueueBufferRef pBuf = pStreamCA->paBuffers[iBuf].pBuf;
1802
1803 RT_BZERO(pBuf->mAudioData, pBuf->mAudioDataBytesCapacity);
1804 pBuf->mAudioDataByteSize = 0;
1805 ASMAtomicWriteBool(&pStreamCA->paBuffers[iBuf].fQueued, true);
1806
1807 orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDescs*/, NULL /*inPacketDescs*/);
1808 AssertLogRelMsgBreakStmt(orc == noErr, ("CoreAudio: AudioQueueEnqueueBuffer(#%u) -> %#x (%d) - stream '%s'\n",
1809 iBuf, orc, orc, pStreamCA->Cfg.szName),
1810 pStreamCA->paBuffers[iBuf].fQueued = false);
1811 }
1812
1813 /* Start the stream. */
1814 if (orc == noErr)
1815 {
1816 LogFlowFunc(("Start input stream '%s'...\n", pStreamCA->Cfg.szName));
1817 orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
1818 AssertLogRelMsgStmt(orc == noErr, ("CoreAudio: AudioQueueStart(%s) -> %#x (%d) \n", pStreamCA->Cfg.szName, orc, orc),
1819 rc = VERR_AUDIO_STREAM_NOT_READY);
1820 pStreamCA->fStarted = orc == noErr;
1821 }
1822 else
1823 rc = VERR_AUDIO_STREAM_NOT_READY;
1824 }
1825 else
1826 Assert(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
1827
1828 RTCritSectLeave(&pStreamCA->CritSect);
1829 LogFlowFunc(("returns %Rrc\n", rc));
1830 return rc;
1831}
1832
1833
1834/**
1835 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
1836 */
1837static DECLCALLBACK(int) drvHostAudioCaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1838{
1839 RT_NOREF(pInterface);
1840 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1841 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1842 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
1843 pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
1844 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1845 RTCritSectEnter(&pStreamCA->CritSect);
1846
1847 /*
1848 * Always stop it (draining or no).
1849 */
1850 pStreamCA->fEnabled = false;
1851 pStreamCA->fRestartOnResume = false;
1852 Assert(!pStreamCA->fDraining || pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
1853
1854 int rc = VINF_SUCCESS;
1855 if (pStreamCA->fStarted)
1856 {
1857#if 0
1858 OSStatus orc2 = AudioQueueReset(pStreamCA->hAudioQueue);
1859 LogFlowFunc(("AudioQueueReset(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2);
1860 orc2 = AudioQueueFlush(pStreamCA->hAudioQueue);
1861 LogFlowFunc(("AudioQueueFlush(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2);
1862#endif
1863
1864 OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
1865 LogFlowFunc(("AudioQueueStop(%s,TRUE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1866 if (orc != noErr)
1867 {
1868 LogRelMax(64, ("CoreAudio: Stopping '%s' failed (disable): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1869 rc = VERR_GENERAL_FAILURE;
1870 }
1871 pStreamCA->fStarted = false;
1872 pStreamCA->fDraining = false;
1873 }
1874
1875 RTCritSectLeave(&pStreamCA->CritSect);
1876 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostAudioCaStreamStatusString(pStreamCA)));
1877 return rc;
1878}
1879
1880
1881/**
1882 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
1883 */
1884static DECLCALLBACK(int) drvHostAudioCaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1885{
1886 RT_NOREF(pInterface);
1887 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1888 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1889 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
1890 pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
1891 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1892 RTCritSectEnter(&pStreamCA->CritSect);
1893
1894 /*
1895 * Unless we're draining the stream, pause it if it has started.
1896 */
1897 int rc = VINF_SUCCESS;
1898 if (pStreamCA->fStarted && !pStreamCA->fDraining)
1899 {
1900 pStreamCA->fRestartOnResume = true;
1901
1902 OSStatus orc = AudioQueuePause(pStreamCA->hAudioQueue);
1903 LogFlowFunc(("AudioQueuePause(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1904 if (orc != noErr)
1905 {
1906 LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1907 rc = VERR_GENERAL_FAILURE;
1908 }
1909 pStreamCA->fStarted = false;
1910 }
1911 else
1912 {
1913 pStreamCA->fRestartOnResume = false;
1914 if (pStreamCA->fDraining)
1915 {
1916 LogFunc(("Stream '%s' is draining\n", pStreamCA->Cfg.szName));
1917 Assert(pStreamCA->fStarted);
1918 }
1919 }
1920
1921 RTCritSectLeave(&pStreamCA->CritSect);
1922 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostAudioCaStreamStatusString(pStreamCA)));
1923 return rc;
1924}
1925
1926
1927/**
1928 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
1929 */
1930static DECLCALLBACK(int) drvHostAudioCaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1931{
1932 RT_NOREF(pInterface);
1933 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1934 LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA)));
1935 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1936 RTCritSectEnter(&pStreamCA->CritSect);
1937
1938 /*
1939 * Resume according to state saved by drvHostAudioCaHA_StreamPause.
1940 */
1941 int rc = VINF_SUCCESS;
1942 if (pStreamCA->fRestartOnResume)
1943 {
1944 OSStatus orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
1945 LogFlowFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1946 if (orc != noErr)
1947 {
1948 LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1949 rc = VERR_AUDIO_STREAM_NOT_READY;
1950 }
1951 }
1952 pStreamCA->fRestartOnResume = false;
1953
1954 RTCritSectLeave(&pStreamCA->CritSect);
1955 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostAudioCaStreamStatusString(pStreamCA)));
1956 return rc;
1957}
1958
1959
1960/**
1961 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
1962 */
1963static DECLCALLBACK(int) drvHostAudioCaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1964{
1965 RT_NOREF(pInterface);
1966 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1967 AssertReturn(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1968 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1969 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
1970 pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
1971 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1972 RTCritSectEnter(&pStreamCA->CritSect);
1973
1974 /*
1975 * The AudioQueueStop function has both an immediate and a drain mode,
1976 * so we'll obviously use the latter here. For checking draining progress,
1977 * we will just check if all buffers have been returned or not.
1978 */
1979 int rc = VINF_SUCCESS;
1980 if (pStreamCA->fStarted)
1981 {
1982 if (!pStreamCA->fDraining)
1983 {
1984 OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, FALSE /*inImmediate*/);
1985 LogFlowFunc(("AudioQueueStop(%s, FALSE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1986 if (orc == noErr)
1987 pStreamCA->fDraining = true;
1988 else
1989 {
1990 LogRelMax(64, ("CoreAudio: Stopping '%s' failed (drain): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1991 rc = VERR_GENERAL_FAILURE;
1992 }
1993 }
1994 else
1995 LogFlowFunc(("Already draining '%s' ...\n", pStreamCA->Cfg.szName));
1996 }
1997 else
1998 {
1999 LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamCA->Cfg.szName));
2000 AssertStmt(!pStreamCA->fDraining, pStreamCA->fDraining = false);
2001 }
2002
2003 RTCritSectLeave(&pStreamCA->CritSect);
2004 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostAudioCaStreamStatusString(pStreamCA)));
2005 return rc;
2006}
2007
2008
2009/**
2010 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
2011 */
2012static DECLCALLBACK(int) drvHostAudioCaHA_StreamControl(PPDMIHOSTAUDIO pInterface,
2013 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2014{
2015 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
2016 * replacing it with individual StreamXxxx methods. That would save us
2017 * potentally huge switches and more easily see which drivers implement
2018 * which operations (grep for pfnStreamXxxx). */
2019 switch (enmStreamCmd)
2020 {
2021 case PDMAUDIOSTREAMCMD_ENABLE:
2022 return drvHostAudioCaHA_StreamEnable(pInterface, pStream);
2023 case PDMAUDIOSTREAMCMD_DISABLE:
2024 return drvHostAudioCaHA_StreamDisable(pInterface, pStream);
2025 case PDMAUDIOSTREAMCMD_PAUSE:
2026 return drvHostAudioCaHA_StreamPause(pInterface, pStream);
2027 case PDMAUDIOSTREAMCMD_RESUME:
2028 return drvHostAudioCaHA_StreamResume(pInterface, pStream);
2029 case PDMAUDIOSTREAMCMD_DRAIN:
2030 return drvHostAudioCaHA_StreamDrain(pInterface, pStream);
2031
2032 case PDMAUDIOSTREAMCMD_END:
2033 case PDMAUDIOSTREAMCMD_32BIT_HACK:
2034 case PDMAUDIOSTREAMCMD_INVALID:
2035 /* no default*/
2036 break;
2037 }
2038 return VERR_NOT_SUPPORTED;
2039}
2040
2041
2042/**
2043 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2044 */
2045static DECLCALLBACK(uint32_t) drvHostAudioCaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2046{
2047 RT_NOREF(pInterface);
2048 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2049 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2050 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
2051
2052 uint32_t cbReadable = 0;
2053 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
2054 {
2055 RTCritSectEnter(&pStreamCA->CritSect);
2056 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2057 uint32_t const cBuffers = pStreamCA->cBuffers;
2058 uint32_t const idxStart = pStreamCA->idxBuffer;
2059 uint32_t idxBuffer = idxStart;
2060
2061 if ( cBuffers > 0
2062 && !paBuffers[idxBuffer].fQueued)
2063 {
2064 do
2065 {
2066 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2067 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2068 uint32_t cbFill = pBuf->mAudioDataByteSize;
2069 AssertStmt(cbFill <= cbTotal, cbFill = cbTotal);
2070 uint32_t off = paBuffers[idxBuffer].offRead;
2071 AssertStmt(off < cbFill, off = cbFill);
2072
2073 cbReadable += cbFill - off;
2074
2075 /* Advance. */
2076 idxBuffer++;
2077 if (idxBuffer < cBuffers)
2078 { /* likely */ }
2079 else
2080 idxBuffer = 0;
2081 } while (idxBuffer != idxStart && !paBuffers[idxBuffer].fQueued);
2082 }
2083
2084 RTCritSectLeave(&pStreamCA->CritSect);
2085 }
2086 Log2Func(("returns %#x for '%s'\n", cbReadable, pStreamCA->Cfg.szName));
2087 return cbReadable;
2088}
2089
2090
2091/**
2092 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2093 */
2094static DECLCALLBACK(uint32_t) drvHostAudioCaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2095{
2096 RT_NOREF(pInterface);
2097 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2098 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2099 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
2100
2101 uint32_t cbWritable = 0;
2102 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT)
2103 {
2104 RTCritSectEnter(&pStreamCA->CritSect);
2105 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2106 uint32_t const cBuffers = pStreamCA->cBuffers;
2107 uint32_t const idxStart = pStreamCA->idxBuffer;
2108 uint32_t idxBuffer = idxStart;
2109
2110 if ( cBuffers > 0
2111 && !paBuffers[idxBuffer].fQueued)
2112 {
2113 do
2114 {
2115 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2116 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2117 uint32_t cbUsed = pBuf->mAudioDataByteSize;
2118 AssertStmt(cbUsed <= cbTotal, paBuffers[idxBuffer].pBuf->mAudioDataByteSize = cbUsed = cbTotal);
2119
2120 cbWritable += cbTotal - cbUsed;
2121
2122 /* Advance. */
2123 idxBuffer++;
2124 if (idxBuffer < cBuffers)
2125 { /* likely */ }
2126 else
2127 idxBuffer = 0;
2128 } while (idxBuffer != idxStart && !paBuffers[idxBuffer].fQueued);
2129 }
2130
2131 RTCritSectLeave(&pStreamCA->CritSect);
2132 }
2133 Log2Func(("returns %#x for '%s'\n", cbWritable, pStreamCA->Cfg.szName));
2134 return cbWritable;
2135}
2136
2137
2138/**
2139 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
2140 */
2141static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAudioCaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
2142 PPDMAUDIOBACKENDSTREAM pStream)
2143{
2144 RT_NOREF(pInterface);
2145 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2146 AssertPtrReturn(pStreamCA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
2147
2148 if (ASMAtomicReadU32(&pStreamCA->enmInitState) == COREAUDIOINITSTATE_INIT)
2149 {
2150 if (!pStreamCA->fDraining)
2151 { /* likely */ }
2152 else
2153 {
2154 /*
2155 * If we're draining, we're done when we've got all the buffers back.
2156 */
2157 RTCritSectEnter(&pStreamCA->CritSect);
2158 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2159 uintptr_t idxBuffer = pStreamCA->cBuffers;
2160 while (idxBuffer-- > 0)
2161 if (!paBuffers[idxBuffer].fQueued)
2162 { /* likely */ }
2163 else
2164 {
2165#ifdef LOG_ENABLED
2166 uint32_t cQueued = 1;
2167 while (idxBuffer-- > 0)
2168 cQueued += paBuffers[idxBuffer].fQueued;
2169 LogFunc(("Still done draining '%s': %u queued buffers\n", pStreamCA->Cfg.szName, cQueued));
2170#endif
2171 RTCritSectLeave(&pStreamCA->CritSect);
2172 return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
2173 }
2174
2175 LogFunc(("Done draining '%s'\n", pStreamCA->Cfg.szName));
2176 pStreamCA->fDraining = false;
2177 pStreamCA->fEnabled = false;
2178 pStreamCA->fStarted = false;
2179 RTCritSectLeave(&pStreamCA->CritSect);
2180 }
2181
2182 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
2183 }
2184 return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; /** @todo ?? */
2185}
2186
2187/**
2188 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2189 */
2190static DECLCALLBACK(int) drvHostAudioCaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2191 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2192{
2193 RT_NOREF(pInterface);
2194 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2195 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2196 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
2197 if (cbBuf)
2198 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2199 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
2200 AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbWritten = 0, VERR_AUDIO_STREAM_NOT_READY);
2201
2202 RTCritSectEnter(&pStreamCA->CritSect);
2203 if (pStreamCA->fEnabled)
2204 { /* likely */ }
2205 else
2206 {
2207 RTCritSectLeave(&pStreamCA->CritSect);
2208 *pcbWritten = 0;
2209 LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2210 return VINF_SUCCESS;
2211 }
2212 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
2213
2214 /*
2215 * Transfer loop.
2216 */
2217 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2218 uint32_t const cBuffers = pStreamCA->cBuffers;
2219 AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
2220 RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
2221
2222 uint32_t idxBuffer = pStreamCA->idxBuffer;
2223 AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
2224
2225 int rc = VINF_SUCCESS;
2226 uint32_t cbWritten = 0;
2227 while (cbBuf > 0)
2228 {
2229 AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
2230
2231 /*
2232 * Check out how much we can put into the current buffer.
2233 */
2234 if (!paBuffers[idxBuffer].fQueued)
2235 { /* likely */ }
2236 else
2237 {
2238 LogFunc(("@%#RX64: Warning! Out of buffer space! (%#x bytes unwritten)\n", pStreamCA->offInternal, cbBuf));
2239 /** @todo stats */
2240 break;
2241 }
2242
2243 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2244 AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
2245 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2246 uint32_t cbUsed = pBuf->mAudioDataByteSize;
2247 AssertStmt(cbUsed < cbTotal, cbUsed = cbTotal);
2248 uint32_t const cbAvail = cbTotal - cbUsed;
2249
2250 /*
2251 * Copy over the data.
2252 */
2253 if (cbBuf < cbAvail)
2254 {
2255 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x only - leaving unqueued {%s}\n",
2256 pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2257 memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbBuf);
2258 pBuf->mAudioDataByteSize = cbUsed + cbBuf;
2259 cbWritten += cbBuf;
2260 pStreamCA->offInternal += cbBuf;
2261 /** @todo Maybe queue it anyway if it's almost full or we haven't got a lot of
2262 * buffers queued. */
2263 break;
2264 }
2265
2266 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x - will queue {%s}\n",
2267 pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2268 memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbAvail);
2269 pBuf->mAudioDataByteSize = cbTotal;
2270 cbWritten += cbAvail;
2271 pStreamCA->offInternal += cbAvail;
2272 ASMAtomicWriteBool(&paBuffers[idxBuffer].fQueued, true);
2273
2274 OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
2275 if (orc == noErr)
2276 { /* likely */ }
2277 else
2278 {
2279 LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
2280 pStreamCA->Cfg.szName, idxBuffer, orc, orc));
2281 ASMAtomicWriteBool(&paBuffers[idxBuffer].fQueued, false);
2282 pBuf->mAudioDataByteSize -= PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, 1); /* avoid assertions above */
2283 rc = VERR_AUDIO_STREAM_NOT_READY;
2284 break;
2285 }
2286
2287 /*
2288 * Advance.
2289 */
2290 idxBuffer += 1;
2291 if (idxBuffer < cBuffers)
2292 { /* likely */ }
2293 else
2294 idxBuffer = 0;
2295 pStreamCA->idxBuffer = idxBuffer;
2296
2297 pvBuf = (const uint8_t *)pvBuf + cbAvail;
2298 cbBuf -= cbAvail;
2299 }
2300
2301 /*
2302 * Start the stream if we haven't do so yet.
2303 */
2304 if ( pStreamCA->fStarted
2305 || cbWritten == 0
2306 || RT_FAILURE_NP(rc))
2307 { /* likely */ }
2308 else
2309 {
2310 UInt32 cFramesPrepared = 0;
2311#if 0 /* taking too long? */
2312 OSStatus orc = AudioQueuePrime(pStreamCA->hAudioQueue, 0 /*inNumberOfFramesToPrepare*/, &cFramesPrepared);
2313 LogFlowFunc(("AudioQueuePrime(%s, 0,) returns %#x (%d) and cFramesPrepared=%u (offInternal=%#RX64)\n",
2314 pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
2315 AssertMsg(orc == noErr, ("%#x (%d)\n", orc, orc));
2316#else
2317 OSStatus orc;
2318#endif
2319 orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
2320 LogFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2321 if (orc == noErr)
2322 pStreamCA->fStarted = true;
2323 else
2324 {
2325 LogRelMax(128, ("CoreAudio: Starting '%s' failed: %#x (%d) - %u frames primed, %#x bytes queued\n",
2326 pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
2327 rc = VERR_AUDIO_STREAM_NOT_READY;
2328 }
2329 }
2330
2331 /*
2332 * Done.
2333 */
2334#ifdef LOG_ENABLED
2335 uint64_t const msPrev = pStreamCA->msLastTransfer;
2336#endif
2337 uint64_t const msNow = RTTimeMilliTS();
2338 if (cbWritten)
2339 pStreamCA->msLastTransfer = msNow;
2340
2341 RTCritSectLeave(&pStreamCA->CritSect);
2342
2343 *pcbWritten = cbWritten;
2344 if (RT_SUCCESS(rc) || !cbWritten)
2345 { }
2346 else
2347 {
2348 LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten));
2349 rc = VINF_SUCCESS;
2350 }
2351 LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbWritten,
2352 msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHostAudioCaStreamStatusString(pStreamCA) ));
2353 return rc;
2354}
2355
2356
2357/**
2358 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2359 */
2360static DECLCALLBACK(int) drvHostAudioCaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2361 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2362{
2363 RT_NOREF(pInterface);
2364 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2365 AssertPtrReturn(pStreamCA, 0);
2366 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2367 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2368 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2369 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
2370 AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbRead = 0, VERR_AUDIO_STREAM_NOT_READY);
2371
2372 RTCritSectEnter(&pStreamCA->CritSect);
2373 if (pStreamCA->fEnabled)
2374 { /* likely */ }
2375 else
2376 {
2377 RTCritSectLeave(&pStreamCA->CritSect);
2378 *pcbRead = 0;
2379 LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHostAudioCaStreamStatusString(pStreamCA)));
2380 return VINF_SUCCESS;
2381 }
2382 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
2383
2384
2385 /*
2386 * Transfer loop.
2387 */
2388 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamCA->Cfg.Props);
2389 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2390 uint32_t const cBuffers = pStreamCA->cBuffers;
2391 AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
2392 RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
2393
2394 uint32_t idxBuffer = pStreamCA->idxBuffer;
2395 AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
2396
2397 int rc = VINF_SUCCESS;
2398 uint32_t cbRead = 0;
2399 while (cbBuf > cbFrame)
2400 {
2401 AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
2402
2403 /*
2404 * Check out how much we can read from the current buffer (if anything at all).
2405 */
2406 if (!paBuffers[idxBuffer].fQueued)
2407 { /* likely */ }
2408 else
2409 {
2410 LogFunc(("@%#RX64: Warning! Underrun! (%#x bytes unread)\n", pStreamCA->offInternal, cbBuf));
2411 /** @todo stats */
2412 break;
2413 }
2414
2415 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2416 AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
2417 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2418 uint32_t cbValid = pBuf->mAudioDataByteSize;
2419 AssertStmt(cbValid < cbTotal, cbValid = cbTotal);
2420 uint32_t offRead = paBuffers[idxBuffer].offRead;
2421 uint32_t const cbLeft = cbValid - offRead;
2422
2423 /*
2424 * Copy over the data.
2425 */
2426 if (cbBuf < cbLeft)
2427 {
2428 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want %#x - leaving unqueued {%s}\n",
2429 pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2430 memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbBuf);
2431 paBuffers[idxBuffer].offRead = offRead + cbBuf;
2432 cbRead += cbBuf;
2433 pStreamCA->offInternal += cbBuf;
2434 break;
2435 }
2436
2437 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want all (%#x) - will queue {%s}\n",
2438 pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2439 memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbLeft);
2440 cbRead += cbLeft;
2441 pStreamCA->offInternal += cbLeft;
2442
2443 RT_BZERO(pBuf->mAudioData, cbTotal); /* paranoia */
2444 paBuffers[idxBuffer].offRead = 0;
2445 pBuf->mAudioDataByteSize = 0;
2446 ASMAtomicWriteBool(&paBuffers[idxBuffer].fQueued, true);
2447
2448 OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
2449 if (orc == noErr)
2450 { /* likely */ }
2451 else
2452 {
2453 LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
2454 pStreamCA->Cfg.szName, idxBuffer, orc, orc));
2455 ASMAtomicWriteBool(&paBuffers[idxBuffer].fQueued, false);
2456 rc = VERR_AUDIO_STREAM_NOT_READY;
2457 break;
2458 }
2459
2460 /*
2461 * Advance.
2462 */
2463 idxBuffer += 1;
2464 if (idxBuffer < cBuffers)
2465 { /* likely */ }
2466 else
2467 idxBuffer = 0;
2468 pStreamCA->idxBuffer = idxBuffer;
2469
2470 pvBuf = (uint8_t *)pvBuf + cbLeft;
2471 cbBuf -= cbLeft;
2472 }
2473
2474 /*
2475 * Done.
2476 */
2477#ifdef LOG_ENABLED
2478 uint64_t const msPrev = pStreamCA->msLastTransfer;
2479#endif
2480 uint64_t const msNow = RTTimeMilliTS();
2481 if (cbRead)
2482 pStreamCA->msLastTransfer = msNow;
2483
2484 RTCritSectLeave(&pStreamCA->CritSect);
2485
2486 *pcbRead = cbRead;
2487 if (RT_SUCCESS(rc) || !cbRead)
2488 { }
2489 else
2490 {
2491 LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead));
2492 rc = VINF_SUCCESS;
2493 }
2494 LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbRead,
2495 msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHostAudioCaStreamStatusString(pStreamCA) ));
2496 return rc;
2497}
2498
2499
2500/*********************************************************************************************************************************
2501* PDMIBASE *
2502*********************************************************************************************************************************/
2503
2504/**
2505 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2506 */
2507static DECLCALLBACK(void *) drvHostAudioCaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2508{
2509 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2510 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2511
2512 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2513 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2514
2515 return NULL;
2516}
2517
2518
2519/*********************************************************************************************************************************
2520* PDMDRVREG *
2521*********************************************************************************************************************************/
2522
2523/**
2524 * Worker for the power off and destructor callbacks.
2525 */
2526static void drvHostAudioCaRemoveDefaultDeviceListners(PDRVHOSTCOREAUDIO pThis)
2527{
2528 /*
2529 * Unregister system callbacks.
2530 */
2531 AudioObjectPropertyAddress PropAddr =
2532 {
2533 kAudioHardwarePropertyDefaultInputDevice,
2534 kAudioObjectPropertyScopeGlobal,
2535 kAudioObjectPropertyElementMaster
2536 };
2537
2538 OSStatus orc;
2539 if (pThis->fRegisteredDefaultInputListener)
2540 {
2541 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2542 if ( orc != noErr
2543 && orc != kAudioHardwareBadObjectError)
2544 LogRel(("CoreAudio: Failed to remove the default input device changed listener: %d (%#x))\n", orc, orc));
2545 pThis->fRegisteredDefaultInputListener = false;
2546 }
2547
2548 if (pThis->fRegisteredDefaultOutputListener)
2549 {
2550
2551 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2552 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2553 if ( orc != noErr
2554 && orc != kAudioHardwareBadObjectError)
2555 LogRel(("CoreAudio: Failed to remove the default output device changed listener: %d (%#x))\n", orc, orc));
2556 pThis->fRegisteredDefaultOutputListener = false;
2557 }
2558
2559 LogFlowFuncEnter();
2560}
2561
2562
2563/**
2564 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
2565 */
2566static DECLCALLBACK(void) drvHostAudioCaPowerOff(PPDMDRVINS pDrvIns)
2567{
2568 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2569 drvHostAudioCaRemoveDefaultDeviceListners(pThis);
2570}
2571
2572
2573/**
2574 * @callback_method_impl{FNPDMDRVDESTRUCT}
2575 */
2576static DECLCALLBACK(void) drvHostAudioCaDestruct(PPDMDRVINS pDrvIns)
2577{
2578 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2579 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2580
2581 drvHostAudioCaRemoveDefaultDeviceListners(pThis);
2582
2583#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2584 if (pThis->hThread != NIL_RTTHREAD)
2585 {
2586 for (unsigned iLoop = 0; iLoop < 60; iLoop++)
2587 {
2588 if (pThis->hThreadRunLoop)
2589 CFRunLoopStop(pThis->hThreadRunLoop);
2590 if (iLoop > 10)
2591 RTThreadPoke(pThis->hThread);
2592 int rc = RTThreadWait(pThis->hThread, 500 /*ms*/, NULL /*prcThread*/);
2593 if (RT_SUCCESS(rc))
2594 break;
2595 AssertMsgBreak(rc == VERR_TIMEOUT, ("RTThreadWait -> %Rrc\n",rc));
2596 }
2597 pThis->hThread = NIL_RTTHREAD;
2598 }
2599 if (pThis->hThreadPortSrc)
2600 {
2601 CFRelease(pThis->hThreadPortSrc);
2602 pThis->hThreadPortSrc = NULL;
2603 }
2604 if (pThis->hThreadPort)
2605 {
2606 CFMachPortInvalidate(pThis->hThreadPort);
2607 CFRelease(pThis->hThreadPort);
2608 pThis->hThreadPort = NULL;
2609 }
2610 if (pThis->hThreadRunLoop)
2611 {
2612 CFRelease(pThis->hThreadRunLoop);
2613 pThis->hThreadRunLoop = NULL;
2614 }
2615#endif
2616
2617#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2618 if (pThis->hBreakpointTimer != NIL_RTTIMERLR)
2619 {
2620 RTTimerLRDestroy(pThis->hBreakpointTimer);
2621 pThis->hBreakpointTimer = NIL_RTTIMERLR;
2622 }
2623#endif
2624
2625 int rc2 = RTCritSectDelete(&pThis->CritSect);
2626 AssertRC(rc2);
2627
2628 LogFlowFuncLeaveRC(rc2);
2629}
2630
2631
2632/**
2633 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2634 * Construct a Core Audio driver instance.}
2635 */
2636static DECLCALLBACK(int) drvHostAudioCaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2637{
2638 RT_NOREF(pCfg, fFlags);
2639 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2640 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2641 LogRel(("Audio: Initializing Core Audio driver\n"));
2642
2643 /*
2644 * Init the static parts.
2645 */
2646 pThis->pDrvIns = pDrvIns;
2647#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2648 pThis->hThread = NIL_RTTHREAD;
2649#endif
2650#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2651 pThis->hBreakpointTimer = NIL_RTTIMERLR;
2652#endif
2653 PDMAudioHostEnumInit(&pThis->Devices);
2654 /* IBase */
2655 pDrvIns->IBase.pfnQueryInterface = drvHostAudioCaQueryInterface;
2656 /* IHostAudio */
2657 pThis->IHostAudio.pfnGetConfig = drvHostAudioCaHA_GetConfig;
2658 pThis->IHostAudio.pfnGetDevices = drvHostAudioCaHA_GetDevices;
2659 pThis->IHostAudio.pfnGetStatus = drvHostAudioCaHA_GetStatus;
2660 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
2661 pThis->IHostAudio.pfnStreamConfigHint = NULL;
2662 pThis->IHostAudio.pfnStreamCreate = drvHostAudioCaHA_StreamCreate;
2663 pThis->IHostAudio.pfnStreamInitAsync = NULL;
2664 pThis->IHostAudio.pfnStreamDestroy = drvHostAudioCaHA_StreamDestroy;
2665 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
2666 pThis->IHostAudio.pfnStreamControl = drvHostAudioCaHA_StreamControl;
2667 pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioCaHA_StreamGetReadable;
2668 pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioCaHA_StreamGetWritable;
2669 pThis->IHostAudio.pfnStreamGetPending = NULL;
2670 pThis->IHostAudio.pfnStreamGetState = drvHostAudioCaHA_StreamGetState;
2671 pThis->IHostAudio.pfnStreamPlay = drvHostAudioCaHA_StreamPlay;
2672 pThis->IHostAudio.pfnStreamCapture = drvHostAudioCaHA_StreamCapture;
2673
2674 int rc = RTCritSectInit(&pThis->CritSect);
2675 AssertRCReturn(rc, rc);
2676
2677#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2678 /*
2679 * Create worker thread for running callbacks on.
2680 */
2681 CFMachPortContext PortCtx = { .version = 0, .info = pThis, .retain = NULL, .release = NULL, .copyDescription = NULL };
2682 pThis->hThreadPort = CFMachPortCreate(NULL /*allocator*/, drvHostAudioCaThreadPortCallback, &PortCtx, NULL);
2683 AssertLogRelReturn(pThis->hThreadPort != NULL, VERR_NO_MEMORY);
2684
2685 pThis->hThreadPortSrc = CFMachPortCreateRunLoopSource(NULL, pThis->hThreadPort, 0 /*order*/);
2686 AssertLogRelReturn(pThis->hThreadPortSrc != NULL, VERR_NO_MEMORY);
2687
2688 rc = RTThreadCreateF(&pThis->hThread, drvHostAudioCaThread, pThis, 0, RTTHREADTYPE_IO,
2689 RTTHREADFLAGS_WAITABLE, "CaAud-%u", pDrvIns->iInstance);
2690 AssertLogRelMsgReturn(RT_SUCCESS(rc), ("RTThreadCreateF failed: %Rrc\n", rc), rc);
2691
2692 RTThreadUserWait(pThis->hThread, RT_MS_10SEC);
2693 AssertLogRel(pThis->hThreadRunLoop);
2694#endif
2695
2696#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2697 /*
2698 * Create a IPRT timer. The TM timers won't necessarily work as EMT is probably busy.
2699 */
2700 rc = RTTimerLRCreateEx(&pThis->hBreakpointTimer, 0 /*no interval*/, 0, drvHostAudioCaBreakpointTimer, pThis);
2701 AssertRCReturn(rc, rc);
2702#endif
2703
2704 /*
2705 * Enumerate audio devices.
2706 */
2707 rc = drvHostAudioCaEnumerateDevices(pThis);
2708 AssertRCReturn(rc, rc);
2709
2710 /*
2711 * Register callbacks for default device input and output changes.
2712 * We just ignore errors here it seems.
2713 */
2714 AudioObjectPropertyAddress PropAddr =
2715 {
2716 /* .mSelector = */ kAudioHardwarePropertyDefaultInputDevice,
2717 /* .mScope = */ kAudioObjectPropertyScopeGlobal,
2718 /* .mElement = */ kAudioObjectPropertyElementMaster
2719 };
2720
2721 OSStatus orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2722 pThis->fRegisteredDefaultInputListener = orc == noErr;
2723 if ( orc != noErr
2724 && orc != kAudioHardwareIllegalOperationError)
2725 LogRel(("CoreAudio: Failed to add the input default device changed listener: %d (%#x)\n", orc, orc));
2726
2727 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2728 orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2729 pThis->fRegisteredDefaultOutputListener = orc == noErr;
2730 if ( orc != noErr
2731 && orc != kAudioHardwareIllegalOperationError)
2732 LogRel(("CoreAudio: Failed to add the output default device changed listener: %d (%#x)\n", orc, orc));
2733
2734 /*
2735 * Query the notification interface from the driver/device above us.
2736 */
2737 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
2738 AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
2739
2740#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2741 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm");
2742 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm");
2743#endif
2744
2745 LogFlowFuncLeaveRC(rc);
2746 return rc;
2747}
2748
2749
2750/**
2751 * Char driver registration record.
2752 */
2753const PDMDRVREG g_DrvHostCoreAudio =
2754{
2755 /* u32Version */
2756 PDM_DRVREG_VERSION,
2757 /* szName */
2758 "CoreAudio",
2759 /* szRCMod */
2760 "",
2761 /* szR0Mod */
2762 "",
2763 /* pszDescription */
2764 "Core Audio host driver",
2765 /* fFlags */
2766 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2767 /* fClass. */
2768 PDM_DRVREG_CLASS_AUDIO,
2769 /* cMaxInstances */
2770 ~0U,
2771 /* cbInstance */
2772 sizeof(DRVHOSTCOREAUDIO),
2773 /* pfnConstruct */
2774 drvHostAudioCaConstruct,
2775 /* pfnDestruct */
2776 drvHostAudioCaDestruct,
2777 /* pfnRelocate */
2778 NULL,
2779 /* pfnIOCtl */
2780 NULL,
2781 /* pfnPowerOn */
2782 NULL,
2783 /* pfnReset */
2784 NULL,
2785 /* pfnSuspend */
2786 NULL,
2787 /* pfnResume */
2788 NULL,
2789 /* pfnAttach */
2790 NULL,
2791 /* pfnDetach */
2792 NULL,
2793 /* pfnPowerOff */
2794 drvHostAudioCaPowerOff,
2795 /* pfnSoftReset */
2796 NULL,
2797 /* u32EndVersion */
2798 PDM_DRVREG_VERSION
2799};
2800
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