VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp@ 88163

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

Audio: Moving some of the DrvAudio.h stuff into PDM - VBox/vmm/pdmaudioinline.h. bugref:9890

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