VirtualBox

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

Last change on this file since 90778 was 89551, checked in by vboxsync, 3 years ago

Audio: Nits/6.1. bugref:bugref:9890

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

© 2023 Oracle
ContactPrivacy policyTerms of Use