VirtualBox

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

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

Audio: Removed PDMIHOSTAUDIO::pfnSetCallback (replaced by PDMIAUDIONOTIFYFROMHOST). bugref:9890

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