X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/8ead62cfc21f61a32677892c721674e06e9f6153..ad044d09de62c10c361003765d5039396c057abe:/audio/ossaudio.c diff --git a/audio/ossaudio.c b/audio/ossaudio.c index 0bdc8eaab1..00be9c91e3 100644 --- a/audio/ossaudio.c +++ b/audio/ossaudio.c @@ -21,23 +21,36 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#include #include #include #include +#ifdef __OpenBSD__ +#include +#else #include -#include "vl.h" +#endif +#include "qemu-common.h" +#include "qemu/main-loop.h" +#include "qemu/host-utils.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 old_optr; + int pending; } OSSVoiceOut; typedef struct OSSVoiceIn { @@ -46,7 +59,6 @@ typedef struct OSSVoiceIn { int fd; int nfrags; int fragsize; - int old_optr; } OSSVoiceIn; static struct { @@ -56,13 +68,17 @@ static struct { 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 + .debug = 0, + .exclusive = 0, + .policy = 5 }; struct oss_params { @@ -104,19 +120,48 @@ static void GCC_FMT_ATTR (3, 4) oss_logerr2 ( static void oss_anal_close (int *fdp) { - int err = close (*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) +static int aud_to_ossfmt (audfmt_e fmt, int endianness) { switch (fmt) { case AUD_FMT_S8: @@ -126,10 +171,20 @@ static int aud_to_ossfmt (audfmt_e fmt) return AFMT_U8; case AUD_FMT_S16: - return AFMT_S16_LE; + if (endianness) { + return AFMT_S16_BE; + } + else { + return AFMT_S16_LE; + } case AUD_FMT_U16: - return AFMT_U16_LE; + if (endianness) { + return AFMT_U16_BE; + } + else { + return AFMT_U16_LE; + } default: dolog ("Internal logic error: Bad audio format %d\n", fmt); @@ -144,7 +199,7 @@ static int oss_to_audfmt (int ossfmt, audfmt_e *fmt, int *endianness) { switch (ossfmt) { case AFMT_S8: - *endianness =0; + *endianness = 0; *fmt = AUD_FMT_S8; break; @@ -195,17 +250,46 @@ static void oss_dump_info (struct oss_params *req, struct oss_params *obt) } #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 mmmmssss; + 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"; - fd = open (dspname, (in ? O_RDONLY : O_WRONLY) | O_NONBLOCK); + /* 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; @@ -231,16 +315,41 @@ static int oss_open (int in, struct oss_params *req, goto err; } - if (ioctl (fd, SNDCTL_DSP_NONBLOCK)) { + if (ioctl (fd, SNDCTL_DSP_NONBLOCK, NULL)) { oss_logerr2 (errno, typ, "Failed to set non-blocking mode\n"); goto err; } - mmmmssss = (req->nfrags << 16) | lsbindex (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; +#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)) { @@ -248,6 +357,12 @@ static int oss_open (int in, struct oss_params *req, 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; @@ -276,26 +391,58 @@ static int oss_open (int in, struct oss_params *req, return -1; } -static int oss_run_out (HWVoiceOut *hw) +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, rpos, live, decr; - int samples; - uint8_t *dst; - st_sample_t *src; + int err, decr; struct audio_buf_info abinfo; struct count_info cntinfo; int bufsize; - live = audio_pcm_hw_get_live_out (hw); - if (!live) { - return 0; - } - bufsize = hw->samples << hw->info.shift; if (oss->mmapped) { - int bytes; + int bytes, pos; err = ioctl (oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo); if (err < 0) { @@ -303,20 +450,8 @@ static int oss_run_out (HWVoiceOut *hw) return 0; } - if (cntinfo.ptr == oss->old_optr) { - if (abs (hw->samples - live) < 64) { - dolog ("warning: Overrun\n"); - } - return 0; - } - - if (cntinfo.ptr > oss->old_optr) { - bytes = cntinfo.ptr - oss->old_optr; - } - else { - bytes = bufsize + cntinfo.ptr - oss->old_optr; - } - + pos = hw->rpos << hw->info.shift; + bytes = audio_ring_dist (cntinfo.ptr, pos, bufsize); decr = audio_MIN (bytes >> hw->info.shift, live); } else { @@ -329,7 +464,7 @@ static int oss_run_out (HWVoiceOut *hw) if (abinfo.bytes > bufsize) { if (conf.debug) { dolog ("warning: Invalid available size, size=%d bufsize=%d\n" - "please report your OS/audio hw to malc@pulsesoft.com\n", + "please report your OS/audio hw to av1474@comtv.ru\n", abinfo.bytes, bufsize); } abinfo.bytes = bufsize; @@ -349,53 +484,10 @@ static int oss_run_out (HWVoiceOut *hw) } } - samples = decr; - rpos = hw->rpos; - while (samples) { - int left_till_end_samples = hw->samples - rpos; - int convert_samples = audio_MIN (samples, left_till_end_samples); - - src = hw->mix_buf + rpos; - dst = advance (oss->pcm_buf, rpos << hw->info.shift); - - hw->clip (dst, src, convert_samples); - if (!oss->mmapped) { - int written; - - written = write (oss->fd, dst, convert_samples << hw->info.shift); - /* XXX: follow errno recommendations ? */ - if (written == -1) { - oss_logerr ( - errno, - "Failed to write %d bytes of audio data from %p\n", - convert_samples << hw->info.shift, - dst - ); - continue; - } + decr = audio_pcm_hw_clip_out (hw, oss->pcm_buf, decr, oss->pending); + oss->pending += decr; + oss_write_pending (oss); - if (written != convert_samples << hw->info.shift) { - int wsamples = written >> hw->info.shift; - int wbytes = wsamples << hw->info.shift; - if (wbytes != written) { - dolog ("warning: Misaligned write %d (requested %d), " - "alignment %d\n", - wbytes, written, hw->info.align + 1); - } - decr -= wsamples; - rpos = (rpos + wsamples) % hw->samples; - break; - } - } - - rpos = (rpos + convert_samples) % hw->samples; - samples -= convert_samples; - } - if (oss->mmapped) { - oss->old_optr = cntinfo.ptr; - } - - hw->rpos = rpos; return decr; } @@ -416,13 +508,13 @@ static void oss_fini_out (HWVoiceOut *hw) } } else { - qemu_free (oss->pcm_buf); + g_free (oss->pcm_buf); } oss->pcm_buf = NULL; } } -static int oss_init_out (HWVoiceOut *hw, audsettings_t *as) +static int oss_init_out (HWVoiceOut *hw, struct audsettings *as) { OSSVoiceOut *oss = (OSSVoiceOut *) hw; struct oss_params req, obt; @@ -430,11 +522,11 @@ static int oss_init_out (HWVoiceOut *hw, audsettings_t *as) int err; int fd; audfmt_e effective_fmt; - audsettings_t obt_as; + struct audsettings obt_as; oss->fd = -1; - req.fmt = aud_to_ossfmt (as->fmt); + req.fmt = aud_to_ossfmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; req.fragsize = conf.fragsize; @@ -453,12 +545,9 @@ static int oss_init_out (HWVoiceOut *hw, audsettings_t *as) 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, - audio_need_to_swap_endian (endianness) - ); + audio_pcm_init_info (&hw->info, &obt_as); oss->nfrags = obt.nfrags; oss->fragsize = obt.fragsize; @@ -472,7 +561,7 @@ static int oss_init_out (HWVoiceOut *hw, audsettings_t *as) oss->mmapped = 0; if (conf.try_mmap) { oss->pcm_buf = mmap ( - 0, + NULL, hw->samples << hw->info.shift, PROT_READ | PROT_WRITE, MAP_SHARED, @@ -482,7 +571,8 @@ static int oss_init_out (HWVoiceOut *hw, audsettings_t *as) if (oss->pcm_buf == MAP_FAILED) { oss_logerr (errno, "Failed to map %d bytes of DAC\n", hw->samples << hw->info.shift); - } else { + } + else { int err; int trig = 0; if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { @@ -537,25 +627,48 @@ static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...) int trig; OSSVoiceOut *oss = (OSSVoiceOut *) hw; - if (!oss->mmapped) { - return 0; - } - switch (cmd) { case VOICE_ENABLE: - ldebug ("enabling voice\n"); - 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; + { + 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) { @@ -567,7 +680,7 @@ static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...) return 0; } -static int oss_init_in (HWVoiceIn *hw, audsettings_t *as) +static int oss_init_in (HWVoiceIn *hw, struct audsettings *as) { OSSVoiceIn *oss = (OSSVoiceIn *) hw; struct oss_params req, obt; @@ -575,11 +688,11 @@ static int oss_init_in (HWVoiceIn *hw, audsettings_t *as) int err; int fd; audfmt_e effective_fmt; - audsettings_t obt_as; + struct audsettings obt_as; oss->fd = -1; - req.fmt = aud_to_ossfmt (as->fmt); + req.fmt = aud_to_ossfmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; req.fragsize = conf.fragsize; @@ -597,12 +710,9 @@ static int oss_init_in (HWVoiceIn *hw, audsettings_t *as) 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, - audio_need_to_swap_endian (endianness) - ); + audio_pcm_init_info (&hw->info, &obt_as); oss->nfrags = obt.nfrags; oss->fragsize = obt.fragsize; @@ -631,7 +741,7 @@ static void oss_fini_in (HWVoiceIn *hw) oss_anal_close (&oss->fd); if (oss->pcm_buf) { - qemu_free (oss->pcm_buf); + g_free (oss->pcm_buf); oss->pcm_buf = NULL; } } @@ -648,8 +758,8 @@ static int oss_run_in (HWVoiceIn *hw) int add; int len; } bufs[2] = { - { hw->wpos, 0 }, - { 0, 0 } + { .add = hw->wpos, .len = 0 }, + { .add = 0, .len = 0 } }; if (!dead) { @@ -664,7 +774,6 @@ static int oss_run_in (HWVoiceIn *hw) bufs[0].len = dead << hwshift; } - for (i = 0; i < 2; ++i) { ssize_t nread; @@ -679,8 +788,7 @@ static int oss_run_in (HWVoiceIn *hw) hw->info.align + 1); } read_samples += nread >> hwshift; - hw->conv (hw->conv_buf + bufs[i].add, p, nread >> hwshift, - &nominal_volume); + hw->conv (hw->conv_buf + bufs[i].add, p, nread >> hwshift); } if (bufs[i].len - nread) { @@ -714,8 +822,32 @@ static int oss_read (SWVoiceIn *sw, void *buf, int size) static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...) { - (void) hw; - (void) 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; } @@ -730,45 +862,83 @@ static void oss_audio_fini (void *opaque) } static struct audio_option oss_options[] = { - {"FRAGSIZE", AUD_OPT_INT, &conf.fragsize, - "Fragment size in bytes", NULL, 0}, - {"NFRAGS", AUD_OPT_INT, &conf.nfrags, - "Number of fragments", NULL, 0}, - {"MMAP", AUD_OPT_BOOL, &conf.try_mmap, - "Try using memory mapped access", NULL, 0}, - {"DAC_DEV", AUD_OPT_STR, &conf.devpath_out, - "Path to DAC device", NULL, 0}, - {"ADC_DEV", AUD_OPT_STR, &conf.devpath_in, - "Path to ADC device", NULL, 0}, - {"DEBUG", AUD_OPT_BOOL, &conf.debug, - "Turn on some debugging messages", NULL, 0}, - {NULL, 0, NULL, NULL, NULL, 0} + { + .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 = { - oss_init_out, - oss_fini_out, - oss_run_out, - oss_write, - oss_ctl_out, - - oss_init_in, - oss_fini_in, - oss_run_in, - oss_read, - oss_ctl_in + .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 = { - INIT_FIELD (name = ) "oss", - INIT_FIELD (descr = ) "OSS http://www.opensound.com", - INIT_FIELD (options = ) oss_options, - INIT_FIELD (init = ) oss_audio_init, - INIT_FIELD (fini = ) oss_audio_fini, - INIT_FIELD (pcm_ops = ) &oss_pcm_ops, - INIT_FIELD (can_be_default = ) 1, - INIT_FIELD (max_voices_out = ) INT_MAX, - INIT_FIELD (max_voices_in = ) INT_MAX, - INIT_FIELD (voice_size_out = ) sizeof (OSSVoiceOut), - INIT_FIELD (voice_size_in = ) sizeof (OSSVoiceIn) + .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) };