VirtualBox

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

Last change on this file since 89132 was 89055, checked in by vboxsync, 4 years ago

ValKit/Audio: Eliminated VBoxDDVKAT.h. Made some progress getting the PulseAudio driver to work (integer overflow now due to invalid config). bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.8 KB
Line 
1/* $Id: DrvHostAudioAlsa.cpp 89055 2021-05-15 16:03:07Z 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). */
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 < 16, 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 pCfgReq Requested configuration to set.
463 * @param pCfgObt Obtained configuration on success. Might differ from requested configuration.
464 */
465static int alsaStreamSetSWParams(snd_pcm_t *hPCM, bool fIn, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt)
466{
467 if (fIn) /* For input streams there's nothing to do in here right now. */
468 return VINF_SUCCESS;
469
470 snd_pcm_sw_params_t *pSWParms = NULL;
471 snd_pcm_sw_params_alloca(&pSWParms);
472 AssertReturn(pSWParms, -ENOMEM);
473
474 int err = snd_pcm_sw_params_current(hPCM, pSWParms);
475 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to get current software parameters: %s\n", snd_strerror(err)), err);
476
477 /* Under normal circumstance, we don't need to set a playback threshold
478 because DrvAudio will do the pre-buffering and hand us everything in
479 one continuous chunk when we should start playing. But since it is
480 configurable, we'll set a reasonable minimum of two DMA periods or
481 max 64 milliseconds (the pCfgReq->threshold value).
482
483 Of course we also have to make sure the threshold is below the buffer
484 size, or ALSA will never start playing. */
485 unsigned long cFramesThreshold = RT_MIN(pCfgObt->period_size * 2, pCfgReq->threshold);
486 if (cFramesThreshold >= pCfgObt->buffer_size - pCfgObt->buffer_size / 16)
487 cFramesThreshold = pCfgObt->buffer_size - pCfgObt->buffer_size / 16;
488
489 err = snd_pcm_sw_params_set_start_threshold(hPCM, pSWParms, cFramesThreshold);
490 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set software threshold to %lu: %s\n", cFramesThreshold, snd_strerror(err)), err);
491
492 err = snd_pcm_sw_params_set_avail_min(hPCM, pSWParms, pCfgReq->period_size);
493 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set available minimum to %lu: %s\n", pCfgReq->period_size, snd_strerror(err)), err);
494
495 /* Commit the software parameters: */
496 err = snd_pcm_sw_params(hPCM, pSWParms);
497 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set new software parameters: %s\n", snd_strerror(err)), err);
498
499 /* Get the actual parameters: */
500 err = snd_pcm_sw_params_get_start_threshold(pSWParms, &pCfgObt->threshold);
501 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to get start threshold: %s\n", snd_strerror(err)), err);
502
503 LogRel2(("ALSA: SW params: %ul frames threshold, %ul frame avail minimum\n",
504 pCfgObt->threshold, pCfgReq->period_size));
505 return 0;
506}
507
508
509/**
510 * Sets the hardware parameters of an ALSA stream.
511 *
512 * @returns 0 on success, negative errno on failure.
513 * @param hPCM ALSA stream to set software parameters for.
514 * @param pCfgReq Requested configuration to set.
515 * @param pCfgObt Obtained configuration on success. Might differ from
516 * requested configuration.
517 */
518static int alsaStreamSetHwParams(snd_pcm_t *hPCM, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt)
519{
520 /*
521 * Get the current hardware parameters.
522 */
523 snd_pcm_hw_params_t *pHWParms = NULL;
524 snd_pcm_hw_params_alloca(&pHWParms);
525 AssertReturn(pHWParms, -ENOMEM);
526
527 int err = snd_pcm_hw_params_any(hPCM, pHWParms);
528 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to initialize hardware parameters: %s\n", snd_strerror(err)), err);
529
530 /*
531 * Modify them according to pCfgReq.
532 * We update pCfgObt as we go for parameters set by "near" methods.
533 */
534 /* We'll use snd_pcm_writei/snd_pcm_readi: */
535 err = snd_pcm_hw_params_set_access(hPCM, pHWParms, SND_PCM_ACCESS_RW_INTERLEAVED);
536 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set access type: %s\n", snd_strerror(err)), err);
537
538 /* Set the format, frequency and channel count. */
539 err = snd_pcm_hw_params_set_format(hPCM, pHWParms, pCfgReq->fmt);
540 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set audio format to %d: %s\n", pCfgReq->fmt, snd_strerror(err)), err);
541
542 unsigned int uFreq = pCfgReq->freq;
543 err = snd_pcm_hw_params_set_rate_near(hPCM, pHWParms, &uFreq, NULL /*dir*/);
544 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set frequency to %uHz: %s\n", pCfgReq->freq, snd_strerror(err)), err);
545 pCfgObt->freq = uFreq;
546
547 unsigned int cChannels = pCfgReq->cChannels;
548 err = snd_pcm_hw_params_set_channels_near(hPCM, pHWParms, &cChannels);
549 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set number of channels to %d\n", pCfgReq->cChannels), err);
550 AssertLogRelMsgReturn(cChannels == 1 || cChannels == 2, ("ALSA: Number of audio channels (%u) not supported\n", cChannels), -1);
551 pCfgObt->cChannels = cChannels;
552
553 /* The period size (reportedly frame count per hw interrupt): */
554 int dir = 0;
555 snd_pcm_uframes_t minval = pCfgReq->period_size;
556 err = snd_pcm_hw_params_get_period_size_min(pHWParms, &minval, &dir);
557 AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not determine minimal period size: %s\n", snd_strerror(err)), err);
558
559 snd_pcm_uframes_t period_size_f = pCfgReq->period_size;
560 if (period_size_f < minval)
561 period_size_f = minval;
562 err = snd_pcm_hw_params_set_period_size_near(hPCM, pHWParms, &period_size_f, 0);
563 LogRel2(("ALSA: Period size is: %lu frames (min %lu, requested %lu)\n", period_size_f, minval, pCfgReq->period_size));
564 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set period size %d (%s)\n", period_size_f, snd_strerror(err)), err);
565
566 /* The buffer size: */
567 minval = pCfgReq->buffer_size;
568 err = snd_pcm_hw_params_get_buffer_size_min(pHWParms, &minval);
569 AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not retrieve minimal buffer size: %s\n", snd_strerror(err)), err);
570
571 snd_pcm_uframes_t buffer_size_f = pCfgReq->buffer_size;
572 if (buffer_size_f < minval)
573 buffer_size_f = minval;
574 err = snd_pcm_hw_params_set_buffer_size_near(hPCM, pHWParms, &buffer_size_f);
575 LogRel2(("ALSA: Buffer size is: %lu frames (min %lu, requested %lu)\n", buffer_size_f, minval, pCfgReq->buffer_size));
576 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set near buffer size %RU32: %s\n", buffer_size_f, snd_strerror(err)), err);
577
578 /*
579 * Set the hardware parameters.
580 */
581 err = snd_pcm_hw_params(hPCM, pHWParms);
582 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to apply audio parameters: %s\n", snd_strerror(err)), err);
583
584 /*
585 * Get relevant parameters and put them in the pCfgObt structure.
586 */
587 snd_pcm_uframes_t obt_buffer_size = buffer_size_f;
588 err = snd_pcm_hw_params_get_buffer_size(pHWParms, &obt_buffer_size);
589 AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get buffer size: %s\n", snd_strerror(err)), obt_buffer_size = buffer_size_f);
590 pCfgObt->buffer_size = obt_buffer_size;
591
592 snd_pcm_uframes_t obt_period_size = period_size_f;
593 err = snd_pcm_hw_params_get_period_size(pHWParms, &obt_period_size, &dir);
594 AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get period size: %s\n", snd_strerror(err)), obt_period_size = period_size_f);
595 pCfgObt->period_size = obt_period_size;
596
597// pCfgObt->access = pCfgReq->access; - unused and uninitialized.
598 pCfgObt->fmt = pCfgReq->fmt;
599
600 LogRel2(("ALSA: HW params: %u Hz, %lu frames period, %lu frames buffer, %u channel(s), fmt=%d, access=%d\n",
601 pCfgObt->freq, pCfgObt->period_size, pCfgObt->buffer_size, pCfgObt->cChannels, pCfgObt->fmt, -1 /*pCfgObt->access*/));
602 return 0;
603}
604
605
606/**
607 * Opens (creates) an ALSA stream.
608 *
609 * @returns VBox status code.
610 * @param pszDev The name of the device to open.
611 * @param fIn Whether this is an input stream to create or not.
612 * @param pCfgReq Requested configuration to create stream with.
613 * @param pCfgObt Obtained configuration the stream got created on success.
614 * @param phPCM Where to store the ALSA stream handle on success.
615 */
616static int alsaStreamOpen(const char *pszDev, bool fIn, PALSAAUDIOSTREAMCFG pCfgReq,
617 PALSAAUDIOSTREAMCFG pCfgObt, snd_pcm_t **phPCM)
618{
619 AssertLogRelMsgReturn(pszDev && *pszDev,
620 ("ALSA: Invalid or no %s device name set\n", fIn ? "input" : "output"),
621 VERR_INVALID_NAME);
622
623 /*
624 * Open the stream.
625 */
626 int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
627 snd_pcm_t *hPCM = NULL;
628 LogRel(("ALSA: Using %s device \"%s\"\n", fIn ? "input" : "output", pszDev));
629 int err = snd_pcm_open(&hPCM, pszDev,
630 fIn ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
631 SND_PCM_NONBLOCK);
632 if (err >= 0)
633 {
634 err = snd_pcm_nonblock(hPCM, 1);
635 if (err >= 0)
636 {
637 /*
638 * Configure hardware stream parameters.
639 */
640 err = alsaStreamSetHwParams(hPCM, pCfgReq, pCfgObt);
641 if (err >= 0)
642 {
643 /*
644 * Prepare it.
645 */
646 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
647 err = snd_pcm_prepare(hPCM);
648 if (err >= 0)
649 {
650 /*
651 * Configure software stream parameters and we're done.
652 */
653 rc = alsaStreamSetSWParams(hPCM, fIn, pCfgReq, pCfgObt);
654 if (RT_SUCCESS(rc))
655 {
656 *phPCM = hPCM;
657 return VINF_SUCCESS;
658 }
659 }
660 else
661 LogRel(("ALSA: snd_pcm_prepare failed: %s\n", snd_strerror(err)));
662 }
663 }
664 else
665 LogRel(("ALSA: Error setting output non-blocking mode: %s\n", snd_strerror(err)));
666 alsaStreamClose(&hPCM);
667 }
668 else
669 LogRel(("ALSA: Failed to open \"%s\" as %s device: %s\n", pszDev, fIn ? "input" : "output", snd_strerror(err)));
670 *phPCM = NULL;
671 return rc;
672}
673
674
675/**
676 * Creates an ALSA output stream.
677 *
678 * @returns VBox status code.
679 * @param pThis The ALSA driver instance data.
680 * @param pStreamALSA ALSA output stream to create.
681 * @param pCfgReq Requested configuration to create stream with.
682 * @param pCfgAcq Obtained configuration the stream got created
683 * with on success.
684 */
685static int alsaCreateStreamOut(PDRVHOSTALSAAUDIO pThis, PALSAAUDIOSTREAM pStreamALSA,
686 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
687{
688 ALSAAUDIOSTREAMCFG Req;
689 Req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props);
690 Req.freq = PDMAudioPropsHz(&pCfgReq->Props);
691 Req.cChannels = PDMAudioPropsChannels(&pCfgReq->Props);
692 Req.period_size = pCfgReq->Backend.cFramesPeriod;
693 Req.buffer_size = pCfgReq->Backend.cFramesBufferSize;
694 Req.threshold = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 50);
695 int rc = alsaStreamOpen(pThis->szDefaultOut, false /*fIn*/, &Req, &pStreamALSA->AlsaCfg, &pStreamALSA->hPCM);
696 if (RT_SUCCESS(rc))
697 {
698 rc = alsaALSAToAudioProps(&pCfgAcq->Props, pStreamALSA->AlsaCfg.fmt,
699 pStreamALSA->AlsaCfg.cChannels, pStreamALSA->AlsaCfg.freq);
700 if (RT_SUCCESS(rc))
701 {
702 pCfgAcq->Backend.cFramesPeriod = pStreamALSA->AlsaCfg.period_size;
703 pCfgAcq->Backend.cFramesBufferSize = pStreamALSA->AlsaCfg.buffer_size;
704
705 /* We have no objections to the pre-buffering that DrvAudio applies,
706 only we need to adjust it relative to the actual buffer size. */
707 /** @todo DrvAudio should do this. */
708 pCfgAcq->Backend.cFramesPreBuffering = (uint64_t)pCfgReq->Backend.cFramesPreBuffering
709 * pCfgAcq->Backend.cFramesBufferSize
710 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
711
712 LogFlowFunc(("returns success - hPCM=%p\n", pStreamALSA->hPCM));
713 return VINF_SUCCESS;
714 }
715 alsaStreamClose(&pStreamALSA->hPCM);
716 }
717 LogFlowFuncLeaveRC(rc);
718 return rc;
719}
720
721
722/**
723 * Creates an ALSA input stream.
724 *
725 * @returns VBox status code.
726 * @param pThis The ALSA driver instance data.
727 * @param pStreamALSA ALSA input stream to create.
728 * @param pCfgReq Requested configuration to create stream with.
729 * @param pCfgAcq Obtained configuration the stream got created
730 * with on success.
731 */
732static int alsaCreateStreamIn(PDRVHOSTALSAAUDIO pThis, PALSAAUDIOSTREAM pStreamALSA,
733 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
734{
735 ALSAAUDIOSTREAMCFG Req;
736 Req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props);
737 Req.freq = PDMAudioPropsHz(&pCfgReq->Props);
738 Req.cChannels = PDMAudioPropsChannels(&pCfgReq->Props);
739/** @todo r=bird: Isn't all this configurable already?!? */
740 Req.period_size = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 50 /*ms*/); /** @todo Make this configurable. */
741 Req.buffer_size = Req.period_size * 2; /** @todo Make this configurable. */
742 Req.threshold = Req.period_size;
743 int rc = alsaStreamOpen(pThis->szDefaultIn, true /* fIn */, &Req, &pStreamALSA->AlsaCfg, &pStreamALSA->hPCM);
744 if (RT_SUCCESS(rc))
745 {
746 rc = alsaALSAToAudioProps(&pCfgAcq->Props, pStreamALSA->AlsaCfg.fmt, pStreamALSA->AlsaCfg.cChannels, pStreamALSA->AlsaCfg.freq);
747 if (RT_SUCCESS(rc))
748 {
749 pCfgAcq->Backend.cFramesPeriod = pStreamALSA->AlsaCfg.period_size;
750 pCfgAcq->Backend.cFramesBufferSize = pStreamALSA->AlsaCfg.buffer_size;
751 pCfgAcq->Backend.cFramesPreBuffering = 0; /* No pre-buffering. */
752 return VINF_SUCCESS;
753 }
754
755 alsaStreamClose(&pStreamALSA->hPCM);
756 }
757 LogFlowFuncLeaveRC(rc);
758 return rc;
759}
760
761
762/**
763 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
764 */
765static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
766 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
767{
768 PDRVHOSTALSAAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTALSAAUDIO, IHostAudio);
769 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
770 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
771 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
772 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
773
774 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
775 PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgReq);
776
777 int rc;
778 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
779 rc = alsaCreateStreamIn( pThis, pStreamALSA, pCfgReq, pCfgAcq);
780 else
781 rc = alsaCreateStreamOut(pThis, pStreamALSA, pCfgReq, pCfgAcq);
782 if (RT_SUCCESS(rc))
783 PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgAcq);
784 return rc;
785}
786
787
788/**
789 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
790 */
791static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
792{
793 RT_NOREF(pInterface);
794 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
795 AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
796
797 /** @todo r=bird: It's not like we can do much with a bad status... Check
798 * what the caller does... */
799 return alsaStreamClose(&pStreamALSA->hPCM);
800}
801
802
803/**
804 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
805 */
806static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
807{
808 RT_NOREF(pInterface);
809 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
810
811 /*
812 * Prepare the stream.
813 */
814 int rc = snd_pcm_prepare(pStreamALSA->hPCM);
815 if (rc >= 0)
816 {
817 Assert(snd_pcm_state(pStreamALSA->hPCM) == SND_PCM_STATE_PREPARED);
818
819 /*
820 * Input streams should be started now, whereas output streams must
821 * pre-buffer sufficent data before starting.
822 */
823 if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_IN)
824 {
825 rc = snd_pcm_start(pStreamALSA->hPCM);
826 if (rc >= 0)
827 rc = VINF_SUCCESS;
828 else
829 {
830 LogRel(("ALSA: Error starting input stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
831 rc = RTErrConvertFromErrno(-rc);
832 }
833 }
834 else
835 rc = VINF_SUCCESS;
836 }
837 else
838 {
839 LogRel(("ALSA: Error preparing stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
840 rc = RTErrConvertFromErrno(-rc);
841 }
842 LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
843 return rc;
844}
845
846
847/**
848 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
849 */
850static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
851{
852 RT_NOREF(pInterface);
853 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
854
855 int rc = snd_pcm_drop(pStreamALSA->hPCM);
856 if (rc >= 0)
857 rc = VINF_SUCCESS;
858 else
859 {
860 LogRel(("ALSA: Error stopping stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
861 rc = RTErrConvertFromErrno(-rc);
862 }
863 LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
864 return rc;
865}
866
867
868/**
869 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
870 */
871static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
872{
873 /* Same as disable. */
874 /** @todo r=bird: Try use pause and fallback on disable/enable if it isn't
875 * supported or doesn't work. */
876 return drvHostAlsaAudioHA_StreamDisable(pInterface, pStream);
877}
878
879
880/**
881 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
882 */
883static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
884{
885 /* Same as enable. */
886 return drvHostAlsaAudioHA_StreamEnable(pInterface, pStream);
887}
888
889
890/**
891 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
892 */
893static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
894{
895 RT_NOREF(pInterface);
896 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
897
898 snd_pcm_state_t const enmState = snd_pcm_state(pStreamALSA->hPCM);
899 LogFlowFunc(("Stream '%s' input state: %s (%d)\n", pStreamALSA->Cfg.szName, snd_pcm_state_name(enmState), enmState));
900
901 /* Only for output streams. */
902 AssertReturn(pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER);
903
904 int rc;
905 switch (enmState)
906 {
907 case SND_PCM_STATE_RUNNING:
908 case SND_PCM_STATE_PREPARED: /* not yet started */
909 {
910#if 0 /** @todo r=bird: You want EMT to block here for potentially 200-300ms worth
911 * of buffer to be drained? That's a certifiably bad idea. */
912 int rc2 = snd_pcm_nonblock(pStreamALSA->hPCM, 0);
913 AssertMsg(rc2 >= 0, ("snd_pcm_nonblock(, 0) -> %d\n", rc2));
914#endif
915 rc = snd_pcm_drain(pStreamALSA->hPCM);
916 if (rc >= 0 || rc == -EAGAIN)
917 rc = VINF_SUCCESS;
918 else
919 {
920 LogRel(("ALSA: Error draining output of '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
921 rc = RTErrConvertFromErrno(-rc);
922 }
923#if 0
924 rc2 = snd_pcm_nonblock(pStreamALSA->hPCM, 1);
925 AssertMsg(rc2 >= 0, ("snd_pcm_nonblock(, 1) -> %d\n", rc2));
926#endif
927 break;
928 }
929
930 default:
931 rc = VINF_SUCCESS;
932 break;
933 }
934 LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
935 return rc;
936}
937
938
939/**
940 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
941 */
942static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface,
943 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
944{
945 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
946 * replacing it with individual StreamXxxx methods. That would save us
947 * potentally huge switches and more easily see which drivers implement
948 * which operations (grep for pfnStreamXxxx). */
949 switch (enmStreamCmd)
950 {
951 case PDMAUDIOSTREAMCMD_ENABLE:
952 return drvHostAlsaAudioHA_StreamEnable(pInterface, pStream);
953 case PDMAUDIOSTREAMCMD_DISABLE:
954 return drvHostAlsaAudioHA_StreamDisable(pInterface, pStream);
955 case PDMAUDIOSTREAMCMD_PAUSE:
956 return drvHostAlsaAudioHA_StreamPause(pInterface, pStream);
957 case PDMAUDIOSTREAMCMD_RESUME:
958 return drvHostAlsaAudioHA_StreamResume(pInterface, pStream);
959 case PDMAUDIOSTREAMCMD_DRAIN:
960 return drvHostAlsaAudioHA_StreamDrain(pInterface, pStream);
961
962 case PDMAUDIOSTREAMCMD_END:
963 case PDMAUDIOSTREAMCMD_32BIT_HACK:
964 case PDMAUDIOSTREAMCMD_INVALID:
965 /* no default*/
966 break;
967 }
968 return VERR_NOT_SUPPORTED;
969}
970
971
972/**
973 * Returns the available audio frames queued.
974 *
975 * @returns VBox status code.
976 * @param hPCM ALSA stream handle.
977 * @param pcFramesAvail Where to store the available frames.
978 */
979static int alsaStreamGetAvail(snd_pcm_t *hPCM, snd_pcm_sframes_t *pcFramesAvail)
980{
981 AssertPtr(hPCM);
982 AssertPtr(pcFramesAvail);
983
984 int rc;
985 snd_pcm_sframes_t cFramesAvail = snd_pcm_avail_update(hPCM);
986 if (cFramesAvail > 0)
987 {
988 LogFunc(("cFramesAvail=%ld\n", cFramesAvail));
989 *pcFramesAvail = cFramesAvail;
990 return VINF_SUCCESS;
991 }
992
993 /*
994 * We can maybe recover from an EPIPE...
995 */
996 if (cFramesAvail == -EPIPE)
997 {
998 rc = alsaStreamRecover(hPCM);
999 if (RT_SUCCESS(rc))
1000 {
1001 cFramesAvail = snd_pcm_avail_update(hPCM);
1002 if (cFramesAvail >= 0)
1003 {
1004 LogFunc(("cFramesAvail=%ld\n", cFramesAvail));
1005 *pcFramesAvail = cFramesAvail;
1006 return VINF_SUCCESS;
1007 }
1008 }
1009 else
1010 {
1011 *pcFramesAvail = 0;
1012 return rc;
1013 }
1014 }
1015
1016 rc = RTErrConvertFromErrno(-(int)cFramesAvail);
1017 LogFunc(("failed - cFramesAvail=%ld rc=%Rrc\n", cFramesAvail, rc));
1018 *pcFramesAvail = 0;
1019 return rc;
1020}
1021
1022
1023/**
1024 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1025 */
1026static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1027{
1028 RT_NOREF(pInterface);
1029 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1030
1031 uint32_t cbAvail = 0;
1032 snd_pcm_sframes_t cFramesAvail = 0;
1033 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1034 if (RT_SUCCESS(rc))
1035 cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail);
1036
1037 return cbAvail;
1038}
1039
1040
1041/**
1042 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1043 */
1044static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1045{
1046 RT_NOREF(pInterface);
1047 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1048
1049 uint32_t cbAvail = 0;
1050 snd_pcm_sframes_t cFramesAvail = 0;
1051 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1052 if (RT_SUCCESS(rc))
1053 cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail);
1054
1055 return cbAvail;
1056}
1057
1058
1059/**
1060 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
1061 */
1062static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1063{
1064 RT_NOREF(pInterface);
1065 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1066 AssertPtrReturn(pStreamALSA, 0);
1067
1068 /*
1069 * This is only relevant to output streams (input streams can't have
1070 * any pending, unplayed data).
1071 */
1072 uint32_t cbPending = 0;
1073 if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT)
1074 {
1075 /*
1076 * Getting the delay (in audio frames) reports the time it will take
1077 * to hear a new sample after all queued samples have been played out.
1078 *
1079 * We use snd_pcm_avail_delay instead of snd_pcm_delay here as it will
1080 * update the buffer positions, and we can use the extra value against
1081 * the buffer size to double check since the delay value may include
1082 * fixed built-in delays in the processing chain and hardware.
1083 */
1084 snd_pcm_sframes_t cFramesAvail = 0;
1085 snd_pcm_sframes_t cFramesDelay = 0;
1086 int rc = snd_pcm_avail_delay(pStreamALSA->hPCM, &cFramesAvail, &cFramesDelay);
1087
1088 /*
1089 * We now also get the state as the pending value should be zero when
1090 * we're not in a playing state.
1091 */
1092 snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM);
1093 switch (enmState)
1094 {
1095 case SND_PCM_STATE_RUNNING:
1096 case SND_PCM_STATE_DRAINING:
1097 if (rc >= 0)
1098 {
1099 if ((uint32_t)cFramesAvail >= pStreamALSA->Cfg.Backend.cFramesBufferSize)
1100 cbPending = 0;
1101 else
1102 cbPending = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesDelay);
1103 }
1104 break;
1105
1106 default:
1107 break;
1108 }
1109 Log2Func(("returns %u (%#x) - cFramesBufferSize=%RU32 cFramesAvail=%ld cFramesDelay=%ld rc=%d; enmState=%s (%d) \n",
1110 cbPending, cbPending, pStreamALSA->Cfg.Backend.cFramesBufferSize, cFramesAvail, cFramesDelay, rc,
1111 snd_pcm_state_name(enmState), enmState));
1112 }
1113 return cbPending;
1114}
1115
1116
1117/**
1118 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
1119 */
1120static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAlsaAudioHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
1121 PPDMAUDIOBACKENDSTREAM pStream)
1122{
1123 RT_NOREF(pInterface);
1124 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1125 AssertPtrReturn(pStreamALSA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
1126
1127 PDMHOSTAUDIOSTREAMSTATE enmStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
1128 snd_pcm_state_t enmAlsaState = snd_pcm_state(pStreamALSA->hPCM);
1129 if (enmAlsaState == SND_PCM_STATE_DRAINING)
1130 enmStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
1131#if (((SND_LIB_MAJOR) << 16) | ((SND_LIB_MAJOR) << 8) | (SND_LIB_SUBMINOR)) >= 0x10002 /* was added in 1.0.2 */
1132 else if (enmAlsaState == SND_PCM_STATE_DISCONNECTED)
1133 enmStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
1134#endif
1135
1136 Log5Func(("Stream '%s': ALSA state=%s -> %s\n",
1137 pStreamALSA->Cfg.szName, snd_pcm_state_name(enmAlsaState), PDMHostAudioStreamStateGetName(enmStreamState) ));
1138 return enmStreamState;
1139}
1140
1141
1142/**
1143 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1144 */
1145static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1146 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1147{
1148 RT_NOREF_PV(pInterface);
1149 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1150 AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
1151 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1152 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1153 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
1154 Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
1155 snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
1156
1157 /*
1158 * Figure out how much we can read without trouble (we're doing
1159 * non-blocking reads, but whatever).
1160 */
1161 snd_pcm_sframes_t cAvail;
1162 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cAvail);
1163 if (RT_SUCCESS(rc))
1164 {
1165 if (!cAvail) /* No data yet? */
1166 {
1167 snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM);
1168 switch (enmState)
1169 {
1170 case SND_PCM_STATE_PREPARED:
1171 /** @todo r=bird: explain the logic here... */
1172 cAvail = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbBuf);
1173 break;
1174
1175 case SND_PCM_STATE_SUSPENDED:
1176 rc = alsaStreamResume(pStreamALSA->hPCM);
1177 if (RT_SUCCESS(rc))
1178 {
1179 LogFlowFunc(("Resumed suspended input stream.\n"));
1180 break;
1181 }
1182 LogFunc(("Failed resuming suspended input stream: %Rrc\n", rc));
1183 return rc;
1184
1185 default:
1186 LogFlow(("No frames available: state=%s (%d)\n", snd_pcm_state_name(enmState), enmState));
1187 break;
1188 }
1189 if (!cAvail)
1190 {
1191 *pcbRead = 0;
1192 return VINF_SUCCESS;
1193 }
1194 }
1195 }
1196 else
1197 {
1198 LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc));
1199 return rc;
1200 }
1201
1202 size_t cbToRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cAvail);
1203 cbToRead = RT_MIN(cbToRead, cbBuf);
1204 LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail));
1205
1206 /*
1207 * Read loop.
1208 */
1209 uint32_t cbReadTotal = 0;
1210 while (cbToRead > 0)
1211 {
1212 /*
1213 * Do the reading.
1214 */
1215 snd_pcm_uframes_t const cFramesToRead = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbToRead);
1216 AssertBreakStmt(cFramesToRead > 0, rc = VERR_NO_DATA);
1217
1218 snd_pcm_sframes_t cFramesRead = snd_pcm_readi(pStreamALSA->hPCM, pvBuf, cFramesToRead);
1219 if (cFramesRead > 0)
1220 {
1221 /*
1222 * We should not run into a full mixer buffer or we lose samples and
1223 * run into an endless loop if ALSA keeps producing samples ("null"
1224 * capture device for example).
1225 */
1226 uint32_t const cbRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesRead);
1227 Assert(cbRead <= cbToRead);
1228
1229 cbToRead -= cbRead;
1230 cbReadTotal += cbRead;
1231 pvBuf = (uint8_t *)pvBuf + cbRead;
1232 pStreamALSA->offInternal += cbRead;
1233 }
1234 else
1235 {
1236 /*
1237 * Try recover from overrun and re-try.
1238 * Other conditions/errors we cannot and will just quit the loop.
1239 */
1240 if (cFramesRead == -EPIPE)
1241 {
1242 rc = alsaStreamRecover(pStreamALSA->hPCM);
1243 if (RT_SUCCESS(rc))
1244 {
1245 LogFlowFunc(("Successfully recovered from overrun\n"));
1246 continue;
1247 }
1248 LogFunc(("Failed to recover from overrun: %Rrc\n", rc));
1249 }
1250 else if (cFramesRead == -EAGAIN)
1251 LogFunc(("No input frames available (EAGAIN)\n"));
1252 else if (cFramesRead == 0)
1253 LogFunc(("No input frames available (0)\n"));
1254 else
1255 {
1256 rc = RTErrConvertFromErrno(-(int)cFramesRead);
1257 LogFunc(("Failed to read input frames: %s (%ld, %Rrc)\n", snd_strerror(cFramesRead), cFramesRead, rc));
1258 }
1259
1260 /* If we've read anything, suppress the error. */
1261 if (RT_FAILURE(rc) && cbReadTotal > 0)
1262 {
1263 LogFunc(("Suppressing %Rrc because %#x bytes has been read already\n", rc, cbReadTotal));
1264 rc = VINF_SUCCESS;
1265 }
1266 break;
1267 }
1268 }
1269
1270 LogFlowFunc(("returns %Rrc and %#x (%d) bytes (%u bytes left); state %s\n",
1271 rc, cbReadTotal, cbReadTotal, cbToRead, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
1272 *pcbRead = cbReadTotal;
1273 return rc;
1274}
1275
1276/**
1277 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1278 */
1279static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1280 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1281{
1282 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1283 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1284 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1285 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
1286 Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
1287 snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
1288 if (cbBuf)
1289 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1290 else
1291 {
1292 /* Fend off draining calls. */
1293 *pcbWritten = 0;
1294 return VINF_SUCCESS;
1295 }
1296
1297 /*
1298 * Determine how much we can write (caller actually did this
1299 * already, but we repeat it just to be sure or something).
1300 */
1301 snd_pcm_sframes_t cFramesAvail;
1302 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1303 if (RT_SUCCESS(rc))
1304 {
1305 Assert(cFramesAvail);
1306 if (cFramesAvail)
1307 {
1308 PCPDMAUDIOPCMPROPS pProps = &pStreamALSA->Cfg.Props;
1309 uint32_t cbToWrite = PDMAudioPropsFramesToBytes(pProps, (uint32_t)cFramesAvail);
1310 if (cbToWrite)
1311 {
1312 if (cbToWrite > cbBuf)
1313 cbToWrite = cbBuf;
1314
1315 /*
1316 * Try write the data.
1317 */
1318 uint32_t cFramesToWrite = PDMAudioPropsBytesToFrames(pProps, cbToWrite);
1319 snd_pcm_sframes_t cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
1320 if (cFramesWritten > 0)
1321 {
1322 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
1323 cbToWrite, cFramesWritten, cFramesAvail));
1324 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
1325 pStreamALSA->offInternal += *pcbWritten;
1326 return VINF_SUCCESS;
1327 }
1328 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld]\n", cbToWrite, cFramesWritten, cFramesAvail));
1329
1330
1331 /*
1332 * There are a couple of error we can recover from, try to do so.
1333 * Only don't try too many times.
1334 */
1335 for (unsigned iTry = 0;
1336 (cFramesWritten == -EPIPE || cFramesWritten == -ESTRPIPE) && iTry < ALSA_RECOVERY_TRIES_MAX;
1337 iTry++)
1338 {
1339 if (cFramesWritten == -EPIPE)
1340 {
1341 /* Underrun occurred. */
1342 rc = alsaStreamRecover(pStreamALSA->hPCM);
1343 if (RT_FAILURE(rc))
1344 break;
1345 LogFlowFunc(("Recovered from playback (iTry=%u)\n", iTry));
1346 }
1347 else
1348 {
1349 /* An suspended event occurred, needs resuming. */
1350 rc = alsaStreamResume(pStreamALSA->hPCM);
1351 if (RT_FAILURE(rc))
1352 {
1353 LogRel(("ALSA: Failed to resume output stream (iTry=%u, rc=%Rrc)\n", iTry, rc));
1354 break;
1355 }
1356 LogFlowFunc(("Resumed suspended output stream (iTry=%u)\n", iTry));
1357 }
1358
1359 cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
1360 if (cFramesWritten > 0)
1361 {
1362 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
1363 cbToWrite, cFramesWritten, cFramesAvail));
1364 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
1365 pStreamALSA->offInternal += *pcbWritten;
1366 return VINF_SUCCESS;
1367 }
1368 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld, iTry=%d]\n", cbToWrite, cFramesWritten, cFramesAvail, iTry));
1369 }
1370
1371 /* Make sure we return with an error status. */
1372 if (RT_SUCCESS_NP(rc))
1373 {
1374 if (cFramesWritten == 0)
1375 rc = VERR_ACCESS_DENIED;
1376 else
1377 {
1378 rc = RTErrConvertFromErrno(-(int)cFramesWritten);
1379 LogFunc(("Failed to write %RU32 bytes: %ld (%Rrc)\n", cbToWrite, cFramesWritten, rc));
1380 }
1381 }
1382 }
1383 }
1384 }
1385 else
1386 LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc));
1387 *pcbWritten = 0;
1388 return rc;
1389}
1390
1391
1392/**
1393 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1394 */
1395static DECLCALLBACK(void *) drvHostAlsaAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1396{
1397 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1398 PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
1399 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1400 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1401
1402 return NULL;
1403}
1404
1405
1406/**
1407 * Construct a DirectSound Audio driver instance.
1408 *
1409 * @copydoc FNPDMDRVCONSTRUCT
1410 */
1411static DECLCALLBACK(int) drvHostAlsaAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1412{
1413 RT_NOREF(fFlags);
1414 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1415 PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
1416 LogRel(("Audio: Initializing ALSA driver\n"));
1417
1418 /*
1419 * Init the static parts.
1420 */
1421 pThis->pDrvIns = pDrvIns;
1422 /* IBase */
1423 pDrvIns->IBase.pfnQueryInterface = drvHostAlsaAudioQueryInterface;
1424 /* IHostAudio */
1425 pThis->IHostAudio.pfnGetConfig = drvHostAlsaAudioHA_GetConfig;
1426 pThis->IHostAudio.pfnGetDevices = drvHostAlsaAudioHA_GetDevices;
1427 pThis->IHostAudio.pfnGetStatus = drvHostAlsaAudioHA_GetStatus;
1428 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
1429 pThis->IHostAudio.pfnStreamConfigHint = NULL;
1430 pThis->IHostAudio.pfnStreamCreate = drvHostAlsaAudioHA_StreamCreate;
1431 pThis->IHostAudio.pfnStreamInitAsync = NULL;
1432 pThis->IHostAudio.pfnStreamDestroy = drvHostAlsaAudioHA_StreamDestroy;
1433 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
1434 pThis->IHostAudio.pfnStreamControl = drvHostAlsaAudioHA_StreamControl;
1435 pThis->IHostAudio.pfnStreamGetReadable = drvHostAlsaAudioHA_StreamGetReadable;
1436 pThis->IHostAudio.pfnStreamGetWritable = drvHostAlsaAudioHA_StreamGetWritable;
1437 pThis->IHostAudio.pfnStreamGetPending = drvHostAlsaAudioHA_StreamGetPending;
1438 pThis->IHostAudio.pfnStreamGetState = drvHostAlsaAudioHA_StreamGetState;
1439 pThis->IHostAudio.pfnStreamPlay = drvHostAlsaAudioHA_StreamPlay;
1440 pThis->IHostAudio.pfnStreamCapture = drvHostAlsaAudioHA_StreamCapture;
1441
1442 /*
1443 * Read configuration.
1444 */
1445 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "DefaultOutput|DefaultInput", "");
1446
1447 int rc = CFGMR3QueryStringDef(pCfg, "DefaultInput", pThis->szDefaultIn, sizeof(pThis->szDefaultIn), "default");
1448 AssertRCReturn(rc, rc);
1449 rc = CFGMR3QueryStringDef(pCfg, "DefaultOutput", pThis->szDefaultOut, sizeof(pThis->szDefaultOut), "default");
1450 AssertRCReturn(rc, rc);
1451
1452 /*
1453 * Init the alsa library.
1454 */
1455 rc = audioLoadAlsaLib();
1456 if (RT_FAILURE(rc))
1457 {
1458 LogRel(("ALSA: Failed to load the ALSA shared library: %Rrc\n", rc));
1459 return rc;
1460 }
1461#ifdef DEBUG
1462 snd_lib_error_set_handler(alsaDbgErrorHandler);
1463#endif
1464 return VINF_SUCCESS;
1465}
1466
1467
1468/**
1469 * ALSA audio driver registration record.
1470 */
1471const PDMDRVREG g_DrvHostALSAAudio =
1472{
1473 /* u32Version */
1474 PDM_DRVREG_VERSION,
1475 /* szName */
1476 "ALSAAudio",
1477 /* szRCMod */
1478 "",
1479 /* szR0Mod */
1480 "",
1481 /* pszDescription */
1482 "ALSA host audio driver",
1483 /* fFlags */
1484 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1485 /* fClass. */
1486 PDM_DRVREG_CLASS_AUDIO,
1487 /* cMaxInstances */
1488 ~0U,
1489 /* cbInstance */
1490 sizeof(DRVHOSTALSAAUDIO),
1491 /* pfnConstruct */
1492 drvHostAlsaAudioConstruct,
1493 /* pfnDestruct */
1494 NULL,
1495 /* pfnRelocate */
1496 NULL,
1497 /* pfnIOCtl */
1498 NULL,
1499 /* pfnPowerOn */
1500 NULL,
1501 /* pfnReset */
1502 NULL,
1503 /* pfnSuspend */
1504 NULL,
1505 /* pfnResume */
1506 NULL,
1507 /* pfnAttach */
1508 NULL,
1509 /* pfnDetach */
1510 NULL,
1511 /* pfnPowerOff */
1512 NULL,
1513 /* pfnSoftReset */
1514 NULL,
1515 /* u32EndVersion */
1516 PDM_DRVREG_VERSION
1517};
1518
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