VirtualBox

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

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

Audio: For anyone wishing to use the VBOX_AUDIO_DEBUG_DUMP_PCM_DATA stuff, you now have to define VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH to the output path string. Don't want hardly ever used debug fun like VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH in pdmaudioifs.h. bugref:9890

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