VirtualBox

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

Last change on this file since 88479 was 88455, checked in by vboxsync, 4 years ago

DrvHostAudioOss: Implemented draining (via thread). Implemented arbritrary buffer sizes. Bunch of cleanups. bugref:9890

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