VirtualBox

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

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

DrvHostAudioOss: Don't refuse to pre-buffer input streams. Cleaned up function prefix and struct names. bugref:9890

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