VirtualBox

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

Last change on this file since 88956 was 88928, checked in by vboxsync, 4 years ago
Audio/VaKit: Allow dynamic test backend selection via "[-bbackend]". bugref:10008
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.0 KB
Line 
1/* $Id: DrvHostAudioOss.cpp 88928 2021-05-07 14:25:30Z 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 pStreamOSS->fDraining = false;
464
465 int rc;
466 if (pStreamOSS->Cfg.enmDir == PDMAUDIODIR_IN)
467 rc = VINF_SUCCESS; /** @todo apparently nothing to do here? */
468 else
469 {
470 int fMask = PCM_ENABLE_OUTPUT;
471 if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0)
472 rc = VINF_SUCCESS;
473 else
474 {
475 LogRel(("OSS: Failed to enable output stream: %s (%d)\n", strerror(errno), errno));
476 rc = RTErrConvertFromErrno(errno);
477 }
478 }
479
480 LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName));
481 return rc;
482}
483
484
485/**
486 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
487 */
488static DECLCALLBACK(int) drvHostOssAudioHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
489{
490 RT_NOREF(pInterface);
491 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
492
493 int rc;
494 if (pStreamOSS->Cfg.enmDir == PDMAUDIODIR_IN)
495 rc = VINF_SUCCESS; /** @todo apparently nothing to do here? */
496 else if (pStreamOSS->fDraining)
497 {
498 LogFlow(("Ignoring stream disable because we're draining...\n"));
499 rc = VINF_SUCCESS; /** @todo apparently nothing to do here? */
500 }
501 else
502 {
503 /** @todo Official documentation says this isn't the right way to stop playback.
504 * It may work in some implementations but fail in all others... Suggest
505 * using SNDCTL_DSP_RESET / SNDCTL_DSP_HALT. */
506 int fMask = 0;
507 if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0)
508 rc = VINF_SUCCESS;
509 else
510 {
511 LogRel(("OSS: Failed to enable output stream: %s (%d)\n", strerror(errno), errno));
512 rc = RTErrConvertFromErrno(errno);
513 }
514 }
515 LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName));
516 return rc;
517}
518
519
520/**
521 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
522 */
523static DECLCALLBACK(int) drvHostOssAudioHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
524{
525 return drvHostOssAudioHA_StreamDisable(pInterface, pStream);
526}
527
528
529/**
530 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
531 */
532static DECLCALLBACK(int) drvHostOssAudioHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
533{
534 return drvHostOssAudioHA_StreamEnable(pInterface, pStream);
535}
536
537
538/**
539 * @callback_method_impl{FNRTTHREAD,
540 * Thread for calling SNDCTL_DSP_SYNC (blocking) on an output stream.}
541 */
542static DECLCALLBACK(int) drvHostOssAudioDrainThread(RTTHREAD ThreadSelf, void *pvUser)
543{
544 RT_NOREF(ThreadSelf);
545 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pvUser;
546 int rc;
547
548 /* Make it blocking (for Linux). */
549 int fOrgFlags = fcntl(pStreamOSS->hFile, F_GETFL, 0);
550 LogFunc(("F_GETFL -> %#x\n", fOrgFlags));
551 Assert(fOrgFlags != -1);
552 if (fOrgFlags != -1)
553 {
554 rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags & ~O_NONBLOCK);
555 AssertStmt(rc != -1, fOrgFlags = -1);
556 }
557
558 /* Drain it. */
559 LogFunc(("Calling SNDCTL_DSP_SYNC now...\n"));
560 rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SYNC, NULL);
561 LogFunc(("SNDCTL_DSP_SYNC returned %d / errno=%d\n", rc, errno)); RT_NOREF(rc);
562
563 /* Re-enable non-blocking mode and disable it. */
564 if (fOrgFlags != -1)
565 {
566 rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags);
567 Assert(rc != -1);
568
569 int fMask = 0;
570 rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask);
571 Assert(rc >= 0);
572
573 pStreamOSS->fDraining = false;
574 LogFunc(("Restored non-block mode and cleared the trigger mask\n"));
575 }
576
577 return VINF_SUCCESS;
578}
579
580
581/**
582 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
583 */
584static DECLCALLBACK(int) drvHostOssAudioHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
585{
586 PDRVHOSTOSSAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTOSSAUDIO, IHostAudio);
587 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
588 AssertReturn(pStreamOSS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER);
589
590 pStreamOSS->fDraining = true;
591
592 /*
593 * Because the SNDCTL_DSP_SYNC call is blocking on real OSS,
594 * we kick off a thread to deal with it as we're probably on EMT
595 * and cannot block for extended periods.
596 */
597 if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
598 {
599 int rc = RTThreadWait(pStreamOSS->hThreadDrain, 0, NULL);
600 if (RT_SUCCESS(rc))
601 {
602 pStreamOSS->hThreadDrain = NIL_RTTHREAD;
603 LogFunc(("Cleaned up stale thread handle.\n"));
604 }
605 else
606 {
607 LogFunc(("Drain thread already running (%Rrc).\n", rc));
608 AssertMsg(rc == VERR_TIMEOUT, ("%Rrc\n", rc));
609 return rc == VERR_TIMEOUT ? VINF_SUCCESS : rc;
610 }
611 }
612
613 int rc = RTThreadCreateF(&pStreamOSS->hThreadDrain, drvHostOssAudioDrainThread, pStreamOSS, 0,
614 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ossdrai%u", pThis->pDrvIns->iInstance);
615 LogFunc(("Started drain thread: %Rrc\n", rc));
616 AssertRCReturn(rc, rc);
617
618 return VINF_SUCCESS;
619}
620
621
622
623/**
624 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
625 */
626static DECLCALLBACK(int) drvHostOssAudioHA_StreamControl(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
627 PDMAUDIOSTREAMCMD enmStreamCmd)
628{
629 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
630 * replacing it with individual StreamXxxx methods. That would save us
631 * potentally huge switches and more easily see which drivers implement
632 * which operations (grep for pfnStreamXxxx). */
633 switch (enmStreamCmd)
634 {
635 case PDMAUDIOSTREAMCMD_ENABLE:
636 return drvHostOssAudioHA_StreamEnable(pInterface, pStream);
637 case PDMAUDIOSTREAMCMD_DISABLE:
638 return drvHostOssAudioHA_StreamDisable(pInterface, pStream);
639 case PDMAUDIOSTREAMCMD_PAUSE:
640 return drvHostOssAudioHA_StreamPause(pInterface, pStream);
641 case PDMAUDIOSTREAMCMD_RESUME:
642 return drvHostOssAudioHA_StreamResume(pInterface, pStream);
643 case PDMAUDIOSTREAMCMD_DRAIN:
644 return drvHostOssAudioHA_StreamDrain(pInterface, pStream);
645 /** @todo the drain call for OSS is SNDCTL_DSP_SYNC, however in the non-ALSA
646 * implementation of OSS it is probably blocking. Also, it comes with
647 * caveats about clicks and silence... */
648 case PDMAUDIOSTREAMCMD_END:
649 case PDMAUDIOSTREAMCMD_32BIT_HACK:
650 case PDMAUDIOSTREAMCMD_INVALID:
651 /* no default*/
652 break;
653 }
654 return VERR_NOT_SUPPORTED;
655}
656
657
658/**
659 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
660 */
661static DECLCALLBACK(uint32_t) drvHostOssAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
662{
663 RT_NOREF(pInterface, pStream);
664 Log4Func(("returns UINT32_MAX\n"));
665 return UINT32_MAX;
666}
667
668
669/**
670 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
671 */
672static DECLCALLBACK(uint32_t) drvHostOssAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
673{
674 RT_NOREF(pInterface);
675 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
676 AssertPtr(pStreamOSS);
677
678 /*
679 * Note! This logic was found in StreamPlay and corrected a little.
680 *
681 * The logic here must match what StreamPlay does.
682 */
683 audio_buf_info BufInfo = { 0, 0, 0, 0 };
684 int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo);
685 AssertMsgReturn(rc2 >= 0, ("SNDCTL_DSP_GETOSPACE failed: %s (%d)\n", strerror(errno), errno), 0);
686
687#if 0 /** @todo we could return BufInfo.bytes here iff StreamPlay didn't use the fragmented approach */
688 /** @todo r=bird: WTF do we make a fuss over BufInfo.bytes for when we don't
689 * even use it?!? */
690 AssertLogRelMsgReturn(BufInfo.bytes >= 0, ("OSS: Warning: Invalid available size: %d\n", BufInfo.bytes), VERR_INTERNAL_ERROR_3);
691 if ((unsigned)BufInfo.bytes > cbBuf)
692 {
693 LogRel2(("OSS: Warning: Too big output size (%d > %RU32), limiting to %RU32\n", BufInfo.bytes, cbBuf, cbBuf));
694 BufInfo.bytes = cbBuf;
695 /* Keep going. */
696 }
697#endif
698
699 uint32_t cbRet = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
700 Log4Func(("returns %#x (%u)\n", cbRet, cbRet));
701 return cbRet;
702}
703
704
705/**
706 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
707 */
708static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostOssAudioHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
709 PPDMAUDIOBACKENDSTREAM pStream)
710{
711 RT_NOREF(pInterface);
712 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
713 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
714}
715
716
717/**
718 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
719 */
720static DECLCALLBACK(int) drvHostOssAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
721 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
722{
723 RT_NOREF(pInterface);
724 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
725 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
726
727 /*
728 * Figure out now much to write.
729 */
730 audio_buf_info BufInfo;
731 int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo);
732 AssertLogRelMsgReturn(rc2 >= 0, ("OSS: Failed to retrieve current playback buffer: %s (%d)\n", strerror(errno), errno),
733 RTErrConvertFromErrno(errno));
734
735#if 0 /** @todo r=bird: WTF do we make a fuss over BufInfo.bytes for when we don't even use it?!? */
736 AssertLogRelMsgReturn(BufInfo.bytes >= 0, ("OSS: Warning: Invalid available size: %d\n", BufInfo.bytes), VERR_INTERNAL_ERROR_3);
737 if ((unsigned)BufInfo.bytes > cbBuf)
738 {
739 LogRel2(("OSS: Warning: Too big output size (%d > %RU32), limiting to %RU32\n", BufInfo.bytes, cbBuf, cbBuf));
740 BufInfo.bytes = cbBuf;
741 /* Keep going. */
742 }
743#endif
744 uint32_t cbToWrite = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
745 cbToWrite = RT_MIN(cbToWrite, cbBuf);
746 Log3Func(("@%#RX64 cbBuf=%#x BufInfo: fragments=%#x fragstotal=%#x fragsize=%#x bytes=%#x %s cbToWrite=%#x\n",
747 pStreamOSS->offInternal, cbBuf, BufInfo.fragments, BufInfo.fragstotal, BufInfo.fragsize, BufInfo.bytes,
748 pStreamOSS->Cfg.szName, cbToWrite));
749
750 /*
751 * Write.
752 */
753 uint8_t const *pbBuf = (uint8_t const *)pvBuf;
754 uint32_t cbChunk = cbToWrite;
755 uint32_t offChunk = 0;
756 while (cbChunk > 0)
757 {
758 ssize_t cbWritten = write(pStreamOSS->hFile, &pbBuf[offChunk], RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment));
759 if (cbWritten > 0)
760 {
761 AssertLogRelMsg(!(cbWritten & pStreamOSS->uAlign),
762 ("OSS: Misaligned write (written %#zx, alignment %#x)\n", cbWritten, pStreamOSS->uAlign));
763
764 Assert((uint32_t)cbWritten <= cbChunk);
765 offChunk += (uint32_t)cbWritten;
766 cbChunk -= (uint32_t)cbWritten;
767 pStreamOSS->offInternal += cbWritten;
768 }
769 else if (cbWritten == 0)
770 {
771 LogFunc(("@%#RX64 write(%#x) returned zeroed (previously wrote %#x bytes)!\n",
772 pStreamOSS->offInternal, RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment), cbWritten));
773 break;
774 }
775 else
776 {
777 LogRel(("OSS: Failed writing output data: %s (%d)\n", strerror(errno), errno));
778 return RTErrConvertFromErrno(errno);
779 }
780 }
781
782 *pcbWritten = offChunk;
783 return VINF_SUCCESS;
784}
785
786
787/**
788 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
789 */
790static DECLCALLBACK(int) drvHostOssAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
791 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
792{
793 RT_NOREF(pInterface);
794 POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
795 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
796 Log3Func(("@%#RX64 cbBuf=%#x %s\n", pStreamOSS->offInternal, cbBuf, pStreamOSS->Cfg.szName));
797
798 size_t cbToRead = cbBuf;
799 uint8_t * const pbDst = (uint8_t *)pvBuf;
800 size_t offWrite = 0;
801 while (cbToRead > 0)
802 {
803 ssize_t cbRead = read(pStreamOSS->hFile, &pbDst[offWrite], cbToRead);
804 if (cbRead)
805 {
806 LogFlowFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu\n", cbRead, offWrite, cbToRead));
807 Assert((ssize_t)cbToRead >= cbRead);
808 cbToRead -= cbRead;
809 offWrite += cbRead;
810 pStreamOSS->offInternal += cbRead;
811 }
812 else
813 {
814 LogFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu errno=%d\n", cbRead, offWrite, cbToRead, errno));
815
816 /* Don't complain about errors if we've retrieved some audio data already. */
817 if (cbRead < 0 && offWrite == 0 && errno != EINTR && errno != EAGAIN)
818 {
819 AssertStmt(errno != 0, errno = EACCES);
820 int rc = RTErrConvertFromErrno(errno);
821 LogFunc(("Failed to read %zu input frames, errno=%d rc=%Rrc\n", cbToRead, errno, rc));
822 return rc;
823 }
824 break;
825 }
826 }
827
828 *pcbRead = offWrite;
829 return VINF_SUCCESS;
830}
831
832
833
834/**
835 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
836 */
837static DECLCALLBACK(void *) drvHostOSSAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
838{
839 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
840 PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO);
841
842 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
843 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
844
845 return NULL;
846}
847
848/**
849 * Constructs an OSS audio driver instance.
850 *
851 * @copydoc FNPDMDRVCONSTRUCT
852 */
853static DECLCALLBACK(int) drvHostOSSAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
854{
855 RT_NOREF(pCfg, fFlags);
856 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
857 PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO);
858 LogRel(("Audio: Initializing OSS driver\n"));
859
860 /*
861 * Init the static parts.
862 */
863 pThis->pDrvIns = pDrvIns;
864 /* IBase */
865 pDrvIns->IBase.pfnQueryInterface = drvHostOSSAudioQueryInterface;
866 /* IHostAudio */
867 pThis->IHostAudio.pfnGetConfig = drvHostOssAudioHA_GetConfig;
868 pThis->IHostAudio.pfnGetDevices = NULL;
869 pThis->IHostAudio.pfnGetStatus = drvHostOssAudioHA_GetStatus;
870 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
871 pThis->IHostAudio.pfnStreamConfigHint = NULL;
872 pThis->IHostAudio.pfnStreamCreate = drvHostOssAudioHA_StreamCreate;
873 pThis->IHostAudio.pfnStreamInitAsync = NULL;
874 pThis->IHostAudio.pfnStreamDestroy = drvHostOssAudioHA_StreamDestroy;
875 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
876 pThis->IHostAudio.pfnStreamControl = drvHostOssAudioHA_StreamControl;
877 pThis->IHostAudio.pfnStreamGetReadable = drvHostOssAudioHA_StreamGetReadable;
878 pThis->IHostAudio.pfnStreamGetWritable = drvHostOssAudioHA_StreamGetWritable;
879 pThis->IHostAudio.pfnStreamGetPending = NULL;
880 pThis->IHostAudio.pfnStreamGetState = drvHostOssAudioHA_StreamGetState;
881 pThis->IHostAudio.pfnStreamPlay = drvHostOssAudioHA_StreamPlay;
882 pThis->IHostAudio.pfnStreamCapture = drvHostOssAudioHA_StreamCapture;
883
884 return VINF_SUCCESS;
885}
886
887#ifndef VBOX_AUDIO_VKAT
888/**
889 * Char driver registration record.
890 */
891const PDMDRVREG g_DrvHostOSSAudio =
892{
893 /* u32Version */
894 PDM_DRVREG_VERSION,
895 /* szName */
896 "OSSAudio",
897 /* szRCMod */
898 "",
899 /* szR0Mod */
900 "",
901 /* pszDescription */
902 "OSS audio host driver",
903 /* fFlags */
904 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
905 /* fClass. */
906 PDM_DRVREG_CLASS_AUDIO,
907 /* cMaxInstances */
908 ~0U,
909 /* cbInstance */
910 sizeof(DRVHOSTOSSAUDIO),
911 /* pfnConstruct */
912 drvHostOSSAudioConstruct,
913 /* pfnDestruct */
914 NULL,
915 /* pfnRelocate */
916 NULL,
917 /* pfnIOCtl */
918 NULL,
919 /* pfnPowerOn */
920 NULL,
921 /* pfnReset */
922 NULL,
923 /* pfnSuspend */
924 NULL,
925 /* pfnResume */
926 NULL,
927 /* pfnAttach */
928 NULL,
929 /* pfnDetach */
930 NULL,
931 /* pfnPowerOff */
932 NULL,
933 /* pfnSoftReset */
934 NULL,
935 /* u32EndVersion */
936 PDM_DRVREG_VERSION
937};
938#else
939const PDMDRVREG g_DrvVKATOss =
940{
941 /* szName */
942 "OSSAudio",
943 /* cbInstance */
944 sizeof(DRVHOSTOSSAUDIO),
945 drvHostOSSAudioConstruct,
946 /* pfnDestruct */
947 NULL
948};
949#endif
950
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