VirtualBox

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

Last change on this file since 94368 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.2 KB
Line 
1/* $Id: DrvHostAudioOss.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * Host audio driver - OSS (Open Sound System).
4 */
5
6/*
7 * Copyright (C) 2014-2022 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 audio stream configuration.
56 */
57typedef struct DRVHSTAUDOSSSTREAMCFG
58{
59 PDMAUDIOPCMPROPS Props;
60 uint16_t cFragments;
61 /** The log2 of cbFragment. */
62 uint16_t cbFragmentLog2;
63 uint32_t cbFragment;
64} DRVHSTAUDOSSSTREAMCFG;
65/** Pointer to an OSS audio stream configuration. */
66typedef DRVHSTAUDOSSSTREAMCFG *PDRVHSTAUDOSSSTREAMCFG;
67
68/**
69 * OSS audio stream.
70 */
71typedef struct DRVHSTAUDOSSSTREAM
72{
73 /** Common part. */
74 PDMAUDIOBACKENDSTREAM Core;
75 /** The file descriptor. */
76 int hFile;
77 /** Buffer alignment. */
78 uint8_t uAlign;
79 /** Set if we're draining the stream (output only). */
80 bool fDraining;
81 /** Internal stream byte offset. */
82 uint64_t offInternal;
83 /** The stream's acquired configuration. */
84 PDMAUDIOSTREAMCFG Cfg;
85 /** The acquired OSS configuration. */
86 DRVHSTAUDOSSSTREAMCFG OssCfg;
87 /** Handle to the thread draining output streams. */
88 RTTHREAD hThreadDrain;
89} DRVHSTAUDOSSSTREAM;
90/** Pointer to an OSS audio stream. */
91typedef DRVHSTAUDOSSSTREAM *PDRVHSTAUDOSSSTREAM;
92
93/**
94 * OSS host audio driver instance data.
95 * @implements PDMIAUDIOCONNECTOR
96 */
97typedef struct DRVHSTAUDOSS
98{
99 /** Pointer to the driver instance structure. */
100 PPDMDRVINS pDrvIns;
101 /** Pointer to host audio interface. */
102 PDMIHOSTAUDIO IHostAudio;
103 /** Error count for not flooding the release log.
104 * UINT32_MAX for unlimited logging. */
105 uint32_t cLogErrors;
106} DRVHSTAUDOSS;
107/** Pointer to the instance data for an OSS host audio driver. */
108typedef DRVHSTAUDOSS *PDRVHSTAUDOSS;
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 drvHstAudOssToPdmAudioProps(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 drvHstAudOssStreamClose(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) drvHstAudOssHA_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(DRVHSTAUDOSSSTREAM);
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 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
246 */
247static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudOssHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
248{
249 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
250 RT_NOREF(enmDir);
251
252 return PDMAUDIOBACKENDSTS_RUNNING;
253}
254
255
256static int drvHstAudOssStreamConfigure(int hFile, bool fInput, PDRVHSTAUDOSSSTREAMCFG pOSSReq, PDRVHSTAUDOSSSTREAMCFG pOSSAcq)
257{
258 /*
259 * Format.
260 */
261 int iFormat;
262 switch (PDMAudioPropsSampleSize(&pOSSReq->Props))
263 {
264 case 1:
265 iFormat = pOSSReq->Props.fSigned ? AFMT_S8 : AFMT_U8;
266 break;
267
268 case 2:
269 if (PDMAudioPropsIsLittleEndian(&pOSSReq->Props))
270 iFormat = pOSSReq->Props.fSigned ? AFMT_S16_LE : AFMT_U16_LE;
271 else
272 iFormat = pOSSReq->Props.fSigned ? AFMT_S16_BE : AFMT_U16_BE;
273 break;
274
275 default:
276 LogRel2(("OSS: Unsupported sample size: %u\n", PDMAudioPropsSampleSize(&pOSSReq->Props)));
277 return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
278 }
279 AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SAMPLESIZE, &iFormat) >= 0,
280 ("OSS: Failed to set audio format to %d: %s (%d)\n", iFormat, strerror(errno), errno),
281 RTErrConvertFromErrno(errno));
282
283 /*
284 * Channel count.
285 */
286 int cChannels = PDMAudioPropsChannels(&pOSSReq->Props);
287 AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_CHANNELS, &cChannels) >= 0,
288 ("OSS: Failed to set number of audio channels (%RU8): %s (%d)\n",
289 PDMAudioPropsChannels(&pOSSReq->Props), strerror(errno), errno),
290 RTErrConvertFromErrno(errno));
291
292 /*
293 * Frequency.
294 */
295 int iFrequenc = pOSSReq->Props.uHz;
296 AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SPEED, &iFrequenc) >= 0,
297 ("OSS: Failed to set audio frequency to %d Hz: %s (%d)\n", pOSSReq->Props.uHz, strerror(errno), errno),
298 RTErrConvertFromErrno(errno));
299
300 /*
301 * Set fragment size and count.
302 */
303 LogRel2(("OSS: Requested %RU16 %s fragments, %RU32 bytes each\n",
304 pOSSReq->cFragments, fInput ? "input" : "output", pOSSReq->cbFragment));
305
306 int mmmmssss = (pOSSReq->cFragments << 16) | pOSSReq->cbFragmentLog2;
307 AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SETFRAGMENT, &mmmmssss) >= 0,
308 ("OSS: Failed to set %RU16 fragments to %RU32 bytes each: %s (%d)\n",
309 pOSSReq->cFragments, pOSSReq->cbFragment, strerror(errno), errno),
310 RTErrConvertFromErrno(errno));
311
312 /*
313 * Get parameters and popuplate pOSSAcq.
314 */
315 audio_buf_info BufInfo = { 0, 0, 0, 0 };
316 AssertLogRelMsgReturn(ioctl(hFile, fInput ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &BufInfo) >= 0,
317 ("OSS: Failed to retrieve %s buffer length: %s (%d)\n",
318 fInput ? "input" : "output", strerror(errno), errno),
319 RTErrConvertFromErrno(errno));
320
321 int rc = drvHstAudOssToPdmAudioProps(&pOSSAcq->Props, iFormat, cChannels, iFrequenc);
322 if (RT_SUCCESS(rc))
323 {
324 pOSSAcq->cFragments = BufInfo.fragstotal;
325 pOSSAcq->cbFragment = BufInfo.fragsize;
326 pOSSAcq->cbFragmentLog2 = ASMBitFirstSetU32(BufInfo.fragsize) - 1;
327 Assert(RT_BIT_32(pOSSAcq->cbFragmentLog2) == pOSSAcq->cbFragment);
328
329 LogRel2(("OSS: Got %RU16 %s fragments, %RU32 bytes each\n",
330 pOSSAcq->cFragments, fInput ? "input" : "output", pOSSAcq->cbFragment));
331 }
332
333 return rc;
334}
335
336
337/**
338 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
339 */
340static DECLCALLBACK(int) drvHstAudOssHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
341 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
342{
343 AssertPtr(pInterface); RT_NOREF(pInterface);
344 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
345 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
346 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
347 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
348
349 pStreamOSS->hThreadDrain = NIL_RTTHREAD;
350
351 /*
352 * Open the device
353 */
354 int rc;
355 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
356 pStreamOSS->hFile = open(g_szPathInputDev, O_RDONLY);
357 else
358 pStreamOSS->hFile = open(g_szPathOutputDev, O_WRONLY);
359 if (pStreamOSS->hFile >= 0)
360 {
361 /*
362 * Configure it.
363 *
364 * Note! We limit the output channels to mono or stereo for now just
365 * to keep things simple and avoid wasting time here. If the
366 * channel count isn't a power of two, our code below trips up
367 * on the fragment size. We'd also need to try report/get
368 * channel mappings and whatnot.
369 */
370 DRVHSTAUDOSSSTREAMCFG ReqOssCfg;
371 RT_ZERO(ReqOssCfg);
372
373 memcpy(&ReqOssCfg.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS));
374 if (PDMAudioPropsChannels(&ReqOssCfg.Props) > 2)
375 {
376 LogRel2(("OSS: Limiting output to two channels, requested %u.\n", PDMAudioPropsChannels(&ReqOssCfg.Props) ));
377 PDMAudioPropsSetChannels(&ReqOssCfg.Props, 2);
378 }
379
380 ReqOssCfg.cbFragmentLog2 = 12;
381 ReqOssCfg.cbFragment = RT_BIT_32(ReqOssCfg.cbFragmentLog2);
382 uint32_t const cbBuffer = PDMAudioPropsFramesToBytes(&ReqOssCfg.Props, pCfgReq->Backend.cFramesBufferSize);
383 ReqOssCfg.cFragments = cbBuffer >> ReqOssCfg.cbFragmentLog2;
384 AssertLogRelStmt(cbBuffer < ((uint32_t)0x7ffe << ReqOssCfg.cbFragmentLog2), ReqOssCfg.cFragments = 0x7ffe);
385
386 rc = drvHstAudOssStreamConfigure(pStreamOSS->hFile, pCfgReq->enmDir == PDMAUDIODIR_IN, &ReqOssCfg, &pStreamOSS->OssCfg);
387 if (RT_SUCCESS(rc))
388 {
389 pStreamOSS->uAlign = 0; /** @todo r=bird: Where did the correct assignment of this go? */
390
391 /*
392 * Complete the stream structure and fill in the pCfgAcq bits.
393 */
394 if ((pStreamOSS->OssCfg.cFragments * pStreamOSS->OssCfg.cbFragment) & pStreamOSS->uAlign)
395 LogRel(("OSS: Warning: Misaligned playback buffer: Size = %zu, Alignment = %u\n",
396 pStreamOSS->OssCfg.cFragments * pStreamOSS->OssCfg.cbFragment, pStreamOSS->uAlign + 1));
397
398 memcpy(&pCfgAcq->Props, &pStreamOSS->OssCfg.Props, sizeof(PDMAUDIOPCMPROPS));
399 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamOSS->OssCfg.cbFragment);
400 pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * pStreamOSS->OssCfg.cFragments;
401 pCfgAcq->Backend.cFramesPreBuffering = (uint64_t)pCfgReq->Backend.cFramesPreBuffering
402 * pCfgAcq->Backend.cFramesBufferSize
403 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
404
405 /*
406 * Copy the stream config and we're done!
407 */
408 PDMAudioStrmCfgCopy(&pStreamOSS->Cfg, pCfgAcq);
409 return VINF_SUCCESS;
410 }
411 drvHstAudOssStreamClose(&pStreamOSS->hFile);
412 }
413 else
414 {
415 rc = RTErrConvertFromErrno(errno);
416 LogRel(("OSS: Failed to open '%s': %s (%d) / %Rrc\n",
417 pCfgReq->enmDir == PDMAUDIODIR_IN ? g_szPathInputDev : g_szPathOutputDev, strerror(errno), errno, rc));
418 }
419 return rc;
420}
421
422
423/**
424 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
425 */
426static DECLCALLBACK(int) drvHstAudOssHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
427{
428 RT_NOREF(pInterface, fImmediate);
429 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
430 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
431
432 drvHstAudOssStreamClose(&pStreamOSS->hFile);
433
434 if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
435 {
436 int rc = RTThreadWait(pStreamOSS->hThreadDrain, 1, NULL);
437 AssertRC(rc);
438 pStreamOSS->hThreadDrain = NIL_RTTHREAD;
439 }
440
441 return VINF_SUCCESS;
442}
443
444
445/**
446 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
447 */
448static DECLCALLBACK(int) drvHstAudOssHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
449{
450 RT_NOREF(pInterface);
451 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
452 int rc;
453
454 /*
455 * This is most probably untested...
456 */
457 if (pStreamOSS->fDraining)
458 {
459 LogFlowFunc(("Still draining...\n"));
460 rc = RTThreadWait(pStreamOSS->hThreadDrain, 0 /*ms*/, NULL);
461 if (RT_FAILURE(rc))
462 {
463 LogFlowFunc(("Resetting...\n"));
464 ioctl(pStreamOSS->hFile, SNDCTL_DSP_RESET, NULL);
465 rc = RTThreadWait(pStreamOSS->hThreadDrain, 0 /*ms*/, NULL);
466 if (RT_FAILURE(rc))
467 {
468 LogFlowFunc(("Poking...\n"));
469 RTThreadPoke(pStreamOSS->hThreadDrain);
470 rc = RTThreadWait(pStreamOSS->hThreadDrain, 1 /*ms*/, NULL);
471 }
472 }
473 if (RT_SUCCESS(rc))
474 {
475 LogFlowFunc(("Done draining.\n"));
476 pStreamOSS->hThreadDrain = NIL_RTTHREAD;
477 }
478 else
479 LogFlowFunc(("No, still draining...\n"));
480 pStreamOSS->fDraining = false;
481 }
482
483 /*
484 * Enable the stream.
485 */
486 int fMask = pStreamOSS->Cfg.enmDir == PDMAUDIODIR_IN ? PCM_ENABLE_INPUT : PCM_ENABLE_OUTPUT;
487 if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0)
488 rc = VINF_SUCCESS;
489 else
490 {
491 LogRel(("OSS: Failed to enable output stream: %s (%d)\n", strerror(errno), errno));
492 rc = RTErrConvertFromErrno(errno);
493 }
494
495 LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName));
496 return rc;
497}
498
499
500/**
501 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
502 */
503static DECLCALLBACK(int) drvHstAudOssHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
504{
505 RT_NOREF(pInterface);
506 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
507 LogFlowFunc(("Stream '%s'\n", pStreamOSS->Cfg.szName));
508 int rc;
509
510 /*
511 * If we're still draining, try kick the thread before we try disable the stream.
512 */
513 if (pStreamOSS->fDraining)
514 {
515 LogFlowFunc(("Trying to cancel draining...\n"));
516 if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
517 {
518 RTThreadPoke(pStreamOSS->hThreadDrain);
519 rc = RTThreadWait(pStreamOSS->hThreadDrain, 1 /*ms*/, NULL);
520 if (RT_SUCCESS(rc) || rc == VERR_INVALID_HANDLE)
521 pStreamOSS->fDraining = false;
522 else
523 LogFunc(("Failed to cancel draining (%Rrc)\n", rc));
524 }
525 else
526 {
527 LogFlowFunc(("Thread handle is NIL, so we can't be draining\n"));
528 pStreamOSS->fDraining = false;
529 }
530 }
531
532 /*
533 * The Official documentation says this isn't the right way to stop
534 * playback. It may work in some implementations but fail in all others...
535 * Suggest SNDCTL_DSP_RESET / SNDCTL_DSP_HALT.
536 *
537 * So, let's do both and see how that works out...
538 */
539 rc = VINF_SUCCESS;
540 int fMask = 0;
541 if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0)
542 LogFlowFunc(("SNDCTL_DSP_SETTRIGGER succeeded\n"));
543 else
544 {
545 LogRel(("OSS: Failed to clear triggers for stream '%s': %s (%d)\n", pStreamOSS->Cfg.szName, strerror(errno), errno));
546 rc = RTErrConvertFromErrno(errno);
547 }
548
549 if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_RESET, NULL) >= 0)
550 LogFlowFunc(("SNDCTL_DSP_RESET succeeded\n"));
551 else
552 {
553 LogRel(("OSS: Failed to reset stream '%s': %s (%d)\n", pStreamOSS->Cfg.szName, strerror(errno), errno));
554 rc = RTErrConvertFromErrno(errno);
555 }
556
557 LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName));
558 return rc;
559}
560
561
562/**
563 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
564 */
565static DECLCALLBACK(int) drvHstAudOssHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
566{
567 return drvHstAudOssHA_StreamDisable(pInterface, pStream);
568}
569
570
571/**
572 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
573 */
574static DECLCALLBACK(int) drvHstAudOssHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
575{
576 return drvHstAudOssHA_StreamEnable(pInterface, pStream);
577}
578
579
580/**
581 * @callback_method_impl{FNRTTHREAD,
582 * Thread for calling SNDCTL_DSP_SYNC (blocking) on an output stream.}
583 */
584static DECLCALLBACK(int) drvHstAudOssDrainThread(RTTHREAD ThreadSelf, void *pvUser)
585{
586 RT_NOREF(ThreadSelf);
587 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pvUser;
588 int rc;
589
590 /* Make it blocking (for Linux). */
591 int fOrgFlags = fcntl(pStreamOSS->hFile, F_GETFL, 0);
592 LogFunc(("F_GETFL -> %#x\n", fOrgFlags));
593 Assert(fOrgFlags != -1);
594 if (fOrgFlags != -1 && (fOrgFlags & O_NONBLOCK))
595 {
596 rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags & ~O_NONBLOCK);
597 AssertStmt(rc != -1, fOrgFlags = -1);
598 }
599
600 /* Drain it. */
601 LogFunc(("Calling SNDCTL_DSP_SYNC now...\n"));
602 rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SYNC, NULL);
603 LogFunc(("SNDCTL_DSP_SYNC returned %d / errno=%d\n", rc, errno)); RT_NOREF(rc);
604
605 /* Re-enable non-blocking mode and disable it. */
606 if (fOrgFlags != -1)
607 {
608 rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags);
609 Assert(rc != -1);
610
611 int fMask = 0;
612 rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask);
613 Assert(rc >= 0);
614
615 pStreamOSS->fDraining = false;
616 LogFunc(("Restored non-block mode and cleared the trigger mask\n"));
617 }
618
619 return VINF_SUCCESS;
620}
621
622
623/**
624 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
625 */
626static DECLCALLBACK(int) drvHstAudOssHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
627{
628 PDRVHSTAUDOSS pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDOSS, IHostAudio);
629 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
630 AssertReturn(pStreamOSS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER);
631
632 pStreamOSS->fDraining = true;
633
634 /*
635 * Because the SNDCTL_DSP_SYNC call is blocking on real OSS,
636 * we kick off a thread to deal with it as we're probably on EMT
637 * and cannot block for extended periods.
638 */
639 if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
640 {
641 int rc = RTThreadWait(pStreamOSS->hThreadDrain, 0, NULL);
642 if (RT_SUCCESS(rc))
643 {
644 pStreamOSS->hThreadDrain = NIL_RTTHREAD;
645 LogFunc(("Cleaned up stale thread handle.\n"));
646 }
647 else
648 {
649 LogFunc(("Drain thread already running (%Rrc).\n", rc));
650 AssertMsg(rc == VERR_TIMEOUT, ("%Rrc\n", rc));
651 return rc == VERR_TIMEOUT ? VINF_SUCCESS : rc;
652 }
653 }
654
655 int rc = RTThreadCreateF(&pStreamOSS->hThreadDrain, drvHstAudOssDrainThread, pStreamOSS, 0,
656 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ossdrai%u", pThis->pDrvIns->iInstance);
657 LogFunc(("Started drain thread: %Rrc\n", rc));
658 AssertRCReturn(rc, rc);
659
660 return VINF_SUCCESS;
661}
662
663
664/**
665 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
666 */
667static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudOssHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
668 PPDMAUDIOBACKENDSTREAM pStream)
669{
670 RT_NOREF(pInterface);
671 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
672 AssertPtrReturn(pStreamOSS, PDMHOSTAUDIOSTREAMSTATE_INVALID);
673 if (!pStreamOSS->fDraining)
674 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
675 return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
676}
677
678
679/**
680 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
681 */
682static DECLCALLBACK(uint32_t) drvHstAudOssHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
683{
684 RT_NOREF(pInterface);
685 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
686 AssertPtr(pStreamOSS);
687
688 /*
689 * The logic here must match what StreamPlay does.
690 *
691 * Note! We now use 'bytes' rather than the fragments * fragsize as we used
692 * to do (up to 2021), as these are documented as obsolete.
693 */
694 audio_buf_info BufInfo = { 0, 0, 0, 0 };
695 int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo);
696 AssertMsgReturn(rc2 >= 0, ("SNDCTL_DSP_GETOSPACE failed: %s (%d)\n", strerror(errno), errno), 0);
697
698 /* Try use the size. */
699 uint32_t cbRet;
700 uint32_t const cbBuf = pStreamOSS->OssCfg.cbFragment * pStreamOSS->OssCfg.cFragments;
701 if (BufInfo.bytes >= 0 && (unsigned)BufInfo.bytes <= cbBuf)
702 cbRet = BufInfo.bytes;
703 else
704 {
705 AssertMsgFailed(("Invalid available size: %d\n", BufInfo.bytes));
706 AssertMsgReturn(BufInfo.fragments >= 0, ("fragments: %d\n", BufInfo.fragments), 0);
707 AssertMsgReturn(BufInfo.fragsize >= 0, ("fragsize: %d\n", BufInfo.fragsize), 0);
708 cbRet = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
709 AssertMsgStmt(cbRet <= cbBuf, ("fragsize*fragments: %d, cbBuf=%#x\n", cbRet, cbBuf), 0);
710 }
711
712 Log4Func(("returns %#x (%u) [cbBuf=%#x]\n", cbRet, cbRet, cbBuf));
713 return cbRet;
714}
715
716
717/**
718 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
719 */
720static DECLCALLBACK(int) drvHstAudOssHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
721 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
722{
723 RT_NOREF(pInterface);
724 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
725 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
726
727 /*
728 * Return immediately if this is a draining service call.
729 *
730 * Otherwise the ioctl below will race the drain thread and sometimes fail,
731 * triggering annoying assertion and release logging.
732 */
733 if (cbBuf || !pStreamOSS->fDraining)
734 { /* likely */ }
735 else
736 {
737 *pcbWritten = 0;
738 return VINF_SUCCESS;
739 }
740
741 /*
742 * Figure out now much to write (same as drvHstAudOssHA_StreamGetWritable,
743 * must match exactly).
744 */
745 audio_buf_info BufInfo;
746 int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo);
747 AssertLogRelMsgReturn(rc2 >= 0, ("OSS: Failed to retrieve current playback buffer: %s (%d, hFile=%d, rc2=%d)\n",
748 strerror(errno), errno, pStreamOSS->hFile, rc2),
749 RTErrConvertFromErrno(errno));
750
751 uint32_t cbToWrite;
752 uint32_t const cbStreamBuf = pStreamOSS->OssCfg.cbFragment * pStreamOSS->OssCfg.cFragments;
753 if (BufInfo.bytes >= 0 && (unsigned)BufInfo.bytes <= cbStreamBuf)
754 cbToWrite = BufInfo.bytes;
755 else
756 {
757 AssertMsgFailed(("Invalid available size: %d\n", BufInfo.bytes));
758 AssertMsgReturn(BufInfo.fragments >= 0, ("fragments: %d\n", BufInfo.fragments), 0);
759 AssertMsgReturn(BufInfo.fragsize >= 0, ("fragsize: %d\n", BufInfo.fragsize), 0);
760 cbToWrite = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
761 AssertMsgStmt(cbToWrite <= cbStreamBuf, ("fragsize*fragments: %d, cbStreamBuf=%#x\n", cbToWrite, cbStreamBuf), 0);
762 }
763
764 cbToWrite = RT_MIN(cbToWrite, cbBuf);
765 Log3Func(("@%#RX64 cbBuf=%#x BufInfo: fragments=%#x fragstotal=%#x fragsize=%#x bytes=%#x %s cbToWrite=%#x\n",
766 pStreamOSS->offInternal, cbBuf, BufInfo.fragments, BufInfo.fragstotal, BufInfo.fragsize, BufInfo.bytes,
767 pStreamOSS->Cfg.szName, cbToWrite));
768
769 /*
770 * Write.
771 */
772 uint8_t const *pbBuf = (uint8_t const *)pvBuf;
773 uint32_t cbChunk = cbToWrite;
774 uint32_t offChunk = 0;
775 while (cbChunk > 0)
776 {
777 ssize_t cbWritten = write(pStreamOSS->hFile, &pbBuf[offChunk], RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment));
778 if (cbWritten > 0)
779 {
780 AssertLogRelMsg(!(cbWritten & pStreamOSS->uAlign),
781 ("OSS: Misaligned write (written %#zx, alignment %#x)\n", cbWritten, pStreamOSS->uAlign));
782
783 Assert((uint32_t)cbWritten <= cbChunk);
784 offChunk += (uint32_t)cbWritten;
785 cbChunk -= (uint32_t)cbWritten;
786 pStreamOSS->offInternal += cbWritten;
787 }
788 else if (cbWritten == 0)
789 {
790 LogFunc(("@%#RX64 write(%#x) returned zeroed (previously wrote %#x bytes)!\n",
791 pStreamOSS->offInternal, RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment), cbWritten));
792 break;
793 }
794 else
795 {
796 LogRel(("OSS: Failed writing output data: %s (%d)\n", strerror(errno), errno));
797 return RTErrConvertFromErrno(errno);
798 }
799 }
800
801 *pcbWritten = offChunk;
802 return VINF_SUCCESS;
803}
804
805
806/**
807 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
808 */
809static DECLCALLBACK(uint32_t) drvHstAudOssHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
810{
811 RT_NOREF(pInterface);
812 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
813 AssertPtr(pStreamOSS);
814
815 /*
816 * Use SNDCTL_DSP_GETISPACE to see how much we can read.
817 *
818 * Note! We now use 'bytes' rather than the fragments * fragsize as we used
819 * to do (up to 2021), as these are documented as obsolete.
820 */
821 audio_buf_info BufInfo = { 0, 0, 0, 0 };
822 int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETISPACE, &BufInfo);
823 AssertMsgReturn(rc2 >= 0, ("SNDCTL_DSP_GETISPACE failed: %s (%d)\n", strerror(errno), errno), 0);
824
825 uint32_t cbRet;
826 uint32_t const cbBuf = pStreamOSS->OssCfg.cbFragment * pStreamOSS->OssCfg.cFragments;
827 if (BufInfo.bytes >= 0 && (unsigned)BufInfo.bytes <= cbBuf)
828 cbRet = BufInfo.bytes;
829 else
830 {
831 AssertMsgFailed(("Invalid available size: %d\n", BufInfo.bytes));
832 AssertMsgReturn(BufInfo.fragments >= 0, ("fragments: %d\n", BufInfo.fragments), 0);
833 AssertMsgReturn(BufInfo.fragsize >= 0, ("fragsize: %d\n", BufInfo.fragsize), 0);
834 cbRet = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
835 AssertMsgStmt(cbRet <= cbBuf, ("fragsize*fragments: %d, cbBuf=%#x\n", cbRet, cbBuf), 0);
836 }
837
838 /*
839 * HACK ALERT! To force the stream to start recording, we read a frame
840 * here if we get back that there are zero bytes available
841 * and we're at the start of the stream. (We cannot just
842 * return a frame size, we have to read it, as pre-buffering
843 * would prevent it from being read.)
844 */
845 if (BufInfo.bytes > 0 || pStreamOSS->offInternal != 0)
846 { /* likely */ }
847 else
848 {
849 uint32_t cbToRead = PDMAudioPropsFramesToBytes(&pStreamOSS->Cfg.Props, 1);
850 uint8_t abFrame[256];
851 Assert(cbToRead < sizeof(abFrame));
852 ssize_t cbRead = read(pStreamOSS->hFile, abFrame, cbToRead);
853 RT_NOREF(cbRead);
854 LogFunc(("Dummy read for '%s' returns %zd (errno=%d)\n", pStreamOSS->Cfg.szName, cbRead, errno));
855 }
856
857 Log4Func(("returns %#x (%u) [cbBuf=%#x]\n", cbRet, cbRet, cbBuf));
858 return cbRet;
859}
860
861
862/**
863 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
864 */
865static DECLCALLBACK(int) drvHstAudOssHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
866 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
867{
868 RT_NOREF(pInterface);
869 PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
870 AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
871 Log3Func(("@%#RX64 cbBuf=%#x %s\n", pStreamOSS->offInternal, cbBuf, pStreamOSS->Cfg.szName));
872
873 size_t cbToRead = cbBuf;
874 uint8_t * const pbDst = (uint8_t *)pvBuf;
875 size_t offWrite = 0;
876 while (cbToRead > 0)
877 {
878 ssize_t cbRead = read(pStreamOSS->hFile, &pbDst[offWrite], cbToRead);
879 if (cbRead > 0)
880 {
881 LogFlowFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu\n", cbRead, offWrite, cbToRead));
882 Assert((ssize_t)cbToRead >= cbRead);
883 cbToRead -= cbRead;
884 offWrite += cbRead;
885 pStreamOSS->offInternal += cbRead;
886 }
887 else
888 {
889 LogFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu errno=%d\n", cbRead, offWrite, cbToRead, errno));
890
891 /* Don't complain about errors if we've retrieved some audio data already. */
892 if (cbRead < 0 && offWrite == 0 && errno != EINTR && errno != EAGAIN)
893 {
894 AssertStmt(errno != 0, errno = EACCES);
895 int rc = RTErrConvertFromErrno(errno);
896 LogFunc(("Failed to read %zu input frames, errno=%d rc=%Rrc\n", cbToRead, errno, rc));
897 return rc;
898 }
899 break;
900 }
901 }
902
903 *pcbRead = offWrite;
904 return VINF_SUCCESS;
905}
906
907
908/**
909 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
910 */
911static DECLCALLBACK(void *) drvHstAudOssQueryInterface(PPDMIBASE pInterface, const char *pszIID)
912{
913 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
914 PDRVHSTAUDOSS pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDOSS);
915
916 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
917 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
918
919 return NULL;
920}
921
922
923/**
924 * @interface_method_impl{PDMDRVREG,pfnConstruct,
925 * Constructs an OSS audio driver instance.}
926 */
927static DECLCALLBACK(int) drvHstAudOssConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
928{
929 RT_NOREF(pCfg, fFlags);
930 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
931 PDRVHSTAUDOSS pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDOSS);
932 LogRel(("Audio: Initializing OSS driver\n"));
933
934 /*
935 * Init the static parts.
936 */
937 pThis->pDrvIns = pDrvIns;
938 /* IBase */
939 pDrvIns->IBase.pfnQueryInterface = drvHstAudOssQueryInterface;
940 /* IHostAudio */
941 pThis->IHostAudio.pfnGetConfig = drvHstAudOssHA_GetConfig;
942 pThis->IHostAudio.pfnGetDevices = NULL;
943 pThis->IHostAudio.pfnSetDevice = NULL;
944 pThis->IHostAudio.pfnGetStatus = drvHstAudOssHA_GetStatus;
945 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
946 pThis->IHostAudio.pfnStreamConfigHint = NULL;
947 pThis->IHostAudio.pfnStreamCreate = drvHstAudOssHA_StreamCreate;
948 pThis->IHostAudio.pfnStreamInitAsync = NULL;
949 pThis->IHostAudio.pfnStreamDestroy = drvHstAudOssHA_StreamDestroy;
950 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
951 pThis->IHostAudio.pfnStreamEnable = drvHstAudOssHA_StreamEnable;
952 pThis->IHostAudio.pfnStreamDisable = drvHstAudOssHA_StreamDisable;
953 pThis->IHostAudio.pfnStreamPause = drvHstAudOssHA_StreamPause;
954 pThis->IHostAudio.pfnStreamResume = drvHstAudOssHA_StreamResume;
955 pThis->IHostAudio.pfnStreamDrain = drvHstAudOssHA_StreamDrain;
956 pThis->IHostAudio.pfnStreamGetState = drvHstAudOssHA_StreamGetState;
957 pThis->IHostAudio.pfnStreamGetPending = NULL;
958 pThis->IHostAudio.pfnStreamGetWritable = drvHstAudOssHA_StreamGetWritable;
959 pThis->IHostAudio.pfnStreamPlay = drvHstAudOssHA_StreamPlay;
960 pThis->IHostAudio.pfnStreamGetReadable = drvHstAudOssHA_StreamGetReadable;
961 pThis->IHostAudio.pfnStreamCapture = drvHstAudOssHA_StreamCapture;
962
963 return VINF_SUCCESS;
964}
965
966
967/**
968 * OSS driver registration record.
969 */
970const PDMDRVREG g_DrvHostOSSAudio =
971{
972 /* u32Version */
973 PDM_DRVREG_VERSION,
974 /* szName */
975 "OSSAudio",
976 /* szRCMod */
977 "",
978 /* szR0Mod */
979 "",
980 /* pszDescription */
981 "OSS audio host driver",
982 /* fFlags */
983 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
984 /* fClass. */
985 PDM_DRVREG_CLASS_AUDIO,
986 /* cMaxInstances */
987 ~0U,
988 /* cbInstance */
989 sizeof(DRVHSTAUDOSS),
990 /* pfnConstruct */
991 drvHstAudOssConstruct,
992 /* pfnDestruct */
993 NULL,
994 /* pfnRelocate */
995 NULL,
996 /* pfnIOCtl */
997 NULL,
998 /* pfnPowerOn */
999 NULL,
1000 /* pfnReset */
1001 NULL,
1002 /* pfnSuspend */
1003 NULL,
1004 /* pfnResume */
1005 NULL,
1006 /* pfnAttach */
1007 NULL,
1008 /* pfnDetach */
1009 NULL,
1010 /* pfnPowerOff */
1011 NULL,
1012 /* pfnSoftReset */
1013 NULL,
1014 /* u32EndVersion */
1015 PDM_DRVREG_VERSION
1016};
1017
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