@@ -1,4 +1,4 @@
-common-obj-y = audio.o noaudio.o wavaudio.o mixeng.o
+common-obj-y = audio.o audio_legacy.o noaudio.o wavaudio.o mixeng.o
common-obj-$(CONFIG_SDL) += sdlaudio.o
common-obj-$(CONFIG_OSS) += ossaudio.o
common-obj-$(CONFIG_SPICE) += spiceaudio.o
@@ -1126,7 +1126,7 @@ static ALSAConf glob_conf = {
.pcm_name_in = "default",
};
-static void *alsa_audio_init (void)
+static void *alsa_audio_init(Audiodev *dev)
{
ALSAConf *conf = g_malloc(sizeof(ALSAConf));
*conf = glob_conf;
@@ -24,7 +24,10 @@
#include "hw/hw.h"
#include "audio.h"
#include "monitor/monitor.h"
+#include "qapi-visit.h"
+#include "qapi/opts-visitor.h"
#include "qemu/timer.h"
+#include "qemu/config-file.h"
#include "sysemu/sysemu.h"
#define AUDIO_CAP "audio"
@@ -42,59 +45,14 @@
The 1st one is the one used by default, that is the reason
that we generate the list.
*/
-static struct audio_driver *drvtab[] = {
+struct audio_driver *drvtab[] = {
#ifdef CONFIG_SPICE
&spice_audio_driver,
#endif
CONFIG_AUDIO_DRIVERS
&no_audio_driver,
- &wav_audio_driver
-};
-
-struct fixed_settings {
- int enabled;
- int nb_voices;
- int greedy;
- struct audsettings settings;
-};
-
-static struct {
- struct fixed_settings fixed_out;
- struct fixed_settings fixed_in;
- union {
- int hertz;
- int64_t ticks;
- } period;
- int try_poll_in;
- int try_poll_out;
-} conf = {
- .fixed_out = { /* DAC fixed settings */
- .enabled = 1,
- .nb_voices = 1,
- .greedy = 1,
- .settings = {
- .freq = 44100,
- .nchannels = 2,
- .fmt = AUDIO_FORMAT_S16,
- .endianness = AUDIO_HOST_ENDIANNESS,
- }
- },
-
- .fixed_in = { /* ADC fixed settings */
- .enabled = 1,
- .nb_voices = 1,
- .greedy = 1,
- .settings = {
- .freq = 44100,
- .nchannels = 2,
- .fmt = AUDIO_FORMAT_S16,
- .endianness = AUDIO_HOST_ENDIANNESS,
- }
- },
-
- .period = { .hertz = 100 },
- .try_poll_in = 1,
- .try_poll_out = 1,
+ &wav_audio_driver,
+ NULL
};
static AudioState glob_audio_state;
@@ -113,9 +71,6 @@ const struct mixeng_volume nominal_volume = {
#ifdef AUDIO_IS_FLAWLESS_AND_NO_CHECKS_ARE_REQURIED
#error No its not
#else
-static void audio_print_options (const char *prefix,
- struct audio_option *opt);
-
int audio_bug (const char *funcname, int cond)
{
if (cond) {
@@ -123,16 +78,9 @@ int audio_bug (const char *funcname, int cond)
AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
if (!shown) {
- struct audio_driver *d;
-
shown = 1;
AUD_log (NULL, "Save all your work and restart without audio\n");
- AUD_log (NULL, "Please send bug report to av1474@comtv.ru\n");
AUD_log (NULL, "I am sorry\n");
- d = glob_audio_state.drv;
- if (d) {
- audio_print_options (d->name, d->options);
- }
}
AUD_log (NULL, "Context:\n");
@@ -194,31 +142,6 @@ void *audio_calloc (const char *funcname, int nmemb, size_t size)
return g_malloc0 (len);
}
-static char *audio_alloc_prefix (const char *s)
-{
- const char qemu_prefix[] = "QEMU_";
- size_t len, i;
- char *r, *u;
-
- if (!s) {
- return NULL;
- }
-
- len = strlen (s);
- r = g_malloc (len + sizeof (qemu_prefix));
-
- u = r + sizeof (qemu_prefix) - 1;
-
- pstrcpy (r, len + sizeof (qemu_prefix), qemu_prefix);
- pstrcat (r, len + sizeof (qemu_prefix), s);
-
- for (i = 0; i < len; ++i) {
- u[i] = qemu_toupper(u[i]);
- }
-
- return r;
-}
-
static const char *audio_audfmt_to_string (AudioFormat fmt)
{
switch (fmt) {
@@ -345,78 +268,6 @@ void AUD_log (const char *cap, const char *fmt, ...)
va_end (ap);
}
-static void audio_print_options (const char *prefix,
- struct audio_option *opt)
-{
- char *uprefix;
-
- if (!prefix) {
- dolog ("No prefix specified\n");
- return;
- }
-
- if (!opt) {
- dolog ("No options\n");
- return;
- }
-
- uprefix = audio_alloc_prefix (prefix);
-
- for (; opt->name; opt++) {
- const char *state = "default";
- printf (" %s_%s: ", uprefix, opt->name);
-
- if (opt->overriddenp && *opt->overriddenp) {
- state = "current";
- }
-
- switch (opt->tag) {
- case AUD_OPT_BOOL:
- {
- int *intp = opt->valp;
- printf ("boolean, %s = %d\n", state, *intp ? 1 : 0);
- }
- break;
-
- case AUD_OPT_INT:
- {
- int *intp = opt->valp;
- printf ("integer, %s = %d\n", state, *intp);
- }
- break;
-
- case AUD_OPT_FMT:
- {
- AudioFormat *fmtp = opt->valp;
- printf (
- "format, %s = %s, (one of: U8 S8 U16 S16 U32 S32)\n",
- state,
- audio_audfmt_to_string (*fmtp)
- );
- }
- break;
-
- case AUD_OPT_STR:
- {
- const char **strp = opt->valp;
- printf ("string, %s = %s\n",
- state,
- *strp ? *strp : "(not set)");
- }
- break;
-
- default:
- printf ("???\n");
- dolog ("Bad value tag for option %s_%s %d\n",
- uprefix, opt->name, opt->tag);
- break;
- }
- printf (" %s\n", opt->descr);
- }
-
- g_free (uprefix);
-}
-
static void audio_process_options (const char *prefix,
struct audio_option *opt)
{
@@ -1120,7 +971,7 @@ static void audio_reset_timer (AudioState *s)
{
if (audio_is_timer_needed ()) {
timer_mod (s->ts,
- qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + conf.period.ticks);
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
}
else {
timer_del (s->ts);
@@ -1196,7 +1047,7 @@ void AUD_set_active_out (SWVoiceOut *sw, int on)
if (!hw->enabled) {
hw->enabled = 1;
if (s->vm_running) {
- hw->pcm_ops->ctl_out (hw, VOICE_ENABLE, conf.try_poll_out);
+ hw->pcm_ops->ctl_out(hw, VOICE_ENABLE, true /* todo */);
audio_reset_timer (s);
}
}
@@ -1241,7 +1092,7 @@ void AUD_set_active_in (SWVoiceIn *sw, int on)
if (!hw->enabled) {
hw->enabled = 1;
if (s->vm_running) {
- hw->pcm_ops->ctl_in (hw, VOICE_ENABLE, conf.try_poll_in);
+ hw->pcm_ops->ctl_in(hw, VOICE_ENABLE, true /* todo */);
audio_reset_timer (s);
}
}
@@ -1558,168 +1409,13 @@ void audio_run (const char *msg)
#endif
}
-static struct audio_option audio_options[] = {
- /* DAC */
- {
- .name = "DAC_FIXED_SETTINGS",
- .tag = AUD_OPT_BOOL,
- .valp = &conf.fixed_out.enabled,
- .descr = "Use fixed settings for host DAC"
- },
- {
- .name = "DAC_FIXED_FREQ",
- .tag = AUD_OPT_INT,
- .valp = &conf.fixed_out.settings.freq,
- .descr = "Frequency for fixed host DAC"
- },
- {
- .name = "DAC_FIXED_FMT",
- .tag = AUD_OPT_FMT,
- .valp = &conf.fixed_out.settings.fmt,
- .descr = "Format for fixed host DAC"
- },
- {
- .name = "DAC_FIXED_CHANNELS",
- .tag = AUD_OPT_INT,
- .valp = &conf.fixed_out.settings.nchannels,
- .descr = "Number of channels for fixed DAC (1 - mono, 2 - stereo)"
- },
- {
- .name = "DAC_VOICES",
- .tag = AUD_OPT_INT,
- .valp = &conf.fixed_out.nb_voices,
- .descr = "Number of voices for DAC"
- },
- {
- .name = "DAC_TRY_POLL",
- .tag = AUD_OPT_BOOL,
- .valp = &conf.try_poll_out,
- .descr = "Attempt using poll mode for DAC"
- },
- /* ADC */
- {
- .name = "ADC_FIXED_SETTINGS",
- .tag = AUD_OPT_BOOL,
- .valp = &conf.fixed_in.enabled,
- .descr = "Use fixed settings for host ADC"
- },
- {
- .name = "ADC_FIXED_FREQ",
- .tag = AUD_OPT_INT,
- .valp = &conf.fixed_in.settings.freq,
- .descr = "Frequency for fixed host ADC"
- },
- {
- .name = "ADC_FIXED_FMT",
- .tag = AUD_OPT_FMT,
- .valp = &conf.fixed_in.settings.fmt,
- .descr = "Format for fixed host ADC"
- },
- {
- .name = "ADC_FIXED_CHANNELS",
- .tag = AUD_OPT_INT,
- .valp = &conf.fixed_in.settings.nchannels,
- .descr = "Number of channels for fixed ADC (1 - mono, 2 - stereo)"
- },
- {
- .name = "ADC_VOICES",
- .tag = AUD_OPT_INT,
- .valp = &conf.fixed_in.nb_voices,
- .descr = "Number of voices for ADC"
- },
- {
- .name = "ADC_TRY_POLL",
- .tag = AUD_OPT_BOOL,
- .valp = &conf.try_poll_in,
- .descr = "Attempt using poll mode for ADC"
- },
- /* Misc */
- {
- .name = "TIMER_PERIOD",
- .tag = AUD_OPT_INT,
- .valp = &conf.period.hertz,
- .descr = "Timer period in HZ (0 - use lowest possible)"
- },
- { /* End of list */ }
-};
-
-static void audio_pp_nb_voices (const char *typ, int nb)
-{
- switch (nb) {
- case 0:
- printf ("Does not support %s\n", typ);
- break;
- case 1:
- printf ("One %s voice\n", typ);
- break;
- case INT_MAX:
- printf ("Theoretically supports many %s voices\n", typ);
- break;
- default:
- printf ("Theoretically supports up to %d %s voices\n", nb, typ);
- break;
- }
-
-}
-
-void AUD_help (void)
-{
- size_t i;
-
- audio_process_options ("AUDIO", audio_options);
- for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
- struct audio_driver *d = drvtab[i];
- if (d->options) {
- audio_process_options (d->name, d->options);
- }
- }
-
- printf ("Audio options:\n");
- audio_print_options ("AUDIO", audio_options);
- printf ("\n");
-
- printf ("Available drivers:\n");
-
- for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
- struct audio_driver *d = drvtab[i];
-
- printf ("Name: %s\n", d->name);
- printf ("Description: %s\n", d->descr);
-
- audio_pp_nb_voices ("playback", d->max_voices_out);
- audio_pp_nb_voices ("capture", d->max_voices_in);
-
- if (d->options) {
- printf ("Options:\n");
- audio_print_options (d->name, d->options);
- }
- else {
- printf ("No options\n");
- }
- printf ("\n");
- }
-
- printf (
- "Options are settable through environment variables.\n"
- "Example:\n"
-#ifdef _WIN32
- " set QEMU_AUDIO_DRV=wav\n"
- " set QEMU_WAV_PATH=c:\\tune.wav\n"
-#else
- " export QEMU_AUDIO_DRV=wav\n"
- " export QEMU_WAV_PATH=$HOME/tune.wav\n"
- "(for csh replace export with setenv in the above)\n"
-#endif
- " qemu ...\n\n"
- );
-}
-
-static int audio_driver_init (AudioState *s, struct audio_driver *drv)
+static int audio_driver_init(AudioState *s, struct audio_driver *drv,
+ Audiodev *dev)
{
if (drv->options) {
audio_process_options (drv->name, drv->options);
}
- s->drv_opaque = drv->init ();
+ s->drv_opaque = drv->init(dev);
if (s->drv_opaque) {
audio_init_nb_voices_out (drv);
@@ -1743,11 +1439,11 @@ static void audio_vm_change_state_handler (void *opaque, int running,
s->vm_running = running;
while ((hwo = audio_pcm_hw_find_any_enabled_out (hwo))) {
- hwo->pcm_ops->ctl_out (hwo, op, conf.try_poll_out);
+ hwo->pcm_ops->ctl_out(hwo, op, true /* todo */);
}
while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) {
- hwi->pcm_ops->ctl_in (hwi, op, conf.try_poll_in);
+ hwi->pcm_ops->ctl_in(hwi, op, true /* todo */);
}
audio_reset_timer (s);
}
@@ -1786,6 +1482,8 @@ static void audio_atexit (void)
if (s->drv) {
s->drv->fini (s->drv_opaque);
}
+
+ qapi_free_Audiodev(s->dev);
}
static const VMStateDescription vmstate_audio = {
@@ -1797,18 +1495,37 @@ static const VMStateDescription vmstate_audio = {
}
};
-static void audio_init (void)
+static Audiodev *parse_option(QemuOpts *opts, Error **errp);
+static int audio_init(Audiodev *dev)
{
size_t i;
int done = 0;
- const char *drvname;
+ const char *drvname = NULL;
VMChangeStateEntry *e;
AudioState *s = &glob_audio_state;
+ QemuOptsList *list = NULL; /* silence gcc warning about uninitialized
+ * variable */
if (s->drv) {
- return;
+ if (dev) {
+ dolog("Cannot create more than one audio backend, sorry\n");
+ qapi_free_Audiodev(dev);
+ }
+ return -1;
}
+ if (dev) {
+ drvname = AudiodevDriver_lookup[dev->driver];
+ } else {
+ audio_handle_legacy_opts();
+ list = qemu_find_opts("audiodev");
+ dev = parse_option(QTAILQ_FIRST(&list->head), &error_abort);
+ if (!dev) {
+ exit(1);
+ }
+ }
+ s->dev = dev;
+
QLIST_INIT (&s->hw_head_out);
QLIST_INIT (&s->hw_head_in);
QLIST_INIT (&s->cap_head);
@@ -1819,10 +1536,8 @@ static void audio_init (void)
hw_error("Could not create audio timer\n");
}
- audio_process_options ("AUDIO", audio_options);
-
- s->nb_hw_voices_out = conf.fixed_out.nb_voices;
- s->nb_hw_voices_in = conf.fixed_in.nb_voices;
+ s->nb_hw_voices_out = dev->out->voices;
+ s->nb_hw_voices_in = dev->in->voices;
if (s->nb_hw_voices_out <= 0) {
dolog ("Bogus number of playback voices %d, setting to 1\n",
@@ -1836,17 +1551,12 @@ static void audio_init (void)
s->nb_hw_voices_in = 0;
}
- {
- int def;
- drvname = audio_get_conf_str ("QEMU_AUDIO_DRV", NULL, &def);
- }
-
if (drvname) {
int found = 0;
- for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
+ for (i = 0; drvtab[i]; i++) {
if (!strcmp (drvname, drvtab[i]->name)) {
- done = !audio_driver_init (s, drvtab[i]);
+ done = !audio_driver_init (s, drvtab[i], dev);
found = 1;
break;
}
@@ -1854,20 +1564,24 @@ static void audio_init (void)
if (!found) {
dolog ("Unknown audio driver `%s'\n", drvname);
- dolog ("Run with -audio-help to list available drivers\n");
}
- }
-
- if (!done) {
- for (i = 0; !done && i < ARRAY_SIZE (drvtab); i++) {
- if (drvtab[i]->can_be_default) {
- done = !audio_driver_init (s, drvtab[i]);
+ } else {
+ for (i = 0; !done && drvtab[i]; i++) {
+ QemuOpts *opts = qemu_opts_find(list, drvtab[i]->name);
+ if (opts) {
+ qapi_free_Audiodev(dev);
+ dev = parse_option(opts, &error_abort);
+ if (!dev) {
+ exit(1);
+ }
+ s->dev = dev;
+ done = !audio_driver_init(s, drvtab[i], dev);
}
}
}
if (!done) {
- done = !audio_driver_init (s, &no_audio_driver);
+ done = !audio_driver_init (s, &no_audio_driver, dev);
if (!done) {
hw_error("Could not initialize audio subsystem\n");
}
@@ -1876,16 +1590,16 @@ static void audio_init (void)
}
}
- if (conf.period.hertz <= 0) {
- if (conf.period.hertz < 0) {
- dolog ("warning: Timer period is negative - %d "
- "treating as zero\n",
- conf.period.hertz);
+ if (dev->timer_period <= 0) {
+ if (dev->timer_period < 0) {
+ dolog ("warning: Timer period is negative - %" PRId64
+ " treating as zero\n",
+ dev->timer_period);
}
- conf.period.ticks = 1;
+ s->period_ticks = 1;
} else {
- conf.period.ticks =
- muldiv64 (1, get_ticks_per_sec (), conf.period.hertz);
+ s->period_ticks =
+ muldiv64(dev->timer_period, get_ticks_per_sec(), 1000000);
}
e = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s);
@@ -1896,11 +1610,12 @@ static void audio_init (void)
QLIST_INIT (&s->card_head);
vmstate_register (NULL, 0, &vmstate_audio, s);
+ return 0;
}
void AUD_register_card (const char *name, QEMUSoundCard *card)
{
- audio_init ();
+ audio_init(NULL);
card->name = g_strdup (name);
memset (&card->entries, 0, sizeof (card->entries));
QLIST_INSERT_HEAD (&glob_audio_state.card_head, card, entries);
@@ -2070,3 +1785,156 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol)
}
}
}
+
+QemuOptsList qemu_audiodev_opts = {
+ .name = "audiodev",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_audiodev_opts.head),
+ .implied_opt_name = "driver",
+ .desc = {
+ /*
+ * no elements => accept any params
+ * sanity checking will happen later
+ */
+ { /* end of list */ }
+ },
+};
+
+static void validate_per_direction_opts(AudiodevPerDirectionOptions *pdo,
+ Error **errp)
+{
+ if (!pdo->has_fixed_settings) {
+ pdo->has_fixed_settings = true;
+ pdo->fixed_settings = true;
+ }
+ 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->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 = 1;
+ }
+ if (!pdo->has_format) {
+ pdo->has_format = true;
+ pdo->format = AUDIO_FORMAT_S16;
+ }
+}
+
+static Audiodev *parse_option(QemuOpts *opts, Error **errp)
+{
+ Error *local_err = NULL;
+ OptsVisitor *ov = opts_visitor_new(opts, true);
+ Audiodev *dev = NULL;
+ visit_type_Audiodev(opts_get_visitor(ov), &dev, NULL, &local_err);
+ opts_visitor_cleanup(ov);
+
+ if (local_err) {
+ goto err2;
+ }
+
+ validate_per_direction_opts(dev->in, &local_err);
+ if (local_err) {
+ goto err;
+ }
+ validate_per_direction_opts(dev->out, &local_err);
+ if (local_err) {
+ goto err;
+ }
+
+ if (!dev->has_timer_period) {
+ dev->has_timer_period = true;
+ dev->timer_period = 10000; /* 100Hz -> 10ms */
+ }
+
+ return dev;
+
+err:
+ qapi_free_Audiodev(dev);
+err2:
+ error_propagate(errp, local_err);
+ return NULL;
+}
+
+static int each_option(void *opaque, QemuOpts *opts, Error **errp)
+{
+ Audiodev *dev = parse_option(opts, errp);
+ if (!dev) {
+ return -1;
+ }
+ return audio_init(dev);
+}
+
+void audio_set_options(void)
+{
+ if (qemu_opts_foreach(qemu_find_opts("audiodev"), each_option, NULL,
+ &error_abort)) {
+ exit(1);
+ }
+}
+
+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:
+ 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_len ? pdo->buffer_len : 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);
+}
@@ -24,7 +24,10 @@
#ifndef QEMU_AUDIO_H
#define QEMU_AUDIO_H
+#include <stdarg.h>
#include "config-host.h"
+#include "qapi-types.h"
+#include "qemu/option.h"
#include "qemu/queue.h"
typedef void (*audio_callback_fn) (void *opaque, int avail);
@@ -35,12 +38,21 @@ typedef void (*audio_callback_fn) (void *opaque, int avail);
#define AUDIO_HOST_ENDIANNESS 0
#endif
-struct audsettings {
+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,
@@ -77,10 +89,11 @@ typedef struct QEMUAudioTimeStamp {
uint64_t old_ts;
} QEMUAudioTimeStamp;
+extern QemuOptsList qemu_audiodev_opts;
+
void AUD_vlog (const char *cap, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
void AUD_log (const char *cap, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
-void AUD_help (void);
void AUD_register_card (const char *name, QEMUSoundCard *card);
void AUD_remove_card (QEMUSoundCard *card);
CaptureVoiceOut *AUD_add_capture (
@@ -154,4 +167,8 @@ static inline void *advance (void *p, int incr)
int wav_start_capture (CaptureState *s, const char *path, int freq,
int bits, int nchannels);
+void audio_set_options(void);
+void audio_handle_legacy_opts(void);
+void audio_legacy_help(void);
+
#endif /* audio.h */
@@ -144,7 +144,7 @@ struct audio_driver {
const char *name;
const char *descr;
struct audio_option *options;
- void *(*init) (void);
+ void *(*init) (Audiodev *);
void (*fini) (void *);
struct audio_pcm_ops *pcm_ops;
int can_be_default;
@@ -190,6 +190,7 @@ struct SWVoiceCap {
struct AudioState {
struct audio_driver *drv;
+ Audiodev *dev;
void *drv_opaque;
QEMUTimer *ts;
@@ -200,6 +201,7 @@ struct AudioState {
int nb_hw_voices_out;
int nb_hw_voices_in;
int vm_running;
+ int64_t period_ticks;
};
extern struct audio_driver no_audio_driver;
@@ -213,6 +215,8 @@ extern struct audio_driver pa_audio_driver;
extern struct audio_driver spice_audio_driver;
extern const struct mixeng_volume nominal_volume;
+extern struct audio_driver *drvtab[];
+
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);
new file mode 100644
@@ -0,0 +1,202 @@
+#include "audio.h"
+#include "qemu-common.h"
+#include "qemu/config-file.h"
+
+#define AUDIO_CAP "audio-legacy"
+#include "audio_int.h"
+
+typedef enum EnvTransform {
+ ENV_TRANSFORM_NONE,
+ ENV_TRANSFORM_BOOL,
+ ENV_TRANSFORM_FMT,
+ ENV_TRANSFORM_FRAMES_TO_USECS_IN,
+ ENV_TRANSFORM_FRAMES_TO_USECS_OUT,
+ ENV_TRANSFORM_SAMPLES_TO_USECS_IN,
+ ENV_TRANSFORM_SAMPLES_TO_USECS_OUT,
+ ENV_TRANSFORM_BYTES_TO_USECS_IN,
+ ENV_TRANSFORM_BYTES_TO_USECS_OUT,
+ ENV_TRANSFORM_MILLIS_TO_USECS,
+ ENV_TRANSFORM_HZ_TO_USECS,
+} EnvTransform;
+
+typedef struct SimpleEnvMap {
+ const char *name;
+ const char *option;
+ EnvTransform transform;
+} SimpleEnvMap;
+
+SimpleEnvMap global_map[] = {
+ /* DAC/out settings */
+ { "QEMU_AUDIO_DAC_FIXED_SETTINGS", "out.fixed-settings",
+ ENV_TRANSFORM_BOOL },
+ { "QEMU_AUDIO_DAC_FIXED_FREQ", "out.frequency" },
+ { "QEMU_AUDIO_DAC_FIXED_FMT", "out.format", ENV_TRANSFORM_FMT },
+ { "QEMU_AUDIO_DAC_FIXED_CHANNELS", "out.channels" },
+ { "QEMU_AUDIO_DAC_VOICES", "out.voices" },
+
+ /* ADC/in settings */
+ { "QEMU_AUDIO_ADC_FIXED_SETTINGS", "in.fixed-settings",
+ ENV_TRANSFORM_BOOL },
+ { "QEMU_AUDIO_ADC_FIXED_FREQ", "in.frequency" },
+ { "QEMU_AUDIO_ADC_FIXED_FMT", "in.format", ENV_TRANSFORM_FMT },
+ { "QEMU_AUDIO_ADC_FIXED_CHANNELS", "in.channels" },
+ { "QEMU_AUDIO_ADC_VOICES", "in.voices" },
+
+ /* general */
+ { "QEMU_AUDIO_TIMER_PERIOD", "timer-period", ENV_TRANSFORM_HZ_TO_USECS },
+ { /* End of list */ }
+};
+
+static unsigned long long toull(const char *str)
+{
+ unsigned long long ret;
+ if (parse_uint_full(str, &ret, 10)) {
+ dolog("Invalid integer value `%s'\n", str);
+ exit(1);
+ }
+ return ret;
+}
+
+/* non reentrant typesafe or anything, but enough in this small c file */
+static const char *tostr(unsigned long long val)
+{
+ /* max length in decimal possible for an unsigned long long number */
+ #define LEN ((CHAR_BIT * sizeof(unsigned long long) - 1) / 3 + 2)
+ static char ret[LEN];
+ snprintf(ret, LEN, "%llu", val);
+ return ret;
+}
+
+static uint64_t frames_to_usecs(QemuOpts *opts, uint64_t frames, bool in)
+{
+ const char *opt = in ? "in.frequency" : "out.frequency";
+ uint64_t freq = qemu_opt_get_number(opts, opt, 44100);
+ return (frames * 1000000 + freq/2) / freq;
+}
+
+static uint64_t samples_to_usecs(QemuOpts *opts, uint64_t samples, bool in)
+{
+ const char *opt = in ? "in.channels" : "out.channels";
+ uint64_t channels = qemu_opt_get_number(opts, opt, 2);
+ return frames_to_usecs(opts, samples/channels, in);
+}
+
+static uint64_t bytes_to_usecs(QemuOpts *opts, uint64_t bytes, bool in)
+{
+ const char *opt = in ? "in.format" : "out.format";
+ const char *val = qemu_opt_get(opts, opt);
+ uint64_t bytes_per_sample = (val ? toull(val) : 16) / 8;
+ return samples_to_usecs(opts, bytes * bytes_per_sample, in);
+}
+
+static const char *transform_val(QemuOpts *opts, const char *val,
+ EnvTransform transform)
+{
+ switch (transform) {
+ case ENV_TRANSFORM_NONE:
+ return val;
+
+ case ENV_TRANSFORM_BOOL:
+ return toull(val) ? "on" : "off";
+
+ case ENV_TRANSFORM_FMT:
+ if (strcasecmp(val, "u8") == 0) {
+ return "u8";
+ } else if (strcasecmp(val, "u16") == 0) {
+ return "u16";
+ } else if (strcasecmp(val, "u32") == 0) {
+ return "u32";
+ } else if (strcasecmp(val, "s8") == 0) {
+ return "s8";
+ } else if (strcasecmp(val, "s16") == 0) {
+ return "s16";
+ } else if (strcasecmp(val, "s32") == 0) {
+ return "s32";
+ } else {
+ dolog("Invalid audio format `%s'\n", val);
+ exit(1);
+ }
+
+ case ENV_TRANSFORM_FRAMES_TO_USECS_IN:
+ return tostr(frames_to_usecs(opts, toull(val), true));
+ case ENV_TRANSFORM_FRAMES_TO_USECS_OUT:
+ return tostr(frames_to_usecs(opts, toull(val), false));
+
+ case ENV_TRANSFORM_SAMPLES_TO_USECS_IN:
+ return tostr(samples_to_usecs(opts, toull(val), true));
+ case ENV_TRANSFORM_SAMPLES_TO_USECS_OUT:
+ return tostr(samples_to_usecs(opts, toull(val), false));
+
+ case ENV_TRANSFORM_BYTES_TO_USECS_IN:
+ return tostr(bytes_to_usecs(opts, toull(val), true));
+ case ENV_TRANSFORM_BYTES_TO_USECS_OUT:
+ return tostr(bytes_to_usecs(opts, toull(val), false));
+
+ case ENV_TRANSFORM_MILLIS_TO_USECS:
+ return tostr(toull(val) * 1000);
+
+ case ENV_TRANSFORM_HZ_TO_USECS:
+ return tostr(1000000 / toull(val));
+ }
+
+ abort(); /* it's unreachable, gcc */
+}
+
+static void handle_env_opts(QemuOpts *opts, SimpleEnvMap *map)
+{
+ while (map->name) {
+ const char *val = getenv(map->name);
+
+ if (val) {
+ qemu_opt_set(opts, map->option,
+ transform_val(opts, val, map->transform),
+ &error_abort);
+ }
+
+ ++map;
+ }
+}
+
+static void legacy_opt(const char *drv)
+{
+ QemuOpts *opts;
+ opts = qemu_opts_create(qemu_find_opts("audiodev"), drv, true,
+ &error_abort);
+ qemu_opt_set(opts, "driver", drv, &error_abort);
+
+ handle_env_opts(opts, global_map);
+}
+
+void audio_handle_legacy_opts(void)
+{
+ const char *drv = getenv("QEMU_AUDIO_DRV");
+
+ if (drv) {
+ legacy_opt(drv);
+ } else {
+ struct audio_driver **drv;
+ for (drv = drvtab; *drv; ++drv) {
+ if ((*drv)->can_be_default) {
+ legacy_opt((*drv)->name);
+ }
+ }
+ }
+}
+
+static int legacy_help_each(void *opaque, QemuOpts *opts, Error **errp)
+{
+ printf("-audiodev ");
+ qemu_opts_print(opts, ",");
+ printf("\n");
+ return 0;
+}
+
+void audio_legacy_help(void)
+{
+ printf("Environment variable based configuration deprecated.\n");
+ printf("Please use the new -audiodev option.\n");
+
+ audio_handle_legacy_opts();
+ printf("\nEquivalent -audiodev to your current environment variables:\n");
+ qemu_opts_foreach(qemu_find_opts("audiodev"), legacy_help_each, NULL, NULL);
+}
@@ -302,8 +302,10 @@ static HW *glue (audio_pcm_hw_add_new_, TYPE) (struct audsettings *as)
static HW *glue (audio_pcm_hw_add_, TYPE) (struct audsettings *as)
{
HW *hw;
+ AudioState *s = &glob_audio_state;
+ AudiodevPerDirectionOptions *pdo = s->dev->TYPE;
- if (glue (conf.fixed_, TYPE).enabled && glue (conf.fixed_, TYPE).greedy) {
+ if (pdo->fixed_settings) {
hw = glue (audio_pcm_hw_add_new_, TYPE) (as);
if (hw) {
return hw;
@@ -331,9 +333,11 @@ static SW *glue (audio_pcm_create_voice_pair_, TYPE) (
SW *sw;
HW *hw;
struct audsettings hw_as;
+ AudioState *s = &glob_audio_state;
+ AudiodevPerDirectionOptions *pdo = s->dev->TYPE;
- if (glue (conf.fixed_, TYPE).enabled) {
- hw_as = glue (conf.fixed_, TYPE).settings;
+ if (pdo->fixed_settings) {
+ hw_as = audiodev_to_audsettings(pdo);
}
else {
hw_as = *as;
@@ -398,6 +402,7 @@ SW *glue (AUD_open_, TYPE) (
)
{
AudioState *s = &glob_audio_state;
+ AudiodevPerDirectionOptions *pdo = s->dev->TYPE;
if (audio_bug (AUDIO_FUNC, !card || !name || !callback_fn || !as)) {
dolog ("card=%p name=%p callback_fn=%p as=%p\n",
@@ -422,7 +427,7 @@ SW *glue (AUD_open_, TYPE) (
return sw;
}
- if (!glue (conf.fixed_, TYPE).enabled && sw) {
+ if (!pdo->fixed_settings && sw) {
glue (AUD_close_, TYPE) (card, sw);
sw = NULL;
}
@@ -692,7 +692,7 @@ static CoreaudioConf glob_conf = {
.nbuffers = 4,
};
-static void *coreaudio_audio_init (void)
+static void *coreaudio_audio_init(Audiodev *dev)
{
CoreaudioConf *conf = g_malloc(sizeof(CoreaudioConf));
*conf = glob_conf;
@@ -782,7 +782,7 @@ static void dsound_audio_fini (void *opaque)
g_free(s);
}
-static void *dsound_audio_init (void)
+static void *dsound_audio_init(Audiodev *dev)
{
int err;
HRESULT hr;
@@ -134,7 +134,7 @@ static int no_ctl_in (HWVoiceIn *hw, int cmd, ...)
return 0;
}
-static void *no_audio_init (void)
+static void *no_audio_init (Audiodev *dev)
{
return &no_audio_init;
}
@@ -846,7 +846,7 @@ static OSSConf glob_conf = {
.policy = 5
};
-static void *oss_audio_init (void)
+static void *oss_audio_init(Audiodev *dev)
{
OSSConf *conf = g_malloc(sizeof(OSSConf));
*conf = glob_conf;
@@ -812,7 +812,7 @@ static PAConf glob_conf = {
.samples = 4096,
};
-static void *qpa_audio_init (void)
+static void *qpa_audio_init(Audiodev *dev)
{
paaudio *g = g_malloc(sizeof(paaudio));
g->conf = glob_conf;
@@ -391,7 +391,7 @@ static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...)
return 0;
}
-static void *sdl_audio_init (void)
+static void *sdl_audio_init(Audiodev *dev)
{
SDLAudioState *s = &glob_sdl;
if (s->driver_created) {
@@ -75,7 +75,7 @@ static const SpiceRecordInterface record_sif = {
.base.minor_version = SPICE_INTERFACE_RECORD_MINOR,
};
-static void *spice_audio_init (void)
+static void *spice_audio_init(Audiodev *dev)
{
if (!using_spice) {
return NULL;
@@ -231,7 +231,7 @@ static WAVConf glob_conf = {
.wav_path = "qemu.wav"
};
-static void *wav_audio_init (void)
+static void *wav_audio_init(Audiodev *dev)
{
WAVConf *conf = g_malloc(sizeof(WAVConf));
*conf = glob_conf;
@@ -3025,6 +3025,7 @@ int main(int argc, char **argv, char **envp)
qemu_add_opts(&qemu_trace_opts);
qemu_add_opts(&qemu_option_rom_opts);
qemu_add_opts(&qemu_machine_opts);
+ qemu_add_opts(&qemu_audiodev_opts);
qemu_add_opts(&qemu_mem_opts);
qemu_add_opts(&qemu_smp_opts);
qemu_add_opts(&qemu_boot_opts);
@@ -3328,9 +3329,15 @@ int main(int argc, char **argv, char **envp)
add_device_config(DEV_BT, optarg);
break;
case QEMU_OPTION_audio_help:
- AUD_help ();
+ audio_legacy_help();
exit (0);
break;
+ case QEMU_OPTION_audiodev:
+ if (!qemu_opts_parse_noisily(qemu_find_opts("audiodev"),
+ optarg, true)) {
+ exit(1);
+ }
+ break;
case QEMU_OPTION_soundhw:
select_soundhw (optarg);
break;
@@ -4533,6 +4540,7 @@ int main(int argc, char **argv, char **envp)
realtime_init();
+ audio_set_options();
audio_init();
cpu_synchronize_all_post_init();