VirtualBox

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

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

DrvHostAudioAlsa: Kicked out the scratch buffer as I could find no reason for having it, other than limiting write sizes. Straightened out the capture code. bugref:9890

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