VirtualBox

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

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

Audio: Added geberuc asynchronous init to DrvAudio for use in WAS (and maybe others). bugref:9890

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