VirtualBox

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

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

DrvHostAudioOss: Only mono and stereo for now. Fend off draining calls to pfnStreamPlay (w/ cbBuf=0) to avoid assertion. bugref:9890

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