VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostAudioOss.cpp@ 88991

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

Audio: Worked over draining, starting with the internal DMA buffer (instead of just the pre-buffer and backend buffer) and using the async I/O thread to keep calling PDMIAUDIOCONNECTOR::pfnStreamIterate and PDMIHOSTAUDIO::pfnStreamPlay (NULL buffer) every so often till the draining is done. Also put a rough deadline on the draining. The PDMAUDIOSTREAMCMD_DISABLE is now defined to stop playback/capturing immediately, even when already draining (if possible). This gets rid of the timers in DrvAudio and windows backends. DrvAudio no longer issue an DISABLE command at the end of the drain, it assumes the backend does that internally itself. After issuing PDMAUDIOSTREAMCMD_DRAIN the client (be it mixer or drvaudio) will not provide any more data for the buffers via pfnStreamPlay. Only tested windows, needs to re-test all platforms. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.6 KB
Line 
1/* $Id: DrvHostAudioOss.cpp 88991 2021-05-12 00:46:35Z vboxsync $ */
2/** @file
3 * Host audio driver - OSS (Open Sound System).
4 */
5
6/*
7 * Copyright (C) 2014-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
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <errno.h>
23#include <fcntl.h>
24#include <sys/ioctl.h>
25#include <sys/mman.h>
26#include <sys/soundcard.h>
27#include <unistd.h>
28
29#include <iprt/alloc.h>
30#include <iprt/thread.h>
31#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
32
33#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
34#include <VBox/log.h>
35#include <VBox/vmm/pdmaudioifs.h>
36#include <VBox/vmm/pdmaudioinline.h>
37
38#ifdef VBOX_AUDIO_VKAT
39# include "VBoxDDVKAT.h"
40#else
41# include "VBoxDD.h"
42#endif
43
44
45/*********************************************************************************************************************************
46* Defines *
47*********************************************************************************************************************************/
48#if ((SOUND_VERSION > 360) && (defined(OSS_SYSINFO)))
49/* OSS > 3.6 has a new syscall available for querying a bit more detailed information
50 * about OSS' audio capabilities. This is handy for e.g. Solaris. */
51# define VBOX_WITH_AUDIO_OSS_SYSINFO 1
52#endif
53
54
55/*********************************************************************************************************************************
56* Structures *
57*********************************************************************************************************************************/
58/**
59 * OSS host audio driver instance data.
60 * @implements PDMIAUDIOCONNECTOR
61 */
62typedef struct DRVHOSTOSSAUDIO
63{
64 /** Pointer to the driver instance structure. */
65 PPDMDRVINS pDrvIns;
66 /** Pointer to host audio interface. */
67 PDMIHOSTAUDIO IHostAudio;
68 /** Error count for not flooding the release log.
69 * UINT32_MAX for unlimited logging. */
70 uint32_t cLogErrors;
71} DRVHOSTOSSAUDIO;
72/** Pointer to the instance data for an OSS host audio driver. */
73typedef DRVHOSTOSSAUDIO *PDRVHOSTOSSAUDIO;
74
75/**
76 * OSS audio stream configuration.
77 */
78typedef struct OSSAUDIOSTREAMCFG
79{
80 PDMAUDIOPCMPROPS Props;
81 uint16_t cFragments;
82 /** The log2 of cbFragment. */
83 uint16_t cbFragmentLog2;
84 uint32_t cbFragment;
85} OSSAUDIOSTREAMCFG;
86/** Pointer to an OSS audio stream configuration. */
87typedef OSSAUDIOSTREAMCFG *POSSAUDIOSTREAMCFG;
88
89/**
90 * OSS audio stream.
91 */
92typedef struct OSSAUDIOSTREAM
93{
94 /** Common part. */
95 PDMAUDIOBACKENDSTREAM Core;
96 /** The file descriptor. */
97 int hFile;
98 /** Buffer alignment. */
99 uint8_t uAlign;
100 /** Set if we're draining the stream (output only). */
101 bool fDraining;
102 /** Internal stream byte offset. */
103 uint64_t offInternal;
104 /** The stream's acquired configuration. */
105 PDMAUDIOSTREAMCFG Cfg;
106 /** The acquired OSS configuration. */
107 OSSAUDIOSTREAMCFG OssCfg;
108 /** Handle to the thread draining output streams. */
109 RTTHREAD hThreadDrain;
110} OSSAUDIOSTREAM;
111/** Pointer to an OSS audio stream. */
112typedef OSSAUDIOSTREAM *POSSAUDIOSTREAM;
113
114
115/*********************************************************************************************************************************
116* Global Variables *
117*********************************************************************************************************************************/
118/** The path to the output OSS device. */
119static char g_szPathOutputDev[] = "/dev/dsp";
120/** The path to the input OSS device. */
121static char g_szPathInputDev[] = "/dev/dsp";
122
123
124
125static int ossOSSToAudioProps(PPDMAUDIOPCMPROPS pProps, int fmt, int cChannels, int uHz)
126{
127 switch (fmt)
128 {
129 case AFMT_S8:
130 PDMAudioPropsInit(pProps, 1 /*8-bit*/, true /*signed*/, cChannels, uHz);
131 break;
132
133 case AFMT_U8:
134 PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz);
135 break;
136
137 case AFMT_S16_LE:
138 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
139 break;
140
141 case AFMT_U16_LE:
142 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, false /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
143 break;
144
145 case AFMT_S16_BE:
146 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
147 break;
148
149 case AFMT_U16_BE:
150 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, false /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
151 break;
152
153 default:
154 AssertMsgFailedReturn(("Format %d not supported\n", fmt), VERR_NOT_SUPPORTED);
155 }
156
157 return VINF_SUCCESS;
158}
159
160
161static int ossStreamClose(int *phFile)
162{
163 if (!phFile || !*phFile || *phFile == -1)
164 return VINF_SUCCESS;
165
166 int rc;
167 if (close(*phFile))
168 {
169 rc = RTErrConvertFromErrno(errno);
170 LogRel(("OSS: Closing stream failed: %s / %Rrc\n", strerror(errno), rc));
171 }
172 else
173 {
174 *phFile = -1;
175 rc = VINF_SUCCESS;
176 }
177
178 return rc;
179}
180
181
182/**
183 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
184 */
185static DECLCALLBACK(int) drvHostOssAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
186{
187 RT_NOREF(pInterface);
188
189 /*
190 * Fill in the config structure.
191 */
192 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "OSS");
193 pBackendCfg->cbStream = sizeof(OSSAUDIOSTREAM);
194 pBackendCfg->fFlags = 0;
195 pBackendCfg->cMaxStreamsIn = 0;
196 pBackendCfg->cMaxStreamsOut = 0;
197
198 int hFile = open("/dev/dsp", O_WRONLY | O_NONBLOCK, 0);
199 if (hFile == -1)
200 {
201 /* Try opening the mixing device instead. */
202 hFile = open("/dev/mixer", O_RDONLY | O_NONBLOCK, 0);
203 }
204 if (hFile != -1)
205 {
206 int ossVer = -1;
207 int err = ioctl(hFile, OSS_GETVERSION, &ossVer);
208 if (err == 0)
209 {
210 LogRel2(("OSS: Using version: %d\n", ossVer));
211#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO
212 oss_sysinfo ossInfo;
213 RT_ZERO(ossInfo);
214 err = ioctl(hFile, OSS_SYSINFO, &ossInfo);
215 if (err == 0)
216 {
217 LogRel2(("OSS: Number of DSPs: %d\n", ossInfo.numaudios));
218 LogRel2(("OSS: Number of mixers: %d\n", ossInfo.nummixers));
219
220 int cDev = ossInfo.nummixers;
221 if (!cDev)
222 cDev = ossInfo.numaudios;
223
224 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
225 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
226 }
227 else
228#endif
229 {
230 /* Since we cannot query anything, assume that we have at least
231 * one input and one output if we found "/dev/dsp" or "/dev/mixer". */
232
233 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
234 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
235 }
236 }
237 else
238 LogRel(("OSS: Unable to determine installed version: %s (%d)\n", strerror(err), err));
239 close(hFile);
240 }
241 else
242 LogRel(("OSS: No devices found, audio is not available\n"));
243
244 return VINF_SUCCESS;
245}
246
247
248
249
250/**
251 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
252 */
253static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostOssAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
254{
255 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
256 RT_NOREF(enmDir);
257
258 return PDMAUDIOBACKENDSTS_RUNNING;
259}
260
261
262static int ossStreamConfigure(int hFile, bool fInput, POSSAUDIOSTREAMCFG pOSSReq, POSSAUDIOSTREAMCFG pOSSAcq)
263{
264 /*
265 * Format.
266 */
267 int iFormat;
268 switch (PDMAudioPropsSampleSize(&pOSSReq->Props))
269 {
270 case 1:
271 iFormat = pOSSReq->Props.fSigned ? AFMT_S8 : AFMT_U8;
272 break;
273
274 case 2:
275 if (PDMAudioPropsIsLittleEndian(&pOSSReq->Props))
276 iFormat = pOSSReq->Props.fSigned ? AFMT_S16_LE : AFMT_U16_LE;
277 else
278 iFormat = pOSSReq->Props.fSigned ? AFMT_S16_BE : AFMT_U16_BE;
279 break;
280
281 default:
282 LogRel2(("OSS: Unsupported sample size: %u\n", PDMAudioPropsSampleSize(&pOSSReq->Props)));
283 return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
284 }
285 AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SAMPLESIZE, &iFormat) >= 0,
286 ("OSS: Failed to set audio format to %d: %s (%d)\n", iFormat, strerror(errno), errno),
287 RTErrConvertFromErrno(errno));
288
289 /*
290 * Channel count.
291 */
292 int cChannels = PDMAudioPropsChannels(&pOSSReq->Props);
293 AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_CHANNELS, &cChannels) >= 0,
294 ("OSS: Failed to set number of audio channels (%RU8): %s (%d)\n",
295 PDMAudioPropsChannels(&pOSSReq->Props), strerror(errno), errno),
296 RTErrConvertFromErrno(errno));
297
298 /*
299 * Frequency.
300 */
301 int iFrequenc = pOSSReq->Props.uHz;
302 AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SPEED, &iFrequenc) >= 0,
303 ("OSS: Failed to set audio frequency to %d Hz: %s (%d)\n", pOSSReq->Props.uHz, strerror(errno), errno),
304 RTErrConvertFromErrno(errno));
305
306
307 /*
308 * Set obsolete non-blocking call for input streams.
309 */
310 if (fInput)
311 {
312#if !(defined(VBOX) && defined(RT_OS_SOLARIS)) /* Obsolete on Solaris (using O_NONBLOCK is sufficient). */
313 AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_NONBLOCK, NULL) >= 0,
314 ("OSS: Failed to set non-blocking mode: %s (%d)\n", strerror(errno), errno),
315 RTErrConvertFromErrno(errno));
316#endif
317 }
318
319 /*
320 * Set fragment size and count.
321 */
322 LogRel2(("OSS: Requested %RU16 %s fragments, %RU32 bytes each\n",
323 pOSSReq->cFragments, fInput ? "input" : "output", pOSSReq->cbFragment));
324
325 int mmmmssss = (pOSSReq->cFragments << 16) | pOSSReq->cbFragmentLog2;
326 AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SETFRAGMENT, &mmmmssss) >= 0,
327 ("OSS: Failed to set %RU16 fragments to %RU32 bytes each: %s (%d)\n",
328 pOSSReq->cFragments, pOSSReq->cbFragment, strerror(errno), errno),
329 RTErrConvertFromErrno(errno));
330
331 /*
332 * Get parameters and popuplate pOSSAcq.
333 */
334 audio_buf_info BufInfo = { 0, 0, 0, 0 };
335 AssertLogRelMsgReturn(ioctl(hFile, fInput ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &BufInfo) >= 0,
336 ("OSS: Failed to retrieve %s buffer length: %s (%d)\n",
337 fInput ? "input" : "output", strerror(errno), errno),
338 RTErrConvertFromErrno(errno));
339
340 int rc = ossOSSToAudioProps(&pOSSAcq->Props, iFormat, cChannels, iFrequenc);
341 if (RT_SUCCESS(rc))
342 {
343 pOSSAcq->cFragments = BufInfo.fragstotal;
344 pOSSAcq->cbFragment = BufInfo.fragsize;
345 pOSSAcq->cbFragmentLog2 = ASMBitFirstSetU32(BufInfo.fragsize) - 1;
346 Assert(RT_BIT_32(pOSSAcq->cbFragmentLog2) == pOSSAcq->cbFragment);
347
348 LogRel2(("OSS: Got %RU16 %s fragments, %RU32 bytes each\n",
349 pOSSAcq->cFragments, fInput ? "input" : "output", pOSSAcq->cbFragment));
350 }
351
352 return rc;
353}
354
355
356/**
357 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
358 */
359static DECLCALLBACK(int) drvHostOssAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
360 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
361{
362 AssertPtr(pInterface); RT_NOREF(pInterface);
363 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
364 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
365 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
366 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
367
368 pStreamOSS->hThreadDrain = NIL_RTTHREAD;
369
370 /*
371 * Open the device
372 */
373 int rc;
374 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
375 pStreamOSS->hFile = open(g_szPathInputDev, O_RDONLY | O_NONBLOCK);
376 else
377 pStreamOSS->hFile = open(g_szPathOutputDev, O_WRONLY);
378 if (pStreamOSS->hFile >= 0)
379 {
380 /*
381 * Configure it.
382 */
383 OSSAUDIOSTREAMCFG ReqOssCfg;
384 RT_ZERO(ReqOssCfg);
385
386 memcpy(&ReqOssCfg.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS));
387 ReqOssCfg.cbFragmentLog2 = 12;
388 ReqOssCfg.cbFragment = RT_BIT_32(ReqOssCfg.cbFragmentLog2);
389 uint32_t const cbBuffer = PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize);
390 ReqOssCfg.cFragments = cbBuffer >> ReqOssCfg.cbFragmentLog2;
391 AssertLogRelStmt(cbBuffer < ((uint32_t)0x7ffe << ReqOssCfg.cbFragmentLog2), ReqOssCfg.cFragments = 0x7ffe);
392
393 rc = ossStreamConfigure(pStreamOSS->hFile, pCfgReq->enmDir == PDMAUDIODIR_IN, &ReqOssCfg, &pStreamOSS->OssCfg);
394 if (RT_SUCCESS(rc))
395 {
396 pStreamOSS->uAlign = 0; /** @todo r=bird: Where did the correct assignment of this go? */
397
398 /*
399 * Complete the stream structure and fill in the pCfgAcq bits.
400 */
401 if ((pStreamOSS->OssCfg.cFragments * pStreamOSS->OssCfg.cbFragment) & pStreamOSS->uAlign)
402 LogRel(("OSS: Warning: Misaligned playback buffer: Size = %zu, Alignment = %u\n",
403 pStreamOSS->OssCfg.cFragments * pStreamOSS->OssCfg.cbFragment, pStreamOSS->uAlign + 1));
404
405 memcpy(&pCfgAcq->Props, &pStreamOSS->OssCfg.Props, sizeof(PDMAUDIOPCMPROPS));
406 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamOSS->OssCfg.cbFragment);
407 pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * pStreamOSS->OssCfg.cFragments;
408 if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
409 pCfgAcq->Backend.cFramesPreBuffering = (uint64_t)pCfgReq->Backend.cFramesPreBuffering
410 * pCfgAcq->Backend.cFramesBufferSize
411 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
412 else
413 pCfgAcq->Backend.cFramesPreBuffering = 0; /** @todo is this sane? */
414
415 /*
416 * Copy the stream config and we're done!
417 */
418 PDMAudioStrmCfgCopy(&pStreamOSS->Cfg, pCfgAcq);
419 return VINF_SUCCESS;
420 }
421 ossStreamClose(&pStreamOSS->hFile);
422 }
423 else
424 {
425 rc = RTErrConvertFromErrno(errno);
426 LogRel(("OSS: Failed to open '%s': %s (%d) / %Rrc\n",
427 pCfgReq->enmDir == PDMAUDIODIR_IN ? g_szPathInputDev : g_szPathOutputDev, strerror(errno), errno, rc));
428 }
429 return rc;
430}
431
432
433/**
434 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
435 */
436static DECLCALLBACK(int) drvHostOssAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
437{
438 RT_NOREF(pInterface);
439 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
440 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
441
442 ossStreamClose(&pStreamOSS->hFile);
443
444 if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
445 {
446 int rc = RTThreadWait(pStreamOSS->hThreadDrain, 1, NULL);
447 AssertRC(rc);
448 pStreamOSS->hThreadDrain = NIL_RTTHREAD;
449 }
450
451 return VINF_SUCCESS;
452}
453
454
455/**
456 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
457 */
458static DECLCALLBACK(int) drvHostOssAudioHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
459{
460 RT_NOREF(pInterface);
461 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
462
463 /** @todo this might be a little optimisitic... */
464 pStreamOSS->fDraining = false;
465
466 int rc;
467 if (pStreamOSS->Cfg.enmDir == PDMAUDIODIR_IN)
468 rc = VINF_SUCCESS; /** @todo apparently nothing to do here? */
469 else
470 {
471 int fMask = PCM_ENABLE_OUTPUT;
472 if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0)
473 rc = VINF_SUCCESS;
474 else
475 {
476 LogRel(("OSS: Failed to enable output stream: %s (%d)\n", strerror(errno), errno));
477 rc = RTErrConvertFromErrno(errno);
478 }
479 }
480
481 LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName));
482 return rc;
483}
484
485
486/**
487 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
488 */
489static DECLCALLBACK(int) drvHostOssAudioHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
490{
491 RT_NOREF(pInterface);
492 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
493
494 int rc;
495 if (pStreamOSS->Cfg.enmDir == PDMAUDIODIR_IN)
496 rc = VINF_SUCCESS; /** @todo apparently nothing to do here? */
497 else
498 {
499 /*
500 * If we're still draining, try kick the thread before we try disable the stream.
501 */
502 if (pStreamOSS->fDraining)
503 {
504 LogFuncFlow(("Trying to cancel draining...\n"));
505 if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
506 {
507 RTThreadPoke(pStreamOSS->hThreadDrain);
508 rc = RTThreadWait(pStreamOSS->hThreadDrain, 1 /*ms*/, NULL);
509 if (RT_SUCCESS(rc) || rc == VERR_INVALID_HANDLE)
510 pStreamOSS->fDraining = false;
511 else
512 LogFuncFlow(("Failed to cancel draining (%Rrc)\n", rc));
513 }
514 else
515 {
516 LogFuncFlow(("Thread handle is NIL, so we can't be draining\n"));
517 pStreamOSS->fDraining = false;
518 }
519 }
520
521 /** @todo Official documentation says this isn't the right way to stop playback.
522 * It may work in some implementations but fail in all others... Suggest
523 * using SNDCTL_DSP_RESET / SNDCTL_DSP_HALT. */
524 int fMask = 0;
525 if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0)
526 rc = VINF_SUCCESS;
527 else
528 {
529 LogRel(("OSS: Failed to enable output stream: %s (%d)\n", strerror(errno), errno));
530 rc = RTErrConvertFromErrno(errno);
531 }
532 }
533 LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName));
534 return rc;
535}
536
537
538/**
539 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
540 */
541static DECLCALLBACK(int) drvHostOssAudioHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
542{
543 return drvHostOssAudioHA_StreamDisable(pInterface, pStream);
544}
545
546
547/**
548 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
549 */
550static DECLCALLBACK(int) drvHostOssAudioHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
551{
552 return drvHostOssAudioHA_StreamEnable(pInterface, pStream);
553}
554
555
556/**
557 * @callback_method_impl{FNRTTHREAD,
558 * Thread for calling SNDCTL_DSP_SYNC (blocking) on an output stream.}
559 */
560static DECLCALLBACK(int) drvHostOssAudioDrainThread(RTTHREAD ThreadSelf, void *pvUser)
561{
562 RT_NOREF(ThreadSelf);
563 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pvUser;
564 int rc;
565
566 /* Make it blocking (for Linux). */
567 int fOrgFlags = fcntl(pStreamOSS->hFile, F_GETFL, 0);
568 LogFunc(("F_GETFL -> %#x\n", fOrgFlags));
569 Assert(fOrgFlags != -1);
570 if (fOrgFlags != -1)
571 {
572 rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags & ~O_NONBLOCK);
573 AssertStmt(rc != -1, fOrgFlags = -1);
574 }
575
576 /* Drain it. */
577 LogFunc(("Calling SNDCTL_DSP_SYNC now...\n"));
578 rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SYNC, NULL);
579 LogFunc(("SNDCTL_DSP_SYNC returned %d / errno=%d\n", rc, errno)); RT_NOREF(rc);
580
581 /* Re-enable non-blocking mode and disable it. */
582 if (fOrgFlags != -1)
583 {
584 rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags);
585 Assert(rc != -1);
586
587 int fMask = 0;
588 rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask);
589 Assert(rc >= 0);
590
591 pStreamOSS->fDraining = false;
592 LogFunc(("Restored non-block mode and cleared the trigger mask\n"));
593 }
594
595 return VINF_SUCCESS;
596}
597
598
599/**
600 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
601 */
602static DECLCALLBACK(int) drvHostOssAudioHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
603{
604 PDRVHOSTOSSAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTOSSAUDIO, IHostAudio);
605 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
606 AssertReturn(pStreamOSS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER);
607
608 pStreamOSS->fDraining = true;
609
610 /*
611 * Because the SNDCTL_DSP_SYNC call is blocking on real OSS,
612 * we kick off a thread to deal with it as we're probably on EMT
613 * and cannot block for extended periods.
614 */
615 if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
616 {
617 int rc = RTThreadWait(pStreamOSS->hThreadDrain, 0, NULL);
618 if (RT_SUCCESS(rc))
619 {
620 pStreamOSS->hThreadDrain = NIL_RTTHREAD;
621 LogFunc(("Cleaned up stale thread handle.\n"));
622 }
623 else
624 {
625 LogFunc(("Drain thread already running (%Rrc).\n", rc));
626 AssertMsg(rc == VERR_TIMEOUT, ("%Rrc\n", rc));
627 return rc == VERR_TIMEOUT ? VINF_SUCCESS : rc;
628 }
629 }
630
631 int rc = RTThreadCreateF(&pStreamOSS->hThreadDrain, drvHostOssAudioDrainThread, pStreamOSS, 0,
632 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ossdrai%u", pThis->pDrvIns->iInstance);
633 LogFunc(("Started drain thread: %Rrc\n", rc));
634 AssertRCReturn(rc, rc);
635
636 return VINF_SUCCESS;
637}
638
639
640
641/**
642 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
643 */
644static DECLCALLBACK(int) drvHostOssAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
645 PDMAUDIOSTREAMCMD enmStreamCmd)
646{
647 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
648 * replacing it with individual StreamXxxx methods. That would save us
649 * potentally huge switches and more easily see which drivers implement
650 * which operations (grep for pfnStreamXxxx). */
651 switch (enmStreamCmd)
652 {
653 case PDMAUDIOSTREAMCMD_ENABLE:
654 return drvHostOssAudioHA_StreamEnable(pInterface, pStream);
655 case PDMAUDIOSTREAMCMD_DISABLE:
656 return drvHostOssAudioHA_StreamDisable(pInterface, pStream);
657 case PDMAUDIOSTREAMCMD_PAUSE:
658 return drvHostOssAudioHA_StreamPause(pInterface, pStream);
659 case PDMAUDIOSTREAMCMD_RESUME:
660 return drvHostOssAudioHA_StreamResume(pInterface, pStream);
661 case PDMAUDIOSTREAMCMD_DRAIN:
662 return drvHostOssAudioHA_StreamDrain(pInterface, pStream);
663 /** @todo the drain call for OSS is SNDCTL_DSP_SYNC, however in the non-ALSA
664 * implementation of OSS it is probably blocking. Also, it comes with
665 * caveats about clicks and silence... */
666 case PDMAUDIOSTREAMCMD_END:
667 case PDMAUDIOSTREAMCMD_32BIT_HACK:
668 case PDMAUDIOSTREAMCMD_INVALID:
669 /* no default*/
670 break;
671 }
672 return VERR_NOT_SUPPORTED;
673}
674
675
676/**
677 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
678 */
679static DECLCALLBACK(uint32_t) drvHostOssAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
680{
681 RT_NOREF(pInterface, pStream);
682 Log4Func(("returns UINT32_MAX\n"));
683 return UINT32_MAX;
684}
685
686
687/**
688 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
689 */
690static DECLCALLBACK(uint32_t) drvHostOssAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
691{
692 RT_NOREF(pInterface);
693 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
694 AssertPtr(pStreamOSS);
695
696 /*
697 * Note! This logic was found in StreamPlay and corrected a little.
698 *
699 * The logic here must match what StreamPlay does.
700 */
701 audio_buf_info BufInfo = { 0, 0, 0, 0 };
702 int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo);
703 AssertMsgReturn(rc2 >= 0, ("SNDCTL_DSP_GETOSPACE failed: %s (%d)\n", strerror(errno), errno), 0);
704
705#if 0 /** @todo we could return BufInfo.bytes here iff StreamPlay didn't use the fragmented approach */
706 /** @todo r=bird: WTF do we make a fuss over BufInfo.bytes for when we don't
707 * even use it?!? */
708 AssertLogRelMsgReturn(BufInfo.bytes >= 0, ("OSS: Warning: Invalid available size: %d\n", BufInfo.bytes), VERR_INTERNAL_ERROR_3);
709 if ((unsigned)BufInfo.bytes > cbBuf)
710 {
711 LogRel2(("OSS: Warning: Too big output size (%d > %RU32), limiting to %RU32\n", BufInfo.bytes, cbBuf, cbBuf));
712 BufInfo.bytes = cbBuf;
713 /* Keep going. */
714 }
715#endif
716
717 uint32_t cbRet = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
718 Log4Func(("returns %#x (%u)\n", cbRet, cbRet));
719 return cbRet;
720}
721
722
723/**
724 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
725 */
726static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostOssAudioHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
727 PPDMAUDIOBACKENDSTREAM pStream)
728{
729 RT_NOREF(pInterface);
730 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
731 AssertPtrReturn(pStreamOSS, PDMHOSTAUDIOSTREAMSTATE_INVALID);
732 if (!pStreamOSS->fDraining)
733 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
734 return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
735}
736
737
738/**
739 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
740 */
741static DECLCALLBACK(int) drvHostOssAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
742 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
743{
744 RT_NOREF(pInterface);
745 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
746 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
747
748 /*
749 * Figure out now much to write.
750 */
751 audio_buf_info BufInfo;
752 int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo);
753 AssertLogRelMsgReturn(rc2 >= 0, ("OSS: Failed to retrieve current playback buffer: %s (%d)\n", strerror(errno), errno),
754 RTErrConvertFromErrno(errno));
755
756#if 0 /** @todo r=bird: WTF do we make a fuss over BufInfo.bytes for when we don't even use it?!? */
757 AssertLogRelMsgReturn(BufInfo.bytes >= 0, ("OSS: Warning: Invalid available size: %d\n", BufInfo.bytes), VERR_INTERNAL_ERROR_3);
758 if ((unsigned)BufInfo.bytes > cbBuf)
759 {
760 LogRel2(("OSS: Warning: Too big output size (%d > %RU32), limiting to %RU32\n", BufInfo.bytes, cbBuf, cbBuf));
761 BufInfo.bytes = cbBuf;
762 /* Keep going. */
763 }
764#endif
765 uint32_t cbToWrite = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
766 cbToWrite = RT_MIN(cbToWrite, cbBuf);
767 Log3Func(("@%#RX64 cbBuf=%#x BufInfo: fragments=%#x fragstotal=%#x fragsize=%#x bytes=%#x %s cbToWrite=%#x\n",
768 pStreamOSS->offInternal, cbBuf, BufInfo.fragments, BufInfo.fragstotal, BufInfo.fragsize, BufInfo.bytes,
769 pStreamOSS->Cfg.szName, cbToWrite));
770
771 /*
772 * Write.
773 */
774 uint8_t const *pbBuf = (uint8_t const *)pvBuf;
775 uint32_t cbChunk = cbToWrite;
776 uint32_t offChunk = 0;
777 while (cbChunk > 0)
778 {
779 ssize_t cbWritten = write(pStreamOSS->hFile, &pbBuf[offChunk], RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment));
780 if (cbWritten > 0)
781 {
782 AssertLogRelMsg(!(cbWritten & pStreamOSS->uAlign),
783 ("OSS: Misaligned write (written %#zx, alignment %#x)\n", cbWritten, pStreamOSS->uAlign));
784
785 Assert((uint32_t)cbWritten <= cbChunk);
786 offChunk += (uint32_t)cbWritten;
787 cbChunk -= (uint32_t)cbWritten;
788 pStreamOSS->offInternal += cbWritten;
789 }
790 else if (cbWritten == 0)
791 {
792 LogFunc(("@%#RX64 write(%#x) returned zeroed (previously wrote %#x bytes)!\n",
793 pStreamOSS->offInternal, RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment), cbWritten));
794 break;
795 }
796 else
797 {
798 LogRel(("OSS: Failed writing output data: %s (%d)\n", strerror(errno), errno));
799 return RTErrConvertFromErrno(errno);
800 }
801 }
802
803 *pcbWritten = offChunk;
804 return VINF_SUCCESS;
805}
806
807
808/**
809 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
810 */
811static DECLCALLBACK(int) drvHostOssAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
812 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
813{
814 RT_NOREF(pInterface);
815 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
816 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
817 Log3Func(("@%#RX64 cbBuf=%#x %s\n", pStreamOSS->offInternal, cbBuf, pStreamOSS->Cfg.szName));
818
819 size_t cbToRead = cbBuf;
820 uint8_t * const pbDst = (uint8_t *)pvBuf;
821 size_t offWrite = 0;
822 while (cbToRead > 0)
823 {
824 ssize_t cbRead = read(pStreamOSS->hFile, &pbDst[offWrite], cbToRead);
825 if (cbRead)
826 {
827 LogFlowFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu\n", cbRead, offWrite, cbToRead));
828 Assert((ssize_t)cbToRead >= cbRead);
829 cbToRead -= cbRead;
830 offWrite += cbRead;
831 pStreamOSS->offInternal += cbRead;
832 }
833 else
834 {
835 LogFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu errno=%d\n", cbRead, offWrite, cbToRead, errno));
836
837 /* Don't complain about errors if we've retrieved some audio data already. */
838 if (cbRead < 0 && offWrite == 0 && errno != EINTR && errno != EAGAIN)
839 {
840 AssertStmt(errno != 0, errno = EACCES);
841 int rc = RTErrConvertFromErrno(errno);
842 LogFunc(("Failed to read %zu input frames, errno=%d rc=%Rrc\n", cbToRead, errno, rc));
843 return rc;
844 }
845 break;
846 }
847 }
848
849 *pcbRead = offWrite;
850 return VINF_SUCCESS;
851}
852
853
854
855/**
856 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
857 */
858static DECLCALLBACK(void *) drvHostOSSAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
859{
860 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
861 PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO);
862
863 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
864 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
865
866 return NULL;
867}
868
869/**
870 * Constructs an OSS audio driver instance.
871 *
872 * @copydoc FNPDMDRVCONSTRUCT
873 */
874static DECLCALLBACK(int) drvHostOSSAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
875{
876 RT_NOREF(pCfg, fFlags);
877 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
878 PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO);
879 LogRel(("Audio: Initializing OSS driver\n"));
880
881 /*
882 * Init the static parts.
883 */
884 pThis->pDrvIns = pDrvIns;
885 /* IBase */
886 pDrvIns->IBase.pfnQueryInterface = drvHostOSSAudioQueryInterface;
887 /* IHostAudio */
888 pThis->IHostAudio.pfnGetConfig = drvHostOssAudioHA_GetConfig;
889 pThis->IHostAudio.pfnGetDevices = NULL;
890 pThis->IHostAudio.pfnGetStatus = drvHostOssAudioHA_GetStatus;
891 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
892 pThis->IHostAudio.pfnStreamConfigHint = NULL;
893 pThis->IHostAudio.pfnStreamCreate = drvHostOssAudioHA_StreamCreate;
894 pThis->IHostAudio.pfnStreamInitAsync = NULL;
895 pThis->IHostAudio.pfnStreamDestroy = drvHostOssAudioHA_StreamDestroy;
896 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
897 pThis->IHostAudio.pfnStreamControl = drvHostOssAudioHA_StreamControl;
898 pThis->IHostAudio.pfnStreamGetReadable = drvHostOssAudioHA_StreamGetReadable;
899 pThis->IHostAudio.pfnStreamGetWritable = drvHostOssAudioHA_StreamGetWritable;
900 pThis->IHostAudio.pfnStreamGetPending = NULL;
901 pThis->IHostAudio.pfnStreamGetState = drvHostOssAudioHA_StreamGetState;
902 pThis->IHostAudio.pfnStreamPlay = drvHostOssAudioHA_StreamPlay;
903 pThis->IHostAudio.pfnStreamCapture = drvHostOssAudioHA_StreamCapture;
904
905 return VINF_SUCCESS;
906}
907
908
909/**
910 * Char driver registration record.
911 */
912const PDMDRVREG g_DrvHostOSSAudio =
913{
914 /* u32Version */
915 PDM_DRVREG_VERSION,
916 /* szName */
917 "OSSAudio",
918 /* szRCMod */
919 "",
920 /* szR0Mod */
921 "",
922 /* pszDescription */
923 "OSS audio host driver",
924 /* fFlags */
925 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
926 /* fClass. */
927 PDM_DRVREG_CLASS_AUDIO,
928 /* cMaxInstances */
929 ~0U,
930 /* cbInstance */
931 sizeof(DRVHOSTOSSAUDIO),
932 /* pfnConstruct */
933 drvHostOSSAudioConstruct,
934 /* pfnDestruct */
935 NULL,
936 /* pfnRelocate */
937 NULL,
938 /* pfnIOCtl */
939 NULL,
940 /* pfnPowerOn */
941 NULL,
942 /* pfnReset */
943 NULL,
944 /* pfnSuspend */
945 NULL,
946 /* pfnResume */
947 NULL,
948 /* pfnAttach */
949 NULL,
950 /* pfnDetach */
951 NULL,
952 /* pfnPowerOff */
953 NULL,
954 /* pfnSoftReset */
955 NULL,
956 /* u32EndVersion */
957 PDM_DRVREG_VERSION
958};
959
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