VirtualBox

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

Last change on this file since 89132 was 89055, checked in by vboxsync, 4 years ago

ValKit/Audio: Eliminated VBoxDDVKAT.h. Made some progress getting the PulseAudio driver to work (integer overflow now due to invalid config). bugref:10008

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