VirtualBox

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

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

Audio: Added an fImmediate indicator to the pfnStreamDestroy methods so the backend knows whether it's okay to continue draining the stream or if it must be destroyed without delay. The latter is typically only for shutdown and driver plumbing. This helps quite a bit for HDA/CoreAudio/knoppix. 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 89213 2021-05-21 10:00:12Z 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 bool fImmediate)
793{
794 RT_NOREF(pInterface);
795 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
796 AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
797 RT_NOREF(fImmediate);
798
799 /** @todo r=bird: It's not like we can do much with a bad status... Check
800 * what the caller does... */
801 return alsaStreamClose(&pStreamALSA->hPCM);
802}
803
804
805/**
806 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
807 */
808static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
809{
810 RT_NOREF(pInterface);
811 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
812
813 /*
814 * Prepare the stream.
815 */
816 int rc = snd_pcm_prepare(pStreamALSA->hPCM);
817 if (rc >= 0)
818 {
819 Assert(snd_pcm_state(pStreamALSA->hPCM) == SND_PCM_STATE_PREPARED);
820
821 /*
822 * Input streams should be started now, whereas output streams must
823 * pre-buffer sufficent data before starting.
824 */
825 if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_IN)
826 {
827 rc = snd_pcm_start(pStreamALSA->hPCM);
828 if (rc >= 0)
829 rc = VINF_SUCCESS;
830 else
831 {
832 LogRel(("ALSA: Error starting input stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
833 rc = RTErrConvertFromErrno(-rc);
834 }
835 }
836 else
837 rc = VINF_SUCCESS;
838 }
839 else
840 {
841 LogRel(("ALSA: Error preparing stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
842 rc = RTErrConvertFromErrno(-rc);
843 }
844 LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
845 return rc;
846}
847
848
849/**
850 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
851 */
852static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
853{
854 RT_NOREF(pInterface);
855 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
856
857 int rc = snd_pcm_drop(pStreamALSA->hPCM);
858 if (rc >= 0)
859 rc = VINF_SUCCESS;
860 else
861 {
862 LogRel(("ALSA: Error stopping stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
863 rc = RTErrConvertFromErrno(-rc);
864 }
865 LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
866 return rc;
867}
868
869
870/**
871 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
872 */
873static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
874{
875 /* Same as disable. */
876 /** @todo r=bird: Try use pause and fallback on disable/enable if it isn't
877 * supported or doesn't work. */
878 return drvHostAlsaAudioHA_StreamDisable(pInterface, pStream);
879}
880
881
882/**
883 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
884 */
885static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
886{
887 /* Same as enable. */
888 return drvHostAlsaAudioHA_StreamEnable(pInterface, pStream);
889}
890
891
892/**
893 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
894 */
895static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
896{
897 RT_NOREF(pInterface);
898 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
899
900 snd_pcm_state_t const enmState = snd_pcm_state(pStreamALSA->hPCM);
901 LogFlowFunc(("Stream '%s' input state: %s (%d)\n", pStreamALSA->Cfg.szName, snd_pcm_state_name(enmState), enmState));
902
903 /* Only for output streams. */
904 AssertReturn(pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER);
905
906 int rc;
907 switch (enmState)
908 {
909 case SND_PCM_STATE_RUNNING:
910 case SND_PCM_STATE_PREPARED: /* not yet started */
911 {
912#if 0 /** @todo r=bird: You want EMT to block here for potentially 200-300ms worth
913 * of buffer to be drained? That's a certifiably bad idea. */
914 int rc2 = snd_pcm_nonblock(pStreamALSA->hPCM, 0);
915 AssertMsg(rc2 >= 0, ("snd_pcm_nonblock(, 0) -> %d\n", rc2));
916#endif
917 rc = snd_pcm_drain(pStreamALSA->hPCM);
918 if (rc >= 0 || rc == -EAGAIN)
919 rc = VINF_SUCCESS;
920 else
921 {
922 LogRel(("ALSA: Error draining output of '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
923 rc = RTErrConvertFromErrno(-rc);
924 }
925#if 0
926 rc2 = snd_pcm_nonblock(pStreamALSA->hPCM, 1);
927 AssertMsg(rc2 >= 0, ("snd_pcm_nonblock(, 1) -> %d\n", rc2));
928#endif
929 break;
930 }
931
932 default:
933 rc = VINF_SUCCESS;
934 break;
935 }
936 LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
937 return rc;
938}
939
940
941/**
942 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
943 */
944static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface,
945 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
946{
947 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
948 * replacing it with individual StreamXxxx methods. That would save us
949 * potentally huge switches and more easily see which drivers implement
950 * which operations (grep for pfnStreamXxxx). */
951 switch (enmStreamCmd)
952 {
953 case PDMAUDIOSTREAMCMD_ENABLE:
954 return drvHostAlsaAudioHA_StreamEnable(pInterface, pStream);
955 case PDMAUDIOSTREAMCMD_DISABLE:
956 return drvHostAlsaAudioHA_StreamDisable(pInterface, pStream);
957 case PDMAUDIOSTREAMCMD_PAUSE:
958 return drvHostAlsaAudioHA_StreamPause(pInterface, pStream);
959 case PDMAUDIOSTREAMCMD_RESUME:
960 return drvHostAlsaAudioHA_StreamResume(pInterface, pStream);
961 case PDMAUDIOSTREAMCMD_DRAIN:
962 return drvHostAlsaAudioHA_StreamDrain(pInterface, pStream);
963
964 case PDMAUDIOSTREAMCMD_END:
965 case PDMAUDIOSTREAMCMD_32BIT_HACK:
966 case PDMAUDIOSTREAMCMD_INVALID:
967 /* no default*/
968 break;
969 }
970 return VERR_NOT_SUPPORTED;
971}
972
973
974/**
975 * Returns the available audio frames queued.
976 *
977 * @returns VBox status code.
978 * @param hPCM ALSA stream handle.
979 * @param pcFramesAvail Where to store the available frames.
980 */
981static int alsaStreamGetAvail(snd_pcm_t *hPCM, snd_pcm_sframes_t *pcFramesAvail)
982{
983 AssertPtr(hPCM);
984 AssertPtr(pcFramesAvail);
985
986 int rc;
987 snd_pcm_sframes_t cFramesAvail = snd_pcm_avail_update(hPCM);
988 if (cFramesAvail > 0)
989 {
990 LogFunc(("cFramesAvail=%ld\n", cFramesAvail));
991 *pcFramesAvail = cFramesAvail;
992 return VINF_SUCCESS;
993 }
994
995 /*
996 * We can maybe recover from an EPIPE...
997 */
998 if (cFramesAvail == -EPIPE)
999 {
1000 rc = alsaStreamRecover(hPCM);
1001 if (RT_SUCCESS(rc))
1002 {
1003 cFramesAvail = snd_pcm_avail_update(hPCM);
1004 if (cFramesAvail >= 0)
1005 {
1006 LogFunc(("cFramesAvail=%ld\n", cFramesAvail));
1007 *pcFramesAvail = cFramesAvail;
1008 return VINF_SUCCESS;
1009 }
1010 }
1011 else
1012 {
1013 *pcFramesAvail = 0;
1014 return rc;
1015 }
1016 }
1017
1018 rc = RTErrConvertFromErrno(-(int)cFramesAvail);
1019 LogFunc(("failed - cFramesAvail=%ld rc=%Rrc\n", cFramesAvail, rc));
1020 *pcFramesAvail = 0;
1021 return rc;
1022}
1023
1024
1025/**
1026 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1027 */
1028static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1029{
1030 RT_NOREF(pInterface);
1031 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1032
1033 uint32_t cbAvail = 0;
1034 snd_pcm_sframes_t cFramesAvail = 0;
1035 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1036 if (RT_SUCCESS(rc))
1037 cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail);
1038
1039 return cbAvail;
1040}
1041
1042
1043/**
1044 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1045 */
1046static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1047{
1048 RT_NOREF(pInterface);
1049 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1050
1051 uint32_t cbAvail = 0;
1052 snd_pcm_sframes_t cFramesAvail = 0;
1053 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1054 if (RT_SUCCESS(rc))
1055 cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail);
1056
1057 return cbAvail;
1058}
1059
1060
1061/**
1062 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
1063 */
1064static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1065{
1066 RT_NOREF(pInterface);
1067 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1068 AssertPtrReturn(pStreamALSA, 0);
1069
1070 /*
1071 * This is only relevant to output streams (input streams can't have
1072 * any pending, unplayed data).
1073 */
1074 uint32_t cbPending = 0;
1075 if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT)
1076 {
1077 /*
1078 * Getting the delay (in audio frames) reports the time it will take
1079 * to hear a new sample after all queued samples have been played out.
1080 *
1081 * We use snd_pcm_avail_delay instead of snd_pcm_delay here as it will
1082 * update the buffer positions, and we can use the extra value against
1083 * the buffer size to double check since the delay value may include
1084 * fixed built-in delays in the processing chain and hardware.
1085 */
1086 snd_pcm_sframes_t cFramesAvail = 0;
1087 snd_pcm_sframes_t cFramesDelay = 0;
1088 int rc = snd_pcm_avail_delay(pStreamALSA->hPCM, &cFramesAvail, &cFramesDelay);
1089
1090 /*
1091 * We now also get the state as the pending value should be zero when
1092 * we're not in a playing state.
1093 */
1094 snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM);
1095 switch (enmState)
1096 {
1097 case SND_PCM_STATE_RUNNING:
1098 case SND_PCM_STATE_DRAINING:
1099 if (rc >= 0)
1100 {
1101 if ((uint32_t)cFramesAvail >= pStreamALSA->Cfg.Backend.cFramesBufferSize)
1102 cbPending = 0;
1103 else
1104 cbPending = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesDelay);
1105 }
1106 break;
1107
1108 default:
1109 break;
1110 }
1111 Log2Func(("returns %u (%#x) - cFramesBufferSize=%RU32 cFramesAvail=%ld cFramesDelay=%ld rc=%d; enmState=%s (%d) \n",
1112 cbPending, cbPending, pStreamALSA->Cfg.Backend.cFramesBufferSize, cFramesAvail, cFramesDelay, rc,
1113 snd_pcm_state_name(enmState), enmState));
1114 }
1115 return cbPending;
1116}
1117
1118
1119/**
1120 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
1121 */
1122static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAlsaAudioHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
1123 PPDMAUDIOBACKENDSTREAM pStream)
1124{
1125 RT_NOREF(pInterface);
1126 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1127 AssertPtrReturn(pStreamALSA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
1128
1129 PDMHOSTAUDIOSTREAMSTATE enmStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
1130 snd_pcm_state_t enmAlsaState = snd_pcm_state(pStreamALSA->hPCM);
1131 if (enmAlsaState == SND_PCM_STATE_DRAINING)
1132 enmStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
1133#if (((SND_LIB_MAJOR) << 16) | ((SND_LIB_MAJOR) << 8) | (SND_LIB_SUBMINOR)) >= 0x10002 /* was added in 1.0.2 */
1134 else if (enmAlsaState == SND_PCM_STATE_DISCONNECTED)
1135 enmStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
1136#endif
1137
1138 Log5Func(("Stream '%s': ALSA state=%s -> %s\n",
1139 pStreamALSA->Cfg.szName, snd_pcm_state_name(enmAlsaState), PDMHostAudioStreamStateGetName(enmStreamState) ));
1140 return enmStreamState;
1141}
1142
1143
1144/**
1145 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1146 */
1147static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1148 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1149{
1150 RT_NOREF_PV(pInterface);
1151 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1152 AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
1153 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1154 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1155 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
1156 Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
1157 snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
1158
1159 /*
1160 * Figure out how much we can read without trouble (we're doing
1161 * non-blocking reads, but whatever).
1162 */
1163 snd_pcm_sframes_t cAvail;
1164 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cAvail);
1165 if (RT_SUCCESS(rc))
1166 {
1167 if (!cAvail) /* No data yet? */
1168 {
1169 snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM);
1170 switch (enmState)
1171 {
1172 case SND_PCM_STATE_PREPARED:
1173 /** @todo r=bird: explain the logic here... */
1174 cAvail = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbBuf);
1175 break;
1176
1177 case SND_PCM_STATE_SUSPENDED:
1178 rc = alsaStreamResume(pStreamALSA->hPCM);
1179 if (RT_SUCCESS(rc))
1180 {
1181 LogFlowFunc(("Resumed suspended input stream.\n"));
1182 break;
1183 }
1184 LogFunc(("Failed resuming suspended input stream: %Rrc\n", rc));
1185 return rc;
1186
1187 default:
1188 LogFlow(("No frames available: state=%s (%d)\n", snd_pcm_state_name(enmState), enmState));
1189 break;
1190 }
1191 if (!cAvail)
1192 {
1193 *pcbRead = 0;
1194 return VINF_SUCCESS;
1195 }
1196 }
1197 }
1198 else
1199 {
1200 LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc));
1201 return rc;
1202 }
1203
1204 size_t cbToRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cAvail);
1205 cbToRead = RT_MIN(cbToRead, cbBuf);
1206 LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail));
1207
1208 /*
1209 * Read loop.
1210 */
1211 uint32_t cbReadTotal = 0;
1212 while (cbToRead > 0)
1213 {
1214 /*
1215 * Do the reading.
1216 */
1217 snd_pcm_uframes_t const cFramesToRead = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbToRead);
1218 AssertBreakStmt(cFramesToRead > 0, rc = VERR_NO_DATA);
1219
1220 snd_pcm_sframes_t cFramesRead = snd_pcm_readi(pStreamALSA->hPCM, pvBuf, cFramesToRead);
1221 if (cFramesRead > 0)
1222 {
1223 /*
1224 * We should not run into a full mixer buffer or we lose samples and
1225 * run into an endless loop if ALSA keeps producing samples ("null"
1226 * capture device for example).
1227 */
1228 uint32_t const cbRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesRead);
1229 Assert(cbRead <= cbToRead);
1230
1231 cbToRead -= cbRead;
1232 cbReadTotal += cbRead;
1233 pvBuf = (uint8_t *)pvBuf + cbRead;
1234 pStreamALSA->offInternal += cbRead;
1235 }
1236 else
1237 {
1238 /*
1239 * Try recover from overrun and re-try.
1240 * Other conditions/errors we cannot and will just quit the loop.
1241 */
1242 if (cFramesRead == -EPIPE)
1243 {
1244 rc = alsaStreamRecover(pStreamALSA->hPCM);
1245 if (RT_SUCCESS(rc))
1246 {
1247 LogFlowFunc(("Successfully recovered from overrun\n"));
1248 continue;
1249 }
1250 LogFunc(("Failed to recover from overrun: %Rrc\n", rc));
1251 }
1252 else if (cFramesRead == -EAGAIN)
1253 LogFunc(("No input frames available (EAGAIN)\n"));
1254 else if (cFramesRead == 0)
1255 LogFunc(("No input frames available (0)\n"));
1256 else
1257 {
1258 rc = RTErrConvertFromErrno(-(int)cFramesRead);
1259 LogFunc(("Failed to read input frames: %s (%ld, %Rrc)\n", snd_strerror(cFramesRead), cFramesRead, rc));
1260 }
1261
1262 /* If we've read anything, suppress the error. */
1263 if (RT_FAILURE(rc) && cbReadTotal > 0)
1264 {
1265 LogFunc(("Suppressing %Rrc because %#x bytes has been read already\n", rc, cbReadTotal));
1266 rc = VINF_SUCCESS;
1267 }
1268 break;
1269 }
1270 }
1271
1272 LogFlowFunc(("returns %Rrc and %#x (%d) bytes (%u bytes left); state %s\n",
1273 rc, cbReadTotal, cbReadTotal, cbToRead, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
1274 *pcbRead = cbReadTotal;
1275 return rc;
1276}
1277
1278/**
1279 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1280 */
1281static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1282 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1283{
1284 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1285 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1286 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1287 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
1288 Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
1289 snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
1290 if (cbBuf)
1291 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1292 else
1293 {
1294 /* Fend off draining calls. */
1295 *pcbWritten = 0;
1296 return VINF_SUCCESS;
1297 }
1298
1299 /*
1300 * Determine how much we can write (caller actually did this
1301 * already, but we repeat it just to be sure or something).
1302 */
1303 snd_pcm_sframes_t cFramesAvail;
1304 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1305 if (RT_SUCCESS(rc))
1306 {
1307 Assert(cFramesAvail);
1308 if (cFramesAvail)
1309 {
1310 PCPDMAUDIOPCMPROPS pProps = &pStreamALSA->Cfg.Props;
1311 uint32_t cbToWrite = PDMAudioPropsFramesToBytes(pProps, (uint32_t)cFramesAvail);
1312 if (cbToWrite)
1313 {
1314 if (cbToWrite > cbBuf)
1315 cbToWrite = cbBuf;
1316
1317 /*
1318 * Try write the data.
1319 */
1320 uint32_t cFramesToWrite = PDMAudioPropsBytesToFrames(pProps, cbToWrite);
1321 snd_pcm_sframes_t cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
1322 if (cFramesWritten > 0)
1323 {
1324 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
1325 cbToWrite, cFramesWritten, cFramesAvail));
1326 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
1327 pStreamALSA->offInternal += *pcbWritten;
1328 return VINF_SUCCESS;
1329 }
1330 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld]\n", cbToWrite, cFramesWritten, cFramesAvail));
1331
1332
1333 /*
1334 * There are a couple of error we can recover from, try to do so.
1335 * Only don't try too many times.
1336 */
1337 for (unsigned iTry = 0;
1338 (cFramesWritten == -EPIPE || cFramesWritten == -ESTRPIPE) && iTry < ALSA_RECOVERY_TRIES_MAX;
1339 iTry++)
1340 {
1341 if (cFramesWritten == -EPIPE)
1342 {
1343 /* Underrun occurred. */
1344 rc = alsaStreamRecover(pStreamALSA->hPCM);
1345 if (RT_FAILURE(rc))
1346 break;
1347 LogFlowFunc(("Recovered from playback (iTry=%u)\n", iTry));
1348 }
1349 else
1350 {
1351 /* An suspended event occurred, needs resuming. */
1352 rc = alsaStreamResume(pStreamALSA->hPCM);
1353 if (RT_FAILURE(rc))
1354 {
1355 LogRel(("ALSA: Failed to resume output stream (iTry=%u, rc=%Rrc)\n", iTry, rc));
1356 break;
1357 }
1358 LogFlowFunc(("Resumed suspended output stream (iTry=%u)\n", iTry));
1359 }
1360
1361 cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
1362 if (cFramesWritten > 0)
1363 {
1364 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
1365 cbToWrite, cFramesWritten, cFramesAvail));
1366 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
1367 pStreamALSA->offInternal += *pcbWritten;
1368 return VINF_SUCCESS;
1369 }
1370 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld, iTry=%d]\n", cbToWrite, cFramesWritten, cFramesAvail, iTry));
1371 }
1372
1373 /* Make sure we return with an error status. */
1374 if (RT_SUCCESS_NP(rc))
1375 {
1376 if (cFramesWritten == 0)
1377 rc = VERR_ACCESS_DENIED;
1378 else
1379 {
1380 rc = RTErrConvertFromErrno(-(int)cFramesWritten);
1381 LogFunc(("Failed to write %RU32 bytes: %ld (%Rrc)\n", cbToWrite, cFramesWritten, rc));
1382 }
1383 }
1384 }
1385 }
1386 }
1387 else
1388 LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc));
1389 *pcbWritten = 0;
1390 return rc;
1391}
1392
1393
1394/**
1395 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1396 */
1397static DECLCALLBACK(void *) drvHostAlsaAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1398{
1399 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1400 PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
1401 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1402 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1403
1404 return NULL;
1405}
1406
1407
1408/**
1409 * Construct a DirectSound Audio driver instance.
1410 *
1411 * @copydoc FNPDMDRVCONSTRUCT
1412 */
1413static DECLCALLBACK(int) drvHostAlsaAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1414{
1415 RT_NOREF(fFlags);
1416 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1417 PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
1418 LogRel(("Audio: Initializing ALSA driver\n"));
1419
1420 /*
1421 * Init the static parts.
1422 */
1423 pThis->pDrvIns = pDrvIns;
1424 /* IBase */
1425 pDrvIns->IBase.pfnQueryInterface = drvHostAlsaAudioQueryInterface;
1426 /* IHostAudio */
1427 pThis->IHostAudio.pfnGetConfig = drvHostAlsaAudioHA_GetConfig;
1428 pThis->IHostAudio.pfnGetDevices = drvHostAlsaAudioHA_GetDevices;
1429 pThis->IHostAudio.pfnGetStatus = drvHostAlsaAudioHA_GetStatus;
1430 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
1431 pThis->IHostAudio.pfnStreamConfigHint = NULL;
1432 pThis->IHostAudio.pfnStreamCreate = drvHostAlsaAudioHA_StreamCreate;
1433 pThis->IHostAudio.pfnStreamInitAsync = NULL;
1434 pThis->IHostAudio.pfnStreamDestroy = drvHostAlsaAudioHA_StreamDestroy;
1435 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
1436 pThis->IHostAudio.pfnStreamControl = drvHostAlsaAudioHA_StreamControl;
1437 pThis->IHostAudio.pfnStreamGetReadable = drvHostAlsaAudioHA_StreamGetReadable;
1438 pThis->IHostAudio.pfnStreamGetWritable = drvHostAlsaAudioHA_StreamGetWritable;
1439 pThis->IHostAudio.pfnStreamGetPending = drvHostAlsaAudioHA_StreamGetPending;
1440 pThis->IHostAudio.pfnStreamGetState = drvHostAlsaAudioHA_StreamGetState;
1441 pThis->IHostAudio.pfnStreamPlay = drvHostAlsaAudioHA_StreamPlay;
1442 pThis->IHostAudio.pfnStreamCapture = drvHostAlsaAudioHA_StreamCapture;
1443
1444 /*
1445 * Read configuration.
1446 */
1447 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "DefaultOutput|DefaultInput", "");
1448
1449 int rc = CFGMR3QueryStringDef(pCfg, "DefaultInput", pThis->szDefaultIn, sizeof(pThis->szDefaultIn), "default");
1450 AssertRCReturn(rc, rc);
1451 rc = CFGMR3QueryStringDef(pCfg, "DefaultOutput", pThis->szDefaultOut, sizeof(pThis->szDefaultOut), "default");
1452 AssertRCReturn(rc, rc);
1453
1454 /*
1455 * Init the alsa library.
1456 */
1457 rc = audioLoadAlsaLib();
1458 if (RT_FAILURE(rc))
1459 {
1460 LogRel(("ALSA: Failed to load the ALSA shared library: %Rrc\n", rc));
1461 return rc;
1462 }
1463#ifdef DEBUG
1464 snd_lib_error_set_handler(alsaDbgErrorHandler);
1465#endif
1466 return VINF_SUCCESS;
1467}
1468
1469
1470/**
1471 * ALSA audio driver registration record.
1472 */
1473const PDMDRVREG g_DrvHostALSAAudio =
1474{
1475 /* u32Version */
1476 PDM_DRVREG_VERSION,
1477 /* szName */
1478 "ALSAAudio",
1479 /* szRCMod */
1480 "",
1481 /* szR0Mod */
1482 "",
1483 /* pszDescription */
1484 "ALSA host audio driver",
1485 /* fFlags */
1486 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1487 /* fClass. */
1488 PDM_DRVREG_CLASS_AUDIO,
1489 /* cMaxInstances */
1490 ~0U,
1491 /* cbInstance */
1492 sizeof(DRVHOSTALSAAUDIO),
1493 /* pfnConstruct */
1494 drvHostAlsaAudioConstruct,
1495 /* pfnDestruct */
1496 NULL,
1497 /* pfnRelocate */
1498 NULL,
1499 /* pfnIOCtl */
1500 NULL,
1501 /* pfnPowerOn */
1502 NULL,
1503 /* pfnReset */
1504 NULL,
1505 /* pfnSuspend */
1506 NULL,
1507 /* pfnResume */
1508 NULL,
1509 /* pfnAttach */
1510 NULL,
1511 /* pfnDetach */
1512 NULL,
1513 /* pfnPowerOff */
1514 NULL,
1515 /* pfnSoftReset */
1516 NULL,
1517 /* u32EndVersion */
1518 PDM_DRVREG_VERSION
1519};
1520
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