diff options
author | Timothy Pearson <tpearson@raptorengineering.com> | 2019-05-11 15:12:49 -0500 |
---|---|---|
committer | Timothy Pearson <tpearson@raptorengineering.com> | 2019-05-11 15:12:49 -0500 |
commit | 9e80202352dd49bdd9e67b8b906d86f058431505 (patch) | |
tree | 5673c17aad6e3833da8c4ff21b5a11f666ec9fbe /src/audio | |
download | hqemu-master.zip hqemu-master.tar.gz |
Diffstat (limited to 'src/audio')
-rw-r--r-- | src/audio/Makefile.objs | 13 | ||||
-rw-r--r-- | src/audio/alsaaudio.c | 1227 | ||||
-rw-r--r-- | src/audio/audio.c | 2063 | ||||
-rw-r--r-- | src/audio/audio.h | 166 | ||||
-rw-r--r-- | src/audio/audio_int.h | 260 | ||||
-rw-r--r-- | src/audio/audio_pt_int.c | 173 | ||||
-rw-r--r-- | src/audio/audio_pt_int.h | 22 | ||||
-rw-r--r-- | src/audio/audio_template.h | 514 | ||||
-rw-r--r-- | src/audio/audio_win_int.c | 107 | ||||
-rw-r--r-- | src/audio/audio_win_int.h | 10 | ||||
-rw-r--r-- | src/audio/coreaudio.c | 555 | ||||
-rw-r--r-- | src/audio/dsound_template.h | 278 | ||||
-rw-r--r-- | src/audio/dsoundaudio.c | 904 | ||||
-rw-r--r-- | src/audio/mixeng.c | 366 | ||||
-rw-r--r-- | src/audio/mixeng.h | 51 | ||||
-rw-r--r-- | src/audio/mixeng_template.h | 154 | ||||
-rw-r--r-- | src/audio/noaudio.c | 173 | ||||
-rw-r--r-- | src/audio/ossaudio.c | 941 | ||||
-rw-r--r-- | src/audio/paaudio.c | 953 | ||||
-rw-r--r-- | src/audio/rate_template.h | 111 | ||||
-rw-r--r-- | src/audio/sdlaudio.c | 466 | ||||
-rw-r--r-- | src/audio/spiceaudio.c | 411 | ||||
-rw-r--r-- | src/audio/wavaudio.c | 292 | ||||
-rw-r--r-- | src/audio/wavcapture.c | 194 |
24 files changed, 10404 insertions, 0 deletions
diff --git a/src/audio/Makefile.objs b/src/audio/Makefile.objs new file mode 100644 index 0000000..481d1aa --- /dev/null +++ b/src/audio/Makefile.objs @@ -0,0 +1,13 @@ +common-obj-y = audio.o noaudio.o wavaudio.o mixeng.o +common-obj-$(CONFIG_SDL) += sdlaudio.o +common-obj-$(CONFIG_OSS) += ossaudio.o +common-obj-$(CONFIG_SPICE) += spiceaudio.o +common-obj-$(CONFIG_COREAUDIO) += coreaudio.o +common-obj-$(CONFIG_ALSA) += alsaaudio.o +common-obj-$(CONFIG_DSOUND) += dsoundaudio.o +common-obj-$(CONFIG_PA) += paaudio.o +common-obj-$(CONFIG_AUDIO_PT_INT) += audio_pt_int.o +common-obj-$(CONFIG_AUDIO_WIN_INT) += audio_win_int.o +common-obj-y += wavcapture.o + +sdlaudio.o-cflags := $(SDL_CFLAGS) diff --git a/src/audio/alsaaudio.c b/src/audio/alsaaudio.c new file mode 100644 index 0000000..6315b2d --- /dev/null +++ b/src/audio/alsaaudio.c @@ -0,0 +1,1227 @@ +/* + * QEMU ALSA audio driver + * + * Copyright (c) 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 <alsa/asoundlib.h> +#include "qemu-common.h" +#include "qemu/main-loop.h" +#include "audio.h" +#include "trace.h" + +#if QEMU_GNUC_PREREQ(4, 3) +#pragma GCC diagnostic ignored "-Waddress" +#endif + +#define AUDIO_CAP "alsa" +#include "audio_int.h" + +typedef struct ALSAConf { + int size_in_usec_in; + int size_in_usec_out; + const char *pcm_name_in; + const char *pcm_name_out; + unsigned int buffer_size_in; + unsigned int period_size_in; + unsigned int buffer_size_out; + unsigned int period_size_out; + unsigned int threshold; + + int buffer_size_in_overridden; + int period_size_in_overridden; + + int buffer_size_out_overridden; + int period_size_out_overridden; +} ALSAConf; + +struct pollhlp { + snd_pcm_t *handle; + struct pollfd *pfds; + ALSAConf *conf; + int count; + int mask; +}; + +typedef struct ALSAVoiceOut { + HWVoiceOut hw; + int wpos; + int pending; + void *pcm_buf; + snd_pcm_t *handle; + struct pollhlp pollhlp; +} ALSAVoiceOut; + +typedef struct ALSAVoiceIn { + HWVoiceIn hw; + snd_pcm_t *handle; + void *pcm_buf; + struct pollhlp pollhlp; +} ALSAVoiceIn; + +struct alsa_params_req { + int freq; + snd_pcm_format_t fmt; + int nchannels; + int size_in_usec; + int override_mask; + unsigned int buffer_size; + unsigned int period_size; +}; + +struct alsa_params_obt { + int freq; + audfmt_e fmt; + int endianness; + int nchannels; + snd_pcm_uframes_t samples; +}; + +static void GCC_FMT_ATTR (2, 3) alsa_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", snd_strerror (err)); +} + +static void GCC_FMT_ATTR (3, 4) alsa_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", snd_strerror (err)); +} + +static void alsa_fini_poll (struct pollhlp *hlp) +{ + int i; + struct pollfd *pfds = hlp->pfds; + + if (pfds) { + for (i = 0; i < hlp->count; ++i) { + qemu_set_fd_handler (pfds[i].fd, NULL, NULL, NULL); + } + g_free (pfds); + } + hlp->pfds = NULL; + hlp->count = 0; + hlp->handle = NULL; +} + +static void alsa_anal_close1 (snd_pcm_t **handlep) +{ + int err = snd_pcm_close (*handlep); + if (err) { + alsa_logerr (err, "Failed to close PCM handle %p\n", *handlep); + } + *handlep = NULL; +} + +static void alsa_anal_close (snd_pcm_t **handlep, struct pollhlp *hlp) +{ + alsa_fini_poll (hlp); + alsa_anal_close1 (handlep); +} + +static int alsa_recover (snd_pcm_t *handle) +{ + int err = snd_pcm_prepare (handle); + if (err < 0) { + alsa_logerr (err, "Failed to prepare handle %p\n", handle); + return -1; + } + return 0; +} + +static int alsa_resume (snd_pcm_t *handle) +{ + int err = snd_pcm_resume (handle); + if (err < 0) { + alsa_logerr (err, "Failed to resume handle %p\n", handle); + return -1; + } + return 0; +} + +static void alsa_poll_handler (void *opaque) +{ + int err, count; + snd_pcm_state_t state; + struct pollhlp *hlp = opaque; + unsigned short revents; + + count = poll (hlp->pfds, hlp->count, 0); + if (count < 0) { + dolog ("alsa_poll_handler: poll %s\n", strerror (errno)); + return; + } + + if (!count) { + return; + } + + /* XXX: ALSA example uses initial count, not the one returned by + poll, correct? */ + err = snd_pcm_poll_descriptors_revents (hlp->handle, hlp->pfds, + hlp->count, &revents); + if (err < 0) { + alsa_logerr (err, "snd_pcm_poll_descriptors_revents"); + return; + } + + if (!(revents & hlp->mask)) { + trace_alsa_revents(revents); + return; + } + + state = snd_pcm_state (hlp->handle); + switch (state) { + case SND_PCM_STATE_SETUP: + alsa_recover (hlp->handle); + break; + + case SND_PCM_STATE_XRUN: + alsa_recover (hlp->handle); + break; + + case SND_PCM_STATE_SUSPENDED: + alsa_resume (hlp->handle); + break; + + case SND_PCM_STATE_PREPARED: + audio_run ("alsa run (prepared)"); + break; + + case SND_PCM_STATE_RUNNING: + audio_run ("alsa run (running)"); + break; + + default: + dolog ("Unexpected state %d\n", state); + } +} + +static int alsa_poll_helper (snd_pcm_t *handle, struct pollhlp *hlp, int mask) +{ + int i, count, err; + struct pollfd *pfds; + + count = snd_pcm_poll_descriptors_count (handle); + if (count <= 0) { + dolog ("Could not initialize poll mode\n" + "Invalid number of poll descriptors %d\n", count); + return -1; + } + + pfds = audio_calloc ("alsa_poll_helper", count, sizeof (*pfds)); + if (!pfds) { + dolog ("Could not initialize poll mode\n"); + return -1; + } + + err = snd_pcm_poll_descriptors (handle, pfds, count); + if (err < 0) { + alsa_logerr (err, "Could not initialize poll mode\n" + "Could not obtain poll descriptors\n"); + g_free (pfds); + return -1; + } + + for (i = 0; i < count; ++i) { + if (pfds[i].events & POLLIN) { + qemu_set_fd_handler (pfds[i].fd, alsa_poll_handler, NULL, hlp); + } + if (pfds[i].events & POLLOUT) { + trace_alsa_pollout(i, pfds[i].fd); + qemu_set_fd_handler (pfds[i].fd, NULL, alsa_poll_handler, hlp); + } + trace_alsa_set_handler(pfds[i].events, i, pfds[i].fd, err); + + } + hlp->pfds = pfds; + hlp->count = count; + hlp->handle = handle; + hlp->mask = mask; + return 0; +} + +static int alsa_poll_out (HWVoiceOut *hw) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + + return alsa_poll_helper (alsa->handle, &alsa->pollhlp, POLLOUT); +} + +static int alsa_poll_in (HWVoiceIn *hw) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + + return alsa_poll_helper (alsa->handle, &alsa->pollhlp, POLLIN); +} + +static int alsa_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static snd_pcm_format_t aud_to_alsafmt (audfmt_e fmt, int endianness) +{ + switch (fmt) { + case AUD_FMT_S8: + return SND_PCM_FORMAT_S8; + + case AUD_FMT_U8: + return SND_PCM_FORMAT_U8; + + case AUD_FMT_S16: + if (endianness) { + return SND_PCM_FORMAT_S16_BE; + } + else { + return SND_PCM_FORMAT_S16_LE; + } + + case AUD_FMT_U16: + if (endianness) { + return SND_PCM_FORMAT_U16_BE; + } + else { + return SND_PCM_FORMAT_U16_LE; + } + + case AUD_FMT_S32: + if (endianness) { + return SND_PCM_FORMAT_S32_BE; + } + else { + return SND_PCM_FORMAT_S32_LE; + } + + case AUD_FMT_U32: + if (endianness) { + return SND_PCM_FORMAT_U32_BE; + } + else { + return SND_PCM_FORMAT_U32_LE; + } + + default: + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO + abort (); +#endif + return SND_PCM_FORMAT_U8; + } +} + +static int alsa_to_audfmt (snd_pcm_format_t alsafmt, audfmt_e *fmt, + int *endianness) +{ + switch (alsafmt) { + case SND_PCM_FORMAT_S8: + *endianness = 0; + *fmt = AUD_FMT_S8; + break; + + case SND_PCM_FORMAT_U8: + *endianness = 0; + *fmt = AUD_FMT_U8; + break; + + case SND_PCM_FORMAT_S16_LE: + *endianness = 0; + *fmt = AUD_FMT_S16; + break; + + case SND_PCM_FORMAT_U16_LE: + *endianness = 0; + *fmt = AUD_FMT_U16; + break; + + case SND_PCM_FORMAT_S16_BE: + *endianness = 1; + *fmt = AUD_FMT_S16; + break; + + case SND_PCM_FORMAT_U16_BE: + *endianness = 1; + *fmt = AUD_FMT_U16; + break; + + case SND_PCM_FORMAT_S32_LE: + *endianness = 0; + *fmt = AUD_FMT_S32; + break; + + case SND_PCM_FORMAT_U32_LE: + *endianness = 0; + *fmt = AUD_FMT_U32; + break; + + case SND_PCM_FORMAT_S32_BE: + *endianness = 1; + *fmt = AUD_FMT_S32; + break; + + case SND_PCM_FORMAT_U32_BE: + *endianness = 1; + *fmt = AUD_FMT_U32; + break; + + default: + dolog ("Unrecognized audio format %d\n", alsafmt); + return -1; + } + + return 0; +} + +static void alsa_dump_info (struct alsa_params_req *req, + struct alsa_params_obt *obt, + snd_pcm_format_t obtfmt) +{ + dolog ("parameter | requested value | obtained value\n"); + dolog ("format | %10d | %10d\n", req->fmt, obtfmt); + dolog ("channels | %10d | %10d\n", + req->nchannels, obt->nchannels); + dolog ("frequency | %10d | %10d\n", req->freq, obt->freq); + dolog ("============================================\n"); + dolog ("requested: buffer size %d period size %d\n", + req->buffer_size, req->period_size); + dolog ("obtained: samples %ld\n", obt->samples); +} + +static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) +{ + int err; + snd_pcm_sw_params_t *sw_params; + + snd_pcm_sw_params_alloca (&sw_params); + + err = snd_pcm_sw_params_current (handle, sw_params); + if (err < 0) { + dolog ("Could not fully initialize DAC\n"); + alsa_logerr (err, "Failed to get current software parameters\n"); + return; + } + + err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, threshold); + if (err < 0) { + dolog ("Could not fully initialize DAC\n"); + alsa_logerr (err, "Failed to set software threshold to %ld\n", + threshold); + return; + } + + err = snd_pcm_sw_params (handle, sw_params); + if (err < 0) { + dolog ("Could not fully initialize DAC\n"); + alsa_logerr (err, "Failed to set software parameters\n"); + return; + } +} + +static int alsa_open (int in, struct alsa_params_req *req, + struct alsa_params_obt *obt, snd_pcm_t **handlep, + ALSAConf *conf) +{ + snd_pcm_t *handle; + snd_pcm_hw_params_t *hw_params; + int err; + int size_in_usec; + unsigned int freq, nchannels; + const char *pcm_name = in ? conf->pcm_name_in : conf->pcm_name_out; + snd_pcm_uframes_t obt_buffer_size; + const char *typ = in ? "ADC" : "DAC"; + snd_pcm_format_t obtfmt; + + freq = req->freq; + nchannels = req->nchannels; + size_in_usec = req->size_in_usec; + + snd_pcm_hw_params_alloca (&hw_params); + + err = snd_pcm_open ( + &handle, + pcm_name, + in ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to open `%s':\n", pcm_name); + return -1; + } + + err = snd_pcm_hw_params_any (handle, hw_params); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to initialize hardware parameters\n"); + goto err; + } + + err = snd_pcm_hw_params_set_access ( + handle, + hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set access type\n"); + goto err; + } + + err = snd_pcm_hw_params_set_format (handle, hw_params, req->fmt); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set format %d\n", req->fmt); + } + + err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &freq, 0); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set frequency %d\n", req->freq); + goto err; + } + + err = snd_pcm_hw_params_set_channels_near ( + handle, + hw_params, + &nchannels + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set number of channels %d\n", + req->nchannels); + goto err; + } + + if (nchannels != 1 && nchannels != 2) { + alsa_logerr2 (err, typ, + "Can not handle obtained number of channels %d\n", + nchannels); + goto err; + } + + if (req->buffer_size) { + unsigned long obt; + + if (size_in_usec) { + int dir = 0; + unsigned int btime = req->buffer_size; + + err = snd_pcm_hw_params_set_buffer_time_near ( + handle, + hw_params, + &btime, + &dir + ); + obt = btime; + } + else { + snd_pcm_uframes_t bsize = req->buffer_size; + + err = snd_pcm_hw_params_set_buffer_size_near ( + handle, + hw_params, + &bsize + ); + obt = bsize; + } + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set buffer %s to %d\n", + size_in_usec ? "time" : "size", req->buffer_size); + goto err; + } + + if ((req->override_mask & 2) && (obt - req->buffer_size)) + dolog ("Requested buffer %s %u was rejected, using %lu\n", + size_in_usec ? "time" : "size", req->buffer_size, obt); + } + + if (req->period_size) { + unsigned long obt; + + if (size_in_usec) { + int dir = 0; + unsigned int ptime = req->period_size; + + err = snd_pcm_hw_params_set_period_time_near ( + handle, + hw_params, + &ptime, + &dir + ); + obt = ptime; + } + else { + int dir = 0; + snd_pcm_uframes_t psize = req->period_size; + + err = snd_pcm_hw_params_set_period_size_near ( + handle, + hw_params, + &psize, + &dir + ); + obt = psize; + } + + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set period %s to %d\n", + size_in_usec ? "time" : "size", req->period_size); + goto err; + } + + if (((req->override_mask & 1) && (obt - req->period_size))) + dolog ("Requested period %s %u was rejected, using %lu\n", + size_in_usec ? "time" : "size", req->period_size, obt); + } + + err = snd_pcm_hw_params (handle, hw_params); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to apply audio parameters\n"); + goto err; + } + + err = snd_pcm_hw_params_get_buffer_size (hw_params, &obt_buffer_size); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to get buffer size\n"); + goto err; + } + + err = snd_pcm_hw_params_get_format (hw_params, &obtfmt); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to get format\n"); + goto err; + } + + if (alsa_to_audfmt (obtfmt, &obt->fmt, &obt->endianness)) { + dolog ("Invalid format was returned %d\n", obtfmt); + goto err; + } + + err = snd_pcm_prepare (handle); + if (err < 0) { + alsa_logerr2 (err, typ, "Could not prepare handle %p\n", handle); + goto err; + } + + if (!in && conf->threshold) { + snd_pcm_uframes_t threshold; + int bytes_per_sec; + + bytes_per_sec = freq << (nchannels == 2); + + switch (obt->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + break; + + case AUD_FMT_S16: + case AUD_FMT_U16: + bytes_per_sec <<= 1; + break; + + case AUD_FMT_S32: + case AUD_FMT_U32: + bytes_per_sec <<= 2; + break; + } + + threshold = (conf->threshold * bytes_per_sec) / 1000; + alsa_set_threshold (handle, threshold); + } + + obt->nchannels = nchannels; + obt->freq = freq; + obt->samples = obt_buffer_size; + + *handlep = handle; + + if (obtfmt != req->fmt || + obt->nchannels != req->nchannels || + obt->freq != req->freq) { + dolog ("Audio parameters for %s\n", typ); + alsa_dump_info (req, obt, obtfmt); + } + +#ifdef DEBUG + alsa_dump_info (req, obt, obtfmt); +#endif + return 0; + + err: + alsa_anal_close1 (&handle); + return -1; +} + +static snd_pcm_sframes_t alsa_get_avail (snd_pcm_t *handle) +{ + snd_pcm_sframes_t avail; + + avail = snd_pcm_avail_update (handle); + if (avail < 0) { + if (avail == -EPIPE) { + if (!alsa_recover (handle)) { + avail = snd_pcm_avail_update (handle); + } + } + + if (avail < 0) { + alsa_logerr (avail, + "Could not obtain number of available frames\n"); + return -1; + } + } + + return avail; +} + +static void alsa_write_pending (ALSAVoiceOut *alsa) +{ + HWVoiceOut *hw = &alsa->hw; + + while (alsa->pending) { + int left_till_end_samples = hw->samples - alsa->wpos; + int len = audio_MIN (alsa->pending, left_till_end_samples); + char *src = advance (alsa->pcm_buf, alsa->wpos << hw->info.shift); + + while (len) { + snd_pcm_sframes_t written; + + written = snd_pcm_writei (alsa->handle, src, len); + + if (written <= 0) { + switch (written) { + case 0: + trace_alsa_wrote_zero(len); + return; + + case -EPIPE: + if (alsa_recover (alsa->handle)) { + alsa_logerr (written, "Failed to write %d frames\n", + len); + return; + } + trace_alsa_xrun_out(); + continue; + + case -ESTRPIPE: + /* stream is suspended and waiting for an + application recovery */ + if (alsa_resume (alsa->handle)) { + alsa_logerr (written, "Failed to write %d frames\n", + len); + return; + } + trace_alsa_resume_out(); + continue; + + case -EAGAIN: + return; + + default: + alsa_logerr (written, "Failed to write %d frames from %p\n", + len, src); + return; + } + } + + alsa->wpos = (alsa->wpos + written) % hw->samples; + alsa->pending -= written; + len -= written; + } + } +} + +static int alsa_run_out (HWVoiceOut *hw, int live) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + int decr; + snd_pcm_sframes_t avail; + + avail = alsa_get_avail (alsa->handle); + if (avail < 0) { + dolog ("Could not get number of available playback frames\n"); + return 0; + } + + decr = audio_MIN (live, avail); + decr = audio_pcm_hw_clip_out (hw, alsa->pcm_buf, decr, alsa->pending); + alsa->pending += decr; + alsa_write_pending (alsa); + return decr; +} + +static void alsa_fini_out (HWVoiceOut *hw) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + + ldebug ("alsa_fini\n"); + alsa_anal_close (&alsa->handle, &alsa->pollhlp); + + g_free(alsa->pcm_buf); + alsa->pcm_buf = NULL; +} + +static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + struct alsa_params_req req; + struct alsa_params_obt obt; + snd_pcm_t *handle; + struct audsettings obt_as; + ALSAConf *conf = drv_opaque; + + req.fmt = aud_to_alsafmt (as->fmt, as->endianness); + req.freq = as->freq; + req.nchannels = as->nchannels; + req.period_size = conf->period_size_out; + req.buffer_size = conf->buffer_size_out; + req.size_in_usec = conf->size_in_usec_out; + req.override_mask = + (conf->period_size_out_overridden ? 1 : 0) | + (conf->buffer_size_out_overridden ? 2 : 0); + + if (alsa_open (0, &req, &obt, &handle, conf)) { + return -1; + } + + obt_as.freq = obt.freq; + obt_as.nchannels = obt.nchannels; + obt_as.fmt = obt.fmt; + obt_as.endianness = obt.endianness; + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = obt.samples; + + alsa->pcm_buf = audio_calloc (AUDIO_FUNC, obt.samples, 1 << hw->info.shift); + if (!alsa->pcm_buf) { + dolog ("Could not allocate DAC buffer (%d samples, each %d bytes)\n", + hw->samples, 1 << hw->info.shift); + alsa_anal_close1 (&handle); + return -1; + } + + alsa->handle = handle; + alsa->pollhlp.conf = conf; + return 0; +} + +#define VOICE_CTL_PAUSE 0 +#define VOICE_CTL_PREPARE 1 +#define VOICE_CTL_START 2 + +static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int ctl) +{ + int err; + + if (ctl == VOICE_CTL_PAUSE) { + err = snd_pcm_drop (handle); + if (err < 0) { + alsa_logerr (err, "Could not stop %s\n", typ); + return -1; + } + } + else { + err = snd_pcm_prepare (handle); + if (err < 0) { + alsa_logerr (err, "Could not prepare handle for %s\n", typ); + return -1; + } + if (ctl == VOICE_CTL_START) { + err = snd_pcm_start(handle); + if (err < 0) { + alsa_logerr (err, "Could not start handle for %s\n", typ); + return -1; + } + } + } + + return 0; +} + +static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) 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 && alsa_poll_out (hw)) { + poll_mode = 0; + } + hw->poll_mode = poll_mode; + return alsa_voice_ctl (alsa->handle, "playback", VOICE_CTL_PREPARE); + } + + case VOICE_DISABLE: + ldebug ("disabling voice\n"); + if (hw->poll_mode) { + hw->poll_mode = 0; + alsa_fini_poll (&alsa->pollhlp); + } + return alsa_voice_ctl (alsa->handle, "playback", VOICE_CTL_PAUSE); + } + + return -1; +} + +static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + struct alsa_params_req req; + struct alsa_params_obt obt; + snd_pcm_t *handle; + struct audsettings obt_as; + ALSAConf *conf = drv_opaque; + + req.fmt = aud_to_alsafmt (as->fmt, as->endianness); + req.freq = as->freq; + req.nchannels = as->nchannels; + req.period_size = conf->period_size_in; + req.buffer_size = conf->buffer_size_in; + req.size_in_usec = conf->size_in_usec_in; + req.override_mask = + (conf->period_size_in_overridden ? 1 : 0) | + (conf->buffer_size_in_overridden ? 2 : 0); + + if (alsa_open (1, &req, &obt, &handle, conf)) { + return -1; + } + + obt_as.freq = obt.freq; + obt_as.nchannels = obt.nchannels; + obt_as.fmt = obt.fmt; + obt_as.endianness = obt.endianness; + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = obt.samples; + + alsa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!alsa->pcm_buf) { + dolog ("Could not allocate ADC buffer (%d samples, each %d bytes)\n", + hw->samples, 1 << hw->info.shift); + alsa_anal_close1 (&handle); + return -1; + } + + alsa->handle = handle; + alsa->pollhlp.conf = conf; + return 0; +} + +static void alsa_fini_in (HWVoiceIn *hw) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + + alsa_anal_close (&alsa->handle, &alsa->pollhlp); + + g_free(alsa->pcm_buf); + alsa->pcm_buf = NULL; +} + +static int alsa_run_in (HWVoiceIn *hw) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + int hwshift = hw->info.shift; + int i; + int live = audio_pcm_hw_get_live_in (hw); + int dead = hw->samples - live; + int decr; + struct { + int add; + int len; + } bufs[2] = { + { .add = hw->wpos, .len = 0 }, + { .add = 0, .len = 0 } + }; + snd_pcm_sframes_t avail; + snd_pcm_uframes_t read_samples = 0; + + if (!dead) { + return 0; + } + + avail = alsa_get_avail (alsa->handle); + if (avail < 0) { + dolog ("Could not get number of captured frames\n"); + return 0; + } + + if (!avail) { + snd_pcm_state_t state; + + state = snd_pcm_state (alsa->handle); + switch (state) { + case SND_PCM_STATE_PREPARED: + avail = hw->samples; + break; + case SND_PCM_STATE_SUSPENDED: + /* stream is suspended and waiting for an application recovery */ + if (alsa_resume (alsa->handle)) { + dolog ("Failed to resume suspended input stream\n"); + return 0; + } + trace_alsa_resume_in(); + break; + default: + trace_alsa_no_frames(state); + return 0; + } + } + + decr = audio_MIN (dead, avail); + if (!decr) { + return 0; + } + + if (hw->wpos + decr > hw->samples) { + bufs[0].len = (hw->samples - hw->wpos); + bufs[1].len = (decr - (hw->samples - hw->wpos)); + } + else { + bufs[0].len = decr; + } + + for (i = 0; i < 2; ++i) { + void *src; + struct st_sample *dst; + snd_pcm_sframes_t nread; + snd_pcm_uframes_t len; + + len = bufs[i].len; + + src = advance (alsa->pcm_buf, bufs[i].add << hwshift); + dst = hw->conv_buf + bufs[i].add; + + while (len) { + nread = snd_pcm_readi (alsa->handle, src, len); + + if (nread <= 0) { + switch (nread) { + case 0: + trace_alsa_read_zero(len); + goto exit; + + case -EPIPE: + if (alsa_recover (alsa->handle)) { + alsa_logerr (nread, "Failed to read %ld frames\n", len); + goto exit; + } + trace_alsa_xrun_in(); + continue; + + case -EAGAIN: + goto exit; + + default: + alsa_logerr ( + nread, + "Failed to read %ld frames from %p\n", + len, + src + ); + goto exit; + } + } + + hw->conv (dst, src, nread); + + src = advance (src, nread << hwshift); + dst += nread; + + read_samples += nread; + len -= nread; + } + } + + exit: + hw->wpos = (hw->wpos + read_samples) % hw->samples; + return read_samples; +} + +static int alsa_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) 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 && alsa_poll_in (hw)) { + poll_mode = 0; + } + hw->poll_mode = poll_mode; + + return alsa_voice_ctl (alsa->handle, "capture", VOICE_CTL_START); + } + + case VOICE_DISABLE: + ldebug ("disabling voice\n"); + if (hw->poll_mode) { + hw->poll_mode = 0; + alsa_fini_poll (&alsa->pollhlp); + } + return alsa_voice_ctl (alsa->handle, "capture", VOICE_CTL_PAUSE); + } + + return -1; +} + +static ALSAConf glob_conf = { + .buffer_size_out = 4096, + .period_size_out = 1024, + .pcm_name_out = "default", + .pcm_name_in = "default", +}; + +static void *alsa_audio_init (void) +{ + ALSAConf *conf = g_malloc(sizeof(ALSAConf)); + *conf = glob_conf; + return conf; +} + +static void alsa_audio_fini (void *opaque) +{ + g_free(opaque); +} + +static struct audio_option alsa_options[] = { + { + .name = "DAC_SIZE_IN_USEC", + .tag = AUD_OPT_BOOL, + .valp = &glob_conf.size_in_usec_out, + .descr = "DAC period/buffer size in microseconds (otherwise in frames)" + }, + { + .name = "DAC_PERIOD_SIZE", + .tag = AUD_OPT_INT, + .valp = &glob_conf.period_size_out, + .descr = "DAC period size (0 to go with system default)", + .overriddenp = &glob_conf.period_size_out_overridden + }, + { + .name = "DAC_BUFFER_SIZE", + .tag = AUD_OPT_INT, + .valp = &glob_conf.buffer_size_out, + .descr = "DAC buffer size (0 to go with system default)", + .overriddenp = &glob_conf.buffer_size_out_overridden + }, + { + .name = "ADC_SIZE_IN_USEC", + .tag = AUD_OPT_BOOL, + .valp = &glob_conf.size_in_usec_in, + .descr = + "ADC period/buffer size in microseconds (otherwise in frames)" + }, + { + .name = "ADC_PERIOD_SIZE", + .tag = AUD_OPT_INT, + .valp = &glob_conf.period_size_in, + .descr = "ADC period size (0 to go with system default)", + .overriddenp = &glob_conf.period_size_in_overridden + }, + { + .name = "ADC_BUFFER_SIZE", + .tag = AUD_OPT_INT, + .valp = &glob_conf.buffer_size_in, + .descr = "ADC buffer size (0 to go with system default)", + .overriddenp = &glob_conf.buffer_size_in_overridden + }, + { + .name = "THRESHOLD", + .tag = AUD_OPT_INT, + .valp = &glob_conf.threshold, + .descr = "(undocumented)" + }, + { + .name = "DAC_DEV", + .tag = AUD_OPT_STR, + .valp = &glob_conf.pcm_name_out, + .descr = "DAC device name (for instance dmix)" + }, + { + .name = "ADC_DEV", + .tag = AUD_OPT_STR, + .valp = &glob_conf.pcm_name_in, + .descr = "ADC device name" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops alsa_pcm_ops = { + .init_out = alsa_init_out, + .fini_out = alsa_fini_out, + .run_out = alsa_run_out, + .write = alsa_write, + .ctl_out = alsa_ctl_out, + + .init_in = alsa_init_in, + .fini_in = alsa_fini_in, + .run_in = alsa_run_in, + .read = alsa_read, + .ctl_in = alsa_ctl_in, +}; + +struct audio_driver alsa_audio_driver = { + .name = "alsa", + .descr = "ALSA http://www.alsa-project.org", + .options = alsa_options, + .init = alsa_audio_init, + .fini = alsa_audio_fini, + .pcm_ops = &alsa_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (ALSAVoiceOut), + .voice_size_in = sizeof (ALSAVoiceIn) +}; diff --git a/src/audio/audio.c b/src/audio/audio.c new file mode 100644 index 0000000..5be4b15 --- /dev/null +++ b/src/audio/audio.c @@ -0,0 +1,2063 @@ +/* + * QEMU Audio subsystem + * + * 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 "hw/hw.h" +#include "audio.h" +#include "monitor/monitor.h" +#include "qemu/timer.h" +#include "sysemu/sysemu.h" + +#define AUDIO_CAP "audio" +#include "audio_int.h" + +/* #define DEBUG_LIVE */ +/* #define DEBUG_OUT */ +/* #define DEBUG_CAPTURE */ +/* #define DEBUG_POLL */ + +#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown" + + +/* Order of CONFIG_AUDIO_DRIVERS is import. + The 1st one is the one used by default, that is the reason + that we generate the list. +*/ +static struct audio_driver *drvtab[] = { +#ifdef CONFIG_SPICE + &spice_audio_driver, +#endif + CONFIG_AUDIO_DRIVERS + &no_audio_driver, + &wav_audio_driver +}; + +struct fixed_settings { + int enabled; + int nb_voices; + int greedy; + struct audsettings settings; +}; + +static struct { + struct fixed_settings fixed_out; + struct fixed_settings fixed_in; + union { + int hertz; + int64_t ticks; + } period; + int try_poll_in; + int try_poll_out; +} conf = { + .fixed_out = { /* DAC fixed settings */ + .enabled = 1, + .nb_voices = 1, + .greedy = 1, + .settings = { + .freq = 44100, + .nchannels = 2, + .fmt = AUD_FMT_S16, + .endianness = AUDIO_HOST_ENDIANNESS, + } + }, + + .fixed_in = { /* ADC fixed settings */ + .enabled = 1, + .nb_voices = 1, + .greedy = 1, + .settings = { + .freq = 44100, + .nchannels = 2, + .fmt = AUD_FMT_S16, + .endianness = AUDIO_HOST_ENDIANNESS, + } + }, + + .period = { .hertz = 100 }, + .try_poll_in = 1, + .try_poll_out = 1, +}; + +static AudioState glob_audio_state; + +const struct mixeng_volume nominal_volume = { + .mute = 0, +#ifdef FLOAT_MIXENG + .r = 1.0, + .l = 1.0, +#else + .r = 1ULL << 32, + .l = 1ULL << 32, +#endif +}; + +#ifdef AUDIO_IS_FLAWLESS_AND_NO_CHECKS_ARE_REQURIED +#error No its not +#else +static void audio_print_options (const char *prefix, + struct audio_option *opt); + +int audio_bug (const char *funcname, int cond) +{ + if (cond) { + static int shown; + + AUD_log (NULL, "A bug was just triggered in %s\n", funcname); + if (!shown) { + struct audio_driver *d; + + shown = 1; + AUD_log (NULL, "Save all your work and restart without audio\n"); + AUD_log (NULL, "Please send bug report to av1474@comtv.ru\n"); + AUD_log (NULL, "I am sorry\n"); + d = glob_audio_state.drv; + if (d) { + audio_print_options (d->name, d->options); + } + } + AUD_log (NULL, "Context:\n"); + +#if defined AUDIO_BREAKPOINT_ON_BUG +# if defined HOST_I386 +# if defined __GNUC__ + __asm__ ("int3"); +# elif defined _MSC_VER + _asm _emit 0xcc; +# else + abort (); +# endif +# else + abort (); +# endif +#endif + } + + return cond; +} +#endif + +static inline int audio_bits_to_index (int bits) +{ + switch (bits) { + case 8: + return 0; + + case 16: + return 1; + + case 32: + return 2; + + default: + audio_bug ("bits_to_index", 1); + AUD_log (NULL, "invalid bits %d\n", bits); + return 0; + } +} + +void *audio_calloc (const char *funcname, int nmemb, size_t size) +{ + int cond; + size_t len; + + len = nmemb * size; + cond = !nmemb || !size; + cond |= nmemb < 0; + cond |= len < size; + + if (audio_bug ("audio_calloc", cond)) { + AUD_log (NULL, "%s passed invalid arguments to audio_calloc\n", + funcname); + AUD_log (NULL, "nmemb=%d size=%zu (len=%zu)\n", nmemb, size, len); + return NULL; + } + + return g_malloc0 (len); +} + +static char *audio_alloc_prefix (const char *s) +{ + const char qemu_prefix[] = "QEMU_"; + size_t len, i; + char *r, *u; + + if (!s) { + return NULL; + } + + len = strlen (s); + r = g_malloc (len + sizeof (qemu_prefix)); + + u = r + sizeof (qemu_prefix) - 1; + + pstrcpy (r, len + sizeof (qemu_prefix), qemu_prefix); + pstrcat (r, len + sizeof (qemu_prefix), s); + + for (i = 0; i < len; ++i) { + u[i] = qemu_toupper(u[i]); + } + + return r; +} + +static const char *audio_audfmt_to_string (audfmt_e fmt) +{ + switch (fmt) { + case AUD_FMT_U8: + return "U8"; + + case AUD_FMT_U16: + return "U16"; + + case AUD_FMT_S8: + return "S8"; + + case AUD_FMT_S16: + return "S16"; + + case AUD_FMT_U32: + return "U32"; + + case AUD_FMT_S32: + return "S32"; + } + + dolog ("Bogus audfmt %d returning S16\n", fmt); + return "S16"; +} + +static audfmt_e audio_string_to_audfmt (const char *s, audfmt_e defval, + int *defaultp) +{ + if (!strcasecmp (s, "u8")) { + *defaultp = 0; + return AUD_FMT_U8; + } + else if (!strcasecmp (s, "u16")) { + *defaultp = 0; + return AUD_FMT_U16; + } + else if (!strcasecmp (s, "u32")) { + *defaultp = 0; + return AUD_FMT_U32; + } + else if (!strcasecmp (s, "s8")) { + *defaultp = 0; + return AUD_FMT_S8; + } + else if (!strcasecmp (s, "s16")) { + *defaultp = 0; + return AUD_FMT_S16; + } + else if (!strcasecmp (s, "s32")) { + *defaultp = 0; + return AUD_FMT_S32; + } + else { + dolog ("Bogus audio format `%s' using %s\n", + s, audio_audfmt_to_string (defval)); + *defaultp = 1; + return defval; + } +} + +static audfmt_e audio_get_conf_fmt (const char *envname, + audfmt_e defval, + int *defaultp) +{ + const char *var = getenv (envname); + if (!var) { + *defaultp = 1; + return defval; + } + return audio_string_to_audfmt (var, defval, defaultp); +} + +static int audio_get_conf_int (const char *key, int defval, int *defaultp) +{ + int val; + char *strval; + + strval = getenv (key); + if (strval) { + *defaultp = 0; + val = atoi (strval); + return val; + } + else { + *defaultp = 1; + return defval; + } +} + +static const char *audio_get_conf_str (const char *key, + const char *defval, + int *defaultp) +{ + const char *val = getenv (key); + if (!val) { + *defaultp = 1; + return defval; + } + else { + *defaultp = 0; + return val; + } +} + +void AUD_vlog (const char *cap, const char *fmt, va_list ap) +{ + if (cap) { + fprintf(stderr, "%s: ", cap); + } + + vfprintf(stderr, fmt, ap); +} + +void AUD_log (const char *cap, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (cap, fmt, ap); + va_end (ap); +} + +static void audio_print_options (const char *prefix, + struct audio_option *opt) +{ + char *uprefix; + + if (!prefix) { + dolog ("No prefix specified\n"); + return; + } + + if (!opt) { + dolog ("No options\n"); + return; + } + + uprefix = audio_alloc_prefix (prefix); + + for (; opt->name; opt++) { + const char *state = "default"; + printf (" %s_%s: ", uprefix, opt->name); + + if (opt->overriddenp && *opt->overriddenp) { + state = "current"; + } + + switch (opt->tag) { + case AUD_OPT_BOOL: + { + int *intp = opt->valp; + printf ("boolean, %s = %d\n", state, *intp ? 1 : 0); + } + break; + + case AUD_OPT_INT: + { + int *intp = opt->valp; + printf ("integer, %s = %d\n", state, *intp); + } + break; + + case AUD_OPT_FMT: + { + audfmt_e *fmtp = opt->valp; + printf ( + "format, %s = %s, (one of: U8 S8 U16 S16 U32 S32)\n", + state, + audio_audfmt_to_string (*fmtp) + ); + } + break; + + case AUD_OPT_STR: + { + const char **strp = opt->valp; + printf ("string, %s = %s\n", + state, + *strp ? *strp : "(not set)"); + } + break; + + default: + printf ("???\n"); + dolog ("Bad value tag for option %s_%s %d\n", + uprefix, opt->name, opt->tag); + break; + } + printf (" %s\n", opt->descr); + } + + g_free (uprefix); +} + +static void audio_process_options (const char *prefix, + struct audio_option *opt) +{ + char *optname; + const char qemu_prefix[] = "QEMU_"; + size_t preflen, optlen; + + if (audio_bug (AUDIO_FUNC, !prefix)) { + dolog ("prefix = NULL\n"); + return; + } + + if (audio_bug (AUDIO_FUNC, !opt)) { + dolog ("opt = NULL\n"); + return; + } + + preflen = strlen (prefix); + + for (; opt->name; opt++) { + size_t len, i; + int def; + + if (!opt->valp) { + dolog ("Option value pointer for `%s' is not set\n", + opt->name); + continue; + } + + len = strlen (opt->name); + /* len of opt->name + len of prefix + size of qemu_prefix + * (includes trailing zero) + zero + underscore (on behalf of + * sizeof) */ + optlen = len + preflen + sizeof (qemu_prefix) + 1; + optname = g_malloc (optlen); + + pstrcpy (optname, optlen, qemu_prefix); + + /* copy while upper-casing, including trailing zero */ + for (i = 0; i <= preflen; ++i) { + optname[i + sizeof (qemu_prefix) - 1] = qemu_toupper(prefix[i]); + } + pstrcat (optname, optlen, "_"); + pstrcat (optname, optlen, opt->name); + + def = 1; + switch (opt->tag) { + case AUD_OPT_BOOL: + case AUD_OPT_INT: + { + int *intp = opt->valp; + *intp = audio_get_conf_int (optname, *intp, &def); + } + break; + + case AUD_OPT_FMT: + { + audfmt_e *fmtp = opt->valp; + *fmtp = audio_get_conf_fmt (optname, *fmtp, &def); + } + break; + + case AUD_OPT_STR: + { + const char **strp = opt->valp; + *strp = audio_get_conf_str (optname, *strp, &def); + } + break; + + default: + dolog ("Bad value tag for option `%s' - %d\n", + optname, opt->tag); + break; + } + + if (!opt->overriddenp) { + opt->overriddenp = &opt->overridden; + } + *opt->overriddenp = !def; + g_free (optname); + } +} + +static void audio_print_settings (struct audsettings *as) +{ + dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels); + + switch (as->fmt) { + case AUD_FMT_S8: + AUD_log (NULL, "S8"); + break; + case AUD_FMT_U8: + AUD_log (NULL, "U8"); + break; + case AUD_FMT_S16: + AUD_log (NULL, "S16"); + break; + case AUD_FMT_U16: + AUD_log (NULL, "U16"); + break; + case AUD_FMT_S32: + AUD_log (NULL, "S32"); + break; + case AUD_FMT_U32: + AUD_log (NULL, "U32"); + break; + default: + AUD_log (NULL, "invalid(%d)", as->fmt); + break; + } + + AUD_log (NULL, " endianness="); + switch (as->endianness) { + case 0: + AUD_log (NULL, "little"); + break; + case 1: + AUD_log (NULL, "big"); + break; + default: + AUD_log (NULL, "invalid"); + break; + } + AUD_log (NULL, "\n"); +} + +static int audio_validate_settings (struct audsettings *as) +{ + int invalid; + + invalid = as->nchannels != 1 && as->nchannels != 2; + invalid |= as->endianness != 0 && as->endianness != 1; + + switch (as->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + case AUD_FMT_S16: + case AUD_FMT_U16: + case AUD_FMT_S32: + case AUD_FMT_U32: + break; + default: + invalid = 1; + break; + } + + invalid |= as->freq <= 0; + return invalid ? -1 : 0; +} + +static int audio_pcm_info_eq (struct audio_pcm_info *info, struct audsettings *as) +{ + int bits = 8, sign = 0; + + switch (as->fmt) { + case AUD_FMT_S8: + sign = 1; + /* fall through */ + case AUD_FMT_U8: + break; + + case AUD_FMT_S16: + sign = 1; + /* fall through */ + case AUD_FMT_U16: + bits = 16; + break; + + case AUD_FMT_S32: + sign = 1; + /* fall through */ + case AUD_FMT_U32: + bits = 32; + break; + } + return info->freq == as->freq + && info->nchannels == as->nchannels + && info->sign == sign + && info->bits == bits + && info->swap_endianness == (as->endianness != AUDIO_HOST_ENDIANNESS); +} + +void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as) +{ + int bits = 8, sign = 0, shift = 0; + + switch (as->fmt) { + case AUD_FMT_S8: + sign = 1; + case AUD_FMT_U8: + break; + + case AUD_FMT_S16: + sign = 1; + case AUD_FMT_U16: + bits = 16; + shift = 1; + break; + + case AUD_FMT_S32: + sign = 1; + case AUD_FMT_U32: + bits = 32; + shift = 2; + break; + } + + info->freq = as->freq; + info->bits = bits; + info->sign = sign; + info->nchannels = as->nchannels; + info->shift = (as->nchannels == 2) + shift; + info->align = (1 << info->shift) - 1; + info->bytes_per_second = info->freq << info->shift; + info->swap_endianness = (as->endianness != AUDIO_HOST_ENDIANNESS); +} + +void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len) +{ + if (!len) { + return; + } + + if (info->sign) { + memset (buf, 0x00, len << info->shift); + } + else { + switch (info->bits) { + case 8: + memset (buf, 0x80, len << info->shift); + break; + + case 16: + { + int i; + uint16_t *p = buf; + int shift = info->nchannels - 1; + short s = INT16_MAX; + + if (info->swap_endianness) { + s = bswap16 (s); + } + + for (i = 0; i < len << shift; i++) { + p[i] = s; + } + } + break; + + case 32: + { + int i; + uint32_t *p = buf; + int shift = info->nchannels - 1; + int32_t s = INT32_MAX; + + if (info->swap_endianness) { + s = bswap32 (s); + } + + for (i = 0; i < len << shift; i++) { + p[i] = s; + } + } + break; + + default: + AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n", + info->bits); + break; + } + } +} + +/* + * Capture + */ +static void noop_conv (struct st_sample *dst, const void *src, int samples) +{ + (void) src; + (void) dst; + (void) samples; +} + +static CaptureVoiceOut *audio_pcm_capture_find_specific ( + struct audsettings *as + ) +{ + CaptureVoiceOut *cap; + AudioState *s = &glob_audio_state; + + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { + if (audio_pcm_info_eq (&cap->hw.info, as)) { + return cap; + } + } + return NULL; +} + +static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd) +{ + struct capture_callback *cb; + +#ifdef DEBUG_CAPTURE + dolog ("notification %d sent\n", cmd); +#endif + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + cb->ops.notify (cb->opaque, cmd); + } +} + +static void audio_capture_maybe_changed (CaptureVoiceOut *cap, int enabled) +{ + if (cap->hw.enabled != enabled) { + audcnotification_e cmd; + cap->hw.enabled = enabled; + cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE; + audio_notify_capture (cap, cmd); + } +} + +static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap) +{ + HWVoiceOut *hw = &cap->hw; + SWVoiceOut *sw; + int enabled = 0; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + enabled = 1; + break; + } + } + audio_capture_maybe_changed (cap, enabled); +} + +static void audio_detach_capture (HWVoiceOut *hw) +{ + SWVoiceCap *sc = hw->cap_head.lh_first; + + while (sc) { + SWVoiceCap *sc1 = sc->entries.le_next; + SWVoiceOut *sw = &sc->sw; + CaptureVoiceOut *cap = sc->cap; + int was_active = sw->active; + + if (sw->rate) { + st_rate_stop (sw->rate); + sw->rate = NULL; + } + + QLIST_REMOVE (sw, entries); + QLIST_REMOVE (sc, entries); + g_free (sc); + if (was_active) { + /* We have removed soft voice from the capture: + this might have changed the overall status of the capture + since this might have been the only active voice */ + audio_recalc_and_notify_capture (cap); + } + sc = sc1; + } +} + +static int audio_attach_capture (HWVoiceOut *hw) +{ + AudioState *s = &glob_audio_state; + CaptureVoiceOut *cap; + + audio_detach_capture (hw); + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { + SWVoiceCap *sc; + SWVoiceOut *sw; + HWVoiceOut *hw_cap = &cap->hw; + + sc = audio_calloc (AUDIO_FUNC, 1, sizeof (*sc)); + if (!sc) { + dolog ("Could not allocate soft capture voice (%zu bytes)\n", + sizeof (*sc)); + return -1; + } + + sc->cap = cap; + sw = &sc->sw; + sw->hw = hw_cap; + sw->info = hw->info; + sw->empty = 1; + sw->active = hw->enabled; + sw->conv = noop_conv; + sw->ratio = ((int64_t) hw_cap->info.freq << 32) / sw->info.freq; + sw->vol = nominal_volume; + sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq); + if (!sw->rate) { + dolog ("Could not start rate conversion for `%s'\n", SW_NAME (sw)); + g_free (sw); + return -1; + } + QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries); + QLIST_INSERT_HEAD (&hw->cap_head, sc, entries); +#ifdef DEBUG_CAPTURE + sw->name = g_strdup_printf ("for %p %d,%d,%d", + hw, sw->info.freq, sw->info.bits, + sw->info.nchannels); + dolog ("Added %s active = %d\n", sw->name, sw->active); +#endif + if (sw->active) { + audio_capture_maybe_changed (cap, 1); + } + } + return 0; +} + +/* + * Hard voice (capture) + */ +static int audio_pcm_hw_find_min_in (HWVoiceIn *hw) +{ + SWVoiceIn *sw; + int m = hw->total_samples_captured; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + m = audio_MIN (m, sw->total_hw_samples_acquired); + } + } + return m; +} + +int audio_pcm_hw_get_live_in (HWVoiceIn *hw) +{ + int live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw); + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + return live; +} + +int audio_pcm_hw_clip_out (HWVoiceOut *hw, void *pcm_buf, + int live, int pending) +{ + int left = hw->samples - pending; + int len = audio_MIN (left, live); + int clipped = 0; + + while (len) { + struct st_sample *src = hw->mix_buf + hw->rpos; + uint8_t *dst = advance (pcm_buf, hw->rpos << hw->info.shift); + int samples_till_end_of_buf = hw->samples - hw->rpos; + int samples_to_clip = audio_MIN (len, samples_till_end_of_buf); + + hw->clip (dst, src, samples_to_clip); + + hw->rpos = (hw->rpos + samples_to_clip) % hw->samples; + len -= samples_to_clip; + clipped += samples_to_clip; + } + return clipped; +} + +/* + * Soft voice (capture) + */ +static int audio_pcm_sw_get_rpos_in (SWVoiceIn *sw) +{ + HWVoiceIn *hw = sw->hw; + int live = hw->total_samples_captured - sw->total_hw_samples_acquired; + int rpos; + + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + + rpos = hw->wpos - live; + if (rpos >= 0) { + return rpos; + } + else { + return hw->samples + rpos; + } +} + +int audio_pcm_sw_read (SWVoiceIn *sw, void *buf, int size) +{ + HWVoiceIn *hw = sw->hw; + int samples, live, ret = 0, swlim, isamp, osamp, rpos, total = 0; + struct st_sample *src, *dst = sw->buf; + + rpos = audio_pcm_sw_get_rpos_in (sw) % hw->samples; + + live = hw->total_samples_captured - sw->total_hw_samples_acquired; + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live_in=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + + samples = size >> sw->info.shift; + if (!live) { + return 0; + } + + swlim = (live * sw->ratio) >> 32; + swlim = audio_MIN (swlim, samples); + + while (swlim) { + src = hw->conv_buf + rpos; + isamp = hw->wpos - rpos; + /* XXX: <= ? */ + if (isamp <= 0) { + isamp = hw->samples - rpos; + } + + if (!isamp) { + break; + } + osamp = swlim; + + if (audio_bug (AUDIO_FUNC, osamp < 0)) { + dolog ("osamp=%d\n", osamp); + return 0; + } + + st_rate_flow (sw->rate, src, dst, &isamp, &osamp); + swlim -= osamp; + rpos = (rpos + isamp) % hw->samples; + dst += osamp; + ret += osamp; + total += isamp; + } + + if (!(hw->ctl_caps & VOICE_VOLUME_CAP)) { + mixeng_volume (sw->buf, ret, &sw->vol); + } + + sw->clip (buf, sw->buf, ret); + sw->total_hw_samples_acquired += total; + return ret << sw->info.shift; +} + +/* + * Hard voice (playback) + */ +static int audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep) +{ + SWVoiceOut *sw; + int m = INT_MAX; + int nb_live = 0; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active || !sw->empty) { + m = audio_MIN (m, sw->total_hw_samples_mixed); + nb_live += 1; + } + } + + *nb_livep = nb_live; + return m; +} + +static int audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live) +{ + int smin; + int nb_live1; + + smin = audio_pcm_hw_find_min_out (hw, &nb_live1); + if (nb_live) { + *nb_live = nb_live1; + } + + if (nb_live1) { + int live = smin; + + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + return live; + } + return 0; +} + +/* + * Soft voice (playback) + */ +int audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int size) +{ + int hwsamples, samples, isamp, osamp, wpos, live, dead, left, swlim, blck; + int ret = 0, pos = 0, total = 0; + + if (!sw) { + return size; + } + + hwsamples = sw->hw->samples; + + live = sw->total_hw_samples_mixed; + if (audio_bug (AUDIO_FUNC, live < 0 || live > hwsamples)){ + dolog ("live=%d hw->samples=%d\n", live, hwsamples); + return 0; + } + + if (live == hwsamples) { +#ifdef DEBUG_OUT + dolog ("%s is full %d\n", sw->name, live); +#endif + return 0; + } + + wpos = (sw->hw->rpos + live) % hwsamples; + samples = size >> sw->info.shift; + + dead = hwsamples - live; + swlim = ((int64_t) dead << 32) / sw->ratio; + swlim = audio_MIN (swlim, samples); + if (swlim) { + sw->conv (sw->buf, buf, swlim); + + if (!(sw->hw->ctl_caps & VOICE_VOLUME_CAP)) { + mixeng_volume (sw->buf, swlim, &sw->vol); + } + } + + while (swlim) { + dead = hwsamples - live; + left = hwsamples - wpos; + blck = audio_MIN (dead, left); + if (!blck) { + break; + } + isamp = swlim; + osamp = blck; + st_rate_flow_mix ( + sw->rate, + sw->buf + pos, + sw->hw->mix_buf + wpos, + &isamp, + &osamp + ); + ret += isamp; + swlim -= isamp; + pos += isamp; + live += osamp; + wpos = (wpos + osamp) % hwsamples; + total += osamp; + } + + sw->total_hw_samples_mixed += total; + sw->empty = sw->total_hw_samples_mixed == 0; + +#ifdef DEBUG_OUT + dolog ( + "%s: write size %d ret %d total sw %d\n", + SW_NAME (sw), + size >> sw->info.shift, + ret, + sw->total_hw_samples_mixed + ); +#endif + + return ret << sw->info.shift; +} + +#ifdef DEBUG_AUDIO +static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info) +{ + dolog ("%s: bits %d, sign %d, freq %d, nchan %d\n", + cap, info->bits, info->sign, info->freq, info->nchannels); +} +#endif + +#define DAC +#include "audio_template.h" +#undef DAC +#include "audio_template.h" + +/* + * Timer + */ +static int audio_is_timer_needed (void) +{ + HWVoiceIn *hwi = NULL; + HWVoiceOut *hwo = NULL; + + while ((hwo = audio_pcm_hw_find_any_enabled_out (hwo))) { + if (!hwo->poll_mode) return 1; + } + while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) { + if (!hwi->poll_mode) return 1; + } + return 0; +} + +static void audio_reset_timer (AudioState *s) +{ + if (audio_is_timer_needed ()) { + timer_mod (s->ts, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + conf.period.ticks); + } + else { + timer_del (s->ts); + } +} + +static void audio_timer (void *opaque) +{ + audio_run ("timer"); + audio_reset_timer (opaque); +} + +/* + * Public API + */ +int AUD_write (SWVoiceOut *sw, void *buf, int size) +{ + int bytes; + + if (!sw) { + /* XXX: Consider options */ + return size; + } + + if (!sw->hw->enabled) { + dolog ("Writing to disabled voice %s\n", SW_NAME (sw)); + return 0; + } + + bytes = sw->hw->pcm_ops->write (sw, buf, size); + return bytes; +} + +int AUD_read (SWVoiceIn *sw, void *buf, int size) +{ + int bytes; + + if (!sw) { + /* XXX: Consider options */ + return size; + } + + if (!sw->hw->enabled) { + dolog ("Reading from disabled voice %s\n", SW_NAME (sw)); + return 0; + } + + bytes = sw->hw->pcm_ops->read (sw, buf, size); + return bytes; +} + +int AUD_get_buffer_size_out (SWVoiceOut *sw) +{ + return sw->hw->samples << sw->hw->info.shift; +} + +void AUD_set_active_out (SWVoiceOut *sw, int on) +{ + HWVoiceOut *hw; + + if (!sw) { + return; + } + + hw = sw->hw; + if (sw->active != on) { + AudioState *s = &glob_audio_state; + SWVoiceOut *temp_sw; + SWVoiceCap *sc; + + if (on) { + hw->pending_disable = 0; + if (!hw->enabled) { + hw->enabled = 1; + if (s->vm_running) { + hw->pcm_ops->ctl_out (hw, VOICE_ENABLE, conf.try_poll_out); + audio_reset_timer (s); + } + } + } + else { + if (hw->enabled) { + int nb_active = 0; + + for (temp_sw = hw->sw_head.lh_first; temp_sw; + temp_sw = temp_sw->entries.le_next) { + nb_active += temp_sw->active != 0; + } + + hw->pending_disable = nb_active == 1; + } + } + + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { + sc->sw.active = hw->enabled; + if (hw->enabled) { + audio_capture_maybe_changed (sc->cap, 1); + } + } + sw->active = on; + } +} + +void AUD_set_active_in (SWVoiceIn *sw, int on) +{ + HWVoiceIn *hw; + + if (!sw) { + return; + } + + hw = sw->hw; + if (sw->active != on) { + AudioState *s = &glob_audio_state; + SWVoiceIn *temp_sw; + + if (on) { + if (!hw->enabled) { + hw->enabled = 1; + if (s->vm_running) { + hw->pcm_ops->ctl_in (hw, VOICE_ENABLE, conf.try_poll_in); + audio_reset_timer (s); + } + } + sw->total_hw_samples_acquired = hw->total_samples_captured; + } + else { + if (hw->enabled) { + int nb_active = 0; + + for (temp_sw = hw->sw_head.lh_first; temp_sw; + temp_sw = temp_sw->entries.le_next) { + nb_active += temp_sw->active != 0; + } + + if (nb_active == 1) { + hw->enabled = 0; + hw->pcm_ops->ctl_in (hw, VOICE_DISABLE); + } + } + } + sw->active = on; + } +} + +static int audio_get_avail (SWVoiceIn *sw) +{ + int live; + + if (!sw) { + return 0; + } + + live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; + if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) { + dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples); + return 0; + } + + ldebug ( + "%s: get_avail live %d ret %" PRId64 "\n", + SW_NAME (sw), + live, (((int64_t) live << 32) / sw->ratio) << sw->info.shift + ); + + return (((int64_t) live << 32) / sw->ratio) << sw->info.shift; +} + +static int audio_get_free (SWVoiceOut *sw) +{ + int live, dead; + + if (!sw) { + return 0; + } + + live = sw->total_hw_samples_mixed; + + if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) { + dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples); + return 0; + } + + dead = sw->hw->samples - live; + +#ifdef DEBUG_OUT + dolog ("%s: get_free live %d dead %d ret %" PRId64 "\n", + SW_NAME (sw), + live, dead, (((int64_t) dead << 32) / sw->ratio) << sw->info.shift); +#endif + + return (((int64_t) dead << 32) / sw->ratio) << sw->info.shift; +} + +static void audio_capture_mix_and_clear (HWVoiceOut *hw, int rpos, int samples) +{ + int n; + + if (hw->enabled) { + SWVoiceCap *sc; + + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { + SWVoiceOut *sw = &sc->sw; + int rpos2 = rpos; + + n = samples; + while (n) { + int till_end_of_hw = hw->samples - rpos2; + int to_write = audio_MIN (till_end_of_hw, n); + int bytes = to_write << hw->info.shift; + int written; + + sw->buf = hw->mix_buf + rpos2; + written = audio_pcm_sw_write (sw, NULL, bytes); + if (written - bytes) { + dolog ("Could not mix %d bytes into a capture " + "buffer, mixed %d\n", + bytes, written); + break; + } + n -= to_write; + rpos2 = (rpos2 + to_write) % hw->samples; + } + } + } + + n = audio_MIN (samples, hw->samples - rpos); + mixeng_clear (hw->mix_buf + rpos, n); + mixeng_clear (hw->mix_buf, samples - n); +} + +static void audio_run_out (AudioState *s) +{ + HWVoiceOut *hw = NULL; + SWVoiceOut *sw; + + while ((hw = audio_pcm_hw_find_any_enabled_out (hw))) { + int played; + int live, free, nb_live, cleanup_required, prev_rpos; + + live = audio_pcm_hw_get_live_out (hw, &nb_live); + if (!nb_live) { + live = 0; + } + + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + continue; + } + + if (hw->pending_disable && !nb_live) { + SWVoiceCap *sc; +#ifdef DEBUG_OUT + dolog ("Disabling voice\n"); +#endif + hw->enabled = 0; + hw->pending_disable = 0; + hw->pcm_ops->ctl_out (hw, VOICE_DISABLE); + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { + sc->sw.active = 0; + audio_recalc_and_notify_capture (sc->cap); + } + continue; + } + + if (!live) { + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + free = audio_get_free (sw); + if (free > 0) { + sw->callback.fn (sw->callback.opaque, free); + } + } + } + continue; + } + + prev_rpos = hw->rpos; + played = hw->pcm_ops->run_out (hw, live); + if (audio_bug (AUDIO_FUNC, hw->rpos >= hw->samples)) { + dolog ("hw->rpos=%d hw->samples=%d played=%d\n", + hw->rpos, hw->samples, played); + hw->rpos = 0; + } + +#ifdef DEBUG_OUT + dolog ("played=%d\n", played); +#endif + + if (played) { + hw->ts_helper += played; + audio_capture_mix_and_clear (hw, prev_rpos, played); + } + + cleanup_required = 0; + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (!sw->active && sw->empty) { + continue; + } + + if (audio_bug (AUDIO_FUNC, played > sw->total_hw_samples_mixed)) { + dolog ("played=%d sw->total_hw_samples_mixed=%d\n", + played, sw->total_hw_samples_mixed); + played = sw->total_hw_samples_mixed; + } + + sw->total_hw_samples_mixed -= played; + + if (!sw->total_hw_samples_mixed) { + sw->empty = 1; + cleanup_required |= !sw->active && !sw->callback.fn; + } + + if (sw->active) { + free = audio_get_free (sw); + if (free > 0) { + sw->callback.fn (sw->callback.opaque, free); + } + } + } + + if (cleanup_required) { + SWVoiceOut *sw1; + + sw = hw->sw_head.lh_first; + while (sw) { + sw1 = sw->entries.le_next; + if (!sw->active && !sw->callback.fn) { + audio_close_out (sw); + } + sw = sw1; + } + } + } +} + +static void audio_run_in (AudioState *s) +{ + HWVoiceIn *hw = NULL; + + while ((hw = audio_pcm_hw_find_any_enabled_in (hw))) { + SWVoiceIn *sw; + int captured, min; + + captured = hw->pcm_ops->run_in (hw); + + min = audio_pcm_hw_find_min_in (hw); + hw->total_samples_captured += captured - min; + hw->ts_helper += captured; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + sw->total_hw_samples_acquired -= min; + + if (sw->active) { + int avail; + + avail = audio_get_avail (sw); + if (avail > 0) { + sw->callback.fn (sw->callback.opaque, avail); + } + } + } + } +} + +static void audio_run_capture (AudioState *s) +{ + CaptureVoiceOut *cap; + + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { + int live, rpos, captured; + HWVoiceOut *hw = &cap->hw; + SWVoiceOut *sw; + + captured = live = audio_pcm_hw_get_live_out (hw, NULL); + rpos = hw->rpos; + while (live) { + int left = hw->samples - rpos; + int to_capture = audio_MIN (live, left); + struct st_sample *src; + struct capture_callback *cb; + + src = hw->mix_buf + rpos; + hw->clip (cap->buf, src, to_capture); + mixeng_clear (src, to_capture); + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + cb->ops.capture (cb->opaque, cap->buf, + to_capture << hw->info.shift); + } + rpos = (rpos + to_capture) % hw->samples; + live -= to_capture; + } + hw->rpos = rpos; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (!sw->active && sw->empty) { + continue; + } + + if (audio_bug (AUDIO_FUNC, captured > sw->total_hw_samples_mixed)) { + dolog ("captured=%d sw->total_hw_samples_mixed=%d\n", + captured, sw->total_hw_samples_mixed); + captured = sw->total_hw_samples_mixed; + } + + sw->total_hw_samples_mixed -= captured; + sw->empty = sw->total_hw_samples_mixed == 0; + } + } +} + +void audio_run (const char *msg) +{ + AudioState *s = &glob_audio_state; + + audio_run_out (s); + audio_run_in (s); + audio_run_capture (s); +#ifdef DEBUG_POLL + { + static double prevtime; + double currtime; + struct timeval tv; + + if (gettimeofday (&tv, NULL)) { + perror ("audio_run: gettimeofday"); + return; + } + + currtime = tv.tv_sec + tv.tv_usec * 1e-6; + dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime); + prevtime = currtime; + } +#endif +} + +static struct audio_option audio_options[] = { + /* DAC */ + { + .name = "DAC_FIXED_SETTINGS", + .tag = AUD_OPT_BOOL, + .valp = &conf.fixed_out.enabled, + .descr = "Use fixed settings for host DAC" + }, + { + .name = "DAC_FIXED_FREQ", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_out.settings.freq, + .descr = "Frequency for fixed host DAC" + }, + { + .name = "DAC_FIXED_FMT", + .tag = AUD_OPT_FMT, + .valp = &conf.fixed_out.settings.fmt, + .descr = "Format for fixed host DAC" + }, + { + .name = "DAC_FIXED_CHANNELS", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_out.settings.nchannels, + .descr = "Number of channels for fixed DAC (1 - mono, 2 - stereo)" + }, + { + .name = "DAC_VOICES", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_out.nb_voices, + .descr = "Number of voices for DAC" + }, + { + .name = "DAC_TRY_POLL", + .tag = AUD_OPT_BOOL, + .valp = &conf.try_poll_out, + .descr = "Attempt using poll mode for DAC" + }, + /* ADC */ + { + .name = "ADC_FIXED_SETTINGS", + .tag = AUD_OPT_BOOL, + .valp = &conf.fixed_in.enabled, + .descr = "Use fixed settings for host ADC" + }, + { + .name = "ADC_FIXED_FREQ", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_in.settings.freq, + .descr = "Frequency for fixed host ADC" + }, + { + .name = "ADC_FIXED_FMT", + .tag = AUD_OPT_FMT, + .valp = &conf.fixed_in.settings.fmt, + .descr = "Format for fixed host ADC" + }, + { + .name = "ADC_FIXED_CHANNELS", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_in.settings.nchannels, + .descr = "Number of channels for fixed ADC (1 - mono, 2 - stereo)" + }, + { + .name = "ADC_VOICES", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_in.nb_voices, + .descr = "Number of voices for ADC" + }, + { + .name = "ADC_TRY_POLL", + .tag = AUD_OPT_BOOL, + .valp = &conf.try_poll_in, + .descr = "Attempt using poll mode for ADC" + }, + /* Misc */ + { + .name = "TIMER_PERIOD", + .tag = AUD_OPT_INT, + .valp = &conf.period.hertz, + .descr = "Timer period in HZ (0 - use lowest possible)" + }, + { /* End of list */ } +}; + +static void audio_pp_nb_voices (const char *typ, int nb) +{ + switch (nb) { + case 0: + printf ("Does not support %s\n", typ); + break; + case 1: + printf ("One %s voice\n", typ); + break; + case INT_MAX: + printf ("Theoretically supports many %s voices\n", typ); + break; + default: + printf ("Theoretically supports up to %d %s voices\n", nb, typ); + break; + } + +} + +void AUD_help (void) +{ + size_t i; + + audio_process_options ("AUDIO", audio_options); + for (i = 0; i < ARRAY_SIZE (drvtab); i++) { + struct audio_driver *d = drvtab[i]; + if (d->options) { + audio_process_options (d->name, d->options); + } + } + + printf ("Audio options:\n"); + audio_print_options ("AUDIO", audio_options); + printf ("\n"); + + printf ("Available drivers:\n"); + + for (i = 0; i < ARRAY_SIZE (drvtab); i++) { + struct audio_driver *d = drvtab[i]; + + printf ("Name: %s\n", d->name); + printf ("Description: %s\n", d->descr); + + audio_pp_nb_voices ("playback", d->max_voices_out); + audio_pp_nb_voices ("capture", d->max_voices_in); + + if (d->options) { + printf ("Options:\n"); + audio_print_options (d->name, d->options); + } + else { + printf ("No options\n"); + } + printf ("\n"); + } + + printf ( + "Options are settable through environment variables.\n" + "Example:\n" +#ifdef _WIN32 + " set QEMU_AUDIO_DRV=wav\n" + " set QEMU_WAV_PATH=c:\\tune.wav\n" +#else + " export QEMU_AUDIO_DRV=wav\n" + " export QEMU_WAV_PATH=$HOME/tune.wav\n" + "(for csh replace export with setenv in the above)\n" +#endif + " qemu ...\n\n" + ); +} + +static int audio_driver_init (AudioState *s, struct audio_driver *drv) +{ + if (drv->options) { + audio_process_options (drv->name, drv->options); + } + s->drv_opaque = drv->init (); + + if (s->drv_opaque) { + audio_init_nb_voices_out (drv); + audio_init_nb_voices_in (drv); + s->drv = drv; + return 0; + } + else { + dolog ("Could not init `%s' audio driver\n", drv->name); + return -1; + } +} + +static void audio_vm_change_state_handler (void *opaque, int running, + RunState state) +{ + AudioState *s = opaque; + HWVoiceOut *hwo = NULL; + HWVoiceIn *hwi = NULL; + int op = running ? VOICE_ENABLE : VOICE_DISABLE; + + s->vm_running = running; + while ((hwo = audio_pcm_hw_find_any_enabled_out (hwo))) { + hwo->pcm_ops->ctl_out (hwo, op, conf.try_poll_out); + } + + while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) { + hwi->pcm_ops->ctl_in (hwi, op, conf.try_poll_in); + } + audio_reset_timer (s); +} + +static void audio_atexit (void) +{ + AudioState *s = &glob_audio_state; + HWVoiceOut *hwo = NULL; + HWVoiceIn *hwi = NULL; + + while ((hwo = audio_pcm_hw_find_any_out (hwo))) { + SWVoiceCap *sc; + + if (hwo->enabled) { + hwo->pcm_ops->ctl_out (hwo, VOICE_DISABLE); + } + hwo->pcm_ops->fini_out (hwo); + + for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) { + CaptureVoiceOut *cap = sc->cap; + struct capture_callback *cb; + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + cb->ops.destroy (cb->opaque); + } + } + } + + while ((hwi = audio_pcm_hw_find_any_in (hwi))) { + if (hwi->enabled) { + hwi->pcm_ops->ctl_in (hwi, VOICE_DISABLE); + } + hwi->pcm_ops->fini_in (hwi); + } + + if (s->drv) { + s->drv->fini (s->drv_opaque); + } +} + +static const VMStateDescription vmstate_audio = { + .name = "audio", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_END_OF_LIST() + } +}; + +static void audio_init (void) +{ + size_t i; + int done = 0; + const char *drvname; + VMChangeStateEntry *e; + AudioState *s = &glob_audio_state; + + if (s->drv) { + return; + } + + QLIST_INIT (&s->hw_head_out); + QLIST_INIT (&s->hw_head_in); + QLIST_INIT (&s->cap_head); + atexit (audio_atexit); + + s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s); + if (!s->ts) { + hw_error("Could not create audio timer\n"); + } + + audio_process_options ("AUDIO", audio_options); + + s->nb_hw_voices_out = conf.fixed_out.nb_voices; + s->nb_hw_voices_in = conf.fixed_in.nb_voices; + + if (s->nb_hw_voices_out <= 0) { + dolog ("Bogus number of playback voices %d, setting to 1\n", + s->nb_hw_voices_out); + s->nb_hw_voices_out = 1; + } + + if (s->nb_hw_voices_in <= 0) { + dolog ("Bogus number of capture voices %d, setting to 0\n", + s->nb_hw_voices_in); + s->nb_hw_voices_in = 0; + } + + { + int def; + drvname = audio_get_conf_str ("QEMU_AUDIO_DRV", NULL, &def); + } + + if (drvname) { + int found = 0; + + for (i = 0; i < ARRAY_SIZE (drvtab); i++) { + if (!strcmp (drvname, drvtab[i]->name)) { + done = !audio_driver_init (s, drvtab[i]); + found = 1; + break; + } + } + + if (!found) { + dolog ("Unknown audio driver `%s'\n", drvname); + dolog ("Run with -audio-help to list available drivers\n"); + } + } + + if (!done) { + for (i = 0; !done && i < ARRAY_SIZE (drvtab); i++) { + if (drvtab[i]->can_be_default) { + done = !audio_driver_init (s, drvtab[i]); + } + } + } + + if (!done) { + done = !audio_driver_init (s, &no_audio_driver); + if (!done) { + hw_error("Could not initialize audio subsystem\n"); + } + else { + dolog ("warning: Using timer based audio emulation\n"); + } + } + + if (conf.period.hertz <= 0) { + if (conf.period.hertz < 0) { + dolog ("warning: Timer period is negative - %d " + "treating as zero\n", + conf.period.hertz); + } + conf.period.ticks = 1; + } else { + conf.period.ticks = + muldiv64 (1, get_ticks_per_sec (), conf.period.hertz); + } + + e = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s); + if (!e) { + dolog ("warning: Could not register change state handler\n" + "(Audio can continue looping even after stopping the VM)\n"); + } + + QLIST_INIT (&s->card_head); + vmstate_register (NULL, 0, &vmstate_audio, s); +} + +void AUD_register_card (const char *name, QEMUSoundCard *card) +{ + audio_init (); + card->name = g_strdup (name); + memset (&card->entries, 0, sizeof (card->entries)); + QLIST_INSERT_HEAD (&glob_audio_state.card_head, card, entries); +} + +void AUD_remove_card (QEMUSoundCard *card) +{ + QLIST_REMOVE (card, entries); + g_free (card->name); +} + + +CaptureVoiceOut *AUD_add_capture ( + struct audsettings *as, + struct audio_capture_ops *ops, + void *cb_opaque + ) +{ + AudioState *s = &glob_audio_state; + CaptureVoiceOut *cap; + struct capture_callback *cb; + + if (audio_validate_settings (as)) { + dolog ("Invalid settings were passed when trying to add capture\n"); + audio_print_settings (as); + goto err0; + } + + cb = audio_calloc (AUDIO_FUNC, 1, sizeof (*cb)); + if (!cb) { + dolog ("Could not allocate capture callback information, size %zu\n", + sizeof (*cb)); + goto err0; + } + cb->ops = *ops; + cb->opaque = cb_opaque; + + cap = audio_pcm_capture_find_specific (as); + if (cap) { + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); + return cap; + } + else { + HWVoiceOut *hw; + CaptureVoiceOut *cap; + + cap = audio_calloc (AUDIO_FUNC, 1, sizeof (*cap)); + if (!cap) { + dolog ("Could not allocate capture voice, size %zu\n", + sizeof (*cap)); + goto err1; + } + + hw = &cap->hw; + QLIST_INIT (&hw->sw_head); + QLIST_INIT (&cap->cb_head); + + /* XXX find a more elegant way */ + hw->samples = 4096 * 4; + hw->mix_buf = audio_calloc (AUDIO_FUNC, hw->samples, + sizeof (struct st_sample)); + if (!hw->mix_buf) { + dolog ("Could not allocate capture mix buffer (%d samples)\n", + hw->samples); + goto err2; + } + + audio_pcm_init_info (&hw->info, as); + + cap->buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!cap->buf) { + dolog ("Could not allocate capture buffer " + "(%d samples, each %d bytes)\n", + hw->samples, 1 << hw->info.shift); + goto err3; + } + + hw->clip = mixeng_clip + [hw->info.nchannels == 2] + [hw->info.sign] + [hw->info.swap_endianness] + [audio_bits_to_index (hw->info.bits)]; + + QLIST_INSERT_HEAD (&s->cap_head, cap, entries); + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); + + hw = NULL; + while ((hw = audio_pcm_hw_find_any_out (hw))) { + audio_attach_capture (hw); + } + return cap; + + err3: + g_free (cap->hw.mix_buf); + err2: + g_free (cap); + err1: + g_free (cb); + err0: + return NULL; + } +} + +void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque) +{ + struct capture_callback *cb; + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + if (cb->opaque == cb_opaque) { + cb->ops.destroy (cb_opaque); + QLIST_REMOVE (cb, entries); + g_free (cb); + + if (!cap->cb_head.lh_first) { + SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1; + + while (sw) { + SWVoiceCap *sc = (SWVoiceCap *) sw; +#ifdef DEBUG_CAPTURE + dolog ("freeing %s\n", sw->name); +#endif + + sw1 = sw->entries.le_next; + if (sw->rate) { + st_rate_stop (sw->rate); + sw->rate = NULL; + } + QLIST_REMOVE (sw, entries); + QLIST_REMOVE (sc, entries); + g_free (sc); + sw = sw1; + } + QLIST_REMOVE (cap, entries); + g_free (cap); + } + return; + } + } +} + +void AUD_set_volume_out (SWVoiceOut *sw, int mute, uint8_t lvol, uint8_t rvol) +{ + if (sw) { + HWVoiceOut *hw = sw->hw; + + sw->vol.mute = mute; + sw->vol.l = nominal_volume.l * lvol / 255; + sw->vol.r = nominal_volume.r * rvol / 255; + + if (hw->pcm_ops->ctl_out) { + hw->pcm_ops->ctl_out (hw, VOICE_VOLUME, sw); + } + } +} + +void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol) +{ + if (sw) { + HWVoiceIn *hw = sw->hw; + + sw->vol.mute = mute; + sw->vol.l = nominal_volume.l * lvol / 255; + sw->vol.r = nominal_volume.r * rvol / 255; + + if (hw->pcm_ops->ctl_in) { + hw->pcm_ops->ctl_in (hw, VOICE_VOLUME, sw); + } + } +} diff --git a/src/audio/audio.h b/src/audio/audio.h new file mode 100644 index 0000000..e7ea397 --- /dev/null +++ b/src/audio/audio.h @@ -0,0 +1,166 @@ +/* + * QEMU Audio subsystem header + * + * 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. + */ +#ifndef QEMU_AUDIO_H +#define QEMU_AUDIO_H + +#include "config-host.h" +#include "qemu/queue.h" + +typedef void (*audio_callback_fn) (void *opaque, int avail); + +typedef enum { + AUD_FMT_U8, + AUD_FMT_S8, + AUD_FMT_U16, + AUD_FMT_S16, + AUD_FMT_U32, + AUD_FMT_S32 +} audfmt_e; + +#ifdef HOST_WORDS_BIGENDIAN +#define AUDIO_HOST_ENDIANNESS 1 +#else +#define AUDIO_HOST_ENDIANNESS 0 +#endif + +struct audsettings { + int freq; + int nchannels; + audfmt_e fmt; + int endianness; +}; + +typedef enum { + AUD_CNOTIFY_ENABLE, + AUD_CNOTIFY_DISABLE +} audcnotification_e; + +struct audio_capture_ops { + void (*notify) (void *opaque, audcnotification_e cmd); + void (*capture) (void *opaque, void *buf, int size); + void (*destroy) (void *opaque); +}; + +struct capture_ops { + void (*info) (void *opaque); + void (*destroy) (void *opaque); +}; + +typedef struct CaptureState { + void *opaque; + struct capture_ops ops; + QLIST_ENTRY (CaptureState) entries; +} CaptureState; + +typedef struct SWVoiceOut SWVoiceOut; +typedef struct CaptureVoiceOut CaptureVoiceOut; +typedef struct SWVoiceIn SWVoiceIn; + +typedef struct QEMUSoundCard { + char *name; + QLIST_ENTRY (QEMUSoundCard) entries; +} QEMUSoundCard; + +typedef struct QEMUAudioTimeStamp { + uint64_t old_ts; +} QEMUAudioTimeStamp; + +void AUD_vlog (const char *cap, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0); +void AUD_log (const char *cap, const char *fmt, ...) GCC_FMT_ATTR(2, 3); + +void AUD_help (void); +void AUD_register_card (const char *name, QEMUSoundCard *card); +void AUD_remove_card (QEMUSoundCard *card); +CaptureVoiceOut *AUD_add_capture ( + struct audsettings *as, + struct audio_capture_ops *ops, + void *opaque + ); +void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque); + +SWVoiceOut *AUD_open_out ( + QEMUSoundCard *card, + SWVoiceOut *sw, + const char *name, + void *callback_opaque, + audio_callback_fn callback_fn, + struct audsettings *settings + ); + +void AUD_close_out (QEMUSoundCard *card, SWVoiceOut *sw); +int AUD_write (SWVoiceOut *sw, void *pcm_buf, int size); +int AUD_get_buffer_size_out (SWVoiceOut *sw); +void AUD_set_active_out (SWVoiceOut *sw, int on); +int AUD_is_active_out (SWVoiceOut *sw); + +void AUD_init_time_stamp_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts); +uint64_t AUD_get_elapsed_usec_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts); + +void AUD_set_volume_out (SWVoiceOut *sw, int mute, uint8_t lvol, uint8_t rvol); +void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol); + +SWVoiceIn *AUD_open_in ( + QEMUSoundCard *card, + SWVoiceIn *sw, + const char *name, + void *callback_opaque, + audio_callback_fn callback_fn, + struct audsettings *settings + ); + +void AUD_close_in (QEMUSoundCard *card, SWVoiceIn *sw); +int AUD_read (SWVoiceIn *sw, void *pcm_buf, int size); +void AUD_set_active_in (SWVoiceIn *sw, int on); +int AUD_is_active_in (SWVoiceIn *sw); + +void AUD_init_time_stamp_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts); +uint64_t AUD_get_elapsed_usec_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts); + +static inline void *advance (void *p, int incr) +{ + uint8_t *d = p; + return (d + incr); +} + +#ifdef __GNUC__ +#define audio_MIN(a, b) ( __extension__ ({ \ + __typeof (a) ta = a; \ + __typeof (b) tb = b; \ + ((ta)>(tb)?(tb):(ta)); \ +})) + +#define audio_MAX(a, b) ( __extension__ ({ \ + __typeof (a) ta = a; \ + __typeof (b) tb = b; \ + ((ta)<(tb)?(tb):(ta)); \ +})) +#else +#define audio_MIN(a, b) ((a)>(b)?(b):(a)) +#define audio_MAX(a, b) ((a)<(b)?(b):(a)) +#endif + +int wav_start_capture (CaptureState *s, const char *path, int freq, + int bits, int nchannels); + +#endif /* audio.h */ diff --git a/src/audio/audio_int.h b/src/audio/audio_int.h new file mode 100644 index 0000000..566df5e --- /dev/null +++ b/src/audio/audio_int.h @@ -0,0 +1,260 @@ +/* + * QEMU Audio subsystem header + * + * 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. + */ +#ifndef QEMU_AUDIO_INT_H +#define QEMU_AUDIO_INT_H + +#ifdef CONFIG_COREAUDIO +#define FLOAT_MIXENG +/* #define RECIPROCAL */ +#endif +#include "mixeng.h" + +struct audio_pcm_ops; + +typedef enum { + AUD_OPT_INT, + AUD_OPT_FMT, + AUD_OPT_STR, + AUD_OPT_BOOL +} audio_option_tag_e; + +struct audio_option { + const char *name; + audio_option_tag_e tag; + void *valp; + const char *descr; + int *overriddenp; + int overridden; +}; + +struct audio_callback { + void *opaque; + audio_callback_fn fn; +}; + +struct audio_pcm_info { + int bits; + int sign; + int freq; + int nchannels; + int align; + int shift; + int bytes_per_second; + int swap_endianness; +}; + +typedef struct SWVoiceCap SWVoiceCap; + +typedef struct HWVoiceOut { + int enabled; + int poll_mode; + int pending_disable; + struct audio_pcm_info info; + + f_sample *clip; + + int rpos; + uint64_t ts_helper; + + struct st_sample *mix_buf; + + int samples; + QLIST_HEAD (sw_out_listhead, SWVoiceOut) sw_head; + QLIST_HEAD (sw_cap_listhead, SWVoiceCap) cap_head; + int ctl_caps; + struct audio_pcm_ops *pcm_ops; + QLIST_ENTRY (HWVoiceOut) entries; +} HWVoiceOut; + +typedef struct HWVoiceIn { + int enabled; + int poll_mode; + struct audio_pcm_info info; + + t_sample *conv; + + int wpos; + int total_samples_captured; + uint64_t ts_helper; + + struct st_sample *conv_buf; + + int samples; + QLIST_HEAD (sw_in_listhead, SWVoiceIn) sw_head; + int ctl_caps; + struct audio_pcm_ops *pcm_ops; + QLIST_ENTRY (HWVoiceIn) entries; +} HWVoiceIn; + +struct SWVoiceOut { + QEMUSoundCard *card; + struct audio_pcm_info info; + t_sample *conv; + int64_t ratio; + struct st_sample *buf; + void *rate; + int total_hw_samples_mixed; + int active; + int empty; + HWVoiceOut *hw; + char *name; + struct mixeng_volume vol; + struct audio_callback callback; + QLIST_ENTRY (SWVoiceOut) entries; +}; + +struct SWVoiceIn { + QEMUSoundCard *card; + int active; + struct audio_pcm_info info; + int64_t ratio; + void *rate; + int total_hw_samples_acquired; + struct st_sample *buf; + f_sample *clip; + HWVoiceIn *hw; + char *name; + struct mixeng_volume vol; + struct audio_callback callback; + QLIST_ENTRY (SWVoiceIn) entries; +}; + +struct audio_driver { + const char *name; + const char *descr; + struct audio_option *options; + void *(*init) (void); + void (*fini) (void *); + struct audio_pcm_ops *pcm_ops; + int can_be_default; + int max_voices_out; + int max_voices_in; + int voice_size_out; + int voice_size_in; + int ctl_caps; +}; + +struct audio_pcm_ops { + int (*init_out)(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque); + void (*fini_out)(HWVoiceOut *hw); + int (*run_out) (HWVoiceOut *hw, int live); + int (*write) (SWVoiceOut *sw, void *buf, int size); + int (*ctl_out) (HWVoiceOut *hw, int cmd, ...); + + int (*init_in) (HWVoiceIn *hw, struct audsettings *as, void *drv_opaque); + void (*fini_in) (HWVoiceIn *hw); + int (*run_in) (HWVoiceIn *hw); + int (*read) (SWVoiceIn *sw, void *buf, int size); + int (*ctl_in) (HWVoiceIn *hw, int cmd, ...); +}; + +struct capture_callback { + struct audio_capture_ops ops; + void *opaque; + QLIST_ENTRY (capture_callback) entries; +}; + +struct CaptureVoiceOut { + HWVoiceOut hw; + void *buf; + QLIST_HEAD (cb_listhead, capture_callback) cb_head; + QLIST_ENTRY (CaptureVoiceOut) entries; +}; + +struct SWVoiceCap { + SWVoiceOut sw; + CaptureVoiceOut *cap; + QLIST_ENTRY (SWVoiceCap) entries; +}; + +struct AudioState { + struct audio_driver *drv; + void *drv_opaque; + + QEMUTimer *ts; + QLIST_HEAD (card_listhead, QEMUSoundCard) card_head; + QLIST_HEAD (hw_in_listhead, HWVoiceIn) hw_head_in; + QLIST_HEAD (hw_out_listhead, HWVoiceOut) hw_head_out; + QLIST_HEAD (cap_listhead, CaptureVoiceOut) cap_head; + int nb_hw_voices_out; + int nb_hw_voices_in; + int vm_running; +}; + +extern struct audio_driver no_audio_driver; +extern struct audio_driver oss_audio_driver; +extern struct audio_driver sdl_audio_driver; +extern struct audio_driver wav_audio_driver; +extern struct audio_driver alsa_audio_driver; +extern struct audio_driver coreaudio_audio_driver; +extern struct audio_driver dsound_audio_driver; +extern struct audio_driver pa_audio_driver; +extern struct audio_driver spice_audio_driver; +extern const struct mixeng_volume nominal_volume; + +void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as); +void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len); + +int audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int len); +int audio_pcm_hw_get_live_in (HWVoiceIn *hw); + +int audio_pcm_sw_read (SWVoiceIn *sw, void *buf, int len); + +int audio_pcm_hw_clip_out (HWVoiceOut *hw, void *pcm_buf, + int live, int pending); + +int audio_bug (const char *funcname, int cond); +void *audio_calloc (const char *funcname, int nmemb, size_t size); + +void audio_run (const char *msg); + +#define VOICE_ENABLE 1 +#define VOICE_DISABLE 2 +#define VOICE_VOLUME 3 + +#define VOICE_VOLUME_CAP (1 << VOICE_VOLUME) + +static inline int audio_ring_dist (int dst, int src, int len) +{ + return (dst >= src) ? (dst - src) : (len - src + dst); +} + +#define dolog(fmt, ...) AUD_log(AUDIO_CAP, fmt, ## __VA_ARGS__) + +#ifdef DEBUG +#define ldebug(fmt, ...) AUD_log(AUDIO_CAP, fmt, ## __VA_ARGS__) +#else +#define ldebug(fmt, ...) (void)0 +#endif + +#define AUDIO_STRINGIFY_(n) #n +#define AUDIO_STRINGIFY(n) AUDIO_STRINGIFY_(n) + +#if defined _MSC_VER || defined __GNUC__ +#define AUDIO_FUNC __FUNCTION__ +#else +#define AUDIO_FUNC __FILE__ ":" AUDIO_STRINGIFY (__LINE__) +#endif + +#endif /* audio_int.h */ diff --git a/src/audio/audio_pt_int.c b/src/audio/audio_pt_int.c new file mode 100644 index 0000000..9a9c306 --- /dev/null +++ b/src/audio/audio_pt_int.c @@ -0,0 +1,173 @@ +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "audio-pt" + +#include "audio_int.h" +#include "audio_pt_int.h" + +static void GCC_FMT_ATTR(3, 4) logerr (struct audio_pt *pt, int err, + const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (pt->drv, fmt, ap); + va_end (ap); + + AUD_log (NULL, "\n"); + AUD_log (pt->drv, "Reason: %s\n", strerror (err)); +} + +int audio_pt_init (struct audio_pt *p, void *(*func) (void *), + void *opaque, const char *drv, const char *cap) +{ + int err, err2; + const char *efunc; + sigset_t set, old_set; + + p->drv = drv; + + err = sigfillset (&set); + if (err) { + logerr (p, errno, "%s(%s): sigfillset failed", cap, AUDIO_FUNC); + return -1; + } + + err = pthread_mutex_init (&p->mutex, NULL); + if (err) { + efunc = "pthread_mutex_init"; + goto err0; + } + + err = pthread_cond_init (&p->cond, NULL); + if (err) { + efunc = "pthread_cond_init"; + goto err1; + } + + err = pthread_sigmask (SIG_BLOCK, &set, &old_set); + if (err) { + efunc = "pthread_sigmask"; + goto err2; + } + + err = pthread_create (&p->thread, NULL, func, opaque); + + err2 = pthread_sigmask (SIG_SETMASK, &old_set, NULL); + if (err2) { + logerr (p, err2, "%s(%s): pthread_sigmask (restore) failed", + cap, AUDIO_FUNC); + /* We have failed to restore original signal mask, all bets are off, + so terminate the process */ + exit (EXIT_FAILURE); + } + + if (err) { + efunc = "pthread_create"; + goto err2; + } + + return 0; + + err2: + err2 = pthread_cond_destroy (&p->cond); + if (err2) { + logerr (p, err2, "%s(%s): pthread_cond_destroy failed", cap, AUDIO_FUNC); + } + + err1: + err2 = pthread_mutex_destroy (&p->mutex); + if (err2) { + logerr (p, err2, "%s(%s): pthread_mutex_destroy failed", cap, AUDIO_FUNC); + } + + err0: + logerr (p, err, "%s(%s): %s failed", cap, AUDIO_FUNC, efunc); + return -1; +} + +int audio_pt_fini (struct audio_pt *p, const char *cap) +{ + int err, ret = 0; + + err = pthread_cond_destroy (&p->cond); + if (err) { + logerr (p, err, "%s(%s): pthread_cond_destroy failed", cap, AUDIO_FUNC); + ret = -1; + } + + err = pthread_mutex_destroy (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_destroy failed", cap, AUDIO_FUNC); + ret = -1; + } + return ret; +} + +int audio_pt_lock (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_mutex_lock (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_lock failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_unlock (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_mutex_unlock (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_unlock failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_wait (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_cond_wait (&p->cond, &p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_cond_wait failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_unlock_and_signal (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_mutex_unlock (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_unlock failed", cap, AUDIO_FUNC); + return -1; + } + err = pthread_cond_signal (&p->cond); + if (err) { + logerr (p, err, "%s(%s): pthread_cond_signal failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_join (struct audio_pt *p, void **arg, const char *cap) +{ + int err; + void *ret; + + err = pthread_join (p->thread, &ret); + if (err) { + logerr (p, err, "%s(%s): pthread_join failed", cap, AUDIO_FUNC); + return -1; + } + *arg = ret; + return 0; +} diff --git a/src/audio/audio_pt_int.h b/src/audio/audio_pt_int.h new file mode 100644 index 0000000..0dfff76 --- /dev/null +++ b/src/audio/audio_pt_int.h @@ -0,0 +1,22 @@ +#ifndef QEMU_AUDIO_PT_INT_H +#define QEMU_AUDIO_PT_INT_H + +#include <pthread.h> + +struct audio_pt { + const char *drv; + pthread_t thread; + pthread_cond_t cond; + pthread_mutex_t mutex; +}; + +int audio_pt_init (struct audio_pt *, void *(*) (void *), void *, + const char *, const char *); +int audio_pt_fini (struct audio_pt *, const char *); +int audio_pt_lock (struct audio_pt *, const char *); +int audio_pt_unlock (struct audio_pt *, const char *); +int audio_pt_wait (struct audio_pt *, const char *); +int audio_pt_unlock_and_signal (struct audio_pt *, const char *); +int audio_pt_join (struct audio_pt *, void **, const char *); + +#endif /* audio_pt_int.h */ diff --git a/src/audio/audio_template.h b/src/audio/audio_template.h new file mode 100644 index 0000000..99b27b2 --- /dev/null +++ b/src/audio/audio_template.h @@ -0,0 +1,514 @@ +/* + * QEMU Audio subsystem header + * + * Copyright (c) 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. + */ + +#ifdef DAC +#define NAME "playback" +#define HWBUF hw->mix_buf +#define TYPE out +#define HW HWVoiceOut +#define SW SWVoiceOut +#else +#define NAME "capture" +#define TYPE in +#define HW HWVoiceIn +#define SW SWVoiceIn +#define HWBUF hw->conv_buf +#endif + +static void glue (audio_init_nb_voices_, TYPE) (struct audio_driver *drv) +{ + AudioState *s = &glob_audio_state; + int max_voices = glue (drv->max_voices_, TYPE); + int voice_size = glue (drv->voice_size_, TYPE); + + if (glue (s->nb_hw_voices_, TYPE) > max_voices) { + if (!max_voices) { +#ifdef DAC + dolog ("Driver `%s' does not support " NAME "\n", drv->name); +#endif + } + else { + dolog ("Driver `%s' does not support %d " NAME " voices, max %d\n", + drv->name, + glue (s->nb_hw_voices_, TYPE), + max_voices); + } + glue (s->nb_hw_voices_, TYPE) = max_voices; + } + + if (audio_bug (AUDIO_FUNC, !voice_size && max_voices)) { + dolog ("drv=`%s' voice_size=0 max_voices=%d\n", + drv->name, max_voices); + glue (s->nb_hw_voices_, TYPE) = 0; + } + + if (audio_bug (AUDIO_FUNC, voice_size && !max_voices)) { + dolog ("drv=`%s' voice_size=%d max_voices=0\n", + drv->name, voice_size); + } +} + +static void glue (audio_pcm_hw_free_resources_, TYPE) (HW *hw) +{ + g_free (HWBUF); + HWBUF = NULL; +} + +static int glue (audio_pcm_hw_alloc_resources_, TYPE) (HW *hw) +{ + HWBUF = audio_calloc (AUDIO_FUNC, hw->samples, sizeof (struct st_sample)); + if (!HWBUF) { + dolog ("Could not allocate " NAME " buffer (%d samples)\n", + hw->samples); + return -1; + } + + return 0; +} + +static void glue (audio_pcm_sw_free_resources_, TYPE) (SW *sw) +{ + g_free (sw->buf); + + if (sw->rate) { + st_rate_stop (sw->rate); + } + + sw->buf = NULL; + sw->rate = NULL; +} + +static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw) +{ + int samples; + + samples = ((int64_t) sw->hw->samples << 32) / sw->ratio; + + sw->buf = audio_calloc (AUDIO_FUNC, samples, sizeof (struct st_sample)); + if (!sw->buf) { + dolog ("Could not allocate buffer for `%s' (%d samples)\n", + SW_NAME (sw), samples); + return -1; + } + +#ifdef DAC + sw->rate = st_rate_start (sw->info.freq, sw->hw->info.freq); +#else + sw->rate = st_rate_start (sw->hw->info.freq, sw->info.freq); +#endif + if (!sw->rate) { + g_free (sw->buf); + sw->buf = NULL; + return -1; + } + return 0; +} + +static int glue (audio_pcm_sw_init_, TYPE) ( + SW *sw, + HW *hw, + const char *name, + struct audsettings *as + ) +{ + int err; + + audio_pcm_init_info (&sw->info, as); + sw->hw = hw; + sw->active = 0; +#ifdef DAC + sw->ratio = ((int64_t) sw->hw->info.freq << 32) / sw->info.freq; + sw->total_hw_samples_mixed = 0; + sw->empty = 1; +#else + sw->ratio = ((int64_t) sw->info.freq << 32) / sw->hw->info.freq; +#endif + +#ifdef DAC + sw->conv = mixeng_conv +#else + sw->clip = mixeng_clip +#endif + [sw->info.nchannels == 2] + [sw->info.sign] + [sw->info.swap_endianness] + [audio_bits_to_index (sw->info.bits)]; + + sw->name = g_strdup (name); + err = glue (audio_pcm_sw_alloc_resources_, TYPE) (sw); + if (err) { + g_free (sw->name); + sw->name = NULL; + } + return err; +} + +static void glue (audio_pcm_sw_fini_, TYPE) (SW *sw) +{ + glue (audio_pcm_sw_free_resources_, TYPE) (sw); + g_free (sw->name); + sw->name = NULL; +} + +static void glue (audio_pcm_hw_add_sw_, TYPE) (HW *hw, SW *sw) +{ + QLIST_INSERT_HEAD (&hw->sw_head, sw, entries); +} + +static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw) +{ + QLIST_REMOVE (sw, entries); +} + +static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp) +{ + AudioState *s = &glob_audio_state; + HW *hw = *hwp; + + if (!hw->sw_head.lh_first) { +#ifdef DAC + audio_detach_capture (hw); +#endif + QLIST_REMOVE (hw, entries); + glue (hw->pcm_ops->fini_, TYPE) (hw); + glue (s->nb_hw_voices_, TYPE) += 1; + glue (audio_pcm_hw_free_resources_ ,TYPE) (hw); + g_free (hw); + *hwp = NULL; + } +} + +static HW *glue (audio_pcm_hw_find_any_, TYPE) (HW *hw) +{ + AudioState *s = &glob_audio_state; + return hw ? hw->entries.le_next : glue (s->hw_head_, TYPE).lh_first; +} + +static HW *glue (audio_pcm_hw_find_any_enabled_, TYPE) (HW *hw) +{ + while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) { + if (hw->enabled) { + return hw; + } + } + return NULL; +} + +static HW *glue (audio_pcm_hw_find_specific_, TYPE) ( + HW *hw, + struct audsettings *as + ) +{ + while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) { + if (audio_pcm_info_eq (&hw->info, as)) { + return hw; + } + } + return NULL; +} + +static HW *glue (audio_pcm_hw_add_new_, TYPE) (struct audsettings *as) +{ + HW *hw; + AudioState *s = &glob_audio_state; + struct audio_driver *drv = s->drv; + + if (!glue (s->nb_hw_voices_, TYPE)) { + return NULL; + } + + if (audio_bug (AUDIO_FUNC, !drv)) { + dolog ("No host audio driver\n"); + return NULL; + } + + if (audio_bug (AUDIO_FUNC, !drv->pcm_ops)) { + dolog ("Host audio driver without pcm_ops\n"); + return NULL; + } + + hw = audio_calloc (AUDIO_FUNC, 1, glue (drv->voice_size_, TYPE)); + if (!hw) { + dolog ("Can not allocate voice `%s' size %d\n", + drv->name, glue (drv->voice_size_, TYPE)); + return NULL; + } + + hw->pcm_ops = drv->pcm_ops; + hw->ctl_caps = drv->ctl_caps; + + QLIST_INIT (&hw->sw_head); +#ifdef DAC + QLIST_INIT (&hw->cap_head); +#endif + if (glue (hw->pcm_ops->init_, TYPE) (hw, as, s->drv_opaque)) { + goto err0; + } + + if (audio_bug (AUDIO_FUNC, hw->samples <= 0)) { + dolog ("hw->samples=%d\n", hw->samples); + goto err1; + } + +#ifdef DAC + hw->clip = mixeng_clip +#else + hw->conv = mixeng_conv +#endif + [hw->info.nchannels == 2] + [hw->info.sign] + [hw->info.swap_endianness] + [audio_bits_to_index (hw->info.bits)]; + + if (glue (audio_pcm_hw_alloc_resources_, TYPE) (hw)) { + goto err1; + } + + QLIST_INSERT_HEAD (&s->glue (hw_head_, TYPE), hw, entries); + glue (s->nb_hw_voices_, TYPE) -= 1; +#ifdef DAC + audio_attach_capture (hw); +#endif + return hw; + + err1: + glue (hw->pcm_ops->fini_, TYPE) (hw); + err0: + g_free (hw); + return NULL; +} + +static HW *glue (audio_pcm_hw_add_, TYPE) (struct audsettings *as) +{ + HW *hw; + + if (glue (conf.fixed_, TYPE).enabled && glue (conf.fixed_, TYPE).greedy) { + hw = glue (audio_pcm_hw_add_new_, TYPE) (as); + if (hw) { + return hw; + } + } + + hw = glue (audio_pcm_hw_find_specific_, TYPE) (NULL, as); + if (hw) { + return hw; + } + + hw = glue (audio_pcm_hw_add_new_, TYPE) (as); + if (hw) { + return hw; + } + + return glue (audio_pcm_hw_find_any_, TYPE) (NULL); +} + +static SW *glue (audio_pcm_create_voice_pair_, TYPE) ( + const char *sw_name, + struct audsettings *as + ) +{ + SW *sw; + HW *hw; + struct audsettings hw_as; + + if (glue (conf.fixed_, TYPE).enabled) { + hw_as = glue (conf.fixed_, TYPE).settings; + } + else { + hw_as = *as; + } + + sw = audio_calloc (AUDIO_FUNC, 1, sizeof (*sw)); + if (!sw) { + dolog ("Could not allocate soft voice `%s' (%zu bytes)\n", + sw_name ? sw_name : "unknown", sizeof (*sw)); + goto err1; + } + + hw = glue (audio_pcm_hw_add_, TYPE) (&hw_as); + if (!hw) { + goto err2; + } + + glue (audio_pcm_hw_add_sw_, TYPE) (hw, sw); + + if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, sw_name, as)) { + goto err3; + } + + return sw; + +err3: + glue (audio_pcm_hw_del_sw_, TYPE) (sw); + glue (audio_pcm_hw_gc_, TYPE) (&hw); +err2: + g_free (sw); +err1: + return NULL; +} + +static void glue (audio_close_, TYPE) (SW *sw) +{ + glue (audio_pcm_sw_fini_, TYPE) (sw); + glue (audio_pcm_hw_del_sw_, TYPE) (sw); + glue (audio_pcm_hw_gc_, TYPE) (&sw->hw); + g_free (sw); +} + +void glue (AUD_close_, TYPE) (QEMUSoundCard *card, SW *sw) +{ + if (sw) { + if (audio_bug (AUDIO_FUNC, !card)) { + dolog ("card=%p\n", card); + return; + } + + glue (audio_close_, TYPE) (sw); + } +} + +SW *glue (AUD_open_, TYPE) ( + QEMUSoundCard *card, + SW *sw, + const char *name, + void *callback_opaque , + audio_callback_fn callback_fn, + struct audsettings *as + ) +{ + AudioState *s = &glob_audio_state; + + if (audio_bug (AUDIO_FUNC, !card || !name || !callback_fn || !as)) { + dolog ("card=%p name=%p callback_fn=%p as=%p\n", + card, name, callback_fn, as); + goto fail; + } + + ldebug ("open %s, freq %d, nchannels %d, fmt %d\n", + name, as->freq, as->nchannels, as->fmt); + + if (audio_bug (AUDIO_FUNC, audio_validate_settings (as))) { + audio_print_settings (as); + goto fail; + } + + if (audio_bug (AUDIO_FUNC, !s->drv)) { + dolog ("Can not open `%s' (no host audio driver)\n", name); + goto fail; + } + + if (sw && audio_pcm_info_eq (&sw->info, as)) { + return sw; + } + + if (!glue (conf.fixed_, TYPE).enabled && sw) { + glue (AUD_close_, TYPE) (card, sw); + sw = NULL; + } + + if (sw) { + HW *hw = sw->hw; + + if (!hw) { + dolog ("Internal logic error voice `%s' has no hardware store\n", + SW_NAME (sw)); + goto fail; + } + + glue (audio_pcm_sw_fini_, TYPE) (sw); + if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, name, as)) { + goto fail; + } + } + else { + sw = glue (audio_pcm_create_voice_pair_, TYPE) (name, as); + if (!sw) { + dolog ("Failed to create voice `%s'\n", name); + return NULL; + } + } + + sw->card = card; + sw->vol = nominal_volume; + sw->callback.fn = callback_fn; + sw->callback.opaque = callback_opaque; + +#ifdef DEBUG_AUDIO + dolog ("%s\n", name); + audio_pcm_print_info ("hw", &sw->hw->info); + audio_pcm_print_info ("sw", &sw->info); +#endif + + return sw; + + fail: + glue (AUD_close_, TYPE) (card, sw); + return NULL; +} + +int glue (AUD_is_active_, TYPE) (SW *sw) +{ + return sw ? sw->active : 0; +} + +void glue (AUD_init_time_stamp_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts) +{ + if (!sw) { + return; + } + + ts->old_ts = sw->hw->ts_helper; +} + +uint64_t glue (AUD_get_elapsed_usec_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts) +{ + uint64_t delta, cur_ts, old_ts; + + if (!sw) { + return 0; + } + + cur_ts = sw->hw->ts_helper; + old_ts = ts->old_ts; + /* dolog ("cur %" PRId64 " old %" PRId64 "\n", cur_ts, old_ts); */ + + if (cur_ts >= old_ts) { + delta = cur_ts - old_ts; + } + else { + delta = UINT64_MAX - old_ts + cur_ts; + } + + if (!delta) { + return 0; + } + + return muldiv64 (delta, sw->hw->info.freq, 1000000); +} + +#undef TYPE +#undef HW +#undef SW +#undef HWBUF +#undef NAME diff --git a/src/audio/audio_win_int.c b/src/audio/audio_win_int.c new file mode 100644 index 0000000..e132405 --- /dev/null +++ b/src/audio/audio_win_int.c @@ -0,0 +1,107 @@ +/* public domain */ + +#include "qemu-common.h" + +#define AUDIO_CAP "win-int" +#include <windows.h> +#include <mmsystem.h> + +#include "audio.h" +#include "audio_int.h" +#include "audio_win_int.h" + +int waveformat_from_audio_settings (WAVEFORMATEX *wfx, + struct audsettings *as) +{ + memset (wfx, 0, sizeof (*wfx)); + + wfx->wFormatTag = WAVE_FORMAT_PCM; + wfx->nChannels = as->nchannels; + wfx->nSamplesPerSec = as->freq; + wfx->nAvgBytesPerSec = as->freq << (as->nchannels == 2); + wfx->nBlockAlign = 1 << (as->nchannels == 2); + wfx->cbSize = 0; + + switch (as->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + wfx->wBitsPerSample = 8; + break; + + case AUD_FMT_S16: + case AUD_FMT_U16: + wfx->wBitsPerSample = 16; + wfx->nAvgBytesPerSec <<= 1; + wfx->nBlockAlign <<= 1; + break; + + case AUD_FMT_S32: + case AUD_FMT_U32: + wfx->wBitsPerSample = 32; + wfx->nAvgBytesPerSec <<= 2; + wfx->nBlockAlign <<= 2; + break; + + default: + dolog ("Internal logic error: Bad audio format %d\n", as->freq); + return -1; + } + + return 0; +} + +int waveformat_to_audio_settings (WAVEFORMATEX *wfx, + struct audsettings *as) +{ + if (wfx->wFormatTag != WAVE_FORMAT_PCM) { + dolog ("Invalid wave format, tag is not PCM, but %d\n", + wfx->wFormatTag); + return -1; + } + + if (!wfx->nSamplesPerSec) { + dolog ("Invalid wave format, frequency is zero\n"); + return -1; + } + as->freq = wfx->nSamplesPerSec; + + switch (wfx->nChannels) { + case 1: + as->nchannels = 1; + break; + + case 2: + as->nchannels = 2; + break; + + default: + dolog ( + "Invalid wave format, number of channels is not 1 or 2, but %d\n", + wfx->nChannels + ); + return -1; + } + + switch (wfx->wBitsPerSample) { + case 8: + as->fmt = AUD_FMT_U8; + break; + + case 16: + as->fmt = AUD_FMT_S16; + break; + + case 32: + as->fmt = AUD_FMT_S32; + break; + + default: + dolog ("Invalid wave format, bits per sample is not " + "8, 16 or 32, but %d\n", + wfx->wBitsPerSample); + return -1; + } + + return 0; +} + diff --git a/src/audio/audio_win_int.h b/src/audio/audio_win_int.h new file mode 100644 index 0000000..fa5b3cb --- /dev/null +++ b/src/audio/audio_win_int.h @@ -0,0 +1,10 @@ +#ifndef AUDIO_WIN_INT_H +#define AUDIO_WIN_INT_H + +int waveformat_from_audio_settings (WAVEFORMATEX *wfx, + struct audsettings *as); + +int waveformat_to_audio_settings (WAVEFORMATEX *wfx, + struct audsettings *as); + +#endif /* AUDIO_WIN_INT_H */ diff --git a/src/audio/coreaudio.c b/src/audio/coreaudio.c new file mode 100644 index 0000000..6dfd63e --- /dev/null +++ b/src/audio/coreaudio.c @@ -0,0 +1,555 @@ +/* + * QEMU OS X CoreAudio audio driver + * + * Copyright (c) 2005 Mike Kronenberg + * + * 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 <CoreAudio/CoreAudio.h> +#include <string.h> /* strerror */ +#include <pthread.h> /* pthread_X */ + +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "coreaudio" +#include "audio_int.h" + +static int isAtexit; + +typedef struct { + int buffer_frames; + int nbuffers; +} CoreaudioConf; + +typedef struct coreaudioVoiceOut { + HWVoiceOut hw; + pthread_mutex_t mutex; + AudioDeviceID outputDeviceID; + UInt32 audioDevicePropertyBufferFrameSize; + AudioStreamBasicDescription outputStreamBasicDescription; + int live; + int decr; + int rpos; +} coreaudioVoiceOut; + +static void coreaudio_logstatus (OSStatus status) +{ + const char *str = "BUG"; + + switch(status) { + case kAudioHardwareNoError: + str = "kAudioHardwareNoError"; + break; + + case kAudioHardwareNotRunningError: + str = "kAudioHardwareNotRunningError"; + break; + + case kAudioHardwareUnspecifiedError: + str = "kAudioHardwareUnspecifiedError"; + break; + + case kAudioHardwareUnknownPropertyError: + str = "kAudioHardwareUnknownPropertyError"; + break; + + case kAudioHardwareBadPropertySizeError: + str = "kAudioHardwareBadPropertySizeError"; + break; + + case kAudioHardwareIllegalOperationError: + str = "kAudioHardwareIllegalOperationError"; + break; + + case kAudioHardwareBadDeviceError: + str = "kAudioHardwareBadDeviceError"; + break; + + case kAudioHardwareBadStreamError: + str = "kAudioHardwareBadStreamError"; + break; + + case kAudioHardwareUnsupportedOperationError: + str = "kAudioHardwareUnsupportedOperationError"; + break; + + case kAudioDeviceUnsupportedFormatError: + str = "kAudioDeviceUnsupportedFormatError"; + break; + + case kAudioDevicePermissionsError: + str = "kAudioDevicePermissionsError"; + break; + + default: + AUD_log (AUDIO_CAP, "Reason: status code %" PRId32 "\n", (int32_t)status); + return; + } + + AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) coreaudio_logerr ( + OSStatus status, + const char *fmt, + ... + ) +{ + va_list ap; + + va_start (ap, fmt); + AUD_log (AUDIO_CAP, fmt, ap); + va_end (ap); + + coreaudio_logstatus (status); +} + +static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 ( + OSStatus status, + 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); + + coreaudio_logstatus (status); +} + +static inline UInt32 isPlaying (AudioDeviceID outputDeviceID) +{ + OSStatus status; + UInt32 result = 0; + UInt32 propertySize = sizeof(outputDeviceID); + status = AudioDeviceGetProperty( + outputDeviceID, 0, 0, + kAudioDevicePropertyDeviceIsRunning, &propertySize, &result); + if (status != kAudioHardwareNoError) { + coreaudio_logerr(status, + "Could not determine whether Device is playing\n"); + } + return result; +} + +static void coreaudio_atexit (void) +{ + isAtexit = 1; +} + +static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name) +{ + int err; + + err = pthread_mutex_lock (&core->mutex); + if (err) { + dolog ("Could not lock voice for %s\nReason: %s\n", + fn_name, strerror (err)); + return -1; + } + return 0; +} + +static int coreaudio_unlock (coreaudioVoiceOut *core, const char *fn_name) +{ + int err; + + err = pthread_mutex_unlock (&core->mutex); + if (err) { + dolog ("Could not unlock voice for %s\nReason: %s\n", + fn_name, strerror (err)); + return -1; + } + return 0; +} + +static int coreaudio_run_out (HWVoiceOut *hw, int live) +{ + int decr; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + if (coreaudio_lock (core, "coreaudio_run_out")) { + return 0; + } + + if (core->decr > live) { + ldebug ("core->decr %d live %d core->live %d\n", + core->decr, + live, + core->live); + } + + decr = audio_MIN (core->decr, live); + core->decr -= decr; + + core->live = live - decr; + hw->rpos = core->rpos; + + coreaudio_unlock (core, "coreaudio_run_out"); + return decr; +} + +/* callback to feed audiooutput buffer */ +static OSStatus audioDeviceIOProc( + AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* hwptr) +{ + UInt32 frame, frameCount; + float *out = outOutputData->mBuffers[0].mData; + HWVoiceOut *hw = hwptr; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; + int rpos, live; + struct st_sample *src; +#ifndef FLOAT_MIXENG +#ifdef RECIPROCAL + const float scale = 1.f / UINT_MAX; +#else + const float scale = UINT_MAX; +#endif +#endif + + if (coreaudio_lock (core, "audioDeviceIOProc")) { + inInputTime = 0; + return 0; + } + + frameCount = core->audioDevicePropertyBufferFrameSize; + live = core->live; + + /* if there are not enough samples, set signal and return */ + if (live < frameCount) { + inInputTime = 0; + coreaudio_unlock (core, "audioDeviceIOProc(empty)"); + return 0; + } + + rpos = core->rpos; + src = hw->mix_buf + rpos; + + /* fill buffer */ + for (frame = 0; frame < frameCount; frame++) { +#ifdef FLOAT_MIXENG + *out++ = src[frame].l; /* left channel */ + *out++ = src[frame].r; /* right channel */ +#else +#ifdef RECIPROCAL + *out++ = src[frame].l * scale; /* left channel */ + *out++ = src[frame].r * scale; /* right channel */ +#else + *out++ = src[frame].l / scale; /* left channel */ + *out++ = src[frame].r / scale; /* right channel */ +#endif +#endif + } + + rpos = (rpos + frameCount) % hw->samples; + core->decr += frameCount; + core->rpos = rpos; + + coreaudio_unlock (core, "audioDeviceIOProc"); + return 0; +} + +static int coreaudio_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) +{ + OSStatus status; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + UInt32 propertySize; + int err; + const char *typ = "playback"; + AudioValueRange frameRange; + CoreaudioConf *conf = drv_opaque; + + /* create mutex */ + err = pthread_mutex_init(&core->mutex, NULL); + if (err) { + dolog("Could not create mutex\nReason: %s\n", strerror (err)); + return -1; + } + + audio_pcm_init_info (&hw->info, as); + + /* open default output device */ + propertySize = sizeof(core->outputDeviceID); + status = AudioHardwareGetProperty( + kAudioHardwarePropertyDefaultOutputDevice, + &propertySize, + &core->outputDeviceID); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not get default output Device\n"); + return -1; + } + if (core->outputDeviceID == kAudioDeviceUnknown) { + dolog ("Could not initialize %s - Unknown Audiodevice\n", typ); + return -1; + } + + /* get minimum and maximum buffer frame sizes */ + propertySize = sizeof(frameRange); + status = AudioDeviceGetProperty( + core->outputDeviceID, + 0, + 0, + kAudioDevicePropertyBufferFrameSizeRange, + &propertySize, + &frameRange); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not get device buffer frame range\n"); + return -1; + } + + if (frameRange.mMinimum > conf->buffer_frames) { + core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; + dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); + } + else if (frameRange.mMaximum < conf->buffer_frames) { + core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; + dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); + } + else { + core->audioDevicePropertyBufferFrameSize = conf->buffer_frames; + } + + /* set Buffer Frame Size */ + propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); + status = AudioDeviceSetProperty( + core->outputDeviceID, + NULL, + 0, + false, + kAudioDevicePropertyBufferFrameSize, + propertySize, + &core->audioDevicePropertyBufferFrameSize); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not set device buffer frame size %" PRIu32 "\n", + (uint32_t)core->audioDevicePropertyBufferFrameSize); + return -1; + } + + /* get Buffer Frame Size */ + propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); + status = AudioDeviceGetProperty( + core->outputDeviceID, + 0, + false, + kAudioDevicePropertyBufferFrameSize, + &propertySize, + &core->audioDevicePropertyBufferFrameSize); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not get device buffer frame size\n"); + return -1; + } + hw->samples = conf->nbuffers * core->audioDevicePropertyBufferFrameSize; + + /* get StreamFormat */ + propertySize = sizeof(core->outputStreamBasicDescription); + status = AudioDeviceGetProperty( + core->outputDeviceID, + 0, + false, + kAudioDevicePropertyStreamFormat, + &propertySize, + &core->outputStreamBasicDescription); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not get Device Stream properties\n"); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + + /* set Samplerate */ + core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq; + propertySize = sizeof(core->outputStreamBasicDescription); + status = AudioDeviceSetProperty( + core->outputDeviceID, + 0, + 0, + 0, + kAudioDevicePropertyStreamFormat, + propertySize, + &core->outputStreamBasicDescription); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n", + as->freq); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + + /* set Callback */ + status = AudioDeviceAddIOProc(core->outputDeviceID, audioDeviceIOProc, hw); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Could not set IOProc\n"); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + + /* start Playback */ + if (!isPlaying(core->outputDeviceID)) { + status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Could not start playback\n"); + AudioDeviceRemoveIOProc(core->outputDeviceID, audioDeviceIOProc); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + } + + return 0; +} + +static void coreaudio_fini_out (HWVoiceOut *hw) +{ + OSStatus status; + int err; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + if (!isAtexit) { + /* stop playback */ + if (isPlaying(core->outputDeviceID)) { + status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not stop playback\n"); + } + } + + /* remove callback */ + status = AudioDeviceRemoveIOProc(core->outputDeviceID, + audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not remove IOProc\n"); + } + } + core->outputDeviceID = kAudioDeviceUnknown; + + /* destroy mutex */ + err = pthread_mutex_destroy(&core->mutex); + if (err) { + dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); + } +} + +static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + OSStatus status; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + switch (cmd) { + case VOICE_ENABLE: + /* start playback */ + if (!isPlaying(core->outputDeviceID)) { + status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not resume playback\n"); + } + } + break; + + case VOICE_DISABLE: + /* stop playback */ + if (!isAtexit) { + if (isPlaying(core->outputDeviceID)) { + status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not pause playback\n"); + } + } + } + break; + } + return 0; +} + +static CoreaudioConf glob_conf = { + .buffer_frames = 512, + .nbuffers = 4, +}; + +static void *coreaudio_audio_init (void) +{ + CoreaudioConf *conf = g_malloc(sizeof(CoreaudioConf)); + *conf = glob_conf; + + atexit(coreaudio_atexit); + return conf; +} + +static void coreaudio_audio_fini (void *opaque) +{ + g_free(opaque); +} + +static struct audio_option coreaudio_options[] = { + { + .name = "BUFFER_SIZE", + .tag = AUD_OPT_INT, + .valp = &glob_conf.buffer_frames, + .descr = "Size of the buffer in frames" + }, + { + .name = "BUFFER_COUNT", + .tag = AUD_OPT_INT, + .valp = &glob_conf.nbuffers, + .descr = "Number of buffers" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops coreaudio_pcm_ops = { + .init_out = coreaudio_init_out, + .fini_out = coreaudio_fini_out, + .run_out = coreaudio_run_out, + .write = coreaudio_write, + .ctl_out = coreaudio_ctl_out +}; + +struct audio_driver coreaudio_audio_driver = { + .name = "coreaudio", + .descr = "CoreAudio http://developer.apple.com/audio/coreaudio.html", + .options = coreaudio_options, + .init = coreaudio_audio_init, + .fini = coreaudio_audio_fini, + .pcm_ops = &coreaudio_pcm_ops, + .can_be_default = 1, + .max_voices_out = 1, + .max_voices_in = 0, + .voice_size_out = sizeof (coreaudioVoiceOut), + .voice_size_in = 0 +}; diff --git a/src/audio/dsound_template.h b/src/audio/dsound_template.h new file mode 100644 index 0000000..b439f33 --- /dev/null +++ b/src/audio/dsound_template.h @@ -0,0 +1,278 @@ +/* + * QEMU DirectSound audio driver header + * + * Copyright (c) 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. + */ +#ifdef DSBTYPE_IN +#define NAME "capture buffer" +#define NAME2 "DirectSoundCapture" +#define TYPE in +#define IFACE IDirectSoundCaptureBuffer +#define BUFPTR LPDIRECTSOUNDCAPTUREBUFFER +#define FIELD dsound_capture_buffer +#define FIELD2 dsound_capture +#else +#define NAME "playback buffer" +#define NAME2 "DirectSound" +#define TYPE out +#define IFACE IDirectSoundBuffer +#define BUFPTR LPDIRECTSOUNDBUFFER +#define FIELD dsound_buffer +#define FIELD2 dsound +#endif + +static int glue (dsound_unlock_, TYPE) ( + BUFPTR buf, + LPVOID p1, + LPVOID p2, + DWORD blen1, + DWORD blen2 + ) +{ + HRESULT hr; + + hr = glue (IFACE, _Unlock) (buf, p1, blen1, p2, blen2); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not unlock " NAME "\n"); + return -1; + } + + return 0; +} + +static int glue (dsound_lock_, TYPE) ( + BUFPTR buf, + struct audio_pcm_info *info, + DWORD pos, + DWORD len, + LPVOID *p1p, + LPVOID *p2p, + DWORD *blen1p, + DWORD *blen2p, + int entire, + dsound *s + ) +{ + HRESULT hr; + LPVOID p1 = NULL, p2 = NULL; + DWORD blen1 = 0, blen2 = 0; + DWORD flag; + +#ifdef DSBTYPE_IN + flag = entire ? DSCBLOCK_ENTIREBUFFER : 0; +#else + flag = entire ? DSBLOCK_ENTIREBUFFER : 0; +#endif + hr = glue(IFACE, _Lock)(buf, pos, len, &p1, &blen1, &p2, &blen2, flag); + + if (FAILED (hr)) { +#ifndef DSBTYPE_IN + if (hr == DSERR_BUFFERLOST) { + if (glue (dsound_restore_, TYPE) (buf, s)) { + dsound_logerr (hr, "Could not lock " NAME "\n"); + } + goto fail; + } +#endif + dsound_logerr (hr, "Could not lock " NAME "\n"); + goto fail; + } + + if ((p1 && (blen1 & info->align)) || (p2 && (blen2 & info->align))) { + dolog ("DirectSound returned misaligned buffer %ld %ld\n", + blen1, blen2); + glue (dsound_unlock_, TYPE) (buf, p1, p2, blen1, blen2); + goto fail; + } + + if (!p1 && blen1) { + dolog ("warning: !p1 && blen1=%ld\n", blen1); + blen1 = 0; + } + + if (!p2 && blen2) { + dolog ("warning: !p2 && blen2=%ld\n", blen2); + blen2 = 0; + } + + *p1p = p1; + *p2p = p2; + *blen1p = blen1; + *blen2p = blen2; + return 0; + + fail: + *p1p = NULL - 1; + *p2p = NULL - 1; + *blen1p = -1; + *blen2p = -1; + return -1; +} + +#ifdef DSBTYPE_IN +static void dsound_fini_in (HWVoiceIn *hw) +#else +static void dsound_fini_out (HWVoiceOut *hw) +#endif +{ + HRESULT hr; +#ifdef DSBTYPE_IN + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; +#else + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; +#endif + + if (ds->FIELD) { + hr = glue (IFACE, _Stop) (ds->FIELD); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not stop " NAME "\n"); + } + + hr = glue (IFACE, _Release) (ds->FIELD); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release " NAME "\n"); + } + ds->FIELD = NULL; + } +} + +#ifdef DSBTYPE_IN +static int dsound_init_in(HWVoiceIn *hw, struct audsettings *as, + void *drv_opaque) +#else +static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) +#endif +{ + int err; + HRESULT hr; + dsound *s = drv_opaque; + WAVEFORMATEX wfx; + struct audsettings obt_as; + DSoundConf *conf = &s->conf; +#ifdef DSBTYPE_IN + const char *typ = "ADC"; + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; + DSCBUFFERDESC bd; + DSCBCAPS bc; +#else + const char *typ = "DAC"; + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; + DSBUFFERDESC bd; + DSBCAPS bc; +#endif + + if (!s->FIELD2) { + dolog ("Attempt to initialize voice without " NAME2 " object\n"); + return -1; + } + + err = waveformat_from_audio_settings (&wfx, as); + if (err) { + return -1; + } + + memset (&bd, 0, sizeof (bd)); + bd.dwSize = sizeof (bd); + bd.lpwfxFormat = &wfx; +#ifdef DSBTYPE_IN + bd.dwBufferBytes = conf->bufsize_in; + hr = IDirectSoundCapture_CreateCaptureBuffer ( + s->dsound_capture, + &bd, + &ds->dsound_capture_buffer, + NULL + ); +#else + bd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2; + bd.dwBufferBytes = conf->bufsize_out; + hr = IDirectSound_CreateSoundBuffer ( + s->dsound, + &bd, + &ds->dsound_buffer, + NULL + ); +#endif + + if (FAILED (hr)) { + dsound_logerr2 (hr, typ, "Could not create " NAME "\n"); + return -1; + } + + hr = glue (IFACE, _GetFormat) (ds->FIELD, &wfx, sizeof (wfx), NULL); + if (FAILED (hr)) { + dsound_logerr2 (hr, typ, "Could not get " NAME " format\n"); + goto fail0; + } + +#ifdef DEBUG_DSOUND + dolog (NAME "\n"); + print_wave_format (&wfx); +#endif + + memset (&bc, 0, sizeof (bc)); + bc.dwSize = sizeof (bc); + + hr = glue (IFACE, _GetCaps) (ds->FIELD, &bc); + if (FAILED (hr)) { + dsound_logerr2 (hr, typ, "Could not get " NAME " format\n"); + goto fail0; + } + + err = waveformat_to_audio_settings (&wfx, &obt_as); + if (err) { + goto fail0; + } + + ds->first_time = 1; + obt_as.endianness = 0; + audio_pcm_init_info (&hw->info, &obt_as); + + if (bc.dwBufferBytes & hw->info.align) { + dolog ( + "GetCaps returned misaligned buffer size %ld, alignment %d\n", + bc.dwBufferBytes, hw->info.align + 1 + ); + } + hw->samples = bc.dwBufferBytes >> hw->info.shift; + ds->s = s; + +#ifdef DEBUG_DSOUND + dolog ("caps %ld, desc %ld\n", + bc.dwBufferBytes, bd.dwBufferBytes); + + dolog ("bufsize %d, freq %d, chan %d, fmt %d\n", + hw->bufsize, settings.freq, settings.nchannels, settings.fmt); +#endif + return 0; + + fail0: + glue (dsound_fini_, TYPE) (hw); + return -1; +} + +#undef NAME +#undef NAME2 +#undef TYPE +#undef IFACE +#undef BUFPTR +#undef FIELD +#undef FIELD2 diff --git a/src/audio/dsoundaudio.c b/src/audio/dsoundaudio.c new file mode 100644 index 0000000..e9472c1 --- /dev/null +++ b/src/audio/dsoundaudio.c @@ -0,0 +1,904 @@ +/* + * QEMU DirectSound audio driver + * + * Copyright (c) 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. + */ + +/* + * SEAL 1.07 by Carlos 'pel' Hasan was used as documentation + */ + +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "dsound" +#include "audio_int.h" + +#include <windows.h> +#include <mmsystem.h> +#include <objbase.h> +#include <dsound.h> + +#include "audio_win_int.h" + +/* #define DEBUG_DSOUND */ + +typedef struct { + int bufsize_in; + int bufsize_out; + int latency_millis; +} DSoundConf; + +typedef struct { + LPDIRECTSOUND dsound; + LPDIRECTSOUNDCAPTURE dsound_capture; + struct audsettings settings; + DSoundConf conf; +} dsound; + +typedef struct { + HWVoiceOut hw; + LPDIRECTSOUNDBUFFER dsound_buffer; + DWORD old_pos; + int first_time; + dsound *s; +#ifdef DEBUG_DSOUND + DWORD old_ppos; + DWORD played; + DWORD mixed; +#endif +} DSoundVoiceOut; + +typedef struct { + HWVoiceIn hw; + int first_time; + LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer; + dsound *s; +} DSoundVoiceIn; + +static void dsound_log_hresult (HRESULT hr) +{ + const char *str = "BUG"; + + switch (hr) { + case DS_OK: + str = "The method succeeded"; + break; +#ifdef DS_NO_VIRTUALIZATION + case DS_NO_VIRTUALIZATION: + str = "The buffer was created, but another 3D algorithm was substituted"; + break; +#endif +#ifdef DS_INCOMPLETE + case DS_INCOMPLETE: + str = "The method succeeded, but not all the optional effects were obtained"; + break; +#endif +#ifdef DSERR_ACCESSDENIED + case DSERR_ACCESSDENIED: + str = "The request failed because access was denied"; + break; +#endif +#ifdef DSERR_ALLOCATED + case DSERR_ALLOCATED: + str = "The request failed because resources, such as a priority level, were already in use by another caller"; + break; +#endif +#ifdef DSERR_ALREADYINITIALIZED + case DSERR_ALREADYINITIALIZED: + str = "The object is already initialized"; + break; +#endif +#ifdef DSERR_BADFORMAT + case DSERR_BADFORMAT: + str = "The specified wave format is not supported"; + break; +#endif +#ifdef DSERR_BADSENDBUFFERGUID + case DSERR_BADSENDBUFFERGUID: + str = "The GUID specified in an audiopath file does not match a valid mix-in buffer"; + break; +#endif +#ifdef DSERR_BUFFERLOST + case DSERR_BUFFERLOST: + str = "The buffer memory has been lost and must be restored"; + break; +#endif +#ifdef DSERR_BUFFERTOOSMALL + case DSERR_BUFFERTOOSMALL: + str = "The buffer size is not great enough to enable effects processing"; + break; +#endif +#ifdef DSERR_CONTROLUNAVAIL + case DSERR_CONTROLUNAVAIL: + str = "The buffer control (volume, pan, and so on) requested by the caller is not available. Controls must be specified when the buffer is created, using the dwFlags member of DSBUFFERDESC"; + break; +#endif +#ifdef DSERR_DS8_REQUIRED + case DSERR_DS8_REQUIRED: + str = "A DirectSound object of class CLSID_DirectSound8 or later is required for the requested functionality. For more information, see IDirectSound8 Interface"; + break; +#endif +#ifdef DSERR_FXUNAVAILABLE + case DSERR_FXUNAVAILABLE: + str = "The effects requested could not be found on the system, or they are in the wrong order or in the wrong location; for example, an effect expected in hardware was found in software"; + break; +#endif +#ifdef DSERR_GENERIC + case DSERR_GENERIC : + str = "An undetermined error occurred inside the DirectSound subsystem"; + break; +#endif +#ifdef DSERR_INVALIDCALL + case DSERR_INVALIDCALL: + str = "This function is not valid for the current state of this object"; + break; +#endif +#ifdef DSERR_INVALIDPARAM + case DSERR_INVALIDPARAM: + str = "An invalid parameter was passed to the returning function"; + break; +#endif +#ifdef DSERR_NOAGGREGATION + case DSERR_NOAGGREGATION: + str = "The object does not support aggregation"; + break; +#endif +#ifdef DSERR_NODRIVER + case DSERR_NODRIVER: + str = "No sound driver is available for use, or the given GUID is not a valid DirectSound device ID"; + break; +#endif +#ifdef DSERR_NOINTERFACE + case DSERR_NOINTERFACE: + str = "The requested COM interface is not available"; + break; +#endif +#ifdef DSERR_OBJECTNOTFOUND + case DSERR_OBJECTNOTFOUND: + str = "The requested object was not found"; + break; +#endif +#ifdef DSERR_OTHERAPPHASPRIO + case DSERR_OTHERAPPHASPRIO: + str = "Another application has a higher priority level, preventing this call from succeeding"; + break; +#endif +#ifdef DSERR_OUTOFMEMORY + case DSERR_OUTOFMEMORY: + str = "The DirectSound subsystem could not allocate sufficient memory to complete the caller's request"; + break; +#endif +#ifdef DSERR_PRIOLEVELNEEDED + case DSERR_PRIOLEVELNEEDED: + str = "A cooperative level of DSSCL_PRIORITY or higher is required"; + break; +#endif +#ifdef DSERR_SENDLOOP + case DSERR_SENDLOOP: + str = "A circular loop of send effects was detected"; + break; +#endif +#ifdef DSERR_UNINITIALIZED + case DSERR_UNINITIALIZED: + str = "The Initialize method has not been called or has not been called successfully before other methods were called"; + break; +#endif +#ifdef DSERR_UNSUPPORTED + case DSERR_UNSUPPORTED: + str = "The function called is not supported at this time"; + break; +#endif + default: + AUD_log (AUDIO_CAP, "Reason: Unknown (HRESULT %#lx)\n", hr); + return; + } + + AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) dsound_logerr ( + HRESULT hr, + const char *fmt, + ... + ) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + dsound_log_hresult (hr); +} + +static void GCC_FMT_ATTR (3, 4) dsound_logerr2 ( + HRESULT hr, + 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); + + dsound_log_hresult (hr); +} + +static DWORD millis_to_bytes (struct audio_pcm_info *info, DWORD millis) +{ + return (millis * info->bytes_per_second) / 1000; +} + +#ifdef DEBUG_DSOUND +static void print_wave_format (WAVEFORMATEX *wfx) +{ + dolog ("tag = %d\n", wfx->wFormatTag); + dolog ("nChannels = %d\n", wfx->nChannels); + dolog ("nSamplesPerSec = %ld\n", wfx->nSamplesPerSec); + dolog ("nAvgBytesPerSec = %ld\n", wfx->nAvgBytesPerSec); + dolog ("nBlockAlign = %d\n", wfx->nBlockAlign); + dolog ("wBitsPerSample = %d\n", wfx->wBitsPerSample); + dolog ("cbSize = %d\n", wfx->cbSize); +} +#endif + +static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb, dsound *s) +{ + HRESULT hr; + + hr = IDirectSoundBuffer_Restore (dsb); + + if (hr != DS_OK) { + dsound_logerr (hr, "Could not restore playback buffer\n"); + return -1; + } + return 0; +} + +#include "dsound_template.h" +#define DSBTYPE_IN +#include "dsound_template.h" +#undef DSBTYPE_IN + +static int dsound_get_status_out (LPDIRECTSOUNDBUFFER dsb, DWORD *statusp, + dsound *s) +{ + HRESULT hr; + + hr = IDirectSoundBuffer_GetStatus (dsb, statusp); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not get playback buffer status\n"); + return -1; + } + + if (*statusp & DSERR_BUFFERLOST) { + dsound_restore_out(dsb, s); + return -1; + } + + return 0; +} + +static int dsound_get_status_in (LPDIRECTSOUNDCAPTUREBUFFER dscb, + DWORD *statusp) +{ + HRESULT hr; + + hr = IDirectSoundCaptureBuffer_GetStatus (dscb, statusp); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not get capture buffer status\n"); + return -1; + } + + return 0; +} + +static void dsound_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len) +{ + int src_len1 = dst_len; + int src_len2 = 0; + int pos = hw->rpos + dst_len; + struct st_sample *src1 = hw->mix_buf + hw->rpos; + struct st_sample *src2 = NULL; + + if (pos > hw->samples) { + src_len1 = hw->samples - hw->rpos; + src2 = hw->mix_buf; + src_len2 = dst_len - src_len1; + pos = src_len2; + } + + if (src_len1) { + hw->clip (dst, src1, src_len1); + } + + if (src_len2) { + dst = advance (dst, src_len1 << hw->info.shift); + hw->clip (dst, src2, src_len2); + } + + hw->rpos = pos % hw->samples; +} + +static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb, + dsound *s) +{ + int err; + LPVOID p1, p2; + DWORD blen1, blen2, len1, len2; + + err = dsound_lock_out ( + dsb, + &hw->info, + 0, + hw->samples << hw->info.shift, + &p1, &p2, + &blen1, &blen2, + 1, + s + ); + if (err) { + return; + } + + len1 = blen1 >> hw->info.shift; + len2 = blen2 >> hw->info.shift; + +#ifdef DEBUG_DSOUND + dolog ("clear %p,%ld,%ld %p,%ld,%ld\n", + p1, blen1, len1, + p2, blen2, len2); +#endif + + if (p1 && len1) { + audio_pcm_info_clear_buf (&hw->info, p1, len1); + } + + if (p2 && len2) { + audio_pcm_info_clear_buf (&hw->info, p2, len2); + } + + dsound_unlock_out (dsb, p1, p2, blen1, blen2); +} + +static int dsound_open (dsound *s) +{ + HRESULT hr; + HWND hwnd; + + hwnd = GetForegroundWindow (); + hr = IDirectSound_SetCooperativeLevel ( + s->dsound, + hwnd, + DSSCL_PRIORITY + ); + + if (FAILED (hr)) { + dsound_logerr (hr, "Could not set cooperative level for window %p\n", + hwnd); + return -1; + } + + return 0; +} + +static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + HRESULT hr; + DWORD status; + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; + LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer; + dsound *s = ds->s; + + if (!dsb) { + dolog ("Attempt to control voice without a buffer\n"); + return 0; + } + + switch (cmd) { + case VOICE_ENABLE: + if (dsound_get_status_out (dsb, &status, s)) { + return -1; + } + + if (status & DSBSTATUS_PLAYING) { + dolog ("warning: Voice is already playing\n"); + return 0; + } + + dsound_clear_sample (hw, dsb, s); + + hr = IDirectSoundBuffer_Play (dsb, 0, 0, DSBPLAY_LOOPING); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not start playing buffer\n"); + return -1; + } + break; + + case VOICE_DISABLE: + if (dsound_get_status_out (dsb, &status, s)) { + return -1; + } + + if (status & DSBSTATUS_PLAYING) { + hr = IDirectSoundBuffer_Stop (dsb); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not stop playing buffer\n"); + return -1; + } + } + else { + dolog ("warning: Voice is not playing\n"); + } + break; + } + return 0; +} + +static int dsound_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int dsound_run_out (HWVoiceOut *hw, int live) +{ + int err; + HRESULT hr; + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; + LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer; + int len, hwshift; + DWORD blen1, blen2; + DWORD len1, len2; + DWORD decr; + DWORD wpos, ppos, old_pos; + LPVOID p1, p2; + int bufsize; + dsound *s = ds->s; + DSoundConf *conf = &s->conf; + + if (!dsb) { + dolog ("Attempt to run empty with playback buffer\n"); + return 0; + } + + hwshift = hw->info.shift; + bufsize = hw->samples << hwshift; + + hr = IDirectSoundBuffer_GetCurrentPosition ( + dsb, + &ppos, + ds->first_time ? &wpos : NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not get playback buffer position\n"); + return 0; + } + + len = live << hwshift; + + if (ds->first_time) { + if (conf->latency_millis) { + DWORD cur_blat; + + cur_blat = audio_ring_dist (wpos, ppos, bufsize); + ds->first_time = 0; + old_pos = wpos; + old_pos += + millis_to_bytes (&hw->info, conf->latency_millis) - cur_blat; + old_pos %= bufsize; + old_pos &= ~hw->info.align; + } + else { + old_pos = wpos; + } +#ifdef DEBUG_DSOUND + ds->played = 0; + ds->mixed = 0; +#endif + } + else { + if (ds->old_pos == ppos) { +#ifdef DEBUG_DSOUND + dolog ("old_pos == ppos\n"); +#endif + return 0; + } + +#ifdef DEBUG_DSOUND + ds->played += audio_ring_dist (ds->old_pos, ppos, hw->bufsize); +#endif + old_pos = ds->old_pos; + } + + if ((old_pos < ppos) && ((old_pos + len) > ppos)) { + len = ppos - old_pos; + } + else { + if ((old_pos > ppos) && ((old_pos + len) > (ppos + bufsize))) { + len = bufsize - old_pos + ppos; + } + } + + if (audio_bug (AUDIO_FUNC, len < 0 || len > bufsize)) { + dolog ("len=%d bufsize=%d old_pos=%ld ppos=%ld\n", + len, bufsize, old_pos, ppos); + return 0; + } + + len &= ~hw->info.align; + if (!len) { + return 0; + } + +#ifdef DEBUG_DSOUND + ds->old_ppos = ppos; +#endif + err = dsound_lock_out ( + dsb, + &hw->info, + old_pos, + len, + &p1, &p2, + &blen1, &blen2, + 0, + s + ); + if (err) { + return 0; + } + + len1 = blen1 >> hwshift; + len2 = blen2 >> hwshift; + decr = len1 + len2; + + if (p1 && len1) { + dsound_write_sample (hw, p1, len1); + } + + if (p2 && len2) { + dsound_write_sample (hw, p2, len2); + } + + dsound_unlock_out (dsb, p1, p2, blen1, blen2); + ds->old_pos = (old_pos + (decr << hwshift)) % bufsize; + +#ifdef DEBUG_DSOUND + ds->mixed += decr << hwshift; + + dolog ("played %lu mixed %lu diff %ld sec %f\n", + ds->played, + ds->mixed, + ds->mixed - ds->played, + abs (ds->mixed - ds->played) / (double) hw->info.bytes_per_second); +#endif + return decr; +} + +static int dsound_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + HRESULT hr; + DWORD status; + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; + LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer; + + if (!dscb) { + dolog ("Attempt to control capture voice without a buffer\n"); + return -1; + } + + switch (cmd) { + case VOICE_ENABLE: + if (dsound_get_status_in (dscb, &status)) { + return -1; + } + + if (status & DSCBSTATUS_CAPTURING) { + dolog ("warning: Voice is already capturing\n"); + return 0; + } + + /* clear ?? */ + + hr = IDirectSoundCaptureBuffer_Start (dscb, DSCBSTART_LOOPING); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not start capturing\n"); + return -1; + } + break; + + case VOICE_DISABLE: + if (dsound_get_status_in (dscb, &status)) { + return -1; + } + + if (status & DSCBSTATUS_CAPTURING) { + hr = IDirectSoundCaptureBuffer_Stop (dscb); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not stop capturing\n"); + return -1; + } + } + else { + dolog ("warning: Voice is not capturing\n"); + } + break; + } + return 0; +} + +static int dsound_read (SWVoiceIn *sw, void *buf, int len) +{ + return audio_pcm_sw_read (sw, buf, len); +} + +static int dsound_run_in (HWVoiceIn *hw) +{ + int err; + HRESULT hr; + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; + LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer; + int live, len, dead; + DWORD blen1, blen2; + DWORD len1, len2; + DWORD decr; + DWORD cpos, rpos; + LPVOID p1, p2; + int hwshift; + dsound *s = ds->s; + + if (!dscb) { + dolog ("Attempt to run without capture buffer\n"); + return 0; + } + + hwshift = hw->info.shift; + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + if (!dead) { + return 0; + } + + hr = IDirectSoundCaptureBuffer_GetCurrentPosition ( + dscb, + &cpos, + ds->first_time ? &rpos : NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not get capture buffer position\n"); + return 0; + } + + if (ds->first_time) { + ds->first_time = 0; + if (rpos & hw->info.align) { + ldebug ("warning: Misaligned capture read position %ld(%d)\n", + rpos, hw->info.align); + } + hw->wpos = rpos >> hwshift; + } + + if (cpos & hw->info.align) { + ldebug ("warning: Misaligned capture position %ld(%d)\n", + cpos, hw->info.align); + } + cpos >>= hwshift; + + len = audio_ring_dist (cpos, hw->wpos, hw->samples); + if (!len) { + return 0; + } + len = audio_MIN (len, dead); + + err = dsound_lock_in ( + dscb, + &hw->info, + hw->wpos << hwshift, + len << hwshift, + &p1, + &p2, + &blen1, + &blen2, + 0, + s + ); + if (err) { + return 0; + } + + len1 = blen1 >> hwshift; + len2 = blen2 >> hwshift; + decr = len1 + len2; + + if (p1 && len1) { + hw->conv (hw->conv_buf + hw->wpos, p1, len1); + } + + if (p2 && len2) { + hw->conv (hw->conv_buf, p2, len2); + } + + dsound_unlock_in (dscb, p1, p2, blen1, blen2); + hw->wpos = (hw->wpos + decr) % hw->samples; + return decr; +} + +static DSoundConf glob_conf = { + .bufsize_in = 16384, + .bufsize_out = 16384, + .latency_millis = 10 +}; + +static void dsound_audio_fini (void *opaque) +{ + HRESULT hr; + dsound *s = opaque; + + if (!s->dsound) { + g_free(s); + return; + } + + hr = IDirectSound_Release (s->dsound); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release DirectSound\n"); + } + s->dsound = NULL; + + if (!s->dsound_capture) { + g_free(s); + return; + } + + hr = IDirectSoundCapture_Release (s->dsound_capture); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release DirectSoundCapture\n"); + } + s->dsound_capture = NULL; + + g_free(s); +} + +static void *dsound_audio_init (void) +{ + int err; + HRESULT hr; + dsound *s = g_malloc0(sizeof(dsound)); + + s->conf = glob_conf; + hr = CoInitialize (NULL); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not initialize COM\n"); + g_free(s); + return NULL; + } + + hr = CoCreateInstance ( + &CLSID_DirectSound, + NULL, + CLSCTX_ALL, + &IID_IDirectSound, + (void **) &s->dsound + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not create DirectSound instance\n"); + g_free(s); + return NULL; + } + + hr = IDirectSound_Initialize (s->dsound, NULL); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not initialize DirectSound\n"); + + hr = IDirectSound_Release (s->dsound); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release DirectSound\n"); + } + g_free(s); + return NULL; + } + + hr = CoCreateInstance ( + &CLSID_DirectSoundCapture, + NULL, + CLSCTX_ALL, + &IID_IDirectSoundCapture, + (void **) &s->dsound_capture + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not create DirectSoundCapture instance\n"); + } + else { + hr = IDirectSoundCapture_Initialize (s->dsound_capture, NULL); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not initialize DirectSoundCapture\n"); + + hr = IDirectSoundCapture_Release (s->dsound_capture); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release DirectSoundCapture\n"); + } + s->dsound_capture = NULL; + } + } + + err = dsound_open (s); + if (err) { + dsound_audio_fini (s); + return NULL; + } + + return s; +} + +static struct audio_option dsound_options[] = { + { + .name = "LATENCY_MILLIS", + .tag = AUD_OPT_INT, + .valp = &glob_conf.latency_millis, + .descr = "(undocumented)" + }, + { + .name = "BUFSIZE_OUT", + .tag = AUD_OPT_INT, + .valp = &glob_conf.bufsize_out, + .descr = "(undocumented)" + }, + { + .name = "BUFSIZE_IN", + .tag = AUD_OPT_INT, + .valp = &glob_conf.bufsize_in, + .descr = "(undocumented)" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops dsound_pcm_ops = { + .init_out = dsound_init_out, + .fini_out = dsound_fini_out, + .run_out = dsound_run_out, + .write = dsound_write, + .ctl_out = dsound_ctl_out, + + .init_in = dsound_init_in, + .fini_in = dsound_fini_in, + .run_in = dsound_run_in, + .read = dsound_read, + .ctl_in = dsound_ctl_in +}; + +struct audio_driver dsound_audio_driver = { + .name = "dsound", + .descr = "DirectSound http://wikipedia.org/wiki/DirectSound", + .options = dsound_options, + .init = dsound_audio_init, + .fini = dsound_audio_fini, + .pcm_ops = &dsound_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = 1, + .voice_size_out = sizeof (DSoundVoiceOut), + .voice_size_in = sizeof (DSoundVoiceIn) +}; diff --git a/src/audio/mixeng.c b/src/audio/mixeng.c new file mode 100644 index 0000000..0e4976f --- /dev/null +++ b/src/audio/mixeng.c @@ -0,0 +1,366 @@ +/* + * QEMU Mixing engine + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * Copyright (c) 1998 Fabrice Bellard + * + * 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 "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "mixeng" +#include "audio_int.h" + +/* 8 bit */ +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) + +/* Signed 8 bit */ +#define BSIZE 8 +#define ITYPE int +#define IN_MIN SCHAR_MIN +#define IN_MAX SCHAR_MAX +#define SIGNED +#define SHIFT 8 +#include "mixeng_template.h" +#undef SIGNED +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +/* Unsigned 8 bit */ +#define BSIZE 8 +#define ITYPE uint +#define IN_MIN 0 +#define IN_MAX UCHAR_MAX +#define SHIFT 8 +#include "mixeng_template.h" +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION + +/* Signed 16 bit */ +#define BSIZE 16 +#define ITYPE int +#define IN_MIN SHRT_MIN +#define IN_MAX SHRT_MAX +#define SIGNED +#define SHIFT 16 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap16 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef SIGNED +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +/* Unsigned 16 bit */ +#define BSIZE 16 +#define ITYPE uint +#define IN_MIN 0 +#define IN_MAX USHRT_MAX +#define SHIFT 16 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap16 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +/* Signed 32 bit */ +#define BSIZE 32 +#define ITYPE int +#define IN_MIN INT32_MIN +#define IN_MAX INT32_MAX +#define SIGNED +#define SHIFT 32 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap32 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef SIGNED +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +/* Unsigned 32 bit */ +#define BSIZE 32 +#define ITYPE uint +#define IN_MIN 0 +#define IN_MAX UINT32_MAX +#define SHIFT 32 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap32 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +t_sample *mixeng_conv[2][2][2][3] = { + { + { + { + conv_natural_uint8_t_to_mono, + conv_natural_uint16_t_to_mono, + conv_natural_uint32_t_to_mono + }, + { + conv_natural_uint8_t_to_mono, + conv_swap_uint16_t_to_mono, + conv_swap_uint32_t_to_mono, + } + }, + { + { + conv_natural_int8_t_to_mono, + conv_natural_int16_t_to_mono, + conv_natural_int32_t_to_mono + }, + { + conv_natural_int8_t_to_mono, + conv_swap_int16_t_to_mono, + conv_swap_int32_t_to_mono + } + } + }, + { + { + { + conv_natural_uint8_t_to_stereo, + conv_natural_uint16_t_to_stereo, + conv_natural_uint32_t_to_stereo + }, + { + conv_natural_uint8_t_to_stereo, + conv_swap_uint16_t_to_stereo, + conv_swap_uint32_t_to_stereo + } + }, + { + { + conv_natural_int8_t_to_stereo, + conv_natural_int16_t_to_stereo, + conv_natural_int32_t_to_stereo + }, + { + conv_natural_int8_t_to_stereo, + conv_swap_int16_t_to_stereo, + conv_swap_int32_t_to_stereo, + } + } + } +}; + +f_sample *mixeng_clip[2][2][2][3] = { + { + { + { + clip_natural_uint8_t_from_mono, + clip_natural_uint16_t_from_mono, + clip_natural_uint32_t_from_mono + }, + { + clip_natural_uint8_t_from_mono, + clip_swap_uint16_t_from_mono, + clip_swap_uint32_t_from_mono + } + }, + { + { + clip_natural_int8_t_from_mono, + clip_natural_int16_t_from_mono, + clip_natural_int32_t_from_mono + }, + { + clip_natural_int8_t_from_mono, + clip_swap_int16_t_from_mono, + clip_swap_int32_t_from_mono + } + } + }, + { + { + { + clip_natural_uint8_t_from_stereo, + clip_natural_uint16_t_from_stereo, + clip_natural_uint32_t_from_stereo + }, + { + clip_natural_uint8_t_from_stereo, + clip_swap_uint16_t_from_stereo, + clip_swap_uint32_t_from_stereo + } + }, + { + { + clip_natural_int8_t_from_stereo, + clip_natural_int16_t_from_stereo, + clip_natural_int32_t_from_stereo + }, + { + clip_natural_int8_t_from_stereo, + clip_swap_int16_t_from_stereo, + clip_swap_int32_t_from_stereo + } + } + } +}; + +/* + * August 21, 1998 + * Copyright 1998 Fabrice Bellard. + * + * [Rewrote completly the code of Lance Norskog And Sundry + * Contributors with a more efficient algorithm.] + * + * This source code is freely redistributable and may be used for + * any purpose. This copyright notice must be maintained. + * Lance Norskog And Sundry Contributors are not responsible for + * the consequences of using this software. + */ + +/* + * Sound Tools rate change effect file. + */ +/* + * Linear Interpolation. + * + * The use of fractional increment allows us to use no buffer. It + * avoid the problems at the end of the buffer we had with the old + * method which stored a possibly big buffer of size + * lcm(in_rate,out_rate). + * + * Limited to 16 bit samples and sampling frequency <= 65535 Hz. If + * the input & output frequencies are equal, a delay of one sample is + * introduced. Limited to processing 32-bit count worth of samples. + * + * 1 << FRAC_BITS evaluating to zero in several places. Changed with + * an (unsigned long) cast to make it safe. MarkMLl 2/1/99 + */ + +/* Private data */ +struct rate { + uint64_t opos; + uint64_t opos_inc; + uint32_t ipos; /* position in the input stream (integer) */ + struct st_sample ilast; /* last sample in the input stream */ +}; + +/* + * Prepare processing. + */ +void *st_rate_start (int inrate, int outrate) +{ + struct rate *rate = audio_calloc (AUDIO_FUNC, 1, sizeof (*rate)); + + if (!rate) { + dolog ("Could not allocate resampler (%zu bytes)\n", sizeof (*rate)); + return NULL; + } + + rate->opos = 0; + + /* increment */ + rate->opos_inc = ((uint64_t) inrate << 32) / outrate; + + rate->ipos = 0; + rate->ilast.l = 0; + rate->ilast.r = 0; + return rate; +} + +#define NAME st_rate_flow_mix +#define OP(a, b) a += b +#include "rate_template.h" + +#define NAME st_rate_flow +#define OP(a, b) a = b +#include "rate_template.h" + +void st_rate_stop (void *opaque) +{ + g_free (opaque); +} + +void mixeng_clear (struct st_sample *buf, int len) +{ + memset (buf, 0, len * sizeof (struct st_sample)); +} + +void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol) +{ + if (vol->mute) { + mixeng_clear (buf, len); + return; + } + + while (len--) { +#ifdef FLOAT_MIXENG + buf->l = buf->l * vol->l; + buf->r = buf->r * vol->r; +#else + buf->l = (buf->l * vol->l) >> 32; + buf->r = (buf->r * vol->r) >> 32; +#endif + buf += 1; + } +} diff --git a/src/audio/mixeng.h b/src/audio/mixeng.h new file mode 100644 index 0000000..9de443b --- /dev/null +++ b/src/audio/mixeng.h @@ -0,0 +1,51 @@ +/* + * QEMU Mixing engine header + * + * Copyright (c) 2004-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. + */ +#ifndef QEMU_MIXENG_H +#define QEMU_MIXENG_H + +#ifdef FLOAT_MIXENG +typedef float mixeng_real; +struct mixeng_volume { int mute; mixeng_real r; mixeng_real l; }; +struct st_sample { mixeng_real l; mixeng_real r; }; +#else +struct mixeng_volume { int mute; int64_t r; int64_t l; }; +struct st_sample { int64_t l; int64_t r; }; +#endif + +typedef void (t_sample) (struct st_sample *dst, const void *src, int samples); +typedef void (f_sample) (void *dst, const struct st_sample *src, int samples); + +extern t_sample *mixeng_conv[2][2][2][3]; +extern f_sample *mixeng_clip[2][2][2][3]; + +void *st_rate_start (int inrate, int outrate); +void st_rate_flow (void *opaque, struct st_sample *ibuf, struct st_sample *obuf, + int *isamp, int *osamp); +void st_rate_flow_mix (void *opaque, struct st_sample *ibuf, struct st_sample *obuf, + int *isamp, int *osamp); +void st_rate_stop (void *opaque); +void mixeng_clear (struct st_sample *buf, int len); +void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol); + +#endif /* mixeng.h */ diff --git a/src/audio/mixeng_template.h b/src/audio/mixeng_template.h new file mode 100644 index 0000000..77cc89b --- /dev/null +++ b/src/audio/mixeng_template.h @@ -0,0 +1,154 @@ +/* + * QEMU Mixing engine + * + * Copyright (c) 2004-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. + */ + +/* + * Tusen tack till Mike Nordell + * dec++'ified by Dscho + */ + +#ifndef SIGNED +#define HALF (IN_MAX >> 1) +#endif + +#define ET glue (ENDIAN_CONVERSION, glue (glue (glue (_, ITYPE), BSIZE), _t)) +#define IN_T glue (glue (ITYPE, BSIZE), _t) + +#ifdef FLOAT_MIXENG +static inline mixeng_real glue (conv_, ET) (IN_T v) +{ + IN_T nv = ENDIAN_CONVERT (v); + +#ifdef RECIPROCAL +#ifdef SIGNED + return nv * (1.f / (mixeng_real) (IN_MAX - IN_MIN)); +#else + return (nv - HALF) * (1.f / (mixeng_real) IN_MAX); +#endif +#else /* !RECIPROCAL */ +#ifdef SIGNED + return nv / (mixeng_real) ((mixeng_real) IN_MAX - IN_MIN); +#else + return (nv - HALF) / (mixeng_real) IN_MAX; +#endif +#endif +} + +static inline IN_T glue (clip_, ET) (mixeng_real v) +{ + if (v >= 0.5) { + return IN_MAX; + } + else if (v < -0.5) { + return IN_MIN; + } + +#ifdef SIGNED + return ENDIAN_CONVERT ((IN_T) (v * ((mixeng_real) IN_MAX - IN_MIN))); +#else + return ENDIAN_CONVERT ((IN_T) ((v * IN_MAX) + HALF)); +#endif +} + +#else /* !FLOAT_MIXENG */ + +static inline int64_t glue (conv_, ET) (IN_T v) +{ + IN_T nv = ENDIAN_CONVERT (v); +#ifdef SIGNED + return ((int64_t) nv) << (32 - SHIFT); +#else + return ((int64_t) nv - HALF) << (32 - SHIFT); +#endif +} + +static inline IN_T glue (clip_, ET) (int64_t v) +{ + if (v >= 0x7f000000) { + return IN_MAX; + } + else if (v < -2147483648LL) { + return IN_MIN; + } + +#ifdef SIGNED + return ENDIAN_CONVERT ((IN_T) (v >> (32 - SHIFT))); +#else + return ENDIAN_CONVERT ((IN_T) ((v >> (32 - SHIFT)) + HALF)); +#endif +} +#endif + +static void glue (glue (conv_, ET), _to_stereo) + (struct st_sample *dst, const void *src, int samples) +{ + struct st_sample *out = dst; + IN_T *in = (IN_T *) src; + + while (samples--) { + out->l = glue (conv_, ET) (*in++); + out->r = glue (conv_, ET) (*in++); + out += 1; + } +} + +static void glue (glue (conv_, ET), _to_mono) + (struct st_sample *dst, const void *src, int samples) +{ + struct st_sample *out = dst; + IN_T *in = (IN_T *) src; + + while (samples--) { + out->l = glue (conv_, ET) (in[0]); + out->r = out->l; + out += 1; + in += 1; + } +} + +static void glue (glue (clip_, ET), _from_stereo) + (void *dst, const struct st_sample *src, int samples) +{ + const struct st_sample *in = src; + IN_T *out = (IN_T *) dst; + while (samples--) { + *out++ = glue (clip_, ET) (in->l); + *out++ = glue (clip_, ET) (in->r); + in += 1; + } +} + +static void glue (glue (clip_, ET), _from_mono) + (void *dst, const struct st_sample *src, int samples) +{ + const struct st_sample *in = src; + IN_T *out = (IN_T *) dst; + while (samples--) { + *out++ = glue (clip_, ET) (in->l + in->r); + in += 1; + } +} + +#undef ET +#undef HALF +#undef IN_T diff --git a/src/audio/noaudio.c b/src/audio/noaudio.c new file mode 100644 index 0000000..50db1f3 --- /dev/null +++ b/src/audio/noaudio.c @@ -0,0 +1,173 @@ +/* + * QEMU Timer based audio emulation + * + * Copyright (c) 2004-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 "qemu-common.h" +#include "audio.h" +#include "qemu/timer.h" + +#define AUDIO_CAP "noaudio" +#include "audio_int.h" + +typedef struct NoVoiceOut { + HWVoiceOut hw; + int64_t old_ticks; +} NoVoiceOut; + +typedef struct NoVoiceIn { + HWVoiceIn hw; + int64_t old_ticks; +} NoVoiceIn; + +static int no_run_out (HWVoiceOut *hw, int live) +{ + NoVoiceOut *no = (NoVoiceOut *) hw; + int decr, samples; + int64_t now; + int64_t ticks; + int64_t bytes; + + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ticks = now - no->old_ticks; + bytes = muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ()); + bytes = audio_MIN (bytes, INT_MAX); + samples = bytes >> hw->info.shift; + + no->old_ticks = now; + decr = audio_MIN (live, samples); + hw->rpos = (hw->rpos + decr) % hw->samples; + return decr; +} + +static int no_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int no_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) +{ + audio_pcm_init_info (&hw->info, as); + hw->samples = 1024; + return 0; +} + +static void no_fini_out (HWVoiceOut *hw) +{ + (void) hw; +} + +static int no_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static int no_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +{ + audio_pcm_init_info (&hw->info, as); + hw->samples = 1024; + return 0; +} + +static void no_fini_in (HWVoiceIn *hw) +{ + (void) hw; +} + +static int no_run_in (HWVoiceIn *hw) +{ + NoVoiceIn *no = (NoVoiceIn *) hw; + int live = audio_pcm_hw_get_live_in (hw); + int dead = hw->samples - live; + int samples = 0; + + if (dead) { + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + int64_t ticks = now - no->old_ticks; + int64_t bytes = + muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ()); + + no->old_ticks = now; + bytes = audio_MIN (bytes, INT_MAX); + samples = bytes >> hw->info.shift; + samples = audio_MIN (samples, dead); + } + return samples; +} + +static int no_read (SWVoiceIn *sw, void *buf, int size) +{ + /* use custom code here instead of audio_pcm_sw_read() to avoid + * useless resampling/mixing */ + int samples = size >> sw->info.shift; + int total = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; + int to_clear = audio_MIN (samples, total); + sw->total_hw_samples_acquired += total; + audio_pcm_info_clear_buf (&sw->info, buf, to_clear); + return to_clear << sw->info.shift; +} + +static int no_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static void *no_audio_init (void) +{ + return &no_audio_init; +} + +static void no_audio_fini (void *opaque) +{ + (void) opaque; +} + +static struct audio_pcm_ops no_pcm_ops = { + .init_out = no_init_out, + .fini_out = no_fini_out, + .run_out = no_run_out, + .write = no_write, + .ctl_out = no_ctl_out, + + .init_in = no_init_in, + .fini_in = no_fini_in, + .run_in = no_run_in, + .read = no_read, + .ctl_in = no_ctl_in +}; + +struct audio_driver no_audio_driver = { + .name = "none", + .descr = "Timer based audio emulation", + .options = NULL, + .init = no_audio_init, + .fini = no_audio_fini, + .pcm_ops = &no_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (NoVoiceOut), + .voice_size_in = sizeof (NoVoiceIn) +}; diff --git a/src/audio/ossaudio.c b/src/audio/ossaudio.c new file mode 100644 index 0000000..7dbe333 --- /dev/null +++ b/src/audio/ossaudio.c @@ -0,0 +1,941 @@ +/* + * 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> +#include <sys/soundcard.h> +#include "qemu-common.h" +#include "qemu/main-loop.h" +#include "qemu/host-utils.h" +#include "audio.h" +#include "trace.h" + +#define AUDIO_CAP "oss" +#include "audio_int.h" + +#if defined OSS_GETVERSION && defined SNDCTL_DSP_POLICY +#define USE_DSP_POLICY +#endif + +typedef struct OSSConf { + int try_mmap; + int nfrags; + int fragsize; + const char *devpath_out; + const char *devpath_in; + int exclusive; + int policy; +} OSSConf; + +typedef struct OSSVoiceOut { + HWVoiceOut hw; + void *pcm_buf; + int fd; + int wpos; + int nfrags; + int fragsize; + int mmapped; + int pending; + OSSConf *conf; +} OSSVoiceOut; + +typedef struct OSSVoiceIn { + HWVoiceIn hw; + void *pcm_buf; + int fd; + int nfrags; + int fragsize; + OSSConf *conf; +} OSSVoiceIn; + +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 void oss_poll_out (HWVoiceOut *hw) +{ + OSSVoiceOut *oss = (OSSVoiceOut *) hw; + + qemu_set_fd_handler (oss->fd, NULL, oss_helper_poll_out, NULL); +} + +static void oss_poll_in (HWVoiceIn *hw) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + + 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, int endianness) +{ + switch (fmt) { + case AUD_FMT_S8: + return AFMT_S8; + + case AUD_FMT_U8: + return AFMT_U8; + + case AUD_FMT_S16: + if (endianness) { + return AFMT_S16_BE; + } + else { + return AFMT_S16_LE; + } + + case AUD_FMT_U16: + if (endianness) { + return AFMT_U16_BE; + } + else { + 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, OSSConf* conf) +{ + 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)) { + trace_oss_version(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) { + trace_oss_invalid_available_size(abinfo.bytes, bufsize); + abinfo.bytes = bufsize; + } + + if (abinfo.bytes < 0) { + trace_oss_invalid_available_size(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 { + g_free (oss->pcm_buf); + } + oss->pcm_buf = NULL; + } +} + +static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) +{ + OSSVoiceOut *oss = (OSSVoiceOut *) hw; + struct oss_params req, obt; + int endianness; + int err; + int fd; + audfmt_e effective_fmt; + struct audsettings obt_as; + OSSConf *conf = drv_opaque; + + oss->fd = -1; + + req.fmt = aud_to_ossfmt (as->fmt, as->endianness); + req.freq = as->freq; + req.nchannels = as->nchannels; + req.fragsize = conf->fragsize; + req.nfrags = conf->nfrags; + + if (oss_open (0, &req, &obt, &fd, conf)) { + 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; + oss->conf = conf; + 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, void *drv_opaque) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + struct oss_params req, obt; + int endianness; + int err; + int fd; + audfmt_e effective_fmt; + struct audsettings obt_as; + OSSConf *conf = drv_opaque; + + oss->fd = -1; + + req.fmt = aud_to_ossfmt (as->fmt, as->endianness); + req.freq = as->freq; + req.nchannels = as->nchannels; + req.fragsize = conf->fragsize; + req.nfrags = conf->nfrags; + if (oss_open (1, &req, &obt, &fd, conf)) { + 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; + oss->conf = conf; + return 0; +} + +static void oss_fini_in (HWVoiceIn *hw) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + + oss_anal_close (&oss->fd); + + g_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); + } + + 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 OSSConf glob_conf = { + .try_mmap = 0, + .nfrags = 4, + .fragsize = 4096, + .devpath_out = "/dev/dsp", + .devpath_in = "/dev/dsp", + .exclusive = 0, + .policy = 5 +}; + +static void *oss_audio_init (void) +{ + OSSConf *conf = g_malloc(sizeof(OSSConf)); + *conf = glob_conf; + + if (access(conf->devpath_in, R_OK | W_OK) < 0 || + access(conf->devpath_out, R_OK | W_OK) < 0) { + g_free(conf); + return NULL; + } + return conf; +} + +static void oss_audio_fini (void *opaque) +{ + g_free(opaque); +} + +static struct audio_option oss_options[] = { + { + .name = "FRAGSIZE", + .tag = AUD_OPT_INT, + .valp = &glob_conf.fragsize, + .descr = "Fragment size in bytes" + }, + { + .name = "NFRAGS", + .tag = AUD_OPT_INT, + .valp = &glob_conf.nfrags, + .descr = "Number of fragments" + }, + { + .name = "MMAP", + .tag = AUD_OPT_BOOL, + .valp = &glob_conf.try_mmap, + .descr = "Try using memory mapped access" + }, + { + .name = "DAC_DEV", + .tag = AUD_OPT_STR, + .valp = &glob_conf.devpath_out, + .descr = "Path to DAC device" + }, + { + .name = "ADC_DEV", + .tag = AUD_OPT_STR, + .valp = &glob_conf.devpath_in, + .descr = "Path to ADC device" + }, + { + .name = "EXCLUSIVE", + .tag = AUD_OPT_BOOL, + .valp = &glob_conf.exclusive, + .descr = "Open device in exclusive mode (vmix wont work)" + }, +#ifdef USE_DSP_POLICY + { + .name = "POLICY", + .tag = AUD_OPT_INT, + .valp = &glob_conf.policy, + .descr = "Set the timing policy of the device, -1 to use fragment mode", + }, +#endif + { /* 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) +}; diff --git a/src/audio/paaudio.c b/src/audio/paaudio.c new file mode 100644 index 0000000..fea6071 --- /dev/null +++ b/src/audio/paaudio.c @@ -0,0 +1,953 @@ +/* public domain */ +#include "qemu-common.h" +#include "audio.h" + +#include <pulse/pulseaudio.h> + +#define AUDIO_CAP "pulseaudio" +#include "audio_int.h" +#include "audio_pt_int.h" + +typedef struct { + int samples; + char *server; + char *sink; + char *source; +} PAConf; + +typedef struct { + PAConf conf; + pa_threaded_mainloop *mainloop; + pa_context *context; +} paaudio; + +typedef struct { + HWVoiceOut hw; + int done; + int live; + int decr; + int rpos; + pa_stream *stream; + void *pcm_buf; + struct audio_pt pt; + paaudio *g; +} PAVoiceOut; + +typedef struct { + HWVoiceIn hw; + int done; + int dead; + int incr; + int wpos; + pa_stream *stream; + void *pcm_buf; + struct audio_pt pt; + const void *read_data; + size_t read_index, read_length; + paaudio *g; +} PAVoiceIn; + +static void qpa_audio_fini(void *opaque); + +static void GCC_FMT_ATTR (2, 3) qpa_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", pa_strerror (err)); +} + +#ifndef PA_CONTEXT_IS_GOOD +static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) +{ + return + x == PA_CONTEXT_CONNECTING || + x == PA_CONTEXT_AUTHORIZING || + x == PA_CONTEXT_SETTING_NAME || + x == PA_CONTEXT_READY; +} +#endif + +#ifndef PA_STREAM_IS_GOOD +static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) +{ + return + x == PA_STREAM_CREATING || + x == PA_STREAM_READY; +} +#endif + +#define CHECK_SUCCESS_GOTO(c, rerror, expression, label) \ + do { \ + if (!(expression)) { \ + if (rerror) { \ + *(rerror) = pa_context_errno ((c)->context); \ + } \ + goto label; \ + } \ + } while (0); + +#define CHECK_DEAD_GOTO(c, stream, rerror, label) \ + do { \ + if (!(c)->context || !PA_CONTEXT_IS_GOOD (pa_context_get_state((c)->context)) || \ + !(stream) || !PA_STREAM_IS_GOOD (pa_stream_get_state ((stream)))) { \ + if (((c)->context && pa_context_get_state ((c)->context) == PA_CONTEXT_FAILED) || \ + ((stream) && pa_stream_get_state ((stream)) == PA_STREAM_FAILED)) { \ + if (rerror) { \ + *(rerror) = pa_context_errno ((c)->context); \ + } \ + } else { \ + if (rerror) { \ + *(rerror) = PA_ERR_BADSTATE; \ + } \ + } \ + goto label; \ + } \ + } while (0); + +static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror) +{ + paaudio *g = p->g; + + pa_threaded_mainloop_lock (g->mainloop); + + CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); + + while (length > 0) { + size_t l; + + while (!p->read_data) { + int r; + + r = pa_stream_peek (p->stream, &p->read_data, &p->read_length); + CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); + + if (!p->read_data) { + pa_threaded_mainloop_wait (g->mainloop); + CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); + } else { + p->read_index = 0; + } + } + + l = p->read_length < length ? p->read_length : length; + memcpy (data, (const uint8_t *) p->read_data+p->read_index, l); + + data = (uint8_t *) data + l; + length -= l; + + p->read_index += l; + p->read_length -= l; + + if (!p->read_length) { + int r; + + r = pa_stream_drop (p->stream); + p->read_data = NULL; + p->read_length = 0; + p->read_index = 0; + + CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); + } + } + + pa_threaded_mainloop_unlock (g->mainloop); + return 0; + +unlock_and_fail: + pa_threaded_mainloop_unlock (g->mainloop); + return -1; +} + +static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror) +{ + paaudio *g = p->g; + + pa_threaded_mainloop_lock (g->mainloop); + + CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); + + while (length > 0) { + size_t l; + int r; + + while (!(l = pa_stream_writable_size (p->stream))) { + pa_threaded_mainloop_wait (g->mainloop); + CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); + } + + CHECK_SUCCESS_GOTO (g, rerror, l != (size_t) -1, unlock_and_fail); + + if (l > length) { + l = length; + } + + r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); + CHECK_SUCCESS_GOTO (g, rerror, r >= 0, unlock_and_fail); + + data = (const uint8_t *) data + l; + length -= l; + } + + pa_threaded_mainloop_unlock (g->mainloop); + return 0; + +unlock_and_fail: + pa_threaded_mainloop_unlock (g->mainloop); + return -1; +} + +static void *qpa_thread_out (void *arg) +{ + PAVoiceOut *pa = arg; + HWVoiceOut *hw = &pa->hw; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + for (;;) { + int decr, to_mix, rpos; + + for (;;) { + if (pa->done) { + goto exit; + } + + if (pa->live > 0) { + break; + } + + if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { + goto exit; + } + } + + decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2); + rpos = pa->rpos; + + if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + while (to_mix) { + int error; + int chunk = audio_MIN (to_mix, hw->samples - rpos); + struct st_sample *src = hw->mix_buf + rpos; + + hw->clip (pa->pcm_buf, src, chunk); + + if (qpa_simple_write (pa, pa->pcm_buf, + chunk << hw->info.shift, &error) < 0) { + qpa_logerr (error, "pa_simple_write failed\n"); + return NULL; + } + + rpos = (rpos + chunk) % hw->samples; + to_mix -= chunk; + } + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + pa->rpos = rpos; + pa->live -= decr; + pa->decr += decr; + } + + exit: + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + return NULL; +} + +static int qpa_run_out (HWVoiceOut *hw, int live) +{ + int decr; + PAVoiceOut *pa = (PAVoiceOut *) hw; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return 0; + } + + decr = audio_MIN (live, pa->decr); + pa->decr -= decr; + pa->live = live - decr; + hw->rpos = pa->rpos; + if (pa->live > 0) { + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + } + else { + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + } + return decr; +} + +static int qpa_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +/* capture */ +static void *qpa_thread_in (void *arg) +{ + PAVoiceIn *pa = arg; + HWVoiceIn *hw = &pa->hw; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + for (;;) { + int incr, to_grab, wpos; + + for (;;) { + if (pa->done) { + goto exit; + } + + if (pa->dead > 0) { + break; + } + + if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { + goto exit; + } + } + + incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2); + wpos = pa->wpos; + + if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + while (to_grab) { + int error; + int chunk = audio_MIN (to_grab, hw->samples - wpos); + void *buf = advance (pa->pcm_buf, wpos); + + if (qpa_simple_read (pa, buf, + chunk << hw->info.shift, &error) < 0) { + qpa_logerr (error, "pa_simple_read failed\n"); + return NULL; + } + + hw->conv (hw->conv_buf + wpos, buf, chunk); + wpos = (wpos + chunk) % hw->samples; + to_grab -= chunk; + } + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + pa->wpos = wpos; + pa->dead -= incr; + pa->incr += incr; + } + + exit: + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + return NULL; +} + +static int qpa_run_in (HWVoiceIn *hw) +{ + int live, incr, dead; + PAVoiceIn *pa = (PAVoiceIn *) hw; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return 0; + } + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + incr = audio_MIN (dead, pa->incr); + pa->incr -= incr; + pa->dead = dead - incr; + hw->wpos = pa->wpos; + if (pa->dead > 0) { + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + } + else { + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + } + return incr; +} + +static int qpa_read (SWVoiceIn *sw, void *buf, int len) +{ + return audio_pcm_sw_read (sw, buf, len); +} + +static pa_sample_format_t audfmt_to_pa (audfmt_e afmt, int endianness) +{ + int format; + + switch (afmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + format = PA_SAMPLE_U8; + break; + case AUD_FMT_S16: + case AUD_FMT_U16: + format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE; + break; + case AUD_FMT_S32: + case AUD_FMT_U32: + format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE; + break; + default: + dolog ("Internal logic error: Bad audio format %d\n", afmt); + format = PA_SAMPLE_U8; + break; + } + return format; +} + +static audfmt_e pa_to_audfmt (pa_sample_format_t fmt, int *endianness) +{ + switch (fmt) { + case PA_SAMPLE_U8: + return AUD_FMT_U8; + case PA_SAMPLE_S16BE: + *endianness = 1; + return AUD_FMT_S16; + case PA_SAMPLE_S16LE: + *endianness = 0; + return AUD_FMT_S16; + case PA_SAMPLE_S32BE: + *endianness = 1; + return AUD_FMT_S32; + case PA_SAMPLE_S32LE: + *endianness = 0; + return AUD_FMT_S32; + default: + dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); + return AUD_FMT_U8; + } +} + +static void context_state_cb (pa_context *c, void *userdata) +{ + paaudio *g = userdata; + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal (g->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void stream_state_cb (pa_stream *s, void * userdata) +{ + paaudio *g = userdata; + + switch (pa_stream_get_state (s)) { + + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal (g->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void stream_request_cb (pa_stream *s, size_t length, void *userdata) +{ + paaudio *g = userdata; + + pa_threaded_mainloop_signal (g->mainloop, 0); +} + +static pa_stream *qpa_simple_new ( + paaudio *g, + const char *name, + pa_stream_direction_t dir, + const char *dev, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_buffer_attr *attr, + int *rerror) +{ + int r; + pa_stream *stream; + + pa_threaded_mainloop_lock (g->mainloop); + + stream = pa_stream_new (g->context, name, ss, map); + if (!stream) { + goto fail; + } + + pa_stream_set_state_callback (stream, stream_state_cb, g); + pa_stream_set_read_callback (stream, stream_request_cb, g); + pa_stream_set_write_callback (stream, stream_request_cb, g); + + if (dir == PA_STREAM_PLAYBACK) { + r = pa_stream_connect_playback (stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING +#ifdef PA_STREAM_ADJUST_LATENCY + |PA_STREAM_ADJUST_LATENCY +#endif + |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + } else { + r = pa_stream_connect_record (stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING +#ifdef PA_STREAM_ADJUST_LATENCY + |PA_STREAM_ADJUST_LATENCY +#endif + |PA_STREAM_AUTO_TIMING_UPDATE); + } + + if (r < 0) { + goto fail; + } + + pa_threaded_mainloop_unlock (g->mainloop); + + return stream; + +fail: + pa_threaded_mainloop_unlock (g->mainloop); + + if (stream) { + pa_stream_unref (stream); + } + + *rerror = pa_context_errno (g->context); + + return NULL; +} + +static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) +{ + int error; + pa_sample_spec ss; + pa_buffer_attr ba; + struct audsettings obt_as = *as; + PAVoiceOut *pa = (PAVoiceOut *) hw; + paaudio *g = pa->g = drv_opaque; + + ss.format = audfmt_to_pa (as->fmt, as->endianness); + ss.channels = as->nchannels; + ss.rate = as->freq; + + /* + * qemu audio tick runs at 100 Hz (by default), so processing + * data chunks worth 10 ms of sound should be a good fit. + */ + ba.tlength = pa_usec_to_bytes (10 * 1000, &ss); + ba.minreq = pa_usec_to_bytes (5 * 1000, &ss); + ba.maxlength = -1; + ba.prebuf = -1; + + obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + + pa->stream = qpa_simple_new ( + g, + "qemu", + PA_STREAM_PLAYBACK, + g->conf.sink, + &ss, + NULL, /* channel map */ + &ba, /* buffering attributes */ + &error + ); + if (!pa->stream) { + qpa_logerr (error, "pa_simple_new for playback failed\n"); + goto fail1; + } + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = g->conf.samples; + pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + pa->rpos = hw->rpos; + if (!pa->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + goto fail2; + } + + if (audio_pt_init (&pa->pt, qpa_thread_out, hw, AUDIO_CAP, AUDIO_FUNC)) { + goto fail3; + } + + return 0; + + fail3: + g_free (pa->pcm_buf); + pa->pcm_buf = NULL; + fail2: + if (pa->stream) { + pa_stream_unref (pa->stream); + pa->stream = NULL; + } + fail1: + return -1; +} + +static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +{ + int error; + pa_sample_spec ss; + struct audsettings obt_as = *as; + PAVoiceIn *pa = (PAVoiceIn *) hw; + paaudio *g = pa->g = drv_opaque; + + ss.format = audfmt_to_pa (as->fmt, as->endianness); + ss.channels = as->nchannels; + ss.rate = as->freq; + + obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + + pa->stream = qpa_simple_new ( + g, + "qemu", + PA_STREAM_RECORD, + g->conf.source, + &ss, + NULL, /* channel map */ + NULL, /* buffering attributes */ + &error + ); + if (!pa->stream) { + qpa_logerr (error, "pa_simple_new for capture failed\n"); + goto fail1; + } + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = g->conf.samples; + pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + pa->wpos = hw->wpos; + if (!pa->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + goto fail2; + } + + if (audio_pt_init (&pa->pt, qpa_thread_in, hw, AUDIO_CAP, AUDIO_FUNC)) { + goto fail3; + } + + return 0; + + fail3: + g_free (pa->pcm_buf); + pa->pcm_buf = NULL; + fail2: + if (pa->stream) { + pa_stream_unref (pa->stream); + pa->stream = NULL; + } + fail1: + return -1; +} + +static void qpa_fini_out (HWVoiceOut *hw) +{ + void *ret; + PAVoiceOut *pa = (PAVoiceOut *) hw; + + audio_pt_lock (&pa->pt, AUDIO_FUNC); + pa->done = 1; + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); + + if (pa->stream) { + pa_stream_unref (pa->stream); + pa->stream = NULL; + } + + audio_pt_fini (&pa->pt, AUDIO_FUNC); + g_free (pa->pcm_buf); + pa->pcm_buf = NULL; +} + +static void qpa_fini_in (HWVoiceIn *hw) +{ + void *ret; + PAVoiceIn *pa = (PAVoiceIn *) hw; + + audio_pt_lock (&pa->pt, AUDIO_FUNC); + pa->done = 1; + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); + + if (pa->stream) { + pa_stream_unref (pa->stream); + pa->stream = NULL; + } + + audio_pt_fini (&pa->pt, AUDIO_FUNC); + g_free (pa->pcm_buf); + pa->pcm_buf = NULL; +} + +static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + PAVoiceOut *pa = (PAVoiceOut *) hw; + pa_operation *op; + pa_cvolume v; + paaudio *g = pa->g; + +#ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */ + pa_cvolume_init (&v); /* function is present in 0.9.13+ */ +#endif + + switch (cmd) { + case VOICE_VOLUME: + { + SWVoiceOut *sw; + va_list ap; + + va_start (ap, cmd); + sw = va_arg (ap, SWVoiceOut *); + va_end (ap); + + v.channels = 2; + v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.l) / UINT32_MAX; + v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.r) / UINT32_MAX; + + pa_threaded_mainloop_lock (g->mainloop); + + op = pa_context_set_sink_input_volume (g->context, + pa_stream_get_index (pa->stream), + &v, NULL, NULL); + if (!op) + qpa_logerr (pa_context_errno (g->context), + "set_sink_input_volume() failed\n"); + else + pa_operation_unref (op); + + op = pa_context_set_sink_input_mute (g->context, + pa_stream_get_index (pa->stream), + sw->vol.mute, NULL, NULL); + if (!op) { + qpa_logerr (pa_context_errno (g->context), + "set_sink_input_mute() failed\n"); + } else { + pa_operation_unref (op); + } + + pa_threaded_mainloop_unlock (g->mainloop); + } + } + return 0; +} + +static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + PAVoiceIn *pa = (PAVoiceIn *) hw; + pa_operation *op; + pa_cvolume v; + paaudio *g = pa->g; + +#ifdef PA_CHECK_VERSION + pa_cvolume_init (&v); +#endif + + switch (cmd) { + case VOICE_VOLUME: + { + SWVoiceIn *sw; + va_list ap; + + va_start (ap, cmd); + sw = va_arg (ap, SWVoiceIn *); + va_end (ap); + + v.channels = 2; + v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.l) / UINT32_MAX; + v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.r) / UINT32_MAX; + + pa_threaded_mainloop_lock (g->mainloop); + + /* FIXME: use the upcoming "set_source_output_{volume,mute}" */ + op = pa_context_set_source_volume_by_index (g->context, + pa_stream_get_device_index (pa->stream), + &v, NULL, NULL); + if (!op) { + qpa_logerr (pa_context_errno (g->context), + "set_source_volume() failed\n"); + } else { + pa_operation_unref(op); + } + + op = pa_context_set_source_mute_by_index (g->context, + pa_stream_get_index (pa->stream), + sw->vol.mute, NULL, NULL); + if (!op) { + qpa_logerr (pa_context_errno (g->context), + "set_source_mute() failed\n"); + } else { + pa_operation_unref (op); + } + + pa_threaded_mainloop_unlock (g->mainloop); + } + } + return 0; +} + +/* common */ +static PAConf glob_conf = { + .samples = 4096, +}; + +static void *qpa_audio_init (void) +{ + paaudio *g = g_malloc(sizeof(paaudio)); + g->conf = glob_conf; + g->mainloop = NULL; + g->context = NULL; + + g->mainloop = pa_threaded_mainloop_new (); + if (!g->mainloop) { + goto fail; + } + + g->context = pa_context_new (pa_threaded_mainloop_get_api (g->mainloop), + g->conf.server); + if (!g->context) { + goto fail; + } + + pa_context_set_state_callback (g->context, context_state_cb, g); + + if (pa_context_connect (g->context, g->conf.server, 0, NULL) < 0) { + qpa_logerr (pa_context_errno (g->context), + "pa_context_connect() failed\n"); + goto fail; + } + + pa_threaded_mainloop_lock (g->mainloop); + + if (pa_threaded_mainloop_start (g->mainloop) < 0) { + goto unlock_and_fail; + } + + for (;;) { + pa_context_state_t state; + + state = pa_context_get_state (g->context); + + if (state == PA_CONTEXT_READY) { + break; + } + + if (!PA_CONTEXT_IS_GOOD (state)) { + qpa_logerr (pa_context_errno (g->context), + "Wrong context state\n"); + goto unlock_and_fail; + } + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait (g->mainloop); + } + + pa_threaded_mainloop_unlock (g->mainloop); + + return g; + +unlock_and_fail: + pa_threaded_mainloop_unlock (g->mainloop); +fail: + AUD_log (AUDIO_CAP, "Failed to initialize PA context"); + qpa_audio_fini(g); + return NULL; +} + +static void qpa_audio_fini (void *opaque) +{ + paaudio *g = opaque; + + if (g->mainloop) { + pa_threaded_mainloop_stop (g->mainloop); + } + + if (g->context) { + pa_context_disconnect (g->context); + pa_context_unref (g->context); + } + + if (g->mainloop) { + pa_threaded_mainloop_free (g->mainloop); + } + + g_free(g); +} + +struct audio_option qpa_options[] = { + { + .name = "SAMPLES", + .tag = AUD_OPT_INT, + .valp = &glob_conf.samples, + .descr = "buffer size in samples" + }, + { + .name = "SERVER", + .tag = AUD_OPT_STR, + .valp = &glob_conf.server, + .descr = "server address" + }, + { + .name = "SINK", + .tag = AUD_OPT_STR, + .valp = &glob_conf.sink, + .descr = "sink device name" + }, + { + .name = "SOURCE", + .tag = AUD_OPT_STR, + .valp = &glob_conf.source, + .descr = "source device name" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops qpa_pcm_ops = { + .init_out = qpa_init_out, + .fini_out = qpa_fini_out, + .run_out = qpa_run_out, + .write = qpa_write, + .ctl_out = qpa_ctl_out, + + .init_in = qpa_init_in, + .fini_in = qpa_fini_in, + .run_in = qpa_run_in, + .read = qpa_read, + .ctl_in = qpa_ctl_in +}; + +struct audio_driver pa_audio_driver = { + .name = "pa", + .descr = "http://www.pulseaudio.org/", + .options = qpa_options, + .init = qpa_audio_init, + .fini = qpa_audio_fini, + .pcm_ops = &qpa_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (PAVoiceOut), + .voice_size_in = sizeof (PAVoiceIn), + .ctl_caps = VOICE_VOLUME_CAP +}; diff --git a/src/audio/rate_template.h b/src/audio/rate_template.h new file mode 100644 index 0000000..bd4b1c7 --- /dev/null +++ b/src/audio/rate_template.h @@ -0,0 +1,111 @@ +/* + * QEMU Mixing engine + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * Copyright (c) 1998 Fabrice Bellard + * + * 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. + */ + +/* + * Processed signed long samples from ibuf to obuf. + * Return number of samples processed. + */ +void NAME (void *opaque, struct st_sample *ibuf, struct st_sample *obuf, + int *isamp, int *osamp) +{ + struct rate *rate = opaque; + struct st_sample *istart, *iend; + struct st_sample *ostart, *oend; + struct st_sample ilast, icur, out; +#ifdef FLOAT_MIXENG + mixeng_real t; +#else + int64_t t; +#endif + + ilast = rate->ilast; + + istart = ibuf; + iend = ibuf + *isamp; + + ostart = obuf; + oend = obuf + *osamp; + + if (rate->opos_inc == (1ULL + UINT_MAX)) { + int i, n = *isamp > *osamp ? *osamp : *isamp; + for (i = 0; i < n; i++) { + OP (obuf[i].l, ibuf[i].l); + OP (obuf[i].r, ibuf[i].r); + } + *isamp = n; + *osamp = n; + return; + } + + while (obuf < oend) { + + /* Safety catch to make sure we have input samples. */ + if (ibuf >= iend) { + break; + } + + /* read as many input samples so that ipos > opos */ + + while (rate->ipos <= (rate->opos >> 32)) { + ilast = *ibuf++; + rate->ipos++; + /* See if we finished the input buffer yet */ + if (ibuf >= iend) { + goto the_end; + } + } + + icur = *ibuf; + + /* interpolate */ +#ifdef FLOAT_MIXENG +#ifdef RECIPROCAL + t = (rate->opos & UINT_MAX) * (1.f / UINT_MAX); +#else + t = (rate->opos & UINT_MAX) / (mixeng_real) UINT_MAX; +#endif + out.l = (ilast.l * (1.0 - t)) + icur.l * t; + out.r = (ilast.r * (1.0 - t)) + icur.r * t; +#else + t = rate->opos & 0xffffffff; + out.l = (ilast.l * ((int64_t) UINT_MAX - t) + icur.l * t) >> 32; + out.r = (ilast.r * ((int64_t) UINT_MAX - t) + icur.r * t) >> 32; +#endif + + /* output sample & increment position */ + OP (obuf->l, out.l); + OP (obuf->r, out.r); + obuf += 1; + rate->opos += rate->opos_inc; + } + +the_end: + *isamp = ibuf - istart; + *osamp = obuf - ostart; + rate->ilast = ilast; +} + +#undef NAME +#undef OP diff --git a/src/audio/sdlaudio.c b/src/audio/sdlaudio.c new file mode 100644 index 0000000..1140f2e --- /dev/null +++ b/src/audio/sdlaudio.c @@ -0,0 +1,466 @@ +/* + * QEMU SDL audio driver + * + * Copyright (c) 2004-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 <SDL.h> +#include <SDL_thread.h> +#include "qemu-common.h" +#include "audio.h" + +#ifndef _WIN32 +#ifdef __sun__ +#define _POSIX_PTHREAD_SEMANTICS 1 +#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) +#include <pthread.h> +#endif +#endif + +#define AUDIO_CAP "sdl" +#include "audio_int.h" + +typedef struct SDLVoiceOut { + HWVoiceOut hw; + int live; + int rpos; + int decr; +} SDLVoiceOut; + +static struct { + int nb_samples; +} conf = { + .nb_samples = 1024 +}; + +static struct SDLAudioState { + int exit; + SDL_mutex *mutex; + SDL_sem *sem; + int initialized; + bool driver_created; +} glob_sdl; +typedef struct SDLAudioState SDLAudioState; + +static void GCC_FMT_ATTR (1, 2) sdl_logerr (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", SDL_GetError ()); +} + +static int sdl_lock (SDLAudioState *s, const char *forfn) +{ + if (SDL_LockMutex (s->mutex)) { + sdl_logerr ("SDL_LockMutex for %s failed\n", forfn); + return -1; + } + return 0; +} + +static int sdl_unlock (SDLAudioState *s, const char *forfn) +{ + if (SDL_UnlockMutex (s->mutex)) { + sdl_logerr ("SDL_UnlockMutex for %s failed\n", forfn); + return -1; + } + return 0; +} + +static int sdl_post (SDLAudioState *s, const char *forfn) +{ + if (SDL_SemPost (s->sem)) { + sdl_logerr ("SDL_SemPost for %s failed\n", forfn); + return -1; + } + return 0; +} + +static int sdl_wait (SDLAudioState *s, const char *forfn) +{ + if (SDL_SemWait (s->sem)) { + sdl_logerr ("SDL_SemWait for %s failed\n", forfn); + return -1; + } + return 0; +} + +static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn) +{ + if (sdl_unlock (s, forfn)) { + return -1; + } + + return sdl_post (s, forfn); +} + +static int aud_to_sdlfmt (audfmt_e fmt) +{ + switch (fmt) { + case AUD_FMT_S8: + return AUDIO_S8; + + case AUD_FMT_U8: + return AUDIO_U8; + + case AUD_FMT_S16: + return AUDIO_S16LSB; + + case AUD_FMT_U16: + return AUDIO_U16LSB; + + default: + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO + abort (); +#endif + return AUDIO_U8; + } +} + +static int sdl_to_audfmt(int sdlfmt, audfmt_e *fmt, int *endianness) +{ + switch (sdlfmt) { + case AUDIO_S8: + *endianness = 0; + *fmt = AUD_FMT_S8; + break; + + case AUDIO_U8: + *endianness = 0; + *fmt = AUD_FMT_U8; + break; + + case AUDIO_S16LSB: + *endianness = 0; + *fmt = AUD_FMT_S16; + break; + + case AUDIO_U16LSB: + *endianness = 0; + *fmt = AUD_FMT_U16; + break; + + case AUDIO_S16MSB: + *endianness = 1; + *fmt = AUD_FMT_S16; + break; + + case AUDIO_U16MSB: + *endianness = 1; + *fmt = AUD_FMT_U16; + break; + + default: + dolog ("Unrecognized SDL audio format %d\n", sdlfmt); + return -1; + } + + return 0; +} + +static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) +{ + int status; +#ifndef _WIN32 + int err; + sigset_t new, old; + + /* Make sure potential threads created by SDL don't hog signals. */ + err = sigfillset (&new); + if (err) { + dolog ("sdl_open: sigfillset failed: %s\n", strerror (errno)); + return -1; + } + err = pthread_sigmask (SIG_BLOCK, &new, &old); + if (err) { + dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err)); + return -1; + } +#endif + + status = SDL_OpenAudio (req, obt); + if (status) { + sdl_logerr ("SDL_OpenAudio failed\n"); + } + +#ifndef _WIN32 + err = pthread_sigmask (SIG_SETMASK, &old, NULL); + if (err) { + dolog ("sdl_open: pthread_sigmask (restore) failed: %s\n", + strerror (errno)); + /* We have failed to restore original signal mask, all bets are off, + so exit the process */ + exit (EXIT_FAILURE); + } +#endif + return status; +} + +static void sdl_close (SDLAudioState *s) +{ + if (s->initialized) { + sdl_lock (s, "sdl_close"); + s->exit = 1; + sdl_unlock_and_post (s, "sdl_close"); + SDL_PauseAudio (1); + SDL_CloseAudio (); + s->initialized = 0; + } +} + +static void sdl_callback (void *opaque, Uint8 *buf, int len) +{ + SDLVoiceOut *sdl = opaque; + SDLAudioState *s = &glob_sdl; + HWVoiceOut *hw = &sdl->hw; + int samples = len >> hw->info.shift; + + if (s->exit) { + return; + } + + while (samples) { + int to_mix, decr; + + /* dolog ("in callback samples=%d\n", samples); */ + sdl_wait (s, "sdl_callback"); + if (s->exit) { + return; + } + + if (sdl_lock (s, "sdl_callback")) { + return; + } + + if (audio_bug (AUDIO_FUNC, sdl->live < 0 || sdl->live > hw->samples)) { + dolog ("sdl->live=%d hw->samples=%d\n", + sdl->live, hw->samples); + return; + } + + if (!sdl->live) { + goto again; + } + + /* dolog ("in callback live=%d\n", live); */ + to_mix = audio_MIN (samples, sdl->live); + decr = to_mix; + while (to_mix) { + int chunk = audio_MIN (to_mix, hw->samples - hw->rpos); + struct st_sample *src = hw->mix_buf + hw->rpos; + + /* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */ + hw->clip (buf, src, chunk); + sdl->rpos = (sdl->rpos + chunk) % hw->samples; + to_mix -= chunk; + buf += chunk << hw->info.shift; + } + samples -= decr; + sdl->live -= decr; + sdl->decr += decr; + + again: + if (sdl_unlock (s, "sdl_callback")) { + return; + } + } + /* dolog ("done len=%d\n", len); */ +} + +static int sdl_write_out (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int sdl_run_out (HWVoiceOut *hw, int live) +{ + int decr; + SDLVoiceOut *sdl = (SDLVoiceOut *) hw; + SDLAudioState *s = &glob_sdl; + + if (sdl_lock (s, "sdl_run_out")) { + return 0; + } + + if (sdl->decr > live) { + ldebug ("sdl->decr %d live %d sdl->live %d\n", + sdl->decr, + live, + sdl->live); + } + + decr = audio_MIN (sdl->decr, live); + sdl->decr -= decr; + + sdl->live = live - decr; + hw->rpos = sdl->rpos; + + if (sdl->live > 0) { + sdl_unlock_and_post (s, "sdl_run_out"); + } + else { + sdl_unlock (s, "sdl_run_out"); + } + return decr; +} + +static void sdl_fini_out (HWVoiceOut *hw) +{ + (void) hw; + + sdl_close (&glob_sdl); +} + +static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) +{ + SDLVoiceOut *sdl = (SDLVoiceOut *) hw; + SDLAudioState *s = &glob_sdl; + SDL_AudioSpec req, obt; + int endianness; + int err; + audfmt_e effective_fmt; + struct audsettings obt_as; + + req.freq = as->freq; + req.format = aud_to_sdlfmt (as->fmt); + req.channels = as->nchannels; + req.samples = conf.nb_samples; + req.callback = sdl_callback; + req.userdata = sdl; + + if (sdl_open (&req, &obt)) { + return -1; + } + + err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness); + if (err) { + sdl_close (s); + return -1; + } + + obt_as.freq = obt.freq; + obt_as.nchannels = obt.channels; + obt_as.fmt = effective_fmt; + obt_as.endianness = endianness; + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = obt.samples; + + s->initialized = 1; + s->exit = 0; + SDL_PauseAudio (0); + return 0; +} + +static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + + switch (cmd) { + case VOICE_ENABLE: + SDL_PauseAudio (0); + break; + + case VOICE_DISABLE: + SDL_PauseAudio (1); + break; + } + return 0; +} + +static void *sdl_audio_init (void) +{ + SDLAudioState *s = &glob_sdl; + if (s->driver_created) { + sdl_logerr("Can't create multiple sdl backends\n"); + return NULL; + } + + if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { + sdl_logerr ("SDL failed to initialize audio subsystem\n"); + return NULL; + } + + s->mutex = SDL_CreateMutex (); + if (!s->mutex) { + sdl_logerr ("Failed to create SDL mutex\n"); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return NULL; + } + + s->sem = SDL_CreateSemaphore (0); + if (!s->sem) { + sdl_logerr ("Failed to create SDL semaphore\n"); + SDL_DestroyMutex (s->mutex); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return NULL; + } + + s->driver_created = true; + return s; +} + +static void sdl_audio_fini (void *opaque) +{ + SDLAudioState *s = opaque; + sdl_close (s); + SDL_DestroySemaphore (s->sem); + SDL_DestroyMutex (s->mutex); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + s->driver_created = false; +} + +static struct audio_option sdl_options[] = { + { + .name = "SAMPLES", + .tag = AUD_OPT_INT, + .valp = &conf.nb_samples, + .descr = "Size of SDL buffer in samples" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops sdl_pcm_ops = { + .init_out = sdl_init_out, + .fini_out = sdl_fini_out, + .run_out = sdl_run_out, + .write = sdl_write_out, + .ctl_out = sdl_ctl_out, +}; + +struct audio_driver sdl_audio_driver = { + .name = "sdl", + .descr = "SDL http://www.libsdl.org", + .options = sdl_options, + .init = sdl_audio_init, + .fini = sdl_audio_fini, + .pcm_ops = &sdl_pcm_ops, + .can_be_default = 1, + .max_voices_out = 1, + .max_voices_in = 0, + .voice_size_out = sizeof (SDLVoiceOut), + .voice_size_in = 0 +}; diff --git a/src/audio/spiceaudio.c b/src/audio/spiceaudio.c new file mode 100644 index 0000000..42ae4a4 --- /dev/null +++ b/src/audio/spiceaudio.c @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * maintained by Gerd Hoffmann <kraxel@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/hw.h" +#include "qemu/error-report.h" +#include "qemu/timer.h" +#include "ui/qemu-spice.h" + +#define AUDIO_CAP "spice" +#include "audio.h" +#include "audio_int.h" + +#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 +#define LINE_OUT_SAMPLES (480 * 4) +#else +#define LINE_OUT_SAMPLES (256 * 4) +#endif + +#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 +#define LINE_IN_SAMPLES (480 * 4) +#else +#define LINE_IN_SAMPLES (256 * 4) +#endif + +typedef struct SpiceRateCtl { + int64_t start_ticks; + int64_t bytes_sent; +} SpiceRateCtl; + +typedef struct SpiceVoiceOut { + HWVoiceOut hw; + SpicePlaybackInstance sin; + SpiceRateCtl rate; + int active; + uint32_t *frame; + uint32_t *fpos; + uint32_t fsize; +} SpiceVoiceOut; + +typedef struct SpiceVoiceIn { + HWVoiceIn hw; + SpiceRecordInstance sin; + SpiceRateCtl rate; + int active; + uint32_t samples[LINE_IN_SAMPLES]; +} SpiceVoiceIn; + +static const SpicePlaybackInterface playback_sif = { + .base.type = SPICE_INTERFACE_PLAYBACK, + .base.description = "playback", + .base.major_version = SPICE_INTERFACE_PLAYBACK_MAJOR, + .base.minor_version = SPICE_INTERFACE_PLAYBACK_MINOR, +}; + +static const SpiceRecordInterface record_sif = { + .base.type = SPICE_INTERFACE_RECORD, + .base.description = "record", + .base.major_version = SPICE_INTERFACE_RECORD_MAJOR, + .base.minor_version = SPICE_INTERFACE_RECORD_MINOR, +}; + +static void *spice_audio_init (void) +{ + if (!using_spice) { + return NULL; + } + return &spice_audio_init; +} + +static void spice_audio_fini (void *opaque) +{ + /* nothing */ +} + +static void rate_start (SpiceRateCtl *rate) +{ + memset (rate, 0, sizeof (*rate)); + rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate) +{ + int64_t now; + int64_t ticks; + int64_t bytes; + int64_t samples; + + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ticks = now - rate->start_ticks; + bytes = muldiv64 (ticks, info->bytes_per_second, get_ticks_per_sec ()); + samples = (bytes - rate->bytes_sent) >> info->shift; + if (samples < 0 || samples > 65536) { + error_report("Resetting rate control (%" PRId64 " samples)", samples); + rate_start (rate); + samples = 0; + } + rate->bytes_sent += samples << info->shift; + return samples; +} + +/* playback */ + +static int line_out_init(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) +{ + SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + struct audsettings settings; + +#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 + settings.freq = spice_server_get_best_playback_rate(NULL); +#else + settings.freq = SPICE_INTERFACE_PLAYBACK_FREQ; +#endif + settings.nchannels = SPICE_INTERFACE_PLAYBACK_CHAN; + settings.fmt = AUD_FMT_S16; + settings.endianness = AUDIO_HOST_ENDIANNESS; + + audio_pcm_init_info (&hw->info, &settings); + hw->samples = LINE_OUT_SAMPLES; + out->active = 0; + + out->sin.base.sif = &playback_sif.base; + qemu_spice_add_interface (&out->sin.base); +#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 + spice_server_set_playback_rate(&out->sin, settings.freq); +#endif + return 0; +} + +static void line_out_fini (HWVoiceOut *hw) +{ + SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + + spice_server_remove_interface (&out->sin.base); +} + +static int line_out_run (HWVoiceOut *hw, int live) +{ + SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + int rpos, decr; + int samples; + + if (!live) { + return 0; + } + + decr = rate_get_samples (&hw->info, &out->rate); + decr = audio_MIN (live, decr); + + samples = decr; + rpos = hw->rpos; + while (samples) { + int left_till_end_samples = hw->samples - rpos; + int len = audio_MIN (samples, left_till_end_samples); + + if (!out->frame) { + spice_server_playback_get_buffer (&out->sin, &out->frame, &out->fsize); + out->fpos = out->frame; + } + if (out->frame) { + len = audio_MIN (len, out->fsize); + hw->clip (out->fpos, hw->mix_buf + rpos, len); + out->fsize -= len; + out->fpos += len; + if (out->fsize == 0) { + spice_server_playback_put_samples (&out->sin, out->frame); + out->frame = out->fpos = NULL; + } + } + rpos = (rpos + len) % hw->samples; + samples -= len; + } + hw->rpos = rpos; + return decr; +} + +static int line_out_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int line_out_ctl (HWVoiceOut *hw, int cmd, ...) +{ + SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + + switch (cmd) { + case VOICE_ENABLE: + if (out->active) { + break; + } + out->active = 1; + rate_start (&out->rate); + spice_server_playback_start (&out->sin); + break; + case VOICE_DISABLE: + if (!out->active) { + break; + } + out->active = 0; + if (out->frame) { + memset (out->fpos, 0, out->fsize << 2); + spice_server_playback_put_samples (&out->sin, out->frame); + out->frame = out->fpos = NULL; + } + spice_server_playback_stop (&out->sin); + break; + case VOICE_VOLUME: + { +#if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2)) + SWVoiceOut *sw; + va_list ap; + uint16_t vol[2]; + + va_start (ap, cmd); + sw = va_arg (ap, SWVoiceOut *); + va_end (ap); + + vol[0] = sw->vol.l / ((1ULL << 16) + 1); + vol[1] = sw->vol.r / ((1ULL << 16) + 1); + spice_server_playback_set_volume (&out->sin, 2, vol); + spice_server_playback_set_mute (&out->sin, sw->vol.mute); +#endif + break; + } + } + + return 0; +} + +/* record */ + +static int line_in_init(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +{ + SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + struct audsettings settings; + +#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 + settings.freq = spice_server_get_best_record_rate(NULL); +#else + settings.freq = SPICE_INTERFACE_RECORD_FREQ; +#endif + settings.nchannels = SPICE_INTERFACE_RECORD_CHAN; + settings.fmt = AUD_FMT_S16; + settings.endianness = AUDIO_HOST_ENDIANNESS; + + audio_pcm_init_info (&hw->info, &settings); + hw->samples = LINE_IN_SAMPLES; + in->active = 0; + + in->sin.base.sif = &record_sif.base; + qemu_spice_add_interface (&in->sin.base); +#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 + spice_server_set_record_rate(&in->sin, settings.freq); +#endif + return 0; +} + +static void line_in_fini (HWVoiceIn *hw) +{ + SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + + spice_server_remove_interface (&in->sin.base); +} + +static int line_in_run (HWVoiceIn *hw) +{ + SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + int num_samples; + int ready; + int len[2]; + uint64_t delta_samp; + const uint32_t *samples; + + if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in (hw))) { + return 0; + } + + delta_samp = rate_get_samples (&hw->info, &in->rate); + num_samples = audio_MIN (num_samples, delta_samp); + + ready = spice_server_record_get_samples (&in->sin, in->samples, num_samples); + samples = in->samples; + if (ready == 0) { + static const uint32_t silence[LINE_IN_SAMPLES]; + samples = silence; + ready = LINE_IN_SAMPLES; + } + + num_samples = audio_MIN (ready, num_samples); + + if (hw->wpos + num_samples > hw->samples) { + len[0] = hw->samples - hw->wpos; + len[1] = num_samples - len[0]; + } else { + len[0] = num_samples; + len[1] = 0; + } + + hw->conv (hw->conv_buf + hw->wpos, samples, len[0]); + + if (len[1]) { + hw->conv (hw->conv_buf, samples + len[0], len[1]); + } + + hw->wpos = (hw->wpos + num_samples) % hw->samples; + + return num_samples; +} + +static int line_in_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int line_in_ctl (HWVoiceIn *hw, int cmd, ...) +{ + SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + + switch (cmd) { + case VOICE_ENABLE: + if (in->active) { + break; + } + in->active = 1; + rate_start (&in->rate); + spice_server_record_start (&in->sin); + break; + case VOICE_DISABLE: + if (!in->active) { + break; + } + in->active = 0; + spice_server_record_stop (&in->sin); + break; + case VOICE_VOLUME: + { +#if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2)) + SWVoiceIn *sw; + va_list ap; + uint16_t vol[2]; + + va_start (ap, cmd); + sw = va_arg (ap, SWVoiceIn *); + va_end (ap); + + vol[0] = sw->vol.l / ((1ULL << 16) + 1); + vol[1] = sw->vol.r / ((1ULL << 16) + 1); + spice_server_record_set_volume (&in->sin, 2, vol); + spice_server_record_set_mute (&in->sin, sw->vol.mute); +#endif + break; + } + } + + return 0; +} + +static struct audio_option audio_options[] = { + { /* end of list */ }, +}; + +static struct audio_pcm_ops audio_callbacks = { + .init_out = line_out_init, + .fini_out = line_out_fini, + .run_out = line_out_run, + .write = line_out_write, + .ctl_out = line_out_ctl, + + .init_in = line_in_init, + .fini_in = line_in_fini, + .run_in = line_in_run, + .read = line_in_read, + .ctl_in = line_in_ctl, +}; + +struct audio_driver spice_audio_driver = { + .name = "spice", + .descr = "spice audio driver", + .options = audio_options, + .init = spice_audio_init, + .fini = spice_audio_fini, + .pcm_ops = &audio_callbacks, + .max_voices_out = 1, + .max_voices_in = 1, + .voice_size_out = sizeof (SpiceVoiceOut), + .voice_size_in = sizeof (SpiceVoiceIn), +#if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2)) + .ctl_caps = VOICE_VOLUME_CAP +#endif +}; + +void qemu_spice_audio_init (void) +{ + spice_audio_driver.can_be_default = 1; +} diff --git a/src/audio/wavaudio.c b/src/audio/wavaudio.c new file mode 100644 index 0000000..c586020 --- /dev/null +++ b/src/audio/wavaudio.c @@ -0,0 +1,292 @@ +/* + * QEMU WAV audio driver + * + * Copyright (c) 2004-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 "hw/hw.h" +#include "qemu/timer.h" +#include "audio.h" + +#define AUDIO_CAP "wav" +#include "audio_int.h" + +typedef struct WAVVoiceOut { + HWVoiceOut hw; + FILE *f; + int64_t old_ticks; + void *pcm_buf; + int total_samples; +} WAVVoiceOut; + +typedef struct { + struct audsettings settings; + const char *wav_path; +} WAVConf; + +static int wav_run_out (HWVoiceOut *hw, int live) +{ + WAVVoiceOut *wav = (WAVVoiceOut *) hw; + int rpos, decr, samples; + uint8_t *dst; + struct st_sample *src; + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + int64_t ticks = now - wav->old_ticks; + int64_t bytes = + muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ()); + + if (bytes > INT_MAX) { + samples = INT_MAX >> hw->info.shift; + } + else { + samples = bytes >> hw->info.shift; + } + + wav->old_ticks = now; + decr = audio_MIN (live, samples); + 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 (wav->pcm_buf, rpos << hw->info.shift); + + hw->clip (dst, src, convert_samples); + if (fwrite (dst, convert_samples << hw->info.shift, 1, wav->f) != 1) { + dolog ("wav_run_out: fwrite of %d bytes failed\nReaons: %s\n", + convert_samples << hw->info.shift, strerror (errno)); + } + + rpos = (rpos + convert_samples) % hw->samples; + samples -= convert_samples; + wav->total_samples += convert_samples; + } + + hw->rpos = rpos; + return decr; +} + +static int wav_write_out (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +/* VICE code: Store number as little endian. */ +static void le_store (uint8_t *buf, uint32_t val, int len) +{ + int i; + for (i = 0; i < len; i++) { + buf[i] = (uint8_t) (val & 0xff); + val >>= 8; + } +} + +static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) +{ + WAVVoiceOut *wav = (WAVVoiceOut *) hw; + int bits16 = 0, stereo = 0; + uint8_t hdr[] = { + 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, + 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, + 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 + }; + WAVConf *conf = drv_opaque; + struct audsettings wav_as = conf->settings; + + stereo = wav_as.nchannels == 2; + switch (wav_as.fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + bits16 = 0; + break; + + case AUD_FMT_S16: + case AUD_FMT_U16: + bits16 = 1; + break; + + case AUD_FMT_S32: + case AUD_FMT_U32: + dolog ("WAVE files can not handle 32bit formats\n"); + return -1; + } + + hdr[34] = bits16 ? 0x10 : 0x08; + + wav_as.endianness = 0; + audio_pcm_init_info (&hw->info, &wav_as); + + hw->samples = 1024; + wav->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!wav->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + return -1; + } + + le_store (hdr + 22, hw->info.nchannels, 2); + le_store (hdr + 24, hw->info.freq, 4); + le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4); + le_store (hdr + 32, 1 << (bits16 + stereo), 2); + + wav->f = fopen (conf->wav_path, "wb"); + if (!wav->f) { + dolog ("Failed to open wave file `%s'\nReason: %s\n", + conf->wav_path, strerror (errno)); + g_free (wav->pcm_buf); + wav->pcm_buf = NULL; + return -1; + } + + if (fwrite (hdr, sizeof (hdr), 1, wav->f) != 1) { + dolog ("wav_init_out: failed to write header\nReason: %s\n", + strerror(errno)); + return -1; + } + return 0; +} + +static void wav_fini_out (HWVoiceOut *hw) +{ + WAVVoiceOut *wav = (WAVVoiceOut *) hw; + uint8_t rlen[4]; + uint8_t dlen[4]; + uint32_t datalen = wav->total_samples << hw->info.shift; + uint32_t rifflen = datalen + 36; + + if (!wav->f) { + return; + } + + le_store (rlen, rifflen, 4); + le_store (dlen, datalen, 4); + + if (fseek (wav->f, 4, SEEK_SET)) { + dolog ("wav_fini_out: fseek to rlen failed\nReason: %s\n", + strerror(errno)); + goto doclose; + } + if (fwrite (rlen, 4, 1, wav->f) != 1) { + dolog ("wav_fini_out: failed to write rlen\nReason: %s\n", + strerror (errno)); + goto doclose; + } + if (fseek (wav->f, 32, SEEK_CUR)) { + dolog ("wav_fini_out: fseek to dlen failed\nReason: %s\n", + strerror (errno)); + goto doclose; + } + if (fwrite (dlen, 4, 1, wav->f) != 1) { + dolog ("wav_fini_out: failed to write dlen\nReaons: %s\n", + strerror (errno)); + goto doclose; + } + + doclose: + if (fclose (wav->f)) { + dolog ("wav_fini_out: fclose %p failed\nReason: %s\n", + wav->f, strerror (errno)); + } + wav->f = NULL; + + g_free (wav->pcm_buf); + wav->pcm_buf = NULL; +} + +static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static WAVConf glob_conf = { + .settings.freq = 44100, + .settings.nchannels = 2, + .settings.fmt = AUD_FMT_S16, + .wav_path = "qemu.wav" +}; + +static void *wav_audio_init (void) +{ + WAVConf *conf = g_malloc(sizeof(WAVConf)); + *conf = glob_conf; + return conf; +} + +static void wav_audio_fini (void *opaque) +{ + ldebug ("wav_fini"); + g_free(opaque); +} + +static struct audio_option wav_options[] = { + { + .name = "FREQUENCY", + .tag = AUD_OPT_INT, + .valp = &glob_conf.settings.freq, + .descr = "Frequency" + }, + { + .name = "FORMAT", + .tag = AUD_OPT_FMT, + .valp = &glob_conf.settings.fmt, + .descr = "Format" + }, + { + .name = "DAC_FIXED_CHANNELS", + .tag = AUD_OPT_INT, + .valp = &glob_conf.settings.nchannels, + .descr = "Number of channels (1 - mono, 2 - stereo)" + }, + { + .name = "PATH", + .tag = AUD_OPT_STR, + .valp = &glob_conf.wav_path, + .descr = "Path to wave file" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops wav_pcm_ops = { + .init_out = wav_init_out, + .fini_out = wav_fini_out, + .run_out = wav_run_out, + .write = wav_write_out, + .ctl_out = wav_ctl_out, +}; + +struct audio_driver wav_audio_driver = { + .name = "wav", + .descr = "WAV renderer http://wikipedia.org/wiki/WAV", + .options = wav_options, + .init = wav_audio_init, + .fini = wav_audio_fini, + .pcm_ops = &wav_pcm_ops, + .can_be_default = 0, + .max_voices_out = 1, + .max_voices_in = 0, + .voice_size_out = sizeof (WAVVoiceOut), + .voice_size_in = 0 +}; diff --git a/src/audio/wavcapture.c b/src/audio/wavcapture.c new file mode 100644 index 0000000..86e9056 --- /dev/null +++ b/src/audio/wavcapture.c @@ -0,0 +1,194 @@ +#include "hw/hw.h" +#include "monitor/monitor.h" +#include "qemu/error-report.h" +#include "audio.h" + +typedef struct { + FILE *f; + int bytes; + char *path; + int freq; + int bits; + int nchannels; + CaptureVoiceOut *cap; +} WAVState; + +/* VICE code: Store number as little endian. */ +static void le_store (uint8_t *buf, uint32_t val, int len) +{ + int i; + for (i = 0; i < len; i++) { + buf[i] = (uint8_t) (val & 0xff); + val >>= 8; + } +} + +static void wav_notify (void *opaque, audcnotification_e cmd) +{ + (void) opaque; + (void) cmd; +} + +static void wav_destroy (void *opaque) +{ + WAVState *wav = opaque; + uint8_t rlen[4]; + uint8_t dlen[4]; + uint32_t datalen = wav->bytes; + uint32_t rifflen = datalen + 36; + Monitor *mon = cur_mon; + + if (wav->f) { + le_store (rlen, rifflen, 4); + le_store (dlen, datalen, 4); + + if (fseek (wav->f, 4, SEEK_SET)) { + monitor_printf (mon, "wav_destroy: rlen fseek failed\nReason: %s\n", + strerror (errno)); + goto doclose; + } + if (fwrite (rlen, 4, 1, wav->f) != 1) { + monitor_printf (mon, "wav_destroy: rlen fwrite failed\nReason %s\n", + strerror (errno)); + goto doclose; + } + if (fseek (wav->f, 32, SEEK_CUR)) { + monitor_printf (mon, "wav_destroy: dlen fseek failed\nReason %s\n", + strerror (errno)); + goto doclose; + } + if (fwrite (dlen, 1, 4, wav->f) != 4) { + monitor_printf (mon, "wav_destroy: dlen fwrite failed\nReason %s\n", + strerror (errno)); + goto doclose; + } + doclose: + if (fclose (wav->f)) { + error_report("wav_destroy: fclose failed: %s", strerror(errno)); + } + } + + g_free (wav->path); +} + +static void wav_capture (void *opaque, void *buf, int size) +{ + WAVState *wav = opaque; + + if (fwrite (buf, size, 1, wav->f) != 1) { + monitor_printf (cur_mon, "wav_capture: fwrite error\nReason: %s", + strerror (errno)); + } + wav->bytes += size; +} + +static void wav_capture_destroy (void *opaque) +{ + WAVState *wav = opaque; + + AUD_del_capture (wav->cap, wav); +} + +static void wav_capture_info (void *opaque) +{ + WAVState *wav = opaque; + char *path = wav->path; + + monitor_printf (cur_mon, "Capturing audio(%d,%d,%d) to %s: %d bytes\n", + wav->freq, wav->bits, wav->nchannels, + path ? path : "<not available>", wav->bytes); +} + +static struct capture_ops wav_capture_ops = { + .destroy = wav_capture_destroy, + .info = wav_capture_info +}; + +int wav_start_capture (CaptureState *s, const char *path, int freq, + int bits, int nchannels) +{ + Monitor *mon = cur_mon; + WAVState *wav; + uint8_t hdr[] = { + 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, + 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, + 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 + }; + struct audsettings as; + struct audio_capture_ops ops; + int stereo, bits16, shift; + CaptureVoiceOut *cap; + + if (bits != 8 && bits != 16) { + monitor_printf (mon, "incorrect bit count %d, must be 8 or 16\n", bits); + return -1; + } + + if (nchannels != 1 && nchannels != 2) { + monitor_printf (mon, "incorrect channel count %d, must be 1 or 2\n", + nchannels); + return -1; + } + + stereo = nchannels == 2; + bits16 = bits == 16; + + as.freq = freq; + as.nchannels = 1 << stereo; + as.fmt = bits16 ? AUD_FMT_S16 : AUD_FMT_U8; + as.endianness = 0; + + ops.notify = wav_notify; + ops.capture = wav_capture; + ops.destroy = wav_destroy; + + wav = g_malloc0 (sizeof (*wav)); + + shift = bits16 + stereo; + hdr[34] = bits16 ? 0x10 : 0x08; + + le_store (hdr + 22, as.nchannels, 2); + le_store (hdr + 24, freq, 4); + le_store (hdr + 28, freq << shift, 4); + le_store (hdr + 32, 1 << shift, 2); + + wav->f = fopen (path, "wb"); + if (!wav->f) { + monitor_printf (mon, "Failed to open wave file `%s'\nReason: %s\n", + path, strerror (errno)); + g_free (wav); + return -1; + } + + wav->path = g_strdup (path); + wav->bits = bits; + wav->nchannels = nchannels; + wav->freq = freq; + + if (fwrite (hdr, sizeof (hdr), 1, wav->f) != 1) { + monitor_printf (mon, "Failed to write header\nReason: %s\n", + strerror (errno)); + goto error_free; + } + + cap = AUD_add_capture (&as, &ops, wav); + if (!cap) { + monitor_printf (mon, "Failed to add audio capture\n"); + goto error_free; + } + + wav->cap = cap; + s->opaque = wav; + s->ops = wav_capture_ops; + return 0; + +error_free: + g_free (wav->path); + if (fclose (wav->f)) { + monitor_printf (mon, "Failed to close wave file\nReason: %s\n", + strerror (errno)); + } + g_free (wav); + return -1; +} |