VirtualBox

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

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

Audio: Removed the pfnInit and pfnShutdown methods from PDMIHOSTAUDIO. These methods duplicates PDMDRVREG callbacks and were therefore superfluous. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 51.7 KB
Line 
1/* $Id: DrvHostAudioAlsa.cpp 88390 2021-04-07 10:35:06Z 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,pfnStreamCapture}
572 */
573static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
574 void *pvBuf, uint32_t uBufSize, uint32_t *puRead)
575{
576 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
577 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
578 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
579 AssertReturn(uBufSize, VERR_INVALID_PARAMETER);
580 /* pcbRead is optional. */
581
582 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
583
584 snd_pcm_sframes_t cAvail;
585 int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cAvail);
586 if (RT_FAILURE(rc))
587 {
588 LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc));
589 return rc;
590 }
591
592 PPDMAUDIOSTREAMCFG pCfg = pStreamALSA->pCfg;
593 AssertPtr(pCfg);
594
595 if (!cAvail) /* No data yet? */
596 {
597 snd_pcm_state_t state = snd_pcm_state(pStreamALSA->phPCM);
598 switch (state)
599 {
600 case SND_PCM_STATE_PREPARED:
601 cAvail = PDMAUDIOSTREAMCFG_B2F(pCfg, uBufSize);
602 break;
603
604 case SND_PCM_STATE_SUSPENDED:
605 {
606 rc = alsaStreamResume(pStreamALSA->phPCM);
607 if (RT_FAILURE(rc))
608 break;
609
610 LogFlow(("Resuming suspended input stream\n"));
611 break;
612 }
613
614 default:
615 LogFlow(("No frames available, state=%d\n", state));
616 break;
617 }
618
619 if (!cAvail)
620 {
621 if (puRead)
622 *puRead = 0;
623 return VINF_SUCCESS;
624 }
625 }
626
627 /*
628 * Check how much we can read from the capture device without overflowing
629 * the mixer buffer.
630 */
631 size_t cbToRead = RT_MIN((size_t)PDMAUDIOSTREAMCFG_F2B(pCfg, cAvail), uBufSize);
632
633 LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail));
634
635 uint32_t cbReadTotal = 0;
636
637 snd_pcm_uframes_t cToRead;
638 snd_pcm_sframes_t cRead;
639
640 while ( cbToRead
641 && RT_SUCCESS(rc))
642 {
643 cToRead = RT_MIN(PDMAUDIOSTREAMCFG_B2F(pCfg, cbToRead),
644 PDMAUDIOSTREAMCFG_B2F(pCfg, pStreamALSA->cbBuf));
645 AssertBreakStmt(cToRead, rc = VERR_NO_DATA);
646 cRead = snd_pcm_readi(pStreamALSA->phPCM, pStreamALSA->pvBuf, cToRead);
647 if (cRead <= 0)
648 {
649 switch (cRead)
650 {
651 case 0:
652 {
653 LogFunc(("No input frames available\n"));
654 rc = VERR_ACCESS_DENIED;
655 break;
656 }
657
658 case -EAGAIN:
659 {
660 /*
661 * Don't set error here because EAGAIN means there are no further frames
662 * available at the moment, try later. As we might have read some frames
663 * already these need to be processed instead.
664 */
665 cbToRead = 0;
666 break;
667 }
668
669 case -EPIPE:
670 {
671 rc = alsaStreamRecover(pStreamALSA->phPCM);
672 if (RT_FAILURE(rc))
673 break;
674
675 LogFlowFunc(("Recovered from capturing\n"));
676 continue;
677 }
678
679 default:
680 {
681 LogFunc(("Failed to read input frames: %s\n", snd_strerror(cRead)));
682 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
683 break;
684 }
685 }
686 }
687 else
688 {
689 /*
690 * We should not run into a full mixer buffer or we loose samples and
691 * run into an endless loop if ALSA keeps producing samples ("null"
692 * capture device for example).
693 */
694 uint32_t cbRead = PDMAUDIOSTREAMCFG_F2B(pCfg, cRead);
695
696 memcpy(pvBuf, pStreamALSA->pvBuf, cbRead);
697
698 Assert(cbToRead >= cbRead);
699 cbToRead -= cbRead;
700 cbReadTotal += cbRead;
701 }
702 }
703
704 if (RT_SUCCESS(rc))
705 {
706 if (puRead)
707 *puRead = cbReadTotal;
708 }
709
710 return rc;
711}
712
713/**
714 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
715 */
716static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
717 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
718{
719 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
720 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
721 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
722 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
723 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
724 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
725 Log4Func(("pvBuf=%p uBufSize=%#x (%u) state=%s - %s\n", pvBuf, cbBuf, cbBuf,
726 snd_pcm_state_name(snd_pcm_state(pStreamALSA->phPCM)), pStreamALSA->pCfg->szName));
727
728 /*
729 * Determine how much we can write (caller actually did this
730 * already, but we repeat it just to be sure or something).
731 */
732 snd_pcm_sframes_t cFramesAvail;
733 int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cFramesAvail);
734 if (RT_SUCCESS(rc))
735 {
736 Assert(cFramesAvail);
737 if (cFramesAvail)
738 {
739 PCPDMAUDIOPCMPROPS pProps = &pStreamALSA->pCfg->Props;
740 uint32_t cbToWrite = PDMAudioPropsFramesToBytes(pProps, (uint32_t)cFramesAvail);
741 cbToWrite = RT_MIN(cbToWrite, (uint32_t)pStreamALSA->cbBuf);
742 if (cbToWrite)
743 {
744 if (cbToWrite > cbBuf)
745 cbToWrite = cbBuf;
746
747 /*
748 * Now we copy the stuff into our scratch buffer for some
749 * totally unexplained reason.
750 */
751 memcpy(pStreamALSA->pvBuf, pvBuf, cbToWrite);
752
753 /*
754 * Try write the data.
755 */
756 uint32_t cFramesToWrite = PDMAudioPropsBytesToFrames(pProps, cbToWrite);
757 snd_pcm_sframes_t cFramesWritten = snd_pcm_writei(pStreamALSA->phPCM, pStreamALSA->pvBuf, cFramesToWrite);
758 if (cFramesWritten > 0)
759 {
760 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
761 cbToWrite, cFramesWritten, cFramesAvail));
762 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
763 return VINF_SUCCESS;
764 }
765 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld]\n", cbToWrite, cFramesWritten, cFramesAvail));
766
767
768 /*
769 * There are a couple of error we can recover from, try to do so.
770 * Only don't try too many times.
771 */
772 for (unsigned iTry = 0;
773 (cFramesWritten == -EPIPE || cFramesWritten == -ESTRPIPE) && iTry < ALSA_RECOVERY_TRIES_MAX;
774 iTry++)
775 {
776 if (cFramesWritten == -EPIPE)
777 {
778 /* Underrun occurred. */
779 rc = alsaStreamRecover(pStreamALSA->phPCM);
780 if (RT_FAILURE(rc))
781 break;
782 LogFlowFunc(("Recovered from playback (iTry=%u)\n", iTry));
783 }
784 else
785 {
786 /* An suspended event occurred, needs resuming. */
787 rc = alsaStreamResume(pStreamALSA->phPCM);
788 if (RT_FAILURE(rc))
789 {
790 LogRel(("ALSA: Failed to resume output stream (iTry=%u, rc=%Rrc)\n", iTry, rc));
791 break;
792 }
793 LogFlowFunc(("Resumed suspended output stream (iTry=%u)\n", iTry));
794 }
795
796 cFramesWritten = snd_pcm_writei(pStreamALSA->phPCM, pStreamALSA->pvBuf, cFramesToWrite);
797 if (cFramesWritten > 0)
798 {
799 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
800 cbToWrite, cFramesWritten, cFramesAvail));
801 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
802 return VINF_SUCCESS;
803 }
804 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld, iTry=%d]\n", cbToWrite, cFramesWritten, cFramesAvail, iTry));
805 }
806
807 /* Make sure we return with an error status. */
808 if (RT_SUCCESS_NP(rc))
809 {
810 if (cFramesWritten == 0)
811 rc = VERR_ACCESS_DENIED;
812 else
813 {
814 rc = RTErrConvertFromErrno(-cFramesWritten);
815 LogFunc(("Failed to write %RU32 bytes: %ld (%Rrc)\n", cbToWrite, cFramesWritten, rc));
816 }
817 }
818 }
819 }
820 }
821 else
822 LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc));
823 *pcbWritten = 0;
824 return rc;
825}
826
827/**
828 * Destroys an ALSA input stream.
829 *
830 * @returns VBox status code.
831 * @param pStreamALSA ALSA input stream to destroy.
832 */
833static int alsaDestroyStreamIn(PALSAAUDIOSTREAM pStreamALSA)
834{
835 alsaStreamClose(&pStreamALSA->phPCM);
836
837 if (pStreamALSA->pvBuf)
838 {
839 RTMemFree(pStreamALSA->pvBuf);
840 pStreamALSA->pvBuf = NULL;
841 }
842
843 return VINF_SUCCESS;
844}
845
846/**
847 * Destroys an ALSA output stream.
848 *
849 * @returns VBox status code.
850 * @param pStreamALSA ALSA output stream to destroy.
851 */
852static int alsaDestroyStreamOut(PALSAAUDIOSTREAM pStreamALSA)
853{
854 alsaStreamClose(&pStreamALSA->phPCM);
855
856 if (pStreamALSA->pvBuf)
857 {
858 RTMemFree(pStreamALSA->pvBuf);
859 pStreamALSA->pvBuf = NULL;
860 }
861
862 return VINF_SUCCESS;
863}
864
865/**
866 * Creates an ALSA output stream.
867 *
868 * @returns VBox status code.
869 * @param pThis The ALSA driver instance data.
870 * @param pStreamALSA ALSA output stream to create.
871 * @param pCfgReq Requested configuration to create stream with.
872 * @param pCfgAcq Obtained configuration the stream got created
873 * with on success.
874 */
875static int alsaCreateStreamOut(PDRVHOSTALSAAUDIO pThis, PALSAAUDIOSTREAM pStreamALSA,
876 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
877{
878 snd_pcm_t *phPCM = NULL;
879
880 int rc;
881
882 do
883 {
884 ALSAAUDIOSTREAMCFG req;
885 req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props);
886 req.freq = PDMAudioPropsHz(&pCfgReq->Props);
887 req.nchannels = PDMAudioPropsChannels(&pCfgReq->Props);
888 req.period_size = pCfgReq->Backend.cFramesPeriod;
889 req.buffer_size = pCfgReq->Backend.cFramesBufferSize;
890 req.threshold = pCfgReq->Backend.cFramesPreBuffering;
891
892 ALSAAUDIOSTREAMCFG obt;
893 rc = alsaStreamOpen(pThis->szDefaultOut, false /* fIn */, &req, &obt, &phPCM);
894 if (RT_FAILURE(rc))
895 break;
896
897 rc = alsaALSAToAudioProps(&pCfgAcq->Props, obt.fmt, obt.nchannels, obt.freq);
898 if (RT_FAILURE(rc))
899 break;
900
901 pCfgAcq->Backend.cFramesPeriod = obt.period_size;
902 pCfgAcq->Backend.cFramesBufferSize = obt.buffer_size;
903 pCfgAcq->Backend.cFramesPreBuffering = obt.threshold;
904
905 pStreamALSA->cbBuf = pCfgAcq->Backend.cFramesBufferSize * PDMAudioPropsBytesPerFrame(&pCfgAcq->Props);
906 pStreamALSA->pvBuf = RTMemAllocZ(pStreamALSA->cbBuf);
907 if (!pStreamALSA->pvBuf)
908 {
909 LogRel(("ALSA: Not enough memory for output DAC buffer (%zu frames)\n", pCfgAcq->Backend.cFramesBufferSize));
910 rc = VERR_NO_MEMORY;
911 break;
912 }
913
914 pStreamALSA->phPCM = phPCM;
915 }
916 while (0);
917
918 if (RT_FAILURE(rc))
919 alsaStreamClose(&phPCM);
920
921 LogFlowFuncLeaveRC(rc);
922 return rc;
923}
924
925/**
926 * Creates an ALSA input stream.
927 *
928 * @returns VBox status code.
929 * @param pThis The ALSA driver instance data.
930 * @param pStreamALSA ALSA input stream to create.
931 * @param pCfgReq Requested configuration to create stream with.
932 * @param pCfgAcq Obtained configuration the stream got created
933 * with on success.
934 */
935static int alsaCreateStreamIn(PDRVHOSTALSAAUDIO pThis, PALSAAUDIOSTREAM pStreamALSA,
936 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
937{
938 int rc;
939
940 snd_pcm_t *phPCM = NULL;
941
942 do
943 {
944 ALSAAUDIOSTREAMCFG req;
945 req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props);
946 req.freq = PDMAudioPropsHz(&pCfgReq->Props);
947 req.nchannels = PDMAudioPropsChannels(&pCfgReq->Props);
948 req.period_size = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 50 /*ms*/); /** @todo Make this configurable. */
949 req.buffer_size = req.period_size * 2; /** @todo Make this configurable. */
950 req.threshold = req.period_size;
951
952 ALSAAUDIOSTREAMCFG obt;
953 rc = alsaStreamOpen(pThis->szDefaultIn, true /* fIn */, &req, &obt, &phPCM);
954 if (RT_FAILURE(rc))
955 break;
956
957 rc = alsaALSAToAudioProps(&pCfgAcq->Props, obt.fmt, obt.nchannels, obt.freq);
958 if (RT_FAILURE(rc))
959 break;
960
961 pCfgAcq->Backend.cFramesPeriod = obt.period_size;
962 pCfgAcq->Backend.cFramesBufferSize = obt.buffer_size;
963 /* No pre-buffering. */
964
965 pStreamALSA->cbBuf = pCfgAcq->Backend.cFramesBufferSize * PDMAudioPropsBytesPerFrame(&pCfgAcq->Props);
966 pStreamALSA->pvBuf = RTMemAlloc(pStreamALSA->cbBuf);
967 if (!pStreamALSA->pvBuf)
968 {
969 LogRel(("ALSA: Not enough memory for input ADC buffer (%zu frames)\n", pCfgAcq->Backend.cFramesBufferSize));
970 rc = VERR_NO_MEMORY;
971 break;
972 }
973
974 pStreamALSA->phPCM = phPCM;
975 }
976 while (0);
977
978 if (RT_FAILURE(rc))
979 alsaStreamClose(&phPCM);
980
981 LogFlowFuncLeaveRC(rc);
982 return rc;
983}
984
985/**
986 * Controls an ALSA input stream.
987 *
988 * @returns VBox status code.
989 * @param pStreamALSA ALSA input stream to control.
990 * @param enmStreamCmd Stream command to issue.
991 */
992static int alsaControlStreamIn(PALSAAUDIOSTREAM pStreamALSA, PDMAUDIOSTREAMCMD enmStreamCmd)
993{
994 int rc = VINF_SUCCESS;
995
996 int err;
997
998 switch (enmStreamCmd)
999 {
1000 case PDMAUDIOSTREAMCMD_ENABLE:
1001 case PDMAUDIOSTREAMCMD_RESUME:
1002 {
1003 err = snd_pcm_prepare(pStreamALSA->phPCM);
1004 if (err < 0)
1005 {
1006 LogRel(("ALSA: Error preparing input stream: %s\n", snd_strerror(err)));
1007 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1008 }
1009 else
1010 {
1011 Assert(snd_pcm_state(pStreamALSA->phPCM) == SND_PCM_STATE_PREPARED);
1012
1013 /* Only start the PCM stream for input streams. */
1014 err = snd_pcm_start(pStreamALSA->phPCM);
1015 if (err < 0)
1016 {
1017 LogRel(("ALSA: Error starting input stream: %s\n", snd_strerror(err)));
1018 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1019 }
1020 }
1021
1022 break;
1023 }
1024
1025 case PDMAUDIOSTREAMCMD_DISABLE:
1026 {
1027 err = snd_pcm_drop(pStreamALSA->phPCM);
1028 if (err < 0)
1029 {
1030 LogRel(("ALSA: Error disabling input stream: %s\n", snd_strerror(err)));
1031 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1032 }
1033 break;
1034 }
1035
1036 case PDMAUDIOSTREAMCMD_PAUSE:
1037 {
1038 err = snd_pcm_drop(pStreamALSA->phPCM);
1039 if (err < 0)
1040 {
1041 LogRel(("ALSA: Error pausing input stream: %s\n", snd_strerror(err)));
1042 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1043 }
1044 break;
1045 }
1046
1047 default:
1048 rc = VERR_NOT_SUPPORTED;
1049 break;
1050 }
1051
1052 LogFlowFuncLeaveRC(rc);
1053 return rc;
1054}
1055
1056/**
1057 * Controls an ALSA output stream.
1058 *
1059 * @returns VBox status code.
1060 * @param pStreamALSA ALSA output stream to control.
1061 * @param enmStreamCmd Stream command to issue.
1062 */
1063static int alsaControlStreamOut(PALSAAUDIOSTREAM pStreamALSA, PDMAUDIOSTREAMCMD enmStreamCmd)
1064{
1065 int rc = VINF_SUCCESS;
1066
1067 int err;
1068
1069 switch (enmStreamCmd)
1070 {
1071 case PDMAUDIOSTREAMCMD_ENABLE:
1072 case PDMAUDIOSTREAMCMD_RESUME:
1073 {
1074 err = snd_pcm_prepare(pStreamALSA->phPCM);
1075 if (err < 0)
1076 {
1077 LogRel(("ALSA: Error preparing output stream: %s\n", snd_strerror(err)));
1078 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1079 }
1080 else
1081 {
1082 Assert(snd_pcm_state(pStreamALSA->phPCM) == SND_PCM_STATE_PREPARED);
1083 }
1084
1085 break;
1086 }
1087
1088 case PDMAUDIOSTREAMCMD_DISABLE:
1089 {
1090 err = snd_pcm_drop(pStreamALSA->phPCM);
1091 if (err < 0)
1092 {
1093 LogRel(("ALSA: Error disabling output stream: %s\n", snd_strerror(err)));
1094 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1095 }
1096 break;
1097 }
1098
1099 case PDMAUDIOSTREAMCMD_PAUSE:
1100 {
1101 err = snd_pcm_drop(pStreamALSA->phPCM);
1102 if (err < 0)
1103 {
1104 LogRel(("ALSA: Error pausing output stream: %s\n", snd_strerror(err)));
1105 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1106 }
1107 break;
1108 }
1109
1110 case PDMAUDIOSTREAMCMD_DRAIN:
1111 {
1112 snd_pcm_state_t streamState = snd_pcm_state(pStreamALSA->phPCM);
1113 Log2Func(("Stream state is: %d\n", streamState));
1114
1115 if ( streamState == SND_PCM_STATE_PREPARED
1116 || streamState == SND_PCM_STATE_RUNNING)
1117 {
1118 err = snd_pcm_nonblock(pStreamALSA->phPCM, 0);
1119 if (err < 0)
1120 {
1121 LogRel(("ALSA: Error disabling output non-blocking mode: %s\n", snd_strerror(err)));
1122 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1123 break;
1124 }
1125
1126 err = snd_pcm_drain(pStreamALSA->phPCM);
1127 if (err < 0)
1128 {
1129 LogRel(("ALSA: Error draining output: %s\n", snd_strerror(err)));
1130 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1131 break;
1132 }
1133
1134 err = snd_pcm_nonblock(pStreamALSA->phPCM, 1);
1135 if (err < 0)
1136 {
1137 LogRel(("ALSA: Error re-enabling output non-blocking mode: %s\n", snd_strerror(err)));
1138 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1139 }
1140 }
1141 break;
1142 }
1143
1144 default:
1145 rc = VERR_NOT_SUPPORTED;
1146 break;
1147 }
1148
1149 LogFlowFuncLeaveRC(rc);
1150 return rc;
1151}
1152
1153
1154/**
1155 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
1156 */
1157static DECLCALLBACK(int) drvHostAlsaAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
1158{
1159 RT_NOREF(pInterface);
1160 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
1161
1162 RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "ALSA");
1163
1164 pBackendCfg->cbStreamIn = sizeof(ALSAAUDIOSTREAM);
1165 pBackendCfg->cbStreamOut = sizeof(ALSAAUDIOSTREAM);
1166
1167 /* Enumerate sound devices. */
1168 char **pszHints;
1169 int err = snd_device_name_hint(-1 /* All cards */, "pcm", (void***)&pszHints);
1170 if (err == 0)
1171 {
1172 char** pszHintCur = pszHints;
1173 while (*pszHintCur != NULL)
1174 {
1175 char *pszDev = snd_device_name_get_hint(*pszHintCur, "NAME");
1176 bool fSkip = !pszDev
1177 || !RTStrICmp("null", pszDev);
1178 if (fSkip)
1179 {
1180 if (pszDev)
1181 free(pszDev);
1182 pszHintCur++;
1183 continue;
1184 }
1185
1186 char *pszIOID = snd_device_name_get_hint(*pszHintCur, "IOID");
1187 if (pszIOID)
1188 {
1189#if 0
1190 if (!RTStrICmp("input", pszIOID))
1191
1192 else if (!RTStrICmp("output", pszIOID))
1193#endif
1194 }
1195 else /* NULL means bidirectional, input + output. */
1196 {
1197 }
1198
1199 LogRel2(("ALSA: Found %s device: %s\n", pszIOID ? RTStrToLower(pszIOID) : "bidirectional", pszDev));
1200
1201 /* Special case for ALSAAudio. */
1202 if ( pszDev
1203 && RTStrIStr("pulse", pszDev) != NULL)
1204 LogRel2(("ALSA: ALSAAudio plugin in use\n"));
1205
1206 if (pszIOID)
1207 free(pszIOID);
1208
1209 if (pszDev)
1210 free(pszDev);
1211
1212 pszHintCur++;
1213 }
1214
1215 snd_device_name_free_hint((void **)pszHints);
1216 pszHints = NULL;
1217 }
1218 else
1219 LogRel2(("ALSA: Error enumerating PCM devices: %Rrc (%d)\n", RTErrConvertFromErrno(err), err));
1220
1221 /* ALSA allows exactly one input and one output used at a time for the selected device(s). */
1222 pBackendCfg->cMaxStreamsIn = 1;
1223 pBackendCfg->cMaxStreamsOut = 1;
1224
1225 return VINF_SUCCESS;
1226}
1227
1228
1229/**
1230 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1231 */
1232static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAlsaAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1233{
1234 RT_NOREF(enmDir);
1235 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
1236
1237 return PDMAUDIOBACKENDSTS_RUNNING;
1238}
1239
1240
1241/**
1242 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1243 */
1244static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1245 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1246{
1247 PDRVHOSTALSAAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTALSAAUDIO, IHostAudio);
1248 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1249 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1250 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1251 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1252
1253 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1254
1255 int rc;
1256 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1257 rc = alsaCreateStreamIn( pThis, pStreamALSA, pCfgReq, pCfgAcq);
1258 else
1259 rc = alsaCreateStreamOut(pThis, pStreamALSA, pCfgReq, pCfgAcq);
1260
1261 if (RT_SUCCESS(rc))
1262 {
1263 pStreamALSA->pCfg = PDMAudioStrmCfgDup(pCfgAcq);
1264 if (!pStreamALSA->pCfg)
1265 rc = VERR_NO_MEMORY;
1266 }
1267
1268 return rc;
1269}
1270
1271
1272/**
1273 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1274 */
1275static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1276{
1277 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1278 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1279
1280 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1281
1282 if (!pStreamALSA->pCfg) /* Not (yet) configured? Skip. */
1283 return VINF_SUCCESS;
1284
1285 int rc;
1286 if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_IN)
1287 rc = alsaDestroyStreamIn(pStreamALSA);
1288 else
1289 rc = alsaDestroyStreamOut(pStreamALSA);
1290
1291 if (RT_SUCCESS(rc))
1292 {
1293 PDMAudioStrmCfgFree(pStreamALSA->pCfg);
1294 pStreamALSA->pCfg = NULL;
1295 }
1296
1297 return rc;
1298}
1299
1300
1301/**
1302 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
1303 */
1304static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface,
1305 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
1306{
1307 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1308 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1309
1310 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1311
1312 if (!pStreamALSA->pCfg) /* Not (yet) configured? Skip. */
1313 return VINF_SUCCESS;
1314
1315 int rc;
1316 if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_IN)
1317 rc = alsaControlStreamIn (pStreamALSA, enmStreamCmd);
1318 else
1319 rc = alsaControlStreamOut(pStreamALSA, enmStreamCmd);
1320
1321 return rc;
1322}
1323
1324
1325/**
1326 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1327 */
1328static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1329{
1330 RT_NOREF(pInterface);
1331
1332 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1333
1334 uint32_t cbAvail = 0;
1335
1336 snd_pcm_sframes_t cFramesAvail;
1337 int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cFramesAvail);
1338 if (RT_SUCCESS(rc))
1339 cbAvail = PDMAUDIOSTREAMCFG_F2B(pStreamALSA->pCfg, cFramesAvail);
1340
1341 return cbAvail;
1342}
1343
1344
1345/**
1346 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1347 */
1348static DECLCALLBACK(uint32_t) drvHostAlsaAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1349{
1350 RT_NOREF(pInterface);
1351
1352 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1353
1354 uint32_t cbAvail = 0;
1355
1356 snd_pcm_sframes_t cFramesAvail;
1357 int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cFramesAvail);
1358 if (RT_SUCCESS(rc))
1359 cbAvail = PDMAUDIOSTREAMCFG_F2B(pStreamALSA->pCfg, cFramesAvail);
1360
1361 return cbAvail;
1362}
1363
1364
1365/**
1366 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
1367 */
1368static DECLCALLBACK(uint32_t) drvHostALSAStreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1369{
1370 RT_NOREF(pInterface);
1371 AssertPtrReturn(pStream, 0);
1372
1373 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1374
1375 snd_pcm_sframes_t cFramesDelay = 0;
1376 snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->phPCM);
1377
1378 int rc = VINF_SUCCESS;
1379
1380 AssertPtr(pStreamALSA->pCfg);
1381 if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_OUT)
1382 {
1383 /* Getting the delay (in audio frames) reports the time it will take
1384 * to hear a new sample after all queued samples have been played out. */
1385 int rc2 = snd_pcm_delay(pStreamALSA->phPCM, &cFramesDelay);
1386 if (RT_SUCCESS(rc))
1387 rc = rc2;
1388
1389 /* Make sure to check the stream's status.
1390 * If it's anything but SND_PCM_STATE_RUNNING, the delay is meaningless and therefore 0. */
1391 if (enmState != SND_PCM_STATE_RUNNING)
1392 cFramesDelay = 0;
1393 }
1394
1395 /* Note: For input streams we never have pending data left. */
1396
1397 Log2Func(("cFramesDelay=%RI32, enmState=%d, rc=%d\n", cFramesDelay, enmState, rc));
1398
1399 return PDMAudioPropsFramesToBytes(&pStreamALSA->pCfg->Props, cFramesDelay);
1400}
1401
1402
1403/**
1404 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
1405 */
1406static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostAlsaAudioHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1407{
1408 RT_NOREF(pInterface, pStream);
1409
1410 return PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED;
1411}
1412
1413
1414/**
1415 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1416 */
1417static DECLCALLBACK(void *) drvHostAlsaAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1418{
1419 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1420 PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
1421 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1422 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1423
1424 return NULL;
1425}
1426
1427
1428/**
1429 * Construct a DirectSound Audio driver instance.
1430 *
1431 * @copydoc FNPDMDRVCONSTRUCT
1432 */
1433static DECLCALLBACK(int) drvHostAlsaAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1434{
1435 RT_NOREF(fFlags);
1436 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1437 PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
1438 LogRel(("Audio: Initializing ALSA driver\n"));
1439
1440 /*
1441 * Init the static parts.
1442 */
1443 pThis->pDrvIns = pDrvIns;
1444 /* IBase */
1445 pDrvIns->IBase.pfnQueryInterface = drvHostAlsaAudioQueryInterface;
1446 /* IHostAudio */
1447 pThis->IHostAudio.pfnGetConfig = drvHostAlsaAudioHA_GetConfig;
1448 pThis->IHostAudio.pfnGetStatus = drvHostAlsaAudioHA_GetStatus;
1449 pThis->IHostAudio.pfnStreamCreate = drvHostAlsaAudioHA_StreamCreate;
1450 pThis->IHostAudio.pfnStreamDestroy = drvHostAlsaAudioHA_StreamDestroy;
1451 pThis->IHostAudio.pfnStreamControl = drvHostAlsaAudioHA_StreamControl;
1452 pThis->IHostAudio.pfnStreamGetReadable = drvHostAlsaAudioHA_StreamGetReadable;
1453 pThis->IHostAudio.pfnStreamGetWritable = drvHostAlsaAudioHA_StreamGetWritable;
1454 pThis->IHostAudio.pfnStreamGetStatus = drvHostAlsaAudioHA_StreamGetStatus;
1455 pThis->IHostAudio.pfnStreamPlay = drvHostAlsaAudioHA_StreamPlay;
1456 pThis->IHostAudio.pfnStreamCapture = drvHostAlsaAudioHA_StreamCapture;
1457 pThis->IHostAudio.pfnGetDevices = NULL;
1458 pThis->IHostAudio.pfnStreamGetPending = drvHostALSAStreamGetPending;
1459
1460 /*
1461 * Read configuration.
1462 */
1463 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "StreamName|DefaultOutput|DefaultInput", "");
1464
1465 int rc = CFGMR3QueryStringDef(pCfg, "DefaultInput", pThis->szDefaultIn, sizeof(pThis->szDefaultIn), "default");
1466 AssertRCReturn(rc, rc);
1467 rc = CFGMR3QueryStringDef(pCfg, "DefaultOutput", pThis->szDefaultOut, sizeof(pThis->szDefaultOut), "default");
1468 AssertRCReturn(rc, rc);
1469
1470 /*
1471 * Init the alsa library.
1472 */
1473 rc = audioLoadAlsaLib();
1474 if (RT_FAILURE(rc))
1475 {
1476 LogRel(("ALSA: Failed to load the ALSA shared library: %Rrc\n", rc));
1477 return rc;
1478 }
1479#ifdef DEBUG
1480 snd_lib_error_set_handler(alsaDbgErrorHandler);
1481#endif
1482 return VINF_SUCCESS;
1483}
1484
1485
1486/**
1487 * Char driver registration record.
1488 */
1489const PDMDRVREG g_DrvHostALSAAudio =
1490{
1491 /* u32Version */
1492 PDM_DRVREG_VERSION,
1493 /* szName */
1494 "ALSAAudio",
1495 /* szRCMod */
1496 "",
1497 /* szR0Mod */
1498 "",
1499 /* pszDescription */
1500 "ALSA host audio driver",
1501 /* fFlags */
1502 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1503 /* fClass. */
1504 PDM_DRVREG_CLASS_AUDIO,
1505 /* cMaxInstances */
1506 ~0U,
1507 /* cbInstance */
1508 sizeof(DRVHOSTALSAAUDIO),
1509 /* pfnConstruct */
1510 drvHostAlsaAudioConstruct,
1511 /* pfnDestruct */
1512 NULL,
1513 /* pfnRelocate */
1514 NULL,
1515 /* pfnIOCtl */
1516 NULL,
1517 /* pfnPowerOn */
1518 NULL,
1519 /* pfnReset */
1520 NULL,
1521 /* pfnSuspend */
1522 NULL,
1523 /* pfnResume */
1524 NULL,
1525 /* pfnAttach */
1526 NULL,
1527 /* pfnDetach */
1528 NULL,
1529 /* pfnPowerOff */
1530 NULL,
1531 /* pfnSoftReset */
1532 NULL,
1533 /* u32EndVersion */
1534 PDM_DRVREG_VERSION
1535};
1536
Note: See TracBrowser for help on using the repository browser.

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