VirtualBox

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

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

DrvHostAudioAlsa: Rewrote the pfnStreamGetPending implementation. Didn't take draining into account nor that the delay may always be non-zero due to fixed delays. bugref:9890

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

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