VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 60.8 KB
Line 
1/* $Id: DrvHostAudioAlsa.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * Host audio driver - Advanced Linux Sound Architecture (ALSA).
4 */
5
6/*
7 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 * --------------------------------------------------------------------
27 *
28 * This code is based on: alsaaudio.c
29 *
30 * QEMU ALSA audio driver
31 *
32 * Copyright (c) 2005 Vassili Karpov (malc)
33 *
34 * Permission is hereby granted, free of charge, to any person obtaining a copy
35 * of this software and associated documentation files (the "Software"), to deal
36 * in the Software without restriction, including without limitation the rights
37 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
38 * copies of the Software, and to permit persons to whom the Software is
39 * furnished to do so, subject to the following conditions:
40 *
41 * The above copyright notice and this permission notice shall be included in
42 * all copies or substantial portions of the Software.
43 *
44 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
47 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
50 * THE SOFTWARE.
51 */
52
53
54/*********************************************************************************************************************************
55* Header Files *
56*********************************************************************************************************************************/
57#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
58#include <VBox/log.h>
59#include <iprt/alloc.h>
60#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
61#include <VBox/vmm/pdmaudioifs.h>
62#include <VBox/vmm/pdmaudioinline.h>
63#include <VBox/vmm/pdmaudiohostenuminline.h>
64
65#include "DrvHostAudioAlsaStubsMangling.h"
66#include <alsa/asoundlib.h>
67#include <alsa/control.h> /* For device enumeration. */
68#include <alsa/version.h>
69#include "DrvHostAudioAlsaStubs.h"
70
71#include "VBoxDD.h"
72
73
74/*********************************************************************************************************************************
75* Defined Constants And Macros *
76*********************************************************************************************************************************/
77/** Maximum number of tries to recover a broken pipe. */
78#define ALSA_RECOVERY_TRIES_MAX 5
79
80
81/*********************************************************************************************************************************
82* Structures *
83*********************************************************************************************************************************/
84/**
85 * ALSA host audio specific stream data.
86 */
87typedef struct DRVHSTAUDALSASTREAM
88{
89 /** Common part. */
90 PDMAUDIOBACKENDSTREAM Core;
91
92 /** Handle to the ALSA PCM stream. */
93 snd_pcm_t *hPCM;
94 /** Internal stream offset (for debugging). */
95 uint64_t offInternal;
96
97 /** The stream's acquired configuration. */
98 PDMAUDIOSTREAMCFG Cfg;
99} DRVHSTAUDALSASTREAM;
100/** Pointer to the ALSA host audio specific stream data. */
101typedef DRVHSTAUDALSASTREAM *PDRVHSTAUDALSASTREAM;
102
103
104/**
105 * Host Alsa audio driver instance data.
106 * @implements PDMIAUDIOCONNECTOR
107 */
108typedef struct DRVHSTAUDALSA
109{
110 /** Pointer to the driver instance structure. */
111 PPDMDRVINS pDrvIns;
112 /** Pointer to host audio interface. */
113 PDMIHOSTAUDIO IHostAudio;
114 /** Error count for not flooding the release log.
115 * UINT32_MAX for unlimited logging. */
116 uint32_t cLogErrors;
117
118 /** Critical section protecting the default device strings. */
119 RTCRITSECT CritSect;
120 /** Default input device name. */
121 char szInputDev[256];
122 /** Default output device name. */
123 char szOutputDev[256];
124 /** Upwards notification interface. */
125 PPDMIHOSTAUDIOPORT pIHostAudioPort;
126} DRVHSTAUDALSA;
127/** Pointer to the instance data of an ALSA host audio driver. */
128typedef DRVHSTAUDALSA *PDRVHSTAUDALSA;
129
130
131
132/**
133 * Closes an ALSA stream
134 *
135 * @returns VBox status code.
136 * @param phPCM Pointer to the ALSA stream handle to close. Will be set to
137 * NULL.
138 */
139static int drvHstAudAlsaStreamClose(snd_pcm_t **phPCM)
140{
141 if (!phPCM || !*phPCM)
142 return VINF_SUCCESS;
143
144 LogRelFlowFuncEnter();
145
146 int rc;
147 int rc2 = snd_pcm_close(*phPCM);
148 if (rc2 == 0)
149 {
150 *phPCM = NULL;
151 rc = VINF_SUCCESS;
152 }
153 else
154 {
155 rc = RTErrConvertFromErrno(-rc2);
156 LogRel(("ALSA: Closing PCM descriptor failed: %s (%d, %Rrc)\n", snd_strerror(rc2), rc2, rc));
157 }
158
159 LogRelFlowFuncLeaveRC(rc);
160 return rc;
161}
162
163
164#ifdef DEBUG
165static void drvHstAudAlsaDbgErrorHandler(const char *file, int line, const char *function,
166 int err, const char *fmt, ...)
167{
168 /** @todo Implement me! */
169 RT_NOREF(file, line, function, err, fmt);
170}
171#endif
172
173
174/**
175 * Tries to recover an ALSA stream.
176 *
177 * @returns VBox status code.
178 * @param hPCM ALSA stream handle.
179 */
180static int drvHstAudAlsaStreamRecover(snd_pcm_t *hPCM)
181{
182 AssertPtrReturn(hPCM, VERR_INVALID_POINTER);
183
184 int rc = snd_pcm_prepare(hPCM);
185 if (rc >= 0)
186 {
187 LogFlowFunc(("Successfully recovered %p.\n", hPCM));
188 return VINF_SUCCESS;
189 }
190 LogFunc(("Failed to recover stream %p: %s (%d)\n", hPCM, snd_strerror(rc), rc));
191 return RTErrConvertFromErrno(-rc);
192}
193
194
195/**
196 * Resumes an ALSA stream.
197 *
198 * Used by drvHstAudAlsaHA_StreamPlay() and drvHstAudAlsaHA_StreamCapture().
199 *
200 * @returns VBox status code.
201 * @param hPCM ALSA stream to resume.
202 */
203static int drvHstAudAlsaStreamResume(snd_pcm_t *hPCM)
204{
205 AssertPtrReturn(hPCM, VERR_INVALID_POINTER);
206
207 int rc = snd_pcm_resume(hPCM);
208 if (rc >= 0)
209 {
210 LogFlowFunc(("Successfuly resumed %p.\n", hPCM));
211 return VINF_SUCCESS;
212 }
213 LogFunc(("Failed to resume stream %p: %s (%d)\n", hPCM, snd_strerror(rc), rc));
214 return RTErrConvertFromErrno(-rc);
215}
216
217
218/**
219 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
220 */
221static DECLCALLBACK(int) drvHstAudAlsaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
222{
223 RT_NOREF(pInterface);
224 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
225
226 /*
227 * Fill in the config structure.
228 */
229 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "ALSA");
230 pBackendCfg->cbStream = sizeof(DRVHSTAUDALSASTREAM);
231 pBackendCfg->fFlags = 0;
232 /* ALSA allows exactly one input and one output used at a time for the selected device(s). */
233 pBackendCfg->cMaxStreamsIn = 1;
234 pBackendCfg->cMaxStreamsOut = 1;
235
236 return VINF_SUCCESS;
237}
238
239
240/**
241 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
242 */
243static DECLCALLBACK(int) drvHstAudAlsaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
244{
245 RT_NOREF(pInterface);
246 PDMAudioHostEnumInit(pDeviceEnum);
247
248 char **papszHints = NULL;
249 int rc = snd_device_name_hint(-1 /* All cards */, "pcm", (void***)&papszHints);
250 if (rc == 0)
251 {
252 rc = VINF_SUCCESS;
253 for (size_t iHint = 0; papszHints[iHint] != NULL && RT_SUCCESS(rc); iHint++)
254 {
255 /*
256 * Retrieve the available info:
257 */
258 const char * const pszHint = papszHints[iHint];
259 char * const pszDev = snd_device_name_get_hint(pszHint, "NAME");
260 char * const pszInOutId = snd_device_name_get_hint(pszHint, "IOID");
261 char * const pszDesc = snd_device_name_get_hint(pszHint, "DESC");
262
263 if (pszDev && RTStrICmpAscii(pszDev, "null") != 0)
264 {
265 /* Detect and log presence of pulse audio plugin. */
266 if (RTStrIStr("pulse", pszDev) != NULL)
267 LogRel(("ALSA: The ALSAAudio plugin for pulse audio is being used (%s).\n", pszDev));
268
269 /*
270 * Add an entry to the enumeration result.
271 * We engage in some trickery here to deal with device names that
272 * are more than 63 characters long.
273 */
274 size_t const cbId = pszDev ? strlen(pszDev) + 1 : 1;
275 size_t const cbName = pszDesc ? strlen(pszDesc) + 2 + 1 : cbId;
276 PPDMAUDIOHOSTDEV pDev = PDMAudioHostDevAlloc(sizeof(*pDev), cbName, cbId);
277 if (pDev)
278 {
279 RTStrCopy(pDev->pszId, cbId, pszDev);
280 if (pDev->pszId)
281 {
282 pDev->fFlags = PDMAUDIOHOSTDEV_F_NONE;
283 pDev->enmType = PDMAUDIODEVICETYPE_UNKNOWN;
284
285 if (pszInOutId == NULL)
286 {
287 pDev->enmUsage = PDMAUDIODIR_DUPLEX;
288 pDev->cMaxInputChannels = 2;
289 pDev->cMaxOutputChannels = 2;
290 }
291 else if (RTStrICmpAscii(pszInOutId, "Input") == 0)
292 {
293 pDev->enmUsage = PDMAUDIODIR_IN;
294 pDev->cMaxInputChannels = 2;
295 pDev->cMaxOutputChannels = 0;
296 }
297 else
298 {
299 AssertMsg(RTStrICmpAscii(pszInOutId, "Output") == 0, ("%s (%s)\n", pszInOutId, pszHint));
300 pDev->enmUsage = PDMAUDIODIR_OUT;
301 pDev->cMaxInputChannels = 0;
302 pDev->cMaxOutputChannels = 2;
303 }
304
305 if (pszDesc && *pszDesc)
306 {
307 char *pszDesc2 = strchr(pszDesc, '\n');
308 if (!pszDesc2)
309 RTStrCopy(pDev->pszName, cbName, pszDesc);
310 else
311 {
312 *pszDesc2++ = '\0';
313 char *psz;
314 while ((psz = strchr(pszDesc2, '\n')) != NULL)
315 *psz = ' ';
316 RTStrPrintf(pDev->pszName, cbName, "%s (%s)", pszDesc2, pszDesc);
317 }
318 }
319 else
320 RTStrCopy(pDev->pszName, cbName, pszDev);
321
322 PDMAudioHostEnumAppend(pDeviceEnum, pDev);
323
324 LogRel2(("ALSA: Device #%u: '%s' enmDir=%s: %s\n", iHint, pszDev,
325 PDMAudioDirGetName(pDev->enmUsage), pszDesc));
326 }
327 else
328 {
329 PDMAudioHostDevFree(pDev);
330 rc = VERR_NO_STR_MEMORY;
331 }
332 }
333 else
334 rc = VERR_NO_MEMORY;
335 }
336
337 /*
338 * Clean up.
339 */
340 if (pszInOutId)
341 free(pszInOutId);
342 if (pszDesc)
343 free(pszDesc);
344 if (pszDev)
345 free(pszDev);
346 }
347
348 snd_device_name_free_hint((void **)papszHints);
349
350 if (RT_FAILURE(rc))
351 {
352 PDMAudioHostEnumDelete(pDeviceEnum);
353 PDMAudioHostEnumInit(pDeviceEnum);
354 }
355 }
356 else
357 {
358 int rc2 = RTErrConvertFromErrno(-rc);
359 LogRel2(("ALSA: Error enumerating PCM devices: %Rrc (%d)\n", rc2, rc));
360 rc = rc2;
361 }
362 return rc;
363}
364
365
366/**
367 * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
368 */
369static DECLCALLBACK(int) drvHstAudAlsaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
370{
371 PDRVHSTAUDALSA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDALSA, IHostAudio);
372
373 /*
374 * Validate and normalize input.
375 */
376 AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER);
377 AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
378 if (!pszId || !*pszId)
379 pszId = "default";
380 else
381 {
382 size_t cch = strlen(pszId);
383 AssertReturn(cch < sizeof(pThis->szInputDev), VERR_INVALID_NAME);
384 }
385 LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId));
386
387 /*
388 * Update input.
389 */
390 if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
391 {
392 int rc = RTCritSectEnter(&pThis->CritSect);
393 AssertRCReturn(rc, rc);
394 if (strcmp(pThis->szInputDev, pszId) == 0)
395 RTCritSectLeave(&pThis->CritSect);
396 else
397 {
398 LogRel(("ALSA: Changing input device: '%s' -> '%s'\n", pThis->szInputDev, pszId));
399 RTStrCopy(pThis->szInputDev, sizeof(pThis->szInputDev), pszId);
400 PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
401 RTCritSectLeave(&pThis->CritSect);
402 if (pIHostAudioPort)
403 {
404 LogFlowFunc(("Notifying parent driver about input device change...\n"));
405 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
406 }
407 }
408 }
409
410 /*
411 * Update output.
412 */
413 if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX)
414 {
415 int rc = RTCritSectEnter(&pThis->CritSect);
416 AssertRCReturn(rc, rc);
417 if (strcmp(pThis->szOutputDev, pszId) == 0)
418 RTCritSectLeave(&pThis->CritSect);
419 else
420 {
421 LogRel(("ALSA: Changing output device: '%s' -> '%s'\n", pThis->szOutputDev, pszId));
422 RTStrCopy(pThis->szOutputDev, sizeof(pThis->szOutputDev), pszId);
423 PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
424 RTCritSectLeave(&pThis->CritSect);
425 if (pIHostAudioPort)
426 {
427 LogFlowFunc(("Notifying parent driver about output device change...\n"));
428 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
429 }
430 }
431 }
432
433 return VINF_SUCCESS;
434}
435
436
437/**
438 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
439 */
440static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudAlsaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
441{
442 RT_NOREF(enmDir);
443 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
444
445 return PDMAUDIOBACKENDSTS_RUNNING;
446}
447
448
449/**
450 * Converts internal audio PCM properties to an ALSA PCM format.
451 *
452 * @returns Converted ALSA PCM format.
453 * @param pProps Internal audio PCM configuration to convert.
454 */
455static snd_pcm_format_t alsaAudioPropsToALSA(PCPDMAUDIOPCMPROPS pProps)
456{
457 switch (PDMAudioPropsSampleSize(pProps))
458 {
459 case 1:
460 return pProps->fSigned ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8;
461
462 case 2:
463 if (PDMAudioPropsIsLittleEndian(pProps))
464 return pProps->fSigned ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U16_LE;
465 return pProps->fSigned ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_U16_BE;
466
467 case 4:
468 if (PDMAudioPropsIsLittleEndian(pProps))
469 return pProps->fSigned ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_U32_LE;
470 return pProps->fSigned ? SND_PCM_FORMAT_S32_BE : SND_PCM_FORMAT_U32_BE;
471
472 default:
473 AssertLogRelMsgFailed(("%RU8 bytes not supported\n", PDMAudioPropsSampleSize(pProps)));
474 return SND_PCM_FORMAT_UNKNOWN;
475 }
476}
477
478
479/**
480 * Sets the software parameters of an ALSA stream.
481 *
482 * @returns 0 on success, negative errno on failure.
483 * @param hPCM ALSA stream to set software parameters for.
484 * @param pCfgReq Requested stream configuration (PDM).
485 * @param pCfgAcq The actual stream configuration (PDM). Updated as
486 * needed.
487 */
488static int alsaStreamSetSWParams(snd_pcm_t *hPCM, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
489{
490 if (pCfgReq->enmDir == PDMAUDIODIR_IN) /* For input streams there's nothing to do in here right now. */
491 return 0;
492
493 snd_pcm_sw_params_t *pSWParms = NULL;
494 snd_pcm_sw_params_alloca(&pSWParms);
495 AssertReturn(pSWParms, -ENOMEM);
496
497 int err = snd_pcm_sw_params_current(hPCM, pSWParms);
498 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to get current software parameters: %s\n", snd_strerror(err)), err);
499
500 /* Under normal circumstance, we don't need to set a playback threshold
501 because DrvAudio will do the pre-buffering and hand us everything in
502 one continuous chunk when we should start playing. But since it is
503 configurable, we'll set a reasonable minimum of two DMA periods or
504 max 50 milliseconds (the pAlsaCfgReq->threshold value).
505
506 Of course we also have to make sure the threshold is below the buffer
507 size, or ALSA will never start playing. */
508 unsigned long const cFramesMax = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 50);
509 unsigned long cFramesThreshold = RT_MIN(pCfgAcq->Backend.cFramesPeriod * 2, cFramesMax);
510 if (cFramesThreshold >= pCfgAcq->Backend.cFramesBufferSize - pCfgAcq->Backend.cFramesBufferSize / 16)
511 cFramesThreshold = pCfgAcq->Backend.cFramesBufferSize - pCfgAcq->Backend.cFramesBufferSize / 16;
512
513 err = snd_pcm_sw_params_set_start_threshold(hPCM, pSWParms, cFramesThreshold);
514 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set software threshold to %lu: %s\n", cFramesThreshold, snd_strerror(err)), err);
515
516 err = snd_pcm_sw_params_set_avail_min(hPCM, pSWParms, pCfgReq->Backend.cFramesPeriod);
517 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set available minimum to %u: %s\n",
518 pCfgReq->Backend.cFramesPeriod, snd_strerror(err)), err);
519
520 /* Commit the software parameters: */
521 err = snd_pcm_sw_params(hPCM, pSWParms);
522 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set new software parameters: %s\n", snd_strerror(err)), err);
523
524 /* Get the actual parameters: */
525 snd_pcm_uframes_t cFramesThresholdActual = cFramesThreshold;
526 err = snd_pcm_sw_params_get_start_threshold(pSWParms, &cFramesThresholdActual);
527 AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get start threshold: %s\n", snd_strerror(err)),
528 cFramesThresholdActual = cFramesThreshold);
529
530 LogRel2(("ALSA: SW params: %lu frames threshold, %u frames avail minimum\n",
531 cFramesThresholdActual, pCfgAcq->Backend.cFramesPeriod));
532 return 0;
533}
534
535
536/**
537 * Maps a PDM channel ID to an ASLA channel map position.
538 */
539static unsigned int drvHstAudAlsaPdmChToAlsa(PDMAUDIOCHANNELID enmId, uint8_t cChannels)
540{
541 switch (enmId)
542 {
543 case PDMAUDIOCHANNELID_UNKNOWN: return SND_CHMAP_UNKNOWN;
544 case PDMAUDIOCHANNELID_UNUSED_ZERO: return SND_CHMAP_NA;
545 case PDMAUDIOCHANNELID_UNUSED_SILENCE: return SND_CHMAP_NA;
546
547 case PDMAUDIOCHANNELID_FRONT_LEFT: return SND_CHMAP_FL;
548 case PDMAUDIOCHANNELID_FRONT_RIGHT: return SND_CHMAP_FR;
549 case PDMAUDIOCHANNELID_FRONT_CENTER: return cChannels == 1 ? SND_CHMAP_MONO : SND_CHMAP_FC;
550 case PDMAUDIOCHANNELID_LFE: return SND_CHMAP_LFE;
551 case PDMAUDIOCHANNELID_REAR_LEFT: return SND_CHMAP_RL;
552 case PDMAUDIOCHANNELID_REAR_RIGHT: return SND_CHMAP_RR;
553 case PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER: return SND_CHMAP_FLC;
554 case PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER: return SND_CHMAP_FRC;
555 case PDMAUDIOCHANNELID_REAR_CENTER: return SND_CHMAP_RC;
556 case PDMAUDIOCHANNELID_SIDE_LEFT: return SND_CHMAP_SL;
557 case PDMAUDIOCHANNELID_SIDE_RIGHT: return SND_CHMAP_SR;
558 case PDMAUDIOCHANNELID_TOP_CENTER: return SND_CHMAP_TC;
559 case PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT: return SND_CHMAP_TFL;
560 case PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT: return SND_CHMAP_TFC;
561 case PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT: return SND_CHMAP_TFR;
562 case PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT: return SND_CHMAP_TRL;
563 case PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT: return SND_CHMAP_TRC;
564 case PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT: return SND_CHMAP_TRR;
565
566 case PDMAUDIOCHANNELID_INVALID:
567 case PDMAUDIOCHANNELID_END:
568 case PDMAUDIOCHANNELID_32BIT_HACK:
569 break;
570 }
571 AssertFailed();
572 return SND_CHMAP_NA;
573}
574
575
576/**
577 * Sets the hardware parameters of an ALSA stream.
578 *
579 * @returns 0 on success, negative errno on failure.
580 * @param hPCM ALSA stream to set software parameters for.
581 * @param enmAlsaFmt The ALSA format to use.
582 * @param pCfgReq Requested stream configuration (PDM).
583 * @param pCfgAcq The actual stream configuration (PDM). This is assumed
584 * to be a copy of pCfgReq on input, at least for
585 * properties handled here. On output some of the
586 * properties may be updated to match the actual stream
587 * configuration.
588 */
589static int alsaStreamSetHwParams(snd_pcm_t *hPCM, snd_pcm_format_t enmAlsaFmt,
590 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
591{
592 /*
593 * Get the current hardware parameters.
594 */
595 snd_pcm_hw_params_t *pHWParms = NULL;
596 snd_pcm_hw_params_alloca(&pHWParms);
597 AssertReturn(pHWParms, -ENOMEM);
598
599 int err = snd_pcm_hw_params_any(hPCM, pHWParms);
600 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to initialize hardware parameters: %s\n", snd_strerror(err)), err);
601
602 /*
603 * Modify them according to pAlsaCfgReq.
604 * We update pAlsaCfgObt as we go for parameters set by "near" methods.
605 */
606 /* We'll use snd_pcm_writei/snd_pcm_readi: */
607 err = snd_pcm_hw_params_set_access(hPCM, pHWParms, SND_PCM_ACCESS_RW_INTERLEAVED);
608 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set access type: %s\n", snd_strerror(err)), err);
609
610 /* Set the format and frequency. */
611 err = snd_pcm_hw_params_set_format(hPCM, pHWParms, enmAlsaFmt);
612 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set audio format to %d: %s\n", enmAlsaFmt, snd_strerror(err)), err);
613
614 unsigned int uFreq = PDMAudioPropsHz(&pCfgReq->Props);
615 err = snd_pcm_hw_params_set_rate_near(hPCM, pHWParms, &uFreq, NULL /*dir*/);
616 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set frequency to %uHz: %s\n",
617 PDMAudioPropsHz(&pCfgReq->Props), snd_strerror(err)), err);
618 pCfgAcq->Props.uHz = uFreq;
619
620 /* Channel count currently does not change with the mapping translations,
621 as ALSA can express both silent and unknown channel positions. */
622 union
623 {
624 snd_pcm_chmap_t Map;
625 unsigned int padding[1 + PDMAUDIO_MAX_CHANNELS];
626 } u;
627 uint8_t aidSrcChannels[PDMAUDIO_MAX_CHANNELS];
628 unsigned int *aidDstChannels = u.Map.pos;
629 unsigned int cChannels = u.Map.channels = PDMAudioPropsChannels(&pCfgReq->Props);
630 unsigned int iDst = 0;
631 for (unsigned int iSrc = 0; iSrc < cChannels; iSrc++)
632 {
633 uint8_t const idSrc = pCfgReq->Props.aidChannels[iSrc];
634 aidSrcChannels[iDst] = idSrc;
635 aidDstChannels[iDst] = drvHstAudAlsaPdmChToAlsa((PDMAUDIOCHANNELID)idSrc, cChannels);
636 iDst++;
637 }
638 u.Map.channels = cChannels = iDst;
639 for (; iDst < PDMAUDIO_MAX_CHANNELS; iDst++)
640 {
641 aidSrcChannels[iDst] = PDMAUDIOCHANNELID_INVALID;
642 aidDstChannels[iDst] = SND_CHMAP_NA;
643 }
644
645 err = snd_pcm_hw_params_set_channels_near(hPCM, pHWParms, &cChannels);
646 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set number of channels to %d\n", PDMAudioPropsChannels(&pCfgReq->Props)),
647 err);
648 if (cChannels == PDMAudioPropsChannels(&pCfgReq->Props))
649 memcpy(pCfgAcq->Props.aidChannels, aidSrcChannels, sizeof(pCfgAcq->Props.aidChannels));
650 else
651 {
652 LogRel2(("ALSA: Requested %u channels, got %u\n", u.Map.channels, cChannels));
653 AssertLogRelMsgReturn(cChannels > 0 && cChannels <= PDMAUDIO_MAX_CHANNELS,
654 ("ALSA: Unsupported channel count: %u (requested %d)\n",
655 cChannels, PDMAudioPropsChannels(&pCfgReq->Props)), -ERANGE);
656 PDMAudioPropsSetChannels(&pCfgAcq->Props, (uint8_t)cChannels);
657 /** @todo Can we somehow guess channel IDs? snd_pcm_get_chmap? */
658 }
659
660 /* The period size (reportedly frame count per hw interrupt): */
661 int dir = 0;
662 snd_pcm_uframes_t minval = pCfgReq->Backend.cFramesPeriod;
663 err = snd_pcm_hw_params_get_period_size_min(pHWParms, &minval, &dir);
664 AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not determine minimal period size: %s\n", snd_strerror(err)), err);
665
666 snd_pcm_uframes_t period_size_f = pCfgReq->Backend.cFramesPeriod;
667 if (period_size_f < minval)
668 period_size_f = minval;
669 err = snd_pcm_hw_params_set_period_size_near(hPCM, pHWParms, &period_size_f, 0);
670 LogRel2(("ALSA: Period size is: %lu frames (min %lu, requested %u)\n", period_size_f, minval, pCfgReq->Backend.cFramesPeriod));
671 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set period size %d (%s)\n", period_size_f, snd_strerror(err)), err);
672
673 /* The buffer size: */
674 minval = pCfgReq->Backend.cFramesBufferSize;
675 err = snd_pcm_hw_params_get_buffer_size_min(pHWParms, &minval);
676 AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not retrieve minimal buffer size: %s\n", snd_strerror(err)), err);
677
678 snd_pcm_uframes_t buffer_size_f = pCfgReq->Backend.cFramesBufferSize;
679 if (buffer_size_f < minval)
680 buffer_size_f = minval;
681 err = snd_pcm_hw_params_set_buffer_size_near(hPCM, pHWParms, &buffer_size_f);
682 LogRel2(("ALSA: Buffer size is: %lu frames (min %lu, requested %u)\n", buffer_size_f, minval, pCfgReq->Backend.cFramesBufferSize));
683 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set near buffer size %RU32: %s\n", buffer_size_f, snd_strerror(err)), err);
684
685 /*
686 * Set the hardware parameters.
687 */
688 err = snd_pcm_hw_params(hPCM, pHWParms);
689 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to apply audio parameters: %s\n", snd_strerror(err)), err);
690
691 /*
692 * Get relevant parameters and put them in the pAlsaCfgObt structure.
693 */
694 snd_pcm_uframes_t obt_buffer_size = buffer_size_f;
695 err = snd_pcm_hw_params_get_buffer_size(pHWParms, &obt_buffer_size);
696 AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get buffer size: %s\n", snd_strerror(err)), obt_buffer_size = buffer_size_f);
697 pCfgAcq->Backend.cFramesBufferSize = obt_buffer_size;
698
699 snd_pcm_uframes_t obt_period_size = period_size_f;
700 err = snd_pcm_hw_params_get_period_size(pHWParms, &obt_period_size, &dir);
701 AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get period size: %s\n", snd_strerror(err)), obt_period_size = period_size_f);
702 pCfgAcq->Backend.cFramesPeriod = obt_period_size;
703
704 LogRel2(("ALSA: HW params: %u Hz, %u frames period, %u frames buffer, %u channel(s), enmAlsaFmt=%d\n",
705 PDMAudioPropsHz(&pCfgAcq->Props), pCfgAcq->Backend.cFramesPeriod, pCfgAcq->Backend.cFramesBufferSize,
706 PDMAudioPropsChannels(&pCfgAcq->Props), enmAlsaFmt));
707
708#if 0 /* Disabled in the hope to resolve testboxes not being able to drain + crashing when closing the PCM streams. */
709 /*
710 * Channel config (not fatal).
711 */
712 if (PDMAudioPropsChannels(&pCfgAcq->Props) == PDMAudioPropsChannels(&pCfgReq->Props))
713 {
714 err = snd_pcm_set_chmap(hPCM, &u.Map);
715 if (err < 0)
716 {
717 if (err == -ENXIO)
718 LogRel2(("ALSA: Audio device does not support channel maps, skipping\n"));
719 else
720 LogRel2(("ALSA: snd_pcm_set_chmap failed: %s (%d)\n", snd_strerror(err), err));
721 }
722 }
723#endif
724
725 return 0;
726}
727
728
729/**
730 * Opens (creates) an ALSA stream.
731 *
732 * @returns VBox status code.
733 * @param pThis The alsa driver instance data.
734 * @param enmAlsaFmt The ALSA format to use.
735 * @param pCfgReq Requested configuration to create stream with (PDM).
736 * @param pCfgAcq The actual stream configuration (PDM). This is assumed
737 * to be a copy of pCfgReq on input, at least for
738 * properties handled here. On output some of the
739 * properties may be updated to match the actual stream
740 * configuration.
741 * @param phPCM Where to store the ALSA stream handle on success.
742 */
743static int alsaStreamOpen(PDRVHSTAUDALSA pThis, snd_pcm_format_t enmAlsaFmt, PCPDMAUDIOSTREAMCFG pCfgReq,
744 PPDMAUDIOSTREAMCFG pCfgAcq, snd_pcm_t **phPCM)
745{
746 /*
747 * Open the stream.
748 */
749 int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
750 const char * const pszType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "input" : "output";
751 const char * const pszDev = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->szInputDev : pThis->szOutputDev;
752 snd_pcm_stream_t enmType = pCfgReq->enmDir == PDMAUDIODIR_IN ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
753
754 snd_pcm_t *hPCM = NULL;
755 LogRel(("ALSA: Using %s device \"%s\"\n", pszType, pszDev));
756 int err = snd_pcm_open(&hPCM, pszDev, enmType, SND_PCM_NONBLOCK);
757 if (err >= 0)
758 {
759 err = snd_pcm_nonblock(hPCM, 1);
760 if (err >= 0)
761 {
762 /*
763 * Configure hardware stream parameters.
764 */
765 err = alsaStreamSetHwParams(hPCM, enmAlsaFmt, pCfgReq, pCfgAcq);
766 if (err >= 0)
767 {
768 /*
769 * Prepare it.
770 */
771 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
772 err = snd_pcm_prepare(hPCM);
773 if (err >= 0)
774 {
775 /*
776 * Configure software stream parameters.
777 */
778 rc = alsaStreamSetSWParams(hPCM, pCfgReq, pCfgAcq);
779 if (RT_SUCCESS(rc))
780 {
781 *phPCM = hPCM;
782 return VINF_SUCCESS;
783 }
784 }
785 else
786 LogRel(("ALSA: snd_pcm_prepare failed: %s\n", snd_strerror(err)));
787 }
788 }
789 else
790 LogRel(("ALSA: Error setting non-blocking mode for %s stream: %s\n", pszType, snd_strerror(err)));
791 drvHstAudAlsaStreamClose(&hPCM);
792 }
793 else
794 LogRel(("ALSA: Failed to open \"%s\" as %s device: %s\n", pszDev, pszType, snd_strerror(err)));
795 *phPCM = NULL;
796 return rc;
797}
798
799
800/**
801 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
802 */
803static DECLCALLBACK(int) drvHstAudAlsaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
804 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
805{
806 PDRVHSTAUDALSA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDALSA, IHostAudio);
807 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
808 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
809 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
810 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
811
812 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
813 PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgReq);
814
815 int rc;
816 snd_pcm_format_t const enmFmt = alsaAudioPropsToALSA(&pCfgReq->Props);
817 if (enmFmt != SND_PCM_FORMAT_UNKNOWN)
818 {
819 rc = alsaStreamOpen(pThis, enmFmt, pCfgReq, pCfgAcq, &pStreamALSA->hPCM);
820 if (RT_SUCCESS(rc))
821 {
822 /* We have no objections to the pre-buffering that DrvAudio applies,
823 only we need to adjust it relative to the actual buffer size. */
824 pCfgAcq->Backend.cFramesPreBuffering = (uint64_t)pCfgReq->Backend.cFramesPreBuffering
825 * pCfgAcq->Backend.cFramesBufferSize
826 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
827
828 PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgAcq);
829 LogFlowFunc(("returns success - hPCM=%p\n", pStreamALSA->hPCM));
830 return rc;
831 }
832 }
833 else
834 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
835 LogFunc(("returns %Rrc\n", rc));
836 return rc;
837}
838
839
840/**
841 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
842 */
843static DECLCALLBACK(int) drvHstAudAlsaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
844{
845 RT_NOREF(pInterface);
846 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
847 AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
848 RT_NOREF(fImmediate);
849
850 LogRelFlowFunc(("Stream '%s' state is '%s'\n", pStreamALSA->Cfg.szName, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
851
852 /** @todo r=bird: It's not like we can do much with a bad status... Check
853 * what the caller does... */
854 int rc = drvHstAudAlsaStreamClose(&pStreamALSA->hPCM);
855
856 LogRelFlowFunc(("returns %Rrc\n", rc));
857
858 return rc;
859}
860
861
862/**
863 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
864 */
865static DECLCALLBACK(int) drvHstAudAlsaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
866{
867 RT_NOREF(pInterface);
868 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
869
870 /*
871 * Prepare the stream.
872 */
873 int rc = snd_pcm_prepare(pStreamALSA->hPCM);
874 if (rc >= 0)
875 {
876 Assert(snd_pcm_state(pStreamALSA->hPCM) == SND_PCM_STATE_PREPARED);
877
878 /*
879 * Input streams should be started now, whereas output streams must
880 * pre-buffer sufficent data before starting.
881 */
882 if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_IN)
883 {
884 rc = snd_pcm_start(pStreamALSA->hPCM);
885 if (rc >= 0)
886 rc = VINF_SUCCESS;
887 else
888 {
889 LogRel(("ALSA: Error starting input stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
890 rc = RTErrConvertFromErrno(-rc);
891 }
892 }
893 else
894 rc = VINF_SUCCESS;
895 }
896 else
897 {
898 LogRel(("ALSA: Error preparing stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
899 rc = RTErrConvertFromErrno(-rc);
900 }
901 LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
902 return rc;
903}
904
905
906/**
907 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
908 */
909static DECLCALLBACK(int) drvHstAudAlsaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
910{
911 RT_NOREF(pInterface);
912 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
913
914 int rc = snd_pcm_drop(pStreamALSA->hPCM);
915 if (rc >= 0)
916 rc = VINF_SUCCESS;
917 else
918 {
919 LogRel(("ALSA: Error stopping stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
920 rc = RTErrConvertFromErrno(-rc);
921 }
922 LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
923 return rc;
924}
925
926
927/**
928 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
929 */
930static DECLCALLBACK(int) drvHstAudAlsaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
931{
932 /* Same as disable. */
933 /** @todo r=bird: Try use pause and fallback on disable/enable if it isn't
934 * supported or doesn't work. */
935 return drvHstAudAlsaHA_StreamDisable(pInterface, pStream);
936}
937
938
939/**
940 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
941 */
942static DECLCALLBACK(int) drvHstAudAlsaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
943{
944 /* Same as enable. */
945 return drvHstAudAlsaHA_StreamEnable(pInterface, pStream);
946}
947
948
949/**
950 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
951 */
952static DECLCALLBACK(int) drvHstAudAlsaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
953{
954 RT_NOREF(pInterface);
955 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
956
957 snd_pcm_state_t const enmState = snd_pcm_state(pStreamALSA->hPCM);
958 LogRelFlowFunc(("Stream '%s' input state: %s (%d)\n", pStreamALSA->Cfg.szName, snd_pcm_state_name(enmState), enmState));
959
960 /* Only for output streams. */
961 AssertReturn(pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER);
962
963 int rc;
964 switch (enmState)
965 {
966 case SND_PCM_STATE_RUNNING:
967 case SND_PCM_STATE_PREPARED: /* not yet started */
968 {
969 /* Do not change to blocking here! */
970 rc = snd_pcm_drain(pStreamALSA->hPCM);
971 if (rc >= 0 || rc == -EAGAIN)
972 rc = VINF_SUCCESS;
973 else
974 {
975 snd_pcm_state_t const enmState2 = snd_pcm_state(pStreamALSA->hPCM);
976 if (rc == -EPIPE && enmState2 == enmState)
977 {
978 /* Not entirely sure, but possibly an underrun, so just disable the stream. */
979 LogRel2(("ALSA: snd_pcm_drain failed with -EPIPE, stopping stream (%s)\n", pStreamALSA->Cfg.szName));
980 rc = snd_pcm_drop(pStreamALSA->hPCM);
981 if (rc >= 0)
982 rc = VINF_SUCCESS;
983 else
984 {
985 LogRel(("ALSA: Error draining/stopping stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
986 rc = RTErrConvertFromErrno(-rc);
987 }
988 }
989 else
990 {
991 LogRel(("ALSA: Error draining output of '%s': %s (%d; %s -> %s)\n", pStreamALSA->Cfg.szName, snd_strerror(rc),
992 rc, snd_pcm_state_name(enmState), snd_pcm_state_name(enmState2)));
993 rc = RTErrConvertFromErrno(-rc);
994 }
995 }
996 break;
997 }
998
999 default:
1000 rc = VINF_SUCCESS;
1001 break;
1002 }
1003 LogRelFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
1004 return rc;
1005}
1006
1007
1008/**
1009 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
1010 */
1011static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudAlsaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
1012 PPDMAUDIOBACKENDSTREAM pStream)
1013{
1014 RT_NOREF(pInterface);
1015 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
1016 AssertPtrReturn(pStreamALSA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
1017
1018 PDMHOSTAUDIOSTREAMSTATE enmStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
1019 snd_pcm_state_t enmAlsaState = snd_pcm_state(pStreamALSA->hPCM);
1020 if (enmAlsaState == SND_PCM_STATE_DRAINING)
1021 {
1022 /* We're operating in non-blocking mode, so we must (at least for a demux
1023 config) call snd_pcm_drain again to drive it forward. Otherwise we
1024 might be stuck in the drain state forever. */
1025 Log5Func(("Calling snd_pcm_drain again...\n"));
1026 snd_pcm_drain(pStreamALSA->hPCM);
1027 enmAlsaState = snd_pcm_state(pStreamALSA->hPCM);
1028 }
1029
1030 if (enmAlsaState == SND_PCM_STATE_DRAINING)
1031 enmStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
1032#if (((SND_LIB_MAJOR) << 16) | ((SND_LIB_MAJOR) << 8) | (SND_LIB_SUBMINOR)) >= 0x10002 /* was added in 1.0.2 */
1033 else if (enmAlsaState == SND_PCM_STATE_DISCONNECTED)
1034 enmStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
1035#endif
1036
1037 Log5Func(("Stream '%s': ALSA state=%s -> %s\n",
1038 pStreamALSA->Cfg.szName, snd_pcm_state_name(enmAlsaState), PDMHostAudioStreamStateGetName(enmStreamState) ));
1039 return enmStreamState;
1040}
1041
1042
1043/**
1044 * Returns the available audio frames queued.
1045 *
1046 * @returns VBox status code.
1047 * @param hPCM ALSA stream handle.
1048 * @param pcFramesAvail Where to store the available frames.
1049 */
1050static int alsaStreamGetAvail(snd_pcm_t *hPCM, snd_pcm_sframes_t *pcFramesAvail)
1051{
1052 AssertPtr(hPCM);
1053 AssertPtr(pcFramesAvail);
1054
1055 int rc;
1056 snd_pcm_sframes_t cFramesAvail = snd_pcm_avail_update(hPCM);
1057 if (cFramesAvail > 0)
1058 {
1059 LogFunc(("cFramesAvail=%ld\n", cFramesAvail));
1060 *pcFramesAvail = cFramesAvail;
1061 return VINF_SUCCESS;
1062 }
1063
1064 /*
1065 * We can maybe recover from an EPIPE...
1066 */
1067 if (cFramesAvail == -EPIPE)
1068 {
1069 rc = drvHstAudAlsaStreamRecover(hPCM);
1070 if (RT_SUCCESS(rc))
1071 {
1072 cFramesAvail = snd_pcm_avail_update(hPCM);
1073 if (cFramesAvail >= 0)
1074 {
1075 LogFunc(("cFramesAvail=%ld\n", cFramesAvail));
1076 *pcFramesAvail = cFramesAvail;
1077 return VINF_SUCCESS;
1078 }
1079 }
1080 else
1081 {
1082 *pcFramesAvail = 0;
1083 return rc;
1084 }
1085 }
1086
1087 rc = RTErrConvertFromErrno(-(int)cFramesAvail);
1088 LogFunc(("failed - cFramesAvail=%ld rc=%Rrc\n", cFramesAvail, rc));
1089 *pcFramesAvail = 0;
1090 return rc;
1091}
1092
1093
1094/**
1095 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
1096 */
1097static DECLCALLBACK(uint32_t) drvHstAudAlsaHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1098{
1099 RT_NOREF(pInterface);
1100 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
1101 AssertPtrReturn(pStreamALSA, 0);
1102
1103 /*
1104 * This is only relevant to output streams (input streams can't have
1105 * any pending, unplayed data).
1106 */
1107 uint32_t cbPending = 0;
1108 if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT)
1109 {
1110 /*
1111 * Getting the delay (in audio frames) reports the time it will take
1112 * to hear a new sample after all queued samples have been played out.
1113 *
1114 * We use snd_pcm_avail_delay instead of snd_pcm_delay here as it will
1115 * update the buffer positions, and we can use the extra value against
1116 * the buffer size to double check since the delay value may include
1117 * fixed built-in delays in the processing chain and hardware.
1118 */
1119 snd_pcm_sframes_t cFramesAvail = 0;
1120 snd_pcm_sframes_t cFramesDelay = 0;
1121 int rc = snd_pcm_avail_delay(pStreamALSA->hPCM, &cFramesAvail, &cFramesDelay);
1122
1123 /*
1124 * We now also get the state as the pending value should be zero when
1125 * we're not in a playing state.
1126 */
1127 snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM);
1128 switch (enmState)
1129 {
1130 case SND_PCM_STATE_RUNNING:
1131 case SND_PCM_STATE_DRAINING:
1132 if (rc >= 0)
1133 {
1134 if ((uint32_t)cFramesAvail >= pStreamALSA->Cfg.Backend.cFramesBufferSize)
1135 cbPending = 0;
1136 else
1137 cbPending = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesDelay);
1138 }
1139 break;
1140
1141 default:
1142 break;
1143 }
1144 Log2Func(("returns %u (%#x) - cFramesBufferSize=%RU32 cFramesAvail=%ld cFramesDelay=%ld rc=%d; enmState=%s (%d) \n",
1145 cbPending, cbPending, pStreamALSA->Cfg.Backend.cFramesBufferSize, cFramesAvail, cFramesDelay, rc,
1146 snd_pcm_state_name(enmState), enmState));
1147 }
1148 return cbPending;
1149}
1150
1151
1152/**
1153 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1154 */
1155static DECLCALLBACK(uint32_t) drvHstAudAlsaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1156{
1157 RT_NOREF(pInterface);
1158 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
1159
1160 uint32_t cbAvail = 0;
1161 snd_pcm_sframes_t cFramesAvail = 0;
1162 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1163 if (RT_SUCCESS(rc))
1164 cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail);
1165
1166 return cbAvail;
1167}
1168
1169
1170/**
1171 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1172 */
1173static DECLCALLBACK(int) drvHstAudAlsaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1174 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1175{
1176 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
1177 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1178 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1179 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
1180 Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
1181 snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
1182 if (cbBuf)
1183 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1184 else
1185 {
1186 /* Fend off draining calls. */
1187 *pcbWritten = 0;
1188 return VINF_SUCCESS;
1189 }
1190
1191 /*
1192 * Determine how much we can write (caller actually did this
1193 * already, but we repeat it just to be sure or something).
1194 */
1195 snd_pcm_sframes_t cFramesAvail;
1196 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1197 if (RT_SUCCESS(rc))
1198 {
1199 Assert(cFramesAvail);
1200 if (cFramesAvail)
1201 {
1202 PCPDMAUDIOPCMPROPS pProps = &pStreamALSA->Cfg.Props;
1203 uint32_t cbToWrite = PDMAudioPropsFramesToBytes(pProps, (uint32_t)cFramesAvail);
1204 if (cbToWrite)
1205 {
1206 if (cbToWrite > cbBuf)
1207 cbToWrite = cbBuf;
1208
1209 /*
1210 * Try write the data.
1211 */
1212 uint32_t cFramesToWrite = PDMAudioPropsBytesToFrames(pProps, cbToWrite);
1213 snd_pcm_sframes_t cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
1214 if (cFramesWritten > 0)
1215 {
1216 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
1217 cbToWrite, cFramesWritten, cFramesAvail));
1218 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
1219 pStreamALSA->offInternal += *pcbWritten;
1220 return VINF_SUCCESS;
1221 }
1222 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld]\n", cbToWrite, cFramesWritten, cFramesAvail));
1223
1224
1225 /*
1226 * There are a couple of error we can recover from, try to do so.
1227 * Only don't try too many times.
1228 */
1229 for (unsigned iTry = 0;
1230 (cFramesWritten == -EPIPE || cFramesWritten == -ESTRPIPE) && iTry < ALSA_RECOVERY_TRIES_MAX;
1231 iTry++)
1232 {
1233 if (cFramesWritten == -EPIPE)
1234 {
1235 /* Underrun occurred. */
1236 rc = drvHstAudAlsaStreamRecover(pStreamALSA->hPCM);
1237 if (RT_FAILURE(rc))
1238 break;
1239 LogFlowFunc(("Recovered from playback (iTry=%u)\n", iTry));
1240 }
1241 else
1242 {
1243 /* An suspended event occurred, needs resuming. */
1244 rc = drvHstAudAlsaStreamResume(pStreamALSA->hPCM);
1245 if (RT_FAILURE(rc))
1246 {
1247 LogRel(("ALSA: Failed to resume output stream (iTry=%u, rc=%Rrc)\n", iTry, rc));
1248 break;
1249 }
1250 LogFlowFunc(("Resumed suspended output stream (iTry=%u)\n", iTry));
1251 }
1252
1253 cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
1254 if (cFramesWritten > 0)
1255 {
1256 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
1257 cbToWrite, cFramesWritten, cFramesAvail));
1258 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
1259 pStreamALSA->offInternal += *pcbWritten;
1260 return VINF_SUCCESS;
1261 }
1262 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld, iTry=%d]\n", cbToWrite, cFramesWritten, cFramesAvail, iTry));
1263 }
1264
1265 /* Make sure we return with an error status. */
1266 if (RT_SUCCESS_NP(rc))
1267 {
1268 if (cFramesWritten == 0)
1269 rc = VERR_ACCESS_DENIED;
1270 else
1271 {
1272 rc = RTErrConvertFromErrno(-(int)cFramesWritten);
1273 LogFunc(("Failed to write %RU32 bytes: %ld (%Rrc)\n", cbToWrite, cFramesWritten, rc));
1274 }
1275 }
1276 }
1277 }
1278 }
1279 else
1280 LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc));
1281 *pcbWritten = 0;
1282 return rc;
1283}
1284
1285
1286/**
1287 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1288 */
1289static DECLCALLBACK(uint32_t) drvHstAudAlsaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1290{
1291 RT_NOREF(pInterface);
1292 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
1293
1294 uint32_t cbAvail = 0;
1295 snd_pcm_sframes_t cFramesAvail = 0;
1296 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1297 if (RT_SUCCESS(rc))
1298 cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail);
1299
1300 return cbAvail;
1301}
1302
1303
1304/**
1305 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1306 */
1307static DECLCALLBACK(int) drvHstAudAlsaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1308 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1309{
1310 RT_NOREF_PV(pInterface);
1311 PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
1312 AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
1313 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1314 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1315 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
1316 Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
1317 snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
1318
1319 /*
1320 * Figure out how much we can read without trouble (we're doing
1321 * non-blocking reads, but whatever).
1322 */
1323 snd_pcm_sframes_t cAvail;
1324 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cAvail);
1325 if (RT_SUCCESS(rc))
1326 {
1327 if (!cAvail) /* No data yet? */
1328 {
1329 snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM);
1330 switch (enmState)
1331 {
1332 case SND_PCM_STATE_PREPARED:
1333 /** @todo r=bird: explain the logic here... */
1334 cAvail = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbBuf);
1335 break;
1336
1337 case SND_PCM_STATE_SUSPENDED:
1338 rc = drvHstAudAlsaStreamResume(pStreamALSA->hPCM);
1339 if (RT_SUCCESS(rc))
1340 {
1341 LogFlowFunc(("Resumed suspended input stream.\n"));
1342 break;
1343 }
1344 LogFunc(("Failed resuming suspended input stream: %Rrc\n", rc));
1345 return rc;
1346
1347 default:
1348 LogFlow(("No frames available: state=%s (%d)\n", snd_pcm_state_name(enmState), enmState));
1349 break;
1350 }
1351 if (!cAvail)
1352 {
1353 *pcbRead = 0;
1354 return VINF_SUCCESS;
1355 }
1356 }
1357 }
1358 else
1359 {
1360 LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc));
1361 return rc;
1362 }
1363
1364 size_t cbToRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cAvail);
1365 cbToRead = RT_MIN(cbToRead, cbBuf);
1366 LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail));
1367
1368 /*
1369 * Read loop.
1370 */
1371 uint32_t cbReadTotal = 0;
1372 while (cbToRead > 0)
1373 {
1374 /*
1375 * Do the reading.
1376 */
1377 snd_pcm_uframes_t const cFramesToRead = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbToRead);
1378 AssertBreakStmt(cFramesToRead > 0, rc = VERR_NO_DATA);
1379
1380 snd_pcm_sframes_t cFramesRead = snd_pcm_readi(pStreamALSA->hPCM, pvBuf, cFramesToRead);
1381 if (cFramesRead > 0)
1382 {
1383 /*
1384 * We should not run into a full mixer buffer or we lose samples and
1385 * run into an endless loop if ALSA keeps producing samples ("null"
1386 * capture device for example).
1387 */
1388 uint32_t const cbRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesRead);
1389 Assert(cbRead <= cbToRead);
1390
1391 cbToRead -= cbRead;
1392 cbReadTotal += cbRead;
1393 pvBuf = (uint8_t *)pvBuf + cbRead;
1394 pStreamALSA->offInternal += cbRead;
1395 }
1396 else
1397 {
1398 /*
1399 * Try recover from overrun and re-try.
1400 * Other conditions/errors we cannot and will just quit the loop.
1401 */
1402 if (cFramesRead == -EPIPE)
1403 {
1404 rc = drvHstAudAlsaStreamRecover(pStreamALSA->hPCM);
1405 if (RT_SUCCESS(rc))
1406 {
1407 LogFlowFunc(("Successfully recovered from overrun\n"));
1408 continue;
1409 }
1410 LogFunc(("Failed to recover from overrun: %Rrc\n", rc));
1411 }
1412 else if (cFramesRead == -EAGAIN)
1413 LogFunc(("No input frames available (EAGAIN)\n"));
1414 else if (cFramesRead == 0)
1415 LogFunc(("No input frames available (0)\n"));
1416 else
1417 {
1418 rc = RTErrConvertFromErrno(-(int)cFramesRead);
1419 LogFunc(("Failed to read input frames: %s (%ld, %Rrc)\n", snd_strerror(cFramesRead), cFramesRead, rc));
1420 }
1421
1422 /* If we've read anything, suppress the error. */
1423 if (RT_FAILURE(rc) && cbReadTotal > 0)
1424 {
1425 LogFunc(("Suppressing %Rrc because %#x bytes has been read already\n", rc, cbReadTotal));
1426 rc = VINF_SUCCESS;
1427 }
1428 break;
1429 }
1430 }
1431
1432 LogFlowFunc(("returns %Rrc and %#x (%d) bytes (%u bytes left); state %s\n",
1433 rc, cbReadTotal, cbReadTotal, cbToRead, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
1434 *pcbRead = cbReadTotal;
1435 return rc;
1436}
1437
1438
1439/*********************************************************************************************************************************
1440* PDMIBASE *
1441*********************************************************************************************************************************/
1442
1443/**
1444 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1445 */
1446static DECLCALLBACK(void *) drvHstAudAlsaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1447{
1448 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1449 PDRVHSTAUDALSA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDALSA);
1450 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1451 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1452 return NULL;
1453}
1454
1455
1456/*********************************************************************************************************************************
1457* PDMDRVREG *
1458*********************************************************************************************************************************/
1459
1460/**
1461 * @interface_method_impl{PDMDRVREG,pfnDestruct,
1462 * Destructs an ALSA host audio driver instance.}
1463 */
1464static DECLCALLBACK(void) drvHstAudAlsaDestruct(PPDMDRVINS pDrvIns)
1465{
1466 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1467 PDRVHSTAUDALSA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDALSA);
1468 LogFlowFuncEnter();
1469
1470 if (RTCritSectIsInitialized(&pThis->CritSect))
1471 {
1472 RTCritSectEnter(&pThis->CritSect);
1473 pThis->pIHostAudioPort = NULL;
1474 RTCritSectLeave(&pThis->CritSect);
1475 RTCritSectDelete(&pThis->CritSect);
1476 }
1477
1478 LogFlowFuncLeave();
1479}
1480
1481
1482/**
1483 * @interface_method_impl{PDMDRVREG,pfnConstruct,
1484 * Construct an ALSA host audio driver instance.}
1485 */
1486static DECLCALLBACK(int) drvHstAudAlsaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1487{
1488 RT_NOREF(fFlags);
1489 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1490 PDRVHSTAUDALSA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDALSA);
1491 PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
1492 LogRel(("Audio: Initializing ALSA driver\n"));
1493
1494 /*
1495 * Init the static parts.
1496 */
1497 pThis->pDrvIns = pDrvIns;
1498 int rc = RTCritSectInit(&pThis->CritSect);
1499 AssertRCReturn(rc, rc);
1500 /* IBase */
1501 pDrvIns->IBase.pfnQueryInterface = drvHstAudAlsaQueryInterface;
1502 /* IHostAudio */
1503 pThis->IHostAudio.pfnGetConfig = drvHstAudAlsaHA_GetConfig;
1504 pThis->IHostAudio.pfnGetDevices = drvHstAudAlsaHA_GetDevices;
1505 pThis->IHostAudio.pfnSetDevice = drvHstAudAlsaHA_SetDevice;
1506 pThis->IHostAudio.pfnGetStatus = drvHstAudAlsaHA_GetStatus;
1507 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
1508 pThis->IHostAudio.pfnStreamConfigHint = NULL;
1509 pThis->IHostAudio.pfnStreamCreate = drvHstAudAlsaHA_StreamCreate;
1510 pThis->IHostAudio.pfnStreamInitAsync = NULL;
1511 pThis->IHostAudio.pfnStreamDestroy = drvHstAudAlsaHA_StreamDestroy;
1512 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
1513 pThis->IHostAudio.pfnStreamEnable = drvHstAudAlsaHA_StreamEnable;
1514 pThis->IHostAudio.pfnStreamDisable = drvHstAudAlsaHA_StreamDisable;
1515 pThis->IHostAudio.pfnStreamPause = drvHstAudAlsaHA_StreamPause;
1516 pThis->IHostAudio.pfnStreamResume = drvHstAudAlsaHA_StreamResume;
1517 pThis->IHostAudio.pfnStreamDrain = drvHstAudAlsaHA_StreamDrain;
1518 pThis->IHostAudio.pfnStreamGetPending = drvHstAudAlsaHA_StreamGetPending;
1519 pThis->IHostAudio.pfnStreamGetState = drvHstAudAlsaHA_StreamGetState;
1520 pThis->IHostAudio.pfnStreamGetWritable = drvHstAudAlsaHA_StreamGetWritable;
1521 pThis->IHostAudio.pfnStreamPlay = drvHstAudAlsaHA_StreamPlay;
1522 pThis->IHostAudio.pfnStreamGetReadable = drvHstAudAlsaHA_StreamGetReadable;
1523 pThis->IHostAudio.pfnStreamCapture = drvHstAudAlsaHA_StreamCapture;
1524
1525 /*
1526 * Read configuration.
1527 */
1528 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "OutputDeviceID|InputDeviceID", "");
1529
1530 rc = pHlp->pfnCFGMQueryStringDef(pCfg, "InputDeviceID", pThis->szInputDev, sizeof(pThis->szInputDev), "default");
1531 AssertRCReturn(rc, rc);
1532 rc = pHlp->pfnCFGMQueryStringDef(pCfg, "OutputDeviceID", pThis->szOutputDev, sizeof(pThis->szOutputDev), "default");
1533 AssertRCReturn(rc, rc);
1534
1535 /*
1536 * Init the alsa library.
1537 */
1538 rc = audioLoadAlsaLib();
1539 if (RT_FAILURE(rc))
1540 {
1541 LogRel(("ALSA: Failed to load the ALSA shared library: %Rrc\n", rc));
1542 return rc;
1543 }
1544
1545 /*
1546 * Query the notification interface from the driver/device above us.
1547 */
1548 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
1549 AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
1550
1551#ifdef DEBUG
1552 /*
1553 * Some debug stuff we don't use for anything at all.
1554 */
1555 snd_lib_error_set_handler(drvHstAudAlsaDbgErrorHandler);
1556#endif
1557 return VINF_SUCCESS;
1558}
1559
1560
1561/**
1562 * ALSA audio driver registration record.
1563 */
1564const PDMDRVREG g_DrvHostALSAAudio =
1565{
1566 /* u32Version */
1567 PDM_DRVREG_VERSION,
1568 /* szName */
1569 "ALSAAudio",
1570 /* szRCMod */
1571 "",
1572 /* szR0Mod */
1573 "",
1574 /* pszDescription */
1575 "ALSA host audio driver",
1576 /* fFlags */
1577 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1578 /* fClass. */
1579 PDM_DRVREG_CLASS_AUDIO,
1580 /* cMaxInstances */
1581 ~0U,
1582 /* cbInstance */
1583 sizeof(DRVHSTAUDALSA),
1584 /* pfnConstruct */
1585 drvHstAudAlsaConstruct,
1586 /* pfnDestruct */
1587 drvHstAudAlsaDestruct,
1588 /* pfnRelocate */
1589 NULL,
1590 /* pfnIOCtl */
1591 NULL,
1592 /* pfnPowerOn */
1593 NULL,
1594 /* pfnReset */
1595 NULL,
1596 /* pfnSuspend */
1597 NULL,
1598 /* pfnResume */
1599 NULL,
1600 /* pfnAttach */
1601 NULL,
1602 /* pfnDetach */
1603 NULL,
1604 /* pfnPowerOff */
1605 NULL,
1606 /* pfnSoftReset */
1607 NULL,
1608 /* u32EndVersion */
1609 PDM_DRVREG_VERSION
1610};
1611
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