VirtualBox

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

Last change on this file since 89027 was 88995, checked in by vboxsync, 4 years ago

DrvHostAudioOss: Build fix (draining revamp). bugref:9890

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