VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

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