VirtualBox

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

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

Audio: Use HostAudioWas on windows 10 instead of DSound. Only set the StreamName CFGM value for host audio drivers who needs it (PulseAudio and now WAS). Renamed StreamName to VmName. Added VmUuid to WAS. bugref:9890

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