summaryrefslogtreecommitdiffstats
path: root/src/audio
diff options
context:
space:
mode:
authorTimothy Pearson <tpearson@raptorengineering.com>2019-05-11 15:12:49 -0500
committerTimothy Pearson <tpearson@raptorengineering.com>2019-05-11 15:12:49 -0500
commit9e80202352dd49bdd9e67b8b906d86f058431505 (patch)
tree5673c17aad6e3833da8c4ff21b5a11f666ec9fbe /src/audio
downloadhqemu-9e80202352dd49bdd9e67b8b906d86f058431505.zip
hqemu-9e80202352dd49bdd9e67b8b906d86f058431505.tar.gz
Initial import of abandoned HQEMU version 2.5.2HEADmaster
Diffstat (limited to 'src/audio')
-rw-r--r--src/audio/Makefile.objs13
-rw-r--r--src/audio/alsaaudio.c1227
-rw-r--r--src/audio/audio.c2063
-rw-r--r--src/audio/audio.h166
-rw-r--r--src/audio/audio_int.h260
-rw-r--r--src/audio/audio_pt_int.c173
-rw-r--r--src/audio/audio_pt_int.h22
-rw-r--r--src/audio/audio_template.h514
-rw-r--r--src/audio/audio_win_int.c107
-rw-r--r--src/audio/audio_win_int.h10
-rw-r--r--src/audio/coreaudio.c555
-rw-r--r--src/audio/dsound_template.h278
-rw-r--r--src/audio/dsoundaudio.c904
-rw-r--r--src/audio/mixeng.c366
-rw-r--r--src/audio/mixeng.h51
-rw-r--r--src/audio/mixeng_template.h154
-rw-r--r--src/audio/noaudio.c173
-rw-r--r--src/audio/ossaudio.c941
-rw-r--r--src/audio/paaudio.c953
-rw-r--r--src/audio/rate_template.h111
-rw-r--r--src/audio/sdlaudio.c466
-rw-r--r--src/audio/spiceaudio.c411
-rw-r--r--src/audio/wavaudio.c292
-rw-r--r--src/audio/wavcapture.c194
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;
+}
OpenPOWER on IntegriCloud