From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:55148) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z5cvK-000825-VL for qemu-devel@nongnu.org; Thu, 18 Jun 2015 12:44:28 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Z5cv6-0000vI-FA for qemu-devel@nongnu.org; Thu, 18 Jun 2015 12:44:18 -0400 Received: from mail-wi0-x232.google.com ([2a00:1450:400c:c05::232]:37613) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z5cv5-0000uk-NM for qemu-devel@nongnu.org; Thu, 18 Jun 2015 12:44:04 -0400 Received: by wicgi11 with SMTP id gi11so19545929wic.0 for ; Thu, 18 Jun 2015 09:44:03 -0700 (PDT) From: "=?UTF-8?q?K=C5=91v=C3=A1g=C3=B3=2C=20Zolt=C3=A1n?=" Date: Thu, 18 Jun 2015 18:43:50 +0200 Message-Id: <9091fb3e2046f4d23c8209d89883a902c5079332.1434644665.git.DirtY.iCE.hu@gmail.com> In-Reply-To: References: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Subject: [Qemu-devel] [PATCH v3 8/8] audio: -audiodev command line option List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Gerd Hoffmann This patch adds an -audiodev command line option, and deprecates the QEMU_* environment variables for audio backend configuration. It's syntax is similar to existing options (-netdev, -device, etc): -audiodev driver_name,property=value,... 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. Although now it's possible to specify multiple -audiodev options on command line, multiple audio backends are not supported yet. Signed-off-by: Kővágó, Zoltán --- Changes from v2: * fixed alsa threshold * updated to audio qapi changes Changes from v1: * updated to everything usecs without suffix * better rounding when converting between usecs and frames/samples/bytes * bugfixes in audio_legacy.c * fixed code broken by qemu_opts changes by Markus audio/Makefile.objs | 2 +- audio/alsaaudio.c | 311 ++++++-------------- audio/audio.c | 741 +++++++++++++----------------------------------- audio/audio.h | 23 +- audio/audio_int.h | 7 +- audio/audio_legacy.c | 328 +++++++++++++++++++++ audio/audio_template.h | 13 +- audio/coreaudio.c | 49 +--- audio/dsound_template.h | 6 +- audio/dsoundaudio.c | 60 ++-- audio/noaudio.c | 3 +- audio/ossaudio.c | 154 +++------- audio/paaudio.c | 80 ++---- audio/sdlaudio.c | 24 +- audio/spiceaudio.c | 7 +- audio/wavaudio.c | 60 +--- qemu-options.hx | 230 ++++++++++++++- vl.c | 9 +- 18 files changed, 997 insertions(+), 1110 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..cfe4aec 100644 --- a/audio/alsaaudio.c +++ b/audio/alsaaudio.c @@ -22,6 +22,7 @@ * THE SOFTWARE. */ #include +#include "qapi-visit.h" #include "qemu-common.h" #include "qemu/main-loop.h" #include "audio.h" @@ -34,28 +35,9 @@ #define AUDIO_CAP "alsa" #include "audio_int.h" -typedef struct ALSAConf { - int size_in_usec_in; - int size_in_usec_out; - const char *pcm_name_in; - const char *pcm_name_out; - unsigned int buffer_size_in; - unsigned int period_size_in; - unsigned int buffer_size_out; - unsigned int period_size_out; - unsigned int threshold; - - int buffer_size_in_overridden; - int period_size_in_overridden; - - int buffer_size_out_overridden; - int period_size_out_overridden; -} ALSAConf; - struct pollhlp { snd_pcm_t *handle; struct pollfd *pfds; - ALSAConf *conf; int count; int mask; }; @@ -67,6 +49,7 @@ typedef struct ALSAVoiceOut { void *pcm_buf; snd_pcm_t *handle; struct pollhlp pollhlp; + Audiodev *dev; } ALSAVoiceOut; typedef struct ALSAVoiceIn { @@ -74,16 +57,13 @@ typedef struct ALSAVoiceIn { snd_pcm_t *handle; void *pcm_buf; struct pollhlp pollhlp; + Audiodev *dev; } ALSAVoiceIn; struct alsa_params_req { int freq; snd_pcm_format_t fmt; int nchannels; - int size_in_usec; - int override_mask; - unsigned int buffer_size; - unsigned int period_size; }; struct alsa_params_obt { @@ -409,7 +389,8 @@ static int alsa_to_audfmt (snd_pcm_format_t alsafmt, AudioFormat *fmt, static void alsa_dump_info (struct alsa_params_req *req, struct alsa_params_obt *obt, - snd_pcm_format_t obtfmt) + snd_pcm_format_t obtfmt, + AudiodevPerDirectionOptions *pdo) { dolog ("parameter | requested value | obtained value\n"); dolog ("format | %10d | %10d\n", req->fmt, obtfmt); @@ -417,8 +398,9 @@ static void alsa_dump_info (struct alsa_params_req *req, req->nchannels, obt->nchannels); dolog ("frequency | %10d | %10d\n", req->freq, obt->freq); dolog ("============================================\n"); - dolog ("requested: buffer size %d period size %d\n", - req->buffer_size, req->period_size); + dolog ("requested: buffer size %" PRId64 " buffer count %" PRId64 "\n", + pdo->has_buffer_len ? pdo->buffer_len : 0, + pdo->has_buffer_len ? pdo->buffer_len : 0); dolog ("obtained: samples %ld\n", obt->samples); } @@ -452,23 +434,25 @@ static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) } } -static int alsa_open (int in, struct alsa_params_req *req, - struct alsa_params_obt *obt, snd_pcm_t **handlep, - ALSAConf *conf) +static int alsa_open(bool in, struct alsa_params_req *req, + struct alsa_params_obt *obt, snd_pcm_t **handlep, + Audiodev *dev) { + AudiodevPerDirectionOptions *pdo = in ? dev->in : dev->out; + AudiodevAlsaOptions *aopts = dev->alsa; + AudiodevAlsaPerDirectionOptions *apdo = + in ? aopts->alsa_in : aopts->alsa_out; snd_pcm_t *handle; snd_pcm_hw_params_t *hw_params; int err; - int size_in_usec; unsigned int freq, nchannels; - const char *pcm_name = in ? conf->pcm_name_in : conf->pcm_name_out; + const char *pcm_name = apdo->has_dev ? apdo->dev : "default"; snd_pcm_uframes_t obt_buffer_size; const char *typ = in ? "ADC" : "DAC"; snd_pcm_format_t obtfmt; freq = req->freq; nchannels = req->nchannels; - size_in_usec = req->size_in_usec; snd_pcm_hw_params_alloca (&hw_params); @@ -528,79 +512,49 @@ static int alsa_open (int in, struct alsa_params_req *req, goto err; } - if (req->buffer_size) { - unsigned long obt; + if (pdo->buffer_count) { + if (pdo->buffer_len) { + int64_t req = pdo->buffer_len * pdo->buffer_count; - if (size_in_usec) { int dir = 0; - unsigned int btime = req->buffer_size; + unsigned int btime = req; - err = snd_pcm_hw_params_set_buffer_time_near ( - handle, - hw_params, - &btime, - &dir - ); - obt = btime; - } - else { - snd_pcm_uframes_t bsize = req->buffer_size; + err = snd_pcm_hw_params_set_buffer_time_near( + handle, hw_params, &btime, &dir); - err = snd_pcm_hw_params_set_buffer_size_near ( - handle, - hw_params, - &bsize - ); - obt = bsize; - } - if (err < 0) { - alsa_logerr2 (err, typ, "Failed to set buffer %s to %d\n", - size_in_usec ? "time" : "size", req->buffer_size); - goto err; - } + if (err < 0) { + alsa_logerr2(err, typ, + "Failed to set buffer time to %" PRId64 "\n", + req); + goto err; + } - if ((req->override_mask & 2) && (obt - req->buffer_size)) - dolog ("Requested buffer %s %u was rejected, using %lu\n", - size_in_usec ? "time" : "size", req->buffer_size, obt); + if (pdo->has_buffer_count && btime != req) { + dolog("Requested buffer time %" PRId64 + " was rejected, using %u\n", req, btime); + } + } else { + dolog("Can't set buffer_count without buffer_size!\n"); + } } - if (req->period_size) { - unsigned long obt; + if (pdo->buffer_len) { + int dir = 0; + unsigned int ptime = pdo->buffer_len; - if (size_in_usec) { - int dir = 0; - unsigned int ptime = req->period_size; - - err = snd_pcm_hw_params_set_period_time_near ( - handle, - hw_params, - &ptime, - &dir - ); - obt = ptime; - } - else { - int dir = 0; - snd_pcm_uframes_t psize = req->period_size; - - err = snd_pcm_hw_params_set_period_size_near ( - handle, - hw_params, - &psize, - &dir - ); - obt = psize; - } + err = snd_pcm_hw_params_set_period_time_near(handle, hw_params, &ptime, + &dir); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to set period %s to %d\n", - size_in_usec ? "time" : "size", req->period_size); + alsa_logerr2(err, typ, "Failed to set period time to %" PRId64 "\n", + pdo->buffer_len); goto err; } - if (((req->override_mask & 1) && (obt - req->period_size))) - dolog ("Requested period %s %u was rejected, using %lu\n", - size_in_usec ? "time" : "size", req->period_size, obt); + if (pdo->has_buffer_len && ptime != pdo->buffer_len) { + dolog("Requested period time %" PRId64 " was rejected, using %d\n", + pdo->buffer_len, ptime); + } } err = snd_pcm_hw_params (handle, hw_params); @@ -632,33 +586,10 @@ static int alsa_open (int in, struct alsa_params_req *req, goto err; } - if (!in && conf->threshold) { - snd_pcm_uframes_t threshold; - int bytes_per_sec; - - bytes_per_sec = freq << (nchannels == 2); - - switch (obt->fmt) { - case AUDIO_FORMAT_S8: - case AUDIO_FORMAT_U8: - break; - - case AUDIO_FORMAT_S16: - case AUDIO_FORMAT_U16: - bytes_per_sec <<= 1; - break; - - case AUDIO_FORMAT_S32: - case AUDIO_FORMAT_U32: - bytes_per_sec <<= 2; - break; - - default: - abort(); - } - - threshold = (conf->threshold * bytes_per_sec) / 1000; - alsa_set_threshold (handle, threshold); + if (!in && aopts->has_threshold && aopts->threshold) { + struct audsettings as = { .freq = freq }; + alsa_set_threshold(handle, + audio_buffer_frames(pdo, &as, aopts->threshold)); } obt->nchannels = nchannels; @@ -671,11 +602,11 @@ static int alsa_open (int in, struct alsa_params_req *req, obt->nchannels != req->nchannels || obt->freq != req->freq) { dolog ("Audio parameters for %s\n", typ); - alsa_dump_info (req, obt, obtfmt); + alsa_dump_info (req, obt, obtfmt, pdo); } #ifdef DEBUG - alsa_dump_info (req, obt, obtfmt); + alsa_dump_info (req, obt, obtfmt, pdo); #endif return 0; @@ -801,19 +732,13 @@ static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as, struct alsa_params_obt obt; snd_pcm_t *handle; struct audsettings obt_as; - ALSAConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; req.fmt = aud_to_alsafmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; - req.period_size = conf->period_size_out; - req.buffer_size = conf->buffer_size_out; - req.size_in_usec = conf->size_in_usec_out; - req.override_mask = - (conf->period_size_out_overridden ? 1 : 0) | - (conf->buffer_size_out_overridden ? 2 : 0); - if (alsa_open (0, &req, &obt, &handle, conf)) { + if (alsa_open (0, &req, &obt, &handle, dev)) { return -1; } @@ -834,7 +759,7 @@ static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as, } alsa->handle = handle; - alsa->pollhlp.conf = conf; + alsa->dev = dev; return 0; } @@ -874,16 +799,12 @@ static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int ctl) static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...) { ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->alsa->alsa_out; switch (cmd) { case VOICE_ENABLE: { - va_list ap; - int poll_mode; - - va_start (ap, cmd); - poll_mode = va_arg (ap, int); - va_end (ap); + bool poll_mode = !apdo->has_try_poll || apdo->try_poll; ldebug ("enabling voice\n"); if (poll_mode && alsa_poll_out (hw)) { @@ -912,19 +833,13 @@ static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) struct alsa_params_obt obt; snd_pcm_t *handle; struct audsettings obt_as; - ALSAConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; req.fmt = aud_to_alsafmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; - req.period_size = conf->period_size_in; - req.buffer_size = conf->buffer_size_in; - req.size_in_usec = conf->size_in_usec_in; - req.override_mask = - (conf->period_size_in_overridden ? 1 : 0) | - (conf->buffer_size_in_overridden ? 2 : 0); - if (alsa_open (1, &req, &obt, &handle, conf)) { + if (alsa_open (1, &req, &obt, &handle, dev)) { return -1; } @@ -945,7 +860,7 @@ static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) } alsa->handle = handle; - alsa->pollhlp.conf = conf; + alsa->dev = dev; return 0; } @@ -1087,16 +1002,12 @@ static int alsa_read (SWVoiceIn *sw, void *buf, int size) static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...) { ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->alsa->alsa_in; switch (cmd) { case VOICE_ENABLE: { - va_list ap; - int poll_mode; - - va_start (ap, cmd); - poll_mode = va_arg (ap, int); - va_end (ap); + bool poll_mode = !apdo->has_try_poll || apdo->try_poll; ldebug ("enabling voice\n"); if (poll_mode && alsa_poll_in (hw)) { @@ -1119,88 +1030,35 @@ static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...) return -1; } -static ALSAConf glob_conf = { - .buffer_size_out = 4096, - .period_size_out = 1024, - .pcm_name_out = "default", - .pcm_name_in = "default", -}; - -static void *alsa_audio_init (void) +static void *alsa_audio_init(Audiodev *dev) { - ALSAConf *conf = g_malloc(sizeof(ALSAConf)); - *conf = glob_conf; - return conf; + assert(dev->kind == AUDIODEV_DRIVER_ALSA); + + /* need to define them, as otherwise alsa produces no sound + * doesn't set has_* so alsa_open can identify it wasn't set by the user */ + if (!dev->out->has_buffer_count) { + dev->out->buffer_count = 4; + } + if (!dev->out->has_buffer_len) { + dev->out->buffer_len = 23219; /* 1024 frames assuming 44100Hz */ + } + + /* OptsVisitor sets unspecified optional fields to zero, but do not depend + * on it... */ + if (!dev->in->has_buffer_count) { + dev->in->buffer_count = 0; + } + if (!dev->in->has_buffer_len) { + dev->in->buffer_len = 0; + } + + return dev; } static void alsa_audio_fini (void *opaque) { - g_free(opaque); } -static struct audio_option alsa_options[] = { - { - .name = "DAC_SIZE_IN_USEC", - .tag = AUD_OPT_BOOL, - .valp = &glob_conf.size_in_usec_out, - .descr = "DAC period/buffer size in microseconds (otherwise in frames)" - }, - { - .name = "DAC_PERIOD_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.period_size_out, - .descr = "DAC period size (0 to go with system default)", - .overriddenp = &glob_conf.period_size_out_overridden - }, - { - .name = "DAC_BUFFER_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.buffer_size_out, - .descr = "DAC buffer size (0 to go with system default)", - .overriddenp = &glob_conf.buffer_size_out_overridden - }, - { - .name = "ADC_SIZE_IN_USEC", - .tag = AUD_OPT_BOOL, - .valp = &glob_conf.size_in_usec_in, - .descr = - "ADC period/buffer size in microseconds (otherwise in frames)" - }, - { - .name = "ADC_PERIOD_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.period_size_in, - .descr = "ADC period size (0 to go with system default)", - .overriddenp = &glob_conf.period_size_in_overridden - }, - { - .name = "ADC_BUFFER_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.buffer_size_in, - .descr = "ADC buffer size (0 to go with system default)", - .overriddenp = &glob_conf.buffer_size_in_overridden - }, - { - .name = "THRESHOLD", - .tag = AUD_OPT_INT, - .valp = &glob_conf.threshold, - .descr = "(undocumented)" - }, - { - .name = "DAC_DEV", - .tag = AUD_OPT_STR, - .valp = &glob_conf.pcm_name_out, - .descr = "DAC device name (for instance dmix)" - }, - { - .name = "ADC_DEV", - .tag = AUD_OPT_STR, - .valp = &glob_conf.pcm_name_in, - .descr = "ADC device name" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops alsa_pcm_ops = { .init_out = alsa_init_out, .fini_out = alsa_fini_out, @@ -1218,7 +1076,6 @@ static struct audio_pcm_ops alsa_pcm_ops = { struct audio_driver alsa_audio_driver = { .name = "alsa", .descr = "ALSA http://www.alsa-project.org", - .options = alsa_options, .init = alsa_audio_init, .fini = alsa_audio_fini, .pcm_ops = &alsa_pcm_ops, diff --git a/audio/audio.c b/audio/audio.c index 334c935..76d4b8f 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,139 +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) { - case AUDIO_FORMAT_U8: - return "U8"; - - case AUDIO_FORMAT_U16: - return "U16"; - - case AUDIO_FORMAT_S8: - return "S8"; - - case AUDIO_FORMAT_S16: - return "S16"; - - case AUDIO_FORMAT_U32: - return "U32"; - - case AUDIO_FORMAT_S32: - return "S32"; - - default: - abort(); - } - - dolog ("Bogus audfmt %d returning S16\n", fmt); - return "S16"; -} - -static AudioFormat audio_string_to_audfmt (const char *s, AudioFormat defval, - int *defaultp) -{ - if (!strcasecmp (s, "u8")) { - *defaultp = 0; - return AUDIO_FORMAT_U8; - } - else if (!strcasecmp (s, "u16")) { - *defaultp = 0; - return AUDIO_FORMAT_U16; - } - else if (!strcasecmp (s, "u32")) { - *defaultp = 0; - return AUDIO_FORMAT_U32; - } - else if (!strcasecmp (s, "s8")) { - *defaultp = 0; - return AUDIO_FORMAT_S8; - } - else if (!strcasecmp (s, "s16")) { - *defaultp = 0; - return AUDIO_FORMAT_S16; - } - else if (!strcasecmp (s, "s32")) { - *defaultp = 0; - return AUDIO_FORMAT_S32; - } - else { - dolog ("Bogus audio format `%s' using %s\n", - s, audio_audfmt_to_string (defval)); - *defaultp = 1; - return defval; - } -} - -static AudioFormat audio_get_conf_fmt (const char *envname, - AudioFormat defval, - int *defaultp) -{ - const char *var = getenv (envname); - if (!var) { - *defaultp = 1; - return defval; - } - return audio_string_to_audfmt (var, defval, defaultp); -} - -static int audio_get_conf_int (const char *key, int defval, int *defaultp) -{ - int val; - char *strval; - - strval = getenv (key); - if (strval) { - *defaultp = 0; - val = atoi (strval); - return val; - } - else { - *defaultp = 1; - return defval; - } -} - -static const char *audio_get_conf_str (const char *key, - const char *defval, - int *defaultp) -{ - const char *val = getenv (key); - if (!val) { - *defaultp = 1; - return defval; - } - else { - *defaultp = 0; - return val; - } -} - void AUD_vlog (const char *cap, const char *fmt, va_list ap) { if (cap) { @@ -345,161 +160,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) -{ - char *optname; - const char qemu_prefix[] = "QEMU_"; - size_t preflen, optlen; - - if (audio_bug (AUDIO_FUNC, !prefix)) { - dolog ("prefix = NULL\n"); - return; - } - - if (audio_bug (AUDIO_FUNC, !opt)) { - dolog ("opt = NULL\n"); - return; - } - - preflen = strlen (prefix); - - for (; opt->name; opt++) { - size_t len, i; - int def; - - if (!opt->valp) { - dolog ("Option value pointer for `%s' is not set\n", - opt->name); - continue; - } - - len = strlen (opt->name); - /* len of opt->name + len of prefix + size of qemu_prefix - * (includes trailing zero) + zero + underscore (on behalf of - * sizeof) */ - optlen = len + preflen + sizeof (qemu_prefix) + 1; - optname = g_malloc (optlen); - - pstrcpy (optname, optlen, qemu_prefix); - - /* copy while upper-casing, including trailing zero */ - for (i = 0; i <= preflen; ++i) { - optname[i + sizeof (qemu_prefix) - 1] = qemu_toupper(prefix[i]); - } - pstrcat (optname, optlen, "_"); - pstrcat (optname, optlen, opt->name); - - def = 1; - switch (opt->tag) { - case AUD_OPT_BOOL: - case AUD_OPT_INT: - { - int *intp = opt->valp; - *intp = audio_get_conf_int (optname, *intp, &def); - } - break; - - case AUD_OPT_FMT: - { - AudioFormat *fmtp = opt->valp; - *fmtp = audio_get_conf_fmt (optname, *fmtp, &def); - } - break; - - case AUD_OPT_STR: - { - const char **strp = opt->valp; - *strp = audio_get_conf_str (optname, *strp, &def); - } - break; - - default: - dolog ("Bad value tag for option `%s' - %d\n", - optname, opt->tag); - break; - } - - if (!opt->overriddenp) { - opt->overriddenp = &opt->overridden; - } - *opt->overriddenp = !def; - g_free (optname); - } -} - static void audio_print_settings (struct audsettings *as) { dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels); @@ -1120,7 +780,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 +856,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); audio_reset_timer (s); } } @@ -1241,7 +901,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); audio_reset_timer (s); } } @@ -1558,168 +1218,10 @@ 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) +static int audio_driver_init(AudioState *s, struct audio_driver *drv, + Audiodev *dev) { - switch (nb) { - case 0: - printf ("Does not support %s\n", typ); - break; - case 1: - printf ("One %s voice\n", typ); - break; - case INT_MAX: - printf ("Theoretically supports many %s voices\n", typ); - break; - default: - printf ("Theoretically supports up to %d %s voices\n", nb, typ); - break; - } - -} - -void AUD_help (void) -{ - size_t i; - - audio_process_options ("AUDIO", audio_options); - for (i = 0; i < ARRAY_SIZE (drvtab); i++) { - struct audio_driver *d = drvtab[i]; - if (d->options) { - audio_process_options (d->name, d->options); - } - } - - printf ("Audio options:\n"); - audio_print_options ("AUDIO", audio_options); - printf ("\n"); - - printf ("Available drivers:\n"); - - for (i = 0; i < ARRAY_SIZE (drvtab); i++) { - struct audio_driver *d = drvtab[i]; - - printf ("Name: %s\n", d->name); - printf ("Description: %s\n", d->descr); - - audio_pp_nb_voices ("playback", d->max_voices_out); - audio_pp_nb_voices ("capture", d->max_voices_in); - - if (d->options) { - printf ("Options:\n"); - audio_print_options (d->name, d->options); - } - else { - printf ("No options\n"); - } - printf ("\n"); - } - - printf ( - "Options are settable through environment variables.\n" - "Example:\n" -#ifdef _WIN32 - " set QEMU_AUDIO_DRV=wav\n" - " set QEMU_WAV_PATH=c:\\tune.wav\n" -#else - " export QEMU_AUDIO_DRV=wav\n" - " export QEMU_WAV_PATH=$HOME/tune.wav\n" - "(for csh replace export with setenv in the above)\n" -#endif - " qemu ...\n\n" - ); -} - -static int audio_driver_init (AudioState *s, struct audio_driver *drv) -{ - if (drv->options) { - audio_process_options (drv->name, drv->options); - } - s->drv_opaque = drv->init (); + s->drv_opaque = drv->init(dev); if (s->drv_opaque) { audio_init_nb_voices_out (drv); @@ -1743,11 +1245,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); } 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); } audio_reset_timer (s); } @@ -1786,6 +1288,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 +1301,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 +1342,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 +1357,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 +1370,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 +1396,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 +1416,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 +1591,137 @@ 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 set_per_direction_defaults(AudiodevPerDirectionOptions *pdo) +{ + if (!pdo->has_fixed_settings) { + pdo->has_fixed_settings = true; + pdo->fixed_settings = true; + } + 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) { + error_propagate(errp, local_err); + return NULL; + } + + set_per_direction_defaults(dev->in); + set_per_direction_defaults(dev->out); + + if (!dev->has_timer_period) { + dev->has_timer_period = true; + dev->timer_period = 10000; /* 100Hz -> 10ms */ + } + + return dev; +} + +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..c4539e7 100644 --- a/audio/audio_int.h +++ b/audio/audio_int.h @@ -143,8 +143,7 @@ struct SWVoiceIn { 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 +189,7 @@ struct SWVoiceCap { struct AudioState { struct audio_driver *drv; + Audiodev *dev; void *drv_opaque; QEMUTimer *ts; @@ -200,6 +200,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 +214,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..88f577d --- /dev/null +++ b/audio/audio_legacy.c @@ -0,0 +1,328 @@ +#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 */ } +}; + +SimpleEnvMap alsa_map[] = { + { "QEMU_AUDIO_DAC_TRY_POLL", "alsa-out.try-poll", ENV_TRANSFORM_BOOL }, + { "QEMU_AUDIO_ADC_TRY_POLL", "alsa-in.try-poll", ENV_TRANSFORM_BOOL }, + + { "QEMU_ALSA_THRESHOLD", "threshold", ENV_TRANSFORM_MILLIS_TO_USECS }, + { "QEMU_ALSA_DAC_DEV", "alsa-out.dev" }, + { "QEMU_ALSA_ADC_DEV", "alsa-in.dev" }, + + { /* End of list */ } +}; + +SimpleEnvMap coreaudio_map[] = { + { "QEMU_COREAUDIO_BUFFER_SIZE", "buffer-len", + ENV_TRANSFORM_FRAMES_TO_USECS_OUT }, + { "QEMU_COREAUDIO_BUFFER_COUNT", "buffer-count" }, + + { /* End of list */ } +}; + +SimpleEnvMap dsound_map[] = { + { "QEMU_DSOUND_LATENCY_MILLIS", "latency", ENV_TRANSFORM_MILLIS_TO_USECS }, + { "QEMU_DSOUND_BUFSIZE_OUT", "out.buffer-len", + ENV_TRANSFORM_BYTES_TO_USECS_OUT }, + { "QEMU_DSOUND_BUFSIZE_IN", "in.buffer-len", + ENV_TRANSFORM_BYTES_TO_USECS_IN }, + + { /* End of list */ } +}; + +SimpleEnvMap oss_map[] = { + { "QEMU_AUDIO_DAC_TRY_POLL", "oss-out.try-poll", ENV_TRANSFORM_BOOL }, + { "QEMU_AUDIO_ADC_TRY_POLL", "oss-in.try-poll", ENV_TRANSFORM_BOOL }, + + { "QEMU_OSS_FRAGSIZE", "buffer-len", ENV_TRANSFORM_BYTES_TO_USECS_OUT }, + { "QEMU_OSS_NFRAGS", "buffer-count" }, + { "QEMU_OSS_MMAP", "try-mmap", ENV_TRANSFORM_BOOL }, + { "QEMU_OSS_DAC_DEV", "oss-out.dev" }, + { "QEMU_OSS_ADC_DEV", "oss-in.dev" }, + { "QEMU_OSS_EXCLUSIVE", "exclusive", ENV_TRANSFORM_BOOL }, + { "QEMU_OSS_POLICY", "dsp-policy" }, + + { /* End of list */ } +}; + +SimpleEnvMap pa_map[] = { + { "QEMU_PA_SAMPLES", "buffer", ENV_TRANSFORM_SAMPLES_TO_USECS_OUT }, + { "QEMU_PA_SERVER", "server" }, + { "QEMU_PA_SINK", "sink" }, + { "QEMU_PA_SOURCE", "source" }, + + { /* End of list */ } +}; + +SimpleEnvMap sdl_map[] = { + { "QEMU_SDL_SAMPLES", "buffer-len", ENV_TRANSFORM_SAMPLES_TO_USECS_OUT }, + { /* End of list */ } +}; + +SimpleEnvMap wav_map[] = { + { "QEMU_WAV_FREQUENCY", "out.frequency" }, + { "QEMU_WAV_FORMAT", "out.format", ENV_TRANSFORM_FMT }, + { "QEMU_WAV_DAC_FIXED_CHANNELS", "out.channels" }, + { "QEMU_WAV_PATH", "path" }, + { /* End of list */ } +}; + +static unsigned long long toull(const char *str) +{ + unsigned long long ret; + if (parse_uint_full(str, &ret, 10)) { + dolog("Invalid boolean 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) +{ + #define LEN ((CHAR_BIT * sizeof(int) - 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 handle_alsa_side(QemuOpts *opts, int period, int buffer, + const char *usec_env, const char *period_env, + const char *buffer_env, const char *usec_opt, + const char *count_opt, bool in) +{ + char *usec_s, *period_s, *buffer_s; + bool usec = false; + + usec_s = getenv(usec_env); + if (usec_s) { + usec = toull(usec_s); + } + + period_s = getenv(period_env); + if (period_s) { + period = toull(period_s); + } + if (!usec) { + period = frames_to_usecs(opts, period, in); + } + if (period_s) { + qemu_opt_set(opts, usec_opt, tostr(period), &error_abort); + } + + buffer_s = getenv(buffer_env); + if (buffer_s) { + buffer = toull(buffer_s); + if (!usec) { + buffer = frames_to_usecs(opts, buffer, in); + } + printf("buffer %d period %d\n", buffer, period); + qemu_opt_set(opts, count_opt, tostr((buffer+period/2)/period), + &error_abort); + } +} + +static void handle_alsa(QemuOpts *opts) +{ + handle_alsa_side(opts, 1024, 4096, + "QEMU_ALSA_DAC_SIZE_IN_USEC", "QEMU_ALSA_DAC_PERIOD_SIZE", + "QEMU_ALSA_DAC_BUFFER_SIZE", + "out.buffer-len", "out.buffer-count", false); + handle_alsa_side(opts, 0, 0, + "QEMU_ALSA_ADC_SIZE_IN_USEC", "QEMU_ALSA_ADC_PERIOD_SIZE", + "QEMU_ALSA_ADC_BUFFER_SIZE", + "in.buffer-len", "in.buffer-count", true); +} + +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); + + if (strcmp(drv, "alsa") == 0) { + handle_env_opts(opts, alsa_map); + handle_alsa(opts); + } else if (strcmp(drv, "oss") == 0) { + handle_env_opts(opts, oss_map); + } else if (strcmp(drv, "pa") == 0) { + handle_env_opts(opts, pa_map); + } else if (strcmp(drv, "sdl") == 0) { + handle_env_opts(opts, sdl_map); + } else if (strcmp(drv, "wav") == 0) { + handle_env_opts(opts, wav_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..dfa5e79 100644 --- a/audio/coreaudio.c +++ b/audio/coreaudio.c @@ -34,11 +34,6 @@ static int isAtexit; -typedef struct { - int buffer_frames; - int nbuffers; -} CoreaudioConf; - typedef struct coreaudioVoiceOut { HWVoiceOut hw; pthread_mutex_t mutex; @@ -292,7 +287,9 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, int err; const char *typ = "playback"; AudioValueRange frameRange; - CoreaudioConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; + AudiodevPerDirectionOptions *pdo = dev->out; + int frames; /* create mutex */ err = pthread_mutex_init(&core->mutex, NULL); @@ -334,16 +331,17 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, return -1; } - if (frameRange.mMinimum > conf->buffer_frames) { + frames = audio_buffer_frames(pdo, as, 11610); + if (frameRange.mMinimum > frames) { core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); } - else if (frameRange.mMaximum < conf->buffer_frames) { + else if (frameRange.mMaximum < frames) { core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); } else { - core->audioDevicePropertyBufferFrameSize = conf->buffer_frames; + core->audioDevicePropertyBufferFrameSize = frames; } /* set Buffer Frame Size */ @@ -377,7 +375,8 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, "Could not get device buffer frame size\n"); return -1; } - hw->samples = conf->nbuffers * core->audioDevicePropertyBufferFrameSize; + hw->samples = (pdo->has_buffer_count ? pdo->buffer_count : 4) * + core->audioDevicePropertyBufferFrameSize; /* get StreamFormat */ propertySize = sizeof(core->outputStreamBasicDescription); @@ -497,41 +496,16 @@ static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...) return 0; } -static CoreaudioConf glob_conf = { - .buffer_frames = 512, - .nbuffers = 4, -}; - -static void *coreaudio_audio_init (void) +static void *coreaudio_audio_init(Audiodev *dev) { - CoreaudioConf *conf = g_malloc(sizeof(CoreaudioConf)); - *conf = glob_conf; - atexit(coreaudio_atexit); - return conf; + return dev; } static void coreaudio_audio_fini (void *opaque) { - g_free(opaque); } -static struct audio_option coreaudio_options[] = { - { - .name = "BUFFER_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.buffer_frames, - .descr = "Size of the buffer in frames" - }, - { - .name = "BUFFER_COUNT", - .tag = AUD_OPT_INT, - .valp = &glob_conf.nbuffers, - .descr = "Number of buffers" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops coreaudio_pcm_ops = { .init_out = coreaudio_init_out, .fini_out = coreaudio_fini_out, @@ -543,7 +517,6 @@ static struct audio_pcm_ops coreaudio_pcm_ops = { struct audio_driver coreaudio_audio_driver = { .name = "coreaudio", .descr = "CoreAudio http://developer.apple.com/audio/coreaudio.html", - .options = coreaudio_options, .init = coreaudio_audio_init, .fini = coreaudio_audio_fini, .pcm_ops = &coreaudio_pcm_ops, diff --git a/audio/dsound_template.h b/audio/dsound_template.h index b439f33..96181ef 100644 --- a/audio/dsound_template.h +++ b/audio/dsound_template.h @@ -167,17 +167,18 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, dsound *s = drv_opaque; WAVEFORMATEX wfx; struct audsettings obt_as; - DSoundConf *conf = &s->conf; #ifdef DSBTYPE_IN const char *typ = "ADC"; DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; DSCBUFFERDESC bd; DSCBCAPS bc; + AudiodevPerDirectionOptions *pdo = s->dev->in; #else const char *typ = "DAC"; DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; DSBUFFERDESC bd; DSBCAPS bc; + AudiodevPerDirectionOptions *pdo = s->dev->out; #endif if (!s->FIELD2) { @@ -193,8 +194,8 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, memset (&bd, 0, sizeof (bd)); bd.dwSize = sizeof (bd); bd.lpwfxFormat = &wfx; + bd.dwBufferBytes = audio_buffer_bytes(pdo, as, 92880); #ifdef DSBTYPE_IN - bd.dwBufferBytes = conf->bufsize_in; hr = IDirectSoundCapture_CreateCaptureBuffer ( s->dsound_capture, &bd, @@ -203,7 +204,6 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, ); #else bd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2; - bd.dwBufferBytes = conf->bufsize_out; hr = IDirectSound_CreateSoundBuffer ( s->dsound, &bd, diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c index e9472c1..7e7b3f2 100644 --- a/audio/dsoundaudio.c +++ b/audio/dsoundaudio.c @@ -42,16 +42,10 @@ /* #define DEBUG_DSOUND */ typedef struct { - int bufsize_in; - int bufsize_out; - int latency_millis; -} DSoundConf; - -typedef struct { LPDIRECTSOUND dsound; LPDIRECTSOUNDCAPTURE dsound_capture; struct audsettings settings; - DSoundConf conf; + Audiodev *dev; } dsound; typedef struct { @@ -247,9 +241,9 @@ static void GCC_FMT_ATTR (3, 4) dsound_logerr2 ( dsound_log_hresult (hr); } -static DWORD millis_to_bytes (struct audio_pcm_info *info, DWORD millis) +static uint64_t usecs_to_bytes(struct audio_pcm_info *info, uint32_t usecs) { - return (millis * info->bytes_per_second) / 1000; + return muldiv64(usecs, info->bytes_per_second, 1000000); } #ifdef DEBUG_DSOUND @@ -477,7 +471,7 @@ static int dsound_run_out (HWVoiceOut *hw, int live) LPVOID p1, p2; int bufsize; dsound *s = ds->s; - DSoundConf *conf = &s->conf; + AudiodevDsoundOptions *dso = s->dev->dsound; if (!dsb) { dolog ("Attempt to run empty with playback buffer\n"); @@ -500,14 +494,14 @@ static int dsound_run_out (HWVoiceOut *hw, int live) len = live << hwshift; if (ds->first_time) { - if (conf->latency_millis) { + if (dso->latency) { DWORD cur_blat; cur_blat = audio_ring_dist (wpos, ppos, bufsize); ds->first_time = 0; old_pos = wpos; old_pos += - millis_to_bytes (&hw->info, conf->latency_millis) - cur_blat; + usecs_to_bytes(&hw->info, dso->latency) - cur_blat; old_pos %= bufsize; old_pos &= ~hw->info.align; } @@ -746,12 +740,6 @@ static int dsound_run_in (HWVoiceIn *hw) return decr; } -static DSoundConf glob_conf = { - .bufsize_in = 16384, - .bufsize_out = 16384, - .latency_millis = 10 -}; - static void dsound_audio_fini (void *opaque) { HRESULT hr; @@ -782,13 +770,22 @@ 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; dsound *s = g_malloc0(sizeof(dsound)); + AudiodevDsoundOptions *dso; + + assert(dev->kind == AUDIODEV_DRIVER_DSOUND); + s->dev = dev; + dso = dev->dsound; + + if (!dso->has_latency) { + dso->has_latency = true; + dso->latency = 10000; /* 10 ms */ + } - s->conf = glob_conf; hr = CoInitialize (NULL); if (FAILED (hr)) { dsound_logerr (hr, "Could not initialize COM\n"); @@ -853,28 +850,6 @@ static void *dsound_audio_init (void) return s; } -static struct audio_option dsound_options[] = { - { - .name = "LATENCY_MILLIS", - .tag = AUD_OPT_INT, - .valp = &glob_conf.latency_millis, - .descr = "(undocumented)" - }, - { - .name = "BUFSIZE_OUT", - .tag = AUD_OPT_INT, - .valp = &glob_conf.bufsize_out, - .descr = "(undocumented)" - }, - { - .name = "BUFSIZE_IN", - .tag = AUD_OPT_INT, - .valp = &glob_conf.bufsize_in, - .descr = "(undocumented)" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops dsound_pcm_ops = { .init_out = dsound_init_out, .fini_out = dsound_fini_out, @@ -892,7 +867,6 @@ static struct audio_pcm_ops dsound_pcm_ops = { struct audio_driver dsound_audio_driver = { .name = "dsound", .descr = "DirectSound http://wikipedia.org/wiki/DirectSound", - .options = dsound_options, .init = dsound_audio_init, .fini = dsound_audio_fini, .pcm_ops = &dsound_pcm_ops, diff --git a/audio/noaudio.c b/audio/noaudio.c index 50db1f3..4c94a26 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; } @@ -161,7 +161,6 @@ static struct audio_pcm_ops no_pcm_ops = { struct audio_driver no_audio_driver = { .name = "none", .descr = "Timer based audio emulation", - .options = NULL, .init = no_audio_init, .fini = no_audio_fini, .pcm_ops = &no_pcm_ops, diff --git a/audio/ossaudio.c b/audio/ossaudio.c index 02a3a95..a5e7f7c 100644 --- a/audio/ossaudio.c +++ b/audio/ossaudio.c @@ -29,6 +29,7 @@ #include "qemu-common.h" #include "qemu/main-loop.h" #include "qemu/host-utils.h" +#include "qapi-visit.h" #include "audio.h" #include "trace.h" @@ -39,16 +40,6 @@ #define USE_DSP_POLICY #endif -typedef struct OSSConf { - int try_mmap; - int nfrags; - int fragsize; - const char *devpath_out; - const char *devpath_in; - int exclusive; - int policy; -} OSSConf; - typedef struct OSSVoiceOut { HWVoiceOut hw; void *pcm_buf; @@ -58,7 +49,7 @@ typedef struct OSSVoiceOut { int fragsize; int mmapped; int pending; - OSSConf *conf; + Audiodev *dev; } OSSVoiceOut; typedef struct OSSVoiceIn { @@ -67,12 +58,12 @@ typedef struct OSSVoiceIn { int fd; int nfrags; int fragsize; - OSSConf *conf; + Audiodev *dev; } OSSVoiceIn; struct oss_params { int freq; - AudioFormat fmt; + int fmt; int nchannels; int nfrags; int fragsize; @@ -264,19 +255,26 @@ static int oss_get_version (int fd, int *version, const char *typ) } #endif -static int oss_open (int in, struct oss_params *req, - struct oss_params *obt, int *pfd, OSSConf* conf) +static int oss_open(int in, struct oss_params *req, audsettings *as, + struct oss_params *obt, int *pfd, Audiodev *dev) { + AudiodevOssOptions *oopts = dev->oss; + AudiodevOssPerDirectionOptions *opdo = in ? oopts->oss_in : oopts->oss_out; + AudiodevPerDirectionOptions *pdo = in ? dev->in : dev->out; int fd; - int oflags = conf->exclusive ? O_EXCL : 0; + int oflags = (oopts->has_exclusive && oopts->exclusive) ? O_EXCL : 0; audio_buf_info abinfo; int fmt, freq, nchannels; int setfragment = 1; - const char *dspname = in ? conf->devpath_in : conf->devpath_out; + const char *dspname = opdo->has_dev ? opdo->dev : "/dev/dsp"; const char *typ = in ? "ADC" : "DAC"; +#ifdef USE_DSP_POLICY + int policy = oopts->has_dsp_policy ? oopts->dsp_policy : 5; +#endif /* Kludge needed to have working mmap on Linux */ - oflags |= conf->try_mmap ? O_RDWR : (in ? O_RDONLY : O_WRONLY); + oflags |= (oopts->has_try_mmap && oopts->try_mmap) ? + O_RDWR : (in ? O_RDONLY : O_WRONLY); fd = open (dspname, oflags | O_NONBLOCK); if (-1 == fd) { @@ -287,6 +285,8 @@ static int oss_open (int in, struct oss_params *req, freq = req->freq; nchannels = req->nchannels; fmt = req->fmt; + req->nfrags = pdo->has_buffer_count ? pdo->buffer_count : 4; + req->fragsize = audio_buffer_bytes(pdo, as, 23220); if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) { oss_logerr2 (errno, typ, "Failed to set sample size %d\n", req->fmt); @@ -310,18 +310,18 @@ static int oss_open (int in, struct oss_params *req, } #ifdef USE_DSP_POLICY - if (conf->policy >= 0) { + if (policy >= 0) { int version; if (!oss_get_version (fd, &version, typ)) { trace_oss_version(version); if (version >= 0x040000) { - int policy = conf->policy; - if (ioctl (fd, SNDCTL_DSP_POLICY, &policy)) { + int policy2 = policy; + if (ioctl (fd, SNDCTL_DSP_POLICY, &policy2)) { oss_logerr2 (errno, typ, "Failed to set timing policy to %d\n", - conf->policy); + policy); goto err; } setfragment = 0; @@ -504,17 +504,16 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, int fd; AudioFormat effective_fmt; struct audsettings obt_as; - OSSConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; + AudiodevOssOptions *oopts = dev->oss; oss->fd = -1; req.fmt = aud_to_ossfmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; - req.fragsize = conf->fragsize; - req.nfrags = conf->nfrags; - if (oss_open (0, &req, &obt, &fd, conf)) { + if (oss_open(0, &req, as, &obt, &fd, dev)) { return -1; } @@ -541,7 +540,7 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; oss->mmapped = 0; - if (conf->try_mmap) { + if (oopts->has_try_mmap && oopts->try_mmap) { oss->pcm_buf = mmap ( NULL, hw->samples << hw->info.shift, @@ -601,7 +600,7 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, } oss->fd = fd; - oss->conf = conf; + oss->dev = dev; return 0; } @@ -609,16 +608,12 @@ static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...) { int trig; OSSVoiceOut *oss = (OSSVoiceOut *) hw; + AudiodevOssPerDirectionOptions *opdo = oss->dev->oss->oss_out; switch (cmd) { case VOICE_ENABLE: { - va_list ap; - int poll_mode; - - va_start (ap, cmd); - poll_mode = va_arg (ap, int); - va_end (ap); + bool poll_mode = !opdo->has_try_poll || opdo->try_poll; ldebug ("enabling voice\n"); if (poll_mode) { @@ -673,16 +668,14 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) int fd; AudioFormat effective_fmt; struct audsettings obt_as; - OSSConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; oss->fd = -1; req.fmt = aud_to_ossfmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; - req.fragsize = conf->fragsize; - req.nfrags = conf->nfrags; - if (oss_open (1, &req, &obt, &fd, conf)) { + if (oss_open(1, &req, as, &obt, &fd, dev)) { return -1; } @@ -716,7 +709,7 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) } oss->fd = fd; - oss->conf = conf; + oss->dev = dev; return 0; } @@ -807,16 +800,12 @@ static int oss_read (SWVoiceIn *sw, void *buf, int size) static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...) { OSSVoiceIn *oss = (OSSVoiceIn *) hw; + AudiodevOssPerDirectionOptions *opdo = oss->dev->oss->oss_out; switch (cmd) { case VOICE_ENABLE: { - va_list ap; - int poll_mode; - - va_start (ap, cmd); - poll_mode = va_arg (ap, int); - va_end (ap); + bool poll_mode = !opdo->has_try_poll || opdo->try_poll; if (poll_mode) { oss_poll_in (hw); @@ -836,81 +825,25 @@ static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...) return 0; } -static OSSConf glob_conf = { - .try_mmap = 0, - .nfrags = 4, - .fragsize = 4096, - .devpath_out = "/dev/dsp", - .devpath_in = "/dev/dsp", - .exclusive = 0, - .policy = 5 -}; - -static void *oss_audio_init (void) +static void *oss_audio_init(Audiodev *dev) { - OSSConf *conf = g_malloc(sizeof(OSSConf)); - *conf = glob_conf; + AudiodevOssOptions *oopts; + assert(dev->kind == AUDIODEV_DRIVER_OSS); - if (access(conf->devpath_in, R_OK | W_OK) < 0 || - access(conf->devpath_out, R_OK | W_OK) < 0) { + oopts = dev->oss; + if (access(oopts->oss_in->has_dev ? oopts->oss_in->dev : "/dev/dsp", + R_OK | W_OK) < 0 || + access(oopts->oss_out->has_dev ? oopts->oss_out->dev : "/dev/dsp", + R_OK | W_OK) < 0) { return NULL; } - return conf; + return dev; } static void oss_audio_fini (void *opaque) { - g_free(opaque); } -static struct audio_option oss_options[] = { - { - .name = "FRAGSIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.fragsize, - .descr = "Fragment size in bytes" - }, - { - .name = "NFRAGS", - .tag = AUD_OPT_INT, - .valp = &glob_conf.nfrags, - .descr = "Number of fragments" - }, - { - .name = "MMAP", - .tag = AUD_OPT_BOOL, - .valp = &glob_conf.try_mmap, - .descr = "Try using memory mapped access" - }, - { - .name = "DAC_DEV", - .tag = AUD_OPT_STR, - .valp = &glob_conf.devpath_out, - .descr = "Path to DAC device" - }, - { - .name = "ADC_DEV", - .tag = AUD_OPT_STR, - .valp = &glob_conf.devpath_in, - .descr = "Path to ADC device" - }, - { - .name = "EXCLUSIVE", - .tag = AUD_OPT_BOOL, - .valp = &glob_conf.exclusive, - .descr = "Open device in exclusive mode (vmix wont work)" - }, -#ifdef USE_DSP_POLICY - { - .name = "POLICY", - .tag = AUD_OPT_INT, - .valp = &glob_conf.policy, - .descr = "Set the timing policy of the device, -1 to use fragment mode", - }, -#endif - { /* End of list */ } -}; - static struct audio_pcm_ops oss_pcm_ops = { .init_out = oss_init_out, .fini_out = oss_fini_out, @@ -928,7 +861,6 @@ static struct audio_pcm_ops oss_pcm_ops = { struct audio_driver oss_audio_driver = { .name = "oss", .descr = "OSS http://www.opensound.com", - .options = oss_options, .init = oss_audio_init, .fini = oss_audio_fini, .pcm_ops = &oss_pcm_ops, diff --git a/audio/paaudio.c b/audio/paaudio.c index cfdbdc6..a9a6a5c 100644 --- a/audio/paaudio.c +++ b/audio/paaudio.c @@ -1,6 +1,7 @@ /* public domain */ #include "qemu-common.h" #include "audio.h" +#include "qapi-visit.h" #include @@ -9,14 +10,7 @@ #include "audio_pt_int.h" typedef struct { - int samples; - char *server; - char *sink; - char *source; -} PAConf; - -typedef struct { - PAConf conf; + Audiodev *dev; pa_threaded_mainloop *mainloop; pa_context *context; } paaudio; @@ -31,6 +25,7 @@ typedef struct { void *pcm_buf; struct audio_pt pt; paaudio *g; + int samples; } PAVoiceOut; typedef struct { @@ -45,6 +40,7 @@ typedef struct { const void *read_data; size_t read_index, read_length; paaudio *g; + int samples; } PAVoiceIn; static void qpa_audio_fini(void *opaque); @@ -226,7 +222,7 @@ static void *qpa_thread_out (void *arg) } } - decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2); + decr = to_mix = audio_MIN (pa->live, pa->samples >> 2); rpos = pa->rpos; if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { @@ -318,7 +314,7 @@ static void *qpa_thread_in (void *arg) } } - incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2); + incr = to_grab = audio_MIN (pa->dead, pa->samples >> 2); wpos = pa->wpos; if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { @@ -545,6 +541,7 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, struct audsettings obt_as = *as; PAVoiceOut *pa = (PAVoiceOut *) hw; paaudio *g = pa->g = drv_opaque; + AudiodevPaOptions *popts = g->dev->pa; ss.format = audfmt_to_pa (as->fmt, as->endianness); ss.channels = as->nchannels; @@ -565,7 +562,7 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, g, "qemu", PA_STREAM_PLAYBACK, - g->conf.sink, + popts->has_sink ? popts->sink : NULL, &ss, NULL, /* channel map */ &ba, /* buffering attributes */ @@ -577,7 +574,8 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, } audio_pcm_init_info (&hw->info, &obt_as); - hw->samples = g->conf.samples; + hw->samples = pa->samples = audio_buffer_samples(g->dev->out, &obt_as, + 46440); pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); pa->rpos = hw->rpos; if (!pa->pcm_buf) { @@ -611,6 +609,7 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) struct audsettings obt_as = *as; PAVoiceIn *pa = (PAVoiceIn *) hw; paaudio *g = pa->g = drv_opaque; + AudiodevPaOptions *popts = g->dev->pa; ss.format = audfmt_to_pa (as->fmt, as->endianness); ss.channels = as->nchannels; @@ -622,7 +621,7 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) g, "qemu", PA_STREAM_RECORD, - g->conf.source, + popts->has_source ? popts->source : NULL, &ss, NULL, /* channel map */ NULL, /* buffering attributes */ @@ -634,7 +633,8 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) } audio_pcm_init_info (&hw->info, &obt_as); - hw->samples = g->conf.samples; + hw->samples = pa->samples = audio_buffer_samples(g->dev->in, &obt_as, + 46440); pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); pa->wpos = hw->wpos; if (!pa->pcm_buf) { @@ -808,14 +808,19 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) } /* common */ -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; + paaudio *g; + AudiodevPaOptions *popts; + const char *server; + + assert(dev->kind == AUDIODEV_DRIVER_PA); + + g = g_malloc(sizeof(paaudio)); + popts = dev->pa; + server = popts->has_server ? popts->server : NULL; + + g->dev = dev; g->mainloop = NULL; g->context = NULL; @@ -825,14 +830,14 @@ static void *qpa_audio_init (void) } g->context = pa_context_new (pa_threaded_mainloop_get_api (g->mainloop), - g->conf.server); + server); if (!g->context) { goto fail; } pa_context_set_state_callback (g->context, context_state_cb, g); - if (pa_context_connect (g->context, g->conf.server, 0, NULL) < 0) { + if (pa_context_connect (g->context, server, 0, NULL) < 0) { qpa_logerr (pa_context_errno (g->context), "pa_context_connect() failed\n"); goto fail; @@ -895,34 +900,6 @@ static void qpa_audio_fini (void *opaque) g_free(g); } -struct audio_option qpa_options[] = { - { - .name = "SAMPLES", - .tag = AUD_OPT_INT, - .valp = &glob_conf.samples, - .descr = "buffer size in samples" - }, - { - .name = "SERVER", - .tag = AUD_OPT_STR, - .valp = &glob_conf.server, - .descr = "server address" - }, - { - .name = "SINK", - .tag = AUD_OPT_STR, - .valp = &glob_conf.sink, - .descr = "sink device name" - }, - { - .name = "SOURCE", - .tag = AUD_OPT_STR, - .valp = &glob_conf.source, - .descr = "source device name" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops qpa_pcm_ops = { .init_out = qpa_init_out, .fini_out = qpa_fini_out, @@ -940,7 +917,6 @@ static struct audio_pcm_ops qpa_pcm_ops = { struct audio_driver pa_audio_driver = { .name = "pa", .descr = "http://www.pulseaudio.org/", - .options = qpa_options, .init = qpa_audio_init, .fini = qpa_audio_fini, .pcm_ops = &qpa_pcm_ops, diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c index db0f95a..796238a 100644 --- a/audio/sdlaudio.c +++ b/audio/sdlaudio.c @@ -44,18 +44,13 @@ typedef struct SDLVoiceOut { int decr; } SDLVoiceOut; -static struct { - int nb_samples; -} conf = { - .nb_samples = 1024 -}; - static struct SDLAudioState { int exit; SDL_mutex *mutex; SDL_sem *sem; int initialized; bool driver_created; + Audiodev *dev; } glob_sdl; typedef struct SDLAudioState SDLAudioState; @@ -347,7 +342,7 @@ static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, req.freq = as->freq; req.format = aud_to_sdlfmt (as->fmt); req.channels = as->nchannels; - req.samples = conf.nb_samples; + req.samples = audio_buffer_samples(s->dev->out, as, 11610); req.callback = sdl_callback; req.userdata = sdl; @@ -391,7 +386,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) { @@ -420,6 +415,7 @@ static void *sdl_audio_init (void) } s->driver_created = true; + s->dev = dev; return s; } @@ -431,18 +427,9 @@ static void sdl_audio_fini (void *opaque) SDL_DestroyMutex (s->mutex); SDL_QuitSubSystem (SDL_INIT_AUDIO); s->driver_created = false; + s->dev = NULL; } -static struct audio_option sdl_options[] = { - { - .name = "SAMPLES", - .tag = AUD_OPT_INT, - .valp = &conf.nb_samples, - .descr = "Size of SDL buffer in samples" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops sdl_pcm_ops = { .init_out = sdl_init_out, .fini_out = sdl_fini_out, @@ -454,7 +441,6 @@ static struct audio_pcm_ops sdl_pcm_ops = { struct audio_driver sdl_audio_driver = { .name = "sdl", .descr = "SDL http://www.libsdl.org", - .options = sdl_options, .init = sdl_audio_init, .fini = sdl_audio_fini, .pcm_ops = &sdl_pcm_ops, diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c index f556b3b..441fbcb 100644 --- a/audio/spiceaudio.c +++ b/audio/spiceaudio.c @@ -74,7 +74,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; @@ -370,10 +370,6 @@ static int line_in_ctl (HWVoiceIn *hw, int cmd, ...) return 0; } -static struct audio_option audio_options[] = { - { /* end of list */ }, -}; - static struct audio_pcm_ops audio_callbacks = { .init_out = line_out_init, .fini_out = line_out_fini, @@ -391,7 +387,6 @@ static struct audio_pcm_ops audio_callbacks = { struct audio_driver spice_audio_driver = { .name = "spice", .descr = "spice audio driver", - .options = audio_options, .init = spice_audio_init, .fini = spice_audio_fini, .pcm_ops = &audio_callbacks, diff --git a/audio/wavaudio.c b/audio/wavaudio.c index 81250e6..1af6d23 100644 --- a/audio/wavaudio.c +++ b/audio/wavaudio.c @@ -23,6 +23,7 @@ */ #include "hw/hw.h" #include "qemu/timer.h" +#include "qapi-visit.h" #include "audio.h" #define AUDIO_CAP "wav" @@ -36,11 +37,6 @@ typedef struct WAVVoiceOut { int total_samples; } WAVVoiceOut; -typedef struct { - struct audsettings settings; - const char *wav_path; -} WAVConf; - static int wav_run_out (HWVoiceOut *hw, int live) { WAVVoiceOut *wav = (WAVVoiceOut *) hw; @@ -111,8 +107,10 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 }; - WAVConf *conf = drv_opaque; - struct audsettings wav_as = conf->settings; + Audiodev *dev = drv_opaque; + AudiodevWavOptions *wopts = dev->wav; + struct audsettings wav_as = audiodev_to_audsettings(dev->out); + const char *wav_path = wopts->has_path ? wopts->path : "qemu.wav"; stereo = wav_as.nchannels == 2; switch (wav_as.fmt) { @@ -153,10 +151,10 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4); le_store (hdr + 32, 1 << (bits16 + stereo), 2); - wav->f = fopen (conf->wav_path, "wb"); + wav->f = fopen(wav_path, "wb"); if (!wav->f) { dolog ("Failed to open wave file `%s'\nReason: %s\n", - conf->wav_path, strerror (errno)); + wav_path, strerror(errno)); g_free (wav->pcm_buf); wav->pcm_buf = NULL; return -1; @@ -224,54 +222,17 @@ static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...) return 0; } -static WAVConf glob_conf = { - .settings.freq = 44100, - .settings.nchannels = 2, - .settings.fmt = AUDIO_FORMAT_S16, - .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; - return conf; + assert(dev->kind == AUDIODEV_DRIVER_WAV); + return dev; } static void wav_audio_fini (void *opaque) { ldebug ("wav_fini"); - g_free(opaque); } -static struct audio_option wav_options[] = { - { - .name = "FREQUENCY", - .tag = AUD_OPT_INT, - .valp = &glob_conf.settings.freq, - .descr = "Frequency" - }, - { - .name = "FORMAT", - .tag = AUD_OPT_FMT, - .valp = &glob_conf.settings.fmt, - .descr = "Format" - }, - { - .name = "DAC_FIXED_CHANNELS", - .tag = AUD_OPT_INT, - .valp = &glob_conf.settings.nchannels, - .descr = "Number of channels (1 - mono, 2 - stereo)" - }, - { - .name = "PATH", - .tag = AUD_OPT_STR, - .valp = &glob_conf.wav_path, - .descr = "Path to wave file" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops wav_pcm_ops = { .init_out = wav_init_out, .fini_out = wav_fini_out, @@ -283,7 +244,6 @@ static struct audio_pcm_ops wav_pcm_ops = { struct audio_driver wav_audio_driver = { .name = "wav", .descr = "WAV renderer http://wikipedia.org/wiki/WAV", - .options = wav_options, .init = wav_audio_init, .fini = wav_audio_fini, .pcm_ops = &wav_pcm_ops, diff --git a/qemu-options.hx b/qemu-options.hx index 5438f98..2ce03c4 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -185,8 +185,8 @@ Set default value of @var{driver}'s property @var{prop} to @var{value}, e.g.: qemu-system-i386 -global ide-drive.physical_block_size=4096 -drive file=file,if=ide,index=0,media=disk @end example -In particular, you can use this to set driver properties for devices which are -created automatically by the machine model. To create a device which is not +In particular, you can use this to set driver properties for devices which are +created automatically by the machine model. To create a device which is not created automatically and set properties on it, use -@option{device}. The two syntaxes are equivalent. The longer one works for drivers whose name @@ -312,14 +312,234 @@ The default is @code{en-us}. ETEXI +HXCOMM Deprecated by -audiodev DEF("audio-help", 0, QEMU_OPTION_audio_help, - "-audio-help print list of audio drivers and their options\n", + "-audio-help show -audiodev equivalent of the current audio settings\n", QEMU_ARCH_ALL) STEXI @item -audio-help @findex -audio-help -Will show the audio subsystem help: list of drivers, tunable -parameters. +Will show the -audiodev equivalent of the currently specified +(deprecated) environment variables. +ETEXI + +DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev, + "-audiodev [driver=]driver,id=id[,prop[=value][,...]]\n" + " specifies the audio backend to use\n" + " id= identifier of the backend\n" + " timer-period= timer period in microseconds\n" + " in|out.fixed-settings= use fixed settings for host audio\n" + " in|out.frequency= frequency to use with fixed settings\n" + " in|out.channels= number of channels to use with fixed settings\n" + " in|out.format= sample format to use with fixed settings\n" + " valid values: s8, s16, s32, u8, u16, u32\n" + " in|out.voices= number of voices to use\n" + " in|out.buffer-len= size of buffer in microseconds\n" + " in|out.buffer-count= number of buffers\n" + "-audiodev none,id=id,[,prop[=value][,...]]\n" + " dummy driver that discards all output\n" +#ifdef CONFIG_ALSA + "-audiodev alsa,id=id[,prop[=value][,...]]\n" + " alsa-in|alsa-out.dev= name of the audio device to use\n" + " alsa-in|alsa-out.try-poll= attempt to use poll mode\n" + " threshold= threshold (in microseconds) when playback starts\n" +#endif +#ifdef CONFIG_COREAUDIO + "-audiodev coreaudio,id=id[,prop[=value][,...]]\n" +#endif +#ifdef CONFIG_DSOUND + "-audiodev dsound,id=id[,prop[=value][,...]]\n" + " latency= add extra latency to playback in microseconds\n" +#endif +#ifdef CONFIG_OSS + "-audiodev oss,id=id[,prop[=value][,...]]\n" + " oss-in|oss-out.dev= path of the audio device to use\n" + " oss-in|oss-out.try-poll= attempt to use poll mode\n" + " try-mmap= try using memory mapped access\n" + " exclusive= open device in exclusive mode\n" + " dsp-policy= set timing policy (0..10), -1 to use fragment mode\n" +#endif +#ifdef CONFIG_PA + "-audiodev pa,id=id[,prop[=value][,...]]\n" + " server= PulseAudio server address\n" + " sink= sink device name\n" + " source= source device name\n" +#endif +#ifdef CONFIG_SDL + "-audiodev sdl,id=id[,prop[=value][,...]]\n" +#endif +#ifdef CONFIG_SPICE + "-audiodev spice,id=id[,prop[=value][,...]]\n" +#endif + "-audiodev wav,id=id[,prop[=value][,...]]\n" + " path= path of wav file to record\n", + QEMU_ARCH_ALL) +STEXI +@item -audiodev [driver=]@var{driver},id=@var{id}[,@var{prop}[=@var{value}][,...]] +@findex -audiodev +Adds a new audio backend @var{driver} identified by @var{id}. There are +global and driver specific properties. Some values can be set +differently for input and output, they're marked with @code{in|out.}. +You can set the input's property with @code{in.@var{prop}} and the +output's property with @code{out.@var{prop}}. For example: +@example +-audiodev alsa,in.frequency=44110,out.frequency=8000 +-audiodev alsa,out.channels=1 # leaves in.channels unspecified +@end example + +Valid global options are: + +@table @option +@item id=@var{identifier} +Identifies the audio backend. + +@item timer-period=@var{period} +Sets the timer @var{period} used by the audio subsystem in microseconds. +Default is 10000 (10 ms). + +@item in|out.fixed-settings=on|off +Use fixed settings for host audio. When off, it will change based on +how the guest opens the sound card. In this case you must not specify +@var{frequency}, @var{channels} or @var{format}. Default is on. + +@item in|out.frequency=@var{frequency} +Specify the @var{frequency} to use when using @var{fixed-settings}. +Default is 44100Hz. + +@item in|out.channels=@var{channels} +Specify the number of @var{channels} to use when using +@var{fixed-settings}. Default is 2 (stereo). + +@item in|out.format=@var{format} +Specify the sample @var{format} to use when using @var{fixed-settings}. +Valid values are: @code{s8}, @code{s16}, @code{s32}, @code{u8}, +@code{u16}, @code{u32}. Default is @code{s16}. + +@item in|out.voices=@var{voices} +Specify the number of @var{voices} to use. Default is 1. + +@item in|out.buffer=@var{usecs} +Sets the size of the buffer in microseconds. + +@item in|out.buffer-count=@var{count} +Sets the @var{count} of the buffers. + +@end table + +@item -audiodev none,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a dummy backend that discards all outputs. This backend has no +backend specific properties. + +@item -audiodev alsa,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates backend using the ALSA. This backend is only available on +Linux. + +ALSA specific options are: + +@table @option +@item alsa-in|alsa-out.dev=@var{device} +Specify the ALSA @var{device} to use for input and/or output. Default +is @code{default}. + +@item alsa-in|alsa-out.try-poll=on|off +Attempt to use poll mode with the device. Default is on. + +@item threshold=@var{threshold} +Threshold (in microseconds) when playback starts. Default is 0. + +@end table + +@item -audiodev coreaudio,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using Apple's Core Audio. This backend is only +available on Mac OS and only supports playback. This backend has no +backend specific properties. + +@item -audiodev dsound,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using Microsoft's DirectSound. This backend is only +available on Windows and only supports playback. + +Backend specific options are: + +@table @option + +@item latency=@var{usecs} +Add extra @var{usecs} microseconds latency to playback. Default is +10000 (10 ms). + +@end table + +@item -audiodev oss,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using OSS. This backend is available on most +Unix-like systems. + +OSS specific options are: + +@table @option + +@item oss-in|oss-out.dev=@var{device} +Specify the file name of the OSS @var{device} to use. Default is +@code{/dev/dsp}. + +@item oss-in|oss-out.try-poll=on|of +Attempt to use poll mode with the device. Default is on. + +@item try-mmap=on|off +Try using memory mapped device access. Default is off. + +@item exclusive=on|off +Open the device in exclusive mode (vmix won't work in this case). +Default is off. + +@item dsp-policy=@var{policy} +Sets the timing policy (between 0 and 10, where smaller number means +smaller latency but higher CPU usage). Use -1 to use buffer sizes +specified by @code{buffer} and @code{buffer-count}. This option is +ignored if you do not have OSS 4. Default is 5. + +@end table + +@item -audiodev pa,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using PulseAudio. This backend is available on most +systems. + +PulseAudio specific options are: + +@table @option + +@item server=@var{server} +Sets the PulseAudio @var{server} to connect to. + +@item sink=@var{sink} +Use the specified @var{sink} for playback. + +@item source=@var{source} +Use the specified @var{source} for recording. + +@end table + +@item -audiodev sdl,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using SDL. This backend is available on most systems, +but you should use your platform's native backend if possible. This +backend has no backend specific properties. + +@item -audiodev spice,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend that sends audio through SPICE. This backend requires +@code{-spice} and automatically selected in that case, so usually you +can ignore this option. This backend has no backend specific +properties. + +@item -audiodev wav,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend that writes audio to a WAV file. + +Backend specific options are: + +@table @option + +@item path=@var{path} +Write recorded audio into the specified file. Default is +@code{qemu.wav}. + +@end table ETEXI DEF("soundhw", HAS_ARG, QEMU_OPTION_soundhw, diff --git a/vl.c b/vl.c index 2201e27..c3d6ef9 100644 --- a/vl.c +++ b/vl.c @@ -2873,6 +2873,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); @@ -3170,9 +3171,14 @@ 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(qemu_find_opts("audiodev"), optarg, 1)) { + exit(1); + } + break; case QEMU_OPTION_soundhw: select_soundhw (optarg); break; @@ -4339,6 +4345,7 @@ int main(int argc, char **argv, char **envp) realtime_init(); + audio_set_options(); audio_init(); cpu_synchronize_all_post_init(); -- 2.4.3