VirtualBox

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

Last change on this file since 89234 was 89213, checked in by vboxsync, 4 years ago

Audio: Added an fImmediate indicator to the pfnStreamDestroy methods so the backend knows whether it's okay to continue draining the stream or if it must be destroyed without delay. The latter is typically only for shutdown and driver plumbing. This helps quite a bit for HDA/CoreAudio/knoppix. bugref:9890

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