VirtualBox

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

最後變更 在這個檔案從89500是 89500,由 vboxsync 提交於 4 年 前

Audio: Changed PDMAUDIOHOSTDEV::szName into a pszName so we don't have to truncate anything. Made PDMAudioHostDevAlloc capable of allocating both pszName and pszId as part of the device allocation. bugref:9890

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette