VirtualBox

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

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

DrvHostAudioCoreAudio: Removed the VBOX_WITH_AUDIO_CA_CONVERTER bits as they don't currently fit in anywhere. We can fish them out of svn history if needed again. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 105.7 KB
Line 
1/* $Id: DrvHostAudioCoreAudio.cpp 89167 2021-05-19 13:28:39Z 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 = 0;
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 if (orc == noErr)
1516 {
1517 /*
1518 * Assign device to the queue.
1519 */
1520 UInt32 uSize = sizeof(pDev->UUID);
1521 orc = AudioQueueSetProperty(pStreamCA->hAudioQueue, kAudioQueueProperty_CurrentDevice, &pDev->UUID, uSize);
1522 if (orc == noErr)
1523 {
1524 /*
1525 * Sanity-adjust the requested buffer size.
1526 */
1527 uint32_t cFramesBufferSizeMax = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 2 * RT_MS_1SEC);
1528 uint32_t cFramesBufferSize = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 32 /*ms*/);
1529 cFramesBufferSize = RT_MAX(cFramesBufferSize, pCfgReq->Backend.cFramesBufferSize);
1530 cFramesBufferSize = RT_MIN(cFramesBufferSize, cFramesBufferSizeMax);
1531
1532 /*
1533 * The queue buffers size is based on cMsSchedulingHint so that we're likely to
1534 * have a new one ready/done after each guest DMA transfer. We must however
1535 * make sure we don't end up with too may or too few.
1536 */
1537 Assert(pCfgReq->Device.cMsSchedulingHint > 0);
1538 uint32_t cFramesQueueBuffer = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props,
1539 pCfgReq->Device.cMsSchedulingHint > 0
1540 ? pCfgReq->Device.cMsSchedulingHint : 10);
1541 uint32_t cQueueBuffers;
1542 if (cFramesQueueBuffer * COREAUDIO_MIN_BUFFERS <= cFramesBufferSize)
1543 {
1544 cQueueBuffers = cFramesBufferSize / cFramesQueueBuffer;
1545 if (cQueueBuffers > COREAUDIO_MAX_BUFFERS)
1546 {
1547 cQueueBuffers = COREAUDIO_MAX_BUFFERS;
1548 cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MAX_BUFFERS;
1549 }
1550 }
1551 else
1552 {
1553 cQueueBuffers = COREAUDIO_MIN_BUFFERS;
1554 cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MIN_BUFFERS;
1555 }
1556
1557 cFramesBufferSize = cQueueBuffers * cFramesBufferSize;
1558
1559 /*
1560 * Allocate the audio queue buffers.
1561 */
1562 pStreamCA->paBuffers = (PCOREAUDIOBUF)RTMemAllocZ(sizeof(pStreamCA->paBuffers[0]) * cQueueBuffers);
1563 if (pStreamCA->paBuffers != NULL)
1564 {
1565 pStreamCA->cBuffers = cQueueBuffers;
1566
1567 cFramesBufferSize = 0;
1568 const size_t cbQueueBuffer = PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, cFramesQueueBuffer);
1569 for (uint32_t iBuf = 0; iBuf < cQueueBuffers; iBuf++)
1570 {
1571 AudioQueueBufferRef pBuf = NULL;
1572 orc = AudioQueueAllocateBuffer(pStreamCA->hAudioQueue, cbQueueBuffer, &pBuf);
1573 if (RT_LIKELY(orc == noErr))
1574 {
1575 pBuf->mUserData = (void *)(uintptr_t)iBuf;
1576 pStreamCA->paBuffers[iBuf].pBuf = pBuf;
1577 cFramesBufferSize += PDMAudioPropsBytesToFrames(&pStreamCA->Cfg.Props,
1578 pBuf->mAudioDataBytesCapacity);
1579 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, pBuf->mAudioDataBytesCapacity));
1580 }
1581 else
1582 {
1583 LogRel(("CoreAudio: Out of memory (buffer %#x out of %#x, %#x bytes)\n",
1584 iBuf, cQueueBuffers, cbQueueBuffer));
1585 while (iBuf-- > 0)
1586 {
1587 AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
1588 pStreamCA->paBuffers[iBuf].pBuf = NULL;
1589 }
1590 break;
1591 }
1592 }
1593 if (orc == noErr)
1594 {
1595 /*
1596 * Update the stream config.
1597 */
1598 pStreamCA->Cfg.Backend.cFramesBufferSize = cFramesBufferSize;
1599 pStreamCA->Cfg.Backend.cFramesPeriod = cFramesQueueBuffer; /* whatever */
1600 pStreamCA->Cfg.Backend.cFramesPreBuffering = pStreamCA->Cfg.Backend.cFramesPreBuffering
1601 * pStreamCA->Cfg.Backend.cFramesBufferSize
1602 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
1603
1604 PDMAudioStrmCfgCopy(pCfgAcq, &pStreamCA->Cfg);
1605
1606 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_INIT);
1607
1608 LogFunc(("returns VINF_SUCCESS\n"));
1609 return VINF_SUCCESS;
1610 }
1611 RTMemFree(pStreamCA->paBuffers);
1612 }
1613 else
1614 rc = VERR_NO_MEMORY;
1615 }
1616 else
1617 LogRelMax(64, ("CoreAudio: Failed to associate device with queue: %#x (%d)\n", orc, orc));
1618 AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
1619 }
1620 else
1621 LogRelMax(64, ("CoreAudio: Failed to create audio queue: %#x (%d)\n", orc, orc));
1622 RTCritSectDelete(&pStreamCA->CritSect);
1623 }
1624 else
1625 LogRel(("CoreAudio: Failed to initialize critical section for stream: %Rrc\n", rc));
1626 }
1627 else
1628 {
1629 LogRelMax(64, ("CoreAudio: No device for %s stream.\n", PDMAudioDirGetName(pCfgReq->enmDir)));
1630 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1631 }
1632
1633 LogFunc(("returns %Rrc\n", rc));
1634 return rc;
1635}
1636
1637
1638/**
1639 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamInitAsync}
1640 */
1641static DECLCALLBACK(int) drvHostAudioCaHA_StreamInitAsync(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fDestroyed)
1642{
1643 RT_NOREF(pInterface, pStream, fDestroyed);
1644 return VINF_SUCCESS;
1645}
1646
1647
1648/**
1649 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1650 */
1651static DECLCALLBACK(int) drvHostAudioCaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1652{
1653 RT_NOREF(pInterface);
1654 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1655 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1656
1657 /*
1658 * Never mind if the status isn't INIT (it should always be, though).
1659 */
1660 COREAUDIOINITSTATE const enmInitState = (COREAUDIOINITSTATE)ASMAtomicReadU32(&pStreamCA->enmInitState);
1661 AssertMsg(enmInitState == COREAUDIOINITSTATE_INIT, ("%d\n", enmInitState));
1662 if (enmInitState == COREAUDIOINITSTATE_INIT)
1663 {
1664 Assert(RTCritSectIsInitialized(&pStreamCA->CritSect));
1665
1666 /*
1667 * Change the stream state and stop the stream (just to be sure).
1668 */
1669 OSStatus orc;
1670 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_IN_UNINIT);
1671 if (pStreamCA->hAudioQueue)
1672 {
1673 orc = AudioQueueStop(pStreamCA->hAudioQueue, TRUE /*inImmediate/synchronously*/);
1674 LogFlowFunc(("AudioQueueStop -> %#x\n", orc));
1675 }
1676
1677 /*
1678 * Enter and leave the critsect afterwards for paranoid reasons.
1679 */
1680 RTCritSectEnter(&pStreamCA->CritSect);
1681 RTCritSectLeave(&pStreamCA->CritSect);
1682
1683 /*
1684 * Free the queue buffers and the queue.
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 if (pStreamCA->paBuffers)
1694 {
1695 LogFlowFunc(("Freeing %u buffers ...\n", pStreamCA->cBuffers));
1696 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1697 {
1698 orc = AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
1699 AssertMsg(orc == noErr, ("AudioQueueFreeBuffer(#%u) -> orc=%#x\n", iBuf, orc));
1700 pStreamCA->paBuffers[iBuf].pBuf = NULL;
1701 }
1702 RTMemFree(pStreamCA->paBuffers);
1703 pStreamCA->paBuffers = NULL;
1704 }
1705 pStreamCA->cBuffers = 0;
1706
1707 if (pStreamCA->hAudioQueue)
1708 {
1709 LogFlowFunc(("Disposing of the queue ...\n", orc));
1710 orc = AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate/synchronously*/);
1711 LogFlowFunc(("AudioQueueDispose -> %#x (%d)\n", orc, orc));
1712 AssertMsg(orc == noErr, ("AudioQueueDispose -> orc=%#x\n", orc));
1713 pStreamCA->hAudioQueue = NULL;
1714 }
1715
1716#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1717 RTTimerLRStop(pThis->hBreakpointTimer);
1718 LogRel(("Queue-destruction: %'RU64\n", RTTimeNanoTS() - nsStart));
1719#endif
1720
1721 /*
1722 * Release the device and delete the critsect.
1723 */
1724 pStreamCA->Unit.pDevice = NULL; /** @todo This bugger must be refcounted! */
1725
1726 RTCritSectDelete(&pStreamCA->CritSect);
1727
1728 /*
1729 * Done.
1730 */
1731 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_UNINIT);
1732 }
1733 else
1734 LogFunc(("Wrong stream init state for %p: %d - leaking it\n", pStream, enmInitState));
1735
1736 LogFunc(("returns\n"));
1737 return VINF_SUCCESS;
1738}
1739
1740
1741#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1742/** @callback_method_impl{FNRTTIMERLR, For debugging things that takes too long.} */
1743static DECLCALLBACK(void) drvHostAudioCaBreakpointTimer(RTTIMERLR hTimer, void *pvUser, uint64_t iTick)
1744{
1745 LogFlowFunc(("Queue-destruction timeout! iTick=%RU64\n", iTick));
1746 RT_NOREF(hTimer, pvUser, iTick);
1747 RTLogFlush(NULL);
1748 RT_BREAKPOINT();
1749}
1750#endif
1751
1752
1753/**
1754 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
1755 */
1756static DECLCALLBACK(int) drvHostAudioCaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1757{
1758 RT_NOREF(pInterface);
1759 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1760 LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA)));
1761 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1762 RTCritSectEnter(&pStreamCA->CritSect);
1763
1764 Assert(!pStreamCA->fEnabled);
1765 Assert(!pStreamCA->fStarted);
1766
1767 /*
1768 * We always reset the buffer before enabling the stream (normally never necessary).
1769 */
1770 OSStatus orc = AudioQueueReset(pStreamCA->hAudioQueue);
1771 if (orc != noErr)
1772 LogRelMax(64, ("CoreAudio: Stream reset failed when enabling '%s': %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1773 Assert(orc == noErr);
1774 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1775 Assert(!pStreamCA->paBuffers[iBuf].fQueued);
1776
1777 pStreamCA->offInternal = 0;
1778 pStreamCA->fDraining = false;
1779 pStreamCA->fEnabled = true;
1780 pStreamCA->fRestartOnResume = false;
1781 pStreamCA->idxBuffer = 0;
1782
1783 /*
1784 * Input streams will start capturing, while output streams will only start
1785 * playing once we get some audio data to play (see drvHostAudioCaHA_StreamPlay).
1786 */
1787 int rc = VINF_SUCCESS;
1788 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
1789 {
1790 /* Zero (probably not needed) and submit all the buffers first. */
1791 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1792 {
1793 AudioQueueBufferRef pBuf = pStreamCA->paBuffers[iBuf].pBuf;
1794
1795 RT_BZERO(pBuf->mAudioData, pBuf->mAudioDataBytesCapacity);
1796 pBuf->mAudioDataByteSize = 0;
1797
1798 orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDescs*/, NULL /*inPacketDescs*/);
1799 AssertLogRelMsgBreak(orc == noErr, ("CoreAudio: AudioQueueEnqueueBuffer(#%u) -> %#x (%d) - stream '%s'\n",
1800 iBuf, orc, orc, pStreamCA->Cfg.szName));
1801 }
1802
1803 /* Start the stream. */
1804 if (orc == noErr)
1805 {
1806 LogFlowFunc(("Start input stream '%s'...\n", pStreamCA->Cfg.szName));
1807 orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
1808 AssertLogRelMsgStmt(orc == noErr, ("CoreAudio: AudioQueueStart(%s) -> %#x (%d) \n", pStreamCA->Cfg.szName, orc, orc),
1809 rc = VERR_AUDIO_STREAM_NOT_READY);
1810 pStreamCA->fStarted = orc == noErr;
1811 }
1812 else
1813 rc = VERR_AUDIO_STREAM_NOT_READY;
1814 }
1815 else
1816 Assert(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
1817
1818 RTCritSectLeave(&pStreamCA->CritSect);
1819 LogFlowFunc(("returns %Rrc\n", rc));
1820 return rc;
1821}
1822
1823
1824/**
1825 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
1826 */
1827static DECLCALLBACK(int) drvHostAudioCaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1828{
1829 RT_NOREF(pInterface);
1830 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1831 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1832 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
1833 pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
1834 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1835 RTCritSectEnter(&pStreamCA->CritSect);
1836
1837 /*
1838 * Always stop it (draining or no).
1839 */
1840 pStreamCA->fEnabled = false;
1841 pStreamCA->fRestartOnResume = false;
1842 Assert(!pStreamCA->fDraining || pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
1843
1844 int rc = VINF_SUCCESS;
1845 if (pStreamCA->fStarted)
1846 {
1847 OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
1848 LogFlowFunc(("AudioQueueStop(%s,TRUE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1849 if (orc != noErr)
1850 {
1851 LogRelMax(64, ("CoreAudio: Stopping '%s' failed (disable): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1852 rc = VERR_GENERAL_FAILURE;
1853 }
1854 pStreamCA->fStarted = false;
1855 pStreamCA->fDraining = false;
1856 }
1857
1858 RTCritSectLeave(&pStreamCA->CritSect);
1859 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostAudioCaStreamStatusString(pStreamCA)));
1860 return rc;
1861}
1862
1863
1864/**
1865 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
1866 */
1867static DECLCALLBACK(int) drvHostAudioCaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1868{
1869 RT_NOREF(pInterface);
1870 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1871 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1872 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
1873 pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
1874 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1875 RTCritSectEnter(&pStreamCA->CritSect);
1876
1877 /*
1878 * Unless we're draining the stream, pause it if it has started.
1879 */
1880 int rc = VINF_SUCCESS;
1881 if (pStreamCA->fStarted && !pStreamCA->fDraining)
1882 {
1883 pStreamCA->fRestartOnResume = true;
1884
1885 OSStatus orc = AudioQueuePause(pStreamCA->hAudioQueue);
1886 LogFlowFunc(("AudioQueuePause(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1887 if (orc != noErr)
1888 {
1889 LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1890 rc = VERR_GENERAL_FAILURE;
1891 }
1892 pStreamCA->fStarted = false;
1893 }
1894 else
1895 {
1896 pStreamCA->fRestartOnResume = false;
1897 if (pStreamCA->fDraining)
1898 {
1899 LogFunc(("Stream '%s' is draining\n", pStreamCA->Cfg.szName));
1900 Assert(pStreamCA->fStarted);
1901 }
1902 }
1903
1904 RTCritSectLeave(&pStreamCA->CritSect);
1905 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostAudioCaStreamStatusString(pStreamCA)));
1906 return rc;
1907}
1908
1909
1910/**
1911 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
1912 */
1913static DECLCALLBACK(int) drvHostAudioCaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1914{
1915 RT_NOREF(pInterface);
1916 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1917 LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA)));
1918 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1919 RTCritSectEnter(&pStreamCA->CritSect);
1920
1921 /*
1922 * Resume according to state saved by drvHostAudioCaHA_StreamPause.
1923 */
1924 int rc = VINF_SUCCESS;
1925 if (pStreamCA->fRestartOnResume)
1926 {
1927 OSStatus orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
1928 LogFlowFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1929 if (orc != noErr)
1930 {
1931 LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1932 rc = VERR_AUDIO_STREAM_NOT_READY;
1933 }
1934 }
1935 pStreamCA->fRestartOnResume = false;
1936
1937 RTCritSectLeave(&pStreamCA->CritSect);
1938 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostAudioCaStreamStatusString(pStreamCA)));
1939 return rc;
1940}
1941
1942
1943/**
1944 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
1945 */
1946static DECLCALLBACK(int) drvHostAudioCaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1947{
1948 RT_NOREF(pInterface);
1949 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1950 AssertReturn(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1951 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1952 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
1953 pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
1954 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1955 RTCritSectEnter(&pStreamCA->CritSect);
1956
1957 /*
1958 * The AudioQueueStop function has both an immediate and a drain mode,
1959 * so we'll obviously use the latter here. For checking draining progress,
1960 * we will just check if all buffers have been returned or not.
1961 */
1962 int rc = VINF_SUCCESS;
1963 if (pStreamCA->fStarted)
1964 {
1965 if (!pStreamCA->fDraining)
1966 {
1967 OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, FALSE /*inImmediate*/);
1968 LogFlowFunc(("AudioQueueStop(%s, FALSE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1969 if (orc == noErr)
1970 pStreamCA->fDraining = true;
1971 else
1972 {
1973 LogRelMax(64, ("CoreAudio: Stopping '%s' failed (drain): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1974 rc = VERR_GENERAL_FAILURE;
1975 }
1976 }
1977 else
1978 LogFlowFunc(("Already draining '%s' ...\n", pStreamCA->Cfg.szName));
1979 }
1980 else
1981 {
1982 LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamCA->Cfg.szName));
1983 AssertStmt(!pStreamCA->fDraining, pStreamCA->fDraining = false);
1984 }
1985
1986 RTCritSectLeave(&pStreamCA->CritSect);
1987 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostAudioCaStreamStatusString(pStreamCA)));
1988 return rc;
1989}
1990
1991
1992/**
1993 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
1994 */
1995static DECLCALLBACK(int) drvHostAudioCaHA_StreamControl(PPDMIHOSTAUDIO pInterface,
1996 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
1997{
1998 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
1999 * replacing it with individual StreamXxxx methods. That would save us
2000 * potentally huge switches and more easily see which drivers implement
2001 * which operations (grep for pfnStreamXxxx). */
2002 switch (enmStreamCmd)
2003 {
2004 case PDMAUDIOSTREAMCMD_ENABLE:
2005 return drvHostAudioCaHA_StreamEnable(pInterface, pStream);
2006 case PDMAUDIOSTREAMCMD_DISABLE:
2007 return drvHostAudioCaHA_StreamDisable(pInterface, pStream);
2008 case PDMAUDIOSTREAMCMD_PAUSE:
2009 return drvHostAudioCaHA_StreamPause(pInterface, pStream);
2010 case PDMAUDIOSTREAMCMD_RESUME:
2011 return drvHostAudioCaHA_StreamResume(pInterface, pStream);
2012 case PDMAUDIOSTREAMCMD_DRAIN:
2013 return drvHostAudioCaHA_StreamDrain(pInterface, pStream);
2014
2015 case PDMAUDIOSTREAMCMD_END:
2016 case PDMAUDIOSTREAMCMD_32BIT_HACK:
2017 case PDMAUDIOSTREAMCMD_INVALID:
2018 /* no default*/
2019 break;
2020 }
2021 return VERR_NOT_SUPPORTED;
2022}
2023
2024
2025/**
2026 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2027 */
2028static DECLCALLBACK(uint32_t) drvHostAudioCaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2029{
2030 RT_NOREF(pInterface);
2031 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2032 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2033 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
2034
2035 uint32_t cbReadable = 0;
2036 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
2037 {
2038 RTCritSectEnter(&pStreamCA->CritSect);
2039 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2040 uint32_t const cBuffers = pStreamCA->cBuffers;
2041 uint32_t const idxStart = pStreamCA->idxBuffer;
2042 uint32_t idxBuffer = idxStart;
2043
2044 if ( cBuffers > 0
2045 && !paBuffers[idxBuffer].fQueued)
2046 {
2047 do
2048 {
2049 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2050 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2051 uint32_t cbFill = pBuf->mAudioDataByteSize;
2052 AssertStmt(cbFill <= cbTotal, cbFill = cbTotal);
2053 uint32_t off = paBuffers[idxBuffer].offRead;
2054 AssertStmt(off < cbFill, off = cbFill);
2055
2056 cbReadable += cbFill - off;
2057
2058 /* Advance. */
2059 idxBuffer++;
2060 if (idxBuffer < cBuffers)
2061 { /* likely */ }
2062 else
2063 idxBuffer = 0;
2064 } while (idxBuffer != idxStart && !paBuffers[idxBuffer].fQueued);
2065 }
2066
2067 RTCritSectLeave(&pStreamCA->CritSect);
2068 }
2069 Log2Func(("returns %#x for '%s'\n", cbReadable, pStreamCA->Cfg.szName));
2070 return cbReadable;
2071}
2072
2073
2074/**
2075 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2076 */
2077static DECLCALLBACK(uint32_t) drvHostAudioCaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2078{
2079 RT_NOREF(pInterface);
2080 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2081 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2082 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
2083
2084 uint32_t cbWritable = 0;
2085 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT)
2086 {
2087 RTCritSectEnter(&pStreamCA->CritSect);
2088 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2089 uint32_t const cBuffers = pStreamCA->cBuffers;
2090 uint32_t const idxStart = pStreamCA->idxBuffer;
2091 uint32_t idxBuffer = idxStart;
2092
2093 if ( cBuffers > 0
2094 && !paBuffers[idxBuffer].fQueued)
2095 {
2096 do
2097 {
2098 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2099 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2100 uint32_t cbUsed = pBuf->mAudioDataByteSize;
2101 AssertStmt(cbUsed <= cbTotal, paBuffers[idxBuffer].pBuf->mAudioDataByteSize = cbUsed = cbTotal);
2102
2103 cbWritable += cbTotal - cbUsed;
2104
2105 /* Advance. */
2106 idxBuffer++;
2107 if (idxBuffer < cBuffers)
2108 { /* likely */ }
2109 else
2110 idxBuffer = 0;
2111 } while (idxBuffer != idxStart && !paBuffers[idxBuffer].fQueued);
2112 }
2113
2114 RTCritSectLeave(&pStreamCA->CritSect);
2115 }
2116 Log2Func(("returns %#x for '%s'\n", cbWritable, pStreamCA->Cfg.szName));
2117 return cbWritable;
2118}
2119
2120
2121/**
2122 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
2123 */
2124static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAudioCaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
2125 PPDMAUDIOBACKENDSTREAM pStream)
2126{
2127 RT_NOREF(pInterface);
2128 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2129 AssertPtrReturn(pStreamCA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
2130
2131 if (ASMAtomicReadU32(&pStreamCA->enmInitState) == COREAUDIOINITSTATE_INIT)
2132 {
2133 if (!pStreamCA->fDraining)
2134 { /* likely */ }
2135 else
2136 {
2137 /*
2138 * If we're draining, we're done when we've got all the buffers back.
2139 */
2140 RTCritSectEnter(&pStreamCA->CritSect);
2141 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2142 uintptr_t idxBuffer = pStreamCA->cBuffers;
2143 while (idxBuffer-- > 0)
2144 if (!paBuffers[idxBuffer].fQueued)
2145 { /* likely */ }
2146 else
2147 {
2148#ifdef LOG_ENABLED
2149 uint32_t cQueued = 1;
2150 while (idxBuffer-- > 0)
2151 cQueued += paBuffers[idxBuffer].fQueued;
2152 LogFunc(("Still done draining '%s': %u queued buffers\n", pStreamCA->Cfg.szName, cQueued));
2153#endif
2154 RTCritSectLeave(&pStreamCA->CritSect);
2155 return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
2156 }
2157
2158 LogFunc(("Done draining '%s'\n", pStreamCA->Cfg.szName));
2159 pStreamCA->fDraining = false;
2160 pStreamCA->fEnabled = false;
2161 RTCritSectLeave(&pStreamCA->CritSect);
2162 }
2163
2164 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
2165 }
2166 return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; /** @todo ?? */
2167}
2168
2169/**
2170 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2171 */
2172static DECLCALLBACK(int) drvHostAudioCaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2173 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2174{
2175 RT_NOREF(pInterface);
2176 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2177 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2178 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
2179 if (cbBuf)
2180 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2181 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
2182 AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbWritten = 0, VERR_AUDIO_STREAM_NOT_READY);
2183
2184 RTCritSectEnter(&pStreamCA->CritSect);
2185 if (pStreamCA->fEnabled)
2186 { /* likely */ }
2187 else
2188 {
2189 RTCritSectLeave(&pStreamCA->CritSect);
2190 *pcbWritten = 0;
2191 LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2192 return VINF_SUCCESS;
2193 }
2194 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
2195
2196 /*
2197 * Transfer loop.
2198 */
2199 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2200 uint32_t const cBuffers = pStreamCA->cBuffers;
2201 AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
2202 RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
2203
2204 uint32_t idxBuffer = pStreamCA->idxBuffer;
2205 AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
2206
2207 int rc = VINF_SUCCESS;
2208 uint32_t cbWritten = 0;
2209 while (cbBuf > 0)
2210 {
2211 AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
2212
2213 /*
2214 * Check out how much we can put into the current buffer.
2215 */
2216 if (!paBuffers[idxBuffer].fQueued)
2217 { /* likely */ }
2218 else
2219 {
2220 LogFunc(("@%#RX64: Warning! Out of buffer space! (%#x bytes unwritten)\n", pStreamCA->offInternal, cbBuf));
2221 /** @todo stats */
2222 break;
2223 }
2224
2225 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2226 AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
2227 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2228 uint32_t cbUsed = pBuf->mAudioDataByteSize;
2229 AssertStmt(cbUsed < cbTotal, cbUsed = cbTotal);
2230 uint32_t const cbAvail = cbTotal - cbUsed;
2231
2232 /*
2233 * Copy over the data.
2234 */
2235 if (cbBuf < cbAvail)
2236 {
2237 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x only - leaving unqueued {%s}\n",
2238 pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2239 memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbBuf);
2240 pBuf->mAudioDataByteSize = cbUsed + cbBuf;
2241 cbWritten += cbBuf;
2242 pStreamCA->offInternal += cbBuf;
2243 /** @todo Maybe queue it anyway if it's almost full or we haven't got a lot of
2244 * buffers queued. */
2245 break;
2246 }
2247
2248 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x - will queue {%s}\n",
2249 pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2250 memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbAvail);
2251 pBuf->mAudioDataByteSize = cbTotal;
2252 cbWritten += cbAvail;
2253 pStreamCA->offInternal += cbAvail;
2254 ASMAtomicWriteBool(&paBuffers[idxBuffer].fQueued, true);
2255
2256 OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
2257 if (orc == noErr)
2258 { /* likely */ }
2259 else
2260 {
2261 LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
2262 pStreamCA->Cfg.szName, idxBuffer, orc, orc));
2263 ASMAtomicWriteBool(&paBuffers[idxBuffer].fQueued, false);
2264 pBuf->mAudioDataByteSize -= PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, 1); /* avoid assertions above */
2265 rc = VERR_AUDIO_STREAM_NOT_READY;
2266 break;
2267 }
2268
2269 /*
2270 * Advance.
2271 */
2272 idxBuffer += 1;
2273 if (idxBuffer < cBuffers)
2274 { /* likely */ }
2275 else
2276 idxBuffer = 0;
2277 pStreamCA->idxBuffer = idxBuffer;
2278
2279 pvBuf = (const uint8_t *)pvBuf + cbAvail;
2280 cbBuf -= cbAvail;
2281 }
2282
2283 /*
2284 * Start the stream if we haven't do so yet.
2285 */
2286 if ( pStreamCA->fStarted
2287 || cbWritten == 0
2288 || RT_FAILURE_NP(rc))
2289 { /* likely */ }
2290 else
2291 {
2292 UInt32 cFramesPrepared = 0;
2293#if 0 /* taking too long? */
2294 OSStatus orc = AudioQueuePrime(pStreamCA->hAudioQueue, 0 /*inNumberOfFramesToPrepare*/, &cFramesPrepared);
2295 LogFlowFunc(("AudioQueuePrime(%s, 0,) returns %#x (%d) and cFramesPrepared=%u (offInternal=%#RX64)\n",
2296 pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
2297 AssertMsg(orc == noErr, ("%#x (%d)\n", orc, orc));
2298#else
2299 OSStatus orc;
2300#endif
2301 orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
2302 LogFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2303 if (orc == noErr)
2304 pStreamCA->fStarted = true;
2305 else
2306 {
2307 LogRelMax(128, ("CoreAudio: Starting '%s' failed: %#x (%d) - %u frames primed, %#x bytes queued\n",
2308 pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
2309 rc = VERR_AUDIO_STREAM_NOT_READY;
2310 }
2311 }
2312
2313 /*
2314 * Done.
2315 */
2316#ifdef LOG_ENABLED
2317 uint64_t const msPrev = pStreamCA->msLastTransfer;
2318#endif
2319 uint64_t const msNow = RTTimeMilliTS();
2320 if (cbWritten)
2321 pStreamCA->msLastTransfer = msNow;
2322
2323 RTCritSectLeave(&pStreamCA->CritSect);
2324
2325 *pcbWritten = cbWritten;
2326 if (RT_SUCCESS(rc) || !cbWritten)
2327 { }
2328 else
2329 {
2330 LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten));
2331 rc = VINF_SUCCESS;
2332 }
2333 LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbWritten,
2334 msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHostAudioCaStreamStatusString(pStreamCA) ));
2335 return rc;
2336}
2337
2338
2339/**
2340 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2341 */
2342static DECLCALLBACK(int) drvHostAudioCaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2343 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2344{
2345 RT_NOREF(pInterface);
2346 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2347 AssertPtrReturn(pStreamCA, 0);
2348 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2349 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2350 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2351 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
2352 AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbRead = 0, VERR_AUDIO_STREAM_NOT_READY);
2353
2354 RTCritSectEnter(&pStreamCA->CritSect);
2355 if (pStreamCA->fEnabled)
2356 { /* likely */ }
2357 else
2358 {
2359 RTCritSectLeave(&pStreamCA->CritSect);
2360 *pcbRead = 0;
2361 LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHostAudioCaStreamStatusString(pStreamCA)));
2362 return VINF_SUCCESS;
2363 }
2364 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHostAudioCaStreamStatusString(pStreamCA) ));
2365
2366
2367 /*
2368 * Transfer loop.
2369 */
2370 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamCA->Cfg.Props);
2371 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2372 uint32_t const cBuffers = pStreamCA->cBuffers;
2373 AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
2374 RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
2375
2376 uint32_t idxBuffer = pStreamCA->idxBuffer;
2377 AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
2378
2379 int rc = VINF_SUCCESS;
2380 uint32_t cbRead = 0;
2381 while (cbBuf > cbFrame)
2382 {
2383 AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
2384
2385 /*
2386 * Check out how much we can read from the current buffer (if anything at all).
2387 */
2388 if (!paBuffers[idxBuffer].fQueued)
2389 { /* likely */ }
2390 else
2391 {
2392 LogFunc(("@%#RX64: Warning! Underrun! (%#x bytes unread)\n", pStreamCA->offInternal, cbBuf));
2393 /** @todo stats */
2394 break;
2395 }
2396
2397 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2398 AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
2399 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2400 uint32_t cbValid = pBuf->mAudioDataByteSize;
2401 AssertStmt(cbValid < cbTotal, cbValid = cbTotal);
2402 uint32_t offRead = paBuffers[idxBuffer].offRead;
2403 uint32_t const cbLeft = cbValid - offRead;
2404
2405 /*
2406 * Copy over the data.
2407 */
2408 if (cbBuf < cbLeft)
2409 {
2410 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want %#x - leaving unqueued {%s}\n",
2411 pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2412 memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbBuf);
2413 paBuffers[idxBuffer].offRead = offRead + cbBuf;
2414 cbRead += cbBuf;
2415 pStreamCA->offInternal += cbBuf;
2416 break;
2417 }
2418
2419 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want all (%#x) - will queue {%s}\n",
2420 pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHostAudioCaStreamStatusString(pStreamCA) ));
2421 memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbLeft);
2422 cbRead += cbLeft;
2423 pStreamCA->offInternal += cbLeft;
2424
2425 RT_BZERO(pBuf->mAudioData, cbTotal); /* paranoia */
2426 paBuffers[idxBuffer].offRead = 0;
2427 pBuf->mAudioDataByteSize = 0;
2428 ASMAtomicWriteBool(&paBuffers[idxBuffer].fQueued, true);
2429
2430 OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
2431 if (orc == noErr)
2432 { /* likely */ }
2433 else
2434 {
2435 LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
2436 pStreamCA->Cfg.szName, idxBuffer, orc, orc));
2437 ASMAtomicWriteBool(&paBuffers[idxBuffer].fQueued, false);
2438 rc = VERR_AUDIO_STREAM_NOT_READY;
2439 break;
2440 }
2441
2442 /*
2443 * Advance.
2444 */
2445 idxBuffer += 1;
2446 if (idxBuffer < cBuffers)
2447 { /* likely */ }
2448 else
2449 idxBuffer = 0;
2450 pStreamCA->idxBuffer = idxBuffer;
2451
2452 pvBuf = (uint8_t *)pvBuf + cbLeft;
2453 cbBuf -= cbLeft;
2454 }
2455
2456 /*
2457 * Done.
2458 */
2459#ifdef LOG_ENABLED
2460 uint64_t const msPrev = pStreamCA->msLastTransfer;
2461#endif
2462 uint64_t const msNow = RTTimeMilliTS();
2463 if (cbRead)
2464 pStreamCA->msLastTransfer = msNow;
2465
2466 RTCritSectLeave(&pStreamCA->CritSect);
2467
2468 *pcbRead = cbRead;
2469 if (RT_SUCCESS(rc) || !cbRead)
2470 { }
2471 else
2472 {
2473 LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead));
2474 rc = VINF_SUCCESS;
2475 }
2476 LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbRead,
2477 msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHostAudioCaStreamStatusString(pStreamCA) ));
2478 return rc;
2479}
2480
2481
2482/*********************************************************************************************************************************
2483* PDMIBASE *
2484*********************************************************************************************************************************/
2485
2486/**
2487 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2488 */
2489static DECLCALLBACK(void *) drvHostAudioCaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2490{
2491 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2492 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2493
2494 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2495 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2496
2497 return NULL;
2498}
2499
2500
2501/*********************************************************************************************************************************
2502* PDMDRVREG *
2503*********************************************************************************************************************************/
2504
2505/**
2506 * Worker for the power off and destructor callbacks.
2507 */
2508static void drvHostAudioCaRemoveDefaultDeviceListners(PDRVHOSTCOREAUDIO pThis)
2509{
2510 /*
2511 * Unregister system callbacks.
2512 */
2513 AudioObjectPropertyAddress PropAddr =
2514 {
2515 kAudioHardwarePropertyDefaultInputDevice,
2516 kAudioObjectPropertyScopeGlobal,
2517 kAudioObjectPropertyElementMaster
2518 };
2519
2520 OSStatus orc;
2521 if (pThis->fRegisteredDefaultInputListener)
2522 {
2523 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2524 if ( orc != noErr
2525 && orc != kAudioHardwareBadObjectError)
2526 LogRel(("CoreAudio: Failed to remove the default input device changed listener: %d (%#x))\n", orc, orc));
2527 pThis->fRegisteredDefaultInputListener = false;
2528 }
2529
2530 if (pThis->fRegisteredDefaultOutputListener)
2531 {
2532
2533 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2534 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2535 if ( orc != noErr
2536 && orc != kAudioHardwareBadObjectError)
2537 LogRel(("CoreAudio: Failed to remove the default output device changed listener: %d (%#x))\n", orc, orc));
2538 pThis->fRegisteredDefaultOutputListener = false;
2539 }
2540
2541 LogFlowFuncEnter();
2542}
2543
2544
2545/**
2546 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
2547 */
2548static DECLCALLBACK(void) drvHostAudioCaPowerOff(PPDMDRVINS pDrvIns)
2549{
2550 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2551 drvHostAudioCaRemoveDefaultDeviceListners(pThis);
2552}
2553
2554
2555/**
2556 * @callback_method_impl{FNPDMDRVDESTRUCT}
2557 */
2558static DECLCALLBACK(void) drvHostAudioCaDestruct(PPDMDRVINS pDrvIns)
2559{
2560 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2561 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2562
2563 drvHostAudioCaRemoveDefaultDeviceListners(pThis);
2564
2565#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2566 if (pThis->hThread != NIL_RTTHREAD)
2567 {
2568 for (unsigned iLoop = 0; iLoop < 60; iLoop++)
2569 {
2570 if (pThis->hThreadRunLoop)
2571 CFRunLoopStop(pThis->hThreadRunLoop);
2572 if (iLoop > 10)
2573 RTThreadPoke(pThis->hThread);
2574 int rc = RTThreadWait(pThis->hThread, 500 /*ms*/, NULL /*prcThread*/);
2575 if (RT_SUCCESS(rc))
2576 break;
2577 AssertMsgBreak(rc == VERR_TIMEOUT, ("RTThreadWait -> %Rrc\n",rc));
2578 }
2579 pThis->hThread = NIL_RTTHREAD;
2580 }
2581 if (pThis->hThreadPortSrc)
2582 {
2583 CFRelease(pThis->hThreadPortSrc);
2584 pThis->hThreadPortSrc = NULL;
2585 }
2586 if (pThis->hThreadPort)
2587 {
2588 CFMachPortInvalidate(pThis->hThreadPort);
2589 CFRelease(pThis->hThreadPort);
2590 pThis->hThreadPort = NULL;
2591 }
2592 if (pThis->hThreadRunLoop)
2593 {
2594 CFRelease(pThis->hThreadRunLoop);
2595 pThis->hThreadRunLoop = NULL;
2596 }
2597#endif
2598
2599#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2600 if (pThis->hBreakpointTimer != NIL_RTTIMERLR)
2601 {
2602 RTTimerLRDestroy(pThis->hBreakpointTimer);
2603 pThis->hBreakpointTimer = NIL_RTTIMERLR;
2604 }
2605#endif
2606
2607 int rc2 = RTCritSectDelete(&pThis->CritSect);
2608 AssertRC(rc2);
2609
2610 LogFlowFuncLeaveRC(rc2);
2611}
2612
2613
2614/**
2615 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2616 * Construct a Core Audio driver instance.}
2617 */
2618static DECLCALLBACK(int) drvHostAudioCaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2619{
2620 RT_NOREF(pCfg, fFlags);
2621 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2622 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2623 LogRel(("Audio: Initializing Core Audio driver\n"));
2624
2625 /*
2626 * Init the static parts.
2627 */
2628 pThis->pDrvIns = pDrvIns;
2629#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2630 pThis->hThread = NIL_RTTHREAD;
2631#endif
2632#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2633 pThis->hBreakpointTimer = NIL_RTTIMERLR;
2634#endif
2635 PDMAudioHostEnumInit(&pThis->Devices);
2636 /* IBase */
2637 pDrvIns->IBase.pfnQueryInterface = drvHostAudioCaQueryInterface;
2638 /* IHostAudio */
2639 pThis->IHostAudio.pfnGetConfig = drvHostAudioCaHA_GetConfig;
2640 pThis->IHostAudio.pfnGetDevices = drvHostAudioCaHA_GetDevices;
2641 pThis->IHostAudio.pfnGetStatus = drvHostAudioCaHA_GetStatus;
2642 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
2643 pThis->IHostAudio.pfnStreamConfigHint = NULL;
2644 pThis->IHostAudio.pfnStreamCreate = drvHostAudioCaHA_StreamCreate;
2645 pThis->IHostAudio.pfnStreamInitAsync = drvHostAudioCaHA_StreamInitAsync;
2646 pThis->IHostAudio.pfnStreamDestroy = drvHostAudioCaHA_StreamDestroy;
2647 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
2648 pThis->IHostAudio.pfnStreamControl = drvHostAudioCaHA_StreamControl;
2649 pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioCaHA_StreamGetReadable;
2650 pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioCaHA_StreamGetWritable;
2651 pThis->IHostAudio.pfnStreamGetPending = NULL;
2652 pThis->IHostAudio.pfnStreamGetState = drvHostAudioCaHA_StreamGetState;
2653 pThis->IHostAudio.pfnStreamPlay = drvHostAudioCaHA_StreamPlay;
2654 pThis->IHostAudio.pfnStreamCapture = drvHostAudioCaHA_StreamCapture;
2655
2656 int rc = RTCritSectInit(&pThis->CritSect);
2657 AssertRCReturn(rc, rc);
2658
2659#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2660 /*
2661 * Create worker thread for running callbacks on.
2662 */
2663 CFMachPortContext PortCtx = { .version = 0, .info = pThis, .retain = NULL, .release = NULL, .copyDescription = NULL };
2664 pThis->hThreadPort = CFMachPortCreate(NULL /*allocator*/, drvHostAudioCaThreadPortCallback, &PortCtx, NULL);
2665 AssertLogRelReturn(pThis->hThreadPort != NULL, VERR_NO_MEMORY);
2666
2667 pThis->hThreadPortSrc = CFMachPortCreateRunLoopSource(NULL, pThis->hThreadPort, 0 /*order*/);
2668 AssertLogRelReturn(pThis->hThreadPortSrc != NULL, VERR_NO_MEMORY);
2669
2670 rc = RTThreadCreateF(&pThis->hThread, drvHostAudioCaThread, pThis, 0, RTTHREADTYPE_IO,
2671 RTTHREADFLAGS_WAITABLE, "CaAud-%u", pDrvIns->iInstance);
2672 AssertLogRelMsgReturn(RT_SUCCESS(rc), ("RTThreadCreateF failed: %Rrc\n", rc), rc);
2673
2674 RTThreadUserWait(pThis->hThread, RT_MS_10SEC);
2675 AssertLogRel(pThis->hThreadRunLoop);
2676#endif
2677
2678#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2679 /*
2680 * Create a IPRT timer. The TM timers won't necessarily work as EMT is probably busy.
2681 */
2682 rc = RTTimerLRCreateEx(&pThis->hBreakpointTimer, 0 /*no interval*/, 0, drvHostAudioCaBreakpointTimer, pThis);
2683 AssertRCReturn(rc, rc);
2684#endif
2685
2686 /*
2687 * Enumerate audio devices.
2688 */
2689 rc = drvHostAudioCaEnumerateDevices(pThis);
2690 AssertRCReturn(rc, rc);
2691
2692 /*
2693 * Register callbacks for default device input and output changes.
2694 * We just ignore errors here it seems.
2695 */
2696 AudioObjectPropertyAddress PropAddr =
2697 {
2698 /* .mSelector = */ kAudioHardwarePropertyDefaultInputDevice,
2699 /* .mScope = */ kAudioObjectPropertyScopeGlobal,
2700 /* .mElement = */ kAudioObjectPropertyElementMaster
2701 };
2702
2703 OSStatus orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2704 pThis->fRegisteredDefaultInputListener = orc == noErr;
2705 if ( orc != noErr
2706 && orc != kAudioHardwareIllegalOperationError)
2707 LogRel(("CoreAudio: Failed to add the input default device changed listener: %d (%#x)\n", orc, orc));
2708
2709 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2710 orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHostAudioCaDefaultDeviceChangedCallback, pThis);
2711 pThis->fRegisteredDefaultOutputListener = orc == noErr;
2712 if ( orc != noErr
2713 && orc != kAudioHardwareIllegalOperationError)
2714 LogRel(("CoreAudio: Failed to add the output default device changed listener: %d (%#x)\n", orc, orc));
2715
2716 /*
2717 * Query the notification interface from the driver/device above us.
2718 */
2719 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
2720 AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
2721
2722#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2723 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm");
2724 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm");
2725#endif
2726
2727 LogFlowFuncLeaveRC(rc);
2728 return rc;
2729}
2730
2731
2732/**
2733 * Char driver registration record.
2734 */
2735const PDMDRVREG g_DrvHostCoreAudio =
2736{
2737 /* u32Version */
2738 PDM_DRVREG_VERSION,
2739 /* szName */
2740 "CoreAudio",
2741 /* szRCMod */
2742 "",
2743 /* szR0Mod */
2744 "",
2745 /* pszDescription */
2746 "Core Audio host driver",
2747 /* fFlags */
2748 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2749 /* fClass. */
2750 PDM_DRVREG_CLASS_AUDIO,
2751 /* cMaxInstances */
2752 ~0U,
2753 /* cbInstance */
2754 sizeof(DRVHOSTCOREAUDIO),
2755 /* pfnConstruct */
2756 drvHostAudioCaConstruct,
2757 /* pfnDestruct */
2758 drvHostAudioCaDestruct,
2759 /* pfnRelocate */
2760 NULL,
2761 /* pfnIOCtl */
2762 NULL,
2763 /* pfnPowerOn */
2764 NULL,
2765 /* pfnReset */
2766 NULL,
2767 /* pfnSuspend */
2768 NULL,
2769 /* pfnResume */
2770 NULL,
2771 /* pfnAttach */
2772 NULL,
2773 /* pfnDetach */
2774 NULL,
2775 /* pfnPowerOff */
2776 drvHostAudioCaPowerOff,
2777 /* pfnSoftReset */
2778 NULL,
2779 /* u32EndVersion */
2780 PDM_DRVREG_VERSION
2781};
2782
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