VirtualBox

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

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

Audio: Added PDMIHOSTAUDIO::pfnSetDevice with implementation for CoreAudio. Added CoreAudio config values OutputDeviceID and InputDeviceID for the same purpose. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 56.0 KB
Line 
1/* $Id: DrvHostAudioAlsa.cpp 89258 2021-05-25 09:58:08Z 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
55#include "DrvHostAudioAlsaStubsMangling.h"
56#include <alsa/asoundlib.h>
57#include <alsa/control.h> /* For device enumeration. */
58#include <alsa/version.h>
59#include "DrvHostAudioAlsaStubs.h"
60
61#include "VBoxDD.h"
62
63
64/*********************************************************************************************************************************
65* Defined Constants And Macros *
66*********************************************************************************************************************************/
67/** Maximum number of tries to recover a broken pipe. */
68#define ALSA_RECOVERY_TRIES_MAX 5
69
70
71/*********************************************************************************************************************************
72* Structures *
73*********************************************************************************************************************************/
74/**
75 * ALSA audio stream configuration.
76 */
77typedef struct ALSAAUDIOSTREAMCFG
78{
79 unsigned int freq;
80 /** PCM sound format. */
81 snd_pcm_format_t fmt;
82#if 0 /* Unused. */
83 /** PCM data access type. */
84 snd_pcm_access_t access;
85 /** Whether resampling should be performed by alsalib or not. */
86 int resample;
87#endif
88 /** Number of audio channels. */
89 int cChannels;
90 /** Buffer size (in audio frames). */
91 unsigned long buffer_size;
92 /** Periods (in audio frames). */
93 unsigned long period_size;
94 /** For playback: Starting to play threshold (in audio frames).
95 * For Capturing: Starting to capture threshold (in audio frames). */
96 unsigned long threshold;
97
98 /* latency = period_size * periods / (rate * bytes_per_frame) */
99} ALSAAUDIOSTREAMCFG;
100/** Pointer to an ALSA audio stream config. */
101typedef ALSAAUDIOSTREAMCFG *PALSAAUDIOSTREAMCFG;
102
103
104/**
105 * ALSA host audio specific stream data.
106 */
107typedef struct ALSAAUDIOSTREAM
108{
109 /** Common part. */
110 PDMAUDIOBACKENDSTREAM Core;
111
112 /** Handle to the ALSA PCM stream. */
113 snd_pcm_t *hPCM;
114 /** Internal stream offset (for debugging). */
115 uint64_t offInternal;
116
117 /** The stream's acquired configuration. */
118 PDMAUDIOSTREAMCFG Cfg;
119 /** The acquired ALSA stream config (same as Cfg). */
120 ALSAAUDIOSTREAMCFG AlsaCfg;
121} ALSAAUDIOSTREAM;
122/** Pointer to the ALSA host audio specific stream data. */
123typedef ALSAAUDIOSTREAM *PALSAAUDIOSTREAM;
124
125
126/**
127 * Host Alsa audio driver instance data.
128 * @implements PDMIAUDIOCONNECTOR
129 */
130typedef struct DRVHOSTALSAAUDIO
131{
132 /** Pointer to the driver instance structure. */
133 PPDMDRVINS pDrvIns;
134 /** Pointer to host audio interface. */
135 PDMIHOSTAUDIO IHostAudio;
136 /** Error count for not flooding the release log.
137 * UINT32_MAX for unlimited logging. */
138 uint32_t cLogErrors;
139 /** Default input device name. */
140 char szDefaultIn[256];
141 /** Default output device name. */
142 char szDefaultOut[256];
143} DRVHOSTALSAAUDIO;
144/** Pointer to the instance data of an ALSA host audio driver. */
145typedef DRVHOSTALSAAUDIO *PDRVHOSTALSAAUDIO;
146
147
148
149/**
150 * Closes an ALSA stream
151 *
152 * @returns VBox status code.
153 * @param phPCM Pointer to the ALSA stream handle to close. Will be set to
154 * NULL.
155 */
156static int alsaStreamClose(snd_pcm_t **phPCM)
157{
158 if (!phPCM || !*phPCM)
159 return VINF_SUCCESS;
160
161 int rc;
162 int rc2 = snd_pcm_close(*phPCM);
163 if (rc2 == 0)
164 {
165 *phPCM = NULL;
166 rc = VINF_SUCCESS;
167 }
168 else
169 {
170 rc = RTErrConvertFromErrno(-rc2);
171 LogRel(("ALSA: Closing PCM descriptor failed: %s (%d, %Rrc)\n", snd_strerror(rc2), rc2, rc));
172 }
173
174 LogFlowFuncLeaveRC(rc);
175 return rc;
176}
177
178
179#ifdef DEBUG
180static void alsaDbgErrorHandler(const char *file, int line, const char *function,
181 int err, const char *fmt, ...)
182{
183 /** @todo Implement me! */
184 RT_NOREF(file, line, function, err, fmt);
185}
186#endif
187
188
189/**
190 * Tries to recover an ALSA stream.
191 *
192 * @returns VBox status code.
193 * @param hPCM ALSA stream handle.
194 */
195static int alsaStreamRecover(snd_pcm_t *hPCM)
196{
197 AssertPtrReturn(hPCM, VERR_INVALID_POINTER);
198
199 int rc = snd_pcm_prepare(hPCM);
200 if (rc >= 0)
201 {
202 LogFlowFunc(("Successfully recovered %p.\n", hPCM));
203 return VINF_SUCCESS;
204 }
205 LogFunc(("Failed to recover stream %p: %s (%d)\n", hPCM, snd_strerror(rc), rc));
206 return RTErrConvertFromErrno(-rc);
207}
208
209
210/**
211 * Resumes an ALSA stream.
212 *
213 * @returns VBox status code.
214 * @param hPCM ALSA stream to resume.
215 */
216static int alsaStreamResume(snd_pcm_t *hPCM)
217{
218 AssertPtrReturn(hPCM, VERR_INVALID_POINTER);
219
220 int rc = snd_pcm_resume(hPCM);
221 if (rc >= 0)
222 {
223 LogFlowFunc(("Successfuly resumed %p.\n", hPCM));
224 return VINF_SUCCESS;
225 }
226 LogFunc(("Failed to resume stream %p: %s (%d)\n", hPCM, snd_strerror(rc), rc));
227 return RTErrConvertFromErrno(-rc);
228}
229
230
231/**
232 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
233 */
234static DECLCALLBACK(int) drvHostAlsaAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
235{
236 RT_NOREF(pInterface);
237 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
238
239 /*
240 * Fill in the config structure.
241 */
242 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "ALSA");
243 pBackendCfg->cbStream = sizeof(ALSAAUDIOSTREAM);
244 pBackendCfg->fFlags = 0;
245 /* ALSA allows exactly one input and one output used at a time for the selected device(s). */
246 pBackendCfg->cMaxStreamsIn = 1;
247 pBackendCfg->cMaxStreamsOut = 1;
248
249 return VINF_SUCCESS;
250}
251
252
253/**
254 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
255 */
256static DECLCALLBACK(int) drvHostAlsaAudioHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
257{
258 RT_NOREF(pInterface);
259 PDMAudioHostEnumInit(pDeviceEnum);
260
261 char **papszHints = NULL;
262 int rc = snd_device_name_hint(-1 /* All cards */, "pcm", (void***)&papszHints);
263 if (rc == 0)
264 {
265 rc = VINF_SUCCESS;
266 for (size_t iHint = 0; papszHints[iHint] != NULL && RT_SUCCESS(rc); iHint++)
267 {
268 /*
269 * Retrieve the available info:
270 */
271 const char * const pszHint = papszHints[iHint];
272 char * const pszDev = snd_device_name_get_hint(pszHint, "NAME");
273 char * const pszInOutId = snd_device_name_get_hint(pszHint, "IOID");
274 char * const pszDesc = snd_device_name_get_hint(pszHint, "DESC");
275
276 if (pszDev && RTStrICmp(pszDev, "null") != 0)
277 {
278 /* Detect and log presence of pulse audio plugin. */
279 if (RTStrIStr("pulse", pszDev) != NULL)
280 LogRel(("ALSA: The ALSAAudio plugin for pulse audio is being used (%s).\n", pszDev));
281
282 /*
283 * Add an entry to the enumeration result.
284 */
285 PPDMAUDIOHOSTDEV pDev = PDMAudioHostDevAlloc(sizeof(*pDev));
286 if (pDev)
287 {
288 pDev->fFlags = PDMAUDIOHOSTDEV_F_NONE;
289 pDev->enmType = PDMAUDIODEVICETYPE_UNKNOWN;
290
291 if (pszInOutId == NULL)
292 {
293 pDev->enmUsage = PDMAUDIODIR_DUPLEX;
294 pDev->cMaxInputChannels = 2;
295 pDev->cMaxOutputChannels = 2;
296 }
297 else if (RTStrICmp(pszInOutId, "Input") == 0)
298 {
299 pDev->enmUsage = PDMAUDIODIR_IN;
300 pDev->cMaxInputChannels = 2;
301 pDev->cMaxOutputChannels = 0;
302 }
303 else
304 {
305 AssertMsg(RTStrICmp(pszInOutId, "Output") == 0, ("%s (%s)\n", pszInOutId, pszHint));
306 pDev->enmUsage = PDMAUDIODIR_OUT;
307 pDev->cMaxInputChannels = 0;
308 pDev->cMaxOutputChannels = 2;
309 }
310
311 int rc2 = RTStrCopy(pDev->szName, sizeof(pDev->szName), pszDev);
312 AssertRC(rc2);
313
314 PDMAudioHostEnumAppend(pDeviceEnum, pDev);
315
316 LogRel2(("ALSA: Device #%u: '%s' enmDir=%s: %s\n", iHint, pszDev,
317 PDMAudioDirGetName(pDev->enmUsage), pszDesc));
318 }
319 else
320 rc = VERR_NO_MEMORY;
321 }
322
323 /*
324 * Clean up.
325 */
326 if (pszInOutId)
327 free(pszInOutId);
328 if (pszDesc)
329 free(pszDesc);
330 if (pszDev)
331 free(pszDev);
332 }
333
334 snd_device_name_free_hint((void **)papszHints);
335
336 if (RT_FAILURE(rc))
337 {
338 PDMAudioHostEnumDelete(pDeviceEnum);
339 PDMAudioHostEnumInit(pDeviceEnum);
340 }
341 }
342 else
343 {
344 int rc2 = RTErrConvertFromErrno(-rc);
345 LogRel2(("ALSA: Error enumerating PCM devices: %Rrc (%d)\n", rc2, rc));
346 rc = rc2;
347 }
348 return rc;
349}
350
351
352/**
353 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
354 */
355static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAlsaAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
356{
357 RT_NOREF(enmDir);
358 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
359
360 return PDMAUDIOBACKENDSTS_RUNNING;
361}
362
363
364/**
365 * Converts internal audio PCM properties to an ALSA PCM format.
366 *
367 * @returns Converted ALSA PCM format.
368 * @param pProps Internal audio PCM configuration to convert.
369 */
370static snd_pcm_format_t alsaAudioPropsToALSA(PPDMAUDIOPCMPROPS pProps)
371{
372 switch (PDMAudioPropsSampleSize(pProps))
373 {
374 case 1:
375 return pProps->fSigned ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8;
376
377 case 2:
378 if (PDMAudioPropsIsLittleEndian(pProps))
379 return pProps->fSigned ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U16_LE;
380 return pProps->fSigned ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_U16_BE;
381
382 case 4:
383 if (PDMAudioPropsIsLittleEndian(pProps))
384 return pProps->fSigned ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_U32_LE;
385 return pProps->fSigned ? SND_PCM_FORMAT_S32_BE : SND_PCM_FORMAT_U32_BE;
386
387 default:
388 AssertMsgFailed(("%RU8 bytes not supported\n", PDMAudioPropsSampleSize(pProps)));
389 return SND_PCM_FORMAT_U8;
390 }
391}
392
393
394/**
395 * Converts an ALSA PCM format to internal PCM properties.
396 *
397 * @returns VBox status code.
398 * @param pProps Where to store the converted PCM properties on success.
399 * @param fmt ALSA PCM format to convert.
400 * @param cChannels Number of channels.
401 * @param uHz Frequency.
402 */
403static int alsaALSAToAudioProps(PPDMAUDIOPCMPROPS pProps, snd_pcm_format_t fmt, int cChannels, unsigned uHz)
404{
405 AssertReturn(cChannels > 0, VERR_INVALID_PARAMETER);
406 AssertReturn(cChannels < 16, VERR_INVALID_PARAMETER);
407 switch (fmt)
408 {
409 case SND_PCM_FORMAT_S8:
410 PDMAudioPropsInit(pProps, 1 /*8-bit*/, true /*signed*/, cChannels, uHz);
411 break;
412
413 case SND_PCM_FORMAT_U8:
414 PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz);
415 break;
416
417 case SND_PCM_FORMAT_S16_LE:
418 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
419 break;
420
421 case SND_PCM_FORMAT_U16_LE:
422 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, false /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
423 break;
424
425 case SND_PCM_FORMAT_S16_BE:
426 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
427 break;
428
429 case SND_PCM_FORMAT_U16_BE:
430 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, false /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
431 break;
432
433 case SND_PCM_FORMAT_S32_LE:
434 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
435 break;
436
437 case SND_PCM_FORMAT_U32_LE:
438 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, false /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
439 break;
440
441 case SND_PCM_FORMAT_S32_BE:
442 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
443 break;
444
445 case SND_PCM_FORMAT_U32_BE:
446 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, false /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
447 break;
448
449 default:
450 AssertMsgFailedReturn(("Format %d not supported\n", fmt), VERR_NOT_SUPPORTED);
451 }
452 return VINF_SUCCESS;
453}
454
455
456/**
457 * Sets the software parameters of an ALSA stream.
458 *
459 * @returns 0 on success, negative errno on failure.
460 * @param hPCM ALSA stream to set software parameters for.
461 * @param fIn Whether this is an input stream or not.
462 * @param pCfgReq Requested configuration to set.
463 * @param pCfgObt Obtained configuration on success. Might differ from requested configuration.
464 */
465static int alsaStreamSetSWParams(snd_pcm_t *hPCM, bool fIn, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt)
466{
467 if (fIn) /* For input streams there's nothing to do in here right now. */
468 return VINF_SUCCESS;
469
470 snd_pcm_sw_params_t *pSWParms = NULL;
471 snd_pcm_sw_params_alloca(&pSWParms);
472 AssertReturn(pSWParms, -ENOMEM);
473
474 int err = snd_pcm_sw_params_current(hPCM, pSWParms);
475 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to get current software parameters: %s\n", snd_strerror(err)), err);
476
477 /* Under normal circumstance, we don't need to set a playback threshold
478 because DrvAudio will do the pre-buffering and hand us everything in
479 one continuous chunk when we should start playing. But since it is
480 configurable, we'll set a reasonable minimum of two DMA periods or
481 max 64 milliseconds (the pCfgReq->threshold value).
482
483 Of course we also have to make sure the threshold is below the buffer
484 size, or ALSA will never start playing. */
485 unsigned long cFramesThreshold = RT_MIN(pCfgObt->period_size * 2, pCfgReq->threshold);
486 if (cFramesThreshold >= pCfgObt->buffer_size - pCfgObt->buffer_size / 16)
487 cFramesThreshold = pCfgObt->buffer_size - pCfgObt->buffer_size / 16;
488
489 err = snd_pcm_sw_params_set_start_threshold(hPCM, pSWParms, cFramesThreshold);
490 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set software threshold to %lu: %s\n", cFramesThreshold, snd_strerror(err)), err);
491
492 err = snd_pcm_sw_params_set_avail_min(hPCM, pSWParms, pCfgReq->period_size);
493 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set available minimum to %lu: %s\n", pCfgReq->period_size, snd_strerror(err)), err);
494
495 /* Commit the software parameters: */
496 err = snd_pcm_sw_params(hPCM, pSWParms);
497 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set new software parameters: %s\n", snd_strerror(err)), err);
498
499 /* Get the actual parameters: */
500 err = snd_pcm_sw_params_get_start_threshold(pSWParms, &pCfgObt->threshold);
501 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to get start threshold: %s\n", snd_strerror(err)), err);
502
503 LogRel2(("ALSA: SW params: %ul frames threshold, %ul frame avail minimum\n",
504 pCfgObt->threshold, pCfgReq->period_size));
505 return 0;
506}
507
508
509/**
510 * Sets the hardware parameters of an ALSA stream.
511 *
512 * @returns 0 on success, negative errno on failure.
513 * @param hPCM ALSA stream to set software parameters for.
514 * @param pCfgReq Requested configuration to set.
515 * @param pCfgObt Obtained configuration on success. Might differ from
516 * requested configuration.
517 */
518static int alsaStreamSetHwParams(snd_pcm_t *hPCM, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt)
519{
520 /*
521 * Get the current hardware parameters.
522 */
523 snd_pcm_hw_params_t *pHWParms = NULL;
524 snd_pcm_hw_params_alloca(&pHWParms);
525 AssertReturn(pHWParms, -ENOMEM);
526
527 int err = snd_pcm_hw_params_any(hPCM, pHWParms);
528 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to initialize hardware parameters: %s\n", snd_strerror(err)), err);
529
530 /*
531 * Modify them according to pCfgReq.
532 * We update pCfgObt as we go for parameters set by "near" methods.
533 */
534 /* We'll use snd_pcm_writei/snd_pcm_readi: */
535 err = snd_pcm_hw_params_set_access(hPCM, pHWParms, SND_PCM_ACCESS_RW_INTERLEAVED);
536 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set access type: %s\n", snd_strerror(err)), err);
537
538 /* Set the format, frequency and channel count. */
539 err = snd_pcm_hw_params_set_format(hPCM, pHWParms, pCfgReq->fmt);
540 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set audio format to %d: %s\n", pCfgReq->fmt, snd_strerror(err)), err);
541
542 unsigned int uFreq = pCfgReq->freq;
543 err = snd_pcm_hw_params_set_rate_near(hPCM, pHWParms, &uFreq, NULL /*dir*/);
544 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set frequency to %uHz: %s\n", pCfgReq->freq, snd_strerror(err)), err);
545 pCfgObt->freq = uFreq;
546
547 unsigned int cChannels = pCfgReq->cChannels;
548 err = snd_pcm_hw_params_set_channels_near(hPCM, pHWParms, &cChannels);
549 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set number of channels to %d\n", pCfgReq->cChannels), err);
550 AssertLogRelMsgReturn(cChannels == 1 || cChannels == 2, ("ALSA: Number of audio channels (%u) not supported\n", cChannels), -1);
551 pCfgObt->cChannels = cChannels;
552
553 /* The period size (reportedly frame count per hw interrupt): */
554 int dir = 0;
555 snd_pcm_uframes_t minval = pCfgReq->period_size;
556 err = snd_pcm_hw_params_get_period_size_min(pHWParms, &minval, &dir);
557 AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not determine minimal period size: %s\n", snd_strerror(err)), err);
558
559 snd_pcm_uframes_t period_size_f = pCfgReq->period_size;
560 if (period_size_f < minval)
561 period_size_f = minval;
562 err = snd_pcm_hw_params_set_period_size_near(hPCM, pHWParms, &period_size_f, 0);
563 LogRel2(("ALSA: Period size is: %lu frames (min %lu, requested %lu)\n", period_size_f, minval, pCfgReq->period_size));
564 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set period size %d (%s)\n", period_size_f, snd_strerror(err)), err);
565
566 /* The buffer size: */
567 minval = pCfgReq->buffer_size;
568 err = snd_pcm_hw_params_get_buffer_size_min(pHWParms, &minval);
569 AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not retrieve minimal buffer size: %s\n", snd_strerror(err)), err);
570
571 snd_pcm_uframes_t buffer_size_f = pCfgReq->buffer_size;
572 if (buffer_size_f < minval)
573 buffer_size_f = minval;
574 err = snd_pcm_hw_params_set_buffer_size_near(hPCM, pHWParms, &buffer_size_f);
575 LogRel2(("ALSA: Buffer size is: %lu frames (min %lu, requested %lu)\n", buffer_size_f, minval, pCfgReq->buffer_size));
576 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set near buffer size %RU32: %s\n", buffer_size_f, snd_strerror(err)), err);
577
578 /*
579 * Set the hardware parameters.
580 */
581 err = snd_pcm_hw_params(hPCM, pHWParms);
582 AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to apply audio parameters: %s\n", snd_strerror(err)), err);
583
584 /*
585 * Get relevant parameters and put them in the pCfgObt structure.
586 */
587 snd_pcm_uframes_t obt_buffer_size = buffer_size_f;
588 err = snd_pcm_hw_params_get_buffer_size(pHWParms, &obt_buffer_size);
589 AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get buffer size: %s\n", snd_strerror(err)), obt_buffer_size = buffer_size_f);
590 pCfgObt->buffer_size = obt_buffer_size;
591
592 snd_pcm_uframes_t obt_period_size = period_size_f;
593 err = snd_pcm_hw_params_get_period_size(pHWParms, &obt_period_size, &dir);
594 AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get period size: %s\n", snd_strerror(err)), obt_period_size = period_size_f);
595 pCfgObt->period_size = obt_period_size;
596
597// pCfgObt->access = pCfgReq->access; - unused and uninitialized.
598 pCfgObt->fmt = pCfgReq->fmt;
599
600 LogRel2(("ALSA: HW params: %u Hz, %lu frames period, %lu frames buffer, %u channel(s), fmt=%d, access=%d\n",
601 pCfgObt->freq, pCfgObt->period_size, pCfgObt->buffer_size, pCfgObt->cChannels, pCfgObt->fmt, -1 /*pCfgObt->access*/));
602 return 0;
603}
604
605
606/**
607 * Opens (creates) an ALSA stream.
608 *
609 * @returns VBox status code.
610 * @param pszDev The name of the device to open.
611 * @param fIn Whether this is an input stream to create or not.
612 * @param pCfgReq Requested configuration to create stream with.
613 * @param pCfgObt Obtained configuration the stream got created on success.
614 * @param phPCM Where to store the ALSA stream handle on success.
615 */
616static int alsaStreamOpen(const char *pszDev, bool fIn, PALSAAUDIOSTREAMCFG pCfgReq,
617 PALSAAUDIOSTREAMCFG pCfgObt, snd_pcm_t **phPCM)
618{
619 AssertLogRelMsgReturn(pszDev && *pszDev,
620 ("ALSA: Invalid or no %s device name set\n", fIn ? "input" : "output"),
621 VERR_INVALID_NAME);
622
623 /*
624 * Open the stream.
625 */
626 int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
627 snd_pcm_t *hPCM = NULL;
628 LogRel(("ALSA: Using %s device \"%s\"\n", fIn ? "input" : "output", pszDev));
629 int err = snd_pcm_open(&hPCM, pszDev,
630 fIn ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
631 SND_PCM_NONBLOCK);
632 if (err >= 0)
633 {
634 err = snd_pcm_nonblock(hPCM, 1);
635 if (err >= 0)
636 {
637 /*
638 * Configure hardware stream parameters.
639 */
640 err = alsaStreamSetHwParams(hPCM, pCfgReq, pCfgObt);
641 if (err >= 0)
642 {
643 /*
644 * Prepare it.
645 */
646 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
647 err = snd_pcm_prepare(hPCM);
648 if (err >= 0)
649 {
650 /*
651 * Configure software stream parameters and we're done.
652 */
653 rc = alsaStreamSetSWParams(hPCM, fIn, pCfgReq, pCfgObt);
654 if (RT_SUCCESS(rc))
655 {
656 *phPCM = hPCM;
657 return VINF_SUCCESS;
658 }
659 }
660 else
661 LogRel(("ALSA: snd_pcm_prepare failed: %s\n", snd_strerror(err)));
662 }
663 }
664 else
665 LogRel(("ALSA: Error setting output non-blocking mode: %s\n", snd_strerror(err)));
666 alsaStreamClose(&hPCM);
667 }
668 else
669 LogRel(("ALSA: Failed to open \"%s\" as %s device: %s\n", pszDev, fIn ? "input" : "output", snd_strerror(err)));
670 *phPCM = NULL;
671 return rc;
672}
673
674
675/**
676 * Creates an ALSA output stream.
677 *
678 * @returns VBox status code.
679 * @param pThis The ALSA driver instance data.
680 * @param pStreamALSA ALSA output stream to create.
681 * @param pCfgReq Requested configuration to create stream with.
682 * @param pCfgAcq Obtained configuration the stream got created
683 * with on success.
684 */
685static int alsaCreateStreamOut(PDRVHOSTALSAAUDIO pThis, PALSAAUDIOSTREAM pStreamALSA,
686 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
687{
688 ALSAAUDIOSTREAMCFG Req;
689 Req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props);
690 Req.freq = PDMAudioPropsHz(&pCfgReq->Props);
691 Req.cChannels = PDMAudioPropsChannels(&pCfgReq->Props);
692 Req.period_size = pCfgReq->Backend.cFramesPeriod;
693 Req.buffer_size = pCfgReq->Backend.cFramesBufferSize;
694 Req.threshold = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 50);
695 int rc = alsaStreamOpen(pThis->szDefaultOut, false /*fIn*/, &Req, &pStreamALSA->AlsaCfg, &pStreamALSA->hPCM);
696 if (RT_SUCCESS(rc))
697 {
698 rc = alsaALSAToAudioProps(&pCfgAcq->Props, pStreamALSA->AlsaCfg.fmt,
699 pStreamALSA->AlsaCfg.cChannels, pStreamALSA->AlsaCfg.freq);
700 if (RT_SUCCESS(rc))
701 {
702 pCfgAcq->Backend.cFramesPeriod = pStreamALSA->AlsaCfg.period_size;
703 pCfgAcq->Backend.cFramesBufferSize = pStreamALSA->AlsaCfg.buffer_size;
704
705 /* We have no objections to the pre-buffering that DrvAudio applies,
706 only we need to adjust it relative to the actual buffer size. */
707 /** @todo DrvAudio should do this. */
708 pCfgAcq->Backend.cFramesPreBuffering = (uint64_t)pCfgReq->Backend.cFramesPreBuffering
709 * pCfgAcq->Backend.cFramesBufferSize
710 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
711
712 LogFlowFunc(("returns success - hPCM=%p\n", pStreamALSA->hPCM));
713 return VINF_SUCCESS;
714 }
715 alsaStreamClose(&pStreamALSA->hPCM);
716 }
717 LogFlowFuncLeaveRC(rc);
718 return rc;
719}
720
721
722/**
723 * Creates an ALSA input stream.
724 *
725 * @returns VBox status code.
726 * @param pThis The ALSA driver instance data.
727 * @param pStreamALSA ALSA input stream to create.
728 * @param pCfgReq Requested configuration to create stream with.
729 * @param pCfgAcq Obtained configuration the stream got created
730 * with on success.
731 */
732static int alsaCreateStreamIn(PDRVHOSTALSAAUDIO pThis, PALSAAUDIOSTREAM pStreamALSA,
733 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
734{
735 ALSAAUDIOSTREAMCFG Req;
736 Req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props);
737 Req.freq = PDMAudioPropsHz(&pCfgReq->Props);
738 Req.cChannels = PDMAudioPropsChannels(&pCfgReq->Props);
739/** @todo r=bird: Isn't all this configurable already?!? */
740 Req.period_size = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 50 /*ms*/); /** @todo Make this configurable. */
741 Req.buffer_size = Req.period_size * 2; /** @todo Make this configurable. */
742 Req.threshold = Req.period_size;
743 int rc = alsaStreamOpen(pThis->szDefaultIn, true /* fIn */, &Req, &pStreamALSA->AlsaCfg, &pStreamALSA->hPCM);
744 if (RT_SUCCESS(rc))
745 {
746 rc = alsaALSAToAudioProps(&pCfgAcq->Props, pStreamALSA->AlsaCfg.fmt, pStreamALSA->AlsaCfg.cChannels, pStreamALSA->AlsaCfg.freq);
747 if (RT_SUCCESS(rc))
748 {
749 pCfgAcq->Backend.cFramesPeriod = pStreamALSA->AlsaCfg.period_size;
750 pCfgAcq->Backend.cFramesBufferSize = pStreamALSA->AlsaCfg.buffer_size;
751 pCfgAcq->Backend.cFramesPreBuffering = 0; /* No pre-buffering. */
752 return VINF_SUCCESS;
753 }
754
755 alsaStreamClose(&pStreamALSA->hPCM);
756 }
757 LogFlowFuncLeaveRC(rc);
758 return rc;
759}
760
761
762/**
763 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
764 */
765static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
766 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
767{
768 PDRVHOSTALSAAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTALSAAUDIO, IHostAudio);
769 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
770 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
771 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
772 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
773
774 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
775 PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgReq);
776
777 int rc;
778 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
779 rc = alsaCreateStreamIn( pThis, pStreamALSA, pCfgReq, pCfgAcq);
780 else
781 rc = alsaCreateStreamOut(pThis, pStreamALSA, pCfgReq, pCfgAcq);
782 if (RT_SUCCESS(rc))
783 PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgAcq);
784 return rc;
785}
786
787
788/**
789 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
790 */
791static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
792 bool fImmediate)
793{
794 RT_NOREF(pInterface);
795 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
796 AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
797 RT_NOREF(fImmediate);
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 ((uint32_t)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,pfnStreamGetState}
1121 */
1122static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAlsaAudioHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
1123 PPDMAUDIOBACKENDSTREAM pStream)
1124{
1125 RT_NOREF(pInterface);
1126 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1127 AssertPtrReturn(pStreamALSA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
1128
1129 PDMHOSTAUDIOSTREAMSTATE enmStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
1130 snd_pcm_state_t enmAlsaState = snd_pcm_state(pStreamALSA->hPCM);
1131 if (enmAlsaState == SND_PCM_STATE_DRAINING)
1132 enmStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
1133#if (((SND_LIB_MAJOR) << 16) | ((SND_LIB_MAJOR) << 8) | (SND_LIB_SUBMINOR)) >= 0x10002 /* was added in 1.0.2 */
1134 else if (enmAlsaState == SND_PCM_STATE_DISCONNECTED)
1135 enmStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
1136#endif
1137
1138 Log5Func(("Stream '%s': ALSA state=%s -> %s\n",
1139 pStreamALSA->Cfg.szName, snd_pcm_state_name(enmAlsaState), PDMHostAudioStreamStateGetName(enmStreamState) ));
1140 return enmStreamState;
1141}
1142
1143
1144/**
1145 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1146 */
1147static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1148 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1149{
1150 RT_NOREF_PV(pInterface);
1151 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1152 AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
1153 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1154 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1155 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
1156 Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
1157 snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
1158
1159 /*
1160 * Figure out how much we can read without trouble (we're doing
1161 * non-blocking reads, but whatever).
1162 */
1163 snd_pcm_sframes_t cAvail;
1164 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cAvail);
1165 if (RT_SUCCESS(rc))
1166 {
1167 if (!cAvail) /* No data yet? */
1168 {
1169 snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM);
1170 switch (enmState)
1171 {
1172 case SND_PCM_STATE_PREPARED:
1173 /** @todo r=bird: explain the logic here... */
1174 cAvail = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbBuf);
1175 break;
1176
1177 case SND_PCM_STATE_SUSPENDED:
1178 rc = alsaStreamResume(pStreamALSA->hPCM);
1179 if (RT_SUCCESS(rc))
1180 {
1181 LogFlowFunc(("Resumed suspended input stream.\n"));
1182 break;
1183 }
1184 LogFunc(("Failed resuming suspended input stream: %Rrc\n", rc));
1185 return rc;
1186
1187 default:
1188 LogFlow(("No frames available: state=%s (%d)\n", snd_pcm_state_name(enmState), enmState));
1189 break;
1190 }
1191 if (!cAvail)
1192 {
1193 *pcbRead = 0;
1194 return VINF_SUCCESS;
1195 }
1196 }
1197 }
1198 else
1199 {
1200 LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc));
1201 return rc;
1202 }
1203
1204 size_t cbToRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cAvail);
1205 cbToRead = RT_MIN(cbToRead, cbBuf);
1206 LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail));
1207
1208 /*
1209 * Read loop.
1210 */
1211 uint32_t cbReadTotal = 0;
1212 while (cbToRead > 0)
1213 {
1214 /*
1215 * Do the reading.
1216 */
1217 snd_pcm_uframes_t const cFramesToRead = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbToRead);
1218 AssertBreakStmt(cFramesToRead > 0, rc = VERR_NO_DATA);
1219
1220 snd_pcm_sframes_t cFramesRead = snd_pcm_readi(pStreamALSA->hPCM, pvBuf, cFramesToRead);
1221 if (cFramesRead > 0)
1222 {
1223 /*
1224 * We should not run into a full mixer buffer or we lose samples and
1225 * run into an endless loop if ALSA keeps producing samples ("null"
1226 * capture device for example).
1227 */
1228 uint32_t const cbRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesRead);
1229 Assert(cbRead <= cbToRead);
1230
1231 cbToRead -= cbRead;
1232 cbReadTotal += cbRead;
1233 pvBuf = (uint8_t *)pvBuf + cbRead;
1234 pStreamALSA->offInternal += cbRead;
1235 }
1236 else
1237 {
1238 /*
1239 * Try recover from overrun and re-try.
1240 * Other conditions/errors we cannot and will just quit the loop.
1241 */
1242 if (cFramesRead == -EPIPE)
1243 {
1244 rc = alsaStreamRecover(pStreamALSA->hPCM);
1245 if (RT_SUCCESS(rc))
1246 {
1247 LogFlowFunc(("Successfully recovered from overrun\n"));
1248 continue;
1249 }
1250 LogFunc(("Failed to recover from overrun: %Rrc\n", rc));
1251 }
1252 else if (cFramesRead == -EAGAIN)
1253 LogFunc(("No input frames available (EAGAIN)\n"));
1254 else if (cFramesRead == 0)
1255 LogFunc(("No input frames available (0)\n"));
1256 else
1257 {
1258 rc = RTErrConvertFromErrno(-(int)cFramesRead);
1259 LogFunc(("Failed to read input frames: %s (%ld, %Rrc)\n", snd_strerror(cFramesRead), cFramesRead, rc));
1260 }
1261
1262 /* If we've read anything, suppress the error. */
1263 if (RT_FAILURE(rc) && cbReadTotal > 0)
1264 {
1265 LogFunc(("Suppressing %Rrc because %#x bytes has been read already\n", rc, cbReadTotal));
1266 rc = VINF_SUCCESS;
1267 }
1268 break;
1269 }
1270 }
1271
1272 LogFlowFunc(("returns %Rrc and %#x (%d) bytes (%u bytes left); state %s\n",
1273 rc, cbReadTotal, cbReadTotal, cbToRead, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
1274 *pcbRead = cbReadTotal;
1275 return rc;
1276}
1277
1278/**
1279 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1280 */
1281static DECLCALLBACK(int) drvHostAlsaAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1282 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1283{
1284 PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
1285 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1286 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1287 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
1288 Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
1289 snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
1290 if (cbBuf)
1291 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1292 else
1293 {
1294 /* Fend off draining calls. */
1295 *pcbWritten = 0;
1296 return VINF_SUCCESS;
1297 }
1298
1299 /*
1300 * Determine how much we can write (caller actually did this
1301 * already, but we repeat it just to be sure or something).
1302 */
1303 snd_pcm_sframes_t cFramesAvail;
1304 int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
1305 if (RT_SUCCESS(rc))
1306 {
1307 Assert(cFramesAvail);
1308 if (cFramesAvail)
1309 {
1310 PCPDMAUDIOPCMPROPS pProps = &pStreamALSA->Cfg.Props;
1311 uint32_t cbToWrite = PDMAudioPropsFramesToBytes(pProps, (uint32_t)cFramesAvail);
1312 if (cbToWrite)
1313 {
1314 if (cbToWrite > cbBuf)
1315 cbToWrite = cbBuf;
1316
1317 /*
1318 * Try write the data.
1319 */
1320 uint32_t cFramesToWrite = PDMAudioPropsBytesToFrames(pProps, cbToWrite);
1321 snd_pcm_sframes_t cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
1322 if (cFramesWritten > 0)
1323 {
1324 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
1325 cbToWrite, cFramesWritten, cFramesAvail));
1326 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
1327 pStreamALSA->offInternal += *pcbWritten;
1328 return VINF_SUCCESS;
1329 }
1330 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld]\n", cbToWrite, cFramesWritten, cFramesAvail));
1331
1332
1333 /*
1334 * There are a couple of error we can recover from, try to do so.
1335 * Only don't try too many times.
1336 */
1337 for (unsigned iTry = 0;
1338 (cFramesWritten == -EPIPE || cFramesWritten == -ESTRPIPE) && iTry < ALSA_RECOVERY_TRIES_MAX;
1339 iTry++)
1340 {
1341 if (cFramesWritten == -EPIPE)
1342 {
1343 /* Underrun occurred. */
1344 rc = alsaStreamRecover(pStreamALSA->hPCM);
1345 if (RT_FAILURE(rc))
1346 break;
1347 LogFlowFunc(("Recovered from playback (iTry=%u)\n", iTry));
1348 }
1349 else
1350 {
1351 /* An suspended event occurred, needs resuming. */
1352 rc = alsaStreamResume(pStreamALSA->hPCM);
1353 if (RT_FAILURE(rc))
1354 {
1355 LogRel(("ALSA: Failed to resume output stream (iTry=%u, rc=%Rrc)\n", iTry, rc));
1356 break;
1357 }
1358 LogFlowFunc(("Resumed suspended output stream (iTry=%u)\n", iTry));
1359 }
1360
1361 cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
1362 if (cFramesWritten > 0)
1363 {
1364 Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
1365 cbToWrite, cFramesWritten, cFramesAvail));
1366 *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
1367 pStreamALSA->offInternal += *pcbWritten;
1368 return VINF_SUCCESS;
1369 }
1370 LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld, iTry=%d]\n", cbToWrite, cFramesWritten, cFramesAvail, iTry));
1371 }
1372
1373 /* Make sure we return with an error status. */
1374 if (RT_SUCCESS_NP(rc))
1375 {
1376 if (cFramesWritten == 0)
1377 rc = VERR_ACCESS_DENIED;
1378 else
1379 {
1380 rc = RTErrConvertFromErrno(-(int)cFramesWritten);
1381 LogFunc(("Failed to write %RU32 bytes: %ld (%Rrc)\n", cbToWrite, cFramesWritten, rc));
1382 }
1383 }
1384 }
1385 }
1386 }
1387 else
1388 LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc));
1389 *pcbWritten = 0;
1390 return rc;
1391}
1392
1393
1394/**
1395 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1396 */
1397static DECLCALLBACK(void *) drvHostAlsaAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1398{
1399 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1400 PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
1401 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1402 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1403
1404 return NULL;
1405}
1406
1407
1408/**
1409 * Construct a DirectSound Audio driver instance.
1410 *
1411 * @copydoc FNPDMDRVCONSTRUCT
1412 */
1413static DECLCALLBACK(int) drvHostAlsaAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1414{
1415 RT_NOREF(fFlags);
1416 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1417 PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
1418 LogRel(("Audio: Initializing ALSA driver\n"));
1419
1420 /*
1421 * Init the static parts.
1422 */
1423 pThis->pDrvIns = pDrvIns;
1424 /* IBase */
1425 pDrvIns->IBase.pfnQueryInterface = drvHostAlsaAudioQueryInterface;
1426 /* IHostAudio */
1427 pThis->IHostAudio.pfnGetConfig = drvHostAlsaAudioHA_GetConfig;
1428 pThis->IHostAudio.pfnGetDevices = drvHostAlsaAudioHA_GetDevices;
1429 pThis->IHostAudio.pfnSetDevice = NULL;
1430 pThis->IHostAudio.pfnGetStatus = drvHostAlsaAudioHA_GetStatus;
1431 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
1432 pThis->IHostAudio.pfnStreamConfigHint = NULL;
1433 pThis->IHostAudio.pfnStreamCreate = drvHostAlsaAudioHA_StreamCreate;
1434 pThis->IHostAudio.pfnStreamInitAsync = NULL;
1435 pThis->IHostAudio.pfnStreamDestroy = drvHostAlsaAudioHA_StreamDestroy;
1436 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
1437 pThis->IHostAudio.pfnStreamControl = drvHostAlsaAudioHA_StreamControl;
1438 pThis->IHostAudio.pfnStreamGetReadable = drvHostAlsaAudioHA_StreamGetReadable;
1439 pThis->IHostAudio.pfnStreamGetWritable = drvHostAlsaAudioHA_StreamGetWritable;
1440 pThis->IHostAudio.pfnStreamGetPending = drvHostAlsaAudioHA_StreamGetPending;
1441 pThis->IHostAudio.pfnStreamGetState = drvHostAlsaAudioHA_StreamGetState;
1442 pThis->IHostAudio.pfnStreamPlay = drvHostAlsaAudioHA_StreamPlay;
1443 pThis->IHostAudio.pfnStreamCapture = drvHostAlsaAudioHA_StreamCapture;
1444
1445 /*
1446 * Read configuration.
1447 */
1448 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "DefaultOutput|DefaultInput", "");
1449
1450 int rc = CFGMR3QueryStringDef(pCfg, "DefaultInput", pThis->szDefaultIn, sizeof(pThis->szDefaultIn), "default");
1451 AssertRCReturn(rc, rc);
1452 rc = CFGMR3QueryStringDef(pCfg, "DefaultOutput", pThis->szDefaultOut, sizeof(pThis->szDefaultOut), "default");
1453 AssertRCReturn(rc, rc);
1454
1455 /*
1456 * Init the alsa library.
1457 */
1458 rc = audioLoadAlsaLib();
1459 if (RT_FAILURE(rc))
1460 {
1461 LogRel(("ALSA: Failed to load the ALSA shared library: %Rrc\n", rc));
1462 return rc;
1463 }
1464#ifdef DEBUG
1465 snd_lib_error_set_handler(alsaDbgErrorHandler);
1466#endif
1467 return VINF_SUCCESS;
1468}
1469
1470
1471/**
1472 * ALSA audio driver registration record.
1473 */
1474const PDMDRVREG g_DrvHostALSAAudio =
1475{
1476 /* u32Version */
1477 PDM_DRVREG_VERSION,
1478 /* szName */
1479 "ALSAAudio",
1480 /* szRCMod */
1481 "",
1482 /* szR0Mod */
1483 "",
1484 /* pszDescription */
1485 "ALSA host audio driver",
1486 /* fFlags */
1487 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1488 /* fClass. */
1489 PDM_DRVREG_CLASS_AUDIO,
1490 /* cMaxInstances */
1491 ~0U,
1492 /* cbInstance */
1493 sizeof(DRVHOSTALSAAUDIO),
1494 /* pfnConstruct */
1495 drvHostAlsaAudioConstruct,
1496 /* pfnDestruct */
1497 NULL,
1498 /* pfnRelocate */
1499 NULL,
1500 /* pfnIOCtl */
1501 NULL,
1502 /* pfnPowerOn */
1503 NULL,
1504 /* pfnReset */
1505 NULL,
1506 /* pfnSuspend */
1507 NULL,
1508 /* pfnResume */
1509 NULL,
1510 /* pfnAttach */
1511 NULL,
1512 /* pfnDetach */
1513 NULL,
1514 /* pfnPowerOff */
1515 NULL,
1516 /* pfnSoftReset */
1517 NULL,
1518 /* u32EndVersion */
1519 PDM_DRVREG_VERSION
1520};
1521
Note: See TracBrowser for help on using the repository browser.

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