VirtualBox

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

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

Audio: Added PDMIHOSTAUDIO::pfnSetDevice with implementation for CoreAudio. Added CoreAudio config values OutputDeviceID and InputDeviceID for the same purpose. bugref:9890

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