/*
* QEMU Audio subsystem
- *
- * Copyright (c) 2003-2004 Vassili Karpov (malc)
- *
+ *
+ * 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
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-#include <assert.h>
#include "vl.h"
-#define USE_WAV_AUDIO
+#define AUDIO_CAP "audio"
+#include "audio_int.h"
-#include "audio/audio_int.h"
+/* #define DEBUG_PLIVE */
+/* #define DEBUG_LIVE */
+/* #define DEBUG_OUT */
-#define dolog(...) AUD_log ("audio", __VA_ARGS__)
-#ifdef DEBUG
-#define ldebug(...) dolog (__VA_ARGS__)
-#else
-#define ldebug(...)
+#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
+
+static struct audio_driver *drvtab[] = {
+#ifdef CONFIG_OSS
+ &oss_audio_driver,
+#endif
+#ifdef CONFIG_ALSA
+ &alsa_audio_driver,
+#endif
+#ifdef CONFIG_COREAUDIO
+ &coreaudio_audio_driver,
+#endif
+#ifdef CONFIG_DSOUND
+ &dsound_audio_driver,
+#endif
+#ifdef CONFIG_FMOD
+ &fmod_audio_driver,
#endif
+#ifdef CONFIG_SDL
+ &sdl_audio_driver,
+#endif
+ &no_audio_driver,
+ &wav_audio_driver
+};
+
+struct fixed_settings {
+ int enabled;
+ int nb_voices;
+ int greedy;
+ audsettings_t settings;
+};
+
+static struct {
+ struct fixed_settings fixed_out;
+ struct fixed_settings fixed_in;
+ union {
+ int hz;
+ int64_t ticks;
+ } period;
+ int plive;
+ int log_to_monitor;
+} conf = {
+ { /* DAC fixed settings */
+ 1, /* enabled */
+ 1, /* nb_voices */
+ 1, /* greedy */
+ {
+ 44100, /* freq */
+ 2, /* nchannels */
+ AUD_FMT_S16 /* fmt */
+ }
+ },
+
+ { /* ADC fixed settings */
+ 1, /* enabled */
+ 1, /* nb_voices */
+ 1, /* greedy */
+ {
+ 44100, /* freq */
+ 2, /* nchannels */
+ AUD_FMT_S16 /* fmt */
+ }
+ },
-#define QC_AUDIO_DRV "QEMU_AUDIO_DRV"
-#define QC_VOICES "QEMU_VOICES"
-#define QC_FIXED_FORMAT "QEMU_FIXED_FORMAT"
-#define QC_FIXED_FREQ "QEMU_FIXED_FREQ"
+ { 0 }, /* period */
+ 0, /* plive */
+ 0 /* log_to_monitor */
+};
-static HWVoice *hw_voices;
+static AudioState glob_audio_state;
-AudioState audio_state = {
- 1, /* use fixed settings */
- 44100, /* fixed frequency */
- 2, /* fixed channels */
- AUD_FMT_S16, /* fixed format */
- 1, /* number of hw voices */
- -1 /* voice size */
+volume_t nominal_volume = {
+ 0,
+#ifdef FLOAT_MIXENG
+ 1.0,
+ 1.0
+#else
+ UINT_MAX,
+ UINT_MAX
+#endif
};
/* http://www.df.lth.se/~john_e/gems/gem002d.html */
return popcount ((u&-u)-1);
}
-int audio_get_conf_int (const char *key, int defval)
+#ifdef AUDIO_IS_FLAWLESS_AND_NO_CHECKS_ARE_REQURIED
+#error No its not
+#else
+int audio_bug (const char *funcname, int cond)
+{
+ if (cond) {
+ static int shown;
+
+ AUD_log (NULL, "Error a bug that 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, "Please send bug report to malc@pulsesoft.com\n");
+ AUD_log (NULL, "I am sorry\n");
+ }
+ AUD_log (NULL, "Context:\n");
+
+#if defined AUDIO_BREAKPOINT_ON_BUG
+# if defined HOST_I386
+# if defined __GNUC__
+ __asm__ ("int3");
+# elif defined _MSC_VER
+ _asm _emit 0xcc;
+# else
+ abort ();
+# endif
+# else
+ abort ();
+# endif
+#endif
+ }
+
+ return cond;
+}
+#endif
+
+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 qemu_mallocz (len);
+}
+
+static char *audio_alloc_prefix (const char *s)
+{
+ const char qemu_prefix[] = "QEMU_";
+ size_t len;
+ char *r;
+
+ if (!s) {
+ return NULL;
+ }
+
+ len = strlen (s);
+ r = qemu_malloc (len + sizeof (qemu_prefix));
+
+ if (r) {
+ size_t i;
+ char *u = r + sizeof (qemu_prefix) - 1;
+
+ strcpy (r, qemu_prefix);
+ strcat (r, s);
+
+ for (i = 0; i < len; ++i) {
+ u[i] = toupper (u[i]);
+ }
+ }
+ return r;
+}
+
+const char *audio_audfmt_to_string (audfmt_e fmt)
+{
+ switch (fmt) {
+ case AUD_FMT_U8:
+ return "U8";
+
+ case AUD_FMT_U16:
+ return "U16";
+
+ case AUD_FMT_S8:
+ return "S8";
+
+ case AUD_FMT_S16:
+ return "S16";
+ }
+
+ dolog ("Bogus audfmt %d returning S16\n", fmt);
+ return "S16";
+}
+
+audfmt_e audio_string_to_audfmt (const char *s, audfmt_e defval, int *defaultp)
{
- int val = defval;
+ if (!strcasecmp (s, "u8")) {
+ *defaultp = 0;
+ return AUD_FMT_U8;
+ }
+ else if (!strcasecmp (s, "u16")) {
+ *defaultp = 0;
+ return AUD_FMT_U16;
+ }
+ else if (!strcasecmp (s, "s8")) {
+ *defaultp = 0;
+ return AUD_FMT_S8;
+ }
+ else if (!strcasecmp (s, "s16")) {
+ *defaultp = 0;
+ return AUD_FMT_S16;
+ }
+ else {
+ dolog ("Bogus audio format `%s' using %s\n",
+ s, audio_audfmt_to_string (defval));
+ *defaultp = 1;
+ return defval;
+ }
+}
+
+static audfmt_e audio_get_conf_fmt (const char *envname,
+ audfmt_e defval,
+ int *defaultp)
+{
+ const char *var = getenv (envname);
+ if (!var) {
+ *defaultp = 1;
+ return defval;
+ }
+ return audio_string_to_audfmt (var, defval, defaultp);
+}
+
+static int audio_get_conf_int (const char *key, int defval, int *defaultp)
+{
+ int val;
char *strval;
strval = getenv (key);
if (strval) {
+ *defaultp = 0;
val = atoi (strval);
+ return val;
+ }
+ else {
+ *defaultp = 1;
+ return defval;
}
-
- return val;
}
-const char *audio_get_conf_str (const char *key, const char *defval)
+static const char *audio_get_conf_str (const char *key,
+ const char *defval,
+ int *defaultp)
{
const char *val = getenv (key);
- if (!val)
+ if (!val) {
+ *defaultp = 1;
return defval;
- else
+ }
+ else {
+ *defaultp = 0;
return val;
+ }
+}
+
+void AUD_vlog (const char *cap, const char *fmt, va_list ap)
+{
+ if (conf.log_to_monitor) {
+ if (cap) {
+ term_printf ("%s: ", cap);
+ }
+
+ term_vprintf (fmt, ap);
+ }
+ else {
+ if (cap) {
+ fprintf (stderr, "%s: ", cap);
+ }
+
+ vfprintf (stderr, fmt, ap);
+ }
}
void AUD_log (const char *cap, const char *fmt, ...)
{
va_list ap;
- fprintf (stderr, "%s: ", cap);
+
va_start (ap, fmt);
- vfprintf (stderr, fmt, ap);
+ AUD_vlog (cap, fmt, ap);
va_end (ap);
}
-/*
- * Soft Voice
- */
-void pcm_sw_free_resources (SWVoice *sw)
+static void audio_print_options (const char *prefix,
+ struct audio_option *opt)
{
- if (sw->buf) qemu_free (sw->buf);
- if (sw->rate) st_rate_stop (sw->rate);
- sw->buf = NULL;
- sw->rate = NULL;
+ char *uprefix;
+
+ if (!prefix) {
+ dolog ("No prefix specified\n");
+ return;
+ }
+
+ if (!opt) {
+ dolog ("No options\n");
+ return;
+ }
+
+ uprefix = audio_alloc_prefix (prefix);
+
+ for (; opt->name; opt++) {
+ const char *state = "default";
+ printf (" %s_%s: ", uprefix, opt->name);
+
+ if (opt->overridenp && *opt->overridenp) {
+ state = "current";
+ }
+
+ switch (opt->tag) {
+ case AUD_OPT_BOOL:
+ {
+ int *intp = opt->valp;
+ printf ("boolean, %s = %d\n", state, *intp ? 1 : 0);
+ }
+ break;
+
+ case AUD_OPT_INT:
+ {
+ int *intp = opt->valp;
+ printf ("integer, %s = %d\n", state, *intp);
+ }
+ break;
+
+ case AUD_OPT_FMT:
+ {
+ audfmt_e *fmtp = opt->valp;
+ printf (
+ "format, %s = %s, (one of: U8 S8 U16 S16)\n",
+ state,
+ audio_audfmt_to_string (*fmtp)
+ );
+ }
+ break;
+
+ case AUD_OPT_STR:
+ {
+ const char **strp = opt->valp;
+ printf ("string, %s = %s\n",
+ state,
+ *strp ? *strp : "(not set)");
+ }
+ break;
+
+ default:
+ printf ("???\n");
+ dolog ("Bad value tag for option %s_%s %d\n",
+ uprefix, opt->name, opt->tag);
+ break;
+ }
+ printf (" %s\n", opt->descr);
+ }
+
+ qemu_free (uprefix);
}
-int pcm_sw_alloc_resources (SWVoice *sw)
+static void audio_process_options (const char *prefix,
+ struct audio_option *opt)
{
- sw->buf = qemu_mallocz (sw->hw->samples * sizeof (st_sample_t));
- if (!sw->buf)
- return -1;
+ char *optname;
+ const char qemu_prefix[] = "QEMU_";
+ size_t preflen;
+
+ if (audio_bug (AUDIO_FUNC, !prefix)) {
+ dolog ("prefix = NULL\n");
+ return;
+ }
+
+ if (audio_bug (AUDIO_FUNC, !opt)) {
+ dolog ("opt = NULL\n");
+ return;
+ }
+
+ preflen = strlen (prefix);
+
+ for (; opt->name; opt++) {
+ size_t len, i;
+ int def;
+
+ if (!opt->valp) {
+ dolog ("Option value pointer for `%s' is not set\n",
+ opt->name);
+ continue;
+ }
+
+ len = strlen (opt->name);
+ /* len of opt->name + len of prefix + size of qemu_prefix
+ * (includes trailing zero) + zero + underscore (on behalf of
+ * sizeof) */
+ optname = qemu_malloc (len + preflen + sizeof (qemu_prefix) + 1);
+ if (!optname) {
+ dolog ("Could not allocate memory for option name `%s'\n",
+ opt->name);
+ continue;
+ }
+
+ strcpy (optname, qemu_prefix);
+
+ /* copy while upper-casing, including trailing zero */
+ for (i = 0; i <= preflen; ++i) {
+ optname[i + sizeof (qemu_prefix) - 1] = toupper (prefix[i]);
+ }
+ strcat (optname, "_");
+ strcat (optname, opt->name);
+
+ def = 1;
+ switch (opt->tag) {
+ case AUD_OPT_BOOL:
+ case AUD_OPT_INT:
+ {
+ int *intp = opt->valp;
+ *intp = audio_get_conf_int (optname, *intp, &def);
+ }
+ break;
+
+ case AUD_OPT_FMT:
+ {
+ audfmt_e *fmtp = opt->valp;
+ *fmtp = audio_get_conf_fmt (optname, *fmtp, &def);
+ }
+ break;
+
+ case AUD_OPT_STR:
+ {
+ const char **strp = opt->valp;
+ *strp = audio_get_conf_str (optname, *strp, &def);
+ }
+ break;
+
+ default:
+ dolog ("Bad value tag for option `%s' - %d\n",
+ optname, opt->tag);
+ break;
+ }
+
+ if (!opt->overridenp) {
+ opt->overridenp = &opt->overriden;
+ }
+ *opt->overridenp = !def;
+ qemu_free (optname);
+ }
+}
+
+static void audio_print_settings (audsettings_t *as)
+{
+ dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
+
+ switch (as->fmt) {
+ case AUD_FMT_S8:
+ AUD_log (NULL, "S8");
+ break;
+ case AUD_FMT_U8:
+ AUD_log (NULL, "U8");
+ break;
+ case AUD_FMT_S16:
+ AUD_log (NULL, "S16");
+ break;
+ case AUD_FMT_U16:
+ AUD_log (NULL, "U16");
+ break;
+ default:
+ AUD_log (NULL, "invalid(%d)", as->fmt);
+ break;
+ }
+ AUD_log (NULL, "\n");
+}
+
+static int audio_validate_settigs (audsettings_t *as)
+{
+ int invalid;
+
+ invalid = as->nchannels != 1 && as->nchannels != 2;
- sw->rate = st_rate_start (sw->freq, sw->hw->freq);
- if (!sw->rate) {
- qemu_free (sw->buf);
- sw->buf = NULL;
+ switch (as->fmt) {
+ case AUD_FMT_S8:
+ case AUD_FMT_U8:
+ case AUD_FMT_S16:
+ case AUD_FMT_U16:
+ break;
+ default:
+ invalid = 1;
+ break;
+ }
+
+ invalid |= as->freq <= 0;
+
+ if (invalid) {
return -1;
}
return 0;
}
-void pcm_sw_fini (SWVoice *sw)
+static int audio_pcm_info_eq (struct audio_pcm_info *info, audsettings_t *as)
{
- pcm_sw_free_resources (sw);
+ int bits = 8, sign = 0;
+
+ switch (as->fmt) {
+ case AUD_FMT_S8:
+ sign = 1;
+ case AUD_FMT_U8:
+ break;
+
+ case AUD_FMT_S16:
+ sign = 1;
+ case AUD_FMT_U16:
+ bits = 16;
+ break;
+ }
+ return info->freq == as->freq
+ && info->nchannels == as->nchannels
+ && info->sign == sign
+ && info->bits == bits;
}
-int pcm_sw_init (SWVoice *sw, HWVoice *hw, int freq,
- int nchannels, audfmt_e fmt)
+void audio_pcm_init_info (
+ struct audio_pcm_info *info,
+ audsettings_t *as,
+ int swap_endian
+ )
{
int bits = 8, sign = 0;
- switch (fmt) {
+ switch (as->fmt) {
case AUD_FMT_S8:
sign = 1;
case AUD_FMT_U8:
break;
}
- sw->hw = hw;
- sw->freq = freq;
- sw->fmt = fmt;
- sw->nchannels = nchannels;
- sw->shift = (nchannels == 2) + (bits == 16);
- sw->align = (1 << sw->shift) - 1;
- sw->left = 0;
- sw->pos = 0;
- sw->wpos = 0;
- sw->live = 0;
- sw->ratio = (sw->hw->freq * ((int64_t) INT_MAX)) / sw->freq;
- sw->bytes_per_second = sw->freq << sw->shift;
- sw->conv = mixeng_conv[nchannels == 2][sign][bits == 16];
-
- pcm_sw_free_resources (sw);
- return pcm_sw_alloc_resources (sw);
+ info->freq = as->freq;
+ info->bits = bits;
+ info->sign = sign;
+ info->nchannels = as->nchannels;
+ info->shift = (as->nchannels == 2) + (bits == 16);
+ info->align = (1 << info->shift) - 1;
+ info->bytes_per_second = info->freq << info->shift;
+ info->swap_endian = swap_endian;
}
-/* Hard voice */
-void pcm_hw_free_resources (HWVoice *hw)
+void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
{
- if (hw->mix_buf)
- qemu_free (hw->mix_buf);
- hw->mix_buf = NULL;
+ if (!len) {
+ return;
+ }
+
+ if (info->sign) {
+ memset (buf, len << info->shift, 0x00);
+ }
+ else {
+ if (info->bits == 8) {
+ memset (buf, len << info->shift, 0x80);
+ }
+ else {
+ int i;
+ uint16_t *p = buf;
+ int shift = info->nchannels - 1;
+ short s = INT16_MAX;
+
+ if (info->swap_endian) {
+ s = bswap16 (s);
+ }
+
+ for (i = 0; i < len << shift; i++) {
+ p[i] = s;
+ }
+ }
+ }
}
-int pcm_hw_alloc_resources (HWVoice *hw)
+/*
+ * Hard voice (capture)
+ */
+static int audio_pcm_hw_find_min_in (HWVoiceIn *hw)
{
- hw->mix_buf = qemu_mallocz (hw->samples * sizeof (st_sample_t));
- if (!hw->mix_buf)
- return -1;
- return 0;
+ SWVoiceIn *sw;
+ int m = hw->total_samples_captured;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (sw->active) {
+ m = audio_MIN (m, sw->total_hw_samples_acquired);
+ }
+ }
+ return m;
}
-void pcm_hw_fini (HWVoice *hw)
+int audio_pcm_hw_get_live_in (HWVoiceIn *hw)
{
- if (hw->active) {
- ldebug ("pcm_hw_fini: %d %d %d\n", hw->freq, hw->nchannels, hw->fmt);
- pcm_hw_free_resources (hw);
- hw->pcm_ops->fini (hw);
- memset (hw, 0, audio_state.drv->voice_size);
+ int live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
+ if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) {
+ dolog ("live=%d hw->samples=%d\n", live, hw->samples);
+ return 0;
}
+ return live;
}
-void pcm_hw_gc (HWVoice *hw)
+/*
+ * Soft voice (capture)
+ */
+static int audio_pcm_sw_get_rpos_in (SWVoiceIn *sw)
{
- if (hw->nb_voices)
- return;
+ HWVoiceIn *hw = sw->hw;
+ int live = hw->total_samples_captured - sw->total_hw_samples_acquired;
+ int rpos;
- pcm_hw_fini (hw);
+ if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) {
+ dolog ("live=%d hw->samples=%d\n", live, hw->samples);
+ return 0;
+ }
+
+ rpos = hw->wpos - live;
+ if (rpos >= 0) {
+ return rpos;
+ }
+ else {
+ return hw->samples + rpos;
+ }
}
-int pcm_hw_get_live (HWVoice *hw)
+int audio_pcm_sw_read (SWVoiceIn *sw, void *buf, int size)
{
- int i, alive = 0, live = hw->samples;
+ HWVoiceIn *hw = sw->hw;
+ int samples, live, ret = 0, swlim, isamp, osamp, rpos, total = 0;
+ st_sample_t *src, *dst = sw->buf;
- for (i = 0; i < hw->nb_voices; i++) {
- if (hw->pvoice[i]->live) {
- live = audio_MIN (hw->pvoice[i]->live, live);
- alive += 1;
- }
+ rpos = audio_pcm_sw_get_rpos_in (sw) % hw->samples;
+
+ live = hw->total_samples_captured - sw->total_hw_samples_acquired;
+ if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) {
+ dolog ("live_in=%d hw->samples=%d\n", live, hw->samples);
+ return 0;
}
- if (alive)
- return live;
- else
- return -1;
-}
+ samples = size >> sw->info.shift;
+ if (!live) {
+ return 0;
+ }
-int pcm_hw_get_live2 (HWVoice *hw, int *nb_active)
-{
- int i, alive = 0, live = hw->samples;
+ swlim = (live * sw->ratio) >> 32;
+ swlim = audio_MIN (swlim, samples);
- *nb_active = 0;
- for (i = 0; i < hw->nb_voices; i++) {
- if (hw->pvoice[i]->live) {
- if (hw->pvoice[i]->live < live) {
- *nb_active = hw->pvoice[i]->active != 0;
- live = hw->pvoice[i]->live;
- }
- alive += 1;
+ while (swlim) {
+ src = hw->conv_buf + rpos;
+ isamp = hw->wpos - rpos;
+ /* XXX: <= ? */
+ if (isamp <= 0) {
+ isamp = hw->samples - rpos;
+ }
+
+ if (!isamp) {
+ break;
+ }
+ osamp = swlim;
+
+ if (audio_bug (AUDIO_FUNC, osamp < 0)) {
+ dolog ("osamp=%d\n", osamp);
+ return 0;
}
+
+ st_rate_flow (sw->rate, src, dst, &isamp, &osamp);
+ swlim -= osamp;
+ rpos = (rpos + isamp) % hw->samples;
+ dst += osamp;
+ ret += osamp;
+ total += isamp;
}
- if (alive)
- return live;
- else
- return -1;
+ sw->clip (buf, sw->buf, ret);
+ sw->total_hw_samples_acquired += total;
+ return ret << sw->info.shift;
}
-void pcm_hw_dec_live (HWVoice *hw, int decr)
+/*
+ * Hard voice (playback)
+ */
+static int audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
{
- int i;
-
- for (i = 0; i < hw->nb_voices; i++) {
- if (hw->pvoice[i]->live) {
- hw->pvoice[i]->live -= decr;
+ SWVoiceOut *sw;
+ int m = INT_MAX;
+ int nb_live = 0;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (sw->active || !sw->empty) {
+ m = audio_MIN (m, sw->total_hw_samples_mixed);
+ nb_live += 1;
}
}
+
+ *nb_livep = nb_live;
+ return m;
}
-void pcm_hw_clear (HWVoice *hw, void *buf, int len)
+int audio_pcm_hw_get_live_out2 (HWVoiceOut *hw, int *nb_live)
{
- if (!len)
- return;
+ int smin;
- switch (hw->fmt) {
- case AUD_FMT_S16:
- case AUD_FMT_S8:
- memset (buf, len << hw->shift, 0x00);
- break;
-
- case AUD_FMT_U8:
- memset (buf, len << hw->shift, 0x80);
- break;
+ smin = audio_pcm_hw_find_min_out (hw, nb_live);
- case AUD_FMT_U16:
- {
- unsigned int i;
- uint16_t *p = buf;
- int shift = hw->nchannels - 1;
+ if (!*nb_live) {
+ return 0;
+ }
+ else {
+ int live = smin;
- for (i = 0; i < len << shift; i++) {
- p[i] = INT16_MAX;
- }
+ if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) {
+ dolog ("live=%d hw->samples=%d\n", live, hw->samples);
+ return 0;
}
- break;
+ return live;
}
}
-int pcm_hw_write (SWVoice *sw, void *buf, int size)
+int audio_pcm_hw_get_live_out (HWVoiceOut *hw)
+{
+ int nb_live;
+ int live;
+
+ live = audio_pcm_hw_get_live_out2 (hw, &nb_live);
+ if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) {
+ dolog ("live=%d hw->samples=%d\n", live, hw->samples);
+ return 0;
+ }
+ return live;
+}
+
+/*
+ * Soft voice (playback)
+ */
+int audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int size)
{
int hwsamples, samples, isamp, osamp, wpos, live, dead, left, swlim, blck;
- int ret = 0, pos = 0;
- if (!sw)
+ int ret = 0, pos = 0, total = 0;
+
+ if (!sw) {
return size;
+ }
hwsamples = sw->hw->samples;
- samples = size >> sw->shift;
- if (!sw->live) {
- sw->wpos = sw->hw->rpos;
+ live = sw->total_hw_samples_mixed;
+ if (audio_bug (AUDIO_FUNC, live < 0 || live > hwsamples)){
+ dolog ("live=%d hw->samples=%d\n", live, hwsamples);
+ return 0;
}
- wpos = sw->wpos;
- live = sw->live;
+
+ if (live == hwsamples) {
+ return 0;
+ }
+
+ wpos = (sw->hw->rpos + live) % hwsamples;
+ samples = size >> sw->info.shift;
+
dead = hwsamples - live;
- swlim = (dead * ((int64_t) INT_MAX)) / sw->ratio;
+ swlim = ((int64_t) dead << 32) / sw->ratio;
swlim = audio_MIN (swlim, samples);
-
- ldebug ("size=%d live=%d dead=%d swlim=%d wpos=%d\n",
- size, live, dead, swlim, wpos);
- if (swlim)
- sw->conv (sw->buf, buf, swlim);
+ if (swlim) {
+ sw->conv (sw->buf, buf, swlim, &sw->vol);
+ }
while (swlim) {
dead = hwsamples - live;
left = hwsamples - wpos;
blck = audio_MIN (dead, left);
if (!blck) {
- /* dolog ("swlim=%d\n", swlim); */
break;
}
isamp = swlim;
osamp = blck;
- st_rate_flow (sw->rate, sw->buf + pos, sw->hw->mix_buf + wpos, &isamp, &osamp);
+ st_rate_flow_mix (
+ sw->rate,
+ sw->buf + pos,
+ sw->hw->mix_buf + wpos,
+ &isamp,
+ &osamp
+ );
ret += isamp;
swlim -= isamp;
pos += isamp;
live += osamp;
- wpos = (wpos + osamp) % hwsamples;
- }
-
- sw->wpos = wpos;
- sw->live = live;
- return ret << sw->shift;
-}
-
-int pcm_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt)
-{
- int sign = 0, bits = 8;
-
- pcm_hw_fini (hw);
- ldebug ("pcm_hw_init: %d %d %d\n", freq, nchannels, fmt);
- if (hw->pcm_ops->init (hw, freq, nchannels, fmt)) {
- memset (hw, 0, audio_state.drv->voice_size);
- return -1;
+ wpos = (wpos + osamp) % hwsamples;
+ total += osamp;
}
- switch (hw->fmt) {
- case AUD_FMT_S8:
- sign = 1;
- case AUD_FMT_U8:
- break;
-
- case AUD_FMT_S16:
- sign = 1;
- case AUD_FMT_U16:
- bits = 16;
- break;
- }
+ sw->total_hw_samples_mixed += total;
+ sw->empty = sw->total_hw_samples_mixed == 0;
+
+#ifdef DEBUG_OUT
+ dolog (
+ "%s: write size %d ret %d total sw %d\n",
+ SW_NAME (sw),
+ size >> sw->info.shift,
+ ret,
+ sw->total_hw_samples_mixed
+ );
+#endif
- hw->nb_voices = 0;
- hw->active = 1;
- hw->shift = (hw->nchannels == 2) + (bits == 16);
- hw->bytes_per_second = hw->freq << hw->shift;
- hw->align = (1 << hw->shift) - 1;
- hw->samples = hw->bufsize >> hw->shift;
- hw->clip = mixeng_clip[hw->nchannels == 2][sign][bits == 16];
- if (pcm_hw_alloc_resources (hw)) {
- pcm_hw_fini (hw);
- return -1;
- }
- return 0;
+ return ret << sw->info.shift;
}
-static int dist (void *hw)
+#ifdef DEBUG_AUDIO
+static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
{
- if (hw) {
- return (((uint8_t *) hw - (uint8_t *) hw_voices)
- / audio_state.drv->voice_size) + 1;
- }
- else {
- return 0;
- }
+ dolog ("%s: bits %d, sign %d, freq %d, nchan %d\n",
+ cap, info->bits, info->sign, info->freq, info->nchannels);
}
+#endif
-#define ADVANCE(hw) \
- ((hw) ? advance (hw, audio_state.drv->voice_size) : hw_voices)
+#define DAC
+#include "audio_template.h"
+#undef DAC
+#include "audio_template.h"
-HWVoice *pcm_hw_find_any (HWVoice *hw)
+int AUD_write (SWVoiceOut *sw, void *buf, int size)
{
- int i = dist (hw);
- for (; i < audio_state.nb_hw_voices; i++) {
- hw = ADVANCE (hw);
- return hw;
+ int bytes;
+
+ if (!sw) {
+ /* XXX: Consider options */
+ return size;
}
- return NULL;
-}
-HWVoice *pcm_hw_find_any_active (HWVoice *hw)
-{
- int i = dist (hw);
- for (; i < audio_state.nb_hw_voices; i++) {
- hw = ADVANCE (hw);
- if (hw->active)
- return hw;
+ if (!sw->hw->enabled) {
+ dolog ("Writing to disabled voice %s\n", SW_NAME (sw));
+ return 0;
}
- return NULL;
+
+ bytes = sw->hw->pcm_ops->write (sw, buf, size);
+ return bytes;
}
-HWVoice *pcm_hw_find_any_active_enabled (HWVoice *hw)
+int AUD_read (SWVoiceIn *sw, void *buf, int size)
{
- int i = dist (hw);
- for (; i < audio_state.nb_hw_voices; i++) {
- hw = ADVANCE (hw);
- if (hw->active && hw->enabled)
- return hw;
+ int bytes;
+
+ if (!sw) {
+ /* XXX: Consider options */
+ return size;
}
- return NULL;
-}
-HWVoice *pcm_hw_find_any_passive (HWVoice *hw)
-{
- int i = dist (hw);
- for (; i < audio_state.nb_hw_voices; i++) {
- hw = ADVANCE (hw);
- if (!hw->active)
- return hw;
+ if (!sw->hw->enabled) {
+ dolog ("Reading from disabled voice %s\n", SW_NAME (sw));
+ return 0;
}
- return NULL;
+
+ bytes = sw->hw->pcm_ops->read (sw, buf, size);
+ return bytes;
}
-HWVoice *pcm_hw_find_specific (HWVoice *hw, int freq,
- int nchannels, audfmt_e fmt)
+int AUD_get_buffer_size_out (SWVoiceOut *sw)
{
- while ((hw = pcm_hw_find_any_active (hw))) {
- if (hw->freq == freq &&
- hw->nchannels == nchannels &&
- hw->fmt == fmt)
- return hw;
- }
- return NULL;
+ return sw->hw->samples << sw->hw->info.shift;
}
-HWVoice *pcm_hw_add (int freq, int nchannels, audfmt_e fmt)
+void AUD_set_active_out (SWVoiceOut *sw, int on)
{
- HWVoice *hw;
+ HWVoiceOut *hw;
- if (audio_state.fixed_format) {
- freq = audio_state.fixed_freq;
- nchannels = audio_state.fixed_channels;
- fmt = audio_state.fixed_fmt;
+ if (!sw) {
+ return;
}
- hw = pcm_hw_find_specific (NULL, freq, nchannels, fmt);
+ hw = sw->hw;
+ if (sw->active != on) {
+ SWVoiceOut *temp_sw;
+
+ if (on) {
+ int total;
+
+ hw->pending_disable = 0;
+ if (!hw->enabled) {
+ hw->enabled = 1;
+ hw->pcm_ops->ctl_out (hw, VOICE_ENABLE);
+ }
- if (hw)
- return hw;
+ if (sw->empty) {
+ total = 0;
+ }
+ }
+ else {
+ if (hw->enabled) {
+ int nb_active = 0;
- hw = pcm_hw_find_any_passive (NULL);
- if (hw) {
- hw->pcm_ops = audio_state.drv->pcm_ops;
- if (!hw->pcm_ops)
- return NULL;
+ for (temp_sw = hw->sw_head.lh_first; temp_sw;
+ temp_sw = temp_sw->entries.le_next) {
+ nb_active += temp_sw->active != 0;
+ }
- if (pcm_hw_init (hw, freq, nchannels, fmt)) {
- pcm_hw_gc (hw);
- return NULL;
+ hw->pending_disable = nb_active == 1;
+ }
}
- else
- return hw;
+ sw->active = on;
}
-
- return pcm_hw_find_any (NULL);
}
-int pcm_hw_add_sw (HWVoice *hw, SWVoice *sw)
+void AUD_set_active_in (SWVoiceIn *sw, int on)
{
- SWVoice **pvoice = qemu_mallocz ((hw->nb_voices + 1) * sizeof (sw));
- if (!pvoice)
- return -1;
+ HWVoiceIn *hw;
- memcpy (pvoice, hw->pvoice, hw->nb_voices * sizeof (sw));
- qemu_free (hw->pvoice);
- hw->pvoice = pvoice;
- hw->pvoice[hw->nb_voices++] = sw;
- return 0;
-}
+ if (!sw) {
+ return;
+ }
-int pcm_hw_del_sw (HWVoice *hw, SWVoice *sw)
-{
- int i, j;
- if (hw->nb_voices > 1) {
- SWVoice **pvoice = qemu_mallocz ((hw->nb_voices - 1) * sizeof (sw));
+ hw = sw->hw;
+ if (sw->active != on) {
+ SWVoiceIn *temp_sw;
- if (!pvoice) {
- dolog ("Can not maintain consistent state (not enough memory)\n");
- return -1;
+ if (on) {
+ if (!hw->enabled) {
+ hw->enabled = 1;
+ hw->pcm_ops->ctl_in (hw, VOICE_ENABLE);
+ }
+ 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;
+ }
- for (i = 0, j = 0; i < hw->nb_voices; i++) {
- if (j >= hw->nb_voices - 1) {
- dolog ("Can not maintain consistent state "
- "(invariant violated)\n");
- return -1;
+ if (nb_active == 1) {
+ hw->enabled = 0;
+ hw->pcm_ops->ctl_in (hw, VOICE_DISABLE);
+ }
}
- if (hw->pvoice[i] != sw)
- pvoice[j++] = hw->pvoice[i];
}
- qemu_free (hw->pvoice);
- hw->pvoice = pvoice;
- hw->nb_voices -= 1;
- }
- else {
- qemu_free (hw->pvoice);
- hw->pvoice = NULL;
- hw->nb_voices = 0;
+ sw->active = on;
}
- return 0;
}
-SWVoice *pcm_create_voice_pair (int freq, int nchannels, audfmt_e fmt)
+static int audio_get_avail (SWVoiceIn *sw)
{
- SWVoice *sw;
- HWVoice *hw;
-
- sw = qemu_mallocz (sizeof (*sw));
- if (!sw)
- goto err1;
+ int live;
- hw = pcm_hw_add (freq, nchannels, fmt);
- if (!hw)
- goto err2;
-
- if (pcm_hw_add_sw (hw, sw))
- goto err3;
+ if (!sw) {
+ return 0;
+ }
- if (pcm_sw_init (sw, hw, freq, nchannels, fmt))
- goto err4;
+ live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
+ if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) {
+ dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples);
+ return 0;
+ }
- return sw;
+ ldebug (
+ "%s: get_avail live %d ret %lld\n",
+ SW_NAME (sw),
+ live, (((int64_t) live << 32) / sw->ratio) << sw->info.shift
+ );
-err4:
- pcm_hw_del_sw (hw, sw);
-err3:
- pcm_hw_gc (hw);
-err2:
- qemu_free (sw);
-err1:
- return NULL;
+ return (((int64_t) live << 32) / sw->ratio) << sw->info.shift;
}
-SWVoice *AUD_open (SWVoice *sw, const char *name,
- int freq, int nchannels, audfmt_e fmt)
+static int audio_get_free (SWVoiceOut *sw)
{
- if (!audio_state.drv) {
- return NULL;
- }
+ int live, dead;
- if (sw && freq == sw->freq && sw->nchannels == nchannels && sw->fmt == fmt) {
- return sw;
+ if (!sw) {
+ return 0;
}
- if (sw) {
- ldebug ("Different format %s %d %d %d\n",
- name,
- sw->freq == freq,
- sw->nchannels == nchannels,
- sw->fmt == fmt);
- }
+ live = sw->total_hw_samples_mixed;
- if (nchannels != 1 && nchannels != 2) {
- dolog ("Bogus channel count %d for voice %s\n", nchannels, name);
- return NULL;
+ if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) {
+ dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples);
+ return 0;
}
- if (!audio_state.fixed_format && sw) {
- pcm_sw_fini (sw);
- pcm_hw_del_sw (sw->hw, sw);
- pcm_hw_gc (sw->hw);
- if (sw->name) {
- qemu_free (sw->name);
- sw->name = NULL;
+ dead = sw->hw->samples - live;
+
+#ifdef DEBUG_OUT
+ dolog ("%s: get_free live %d dead %d ret %lld\n",
+ SW_NAME (sw),
+ live, dead, (((int64_t) dead << 32) / sw->ratio) << sw->info.shift);
+#endif
+
+ return (((int64_t) dead << 32) / sw->ratio) << sw->info.shift;
+}
+
+static void audio_run_out (AudioState *s)
+{
+ HWVoiceOut *hw = NULL;
+ SWVoiceOut *sw;
+
+ while ((hw = audio_pcm_hw_find_any_enabled_out (s, hw))) {
+ int played;
+ int live, free, nb_live, cleanup_required;
+
+ live = audio_pcm_hw_get_live_out2 (hw, &nb_live);
+ if (!nb_live) {
+ live = 0;
}
- qemu_free (sw);
- sw = NULL;
- }
- if (sw) {
- HWVoice *hw = sw->hw;
- if (!hw) {
- dolog ("Internal logic error voice %s has no hardware store\n",
- name);
- return sw;
+ if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) {
+ dolog ("live=%d hw->samples=%d\n", live, hw->samples);
+ continue;
+ }
+
+ if (hw->pending_disable && !nb_live) {
+#ifdef DEBUG_OUT
+ dolog ("Disabling voice\n");
+#endif
+ hw->enabled = 0;
+ hw->pending_disable = 0;
+ hw->pcm_ops->ctl_out (hw, VOICE_DISABLE);
+ continue;
}
- if (pcm_sw_init (sw, hw, freq, nchannels, fmt)) {
- pcm_sw_fini (sw);
- pcm_hw_del_sw (hw, sw);
- pcm_hw_gc (hw);
- if (sw->name) {
- qemu_free (sw->name);
- sw->name = NULL;
+ 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);
+ }
+ }
}
- qemu_free (sw);
- return NULL;
+ continue;
}
- }
- else {
- sw = pcm_create_voice_pair (freq, nchannels, fmt);
- if (!sw) {
- dolog ("Failed to create voice %s\n", name);
- return NULL;
+
+ played = hw->pcm_ops->run_out (hw);
+ if (audio_bug (AUDIO_FUNC, hw->rpos >= hw->samples)) {
+ dolog ("hw->rpos=%d hw->samples=%d played=%d\n",
+ hw->rpos, hw->samples, played);
+ hw->rpos = 0;
}
- }
- if (sw->name) {
- qemu_free (sw->name);
- sw->name = NULL;
- }
- sw->name = qemu_strdup (name);
- return sw;
-}
+#ifdef DEBUG_OUT
+ dolog ("played=%d\n", played);
+#endif
-void AUD_close (SWVoice *sw)
-{
- if (!sw)
- return;
+ if (played) {
+ hw->ts_helper += played;
+ }
- pcm_sw_fini (sw);
- pcm_hw_del_sw (sw->hw, sw);
- pcm_hw_gc (sw->hw);
- if (sw->name) {
- qemu_free (sw->name);
- sw->name = NULL;
- }
- qemu_free (sw);
-}
+ cleanup_required = 0;
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (!sw->active && sw->empty) {
+ continue;
+ }
-int AUD_write (SWVoice *sw, void *buf, int size)
-{
- int bytes;
+ if (audio_bug (AUDIO_FUNC, played > sw->total_hw_samples_mixed)) {
+ dolog ("played=%d sw->total_hw_samples_mixed=%d\n",
+ played, sw->total_hw_samples_mixed);
+ played = sw->total_hw_samples_mixed;
+ }
- if (!sw->hw->enabled)
- dolog ("Writing to disabled voice %s\n", sw->name);
- bytes = sw->hw->pcm_ops->write (sw, buf, size);
- return bytes;
-}
+ sw->total_hw_samples_mixed -= played;
-void AUD_run (void)
-{
- HWVoice *hw = NULL;
+ if (!sw->total_hw_samples_mixed) {
+ sw->empty = 1;
+ cleanup_required |= !sw->active && !sw->callback.fn;
+ }
- while ((hw = pcm_hw_find_any_active_enabled (hw))) {
- int i;
- if (hw->pending_disable && pcm_hw_get_live (hw) <= 0) {
- hw->enabled = 0;
- hw->pcm_ops->ctl (hw, VOICE_DISABLE);
- for (i = 0; i < hw->nb_voices; i++) {
- hw->pvoice[i]->live = 0;
- /* hw->pvoice[i]->old_ticks = 0; */
+ if (sw->active) {
+ free = audio_get_free (sw);
+ if (free > 0) {
+ sw->callback.fn (sw->callback.opaque, free);
+ }
}
- continue;
}
- hw->pcm_ops->run (hw);
- assert (hw->rpos < hw->samples);
- for (i = 0; i < hw->nb_voices; i++) {
- SWVoice *sw = hw->pvoice[i];
- if (!sw->active && !sw->live && sw->old_ticks) {
- int64_t delta = qemu_get_clock (vm_clock) - sw->old_ticks;
- if (delta > audio_state.ticks_threshold) {
- ldebug ("resetting old_ticks for %s\n", sw->name);
- sw->old_ticks = 0;
+ if (cleanup_required) {
+ restart:
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (!sw->active && !sw->callback.fn) {
+#ifdef DEBUG_PLIVE
+ dolog ("Finishing with old voice\n");
+#endif
+ audio_close_out (s, sw);
+ goto restart; /* play it safe */
}
}
}
}
}
-int AUD_get_free (SWVoice *sw)
+static void audio_run_in (AudioState *s)
{
- int free;
+ HWVoiceIn *hw = NULL;
- if (!sw)
- return 4096;
+ while ((hw = audio_pcm_hw_find_any_enabled_in (s, hw))) {
+ SWVoiceIn *sw;
+ int captured, min;
- free = ((sw->hw->samples - sw->live) << sw->hw->shift) * sw->ratio
- / INT_MAX;
+ captured = hw->pcm_ops->run_in (hw);
- free &= ~sw->hw->align;
- if (!free) return 0;
+ min = audio_pcm_hw_find_min_in (hw);
+ hw->total_samples_captured += captured - min;
+ hw->ts_helper += captured;
- return free;
-}
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ sw->total_hw_samples_acquired -= min;
-int AUD_get_buffer_size (SWVoice *sw)
-{
- return sw->hw->bufsize;
-}
+ if (sw->active) {
+ int avail;
-void AUD_adjust (SWVoice *sw, int bytes)
-{
- if (!sw)
- return;
- sw->old_ticks += (ticks_per_sec * (int64_t) bytes) / sw->bytes_per_second;
+ avail = audio_get_avail (sw);
+ if (avail > 0) {
+ sw->callback.fn (sw->callback.opaque, avail);
+ }
+ }
+ }
+ }
}
-void AUD_reset (SWVoice *sw)
+static void audio_timer (void *opaque)
{
- sw->active = 0;
- sw->old_ticks = 0;
+ AudioState *s = opaque;
+
+ audio_run_out (s);
+ audio_run_in (s);
+
+ qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + conf.period.ticks);
}
-int AUD_calc_elapsed (SWVoice *sw)
-{
- int64_t now, delta, bytes;
- int dead, swlim;
+static struct audio_option audio_options[] = {
+ /* DAC */
+ {"DAC_FIXED_SETTINGS", AUD_OPT_BOOL, &conf.fixed_out.enabled,
+ "Use fixed settings for host DAC", NULL, 0},
- if (!sw)
- return 0;
+ {"DAC_FIXED_FREQ", AUD_OPT_INT, &conf.fixed_out.settings.freq,
+ "Frequency for fixed host DAC", NULL, 0},
- now = qemu_get_clock (vm_clock);
- delta = now - sw->old_ticks;
- bytes = (delta * sw->bytes_per_second) / ticks_per_sec;
- if (delta < 0) {
- dolog ("whoops delta(<0)=%lld\n", delta);
- return 0;
- }
+ {"DAC_FIXED_FMT", AUD_OPT_FMT, &conf.fixed_out.settings.fmt,
+ "Format for fixed host DAC", NULL, 0},
- dead = sw->hw->samples - sw->live;
- swlim = ((dead * (int64_t) INT_MAX) / sw->ratio);
+ {"DAC_FIXED_CHANNELS", AUD_OPT_INT, &conf.fixed_out.settings.nchannels,
+ "Number of channels for fixed DAC (1 - mono, 2 - stereo)", NULL, 0},
- if (bytes > swlim) {
- return swlim;
- }
- else {
- return bytes;
+ {"DAC_VOICES", AUD_OPT_INT, &conf.fixed_out.nb_voices,
+ "Number of voices for DAC", NULL, 0},
+
+ /* ADC */
+ {"ADC_FIXED_SETTINGS", AUD_OPT_BOOL, &conf.fixed_in.enabled,
+ "Use fixed settings for host ADC", NULL, 0},
+
+ {"ADC_FIXED_FREQ", AUD_OPT_INT, &conf.fixed_in.settings.freq,
+ "Frequency for fixed host ADC", NULL, 0},
+
+ {"ADC_FIXED_FMT", AUD_OPT_FMT, &conf.fixed_in.settings.fmt,
+ "Format for fixed host ADC", NULL, 0},
+
+ {"ADC_FIXED_CHANNELS", AUD_OPT_INT, &conf.fixed_in.settings.nchannels,
+ "Number of channels for fixed ADC (1 - mono, 2 - stereo)", NULL, 0},
+
+ {"ADC_VOICES", AUD_OPT_INT, &conf.fixed_in.nb_voices,
+ "Number of voices for ADC", NULL, 0},
+
+ /* Misc */
+ {"TIMER_PERIOD", AUD_OPT_INT, &conf.period.hz,
+ "Timer period in HZ (0 - use lowest possible)", NULL, 0},
+
+ {"PLIVE", AUD_OPT_BOOL, &conf.plive,
+ "(undocumented)", NULL, 0},
+
+ {"LOG_TO_MONITOR", AUD_OPT_BOOL, &conf.log_to_monitor,
+ "print logging messages to montior instead of stderr", NULL, 0},
+
+ {NULL, 0, NULL, NULL, NULL, 0}
+};
+
+static void audio_pp_nb_voices (const char *typ, int nb)
+{
+ switch (nb) {
+ case 0:
+ printf ("Does not support %s\n", typ);
+ break;
+ case 1:
+ printf ("One %s voice\n", typ);
+ break;
+ case INT_MAX:
+ printf ("Theoretically supports many %s voices\n", typ);
+ break;
+ default:
+ printf ("Theoretically supports upto %d %s voices\n", nb, typ);
+ break;
}
+
}
-void AUD_enable (SWVoice *sw, int on)
+void AUD_help (void)
{
- int i;
- HWVoice *hw;
+ size_t i;
- if (!sw)
- return;
-
- hw = sw->hw;
- if (on) {
- if (!sw->live)
- sw->wpos = sw->hw->rpos;
- if (!sw->old_ticks) {
- sw->old_ticks = qemu_get_clock (vm_clock);
+ audio_process_options ("AUDIO", audio_options);
+ for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) {
+ struct audio_driver *d = drvtab[i];
+ if (d->options) {
+ audio_process_options (d->name, d->options);
}
}
- if (sw->active != on) {
- if (on) {
- hw->pending_disable = 0;
- if (!hw->enabled) {
- hw->enabled = 1;
- for (i = 0; i < hw->nb_voices; i++) {
- ldebug ("resetting voice\n");
- sw = hw->pvoice[i];
- sw->old_ticks = qemu_get_clock (vm_clock);
- }
- hw->pcm_ops->ctl (hw, VOICE_ENABLE);
- }
+ printf ("Audio options:\n");
+ audio_print_options ("AUDIO", audio_options);
+ printf ("\n");
+
+ printf ("Available drivers:\n");
+
+ for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) {
+ struct audio_driver *d = drvtab[i];
+
+ printf ("Name: %s\n", d->name);
+ printf ("Description: %s\n", d->descr);
+
+ audio_pp_nb_voices ("playback", d->max_voices_out);
+ audio_pp_nb_voices ("capture", d->max_voices_in);
+
+ if (d->options) {
+ printf ("Options:\n");
+ audio_print_options (d->name, d->options);
}
else {
- if (hw->enabled && !hw->pending_disable) {
- int nb_active = 0;
- for (i = 0; i < hw->nb_voices; i++) {
- nb_active += hw->pvoice[i]->active != 0;
- }
-
- if (nb_active == 1) {
- hw->pending_disable = 1;
- }
- }
+ printf ("No options\n");
}
- sw->active = on;
+ printf ("\n");
}
-}
-static struct audio_output_driver *drvtab[] = {
-#ifdef CONFIG_OSS
- &oss_output_driver,
-#endif
-#ifdef CONFIG_FMOD
- &fmod_output_driver,
-#endif
-#ifdef CONFIG_SDL
- &sdl_output_driver,
-#endif
- &no_output_driver,
-#ifdef USE_WAV_AUDIO
- &wav_output_driver,
+ printf (
+ "Options are settable through environment variables.\n"
+ "Example:\n"
+#ifdef _WIN32
+ " set QEMU_AUDIO_DRV=wav\n"
+ " set QEMU_WAV_PATH=c:\\tune.wav\n"
+#else
+ " export QEMU_AUDIO_DRV=wav\n"
+ " export QEMU_WAV_PATH=$HOME/tune.wav\n"
+ "(for csh replace export with setenv in the above)\n"
#endif
-};
+ " qemu ...\n\n"
+ );
+}
-static int voice_init (struct audio_output_driver *drv)
+static int audio_driver_init (AudioState *s, struct audio_driver *drv)
{
- audio_state.opaque = drv->init ();
- if (audio_state.opaque) {
- if (audio_state.nb_hw_voices > drv->max_voices) {
- dolog ("`%s' does not support %d multiple hardware channels\n"
- "Resetting to %d\n",
- drv->name, audio_state.nb_hw_voices, drv->max_voices);
- audio_state.nb_hw_voices = drv->max_voices;
- }
- hw_voices = qemu_mallocz (audio_state.nb_hw_voices * drv->voice_size);
- if (hw_voices) {
- audio_state.drv = drv;
- return 1;
- }
- else {
- dolog ("Not enough memory for %d `%s' voices (each %d bytes)\n",
- audio_state.nb_hw_voices, drv->name, drv->voice_size);
- drv->fini (audio_state.opaque);
- return 0;
- }
+ if (drv->options) {
+ audio_process_options (drv->name, drv->options);
}
- else {
- dolog ("Could not init `%s' audio\n", drv->name);
+ s->drv_opaque = drv->init ();
+
+ if (s->drv_opaque) {
+ audio_init_nb_voices_out (s, drv);
+ audio_init_nb_voices_in (s, drv);
+ s->drv = drv;
return 0;
}
+ else {
+ dolog ("Could not init `%s' audio driver\n", drv->name);
+ return -1;
+ }
}
-static void audio_vm_stop_handler (void *opaque, int reason)
+static void audio_vm_change_state_handler (void *opaque, int running)
{
- HWVoice *hw = NULL;
+ AudioState *s = opaque;
+ HWVoiceOut *hwo = NULL;
+ HWVoiceIn *hwi = NULL;
+ int op = running ? VOICE_ENABLE : VOICE_DISABLE;
- while ((hw = pcm_hw_find_any (hw))) {
- if (!hw->pcm_ops)
- continue;
+ while ((hwo = audio_pcm_hw_find_any_enabled_out (s, hwo))) {
+ hwo->pcm_ops->ctl_out (hwo, op);
+ }
- hw->pcm_ops->ctl (hw, reason ? VOICE_ENABLE : VOICE_DISABLE);
+ while ((hwi = audio_pcm_hw_find_any_enabled_in (s, hwi))) {
+ hwi->pcm_ops->ctl_in (hwi, op);
}
}
static void audio_atexit (void)
{
- HWVoice *hw = NULL;
+ AudioState *s = &glob_audio_state;
+ HWVoiceOut *hwo = NULL;
+ HWVoiceIn *hwi = NULL;
- while ((hw = pcm_hw_find_any (hw))) {
- if (!hw->pcm_ops)
- continue;
+ while ((hwo = audio_pcm_hw_find_any_enabled_out (s, hwo))) {
+ hwo->pcm_ops->ctl_out (hwo, VOICE_DISABLE);
+ hwo->pcm_ops->fini_out (hwo);
+ }
+
+ while ((hwi = audio_pcm_hw_find_any_enabled_in (s, hwi))) {
+ hwi->pcm_ops->ctl_in (hwi, VOICE_DISABLE);
+ hwi->pcm_ops->fini_in (hwi);
+ }
- hw->pcm_ops->ctl (hw, VOICE_DISABLE);
- hw->pcm_ops->fini (hw);
+ if (s->drv) {
+ s->drv->fini (s->drv_opaque);
}
- audio_state.drv->fini (audio_state.opaque);
}
static void audio_save (QEMUFile *f, void *opaque)
{
+ (void) f;
+ (void) opaque;
}
static int audio_load (QEMUFile *f, void *opaque, int version_id)
{
- if (version_id != 1)
+ (void) f;
+ (void) opaque;
+
+ if (version_id != 1) {
return -EINVAL;
+ }
return 0;
}
-void AUD_init (void)
+void AUD_register_card (AudioState *s, const char *name, QEMUSoundCard *card)
+{
+ card->audio = s;
+ card->name = qemu_strdup (name);
+ memset (&card->entries, 0, sizeof (card->entries));
+ LIST_INSERT_HEAD (&s->card_head, card, entries);
+}
+
+void AUD_remove_card (QEMUSoundCard *card)
+{
+ LIST_REMOVE (card, entries);
+ card->audio = NULL;
+ qemu_free (card->name);
+}
+
+AudioState *AUD_init (void)
{
- int i;
+ size_t i;
int done = 0;
const char *drvname;
+ AudioState *s = &glob_audio_state;
- audio_state.fixed_format =
- !!audio_get_conf_int (QC_FIXED_FORMAT, audio_state.fixed_format);
- audio_state.fixed_freq =
- audio_get_conf_int (QC_FIXED_FREQ, audio_state.fixed_freq);
- audio_state.nb_hw_voices =
- audio_get_conf_int (QC_VOICES, audio_state.nb_hw_voices);
+ LIST_INIT (&s->hw_head_out);
+ LIST_INIT (&s->hw_head_in);
+ atexit (audio_atexit);
+
+ s->ts = qemu_new_timer (vm_clock, audio_timer, s);
+ if (!s->ts) {
+ dolog ("Could not create audio timer\n");
+ return NULL;
+ }
+
+ audio_process_options ("AUDIO", audio_options);
+
+ s->nb_hw_voices_out = conf.fixed_out.nb_voices;
+ s->nb_hw_voices_in = conf.fixed_in.nb_voices;
+
+ if (s->nb_hw_voices_out <= 0) {
+ dolog ("Bogus number of playback voices %d, setting to 1\n",
+ s->nb_hw_voices_out);
+ s->nb_hw_voices_out = 1;
+ }
- if (audio_state.nb_hw_voices <= 0) {
- dolog ("Bogus number of voices %d, resetting to 1\n",
- audio_state.nb_hw_voices);
+ if (s->nb_hw_voices_in <= 0) {
+ dolog ("Bogus number of capture voices %d, setting to 0\n",
+ s->nb_hw_voices_in);
+ s->nb_hw_voices_in = 0;
+ }
+
+ {
+ int def;
+ drvname = audio_get_conf_str ("QEMU_AUDIO_DRV", NULL, &def);
}
- drvname = audio_get_conf_str (QC_AUDIO_DRV, NULL);
if (drvname) {
int found = 0;
+
for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) {
if (!strcmp (drvname, drvtab[i]->name)) {
- done = voice_init (drvtab[i]);
+ done = !audio_driver_init (s, drvtab[i]);
found = 1;
break;
}
}
+
if (!found) {
dolog ("Unknown audio driver `%s'\n", drvname);
+ dolog ("Run with -audio-help to list available drivers\n");
}
}
- qemu_add_vm_stop_handler (audio_vm_stop_handler, NULL);
- atexit (audio_atexit);
-
if (!done) {
for (i = 0; !done && i < sizeof (drvtab) / sizeof (drvtab[0]); i++) {
- if (drvtab[i]->can_be_default)
- done = voice_init (drvtab[i]);
+ if (drvtab[i]->can_be_default) {
+ done = !audio_driver_init (s, drvtab[i]);
+ }
}
}
- audio_state.ticks_threshold = ticks_per_sec / 50;
- audio_state.freq_threshold = 100;
-
- register_savevm ("audio", 0, 1, audio_save, audio_load, NULL);
if (!done) {
- dolog ("Can not initialize audio subsystem\n");
- voice_init (&no_output_driver);
+ done = !audio_driver_init (s, &no_audio_driver);
+ if (!done) {
+ dolog ("Could not initialize audio subsystem\n");
+ }
+ else {
+ dolog ("warning: Using timer based audio emulation\n");
+ }
+ }
+
+ if (done) {
+ VMChangeStateEntry *e;
+
+ if (conf.period.hz <= 0) {
+ if (conf.period.hz < 0) {
+ dolog ("warning: Timer period is negative - %d "
+ "treating as zero\n",
+ conf.period.hz);
+ }
+ conf.period.ticks = 1;
+ }
+ else {
+ conf.period.ticks = ticks_per_sec / conf.period.hz;
+ }
+
+ 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");
+ }
}
+ else {
+ qemu_del_timer (s->ts);
+ return NULL;
+ }
+
+ LIST_INIT (&s->card_head);
+ register_savevm ("audio", 0, 1, audio_save, audio_load, s);
+ qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + conf.period.ticks);
+ return s;
}