audio_template.h 9.03 KB
/*
 * QEMU Audio subsystem header
 *
 * Copyright (c) 2005 Vassili Karpov (malc)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifdef DAC
#define TYPE out
#define HW glue (HWVoice, Out)
#define SW glue (SWVoice, Out)
#else
#define TYPE in
#define HW glue (HWVoice, In)
#define SW glue (SWVoice, In)
#endif

static void glue (audio_pcm_sw_fini_, TYPE) (SW *sw)
{
    glue (audio_pcm_sw_free_resources_, TYPE) (sw);
    if (sw->name) {
        qemu_free (sw->name);
        sw->name = NULL;
    }
}

static void glue (audio_pcm_hw_add_sw_, TYPE) (HW *hw, SW *sw)
{
    LIST_INSERT_HEAD (&hw->sw_head, sw, entries);
}

static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw)
{
    LIST_REMOVE (sw, entries);
}

static void glue (audio_pcm_hw_fini_, TYPE) (HW *hw)
{
    if (hw->active) {
        glue (audio_pcm_hw_free_resources_ ,TYPE) (hw);
        glue (hw->pcm_ops->fini_, TYPE) (hw);
        memset (hw, 0, glue (audio_state.drv->voice_size_, TYPE));
    }
}

static void glue (audio_pcm_hw_gc_, TYPE) (HW *hw)
{
    if (!hw->sw_head.lh_first) {
        glue (audio_pcm_hw_fini_, TYPE) (hw);
    }
}

static HW *glue (audio_pcm_hw_find_any_, TYPE) (HW *hw)
{
    return hw ? hw->entries.le_next : glue (hw_head_, TYPE).lh_first;
}

static HW *glue (audio_pcm_hw_find_any_active_, TYPE) (HW *hw)
{
    while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) {
        if (hw->active) {
            return hw;
        }
    }
    return NULL;
}

static HW *glue (audio_pcm_hw_find_any_active_enabled_, TYPE) (HW *hw)
{
    while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) {
        if (hw->active && hw->enabled) {
            return hw;
        }
    }
    return NULL;
}

static HW *glue (audio_pcm_hw_find_any_passive_, TYPE) (HW *hw)
{
    while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) {
        if (!hw->active) {
            return hw;
        }
    }
    return NULL;
}

static HW *glue (audio_pcm_hw_find_specific_, TYPE) (
    HW *hw,
    int freq,
    int nchannels,
    audfmt_e fmt
    )
{
    while ((hw = glue (audio_pcm_hw_find_any_active_, TYPE) (hw))) {
        if (audio_pcm_info_eq (&hw->info, freq, nchannels, fmt)) {
            return hw;
        }
    }
    return NULL;
}

static HW *glue (audio_pcm_hw_add_new_, TYPE) (
    int freq,
    int nchannels,
    audfmt_e fmt
    )
{
    HW *hw;

    hw = glue (audio_pcm_hw_find_any_passive_, TYPE) (NULL);
    if (hw) {
        hw->pcm_ops = audio_state.drv->pcm_ops;
        if (!hw->pcm_ops) {
            return NULL;
        }

        if (glue (audio_pcm_hw_init_, TYPE) (hw, freq, nchannels, fmt)) {
            glue (audio_pcm_hw_gc_, TYPE) (hw);
            return NULL;
        }
        else {
            return hw;
        }
    }

    return NULL;
}

static HW *glue (audio_pcm_hw_add_, TYPE) (
    int freq,
    int nchannels,
    audfmt_e fmt
    )
{
    HW *hw;

    if (glue (audio_state.greedy_, TYPE)) {
        hw = glue (audio_pcm_hw_add_new_, TYPE) (freq, nchannels, fmt);
        if (hw) {
            return hw;
        }
    }

    hw = glue (audio_pcm_hw_find_specific_, TYPE) (NULL, freq, nchannels, fmt);
    if (hw) {
        return hw;
    }

    hw = glue (audio_pcm_hw_add_new_, TYPE) (freq, nchannels, fmt);
    if (hw) {
        return hw;
    }

    return glue (audio_pcm_hw_find_any_active_, TYPE) (NULL);
}

static SW *glue (audio_pcm_create_voice_pair_, TYPE) (
    const char *name,
    int freq,
    int nchannels,
    audfmt_e fmt
    )
{
    SW *sw;
    HW *hw;
    int hw_freq = freq;
    int hw_nchannels = nchannels;
    int hw_fmt = fmt;

    if (glue (audio_state.fixed_settings_, TYPE)) {
        hw_freq = glue (audio_state.fixed_freq_, TYPE);
        hw_nchannels = glue (audio_state.fixed_channels_, TYPE);
        hw_fmt = glue (audio_state.fixed_fmt_, TYPE);
    }

    sw = qemu_mallocz (sizeof (*sw));
    if (!sw) {
        goto err1;
    }

    hw = glue (audio_pcm_hw_add_, TYPE) (hw_freq, hw_nchannels, hw_fmt);
    if (!hw) {
        goto err2;
    }

    glue (audio_pcm_hw_add_sw_, TYPE) (hw, sw);

    if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, name, freq, nchannels, fmt)) {
        goto err3;
    }

    return sw;

err3:
    glue (audio_pcm_hw_del_sw_, TYPE) (sw);
    glue (audio_pcm_hw_gc_, TYPE) (hw);
err2:
    qemu_free (sw);
err1:
    return NULL;
}

void glue (AUD_close_, TYPE) (SW *sw)
{
    if (sw) {
        glue (audio_pcm_sw_fini_, TYPE) (sw);
        glue (audio_pcm_hw_del_sw_, TYPE) (sw);
        glue (audio_pcm_hw_gc_, TYPE) (sw->hw);
        qemu_free (sw);
    }
}

SW *glue (AUD_open_, TYPE) (
    SW *sw,
    const char *name,
    void *callback_opaque ,
    audio_callback_fn_t callback_fn,
    int freq,
    int nchannels,
    audfmt_e fmt
    )
{
#ifdef DAC
    int live = 0;
    SW *old_sw = NULL;
#endif

    if (!callback_fn) {
        dolog ("No callback specifed for voice `%s'\n", name);
        goto fail;
    }

    if (nchannels != 1 && nchannels != 2) {
        dolog ("Bogus channel count %d for voice `%s'\n", nchannels, name);
        goto fail;
    }

    if (!audio_state.drv) {
        dolog ("No audio driver defined\n");
        goto fail;
    }

    if (sw && audio_pcm_info_eq (&sw->info, freq, nchannels, fmt)) {
        return sw;
    }

#ifdef DAC
    if (audio_state.plive && sw && (!sw->active && !sw->empty)) {
        live = sw->total_hw_samples_mixed;

#ifdef DEBUG_PLIVE
        dolog ("Replacing voice %s with %d live samples\n", sw->name, live);
        dolog ("Old %s freq %d, bits %d, channels %d\n",
               sw->name, sw->info.freq, sw->info.bits, sw->info.nchannels);
        dolog ("New %s freq %d, bits %d, channels %d\n",
               name, freq, (fmt == AUD_FMT_S16 || fmt == AUD_FMT_U16) ? 16 : 8,
               nchannels);
#endif

        if (live) {
            old_sw = sw;
            old_sw->callback.fn = NULL;
            sw = NULL;
        }
    }
#endif

    if (!glue (audio_state.fixed_settings_, TYPE) && sw) {
        glue (AUD_close_, TYPE) (sw);
        sw = NULL;
    }

    if (sw) {
        HW *hw = sw->hw;

        if (!hw) {
            dolog ("Internal logic error voice %s has no hardware store\n",
                   name);
            goto fail;
        }

        if (glue (audio_pcm_sw_init_, TYPE) (
                sw,
                hw,
                name,
                freq,
                nchannels,
                fmt
                )) {
            goto fail;
        }
    }
    else {
        sw = glue (audio_pcm_create_voice_pair_, TYPE) (
            name,
            freq,
            nchannels,
            fmt);
        if (!sw) {
            dolog ("Failed to create voice %s\n", name);
            goto fail;
        }
    }

    if (sw) {
        sw->vol = nominal_volume;
        sw->callback.fn = callback_fn;
        sw->callback.opaque = callback_opaque;

#ifdef DAC
        if (live) {
            int mixed =
                (live << old_sw->info.shift)
                * old_sw->info.bytes_per_second
                / sw->info.bytes_per_second;

#ifdef DEBUG_PLIVE
            dolog ("Silence will be mixed %d\n", mixed);
#endif
            sw->total_hw_samples_mixed += mixed;
        }
#endif

#ifdef DEBUG_AUDIO
        dolog ("%s\n", name);
        audio_pcm_print_info ("hw", &sw->hw->info);
        audio_pcm_print_info ("sw", &sw->info);
#endif
    }

    return sw;

 fail:
    glue (AUD_close_, TYPE) (sw);
    return NULL;
}

int glue (AUD_is_active_, TYPE) (SW *sw)
{
    return sw ? sw->active : 0;
}

void glue (AUD_init_time_stamp_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts)
{
    if (!sw) {
        return;
    }

    ts->old_ts = sw->hw->ts_helper;
}

uint64_t glue (AUD_time_stamp_get_elapsed_usec_, TYPE) (
    SW *sw,
    QEMUAudioTimeStamp *ts
    )
{
    uint64_t delta, cur_ts, old_ts;

    if (!sw) {
        return 0;
    }

    cur_ts = sw->hw->ts_helper;
    old_ts = ts->old_ts;
    /* dolog ("cur %lld old %lld\n", cur_ts, old_ts); */

    if (cur_ts >= old_ts) {
        delta = cur_ts - old_ts;
    }
    else {
        delta = UINT64_MAX - old_ts + cur_ts;
    }

    if (!delta) {
        return 0;
    }

    return (delta * sw->hw->info.freq) / 1000000;
}

#undef TYPE
#undef HW
#undef SW