mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-09-06 18:05:37 +00:00
![Juergen Lock](/assets/img/avatar_default.png)
Turns out on those versions of FreeBSD (>= 7.x) that know OSS_GETVERSION the ioctl doesn't actually work yet (except in the Linuxolator), so if building on FreeBSD assume the sound drivers are new enough if the ioctl returns the errno it does currently on FreeBSD. (Rev 2 after private discussion with malc.) Signed-off-by: Juergen Lock <nox@jelal.kn-bremen.de> Signed-off-by: malc <av1474@comtv.ru>
936 lines
24 KiB
C
936 lines
24 KiB
C
/*
|
|
* QEMU OSS audio driver
|
|
*
|
|
* Copyright (c) 2003-2005 Vassili Karpov (malc)
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#ifdef __OpenBSD__
|
|
#include <soundcard.h>
|
|
#else
|
|
#include <sys/soundcard.h>
|
|
#endif
|
|
#include "qemu-common.h"
|
|
#include "host-utils.h"
|
|
#include "qemu-char.h"
|
|
#include "audio.h"
|
|
|
|
#define AUDIO_CAP "oss"
|
|
#include "audio_int.h"
|
|
|
|
#if defined OSS_GETVERSION && defined SNDCTL_DSP_POLICY
|
|
#define USE_DSP_POLICY
|
|
#endif
|
|
|
|
typedef struct OSSVoiceOut {
|
|
HWVoiceOut hw;
|
|
void *pcm_buf;
|
|
int fd;
|
|
int wpos;
|
|
int nfrags;
|
|
int fragsize;
|
|
int mmapped;
|
|
int pending;
|
|
} OSSVoiceOut;
|
|
|
|
typedef struct OSSVoiceIn {
|
|
HWVoiceIn hw;
|
|
void *pcm_buf;
|
|
int fd;
|
|
int nfrags;
|
|
int fragsize;
|
|
} OSSVoiceIn;
|
|
|
|
static struct {
|
|
int try_mmap;
|
|
int nfrags;
|
|
int fragsize;
|
|
const char *devpath_out;
|
|
const char *devpath_in;
|
|
int debug;
|
|
int exclusive;
|
|
int policy;
|
|
} conf = {
|
|
.try_mmap = 0,
|
|
.nfrags = 4,
|
|
.fragsize = 4096,
|
|
.devpath_out = "/dev/dsp",
|
|
.devpath_in = "/dev/dsp",
|
|
.debug = 0,
|
|
.exclusive = 0,
|
|
.policy = 5
|
|
};
|
|
|
|
struct oss_params {
|
|
int freq;
|
|
audfmt_e fmt;
|
|
int nchannels;
|
|
int nfrags;
|
|
int fragsize;
|
|
};
|
|
|
|
static void GCC_FMT_ATTR (2, 3) oss_logerr (int err, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start (ap, fmt);
|
|
AUD_vlog (AUDIO_CAP, fmt, ap);
|
|
va_end (ap);
|
|
|
|
AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err));
|
|
}
|
|
|
|
static void GCC_FMT_ATTR (3, 4) oss_logerr2 (
|
|
int err,
|
|
const char *typ,
|
|
const char *fmt,
|
|
...
|
|
)
|
|
{
|
|
va_list ap;
|
|
|
|
AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ);
|
|
|
|
va_start (ap, fmt);
|
|
AUD_vlog (AUDIO_CAP, fmt, ap);
|
|
va_end (ap);
|
|
|
|
AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err));
|
|
}
|
|
|
|
static void oss_anal_close (int *fdp)
|
|
{
|
|
int err;
|
|
|
|
qemu_set_fd_handler (*fdp, NULL, NULL, NULL);
|
|
err = close (*fdp);
|
|
if (err) {
|
|
oss_logerr (errno, "Failed to close file(fd=%d)\n", *fdp);
|
|
}
|
|
*fdp = -1;
|
|
}
|
|
|
|
static void oss_helper_poll_out (void *opaque)
|
|
{
|
|
(void) opaque;
|
|
audio_run ("oss_poll_out");
|
|
}
|
|
|
|
static void oss_helper_poll_in (void *opaque)
|
|
{
|
|
(void) opaque;
|
|
audio_run ("oss_poll_in");
|
|
}
|
|
|
|
static int oss_poll_out (HWVoiceOut *hw)
|
|
{
|
|
OSSVoiceOut *oss = (OSSVoiceOut *) hw;
|
|
|
|
return qemu_set_fd_handler (oss->fd, NULL, oss_helper_poll_out, NULL);
|
|
}
|
|
|
|
static int oss_poll_in (HWVoiceIn *hw)
|
|
{
|
|
OSSVoiceIn *oss = (OSSVoiceIn *) hw;
|
|
|
|
return qemu_set_fd_handler (oss->fd, oss_helper_poll_in, NULL, NULL);
|
|
}
|
|
|
|
static int oss_write (SWVoiceOut *sw, void *buf, int len)
|
|
{
|
|
return audio_pcm_sw_write (sw, buf, len);
|
|
}
|
|
|
|
static int aud_to_ossfmt (audfmt_e fmt)
|
|
{
|
|
switch (fmt) {
|
|
case AUD_FMT_S8:
|
|
return AFMT_S8;
|
|
|
|
case AUD_FMT_U8:
|
|
return AFMT_U8;
|
|
|
|
case AUD_FMT_S16:
|
|
return AFMT_S16_LE;
|
|
|
|
case AUD_FMT_U16:
|
|
return AFMT_U16_LE;
|
|
|
|
default:
|
|
dolog ("Internal logic error: Bad audio format %d\n", fmt);
|
|
#ifdef DEBUG_AUDIO
|
|
abort ();
|
|
#endif
|
|
return AFMT_U8;
|
|
}
|
|
}
|
|
|
|
static int oss_to_audfmt (int ossfmt, audfmt_e *fmt, int *endianness)
|
|
{
|
|
switch (ossfmt) {
|
|
case AFMT_S8:
|
|
*endianness = 0;
|
|
*fmt = AUD_FMT_S8;
|
|
break;
|
|
|
|
case AFMT_U8:
|
|
*endianness = 0;
|
|
*fmt = AUD_FMT_U8;
|
|
break;
|
|
|
|
case AFMT_S16_LE:
|
|
*endianness = 0;
|
|
*fmt = AUD_FMT_S16;
|
|
break;
|
|
|
|
case AFMT_U16_LE:
|
|
*endianness = 0;
|
|
*fmt = AUD_FMT_U16;
|
|
break;
|
|
|
|
case AFMT_S16_BE:
|
|
*endianness = 1;
|
|
*fmt = AUD_FMT_S16;
|
|
break;
|
|
|
|
case AFMT_U16_BE:
|
|
*endianness = 1;
|
|
*fmt = AUD_FMT_U16;
|
|
break;
|
|
|
|
default:
|
|
dolog ("Unrecognized audio format %d\n", ossfmt);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined DEBUG_MISMATCHES || defined DEBUG
|
|
static void oss_dump_info (struct oss_params *req, struct oss_params *obt)
|
|
{
|
|
dolog ("parameter | requested value | obtained value\n");
|
|
dolog ("format | %10d | %10d\n", req->fmt, obt->fmt);
|
|
dolog ("channels | %10d | %10d\n",
|
|
req->nchannels, obt->nchannels);
|
|
dolog ("frequency | %10d | %10d\n", req->freq, obt->freq);
|
|
dolog ("nfrags | %10d | %10d\n", req->nfrags, obt->nfrags);
|
|
dolog ("fragsize | %10d | %10d\n",
|
|
req->fragsize, obt->fragsize);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_DSP_POLICY
|
|
static int oss_get_version (int fd, int *version, const char *typ)
|
|
{
|
|
if (ioctl (fd, OSS_GETVERSION, &version)) {
|
|
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
|
/*
|
|
* Looks like atm (20100109) FreeBSD knows OSS_GETVERSION
|
|
* since 7.x, but currently only on the mixer device (or in
|
|
* the Linuxolator), and in the native version that part of
|
|
* the code is in fact never reached so the ioctl fails anyway.
|
|
* Until this is fixed, just check the errno and if its what
|
|
* FreeBSD's sound drivers return atm assume they are new enough.
|
|
*/
|
|
if (errno == EINVAL) {
|
|
*version = 0x040000;
|
|
return 0;
|
|
}
|
|
#endif
|
|
oss_logerr2 (errno, typ, "Failed to get OSS version\n");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int oss_open (int in, struct oss_params *req,
|
|
struct oss_params *obt, int *pfd)
|
|
{
|
|
int fd;
|
|
int oflags = conf.exclusive ? O_EXCL : 0;
|
|
audio_buf_info abinfo;
|
|
int fmt, freq, nchannels;
|
|
int setfragment = 1;
|
|
const char *dspname = in ? conf.devpath_in : conf.devpath_out;
|
|
const char *typ = in ? "ADC" : "DAC";
|
|
|
|
/* Kludge needed to have working mmap on Linux */
|
|
oflags |= conf.try_mmap ? O_RDWR : (in ? O_RDONLY : O_WRONLY);
|
|
|
|
fd = open (dspname, oflags | O_NONBLOCK);
|
|
if (-1 == fd) {
|
|
oss_logerr2 (errno, typ, "Failed to open `%s'\n", dspname);
|
|
return -1;
|
|
}
|
|
|
|
freq = req->freq;
|
|
nchannels = req->nchannels;
|
|
fmt = req->fmt;
|
|
|
|
if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) {
|
|
oss_logerr2 (errno, typ, "Failed to set sample size %d\n", req->fmt);
|
|
goto err;
|
|
}
|
|
|
|
if (ioctl (fd, SNDCTL_DSP_CHANNELS, &nchannels)) {
|
|
oss_logerr2 (errno, typ, "Failed to set number of channels %d\n",
|
|
req->nchannels);
|
|
goto err;
|
|
}
|
|
|
|
if (ioctl (fd, SNDCTL_DSP_SPEED, &freq)) {
|
|
oss_logerr2 (errno, typ, "Failed to set frequency %d\n", req->freq);
|
|
goto err;
|
|
}
|
|
|
|
if (ioctl (fd, SNDCTL_DSP_NONBLOCK, NULL)) {
|
|
oss_logerr2 (errno, typ, "Failed to set non-blocking mode\n");
|
|
goto err;
|
|
}
|
|
|
|
#ifdef USE_DSP_POLICY
|
|
if (conf.policy >= 0) {
|
|
int version;
|
|
|
|
if (!oss_get_version (fd, &version, typ)) {
|
|
if (conf.debug) {
|
|
dolog ("OSS version = %#x\n", version);
|
|
}
|
|
|
|
if (version >= 0x040000) {
|
|
int policy = conf.policy;
|
|
if (ioctl (fd, SNDCTL_DSP_POLICY, &policy)) {
|
|
oss_logerr2 (errno, typ,
|
|
"Failed to set timing policy to %d\n",
|
|
conf.policy);
|
|
goto err;
|
|
}
|
|
setfragment = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (setfragment) {
|
|
int mmmmssss = (req->nfrags << 16) | ctz32 (req->fragsize);
|
|
if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) {
|
|
oss_logerr2 (errno, typ, "Failed to set buffer length (%d, %d)\n",
|
|
req->nfrags, req->fragsize);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
if (ioctl (fd, in ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo)) {
|
|
oss_logerr2 (errno, typ, "Failed to get buffer length\n");
|
|
goto err;
|
|
}
|
|
|
|
if (!abinfo.fragstotal || !abinfo.fragsize) {
|
|
AUD_log (AUDIO_CAP, "Returned bogus buffer information(%d, %d) for %s\n",
|
|
abinfo.fragstotal, abinfo.fragsize, typ);
|
|
goto err;
|
|
}
|
|
|
|
obt->fmt = fmt;
|
|
obt->nchannels = nchannels;
|
|
obt->freq = freq;
|
|
obt->nfrags = abinfo.fragstotal;
|
|
obt->fragsize = abinfo.fragsize;
|
|
*pfd = fd;
|
|
|
|
#ifdef DEBUG_MISMATCHES
|
|
if ((req->fmt != obt->fmt) ||
|
|
(req->nchannels != obt->nchannels) ||
|
|
(req->freq != obt->freq) ||
|
|
(req->fragsize != obt->fragsize) ||
|
|
(req->nfrags != obt->nfrags)) {
|
|
dolog ("Audio parameters mismatch\n");
|
|
oss_dump_info (req, obt);
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
oss_dump_info (req, obt);
|
|
#endif
|
|
return 0;
|
|
|
|
err:
|
|
oss_anal_close (&fd);
|
|
return -1;
|
|
}
|
|
|
|
static void oss_write_pending (OSSVoiceOut *oss)
|
|
{
|
|
HWVoiceOut *hw = &oss->hw;
|
|
|
|
if (oss->mmapped) {
|
|
return;
|
|
}
|
|
|
|
while (oss->pending) {
|
|
int samples_written;
|
|
ssize_t bytes_written;
|
|
int samples_till_end = hw->samples - oss->wpos;
|
|
int samples_to_write = audio_MIN (oss->pending, samples_till_end);
|
|
int bytes_to_write = samples_to_write << hw->info.shift;
|
|
void *pcm = advance (oss->pcm_buf, oss->wpos << hw->info.shift);
|
|
|
|
bytes_written = write (oss->fd, pcm, bytes_to_write);
|
|
if (bytes_written < 0) {
|
|
if (errno != EAGAIN) {
|
|
oss_logerr (errno, "failed to write %d bytes\n",
|
|
bytes_to_write);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (bytes_written & hw->info.align) {
|
|
dolog ("misaligned write asked for %d, but got %zd\n",
|
|
bytes_to_write, bytes_written);
|
|
return;
|
|
}
|
|
|
|
samples_written = bytes_written >> hw->info.shift;
|
|
oss->pending -= samples_written;
|
|
oss->wpos = (oss->wpos + samples_written) % hw->samples;
|
|
if (bytes_written - bytes_to_write) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int oss_run_out (HWVoiceOut *hw, int live)
|
|
{
|
|
OSSVoiceOut *oss = (OSSVoiceOut *) hw;
|
|
int err, decr;
|
|
struct audio_buf_info abinfo;
|
|
struct count_info cntinfo;
|
|
int bufsize;
|
|
|
|
bufsize = hw->samples << hw->info.shift;
|
|
|
|
if (oss->mmapped) {
|
|
int bytes, pos;
|
|
|
|
err = ioctl (oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo);
|
|
if (err < 0) {
|
|
oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n");
|
|
return 0;
|
|
}
|
|
|
|
pos = hw->rpos << hw->info.shift;
|
|
bytes = audio_ring_dist (cntinfo.ptr, pos, bufsize);
|
|
decr = audio_MIN (bytes >> hw->info.shift, live);
|
|
}
|
|
else {
|
|
err = ioctl (oss->fd, SNDCTL_DSP_GETOSPACE, &abinfo);
|
|
if (err < 0) {
|
|
oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n");
|
|
return 0;
|
|
}
|
|
|
|
if (abinfo.bytes > bufsize) {
|
|
if (conf.debug) {
|
|
dolog ("warning: Invalid available size, size=%d bufsize=%d\n"
|
|
"please report your OS/audio hw to av1474@comtv.ru\n",
|
|
abinfo.bytes, bufsize);
|
|
}
|
|
abinfo.bytes = bufsize;
|
|
}
|
|
|
|
if (abinfo.bytes < 0) {
|
|
if (conf.debug) {
|
|
dolog ("warning: Invalid available size, size=%d bufsize=%d\n",
|
|
abinfo.bytes, bufsize);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
decr = audio_MIN (abinfo.bytes >> hw->info.shift, live);
|
|
if (!decr) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
decr = audio_pcm_hw_clip_out (hw, oss->pcm_buf, decr, oss->pending);
|
|
oss->pending += decr;
|
|
oss_write_pending (oss);
|
|
|
|
return decr;
|
|
}
|
|
|
|
static void oss_fini_out (HWVoiceOut *hw)
|
|
{
|
|
int err;
|
|
OSSVoiceOut *oss = (OSSVoiceOut *) hw;
|
|
|
|
ldebug ("oss_fini\n");
|
|
oss_anal_close (&oss->fd);
|
|
|
|
if (oss->pcm_buf) {
|
|
if (oss->mmapped) {
|
|
err = munmap (oss->pcm_buf, hw->samples << hw->info.shift);
|
|
if (err) {
|
|
oss_logerr (errno, "Failed to unmap buffer %p, size %d\n",
|
|
oss->pcm_buf, hw->samples << hw->info.shift);
|
|
}
|
|
}
|
|
else {
|
|
qemu_free (oss->pcm_buf);
|
|
}
|
|
oss->pcm_buf = NULL;
|
|
}
|
|
}
|
|
|
|
static int oss_init_out (HWVoiceOut *hw, struct audsettings *as)
|
|
{
|
|
OSSVoiceOut *oss = (OSSVoiceOut *) hw;
|
|
struct oss_params req, obt;
|
|
int endianness;
|
|
int err;
|
|
int fd;
|
|
audfmt_e effective_fmt;
|
|
struct audsettings obt_as;
|
|
|
|
oss->fd = -1;
|
|
|
|
req.fmt = aud_to_ossfmt (as->fmt);
|
|
req.freq = as->freq;
|
|
req.nchannels = as->nchannels;
|
|
req.fragsize = conf.fragsize;
|
|
req.nfrags = conf.nfrags;
|
|
|
|
if (oss_open (0, &req, &obt, &fd)) {
|
|
return -1;
|
|
}
|
|
|
|
err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness);
|
|
if (err) {
|
|
oss_anal_close (&fd);
|
|
return -1;
|
|
}
|
|
|
|
obt_as.freq = obt.freq;
|
|
obt_as.nchannels = obt.nchannels;
|
|
obt_as.fmt = effective_fmt;
|
|
obt_as.endianness = endianness;
|
|
|
|
audio_pcm_init_info (&hw->info, &obt_as);
|
|
oss->nfrags = obt.nfrags;
|
|
oss->fragsize = obt.fragsize;
|
|
|
|
if (obt.nfrags * obt.fragsize & hw->info.align) {
|
|
dolog ("warning: Misaligned DAC buffer, size %d, alignment %d\n",
|
|
obt.nfrags * obt.fragsize, hw->info.align + 1);
|
|
}
|
|
|
|
hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift;
|
|
|
|
oss->mmapped = 0;
|
|
if (conf.try_mmap) {
|
|
oss->pcm_buf = mmap (
|
|
NULL,
|
|
hw->samples << hw->info.shift,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_SHARED,
|
|
fd,
|
|
0
|
|
);
|
|
if (oss->pcm_buf == MAP_FAILED) {
|
|
oss_logerr (errno, "Failed to map %d bytes of DAC\n",
|
|
hw->samples << hw->info.shift);
|
|
}
|
|
else {
|
|
int err;
|
|
int trig = 0;
|
|
if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
|
|
oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n");
|
|
}
|
|
else {
|
|
trig = PCM_ENABLE_OUTPUT;
|
|
if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
|
|
oss_logerr (
|
|
errno,
|
|
"SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n"
|
|
);
|
|
}
|
|
else {
|
|
oss->mmapped = 1;
|
|
}
|
|
}
|
|
|
|
if (!oss->mmapped) {
|
|
err = munmap (oss->pcm_buf, hw->samples << hw->info.shift);
|
|
if (err) {
|
|
oss_logerr (errno, "Failed to unmap buffer %p size %d\n",
|
|
oss->pcm_buf, hw->samples << hw->info.shift);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!oss->mmapped) {
|
|
oss->pcm_buf = audio_calloc (
|
|
AUDIO_FUNC,
|
|
hw->samples,
|
|
1 << hw->info.shift
|
|
);
|
|
if (!oss->pcm_buf) {
|
|
dolog (
|
|
"Could not allocate DAC buffer (%d samples, each %d bytes)\n",
|
|
hw->samples,
|
|
1 << hw->info.shift
|
|
);
|
|
oss_anal_close (&fd);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
oss->fd = fd;
|
|
return 0;
|
|
}
|
|
|
|
static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|
{
|
|
int trig;
|
|
OSSVoiceOut *oss = (OSSVoiceOut *) hw;
|
|
|
|
switch (cmd) {
|
|
case VOICE_ENABLE:
|
|
{
|
|
va_list ap;
|
|
int poll_mode;
|
|
|
|
va_start (ap, cmd);
|
|
poll_mode = va_arg (ap, int);
|
|
va_end (ap);
|
|
|
|
ldebug ("enabling voice\n");
|
|
if (poll_mode && oss_poll_out (hw)) {
|
|
poll_mode = 0;
|
|
}
|
|
hw->poll_mode = poll_mode;
|
|
|
|
if (!oss->mmapped) {
|
|
return 0;
|
|
}
|
|
|
|
audio_pcm_info_clear_buf (&hw->info, oss->pcm_buf, hw->samples);
|
|
trig = PCM_ENABLE_OUTPUT;
|
|
if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
|
|
oss_logerr (
|
|
errno,
|
|
"SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n"
|
|
);
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VOICE_DISABLE:
|
|
if (hw->poll_mode) {
|
|
qemu_set_fd_handler (oss->fd, NULL, NULL, NULL);
|
|
hw->poll_mode = 0;
|
|
}
|
|
|
|
if (!oss->mmapped) {
|
|
return 0;
|
|
}
|
|
|
|
ldebug ("disabling voice\n");
|
|
trig = 0;
|
|
if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) {
|
|
oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n");
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int oss_init_in (HWVoiceIn *hw, struct audsettings *as)
|
|
{
|
|
OSSVoiceIn *oss = (OSSVoiceIn *) hw;
|
|
struct oss_params req, obt;
|
|
int endianness;
|
|
int err;
|
|
int fd;
|
|
audfmt_e effective_fmt;
|
|
struct audsettings obt_as;
|
|
|
|
oss->fd = -1;
|
|
|
|
req.fmt = aud_to_ossfmt (as->fmt);
|
|
req.freq = as->freq;
|
|
req.nchannels = as->nchannels;
|
|
req.fragsize = conf.fragsize;
|
|
req.nfrags = conf.nfrags;
|
|
if (oss_open (1, &req, &obt, &fd)) {
|
|
return -1;
|
|
}
|
|
|
|
err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness);
|
|
if (err) {
|
|
oss_anal_close (&fd);
|
|
return -1;
|
|
}
|
|
|
|
obt_as.freq = obt.freq;
|
|
obt_as.nchannels = obt.nchannels;
|
|
obt_as.fmt = effective_fmt;
|
|
obt_as.endianness = endianness;
|
|
|
|
audio_pcm_init_info (&hw->info, &obt_as);
|
|
oss->nfrags = obt.nfrags;
|
|
oss->fragsize = obt.fragsize;
|
|
|
|
if (obt.nfrags * obt.fragsize & hw->info.align) {
|
|
dolog ("warning: Misaligned ADC buffer, size %d, alignment %d\n",
|
|
obt.nfrags * obt.fragsize, hw->info.align + 1);
|
|
}
|
|
|
|
hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift;
|
|
oss->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
|
|
if (!oss->pcm_buf) {
|
|
dolog ("Could not allocate ADC buffer (%d samples, each %d bytes)\n",
|
|
hw->samples, 1 << hw->info.shift);
|
|
oss_anal_close (&fd);
|
|
return -1;
|
|
}
|
|
|
|
oss->fd = fd;
|
|
return 0;
|
|
}
|
|
|
|
static void oss_fini_in (HWVoiceIn *hw)
|
|
{
|
|
OSSVoiceIn *oss = (OSSVoiceIn *) hw;
|
|
|
|
oss_anal_close (&oss->fd);
|
|
|
|
if (oss->pcm_buf) {
|
|
qemu_free (oss->pcm_buf);
|
|
oss->pcm_buf = NULL;
|
|
}
|
|
}
|
|
|
|
static int oss_run_in (HWVoiceIn *hw)
|
|
{
|
|
OSSVoiceIn *oss = (OSSVoiceIn *) hw;
|
|
int hwshift = hw->info.shift;
|
|
int i;
|
|
int live = audio_pcm_hw_get_live_in (hw);
|
|
int dead = hw->samples - live;
|
|
size_t read_samples = 0;
|
|
struct {
|
|
int add;
|
|
int len;
|
|
} bufs[2] = {
|
|
{ .add = hw->wpos, .len = 0 },
|
|
{ .add = 0, .len = 0 }
|
|
};
|
|
|
|
if (!dead) {
|
|
return 0;
|
|
}
|
|
|
|
if (hw->wpos + dead > hw->samples) {
|
|
bufs[0].len = (hw->samples - hw->wpos) << hwshift;
|
|
bufs[1].len = (dead - (hw->samples - hw->wpos)) << hwshift;
|
|
}
|
|
else {
|
|
bufs[0].len = dead << hwshift;
|
|
}
|
|
|
|
for (i = 0; i < 2; ++i) {
|
|
ssize_t nread;
|
|
|
|
if (bufs[i].len) {
|
|
void *p = advance (oss->pcm_buf, bufs[i].add << hwshift);
|
|
nread = read (oss->fd, p, bufs[i].len);
|
|
|
|
if (nread > 0) {
|
|
if (nread & hw->info.align) {
|
|
dolog ("warning: Misaligned read %zd (requested %d), "
|
|
"alignment %d\n", nread, bufs[i].add << hwshift,
|
|
hw->info.align + 1);
|
|
}
|
|
read_samples += nread >> hwshift;
|
|
hw->conv (hw->conv_buf + bufs[i].add, p, nread >> hwshift,
|
|
&nominal_volume);
|
|
}
|
|
|
|
if (bufs[i].len - nread) {
|
|
if (nread == -1) {
|
|
switch (errno) {
|
|
case EINTR:
|
|
case EAGAIN:
|
|
break;
|
|
default:
|
|
oss_logerr (
|
|
errno,
|
|
"Failed to read %d bytes of audio (to %p)\n",
|
|
bufs[i].len, p
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
hw->wpos = (hw->wpos + read_samples) % hw->samples;
|
|
return read_samples;
|
|
}
|
|
|
|
static int oss_read (SWVoiceIn *sw, void *buf, int size)
|
|
{
|
|
return audio_pcm_sw_read (sw, buf, size);
|
|
}
|
|
|
|
static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
|
{
|
|
OSSVoiceIn *oss = (OSSVoiceIn *) hw;
|
|
|
|
switch (cmd) {
|
|
case VOICE_ENABLE:
|
|
{
|
|
va_list ap;
|
|
int poll_mode;
|
|
|
|
va_start (ap, cmd);
|
|
poll_mode = va_arg (ap, int);
|
|
va_end (ap);
|
|
|
|
if (poll_mode && oss_poll_in (hw)) {
|
|
poll_mode = 0;
|
|
}
|
|
hw->poll_mode = poll_mode;
|
|
}
|
|
break;
|
|
|
|
case VOICE_DISABLE:
|
|
if (hw->poll_mode) {
|
|
hw->poll_mode = 0;
|
|
qemu_set_fd_handler (oss->fd, NULL, NULL, NULL);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void *oss_audio_init (void)
|
|
{
|
|
return &conf;
|
|
}
|
|
|
|
static void oss_audio_fini (void *opaque)
|
|
{
|
|
(void) opaque;
|
|
}
|
|
|
|
static struct audio_option oss_options[] = {
|
|
{
|
|
.name = "FRAGSIZE",
|
|
.tag = AUD_OPT_INT,
|
|
.valp = &conf.fragsize,
|
|
.descr = "Fragment size in bytes"
|
|
},
|
|
{
|
|
.name = "NFRAGS",
|
|
.tag = AUD_OPT_INT,
|
|
.valp = &conf.nfrags,
|
|
.descr = "Number of fragments"
|
|
},
|
|
{
|
|
.name = "MMAP",
|
|
.tag = AUD_OPT_BOOL,
|
|
.valp = &conf.try_mmap,
|
|
.descr = "Try using memory mapped access"
|
|
},
|
|
{
|
|
.name = "DAC_DEV",
|
|
.tag = AUD_OPT_STR,
|
|
.valp = &conf.devpath_out,
|
|
.descr = "Path to DAC device"
|
|
},
|
|
{
|
|
.name = "ADC_DEV",
|
|
.tag = AUD_OPT_STR,
|
|
.valp = &conf.devpath_in,
|
|
.descr = "Path to ADC device"
|
|
},
|
|
{
|
|
.name = "EXCLUSIVE",
|
|
.tag = AUD_OPT_BOOL,
|
|
.valp = &conf.exclusive,
|
|
.descr = "Open device in exclusive mode (vmix wont work)"
|
|
},
|
|
#ifdef USE_DSP_POLICY
|
|
{
|
|
.name = "POLICY",
|
|
.tag = AUD_OPT_INT,
|
|
.valp = &conf.policy,
|
|
.descr = "Set the timing policy of the device, -1 to use fragment mode",
|
|
},
|
|
#endif
|
|
{
|
|
.name = "DEBUG",
|
|
.tag = AUD_OPT_BOOL,
|
|
.valp = &conf.debug,
|
|
.descr = "Turn on some debugging messages"
|
|
},
|
|
{ /* End of list */ }
|
|
};
|
|
|
|
static struct audio_pcm_ops oss_pcm_ops = {
|
|
.init_out = oss_init_out,
|
|
.fini_out = oss_fini_out,
|
|
.run_out = oss_run_out,
|
|
.write = oss_write,
|
|
.ctl_out = oss_ctl_out,
|
|
|
|
.init_in = oss_init_in,
|
|
.fini_in = oss_fini_in,
|
|
.run_in = oss_run_in,
|
|
.read = oss_read,
|
|
.ctl_in = oss_ctl_in
|
|
};
|
|
|
|
struct audio_driver oss_audio_driver = {
|
|
.name = "oss",
|
|
.descr = "OSS http://www.opensound.com",
|
|
.options = oss_options,
|
|
.init = oss_audio_init,
|
|
.fini = oss_audio_fini,
|
|
.pcm_ops = &oss_pcm_ops,
|
|
.can_be_default = 1,
|
|
.max_voices_out = INT_MAX,
|
|
.max_voices_in = INT_MAX,
|
|
.voice_size_out = sizeof (OSSVoiceOut),
|
|
.voice_size_in = sizeof (OSSVoiceIn)
|
|
};
|