aboutsummaryrefslogtreecommitdiffstats
path: root/audio
diff options
context:
space:
mode:
Diffstat (limited to 'audio')
-rw-r--r--audio/alsaaudio.c946
-rw-r--r--audio/audio.c2222
-rw-r--r--audio/audio.h181
-rw-r--r--audio/audio_int.h287
-rw-r--r--audio/audio_legacy.c555
-rw-r--r--audio/audio_template.h568
-rw-r--r--audio/audio_win_int.c131
-rw-r--r--audio/audio_win_int.h10
-rw-r--r--audio/coreaudio.c681
-rw-r--r--audio/dsound_template.h280
-rw-r--r--audio/dsoundaudio.c732
-rw-r--r--audio/jackaudio.c694
-rw-r--r--audio/meson.build29
-rw-r--r--audio/mixeng.c470
-rw-r--r--audio/mixeng.h58
-rw-r--r--audio/mixeng_template.h152
-rw-r--r--audio/noaudio.c148
-rw-r--r--audio/ossaudio.c782
-rw-r--r--audio/paaudio.c933
-rw-r--r--audio/rate_template.h117
-rw-r--r--audio/sdlaudio.c508
-rw-r--r--audio/spiceaudio.c321
-rw-r--r--audio/trace-events19
-rw-r--r--audio/trace.h1
-rw-r--r--audio/wavaudio.c221
-rw-r--r--audio/wavcapture.c191
26 files changed, 11237 insertions, 0 deletions
diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c
new file mode 100644
index 000000000..2b9789e64
--- /dev/null
+++ b/audio/alsaaudio.c
@@ -0,0 +1,946 @@
+/*
+ * 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 "qemu/osdep.h"
+#include <alsa/asoundlib.h>
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "audio.h"
+#include "trace.h"
+
+#pragma GCC diagnostic ignored "-Waddress"
+
+#define AUDIO_CAP "alsa"
+#include "audio_int.h"
+
+#define DEBUG_ALSA 0
+
+struct pollhlp {
+ snd_pcm_t *handle;
+ struct pollfd *pfds;
+ int count;
+ int mask;
+ AudioState *s;
+};
+
+typedef struct ALSAVoiceOut {
+ HWVoiceOut hw;
+ snd_pcm_t *handle;
+ struct pollhlp pollhlp;
+ Audiodev *dev;
+} ALSAVoiceOut;
+
+typedef struct ALSAVoiceIn {
+ HWVoiceIn hw;
+ snd_pcm_t *handle;
+ struct pollhlp pollhlp;
+ Audiodev *dev;
+} ALSAVoiceIn;
+
+struct alsa_params_req {
+ int freq;
+ snd_pcm_format_t fmt;
+ int nchannels;
+};
+
+struct alsa_params_obt {
+ int freq;
+ AudioFormat 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(hlp->s, "alsa run (prepared)");
+ break;
+
+ case SND_PCM_STATE_RUNNING:
+ audio_run(hlp->s, "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 snd_pcm_format_t aud_to_alsafmt (AudioFormat fmt, int endianness)
+{
+ switch (fmt) {
+ case AUDIO_FORMAT_S8:
+ return SND_PCM_FORMAT_S8;
+
+ case AUDIO_FORMAT_U8:
+ return SND_PCM_FORMAT_U8;
+
+ case AUDIO_FORMAT_S16:
+ if (endianness) {
+ return SND_PCM_FORMAT_S16_BE;
+ } else {
+ return SND_PCM_FORMAT_S16_LE;
+ }
+
+ case AUDIO_FORMAT_U16:
+ if (endianness) {
+ return SND_PCM_FORMAT_U16_BE;
+ } else {
+ return SND_PCM_FORMAT_U16_LE;
+ }
+
+ case AUDIO_FORMAT_S32:
+ if (endianness) {
+ return SND_PCM_FORMAT_S32_BE;
+ } else {
+ return SND_PCM_FORMAT_S32_LE;
+ }
+
+ case AUDIO_FORMAT_U32:
+ if (endianness) {
+ return SND_PCM_FORMAT_U32_BE;
+ } else {
+ return SND_PCM_FORMAT_U32_LE;
+ }
+
+ case AUDIO_FORMAT_F32:
+ if (endianness) {
+ return SND_PCM_FORMAT_FLOAT_BE;
+ } else {
+ return SND_PCM_FORMAT_FLOAT_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, AudioFormat *fmt,
+ int *endianness)
+{
+ switch (alsafmt) {
+ case SND_PCM_FORMAT_S8:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_S8;
+ break;
+
+ case SND_PCM_FORMAT_U8:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_U8;
+ break;
+
+ case SND_PCM_FORMAT_S16_LE:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_S16;
+ break;
+
+ case SND_PCM_FORMAT_U16_LE:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_U16;
+ break;
+
+ case SND_PCM_FORMAT_S16_BE:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_S16;
+ break;
+
+ case SND_PCM_FORMAT_U16_BE:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_U16;
+ break;
+
+ case SND_PCM_FORMAT_S32_LE:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_S32;
+ break;
+
+ case SND_PCM_FORMAT_U32_LE:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_U32;
+ break;
+
+ case SND_PCM_FORMAT_S32_BE:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_S32;
+ break;
+
+ case SND_PCM_FORMAT_U32_BE:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_U32;
+ break;
+
+ case SND_PCM_FORMAT_FLOAT_LE:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_F32;
+ break;
+
+ case SND_PCM_FORMAT_FLOAT_BE:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_F32;
+ 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,
+ AudiodevAlsaPerDirectionOptions *apdo)
+{
+ 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 len %" PRId32 " period len %" PRId32 "\n",
+ apdo->buffer_length, apdo->period_length);
+ 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(bool in, struct alsa_params_req *req,
+ struct alsa_params_obt *obt, snd_pcm_t **handlep,
+ Audiodev *dev)
+{
+ AudiodevAlsaOptions *aopts = &dev->u.alsa;
+ AudiodevAlsaPerDirectionOptions *apdo = in ? aopts->in : aopts->out;
+ snd_pcm_t *handle;
+ snd_pcm_hw_params_t *hw_params;
+ int err;
+ unsigned int freq, nchannels;
+ const char *pcm_name = apdo->has_dev ? apdo->dev : "default";
+ snd_pcm_uframes_t obt_buffer_size;
+ const char *typ = in ? "ADC" : "DAC";
+ snd_pcm_format_t obtfmt;
+
+ freq = req->freq;
+ nchannels = req->nchannels;
+
+ 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 (apdo->buffer_length) {
+ int dir = 0;
+ unsigned int btime = apdo->buffer_length;
+
+ err = snd_pcm_hw_params_set_buffer_time_near(
+ handle, hw_params, &btime, &dir);
+
+ if (err < 0) {
+ alsa_logerr2(err, typ, "Failed to set buffer time to %" PRId32 "\n",
+ apdo->buffer_length);
+ goto err;
+ }
+
+ if (apdo->has_buffer_length && btime != apdo->buffer_length) {
+ dolog("Requested buffer time %" PRId32
+ " was rejected, using %u\n", apdo->buffer_length, btime);
+ }
+ }
+
+ if (apdo->period_length) {
+ int dir = 0;
+ unsigned int ptime = apdo->period_length;
+
+ err = snd_pcm_hw_params_set_period_time_near(handle, hw_params, &ptime,
+ &dir);
+
+ if (err < 0) {
+ alsa_logerr2(err, typ, "Failed to set period time to %" PRId32 "\n",
+ apdo->period_length);
+ goto err;
+ }
+
+ if (apdo->has_period_length && ptime != apdo->period_length) {
+ dolog("Requested period time %" PRId32 " was rejected, using %d\n",
+ apdo->period_length, ptime);
+ }
+ }
+
+ 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 && aopts->has_threshold && aopts->threshold) {
+ struct audsettings as = { .freq = freq };
+ alsa_set_threshold(
+ handle,
+ audio_buffer_frames(qapi_AudiodevAlsaPerDirectionOptions_base(apdo),
+ &as, aopts->threshold));
+ }
+
+ obt->nchannels = nchannels;
+ obt->freq = freq;
+ obt->samples = obt_buffer_size;
+
+ *handlep = handle;
+
+ if (DEBUG_ALSA || obtfmt != req->fmt ||
+ obt->nchannels != req->nchannels || obt->freq != req->freq) {
+ dolog ("Audio parameters for %s\n", typ);
+ alsa_dump_info(req, obt, obtfmt, apdo);
+ }
+
+ return 0;
+
+ err:
+ alsa_anal_close1 (&handle);
+ return -1;
+}
+
+static size_t alsa_write(HWVoiceOut *hw, void *buf, size_t len)
+{
+ ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
+ size_t pos = 0;
+ size_t len_frames = len / hw->info.bytes_per_frame;
+
+ while (len_frames) {
+ char *src = advance(buf, pos);
+ snd_pcm_sframes_t written;
+
+ written = snd_pcm_writei(alsa->handle, src, len_frames);
+
+ if (written <= 0) {
+ switch (written) {
+ case 0:
+ trace_alsa_wrote_zero(len_frames);
+ return pos;
+
+ case -EPIPE:
+ if (alsa_recover(alsa->handle)) {
+ alsa_logerr(written, "Failed to write %zu frames\n",
+ len_frames);
+ return pos;
+ }
+ 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 %zu frames\n",
+ len_frames);
+ return pos;
+ }
+ trace_alsa_resume_out();
+ continue;
+
+ case -EAGAIN:
+ return pos;
+
+ default:
+ alsa_logerr(written, "Failed to write %zu frames from %p\n",
+ len, src);
+ return pos;
+ }
+ }
+
+ pos += written * hw->info.bytes_per_frame;
+ if (written < len_frames) {
+ break;
+ }
+ len_frames -= written;
+ }
+
+ return pos;
+}
+
+static void alsa_fini_out (HWVoiceOut *hw)
+{
+ ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
+
+ ldebug ("alsa_fini\n");
+ alsa_anal_close (&alsa->handle, &alsa->pollhlp);
+}
+
+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;
+ Audiodev *dev = drv_opaque;
+
+ req.fmt = aud_to_alsafmt (as->fmt, as->endianness);
+ req.freq = as->freq;
+ req.nchannels = as->nchannels;
+
+ if (alsa_open(0, &req, &obt, &handle, dev)) {
+ 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->pollhlp.s = hw->s;
+ alsa->handle = handle;
+ alsa->dev = dev;
+ 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 void alsa_enable_out(HWVoiceOut *hw, bool enable)
+{
+ ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
+ AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->u.alsa.out;
+
+ if (enable) {
+ bool poll_mode = apdo->try_poll;
+
+ ldebug("enabling voice\n");
+ if (poll_mode && alsa_poll_out(hw)) {
+ poll_mode = 0;
+ }
+ hw->poll_mode = poll_mode;
+ alsa_voice_ctl(alsa->handle, "playback", VOICE_CTL_PREPARE);
+ } else {
+ ldebug("disabling voice\n");
+ if (hw->poll_mode) {
+ hw->poll_mode = 0;
+ alsa_fini_poll(&alsa->pollhlp);
+ }
+ alsa_voice_ctl(alsa->handle, "playback", VOICE_CTL_PAUSE);
+ }
+}
+
+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;
+ Audiodev *dev = drv_opaque;
+
+ req.fmt = aud_to_alsafmt (as->fmt, as->endianness);
+ req.freq = as->freq;
+ req.nchannels = as->nchannels;
+
+ if (alsa_open(1, &req, &obt, &handle, dev)) {
+ 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->pollhlp.s = hw->s;
+ alsa->handle = handle;
+ alsa->dev = dev;
+ return 0;
+}
+
+static void alsa_fini_in (HWVoiceIn *hw)
+{
+ ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
+
+ alsa_anal_close (&alsa->handle, &alsa->pollhlp);
+}
+
+static size_t alsa_read(HWVoiceIn *hw, void *buf, size_t len)
+{
+ ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
+ size_t pos = 0;
+
+ while (len) {
+ void *dst = advance(buf, pos);
+ snd_pcm_sframes_t nread;
+
+ nread = snd_pcm_readi(
+ alsa->handle, dst, len / hw->info.bytes_per_frame);
+
+ if (nread <= 0) {
+ switch (nread) {
+ case 0:
+ trace_alsa_read_zero(len);
+ return pos;
+
+ case -EPIPE:
+ if (alsa_recover(alsa->handle)) {
+ alsa_logerr(nread, "Failed to read %zu frames\n", len);
+ return pos;
+ }
+ trace_alsa_xrun_in();
+ continue;
+
+ case -EAGAIN:
+ return pos;
+
+ default:
+ alsa_logerr(nread, "Failed to read %zu frames to %p\n",
+ len, dst);
+ return pos;
+ }
+ }
+
+ pos += nread * hw->info.bytes_per_frame;
+ len -= nread * hw->info.bytes_per_frame;
+ }
+
+ return pos;
+}
+
+static void alsa_enable_in(HWVoiceIn *hw, bool enable)
+{
+ ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
+ AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->u.alsa.in;
+
+ if (enable) {
+ bool poll_mode = apdo->try_poll;
+
+ ldebug("enabling voice\n");
+ if (poll_mode && alsa_poll_in(hw)) {
+ poll_mode = 0;
+ }
+ hw->poll_mode = poll_mode;
+
+ alsa_voice_ctl(alsa->handle, "capture", VOICE_CTL_START);
+ } else {
+ ldebug ("disabling voice\n");
+ if (hw->poll_mode) {
+ hw->poll_mode = 0;
+ alsa_fini_poll(&alsa->pollhlp);
+ }
+ alsa_voice_ctl(alsa->handle, "capture", VOICE_CTL_PAUSE);
+ }
+}
+
+static void alsa_init_per_direction(AudiodevAlsaPerDirectionOptions *apdo)
+{
+ if (!apdo->has_try_poll) {
+ apdo->try_poll = true;
+ apdo->has_try_poll = true;
+ }
+}
+
+static void *alsa_audio_init(Audiodev *dev)
+{
+ AudiodevAlsaOptions *aopts;
+ assert(dev->driver == AUDIODEV_DRIVER_ALSA);
+
+ aopts = &dev->u.alsa;
+ alsa_init_per_direction(aopts->in);
+ alsa_init_per_direction(aopts->out);
+
+ /*
+ * need to define them, as otherwise alsa produces no sound
+ * doesn't set has_* so alsa_open can identify it wasn't set by the user
+ */
+ if (!dev->u.alsa.out->has_period_length) {
+ /* 1024 frames assuming 44100Hz */
+ dev->u.alsa.out->period_length = 1024 * 1000000 / 44100;
+ }
+ if (!dev->u.alsa.out->has_buffer_length) {
+ /* 4096 frames assuming 44100Hz */
+ dev->u.alsa.out->buffer_length = 4096ll * 1000000 / 44100;
+ }
+
+ /*
+ * OptsVisitor sets unspecified optional fields to zero, but do not depend
+ * on it...
+ */
+ if (!dev->u.alsa.in->has_period_length) {
+ dev->u.alsa.in->period_length = 0;
+ }
+ if (!dev->u.alsa.in->has_buffer_length) {
+ dev->u.alsa.in->buffer_length = 0;
+ }
+
+ return dev;
+}
+
+static void alsa_audio_fini (void *opaque)
+{
+}
+
+static struct audio_pcm_ops alsa_pcm_ops = {
+ .init_out = alsa_init_out,
+ .fini_out = alsa_fini_out,
+ .write = alsa_write,
+ .run_buffer_out = audio_generic_run_buffer_out,
+ .enable_out = alsa_enable_out,
+
+ .init_in = alsa_init_in,
+ .fini_in = alsa_fini_in,
+ .read = alsa_read,
+ .run_buffer_in = audio_generic_run_buffer_in,
+ .enable_in = alsa_enable_in,
+};
+
+static struct audio_driver alsa_audio_driver = {
+ .name = "alsa",
+ .descr = "ALSA http://www.alsa-project.org",
+ .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)
+};
+
+static void register_audio_alsa(void)
+{
+ audio_driver_register(&alsa_audio_driver);
+}
+type_init(register_audio_alsa);
diff --git a/audio/audio.c b/audio/audio.c
new file mode 100644
index 000000000..54a153c0e
--- /dev/null
+++ b/audio/audio.c
@@ -0,0 +1,2222 @@
+/*
+ * 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 "qemu/osdep.h"
+#include "audio.h"
+#include "migration/vmstate.h"
+#include "monitor/monitor.h"
+#include "qemu/timer.h"
+#include "qapi/error.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qapi-visit-audio.h"
+#include "qemu/cutils.h"
+#include "qemu/module.h"
+#include "qemu-common.h"
+#include "sysemu/replay.h"
+#include "sysemu/runstate.h"
+#include "ui/qemu-spice.h"
+#include "trace.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.
+*/
+const char *audio_prio_list[] = {
+ "spice",
+ CONFIG_AUDIO_DRIVERS
+ "none",
+ "wav",
+ NULL
+};
+
+static QLIST_HEAD(, audio_driver) audio_drivers;
+static AudiodevListHead audiodevs = QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
+
+void audio_driver_register(audio_driver *drv)
+{
+ QLIST_INSERT_HEAD(&audio_drivers, drv, next);
+}
+
+audio_driver *audio_driver_lookup(const char *name)
+{
+ struct audio_driver *d;
+
+ QLIST_FOREACH(d, &audio_drivers, next) {
+ if (strcmp(name, d->name) == 0) {
+ return d;
+ }
+ }
+
+ audio_module_load_one(name);
+ QLIST_FOREACH(d, &audio_drivers, next) {
+ if (strcmp(name, d->name) == 0) {
+ return d;
+ }
+ }
+
+ return NULL;
+}
+
+static QTAILQ_HEAD(AudioStateHead, AudioState) audio_states =
+ QTAILQ_HEAD_INITIALIZER(audio_states);
+
+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
+};
+
+static bool legacy_config = true;
+
+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) {
+ shown = 1;
+ AUD_log (NULL, "Save all your work and restart without audio\n");
+ AUD_log (NULL, "I am sorry\n");
+ }
+ AUD_log (NULL, "Context:\n");
+ abort();
+ }
+
+ return cond;
+}
+
+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);
+}
+
+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_settings (struct audsettings *as)
+{
+ dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
+
+ switch (as->fmt) {
+ case AUDIO_FORMAT_S8:
+ AUD_log (NULL, "S8");
+ break;
+ case AUDIO_FORMAT_U8:
+ AUD_log (NULL, "U8");
+ break;
+ case AUDIO_FORMAT_S16:
+ AUD_log (NULL, "S16");
+ break;
+ case AUDIO_FORMAT_U16:
+ AUD_log (NULL, "U16");
+ break;
+ case AUDIO_FORMAT_S32:
+ AUD_log (NULL, "S32");
+ break;
+ case AUDIO_FORMAT_U32:
+ AUD_log (NULL, "U32");
+ break;
+ case AUDIO_FORMAT_F32:
+ AUD_log (NULL, "F32");
+ 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;
+ invalid |= as->endianness != 0 && as->endianness != 1;
+
+ switch (as->fmt) {
+ case AUDIO_FORMAT_S8:
+ case AUDIO_FORMAT_U8:
+ case AUDIO_FORMAT_S16:
+ case AUDIO_FORMAT_U16:
+ case AUDIO_FORMAT_S32:
+ case AUDIO_FORMAT_U32:
+ case AUDIO_FORMAT_F32:
+ 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;
+ bool is_signed = false, is_float = false;
+
+ switch (as->fmt) {
+ case AUDIO_FORMAT_S8:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U8:
+ break;
+
+ case AUDIO_FORMAT_S16:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U16:
+ bits = 16;
+ break;
+
+ case AUDIO_FORMAT_F32:
+ is_float = true;
+ /* fall through */
+ case AUDIO_FORMAT_S32:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U32:
+ bits = 32;
+ break;
+
+ default:
+ abort();
+ }
+ return info->freq == as->freq
+ && info->nchannels == as->nchannels
+ && info->is_signed == is_signed
+ && info->is_float == is_float
+ && 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, mul;
+ bool is_signed = false, is_float = false;
+
+ switch (as->fmt) {
+ case AUDIO_FORMAT_S8:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U8:
+ mul = 1;
+ break;
+
+ case AUDIO_FORMAT_S16:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U16:
+ bits = 16;
+ mul = 2;
+ break;
+
+ case AUDIO_FORMAT_F32:
+ is_float = true;
+ /* fall through */
+ case AUDIO_FORMAT_S32:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U32:
+ bits = 32;
+ mul = 4;
+ break;
+
+ default:
+ abort();
+ }
+
+ info->freq = as->freq;
+ info->bits = bits;
+ info->is_signed = is_signed;
+ info->is_float = is_float;
+ info->nchannels = as->nchannels;
+ info->bytes_per_frame = as->nchannels * mul;
+ info->bytes_per_second = info->freq * info->bytes_per_frame;
+ 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->is_signed || info->is_float) {
+ memset(buf, 0x00, len * info->bytes_per_frame);
+ } else {
+ switch (info->bits) {
+ case 8:
+ memset(buf, 0x80, len * info->bytes_per_frame);
+ break;
+
+ case 16:
+ {
+ int i;
+ uint16_t *p = buf;
+ short s = INT16_MAX;
+
+ if (info->swap_endianness) {
+ s = bswap16 (s);
+ }
+
+ for (i = 0; i < len * info->nchannels; i++) {
+ p[i] = s;
+ }
+ }
+ break;
+
+ case 32:
+ {
+ int i;
+ uint32_t *p = buf;
+ int32_t s = INT32_MAX;
+
+ if (info->swap_endianness) {
+ s = bswap32 (s);
+ }
+
+ for (i = 0; i < len * info->nchannels; 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(AudioState *s,
+ struct audsettings *as)
+{
+ CaptureVoiceOut *cap;
+
+ 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 = hw->s;
+ 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 = g_malloc0(sizeof(*sc));
+
+ 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 size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
+{
+ SWVoiceIn *sw;
+ size_t m = hw->total_samples_captured;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (sw->active) {
+ m = MIN (m, sw->total_hw_samples_acquired);
+ }
+ }
+ return m;
+}
+
+static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
+{
+ size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
+ if (audio_bug(__func__, live > hw->conv_buf->size)) {
+ dolog("live=%zu hw->conv_buf->size=%zu\n", live, hw->conv_buf->size);
+ return 0;
+ }
+ return live;
+}
+
+static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
+{
+ size_t clipped = 0;
+ size_t pos = hw->mix_buf->pos;
+
+ while (len) {
+ st_sample *src = hw->mix_buf->samples + pos;
+ uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
+ size_t samples_till_end_of_buf = hw->mix_buf->size - pos;
+ size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
+
+ hw->clip(dst, src, samples_to_clip);
+
+ pos = (pos + samples_to_clip) % hw->mix_buf->size;
+ len -= samples_to_clip;
+ clipped += samples_to_clip;
+ }
+}
+
+/*
+ * Soft voice (capture)
+ */
+static size_t audio_pcm_sw_get_rpos_in(SWVoiceIn *sw)
+{
+ HWVoiceIn *hw = sw->hw;
+ ssize_t live = hw->total_samples_captured - sw->total_hw_samples_acquired;
+ ssize_t rpos;
+
+ if (audio_bug(__func__, live < 0 || live > hw->conv_buf->size)) {
+ dolog("live=%zu hw->conv_buf->size=%zu\n", live, hw->conv_buf->size);
+ return 0;
+ }
+
+ rpos = hw->conv_buf->pos - live;
+ if (rpos >= 0) {
+ return rpos;
+ } else {
+ return hw->conv_buf->size + rpos;
+ }
+}
+
+static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
+{
+ HWVoiceIn *hw = sw->hw;
+ size_t 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->conv_buf->size;
+
+ live = hw->total_samples_captured - sw->total_hw_samples_acquired;
+ if (audio_bug(__func__, live > hw->conv_buf->size)) {
+ dolog("live_in=%zu hw->conv_buf->size=%zu\n", live, hw->conv_buf->size);
+ return 0;
+ }
+
+ samples = size / sw->info.bytes_per_frame;
+ if (!live) {
+ return 0;
+ }
+
+ swlim = (live * sw->ratio) >> 32;
+ swlim = MIN (swlim, samples);
+
+ while (swlim) {
+ src = hw->conv_buf->samples + rpos;
+ if (hw->conv_buf->pos > rpos) {
+ isamp = hw->conv_buf->pos - rpos;
+ } else {
+ isamp = hw->conv_buf->size - rpos;
+ }
+
+ if (!isamp) {
+ break;
+ }
+ osamp = swlim;
+
+ st_rate_flow (sw->rate, src, dst, &isamp, &osamp);
+ swlim -= osamp;
+ rpos = (rpos + isamp) % hw->conv_buf->size;
+ dst += osamp;
+ ret += osamp;
+ total += isamp;
+ }
+
+ if (hw->pcm_ops && !hw->pcm_ops->volume_in) {
+ mixeng_volume (sw->buf, ret, &sw->vol);
+ }
+
+ sw->clip (buf, sw->buf, ret);
+ sw->total_hw_samples_acquired += total;
+ return ret * sw->info.bytes_per_frame;
+}
+
+/*
+ * Hard voice (playback)
+ */
+static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
+{
+ SWVoiceOut *sw;
+ size_t m = SIZE_MAX;
+ int nb_live = 0;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (sw->active || !sw->empty) {
+ m = MIN (m, sw->total_hw_samples_mixed);
+ nb_live += 1;
+ }
+ }
+
+ *nb_livep = nb_live;
+ return m;
+}
+
+static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
+{
+ size_t smin;
+ int nb_live1;
+
+ smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
+ if (nb_live) {
+ *nb_live = nb_live1;
+ }
+
+ if (nb_live1) {
+ size_t live = smin;
+
+ if (audio_bug(__func__, live > hw->mix_buf->size)) {
+ dolog("live=%zu hw->mix_buf->size=%zu\n", live, hw->mix_buf->size);
+ return 0;
+ }
+ return live;
+ }
+ return 0;
+}
+
+/*
+ * Soft voice (playback)
+ */
+static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
+{
+ size_t hwsamples, samples, isamp, osamp, wpos, live, dead, left, swlim, blck;
+ size_t ret = 0, pos = 0, total = 0;
+
+ if (!sw) {
+ return size;
+ }
+
+ hwsamples = sw->hw->mix_buf->size;
+
+ live = sw->total_hw_samples_mixed;
+ if (audio_bug(__func__, live > hwsamples)) {
+ dolog("live=%zu hw->mix_buf->size=%zu\n", live, hwsamples);
+ return 0;
+ }
+
+ if (live == hwsamples) {
+#ifdef DEBUG_OUT
+ dolog ("%s is full %zu\n", sw->name, live);
+#endif
+ return 0;
+ }
+
+ wpos = (sw->hw->mix_buf->pos + live) % hwsamples;
+ samples = size / sw->info.bytes_per_frame;
+
+ dead = hwsamples - live;
+ swlim = ((int64_t) dead << 32) / sw->ratio;
+ swlim = MIN (swlim, samples);
+ if (swlim) {
+ sw->conv (sw->buf, buf, swlim);
+
+ if (sw->hw->pcm_ops && !sw->hw->pcm_ops->volume_out) {
+ mixeng_volume (sw->buf, swlim, &sw->vol);
+ }
+ }
+
+ while (swlim) {
+ dead = hwsamples - live;
+ left = hwsamples - wpos;
+ blck = MIN (dead, left);
+ if (!blck) {
+ break;
+ }
+ isamp = swlim;
+ osamp = blck;
+ st_rate_flow_mix (
+ sw->rate,
+ sw->buf + pos,
+ sw->hw->mix_buf->samples + 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 %zu ret %zu total sw %zu\n",
+ SW_NAME (sw),
+ size / sw->info.bytes_per_frame,
+ ret,
+ sw->total_hw_samples_mixed
+ );
+#endif
+
+ return ret * sw->info.bytes_per_frame;
+}
+
+#ifdef DEBUG_AUDIO
+static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
+{
+ dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
+ cap, info->bits, info->is_signed, info->is_float, info->freq,
+ info->nchannels);
+}
+#endif
+
+#define DAC
+#include "audio_template.h"
+#undef DAC
+#include "audio_template.h"
+
+/*
+ * Timer
+ */
+static int audio_is_timer_needed(AudioState *s)
+{
+ HWVoiceIn *hwi = NULL;
+ HWVoiceOut *hwo = NULL;
+
+ while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
+ if (!hwo->poll_mode) {
+ return 1;
+ }
+ }
+ while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
+ if (!hwi->poll_mode) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void audio_reset_timer (AudioState *s)
+{
+ if (audio_is_timer_needed(s)) {
+ timer_mod_anticipate_ns(s->ts,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
+ if (!s->timer_running) {
+ s->timer_running = true;
+ s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ trace_audio_timer_start(s->period_ticks / SCALE_MS);
+ }
+ } else {
+ timer_del(s->ts);
+ if (s->timer_running) {
+ s->timer_running = false;
+ trace_audio_timer_stop();
+ }
+ }
+}
+
+static void audio_timer (void *opaque)
+{
+ int64_t now, diff;
+ AudioState *s = opaque;
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ diff = now - s->timer_last;
+ if (diff > s->period_ticks * 3 / 2) {
+ trace_audio_timer_delayed(diff / SCALE_MS);
+ }
+ s->timer_last = now;
+
+ audio_run(s, "timer");
+ audio_reset_timer(s);
+}
+
+/*
+ * Public API
+ */
+size_t AUD_write(SWVoiceOut *sw, void *buf, size_t size)
+{
+ HWVoiceOut *hw;
+
+ if (!sw) {
+ /* XXX: Consider options */
+ return size;
+ }
+ hw = sw->hw;
+
+ if (!hw->enabled) {
+ dolog ("Writing to disabled voice %s\n", SW_NAME (sw));
+ return 0;
+ }
+
+ if (audio_get_pdo_out(hw->s->dev)->mixing_engine) {
+ return audio_pcm_sw_write(sw, buf, size);
+ } else {
+ return hw->pcm_ops->write(hw, buf, size);
+ }
+}
+
+size_t AUD_read(SWVoiceIn *sw, void *buf, size_t size)
+{
+ HWVoiceIn *hw;
+
+ if (!sw) {
+ /* XXX: Consider options */
+ return size;
+ }
+ hw = sw->hw;
+
+ if (!hw->enabled) {
+ dolog ("Reading from disabled voice %s\n", SW_NAME (sw));
+ return 0;
+ }
+
+ if (audio_get_pdo_in(hw->s->dev)->mixing_engine) {
+ return audio_pcm_sw_read(sw, buf, size);
+ } else {
+ return hw->pcm_ops->read(hw, buf, size);
+ }
+}
+
+int AUD_get_buffer_size_out(SWVoiceOut *sw)
+{
+ return sw->hw->samples * sw->hw->info.bytes_per_frame;
+}
+
+void AUD_set_active_out (SWVoiceOut *sw, int on)
+{
+ HWVoiceOut *hw;
+
+ if (!sw) {
+ return;
+ }
+
+ hw = sw->hw;
+ if (sw->active != on) {
+ AudioState *s = sw->s;
+ SWVoiceOut *temp_sw;
+ SWVoiceCap *sc;
+
+ if (on) {
+ hw->pending_disable = 0;
+ if (!hw->enabled) {
+ hw->enabled = 1;
+ if (s->vm_running) {
+ if (hw->pcm_ops->enable_out) {
+ hw->pcm_ops->enable_out(hw, true);
+ }
+ 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 = sw->s;
+ SWVoiceIn *temp_sw;
+
+ if (on) {
+ if (!hw->enabled) {
+ hw->enabled = 1;
+ if (s->vm_running) {
+ if (hw->pcm_ops->enable_in) {
+ hw->pcm_ops->enable_in(hw, true);
+ }
+ 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;
+ if (hw->pcm_ops->enable_in) {
+ hw->pcm_ops->enable_in(hw, false);
+ }
+ }
+ }
+ }
+ sw->active = on;
+ }
+}
+
+static size_t audio_get_avail (SWVoiceIn *sw)
+{
+ size_t live;
+
+ if (!sw) {
+ return 0;
+ }
+
+ live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
+ if (audio_bug(__func__, live > sw->hw->conv_buf->size)) {
+ dolog("live=%zu sw->hw->conv_buf->size=%zu\n", live,
+ sw->hw->conv_buf->size);
+ return 0;
+ }
+
+ ldebug (
+ "%s: get_avail live %zu ret %" PRId64 "\n",
+ SW_NAME (sw),
+ live, (((int64_t) live << 32) / sw->ratio) * sw->info.bytes_per_frame
+ );
+
+ return (((int64_t) live << 32) / sw->ratio) * sw->info.bytes_per_frame;
+}
+
+static size_t audio_get_free(SWVoiceOut *sw)
+{
+ size_t live, dead;
+
+ if (!sw) {
+ return 0;
+ }
+
+ live = sw->total_hw_samples_mixed;
+
+ if (audio_bug(__func__, live > sw->hw->mix_buf->size)) {
+ dolog("live=%zu sw->hw->mix_buf->size=%zu\n", live,
+ sw->hw->mix_buf->size);
+ return 0;
+ }
+
+ dead = sw->hw->mix_buf->size - live;
+
+#ifdef DEBUG_OUT
+ dolog ("%s: get_free live %zu dead %zu ret %" PRId64 "\n",
+ SW_NAME (sw),
+ live, dead, (((int64_t) dead << 32) / sw->ratio) *
+ sw->info.bytes_per_frame);
+#endif
+
+ return (((int64_t) dead << 32) / sw->ratio) * sw->info.bytes_per_frame;
+}
+
+static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
+ size_t samples)
+{
+ size_t 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) {
+ size_t till_end_of_hw = hw->mix_buf->size - rpos2;
+ size_t to_write = MIN(till_end_of_hw, n);
+ size_t bytes = to_write * hw->info.bytes_per_frame;
+ size_t written;
+
+ sw->buf = hw->mix_buf->samples + rpos2;
+ written = audio_pcm_sw_write (sw, NULL, bytes);
+ if (written - bytes) {
+ dolog("Could not mix %zu bytes into a capture "
+ "buffer, mixed %zu\n",
+ bytes, written);
+ break;
+ }
+ n -= to_write;
+ rpos2 = (rpos2 + to_write) % hw->mix_buf->size;
+ }
+ }
+ }
+
+ n = MIN(samples, hw->mix_buf->size - rpos);
+ mixeng_clear(hw->mix_buf->samples + rpos, n);
+ mixeng_clear(hw->mix_buf->samples, samples - n);
+}
+
+static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
+{
+ size_t clipped = 0;
+
+ while (live) {
+ size_t size = live * hw->info.bytes_per_frame;
+ size_t decr, proc;
+ void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
+
+ if (size == 0) {
+ break;
+ }
+
+ decr = MIN(size / hw->info.bytes_per_frame, live);
+ if (buf) {
+ audio_pcm_hw_clip_out(hw, buf, decr);
+ }
+ proc = hw->pcm_ops->put_buffer_out(hw, buf,
+ decr * hw->info.bytes_per_frame) /
+ hw->info.bytes_per_frame;
+
+ live -= proc;
+ clipped += proc;
+ hw->mix_buf->pos = (hw->mix_buf->pos + proc) % hw->mix_buf->size;
+
+ if (proc == 0 || proc < decr) {
+ break;
+ }
+ }
+
+ if (hw->pcm_ops->run_buffer_out) {
+ hw->pcm_ops->run_buffer_out(hw);
+ }
+
+ return clipped;
+}
+
+static void audio_run_out (AudioState *s)
+{
+ HWVoiceOut *hw = NULL;
+ SWVoiceOut *sw;
+
+ if (!audio_get_pdo_out(s->dev)->mixing_engine) {
+ while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
+ /* there is exactly 1 sw for each hw with no mixeng */
+ sw = hw->sw_head.lh_first;
+
+ if (hw->pending_disable) {
+ hw->enabled = 0;
+ hw->pending_disable = 0;
+ if (hw->pcm_ops->enable_out) {
+ hw->pcm_ops->enable_out(hw, false);
+ }
+ }
+
+ if (sw->active) {
+ sw->callback.fn(sw->callback.opaque, INT_MAX);
+ }
+ }
+ return;
+ }
+
+ while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
+ size_t played, live, prev_rpos, free;
+ int nb_live;
+
+ live = audio_pcm_hw_get_live_out (hw, &nb_live);
+ if (!nb_live) {
+ live = 0;
+ }
+
+ if (audio_bug(__func__, live > hw->mix_buf->size)) {
+ dolog("live=%zu hw->mix_buf->size=%zu\n", live, hw->mix_buf->size);
+ continue;
+ }
+
+ if (hw->pending_disable && !nb_live) {
+ SWVoiceCap *sc;
+#ifdef DEBUG_OUT
+ dolog ("Disabling voice\n");
+#endif
+ hw->enabled = 0;
+ hw->pending_disable = 0;
+ if (hw->pcm_ops->enable_out) {
+ hw->pcm_ops->enable_out(hw, false);
+ }
+ 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);
+ }
+ }
+ }
+ if (hw->pcm_ops->run_buffer_out) {
+ hw->pcm_ops->run_buffer_out(hw);
+ }
+ continue;
+ }
+
+ prev_rpos = hw->mix_buf->pos;
+ played = audio_pcm_hw_run_out(hw, live);
+ replay_audio_out(&played);
+ if (audio_bug(__func__, hw->mix_buf->pos >= hw->mix_buf->size)) {
+ dolog("hw->mix_buf->pos=%zu hw->mix_buf->size=%zu played=%zu\n",
+ hw->mix_buf->pos, hw->mix_buf->size, played);
+ hw->mix_buf->pos = 0;
+ }
+
+#ifdef DEBUG_OUT
+ dolog("played=%zu\n", played);
+#endif
+
+ if (played) {
+ hw->ts_helper += played;
+ audio_capture_mix_and_clear (hw, prev_rpos, played);
+ }
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (!sw->active && sw->empty) {
+ continue;
+ }
+
+ if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {
+ dolog("played=%zu sw->total_hw_samples_mixed=%zu\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;
+ }
+
+ if (sw->active) {
+ free = audio_get_free (sw);
+ if (free > 0) {
+ sw->callback.fn (sw->callback.opaque, free);
+ }
+ }
+ }
+ }
+}
+
+static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
+{
+ size_t conv = 0;
+ STSampleBuffer *conv_buf = hw->conv_buf;
+
+ if (hw->pcm_ops->run_buffer_in) {
+ hw->pcm_ops->run_buffer_in(hw);
+ }
+
+ while (samples) {
+ size_t proc;
+ size_t size = samples * hw->info.bytes_per_frame;
+ void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
+
+ assert(size % hw->info.bytes_per_frame == 0);
+ if (size == 0) {
+ break;
+ }
+
+ proc = MIN(size / hw->info.bytes_per_frame,
+ conv_buf->size - conv_buf->pos);
+
+ hw->conv(conv_buf->samples + conv_buf->pos, buf, proc);
+ conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
+
+ samples -= proc;
+ conv += proc;
+ hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
+ }
+
+ return conv;
+}
+
+static void audio_run_in (AudioState *s)
+{
+ HWVoiceIn *hw = NULL;
+
+ if (!audio_get_pdo_in(s->dev)->mixing_engine) {
+ while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
+ /* there is exactly 1 sw for each hw with no mixeng */
+ SWVoiceIn *sw = hw->sw_head.lh_first;
+ if (sw->active) {
+ sw->callback.fn(sw->callback.opaque, INT_MAX);
+ }
+ }
+ return;
+ }
+
+ while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
+ SWVoiceIn *sw;
+ size_t captured = 0, min;
+
+ if (replay_mode != REPLAY_MODE_PLAY) {
+ captured = audio_pcm_hw_run_in(
+ hw, hw->conv_buf->size - audio_pcm_hw_get_live_in(hw));
+ }
+ replay_audio_in(&captured, hw->conv_buf->samples, &hw->conv_buf->pos,
+ hw->conv_buf->size);
+
+ 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) {
+ size_t 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) {
+ size_t live, rpos, captured;
+ HWVoiceOut *hw = &cap->hw;
+ SWVoiceOut *sw;
+
+ captured = live = audio_pcm_hw_get_live_out (hw, NULL);
+ rpos = hw->mix_buf->pos;
+ while (live) {
+ size_t left = hw->mix_buf->size - rpos;
+ size_t to_capture = MIN(live, left);
+ struct st_sample *src;
+ struct capture_callback *cb;
+
+ src = hw->mix_buf->samples + 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.bytes_per_frame);
+ }
+ rpos = (rpos + to_capture) % hw->mix_buf->size;
+ live -= to_capture;
+ }
+ hw->mix_buf->pos = rpos;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (!sw->active && sw->empty) {
+ continue;
+ }
+
+ if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {
+ dolog("captured=%zu sw->total_hw_samples_mixed=%zu\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(AudioState *s, const char *msg)
+{
+ 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
+}
+
+void audio_generic_run_buffer_in(HWVoiceIn *hw)
+{
+ if (unlikely(!hw->buf_emul)) {
+ hw->size_emul = hw->samples * hw->info.bytes_per_frame;
+ hw->buf_emul = g_malloc(hw->size_emul);
+ hw->pos_emul = hw->pending_emul = 0;
+ }
+
+ while (hw->pending_emul < hw->size_emul) {
+ size_t read_len = MIN(hw->size_emul - hw->pos_emul,
+ hw->size_emul - hw->pending_emul);
+ size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
+ read_len);
+ hw->pending_emul += read;
+ hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;
+ if (read < read_len) {
+ break;
+ }
+ }
+}
+
+void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
+{
+ ssize_t start = (ssize_t)hw->pos_emul - hw->pending_emul;
+
+ if (start < 0) {
+ start += hw->size_emul;
+ }
+ assert(start >= 0 && start < hw->size_emul);
+
+ *size = MIN(*size, hw->pending_emul);
+ *size = MIN(*size, hw->size_emul - start);
+ return hw->buf_emul + start;
+}
+
+void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
+{
+ assert(size <= hw->pending_emul);
+ hw->pending_emul -= size;
+}
+
+void audio_generic_run_buffer_out(HWVoiceOut *hw)
+{
+ while (hw->pending_emul) {
+ size_t write_len, written;
+ ssize_t start = ((ssize_t) hw->pos_emul) - hw->pending_emul;
+
+ if (start < 0) {
+ start += hw->size_emul;
+ }
+ assert(start >= 0 && start < hw->size_emul);
+
+ write_len = MIN(hw->pending_emul, hw->size_emul - start);
+
+ written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
+ hw->pending_emul -= written;
+
+ if (written < write_len) {
+ break;
+ }
+ }
+}
+
+void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+ if (unlikely(!hw->buf_emul)) {
+ hw->size_emul = hw->samples * hw->info.bytes_per_frame;
+ hw->buf_emul = g_malloc(hw->size_emul);
+ hw->pos_emul = hw->pending_emul = 0;
+ }
+
+ *size = MIN(hw->size_emul - hw->pending_emul,
+ hw->size_emul - hw->pos_emul);
+ return hw->buf_emul + hw->pos_emul;
+}
+
+size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
+{
+ assert(buf == hw->buf_emul + hw->pos_emul &&
+ size + hw->pending_emul <= hw->size_emul);
+
+ hw->pending_emul += size;
+ hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
+
+ return size;
+}
+
+size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
+{
+ size_t total = 0;
+
+ while (total < size) {
+ size_t dst_size = size - total;
+ size_t copy_size, proc;
+ void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
+
+ if (dst_size == 0) {
+ break;
+ }
+
+ copy_size = MIN(size - total, dst_size);
+ if (dst) {
+ memcpy(dst, (char *)buf + total, copy_size);
+ }
+ proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);
+ total += proc;
+
+ if (proc == 0 || proc < copy_size) {
+ break;
+ }
+ }
+
+ if (hw->pcm_ops->run_buffer_out) {
+ hw->pcm_ops->run_buffer_out(hw);
+ }
+
+ return total;
+}
+
+size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
+{
+ size_t total = 0;
+
+ if (hw->pcm_ops->run_buffer_in) {
+ hw->pcm_ops->run_buffer_in(hw);
+ }
+
+ while (total < size) {
+ size_t src_size = size - total;
+ void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);
+
+ if (src_size == 0) {
+ break;
+ }
+
+ memcpy((char *)buf + total, src, src_size);
+ hw->pcm_ops->put_buffer_in(hw, src, src_size);
+ total += src_size;
+ }
+
+ return total;
+}
+
+static int audio_driver_init(AudioState *s, struct audio_driver *drv,
+ bool msg, Audiodev *dev)
+{
+ s->drv_opaque = drv->init(dev);
+
+ if (s->drv_opaque) {
+ if (!drv->pcm_ops->get_buffer_in) {
+ drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
+ drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
+ }
+ if (!drv->pcm_ops->get_buffer_out) {
+ drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
+ drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
+ }
+
+ audio_init_nb_voices_out(s, drv);
+ audio_init_nb_voices_in(s, drv);
+ s->drv = drv;
+ return 0;
+ } else {
+ if (msg) {
+ dolog("Could not init `%s' audio driver\n", drv->name);
+ }
+ return -1;
+ }
+}
+
+static void audio_vm_change_state_handler (void *opaque, bool running,
+ RunState state)
+{
+ AudioState *s = opaque;
+ HWVoiceOut *hwo = NULL;
+ HWVoiceIn *hwi = NULL;
+
+ s->vm_running = running;
+ while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
+ if (hwo->pcm_ops->enable_out) {
+ hwo->pcm_ops->enable_out(hwo, running);
+ }
+ }
+
+ while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
+ if (hwi->pcm_ops->enable_in) {
+ hwi->pcm_ops->enable_in(hwi, running);
+ }
+ }
+ audio_reset_timer (s);
+}
+
+static void free_audio_state(AudioState *s)
+{
+ HWVoiceOut *hwo, *hwon;
+ HWVoiceIn *hwi, *hwin;
+
+ QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {
+ SWVoiceCap *sc;
+
+ if (hwo->enabled && hwo->pcm_ops->enable_out) {
+ hwo->pcm_ops->enable_out(hwo, false);
+ }
+ 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);
+ }
+ }
+ QLIST_REMOVE(hwo, entries);
+ }
+
+ QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {
+ if (hwi->enabled && hwi->pcm_ops->enable_in) {
+ hwi->pcm_ops->enable_in(hwi, false);
+ }
+ hwi->pcm_ops->fini_in (hwi);
+ QLIST_REMOVE(hwi, entries);
+ }
+
+ if (s->drv) {
+ s->drv->fini (s->drv_opaque);
+ s->drv = NULL;
+ }
+
+ if (s->dev) {
+ qapi_free_Audiodev(s->dev);
+ s->dev = NULL;
+ }
+
+ if (s->ts) {
+ timer_free(s->ts);
+ s->ts = NULL;
+ }
+
+ g_free(s);
+}
+
+void audio_cleanup(void)
+{
+ while (!QTAILQ_EMPTY(&audio_states)) {
+ AudioState *s = QTAILQ_FIRST(&audio_states);
+ QTAILQ_REMOVE(&audio_states, s, list);
+ free_audio_state(s);
+ }
+}
+
+static bool vmstate_audio_needed(void *opaque)
+{
+ /*
+ * Never needed, this vmstate only exists in case
+ * an old qemu sends it to us.
+ */
+ return false;
+}
+
+static const VMStateDescription vmstate_audio = {
+ .name = "audio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = vmstate_audio_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void audio_validate_opts(Audiodev *dev, Error **errp);
+
+static AudiodevListEntry *audiodev_find(
+ AudiodevListHead *head, const char *drvname)
+{
+ AudiodevListEntry *e;
+ QSIMPLEQ_FOREACH(e, head, next) {
+ if (strcmp(AudiodevDriver_str(e->dev->driver), drvname) == 0) {
+ return e;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * if we have dev, this function was called because of an -audiodev argument =>
+ * initialize a new state with it
+ * if dev == NULL => legacy implicit initialization, return the already created
+ * state or create a new one
+ */
+static AudioState *audio_init(Audiodev *dev, const char *name)
+{
+ static bool atexit_registered;
+ size_t i;
+ int done = 0;
+ const char *drvname = NULL;
+ VMChangeStateEntry *e;
+ AudioState *s;
+ struct audio_driver *driver;
+ /* silence gcc warning about uninitialized variable */
+ AudiodevListHead head = QSIMPLEQ_HEAD_INITIALIZER(head);
+
+ if (using_spice) {
+ /*
+ * When using spice allow the spice audio driver being picked
+ * as default.
+ *
+ * Temporary hack. Using audio devices without explicit
+ * audiodev= property is already deprecated. Same goes for
+ * the -soundhw switch. Once this support gets finally
+ * removed we can also drop the concept of a default audio
+ * backend and this can go away.
+ */
+ driver = audio_driver_lookup("spice");
+ if (driver) {
+ driver->can_be_default = 1;
+ }
+ }
+
+ if (dev) {
+ /* -audiodev option */
+ legacy_config = false;
+ drvname = AudiodevDriver_str(dev->driver);
+ } else if (!QTAILQ_EMPTY(&audio_states)) {
+ if (!legacy_config) {
+ dolog("Device %s: audiodev default parameter is deprecated, please "
+ "specify audiodev=%s\n", name,
+ QTAILQ_FIRST(&audio_states)->dev->id);
+ }
+ return QTAILQ_FIRST(&audio_states);
+ } else {
+ /* legacy implicit initialization */
+ head = audio_handle_legacy_opts();
+ /*
+ * In case of legacy initialization, all Audiodevs in the list will have
+ * the same configuration (except the driver), so it doesn't matter which
+ * one we chose. We need an Audiodev to set up AudioState before we can
+ * init a driver. Also note that dev at this point is still in the
+ * list.
+ */
+ dev = QSIMPLEQ_FIRST(&head)->dev;
+ audio_validate_opts(dev, &error_abort);
+ }
+
+ s = g_malloc0(sizeof(AudioState));
+ s->dev = dev;
+
+ QLIST_INIT (&s->hw_head_out);
+ QLIST_INIT (&s->hw_head_in);
+ QLIST_INIT (&s->cap_head);
+ if (!atexit_registered) {
+ atexit(audio_cleanup);
+ atexit_registered = true;
+ }
+ QTAILQ_INSERT_TAIL(&audio_states, s, list);
+
+ s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
+
+ s->nb_hw_voices_out = audio_get_pdo_out(dev)->voices;
+ s->nb_hw_voices_in = audio_get_pdo_in(dev)->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;
+ }
+
+ if (drvname) {
+ driver = audio_driver_lookup(drvname);
+ if (driver) {
+ done = !audio_driver_init(s, driver, true, dev);
+ } else {
+ dolog ("Unknown audio driver `%s'\n", drvname);
+ }
+ } else {
+ for (i = 0; audio_prio_list[i]; i++) {
+ AudiodevListEntry *e = audiodev_find(&head, audio_prio_list[i]);
+ driver = audio_driver_lookup(audio_prio_list[i]);
+
+ if (e && driver) {
+ s->dev = dev = e->dev;
+ audio_validate_opts(dev, &error_abort);
+ done = !audio_driver_init(s, driver, false, dev);
+ if (done) {
+ e->dev = NULL;
+ break;
+ }
+ }
+ }
+ }
+ audio_free_audiodev_list(&head);
+
+ if (!done) {
+ driver = audio_driver_lookup("none");
+ done = !audio_driver_init(s, driver, false, dev);
+ assert(done);
+ dolog("warning: Using timer based audio emulation\n");
+ }
+
+ if (dev->timer_period <= 0) {
+ s->period_ticks = 1;
+ } else {
+ s->period_ticks = dev->timer_period * (int64_t)SCALE_US;
+ }
+
+ 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);
+ return s;
+}
+
+void audio_free_audiodev_list(AudiodevListHead *head)
+{
+ AudiodevListEntry *e;
+ while ((e = QSIMPLEQ_FIRST(head))) {
+ QSIMPLEQ_REMOVE_HEAD(head, next);
+ qapi_free_Audiodev(e->dev);
+ g_free(e);
+ }
+}
+
+void AUD_register_card (const char *name, QEMUSoundCard *card)
+{
+ if (!card->state) {
+ card->state = audio_init(NULL, name);
+ }
+
+ card->name = g_strdup (name);
+ memset (&card->entries, 0, sizeof (card->entries));
+ QLIST_INSERT_HEAD(&card->state->card_head, card, entries);
+}
+
+void AUD_remove_card (QEMUSoundCard *card)
+{
+ QLIST_REMOVE (card, entries);
+ g_free (card->name);
+}
+
+
+CaptureVoiceOut *AUD_add_capture(
+ AudioState *s,
+ struct audsettings *as,
+ struct audio_capture_ops *ops,
+ void *cb_opaque
+ )
+{
+ CaptureVoiceOut *cap;
+ struct capture_callback *cb;
+
+ if (!s) {
+ if (!legacy_config) {
+ dolog("Capturing without setting an audiodev is deprecated\n");
+ }
+ s = audio_init(NULL, NULL);
+ }
+
+ if (!audio_get_pdo_out(s->dev)->mixing_engine) {
+ dolog("Can't capture with mixeng disabled\n");
+ return NULL;
+ }
+
+ if (audio_validate_settings (as)) {
+ dolog ("Invalid settings were passed when trying to add capture\n");
+ audio_print_settings (as);
+ return NULL;
+ }
+
+ cb = g_malloc0(sizeof(*cb));
+ cb->ops = *ops;
+ cb->opaque = cb_opaque;
+
+ cap = audio_pcm_capture_find_specific(s, as);
+ if (cap) {
+ QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
+ return cap;
+ } else {
+ HWVoiceOut *hw;
+ CaptureVoiceOut *cap;
+
+ cap = g_malloc0(sizeof(*cap));
+
+ hw = &cap->hw;
+ hw->s = s;
+ QLIST_INIT (&hw->sw_head);
+ QLIST_INIT (&cap->cb_head);
+
+ /* XXX find a more elegant way */
+ hw->samples = 4096 * 4;
+ audio_pcm_hw_alloc_resources_out(hw);
+
+ audio_pcm_init_info (&hw->info, as);
+
+ cap->buf = g_malloc0_n(hw->mix_buf->size, hw->info.bytes_per_frame);
+
+ if (hw->info.is_float) {
+ hw->clip = mixeng_clip_float[hw->info.nchannels == 2];
+ } else {
+ hw->clip = mixeng_clip
+ [hw->info.nchannels == 2]
+ [hw->info.is_signed]
+ [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);
+
+ QLIST_FOREACH(hw, &s->hw_head_out, entries) {
+ audio_attach_capture (hw);
+ }
+ return cap;
+ }
+}
+
+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->hw.mix_buf);
+ g_free (cap->buf);
+ g_free (cap);
+ }
+ return;
+ }
+ }
+}
+
+void AUD_set_volume_out (SWVoiceOut *sw, int mute, uint8_t lvol, uint8_t rvol)
+{
+ Volume vol = { .mute = mute, .channels = 2, .vol = { lvol, rvol } };
+ audio_set_volume_out(sw, &vol);
+}
+
+void audio_set_volume_out(SWVoiceOut *sw, Volume *vol)
+{
+ if (sw) {
+ HWVoiceOut *hw = sw->hw;
+
+ sw->vol.mute = vol->mute;
+ sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
+ sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] /
+ 255;
+
+ if (hw->pcm_ops->volume_out) {
+ hw->pcm_ops->volume_out(hw, vol);
+ }
+ }
+}
+
+void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol)
+{
+ Volume vol = { .mute = mute, .channels = 2, .vol = { lvol, rvol } };
+ audio_set_volume_in(sw, &vol);
+}
+
+void audio_set_volume_in(SWVoiceIn *sw, Volume *vol)
+{
+ if (sw) {
+ HWVoiceIn *hw = sw->hw;
+
+ sw->vol.mute = vol->mute;
+ sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
+ sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] /
+ 255;
+
+ if (hw->pcm_ops->volume_in) {
+ hw->pcm_ops->volume_in(hw, vol);
+ }
+ }
+}
+
+void audio_create_pdos(Audiodev *dev)
+{
+ switch (dev->driver) {
+#define CASE(DRIVER, driver, pdo_name) \
+ case AUDIODEV_DRIVER_##DRIVER: \
+ if (!dev->u.driver.has_in) { \
+ dev->u.driver.in = g_malloc0( \
+ sizeof(Audiodev##pdo_name##PerDirectionOptions)); \
+ dev->u.driver.has_in = true; \
+ } \
+ if (!dev->u.driver.has_out) { \
+ dev->u.driver.out = g_malloc0( \
+ sizeof(Audiodev##pdo_name##PerDirectionOptions)); \
+ dev->u.driver.has_out = true; \
+ } \
+ break
+
+ CASE(NONE, none, );
+ CASE(ALSA, alsa, Alsa);
+ CASE(COREAUDIO, coreaudio, Coreaudio);
+ CASE(DSOUND, dsound, );
+ CASE(JACK, jack, Jack);
+ CASE(OSS, oss, Oss);
+ CASE(PA, pa, Pa);
+ CASE(SDL, sdl, Sdl);
+ CASE(SPICE, spice, );
+ CASE(WAV, wav, );
+
+ case AUDIODEV_DRIVER__MAX:
+ abort();
+ };
+}
+
+static void audio_validate_per_direction_opts(
+ AudiodevPerDirectionOptions *pdo, Error **errp)
+{
+ if (!pdo->has_mixing_engine) {
+ pdo->has_mixing_engine = true;
+ pdo->mixing_engine = true;
+ }
+ if (!pdo->has_fixed_settings) {
+ pdo->has_fixed_settings = true;
+ pdo->fixed_settings = pdo->mixing_engine;
+ }
+ if (!pdo->fixed_settings &&
+ (pdo->has_frequency || pdo->has_channels || pdo->has_format)) {
+ error_setg(errp,
+ "You can't use frequency, channels or format with fixed-settings=off");
+ return;
+ }
+ if (!pdo->mixing_engine && pdo->fixed_settings) {
+ error_setg(errp, "You can't use fixed-settings without mixeng");
+ return;
+ }
+
+ if (!pdo->has_frequency) {
+ pdo->has_frequency = true;
+ pdo->frequency = 44100;
+ }
+ if (!pdo->has_channels) {
+ pdo->has_channels = true;
+ pdo->channels = 2;
+ }
+ if (!pdo->has_voices) {
+ pdo->has_voices = true;
+ pdo->voices = pdo->mixing_engine ? 1 : INT_MAX;
+ }
+ if (!pdo->has_format) {
+ pdo->has_format = true;
+ pdo->format = AUDIO_FORMAT_S16;
+ }
+}
+
+static void audio_validate_opts(Audiodev *dev, Error **errp)
+{
+ Error *err = NULL;
+
+ audio_create_pdos(dev);
+
+ audio_validate_per_direction_opts(audio_get_pdo_in(dev), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ audio_validate_per_direction_opts(audio_get_pdo_out(dev), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ if (!dev->has_timer_period) {
+ dev->has_timer_period = true;
+ dev->timer_period = 10000; /* 100Hz -> 10ms */
+ }
+}
+
+void audio_parse_option(const char *opt)
+{
+ AudiodevListEntry *e;
+ Audiodev *dev = NULL;
+
+ Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
+ visit_type_Audiodev(v, NULL, &dev, &error_fatal);
+ visit_free(v);
+
+ audio_validate_opts(dev, &error_fatal);
+
+ e = g_malloc0(sizeof(AudiodevListEntry));
+ e->dev = dev;
+ QSIMPLEQ_INSERT_TAIL(&audiodevs, e, next);
+}
+
+void audio_init_audiodevs(void)
+{
+ AudiodevListEntry *e;
+
+ QSIMPLEQ_FOREACH(e, &audiodevs, next) {
+ audio_init(e->dev, NULL);
+ }
+}
+
+audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
+{
+ return (audsettings) {
+ .freq = pdo->frequency,
+ .nchannels = pdo->channels,
+ .fmt = pdo->format,
+ .endianness = AUDIO_HOST_ENDIANNESS,
+ };
+}
+
+int audioformat_bytes_per_sample(AudioFormat fmt)
+{
+ switch (fmt) {
+ case AUDIO_FORMAT_U8:
+ case AUDIO_FORMAT_S8:
+ return 1;
+
+ case AUDIO_FORMAT_U16:
+ case AUDIO_FORMAT_S16:
+ return 2;
+
+ case AUDIO_FORMAT_U32:
+ case AUDIO_FORMAT_S32:
+ case AUDIO_FORMAT_F32:
+ return 4;
+
+ case AUDIO_FORMAT__MAX:
+ ;
+ }
+ abort();
+}
+
+
+/* frames = freq * usec / 1e6 */
+int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
+ audsettings *as, int def_usecs)
+{
+ uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;
+ return (as->freq * usecs + 500000) / 1000000;
+}
+
+/* samples = channels * frames = channels * freq * usec / 1e6 */
+int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
+ audsettings *as, int def_usecs)
+{
+ return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
+}
+
+/*
+ * bytes = bytes_per_sample * samples =
+ * bytes_per_sample * channels * freq * usec / 1e6
+ */
+int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
+ audsettings *as, int def_usecs)
+{
+ return audio_buffer_samples(pdo, as, def_usecs) *
+ audioformat_bytes_per_sample(as->fmt);
+}
+
+AudioState *audio_state_by_name(const char *name)
+{
+ AudioState *s;
+ QTAILQ_FOREACH(s, &audio_states, list) {
+ assert(s->dev);
+ if (strcmp(name, s->dev->id) == 0) {
+ return s;
+ }
+ }
+ return NULL;
+}
+
+const char *audio_get_id(QEMUSoundCard *card)
+{
+ if (card->state) {
+ assert(card->state->dev);
+ return card->state->dev->id;
+ } else {
+ return "";
+ }
+}
+
+const char *audio_application_name(void)
+{
+ const char *vm_name;
+
+ vm_name = qemu_get_vm_name();
+ return vm_name ? vm_name : "qemu";
+}
+
+void audio_rate_start(RateCtl *rate)
+{
+ memset(rate, 0, sizeof(RateCtl));
+ rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+size_t audio_rate_get_bytes(struct audio_pcm_info *info, RateCtl *rate,
+ size_t bytes_avail)
+{
+ int64_t now;
+ int64_t ticks;
+ int64_t bytes;
+ int64_t samples;
+ size_t ret;
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ticks = now - rate->start_ticks;
+ bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
+ samples = (bytes - rate->bytes_sent) / info->bytes_per_frame;
+ if (samples < 0 || samples > 65536) {
+ AUD_log(NULL, "Resetting rate control (%" PRId64 " samples)\n", samples);
+ audio_rate_start(rate);
+ samples = 0;
+ }
+
+ ret = MIN(samples * info->bytes_per_frame, bytes_avail);
+ rate->bytes_sent += ret;
+ return ret;
+}
diff --git a/audio/audio.h b/audio/audio.h
new file mode 100644
index 000000000..c8bde536b
--- /dev/null
+++ b/audio/audio.h
@@ -0,0 +1,181 @@
+/*
+ * 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 "qemu/queue.h"
+#include "qapi/qapi-types-audio.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+
+typedef void (*audio_callback_fn) (void *opaque, int avail);
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define AUDIO_HOST_ENDIANNESS 1
+#else
+#define AUDIO_HOST_ENDIANNESS 0
+#endif
+
+typedef struct audsettings {
+ int freq;
+ int nchannels;
+ AudioFormat fmt;
+ int endianness;
+} audsettings;
+
+audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo);
+int audioformat_bytes_per_sample(AudioFormat fmt);
+int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
+ audsettings *as, int def_usecs);
+int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
+ audsettings *as, int def_usecs);
+int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
+ audsettings *as, int def_usecs);
+
+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, const 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 AudioState AudioState;
+typedef struct QEMUSoundCard {
+ char *name;
+ AudioState *state;
+ 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_register_card (const char *name, QEMUSoundCard *card);
+void AUD_remove_card (QEMUSoundCard *card);
+CaptureVoiceOut *AUD_add_capture(
+ AudioState *s,
+ 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);
+size_t AUD_write (SWVoiceOut *sw, void *pcm_buf, size_t 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);
+
+#define AUDIO_MAX_CHANNELS 16
+typedef struct Volume {
+ bool mute;
+ int channels;
+ uint8_t vol[AUDIO_MAX_CHANNELS];
+} Volume;
+
+void audio_set_volume_out(SWVoiceOut *sw, Volume *vol);
+void audio_set_volume_in(SWVoiceIn *sw, Volume *vol);
+
+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);
+size_t AUD_read (SWVoiceIn *sw, void *pcm_buf, size_t 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);
+}
+
+int wav_start_capture(AudioState *state, CaptureState *s, const char *path,
+ int freq, int bits, int nchannels);
+
+void audio_cleanup(void);
+
+void audio_sample_to_uint64(const void *samples, int pos,
+ uint64_t *left, uint64_t *right);
+void audio_sample_from_uint64(void *samples, int pos,
+ uint64_t left, uint64_t right);
+
+void audio_parse_option(const char *opt);
+void audio_init_audiodevs(void);
+void audio_legacy_help(void);
+
+AudioState *audio_state_by_name(const char *name);
+const char *audio_get_id(QEMUSoundCard *card);
+
+#define DEFINE_AUDIO_PROPERTIES(_s, _f) \
+ DEFINE_PROP_AUDIODEV("audiodev", _s, _f)
+
+#endif /* QEMU_AUDIO_H */
diff --git a/audio/audio_int.h b/audio/audio_int.h
new file mode 100644
index 000000000..6d685e24a
--- /dev/null
+++ b/audio/audio_int.h
@@ -0,0 +1,287 @@
+/*
+ * 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_AUDIO_COREAUDIO
+#define FLOAT_MIXENG
+/* #define RECIPROCAL */
+#endif
+#include "mixeng.h"
+
+struct audio_pcm_ops;
+
+struct audio_callback {
+ void *opaque;
+ audio_callback_fn fn;
+};
+
+struct audio_pcm_info {
+ int bits;
+ bool is_signed;
+ bool is_float;
+ int freq;
+ int nchannels;
+ int bytes_per_frame;
+ int bytes_per_second;
+ int swap_endianness;
+};
+
+typedef struct AudioState AudioState;
+typedef struct SWVoiceCap SWVoiceCap;
+
+typedef struct STSampleBuffer {
+ size_t pos, size;
+ st_sample samples[];
+} STSampleBuffer;
+
+typedef struct HWVoiceOut {
+ AudioState *s;
+ int enabled;
+ int poll_mode;
+ int pending_disable;
+ struct audio_pcm_info info;
+
+ f_sample *clip;
+ uint64_t ts_helper;
+
+ STSampleBuffer *mix_buf;
+ void *buf_emul;
+ size_t pos_emul, pending_emul, size_emul;
+
+ size_t samples;
+ QLIST_HEAD (sw_out_listhead, SWVoiceOut) sw_head;
+ QLIST_HEAD (sw_cap_listhead, SWVoiceCap) cap_head;
+ struct audio_pcm_ops *pcm_ops;
+ QLIST_ENTRY (HWVoiceOut) entries;
+} HWVoiceOut;
+
+typedef struct HWVoiceIn {
+ AudioState *s;
+ int enabled;
+ int poll_mode;
+ struct audio_pcm_info info;
+
+ t_sample *conv;
+
+ size_t total_samples_captured;
+ uint64_t ts_helper;
+
+ STSampleBuffer *conv_buf;
+ void *buf_emul;
+ size_t pos_emul, pending_emul, size_emul;
+
+ size_t samples;
+ QLIST_HEAD (sw_in_listhead, SWVoiceIn) sw_head;
+ struct audio_pcm_ops *pcm_ops;
+ QLIST_ENTRY (HWVoiceIn) entries;
+} HWVoiceIn;
+
+struct SWVoiceOut {
+ QEMUSoundCard *card;
+ AudioState *s;
+ struct audio_pcm_info info;
+ t_sample *conv;
+ int64_t ratio;
+ struct st_sample *buf;
+ void *rate;
+ size_t 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;
+ AudioState *s;
+ int active;
+ struct audio_pcm_info info;
+ int64_t ratio;
+ void *rate;
+ size_t 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;
+};
+
+typedef struct audio_driver audio_driver;
+struct audio_driver {
+ const char *name;
+ const char *descr;
+ void *(*init) (Audiodev *);
+ 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;
+ QLIST_ENTRY(audio_driver) next;
+};
+
+struct audio_pcm_ops {
+ int (*init_out)(HWVoiceOut *hw, audsettings *as, void *drv_opaque);
+ void (*fini_out)(HWVoiceOut *hw);
+ size_t (*write) (HWVoiceOut *hw, void *buf, size_t size);
+ void (*run_buffer_out)(HWVoiceOut *hw);
+ /*
+ * get a buffer that after later can be passed to put_buffer_out; optional
+ * returns the buffer, and writes it's size to size (in bytes)
+ * this is unrelated to the above buffer_size_out function
+ */
+ void *(*get_buffer_out)(HWVoiceOut *hw, size_t *size);
+ /*
+ * put back the buffer returned by get_buffer_out; optional
+ * buf must be equal the pointer returned by get_buffer_out,
+ * size may be smaller
+ */
+ size_t (*put_buffer_out)(HWVoiceOut *hw, void *buf, size_t size);
+ void (*enable_out)(HWVoiceOut *hw, bool enable);
+ void (*volume_out)(HWVoiceOut *hw, Volume *vol);
+
+ int (*init_in) (HWVoiceIn *hw, audsettings *as, void *drv_opaque);
+ void (*fini_in) (HWVoiceIn *hw);
+ size_t (*read) (HWVoiceIn *hw, void *buf, size_t size);
+ void (*run_buffer_in)(HWVoiceIn *hw);
+ void *(*get_buffer_in)(HWVoiceIn *hw, size_t *size);
+ void (*put_buffer_in)(HWVoiceIn *hw, void *buf, size_t size);
+ void (*enable_in)(HWVoiceIn *hw, bool enable);
+ void (*volume_in)(HWVoiceIn *hw, Volume *vol);
+};
+
+void audio_generic_run_buffer_in(HWVoiceIn *hw);
+void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size);
+void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size);
+void audio_generic_run_buffer_out(HWVoiceOut *hw);
+void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size);
+size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size);
+size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size);
+size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size);
+
+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;
+};
+
+typedef struct AudioState {
+ struct audio_driver *drv;
+ Audiodev *dev;
+ 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;
+ int64_t period_ticks;
+
+ bool timer_running;
+ uint64_t timer_last;
+
+ QTAILQ_ENTRY(AudioState) list;
+} AudioState;
+
+extern const struct mixeng_volume nominal_volume;
+
+extern const char *audio_prio_list[];
+
+void audio_driver_register(audio_driver *drv);
+audio_driver *audio_driver_lookup(const char *name);
+
+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_bug (const char *funcname, int cond);
+void *audio_calloc (const char *funcname, int nmemb, size_t size);
+
+void audio_run(AudioState *s, const char *msg);
+
+const char *audio_application_name(void);
+
+typedef struct RateCtl {
+ int64_t start_ticks;
+ int64_t bytes_sent;
+} RateCtl;
+
+void audio_rate_start(RateCtl *rate);
+size_t audio_rate_get_bytes(struct audio_pcm_info *info, RateCtl *rate,
+ size_t bytes_avail);
+
+static inline size_t audio_ring_dist(size_t dst, size_t src, size_t 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)
+
+typedef struct AudiodevListEntry {
+ Audiodev *dev;
+ QSIMPLEQ_ENTRY(AudiodevListEntry) next;
+} AudiodevListEntry;
+
+typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
+AudiodevListHead audio_handle_legacy_opts(void);
+
+void audio_free_audiodev_list(AudiodevListHead *head);
+
+void audio_create_pdos(Audiodev *dev);
+AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev);
+AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev);
+
+#endif /* QEMU_AUDIO_INT_H */
diff --git a/audio/audio_legacy.c b/audio/audio_legacy.c
new file mode 100644
index 000000000..0fe827b05
--- /dev/null
+++ b/audio/audio_legacy.c
@@ -0,0 +1,555 @@
+/*
+ * QEMU Audio subsystem: legacy configuration handling
+ *
+ * Copyright (c) 2015-2019 Zoltán Kővágó <DirtY.iCE.hu@gmail.com>
+ *
+ * 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/osdep.h"
+#include "audio.h"
+#include "audio_int.h"
+#include "qemu/cutils.h"
+#include "qemu/timer.h"
+#include "qapi/error.h"
+#include "qapi/qapi-visit-audio.h"
+#include "qapi/visitor-impl.h"
+
+#define AUDIO_CAP "audio-legacy"
+#include "audio_int.h"
+
+static uint32_t toui32(const char *str)
+{
+ unsigned long long ret;
+ if (parse_uint_full(str, &ret, 10) || ret > UINT32_MAX) {
+ dolog("Invalid integer value `%s'\n", str);
+ exit(1);
+ }
+ return ret;
+}
+
+/* helper functions to convert env variables */
+static void get_bool(const char *env, bool *dst, bool *has_dst)
+{
+ const char *val = getenv(env);
+ if (val) {
+ *dst = toui32(val) != 0;
+ *has_dst = true;
+ }
+}
+
+static void get_int(const char *env, uint32_t *dst, bool *has_dst)
+{
+ const char *val = getenv(env);
+ if (val) {
+ *dst = toui32(val);
+ *has_dst = true;
+ }
+}
+
+static void get_str(const char *env, char **dst, bool *has_dst)
+{
+ const char *val = getenv(env);
+ if (val) {
+ if (*has_dst) {
+ g_free(*dst);
+ }
+ *dst = g_strdup(val);
+ *has_dst = true;
+ }
+}
+
+static void get_fmt(const char *env, AudioFormat *dst, bool *has_dst)
+{
+ const char *val = getenv(env);
+ if (val) {
+ size_t i;
+ for (i = 0; AudioFormat_lookup.size; ++i) {
+ if (strcasecmp(val, AudioFormat_lookup.array[i]) == 0) {
+ *dst = i;
+ *has_dst = true;
+ return;
+ }
+ }
+
+ dolog("Invalid audio format `%s'\n", val);
+ exit(1);
+ }
+}
+
+
+static void get_millis_to_usecs(const char *env, uint32_t *dst, bool *has_dst)
+{
+ const char *val = getenv(env);
+ if (val) {
+ *dst = toui32(val) * 1000;
+ *has_dst = true;
+ }
+}
+
+static uint32_t frames_to_usecs(uint32_t frames,
+ AudiodevPerDirectionOptions *pdo)
+{
+ uint32_t freq = pdo->has_frequency ? pdo->frequency : 44100;
+ return (frames * 1000000 + freq / 2) / freq;
+}
+
+
+static void get_frames_to_usecs(const char *env, uint32_t *dst, bool *has_dst,
+ AudiodevPerDirectionOptions *pdo)
+{
+ const char *val = getenv(env);
+ if (val) {
+ *dst = frames_to_usecs(toui32(val), pdo);
+ *has_dst = true;
+ }
+}
+
+static uint32_t samples_to_usecs(uint32_t samples,
+ AudiodevPerDirectionOptions *pdo)
+{
+ uint32_t channels = pdo->has_channels ? pdo->channels : 2;
+ return frames_to_usecs(samples / channels, pdo);
+}
+
+static void get_samples_to_usecs(const char *env, uint32_t *dst, bool *has_dst,
+ AudiodevPerDirectionOptions *pdo)
+{
+ const char *val = getenv(env);
+ if (val) {
+ *dst = samples_to_usecs(toui32(val), pdo);
+ *has_dst = true;
+ }
+}
+
+static uint32_t bytes_to_usecs(uint32_t bytes, AudiodevPerDirectionOptions *pdo)
+{
+ AudioFormat fmt = pdo->has_format ? pdo->format : AUDIO_FORMAT_S16;
+ uint32_t bytes_per_sample = audioformat_bytes_per_sample(fmt);
+ return samples_to_usecs(bytes / bytes_per_sample, pdo);
+}
+
+static void get_bytes_to_usecs(const char *env, uint32_t *dst, bool *has_dst,
+ AudiodevPerDirectionOptions *pdo)
+{
+ const char *val = getenv(env);
+ if (val) {
+ *dst = bytes_to_usecs(toui32(val), pdo);
+ *has_dst = true;
+ }
+}
+
+/* backend specific functions */
+/* ALSA */
+static void handle_alsa_per_direction(
+ AudiodevAlsaPerDirectionOptions *apdo, const char *prefix)
+{
+ char buf[64];
+ size_t len = strlen(prefix);
+ bool size_in_usecs = false;
+ bool dummy;
+
+ memcpy(buf, prefix, len);
+ strcpy(buf + len, "TRY_POLL");
+ get_bool(buf, &apdo->try_poll, &apdo->has_try_poll);
+
+ strcpy(buf + len, "DEV");
+ get_str(buf, &apdo->dev, &apdo->has_dev);
+
+ strcpy(buf + len, "SIZE_IN_USEC");
+ get_bool(buf, &size_in_usecs, &dummy);
+
+ strcpy(buf + len, "PERIOD_SIZE");
+ get_int(buf, &apdo->period_length, &apdo->has_period_length);
+ if (apdo->has_period_length && !size_in_usecs) {
+ apdo->period_length = frames_to_usecs(
+ apdo->period_length,
+ qapi_AudiodevAlsaPerDirectionOptions_base(apdo));
+ }
+
+ strcpy(buf + len, "BUFFER_SIZE");
+ get_int(buf, &apdo->buffer_length, &apdo->has_buffer_length);
+ if (apdo->has_buffer_length && !size_in_usecs) {
+ apdo->buffer_length = frames_to_usecs(
+ apdo->buffer_length,
+ qapi_AudiodevAlsaPerDirectionOptions_base(apdo));
+ }
+}
+
+static void handle_alsa(Audiodev *dev)
+{
+ AudiodevAlsaOptions *aopt = &dev->u.alsa;
+ handle_alsa_per_direction(aopt->in, "QEMU_ALSA_ADC_");
+ handle_alsa_per_direction(aopt->out, "QEMU_ALSA_DAC_");
+
+ get_millis_to_usecs("QEMU_ALSA_THRESHOLD",
+ &aopt->threshold, &aopt->has_threshold);
+}
+
+/* coreaudio */
+static void handle_coreaudio(Audiodev *dev)
+{
+ get_frames_to_usecs(
+ "QEMU_COREAUDIO_BUFFER_SIZE",
+ &dev->u.coreaudio.out->buffer_length,
+ &dev->u.coreaudio.out->has_buffer_length,
+ qapi_AudiodevCoreaudioPerDirectionOptions_base(dev->u.coreaudio.out));
+ get_int("QEMU_COREAUDIO_BUFFER_COUNT",
+ &dev->u.coreaudio.out->buffer_count,
+ &dev->u.coreaudio.out->has_buffer_count);
+}
+
+/* dsound */
+static void handle_dsound(Audiodev *dev)
+{
+ get_millis_to_usecs("QEMU_DSOUND_LATENCY_MILLIS",
+ &dev->u.dsound.latency, &dev->u.dsound.has_latency);
+ get_bytes_to_usecs("QEMU_DSOUND_BUFSIZE_OUT",
+ &dev->u.dsound.out->buffer_length,
+ &dev->u.dsound.out->has_buffer_length,
+ dev->u.dsound.out);
+ get_bytes_to_usecs("QEMU_DSOUND_BUFSIZE_IN",
+ &dev->u.dsound.in->buffer_length,
+ &dev->u.dsound.in->has_buffer_length,
+ dev->u.dsound.in);
+}
+
+/* OSS */
+static void handle_oss_per_direction(
+ AudiodevOssPerDirectionOptions *opdo, const char *try_poll_env,
+ const char *dev_env)
+{
+ get_bool(try_poll_env, &opdo->try_poll, &opdo->has_try_poll);
+ get_str(dev_env, &opdo->dev, &opdo->has_dev);
+
+ get_bytes_to_usecs("QEMU_OSS_FRAGSIZE",
+ &opdo->buffer_length, &opdo->has_buffer_length,
+ qapi_AudiodevOssPerDirectionOptions_base(opdo));
+ get_int("QEMU_OSS_NFRAGS", &opdo->buffer_count,
+ &opdo->has_buffer_count);
+}
+
+static void handle_oss(Audiodev *dev)
+{
+ AudiodevOssOptions *oopt = &dev->u.oss;
+ handle_oss_per_direction(oopt->in, "QEMU_AUDIO_ADC_TRY_POLL",
+ "QEMU_OSS_ADC_DEV");
+ handle_oss_per_direction(oopt->out, "QEMU_AUDIO_DAC_TRY_POLL",
+ "QEMU_OSS_DAC_DEV");
+
+ get_bool("QEMU_OSS_MMAP", &oopt->try_mmap, &oopt->has_try_mmap);
+ get_bool("QEMU_OSS_EXCLUSIVE", &oopt->exclusive, &oopt->has_exclusive);
+ get_int("QEMU_OSS_POLICY", &oopt->dsp_policy, &oopt->has_dsp_policy);
+}
+
+/* pulseaudio */
+static void handle_pa_per_direction(
+ AudiodevPaPerDirectionOptions *ppdo, const char *env)
+{
+ get_str(env, &ppdo->name, &ppdo->has_name);
+}
+
+static void handle_pa(Audiodev *dev)
+{
+ handle_pa_per_direction(dev->u.pa.in, "QEMU_PA_SOURCE");
+ handle_pa_per_direction(dev->u.pa.out, "QEMU_PA_SINK");
+
+ get_samples_to_usecs(
+ "QEMU_PA_SAMPLES", &dev->u.pa.in->buffer_length,
+ &dev->u.pa.in->has_buffer_length,
+ qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in));
+ get_samples_to_usecs(
+ "QEMU_PA_SAMPLES", &dev->u.pa.out->buffer_length,
+ &dev->u.pa.out->has_buffer_length,
+ qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out));
+
+ get_str("QEMU_PA_SERVER", &dev->u.pa.server, &dev->u.pa.has_server);
+}
+
+/* SDL */
+static void handle_sdl(Audiodev *dev)
+{
+ /* SDL is output only */
+ get_samples_to_usecs("QEMU_SDL_SAMPLES", &dev->u.sdl.out->buffer_length,
+ &dev->u.sdl.out->has_buffer_length,
+ qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.out));
+}
+
+/* wav */
+static void handle_wav(Audiodev *dev)
+{
+ get_int("QEMU_WAV_FREQUENCY",
+ &dev->u.wav.out->frequency, &dev->u.wav.out->has_frequency);
+ get_fmt("QEMU_WAV_FORMAT", &dev->u.wav.out->format,
+ &dev->u.wav.out->has_format);
+ get_int("QEMU_WAV_DAC_FIXED_CHANNELS",
+ &dev->u.wav.out->channels, &dev->u.wav.out->has_channels);
+ get_str("QEMU_WAV_PATH", &dev->u.wav.path, &dev->u.wav.has_path);
+}
+
+/* general */
+static void handle_per_direction(
+ AudiodevPerDirectionOptions *pdo, const char *prefix)
+{
+ char buf[64];
+ size_t len = strlen(prefix);
+
+ memcpy(buf, prefix, len);
+ strcpy(buf + len, "FIXED_SETTINGS");
+ get_bool(buf, &pdo->fixed_settings, &pdo->has_fixed_settings);
+
+ strcpy(buf + len, "FIXED_FREQ");
+ get_int(buf, &pdo->frequency, &pdo->has_frequency);
+
+ strcpy(buf + len, "FIXED_FMT");
+ get_fmt(buf, &pdo->format, &pdo->has_format);
+
+ strcpy(buf + len, "FIXED_CHANNELS");
+ get_int(buf, &pdo->channels, &pdo->has_channels);
+
+ strcpy(buf + len, "VOICES");
+ get_int(buf, &pdo->voices, &pdo->has_voices);
+}
+
+static AudiodevListEntry *legacy_opt(const char *drvname)
+{
+ AudiodevListEntry *e = g_malloc0(sizeof(AudiodevListEntry));
+ e->dev = g_malloc0(sizeof(Audiodev));
+ e->dev->id = g_strdup(drvname);
+ e->dev->driver = qapi_enum_parse(
+ &AudiodevDriver_lookup, drvname, -1, &error_abort);
+
+ audio_create_pdos(e->dev);
+
+ handle_per_direction(audio_get_pdo_in(e->dev), "QEMU_AUDIO_ADC_");
+ handle_per_direction(audio_get_pdo_out(e->dev), "QEMU_AUDIO_DAC_");
+
+ /* Original description: Timer period in HZ (0 - use lowest possible) */
+ get_int("QEMU_AUDIO_TIMER_PERIOD",
+ &e->dev->timer_period, &e->dev->has_timer_period);
+ if (e->dev->has_timer_period && e->dev->timer_period) {
+ e->dev->timer_period = NANOSECONDS_PER_SECOND / 1000 /
+ e->dev->timer_period;
+ }
+
+ switch (e->dev->driver) {
+ case AUDIODEV_DRIVER_ALSA:
+ handle_alsa(e->dev);
+ break;
+
+ case AUDIODEV_DRIVER_COREAUDIO:
+ handle_coreaudio(e->dev);
+ break;
+
+ case AUDIODEV_DRIVER_DSOUND:
+ handle_dsound(e->dev);
+ break;
+
+ case AUDIODEV_DRIVER_OSS:
+ handle_oss(e->dev);
+ break;
+
+ case AUDIODEV_DRIVER_PA:
+ handle_pa(e->dev);
+ break;
+
+ case AUDIODEV_DRIVER_SDL:
+ handle_sdl(e->dev);
+ break;
+
+ case AUDIODEV_DRIVER_WAV:
+ handle_wav(e->dev);
+ break;
+
+ default:
+ break;
+ }
+
+ return e;
+}
+
+AudiodevListHead audio_handle_legacy_opts(void)
+{
+ const char *drvname = getenv("QEMU_AUDIO_DRV");
+ AudiodevListHead head = QSIMPLEQ_HEAD_INITIALIZER(head);
+
+ if (drvname) {
+ AudiodevListEntry *e;
+ audio_driver *driver = audio_driver_lookup(drvname);
+ if (!driver) {
+ dolog("Unknown audio driver `%s'\n", drvname);
+ exit(1);
+ }
+ e = legacy_opt(drvname);
+ QSIMPLEQ_INSERT_TAIL(&head, e, next);
+ } else {
+ for (int i = 0; audio_prio_list[i]; i++) {
+ audio_driver *driver = audio_driver_lookup(audio_prio_list[i]);
+ if (driver && driver->can_be_default) {
+ AudiodevListEntry *e = legacy_opt(driver->name);
+ QSIMPLEQ_INSERT_TAIL(&head, e, next);
+ }
+ }
+ if (QSIMPLEQ_EMPTY(&head)) {
+ dolog("Internal error: no default audio driver available\n");
+ exit(1);
+ }
+ }
+
+ return head;
+}
+
+/* visitor to print -audiodev option */
+typedef struct {
+ Visitor visitor;
+
+ bool comma;
+ GList *path;
+} LegacyPrintVisitor;
+
+static bool lv_start_struct(Visitor *v, const char *name, void **obj,
+ size_t size, Error **errp)
+{
+ LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v;
+ lv->path = g_list_append(lv->path, g_strdup(name));
+ return true;
+}
+
+static void lv_end_struct(Visitor *v, void **obj)
+{
+ LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v;
+ lv->path = g_list_delete_link(lv->path, g_list_last(lv->path));
+}
+
+static void lv_print_key(Visitor *v, const char *name)
+{
+ GList *e;
+ LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v;
+ if (lv->comma) {
+ putchar(',');
+ } else {
+ lv->comma = true;
+ }
+
+ for (e = lv->path; e; e = e->next) {
+ if (e->data) {
+ printf("%s.", (const char *) e->data);
+ }
+ }
+
+ printf("%s=", name);
+}
+
+static bool lv_type_int64(Visitor *v, const char *name, int64_t *obj,
+ Error **errp)
+{
+ lv_print_key(v, name);
+ printf("%" PRIi64, *obj);
+ return true;
+}
+
+static bool lv_type_uint64(Visitor *v, const char *name, uint64_t *obj,
+ Error **errp)
+{
+ lv_print_key(v, name);
+ printf("%" PRIu64, *obj);
+ return true;
+}
+
+static bool lv_type_bool(Visitor *v, const char *name, bool *obj, Error **errp)
+{
+ lv_print_key(v, name);
+ printf("%s", *obj ? "on" : "off");
+ return true;
+}
+
+static bool lv_type_str(Visitor *v, const char *name, char **obj, Error **errp)
+{
+ const char *str = *obj;
+ lv_print_key(v, name);
+
+ while (*str) {
+ if (*str == ',') {
+ putchar(',');
+ }
+ putchar(*str++);
+ }
+ return true;
+}
+
+static void lv_complete(Visitor *v, void *opaque)
+{
+ LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v;
+ assert(lv->path == NULL);
+}
+
+static void lv_free(Visitor *v)
+{
+ LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v;
+
+ g_list_free_full(lv->path, g_free);
+ g_free(lv);
+}
+
+static Visitor *legacy_visitor_new(void)
+{
+ LegacyPrintVisitor *lv = g_malloc0(sizeof(LegacyPrintVisitor));
+
+ lv->visitor.start_struct = lv_start_struct;
+ lv->visitor.end_struct = lv_end_struct;
+ /* lists not supported */
+ lv->visitor.type_int64 = lv_type_int64;
+ lv->visitor.type_uint64 = lv_type_uint64;
+ lv->visitor.type_bool = lv_type_bool;
+ lv->visitor.type_str = lv_type_str;
+
+ lv->visitor.type = VISITOR_OUTPUT;
+ lv->visitor.complete = lv_complete;
+ lv->visitor.free = lv_free;
+
+ return &lv->visitor;
+}
+
+void audio_legacy_help(void)
+{
+ AudiodevListHead head;
+ AudiodevListEntry *e;
+
+ printf("Environment variable based configuration deprecated.\n");
+ printf("Please use the new -audiodev option.\n");
+
+ head = audio_handle_legacy_opts();
+ printf("\nEquivalent -audiodev to your current environment variables:\n");
+ if (!getenv("QEMU_AUDIO_DRV")) {
+ printf("(Since you didn't specify QEMU_AUDIO_DRV, I'll list all "
+ "possibilities)\n");
+ }
+
+ QSIMPLEQ_FOREACH(e, &head, next) {
+ Visitor *v;
+ Audiodev *dev = e->dev;
+ printf("-audiodev ");
+
+ v = legacy_visitor_new();
+ visit_type_Audiodev(v, NULL, &dev, &error_abort);
+ visit_free(v);
+
+ printf("\n");
+ }
+ audio_free_audiodev_list(&head);
+}
diff --git a/audio/audio_template.h b/audio/audio_template.h
new file mode 100644
index 000000000..c6714946a
--- /dev/null
+++ b/audio/audio_template.h
@@ -0,0 +1,568 @@
+/*
+ * 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)(AudioState *s,
+ struct audio_driver *drv)
+{
+ 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(__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(__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(hw->buf_emul);
+ g_free (HWBUF);
+ HWBUF = NULL;
+}
+
+static void glue(audio_pcm_hw_alloc_resources_, TYPE)(HW *hw)
+{
+ if (glue(audio_get_pdo_, TYPE)(hw->s->dev)->mixing_engine) {
+ size_t samples = hw->samples;
+ if (audio_bug(__func__, samples == 0)) {
+ dolog("Attempted to allocate empty buffer\n");
+ }
+
+ HWBUF = g_malloc0(sizeof(STSampleBuffer) + sizeof(st_sample) * samples);
+ HWBUF->size = samples;
+ } else {
+ HWBUF = NULL;
+ }
+}
+
+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;
+
+ if (!glue(audio_get_pdo_, TYPE)(sw->s->dev)->mixing_engine) {
+ return 0;
+ }
+
+ samples = ((int64_t) sw->HWBUF->size << 32) / sw->ratio;
+
+ sw->buf = audio_calloc(__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
+
+ if (sw->info.is_float) {
+#ifdef DAC
+ sw->conv = mixeng_conv_float[sw->info.nchannels == 2];
+#else
+ sw->clip = mixeng_clip_float[sw->info.nchannels == 2];
+#endif
+ } else {
+#ifdef DAC
+ sw->conv = mixeng_conv
+#else
+ sw->clip = mixeng_clip
+#endif
+ [sw->info.nchannels == 2]
+ [sw->info.is_signed]
+ [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)
+{
+ HW *hw = *hwp;
+ AudioState *s = hw->s;
+
+ 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)(AudioState *s, HW *hw)
+{
+ return hw ? hw->entries.le_next : glue (s->hw_head_, TYPE).lh_first;
+}
+
+static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioState *s, HW *hw)
+{
+ while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) {
+ if (hw->enabled) {
+ return hw;
+ }
+ }
+ return NULL;
+}
+
+static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioState *s, HW *hw,
+ struct audsettings *as)
+{
+ while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) {
+ if (audio_pcm_info_eq (&hw->info, as)) {
+ return hw;
+ }
+ }
+ return NULL;
+}
+
+static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioState *s,
+ struct audsettings *as)
+{
+ HW *hw;
+ struct audio_driver *drv = s->drv;
+
+ if (!glue (s->nb_hw_voices_, TYPE)) {
+ return NULL;
+ }
+
+ if (audio_bug(__func__, !drv)) {
+ dolog ("No host audio driver\n");
+ return NULL;
+ }
+
+ if (audio_bug(__func__, !drv->pcm_ops)) {
+ dolog ("Host audio driver without pcm_ops\n");
+ return NULL;
+ }
+
+ hw = audio_calloc(__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->s = s;
+ hw->pcm_ops = drv->pcm_ops;
+
+ 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(__func__, hw->samples <= 0)) {
+ dolog("hw->samples=%zd\n", hw->samples);
+ goto err1;
+ }
+
+ if (hw->info.is_float) {
+#ifdef DAC
+ hw->clip = mixeng_clip_float[hw->info.nchannels == 2];
+#else
+ hw->conv = mixeng_conv_float[hw->info.nchannels == 2];
+#endif
+ } else {
+#ifdef DAC
+ hw->clip = mixeng_clip
+#else
+ hw->conv = mixeng_conv
+#endif
+ [hw->info.nchannels == 2]
+ [hw->info.is_signed]
+ [hw->info.swap_endianness]
+ [audio_bits_to_index(hw->info.bits)];
+ }
+
+ glue(audio_pcm_hw_alloc_resources_, TYPE)(hw);
+
+ 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;
+}
+
+AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
+{
+ switch (dev->driver) {
+ case AUDIODEV_DRIVER_NONE:
+ return dev->u.none.TYPE;
+ case AUDIODEV_DRIVER_ALSA:
+ return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.TYPE);
+ case AUDIODEV_DRIVER_COREAUDIO:
+ return qapi_AudiodevCoreaudioPerDirectionOptions_base(
+ dev->u.coreaudio.TYPE);
+ case AUDIODEV_DRIVER_DSOUND:
+ return dev->u.dsound.TYPE;
+ case AUDIODEV_DRIVER_JACK:
+ return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.TYPE);
+ case AUDIODEV_DRIVER_OSS:
+ return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.TYPE);
+ case AUDIODEV_DRIVER_PA:
+ return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.TYPE);
+ case AUDIODEV_DRIVER_SDL:
+ return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.TYPE);
+ case AUDIODEV_DRIVER_SPICE:
+ return dev->u.spice.TYPE;
+ case AUDIODEV_DRIVER_WAV:
+ return dev->u.wav.TYPE;
+
+ case AUDIODEV_DRIVER__MAX:
+ break;
+ }
+ abort();
+}
+
+static HW *glue(audio_pcm_hw_add_, TYPE)(AudioState *s, struct audsettings *as)
+{
+ HW *hw;
+ AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev);
+
+ if (!pdo->mixing_engine || pdo->fixed_settings) {
+ hw = glue(audio_pcm_hw_add_new_, TYPE)(s, as);
+ if (!pdo->mixing_engine || hw) {
+ return hw;
+ }
+ }
+
+ hw = glue(audio_pcm_hw_find_specific_, TYPE)(s, NULL, as);
+ if (hw) {
+ return hw;
+ }
+
+ hw = glue(audio_pcm_hw_add_new_, TYPE)(s, as);
+ if (hw) {
+ return hw;
+ }
+
+ return glue(audio_pcm_hw_find_any_, TYPE)(s, NULL);
+}
+
+static SW *glue(audio_pcm_create_voice_pair_, TYPE)(
+ AudioState *s,
+ const char *sw_name,
+ struct audsettings *as
+ )
+{
+ SW *sw;
+ HW *hw;
+ struct audsettings hw_as;
+ AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev);
+
+ if (pdo->fixed_settings) {
+ hw_as = audiodev_to_audsettings(pdo);
+ } else {
+ hw_as = *as;
+ }
+
+ sw = audio_calloc(__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;
+ }
+ sw->s = s;
+
+ hw = glue(audio_pcm_hw_add_, TYPE)(s, &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(__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;
+ AudiodevPerDirectionOptions *pdo;
+
+ if (audio_bug(__func__, !card || !name || !callback_fn || !as)) {
+ dolog ("card=%p name=%p callback_fn=%p as=%p\n",
+ card, name, callback_fn, as);
+ goto fail;
+ }
+
+ s = card->state;
+ pdo = glue(audio_get_pdo_, TYPE)(s->dev);
+
+ ldebug ("open %s, freq %d, nchannels %d, fmt %d\n",
+ name, as->freq, as->nchannels, as->fmt);
+
+ if (audio_bug(__func__, audio_validate_settings(as))) {
+ audio_print_settings (as);
+ goto fail;
+ }
+
+ if (audio_bug(__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 (!pdo->fixed_settings && 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)(s, 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/audio/audio_win_int.c b/audio/audio_win_int.c
new file mode 100644
index 000000000..5ea8157df
--- /dev/null
+++ b/audio/audio_win_int.c
@@ -0,0 +1,131 @@
+/* public domain */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+
+#define AUDIO_CAP "win-int"
+#include <windows.h>
+#include <mmreg.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->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 AUDIO_FORMAT_S8:
+ case AUDIO_FORMAT_U8:
+ wfx->wFormatTag = WAVE_FORMAT_PCM;
+ wfx->wBitsPerSample = 8;
+ break;
+
+ case AUDIO_FORMAT_S16:
+ case AUDIO_FORMAT_U16:
+ wfx->wFormatTag = WAVE_FORMAT_PCM;
+ wfx->wBitsPerSample = 16;
+ wfx->nAvgBytesPerSec <<= 1;
+ wfx->nBlockAlign <<= 1;
+ break;
+
+ case AUDIO_FORMAT_S32:
+ case AUDIO_FORMAT_U32:
+ wfx->wFormatTag = WAVE_FORMAT_PCM;
+ wfx->wBitsPerSample = 32;
+ wfx->nAvgBytesPerSec <<= 2;
+ wfx->nBlockAlign <<= 2;
+ break;
+
+ case AUDIO_FORMAT_F32:
+ wfx->wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+ wfx->wBitsPerSample = 32;
+ wfx->nAvgBytesPerSec <<= 2;
+ wfx->nBlockAlign <<= 2;
+ break;
+
+ default:
+ dolog("Internal logic error: Bad audio format %d\n", as->fmt);
+ return -1;
+ }
+
+ return 0;
+}
+
+int waveformat_to_audio_settings (WAVEFORMATEX *wfx,
+ struct audsettings *as)
+{
+ 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;
+ }
+
+ if (wfx->wFormatTag == WAVE_FORMAT_PCM) {
+ switch (wfx->wBitsPerSample) {
+ case 8:
+ as->fmt = AUDIO_FORMAT_U8;
+ break;
+
+ case 16:
+ as->fmt = AUDIO_FORMAT_S16;
+ break;
+
+ case 32:
+ as->fmt = AUDIO_FORMAT_S32;
+ break;
+
+ default:
+ dolog("Invalid PCM wave format, bits per sample is not "
+ "8, 16 or 32, but %d\n",
+ wfx->wBitsPerSample);
+ return -1;
+ }
+ } else if (wfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
+ switch (wfx->wBitsPerSample) {
+ case 32:
+ as->fmt = AUDIO_FORMAT_F32;
+ break;
+
+ default:
+ dolog("Invalid IEEE_FLOAT wave format, bits per sample is not "
+ "32, but %d\n",
+ wfx->wBitsPerSample);
+ return -1;
+ }
+ } else {
+ dolog("Invalid wave format, tag is not PCM and not IEEE_FLOAT, "
+ "but %d\n",
+ wfx->wFormatTag);
+ return -1;
+ }
+
+ return 0;
+}
+
diff --git a/audio/audio_win_int.h b/audio/audio_win_int.h
new file mode 100644
index 000000000..fa5b3cb80
--- /dev/null
+++ b/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/audio/coreaudio.c b/audio/coreaudio.c
new file mode 100644
index 000000000..d8a21d3e5
--- /dev/null
+++ b/audio/coreaudio.c
@@ -0,0 +1,681 @@
+/*
+ * 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 "qemu/osdep.h"
+#include <CoreAudio/CoreAudio.h>
+#include <pthread.h> /* pthread_X */
+
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "audio.h"
+
+#define AUDIO_CAP "coreaudio"
+#include "audio_int.h"
+
+typedef struct coreaudioVoiceOut {
+ HWVoiceOut hw;
+ pthread_mutex_t buf_mutex;
+ AudioDeviceID outputDeviceID;
+ int frameSizeSetting;
+ uint32_t bufferCount;
+ UInt32 audioDevicePropertyBufferFrameSize;
+ AudioDeviceIOProcID ioprocid;
+ bool enabled;
+} coreaudioVoiceOut;
+
+static const AudioObjectPropertyAddress voice_addr = {
+ kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+};
+
+static OSStatus coreaudio_get_voice(AudioDeviceID *id)
+{
+ UInt32 size = sizeof(*id);
+
+ return AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &voice_addr,
+ 0,
+ NULL,
+ &size,
+ id);
+}
+
+static OSStatus coreaudio_get_framesizerange(AudioDeviceID id,
+ AudioValueRange *framerange)
+{
+ UInt32 size = sizeof(*framerange);
+ AudioObjectPropertyAddress addr = {
+ kAudioDevicePropertyBufferFrameSizeRange,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+ };
+
+ return AudioObjectGetPropertyData(id,
+ &addr,
+ 0,
+ NULL,
+ &size,
+ framerange);
+}
+
+static OSStatus coreaudio_get_framesize(AudioDeviceID id, UInt32 *framesize)
+{
+ UInt32 size = sizeof(*framesize);
+ AudioObjectPropertyAddress addr = {
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+ };
+
+ return AudioObjectGetPropertyData(id,
+ &addr,
+ 0,
+ NULL,
+ &size,
+ framesize);
+}
+
+static OSStatus coreaudio_set_framesize(AudioDeviceID id, UInt32 *framesize)
+{
+ UInt32 size = sizeof(*framesize);
+ AudioObjectPropertyAddress addr = {
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+ };
+
+ return AudioObjectSetPropertyData(id,
+ &addr,
+ 0,
+ NULL,
+ size,
+ framesize);
+}
+
+static OSStatus coreaudio_set_streamformat(AudioDeviceID id,
+ AudioStreamBasicDescription *d)
+{
+ UInt32 size = sizeof(*d);
+ AudioObjectPropertyAddress addr = {
+ kAudioDevicePropertyStreamFormat,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+ };
+
+ return AudioObjectSetPropertyData(id,
+ &addr,
+ 0,
+ NULL,
+ size,
+ d);
+}
+
+static OSStatus coreaudio_get_isrunning(AudioDeviceID id, UInt32 *result)
+{
+ UInt32 size = sizeof(*result);
+ AudioObjectPropertyAddress addr = {
+ kAudioDevicePropertyDeviceIsRunning,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+ };
+
+ return AudioObjectGetPropertyData(id,
+ &addr,
+ 0,
+ NULL,
+ &size,
+ result);
+}
+
+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);
+}
+
+#define coreaudio_playback_logerr(status, ...) \
+ coreaudio_logerr2(status, "playback", __VA_ARGS__)
+
+static int coreaudio_buf_lock (coreaudioVoiceOut *core, const char *fn_name)
+{
+ int err;
+
+ err = pthread_mutex_lock (&core->buf_mutex);
+ if (err) {
+ dolog ("Could not lock voice for %s\nReason: %s\n",
+ fn_name, strerror (err));
+ return -1;
+ }
+ return 0;
+}
+
+static int coreaudio_buf_unlock (coreaudioVoiceOut *core, const char *fn_name)
+{
+ int err;
+
+ err = pthread_mutex_unlock (&core->buf_mutex);
+ if (err) {
+ dolog ("Could not unlock voice for %s\nReason: %s\n",
+ fn_name, strerror (err));
+ return -1;
+ }
+ return 0;
+}
+
+#define COREAUDIO_WRAPPER_FUNC(name, ret_type, args_decl, args) \
+ static ret_type glue(coreaudio_, name)args_decl \
+ { \
+ coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; \
+ ret_type ret; \
+ \
+ if (coreaudio_buf_lock(core, "coreaudio_" #name)) { \
+ return 0; \
+ } \
+ \
+ ret = glue(audio_generic_, name)args; \
+ \
+ coreaudio_buf_unlock(core, "coreaudio_" #name); \
+ return ret; \
+ }
+COREAUDIO_WRAPPER_FUNC(get_buffer_out, void *, (HWVoiceOut *hw, size_t *size),
+ (hw, size))
+COREAUDIO_WRAPPER_FUNC(put_buffer_out, size_t,
+ (HWVoiceOut *hw, void *buf, size_t size),
+ (hw, buf, size))
+COREAUDIO_WRAPPER_FUNC(write, size_t, (HWVoiceOut *hw, void *buf, size_t size),
+ (hw, buf, size))
+#undef COREAUDIO_WRAPPER_FUNC
+
+/*
+ * callback to feed audiooutput buffer. called without iothread lock.
+ * allowed to lock "buf_mutex", but disallowed to have any other locks.
+ */
+static OSStatus audioDeviceIOProc(
+ AudioDeviceID inDevice,
+ const AudioTimeStamp *inNow,
+ const AudioBufferList *inInputData,
+ const AudioTimeStamp *inInputTime,
+ AudioBufferList *outOutputData,
+ const AudioTimeStamp *inOutputTime,
+ void *hwptr)
+{
+ UInt32 frameCount, pending_frames;
+ void *out = outOutputData->mBuffers[0].mData;
+ HWVoiceOut *hw = hwptr;
+ coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr;
+ size_t len;
+
+ if (coreaudio_buf_lock (core, "audioDeviceIOProc")) {
+ inInputTime = 0;
+ return 0;
+ }
+
+ if (inDevice != core->outputDeviceID) {
+ coreaudio_buf_unlock (core, "audioDeviceIOProc(old device)");
+ return 0;
+ }
+
+ frameCount = core->audioDevicePropertyBufferFrameSize;
+ pending_frames = hw->pending_emul / hw->info.bytes_per_frame;
+
+ /* if there are not enough samples, set signal and return */
+ if (pending_frames < frameCount) {
+ inInputTime = 0;
+ coreaudio_buf_unlock (core, "audioDeviceIOProc(empty)");
+ return 0;
+ }
+
+ len = frameCount * hw->info.bytes_per_frame;
+ while (len) {
+ size_t write_len;
+ ssize_t start = ((ssize_t) hw->pos_emul) - hw->pending_emul;
+ if (start < 0) {
+ start += hw->size_emul;
+ }
+ assert(start >= 0 && start < hw->size_emul);
+
+ write_len = MIN(MIN(hw->pending_emul, len),
+ hw->size_emul - start);
+
+ memcpy(out, hw->buf_emul + start, write_len);
+ hw->pending_emul -= write_len;
+ len -= write_len;
+ out += write_len;
+ }
+
+ coreaudio_buf_unlock (core, "audioDeviceIOProc");
+ return 0;
+}
+
+static OSStatus init_out_device(coreaudioVoiceOut *core)
+{
+ OSStatus status;
+ AudioValueRange frameRange;
+
+ AudioStreamBasicDescription streamBasicDescription = {
+ .mBitsPerChannel = core->hw.info.bits,
+ .mBytesPerFrame = core->hw.info.bytes_per_frame,
+ .mBytesPerPacket = core->hw.info.bytes_per_frame,
+ .mChannelsPerFrame = core->hw.info.nchannels,
+ .mFormatFlags = kLinearPCMFormatFlagIsFloat,
+ .mFormatID = kAudioFormatLinearPCM,
+ .mFramesPerPacket = 1,
+ .mSampleRate = core->hw.info.freq
+ };
+
+ status = coreaudio_get_voice(&core->outputDeviceID);
+ if (status != kAudioHardwareNoError) {
+ coreaudio_playback_logerr (status,
+ "Could not get default output Device\n");
+ return status;
+ }
+ if (core->outputDeviceID == kAudioDeviceUnknown) {
+ dolog ("Could not initialize playback - Unknown Audiodevice\n");
+ return status;
+ }
+
+ /* get minimum and maximum buffer frame sizes */
+ status = coreaudio_get_framesizerange(core->outputDeviceID,
+ &frameRange);
+ if (status == kAudioHardwareBadObjectError) {
+ return 0;
+ }
+ if (status != kAudioHardwareNoError) {
+ coreaudio_playback_logerr (status,
+ "Could not get device buffer frame range\n");
+ return status;
+ }
+
+ if (frameRange.mMinimum > core->frameSizeSetting) {
+ core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum;
+ dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum);
+ } else if (frameRange.mMaximum < core->frameSizeSetting) {
+ core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum;
+ dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum);
+ } else {
+ core->audioDevicePropertyBufferFrameSize = core->frameSizeSetting;
+ }
+
+ /* set Buffer Frame Size */
+ status = coreaudio_set_framesize(core->outputDeviceID,
+ &core->audioDevicePropertyBufferFrameSize);
+ if (status == kAudioHardwareBadObjectError) {
+ return 0;
+ }
+ if (status != kAudioHardwareNoError) {
+ coreaudio_playback_logerr (status,
+ "Could not set device buffer frame size %" PRIu32 "\n",
+ (uint32_t)core->audioDevicePropertyBufferFrameSize);
+ return status;
+ }
+
+ /* get Buffer Frame Size */
+ status = coreaudio_get_framesize(core->outputDeviceID,
+ &core->audioDevicePropertyBufferFrameSize);
+ if (status == kAudioHardwareBadObjectError) {
+ return 0;
+ }
+ if (status != kAudioHardwareNoError) {
+ coreaudio_playback_logerr (status,
+ "Could not get device buffer frame size\n");
+ return status;
+ }
+ core->hw.samples = core->bufferCount * core->audioDevicePropertyBufferFrameSize;
+
+ /* set Samplerate */
+ status = coreaudio_set_streamformat(core->outputDeviceID,
+ &streamBasicDescription);
+ if (status == kAudioHardwareBadObjectError) {
+ return 0;
+ }
+ if (status != kAudioHardwareNoError) {
+ coreaudio_playback_logerr (status,
+ "Could not set samplerate %lf\n",
+ streamBasicDescription.mSampleRate);
+ core->outputDeviceID = kAudioDeviceUnknown;
+ return status;
+ }
+
+ /*
+ * set Callback.
+ *
+ * On macOS 11.3.1, Core Audio calls AudioDeviceIOProc after calling an
+ * internal function named HALB_Mutex::Lock(), which locks a mutex in
+ * HALB_IOThread::Entry(void*). HALB_Mutex::Lock() is also called in
+ * AudioObjectGetPropertyData, which is called by coreaudio driver.
+ * Therefore, the specified callback must be designed to avoid a deadlock
+ * with the callers of AudioObjectGetPropertyData.
+ */
+ core->ioprocid = NULL;
+ status = AudioDeviceCreateIOProcID(core->outputDeviceID,
+ audioDeviceIOProc,
+ &core->hw,
+ &core->ioprocid);
+ if (status == kAudioHardwareBadDeviceError) {
+ return 0;
+ }
+ if (status != kAudioHardwareNoError || core->ioprocid == NULL) {
+ coreaudio_playback_logerr (status, "Could not set IOProc\n");
+ core->outputDeviceID = kAudioDeviceUnknown;
+ return status;
+ }
+
+ return 0;
+}
+
+static void fini_out_device(coreaudioVoiceOut *core)
+{
+ OSStatus status;
+ UInt32 isrunning;
+
+ /* stop playback */
+ status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning);
+ if (status != kAudioHardwareBadObjectError) {
+ if (status != kAudioHardwareNoError) {
+ coreaudio_logerr(status,
+ "Could not determine whether Device is playing\n");
+ }
+
+ if (isrunning) {
+ status = AudioDeviceStop(core->outputDeviceID, core->ioprocid);
+ if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) {
+ coreaudio_logerr(status, "Could not stop playback\n");
+ }
+ }
+ }
+
+ /* remove callback */
+ status = AudioDeviceDestroyIOProcID(core->outputDeviceID,
+ core->ioprocid);
+ if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) {
+ coreaudio_logerr(status, "Could not remove IOProc\n");
+ }
+ core->outputDeviceID = kAudioDeviceUnknown;
+}
+
+static void update_device_playback_state(coreaudioVoiceOut *core)
+{
+ OSStatus status;
+ UInt32 isrunning;
+
+ status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning);
+ if (status != kAudioHardwareNoError) {
+ if (status != kAudioHardwareBadObjectError) {
+ coreaudio_logerr(status,
+ "Could not determine whether Device is playing\n");
+ }
+
+ return;
+ }
+
+ if (core->enabled) {
+ /* start playback */
+ if (!isrunning) {
+ status = AudioDeviceStart(core->outputDeviceID, core->ioprocid);
+ if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) {
+ coreaudio_logerr (status, "Could not resume playback\n");
+ }
+ }
+ } else {
+ /* stop playback */
+ if (isrunning) {
+ status = AudioDeviceStop(core->outputDeviceID,
+ core->ioprocid);
+ if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) {
+ coreaudio_logerr(status, "Could not pause playback\n");
+ }
+ }
+ }
+}
+
+/* called without iothread lock. */
+static OSStatus handle_voice_change(
+ AudioObjectID in_object_id,
+ UInt32 in_number_addresses,
+ const AudioObjectPropertyAddress *in_addresses,
+ void *in_client_data)
+{
+ OSStatus status;
+ coreaudioVoiceOut *core = in_client_data;
+
+ qemu_mutex_lock_iothread();
+
+ if (core->outputDeviceID) {
+ fini_out_device(core);
+ }
+
+ status = init_out_device(core);
+ if (!status) {
+ update_device_playback_state(core);
+ }
+
+ qemu_mutex_unlock_iothread();
+ return status;
+}
+
+static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
+ void *drv_opaque)
+{
+ OSStatus status;
+ coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
+ int err;
+ Audiodev *dev = drv_opaque;
+ AudiodevCoreaudioPerDirectionOptions *cpdo = dev->u.coreaudio.out;
+ struct audsettings obt_as;
+
+ /* create mutex */
+ err = pthread_mutex_init(&core->buf_mutex, NULL);
+ if (err) {
+ dolog("Could not create mutex\nReason: %s\n", strerror (err));
+ return -1;
+ }
+
+ obt_as = *as;
+ as = &obt_as;
+ as->fmt = AUDIO_FORMAT_F32;
+ audio_pcm_init_info (&hw->info, as);
+
+ core->frameSizeSetting = audio_buffer_frames(
+ qapi_AudiodevCoreaudioPerDirectionOptions_base(cpdo), as, 11610);
+
+ core->bufferCount = cpdo->has_buffer_count ? cpdo->buffer_count : 4;
+
+ status = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
+ &voice_addr, handle_voice_change,
+ core);
+ if (status != kAudioHardwareNoError) {
+ coreaudio_playback_logerr (status,
+ "Could not listen to voice property change\n");
+ return -1;
+ }
+
+ if (init_out_device(core)) {
+ status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
+ &voice_addr,
+ handle_voice_change,
+ core);
+ if (status != kAudioHardwareNoError) {
+ coreaudio_playback_logerr(status,
+ "Could not remove voice property change listener\n");
+ }
+ }
+
+ return 0;
+}
+
+static void coreaudio_fini_out (HWVoiceOut *hw)
+{
+ OSStatus status;
+ int err;
+ coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
+
+ status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
+ &voice_addr,
+ handle_voice_change,
+ core);
+ if (status != kAudioHardwareNoError) {
+ coreaudio_logerr(status, "Could not remove voice property change listener\n");
+ }
+
+ fini_out_device(core);
+
+ /* destroy mutex */
+ err = pthread_mutex_destroy(&core->buf_mutex);
+ if (err) {
+ dolog("Could not destroy mutex\nReason: %s\n", strerror (err));
+ }
+}
+
+static void coreaudio_enable_out(HWVoiceOut *hw, bool enable)
+{
+ coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
+
+ core->enabled = enable;
+ update_device_playback_state(core);
+}
+
+static void *coreaudio_audio_init(Audiodev *dev)
+{
+ return dev;
+}
+
+static void coreaudio_audio_fini (void *opaque)
+{
+}
+
+static struct audio_pcm_ops coreaudio_pcm_ops = {
+ .init_out = coreaudio_init_out,
+ .fini_out = coreaudio_fini_out,
+ /* wrapper for audio_generic_write */
+ .write = coreaudio_write,
+ /* wrapper for audio_generic_get_buffer_out */
+ .get_buffer_out = coreaudio_get_buffer_out,
+ /* wrapper for audio_generic_put_buffer_out */
+ .put_buffer_out = coreaudio_put_buffer_out,
+ .enable_out = coreaudio_enable_out
+};
+
+static struct audio_driver coreaudio_audio_driver = {
+ .name = "coreaudio",
+ .descr = "CoreAudio http://developer.apple.com/audio/coreaudio.html",
+ .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
+};
+
+static void register_audio_coreaudio(void)
+{
+ audio_driver_register(&coreaudio_audio_driver);
+}
+type_init(register_audio_coreaudio);
diff --git a/audio/dsound_template.h b/audio/dsound_template.h
new file mode 100644
index 000000000..0678f2de3
--- /dev/null
+++ b/audio/dsound_template.h
@@ -0,0 +1,280 @@
+/*
+ * 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
+#define HWVOICE HWVoiceIn
+#define DSOUNDVOICE DSoundVoiceIn
+#else
+#define NAME "playback buffer"
+#define NAME2 "DirectSound"
+#define TYPE out
+#define IFACE IDirectSoundBuffer
+#define BUFPTR LPDIRECTSOUNDBUFFER
+#define FIELD dsound_buffer
+#define FIELD2 dsound
+#define HWVOICE HWVoiceOut
+#define DSOUNDVOICE DSoundVoiceOut
+#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;
+ DWORD flag;
+
+#ifdef DSBTYPE_IN
+ flag = entire ? DSCBLOCK_ENTIREBUFFER : 0;
+#else
+ flag = entire ? DSBLOCK_ENTIREBUFFER : 0;
+#endif
+ hr = glue(IFACE, _Lock)(buf, pos, len, p1p, blen1p, p2p, blen2p, 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 ((p1p && *p1p && (*blen1p % info->bytes_per_frame)) ||
+ (p2p && *p2p && (*blen2p % info->bytes_per_frame))) {
+ dolog("DirectSound returned misaligned buffer %ld %ld\n",
+ *blen1p, *blen2p);
+ glue(dsound_unlock_, TYPE)(buf, *p1p, p2p ? *p2p : NULL, *blen1p,
+ blen2p ? *blen2p : 0);
+ goto fail;
+ }
+
+ if (p1p && !*p1p && *blen1p) {
+ dolog("warning: !p1 && blen1=%ld\n", *blen1p);
+ *blen1p = 0;
+ }
+
+ if (p2p && !*p2p && *blen2p) {
+ dolog("warning: !p2 && blen2=%ld\n", *blen2p);
+ *blen2p = 0;
+ }
+
+ return 0;
+
+ fail:
+ *p1p = NULL - 1;
+ *blen1p = -1;
+ if (p2p) {
+ *p2p = NULL - 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;
+#ifdef DSBTYPE_IN
+ const char *typ = "ADC";
+ DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
+ DSCBUFFERDESC bd;
+ DSCBCAPS bc;
+ AudiodevPerDirectionOptions *pdo = s->dev->u.dsound.in;
+#else
+ const char *typ = "DAC";
+ DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
+ DSBUFFERDESC bd;
+ DSBCAPS bc;
+ AudiodevPerDirectionOptions *pdo = s->dev->u.dsound.out;
+#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;
+ bd.dwBufferBytes = audio_buffer_bytes(pdo, as, 92880);
+#ifdef DSBTYPE_IN
+ hr = IDirectSoundCapture_CreateCaptureBuffer (
+ s->dsound_capture,
+ &bd,
+ &ds->dsound_capture_buffer,
+ NULL
+ );
+#else
+ bd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+ 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 = true;
+ obt_as.endianness = 0;
+ audio_pcm_init_info (&hw->info, &obt_as);
+
+ if (bc.dwBufferBytes % hw->info.bytes_per_frame) {
+ dolog (
+ "GetCaps returned misaligned buffer size %ld, alignment %d\n",
+ bc.dwBufferBytes, hw->info.bytes_per_frame
+ );
+ }
+ hw->size_emul = bc.dwBufferBytes;
+ hw->samples = bc.dwBufferBytes / hw->info.bytes_per_frame;
+ ds->s = s;
+
+#ifdef DEBUG_DSOUND
+ dolog ("caps %ld, desc %ld\n",
+ bc.dwBufferBytes, bd.dwBufferBytes);
+#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
+#undef HWVOICE
+#undef DSOUNDVOICE
diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c
new file mode 100644
index 000000000..cfc79c129
--- /dev/null
+++ b/audio/dsoundaudio.c
@@ -0,0 +1,732 @@
+/*
+ * 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/osdep.h"
+#include "audio.h"
+
+#define AUDIO_CAP "dsound"
+#include "audio_int.h"
+#include "qemu/host-utils.h"
+#include "qemu/module.h"
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <objbase.h>
+#include <dsound.h>
+
+#include "audio_win_int.h"
+
+/* #define DEBUG_DSOUND */
+
+typedef struct {
+ LPDIRECTSOUND dsound;
+ LPDIRECTSOUNDCAPTURE dsound_capture;
+ struct audsettings settings;
+ Audiodev *dev;
+} dsound;
+
+typedef struct {
+ HWVoiceOut hw;
+ LPDIRECTSOUNDBUFFER dsound_buffer;
+ bool first_time;
+ dsound *s;
+} DSoundVoiceOut;
+
+typedef struct {
+ HWVoiceIn hw;
+ LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer;
+ bool first_time;
+ 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 0x%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);
+}
+
+#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 & DSBSTATUS_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_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->size_emul,
+ &p1, &p2,
+ &blen1, &blen2,
+ 1,
+ s
+ );
+ if (err) {
+ return;
+ }
+
+ len1 = blen1 / hw->info.bytes_per_frame;
+ len2 = blen2 / hw->info.bytes_per_frame;
+
+#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_set_cooperative_level(dsound *s)
+{
+ HRESULT hr;
+ HWND hwnd;
+
+ hwnd = GetDesktopWindow();
+ 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 void dsound_enable_out(HWVoiceOut *hw, bool enable)
+{
+ 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;
+ }
+
+ if (enable) {
+ if (dsound_get_status_out (dsb, &status, s)) {
+ return;
+ }
+
+ if (status & DSBSTATUS_PLAYING) {
+ dolog ("warning: Voice is already playing\n");
+ return;
+ }
+
+ 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;
+ }
+ } else {
+ if (dsound_get_status_out (dsb, &status, s)) {
+ return;
+ }
+
+ if (status & DSBSTATUS_PLAYING) {
+ hr = IDirectSoundBuffer_Stop (dsb);
+ if (FAILED (hr)) {
+ dsound_logerr (hr, "Could not stop playing buffer\n");
+ return;
+ }
+ } else {
+ dolog ("warning: Voice is not playing\n");
+ }
+ }
+}
+
+static void *dsound_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+ DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
+ LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer;
+ HRESULT hr;
+ DWORD ppos, wpos, act_size;
+ size_t req_size;
+ int err;
+ void *ret;
+
+ hr = IDirectSoundBuffer_GetCurrentPosition(
+ dsb, &ppos, ds->first_time ? &wpos : NULL);
+ if (FAILED(hr)) {
+ dsound_logerr(hr, "Could not get playback buffer position\n");
+ *size = 0;
+ return NULL;
+ }
+
+ if (ds->first_time) {
+ hw->pos_emul = wpos;
+ ds->first_time = false;
+ }
+
+ req_size = audio_ring_dist(ppos, hw->pos_emul, hw->size_emul);
+ req_size = MIN(req_size, hw->size_emul - hw->pos_emul);
+
+ if (req_size == 0) {
+ *size = 0;
+ return NULL;
+ }
+
+ err = dsound_lock_out(dsb, &hw->info, hw->pos_emul, req_size, &ret, NULL,
+ &act_size, NULL, false, ds->s);
+ if (err) {
+ dolog("Failed to lock buffer\n");
+ *size = 0;
+ return NULL;
+ }
+
+ *size = act_size;
+ return ret;
+}
+
+static size_t dsound_put_buffer_out(HWVoiceOut *hw, void *buf, size_t len)
+{
+ DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
+ LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer;
+ int err = dsound_unlock_out(dsb, buf, NULL, len, 0);
+
+ if (err) {
+ dolog("Failed to unlock buffer!!\n");
+ return 0;
+ }
+ hw->pos_emul = (hw->pos_emul + len) % hw->size_emul;
+
+ return len;
+}
+
+static void dsound_enable_in(HWVoiceIn *hw, bool enable)
+{
+ 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;
+ }
+
+ if (enable) {
+ if (dsound_get_status_in (dscb, &status)) {
+ return;
+ }
+
+ if (status & DSCBSTATUS_CAPTURING) {
+ dolog ("warning: Voice is already capturing\n");
+ return;
+ }
+
+ /* clear ?? */
+
+ hr = IDirectSoundCaptureBuffer_Start (dscb, DSCBSTART_LOOPING);
+ if (FAILED (hr)) {
+ dsound_logerr (hr, "Could not start capturing\n");
+ return;
+ }
+ } else {
+ if (dsound_get_status_in (dscb, &status)) {
+ return;
+ }
+
+ if (status & DSCBSTATUS_CAPTURING) {
+ hr = IDirectSoundCaptureBuffer_Stop (dscb);
+ if (FAILED (hr)) {
+ dsound_logerr (hr, "Could not stop capturing\n");
+ return;
+ }
+ } else {
+ dolog ("warning: Voice is not capturing\n");
+ }
+ }
+}
+
+static void *dsound_get_buffer_in(HWVoiceIn *hw, size_t *size)
+{
+ DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
+ LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer;
+ HRESULT hr;
+ DWORD cpos, rpos, act_size;
+ size_t req_size;
+ int err;
+ void *ret;
+
+ hr = IDirectSoundCaptureBuffer_GetCurrentPosition(
+ dscb, &cpos, ds->first_time ? &rpos : NULL);
+ if (FAILED(hr)) {
+ dsound_logerr(hr, "Could not get capture buffer position\n");
+ *size = 0;
+ return NULL;
+ }
+
+ if (ds->first_time) {
+ hw->pos_emul = rpos;
+ ds->first_time = false;
+ }
+
+ req_size = audio_ring_dist(cpos, hw->pos_emul, hw->size_emul);
+ req_size = MIN(*size, MIN(req_size, hw->size_emul - hw->pos_emul));
+
+ if (req_size == 0) {
+ *size = 0;
+ return NULL;
+ }
+
+ err = dsound_lock_in(dscb, &hw->info, hw->pos_emul, req_size, &ret, NULL,
+ &act_size, NULL, false, ds->s);
+ if (err) {
+ dolog("Failed to lock buffer\n");
+ *size = 0;
+ return NULL;
+ }
+
+ *size = act_size;
+ return ret;
+}
+
+static void dsound_put_buffer_in(HWVoiceIn *hw, void *buf, size_t len)
+{
+ DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
+ LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer;
+ int err = dsound_unlock_in(dscb, buf, NULL, len, 0);
+
+ if (err) {
+ dolog("Failed to unlock buffer!!\n");
+ return;
+ }
+ hw->pos_emul = (hw->pos_emul + len) % hw->size_emul;
+}
+
+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(Audiodev *dev)
+{
+ int err;
+ HRESULT hr;
+ dsound *s = g_malloc0(sizeof(dsound));
+ AudiodevDsoundOptions *dso;
+
+ assert(dev->driver == AUDIODEV_DRIVER_DSOUND);
+ s->dev = dev;
+ dso = &dev->u.dsound;
+
+ if (!dso->has_latency) {
+ dso->has_latency = true;
+ dso->latency = 10000; /* 10 ms */
+ }
+
+ 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_set_cooperative_level(s);
+ if (err) {
+ dsound_audio_fini (s);
+ return NULL;
+ }
+
+ return s;
+}
+
+static struct audio_pcm_ops dsound_pcm_ops = {
+ .init_out = dsound_init_out,
+ .fini_out = dsound_fini_out,
+ .write = audio_generic_write,
+ .get_buffer_out = dsound_get_buffer_out,
+ .put_buffer_out = dsound_put_buffer_out,
+ .enable_out = dsound_enable_out,
+
+ .init_in = dsound_init_in,
+ .fini_in = dsound_fini_in,
+ .read = audio_generic_read,
+ .get_buffer_in = dsound_get_buffer_in,
+ .put_buffer_in = dsound_put_buffer_in,
+ .enable_in = dsound_enable_in,
+};
+
+static struct audio_driver dsound_audio_driver = {
+ .name = "dsound",
+ .descr = "DirectSound http://wikipedia.org/wiki/DirectSound",
+ .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)
+};
+
+static void register_audio_dsound(void)
+{
+ audio_driver_register(&dsound_audio_driver);
+}
+type_init(register_audio_dsound);
diff --git a/audio/jackaudio.c b/audio/jackaudio.c
new file mode 100644
index 000000000..e7de6d543
--- /dev/null
+++ b/audio/jackaudio.c
@@ -0,0 +1,694 @@
+/*
+ * QEMU JACK Audio Connection Kit Client
+ *
+ * Copyright (c) 2020 Geoffrey McRae (gnif)
+ *
+ * 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/osdep.h"
+#include "qemu/module.h"
+#include "qemu/atomic.h"
+#include "qemu/main-loop.h"
+#include "audio.h"
+
+#define AUDIO_CAP "jack"
+#include "audio_int.h"
+
+#include <jack/jack.h>
+#include <jack/thread.h>
+
+struct QJack;
+
+typedef enum QJackState {
+ QJACK_STATE_DISCONNECTED,
+ QJACK_STATE_RUNNING,
+ QJACK_STATE_SHUTDOWN
+}
+QJackState;
+
+typedef struct QJackBuffer {
+ int channels;
+ int frames;
+ uint32_t used;
+ int rptr, wptr;
+ float **data;
+}
+QJackBuffer;
+
+typedef struct QJackClient {
+ AudiodevJackPerDirectionOptions *opt;
+
+ bool out;
+ bool enabled;
+ bool connect_ports;
+ int packets;
+
+ QJackState state;
+ jack_client_t *client;
+ jack_nframes_t freq;
+ QEMUBH *shutdown_bh;
+
+ struct QJack *j;
+ int nchannels;
+ int buffersize;
+ jack_port_t **port;
+ QJackBuffer fifo;
+}
+QJackClient;
+
+typedef struct QJackOut {
+ HWVoiceOut hw;
+ QJackClient c;
+}
+QJackOut;
+
+typedef struct QJackIn {
+ HWVoiceIn hw;
+ QJackClient c;
+}
+QJackIn;
+
+static int qjack_client_init(QJackClient *c);
+static void qjack_client_connect_ports(QJackClient *c);
+static void qjack_client_fini(QJackClient *c);
+static QemuMutex qjack_shutdown_lock;
+
+static void qjack_buffer_create(QJackBuffer *buffer, int channels, int frames)
+{
+ buffer->channels = channels;
+ buffer->frames = frames;
+ buffer->used = 0;
+ buffer->rptr = 0;
+ buffer->wptr = 0;
+ buffer->data = g_malloc(channels * sizeof(float *));
+ for (int i = 0; i < channels; ++i) {
+ buffer->data[i] = g_malloc(frames * sizeof(float));
+ }
+}
+
+static void qjack_buffer_clear(QJackBuffer *buffer)
+{
+ assert(buffer->data);
+ qatomic_store_release(&buffer->used, 0);
+ buffer->rptr = 0;
+ buffer->wptr = 0;
+}
+
+static void qjack_buffer_free(QJackBuffer *buffer)
+{
+ if (!buffer->data) {
+ return;
+ }
+
+ for (int i = 0; i < buffer->channels; ++i) {
+ g_free(buffer->data[i]);
+ }
+
+ g_free(buffer->data);
+ buffer->data = NULL;
+}
+
+/* write PCM interleaved */
+static int qjack_buffer_write(QJackBuffer *buffer, float *data, int size)
+{
+ assert(buffer->data);
+ const int samples = size / sizeof(float);
+ int frames = samples / buffer->channels;
+ const int avail = buffer->frames - qatomic_load_acquire(&buffer->used);
+
+ if (frames > avail) {
+ frames = avail;
+ }
+
+ int copy = frames;
+ int wptr = buffer->wptr;
+
+ while (copy) {
+
+ for (int c = 0; c < buffer->channels; ++c) {
+ buffer->data[c][wptr] = *data++;
+ }
+
+ if (++wptr == buffer->frames) {
+ wptr = 0;
+ }
+
+ --copy;
+ }
+
+ buffer->wptr = wptr;
+
+ qatomic_add(&buffer->used, frames);
+ return frames * buffer->channels * sizeof(float);
+};
+
+/* write PCM linear */
+static int qjack_buffer_write_l(QJackBuffer *buffer, float **dest, int frames)
+{
+ assert(buffer->data);
+ const int avail = buffer->frames - qatomic_load_acquire(&buffer->used);
+ int wptr = buffer->wptr;
+
+ if (frames > avail) {
+ frames = avail;
+ }
+
+ int right = buffer->frames - wptr;
+ if (right > frames) {
+ right = frames;
+ }
+
+ const int left = frames - right;
+ for (int c = 0; c < buffer->channels; ++c) {
+ memcpy(buffer->data[c] + wptr, dest[c] , right * sizeof(float));
+ memcpy(buffer->data[c] , dest[c] + right, left * sizeof(float));
+ }
+
+ wptr += frames;
+ if (wptr >= buffer->frames) {
+ wptr -= buffer->frames;
+ }
+ buffer->wptr = wptr;
+
+ qatomic_add(&buffer->used, frames);
+ return frames;
+}
+
+/* read PCM interleaved */
+static int qjack_buffer_read(QJackBuffer *buffer, float *dest, int size)
+{
+ assert(buffer->data);
+ const int samples = size / sizeof(float);
+ int frames = samples / buffer->channels;
+ const int avail = qatomic_load_acquire(&buffer->used);
+
+ if (frames > avail) {
+ frames = avail;
+ }
+
+ int copy = frames;
+ int rptr = buffer->rptr;
+
+ while (copy) {
+
+ for (int c = 0; c < buffer->channels; ++c) {
+ *dest++ = buffer->data[c][rptr];
+ }
+
+ if (++rptr == buffer->frames) {
+ rptr = 0;
+ }
+
+ --copy;
+ }
+
+ buffer->rptr = rptr;
+
+ qatomic_sub(&buffer->used, frames);
+ return frames * buffer->channels * sizeof(float);
+}
+
+/* read PCM linear */
+static int qjack_buffer_read_l(QJackBuffer *buffer, float **dest, int frames)
+{
+ assert(buffer->data);
+ int copy = frames;
+ const int used = qatomic_load_acquire(&buffer->used);
+ int rptr = buffer->rptr;
+
+ if (copy > used) {
+ copy = used;
+ }
+
+ int right = buffer->frames - rptr;
+ if (right > copy) {
+ right = copy;
+ }
+
+ const int left = copy - right;
+ for (int c = 0; c < buffer->channels; ++c) {
+ memcpy(dest[c] , buffer->data[c] + rptr, right * sizeof(float));
+ memcpy(dest[c] + right, buffer->data[c] , left * sizeof(float));
+ }
+
+ rptr += copy;
+ if (rptr >= buffer->frames) {
+ rptr -= buffer->frames;
+ }
+ buffer->rptr = rptr;
+
+ qatomic_sub(&buffer->used, copy);
+ return copy;
+}
+
+static int qjack_process(jack_nframes_t nframes, void *arg)
+{
+ QJackClient *c = (QJackClient *)arg;
+
+ if (c->state != QJACK_STATE_RUNNING) {
+ return 0;
+ }
+
+ /* get the buffers for the ports */
+ float *buffers[c->nchannels];
+ for (int i = 0; i < c->nchannels; ++i) {
+ buffers[i] = jack_port_get_buffer(c->port[i], nframes);
+ }
+
+ if (c->out) {
+ if (likely(c->enabled)) {
+ qjack_buffer_read_l(&c->fifo, buffers, nframes);
+ } else {
+ for (int i = 0; i < c->nchannels; ++i) {
+ memset(buffers[i], 0, nframes * sizeof(float));
+ }
+ }
+ } else {
+ if (likely(c->enabled)) {
+ qjack_buffer_write_l(&c->fifo, buffers, nframes);
+ }
+ }
+
+ return 0;
+}
+
+static void qjack_port_registration(jack_port_id_t port, int reg, void *arg)
+{
+ if (reg) {
+ QJackClient *c = (QJackClient *)arg;
+ c->connect_ports = true;
+ }
+}
+
+static int qjack_xrun(void *arg)
+{
+ QJackClient *c = (QJackClient *)arg;
+ if (c->state != QJACK_STATE_RUNNING) {
+ return 0;
+ }
+
+ qjack_buffer_clear(&c->fifo);
+ return 0;
+}
+
+static void qjack_shutdown_bh(void *opaque)
+{
+ QJackClient *c = (QJackClient *)opaque;
+ qjack_client_fini(c);
+}
+
+static void qjack_shutdown(void *arg)
+{
+ QJackClient *c = (QJackClient *)arg;
+ c->state = QJACK_STATE_SHUTDOWN;
+ qemu_bh_schedule(c->shutdown_bh);
+}
+
+static void qjack_client_recover(QJackClient *c)
+{
+ if (c->state != QJACK_STATE_DISCONNECTED) {
+ return;
+ }
+
+ /* packets is used simply to throttle this */
+ if (c->packets % 100 == 0) {
+
+ /* if enabled then attempt to recover */
+ if (c->enabled) {
+ dolog("attempting to reconnect to server\n");
+ qjack_client_init(c);
+ }
+ }
+}
+
+static size_t qjack_write(HWVoiceOut *hw, void *buf, size_t len)
+{
+ QJackOut *jo = (QJackOut *)hw;
+ ++jo->c.packets;
+
+ if (jo->c.state != QJACK_STATE_RUNNING) {
+ qjack_client_recover(&jo->c);
+ return len;
+ }
+
+ qjack_client_connect_ports(&jo->c);
+ return qjack_buffer_write(&jo->c.fifo, buf, len);
+}
+
+static size_t qjack_read(HWVoiceIn *hw, void *buf, size_t len)
+{
+ QJackIn *ji = (QJackIn *)hw;
+ ++ji->c.packets;
+
+ if (ji->c.state != QJACK_STATE_RUNNING) {
+ qjack_client_recover(&ji->c);
+ return len;
+ }
+
+ qjack_client_connect_ports(&ji->c);
+ return qjack_buffer_read(&ji->c.fifo, buf, len);
+}
+
+static void qjack_client_connect_ports(QJackClient *c)
+{
+ if (!c->connect_ports || !c->opt->connect_ports) {
+ return;
+ }
+
+ c->connect_ports = false;
+ const char **ports;
+ ports = jack_get_ports(c->client, c->opt->connect_ports, NULL,
+ c->out ? JackPortIsInput : JackPortIsOutput);
+
+ if (!ports) {
+ return;
+ }
+
+ for (int i = 0; i < c->nchannels && ports[i]; ++i) {
+ const char *p = jack_port_name(c->port[i]);
+ if (jack_port_connected_to(c->port[i], ports[i])) {
+ continue;
+ }
+
+ if (c->out) {
+ dolog("connect %s -> %s\n", p, ports[i]);
+ jack_connect(c->client, p, ports[i]);
+ } else {
+ dolog("connect %s -> %s\n", ports[i], p);
+ jack_connect(c->client, ports[i], p);
+ }
+ }
+}
+
+static int qjack_client_init(QJackClient *c)
+{
+ jack_status_t status;
+ char client_name[jack_client_name_size()];
+ jack_options_t options = JackNullOption;
+
+ if (c->state == QJACK_STATE_RUNNING) {
+ return 0;
+ }
+
+ c->connect_ports = true;
+
+ snprintf(client_name, sizeof(client_name), "%s-%s",
+ c->out ? "out" : "in",
+ c->opt->client_name ? c->opt->client_name : audio_application_name());
+
+ if (c->opt->exact_name) {
+ options |= JackUseExactName;
+ }
+
+ if (!c->opt->start_server) {
+ options |= JackNoStartServer;
+ }
+
+ if (c->opt->server_name) {
+ options |= JackServerName;
+ }
+
+ c->client = jack_client_open(client_name, options, &status,
+ c->opt->server_name);
+
+ if (c->client == NULL) {
+ dolog("jack_client_open failed: status = 0x%2.0x\n", status);
+ if (status & JackServerFailed) {
+ dolog("unable to connect to JACK server\n");
+ }
+ return -1;
+ }
+
+ c->freq = jack_get_sample_rate(c->client);
+
+ if (status & JackServerStarted) {
+ dolog("JACK server started\n");
+ }
+
+ if (status & JackNameNotUnique) {
+ dolog("JACK unique name assigned %s\n",
+ jack_get_client_name(c->client));
+ }
+
+ jack_set_process_callback(c->client, qjack_process , c);
+ jack_set_port_registration_callback(c->client, qjack_port_registration, c);
+ jack_set_xrun_callback(c->client, qjack_xrun, c);
+ jack_on_shutdown(c->client, qjack_shutdown, c);
+
+ /* allocate and register the ports */
+ c->port = g_malloc(sizeof(jack_port_t *) * c->nchannels);
+ for (int i = 0; i < c->nchannels; ++i) {
+
+ char port_name[16];
+ snprintf(
+ port_name,
+ sizeof(port_name),
+ c->out ? "output %d" : "input %d",
+ i);
+
+ c->port[i] = jack_port_register(
+ c->client,
+ port_name,
+ JACK_DEFAULT_AUDIO_TYPE,
+ c->out ? JackPortIsOutput : JackPortIsInput,
+ 0);
+ }
+
+ /* activate the session */
+ jack_activate(c->client);
+ c->buffersize = jack_get_buffer_size(c->client);
+
+ /*
+ * ensure the buffersize is no smaller then 512 samples, some (all?) qemu
+ * virtual devices do not work correctly otherwise
+ */
+ if (c->buffersize < 512) {
+ c->buffersize = 512;
+ }
+
+ /* create a 2 period buffer */
+ qjack_buffer_create(&c->fifo, c->nchannels, c->buffersize * 2);
+
+ qjack_client_connect_ports(c);
+ c->state = QJACK_STATE_RUNNING;
+ return 0;
+}
+
+static int qjack_init_out(HWVoiceOut *hw, struct audsettings *as,
+ void *drv_opaque)
+{
+ QJackOut *jo = (QJackOut *)hw;
+ Audiodev *dev = (Audiodev *)drv_opaque;
+
+ jo->c.out = true;
+ jo->c.enabled = false;
+ jo->c.nchannels = as->nchannels;
+ jo->c.opt = dev->u.jack.out;
+
+ jo->c.shutdown_bh = qemu_bh_new(qjack_shutdown_bh, &jo->c);
+
+ int ret = qjack_client_init(&jo->c);
+ if (ret != 0) {
+ qemu_bh_delete(jo->c.shutdown_bh);
+ return ret;
+ }
+
+ /* report the buffer size to qemu */
+ hw->samples = jo->c.buffersize;
+
+ /* report the audio format we support */
+ struct audsettings os = {
+ .freq = jo->c.freq,
+ .nchannels = jo->c.nchannels,
+ .fmt = AUDIO_FORMAT_F32,
+ .endianness = 0
+ };
+ audio_pcm_init_info(&hw->info, &os);
+
+ dolog("JACK output configured for %dHz (%d samples)\n",
+ jo->c.freq, jo->c.buffersize);
+
+ return 0;
+}
+
+static int qjack_init_in(HWVoiceIn *hw, struct audsettings *as,
+ void *drv_opaque)
+{
+ QJackIn *ji = (QJackIn *)hw;
+ Audiodev *dev = (Audiodev *)drv_opaque;
+
+ ji->c.out = false;
+ ji->c.enabled = false;
+ ji->c.nchannels = as->nchannels;
+ ji->c.opt = dev->u.jack.in;
+
+ ji->c.shutdown_bh = qemu_bh_new(qjack_shutdown_bh, &ji->c);
+
+ int ret = qjack_client_init(&ji->c);
+ if (ret != 0) {
+ qemu_bh_delete(ji->c.shutdown_bh);
+ return ret;
+ }
+
+ /* report the buffer size to qemu */
+ hw->samples = ji->c.buffersize;
+
+ /* report the audio format we support */
+ struct audsettings is = {
+ .freq = ji->c.freq,
+ .nchannels = ji->c.nchannels,
+ .fmt = AUDIO_FORMAT_F32,
+ .endianness = 0
+ };
+ audio_pcm_init_info(&hw->info, &is);
+
+ dolog("JACK input configured for %dHz (%d samples)\n",
+ ji->c.freq, ji->c.buffersize);
+
+ return 0;
+}
+
+static void qjack_client_fini_locked(QJackClient *c)
+{
+ switch (c->state) {
+ case QJACK_STATE_RUNNING:
+ jack_deactivate(c->client);
+ /* fallthrough */
+
+ case QJACK_STATE_SHUTDOWN:
+ jack_client_close(c->client);
+ c->client = NULL;
+
+ qjack_buffer_free(&c->fifo);
+ g_free(c->port);
+
+ c->state = QJACK_STATE_DISCONNECTED;
+ /* fallthrough */
+
+ case QJACK_STATE_DISCONNECTED:
+ break;
+ }
+}
+
+static void qjack_client_fini(QJackClient *c)
+{
+ qemu_mutex_lock(&qjack_shutdown_lock);
+ qjack_client_fini_locked(c);
+ qemu_mutex_unlock(&qjack_shutdown_lock);
+}
+
+static void qjack_fini_out(HWVoiceOut *hw)
+{
+ QJackOut *jo = (QJackOut *)hw;
+ qjack_client_fini(&jo->c);
+
+ qemu_bh_delete(jo->c.shutdown_bh);
+}
+
+static void qjack_fini_in(HWVoiceIn *hw)
+{
+ QJackIn *ji = (QJackIn *)hw;
+ qjack_client_fini(&ji->c);
+
+ qemu_bh_delete(ji->c.shutdown_bh);
+}
+
+static void qjack_enable_out(HWVoiceOut *hw, bool enable)
+{
+ QJackOut *jo = (QJackOut *)hw;
+ jo->c.enabled = enable;
+}
+
+static void qjack_enable_in(HWVoiceIn *hw, bool enable)
+{
+ QJackIn *ji = (QJackIn *)hw;
+ ji->c.enabled = enable;
+}
+
+static int qjack_thread_creator(jack_native_thread_t *thread,
+ const pthread_attr_t *attr, void *(*function)(void *), void *arg)
+{
+ int ret = pthread_create(thread, attr, function, arg);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* set the name of the thread */
+ pthread_setname_np(*thread, "jack-client");
+
+ return ret;
+}
+
+static void *qjack_init(Audiodev *dev)
+{
+ assert(dev->driver == AUDIODEV_DRIVER_JACK);
+ return dev;
+}
+
+static void qjack_fini(void *opaque)
+{
+}
+
+static struct audio_pcm_ops jack_pcm_ops = {
+ .init_out = qjack_init_out,
+ .fini_out = qjack_fini_out,
+ .write = qjack_write,
+ .run_buffer_out = audio_generic_run_buffer_out,
+ .enable_out = qjack_enable_out,
+
+ .init_in = qjack_init_in,
+ .fini_in = qjack_fini_in,
+ .read = qjack_read,
+ .run_buffer_in = audio_generic_run_buffer_in,
+ .enable_in = qjack_enable_in
+};
+
+static struct audio_driver jack_driver = {
+ .name = "jack",
+ .descr = "JACK Audio Connection Kit Client",
+ .init = qjack_init,
+ .fini = qjack_fini,
+ .pcm_ops = &jack_pcm_ops,
+ .can_be_default = 1,
+ .max_voices_out = INT_MAX,
+ .max_voices_in = INT_MAX,
+ .voice_size_out = sizeof(QJackOut),
+ .voice_size_in = sizeof(QJackIn)
+};
+
+static void qjack_error(const char *msg)
+{
+ dolog("E: %s\n", msg);
+}
+
+static void qjack_info(const char *msg)
+{
+ dolog("I: %s\n", msg);
+}
+
+static void register_audio_jack(void)
+{
+ qemu_mutex_init(&qjack_shutdown_lock);
+ audio_driver_register(&jack_driver);
+ jack_set_thread_creator(qjack_thread_creator);
+ jack_set_error_function(qjack_error);
+ jack_set_info_function(qjack_info);
+}
+type_init(register_audio_jack);
diff --git a/audio/meson.build b/audio/meson.build
new file mode 100644
index 000000000..462533bb8
--- /dev/null
+++ b/audio/meson.build
@@ -0,0 +1,29 @@
+softmmu_ss.add([spice_headers, files('audio.c')])
+softmmu_ss.add(files(
+ 'audio_legacy.c',
+ 'mixeng.c',
+ 'noaudio.c',
+ 'wavaudio.c',
+ 'wavcapture.c',
+))
+
+softmmu_ss.add(when: coreaudio, if_true: files('coreaudio.c'))
+softmmu_ss.add(when: dsound, if_true: files('dsoundaudio.c', 'audio_win_int.c'))
+
+audio_modules = {}
+foreach m : [
+ ['alsa', alsa, files('alsaaudio.c')],
+ ['oss', oss, files('ossaudio.c')],
+ ['pa', pulse, files('paaudio.c')],
+ ['sdl', sdl, files('sdlaudio.c')],
+ ['jack', jack, files('jackaudio.c')],
+ ['spice', spice, files('spiceaudio.c')]
+]
+ if m[1].found()
+ module_ss = ss.source_set()
+ module_ss.add(m[1], m[2])
+ audio_modules += {m[0] : module_ss}
+ endif
+endforeach
+
+modules += {'audio': audio_modules}
diff --git a/audio/mixeng.c b/audio/mixeng.c
new file mode 100644
index 000000000..f27deb165
--- /dev/null
+++ b/audio/mixeng.c
@@ -0,0 +1,470 @@
+/*
+ * 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/osdep.h"
+#include "qemu/bswap.h"
+#include "qemu/error-report.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
+ }
+ }
+ }
+};
+
+#ifdef FLOAT_MIXENG
+#define CONV_NATURAL_FLOAT(x) (x)
+#define CLIP_NATURAL_FLOAT(x) (x)
+#else
+/* macros to map [-1.f, 1.f] <-> [INT32_MIN, INT32_MAX + 1] */
+static const float float_scale = (int64_t)INT32_MAX + 1;
+#define CONV_NATURAL_FLOAT(x) ((x) * float_scale)
+
+#ifdef RECIPROCAL
+static const float float_scale_reciprocal = 1.f / ((int64_t)INT32_MAX + 1);
+#define CLIP_NATURAL_FLOAT(x) ((x) * float_scale_reciprocal)
+#else
+#define CLIP_NATURAL_FLOAT(x) ((x) / float_scale)
+#endif
+#endif
+
+static void conv_natural_float_to_mono(struct st_sample *dst, const void *src,
+ int samples)
+{
+ float *in = (float *)src;
+
+ while (samples--) {
+ dst->r = dst->l = CONV_NATURAL_FLOAT(*in++);
+ dst++;
+ }
+}
+
+static void conv_natural_float_to_stereo(struct st_sample *dst, const void *src,
+ int samples)
+{
+ float *in = (float *)src;
+
+ while (samples--) {
+ dst->l = CONV_NATURAL_FLOAT(*in++);
+ dst->r = CONV_NATURAL_FLOAT(*in++);
+ dst++;
+ }
+}
+
+t_sample *mixeng_conv_float[2] = {
+ conv_natural_float_to_mono,
+ conv_natural_float_to_stereo,
+};
+
+static void clip_natural_float_from_mono(void *dst, const struct st_sample *src,
+ int samples)
+{
+ float *out = (float *)dst;
+
+ while (samples--) {
+ *out++ = CLIP_NATURAL_FLOAT(src->l + src->r);
+ src++;
+ }
+}
+
+static void clip_natural_float_from_stereo(
+ void *dst, const struct st_sample *src, int samples)
+{
+ float *out = (float *)dst;
+
+ while (samples--) {
+ *out++ = CLIP_NATURAL_FLOAT(src->l);
+ *out++ = CLIP_NATURAL_FLOAT(src->r);
+ src++;
+ }
+}
+
+f_sample *mixeng_clip_float[2] = {
+ clip_natural_float_from_mono,
+ clip_natural_float_from_stereo,
+};
+
+void audio_sample_to_uint64(const void *samples, int pos,
+ uint64_t *left, uint64_t *right)
+{
+ const struct st_sample *sample = samples;
+ sample += pos;
+#ifdef FLOAT_MIXENG
+ error_report(
+ "Coreaudio and floating point samples are not supported by replay yet");
+ abort();
+#else
+ *left = sample->l;
+ *right = sample->r;
+#endif
+}
+
+void audio_sample_from_uint64(void *samples, int pos,
+ uint64_t left, uint64_t right)
+{
+ struct st_sample *sample = samples;
+ sample += pos;
+#ifdef FLOAT_MIXENG
+ error_report(
+ "Coreaudio and floating point samples are not supported by replay yet");
+ abort();
+#else
+ sample->l = left;
+ sample->r = right;
+#endif
+}
+
+/*
+ * August 21, 1998
+ * Copyright 1998 Fabrice Bellard.
+ *
+ * [Rewrote completely 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(__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/audio/mixeng.h b/audio/mixeng.h
new file mode 100644
index 000000000..2dcd6df24
--- /dev/null
+++ b/audio/mixeng.h
@@ -0,0 +1,58 @@
+/*
+ * 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 struct st_sample st_sample;
+
+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);
+
+/* indices: [stereo][signed][swap endiannes][8, 16 or 32-bits] */
+extern t_sample *mixeng_conv[2][2][2][3];
+extern f_sample *mixeng_clip[2][2][2][3];
+
+/* indices: [stereo] */
+extern t_sample *mixeng_conv_float[2];
+extern f_sample *mixeng_clip_float[2];
+
+void *st_rate_start (int inrate, int outrate);
+void st_rate_flow(void *opaque, st_sample *ibuf, st_sample *obuf,
+ size_t *isamp, size_t *osamp);
+void st_rate_flow_mix(void *opaque, st_sample *ibuf, st_sample *obuf,
+ size_t *isamp, size_t *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 /* QEMU_MIXENG_H */
diff --git a/audio/mixeng_template.h b/audio/mixeng_template.h
new file mode 100644
index 000000000..bc8509e42
--- /dev/null
+++ b/audio/mixeng_template.h
@@ -0,0 +1,152 @@
+/*
+ * 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 * (2.f / ((mixeng_real)IN_MAX - IN_MIN));
+#else
+ return (nv - HALF) * (2.f / (mixeng_real)IN_MAX);
+#endif
+#else /* !RECIPROCAL */
+#ifdef SIGNED
+ return nv / (((mixeng_real)IN_MAX - IN_MIN) / 2.f);
+#else
+ return (nv - HALF) / ((mixeng_real)IN_MAX / 2.f);
+#endif
+#endif
+}
+
+static inline IN_T glue (clip_, ET) (mixeng_real v)
+{
+ if (v >= 1.f) {
+ return IN_MAX;
+ } else if (v < -1.f) {
+ return IN_MIN;
+ }
+
+#ifdef SIGNED
+ return ENDIAN_CONVERT((IN_T)(v * (((mixeng_real)IN_MAX - IN_MIN) / 2.f)));
+#else
+ return ENDIAN_CONVERT((IN_T)((v * ((mixeng_real)IN_MAX / 2.f)) + 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 >= 0x7fffffffLL) {
+ 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/audio/noaudio.c b/audio/noaudio.c
new file mode 100644
index 000000000..aac87dbc9
--- /dev/null
+++ b/audio/noaudio.c
@@ -0,0 +1,148 @@
+/*
+ * 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/osdep.h"
+#include "qemu/host-utils.h"
+#include "qemu/module.h"
+#include "audio.h"
+#include "qemu/timer.h"
+
+#define AUDIO_CAP "noaudio"
+#include "audio_int.h"
+
+typedef struct NoVoiceOut {
+ HWVoiceOut hw;
+ RateCtl rate;
+} NoVoiceOut;
+
+typedef struct NoVoiceIn {
+ HWVoiceIn hw;
+ RateCtl rate;
+} NoVoiceIn;
+
+static size_t no_write(HWVoiceOut *hw, void *buf, size_t len)
+{
+ NoVoiceOut *no = (NoVoiceOut *) hw;
+ return audio_rate_get_bytes(&hw->info, &no->rate, len);
+}
+
+static int no_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
+{
+ NoVoiceOut *no = (NoVoiceOut *) hw;
+
+ audio_pcm_init_info (&hw->info, as);
+ hw->samples = 1024;
+ audio_rate_start(&no->rate);
+ return 0;
+}
+
+static void no_fini_out (HWVoiceOut *hw)
+{
+ (void) hw;
+}
+
+static void no_enable_out(HWVoiceOut *hw, bool enable)
+{
+ NoVoiceOut *no = (NoVoiceOut *) hw;
+
+ if (enable) {
+ audio_rate_start(&no->rate);
+ }
+}
+
+static int no_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
+{
+ NoVoiceIn *no = (NoVoiceIn *) hw;
+
+ audio_pcm_init_info (&hw->info, as);
+ hw->samples = 1024;
+ audio_rate_start(&no->rate);
+ return 0;
+}
+
+static void no_fini_in (HWVoiceIn *hw)
+{
+ (void) hw;
+}
+
+static size_t no_read(HWVoiceIn *hw, void *buf, size_t size)
+{
+ NoVoiceIn *no = (NoVoiceIn *) hw;
+ int64_t bytes = audio_rate_get_bytes(&hw->info, &no->rate, size);
+
+ audio_pcm_info_clear_buf(&hw->info, buf, bytes / hw->info.bytes_per_frame);
+ return bytes;
+}
+
+static void no_enable_in(HWVoiceIn *hw, bool enable)
+{
+ NoVoiceIn *no = (NoVoiceIn *) hw;
+
+ if (enable) {
+ audio_rate_start(&no->rate);
+ }
+}
+
+static void *no_audio_init(Audiodev *dev)
+{
+ 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,
+ .write = no_write,
+ .run_buffer_out = audio_generic_run_buffer_out,
+ .enable_out = no_enable_out,
+
+ .init_in = no_init_in,
+ .fini_in = no_fini_in,
+ .read = no_read,
+ .run_buffer_in = audio_generic_run_buffer_in,
+ .enable_in = no_enable_in
+};
+
+static struct audio_driver no_audio_driver = {
+ .name = "none",
+ .descr = "Timer based audio emulation",
+ .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)
+};
+
+static void register_audio_none(void)
+{
+ audio_driver_register(&no_audio_driver);
+}
+type_init(register_audio_none);
diff --git a/audio/ossaudio.c b/audio/ossaudio.c
new file mode 100644
index 000000000..60eff6642
--- /dev/null
+++ b/audio/ossaudio.c
@@ -0,0 +1,782 @@
+/*
+ * 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 "qemu/osdep.h"
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include "qemu/main-loop.h"
+#include "qemu/module.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 OSSVoiceOut {
+ HWVoiceOut hw;
+ int fd;
+ int nfrags;
+ int fragsize;
+ int mmapped;
+ Audiodev *dev;
+} OSSVoiceOut;
+
+typedef struct OSSVoiceIn {
+ HWVoiceIn hw;
+ int fd;
+ int nfrags;
+ int fragsize;
+ Audiodev *dev;
+} OSSVoiceIn;
+
+struct oss_params {
+ int freq;
+ int 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)
+{
+ AudioState *s = opaque;
+ audio_run(s, "oss_poll_out");
+}
+
+static void oss_helper_poll_in (void *opaque)
+{
+ AudioState *s = opaque;
+ audio_run(s, "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, hw->s);
+}
+
+static void oss_poll_in (HWVoiceIn *hw)
+{
+ OSSVoiceIn *oss = (OSSVoiceIn *) hw;
+
+ qemu_set_fd_handler(oss->fd, oss_helper_poll_in, NULL, hw->s);
+}
+
+static int aud_to_ossfmt (AudioFormat fmt, int endianness)
+{
+ switch (fmt) {
+ case AUDIO_FORMAT_S8:
+ return AFMT_S8;
+
+ case AUDIO_FORMAT_U8:
+ return AFMT_U8;
+
+ case AUDIO_FORMAT_S16:
+ if (endianness) {
+ return AFMT_S16_BE;
+ } else {
+ return AFMT_S16_LE;
+ }
+
+ case AUDIO_FORMAT_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, AudioFormat *fmt, int *endianness)
+{
+ switch (ossfmt) {
+ case AFMT_S8:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_S8;
+ break;
+
+ case AFMT_U8:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_U8;
+ break;
+
+ case AFMT_S16_LE:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_S16;
+ break;
+
+ case AFMT_U16_LE:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_U16;
+ break;
+
+ case AFMT_S16_BE:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_S16;
+ break;
+
+ case AFMT_U16_BE:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_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, audsettings *as,
+ struct oss_params *obt, int *pfd, Audiodev *dev)
+{
+ AudiodevOssOptions *oopts = &dev->u.oss;
+ AudiodevOssPerDirectionOptions *opdo = in ? oopts->in : oopts->out;
+ int fd;
+ int oflags = (oopts->has_exclusive && oopts->exclusive) ? O_EXCL : 0;
+ audio_buf_info abinfo;
+ int fmt, freq, nchannels;
+ int setfragment = 1;
+ const char *dspname = opdo->has_dev ? opdo->dev : "/dev/dsp";
+ const char *typ = in ? "ADC" : "DAC";
+#ifdef USE_DSP_POLICY
+ int policy = oopts->has_dsp_policy ? oopts->dsp_policy : 5;
+#endif
+
+ /* Kludge needed to have working mmap on Linux */
+ oflags |= (oopts->has_try_mmap && oopts->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;
+ req->nfrags = opdo->has_buffer_count ? opdo->buffer_count : 4;
+ req->fragsize = audio_buffer_bytes(
+ qapi_AudiodevOssPerDirectionOptions_base(opdo), as, 23220);
+
+ 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 (policy >= 0) {
+ int version;
+
+ if (!oss_get_version (fd, &version, typ)) {
+ trace_oss_version(version);
+
+ if (version >= 0x040000) {
+ int policy2 = policy;
+ if (ioctl(fd, SNDCTL_DSP_POLICY, &policy2)) {
+ oss_logerr2 (errno, typ,
+ "Failed to set timing policy to %d\n",
+ 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 size_t oss_get_available_bytes(OSSVoiceOut *oss)
+{
+ int err;
+ struct count_info cntinfo;
+ assert(oss->mmapped);
+
+ err = ioctl(oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo);
+ if (err < 0) {
+ oss_logerr(errno, "SNDCTL_DSP_GETOPTR failed\n");
+ return 0;
+ }
+
+ return audio_ring_dist(cntinfo.ptr, oss->hw.pos_emul, oss->hw.size_emul);
+}
+
+static void oss_run_buffer_out(HWVoiceOut *hw)
+{
+ OSSVoiceOut *oss = (OSSVoiceOut *)hw;
+
+ if (!oss->mmapped) {
+ audio_generic_run_buffer_out(hw);
+ }
+}
+
+static void *oss_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+ OSSVoiceOut *oss = (OSSVoiceOut *) hw;
+ if (oss->mmapped) {
+ *size = MIN(oss_get_available_bytes(oss), hw->size_emul - hw->pos_emul);
+ return hw->buf_emul + hw->pos_emul;
+ } else {
+ return audio_generic_get_buffer_out(hw, size);
+ }
+}
+
+static size_t oss_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
+{
+ OSSVoiceOut *oss = (OSSVoiceOut *) hw;
+ if (oss->mmapped) {
+ assert(buf == hw->buf_emul + hw->pos_emul && size < hw->size_emul);
+
+ hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
+ return size;
+ } else {
+ return audio_generic_put_buffer_out(hw, buf, size);
+ }
+}
+
+static size_t oss_write(HWVoiceOut *hw, void *buf, size_t len)
+{
+ OSSVoiceOut *oss = (OSSVoiceOut *) hw;
+ size_t pos;
+
+ if (oss->mmapped) {
+ size_t total_len;
+ len = MIN(len, oss_get_available_bytes(oss));
+
+ total_len = len;
+ while (len) {
+ size_t to_copy = MIN(len, hw->size_emul - hw->pos_emul);
+ memcpy(hw->buf_emul + hw->pos_emul, buf, to_copy);
+
+ hw->pos_emul = (hw->pos_emul + to_copy) % hw->size_emul;
+ buf += to_copy;
+ len -= to_copy;
+ }
+ return total_len;
+ }
+
+ pos = 0;
+ while (len) {
+ ssize_t bytes_written;
+ void *pcm = advance(buf, pos);
+
+ bytes_written = write(oss->fd, pcm, len);
+ if (bytes_written < 0) {
+ if (errno != EAGAIN) {
+ oss_logerr(errno, "failed to write %zu bytes\n",
+ len);
+ }
+ return pos;
+ }
+
+ pos += bytes_written;
+ if (bytes_written < len) {
+ break;
+ }
+ len -= bytes_written;
+ }
+ return pos;
+}
+
+static void oss_fini_out (HWVoiceOut *hw)
+{
+ int err;
+ OSSVoiceOut *oss = (OSSVoiceOut *) hw;
+
+ ldebug ("oss_fini\n");
+ oss_anal_close (&oss->fd);
+
+ if (oss->mmapped && hw->buf_emul) {
+ err = munmap(hw->buf_emul, hw->size_emul);
+ if (err) {
+ oss_logerr(errno, "Failed to unmap buffer %p, size %zu\n",
+ hw->buf_emul, hw->size_emul);
+ }
+ hw->buf_emul = 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;
+ AudioFormat effective_fmt;
+ struct audsettings obt_as;
+ Audiodev *dev = drv_opaque;
+ AudiodevOssOptions *oopts = &dev->u.oss;
+
+ oss->fd = -1;
+
+ req.fmt = aud_to_ossfmt (as->fmt, as->endianness);
+ req.freq = as->freq;
+ req.nchannels = as->nchannels;
+
+ if (oss_open(0, &req, as, &obt, &fd, dev)) {
+ 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.bytes_per_frame) {
+ dolog ("warning: Misaligned DAC buffer, size %d, alignment %d\n",
+ obt.nfrags * obt.fragsize, hw->info.bytes_per_frame);
+ }
+
+ hw->samples = (obt.nfrags * obt.fragsize) / hw->info.bytes_per_frame;
+
+ oss->mmapped = 0;
+ if (oopts->has_try_mmap && oopts->try_mmap) {
+ hw->size_emul = hw->samples * hw->info.bytes_per_frame;
+ hw->buf_emul = mmap(
+ NULL,
+ hw->size_emul,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED,
+ fd,
+ 0
+ );
+ if (hw->buf_emul == MAP_FAILED) {
+ oss_logerr(errno, "Failed to map %zu bytes of DAC\n",
+ hw->size_emul);
+ hw->buf_emul = NULL;
+ } 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(hw->buf_emul, hw->size_emul);
+ if (err) {
+ oss_logerr(errno, "Failed to unmap buffer %p size %zu\n",
+ hw->buf_emul, hw->size_emul);
+ }
+ hw->buf_emul = NULL;
+ }
+ }
+ }
+
+ oss->fd = fd;
+ oss->dev = dev;
+ return 0;
+}
+
+static void oss_enable_out(HWVoiceOut *hw, bool enable)
+{
+ int trig;
+ OSSVoiceOut *oss = (OSSVoiceOut *) hw;
+ AudiodevOssPerDirectionOptions *opdo = oss->dev->u.oss.out;
+
+ if (enable) {
+ hw->poll_mode = opdo->try_poll;
+
+ ldebug("enabling voice\n");
+ if (hw->poll_mode) {
+ oss_poll_out(hw);
+ }
+
+ if (!oss->mmapped) {
+ return;
+ }
+
+ audio_pcm_info_clear_buf(&hw->info, hw->buf_emul, 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;
+ }
+ } else {
+ if (hw->poll_mode) {
+ qemu_set_fd_handler (oss->fd, NULL, NULL, NULL);
+ hw->poll_mode = 0;
+ }
+
+ if (!oss->mmapped) {
+ return;
+ }
+
+ 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;
+ }
+ }
+}
+
+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;
+ AudioFormat effective_fmt;
+ struct audsettings obt_as;
+ Audiodev *dev = drv_opaque;
+
+ oss->fd = -1;
+
+ req.fmt = aud_to_ossfmt (as->fmt, as->endianness);
+ req.freq = as->freq;
+ req.nchannels = as->nchannels;
+ if (oss_open(1, &req, as, &obt, &fd, dev)) {
+ 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.bytes_per_frame) {
+ dolog ("warning: Misaligned ADC buffer, size %d, alignment %d\n",
+ obt.nfrags * obt.fragsize, hw->info.bytes_per_frame);
+ }
+
+ hw->samples = (obt.nfrags * obt.fragsize) / hw->info.bytes_per_frame;
+
+ oss->fd = fd;
+ oss->dev = dev;
+ return 0;
+}
+
+static void oss_fini_in (HWVoiceIn *hw)
+{
+ OSSVoiceIn *oss = (OSSVoiceIn *) hw;
+
+ oss_anal_close (&oss->fd);
+}
+
+static size_t oss_read(HWVoiceIn *hw, void *buf, size_t len)
+{
+ OSSVoiceIn *oss = (OSSVoiceIn *) hw;
+ size_t pos = 0;
+
+ while (len) {
+ ssize_t nread;
+
+ void *dst = advance(buf, pos);
+ nread = read(oss->fd, dst, len);
+
+ if (nread == -1) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+ break;
+ default:
+ oss_logerr(errno, "Failed to read %zu bytes of audio (to %p)\n",
+ len, dst);
+ break;
+ }
+ break;
+ }
+
+ pos += nread;
+ len -= nread;
+ }
+
+ return pos;
+}
+
+static void oss_enable_in(HWVoiceIn *hw, bool enable)
+{
+ OSSVoiceIn *oss = (OSSVoiceIn *) hw;
+ AudiodevOssPerDirectionOptions *opdo = oss->dev->u.oss.out;
+
+ if (enable) {
+ hw->poll_mode = opdo->try_poll;
+
+ if (hw->poll_mode) {
+ oss_poll_in(hw);
+ }
+ } else {
+ if (hw->poll_mode) {
+ qemu_set_fd_handler (oss->fd, NULL, NULL, NULL);
+ hw->poll_mode = 0;
+ }
+ }
+}
+
+static void oss_init_per_direction(AudiodevOssPerDirectionOptions *opdo)
+{
+ if (!opdo->has_try_poll) {
+ opdo->try_poll = true;
+ opdo->has_try_poll = true;
+ }
+}
+
+static void *oss_audio_init(Audiodev *dev)
+{
+ AudiodevOssOptions *oopts;
+ assert(dev->driver == AUDIODEV_DRIVER_OSS);
+
+ oopts = &dev->u.oss;
+ oss_init_per_direction(oopts->in);
+ oss_init_per_direction(oopts->out);
+
+ if (access(oopts->in->has_dev ? oopts->in->dev : "/dev/dsp",
+ R_OK | W_OK) < 0 ||
+ access(oopts->out->has_dev ? oopts->out->dev : "/dev/dsp",
+ R_OK | W_OK) < 0) {
+ return NULL;
+ }
+ return dev;
+}
+
+static void oss_audio_fini (void *opaque)
+{
+}
+
+static struct audio_pcm_ops oss_pcm_ops = {
+ .init_out = oss_init_out,
+ .fini_out = oss_fini_out,
+ .write = oss_write,
+ .run_buffer_out = oss_run_buffer_out,
+ .get_buffer_out = oss_get_buffer_out,
+ .put_buffer_out = oss_put_buffer_out,
+ .enable_out = oss_enable_out,
+
+ .init_in = oss_init_in,
+ .fini_in = oss_fini_in,
+ .read = oss_read,
+ .run_buffer_in = audio_generic_run_buffer_in,
+ .enable_in = oss_enable_in
+};
+
+static struct audio_driver oss_audio_driver = {
+ .name = "oss",
+ .descr = "OSS http://www.opensound.com",
+ .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)
+};
+
+static void register_audio_oss(void)
+{
+ audio_driver_register(&oss_audio_driver);
+}
+type_init(register_audio_oss);
diff --git a/audio/paaudio.c b/audio/paaudio.c
new file mode 100644
index 000000000..75401d539
--- /dev/null
+++ b/audio/paaudio.c
@@ -0,0 +1,933 @@
+/* public domain */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "audio.h"
+#include "qapi/opts-visitor.h"
+
+#include <pulse/pulseaudio.h>
+
+#define AUDIO_CAP "pulseaudio"
+#include "audio_int.h"
+
+typedef struct PAConnection {
+ char *server;
+ int refcount;
+ QTAILQ_ENTRY(PAConnection) list;
+
+ pa_threaded_mainloop *mainloop;
+ pa_context *context;
+} PAConnection;
+
+static QTAILQ_HEAD(PAConnectionHead, PAConnection) pa_conns =
+ QTAILQ_HEAD_INITIALIZER(pa_conns);
+
+typedef struct {
+ Audiodev *dev;
+ PAConnection *conn;
+} paaudio;
+
+typedef struct {
+ HWVoiceOut hw;
+ pa_stream *stream;
+ paaudio *g;
+} PAVoiceOut;
+
+typedef struct {
+ HWVoiceIn hw;
+ pa_stream *stream;
+ const void *read_data;
+ size_t read_length;
+ paaudio *g;
+} PAVoiceIn;
+
+static void qpa_conn_fini(PAConnection *c);
+
+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, expression, label, msg) \
+ do { \
+ if (!(expression)) { \
+ qpa_logerr(pa_context_errno((c)->context), msg); \
+ goto label; \
+ } \
+ } while (0)
+
+#define CHECK_DEAD_GOTO(c, stream, label, msg) \
+ 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)) { \
+ qpa_logerr(pa_context_errno((c)->context), msg); \
+ } else { \
+ qpa_logerr(PA_ERR_BADSTATE, msg); \
+ } \
+ goto label; \
+ } \
+ } while (0)
+
+static void *qpa_get_buffer_in(HWVoiceIn *hw, size_t *size)
+{
+ PAVoiceIn *p = (PAVoiceIn *) hw;
+ PAConnection *c = p->g->conn;
+ int r;
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
+ "pa_threaded_mainloop_lock failed\n");
+
+ if (!p->read_length) {
+ r = pa_stream_peek(p->stream, &p->read_data, &p->read_length);
+ CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
+ "pa_stream_peek failed\n");
+ }
+
+ *size = MIN(p->read_length, *size);
+
+ pa_threaded_mainloop_unlock(c->mainloop);
+ return (void *) p->read_data;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock(c->mainloop);
+ *size = 0;
+ return NULL;
+}
+
+static void qpa_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
+{
+ PAVoiceIn *p = (PAVoiceIn *) hw;
+ PAConnection *c = p->g->conn;
+ int r;
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ CHECK_DEAD_GOTO(c, p->stream, unlock,
+ "pa_threaded_mainloop_lock failed\n");
+
+ assert(buf == p->read_data && size <= p->read_length);
+
+ p->read_data += size;
+ p->read_length -= size;
+
+ if (size && !p->read_length) {
+ r = pa_stream_drop(p->stream);
+ CHECK_SUCCESS_GOTO(c, r == 0, unlock, "pa_stream_drop failed\n");
+ }
+
+unlock:
+ pa_threaded_mainloop_unlock(c->mainloop);
+}
+
+static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length)
+{
+ PAVoiceIn *p = (PAVoiceIn *) hw;
+ PAConnection *c = p->g->conn;
+ size_t total = 0;
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
+ "pa_threaded_mainloop_lock failed\n");
+ if (pa_stream_get_state(p->stream) != PA_STREAM_READY) {
+ /* wait for stream to become ready */
+ goto unlock;
+ }
+
+ while (total < length) {
+ size_t l;
+ int r;
+
+ if (!p->read_length) {
+ r = pa_stream_peek(p->stream, &p->read_data, &p->read_length);
+ CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
+ "pa_stream_peek failed\n");
+ if (!p->read_length) {
+ /* buffer is empty */
+ break;
+ }
+ }
+
+ l = MIN(p->read_length, length - total);
+ memcpy((char *)data + total, p->read_data, l);
+
+ p->read_data += l;
+ p->read_length -= l;
+ total += l;
+
+ if (!p->read_length) {
+ r = pa_stream_drop(p->stream);
+ CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
+ "pa_stream_drop failed\n");
+ }
+ }
+
+unlock:
+ pa_threaded_mainloop_unlock(c->mainloop);
+ return total;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock(c->mainloop);
+ return 0;
+}
+
+static void *qpa_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+ PAVoiceOut *p = (PAVoiceOut *) hw;
+ PAConnection *c = p->g->conn;
+ void *ret;
+ size_t l;
+ int r;
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
+ "pa_threaded_mainloop_lock failed\n");
+ if (pa_stream_get_state(p->stream) != PA_STREAM_READY) {
+ /* wait for stream to become ready */
+ l = 0;
+ ret = NULL;
+ goto unlock;
+ }
+
+ l = pa_stream_writable_size(p->stream);
+ CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail,
+ "pa_stream_writable_size failed\n");
+
+ *size = -1;
+ r = pa_stream_begin_write(p->stream, &ret, size);
+ CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail,
+ "pa_stream_begin_write failed\n");
+
+unlock:
+ pa_threaded_mainloop_unlock(c->mainloop);
+ if (*size > l) {
+ *size = l;
+ }
+ return ret;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock(c->mainloop);
+ *size = 0;
+ return NULL;
+}
+
+static size_t qpa_put_buffer_out(HWVoiceOut *hw, void *data, size_t length)
+{
+ PAVoiceOut *p = (PAVoiceOut *)hw;
+ PAConnection *c = p->g->conn;
+ int r;
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
+ "pa_threaded_mainloop_lock failed\n");
+
+ r = pa_stream_write(p->stream, data, length, NULL, 0LL, PA_SEEK_RELATIVE);
+ CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n");
+
+ pa_threaded_mainloop_unlock(c->mainloop);
+ return length;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock(c->mainloop);
+ return 0;
+}
+
+static size_t qpa_write(HWVoiceOut *hw, void *data, size_t length)
+{
+ PAVoiceOut *p = (PAVoiceOut *) hw;
+ PAConnection *c = p->g->conn;
+ size_t l;
+ int r;
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
+ "pa_threaded_mainloop_lock failed\n");
+ if (pa_stream_get_state(p->stream) != PA_STREAM_READY) {
+ /* wait for stream to become ready */
+ l = 0;
+ goto unlock;
+ }
+
+ l = pa_stream_writable_size(p->stream);
+
+ CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail,
+ "pa_stream_writable_size failed\n");
+
+ if (l > length) {
+ l = length;
+ }
+
+ r = pa_stream_write(p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE);
+ CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n");
+
+unlock:
+ pa_threaded_mainloop_unlock(c->mainloop);
+ return l;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock(c->mainloop);
+ return 0;
+}
+
+static pa_sample_format_t audfmt_to_pa (AudioFormat afmt, int endianness)
+{
+ int format;
+
+ switch (afmt) {
+ case AUDIO_FORMAT_S8:
+ case AUDIO_FORMAT_U8:
+ format = PA_SAMPLE_U8;
+ break;
+ case AUDIO_FORMAT_S16:
+ case AUDIO_FORMAT_U16:
+ format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
+ break;
+ case AUDIO_FORMAT_S32:
+ case AUDIO_FORMAT_U32:
+ format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
+ break;
+ case AUDIO_FORMAT_F32:
+ format = endianness ? PA_SAMPLE_FLOAT32BE : PA_SAMPLE_FLOAT32LE;
+ break;
+ default:
+ dolog ("Internal logic error: Bad audio format %d\n", afmt);
+ format = PA_SAMPLE_U8;
+ break;
+ }
+ return format;
+}
+
+static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, int *endianness)
+{
+ switch (fmt) {
+ case PA_SAMPLE_U8:
+ return AUDIO_FORMAT_U8;
+ case PA_SAMPLE_S16BE:
+ *endianness = 1;
+ return AUDIO_FORMAT_S16;
+ case PA_SAMPLE_S16LE:
+ *endianness = 0;
+ return AUDIO_FORMAT_S16;
+ case PA_SAMPLE_S32BE:
+ *endianness = 1;
+ return AUDIO_FORMAT_S32;
+ case PA_SAMPLE_S32LE:
+ *endianness = 0;
+ return AUDIO_FORMAT_S32;
+ case PA_SAMPLE_FLOAT32BE:
+ *endianness = 1;
+ return AUDIO_FORMAT_F32;
+ case PA_SAMPLE_FLOAT32LE:
+ *endianness = 0;
+ return AUDIO_FORMAT_F32;
+ default:
+ dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt);
+ return AUDIO_FORMAT_U8;
+ }
+}
+
+static void context_state_cb (pa_context *c, void *userdata)
+{
+ PAConnection *conn = userdata;
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ pa_threaded_mainloop_signal(conn->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)
+{
+ PAConnection *c = userdata;
+
+ switch (pa_stream_get_state (s)) {
+
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ pa_threaded_mainloop_signal(c->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ }
+}
+
+static pa_stream *qpa_simple_new (
+ PAConnection *c,
+ const char *name,
+ pa_stream_direction_t dir,
+ const char *dev,
+ const pa_sample_spec *ss,
+ const pa_buffer_attr *attr,
+ int *rerror)
+{
+ int r;
+ pa_stream *stream = NULL;
+ pa_stream_flags_t flags;
+ pa_channel_map map;
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ pa_channel_map_init(&map);
+ map.channels = ss->channels;
+
+ /*
+ * TODO: This currently expects the only frontend supporting more than 2
+ * channels is the usb-audio. We will need some means to set channel
+ * order when a new frontend gains multi-channel support.
+ */
+ switch (ss->channels) {
+ case 1:
+ map.map[0] = PA_CHANNEL_POSITION_MONO;
+ break;
+
+ case 2:
+ map.map[0] = PA_CHANNEL_POSITION_LEFT;
+ map.map[1] = PA_CHANNEL_POSITION_RIGHT;
+ break;
+
+ case 6:
+ map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ map.map[2] = PA_CHANNEL_POSITION_CENTER;
+ map.map[3] = PA_CHANNEL_POSITION_LFE;
+ map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
+ map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
+ break;
+
+ case 8:
+ map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ map.map[2] = PA_CHANNEL_POSITION_CENTER;
+ map.map[3] = PA_CHANNEL_POSITION_LFE;
+ map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
+ map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
+ map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
+ map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
+ break;
+
+ default:
+ dolog("Internal error: unsupported channel count %d\n", ss->channels);
+ goto fail;
+ }
+
+ stream = pa_stream_new(c->context, name, ss, &map);
+ if (!stream) {
+ goto fail;
+ }
+
+ pa_stream_set_state_callback(stream, stream_state_cb, c);
+
+ flags = PA_STREAM_EARLY_REQUESTS;
+
+ if (dev) {
+ /* don't move the stream if the user specified a sink/source */
+ flags |= PA_STREAM_DONT_MOVE;
+ }
+
+ if (dir == PA_STREAM_PLAYBACK) {
+ r = pa_stream_connect_playback(stream, dev, attr, flags, NULL, NULL);
+ } else {
+ r = pa_stream_connect_record(stream, dev, attr, flags);
+ }
+
+ if (r < 0) {
+ goto fail;
+ }
+
+ pa_threaded_mainloop_unlock(c->mainloop);
+
+ return stream;
+
+fail:
+ pa_threaded_mainloop_unlock(c->mainloop);
+
+ if (stream) {
+ pa_stream_unref (stream);
+ }
+
+ *rerror = pa_context_errno(c->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;
+ AudiodevPaOptions *popts = &g->dev->u.pa;
+ AudiodevPaPerDirectionOptions *ppdo = popts->out;
+ PAConnection *c = g->conn;
+
+ ss.format = audfmt_to_pa (as->fmt, as->endianness);
+ ss.channels = as->nchannels;
+ ss.rate = as->freq;
+
+ ba.tlength = pa_usec_to_bytes(ppdo->latency, &ss);
+ ba.minreq = pa_usec_to_bytes(MIN(ppdo->latency >> 2,
+ (g->dev->timer_period >> 2) * 3), &ss);
+ ba.maxlength = -1;
+ ba.prebuf = -1;
+
+ obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
+
+ pa->stream = qpa_simple_new (
+ c,
+ ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
+ PA_STREAM_PLAYBACK,
+ ppdo->has_name ? ppdo->name : NULL,
+ &ss,
+ &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);
+ /*
+ * This is wrong. hw->samples counts in frames. hw->samples will be
+ * number of channels times larger than expected.
+ */
+ hw->samples = audio_buffer_samples(
+ qapi_AudiodevPaPerDirectionOptions_base(ppdo), &obt_as, 46440);
+
+ return 0;
+
+ fail1:
+ return -1;
+}
+
+static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
+{
+ int error;
+ pa_sample_spec ss;
+ pa_buffer_attr ba;
+ struct audsettings obt_as = *as;
+ PAVoiceIn *pa = (PAVoiceIn *) hw;
+ paaudio *g = pa->g = drv_opaque;
+ AudiodevPaOptions *popts = &g->dev->u.pa;
+ AudiodevPaPerDirectionOptions *ppdo = popts->in;
+ PAConnection *c = g->conn;
+
+ ss.format = audfmt_to_pa (as->fmt, as->endianness);
+ ss.channels = as->nchannels;
+ ss.rate = as->freq;
+
+ ba.fragsize = pa_usec_to_bytes((g->dev->timer_period >> 1) * 3, &ss);
+ ba.maxlength = pa_usec_to_bytes(
+ MAX(ppdo->latency, g->dev->timer_period * 3), &ss);
+ ba.minreq = -1;
+ ba.prebuf = -1;
+
+ obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
+
+ pa->stream = qpa_simple_new (
+ c,
+ ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
+ PA_STREAM_RECORD,
+ ppdo->has_name ? ppdo->name : NULL,
+ &ss,
+ &ba, /* 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);
+ /*
+ * This is wrong. hw->samples counts in frames. hw->samples will be
+ * number of channels times larger than expected.
+ */
+ hw->samples = audio_buffer_samples(
+ qapi_AudiodevPaPerDirectionOptions_base(ppdo), &obt_as, 46440);
+
+ return 0;
+
+ fail1:
+ return -1;
+}
+
+static void qpa_simple_disconnect(PAConnection *c, pa_stream *stream)
+{
+ int err;
+
+ /*
+ * wait until actually connects. workaround pa bug #247
+ * https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/247
+ */
+ while (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+ pa_threaded_mainloop_wait(c->mainloop);
+ }
+
+ err = pa_stream_disconnect(stream);
+ if (err != 0) {
+ dolog("Failed to disconnect! err=%d\n", err);
+ }
+ pa_stream_unref(stream);
+}
+
+static void qpa_fini_out (HWVoiceOut *hw)
+{
+ PAVoiceOut *pa = (PAVoiceOut *) hw;
+
+ if (pa->stream) {
+ PAConnection *c = pa->g->conn;
+
+ pa_threaded_mainloop_lock(c->mainloop);
+ qpa_simple_disconnect(c, pa->stream);
+ pa->stream = NULL;
+ pa_threaded_mainloop_unlock(c->mainloop);
+ }
+}
+
+static void qpa_fini_in (HWVoiceIn *hw)
+{
+ PAVoiceIn *pa = (PAVoiceIn *) hw;
+
+ if (pa->stream) {
+ PAConnection *c = pa->g->conn;
+
+ pa_threaded_mainloop_lock(c->mainloop);
+ if (pa->read_length) {
+ int r = pa_stream_drop(pa->stream);
+ if (r) {
+ qpa_logerr(pa_context_errno(c->context),
+ "pa_stream_drop failed\n");
+ }
+ pa->read_length = 0;
+ }
+ qpa_simple_disconnect(c, pa->stream);
+ pa->stream = NULL;
+ pa_threaded_mainloop_unlock(c->mainloop);
+ }
+}
+
+static void qpa_volume_out(HWVoiceOut *hw, Volume *vol)
+{
+ PAVoiceOut *pa = (PAVoiceOut *) hw;
+ pa_operation *op;
+ pa_cvolume v;
+ PAConnection *c = pa->g->conn;
+ int i;
+
+#ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */
+ pa_cvolume_init (&v); /* function is present in 0.9.13+ */
+#endif
+
+ v.channels = vol->channels;
+ for (i = 0; i < vol->channels; ++i) {
+ v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
+ }
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ op = pa_context_set_sink_input_volume(c->context,
+ pa_stream_get_index(pa->stream),
+ &v, NULL, NULL);
+ if (!op) {
+ qpa_logerr(pa_context_errno(c->context),
+ "set_sink_input_volume() failed\n");
+ } else {
+ pa_operation_unref(op);
+ }
+
+ op = pa_context_set_sink_input_mute(c->context,
+ pa_stream_get_index(pa->stream),
+ vol->mute, NULL, NULL);
+ if (!op) {
+ qpa_logerr(pa_context_errno(c->context),
+ "set_sink_input_mute() failed\n");
+ } else {
+ pa_operation_unref(op);
+ }
+
+ pa_threaded_mainloop_unlock(c->mainloop);
+}
+
+static void qpa_volume_in(HWVoiceIn *hw, Volume *vol)
+{
+ PAVoiceIn *pa = (PAVoiceIn *) hw;
+ pa_operation *op;
+ pa_cvolume v;
+ PAConnection *c = pa->g->conn;
+ int i;
+
+#ifdef PA_CHECK_VERSION
+ pa_cvolume_init (&v);
+#endif
+
+ v.channels = vol->channels;
+ for (i = 0; i < vol->channels; ++i) {
+ v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
+ }
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ op = pa_context_set_source_output_volume(c->context,
+ pa_stream_get_index(pa->stream),
+ &v, NULL, NULL);
+ if (!op) {
+ qpa_logerr(pa_context_errno(c->context),
+ "set_source_output_volume() failed\n");
+ } else {
+ pa_operation_unref(op);
+ }
+
+ op = pa_context_set_source_output_mute(c->context,
+ pa_stream_get_index(pa->stream),
+ vol->mute, NULL, NULL);
+ if (!op) {
+ qpa_logerr(pa_context_errno(c->context),
+ "set_source_output_mute() failed\n");
+ } else {
+ pa_operation_unref(op);
+ }
+
+ pa_threaded_mainloop_unlock(c->mainloop);
+}
+
+static int qpa_validate_per_direction_opts(Audiodev *dev,
+ AudiodevPaPerDirectionOptions *pdo)
+{
+ if (!pdo->has_latency) {
+ pdo->has_latency = true;
+ pdo->latency = 15000;
+ }
+ return 1;
+}
+
+/* common */
+static void *qpa_conn_init(const char *server)
+{
+ PAConnection *c = g_malloc0(sizeof(PAConnection));
+ QTAILQ_INSERT_TAIL(&pa_conns, c, list);
+
+ c->mainloop = pa_threaded_mainloop_new();
+ if (!c->mainloop) {
+ goto fail;
+ }
+
+ c->context = pa_context_new(pa_threaded_mainloop_get_api(c->mainloop),
+ audio_application_name());
+ if (!c->context) {
+ goto fail;
+ }
+
+ pa_context_set_state_callback(c->context, context_state_cb, c);
+
+ if (pa_context_connect(c->context, server, 0, NULL) < 0) {
+ qpa_logerr(pa_context_errno(c->context),
+ "pa_context_connect() failed\n");
+ goto fail;
+ }
+
+ pa_threaded_mainloop_lock(c->mainloop);
+
+ if (pa_threaded_mainloop_start(c->mainloop) < 0) {
+ goto unlock_and_fail;
+ }
+
+ for (;;) {
+ pa_context_state_t state;
+
+ state = pa_context_get_state(c->context);
+
+ if (state == PA_CONTEXT_READY) {
+ break;
+ }
+
+ if (!PA_CONTEXT_IS_GOOD(state)) {
+ qpa_logerr(pa_context_errno(c->context),
+ "Wrong context state\n");
+ goto unlock_and_fail;
+ }
+
+ /* Wait until the context is ready */
+ pa_threaded_mainloop_wait(c->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(c->mainloop);
+ return c;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock(c->mainloop);
+fail:
+ AUD_log (AUDIO_CAP, "Failed to initialize PA context");
+ qpa_conn_fini(c);
+ return NULL;
+}
+
+static void *qpa_audio_init(Audiodev *dev)
+{
+ paaudio *g;
+ AudiodevPaOptions *popts = &dev->u.pa;
+ const char *server;
+ PAConnection *c;
+
+ assert(dev->driver == AUDIODEV_DRIVER_PA);
+
+ if (!popts->has_server) {
+ char pidfile[64];
+ char *runtime;
+ struct stat st;
+
+ runtime = getenv("XDG_RUNTIME_DIR");
+ if (!runtime) {
+ return NULL;
+ }
+ snprintf(pidfile, sizeof(pidfile), "%s/pulse/pid", runtime);
+ if (stat(pidfile, &st) != 0) {
+ return NULL;
+ }
+ }
+
+ if (!qpa_validate_per_direction_opts(dev, popts->in)) {
+ return NULL;
+ }
+ if (!qpa_validate_per_direction_opts(dev, popts->out)) {
+ return NULL;
+ }
+
+ g = g_malloc0(sizeof(paaudio));
+ server = popts->has_server ? popts->server : NULL;
+
+ g->dev = dev;
+
+ QTAILQ_FOREACH(c, &pa_conns, list) {
+ if (server == NULL || c->server == NULL ?
+ server == c->server :
+ strcmp(server, c->server) == 0) {
+ g->conn = c;
+ break;
+ }
+ }
+ if (!g->conn) {
+ g->conn = qpa_conn_init(server);
+ }
+ if (!g->conn) {
+ g_free(g);
+ return NULL;
+ }
+
+ ++g->conn->refcount;
+ return g;
+}
+
+static void qpa_conn_fini(PAConnection *c)
+{
+ if (c->mainloop) {
+ pa_threaded_mainloop_stop(c->mainloop);
+ }
+
+ if (c->context) {
+ pa_context_disconnect(c->context);
+ pa_context_unref(c->context);
+ }
+
+ if (c->mainloop) {
+ pa_threaded_mainloop_free(c->mainloop);
+ }
+
+ QTAILQ_REMOVE(&pa_conns, c, list);
+ g_free(c);
+}
+
+static void qpa_audio_fini (void *opaque)
+{
+ paaudio *g = opaque;
+ PAConnection *c = g->conn;
+
+ if (--c->refcount == 0) {
+ qpa_conn_fini(c);
+ }
+
+ g_free(g);
+}
+
+static struct audio_pcm_ops qpa_pcm_ops = {
+ .init_out = qpa_init_out,
+ .fini_out = qpa_fini_out,
+ .write = qpa_write,
+ .get_buffer_out = qpa_get_buffer_out,
+ .put_buffer_out = qpa_put_buffer_out,
+ .volume_out = qpa_volume_out,
+
+ .init_in = qpa_init_in,
+ .fini_in = qpa_fini_in,
+ .read = qpa_read,
+ .get_buffer_in = qpa_get_buffer_in,
+ .put_buffer_in = qpa_put_buffer_in,
+ .volume_in = qpa_volume_in
+};
+
+static struct audio_driver pa_audio_driver = {
+ .name = "pa",
+ .descr = "http://www.pulseaudio.org/",
+ .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),
+};
+
+static void register_audio_pa(void)
+{
+ audio_driver_register(&pa_audio_driver);
+}
+type_init(register_audio_pa);
diff --git a/audio/rate_template.h b/audio/rate_template.h
new file mode 100644
index 000000000..f94c940c6
--- /dev/null
+++ b/audio/rate_template.h
@@ -0,0 +1,117 @@
+/*
+ * 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,
+ size_t *isamp, size_t *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++;
+
+ /* if ipos overflow, there is a infinite loop */
+ if (rate->ipos == 0xffffffff) {
+ rate->ipos = 1;
+ rate->opos = rate->opos & 0xffffffff;
+ }
+ /* 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/audio/sdlaudio.c b/audio/sdlaudio.c
new file mode 100644
index 000000000..c68c62a3e
--- /dev/null
+++ b/audio/sdlaudio.c
@@ -0,0 +1,508 @@
+/*
+ * 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 "qemu/osdep.h"
+#include <SDL.h>
+#include <SDL_thread.h>
+#include "qemu/module.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 exit;
+ int initialized;
+ Audiodev *dev;
+ SDL_AudioDeviceID devid;
+} SDLVoiceOut;
+
+typedef struct SDLVoiceIn {
+ HWVoiceIn hw;
+ int exit;
+ int initialized;
+ Audiodev *dev;
+ SDL_AudioDeviceID devid;
+} SDLVoiceIn;
+
+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 aud_to_sdlfmt (AudioFormat fmt)
+{
+ switch (fmt) {
+ case AUDIO_FORMAT_S8:
+ return AUDIO_S8;
+
+ case AUDIO_FORMAT_U8:
+ return AUDIO_U8;
+
+ case AUDIO_FORMAT_S16:
+ return AUDIO_S16LSB;
+
+ case AUDIO_FORMAT_U16:
+ return AUDIO_U16LSB;
+
+ case AUDIO_FORMAT_S32:
+ return AUDIO_S32LSB;
+
+ /* no unsigned 32-bit support in SDL */
+
+ case AUDIO_FORMAT_F32:
+ return AUDIO_F32LSB;
+
+ 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, AudioFormat *fmt, int *endianness)
+{
+ switch (sdlfmt) {
+ case AUDIO_S8:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_S8;
+ break;
+
+ case AUDIO_U8:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_U8;
+ break;
+
+ case AUDIO_S16LSB:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_S16;
+ break;
+
+ case AUDIO_U16LSB:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_U16;
+ break;
+
+ case AUDIO_S16MSB:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_S16;
+ break;
+
+ case AUDIO_U16MSB:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_U16;
+ break;
+
+ case AUDIO_S32LSB:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_S32;
+ break;
+
+ case AUDIO_S32MSB:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_S32;
+ break;
+
+ case AUDIO_F32LSB:
+ *endianness = 0;
+ *fmt = AUDIO_FORMAT_F32;
+ break;
+
+ case AUDIO_F32MSB:
+ *endianness = 1;
+ *fmt = AUDIO_FORMAT_F32;
+ break;
+
+ default:
+ dolog ("Unrecognized SDL audio format %d\n", sdlfmt);
+ return -1;
+ }
+
+ return 0;
+}
+
+static SDL_AudioDeviceID sdl_open(SDL_AudioSpec *req, SDL_AudioSpec *obt,
+ int rec)
+{
+ SDL_AudioDeviceID devid;
+#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 0;
+ }
+ err = pthread_sigmask (SIG_BLOCK, &new, &old);
+ if (err) {
+ dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err));
+ return 0;
+ }
+#endif
+
+ devid = SDL_OpenAudioDevice(NULL, rec, req, obt, 0);
+ if (!devid) {
+ sdl_logerr("SDL_OpenAudioDevice for %s failed\n",
+ rec ? "recording" : "playback");
+ }
+
+#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 devid;
+}
+
+static void sdl_close_out(SDLVoiceOut *sdl)
+{
+ if (sdl->initialized) {
+ SDL_LockAudioDevice(sdl->devid);
+ sdl->exit = 1;
+ SDL_UnlockAudioDevice(sdl->devid);
+ SDL_PauseAudioDevice(sdl->devid, 1);
+ sdl->initialized = 0;
+ }
+ if (sdl->devid) {
+ SDL_CloseAudioDevice(sdl->devid);
+ sdl->devid = 0;
+ }
+}
+
+static void sdl_callback_out(void *opaque, Uint8 *buf, int len)
+{
+ SDLVoiceOut *sdl = opaque;
+ HWVoiceOut *hw = &sdl->hw;
+
+ if (!sdl->exit) {
+
+ /* dolog("callback_out: len=%d avail=%zu\n", len, hw->pending_emul); */
+
+ while (hw->pending_emul && len) {
+ size_t write_len;
+ ssize_t start = (ssize_t)hw->pos_emul - hw->pending_emul;
+ if (start < 0) {
+ start += hw->size_emul;
+ }
+ assert(start >= 0 && start < hw->size_emul);
+
+ write_len = MIN(MIN(hw->pending_emul, len),
+ hw->size_emul - start);
+
+ memcpy(buf, hw->buf_emul + start, write_len);
+ hw->pending_emul -= write_len;
+ len -= write_len;
+ buf += write_len;
+ }
+ }
+
+ /* clear remaining buffer that we couldn't fill with data */
+ if (len) {
+ audio_pcm_info_clear_buf(&hw->info, buf,
+ len / hw->info.bytes_per_frame);
+ }
+}
+
+static void sdl_close_in(SDLVoiceIn *sdl)
+{
+ if (sdl->initialized) {
+ SDL_LockAudioDevice(sdl->devid);
+ sdl->exit = 1;
+ SDL_UnlockAudioDevice(sdl->devid);
+ SDL_PauseAudioDevice(sdl->devid, 1);
+ sdl->initialized = 0;
+ }
+ if (sdl->devid) {
+ SDL_CloseAudioDevice(sdl->devid);
+ sdl->devid = 0;
+ }
+}
+
+static void sdl_callback_in(void *opaque, Uint8 *buf, int len)
+{
+ SDLVoiceIn *sdl = opaque;
+ HWVoiceIn *hw = &sdl->hw;
+
+ if (sdl->exit) {
+ return;
+ }
+
+ /* dolog("callback_in: len=%d pending=%zu\n", len, hw->pending_emul); */
+
+ while (hw->pending_emul < hw->size_emul && len) {
+ size_t read_len = MIN(len, MIN(hw->size_emul - hw->pos_emul,
+ hw->size_emul - hw->pending_emul));
+
+ memcpy(hw->buf_emul + hw->pos_emul, buf, read_len);
+
+ hw->pending_emul += read_len;
+ hw->pos_emul = (hw->pos_emul + read_len) % hw->size_emul;
+ len -= read_len;
+ buf += read_len;
+ }
+}
+
+#define SDL_WRAPPER_FUNC(name, ret_type, args_decl, args, dir) \
+ static ret_type glue(sdl_, name)args_decl \
+ { \
+ ret_type ret; \
+ glue(SDLVoice, dir) *sdl = (glue(SDLVoice, dir) *)hw; \
+ \
+ SDL_LockAudioDevice(sdl->devid); \
+ ret = glue(audio_generic_, name)args; \
+ SDL_UnlockAudioDevice(sdl->devid); \
+ \
+ return ret; \
+ }
+
+#define SDL_WRAPPER_VOID_FUNC(name, args_decl, args, dir) \
+ static void glue(sdl_, name)args_decl \
+ { \
+ glue(SDLVoice, dir) *sdl = (glue(SDLVoice, dir) *)hw; \
+ \
+ SDL_LockAudioDevice(sdl->devid); \
+ glue(audio_generic_, name)args; \
+ SDL_UnlockAudioDevice(sdl->devid); \
+ }
+
+SDL_WRAPPER_FUNC(get_buffer_out, void *, (HWVoiceOut *hw, size_t *size),
+ (hw, size), Out)
+SDL_WRAPPER_FUNC(put_buffer_out, size_t,
+ (HWVoiceOut *hw, void *buf, size_t size), (hw, buf, size), Out)
+SDL_WRAPPER_FUNC(write, size_t,
+ (HWVoiceOut *hw, void *buf, size_t size), (hw, buf, size), Out)
+SDL_WRAPPER_FUNC(read, size_t, (HWVoiceIn *hw, void *buf, size_t size),
+ (hw, buf, size), In)
+SDL_WRAPPER_FUNC(get_buffer_in, void *, (HWVoiceIn *hw, size_t *size),
+ (hw, size), In)
+SDL_WRAPPER_VOID_FUNC(put_buffer_in, (HWVoiceIn *hw, void *buf, size_t size),
+ (hw, buf, size), In)
+#undef SDL_WRAPPER_FUNC
+#undef SDL_WRAPPER_VOID_FUNC
+
+static void sdl_fini_out(HWVoiceOut *hw)
+{
+ SDLVoiceOut *sdl = (SDLVoiceOut *)hw;
+
+ sdl_close_out(sdl);
+}
+
+static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as,
+ void *drv_opaque)
+{
+ SDLVoiceOut *sdl = (SDLVoiceOut *)hw;
+ SDL_AudioSpec req, obt;
+ int endianness;
+ int err;
+ AudioFormat effective_fmt;
+ Audiodev *dev = drv_opaque;
+ AudiodevSdlPerDirectionOptions *spdo = dev->u.sdl.out;
+ struct audsettings obt_as;
+
+ req.freq = as->freq;
+ req.format = aud_to_sdlfmt (as->fmt);
+ req.channels = as->nchannels;
+ /*
+ * This is wrong. SDL samples are QEMU frames. The buffer size will be
+ * the requested buffer size multiplied by the number of channels.
+ */
+ req.samples = audio_buffer_samples(
+ qapi_AudiodevSdlPerDirectionOptions_base(spdo), as, 11610);
+ req.callback = sdl_callback_out;
+ req.userdata = sdl;
+
+ sdl->dev = dev;
+ sdl->devid = sdl_open(&req, &obt, 0);
+ if (!sdl->devid) {
+ return -1;
+ }
+
+ err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness);
+ if (err) {
+ sdl_close_out(sdl);
+ 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 = (spdo->has_buffer_count ? spdo->buffer_count : 4) *
+ obt.samples;
+
+ sdl->initialized = 1;
+ sdl->exit = 0;
+ return 0;
+}
+
+static void sdl_enable_out(HWVoiceOut *hw, bool enable)
+{
+ SDLVoiceOut *sdl = (SDLVoiceOut *)hw;
+
+ SDL_PauseAudioDevice(sdl->devid, !enable);
+}
+
+static void sdl_fini_in(HWVoiceIn *hw)
+{
+ SDLVoiceIn *sdl = (SDLVoiceIn *)hw;
+
+ sdl_close_in(sdl);
+}
+
+static int sdl_init_in(HWVoiceIn *hw, audsettings *as, void *drv_opaque)
+{
+ SDLVoiceIn *sdl = (SDLVoiceIn *)hw;
+ SDL_AudioSpec req, obt;
+ int endianness;
+ int err;
+ AudioFormat effective_fmt;
+ Audiodev *dev = drv_opaque;
+ AudiodevSdlPerDirectionOptions *spdo = dev->u.sdl.in;
+ struct audsettings obt_as;
+
+ req.freq = as->freq;
+ req.format = aud_to_sdlfmt(as->fmt);
+ req.channels = as->nchannels;
+ /* SDL samples are QEMU frames */
+ req.samples = audio_buffer_frames(
+ qapi_AudiodevSdlPerDirectionOptions_base(spdo), as, 11610);
+ req.callback = sdl_callback_in;
+ req.userdata = sdl;
+
+ sdl->dev = dev;
+ sdl->devid = sdl_open(&req, &obt, 1);
+ if (!sdl->devid) {
+ return -1;
+ }
+
+ err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness);
+ if (err) {
+ sdl_close_in(sdl);
+ 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 = (spdo->has_buffer_count ? spdo->buffer_count : 4) *
+ obt.samples;
+ hw->size_emul = hw->samples * hw->info.bytes_per_frame;
+ hw->buf_emul = g_malloc(hw->size_emul);
+ hw->pos_emul = hw->pending_emul = 0;
+
+ sdl->initialized = 1;
+ sdl->exit = 0;
+ return 0;
+}
+
+static void sdl_enable_in(HWVoiceIn *hw, bool enable)
+{
+ SDLVoiceIn *sdl = (SDLVoiceIn *)hw;
+
+ SDL_PauseAudioDevice(sdl->devid, !enable);
+}
+
+static void *sdl_audio_init(Audiodev *dev)
+{
+ if (SDL_InitSubSystem (SDL_INIT_AUDIO)) {
+ sdl_logerr ("SDL failed to initialize audio subsystem\n");
+ return NULL;
+ }
+
+ return dev;
+}
+
+static void sdl_audio_fini (void *opaque)
+{
+ SDL_QuitSubSystem (SDL_INIT_AUDIO);
+}
+
+static struct audio_pcm_ops sdl_pcm_ops = {
+ .init_out = sdl_init_out,
+ .fini_out = sdl_fini_out,
+ /* wrapper for audio_generic_write */
+ .write = sdl_write,
+ /* wrapper for audio_generic_get_buffer_out */
+ .get_buffer_out = sdl_get_buffer_out,
+ /* wrapper for audio_generic_put_buffer_out */
+ .put_buffer_out = sdl_put_buffer_out,
+ .enable_out = sdl_enable_out,
+ .init_in = sdl_init_in,
+ .fini_in = sdl_fini_in,
+ /* wrapper for audio_generic_read */
+ .read = sdl_read,
+ /* wrapper for audio_generic_get_buffer_in */
+ .get_buffer_in = sdl_get_buffer_in,
+ /* wrapper for audio_generic_put_buffer_in */
+ .put_buffer_in = sdl_put_buffer_in,
+ .enable_in = sdl_enable_in,
+};
+
+static struct audio_driver sdl_audio_driver = {
+ .name = "sdl",
+ .descr = "SDL http://www.libsdl.org",
+ .init = sdl_audio_init,
+ .fini = sdl_audio_fini,
+ .pcm_ops = &sdl_pcm_ops,
+ .can_be_default = 1,
+ .max_voices_out = INT_MAX,
+ .max_voices_in = INT_MAX,
+ .voice_size_out = sizeof(SDLVoiceOut),
+ .voice_size_in = sizeof(SDLVoiceIn),
+};
+
+static void register_audio_sdl(void)
+{
+ audio_driver_register(&sdl_audio_driver);
+}
+type_init(register_audio_sdl);
diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c
new file mode 100644
index 000000000..a8d370fe6
--- /dev/null
+++ b/audio/spiceaudio.c
@@ -0,0 +1,321 @@
+/*
+ * 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 "qemu/osdep.h"
+#include "qemu/host-utils.h"
+#include "qemu/module.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 SpiceVoiceOut {
+ HWVoiceOut hw;
+ SpicePlaybackInstance sin;
+ RateCtl rate;
+ int active;
+ uint32_t *frame;
+ uint32_t fpos;
+ uint32_t fsize;
+} SpiceVoiceOut;
+
+typedef struct SpiceVoiceIn {
+ HWVoiceIn hw;
+ SpiceRecordInstance sin;
+ RateCtl rate;
+ int active;
+} 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(Audiodev *dev)
+{
+ if (!using_spice) {
+ return NULL;
+ }
+ return &spice_audio_init;
+}
+
+static void spice_audio_fini (void *opaque)
+{
+ /* nothing */
+}
+
+/* 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 = AUDIO_FORMAT_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 void *line_out_get_buffer(HWVoiceOut *hw, size_t *size)
+{
+ SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
+
+ if (!out->frame) {
+ spice_server_playback_get_buffer(&out->sin, &out->frame, &out->fsize);
+ out->fpos = 0;
+ }
+
+ if (out->frame) {
+ *size = MIN((out->fsize - out->fpos) << 2, *size);
+ }
+
+ *size = audio_rate_get_bytes(&hw->info, &out->rate, *size);
+
+ return out->frame + out->fpos;
+}
+
+static size_t line_out_put_buffer(HWVoiceOut *hw, void *buf, size_t size)
+{
+ SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
+
+ if (buf) {
+ assert(buf == out->frame + out->fpos && out->fpos <= out->fsize);
+ out->fpos += size >> 2;
+
+ if (out->fpos == out->fsize) { /* buffer full */
+ spice_server_playback_put_samples(&out->sin, out->frame);
+ out->frame = NULL;
+ }
+ }
+
+ return size;
+}
+
+static void line_out_enable(HWVoiceOut *hw, bool enable)
+{
+ SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
+
+ if (enable) {
+ if (out->active) {
+ return;
+ }
+ out->active = 1;
+ audio_rate_start(&out->rate);
+ spice_server_playback_start (&out->sin);
+ } else {
+ if (!out->active) {
+ return;
+ }
+ out->active = 0;
+ if (out->frame) {
+ memset(out->frame + out->fpos, 0, (out->fsize - out->fpos) << 2);
+ spice_server_playback_put_samples (&out->sin, out->frame);
+ out->frame = NULL;
+ }
+ spice_server_playback_stop (&out->sin);
+ }
+}
+
+#if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2))
+static void line_out_volume(HWVoiceOut *hw, Volume *vol)
+{
+ SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
+ uint16_t svol[2];
+
+ assert(vol->channels == 2);
+ svol[0] = vol->vol[0] * 257;
+ svol[1] = vol->vol[1] * 257;
+ spice_server_playback_set_volume(&out->sin, 2, svol);
+ spice_server_playback_set_mute(&out->sin, vol->mute);
+}
+#endif
+
+/* 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 = AUDIO_FORMAT_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 size_t line_in_read(HWVoiceIn *hw, void *buf, size_t len)
+{
+ SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
+ uint64_t to_read = audio_rate_get_bytes(&hw->info, &in->rate, len) >> 2;
+ size_t ready = spice_server_record_get_samples(&in->sin, buf, to_read);
+
+ /* XXX: do we need this? */
+ if (ready == 0) {
+ memset(buf, 0, to_read << 2);
+ ready = to_read;
+ }
+
+ return ready << 2;
+}
+
+static void line_in_enable(HWVoiceIn *hw, bool enable)
+{
+ SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
+
+ if (enable) {
+ if (in->active) {
+ return;
+ }
+ in->active = 1;
+ audio_rate_start(&in->rate);
+ spice_server_record_start (&in->sin);
+ } else {
+ if (!in->active) {
+ return;
+ }
+ in->active = 0;
+ spice_server_record_stop (&in->sin);
+ }
+}
+
+#if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2))
+static void line_in_volume(HWVoiceIn *hw, Volume *vol)
+{
+ SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
+ uint16_t svol[2];
+
+ assert(vol->channels == 2);
+ svol[0] = vol->vol[0] * 257;
+ svol[1] = vol->vol[1] * 257;
+ spice_server_record_set_volume(&in->sin, 2, svol);
+ spice_server_record_set_mute(&in->sin, vol->mute);
+}
+#endif
+
+static struct audio_pcm_ops audio_callbacks = {
+ .init_out = line_out_init,
+ .fini_out = line_out_fini,
+ .write = audio_generic_write,
+ .get_buffer_out = line_out_get_buffer,
+ .put_buffer_out = line_out_put_buffer,
+ .enable_out = line_out_enable,
+#if (SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && \
+ (SPICE_INTERFACE_PLAYBACK_MINOR >= 2)
+ .volume_out = line_out_volume,
+#endif
+
+ .init_in = line_in_init,
+ .fini_in = line_in_fini,
+ .read = line_in_read,
+ .run_buffer_in = audio_generic_run_buffer_in,
+ .enable_in = line_in_enable,
+#if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2))
+ .volume_in = line_in_volume,
+#endif
+};
+
+static struct audio_driver spice_audio_driver = {
+ .name = "spice",
+ .descr = "spice audio driver",
+ .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),
+};
+
+static void register_audio_spice(void)
+{
+ audio_driver_register(&spice_audio_driver);
+}
+type_init(register_audio_spice);
+
+module_dep("ui-spice-core");
diff --git a/audio/trace-events b/audio/trace-events
new file mode 100644
index 000000000..957c92337
--- /dev/null
+++ b/audio/trace-events
@@ -0,0 +1,19 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# alsaaudio.c
+alsa_revents(int revents) "revents = %d"
+alsa_pollout(int i, int fd) "i = %d fd = %d"
+alsa_set_handler(int events, int index, int fd, int err) "events=0x%x index=%d fd=%d err=%d"
+alsa_wrote_zero(int len) "Failed to write %d frames (wrote zero)"
+alsa_read_zero(long len) "Failed to read %ld frames (read zero)"
+alsa_xrun_out(void) "Recovering from playback xrun"
+alsa_xrun_in(void) "Recovering from capture xrun"
+alsa_resume_out(void) "Resuming suspended output stream"
+
+# ossaudio.c
+oss_version(int version) "OSS version = 0x%x"
+
+# audio.c
+audio_timer_start(int interval) "interval %d ms"
+audio_timer_stop(void) ""
+audio_timer_delayed(int interval) "interval %d ms"
diff --git a/audio/trace.h b/audio/trace.h
new file mode 100644
index 000000000..4072a11b0
--- /dev/null
+++ b/audio/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-audio.h"
diff --git a/audio/wavaudio.c b/audio/wavaudio.c
new file mode 100644
index 000000000..20e6853f8
--- /dev/null
+++ b/audio/wavaudio.c
@@ -0,0 +1,221 @@
+/*
+ * 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 "qemu/osdep.h"
+#include "qemu/host-utils.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qapi/opts-visitor.h"
+#include "audio.h"
+
+#define AUDIO_CAP "wav"
+#include "audio_int.h"
+
+typedef struct WAVVoiceOut {
+ HWVoiceOut hw;
+ FILE *f;
+ RateCtl rate;
+ int total_samples;
+} WAVVoiceOut;
+
+static size_t wav_write_out(HWVoiceOut *hw, void *buf, size_t len)
+{
+ WAVVoiceOut *wav = (WAVVoiceOut *) hw;
+ int64_t bytes = audio_rate_get_bytes(&hw->info, &wav->rate, len);
+ assert(bytes % hw->info.bytes_per_frame == 0);
+
+ if (bytes && fwrite(buf, bytes, 1, wav->f) != 1) {
+ dolog("wav_write_out: fwrite of %" PRId64 " bytes failed\nReason: %s\n",
+ bytes, strerror(errno));
+ }
+
+ wav->total_samples += bytes / hw->info.bytes_per_frame;
+ return bytes;
+}
+
+/* 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
+ };
+ Audiodev *dev = drv_opaque;
+ AudiodevWavOptions *wopts = &dev->u.wav;
+ struct audsettings wav_as = audiodev_to_audsettings(dev->u.wav.out);
+ const char *wav_path = wopts->has_path ? wopts->path : "qemu.wav";
+
+ stereo = wav_as.nchannels == 2;
+ switch (wav_as.fmt) {
+ case AUDIO_FORMAT_S8:
+ case AUDIO_FORMAT_U8:
+ bits16 = 0;
+ break;
+
+ case AUDIO_FORMAT_S16:
+ case AUDIO_FORMAT_U16:
+ bits16 = 1;
+ break;
+
+ case AUDIO_FORMAT_S32:
+ case AUDIO_FORMAT_U32:
+ dolog ("WAVE files can not handle 32bit formats\n");
+ return -1;
+
+ default:
+ abort();
+ }
+
+ hdr[34] = bits16 ? 0x10 : 0x08;
+
+ wav_as.endianness = 0;
+ audio_pcm_init_info (&hw->info, &wav_as);
+
+ hw->samples = 1024;
+ 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(wav_path, "wb");
+ if (!wav->f) {
+ dolog ("Failed to open wave file `%s'\nReason: %s\n",
+ wav_path, strerror(errno));
+ 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;
+ }
+
+ audio_rate_start(&wav->rate);
+ 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.bytes_per_frame;
+ 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;
+}
+
+static void wav_enable_out(HWVoiceOut *hw, bool enable)
+{
+ WAVVoiceOut *wav = (WAVVoiceOut *) hw;
+
+ if (enable) {
+ audio_rate_start(&wav->rate);
+ }
+}
+
+static void *wav_audio_init(Audiodev *dev)
+{
+ assert(dev->driver == AUDIODEV_DRIVER_WAV);
+ return dev;
+}
+
+static void wav_audio_fini (void *opaque)
+{
+ ldebug ("wav_fini");
+}
+
+static struct audio_pcm_ops wav_pcm_ops = {
+ .init_out = wav_init_out,
+ .fini_out = wav_fini_out,
+ .write = wav_write_out,
+ .run_buffer_out = audio_generic_run_buffer_out,
+ .enable_out = wav_enable_out,
+};
+
+static struct audio_driver wav_audio_driver = {
+ .name = "wav",
+ .descr = "WAV renderer http://wikipedia.org/wiki/WAV",
+ .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
+};
+
+static void register_audio_wav(void)
+{
+ audio_driver_register(&wav_audio_driver);
+}
+type_init(register_audio_wav);
diff --git a/audio/wavcapture.c b/audio/wavcapture.c
new file mode 100644
index 000000000..c60286e16
--- /dev/null
+++ b/audio/wavcapture.c
@@ -0,0 +1,191 @@
+#include "qemu/osdep.h"
+#include "qemu/qemu-print.h"
+#include "qapi/error.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;
+
+ if (wav->f) {
+ le_store (rlen, rifflen, 4);
+ le_store (dlen, datalen, 4);
+
+ if (fseek (wav->f, 4, SEEK_SET)) {
+ error_report("wav_destroy: rlen fseek failed: %s",
+ strerror(errno));
+ goto doclose;
+ }
+ if (fwrite (rlen, 4, 1, wav->f) != 1) {
+ error_report("wav_destroy: rlen fwrite failed: %s",
+ strerror(errno));
+ goto doclose;
+ }
+ if (fseek (wav->f, 32, SEEK_CUR)) {
+ error_report("wav_destroy: dlen fseek failed: %s",
+ strerror(errno));
+ goto doclose;
+ }
+ if (fwrite (dlen, 1, 4, wav->f) != 4) {
+ error_report("wav_destroy: dlen fwrite failed: %s",
+ 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, const void *buf, int size)
+{
+ WAVState *wav = opaque;
+
+ if (fwrite (buf, size, 1, wav->f) != 1) {
+ error_report("wav_capture: fwrite error: %s", strerror(errno));
+ }
+ wav->bytes += size;
+}
+
+static void wav_capture_destroy (void *opaque)
+{
+ WAVState *wav = opaque;
+
+ AUD_del_capture (wav->cap, wav);
+ g_free (wav);
+}
+
+static void wav_capture_info (void *opaque)
+{
+ WAVState *wav = opaque;
+ char *path = wav->path;
+
+ qemu_printf("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(AudioState *state, CaptureState *s, const char *path,
+ int freq, int bits, int nchannels)
+{
+ 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) {
+ error_report("incorrect bit count %d, must be 8 or 16", bits);
+ return -1;
+ }
+
+ if (nchannels != 1 && nchannels != 2) {
+ error_report("incorrect channel count %d, must be 1 or 2",
+ nchannels);
+ return -1;
+ }
+
+ stereo = nchannels == 2;
+ bits16 = bits == 16;
+
+ as.freq = freq;
+ as.nchannels = 1 << stereo;
+ as.fmt = bits16 ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_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) {
+ error_report("Failed to open wave file `%s': %s",
+ 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) {
+ error_report("Failed to write header: %s", strerror(errno));
+ goto error_free;
+ }
+
+ cap = AUD_add_capture(state, &as, &ops, wav);
+ if (!cap) {
+ error_report("Failed to add audio capture");
+ 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)) {
+ error_report("Failed to close wave file: %s", strerror(errno));
+ }
+ g_free (wav);
+ return -1;
+}