VirtualBox

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

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

DrvAudio: Working on support for asynchronous stream backend init and smoother device switch. bugref:9890

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