VirtualBox

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

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

Audio/ALSA: Documentation.

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