VirtualBox

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

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

Audio: Changed PDMIHOSTAUDIO::pfnStreamGetStatus into pfnStreamGetState and defined a simpler state enum (PDMHOSTAUDIOSTREAMSTATE) that fits what DrvAudio needs and the backends actually want to tell us. Fixes one VRDE issue. bugref:9890

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

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