VirtualBox

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

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

DrvHostAudioAlsa: Simplify the drvHostAlsaAudioHA_StreamCreate stuff, do the same config for both input and output. Started preparing to get rid of ALSAAUDIOSTREAMCFG. bugref:9890

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

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