From patchwork Fri Aug 21 15:37:08 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?UTF-8?B?Wm9sdMOhbiBLxZF2w6Fnw7M=?= X-Patchwork-Id: 509544 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 675AE140187 for ; Sat, 22 Aug 2015 01:52:43 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=z1UllGc4; dkim-atps=neutral Received: from localhost ([::1]:42256 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZSocT-0007R9-It for incoming@patchwork.ozlabs.org; Fri, 21 Aug 2015 11:52:41 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50719) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZSoOC-0002N8-8s for qemu-devel@nongnu.org; Fri, 21 Aug 2015 11:37:59 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ZSoO8-00022j-6e for qemu-devel@nongnu.org; Fri, 21 Aug 2015 11:37:56 -0400 Received: from mail-wi0-x230.google.com ([2a00:1450:400c:c05::230]:34867) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZSoO7-00022X-O2 for qemu-devel@nongnu.org; Fri, 21 Aug 2015 11:37:52 -0400 Received: by wicne3 with SMTP id ne3so19119875wic.0 for ; Fri, 21 Aug 2015 08:37:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=p7mDc9IEz9awTKR763Fw7ozP/jjk/0JDrcW2795gMFM=; b=z1UllGc4NpFJ5xypz+st2H5aFjTdVb+X0Az+R0X0DC2P9Szk8kgb7am+79fJUvnJS1 cv5cdxftpt0pb33VnitIxoTlAEdpWiSfVLPOUcyH0O88LMb1eXp1jscE46QfA9Eu5Bow Fz/WEnPg959xl4WZ+XEEvOxQfH6OkjrFho6xSDyz4lVfG8mXk+2PEVuorqxxVbaO38Iq EWgdR9O4roTpZpQ0AIvV8N2IBsY5/bLnJNW37w+jS1vXeZ95nU+om3v9CD2eCj2tsRk+ gLqQ+FQUaWGQsJSzbGkh1IjsgDvaD2Gf7lTuSkLq1kYlxfgiGVHy7nqa796L64dRDmrq DPaw== X-Received: by 10.180.87.71 with SMTP id v7mr7251412wiz.74.1440171471165; Fri, 21 Aug 2015 08:37:51 -0700 (PDT) Received: from nullptr.home.dirty-ice.org (178-164-172-216.pool.digikabel.hu. [178.164.172.216]) by smtp.gmail.com with ESMTPSA id v8sm10479895wjr.15.2015.08.21.08.37.50 (version=TLSv1/SSLv3 cipher=OTHER); Fri, 21 Aug 2015 08:37:50 -0700 (PDT) From: "=?UTF-8?q?K=C5=91v=C3=A1g=C3=B3=2C=20Zolt=C3=A1n?=" X-Google-Original-From: =?UTF-8?q?K=C5=91v=C3=A1g=C3=B3=2C=20Zolt=C3=A1n?= To: qemu-devel@nongnu.org Date: Fri, 21 Aug 2015 17:37:08 +0200 Message-Id: <50f395a7843521b14879b140dbb4182ceae3c596.1440171025.git.DirtY.iCE.hu@gmail.com> X-Mailer: git-send-email 2.5.0 In-Reply-To: References: X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-Received-From: 2a00:1450:400c:c05::230 Cc: Paolo Bonzini , Gerd Hoffmann Subject: [Qemu-devel] [PATCH v2 12/49] audio: -audiodev command line option basic implementation X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Audio drivers now get an Audiodev * as config paramters, instead of the global audio_option structs. There is some code in audio/audio_legacy.c that converts the old environment variables to audiodev options (this way backends do not have to worry about legacy options). It also contains a replacement of -audio-help, which prints out the equivalent -audiodev based config of the currently specified environment variables. Note that backends are not updated and still rely on environment variables. Also note that (due to moving try-poll from global to backend specific option) currently ALSA and OSS will always try poll mode, regardless of environment variables or -audiodev options. --- audio/Makefile.objs | 2 +- audio/alsaaudio.c | 2 +- audio/audio.c | 566 +++++++++++++++++++------------------------------ audio/audio.h | 23 +- audio/audio_int.h | 6 +- audio/audio_legacy.c | 202 ++++++++++++++++++ audio/audio_template.h | 13 +- audio/coreaudio.c | 2 +- audio/dsoundaudio.c | 2 +- audio/noaudio.c | 2 +- audio/ossaudio.c | 2 +- audio/paaudio.c | 2 +- audio/sdlaudio.c | 2 +- audio/spiceaudio.c | 2 +- audio/wavaudio.c | 2 +- vl.c | 10 +- 16 files changed, 472 insertions(+), 368 deletions(-) create mode 100644 audio/audio_legacy.c diff --git a/audio/Makefile.objs b/audio/Makefile.objs index 481d1aa..9d8f579 100644 --- a/audio/Makefile.objs +++ b/audio/Makefile.objs @@ -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 diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c index 2b28b99..e545766 100644 --- a/audio/alsaaudio.c +++ b/audio/alsaaudio.c @@ -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; diff --git a/audio/audio.c b/audio/audio.c index 334c935..76643ed 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -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->kind]; + } 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); + 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); +} diff --git a/audio/audio.h b/audio/audio.h index e300511..177a673 100644 --- a/audio/audio.h +++ b/audio/audio.h @@ -24,7 +24,10 @@ #ifndef QEMU_AUDIO_H #define QEMU_AUDIO_H +#include #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 */ diff --git a/audio/audio_int.h b/audio/audio_int.h index 566df5e..9139f42 100644 --- a/audio/audio_int.h +++ b/audio/audio_int.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); diff --git a/audio/audio_legacy.c b/audio/audio_legacy.c new file mode 100644 index 0000000..8aa0748 --- /dev/null +++ b/audio/audio_legacy.c @@ -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); +} diff --git a/audio/audio_template.h b/audio/audio_template.h index 99b27b2..096b2b3 100644 --- a/audio/audio_template.h +++ b/audio/audio_template.h @@ -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; } diff --git a/audio/coreaudio.c b/audio/coreaudio.c index 6dfd63e..3612511 100644 --- a/audio/coreaudio.c +++ b/audio/coreaudio.c @@ -502,7 +502,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; diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c index e9472c1..d447e12 100644 --- a/audio/dsoundaudio.c +++ b/audio/dsoundaudio.c @@ -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; diff --git a/audio/noaudio.c b/audio/noaudio.c index 50db1f3..108b02d 100644 --- a/audio/noaudio.c +++ b/audio/noaudio.c @@ -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; } diff --git a/audio/ossaudio.c b/audio/ossaudio.c index 3ea7d27..6d64cf2 100644 --- a/audio/ossaudio.c +++ b/audio/ossaudio.c @@ -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; diff --git a/audio/paaudio.c b/audio/paaudio.c index cfdbdc6..dd46fa0 100644 --- a/audio/paaudio.c +++ b/audio/paaudio.c @@ -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; diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c index db0f95a..580c613 100644 --- a/audio/sdlaudio.c +++ b/audio/sdlaudio.c @@ -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) { diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c index 141fd8d..e6da29a 100644 --- a/audio/spiceaudio.c +++ b/audio/spiceaudio.c @@ -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; diff --git a/audio/wavaudio.c b/audio/wavaudio.c index 81250e6..8b0ba91 100644 --- a/audio/wavaudio.c +++ b/audio/wavaudio.c @@ -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; diff --git a/vl.c b/vl.c index 584ca88..2f153a3 100644 --- a/vl.c +++ b/vl.c @@ -3004,6 +3004,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); @@ -3309,9 +3310,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; @@ -4511,6 +4518,7 @@ int main(int argc, char **argv, char **envp) realtime_init(); + audio_set_options(); audio_init(); cpu_synchronize_all_post_init();