Commit 71830221 authored by Kővágó, Zoltán's avatar Kővágó, Zoltán Committed by Gerd Hoffmann
Browse files

audio: -audiodev command line option basic implementation



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.

Signed-off-by: default avatarKővágó, Zoltán <DirtY.iCE.hu@gmail.com>
Message-id: e99a7cbdac0d13512743880660b2032024703e4c.1552083282.git.DirtY.iCE.hu@gmail.com
Signed-off-by: default avatarGerd Hoffmann <kraxel@redhat.com>
parent f0b3d811
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
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_SPICE) += spiceaudio.o
common-obj-$(CONFIG_AUDIO_COREAUDIO) += coreaudio.o
common-obj-$(CONFIG_AUDIO_DSOUND) += dsoundaudio.o
+1 −1
Original line number Diff line number Diff line
@@ -1125,7 +1125,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;
+269 −361
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@
#include "audio.h"
#include "monitor/monitor.h"
#include "qemu/timer.h"
#include "qapi/error.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qapi-visit-audio.h"
#include "sysemu/sysemu.h"
#include "qemu/cutils.h"
#include "sysemu/replay.h"
@@ -46,14 +49,16 @@
   The 1st one is the one used by default, that is the reason
    that we generate the list.
*/
static const char *audio_prio_list[] = {
const char *audio_prio_list[] = {
    "spice",
    CONFIG_AUDIO_DRIVERS
    "none",
    "wav",
    NULL
};

static QLIST_HEAD(, audio_driver) audio_drivers;
static AudiodevListHead audiodevs = QSIMPLEQ_HEAD_INITIALIZER(audiodevs);

void audio_driver_register(audio_driver *drv)
{
@@ -80,61 +85,6 @@ audio_driver *audio_driver_lookup(const char *name)
    return NULL;
}

static void audio_module_load_all(void)
{
    int i;

    for (i = 0; i < ARRAY_SIZE(audio_prio_list); i++) {
        audio_driver_lookup(audio_prio_list[i]);
    }
}

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,
};

static AudioState glob_audio_state;

const struct mixeng_volume nominal_volume = {
@@ -151,9 +101,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) {
@@ -161,16 +108,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");

@@ -232,31 +172,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) {
@@ -382,78 +297,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)
{
@@ -1141,11 +984,11 @@ static void audio_reset_timer (AudioState *s)
{
    if (audio_is_timer_needed ()) {
        timer_mod_anticipate_ns(s->ts,
            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + conf.period.ticks);
            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
        if (!audio_timer_running) {
            audio_timer_running = true;
            audio_timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
            trace_audio_timer_start(conf.period.ticks / SCALE_MS);
            trace_audio_timer_start(s->period_ticks / SCALE_MS);
        }
    } else {
        timer_del(s->ts);
@@ -1159,16 +1002,17 @@ static void audio_reset_timer (AudioState *s)
static void audio_timer (void *opaque)
{
    int64_t now, diff;
    AudioState *s = opaque;

    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
    diff = now - audio_timer_last;
    if (diff > conf.period.ticks * 3 / 2) {
    if (diff > s->period_ticks * 3 / 2) {
        trace_audio_timer_delayed(diff / SCALE_MS);
    }
    audio_timer_last = now;

    audio_run("timer");
    audio_reset_timer (opaque);
    audio_reset_timer(s);
}

/*
@@ -1228,7 +1072,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);
                    audio_reset_timer (s);
                }
            }
@@ -1273,7 +1117,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);
                    audio_reset_timer (s);
                }
            }
@@ -1594,169 +1438,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)
{
    struct audio_driver *d;

    /* make sure we print the help text for modular drivers too */
    audio_module_load_all();

    audio_process_options ("AUDIO", audio_options);
    QLIST_FOREACH(d, &audio_drivers, next) {
        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");

    QLIST_FOREACH(d, &audio_drivers, next) {

        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, bool msg)
static int audio_driver_init(AudioState *s, struct audio_driver *drv,
                             bool msg, 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);
@@ -1782,11 +1470,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);
    }

    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);
    }
    audio_reset_timer (s);
}
@@ -1836,6 +1524,11 @@ void audio_cleanup(void)
        s->drv->fini (s->drv_opaque);
        s->drv = NULL;
    }

    if (s->dev) {
        qapi_free_Audiodev(s->dev);
        s->dev = NULL;
    }
}

static const VMStateDescription vmstate_audio = {
@@ -1847,18 +1540,57 @@ static const VMStateDescription vmstate_audio = {
    }
};

static void audio_init (void)
static void audio_validate_opts(Audiodev *dev, Error **errp);

static AudiodevListEntry *audiodev_find(
    AudiodevListHead *head, const char *drvname)
{
    AudiodevListEntry *e;
    QSIMPLEQ_FOREACH(e, head, next) {
        if (strcmp(AudiodevDriver_str(e->dev->driver), drvname) == 0) {
            return e;
        }
    }

    return NULL;
}

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;
    struct audio_driver *driver;
    /* silence gcc warning about uninitialized variable */
    AudiodevListHead head = QSIMPLEQ_HEAD_INITIALIZER(head);

    if (s->drv) {
        return;
        if (dev) {
            dolog("Cannot create more than one audio backend, sorry\n");
            qapi_free_Audiodev(dev);
        }
        return -1;
    }

    if (dev) {
        /* -audiodev option */
        drvname = AudiodevDriver_str(dev->driver);
    } else {
        /* legacy implicit initialization */
        head = audio_handle_legacy_opts();
        /*
         * In case of legacy initialization, all Audiodevs in the list will have
         * the same configuration (except the driver), so it does't matter which
         * one we chose.  We need an Audiodev to set up AudioState before we can
         * init a driver.  Also note that dev at this point is still in the
         * list.
         */
        dev = QSIMPLEQ_FIRST(&head)->dev;
        audio_validate_opts(dev, &error_abort);
    }
    s->dev = dev;

    QLIST_INIT (&s->hw_head_out);
    QLIST_INIT (&s->hw_head_in);
@@ -1867,10 +1599,8 @@ static void audio_init (void)

    s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);

    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 = audio_get_pdo_out(dev)->voices;
    s->nb_hw_voices_in = audio_get_pdo_in(dev)->voices;

    if (s->nb_hw_voices_out <= 0) {
        dolog ("Bogus number of playback voices %d, setting to 1\n",
@@ -1884,46 +1614,42 @@ 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) {
        driver = audio_driver_lookup(drvname);
        if (driver) {
            done = !audio_driver_init(s, driver, true);
            done = !audio_driver_init(s, driver, true, dev);
        } else {
            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(audio_prio_list); i++) {
    } else {
        for (i = 0; audio_prio_list[i]; i++) {
            AudiodevListEntry *e = audiodev_find(&head, audio_prio_list[i]);
            driver = audio_driver_lookup(audio_prio_list[i]);
            if (driver && driver->can_be_default) {
                done = !audio_driver_init(s, driver, false);

            if (e && driver) {
                s->dev = dev = e->dev;
                audio_validate_opts(dev, &error_abort);
                done = !audio_driver_init(s, driver, false, dev);
                if (done) {
                    e->dev = NULL;
                    break;
                }
            }
        }
    }
    audio_free_audiodev_list(&head);

    if (!done) {
        driver = audio_driver_lookup("none");
        done = !audio_driver_init(s, driver, false);
        done = !audio_driver_init(s, driver, false, dev);
        assert(done);
        dolog("warning: Using timer based audio emulation\n");
    }

    if (conf.period.hertz <= 0) {
        if (conf.period.hertz < 0) {
            dolog ("warning: Timer period is negative - %d "
                   "treating as zero\n",
                   conf.period.hertz);
        }
        conf.period.ticks = 1;
    if (dev->timer_period <= 0) {
        s->period_ticks = 1;
    } else {
        conf.period.ticks = NANOSECONDS_PER_SECOND / conf.period.hertz;
        s->period_ticks = NANOSECONDS_PER_SECOND / dev->timer_period;
    }

    e = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s);
@@ -1934,11 +1660,22 @@ static void audio_init (void)

    QLIST_INIT (&s->card_head);
    vmstate_register (NULL, 0, &vmstate_audio, s);
    return 0;
}

void audio_free_audiodev_list(AudiodevListHead *head)
{
    AudiodevListEntry *e;
    while ((e = QSIMPLEQ_FIRST(head))) {
        QSIMPLEQ_REMOVE_HEAD(head, next);
        qapi_free_Audiodev(e->dev);
        g_free(e);
    }
}

void AUD_register_card (const char *name, QEMUSoundCard *card)
{
    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);
@@ -2078,3 +1815,174 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol)
        }
    }
}

void audio_create_pdos(Audiodev *dev)
{
    switch (dev->driver) {
#define CASE(DRIVER, driver, pdo_name)                              \
    case AUDIODEV_DRIVER_##DRIVER:                                  \
        if (!dev->u.driver.has_in) {                                \
            dev->u.driver.in = g_malloc0(                           \
                sizeof(Audiodev##pdo_name##PerDirectionOptions));   \
            dev->u.driver.has_in = true;                            \
        }                                                           \
        if (!dev->u.driver.has_out) {                               \
            dev->u.driver.out = g_malloc0(                          \
                sizeof(AudiodevAlsaPerDirectionOptions));           \
            dev->u.driver.has_out = true;                           \
        }                                                           \
        break

        CASE(NONE, none, );
        CASE(ALSA, alsa, Alsa);
        CASE(COREAUDIO, coreaudio, Coreaudio);
        CASE(DSOUND, dsound, );
        CASE(OSS, oss, Oss);
        CASE(PA, pa, Pa);
        CASE(SDL, sdl, );
        CASE(SPICE, spice, );
        CASE(WAV, wav, );

    case AUDIODEV_DRIVER__MAX:
        abort();
    };
}

static void audio_validate_per_direction_opts(
    AudiodevPerDirectionOptions *pdo, Error **errp)
{
    if (!pdo->has_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 void audio_validate_opts(Audiodev *dev, Error **errp)
{
    Error *err = NULL;

    audio_create_pdos(dev);

    audio_validate_per_direction_opts(audio_get_pdo_in(dev), &err);
    if (err) {
        error_propagate(errp, err);
        return;
    }

    audio_validate_per_direction_opts(audio_get_pdo_out(dev), &err);
    if (err) {
        error_propagate(errp, err);
        return;
    }

    if (!dev->has_timer_period) {
        dev->has_timer_period = true;
        dev->timer_period = 10000; /* 100Hz -> 10ms */
    }
}

void audio_parse_option(const char *opt)
{
    AudiodevListEntry *e;
    Audiodev *dev = NULL;

    Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
    visit_type_Audiodev(v, NULL, &dev, &error_fatal);
    visit_free(v);

    audio_validate_opts(dev, &error_fatal);

    e = g_malloc0(sizeof(AudiodevListEntry));
    e->dev = dev;
    QSIMPLEQ_INSERT_TAIL(&audiodevs, e, next);
}

void audio_init_audiodevs(void)
{
    AudiodevListEntry *e;

    QSIMPLEQ_FOREACH(e, &audiodevs, next) {
        audio_init(e->dev);
    }
}

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_length ? pdo->buffer_length : def_usecs;
    return (as->freq * usecs + 500000) / 1000000;
}

/* samples = channels * frames = channels * freq * usec / 1e6 */
int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
                         audsettings *as, int def_usecs)
{
    return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
}

/*
 * bytes = bytes_per_sample * samples =
 *     bytes_per_sample * channels * freq * usec / 1e6
 */
int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
                       audsettings *as, int def_usecs)
{
    return audio_buffer_samples(pdo, as, def_usecs) *
        audioformat_bytes_per_sample(as->fmt);
}
+15 −3
Original line number Diff line number Diff line
@@ -36,12 +36,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,
@@ -81,7 +90,6 @@ typedef struct QEMUAudioTimeStamp {
void AUD_vlog (const char *cap, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
void AUD_log (const char *cap, const char *fmt, ...) GCC_FMT_ATTR(2, 3);

void AUD_help (void);
void AUD_register_card (const char *name, QEMUSoundCard *card);
void AUD_remove_card (QEMUSoundCard *card);
CaptureVoiceOut *AUD_add_capture (
@@ -163,4 +171,8 @@ void audio_sample_to_uint64(void *samples, int pos,
void audio_sample_from_uint64(void *samples, int pos,
                            uint64_t left, uint64_t right);

void audio_parse_option(const char *opt);
void audio_init_audiodevs(void);
void audio_legacy_help(void);

#endif /* QEMU_AUDIO_H */
+19 −1
Original line number Diff line number Diff line
@@ -146,7 +146,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;
@@ -193,6 +193,7 @@ struct SWVoiceCap {

typedef struct AudioState {
    struct audio_driver *drv;
    Audiodev *dev;
    void *drv_opaque;

    QEMUTimer *ts;
@@ -203,10 +204,13 @@ typedef struct AudioState {
    int nb_hw_voices_out;
    int nb_hw_voices_in;
    int vm_running;
    int64_t period_ticks;
} AudioState;

extern const struct mixeng_volume nominal_volume;

extern const char *audio_prio_list[];

void audio_driver_register(audio_driver *drv);
audio_driver *audio_driver_lookup(const char *name);

@@ -248,4 +252,18 @@ static inline int audio_ring_dist (int dst, int src, int len)
#define AUDIO_STRINGIFY_(n) #n
#define AUDIO_STRINGIFY(n) AUDIO_STRINGIFY_(n)

typedef struct AudiodevListEntry {
    Audiodev *dev;
    QSIMPLEQ_ENTRY(AudiodevListEntry) next;
} AudiodevListEntry;

typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
AudiodevListHead audio_handle_legacy_opts(void);

void audio_free_audiodev_list(AudiodevListHead *head);

void audio_create_pdos(Audiodev *dev);
AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev);
AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev);

#endif /* QEMU_AUDIO_INT_H */
Loading