VirtualBox

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

Last change on this file since 88994 was 88991, checked in by vboxsync, 4 years ago

Audio: Worked over draining, starting with the internal DMA buffer (instead of just the pre-buffer and backend buffer) and using the async I/O thread to keep calling PDMIAUDIOCONNECTOR::pfnStreamIterate and PDMIHOSTAUDIO::pfnStreamPlay (NULL buffer) every so often till the draining is done. Also put a rough deadline on the draining. The PDMAUDIOSTREAMCMD_DISABLE is now defined to stop playback/capturing immediately, even when already draining (if possible). This gets rid of the timers in DrvAudio and windows backends. DrvAudio no longer issue an DISABLE command at the end of the drain, it assumes the backend does that internally itself. After issuing PDMAUDIOSTREAMCMD_DRAIN the client (be it mixer or drvaudio) will not provide any more data for the buffers via pfnStreamPlay. Only tested windows, needs to re-test all platforms. bugref:9890

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