VirtualBox

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

Last change on this file since 90778 was 89533, checked in by vboxsync, 3 years ago

DrvHostAudioAlsa: Don't call the output/input device 'default'. bugref:9890

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

© 2023 Oracle
ContactPrivacy policyTerms of Use