diff options
Diffstat (limited to 'audio')
| -rw-r--r-- | audio/Makefile.objs | 13 | ||||
| -rw-r--r-- | audio/alsaaudio.c | 1227 | ||||
| -rw-r--r-- | audio/audio.c | 2063 | ||||
| -rw-r--r-- | audio/audio.h | 166 | ||||
| -rw-r--r-- | audio/audio_int.h | 260 | ||||
| -rw-r--r-- | audio/audio_pt_int.c | 173 | ||||
| -rw-r--r-- | audio/audio_pt_int.h | 22 | ||||
| -rw-r--r-- | audio/audio_template.h | 514 | ||||
| -rw-r--r-- | audio/audio_win_int.c | 107 | ||||
| -rw-r--r-- | audio/audio_win_int.h | 10 | ||||
| -rw-r--r-- | audio/coreaudio.c | 555 | ||||
| -rw-r--r-- | audio/dsound_template.h | 278 | ||||
| -rw-r--r-- | audio/dsoundaudio.c | 904 | ||||
| -rw-r--r-- | audio/mixeng.c | 366 | ||||
| -rw-r--r-- | audio/mixeng.h | 51 | ||||
| -rw-r--r-- | audio/mixeng_template.h | 154 | ||||
| -rw-r--r-- | audio/noaudio.c | 173 | ||||
| -rw-r--r-- | audio/ossaudio.c | 941 | ||||
| -rw-r--r-- | audio/paaudio.c | 953 | ||||
| -rw-r--r-- | audio/rate_template.h | 111 | ||||
| -rw-r--r-- | audio/sdlaudio.c | 466 | ||||
| -rw-r--r-- | audio/spiceaudio.c | 411 | ||||
| -rw-r--r-- | audio/wavaudio.c | 292 | ||||
| -rw-r--r-- | audio/wavcapture.c | 194 | 
24 files changed, 10404 insertions, 0 deletions
diff --git a/audio/Makefile.objs b/audio/Makefile.objs new file mode 100644 index 00000000..481d1aa3 --- /dev/null +++ b/audio/Makefile.objs @@ -0,0 +1,13 @@ +common-obj-y = audio.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 +common-obj-$(CONFIG_COREAUDIO) += coreaudio.o +common-obj-$(CONFIG_ALSA) += alsaaudio.o +common-obj-$(CONFIG_DSOUND) += dsoundaudio.o +common-obj-$(CONFIG_PA) += paaudio.o +common-obj-$(CONFIG_AUDIO_PT_INT) += audio_pt_int.o +common-obj-$(CONFIG_AUDIO_WIN_INT) += audio_win_int.o +common-obj-y += wavcapture.o + +sdlaudio.o-cflags := $(SDL_CFLAGS) diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c new file mode 100644 index 00000000..6315b2d7 --- /dev/null +++ b/audio/alsaaudio.c @@ -0,0 +1,1227 @@ +/* + * QEMU ALSA audio driver + * + * 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. + */ +#include <alsa/asoundlib.h> +#include "qemu-common.h" +#include "qemu/main-loop.h" +#include "audio.h" +#include "trace.h" + +#if QEMU_GNUC_PREREQ(4, 3) +#pragma GCC diagnostic ignored "-Waddress" +#endif + +#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; +}; + +typedef struct ALSAVoiceOut { +    HWVoiceOut hw; +    int wpos; +    int pending; +    void *pcm_buf; +    snd_pcm_t *handle; +    struct pollhlp pollhlp; +} ALSAVoiceOut; + +typedef struct ALSAVoiceIn { +    HWVoiceIn hw; +    snd_pcm_t *handle; +    void *pcm_buf; +    struct pollhlp pollhlp; +} 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 { +    int freq; +    audfmt_e fmt; +    int endianness; +    int nchannels; +    snd_pcm_uframes_t samples; +}; + +static void GCC_FMT_ATTR (2, 3) alsa_logerr (int err, const char *fmt, ...) +{ +    va_list ap; + +    va_start (ap, fmt); +    AUD_vlog (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); +} + +static void GCC_FMT_ATTR (3, 4) alsa_logerr2 ( +    int err, +    const char *typ, +    const char *fmt, +    ... +    ) +{ +    va_list ap; + +    AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + +    va_start (ap, fmt); +    AUD_vlog (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); +} + +static void alsa_fini_poll (struct pollhlp *hlp) +{ +    int i; +    struct pollfd *pfds = hlp->pfds; + +    if (pfds) { +        for (i = 0; i < hlp->count; ++i) { +            qemu_set_fd_handler (pfds[i].fd, NULL, NULL, NULL); +        } +        g_free (pfds); +    } +    hlp->pfds = NULL; +    hlp->count = 0; +    hlp->handle = NULL; +} + +static void alsa_anal_close1 (snd_pcm_t **handlep) +{ +    int err = snd_pcm_close (*handlep); +    if (err) { +        alsa_logerr (err, "Failed to close PCM handle %p\n", *handlep); +    } +    *handlep = NULL; +} + +static void alsa_anal_close (snd_pcm_t **handlep, struct pollhlp *hlp) +{ +    alsa_fini_poll (hlp); +    alsa_anal_close1 (handlep); +} + +static int alsa_recover (snd_pcm_t *handle) +{ +    int err = snd_pcm_prepare (handle); +    if (err < 0) { +        alsa_logerr (err, "Failed to prepare handle %p\n", handle); +        return -1; +    } +    return 0; +} + +static int alsa_resume (snd_pcm_t *handle) +{ +    int err = snd_pcm_resume (handle); +    if (err < 0) { +        alsa_logerr (err, "Failed to resume handle %p\n", handle); +        return -1; +    } +    return 0; +} + +static void alsa_poll_handler (void *opaque) +{ +    int err, count; +    snd_pcm_state_t state; +    struct pollhlp *hlp = opaque; +    unsigned short revents; + +    count = poll (hlp->pfds, hlp->count, 0); +    if (count < 0) { +        dolog ("alsa_poll_handler: poll %s\n", strerror (errno)); +        return; +    } + +    if (!count) { +        return; +    } + +    /* XXX: ALSA example uses initial count, not the one returned by +       poll, correct? */ +    err = snd_pcm_poll_descriptors_revents (hlp->handle, hlp->pfds, +                                            hlp->count, &revents); +    if (err < 0) { +        alsa_logerr (err, "snd_pcm_poll_descriptors_revents"); +        return; +    } + +    if (!(revents & hlp->mask)) { +        trace_alsa_revents(revents); +        return; +    } + +    state = snd_pcm_state (hlp->handle); +    switch (state) { +    case SND_PCM_STATE_SETUP: +        alsa_recover (hlp->handle); +        break; + +    case SND_PCM_STATE_XRUN: +        alsa_recover (hlp->handle); +        break; + +    case SND_PCM_STATE_SUSPENDED: +        alsa_resume (hlp->handle); +        break; + +    case SND_PCM_STATE_PREPARED: +        audio_run ("alsa run (prepared)"); +        break; + +    case SND_PCM_STATE_RUNNING: +        audio_run ("alsa run (running)"); +        break; + +    default: +        dolog ("Unexpected state %d\n", state); +    } +} + +static int alsa_poll_helper (snd_pcm_t *handle, struct pollhlp *hlp, int mask) +{ +    int i, count, err; +    struct pollfd *pfds; + +    count = snd_pcm_poll_descriptors_count (handle); +    if (count <= 0) { +        dolog ("Could not initialize poll mode\n" +               "Invalid number of poll descriptors %d\n", count); +        return -1; +    } + +    pfds = audio_calloc ("alsa_poll_helper", count, sizeof (*pfds)); +    if (!pfds) { +        dolog ("Could not initialize poll mode\n"); +        return -1; +    } + +    err = snd_pcm_poll_descriptors (handle, pfds, count); +    if (err < 0) { +        alsa_logerr (err, "Could not initialize poll mode\n" +                     "Could not obtain poll descriptors\n"); +        g_free (pfds); +        return -1; +    } + +    for (i = 0; i < count; ++i) { +        if (pfds[i].events & POLLIN) { +            qemu_set_fd_handler (pfds[i].fd, alsa_poll_handler, NULL, hlp); +        } +        if (pfds[i].events & POLLOUT) { +            trace_alsa_pollout(i, pfds[i].fd); +            qemu_set_fd_handler (pfds[i].fd, NULL, alsa_poll_handler, hlp); +        } +        trace_alsa_set_handler(pfds[i].events, i, pfds[i].fd, err); + +    } +    hlp->pfds = pfds; +    hlp->count = count; +    hlp->handle = handle; +    hlp->mask = mask; +    return 0; +} + +static int alsa_poll_out (HWVoiceOut *hw) +{ +    ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + +    return alsa_poll_helper (alsa->handle, &alsa->pollhlp, POLLOUT); +} + +static int alsa_poll_in (HWVoiceIn *hw) +{ +    ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + +    return alsa_poll_helper (alsa->handle, &alsa->pollhlp, POLLIN); +} + +static int alsa_write (SWVoiceOut *sw, void *buf, int len) +{ +    return audio_pcm_sw_write (sw, buf, len); +} + +static snd_pcm_format_t aud_to_alsafmt (audfmt_e fmt, int endianness) +{ +    switch (fmt) { +    case AUD_FMT_S8: +        return SND_PCM_FORMAT_S8; + +    case AUD_FMT_U8: +        return SND_PCM_FORMAT_U8; + +    case AUD_FMT_S16: +        if (endianness) { +            return SND_PCM_FORMAT_S16_BE; +        } +        else { +            return SND_PCM_FORMAT_S16_LE; +        } + +    case AUD_FMT_U16: +        if (endianness) { +            return SND_PCM_FORMAT_U16_BE; +        } +        else { +            return SND_PCM_FORMAT_U16_LE; +        } + +    case AUD_FMT_S32: +        if (endianness) { +            return SND_PCM_FORMAT_S32_BE; +        } +        else { +            return SND_PCM_FORMAT_S32_LE; +        } + +    case AUD_FMT_U32: +        if (endianness) { +            return SND_PCM_FORMAT_U32_BE; +        } +        else { +            return SND_PCM_FORMAT_U32_LE; +        } + +    default: +        dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO +        abort (); +#endif +        return SND_PCM_FORMAT_U8; +    } +} + +static int alsa_to_audfmt (snd_pcm_format_t alsafmt, audfmt_e *fmt, +                           int *endianness) +{ +    switch (alsafmt) { +    case SND_PCM_FORMAT_S8: +        *endianness = 0; +        *fmt = AUD_FMT_S8; +        break; + +    case SND_PCM_FORMAT_U8: +        *endianness = 0; +        *fmt = AUD_FMT_U8; +        break; + +    case SND_PCM_FORMAT_S16_LE: +        *endianness = 0; +        *fmt = AUD_FMT_S16; +        break; + +    case SND_PCM_FORMAT_U16_LE: +        *endianness = 0; +        *fmt = AUD_FMT_U16; +        break; + +    case SND_PCM_FORMAT_S16_BE: +        *endianness = 1; +        *fmt = AUD_FMT_S16; +        break; + +    case SND_PCM_FORMAT_U16_BE: +        *endianness = 1; +        *fmt = AUD_FMT_U16; +        break; + +    case SND_PCM_FORMAT_S32_LE: +        *endianness = 0; +        *fmt = AUD_FMT_S32; +        break; + +    case SND_PCM_FORMAT_U32_LE: +        *endianness = 0; +        *fmt = AUD_FMT_U32; +        break; + +    case SND_PCM_FORMAT_S32_BE: +        *endianness = 1; +        *fmt = AUD_FMT_S32; +        break; + +    case SND_PCM_FORMAT_U32_BE: +        *endianness = 1; +        *fmt = AUD_FMT_U32; +        break; + +    default: +        dolog ("Unrecognized audio format %d\n", alsafmt); +        return -1; +    } + +    return 0; +} + +static void alsa_dump_info (struct alsa_params_req *req, +                            struct alsa_params_obt *obt, +                            snd_pcm_format_t obtfmt) +{ +    dolog ("parameter | requested value | obtained value\n"); +    dolog ("format    |      %10d |     %10d\n", req->fmt, obtfmt); +    dolog ("channels  |      %10d |     %10d\n", +           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 ("obtained: samples %ld\n", obt->samples); +} + +static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) +{ +    int err; +    snd_pcm_sw_params_t *sw_params; + +    snd_pcm_sw_params_alloca (&sw_params); + +    err = snd_pcm_sw_params_current (handle, sw_params); +    if (err < 0) { +        dolog ("Could not fully initialize DAC\n"); +        alsa_logerr (err, "Failed to get current software parameters\n"); +        return; +    } + +    err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, threshold); +    if (err < 0) { +        dolog ("Could not fully initialize DAC\n"); +        alsa_logerr (err, "Failed to set software threshold to %ld\n", +                     threshold); +        return; +    } + +    err = snd_pcm_sw_params (handle, sw_params); +    if (err < 0) { +        dolog ("Could not fully initialize DAC\n"); +        alsa_logerr (err, "Failed to set software parameters\n"); +        return; +    } +} + +static int alsa_open (int in, struct alsa_params_req *req, +                      struct alsa_params_obt *obt, snd_pcm_t **handlep, +                      ALSAConf *conf) +{ +    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; +    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); + +    err = snd_pcm_open ( +        &handle, +        pcm_name, +        in ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, +        SND_PCM_NONBLOCK +        ); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Failed to open `%s':\n", pcm_name); +        return -1; +    } + +    err = snd_pcm_hw_params_any (handle, hw_params); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Failed to initialize hardware parameters\n"); +        goto err; +    } + +    err = snd_pcm_hw_params_set_access ( +        handle, +        hw_params, +        SND_PCM_ACCESS_RW_INTERLEAVED +        ); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Failed to set access type\n"); +        goto err; +    } + +    err = snd_pcm_hw_params_set_format (handle, hw_params, req->fmt); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Failed to set format %d\n", req->fmt); +    } + +    err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &freq, 0); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Failed to set frequency %d\n", req->freq); +        goto err; +    } + +    err = snd_pcm_hw_params_set_channels_near ( +        handle, +        hw_params, +        &nchannels +        ); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Failed to set number of channels %d\n", +                      req->nchannels); +        goto err; +    } + +    if (nchannels != 1 && nchannels != 2) { +        alsa_logerr2 (err, typ, +                      "Can not handle obtained number of channels %d\n", +                      nchannels); +        goto err; +    } + +    if (req->buffer_size) { +        unsigned long obt; + +        if (size_in_usec) { +            int dir = 0; +            unsigned int btime = req->buffer_size; + +            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_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 ((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 (req->period_size) { +        unsigned long obt; + +        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; +        } + +        if (err < 0) { +            alsa_logerr2 (err, typ, "Failed to set period %s to %d\n", +                          size_in_usec ? "time" : "size", req->period_size); +            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); +    } + +    err = snd_pcm_hw_params (handle, hw_params); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Failed to apply audio parameters\n"); +        goto err; +    } + +    err = snd_pcm_hw_params_get_buffer_size (hw_params, &obt_buffer_size); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Failed to get buffer size\n"); +        goto err; +    } + +    err = snd_pcm_hw_params_get_format (hw_params, &obtfmt); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Failed to get format\n"); +        goto err; +    } + +    if (alsa_to_audfmt (obtfmt, &obt->fmt, &obt->endianness)) { +        dolog ("Invalid format was returned %d\n", obtfmt); +        goto err; +    } + +    err = snd_pcm_prepare (handle); +    if (err < 0) { +        alsa_logerr2 (err, typ, "Could not prepare handle %p\n", handle); +        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 AUD_FMT_S8: +        case AUD_FMT_U8: +            break; + +        case AUD_FMT_S16: +        case AUD_FMT_U16: +            bytes_per_sec <<= 1; +            break; + +        case AUD_FMT_S32: +        case AUD_FMT_U32: +            bytes_per_sec <<= 2; +            break; +        } + +        threshold = (conf->threshold * bytes_per_sec) / 1000; +        alsa_set_threshold (handle, threshold); +    } + +    obt->nchannels = nchannels; +    obt->freq = freq; +    obt->samples = obt_buffer_size; + +    *handlep = handle; + +    if (obtfmt != req->fmt || +         obt->nchannels != req->nchannels || +         obt->freq != req->freq) { +        dolog ("Audio parameters for %s\n", typ); +        alsa_dump_info (req, obt, obtfmt); +    } + +#ifdef DEBUG +    alsa_dump_info (req, obt, obtfmt); +#endif +    return 0; + + err: +    alsa_anal_close1 (&handle); +    return -1; +} + +static snd_pcm_sframes_t alsa_get_avail (snd_pcm_t *handle) +{ +    snd_pcm_sframes_t avail; + +    avail = snd_pcm_avail_update (handle); +    if (avail < 0) { +        if (avail == -EPIPE) { +            if (!alsa_recover (handle)) { +                avail = snd_pcm_avail_update (handle); +            } +        } + +        if (avail < 0) { +            alsa_logerr (avail, +                         "Could not obtain number of available frames\n"); +            return -1; +        } +    } + +    return avail; +} + +static void alsa_write_pending (ALSAVoiceOut *alsa) +{ +    HWVoiceOut *hw = &alsa->hw; + +    while (alsa->pending) { +        int left_till_end_samples = hw->samples - alsa->wpos; +        int len = audio_MIN (alsa->pending, left_till_end_samples); +        char *src = advance (alsa->pcm_buf, alsa->wpos << hw->info.shift); + +        while (len) { +            snd_pcm_sframes_t written; + +            written = snd_pcm_writei (alsa->handle, src, len); + +            if (written <= 0) { +                switch (written) { +                case 0: +                    trace_alsa_wrote_zero(len); +                    return; + +                case -EPIPE: +                    if (alsa_recover (alsa->handle)) { +                        alsa_logerr (written, "Failed to write %d frames\n", +                                     len); +                        return; +                    } +                    trace_alsa_xrun_out(); +                    continue; + +                case -ESTRPIPE: +                    /* stream is suspended and waiting for an +                       application recovery */ +                    if (alsa_resume (alsa->handle)) { +                        alsa_logerr (written, "Failed to write %d frames\n", +                                     len); +                        return; +                    } +                    trace_alsa_resume_out(); +                    continue; + +                case -EAGAIN: +                    return; + +                default: +                    alsa_logerr (written, "Failed to write %d frames from %p\n", +                                 len, src); +                    return; +                } +            } + +            alsa->wpos = (alsa->wpos + written) % hw->samples; +            alsa->pending -= written; +            len -= written; +        } +    } +} + +static int alsa_run_out (HWVoiceOut *hw, int live) +{ +    ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; +    int decr; +    snd_pcm_sframes_t avail; + +    avail = alsa_get_avail (alsa->handle); +    if (avail < 0) { +        dolog ("Could not get number of available playback frames\n"); +        return 0; +    } + +    decr = audio_MIN (live, avail); +    decr = audio_pcm_hw_clip_out (hw, alsa->pcm_buf, decr, alsa->pending); +    alsa->pending += decr; +    alsa_write_pending (alsa); +    return decr; +} + +static void alsa_fini_out (HWVoiceOut *hw) +{ +    ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + +    ldebug ("alsa_fini\n"); +    alsa_anal_close (&alsa->handle, &alsa->pollhlp); + +    g_free(alsa->pcm_buf); +    alsa->pcm_buf = NULL; +} + +static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as, +                         void *drv_opaque) +{ +    ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; +    struct alsa_params_req req; +    struct alsa_params_obt obt; +    snd_pcm_t *handle; +    struct audsettings obt_as; +    ALSAConf *conf = 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)) { +        return -1; +    } + +    obt_as.freq = obt.freq; +    obt_as.nchannels = obt.nchannels; +    obt_as.fmt = obt.fmt; +    obt_as.endianness = obt.endianness; + +    audio_pcm_init_info (&hw->info, &obt_as); +    hw->samples = obt.samples; + +    alsa->pcm_buf = audio_calloc (AUDIO_FUNC, obt.samples, 1 << hw->info.shift); +    if (!alsa->pcm_buf) { +        dolog ("Could not allocate DAC buffer (%d samples, each %d bytes)\n", +               hw->samples, 1 << hw->info.shift); +        alsa_anal_close1 (&handle); +        return -1; +    } + +    alsa->handle = handle; +    alsa->pollhlp.conf = conf; +    return 0; +} + +#define VOICE_CTL_PAUSE 0 +#define VOICE_CTL_PREPARE 1 +#define VOICE_CTL_START 2 + +static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int ctl) +{ +    int err; + +    if (ctl == VOICE_CTL_PAUSE) { +        err = snd_pcm_drop (handle); +        if (err < 0) { +            alsa_logerr (err, "Could not stop %s\n", typ); +            return -1; +        } +    } +    else { +        err = snd_pcm_prepare (handle); +        if (err < 0) { +            alsa_logerr (err, "Could not prepare handle for %s\n", typ); +            return -1; +        } +        if (ctl == VOICE_CTL_START) { +            err = snd_pcm_start(handle); +            if (err < 0) { +                alsa_logerr (err, "Could not start handle for %s\n", typ); +                return -1; +            } +        } +    } + +    return 0; +} + +static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ +    ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + +    switch (cmd) { +    case VOICE_ENABLE: +        { +            va_list ap; +            int poll_mode; + +            va_start (ap, cmd); +            poll_mode = va_arg (ap, int); +            va_end (ap); + +            ldebug ("enabling voice\n"); +            if (poll_mode && alsa_poll_out (hw)) { +                poll_mode = 0; +            } +            hw->poll_mode = poll_mode; +            return alsa_voice_ctl (alsa->handle, "playback", VOICE_CTL_PREPARE); +        } + +    case VOICE_DISABLE: +        ldebug ("disabling voice\n"); +        if (hw->poll_mode) { +            hw->poll_mode = 0; +            alsa_fini_poll (&alsa->pollhlp); +        } +        return alsa_voice_ctl (alsa->handle, "playback", VOICE_CTL_PAUSE); +    } + +    return -1; +} + +static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +{ +    ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; +    struct alsa_params_req req; +    struct alsa_params_obt obt; +    snd_pcm_t *handle; +    struct audsettings obt_as; +    ALSAConf *conf = 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)) { +        return -1; +    } + +    obt_as.freq = obt.freq; +    obt_as.nchannels = obt.nchannels; +    obt_as.fmt = obt.fmt; +    obt_as.endianness = obt.endianness; + +    audio_pcm_init_info (&hw->info, &obt_as); +    hw->samples = obt.samples; + +    alsa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); +    if (!alsa->pcm_buf) { +        dolog ("Could not allocate ADC buffer (%d samples, each %d bytes)\n", +               hw->samples, 1 << hw->info.shift); +        alsa_anal_close1 (&handle); +        return -1; +    } + +    alsa->handle = handle; +    alsa->pollhlp.conf = conf; +    return 0; +} + +static void alsa_fini_in (HWVoiceIn *hw) +{ +    ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + +    alsa_anal_close (&alsa->handle, &alsa->pollhlp); + +    g_free(alsa->pcm_buf); +    alsa->pcm_buf = NULL; +} + +static int alsa_run_in (HWVoiceIn *hw) +{ +    ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; +    int hwshift = hw->info.shift; +    int i; +    int live = audio_pcm_hw_get_live_in (hw); +    int dead = hw->samples - live; +    int decr; +    struct { +        int add; +        int len; +    } bufs[2] = { +        { .add = hw->wpos, .len = 0 }, +        { .add = 0,        .len = 0 } +    }; +    snd_pcm_sframes_t avail; +    snd_pcm_uframes_t read_samples = 0; + +    if (!dead) { +        return 0; +    } + +    avail = alsa_get_avail (alsa->handle); +    if (avail < 0) { +        dolog ("Could not get number of captured frames\n"); +        return 0; +    } + +    if (!avail) { +        snd_pcm_state_t state; + +        state = snd_pcm_state (alsa->handle); +        switch (state) { +        case SND_PCM_STATE_PREPARED: +            avail = hw->samples; +            break; +        case SND_PCM_STATE_SUSPENDED: +            /* stream is suspended and waiting for an application recovery */ +            if (alsa_resume (alsa->handle)) { +                dolog ("Failed to resume suspended input stream\n"); +                return 0; +            } +            trace_alsa_resume_in(); +            break; +        default: +            trace_alsa_no_frames(state); +            return 0; +        } +    } + +    decr = audio_MIN (dead, avail); +    if (!decr) { +        return 0; +    } + +    if (hw->wpos + decr > hw->samples) { +        bufs[0].len = (hw->samples - hw->wpos); +        bufs[1].len = (decr - (hw->samples - hw->wpos)); +    } +    else { +        bufs[0].len = decr; +    } + +    for (i = 0; i < 2; ++i) { +        void *src; +        struct st_sample *dst; +        snd_pcm_sframes_t nread; +        snd_pcm_uframes_t len; + +        len = bufs[i].len; + +        src = advance (alsa->pcm_buf, bufs[i].add << hwshift); +        dst = hw->conv_buf + bufs[i].add; + +        while (len) { +            nread = snd_pcm_readi (alsa->handle, src, len); + +            if (nread <= 0) { +                switch (nread) { +                case 0: +                    trace_alsa_read_zero(len); +                    goto exit; + +                case -EPIPE: +                    if (alsa_recover (alsa->handle)) { +                        alsa_logerr (nread, "Failed to read %ld frames\n", len); +                        goto exit; +                    } +                    trace_alsa_xrun_in(); +                    continue; + +                case -EAGAIN: +                    goto exit; + +                default: +                    alsa_logerr ( +                        nread, +                        "Failed to read %ld frames from %p\n", +                        len, +                        src +                        ); +                    goto exit; +                } +            } + +            hw->conv (dst, src, nread); + +            src = advance (src, nread << hwshift); +            dst += nread; + +            read_samples += nread; +            len -= nread; +        } +    } + + exit: +    hw->wpos = (hw->wpos + read_samples) % hw->samples; +    return read_samples; +} + +static int alsa_read (SWVoiceIn *sw, void *buf, int size) +{ +    return audio_pcm_sw_read (sw, buf, size); +} + +static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ +    ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + +    switch (cmd) { +    case VOICE_ENABLE: +        { +            va_list ap; +            int poll_mode; + +            va_start (ap, cmd); +            poll_mode = va_arg (ap, int); +            va_end (ap); + +            ldebug ("enabling voice\n"); +            if (poll_mode && alsa_poll_in (hw)) { +                poll_mode = 0; +            } +            hw->poll_mode = poll_mode; + +            return alsa_voice_ctl (alsa->handle, "capture", VOICE_CTL_START); +        } + +    case VOICE_DISABLE: +        ldebug ("disabling voice\n"); +        if (hw->poll_mode) { +            hw->poll_mode = 0; +            alsa_fini_poll (&alsa->pollhlp); +        } +        return alsa_voice_ctl (alsa->handle, "capture", VOICE_CTL_PAUSE); +    } + +    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) +{ +    ALSAConf *conf = g_malloc(sizeof(ALSAConf)); +    *conf = glob_conf; +    return conf; +} + +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, +    .run_out  = alsa_run_out, +    .write    = alsa_write, +    .ctl_out  = alsa_ctl_out, + +    .init_in  = alsa_init_in, +    .fini_in  = alsa_fini_in, +    .run_in   = alsa_run_in, +    .read     = alsa_read, +    .ctl_in   = alsa_ctl_in, +}; + +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, +    .can_be_default = 1, +    .max_voices_out = INT_MAX, +    .max_voices_in  = INT_MAX, +    .voice_size_out = sizeof (ALSAVoiceOut), +    .voice_size_in  = sizeof (ALSAVoiceIn) +}; diff --git a/audio/audio.c b/audio/audio.c new file mode 100644 index 00000000..5be4b15f --- /dev/null +++ b/audio/audio.c @@ -0,0 +1,2063 @@ +/* + * QEMU Audio subsystem + * + * Copyright (c) 2003-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. + */ +#include "hw/hw.h" +#include "audio.h" +#include "monitor/monitor.h" +#include "qemu/timer.h" +#include "sysemu/sysemu.h" + +#define AUDIO_CAP "audio" +#include "audio_int.h" + +/* #define DEBUG_LIVE */ +/* #define DEBUG_OUT */ +/* #define DEBUG_CAPTURE */ +/* #define DEBUG_POLL */ + +#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown" + + +/* Order of CONFIG_AUDIO_DRIVERS is import. +   The 1st one is the one used by default, that is the reason +    that we generate the list. +*/ +static 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 = AUD_FMT_S16, +            .endianness =  AUDIO_HOST_ENDIANNESS, +        } +    }, + +    .fixed_in = { /* ADC fixed settings */ +        .enabled = 1, +        .nb_voices = 1, +        .greedy = 1, +        .settings = { +            .freq = 44100, +            .nchannels = 2, +            .fmt = AUD_FMT_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 = { +    .mute = 0, +#ifdef FLOAT_MIXENG +    .r = 1.0, +    .l = 1.0, +#else +    .r = 1ULL << 32, +    .l = 1ULL << 32, +#endif +}; + +#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) { +        static int shown; + +        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"); + +#if defined AUDIO_BREAKPOINT_ON_BUG +#  if defined HOST_I386 +#    if defined __GNUC__ +        __asm__ ("int3"); +#    elif defined _MSC_VER +        _asm _emit 0xcc; +#    else +        abort (); +#    endif +#  else +        abort (); +#  endif +#endif +    } + +    return cond; +} +#endif + +static inline int audio_bits_to_index (int bits) +{ +    switch (bits) { +    case 8: +        return 0; + +    case 16: +        return 1; + +    case 32: +        return 2; + +    default: +        audio_bug ("bits_to_index", 1); +        AUD_log (NULL, "invalid bits %d\n", bits); +        return 0; +    } +} + +void *audio_calloc (const char *funcname, int nmemb, size_t size) +{ +    int cond; +    size_t len; + +    len = nmemb * size; +    cond = !nmemb || !size; +    cond |= nmemb < 0; +    cond |= len < size; + +    if (audio_bug ("audio_calloc", cond)) { +        AUD_log (NULL, "%s passed invalid arguments to audio_calloc\n", +                 funcname); +        AUD_log (NULL, "nmemb=%d size=%zu (len=%zu)\n", nmemb, size, len); +        return NULL; +    } + +    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 (audfmt_e fmt) +{ +    switch (fmt) { +    case AUD_FMT_U8: +        return "U8"; + +    case AUD_FMT_U16: +        return "U16"; + +    case AUD_FMT_S8: +        return "S8"; + +    case AUD_FMT_S16: +        return "S16"; + +    case AUD_FMT_U32: +        return "U32"; + +    case AUD_FMT_S32: +        return "S32"; +    } + +    dolog ("Bogus audfmt %d returning S16\n", fmt); +    return "S16"; +} + +static audfmt_e audio_string_to_audfmt (const char *s, audfmt_e defval, +                                        int *defaultp) +{ +    if (!strcasecmp (s, "u8")) { +        *defaultp = 0; +        return AUD_FMT_U8; +    } +    else if (!strcasecmp (s, "u16")) { +        *defaultp = 0; +        return AUD_FMT_U16; +    } +    else if (!strcasecmp (s, "u32")) { +        *defaultp = 0; +        return AUD_FMT_U32; +    } +    else if (!strcasecmp (s, "s8")) { +        *defaultp = 0; +        return AUD_FMT_S8; +    } +    else if (!strcasecmp (s, "s16")) { +        *defaultp = 0; +        return AUD_FMT_S16; +    } +    else if (!strcasecmp (s, "s32")) { +        *defaultp = 0; +        return AUD_FMT_S32; +    } +    else { +        dolog ("Bogus audio format `%s' using %s\n", +               s, audio_audfmt_to_string (defval)); +        *defaultp = 1; +        return defval; +    } +} + +static audfmt_e audio_get_conf_fmt (const char *envname, +                                    audfmt_e 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) { +        fprintf(stderr, "%s: ", cap); +    } + +    vfprintf(stderr, fmt, ap); +} + +void AUD_log (const char *cap, const char *fmt, ...) +{ +    va_list ap; + +    va_start (ap, fmt); +    AUD_vlog (cap, fmt, ap); +    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: +            { +                audfmt_e *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: +            { +                audfmt_e *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); + +    switch (as->fmt) { +    case AUD_FMT_S8: +        AUD_log (NULL, "S8"); +        break; +    case AUD_FMT_U8: +        AUD_log (NULL, "U8"); +        break; +    case AUD_FMT_S16: +        AUD_log (NULL, "S16"); +        break; +    case AUD_FMT_U16: +        AUD_log (NULL, "U16"); +        break; +    case AUD_FMT_S32: +        AUD_log (NULL, "S32"); +        break; +    case AUD_FMT_U32: +        AUD_log (NULL, "U32"); +        break; +    default: +        AUD_log (NULL, "invalid(%d)", as->fmt); +        break; +    } + +    AUD_log (NULL, " endianness="); +    switch (as->endianness) { +    case 0: +        AUD_log (NULL, "little"); +        break; +    case 1: +        AUD_log (NULL, "big"); +        break; +    default: +        AUD_log (NULL, "invalid"); +        break; +    } +    AUD_log (NULL, "\n"); +} + +static int audio_validate_settings (struct audsettings *as) +{ +    int invalid; + +    invalid = as->nchannels != 1 && as->nchannels != 2; +    invalid |= as->endianness != 0 && as->endianness != 1; + +    switch (as->fmt) { +    case AUD_FMT_S8: +    case AUD_FMT_U8: +    case AUD_FMT_S16: +    case AUD_FMT_U16: +    case AUD_FMT_S32: +    case AUD_FMT_U32: +        break; +    default: +        invalid = 1; +        break; +    } + +    invalid |= as->freq <= 0; +    return invalid ? -1 : 0; +} + +static int audio_pcm_info_eq (struct audio_pcm_info *info, struct audsettings *as) +{ +    int bits = 8, sign = 0; + +    switch (as->fmt) { +    case AUD_FMT_S8: +        sign = 1; +        /* fall through */ +    case AUD_FMT_U8: +        break; + +    case AUD_FMT_S16: +        sign = 1; +        /* fall through */ +    case AUD_FMT_U16: +        bits = 16; +        break; + +    case AUD_FMT_S32: +        sign = 1; +        /* fall through */ +    case AUD_FMT_U32: +        bits = 32; +        break; +    } +    return info->freq == as->freq +        && info->nchannels == as->nchannels +        && info->sign == sign +        && info->bits == bits +        && info->swap_endianness == (as->endianness != AUDIO_HOST_ENDIANNESS); +} + +void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as) +{ +    int bits = 8, sign = 0, shift = 0; + +    switch (as->fmt) { +    case AUD_FMT_S8: +        sign = 1; +    case AUD_FMT_U8: +        break; + +    case AUD_FMT_S16: +        sign = 1; +    case AUD_FMT_U16: +        bits = 16; +        shift = 1; +        break; + +    case AUD_FMT_S32: +        sign = 1; +    case AUD_FMT_U32: +        bits = 32; +        shift = 2; +        break; +    } + +    info->freq = as->freq; +    info->bits = bits; +    info->sign = sign; +    info->nchannels = as->nchannels; +    info->shift = (as->nchannels == 2) + shift; +    info->align = (1 << info->shift) - 1; +    info->bytes_per_second = info->freq << info->shift; +    info->swap_endianness = (as->endianness != AUDIO_HOST_ENDIANNESS); +} + +void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len) +{ +    if (!len) { +        return; +    } + +    if (info->sign) { +        memset (buf, 0x00, len << info->shift); +    } +    else { +        switch (info->bits) { +        case 8: +            memset (buf, 0x80, len << info->shift); +            break; + +        case 16: +            { +                int i; +                uint16_t *p = buf; +                int shift = info->nchannels - 1; +                short s = INT16_MAX; + +                if (info->swap_endianness) { +                    s = bswap16 (s); +                } + +                for (i = 0; i < len << shift; i++) { +                    p[i] = s; +                } +            } +            break; + +        case 32: +            { +                int i; +                uint32_t *p = buf; +                int shift = info->nchannels - 1; +                int32_t s = INT32_MAX; + +                if (info->swap_endianness) { +                    s = bswap32 (s); +                } + +                for (i = 0; i < len << shift; i++) { +                    p[i] = s; +                } +            } +            break; + +        default: +            AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n", +                     info->bits); +            break; +        } +    } +} + +/* + * Capture + */ +static void noop_conv (struct st_sample *dst, const void *src, int samples) +{ +    (void) src; +    (void) dst; +    (void) samples; +} + +static CaptureVoiceOut *audio_pcm_capture_find_specific ( +    struct audsettings *as +    ) +{ +    CaptureVoiceOut *cap; +    AudioState *s = &glob_audio_state; + +    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { +        if (audio_pcm_info_eq (&cap->hw.info, as)) { +            return cap; +        } +    } +    return NULL; +} + +static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd) +{ +    struct capture_callback *cb; + +#ifdef DEBUG_CAPTURE +    dolog ("notification %d sent\n", cmd); +#endif +    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { +        cb->ops.notify (cb->opaque, cmd); +    } +} + +static void audio_capture_maybe_changed (CaptureVoiceOut *cap, int enabled) +{ +    if (cap->hw.enabled != enabled) { +        audcnotification_e cmd; +        cap->hw.enabled = enabled; +        cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE; +        audio_notify_capture (cap, cmd); +    } +} + +static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap) +{ +    HWVoiceOut *hw = &cap->hw; +    SWVoiceOut *sw; +    int enabled = 0; + +    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { +        if (sw->active) { +            enabled = 1; +            break; +        } +    } +    audio_capture_maybe_changed (cap, enabled); +} + +static void audio_detach_capture (HWVoiceOut *hw) +{ +    SWVoiceCap *sc = hw->cap_head.lh_first; + +    while (sc) { +        SWVoiceCap *sc1 = sc->entries.le_next; +        SWVoiceOut *sw = &sc->sw; +        CaptureVoiceOut *cap = sc->cap; +        int was_active = sw->active; + +        if (sw->rate) { +            st_rate_stop (sw->rate); +            sw->rate = NULL; +        } + +        QLIST_REMOVE (sw, entries); +        QLIST_REMOVE (sc, entries); +        g_free (sc); +        if (was_active) { +            /* We have removed soft voice from the capture: +               this might have changed the overall status of the capture +               since this might have been the only active voice */ +            audio_recalc_and_notify_capture (cap); +        } +        sc = sc1; +    } +} + +static int audio_attach_capture (HWVoiceOut *hw) +{ +    AudioState *s = &glob_audio_state; +    CaptureVoiceOut *cap; + +    audio_detach_capture (hw); +    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { +        SWVoiceCap *sc; +        SWVoiceOut *sw; +        HWVoiceOut *hw_cap = &cap->hw; + +        sc = audio_calloc (AUDIO_FUNC, 1, sizeof (*sc)); +        if (!sc) { +            dolog ("Could not allocate soft capture voice (%zu bytes)\n", +                   sizeof (*sc)); +            return -1; +        } + +        sc->cap = cap; +        sw = &sc->sw; +        sw->hw = hw_cap; +        sw->info = hw->info; +        sw->empty = 1; +        sw->active = hw->enabled; +        sw->conv = noop_conv; +        sw->ratio = ((int64_t) hw_cap->info.freq << 32) / sw->info.freq; +        sw->vol = nominal_volume; +        sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq); +        if (!sw->rate) { +            dolog ("Could not start rate conversion for `%s'\n", SW_NAME (sw)); +            g_free (sw); +            return -1; +        } +        QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries); +        QLIST_INSERT_HEAD (&hw->cap_head, sc, entries); +#ifdef DEBUG_CAPTURE +        sw->name = g_strdup_printf ("for %p %d,%d,%d", +                                    hw, sw->info.freq, sw->info.bits, +                                    sw->info.nchannels); +        dolog ("Added %s active = %d\n", sw->name, sw->active); +#endif +        if (sw->active) { +            audio_capture_maybe_changed (cap, 1); +        } +    } +    return 0; +} + +/* + * Hard voice (capture) + */ +static int audio_pcm_hw_find_min_in (HWVoiceIn *hw) +{ +    SWVoiceIn *sw; +    int m = hw->total_samples_captured; + +    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { +        if (sw->active) { +            m = audio_MIN (m, sw->total_hw_samples_acquired); +        } +    } +    return m; +} + +int audio_pcm_hw_get_live_in (HWVoiceIn *hw) +{ +    int live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw); +    if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { +        dolog ("live=%d hw->samples=%d\n", live, hw->samples); +        return 0; +    } +    return live; +} + +int audio_pcm_hw_clip_out (HWVoiceOut *hw, void *pcm_buf, +                           int live, int pending) +{ +    int left = hw->samples - pending; +    int len = audio_MIN (left, live); +    int clipped = 0; + +    while (len) { +        struct st_sample *src = hw->mix_buf + hw->rpos; +        uint8_t *dst = advance (pcm_buf, hw->rpos << hw->info.shift); +        int samples_till_end_of_buf = hw->samples - hw->rpos; +        int samples_to_clip = audio_MIN (len, samples_till_end_of_buf); + +        hw->clip (dst, src, samples_to_clip); + +        hw->rpos = (hw->rpos + samples_to_clip) % hw->samples; +        len -= samples_to_clip; +        clipped += samples_to_clip; +    } +    return clipped; +} + +/* + * Soft voice (capture) + */ +static int audio_pcm_sw_get_rpos_in (SWVoiceIn *sw) +{ +    HWVoiceIn *hw = sw->hw; +    int live = hw->total_samples_captured - sw->total_hw_samples_acquired; +    int rpos; + +    if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { +        dolog ("live=%d hw->samples=%d\n", live, hw->samples); +        return 0; +    } + +    rpos = hw->wpos - live; +    if (rpos >= 0) { +        return rpos; +    } +    else { +        return hw->samples + rpos; +    } +} + +int audio_pcm_sw_read (SWVoiceIn *sw, void *buf, int size) +{ +    HWVoiceIn *hw = sw->hw; +    int samples, live, ret = 0, swlim, isamp, osamp, rpos, total = 0; +    struct st_sample *src, *dst = sw->buf; + +    rpos = audio_pcm_sw_get_rpos_in (sw) % hw->samples; + +    live = hw->total_samples_captured - sw->total_hw_samples_acquired; +    if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { +        dolog ("live_in=%d hw->samples=%d\n", live, hw->samples); +        return 0; +    } + +    samples = size >> sw->info.shift; +    if (!live) { +        return 0; +    } + +    swlim = (live * sw->ratio) >> 32; +    swlim = audio_MIN (swlim, samples); + +    while (swlim) { +        src = hw->conv_buf + rpos; +        isamp = hw->wpos - rpos; +        /* XXX: <= ? */ +        if (isamp <= 0) { +            isamp = hw->samples - rpos; +        } + +        if (!isamp) { +            break; +        } +        osamp = swlim; + +        if (audio_bug (AUDIO_FUNC, osamp < 0)) { +            dolog ("osamp=%d\n", osamp); +            return 0; +        } + +        st_rate_flow (sw->rate, src, dst, &isamp, &osamp); +        swlim -= osamp; +        rpos = (rpos + isamp) % hw->samples; +        dst += osamp; +        ret += osamp; +        total += isamp; +    } + +    if (!(hw->ctl_caps & VOICE_VOLUME_CAP)) { +        mixeng_volume (sw->buf, ret, &sw->vol); +    } + +    sw->clip (buf, sw->buf, ret); +    sw->total_hw_samples_acquired += total; +    return ret << sw->info.shift; +} + +/* + * Hard voice (playback) + */ +static int audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep) +{ +    SWVoiceOut *sw; +    int m = INT_MAX; +    int nb_live = 0; + +    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { +        if (sw->active || !sw->empty) { +            m = audio_MIN (m, sw->total_hw_samples_mixed); +            nb_live += 1; +        } +    } + +    *nb_livep = nb_live; +    return m; +} + +static int audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live) +{ +    int smin; +    int nb_live1; + +    smin = audio_pcm_hw_find_min_out (hw, &nb_live1); +    if (nb_live) { +        *nb_live = nb_live1; +    } + +    if (nb_live1) { +        int live = smin; + +        if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { +            dolog ("live=%d hw->samples=%d\n", live, hw->samples); +            return 0; +        } +        return live; +    } +    return 0; +} + +/* + * Soft voice (playback) + */ +int audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int size) +{ +    int hwsamples, samples, isamp, osamp, wpos, live, dead, left, swlim, blck; +    int ret = 0, pos = 0, total = 0; + +    if (!sw) { +        return size; +    } + +    hwsamples = sw->hw->samples; + +    live = sw->total_hw_samples_mixed; +    if (audio_bug (AUDIO_FUNC, live < 0 || live > hwsamples)){ +        dolog ("live=%d hw->samples=%d\n", live, hwsamples); +        return 0; +    } + +    if (live == hwsamples) { +#ifdef DEBUG_OUT +        dolog ("%s is full %d\n", sw->name, live); +#endif +        return 0; +    } + +    wpos = (sw->hw->rpos + live) % hwsamples; +    samples = size >> sw->info.shift; + +    dead = hwsamples - live; +    swlim = ((int64_t) dead << 32) / sw->ratio; +    swlim = audio_MIN (swlim, samples); +    if (swlim) { +        sw->conv (sw->buf, buf, swlim); + +        if (!(sw->hw->ctl_caps & VOICE_VOLUME_CAP)) { +            mixeng_volume (sw->buf, swlim, &sw->vol); +        } +    } + +    while (swlim) { +        dead = hwsamples - live; +        left = hwsamples - wpos; +        blck = audio_MIN (dead, left); +        if (!blck) { +            break; +        } +        isamp = swlim; +        osamp = blck; +        st_rate_flow_mix ( +            sw->rate, +            sw->buf + pos, +            sw->hw->mix_buf + wpos, +            &isamp, +            &osamp +            ); +        ret += isamp; +        swlim -= isamp; +        pos += isamp; +        live += osamp; +        wpos = (wpos + osamp) % hwsamples; +        total += osamp; +    } + +    sw->total_hw_samples_mixed += total; +    sw->empty = sw->total_hw_samples_mixed == 0; + +#ifdef DEBUG_OUT +    dolog ( +        "%s: write size %d ret %d total sw %d\n", +        SW_NAME (sw), +        size >> sw->info.shift, +        ret, +        sw->total_hw_samples_mixed +        ); +#endif + +    return ret << sw->info.shift; +} + +#ifdef DEBUG_AUDIO +static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info) +{ +    dolog ("%s: bits %d, sign %d, freq %d, nchan %d\n", +           cap, info->bits, info->sign, info->freq, info->nchannels); +} +#endif + +#define DAC +#include "audio_template.h" +#undef DAC +#include "audio_template.h" + +/* + * Timer + */ +static int audio_is_timer_needed (void) +{ +    HWVoiceIn *hwi = NULL; +    HWVoiceOut *hwo = NULL; + +    while ((hwo = audio_pcm_hw_find_any_enabled_out (hwo))) { +        if (!hwo->poll_mode) return 1; +    } +    while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) { +        if (!hwi->poll_mode) return 1; +    } +    return 0; +} + +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); +    } +    else { +        timer_del (s->ts); +    } +} + +static void audio_timer (void *opaque) +{ +    audio_run ("timer"); +    audio_reset_timer (opaque); +} + +/* + * Public API + */ +int AUD_write (SWVoiceOut *sw, void *buf, int size) +{ +    int bytes; + +    if (!sw) { +        /* XXX: Consider options */ +        return size; +    } + +    if (!sw->hw->enabled) { +        dolog ("Writing to disabled voice %s\n", SW_NAME (sw)); +        return 0; +    } + +    bytes = sw->hw->pcm_ops->write (sw, buf, size); +    return bytes; +} + +int AUD_read (SWVoiceIn *sw, void *buf, int size) +{ +    int bytes; + +    if (!sw) { +        /* XXX: Consider options */ +        return size; +    } + +    if (!sw->hw->enabled) { +        dolog ("Reading from disabled voice %s\n", SW_NAME (sw)); +        return 0; +    } + +    bytes = sw->hw->pcm_ops->read (sw, buf, size); +    return bytes; +} + +int AUD_get_buffer_size_out (SWVoiceOut *sw) +{ +    return sw->hw->samples << sw->hw->info.shift; +} + +void AUD_set_active_out (SWVoiceOut *sw, int on) +{ +    HWVoiceOut *hw; + +    if (!sw) { +        return; +    } + +    hw = sw->hw; +    if (sw->active != on) { +        AudioState *s = &glob_audio_state; +        SWVoiceOut *temp_sw; +        SWVoiceCap *sc; + +        if (on) { +            hw->pending_disable = 0; +            if (!hw->enabled) { +                hw->enabled = 1; +                if (s->vm_running) { +                    hw->pcm_ops->ctl_out (hw, VOICE_ENABLE, conf.try_poll_out); +                    audio_reset_timer (s); +                } +            } +        } +        else { +            if (hw->enabled) { +                int nb_active = 0; + +                for (temp_sw = hw->sw_head.lh_first; temp_sw; +                     temp_sw = temp_sw->entries.le_next) { +                    nb_active += temp_sw->active != 0; +                } + +                hw->pending_disable = nb_active == 1; +            } +        } + +        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { +            sc->sw.active = hw->enabled; +            if (hw->enabled) { +                audio_capture_maybe_changed (sc->cap, 1); +            } +        } +        sw->active = on; +    } +} + +void AUD_set_active_in (SWVoiceIn *sw, int on) +{ +    HWVoiceIn *hw; + +    if (!sw) { +        return; +    } + +    hw = sw->hw; +    if (sw->active != on) { +        AudioState *s = &glob_audio_state; +        SWVoiceIn *temp_sw; + +        if (on) { +            if (!hw->enabled) { +                hw->enabled = 1; +                if (s->vm_running) { +                    hw->pcm_ops->ctl_in (hw, VOICE_ENABLE, conf.try_poll_in); +                    audio_reset_timer (s); +                } +            } +            sw->total_hw_samples_acquired = hw->total_samples_captured; +        } +        else { +            if (hw->enabled) { +                int nb_active = 0; + +                for (temp_sw = hw->sw_head.lh_first; temp_sw; +                     temp_sw = temp_sw->entries.le_next) { +                    nb_active += temp_sw->active != 0; +                } + +                if (nb_active == 1) { +                    hw->enabled = 0; +                    hw->pcm_ops->ctl_in (hw, VOICE_DISABLE); +                } +            } +        } +        sw->active = on; +    } +} + +static int audio_get_avail (SWVoiceIn *sw) +{ +    int live; + +    if (!sw) { +        return 0; +    } + +    live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; +    if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) { +        dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples); +        return 0; +    } + +    ldebug ( +        "%s: get_avail live %d ret %" PRId64 "\n", +        SW_NAME (sw), +        live, (((int64_t) live << 32) / sw->ratio) << sw->info.shift +        ); + +    return (((int64_t) live << 32) / sw->ratio) << sw->info.shift; +} + +static int audio_get_free (SWVoiceOut *sw) +{ +    int live, dead; + +    if (!sw) { +        return 0; +    } + +    live = sw->total_hw_samples_mixed; + +    if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) { +        dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples); +        return 0; +    } + +    dead = sw->hw->samples - live; + +#ifdef DEBUG_OUT +    dolog ("%s: get_free live %d dead %d ret %" PRId64 "\n", +           SW_NAME (sw), +           live, dead, (((int64_t) dead << 32) / sw->ratio) << sw->info.shift); +#endif + +    return (((int64_t) dead << 32) / sw->ratio) << sw->info.shift; +} + +static void audio_capture_mix_and_clear (HWVoiceOut *hw, int rpos, int samples) +{ +    int n; + +    if (hw->enabled) { +        SWVoiceCap *sc; + +        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { +            SWVoiceOut *sw = &sc->sw; +            int rpos2 = rpos; + +            n = samples; +            while (n) { +                int till_end_of_hw = hw->samples - rpos2; +                int to_write = audio_MIN (till_end_of_hw, n); +                int bytes = to_write << hw->info.shift; +                int written; + +                sw->buf = hw->mix_buf + rpos2; +                written = audio_pcm_sw_write (sw, NULL, bytes); +                if (written - bytes) { +                    dolog ("Could not mix %d bytes into a capture " +                           "buffer, mixed %d\n", +                           bytes, written); +                    break; +                } +                n -= to_write; +                rpos2 = (rpos2 + to_write) % hw->samples; +            } +        } +    } + +    n = audio_MIN (samples, hw->samples - rpos); +    mixeng_clear (hw->mix_buf + rpos, n); +    mixeng_clear (hw->mix_buf, samples - n); +} + +static void audio_run_out (AudioState *s) +{ +    HWVoiceOut *hw = NULL; +    SWVoiceOut *sw; + +    while ((hw = audio_pcm_hw_find_any_enabled_out (hw))) { +        int played; +        int live, free, nb_live, cleanup_required, prev_rpos; + +        live = audio_pcm_hw_get_live_out (hw, &nb_live); +        if (!nb_live) { +            live = 0; +        } + +        if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { +            dolog ("live=%d hw->samples=%d\n", live, hw->samples); +            continue; +        } + +        if (hw->pending_disable && !nb_live) { +            SWVoiceCap *sc; +#ifdef DEBUG_OUT +            dolog ("Disabling voice\n"); +#endif +            hw->enabled = 0; +            hw->pending_disable = 0; +            hw->pcm_ops->ctl_out (hw, VOICE_DISABLE); +            for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { +                sc->sw.active = 0; +                audio_recalc_and_notify_capture (sc->cap); +            } +            continue; +        } + +        if (!live) { +            for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { +                if (sw->active) { +                    free = audio_get_free (sw); +                    if (free > 0) { +                        sw->callback.fn (sw->callback.opaque, free); +                    } +                } +            } +            continue; +        } + +        prev_rpos = hw->rpos; +        played = hw->pcm_ops->run_out (hw, live); +        if (audio_bug (AUDIO_FUNC, hw->rpos >= hw->samples)) { +            dolog ("hw->rpos=%d hw->samples=%d played=%d\n", +                   hw->rpos, hw->samples, played); +            hw->rpos = 0; +        } + +#ifdef DEBUG_OUT +        dolog ("played=%d\n", played); +#endif + +        if (played) { +            hw->ts_helper += played; +            audio_capture_mix_and_clear (hw, prev_rpos, played); +        } + +        cleanup_required = 0; +        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { +            if (!sw->active && sw->empty) { +                continue; +            } + +            if (audio_bug (AUDIO_FUNC, played > sw->total_hw_samples_mixed)) { +                dolog ("played=%d sw->total_hw_samples_mixed=%d\n", +                       played, sw->total_hw_samples_mixed); +                played = sw->total_hw_samples_mixed; +            } + +            sw->total_hw_samples_mixed -= played; + +            if (!sw->total_hw_samples_mixed) { +                sw->empty = 1; +                cleanup_required |= !sw->active && !sw->callback.fn; +            } + +            if (sw->active) { +                free = audio_get_free (sw); +                if (free > 0) { +                    sw->callback.fn (sw->callback.opaque, free); +                } +            } +        } + +        if (cleanup_required) { +            SWVoiceOut *sw1; + +            sw = hw->sw_head.lh_first; +            while (sw) { +                sw1 = sw->entries.le_next; +                if (!sw->active && !sw->callback.fn) { +                    audio_close_out (sw); +                } +                sw = sw1; +            } +        } +    } +} + +static void audio_run_in (AudioState *s) +{ +    HWVoiceIn *hw = NULL; + +    while ((hw = audio_pcm_hw_find_any_enabled_in (hw))) { +        SWVoiceIn *sw; +        int captured, min; + +        captured = hw->pcm_ops->run_in (hw); + +        min = audio_pcm_hw_find_min_in (hw); +        hw->total_samples_captured += captured - min; +        hw->ts_helper += captured; + +        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { +            sw->total_hw_samples_acquired -= min; + +            if (sw->active) { +                int avail; + +                avail = audio_get_avail (sw); +                if (avail > 0) { +                    sw->callback.fn (sw->callback.opaque, avail); +                } +            } +        } +    } +} + +static void audio_run_capture (AudioState *s) +{ +    CaptureVoiceOut *cap; + +    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { +        int live, rpos, captured; +        HWVoiceOut *hw = &cap->hw; +        SWVoiceOut *sw; + +        captured = live = audio_pcm_hw_get_live_out (hw, NULL); +        rpos = hw->rpos; +        while (live) { +            int left = hw->samples - rpos; +            int to_capture = audio_MIN (live, left); +            struct st_sample *src; +            struct capture_callback *cb; + +            src = hw->mix_buf + rpos; +            hw->clip (cap->buf, src, to_capture); +            mixeng_clear (src, to_capture); + +            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { +                cb->ops.capture (cb->opaque, cap->buf, +                                 to_capture << hw->info.shift); +            } +            rpos = (rpos + to_capture) % hw->samples; +            live -= to_capture; +        } +        hw->rpos = rpos; + +        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { +            if (!sw->active && sw->empty) { +                continue; +            } + +            if (audio_bug (AUDIO_FUNC, captured > sw->total_hw_samples_mixed)) { +                dolog ("captured=%d sw->total_hw_samples_mixed=%d\n", +                       captured, sw->total_hw_samples_mixed); +                captured = sw->total_hw_samples_mixed; +            } + +            sw->total_hw_samples_mixed -= captured; +            sw->empty = sw->total_hw_samples_mixed == 0; +        } +    } +} + +void audio_run (const char *msg) +{ +    AudioState *s = &glob_audio_state; + +    audio_run_out (s); +    audio_run_in (s); +    audio_run_capture (s); +#ifdef DEBUG_POLL +    { +        static double prevtime; +        double currtime; +        struct timeval tv; + +        if (gettimeofday (&tv, NULL)) { +            perror ("audio_run: gettimeofday"); +            return; +        } + +        currtime = tv.tv_sec + tv.tv_usec * 1e-6; +        dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime); +        prevtime = currtime; +    } +#endif +} + +static struct audio_option audio_options[] = { +    /* DAC */ +    { +        .name  = "DAC_FIXED_SETTINGS", +        .tag   = AUD_OPT_BOOL, +        .valp  = &conf.fixed_out.enabled, +        .descr = "Use fixed settings for host DAC" +    }, +    { +        .name  = "DAC_FIXED_FREQ", +        .tag   = AUD_OPT_INT, +        .valp  = &conf.fixed_out.settings.freq, +        .descr = "Frequency for fixed host DAC" +    }, +    { +        .name  = "DAC_FIXED_FMT", +        .tag   = AUD_OPT_FMT, +        .valp  = &conf.fixed_out.settings.fmt, +        .descr = "Format for fixed host DAC" +    }, +    { +        .name  = "DAC_FIXED_CHANNELS", +        .tag   = AUD_OPT_INT, +        .valp  = &conf.fixed_out.settings.nchannels, +        .descr = "Number of channels for fixed DAC (1 - mono, 2 - stereo)" +    }, +    { +        .name  = "DAC_VOICES", +        .tag   = AUD_OPT_INT, +        .valp  = &conf.fixed_out.nb_voices, +        .descr = "Number of voices for DAC" +    }, +    { +        .name  = "DAC_TRY_POLL", +        .tag   = AUD_OPT_BOOL, +        .valp  = &conf.try_poll_out, +        .descr = "Attempt using poll mode for DAC" +    }, +    /* ADC */ +    { +        .name  = "ADC_FIXED_SETTINGS", +        .tag   = AUD_OPT_BOOL, +        .valp  = &conf.fixed_in.enabled, +        .descr = "Use fixed settings for host ADC" +    }, +    { +        .name  = "ADC_FIXED_FREQ", +        .tag   = AUD_OPT_INT, +        .valp  = &conf.fixed_in.settings.freq, +        .descr = "Frequency for fixed host ADC" +    }, +    { +        .name  = "ADC_FIXED_FMT", +        .tag   = AUD_OPT_FMT, +        .valp  = &conf.fixed_in.settings.fmt, +        .descr = "Format for fixed host ADC" +    }, +    { +        .name  = "ADC_FIXED_CHANNELS", +        .tag   = AUD_OPT_INT, +        .valp  = &conf.fixed_in.settings.nchannels, +        .descr = "Number of channels for fixed ADC (1 - mono, 2 - stereo)" +    }, +    { +        .name  = "ADC_VOICES", +        .tag   = AUD_OPT_INT, +        .valp  = &conf.fixed_in.nb_voices, +        .descr = "Number of voices for ADC" +    }, +    { +        .name  = "ADC_TRY_POLL", +        .tag   = AUD_OPT_BOOL, +        .valp  = &conf.try_poll_in, +        .descr = "Attempt using poll mode for ADC" +    }, +    /* Misc */ +    { +        .name  = "TIMER_PERIOD", +        .tag   = AUD_OPT_INT, +        .valp  = &conf.period.hertz, +        .descr = "Timer period in HZ (0 - use lowest possible)" +    }, +    { /* End of list */ } +}; + +static void audio_pp_nb_voices (const char *typ, int nb) +{ +    switch (nb) { +    case 0: +        printf ("Does not support %s\n", typ); +        break; +    case 1: +        printf ("One %s voice\n", typ); +        break; +    case INT_MAX: +        printf ("Theoretically supports many %s voices\n", typ); +        break; +    default: +        printf ("Theoretically supports up to %d %s voices\n", nb, typ); +        break; +    } + +} + +void AUD_help (void) +{ +    size_t i; + +    audio_process_options ("AUDIO", audio_options); +    for (i = 0; i < ARRAY_SIZE (drvtab); i++) { +        struct audio_driver *d = drvtab[i]; +        if (d->options) { +            audio_process_options (d->name, d->options); +        } +    } + +    printf ("Audio options:\n"); +    audio_print_options ("AUDIO", audio_options); +    printf ("\n"); + +    printf ("Available drivers:\n"); + +    for (i = 0; i < ARRAY_SIZE (drvtab); i++) { +        struct audio_driver *d = drvtab[i]; + +        printf ("Name: %s\n", d->name); +        printf ("Description: %s\n", d->descr); + +        audio_pp_nb_voices ("playback", d->max_voices_out); +        audio_pp_nb_voices ("capture", d->max_voices_in); + +        if (d->options) { +            printf ("Options:\n"); +            audio_print_options (d->name, d->options); +        } +        else { +            printf ("No options\n"); +        } +        printf ("\n"); +    } + +    printf ( +        "Options are settable through environment variables.\n" +        "Example:\n" +#ifdef _WIN32 +        "  set QEMU_AUDIO_DRV=wav\n" +        "  set QEMU_WAV_PATH=c:\\tune.wav\n" +#else +        "  export QEMU_AUDIO_DRV=wav\n" +        "  export QEMU_WAV_PATH=$HOME/tune.wav\n" +        "(for csh replace export with setenv in the above)\n" +#endif +        "  qemu ...\n\n" +        ); +} + +static int audio_driver_init (AudioState *s, struct audio_driver *drv) +{ +    if (drv->options) { +        audio_process_options (drv->name, drv->options); +    } +    s->drv_opaque = drv->init (); + +    if (s->drv_opaque) { +        audio_init_nb_voices_out (drv); +        audio_init_nb_voices_in (drv); +        s->drv = drv; +        return 0; +    } +    else { +        dolog ("Could not init `%s' audio driver\n", drv->name); +        return -1; +    } +} + +static void audio_vm_change_state_handler (void *opaque, int running, +                                           RunState state) +{ +    AudioState *s = opaque; +    HWVoiceOut *hwo = NULL; +    HWVoiceIn *hwi = NULL; +    int op = running ? VOICE_ENABLE : VOICE_DISABLE; + +    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); +    } + +    while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) { +        hwi->pcm_ops->ctl_in (hwi, op, conf.try_poll_in); +    } +    audio_reset_timer (s); +} + +static void audio_atexit (void) +{ +    AudioState *s = &glob_audio_state; +    HWVoiceOut *hwo = NULL; +    HWVoiceIn *hwi = NULL; + +    while ((hwo = audio_pcm_hw_find_any_out (hwo))) { +        SWVoiceCap *sc; + +        if (hwo->enabled) { +            hwo->pcm_ops->ctl_out (hwo, VOICE_DISABLE); +        } +        hwo->pcm_ops->fini_out (hwo); + +        for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) { +            CaptureVoiceOut *cap = sc->cap; +            struct capture_callback *cb; + +            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { +                cb->ops.destroy (cb->opaque); +            } +        } +    } + +    while ((hwi = audio_pcm_hw_find_any_in (hwi))) { +        if (hwi->enabled) { +            hwi->pcm_ops->ctl_in (hwi, VOICE_DISABLE); +        } +        hwi->pcm_ops->fini_in (hwi); +    } + +    if (s->drv) { +        s->drv->fini (s->drv_opaque); +    } +} + +static const VMStateDescription vmstate_audio = { +    .name = "audio", +    .version_id = 1, +    .minimum_version_id = 1, +    .fields = (VMStateField[]) { +        VMSTATE_END_OF_LIST() +    } +}; + +static void audio_init (void) +{ +    size_t i; +    int done = 0; +    const char *drvname; +    VMChangeStateEntry *e; +    AudioState *s = &glob_audio_state; + +    if (s->drv) { +        return; +    } + +    QLIST_INIT (&s->hw_head_out); +    QLIST_INIT (&s->hw_head_in); +    QLIST_INIT (&s->cap_head); +    atexit (audio_atexit); + +    s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s); +    if (!s->ts) { +        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; + +    if (s->nb_hw_voices_out <= 0) { +        dolog ("Bogus number of playback voices %d, setting to 1\n", +               s->nb_hw_voices_out); +        s->nb_hw_voices_out = 1; +    } + +    if (s->nb_hw_voices_in <= 0) { +        dolog ("Bogus number of capture voices %d, setting to 0\n", +               s->nb_hw_voices_in); +        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++) { +            if (!strcmp (drvname, drvtab[i]->name)) { +                done = !audio_driver_init (s, drvtab[i]); +                found = 1; +                break; +            } +        } + +        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]); +            } +        } +    } + +    if (!done) { +        done = !audio_driver_init (s, &no_audio_driver); +        if (!done) { +            hw_error("Could not initialize audio subsystem\n"); +        } +        else { +            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; +    } else { +        conf.period.ticks = +            muldiv64 (1, get_ticks_per_sec (), conf.period.hertz); +    } + +    e = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s); +    if (!e) { +        dolog ("warning: Could not register change state handler\n" +               "(Audio can continue looping even after stopping the VM)\n"); +    } + +    QLIST_INIT (&s->card_head); +    vmstate_register (NULL, 0, &vmstate_audio, s); +} + +void AUD_register_card (const char *name, QEMUSoundCard *card) +{ +    audio_init (); +    card->name = g_strdup (name); +    memset (&card->entries, 0, sizeof (card->entries)); +    QLIST_INSERT_HEAD (&glob_audio_state.card_head, card, entries); +} + +void AUD_remove_card (QEMUSoundCard *card) +{ +    QLIST_REMOVE (card, entries); +    g_free (card->name); +} + + +CaptureVoiceOut *AUD_add_capture ( +    struct audsettings *as, +    struct audio_capture_ops *ops, +    void *cb_opaque +    ) +{ +    AudioState *s = &glob_audio_state; +    CaptureVoiceOut *cap; +    struct capture_callback *cb; + +    if (audio_validate_settings (as)) { +        dolog ("Invalid settings were passed when trying to add capture\n"); +        audio_print_settings (as); +        goto err0; +    } + +    cb = audio_calloc (AUDIO_FUNC, 1, sizeof (*cb)); +    if (!cb) { +        dolog ("Could not allocate capture callback information, size %zu\n", +               sizeof (*cb)); +        goto err0; +    } +    cb->ops = *ops; +    cb->opaque = cb_opaque; + +    cap = audio_pcm_capture_find_specific (as); +    if (cap) { +        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); +        return cap; +    } +    else { +        HWVoiceOut *hw; +        CaptureVoiceOut *cap; + +        cap = audio_calloc (AUDIO_FUNC, 1, sizeof (*cap)); +        if (!cap) { +            dolog ("Could not allocate capture voice, size %zu\n", +                   sizeof (*cap)); +            goto err1; +        } + +        hw = &cap->hw; +        QLIST_INIT (&hw->sw_head); +        QLIST_INIT (&cap->cb_head); + +        /* XXX find a more elegant way */ +        hw->samples = 4096 * 4; +        hw->mix_buf = audio_calloc (AUDIO_FUNC, hw->samples, +                                    sizeof (struct st_sample)); +        if (!hw->mix_buf) { +            dolog ("Could not allocate capture mix buffer (%d samples)\n", +                   hw->samples); +            goto err2; +        } + +        audio_pcm_init_info (&hw->info, as); + +        cap->buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); +        if (!cap->buf) { +            dolog ("Could not allocate capture buffer " +                   "(%d samples, each %d bytes)\n", +                   hw->samples, 1 << hw->info.shift); +            goto err3; +        } + +        hw->clip = mixeng_clip +            [hw->info.nchannels == 2] +            [hw->info.sign] +            [hw->info.swap_endianness] +            [audio_bits_to_index (hw->info.bits)]; + +        QLIST_INSERT_HEAD (&s->cap_head, cap, entries); +        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); + +        hw = NULL; +        while ((hw = audio_pcm_hw_find_any_out (hw))) { +            audio_attach_capture (hw); +        } +        return cap; + +    err3: +        g_free (cap->hw.mix_buf); +    err2: +        g_free (cap); +    err1: +        g_free (cb); +    err0: +        return NULL; +    } +} + +void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque) +{ +    struct capture_callback *cb; + +    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { +        if (cb->opaque == cb_opaque) { +            cb->ops.destroy (cb_opaque); +            QLIST_REMOVE (cb, entries); +            g_free (cb); + +            if (!cap->cb_head.lh_first) { +                SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1; + +                while (sw) { +                    SWVoiceCap *sc = (SWVoiceCap *) sw; +#ifdef DEBUG_CAPTURE +                    dolog ("freeing %s\n", sw->name); +#endif + +                    sw1 = sw->entries.le_next; +                    if (sw->rate) { +                        st_rate_stop (sw->rate); +                        sw->rate = NULL; +                    } +                    QLIST_REMOVE (sw, entries); +                    QLIST_REMOVE (sc, entries); +                    g_free (sc); +                    sw = sw1; +                } +                QLIST_REMOVE (cap, entries); +                g_free (cap); +            } +            return; +        } +    } +} + +void AUD_set_volume_out (SWVoiceOut *sw, int mute, uint8_t lvol, uint8_t rvol) +{ +    if (sw) { +        HWVoiceOut *hw = sw->hw; + +        sw->vol.mute = mute; +        sw->vol.l = nominal_volume.l * lvol / 255; +        sw->vol.r = nominal_volume.r * rvol / 255; + +        if (hw->pcm_ops->ctl_out) { +            hw->pcm_ops->ctl_out (hw, VOICE_VOLUME, sw); +        } +    } +} + +void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol) +{ +    if (sw) { +        HWVoiceIn *hw = sw->hw; + +        sw->vol.mute = mute; +        sw->vol.l = nominal_volume.l * lvol / 255; +        sw->vol.r = nominal_volume.r * rvol / 255; + +        if (hw->pcm_ops->ctl_in) { +            hw->pcm_ops->ctl_in (hw, VOICE_VOLUME, sw); +        } +    } +} diff --git a/audio/audio.h b/audio/audio.h new file mode 100644 index 00000000..e7ea3977 --- /dev/null +++ b/audio/audio.h @@ -0,0 +1,166 @@ +/* + * QEMU Audio subsystem header + * + * Copyright (c) 2003-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. + */ +#ifndef QEMU_AUDIO_H +#define QEMU_AUDIO_H + +#include "config-host.h" +#include "qemu/queue.h" + +typedef void (*audio_callback_fn) (void *opaque, int avail); + +typedef enum { +    AUD_FMT_U8, +    AUD_FMT_S8, +    AUD_FMT_U16, +    AUD_FMT_S16, +    AUD_FMT_U32, +    AUD_FMT_S32 +} audfmt_e; + +#ifdef HOST_WORDS_BIGENDIAN +#define AUDIO_HOST_ENDIANNESS 1 +#else +#define AUDIO_HOST_ENDIANNESS 0 +#endif + +struct audsettings { +    int freq; +    int nchannels; +    audfmt_e fmt; +    int endianness; +}; + +typedef enum { +    AUD_CNOTIFY_ENABLE, +    AUD_CNOTIFY_DISABLE +} audcnotification_e; + +struct audio_capture_ops { +    void (*notify) (void *opaque, audcnotification_e cmd); +    void (*capture) (void *opaque, void *buf, int size); +    void (*destroy) (void *opaque); +}; + +struct capture_ops { +    void (*info) (void *opaque); +    void (*destroy) (void *opaque); +}; + +typedef struct CaptureState { +    void *opaque; +    struct capture_ops ops; +    QLIST_ENTRY (CaptureState) entries; +} CaptureState; + +typedef struct SWVoiceOut SWVoiceOut; +typedef struct CaptureVoiceOut CaptureVoiceOut; +typedef struct SWVoiceIn SWVoiceIn; + +typedef struct QEMUSoundCard { +    char *name; +    QLIST_ENTRY (QEMUSoundCard) entries; +} QEMUSoundCard; + +typedef struct QEMUAudioTimeStamp { +    uint64_t old_ts; +} 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 ( +    struct audsettings *as, +    struct audio_capture_ops *ops, +    void *opaque +    ); +void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque); + +SWVoiceOut *AUD_open_out ( +    QEMUSoundCard *card, +    SWVoiceOut *sw, +    const char *name, +    void *callback_opaque, +    audio_callback_fn callback_fn, +    struct audsettings *settings +    ); + +void AUD_close_out (QEMUSoundCard *card, SWVoiceOut *sw); +int  AUD_write (SWVoiceOut *sw, void *pcm_buf, int size); +int  AUD_get_buffer_size_out (SWVoiceOut *sw); +void AUD_set_active_out (SWVoiceOut *sw, int on); +int  AUD_is_active_out (SWVoiceOut *sw); + +void     AUD_init_time_stamp_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts); +uint64_t AUD_get_elapsed_usec_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts); + +void AUD_set_volume_out (SWVoiceOut *sw, int mute, uint8_t lvol, uint8_t rvol); +void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol); + +SWVoiceIn *AUD_open_in ( +    QEMUSoundCard *card, +    SWVoiceIn *sw, +    const char *name, +    void *callback_opaque, +    audio_callback_fn callback_fn, +    struct audsettings *settings +    ); + +void AUD_close_in (QEMUSoundCard *card, SWVoiceIn *sw); +int  AUD_read (SWVoiceIn *sw, void *pcm_buf, int size); +void AUD_set_active_in (SWVoiceIn *sw, int on); +int  AUD_is_active_in (SWVoiceIn *sw); + +void     AUD_init_time_stamp_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts); +uint64_t AUD_get_elapsed_usec_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts); + +static inline void *advance (void *p, int incr) +{ +    uint8_t *d = p; +    return (d + incr); +} + +#ifdef __GNUC__ +#define audio_MIN(a, b) ( __extension__ ({      \ +    __typeof (a) ta = a;                        \ +    __typeof (b) tb = b;                        \ +    ((ta)>(tb)?(tb):(ta));                      \ +})) + +#define audio_MAX(a, b) ( __extension__ ({      \ +    __typeof (a) ta = a;                        \ +    __typeof (b) tb = b;                        \ +    ((ta)<(tb)?(tb):(ta));                      \ +})) +#else +#define audio_MIN(a, b) ((a)>(b)?(b):(a)) +#define audio_MAX(a, b) ((a)<(b)?(b):(a)) +#endif + +int wav_start_capture (CaptureState *s, const char *path, int freq, +                       int bits, int nchannels); + +#endif  /* audio.h */ diff --git a/audio/audio_int.h b/audio/audio_int.h new file mode 100644 index 00000000..566df5ed --- /dev/null +++ b/audio/audio_int.h @@ -0,0 +1,260 @@ +/* + * QEMU Audio subsystem header + * + * Copyright (c) 2003-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. + */ +#ifndef QEMU_AUDIO_INT_H +#define QEMU_AUDIO_INT_H + +#ifdef CONFIG_COREAUDIO +#define FLOAT_MIXENG +/* #define RECIPROCAL */ +#endif +#include "mixeng.h" + +struct audio_pcm_ops; + +typedef enum { +    AUD_OPT_INT, +    AUD_OPT_FMT, +    AUD_OPT_STR, +    AUD_OPT_BOOL +} audio_option_tag_e; + +struct audio_option { +    const char *name; +    audio_option_tag_e tag; +    void *valp; +    const char *descr; +    int *overriddenp; +    int overridden; +}; + +struct audio_callback { +    void *opaque; +    audio_callback_fn fn; +}; + +struct audio_pcm_info { +    int bits; +    int sign; +    int freq; +    int nchannels; +    int align; +    int shift; +    int bytes_per_second; +    int swap_endianness; +}; + +typedef struct SWVoiceCap SWVoiceCap; + +typedef struct HWVoiceOut { +    int enabled; +    int poll_mode; +    int pending_disable; +    struct audio_pcm_info info; + +    f_sample *clip; + +    int rpos; +    uint64_t ts_helper; + +    struct st_sample *mix_buf; + +    int samples; +    QLIST_HEAD (sw_out_listhead, SWVoiceOut) sw_head; +    QLIST_HEAD (sw_cap_listhead, SWVoiceCap) cap_head; +    int ctl_caps; +    struct audio_pcm_ops *pcm_ops; +    QLIST_ENTRY (HWVoiceOut) entries; +} HWVoiceOut; + +typedef struct HWVoiceIn { +    int enabled; +    int poll_mode; +    struct audio_pcm_info info; + +    t_sample *conv; + +    int wpos; +    int total_samples_captured; +    uint64_t ts_helper; + +    struct st_sample *conv_buf; + +    int samples; +    QLIST_HEAD (sw_in_listhead, SWVoiceIn) sw_head; +    int ctl_caps; +    struct audio_pcm_ops *pcm_ops; +    QLIST_ENTRY (HWVoiceIn) entries; +} HWVoiceIn; + +struct SWVoiceOut { +    QEMUSoundCard *card; +    struct audio_pcm_info info; +    t_sample *conv; +    int64_t ratio; +    struct st_sample *buf; +    void *rate; +    int total_hw_samples_mixed; +    int active; +    int empty; +    HWVoiceOut *hw; +    char *name; +    struct mixeng_volume vol; +    struct audio_callback callback; +    QLIST_ENTRY (SWVoiceOut) entries; +}; + +struct SWVoiceIn { +    QEMUSoundCard *card; +    int active; +    struct audio_pcm_info info; +    int64_t ratio; +    void *rate; +    int total_hw_samples_acquired; +    struct st_sample *buf; +    f_sample *clip; +    HWVoiceIn *hw; +    char *name; +    struct mixeng_volume vol; +    struct audio_callback callback; +    QLIST_ENTRY (SWVoiceIn) entries; +}; + +struct audio_driver { +    const char *name; +    const char *descr; +    struct audio_option *options; +    void *(*init) (void); +    void (*fini) (void *); +    struct audio_pcm_ops *pcm_ops; +    int can_be_default; +    int max_voices_out; +    int max_voices_in; +    int voice_size_out; +    int voice_size_in; +    int ctl_caps; +}; + +struct audio_pcm_ops { +    int  (*init_out)(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque); +    void (*fini_out)(HWVoiceOut *hw); +    int  (*run_out) (HWVoiceOut *hw, int live); +    int  (*write)   (SWVoiceOut *sw, void *buf, int size); +    int  (*ctl_out) (HWVoiceOut *hw, int cmd, ...); + +    int  (*init_in) (HWVoiceIn *hw, struct audsettings *as, void *drv_opaque); +    void (*fini_in) (HWVoiceIn *hw); +    int  (*run_in)  (HWVoiceIn *hw); +    int  (*read)    (SWVoiceIn *sw, void *buf, int size); +    int  (*ctl_in)  (HWVoiceIn *hw, int cmd, ...); +}; + +struct capture_callback { +    struct audio_capture_ops ops; +    void *opaque; +    QLIST_ENTRY (capture_callback) entries; +}; + +struct CaptureVoiceOut { +    HWVoiceOut hw; +    void *buf; +    QLIST_HEAD (cb_listhead, capture_callback) cb_head; +    QLIST_ENTRY (CaptureVoiceOut) entries; +}; + +struct SWVoiceCap { +    SWVoiceOut sw; +    CaptureVoiceOut *cap; +    QLIST_ENTRY (SWVoiceCap) entries; +}; + +struct AudioState { +    struct audio_driver *drv; +    void *drv_opaque; + +    QEMUTimer *ts; +    QLIST_HEAD (card_listhead, QEMUSoundCard) card_head; +    QLIST_HEAD (hw_in_listhead, HWVoiceIn) hw_head_in; +    QLIST_HEAD (hw_out_listhead, HWVoiceOut) hw_head_out; +    QLIST_HEAD (cap_listhead, CaptureVoiceOut) cap_head; +    int nb_hw_voices_out; +    int nb_hw_voices_in; +    int vm_running; +}; + +extern struct audio_driver no_audio_driver; +extern struct audio_driver oss_audio_driver; +extern struct audio_driver sdl_audio_driver; +extern struct audio_driver wav_audio_driver; +extern struct audio_driver alsa_audio_driver; +extern struct audio_driver coreaudio_audio_driver; +extern struct audio_driver dsound_audio_driver; +extern struct audio_driver pa_audio_driver; +extern struct audio_driver spice_audio_driver; +extern const struct mixeng_volume nominal_volume; + +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); + +int  audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int len); +int  audio_pcm_hw_get_live_in (HWVoiceIn *hw); + +int  audio_pcm_sw_read (SWVoiceIn *sw, void *buf, int len); + +int audio_pcm_hw_clip_out (HWVoiceOut *hw, void *pcm_buf, +                           int live, int pending); + +int audio_bug (const char *funcname, int cond); +void *audio_calloc (const char *funcname, int nmemb, size_t size); + +void audio_run (const char *msg); + +#define VOICE_ENABLE 1 +#define VOICE_DISABLE 2 +#define VOICE_VOLUME 3 + +#define VOICE_VOLUME_CAP (1 << VOICE_VOLUME) + +static inline int audio_ring_dist (int dst, int src, int len) +{ +    return (dst >= src) ? (dst - src) : (len - src + dst); +} + +#define dolog(fmt, ...) AUD_log(AUDIO_CAP, fmt, ## __VA_ARGS__) + +#ifdef DEBUG +#define ldebug(fmt, ...) AUD_log(AUDIO_CAP, fmt, ## __VA_ARGS__) +#else +#define ldebug(fmt, ...) (void)0 +#endif + +#define AUDIO_STRINGIFY_(n) #n +#define AUDIO_STRINGIFY(n) AUDIO_STRINGIFY_(n) + +#if defined _MSC_VER || defined __GNUC__ +#define AUDIO_FUNC __FUNCTION__ +#else +#define AUDIO_FUNC __FILE__ ":" AUDIO_STRINGIFY (__LINE__) +#endif + +#endif /* audio_int.h */ diff --git a/audio/audio_pt_int.c b/audio/audio_pt_int.c new file mode 100644 index 00000000..9a9c306a --- /dev/null +++ b/audio/audio_pt_int.c @@ -0,0 +1,173 @@ +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "audio-pt" + +#include "audio_int.h" +#include "audio_pt_int.h" + +static void GCC_FMT_ATTR(3, 4) logerr (struct audio_pt *pt, int err, +                                       const char *fmt, ...) +{ +    va_list ap; + +    va_start (ap, fmt); +    AUD_vlog (pt->drv, fmt, ap); +    va_end (ap); + +    AUD_log (NULL, "\n"); +    AUD_log (pt->drv, "Reason: %s\n", strerror (err)); +} + +int audio_pt_init (struct audio_pt *p, void *(*func) (void *), +                   void *opaque, const char *drv, const char *cap) +{ +    int err, err2; +    const char *efunc; +    sigset_t set, old_set; + +    p->drv = drv; + +    err = sigfillset (&set); +    if (err) { +        logerr (p, errno, "%s(%s): sigfillset failed", cap, AUDIO_FUNC); +        return -1; +    } + +    err = pthread_mutex_init (&p->mutex, NULL); +    if (err) { +        efunc = "pthread_mutex_init"; +        goto err0; +    } + +    err = pthread_cond_init (&p->cond, NULL); +    if (err) { +        efunc = "pthread_cond_init"; +        goto err1; +    } + +    err = pthread_sigmask (SIG_BLOCK, &set, &old_set); +    if (err) { +        efunc = "pthread_sigmask"; +        goto err2; +    } + +    err = pthread_create (&p->thread, NULL, func, opaque); + +    err2 = pthread_sigmask (SIG_SETMASK, &old_set, NULL); +    if (err2) { +        logerr (p, err2, "%s(%s): pthread_sigmask (restore) failed", +                cap, AUDIO_FUNC); +        /* We have failed to restore original signal mask, all bets are off, +           so terminate the process */ +        exit (EXIT_FAILURE); +    } + +    if (err) { +        efunc = "pthread_create"; +        goto err2; +    } + +    return 0; + + err2: +    err2 = pthread_cond_destroy (&p->cond); +    if (err2) { +        logerr (p, err2, "%s(%s): pthread_cond_destroy failed", cap, AUDIO_FUNC); +    } + + err1: +    err2 = pthread_mutex_destroy (&p->mutex); +    if (err2) { +        logerr (p, err2, "%s(%s): pthread_mutex_destroy failed", cap, AUDIO_FUNC); +    } + + err0: +    logerr (p, err, "%s(%s): %s failed", cap, AUDIO_FUNC, efunc); +    return -1; +} + +int audio_pt_fini (struct audio_pt *p, const char *cap) +{ +    int err, ret = 0; + +    err = pthread_cond_destroy (&p->cond); +    if (err) { +        logerr (p, err, "%s(%s): pthread_cond_destroy failed", cap, AUDIO_FUNC); +        ret = -1; +    } + +    err = pthread_mutex_destroy (&p->mutex); +    if (err) { +        logerr (p, err, "%s(%s): pthread_mutex_destroy failed", cap, AUDIO_FUNC); +        ret = -1; +    } +    return ret; +} + +int audio_pt_lock (struct audio_pt *p, const char *cap) +{ +    int err; + +    err = pthread_mutex_lock (&p->mutex); +    if (err) { +        logerr (p, err, "%s(%s): pthread_mutex_lock failed", cap, AUDIO_FUNC); +        return -1; +    } +    return 0; +} + +int audio_pt_unlock (struct audio_pt *p, const char *cap) +{ +    int err; + +    err = pthread_mutex_unlock (&p->mutex); +    if (err) { +        logerr (p, err, "%s(%s): pthread_mutex_unlock failed", cap, AUDIO_FUNC); +        return -1; +    } +    return 0; +} + +int audio_pt_wait (struct audio_pt *p, const char *cap) +{ +    int err; + +    err = pthread_cond_wait (&p->cond, &p->mutex); +    if (err) { +        logerr (p, err, "%s(%s): pthread_cond_wait failed", cap, AUDIO_FUNC); +        return -1; +    } +    return 0; +} + +int audio_pt_unlock_and_signal (struct audio_pt *p, const char *cap) +{ +    int err; + +    err = pthread_mutex_unlock (&p->mutex); +    if (err) { +        logerr (p, err, "%s(%s): pthread_mutex_unlock failed", cap, AUDIO_FUNC); +        return -1; +    } +    err = pthread_cond_signal (&p->cond); +    if (err) { +        logerr (p, err, "%s(%s): pthread_cond_signal failed", cap, AUDIO_FUNC); +        return -1; +    } +    return 0; +} + +int audio_pt_join (struct audio_pt *p, void **arg, const char *cap) +{ +    int err; +    void *ret; + +    err = pthread_join (p->thread, &ret); +    if (err) { +        logerr (p, err, "%s(%s): pthread_join failed", cap, AUDIO_FUNC); +        return -1; +    } +    *arg = ret; +    return 0; +} diff --git a/audio/audio_pt_int.h b/audio/audio_pt_int.h new file mode 100644 index 00000000..0dfff76a --- /dev/null +++ b/audio/audio_pt_int.h @@ -0,0 +1,22 @@ +#ifndef QEMU_AUDIO_PT_INT_H +#define QEMU_AUDIO_PT_INT_H + +#include <pthread.h> + +struct audio_pt { +    const char *drv; +    pthread_t thread; +    pthread_cond_t cond; +    pthread_mutex_t mutex; +}; + +int audio_pt_init (struct audio_pt *, void *(*) (void *), void *, +                   const char *, const char *); +int audio_pt_fini (struct audio_pt *, const char *); +int audio_pt_lock (struct audio_pt *, const char *); +int audio_pt_unlock (struct audio_pt *, const char *); +int audio_pt_wait (struct audio_pt *, const char *); +int audio_pt_unlock_and_signal (struct audio_pt *, const char *); +int audio_pt_join (struct audio_pt *, void **, const char *); + +#endif /* audio_pt_int.h */ diff --git a/audio/audio_template.h b/audio/audio_template.h new file mode 100644 index 00000000..99b27b28 --- /dev/null +++ b/audio/audio_template.h @@ -0,0 +1,514 @@ +/* + * 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 NAME "playback" +#define HWBUF hw->mix_buf +#define TYPE out +#define HW HWVoiceOut +#define SW SWVoiceOut +#else +#define NAME "capture" +#define TYPE in +#define HW HWVoiceIn +#define SW SWVoiceIn +#define HWBUF hw->conv_buf +#endif + +static void glue (audio_init_nb_voices_, TYPE) (struct audio_driver *drv) +{ +    AudioState *s = &glob_audio_state; +    int max_voices = glue (drv->max_voices_, TYPE); +    int voice_size = glue (drv->voice_size_, TYPE); + +    if (glue (s->nb_hw_voices_, TYPE) > max_voices) { +        if (!max_voices) { +#ifdef DAC +            dolog ("Driver `%s' does not support " NAME "\n", drv->name); +#endif +        } +        else { +            dolog ("Driver `%s' does not support %d " NAME " voices, max %d\n", +                   drv->name, +                   glue (s->nb_hw_voices_, TYPE), +                   max_voices); +        } +        glue (s->nb_hw_voices_, TYPE) = max_voices; +    } + +    if (audio_bug (AUDIO_FUNC, !voice_size && max_voices)) { +        dolog ("drv=`%s' voice_size=0 max_voices=%d\n", +               drv->name, max_voices); +        glue (s->nb_hw_voices_, TYPE) = 0; +    } + +    if (audio_bug (AUDIO_FUNC, voice_size && !max_voices)) { +        dolog ("drv=`%s' voice_size=%d max_voices=0\n", +               drv->name, voice_size); +    } +} + +static void glue (audio_pcm_hw_free_resources_, TYPE) (HW *hw) +{ +    g_free (HWBUF); +    HWBUF = NULL; +} + +static int glue (audio_pcm_hw_alloc_resources_, TYPE) (HW *hw) +{ +    HWBUF = audio_calloc (AUDIO_FUNC, hw->samples, sizeof (struct st_sample)); +    if (!HWBUF) { +        dolog ("Could not allocate " NAME " buffer (%d samples)\n", +               hw->samples); +        return -1; +    } + +    return 0; +} + +static void glue (audio_pcm_sw_free_resources_, TYPE) (SW *sw) +{ +    g_free (sw->buf); + +    if (sw->rate) { +        st_rate_stop (sw->rate); +    } + +    sw->buf = NULL; +    sw->rate = NULL; +} + +static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw) +{ +    int samples; + +    samples = ((int64_t) sw->hw->samples << 32) / sw->ratio; + +    sw->buf = audio_calloc (AUDIO_FUNC, samples, sizeof (struct st_sample)); +    if (!sw->buf) { +        dolog ("Could not allocate buffer for `%s' (%d samples)\n", +               SW_NAME (sw), samples); +        return -1; +    } + +#ifdef DAC +    sw->rate = st_rate_start (sw->info.freq, sw->hw->info.freq); +#else +    sw->rate = st_rate_start (sw->hw->info.freq, sw->info.freq); +#endif +    if (!sw->rate) { +        g_free (sw->buf); +        sw->buf = NULL; +        return -1; +    } +    return 0; +} + +static int glue (audio_pcm_sw_init_, TYPE) ( +    SW *sw, +    HW *hw, +    const char *name, +    struct audsettings *as +    ) +{ +    int err; + +    audio_pcm_init_info (&sw->info, as); +    sw->hw = hw; +    sw->active = 0; +#ifdef DAC +    sw->ratio = ((int64_t) sw->hw->info.freq << 32) / sw->info.freq; +    sw->total_hw_samples_mixed = 0; +    sw->empty = 1; +#else +    sw->ratio = ((int64_t) sw->info.freq << 32) / sw->hw->info.freq; +#endif + +#ifdef DAC +    sw->conv = mixeng_conv +#else +    sw->clip = mixeng_clip +#endif +        [sw->info.nchannels == 2] +        [sw->info.sign] +        [sw->info.swap_endianness] +        [audio_bits_to_index (sw->info.bits)]; + +    sw->name = g_strdup (name); +    err = glue (audio_pcm_sw_alloc_resources_, TYPE) (sw); +    if (err) { +        g_free (sw->name); +        sw->name = NULL; +    } +    return err; +} + +static void glue (audio_pcm_sw_fini_, TYPE) (SW *sw) +{ +    glue (audio_pcm_sw_free_resources_, TYPE) (sw); +    g_free (sw->name); +    sw->name = NULL; +} + +static void glue (audio_pcm_hw_add_sw_, TYPE) (HW *hw, SW *sw) +{ +    QLIST_INSERT_HEAD (&hw->sw_head, sw, entries); +} + +static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw) +{ +    QLIST_REMOVE (sw, entries); +} + +static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp) +{ +    AudioState *s = &glob_audio_state; +    HW *hw = *hwp; + +    if (!hw->sw_head.lh_first) { +#ifdef DAC +        audio_detach_capture (hw); +#endif +        QLIST_REMOVE (hw, entries); +        glue (hw->pcm_ops->fini_, TYPE) (hw); +        glue (s->nb_hw_voices_, TYPE) += 1; +        glue (audio_pcm_hw_free_resources_ ,TYPE) (hw); +        g_free (hw); +        *hwp = NULL; +    } +} + +static HW *glue (audio_pcm_hw_find_any_, TYPE) (HW *hw) +{ +    AudioState *s = &glob_audio_state; +    return hw ? hw->entries.le_next : glue (s->hw_head_, TYPE).lh_first; +} + +static HW *glue (audio_pcm_hw_find_any_enabled_, TYPE) (HW *hw) +{ +    while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) { +        if (hw->enabled) { +            return hw; +        } +    } +    return NULL; +} + +static HW *glue (audio_pcm_hw_find_specific_, TYPE) ( +    HW *hw, +    struct audsettings *as +    ) +{ +    while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) { +        if (audio_pcm_info_eq (&hw->info, as)) { +            return hw; +        } +    } +    return NULL; +} + +static HW *glue (audio_pcm_hw_add_new_, TYPE) (struct audsettings *as) +{ +    HW *hw; +    AudioState *s = &glob_audio_state; +    struct audio_driver *drv = s->drv; + +    if (!glue (s->nb_hw_voices_, TYPE)) { +        return NULL; +    } + +    if (audio_bug (AUDIO_FUNC, !drv)) { +        dolog ("No host audio driver\n"); +        return NULL; +    } + +    if (audio_bug (AUDIO_FUNC, !drv->pcm_ops)) { +        dolog ("Host audio driver without pcm_ops\n"); +        return NULL; +    } + +    hw = audio_calloc (AUDIO_FUNC, 1, glue (drv->voice_size_, TYPE)); +    if (!hw) { +        dolog ("Can not allocate voice `%s' size %d\n", +               drv->name, glue (drv->voice_size_, TYPE)); +        return NULL; +    } + +    hw->pcm_ops = drv->pcm_ops; +    hw->ctl_caps = drv->ctl_caps; + +    QLIST_INIT (&hw->sw_head); +#ifdef DAC +    QLIST_INIT (&hw->cap_head); +#endif +    if (glue (hw->pcm_ops->init_, TYPE) (hw, as, s->drv_opaque)) { +        goto err0; +    } + +    if (audio_bug (AUDIO_FUNC, hw->samples <= 0)) { +        dolog ("hw->samples=%d\n", hw->samples); +        goto err1; +    } + +#ifdef DAC +    hw->clip = mixeng_clip +#else +    hw->conv = mixeng_conv +#endif +        [hw->info.nchannels == 2] +        [hw->info.sign] +        [hw->info.swap_endianness] +        [audio_bits_to_index (hw->info.bits)]; + +    if (glue (audio_pcm_hw_alloc_resources_, TYPE) (hw)) { +        goto err1; +    } + +    QLIST_INSERT_HEAD (&s->glue (hw_head_, TYPE), hw, entries); +    glue (s->nb_hw_voices_, TYPE) -= 1; +#ifdef DAC +    audio_attach_capture (hw); +#endif +    return hw; + + err1: +    glue (hw->pcm_ops->fini_, TYPE) (hw); + err0: +    g_free (hw); +    return NULL; +} + +static HW *glue (audio_pcm_hw_add_, TYPE) (struct audsettings *as) +{ +    HW *hw; + +    if (glue (conf.fixed_, TYPE).enabled && glue (conf.fixed_, TYPE).greedy) { +        hw = glue (audio_pcm_hw_add_new_, TYPE) (as); +        if (hw) { +            return hw; +        } +    } + +    hw = glue (audio_pcm_hw_find_specific_, TYPE) (NULL, as); +    if (hw) { +        return hw; +    } + +    hw = glue (audio_pcm_hw_add_new_, TYPE) (as); +    if (hw) { +        return hw; +    } + +    return glue (audio_pcm_hw_find_any_, TYPE) (NULL); +} + +static SW *glue (audio_pcm_create_voice_pair_, TYPE) ( +    const char *sw_name, +    struct audsettings *as +    ) +{ +    SW *sw; +    HW *hw; +    struct audsettings hw_as; + +    if (glue (conf.fixed_, TYPE).enabled) { +        hw_as = glue (conf.fixed_, TYPE).settings; +    } +    else { +        hw_as = *as; +    } + +    sw = audio_calloc (AUDIO_FUNC, 1, sizeof (*sw)); +    if (!sw) { +        dolog ("Could not allocate soft voice `%s' (%zu bytes)\n", +               sw_name ? sw_name : "unknown", sizeof (*sw)); +        goto err1; +    } + +    hw = glue (audio_pcm_hw_add_, TYPE) (&hw_as); +    if (!hw) { +        goto err2; +    } + +    glue (audio_pcm_hw_add_sw_, TYPE) (hw, sw); + +    if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, sw_name, as)) { +        goto err3; +    } + +    return sw; + +err3: +    glue (audio_pcm_hw_del_sw_, TYPE) (sw); +    glue (audio_pcm_hw_gc_, TYPE) (&hw); +err2: +    g_free (sw); +err1: +    return NULL; +} + +static void glue (audio_close_, TYPE) (SW *sw) +{ +    glue (audio_pcm_sw_fini_, TYPE) (sw); +    glue (audio_pcm_hw_del_sw_, TYPE) (sw); +    glue (audio_pcm_hw_gc_, TYPE) (&sw->hw); +    g_free (sw); +} + +void glue (AUD_close_, TYPE) (QEMUSoundCard *card, SW *sw) +{ +    if (sw) { +        if (audio_bug (AUDIO_FUNC, !card)) { +            dolog ("card=%p\n", card); +            return; +        } + +        glue (audio_close_, TYPE) (sw); +    } +} + +SW *glue (AUD_open_, TYPE) ( +    QEMUSoundCard *card, +    SW *sw, +    const char *name, +    void *callback_opaque , +    audio_callback_fn callback_fn, +    struct audsettings *as +    ) +{ +    AudioState *s = &glob_audio_state; + +    if (audio_bug (AUDIO_FUNC, !card || !name || !callback_fn || !as)) { +        dolog ("card=%p name=%p callback_fn=%p as=%p\n", +               card, name, callback_fn, as); +        goto fail; +    } + +    ldebug ("open %s, freq %d, nchannels %d, fmt %d\n", +            name, as->freq, as->nchannels, as->fmt); + +    if (audio_bug (AUDIO_FUNC, audio_validate_settings (as))) { +        audio_print_settings (as); +        goto fail; +    } + +    if (audio_bug (AUDIO_FUNC, !s->drv)) { +        dolog ("Can not open `%s' (no host audio driver)\n", name); +        goto fail; +    } + +    if (sw && audio_pcm_info_eq (&sw->info, as)) { +        return sw; +    } + +    if (!glue (conf.fixed_, TYPE).enabled && sw) { +        glue (AUD_close_, TYPE) (card, sw); +        sw = NULL; +    } + +    if (sw) { +        HW *hw = sw->hw; + +        if (!hw) { +            dolog ("Internal logic error voice `%s' has no hardware store\n", +                   SW_NAME (sw)); +            goto fail; +        } + +        glue (audio_pcm_sw_fini_, TYPE) (sw); +        if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, name, as)) { +            goto fail; +        } +    } +    else { +        sw = glue (audio_pcm_create_voice_pair_, TYPE) (name, as); +        if (!sw) { +            dolog ("Failed to create voice `%s'\n", name); +            return NULL; +        } +    } + +    sw->card = card; +    sw->vol = nominal_volume; +    sw->callback.fn = callback_fn; +    sw->callback.opaque = callback_opaque; + +#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) (card, 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_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 %" PRId64 " old %" PRId64 "\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 muldiv64 (delta, sw->hw->info.freq, 1000000); +} + +#undef TYPE +#undef HW +#undef SW +#undef HWBUF +#undef NAME diff --git a/audio/audio_win_int.c b/audio/audio_win_int.c new file mode 100644 index 00000000..e1324056 --- /dev/null +++ b/audio/audio_win_int.c @@ -0,0 +1,107 @@ +/* public domain */ + +#include "qemu-common.h" + +#define AUDIO_CAP "win-int" +#include <windows.h> +#include <mmsystem.h> + +#include "audio.h" +#include "audio_int.h" +#include "audio_win_int.h" + +int waveformat_from_audio_settings (WAVEFORMATEX *wfx, +                                    struct audsettings *as) +{ +    memset (wfx, 0, sizeof (*wfx)); + +    wfx->wFormatTag = WAVE_FORMAT_PCM; +    wfx->nChannels = as->nchannels; +    wfx->nSamplesPerSec = as->freq; +    wfx->nAvgBytesPerSec = as->freq << (as->nchannels == 2); +    wfx->nBlockAlign = 1 << (as->nchannels == 2); +    wfx->cbSize = 0; + +    switch (as->fmt) { +    case AUD_FMT_S8: +    case AUD_FMT_U8: +        wfx->wBitsPerSample = 8; +        break; + +    case AUD_FMT_S16: +    case AUD_FMT_U16: +        wfx->wBitsPerSample = 16; +        wfx->nAvgBytesPerSec <<= 1; +        wfx->nBlockAlign <<= 1; +        break; + +    case AUD_FMT_S32: +    case AUD_FMT_U32: +        wfx->wBitsPerSample = 32; +        wfx->nAvgBytesPerSec <<= 2; +        wfx->nBlockAlign <<= 2; +        break; + +    default: +        dolog ("Internal logic error: Bad audio format %d\n", as->freq); +        return -1; +    } + +    return 0; +} + +int waveformat_to_audio_settings (WAVEFORMATEX *wfx, +                                  struct audsettings *as) +{ +    if (wfx->wFormatTag != WAVE_FORMAT_PCM) { +        dolog ("Invalid wave format, tag is not PCM, but %d\n", +               wfx->wFormatTag); +        return -1; +    } + +    if (!wfx->nSamplesPerSec) { +        dolog ("Invalid wave format, frequency is zero\n"); +        return -1; +    } +    as->freq = wfx->nSamplesPerSec; + +    switch (wfx->nChannels) { +    case 1: +        as->nchannels = 1; +        break; + +    case 2: +        as->nchannels = 2; +        break; + +    default: +        dolog ( +            "Invalid wave format, number of channels is not 1 or 2, but %d\n", +            wfx->nChannels +            ); +        return -1; +    } + +    switch (wfx->wBitsPerSample) { +    case 8: +        as->fmt = AUD_FMT_U8; +        break; + +    case 16: +        as->fmt = AUD_FMT_S16; +        break; + +    case 32: +        as->fmt = AUD_FMT_S32; +        break; + +    default: +        dolog ("Invalid wave format, bits per sample is not " +               "8, 16 or 32, but %d\n", +               wfx->wBitsPerSample); +        return -1; +    } + +    return 0; +} + diff --git a/audio/audio_win_int.h b/audio/audio_win_int.h new file mode 100644 index 00000000..fa5b3cb8 --- /dev/null +++ b/audio/audio_win_int.h @@ -0,0 +1,10 @@ +#ifndef AUDIO_WIN_INT_H +#define AUDIO_WIN_INT_H + +int waveformat_from_audio_settings (WAVEFORMATEX *wfx, +                                    struct audsettings *as); + +int waveformat_to_audio_settings (WAVEFORMATEX *wfx, +                                  struct audsettings *as); + +#endif /* AUDIO_WIN_INT_H */ diff --git a/audio/coreaudio.c b/audio/coreaudio.c new file mode 100644 index 00000000..6dfd63eb --- /dev/null +++ b/audio/coreaudio.c @@ -0,0 +1,555 @@ +/* + * QEMU OS X CoreAudio audio driver + * + * Copyright (c) 2005 Mike Kronenberg + * + * 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. + */ + +#include <CoreAudio/CoreAudio.h> +#include <string.h>             /* strerror */ +#include <pthread.h>            /* pthread_X */ + +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "coreaudio" +#include "audio_int.h" + +static int isAtexit; + +typedef struct { +    int buffer_frames; +    int nbuffers; +} CoreaudioConf; + +typedef struct coreaudioVoiceOut { +    HWVoiceOut hw; +    pthread_mutex_t mutex; +    AudioDeviceID outputDeviceID; +    UInt32 audioDevicePropertyBufferFrameSize; +    AudioStreamBasicDescription outputStreamBasicDescription; +    int live; +    int decr; +    int rpos; +} coreaudioVoiceOut; + +static void coreaudio_logstatus (OSStatus status) +{ +    const char *str = "BUG"; + +    switch(status) { +    case kAudioHardwareNoError: +        str = "kAudioHardwareNoError"; +        break; + +    case kAudioHardwareNotRunningError: +        str = "kAudioHardwareNotRunningError"; +        break; + +    case kAudioHardwareUnspecifiedError: +        str = "kAudioHardwareUnspecifiedError"; +        break; + +    case kAudioHardwareUnknownPropertyError: +        str = "kAudioHardwareUnknownPropertyError"; +        break; + +    case kAudioHardwareBadPropertySizeError: +        str = "kAudioHardwareBadPropertySizeError"; +        break; + +    case kAudioHardwareIllegalOperationError: +        str = "kAudioHardwareIllegalOperationError"; +        break; + +    case kAudioHardwareBadDeviceError: +        str = "kAudioHardwareBadDeviceError"; +        break; + +    case kAudioHardwareBadStreamError: +        str = "kAudioHardwareBadStreamError"; +        break; + +    case kAudioHardwareUnsupportedOperationError: +        str = "kAudioHardwareUnsupportedOperationError"; +        break; + +    case kAudioDeviceUnsupportedFormatError: +        str = "kAudioDeviceUnsupportedFormatError"; +        break; + +    case kAudioDevicePermissionsError: +        str = "kAudioDevicePermissionsError"; +        break; + +    default: +        AUD_log (AUDIO_CAP, "Reason: status code %" PRId32 "\n", (int32_t)status); +        return; +    } + +    AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) coreaudio_logerr ( +    OSStatus status, +    const char *fmt, +    ... +    ) +{ +    va_list ap; + +    va_start (ap, fmt); +    AUD_log (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    coreaudio_logstatus (status); +} + +static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 ( +    OSStatus status, +    const char *typ, +    const char *fmt, +    ... +    ) +{ +    va_list ap; + +    AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + +    va_start (ap, fmt); +    AUD_vlog (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    coreaudio_logstatus (status); +} + +static inline UInt32 isPlaying (AudioDeviceID outputDeviceID) +{ +    OSStatus status; +    UInt32 result = 0; +    UInt32 propertySize = sizeof(outputDeviceID); +    status = AudioDeviceGetProperty( +        outputDeviceID, 0, 0, +        kAudioDevicePropertyDeviceIsRunning, &propertySize, &result); +    if (status != kAudioHardwareNoError) { +        coreaudio_logerr(status, +                         "Could not determine whether Device is playing\n"); +    } +    return result; +} + +static void coreaudio_atexit (void) +{ +    isAtexit = 1; +} + +static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name) +{ +    int err; + +    err = pthread_mutex_lock (&core->mutex); +    if (err) { +        dolog ("Could not lock voice for %s\nReason: %s\n", +               fn_name, strerror (err)); +        return -1; +    } +    return 0; +} + +static int coreaudio_unlock (coreaudioVoiceOut *core, const char *fn_name) +{ +    int err; + +    err = pthread_mutex_unlock (&core->mutex); +    if (err) { +        dolog ("Could not unlock voice for %s\nReason: %s\n", +               fn_name, strerror (err)); +        return -1; +    } +    return 0; +} + +static int coreaudio_run_out (HWVoiceOut *hw, int live) +{ +    int decr; +    coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + +    if (coreaudio_lock (core, "coreaudio_run_out")) { +        return 0; +    } + +    if (core->decr > live) { +        ldebug ("core->decr %d live %d core->live %d\n", +                core->decr, +                live, +                core->live); +    } + +    decr = audio_MIN (core->decr, live); +    core->decr -= decr; + +    core->live = live - decr; +    hw->rpos = core->rpos; + +    coreaudio_unlock (core, "coreaudio_run_out"); +    return decr; +} + +/* callback to feed audiooutput buffer */ +static OSStatus audioDeviceIOProc( +    AudioDeviceID inDevice, +    const AudioTimeStamp* inNow, +    const AudioBufferList* inInputData, +    const AudioTimeStamp* inInputTime, +    AudioBufferList* outOutputData, +    const AudioTimeStamp* inOutputTime, +    void* hwptr) +{ +    UInt32 frame, frameCount; +    float *out = outOutputData->mBuffers[0].mData; +    HWVoiceOut *hw = hwptr; +    coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; +    int rpos, live; +    struct st_sample *src; +#ifndef FLOAT_MIXENG +#ifdef RECIPROCAL +    const float scale = 1.f / UINT_MAX; +#else +    const float scale = UINT_MAX; +#endif +#endif + +    if (coreaudio_lock (core, "audioDeviceIOProc")) { +        inInputTime = 0; +        return 0; +    } + +    frameCount = core->audioDevicePropertyBufferFrameSize; +    live = core->live; + +    /* if there are not enough samples, set signal and return */ +    if (live < frameCount) { +        inInputTime = 0; +        coreaudio_unlock (core, "audioDeviceIOProc(empty)"); +        return 0; +    } + +    rpos = core->rpos; +    src = hw->mix_buf + rpos; + +    /* fill buffer */ +    for (frame = 0; frame < frameCount; frame++) { +#ifdef FLOAT_MIXENG +        *out++ = src[frame].l; /* left channel */ +        *out++ = src[frame].r; /* right channel */ +#else +#ifdef RECIPROCAL +        *out++ = src[frame].l * scale; /* left channel */ +        *out++ = src[frame].r * scale; /* right channel */ +#else +        *out++ = src[frame].l / scale; /* left channel */ +        *out++ = src[frame].r / scale; /* right channel */ +#endif +#endif +    } + +    rpos = (rpos + frameCount) % hw->samples; +    core->decr += frameCount; +    core->rpos = rpos; + +    coreaudio_unlock (core, "audioDeviceIOProc"); +    return 0; +} + +static int coreaudio_write (SWVoiceOut *sw, void *buf, int len) +{ +    return audio_pcm_sw_write (sw, buf, len); +} + +static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, +                              void *drv_opaque) +{ +    OSStatus status; +    coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; +    UInt32 propertySize; +    int err; +    const char *typ = "playback"; +    AudioValueRange frameRange; +    CoreaudioConf *conf = drv_opaque; + +    /* create mutex */ +    err = pthread_mutex_init(&core->mutex, NULL); +    if (err) { +        dolog("Could not create mutex\nReason: %s\n", strerror (err)); +        return -1; +    } + +    audio_pcm_init_info (&hw->info, as); + +    /* open default output device */ +    propertySize = sizeof(core->outputDeviceID); +    status = AudioHardwareGetProperty( +        kAudioHardwarePropertyDefaultOutputDevice, +        &propertySize, +        &core->outputDeviceID); +    if (status != kAudioHardwareNoError) { +        coreaudio_logerr2 (status, typ, +                           "Could not get default output Device\n"); +        return -1; +    } +    if (core->outputDeviceID == kAudioDeviceUnknown) { +        dolog ("Could not initialize %s - Unknown Audiodevice\n", typ); +        return -1; +    } + +    /* get minimum and maximum buffer frame sizes */ +    propertySize = sizeof(frameRange); +    status = AudioDeviceGetProperty( +        core->outputDeviceID, +        0, +        0, +        kAudioDevicePropertyBufferFrameSizeRange, +        &propertySize, +        &frameRange); +    if (status != kAudioHardwareNoError) { +        coreaudio_logerr2 (status, typ, +                           "Could not get device buffer frame range\n"); +        return -1; +    } + +    if (frameRange.mMinimum > conf->buffer_frames) { +        core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; +        dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); +    } +    else if (frameRange.mMaximum < conf->buffer_frames) { +        core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; +        dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); +    } +    else { +        core->audioDevicePropertyBufferFrameSize = conf->buffer_frames; +    } + +    /* set Buffer Frame Size */ +    propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); +    status = AudioDeviceSetProperty( +        core->outputDeviceID, +        NULL, +        0, +        false, +        kAudioDevicePropertyBufferFrameSize, +        propertySize, +        &core->audioDevicePropertyBufferFrameSize); +    if (status != kAudioHardwareNoError) { +        coreaudio_logerr2 (status, typ, +                           "Could not set device buffer frame size %" PRIu32 "\n", +                           (uint32_t)core->audioDevicePropertyBufferFrameSize); +        return -1; +    } + +    /* get Buffer Frame Size */ +    propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); +    status = AudioDeviceGetProperty( +        core->outputDeviceID, +        0, +        false, +        kAudioDevicePropertyBufferFrameSize, +        &propertySize, +        &core->audioDevicePropertyBufferFrameSize); +    if (status != kAudioHardwareNoError) { +        coreaudio_logerr2 (status, typ, +                           "Could not get device buffer frame size\n"); +        return -1; +    } +    hw->samples = conf->nbuffers * core->audioDevicePropertyBufferFrameSize; + +    /* get StreamFormat */ +    propertySize = sizeof(core->outputStreamBasicDescription); +    status = AudioDeviceGetProperty( +        core->outputDeviceID, +        0, +        false, +        kAudioDevicePropertyStreamFormat, +        &propertySize, +        &core->outputStreamBasicDescription); +    if (status != kAudioHardwareNoError) { +        coreaudio_logerr2 (status, typ, +                           "Could not get Device Stream properties\n"); +        core->outputDeviceID = kAudioDeviceUnknown; +        return -1; +    } + +    /* set Samplerate */ +    core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq; +    propertySize = sizeof(core->outputStreamBasicDescription); +    status = AudioDeviceSetProperty( +        core->outputDeviceID, +        0, +        0, +        0, +        kAudioDevicePropertyStreamFormat, +        propertySize, +        &core->outputStreamBasicDescription); +    if (status != kAudioHardwareNoError) { +        coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n", +                           as->freq); +        core->outputDeviceID = kAudioDeviceUnknown; +        return -1; +    } + +    /* set Callback */ +    status = AudioDeviceAddIOProc(core->outputDeviceID, audioDeviceIOProc, hw); +    if (status != kAudioHardwareNoError) { +        coreaudio_logerr2 (status, typ, "Could not set IOProc\n"); +        core->outputDeviceID = kAudioDeviceUnknown; +        return -1; +    } + +    /* start Playback */ +    if (!isPlaying(core->outputDeviceID)) { +        status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); +        if (status != kAudioHardwareNoError) { +            coreaudio_logerr2 (status, typ, "Could not start playback\n"); +            AudioDeviceRemoveIOProc(core->outputDeviceID, audioDeviceIOProc); +            core->outputDeviceID = kAudioDeviceUnknown; +            return -1; +        } +    } + +    return 0; +} + +static void coreaudio_fini_out (HWVoiceOut *hw) +{ +    OSStatus status; +    int err; +    coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + +    if (!isAtexit) { +        /* stop playback */ +        if (isPlaying(core->outputDeviceID)) { +            status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); +            if (status != kAudioHardwareNoError) { +                coreaudio_logerr (status, "Could not stop playback\n"); +            } +        } + +        /* remove callback */ +        status = AudioDeviceRemoveIOProc(core->outputDeviceID, +                                         audioDeviceIOProc); +        if (status != kAudioHardwareNoError) { +            coreaudio_logerr (status, "Could not remove IOProc\n"); +        } +    } +    core->outputDeviceID = kAudioDeviceUnknown; + +    /* destroy mutex */ +    err = pthread_mutex_destroy(&core->mutex); +    if (err) { +        dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); +    } +} + +static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ +    OSStatus status; +    coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + +    switch (cmd) { +    case VOICE_ENABLE: +        /* start playback */ +        if (!isPlaying(core->outputDeviceID)) { +            status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); +            if (status != kAudioHardwareNoError) { +                coreaudio_logerr (status, "Could not resume playback\n"); +            } +        } +        break; + +    case VOICE_DISABLE: +        /* stop playback */ +        if (!isAtexit) { +            if (isPlaying(core->outputDeviceID)) { +                status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); +                if (status != kAudioHardwareNoError) { +                    coreaudio_logerr (status, "Could not pause playback\n"); +                } +            } +        } +        break; +    } +    return 0; +} + +static CoreaudioConf glob_conf = { +    .buffer_frames = 512, +    .nbuffers = 4, +}; + +static void *coreaudio_audio_init (void) +{ +    CoreaudioConf *conf = g_malloc(sizeof(CoreaudioConf)); +    *conf = glob_conf; + +    atexit(coreaudio_atexit); +    return conf; +} + +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, +    .run_out  = coreaudio_run_out, +    .write    = coreaudio_write, +    .ctl_out  = coreaudio_ctl_out +}; + +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, +    .can_be_default = 1, +    .max_voices_out = 1, +    .max_voices_in  = 0, +    .voice_size_out = sizeof (coreaudioVoiceOut), +    .voice_size_in  = 0 +}; diff --git a/audio/dsound_template.h b/audio/dsound_template.h new file mode 100644 index 00000000..b439f33f --- /dev/null +++ b/audio/dsound_template.h @@ -0,0 +1,278 @@ +/* + * QEMU DirectSound audio driver 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 DSBTYPE_IN +#define NAME "capture buffer" +#define NAME2 "DirectSoundCapture" +#define TYPE in +#define IFACE IDirectSoundCaptureBuffer +#define BUFPTR LPDIRECTSOUNDCAPTUREBUFFER +#define FIELD dsound_capture_buffer +#define FIELD2 dsound_capture +#else +#define NAME "playback buffer" +#define NAME2 "DirectSound" +#define TYPE out +#define IFACE IDirectSoundBuffer +#define BUFPTR LPDIRECTSOUNDBUFFER +#define FIELD dsound_buffer +#define FIELD2 dsound +#endif + +static int glue (dsound_unlock_, TYPE) ( +    BUFPTR buf, +    LPVOID p1, +    LPVOID p2, +    DWORD blen1, +    DWORD blen2 +    ) +{ +    HRESULT hr; + +    hr = glue (IFACE, _Unlock) (buf, p1, blen1, p2, blen2); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not unlock " NAME "\n"); +        return -1; +    } + +    return 0; +} + +static int glue (dsound_lock_, TYPE) ( +    BUFPTR buf, +    struct audio_pcm_info *info, +    DWORD pos, +    DWORD len, +    LPVOID *p1p, +    LPVOID *p2p, +    DWORD *blen1p, +    DWORD *blen2p, +    int entire, +    dsound *s +    ) +{ +    HRESULT hr; +    LPVOID p1 = NULL, p2 = NULL; +    DWORD blen1 = 0, blen2 = 0; +    DWORD flag; + +#ifdef DSBTYPE_IN +    flag = entire ? DSCBLOCK_ENTIREBUFFER : 0; +#else +    flag = entire ? DSBLOCK_ENTIREBUFFER : 0; +#endif +    hr = glue(IFACE, _Lock)(buf, pos, len, &p1, &blen1, &p2, &blen2, flag); + +    if (FAILED (hr)) { +#ifndef DSBTYPE_IN +        if (hr == DSERR_BUFFERLOST) { +            if (glue (dsound_restore_, TYPE) (buf, s)) { +                dsound_logerr (hr, "Could not lock " NAME "\n"); +            } +            goto fail; +        } +#endif +        dsound_logerr (hr, "Could not lock " NAME "\n"); +        goto fail; +    } + +    if ((p1 && (blen1 & info->align)) || (p2 && (blen2 & info->align))) { +        dolog ("DirectSound returned misaligned buffer %ld %ld\n", +               blen1, blen2); +        glue (dsound_unlock_, TYPE) (buf, p1, p2, blen1, blen2); +        goto fail; +    } + +    if (!p1 && blen1) { +        dolog ("warning: !p1 && blen1=%ld\n", blen1); +        blen1 = 0; +    } + +    if (!p2 && blen2) { +        dolog ("warning: !p2 && blen2=%ld\n", blen2); +        blen2 = 0; +    } + +    *p1p = p1; +    *p2p = p2; +    *blen1p = blen1; +    *blen2p = blen2; +    return 0; + + fail: +    *p1p = NULL - 1; +    *p2p = NULL - 1; +    *blen1p = -1; +    *blen2p = -1; +    return -1; +} + +#ifdef DSBTYPE_IN +static void dsound_fini_in (HWVoiceIn *hw) +#else +static void dsound_fini_out (HWVoiceOut *hw) +#endif +{ +    HRESULT hr; +#ifdef DSBTYPE_IN +    DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; +#else +    DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; +#endif + +    if (ds->FIELD) { +        hr = glue (IFACE, _Stop) (ds->FIELD); +        if (FAILED (hr)) { +            dsound_logerr (hr, "Could not stop " NAME "\n"); +        } + +        hr = glue (IFACE, _Release) (ds->FIELD); +        if (FAILED (hr)) { +            dsound_logerr (hr, "Could not release " NAME "\n"); +        } +        ds->FIELD = NULL; +    } +} + +#ifdef DSBTYPE_IN +static int dsound_init_in(HWVoiceIn *hw, struct audsettings *as, +                          void *drv_opaque) +#else +static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, +                           void *drv_opaque) +#endif +{ +    int err; +    HRESULT hr; +    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; +#else +    const char *typ = "DAC"; +    DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; +    DSBUFFERDESC bd; +    DSBCAPS bc; +#endif + +    if (!s->FIELD2) { +        dolog ("Attempt to initialize voice without " NAME2 " object\n"); +        return -1; +    } + +    err = waveformat_from_audio_settings (&wfx, as); +    if (err) { +        return -1; +    } + +    memset (&bd, 0, sizeof (bd)); +    bd.dwSize = sizeof (bd); +    bd.lpwfxFormat = &wfx; +#ifdef DSBTYPE_IN +    bd.dwBufferBytes = conf->bufsize_in; +    hr = IDirectSoundCapture_CreateCaptureBuffer ( +        s->dsound_capture, +        &bd, +        &ds->dsound_capture_buffer, +        NULL +        ); +#else +    bd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2; +    bd.dwBufferBytes = conf->bufsize_out; +    hr = IDirectSound_CreateSoundBuffer ( +        s->dsound, +        &bd, +        &ds->dsound_buffer, +        NULL +        ); +#endif + +    if (FAILED (hr)) { +        dsound_logerr2 (hr, typ, "Could not create " NAME "\n"); +        return -1; +    } + +    hr = glue (IFACE, _GetFormat) (ds->FIELD, &wfx, sizeof (wfx), NULL); +    if (FAILED (hr)) { +        dsound_logerr2 (hr, typ, "Could not get " NAME " format\n"); +        goto fail0; +    } + +#ifdef DEBUG_DSOUND +    dolog (NAME "\n"); +    print_wave_format (&wfx); +#endif + +    memset (&bc, 0, sizeof (bc)); +    bc.dwSize = sizeof (bc); + +    hr = glue (IFACE, _GetCaps) (ds->FIELD, &bc); +    if (FAILED (hr)) { +        dsound_logerr2 (hr, typ, "Could not get " NAME " format\n"); +        goto fail0; +    } + +    err = waveformat_to_audio_settings (&wfx, &obt_as); +    if (err) { +        goto fail0; +    } + +    ds->first_time = 1; +    obt_as.endianness = 0; +    audio_pcm_init_info (&hw->info, &obt_as); + +    if (bc.dwBufferBytes & hw->info.align) { +        dolog ( +            "GetCaps returned misaligned buffer size %ld, alignment %d\n", +            bc.dwBufferBytes, hw->info.align + 1 +            ); +    } +    hw->samples = bc.dwBufferBytes >> hw->info.shift; +    ds->s = s; + +#ifdef DEBUG_DSOUND +    dolog ("caps %ld, desc %ld\n", +           bc.dwBufferBytes, bd.dwBufferBytes); + +    dolog ("bufsize %d, freq %d, chan %d, fmt %d\n", +           hw->bufsize, settings.freq, settings.nchannels, settings.fmt); +#endif +    return 0; + + fail0: +    glue (dsound_fini_, TYPE) (hw); +    return -1; +} + +#undef NAME +#undef NAME2 +#undef TYPE +#undef IFACE +#undef BUFPTR +#undef FIELD +#undef FIELD2 diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c new file mode 100644 index 00000000..e9472c10 --- /dev/null +++ b/audio/dsoundaudio.c @@ -0,0 +1,904 @@ +/* + * QEMU DirectSound audio driver + * + * 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. + */ + +/* + * SEAL 1.07 by Carlos 'pel' Hasan was used as documentation + */ + +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "dsound" +#include "audio_int.h" + +#include <windows.h> +#include <mmsystem.h> +#include <objbase.h> +#include <dsound.h> + +#include "audio_win_int.h" + +/* #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; +} dsound; + +typedef struct { +    HWVoiceOut hw; +    LPDIRECTSOUNDBUFFER dsound_buffer; +    DWORD old_pos; +    int first_time; +    dsound *s; +#ifdef DEBUG_DSOUND +    DWORD old_ppos; +    DWORD played; +    DWORD mixed; +#endif +} DSoundVoiceOut; + +typedef struct { +    HWVoiceIn hw; +    int first_time; +    LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer; +    dsound *s; +} DSoundVoiceIn; + +static void dsound_log_hresult (HRESULT hr) +{ +    const char *str = "BUG"; + +    switch (hr) { +    case DS_OK: +        str = "The method succeeded"; +        break; +#ifdef DS_NO_VIRTUALIZATION +    case DS_NO_VIRTUALIZATION: +        str = "The buffer was created, but another 3D algorithm was substituted"; +        break; +#endif +#ifdef DS_INCOMPLETE +    case DS_INCOMPLETE: +        str = "The method succeeded, but not all the optional effects were obtained"; +        break; +#endif +#ifdef DSERR_ACCESSDENIED +    case DSERR_ACCESSDENIED: +        str = "The request failed because access was denied"; +        break; +#endif +#ifdef DSERR_ALLOCATED +    case DSERR_ALLOCATED: +        str = "The request failed because resources, such as a priority level, were already in use by another caller"; +        break; +#endif +#ifdef DSERR_ALREADYINITIALIZED +    case DSERR_ALREADYINITIALIZED: +        str = "The object is already initialized"; +        break; +#endif +#ifdef DSERR_BADFORMAT +    case DSERR_BADFORMAT: +        str = "The specified wave format is not supported"; +        break; +#endif +#ifdef DSERR_BADSENDBUFFERGUID +    case DSERR_BADSENDBUFFERGUID: +        str = "The GUID specified in an audiopath file does not match a valid mix-in buffer"; +        break; +#endif +#ifdef DSERR_BUFFERLOST +    case DSERR_BUFFERLOST: +        str = "The buffer memory has been lost and must be restored"; +        break; +#endif +#ifdef DSERR_BUFFERTOOSMALL +    case DSERR_BUFFERTOOSMALL: +        str = "The buffer size is not great enough to enable effects processing"; +        break; +#endif +#ifdef DSERR_CONTROLUNAVAIL +    case DSERR_CONTROLUNAVAIL: +        str = "The buffer control (volume, pan, and so on) requested by the caller is not available. Controls must be specified when the buffer is created, using the dwFlags member of DSBUFFERDESC"; +        break; +#endif +#ifdef DSERR_DS8_REQUIRED +    case DSERR_DS8_REQUIRED: +        str = "A DirectSound object of class CLSID_DirectSound8 or later is required for the requested functionality. For more information, see IDirectSound8 Interface"; +        break; +#endif +#ifdef DSERR_FXUNAVAILABLE +    case DSERR_FXUNAVAILABLE: +        str = "The effects requested could not be found on the system, or they are in the wrong order or in the wrong location; for example, an effect expected in hardware was found in software"; +        break; +#endif +#ifdef DSERR_GENERIC +    case DSERR_GENERIC : +        str = "An undetermined error occurred inside the DirectSound subsystem"; +        break; +#endif +#ifdef DSERR_INVALIDCALL +    case DSERR_INVALIDCALL: +        str = "This function is not valid for the current state of this object"; +        break; +#endif +#ifdef DSERR_INVALIDPARAM +    case DSERR_INVALIDPARAM: +        str = "An invalid parameter was passed to the returning function"; +        break; +#endif +#ifdef DSERR_NOAGGREGATION +    case DSERR_NOAGGREGATION: +        str = "The object does not support aggregation"; +        break; +#endif +#ifdef DSERR_NODRIVER +    case DSERR_NODRIVER: +        str = "No sound driver is available for use, or the given GUID is not a valid DirectSound device ID"; +        break; +#endif +#ifdef DSERR_NOINTERFACE +    case DSERR_NOINTERFACE: +        str = "The requested COM interface is not available"; +        break; +#endif +#ifdef DSERR_OBJECTNOTFOUND +    case DSERR_OBJECTNOTFOUND: +        str = "The requested object was not found"; +        break; +#endif +#ifdef DSERR_OTHERAPPHASPRIO +    case DSERR_OTHERAPPHASPRIO: +        str = "Another application has a higher priority level, preventing this call from succeeding"; +        break; +#endif +#ifdef DSERR_OUTOFMEMORY +    case DSERR_OUTOFMEMORY: +        str = "The DirectSound subsystem could not allocate sufficient memory to complete the caller's request"; +        break; +#endif +#ifdef DSERR_PRIOLEVELNEEDED +    case DSERR_PRIOLEVELNEEDED: +        str = "A cooperative level of DSSCL_PRIORITY or higher is required"; +        break; +#endif +#ifdef DSERR_SENDLOOP +    case DSERR_SENDLOOP: +        str = "A circular loop of send effects was detected"; +        break; +#endif +#ifdef DSERR_UNINITIALIZED +    case DSERR_UNINITIALIZED: +        str = "The Initialize method has not been called or has not been called successfully before other methods were called"; +        break; +#endif +#ifdef DSERR_UNSUPPORTED +    case DSERR_UNSUPPORTED: +        str = "The function called is not supported at this time"; +        break; +#endif +    default: +        AUD_log (AUDIO_CAP, "Reason: Unknown (HRESULT %#lx)\n", hr); +        return; +    } + +    AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) dsound_logerr ( +    HRESULT hr, +    const char *fmt, +    ... +    ) +{ +    va_list ap; + +    va_start (ap, fmt); +    AUD_vlog (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    dsound_log_hresult (hr); +} + +static void GCC_FMT_ATTR (3, 4) dsound_logerr2 ( +    HRESULT hr, +    const char *typ, +    const char *fmt, +    ... +    ) +{ +    va_list ap; + +    AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); +    va_start (ap, fmt); +    AUD_vlog (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    dsound_log_hresult (hr); +} + +static DWORD millis_to_bytes (struct audio_pcm_info *info, DWORD millis) +{ +    return (millis * info->bytes_per_second) / 1000; +} + +#ifdef DEBUG_DSOUND +static void print_wave_format (WAVEFORMATEX *wfx) +{ +    dolog ("tag             = %d\n", wfx->wFormatTag); +    dolog ("nChannels       = %d\n", wfx->nChannels); +    dolog ("nSamplesPerSec  = %ld\n", wfx->nSamplesPerSec); +    dolog ("nAvgBytesPerSec = %ld\n", wfx->nAvgBytesPerSec); +    dolog ("nBlockAlign     = %d\n", wfx->nBlockAlign); +    dolog ("wBitsPerSample  = %d\n", wfx->wBitsPerSample); +    dolog ("cbSize          = %d\n", wfx->cbSize); +} +#endif + +static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb, dsound *s) +{ +    HRESULT hr; + +    hr = IDirectSoundBuffer_Restore (dsb); + +    if (hr != DS_OK) { +        dsound_logerr (hr, "Could not restore playback buffer\n"); +        return -1; +    } +    return 0; +} + +#include "dsound_template.h" +#define DSBTYPE_IN +#include "dsound_template.h" +#undef DSBTYPE_IN + +static int dsound_get_status_out (LPDIRECTSOUNDBUFFER dsb, DWORD *statusp, +                                  dsound *s) +{ +    HRESULT hr; + +    hr = IDirectSoundBuffer_GetStatus (dsb, statusp); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not get playback buffer status\n"); +        return -1; +    } + +    if (*statusp & DSERR_BUFFERLOST) { +        dsound_restore_out(dsb, s); +        return -1; +    } + +    return 0; +} + +static int dsound_get_status_in (LPDIRECTSOUNDCAPTUREBUFFER dscb, +                                 DWORD *statusp) +{ +    HRESULT hr; + +    hr = IDirectSoundCaptureBuffer_GetStatus (dscb, statusp); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not get capture buffer status\n"); +        return -1; +    } + +    return 0; +} + +static void dsound_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len) +{ +    int src_len1 = dst_len; +    int src_len2 = 0; +    int pos = hw->rpos + dst_len; +    struct st_sample *src1 = hw->mix_buf + hw->rpos; +    struct st_sample *src2 = NULL; + +    if (pos > hw->samples) { +        src_len1 = hw->samples - hw->rpos; +        src2 = hw->mix_buf; +        src_len2 = dst_len - src_len1; +        pos = src_len2; +    } + +    if (src_len1) { +        hw->clip (dst, src1, src_len1); +    } + +    if (src_len2) { +        dst = advance (dst, src_len1 << hw->info.shift); +        hw->clip (dst, src2, src_len2); +    } + +    hw->rpos = pos % hw->samples; +} + +static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb, +                                 dsound *s) +{ +    int err; +    LPVOID p1, p2; +    DWORD blen1, blen2, len1, len2; + +    err = dsound_lock_out ( +        dsb, +        &hw->info, +        0, +        hw->samples << hw->info.shift, +        &p1, &p2, +        &blen1, &blen2, +        1, +        s +        ); +    if (err) { +        return; +    } + +    len1 = blen1 >> hw->info.shift; +    len2 = blen2 >> hw->info.shift; + +#ifdef DEBUG_DSOUND +    dolog ("clear %p,%ld,%ld %p,%ld,%ld\n", +           p1, blen1, len1, +           p2, blen2, len2); +#endif + +    if (p1 && len1) { +        audio_pcm_info_clear_buf (&hw->info, p1, len1); +    } + +    if (p2 && len2) { +        audio_pcm_info_clear_buf (&hw->info, p2, len2); +    } + +    dsound_unlock_out (dsb, p1, p2, blen1, blen2); +} + +static int dsound_open (dsound *s) +{ +    HRESULT hr; +    HWND hwnd; + +    hwnd = GetForegroundWindow (); +    hr = IDirectSound_SetCooperativeLevel ( +        s->dsound, +        hwnd, +        DSSCL_PRIORITY +        ); + +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not set cooperative level for window %p\n", +                       hwnd); +        return -1; +    } + +    return 0; +} + +static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ +    HRESULT hr; +    DWORD status; +    DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; +    LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer; +    dsound *s = ds->s; + +    if (!dsb) { +        dolog ("Attempt to control voice without a buffer\n"); +        return 0; +    } + +    switch (cmd) { +    case VOICE_ENABLE: +        if (dsound_get_status_out (dsb, &status, s)) { +            return -1; +        } + +        if (status & DSBSTATUS_PLAYING) { +            dolog ("warning: Voice is already playing\n"); +            return 0; +        } + +        dsound_clear_sample (hw, dsb, s); + +        hr = IDirectSoundBuffer_Play (dsb, 0, 0, DSBPLAY_LOOPING); +        if (FAILED (hr)) { +            dsound_logerr (hr, "Could not start playing buffer\n"); +            return -1; +        } +        break; + +    case VOICE_DISABLE: +        if (dsound_get_status_out (dsb, &status, s)) { +            return -1; +        } + +        if (status & DSBSTATUS_PLAYING) { +            hr = IDirectSoundBuffer_Stop (dsb); +            if (FAILED (hr)) { +                dsound_logerr (hr, "Could not stop playing buffer\n"); +                return -1; +            } +        } +        else { +            dolog ("warning: Voice is not playing\n"); +        } +        break; +    } +    return 0; +} + +static int dsound_write (SWVoiceOut *sw, void *buf, int len) +{ +    return audio_pcm_sw_write (sw, buf, len); +} + +static int dsound_run_out (HWVoiceOut *hw, int live) +{ +    int err; +    HRESULT hr; +    DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; +    LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer; +    int len, hwshift; +    DWORD blen1, blen2; +    DWORD len1, len2; +    DWORD decr; +    DWORD wpos, ppos, old_pos; +    LPVOID p1, p2; +    int bufsize; +    dsound *s = ds->s; +    DSoundConf *conf = &s->conf; + +    if (!dsb) { +        dolog ("Attempt to run empty with playback buffer\n"); +        return 0; +    } + +    hwshift = hw->info.shift; +    bufsize = hw->samples << hwshift; + +    hr = IDirectSoundBuffer_GetCurrentPosition ( +        dsb, +        &ppos, +        ds->first_time ? &wpos : NULL +        ); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not get playback buffer position\n"); +        return 0; +    } + +    len = live << hwshift; + +    if (ds->first_time) { +        if (conf->latency_millis) { +            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; +            old_pos %= bufsize; +            old_pos &= ~hw->info.align; +        } +        else { +            old_pos = wpos; +        } +#ifdef DEBUG_DSOUND +        ds->played = 0; +        ds->mixed = 0; +#endif +    } +    else { +        if (ds->old_pos == ppos) { +#ifdef DEBUG_DSOUND +            dolog ("old_pos == ppos\n"); +#endif +            return 0; +        } + +#ifdef DEBUG_DSOUND +        ds->played += audio_ring_dist (ds->old_pos, ppos, hw->bufsize); +#endif +        old_pos = ds->old_pos; +    } + +    if ((old_pos < ppos) && ((old_pos + len) > ppos)) { +        len = ppos - old_pos; +    } +    else { +        if ((old_pos > ppos) && ((old_pos + len) > (ppos + bufsize))) { +            len = bufsize - old_pos + ppos; +        } +    } + +    if (audio_bug (AUDIO_FUNC, len < 0 || len > bufsize)) { +        dolog ("len=%d bufsize=%d old_pos=%ld ppos=%ld\n", +               len, bufsize, old_pos, ppos); +        return 0; +    } + +    len &= ~hw->info.align; +    if (!len) { +        return 0; +    } + +#ifdef DEBUG_DSOUND +    ds->old_ppos = ppos; +#endif +    err = dsound_lock_out ( +        dsb, +        &hw->info, +        old_pos, +        len, +        &p1, &p2, +        &blen1, &blen2, +        0, +        s +        ); +    if (err) { +        return 0; +    } + +    len1 = blen1 >> hwshift; +    len2 = blen2 >> hwshift; +    decr = len1 + len2; + +    if (p1 && len1) { +        dsound_write_sample (hw, p1, len1); +    } + +    if (p2 && len2) { +        dsound_write_sample (hw, p2, len2); +    } + +    dsound_unlock_out (dsb, p1, p2, blen1, blen2); +    ds->old_pos = (old_pos + (decr << hwshift)) % bufsize; + +#ifdef DEBUG_DSOUND +    ds->mixed += decr << hwshift; + +    dolog ("played %lu mixed %lu diff %ld sec %f\n", +           ds->played, +           ds->mixed, +           ds->mixed - ds->played, +           abs (ds->mixed - ds->played) / (double) hw->info.bytes_per_second); +#endif +    return decr; +} + +static int dsound_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ +    HRESULT hr; +    DWORD status; +    DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; +    LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer; + +    if (!dscb) { +        dolog ("Attempt to control capture voice without a buffer\n"); +        return -1; +    } + +    switch (cmd) { +    case VOICE_ENABLE: +        if (dsound_get_status_in (dscb, &status)) { +            return -1; +        } + +        if (status & DSCBSTATUS_CAPTURING) { +            dolog ("warning: Voice is already capturing\n"); +            return 0; +        } + +        /* clear ?? */ + +        hr = IDirectSoundCaptureBuffer_Start (dscb, DSCBSTART_LOOPING); +        if (FAILED (hr)) { +            dsound_logerr (hr, "Could not start capturing\n"); +            return -1; +        } +        break; + +    case VOICE_DISABLE: +        if (dsound_get_status_in (dscb, &status)) { +            return -1; +        } + +        if (status & DSCBSTATUS_CAPTURING) { +            hr = IDirectSoundCaptureBuffer_Stop (dscb); +            if (FAILED (hr)) { +                dsound_logerr (hr, "Could not stop capturing\n"); +                return -1; +            } +        } +        else { +            dolog ("warning: Voice is not capturing\n"); +        } +        break; +    } +    return 0; +} + +static int dsound_read (SWVoiceIn *sw, void *buf, int len) +{ +    return audio_pcm_sw_read (sw, buf, len); +} + +static int dsound_run_in (HWVoiceIn *hw) +{ +    int err; +    HRESULT hr; +    DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; +    LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer; +    int live, len, dead; +    DWORD blen1, blen2; +    DWORD len1, len2; +    DWORD decr; +    DWORD cpos, rpos; +    LPVOID p1, p2; +    int hwshift; +    dsound *s = ds->s; + +    if (!dscb) { +        dolog ("Attempt to run without capture buffer\n"); +        return 0; +    } + +    hwshift = hw->info.shift; + +    live = audio_pcm_hw_get_live_in (hw); +    dead = hw->samples - live; +    if (!dead) { +        return 0; +    } + +    hr = IDirectSoundCaptureBuffer_GetCurrentPosition ( +        dscb, +        &cpos, +        ds->first_time ? &rpos : NULL +        ); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not get capture buffer position\n"); +        return 0; +    } + +    if (ds->first_time) { +        ds->first_time = 0; +        if (rpos & hw->info.align) { +            ldebug ("warning: Misaligned capture read position %ld(%d)\n", +                    rpos, hw->info.align); +        } +        hw->wpos = rpos >> hwshift; +    } + +    if (cpos & hw->info.align) { +        ldebug ("warning: Misaligned capture position %ld(%d)\n", +                cpos, hw->info.align); +    } +    cpos >>= hwshift; + +    len = audio_ring_dist (cpos, hw->wpos, hw->samples); +    if (!len) { +        return 0; +    } +    len = audio_MIN (len, dead); + +    err = dsound_lock_in ( +        dscb, +        &hw->info, +        hw->wpos << hwshift, +        len << hwshift, +        &p1, +        &p2, +        &blen1, +        &blen2, +        0, +        s +        ); +    if (err) { +        return 0; +    } + +    len1 = blen1 >> hwshift; +    len2 = blen2 >> hwshift; +    decr = len1 + len2; + +    if (p1 && len1) { +        hw->conv (hw->conv_buf + hw->wpos, p1, len1); +    } + +    if (p2 && len2) { +        hw->conv (hw->conv_buf, p2, len2); +    } + +    dsound_unlock_in (dscb, p1, p2, blen1, blen2); +    hw->wpos = (hw->wpos + decr) % hw->samples; +    return decr; +} + +static DSoundConf glob_conf = { +    .bufsize_in         = 16384, +    .bufsize_out        = 16384, +    .latency_millis     = 10 +}; + +static void dsound_audio_fini (void *opaque) +{ +    HRESULT hr; +    dsound *s = opaque; + +    if (!s->dsound) { +        g_free(s); +        return; +    } + +    hr = IDirectSound_Release (s->dsound); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not release DirectSound\n"); +    } +    s->dsound = NULL; + +    if (!s->dsound_capture) { +        g_free(s); +        return; +    } + +    hr = IDirectSoundCapture_Release (s->dsound_capture); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not release DirectSoundCapture\n"); +    } +    s->dsound_capture = NULL; + +    g_free(s); +} + +static void *dsound_audio_init (void) +{ +    int err; +    HRESULT hr; +    dsound *s = g_malloc0(sizeof(dsound)); + +    s->conf = glob_conf; +    hr = CoInitialize (NULL); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not initialize COM\n"); +        g_free(s); +        return NULL; +    } + +    hr = CoCreateInstance ( +        &CLSID_DirectSound, +        NULL, +        CLSCTX_ALL, +        &IID_IDirectSound, +        (void **) &s->dsound +        ); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not create DirectSound instance\n"); +        g_free(s); +        return NULL; +    } + +    hr = IDirectSound_Initialize (s->dsound, NULL); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not initialize DirectSound\n"); + +        hr = IDirectSound_Release (s->dsound); +        if (FAILED (hr)) { +            dsound_logerr (hr, "Could not release DirectSound\n"); +        } +        g_free(s); +        return NULL; +    } + +    hr = CoCreateInstance ( +        &CLSID_DirectSoundCapture, +        NULL, +        CLSCTX_ALL, +        &IID_IDirectSoundCapture, +        (void **) &s->dsound_capture +        ); +    if (FAILED (hr)) { +        dsound_logerr (hr, "Could not create DirectSoundCapture instance\n"); +    } +    else { +        hr = IDirectSoundCapture_Initialize (s->dsound_capture, NULL); +        if (FAILED (hr)) { +            dsound_logerr (hr, "Could not initialize DirectSoundCapture\n"); + +            hr = IDirectSoundCapture_Release (s->dsound_capture); +            if (FAILED (hr)) { +                dsound_logerr (hr, "Could not release DirectSoundCapture\n"); +            } +            s->dsound_capture = NULL; +        } +    } + +    err = dsound_open (s); +    if (err) { +        dsound_audio_fini (s); +        return NULL; +    } + +    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, +    .run_out  = dsound_run_out, +    .write    = dsound_write, +    .ctl_out  = dsound_ctl_out, + +    .init_in  = dsound_init_in, +    .fini_in  = dsound_fini_in, +    .run_in   = dsound_run_in, +    .read     = dsound_read, +    .ctl_in   = dsound_ctl_in +}; + +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, +    .can_be_default = 1, +    .max_voices_out = INT_MAX, +    .max_voices_in  = 1, +    .voice_size_out = sizeof (DSoundVoiceOut), +    .voice_size_in  = sizeof (DSoundVoiceIn) +}; diff --git a/audio/mixeng.c b/audio/mixeng.c new file mode 100644 index 00000000..0e4976f2 --- /dev/null +++ b/audio/mixeng.c @@ -0,0 +1,366 @@ +/* + * QEMU Mixing engine + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * Copyright (c) 1998 Fabrice Bellard + * + * 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. + */ +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "mixeng" +#include "audio_int.h" + +/* 8 bit */ +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) + +/* Signed 8 bit */ +#define BSIZE 8 +#define ITYPE int +#define IN_MIN SCHAR_MIN +#define IN_MAX SCHAR_MAX +#define SIGNED +#define SHIFT 8 +#include "mixeng_template.h" +#undef SIGNED +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +/* Unsigned 8 bit */ +#define BSIZE 8 +#define ITYPE uint +#define IN_MIN 0 +#define IN_MAX UCHAR_MAX +#define SHIFT 8 +#include "mixeng_template.h" +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION + +/* Signed 16 bit */ +#define BSIZE 16 +#define ITYPE int +#define IN_MIN SHRT_MIN +#define IN_MAX SHRT_MAX +#define SIGNED +#define SHIFT 16 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap16 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef SIGNED +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +/* Unsigned 16 bit */ +#define BSIZE 16 +#define ITYPE uint +#define IN_MIN 0 +#define IN_MAX USHRT_MAX +#define SHIFT 16 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap16 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +/* Signed 32 bit */ +#define BSIZE 32 +#define ITYPE int +#define IN_MIN INT32_MIN +#define IN_MAX INT32_MAX +#define SIGNED +#define SHIFT 32 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap32 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef SIGNED +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +/* Unsigned 32 bit */ +#define BSIZE 32 +#define ITYPE uint +#define IN_MIN 0 +#define IN_MAX UINT32_MAX +#define SHIFT 32 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap32 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef IN_MAX +#undef IN_MIN +#undef BSIZE +#undef ITYPE +#undef SHIFT + +t_sample *mixeng_conv[2][2][2][3] = { +    { +        { +            { +                conv_natural_uint8_t_to_mono, +                conv_natural_uint16_t_to_mono, +                conv_natural_uint32_t_to_mono +            }, +            { +                conv_natural_uint8_t_to_mono, +                conv_swap_uint16_t_to_mono, +                conv_swap_uint32_t_to_mono, +            } +        }, +        { +            { +                conv_natural_int8_t_to_mono, +                conv_natural_int16_t_to_mono, +                conv_natural_int32_t_to_mono +            }, +            { +                conv_natural_int8_t_to_mono, +                conv_swap_int16_t_to_mono, +                conv_swap_int32_t_to_mono +            } +        } +    }, +    { +        { +            { +                conv_natural_uint8_t_to_stereo, +                conv_natural_uint16_t_to_stereo, +                conv_natural_uint32_t_to_stereo +            }, +            { +                conv_natural_uint8_t_to_stereo, +                conv_swap_uint16_t_to_stereo, +                conv_swap_uint32_t_to_stereo +            } +        }, +        { +            { +                conv_natural_int8_t_to_stereo, +                conv_natural_int16_t_to_stereo, +                conv_natural_int32_t_to_stereo +            }, +            { +                conv_natural_int8_t_to_stereo, +                conv_swap_int16_t_to_stereo, +                conv_swap_int32_t_to_stereo, +            } +        } +    } +}; + +f_sample *mixeng_clip[2][2][2][3] = { +    { +        { +            { +                clip_natural_uint8_t_from_mono, +                clip_natural_uint16_t_from_mono, +                clip_natural_uint32_t_from_mono +            }, +            { +                clip_natural_uint8_t_from_mono, +                clip_swap_uint16_t_from_mono, +                clip_swap_uint32_t_from_mono +            } +        }, +        { +            { +                clip_natural_int8_t_from_mono, +                clip_natural_int16_t_from_mono, +                clip_natural_int32_t_from_mono +            }, +            { +                clip_natural_int8_t_from_mono, +                clip_swap_int16_t_from_mono, +                clip_swap_int32_t_from_mono +            } +        } +    }, +    { +        { +            { +                clip_natural_uint8_t_from_stereo, +                clip_natural_uint16_t_from_stereo, +                clip_natural_uint32_t_from_stereo +            }, +            { +                clip_natural_uint8_t_from_stereo, +                clip_swap_uint16_t_from_stereo, +                clip_swap_uint32_t_from_stereo +            } +        }, +        { +            { +                clip_natural_int8_t_from_stereo, +                clip_natural_int16_t_from_stereo, +                clip_natural_int32_t_from_stereo +            }, +            { +                clip_natural_int8_t_from_stereo, +                clip_swap_int16_t_from_stereo, +                clip_swap_int32_t_from_stereo +            } +        } +    } +}; + +/* + * August 21, 1998 + * Copyright 1998 Fabrice Bellard. + * + * [Rewrote completly the code of Lance Norskog And Sundry + * Contributors with a more efficient algorithm.] + * + * This source code is freely redistributable and may be used for + * any purpose.  This copyright notice must be maintained. + * Lance Norskog And Sundry Contributors are not responsible for + * the consequences of using this software. + */ + +/* + * Sound Tools rate change effect file. + */ +/* + * Linear Interpolation. + * + * The use of fractional increment allows us to use no buffer. It + * avoid the problems at the end of the buffer we had with the old + * method which stored a possibly big buffer of size + * lcm(in_rate,out_rate). + * + * Limited to 16 bit samples and sampling frequency <= 65535 Hz. If + * the input & output frequencies are equal, a delay of one sample is + * introduced.  Limited to processing 32-bit count worth of samples. + * + * 1 << FRAC_BITS evaluating to zero in several places.  Changed with + * an (unsigned long) cast to make it safe.  MarkMLl 2/1/99 + */ + +/* Private data */ +struct rate { +    uint64_t opos; +    uint64_t opos_inc; +    uint32_t ipos;              /* position in the input stream (integer) */ +    struct st_sample ilast;          /* last sample in the input stream */ +}; + +/* + * Prepare processing. + */ +void *st_rate_start (int inrate, int outrate) +{ +    struct rate *rate = audio_calloc (AUDIO_FUNC, 1, sizeof (*rate)); + +    if (!rate) { +        dolog ("Could not allocate resampler (%zu bytes)\n", sizeof (*rate)); +        return NULL; +    } + +    rate->opos = 0; + +    /* increment */ +    rate->opos_inc = ((uint64_t) inrate << 32) / outrate; + +    rate->ipos = 0; +    rate->ilast.l = 0; +    rate->ilast.r = 0; +    return rate; +} + +#define NAME st_rate_flow_mix +#define OP(a, b) a += b +#include "rate_template.h" + +#define NAME st_rate_flow +#define OP(a, b) a = b +#include "rate_template.h" + +void st_rate_stop (void *opaque) +{ +    g_free (opaque); +} + +void mixeng_clear (struct st_sample *buf, int len) +{ +    memset (buf, 0, len * sizeof (struct st_sample)); +} + +void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol) +{ +    if (vol->mute) { +        mixeng_clear (buf, len); +        return; +    } + +    while (len--) { +#ifdef FLOAT_MIXENG +        buf->l = buf->l * vol->l; +        buf->r = buf->r * vol->r; +#else +        buf->l = (buf->l * vol->l) >> 32; +        buf->r = (buf->r * vol->r) >> 32; +#endif +        buf += 1; +    } +} diff --git a/audio/mixeng.h b/audio/mixeng.h new file mode 100644 index 00000000..9de443b0 --- /dev/null +++ b/audio/mixeng.h @@ -0,0 +1,51 @@ +/* + * QEMU Mixing engine header + * + * Copyright (c) 2004-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. + */ +#ifndef QEMU_MIXENG_H +#define QEMU_MIXENG_H + +#ifdef FLOAT_MIXENG +typedef float mixeng_real; +struct mixeng_volume { int mute; mixeng_real r; mixeng_real l; }; +struct st_sample { mixeng_real l; mixeng_real r; }; +#else +struct mixeng_volume { int mute; int64_t r; int64_t l; }; +struct st_sample { int64_t l; int64_t r; }; +#endif + +typedef void (t_sample) (struct st_sample *dst, const void *src, int samples); +typedef void (f_sample) (void *dst, const struct st_sample *src, int samples); + +extern t_sample *mixeng_conv[2][2][2][3]; +extern f_sample *mixeng_clip[2][2][2][3]; + +void *st_rate_start (int inrate, int outrate); +void st_rate_flow (void *opaque, struct st_sample *ibuf, struct st_sample *obuf, +                   int *isamp, int *osamp); +void st_rate_flow_mix (void *opaque, struct st_sample *ibuf, struct st_sample *obuf, +                       int *isamp, int *osamp); +void st_rate_stop (void *opaque); +void mixeng_clear (struct st_sample *buf, int len); +void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol); + +#endif  /* mixeng.h */ diff --git a/audio/mixeng_template.h b/audio/mixeng_template.h new file mode 100644 index 00000000..77cc89b9 --- /dev/null +++ b/audio/mixeng_template.h @@ -0,0 +1,154 @@ +/* + * QEMU Mixing engine + * + * Copyright (c) 2004-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. + */ + +/* + * Tusen tack till Mike Nordell + * dec++'ified by Dscho + */ + +#ifndef SIGNED +#define HALF (IN_MAX >> 1) +#endif + +#define ET glue (ENDIAN_CONVERSION, glue (glue (glue (_, ITYPE), BSIZE), _t)) +#define IN_T glue (glue (ITYPE, BSIZE), _t) + +#ifdef FLOAT_MIXENG +static inline mixeng_real glue (conv_, ET) (IN_T v) +{ +    IN_T nv = ENDIAN_CONVERT (v); + +#ifdef RECIPROCAL +#ifdef SIGNED +    return nv * (1.f / (mixeng_real) (IN_MAX - IN_MIN)); +#else +    return (nv - HALF) * (1.f / (mixeng_real) IN_MAX); +#endif +#else  /* !RECIPROCAL */ +#ifdef SIGNED +    return nv / (mixeng_real) ((mixeng_real) IN_MAX - IN_MIN); +#else +    return (nv - HALF) / (mixeng_real) IN_MAX; +#endif +#endif +} + +static inline IN_T glue (clip_, ET) (mixeng_real v) +{ +    if (v >= 0.5) { +        return IN_MAX; +    } +    else if (v < -0.5) { +        return IN_MIN; +    } + +#ifdef SIGNED +    return ENDIAN_CONVERT ((IN_T) (v * ((mixeng_real) IN_MAX - IN_MIN))); +#else +    return ENDIAN_CONVERT ((IN_T) ((v * IN_MAX) + HALF)); +#endif +} + +#else  /* !FLOAT_MIXENG */ + +static inline int64_t glue (conv_, ET) (IN_T v) +{ +    IN_T nv = ENDIAN_CONVERT (v); +#ifdef SIGNED +    return ((int64_t) nv) << (32 - SHIFT); +#else +    return ((int64_t) nv - HALF) << (32 - SHIFT); +#endif +} + +static inline IN_T glue (clip_, ET) (int64_t v) +{ +    if (v >= 0x7f000000) { +        return IN_MAX; +    } +    else if (v < -2147483648LL) { +        return IN_MIN; +    } + +#ifdef SIGNED +    return ENDIAN_CONVERT ((IN_T) (v >> (32 - SHIFT))); +#else +    return ENDIAN_CONVERT ((IN_T) ((v >> (32 - SHIFT)) + HALF)); +#endif +} +#endif + +static void glue (glue (conv_, ET), _to_stereo) +    (struct st_sample *dst, const void *src, int samples) +{ +    struct st_sample *out = dst; +    IN_T *in = (IN_T *) src; + +    while (samples--) { +        out->l = glue (conv_, ET) (*in++); +        out->r = glue (conv_, ET) (*in++); +        out += 1; +    } +} + +static void glue (glue (conv_, ET), _to_mono) +    (struct st_sample *dst, const void *src, int samples) +{ +    struct st_sample *out = dst; +    IN_T *in = (IN_T *) src; + +    while (samples--) { +        out->l = glue (conv_, ET) (in[0]); +        out->r = out->l; +        out += 1; +        in += 1; +    } +} + +static void glue (glue (clip_, ET), _from_stereo) +    (void *dst, const struct st_sample *src, int samples) +{ +    const struct st_sample *in = src; +    IN_T *out = (IN_T *) dst; +    while (samples--) { +        *out++ = glue (clip_, ET) (in->l); +        *out++ = glue (clip_, ET) (in->r); +        in += 1; +    } +} + +static void glue (glue (clip_, ET), _from_mono) +    (void *dst, const struct st_sample *src, int samples) +{ +    const struct st_sample *in = src; +    IN_T *out = (IN_T *) dst; +    while (samples--) { +        *out++ = glue (clip_, ET) (in->l + in->r); +        in += 1; +    } +} + +#undef ET +#undef HALF +#undef IN_T diff --git a/audio/noaudio.c b/audio/noaudio.c new file mode 100644 index 00000000..50db1f34 --- /dev/null +++ b/audio/noaudio.c @@ -0,0 +1,173 @@ +/* + * QEMU Timer based audio emulation + * + * Copyright (c) 2004-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. + */ +#include "qemu-common.h" +#include "audio.h" +#include "qemu/timer.h" + +#define AUDIO_CAP "noaudio" +#include "audio_int.h" + +typedef struct NoVoiceOut { +    HWVoiceOut hw; +    int64_t old_ticks; +} NoVoiceOut; + +typedef struct NoVoiceIn { +    HWVoiceIn hw; +    int64_t old_ticks; +} NoVoiceIn; + +static int no_run_out (HWVoiceOut *hw, int live) +{ +    NoVoiceOut *no = (NoVoiceOut *) hw; +    int decr, samples; +    int64_t now; +    int64_t ticks; +    int64_t bytes; + +    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +    ticks = now - no->old_ticks; +    bytes = muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ()); +    bytes = audio_MIN (bytes, INT_MAX); +    samples = bytes >> hw->info.shift; + +    no->old_ticks = now; +    decr = audio_MIN (live, samples); +    hw->rpos = (hw->rpos + decr) % hw->samples; +    return decr; +} + +static int no_write (SWVoiceOut *sw, void *buf, int len) +{ +    return audio_pcm_sw_write (sw, buf, len); +} + +static int no_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) +{ +    audio_pcm_init_info (&hw->info, as); +    hw->samples = 1024; +    return 0; +} + +static void no_fini_out (HWVoiceOut *hw) +{ +    (void) hw; +} + +static int no_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ +    (void) hw; +    (void) cmd; +    return 0; +} + +static int no_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +{ +    audio_pcm_init_info (&hw->info, as); +    hw->samples = 1024; +    return 0; +} + +static void no_fini_in (HWVoiceIn *hw) +{ +    (void) hw; +} + +static int no_run_in (HWVoiceIn *hw) +{ +    NoVoiceIn *no = (NoVoiceIn *) hw; +    int live = audio_pcm_hw_get_live_in (hw); +    int dead = hw->samples - live; +    int samples = 0; + +    if (dead) { +        int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +        int64_t ticks = now - no->old_ticks; +        int64_t bytes = +            muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ()); + +        no->old_ticks = now; +        bytes = audio_MIN (bytes, INT_MAX); +        samples = bytes >> hw->info.shift; +        samples = audio_MIN (samples, dead); +    } +    return samples; +} + +static int no_read (SWVoiceIn *sw, void *buf, int size) +{ +    /* use custom code here instead of audio_pcm_sw_read() to avoid +     * useless resampling/mixing */ +    int samples = size >> sw->info.shift; +    int total = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; +    int to_clear = audio_MIN (samples, total); +    sw->total_hw_samples_acquired += total; +    audio_pcm_info_clear_buf (&sw->info, buf, to_clear); +    return to_clear << sw->info.shift; +} + +static int no_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ +    (void) hw; +    (void) cmd; +    return 0; +} + +static void *no_audio_init (void) +{ +    return &no_audio_init; +} + +static void no_audio_fini (void *opaque) +{ +    (void) opaque; +} + +static struct audio_pcm_ops no_pcm_ops = { +    .init_out = no_init_out, +    .fini_out = no_fini_out, +    .run_out  = no_run_out, +    .write    = no_write, +    .ctl_out  = no_ctl_out, + +    .init_in  = no_init_in, +    .fini_in  = no_fini_in, +    .run_in   = no_run_in, +    .read     = no_read, +    .ctl_in   = no_ctl_in +}; + +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, +    .can_be_default = 1, +    .max_voices_out = INT_MAX, +    .max_voices_in  = INT_MAX, +    .voice_size_out = sizeof (NoVoiceOut), +    .voice_size_in  = sizeof (NoVoiceIn) +}; diff --git a/audio/ossaudio.c b/audio/ossaudio.c new file mode 100644 index 00000000..7dbe3332 --- /dev/null +++ b/audio/ossaudio.c @@ -0,0 +1,941 @@ +/* + * QEMU OSS audio driver + * + * Copyright (c) 2003-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. + */ +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/soundcard.h> +#include "qemu-common.h" +#include "qemu/main-loop.h" +#include "qemu/host-utils.h" +#include "audio.h" +#include "trace.h" + +#define AUDIO_CAP "oss" +#include "audio_int.h" + +#if defined OSS_GETVERSION && defined SNDCTL_DSP_POLICY +#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; +    int fd; +    int wpos; +    int nfrags; +    int fragsize; +    int mmapped; +    int pending; +    OSSConf *conf; +} OSSVoiceOut; + +typedef struct OSSVoiceIn { +    HWVoiceIn hw; +    void *pcm_buf; +    int fd; +    int nfrags; +    int fragsize; +    OSSConf *conf; +} OSSVoiceIn; + +struct oss_params { +    int freq; +    audfmt_e fmt; +    int nchannels; +    int nfrags; +    int fragsize; +}; + +static void GCC_FMT_ATTR (2, 3) oss_logerr (int err, const char *fmt, ...) +{ +    va_list ap; + +    va_start (ap, fmt); +    AUD_vlog (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); +} + +static void GCC_FMT_ATTR (3, 4) oss_logerr2 ( +    int err, +    const char *typ, +    const char *fmt, +    ... +    ) +{ +    va_list ap; + +    AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + +    va_start (ap, fmt); +    AUD_vlog (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); +} + +static void oss_anal_close (int *fdp) +{ +    int err; + +    qemu_set_fd_handler (*fdp, NULL, NULL, NULL); +    err = close (*fdp); +    if (err) { +        oss_logerr (errno, "Failed to close file(fd=%d)\n", *fdp); +    } +    *fdp = -1; +} + +static void oss_helper_poll_out (void *opaque) +{ +    (void) opaque; +    audio_run ("oss_poll_out"); +} + +static void oss_helper_poll_in (void *opaque) +{ +    (void) opaque; +    audio_run ("oss_poll_in"); +} + +static void oss_poll_out (HWVoiceOut *hw) +{ +    OSSVoiceOut *oss = (OSSVoiceOut *) hw; + +    qemu_set_fd_handler (oss->fd, NULL, oss_helper_poll_out, NULL); +} + +static void oss_poll_in (HWVoiceIn *hw) +{ +    OSSVoiceIn *oss = (OSSVoiceIn *) hw; + +    qemu_set_fd_handler (oss->fd, oss_helper_poll_in, NULL, NULL); +} + +static int oss_write (SWVoiceOut *sw, void *buf, int len) +{ +    return audio_pcm_sw_write (sw, buf, len); +} + +static int aud_to_ossfmt (audfmt_e fmt, int endianness) +{ +    switch (fmt) { +    case AUD_FMT_S8: +        return AFMT_S8; + +    case AUD_FMT_U8: +        return AFMT_U8; + +    case AUD_FMT_S16: +        if (endianness) { +            return AFMT_S16_BE; +        } +        else { +            return AFMT_S16_LE; +        } + +    case AUD_FMT_U16: +        if (endianness) { +            return AFMT_U16_BE; +        } +        else { +            return AFMT_U16_LE; +        } + +    default: +        dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO +        abort (); +#endif +        return AFMT_U8; +    } +} + +static int oss_to_audfmt (int ossfmt, audfmt_e *fmt, int *endianness) +{ +    switch (ossfmt) { +    case AFMT_S8: +        *endianness = 0; +        *fmt = AUD_FMT_S8; +        break; + +    case AFMT_U8: +        *endianness = 0; +        *fmt = AUD_FMT_U8; +        break; + +    case AFMT_S16_LE: +        *endianness = 0; +        *fmt = AUD_FMT_S16; +        break; + +    case AFMT_U16_LE: +        *endianness = 0; +        *fmt = AUD_FMT_U16; +        break; + +    case AFMT_S16_BE: +        *endianness = 1; +        *fmt = AUD_FMT_S16; +        break; + +    case AFMT_U16_BE: +        *endianness = 1; +        *fmt = AUD_FMT_U16; +        break; + +    default: +        dolog ("Unrecognized audio format %d\n", ossfmt); +        return -1; +    } + +    return 0; +} + +#if defined DEBUG_MISMATCHES || defined DEBUG +static void oss_dump_info (struct oss_params *req, struct oss_params *obt) +{ +    dolog ("parameter | requested value | obtained value\n"); +    dolog ("format    |      %10d |     %10d\n", req->fmt, obt->fmt); +    dolog ("channels  |      %10d |     %10d\n", +           req->nchannels, obt->nchannels); +    dolog ("frequency |      %10d |     %10d\n", req->freq, obt->freq); +    dolog ("nfrags    |      %10d |     %10d\n", req->nfrags, obt->nfrags); +    dolog ("fragsize  |      %10d |     %10d\n", +           req->fragsize, obt->fragsize); +} +#endif + +#ifdef USE_DSP_POLICY +static int oss_get_version (int fd, int *version, const char *typ) +{ +    if (ioctl (fd, OSS_GETVERSION, &version)) { +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +        /* +         * Looks like atm (20100109) FreeBSD knows OSS_GETVERSION +         * since 7.x, but currently only on the mixer device (or in +         * the Linuxolator), and in the native version that part of +         * the code is in fact never reached so the ioctl fails anyway. +         * Until this is fixed, just check the errno and if its what +         * FreeBSD's sound drivers return atm assume they are new enough. +         */ +        if (errno == EINVAL) { +            *version = 0x040000; +            return 0; +        } +#endif +        oss_logerr2 (errno, typ, "Failed to get OSS version\n"); +        return -1; +    } +    return 0; +} +#endif + +static int oss_open (int in, struct oss_params *req, +                     struct oss_params *obt, int *pfd, OSSConf* conf) +{ +    int fd; +    int oflags = conf->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 *typ = in ? "ADC" : "DAC"; + +    /* Kludge needed to have working mmap on Linux */ +    oflags |= conf->try_mmap ? O_RDWR : (in ? O_RDONLY : O_WRONLY); + +    fd = open (dspname, oflags | O_NONBLOCK); +    if (-1 == fd) { +        oss_logerr2 (errno, typ, "Failed to open `%s'\n", dspname); +        return -1; +    } + +    freq = req->freq; +    nchannels = req->nchannels; +    fmt = req->fmt; + +    if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) { +        oss_logerr2 (errno, typ, "Failed to set sample size %d\n", req->fmt); +        goto err; +    } + +    if (ioctl (fd, SNDCTL_DSP_CHANNELS, &nchannels)) { +        oss_logerr2 (errno, typ, "Failed to set number of channels %d\n", +                     req->nchannels); +        goto err; +    } + +    if (ioctl (fd, SNDCTL_DSP_SPEED, &freq)) { +        oss_logerr2 (errno, typ, "Failed to set frequency %d\n", req->freq); +        goto err; +    } + +    if (ioctl (fd, SNDCTL_DSP_NONBLOCK, NULL)) { +        oss_logerr2 (errno, typ, "Failed to set non-blocking mode\n"); +        goto err; +    } + +#ifdef USE_DSP_POLICY +    if (conf->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)) { +                    oss_logerr2 (errno, typ, +                                 "Failed to set timing policy to %d\n", +                                 conf->policy); +                    goto err; +                } +                setfragment = 0; +            } +        } +    } +#endif + +    if (setfragment) { +        int mmmmssss = (req->nfrags << 16) | ctz32 (req->fragsize); +        if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) { +            oss_logerr2 (errno, typ, "Failed to set buffer length (%d, %d)\n", +                         req->nfrags, req->fragsize); +            goto err; +        } +    } + +    if (ioctl (fd, in ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo)) { +        oss_logerr2 (errno, typ, "Failed to get buffer length\n"); +        goto err; +    } + +    if (!abinfo.fragstotal || !abinfo.fragsize) { +        AUD_log (AUDIO_CAP, "Returned bogus buffer information(%d, %d) for %s\n", +                 abinfo.fragstotal, abinfo.fragsize, typ); +        goto err; +    } + +    obt->fmt = fmt; +    obt->nchannels = nchannels; +    obt->freq = freq; +    obt->nfrags = abinfo.fragstotal; +    obt->fragsize = abinfo.fragsize; +    *pfd = fd; + +#ifdef DEBUG_MISMATCHES +    if ((req->fmt != obt->fmt) || +        (req->nchannels != obt->nchannels) || +        (req->freq != obt->freq) || +        (req->fragsize != obt->fragsize) || +        (req->nfrags != obt->nfrags)) { +        dolog ("Audio parameters mismatch\n"); +        oss_dump_info (req, obt); +    } +#endif + +#ifdef DEBUG +    oss_dump_info (req, obt); +#endif +    return 0; + + err: +    oss_anal_close (&fd); +    return -1; +} + +static void oss_write_pending (OSSVoiceOut *oss) +{ +    HWVoiceOut *hw = &oss->hw; + +    if (oss->mmapped) { +        return; +    } + +    while (oss->pending) { +        int samples_written; +        ssize_t bytes_written; +        int samples_till_end = hw->samples - oss->wpos; +        int samples_to_write = audio_MIN (oss->pending, samples_till_end); +        int bytes_to_write = samples_to_write << hw->info.shift; +        void *pcm = advance (oss->pcm_buf, oss->wpos << hw->info.shift); + +        bytes_written = write (oss->fd, pcm, bytes_to_write); +        if (bytes_written < 0) { +            if (errno != EAGAIN) { +                oss_logerr (errno, "failed to write %d bytes\n", +                            bytes_to_write); +            } +            break; +        } + +        if (bytes_written & hw->info.align) { +            dolog ("misaligned write asked for %d, but got %zd\n", +                   bytes_to_write, bytes_written); +            return; +        } + +        samples_written = bytes_written >> hw->info.shift; +        oss->pending -= samples_written; +        oss->wpos = (oss->wpos + samples_written) % hw->samples; +        if (bytes_written - bytes_to_write) { +            break; +        } +    } +} + +static int oss_run_out (HWVoiceOut *hw, int live) +{ +    OSSVoiceOut *oss = (OSSVoiceOut *) hw; +    int err, decr; +    struct audio_buf_info abinfo; +    struct count_info cntinfo; +    int bufsize; + +    bufsize = hw->samples << hw->info.shift; + +    if (oss->mmapped) { +        int bytes, pos; + +        err = ioctl (oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo); +        if (err < 0) { +            oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n"); +            return 0; +        } + +        pos = hw->rpos << hw->info.shift; +        bytes = audio_ring_dist (cntinfo.ptr, pos, bufsize); +        decr = audio_MIN (bytes >> hw->info.shift, live); +    } +    else { +        err = ioctl (oss->fd, SNDCTL_DSP_GETOSPACE, &abinfo); +        if (err < 0) { +            oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n"); +            return 0; +        } + +        if (abinfo.bytes > bufsize) { +            trace_oss_invalid_available_size(abinfo.bytes, bufsize); +            abinfo.bytes = bufsize; +        } + +        if (abinfo.bytes < 0) { +            trace_oss_invalid_available_size(abinfo.bytes, bufsize); +            return 0; +        } + +        decr = audio_MIN (abinfo.bytes >> hw->info.shift, live); +        if (!decr) { +            return 0; +        } +    } + +    decr = audio_pcm_hw_clip_out (hw, oss->pcm_buf, decr, oss->pending); +    oss->pending += decr; +    oss_write_pending (oss); + +    return decr; +} + +static void oss_fini_out (HWVoiceOut *hw) +{ +    int err; +    OSSVoiceOut *oss = (OSSVoiceOut *) hw; + +    ldebug ("oss_fini\n"); +    oss_anal_close (&oss->fd); + +    if (oss->pcm_buf) { +        if (oss->mmapped) { +            err = munmap (oss->pcm_buf, hw->samples << hw->info.shift); +            if (err) { +                oss_logerr (errno, "Failed to unmap buffer %p, size %d\n", +                            oss->pcm_buf, hw->samples << hw->info.shift); +            } +        } +        else { +            g_free (oss->pcm_buf); +        } +        oss->pcm_buf = NULL; +    } +} + +static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, +                        void *drv_opaque) +{ +    OSSVoiceOut *oss = (OSSVoiceOut *) hw; +    struct oss_params req, obt; +    int endianness; +    int err; +    int fd; +    audfmt_e effective_fmt; +    struct audsettings obt_as; +    OSSConf *conf = 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 (0, &req, &obt, &fd, conf)) { +        return -1; +    } + +    err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness); +    if (err) { +        oss_anal_close (&fd); +        return -1; +    } + +    obt_as.freq = obt.freq; +    obt_as.nchannels = obt.nchannels; +    obt_as.fmt = effective_fmt; +    obt_as.endianness = endianness; + +    audio_pcm_init_info (&hw->info, &obt_as); +    oss->nfrags = obt.nfrags; +    oss->fragsize = obt.fragsize; + +    if (obt.nfrags * obt.fragsize & hw->info.align) { +        dolog ("warning: Misaligned DAC buffer, size %d, alignment %d\n", +               obt.nfrags * obt.fragsize, hw->info.align + 1); +    } + +    hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; + +    oss->mmapped = 0; +    if (conf->try_mmap) { +        oss->pcm_buf = mmap ( +            NULL, +            hw->samples << hw->info.shift, +            PROT_READ | PROT_WRITE, +            MAP_SHARED, +            fd, +            0 +            ); +        if (oss->pcm_buf == MAP_FAILED) { +            oss_logerr (errno, "Failed to map %d bytes of DAC\n", +                        hw->samples << hw->info.shift); +        } +        else { +            int err; +            int trig = 0; +            if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { +                oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n"); +            } +            else { +                trig = PCM_ENABLE_OUTPUT; +                if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { +                    oss_logerr ( +                        errno, +                        "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" +                        ); +                } +                else { +                    oss->mmapped = 1; +                } +            } + +            if (!oss->mmapped) { +                err = munmap (oss->pcm_buf, hw->samples << hw->info.shift); +                if (err) { +                    oss_logerr (errno, "Failed to unmap buffer %p size %d\n", +                                oss->pcm_buf, hw->samples << hw->info.shift); +                } +            } +        } +    } + +    if (!oss->mmapped) { +        oss->pcm_buf = audio_calloc ( +            AUDIO_FUNC, +            hw->samples, +            1 << hw->info.shift +            ); +        if (!oss->pcm_buf) { +            dolog ( +                "Could not allocate DAC buffer (%d samples, each %d bytes)\n", +                hw->samples, +                1 << hw->info.shift +                ); +            oss_anal_close (&fd); +            return -1; +        } +    } + +    oss->fd = fd; +    oss->conf = conf; +    return 0; +} + +static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ +    int trig; +    OSSVoiceOut *oss = (OSSVoiceOut *) hw; + +    switch (cmd) { +    case VOICE_ENABLE: +        { +            va_list ap; +            int poll_mode; + +            va_start (ap, cmd); +            poll_mode = va_arg (ap, int); +            va_end (ap); + +            ldebug ("enabling voice\n"); +            if (poll_mode) { +                oss_poll_out (hw); +                poll_mode = 0; +            } +            hw->poll_mode = poll_mode; + +            if (!oss->mmapped) { +                return 0; +            } + +            audio_pcm_info_clear_buf (&hw->info, oss->pcm_buf, hw->samples); +            trig = PCM_ENABLE_OUTPUT; +            if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { +                oss_logerr ( +                    errno, +                    "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" +                    ); +                return -1; +            } +        } +        break; + +    case VOICE_DISABLE: +        if (hw->poll_mode) { +            qemu_set_fd_handler (oss->fd, NULL, NULL, NULL); +            hw->poll_mode = 0; +        } + +        if (!oss->mmapped) { +            return 0; +        } + +        ldebug ("disabling voice\n"); +        trig = 0; +        if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { +            oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n"); +            return -1; +        } +        break; +    } +    return 0; +} + +static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +{ +    OSSVoiceIn *oss = (OSSVoiceIn *) hw; +    struct oss_params req, obt; +    int endianness; +    int err; +    int fd; +    audfmt_e effective_fmt; +    struct audsettings obt_as; +    OSSConf *conf = 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)) { +        return -1; +    } + +    err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness); +    if (err) { +        oss_anal_close (&fd); +        return -1; +    } + +    obt_as.freq = obt.freq; +    obt_as.nchannels = obt.nchannels; +    obt_as.fmt = effective_fmt; +    obt_as.endianness = endianness; + +    audio_pcm_init_info (&hw->info, &obt_as); +    oss->nfrags = obt.nfrags; +    oss->fragsize = obt.fragsize; + +    if (obt.nfrags * obt.fragsize & hw->info.align) { +        dolog ("warning: Misaligned ADC buffer, size %d, alignment %d\n", +               obt.nfrags * obt.fragsize, hw->info.align + 1); +    } + +    hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; +    oss->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); +    if (!oss->pcm_buf) { +        dolog ("Could not allocate ADC buffer (%d samples, each %d bytes)\n", +               hw->samples, 1 << hw->info.shift); +        oss_anal_close (&fd); +        return -1; +    } + +    oss->fd = fd; +    oss->conf = conf; +    return 0; +} + +static void oss_fini_in (HWVoiceIn *hw) +{ +    OSSVoiceIn *oss = (OSSVoiceIn *) hw; + +    oss_anal_close (&oss->fd); + +    g_free(oss->pcm_buf); +    oss->pcm_buf = NULL; +} + +static int oss_run_in (HWVoiceIn *hw) +{ +    OSSVoiceIn *oss = (OSSVoiceIn *) hw; +    int hwshift = hw->info.shift; +    int i; +    int live = audio_pcm_hw_get_live_in (hw); +    int dead = hw->samples - live; +    size_t read_samples = 0; +    struct { +        int add; +        int len; +    } bufs[2] = { +        { .add = hw->wpos, .len = 0 }, +        { .add = 0,        .len = 0 } +    }; + +    if (!dead) { +        return 0; +    } + +    if (hw->wpos + dead > hw->samples) { +        bufs[0].len = (hw->samples - hw->wpos) << hwshift; +        bufs[1].len = (dead - (hw->samples - hw->wpos)) << hwshift; +    } +    else { +        bufs[0].len = dead << hwshift; +    } + +    for (i = 0; i < 2; ++i) { +        ssize_t nread; + +        if (bufs[i].len) { +            void *p = advance (oss->pcm_buf, bufs[i].add << hwshift); +            nread = read (oss->fd, p, bufs[i].len); + +            if (nread > 0) { +                if (nread & hw->info.align) { +                    dolog ("warning: Misaligned read %zd (requested %d), " +                           "alignment %d\n", nread, bufs[i].add << hwshift, +                           hw->info.align + 1); +                } +                read_samples += nread >> hwshift; +                hw->conv (hw->conv_buf + bufs[i].add, p, nread >> hwshift); +            } + +            if (bufs[i].len - nread) { +                if (nread == -1) { +                    switch (errno) { +                    case EINTR: +                    case EAGAIN: +                        break; +                    default: +                        oss_logerr ( +                            errno, +                            "Failed to read %d bytes of audio (to %p)\n", +                            bufs[i].len, p +                            ); +                        break; +                    } +                } +                break; +            } +        } +    } + +    hw->wpos = (hw->wpos + read_samples) % hw->samples; +    return read_samples; +} + +static int oss_read (SWVoiceIn *sw, void *buf, int size) +{ +    return audio_pcm_sw_read (sw, buf, size); +} + +static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ +    OSSVoiceIn *oss = (OSSVoiceIn *) hw; + +    switch (cmd) { +    case VOICE_ENABLE: +        { +            va_list ap; +            int poll_mode; + +            va_start (ap, cmd); +            poll_mode = va_arg (ap, int); +            va_end (ap); + +            if (poll_mode) { +                oss_poll_in (hw); +                poll_mode = 0; +            } +            hw->poll_mode = poll_mode; +        } +        break; + +    case VOICE_DISABLE: +        if (hw->poll_mode) { +            hw->poll_mode = 0; +            qemu_set_fd_handler (oss->fd, NULL, NULL, NULL); +        } +        break; +    } +    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) +{ +    OSSConf *conf = g_malloc(sizeof(OSSConf)); +    *conf = glob_conf; + +    if (access(conf->devpath_in, R_OK | W_OK) < 0 || +        access(conf->devpath_out, R_OK | W_OK) < 0) { +        g_free(conf); +        return NULL; +    } +    return conf; +} + +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, +    .run_out  = oss_run_out, +    .write    = oss_write, +    .ctl_out  = oss_ctl_out, + +    .init_in  = oss_init_in, +    .fini_in  = oss_fini_in, +    .run_in   = oss_run_in, +    .read     = oss_read, +    .ctl_in   = oss_ctl_in +}; + +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, +    .can_be_default = 1, +    .max_voices_out = INT_MAX, +    .max_voices_in  = INT_MAX, +    .voice_size_out = sizeof (OSSVoiceOut), +    .voice_size_in  = sizeof (OSSVoiceIn) +}; diff --git a/audio/paaudio.c b/audio/paaudio.c new file mode 100644 index 00000000..fea60716 --- /dev/null +++ b/audio/paaudio.c @@ -0,0 +1,953 @@ +/* public domain */ +#include "qemu-common.h" +#include "audio.h" + +#include <pulse/pulseaudio.h> + +#define AUDIO_CAP "pulseaudio" +#include "audio_int.h" +#include "audio_pt_int.h" + +typedef struct { +    int samples; +    char *server; +    char *sink; +    char *source; +} PAConf; + +typedef struct { +    PAConf conf; +    pa_threaded_mainloop *mainloop; +    pa_context *context; +} paaudio; + +typedef struct { +    HWVoiceOut hw; +    int done; +    int live; +    int decr; +    int rpos; +    pa_stream *stream; +    void *pcm_buf; +    struct audio_pt pt; +    paaudio *g; +} PAVoiceOut; + +typedef struct { +    HWVoiceIn hw; +    int done; +    int dead; +    int incr; +    int wpos; +    pa_stream *stream; +    void *pcm_buf; +    struct audio_pt pt; +    const void *read_data; +    size_t read_index, read_length; +    paaudio *g; +} PAVoiceIn; + +static void qpa_audio_fini(void *opaque); + +static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...) +{ +    va_list ap; + +    va_start (ap, fmt); +    AUD_vlog (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err)); +} + +#ifndef PA_CONTEXT_IS_GOOD +static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) +{ +    return +        x == PA_CONTEXT_CONNECTING || +        x == PA_CONTEXT_AUTHORIZING || +        x == PA_CONTEXT_SETTING_NAME || +        x == PA_CONTEXT_READY; +} +#endif + +#ifndef PA_STREAM_IS_GOOD +static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) +{ +    return +        x == PA_STREAM_CREATING || +        x == PA_STREAM_READY; +} +#endif + +#define CHECK_SUCCESS_GOTO(c, rerror, expression, label)        \ +    do {                                                        \ +        if (!(expression)) {                                    \ +            if (rerror) {                                       \ +                *(rerror) = pa_context_errno ((c)->context);    \ +            }                                                   \ +            goto label;                                         \ +        }                                                       \ +    } while (0); + +#define CHECK_DEAD_GOTO(c, stream, rerror, label)                       \ +    do {                                                                \ +        if (!(c)->context || !PA_CONTEXT_IS_GOOD (pa_context_get_state((c)->context)) || \ +            !(stream) || !PA_STREAM_IS_GOOD (pa_stream_get_state ((stream)))) { \ +            if (((c)->context && pa_context_get_state ((c)->context) == PA_CONTEXT_FAILED) || \ +                ((stream) && pa_stream_get_state ((stream)) == PA_STREAM_FAILED)) { \ +                if (rerror) {                                           \ +                    *(rerror) = pa_context_errno ((c)->context);        \ +                }                                                       \ +            } else {                                                    \ +                if (rerror) {                                           \ +                    *(rerror) = PA_ERR_BADSTATE;                        \ +                }                                                       \ +            }                                                           \ +            goto label;                                                 \ +        }                                                               \ +    } while (0); + +static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror) +{ +    paaudio *g = p->g; + +    pa_threaded_mainloop_lock (g->mainloop); + +    CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); + +    while (length > 0) { +        size_t l; + +        while (!p->read_data) { +            int r; + +            r = pa_stream_peek (p->stream, &p->read_data, &p->read_length); +            CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); + +            if (!p->read_data) { +                pa_threaded_mainloop_wait (g->mainloop); +                CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); +            } else { +                p->read_index = 0; +            } +        } + +        l = p->read_length < length ? p->read_length : length; +        memcpy (data, (const uint8_t *) p->read_data+p->read_index, l); + +        data = (uint8_t *) data + l; +        length -= l; + +        p->read_index += l; +        p->read_length -= l; + +        if (!p->read_length) { +            int r; + +            r = pa_stream_drop (p->stream); +            p->read_data = NULL; +            p->read_length = 0; +            p->read_index = 0; + +            CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); +        } +    } + +    pa_threaded_mainloop_unlock (g->mainloop); +    return 0; + +unlock_and_fail: +    pa_threaded_mainloop_unlock (g->mainloop); +    return -1; +} + +static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror) +{ +    paaudio *g = p->g; + +    pa_threaded_mainloop_lock (g->mainloop); + +    CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); + +    while (length > 0) { +        size_t l; +        int r; + +        while (!(l = pa_stream_writable_size (p->stream))) { +            pa_threaded_mainloop_wait (g->mainloop); +            CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); +        } + +        CHECK_SUCCESS_GOTO (g, rerror, l != (size_t) -1, unlock_and_fail); + +        if (l > length) { +            l = length; +        } + +        r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); +        CHECK_SUCCESS_GOTO (g, rerror, r >= 0, unlock_and_fail); + +        data = (const uint8_t *) data + l; +        length -= l; +    } + +    pa_threaded_mainloop_unlock (g->mainloop); +    return 0; + +unlock_and_fail: +    pa_threaded_mainloop_unlock (g->mainloop); +    return -1; +} + +static void *qpa_thread_out (void *arg) +{ +    PAVoiceOut *pa = arg; +    HWVoiceOut *hw = &pa->hw; + +    if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +        return NULL; +    } + +    for (;;) { +        int decr, to_mix, rpos; + +        for (;;) { +            if (pa->done) { +                goto exit; +            } + +            if (pa->live > 0) { +                break; +            } + +            if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { +                goto exit; +            } +        } + +        decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2); +        rpos = pa->rpos; + +        if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { +            return NULL; +        } + +        while (to_mix) { +            int error; +            int chunk = audio_MIN (to_mix, hw->samples - rpos); +            struct st_sample *src = hw->mix_buf + rpos; + +            hw->clip (pa->pcm_buf, src, chunk); + +            if (qpa_simple_write (pa, pa->pcm_buf, +                                  chunk << hw->info.shift, &error) < 0) { +                qpa_logerr (error, "pa_simple_write failed\n"); +                return NULL; +            } + +            rpos = (rpos + chunk) % hw->samples; +            to_mix -= chunk; +        } + +        if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +            return NULL; +        } + +        pa->rpos = rpos; +        pa->live -= decr; +        pa->decr += decr; +    } + + exit: +    audio_pt_unlock (&pa->pt, AUDIO_FUNC); +    return NULL; +} + +static int qpa_run_out (HWVoiceOut *hw, int live) +{ +    int decr; +    PAVoiceOut *pa = (PAVoiceOut *) hw; + +    if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +        return 0; +    } + +    decr = audio_MIN (live, pa->decr); +    pa->decr -= decr; +    pa->live = live - decr; +    hw->rpos = pa->rpos; +    if (pa->live > 0) { +        audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); +    } +    else { +        audio_pt_unlock (&pa->pt, AUDIO_FUNC); +    } +    return decr; +} + +static int qpa_write (SWVoiceOut *sw, void *buf, int len) +{ +    return audio_pcm_sw_write (sw, buf, len); +} + +/* capture */ +static void *qpa_thread_in (void *arg) +{ +    PAVoiceIn *pa = arg; +    HWVoiceIn *hw = &pa->hw; + +    if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +        return NULL; +    } + +    for (;;) { +        int incr, to_grab, wpos; + +        for (;;) { +            if (pa->done) { +                goto exit; +            } + +            if (pa->dead > 0) { +                break; +            } + +            if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { +                goto exit; +            } +        } + +        incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2); +        wpos = pa->wpos; + +        if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { +            return NULL; +        } + +        while (to_grab) { +            int error; +            int chunk = audio_MIN (to_grab, hw->samples - wpos); +            void *buf = advance (pa->pcm_buf, wpos); + +            if (qpa_simple_read (pa, buf, +                                 chunk << hw->info.shift, &error) < 0) { +                qpa_logerr (error, "pa_simple_read failed\n"); +                return NULL; +            } + +            hw->conv (hw->conv_buf + wpos, buf, chunk); +            wpos = (wpos + chunk) % hw->samples; +            to_grab -= chunk; +        } + +        if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +            return NULL; +        } + +        pa->wpos = wpos; +        pa->dead -= incr; +        pa->incr += incr; +    } + + exit: +    audio_pt_unlock (&pa->pt, AUDIO_FUNC); +    return NULL; +} + +static int qpa_run_in (HWVoiceIn *hw) +{ +    int live, incr, dead; +    PAVoiceIn *pa = (PAVoiceIn *) hw; + +    if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +        return 0; +    } + +    live = audio_pcm_hw_get_live_in (hw); +    dead = hw->samples - live; +    incr = audio_MIN (dead, pa->incr); +    pa->incr -= incr; +    pa->dead = dead - incr; +    hw->wpos = pa->wpos; +    if (pa->dead > 0) { +        audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); +    } +    else { +        audio_pt_unlock (&pa->pt, AUDIO_FUNC); +    } +    return incr; +} + +static int qpa_read (SWVoiceIn *sw, void *buf, int len) +{ +    return audio_pcm_sw_read (sw, buf, len); +} + +static pa_sample_format_t audfmt_to_pa (audfmt_e afmt, int endianness) +{ +    int format; + +    switch (afmt) { +    case AUD_FMT_S8: +    case AUD_FMT_U8: +        format = PA_SAMPLE_U8; +        break; +    case AUD_FMT_S16: +    case AUD_FMT_U16: +        format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE; +        break; +    case AUD_FMT_S32: +    case AUD_FMT_U32: +        format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE; +        break; +    default: +        dolog ("Internal logic error: Bad audio format %d\n", afmt); +        format = PA_SAMPLE_U8; +        break; +    } +    return format; +} + +static audfmt_e pa_to_audfmt (pa_sample_format_t fmt, int *endianness) +{ +    switch (fmt) { +    case PA_SAMPLE_U8: +        return AUD_FMT_U8; +    case PA_SAMPLE_S16BE: +        *endianness = 1; +        return AUD_FMT_S16; +    case PA_SAMPLE_S16LE: +        *endianness = 0; +        return AUD_FMT_S16; +    case PA_SAMPLE_S32BE: +        *endianness = 1; +        return AUD_FMT_S32; +    case PA_SAMPLE_S32LE: +        *endianness = 0; +        return AUD_FMT_S32; +    default: +        dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); +        return AUD_FMT_U8; +    } +} + +static void context_state_cb (pa_context *c, void *userdata) +{ +    paaudio *g = userdata; + +    switch (pa_context_get_state(c)) { +    case PA_CONTEXT_READY: +    case PA_CONTEXT_TERMINATED: +    case PA_CONTEXT_FAILED: +        pa_threaded_mainloop_signal (g->mainloop, 0); +        break; + +    case PA_CONTEXT_UNCONNECTED: +    case PA_CONTEXT_CONNECTING: +    case PA_CONTEXT_AUTHORIZING: +    case PA_CONTEXT_SETTING_NAME: +        break; +    } +} + +static void stream_state_cb (pa_stream *s, void * userdata) +{ +    paaudio *g = userdata; + +    switch (pa_stream_get_state (s)) { + +    case PA_STREAM_READY: +    case PA_STREAM_FAILED: +    case PA_STREAM_TERMINATED: +        pa_threaded_mainloop_signal (g->mainloop, 0); +        break; + +    case PA_STREAM_UNCONNECTED: +    case PA_STREAM_CREATING: +        break; +    } +} + +static void stream_request_cb (pa_stream *s, size_t length, void *userdata) +{ +    paaudio *g = userdata; + +    pa_threaded_mainloop_signal (g->mainloop, 0); +} + +static pa_stream *qpa_simple_new ( +        paaudio *g, +        const char *name, +        pa_stream_direction_t dir, +        const char *dev, +        const pa_sample_spec *ss, +        const pa_channel_map *map, +        const pa_buffer_attr *attr, +        int *rerror) +{ +    int r; +    pa_stream *stream; + +    pa_threaded_mainloop_lock (g->mainloop); + +    stream = pa_stream_new (g->context, name, ss, map); +    if (!stream) { +        goto fail; +    } + +    pa_stream_set_state_callback (stream, stream_state_cb, g); +    pa_stream_set_read_callback (stream, stream_request_cb, g); +    pa_stream_set_write_callback (stream, stream_request_cb, g); + +    if (dir == PA_STREAM_PLAYBACK) { +        r = pa_stream_connect_playback (stream, dev, attr, +                                        PA_STREAM_INTERPOLATE_TIMING +#ifdef PA_STREAM_ADJUST_LATENCY +                                        |PA_STREAM_ADJUST_LATENCY +#endif +                                        |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); +    } else { +        r = pa_stream_connect_record (stream, dev, attr, +                                      PA_STREAM_INTERPOLATE_TIMING +#ifdef PA_STREAM_ADJUST_LATENCY +                                      |PA_STREAM_ADJUST_LATENCY +#endif +                                      |PA_STREAM_AUTO_TIMING_UPDATE); +    } + +    if (r < 0) { +      goto fail; +    } + +    pa_threaded_mainloop_unlock (g->mainloop); + +    return stream; + +fail: +    pa_threaded_mainloop_unlock (g->mainloop); + +    if (stream) { +        pa_stream_unref (stream); +    } + +    *rerror = pa_context_errno (g->context); + +    return NULL; +} + +static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, +                        void *drv_opaque) +{ +    int error; +    pa_sample_spec ss; +    pa_buffer_attr ba; +    struct audsettings obt_as = *as; +    PAVoiceOut *pa = (PAVoiceOut *) hw; +    paaudio *g = pa->g = drv_opaque; + +    ss.format = audfmt_to_pa (as->fmt, as->endianness); +    ss.channels = as->nchannels; +    ss.rate = as->freq; + +    /* +     * qemu audio tick runs at 100 Hz (by default), so processing +     * data chunks worth 10 ms of sound should be a good fit. +     */ +    ba.tlength = pa_usec_to_bytes (10 * 1000, &ss); +    ba.minreq = pa_usec_to_bytes (5 * 1000, &ss); +    ba.maxlength = -1; +    ba.prebuf = -1; + +    obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + +    pa->stream = qpa_simple_new ( +        g, +        "qemu", +        PA_STREAM_PLAYBACK, +        g->conf.sink, +        &ss, +        NULL,                   /* channel map */ +        &ba,                    /* buffering attributes */ +        &error +        ); +    if (!pa->stream) { +        qpa_logerr (error, "pa_simple_new for playback failed\n"); +        goto fail1; +    } + +    audio_pcm_init_info (&hw->info, &obt_as); +    hw->samples = g->conf.samples; +    pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); +    pa->rpos = hw->rpos; +    if (!pa->pcm_buf) { +        dolog ("Could not allocate buffer (%d bytes)\n", +               hw->samples << hw->info.shift); +        goto fail2; +    } + +    if (audio_pt_init (&pa->pt, qpa_thread_out, hw, AUDIO_CAP, AUDIO_FUNC)) { +        goto fail3; +    } + +    return 0; + + fail3: +    g_free (pa->pcm_buf); +    pa->pcm_buf = NULL; + fail2: +    if (pa->stream) { +        pa_stream_unref (pa->stream); +        pa->stream = NULL; +    } + fail1: +    return -1; +} + +static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +{ +    int error; +    pa_sample_spec ss; +    struct audsettings obt_as = *as; +    PAVoiceIn *pa = (PAVoiceIn *) hw; +    paaudio *g = pa->g = drv_opaque; + +    ss.format = audfmt_to_pa (as->fmt, as->endianness); +    ss.channels = as->nchannels; +    ss.rate = as->freq; + +    obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + +    pa->stream = qpa_simple_new ( +        g, +        "qemu", +        PA_STREAM_RECORD, +        g->conf.source, +        &ss, +        NULL,                   /* channel map */ +        NULL,                   /* buffering attributes */ +        &error +        ); +    if (!pa->stream) { +        qpa_logerr (error, "pa_simple_new for capture failed\n"); +        goto fail1; +    } + +    audio_pcm_init_info (&hw->info, &obt_as); +    hw->samples = g->conf.samples; +    pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); +    pa->wpos = hw->wpos; +    if (!pa->pcm_buf) { +        dolog ("Could not allocate buffer (%d bytes)\n", +               hw->samples << hw->info.shift); +        goto fail2; +    } + +    if (audio_pt_init (&pa->pt, qpa_thread_in, hw, AUDIO_CAP, AUDIO_FUNC)) { +        goto fail3; +    } + +    return 0; + + fail3: +    g_free (pa->pcm_buf); +    pa->pcm_buf = NULL; + fail2: +    if (pa->stream) { +        pa_stream_unref (pa->stream); +        pa->stream = NULL; +    } + fail1: +    return -1; +} + +static void qpa_fini_out (HWVoiceOut *hw) +{ +    void *ret; +    PAVoiceOut *pa = (PAVoiceOut *) hw; + +    audio_pt_lock (&pa->pt, AUDIO_FUNC); +    pa->done = 1; +    audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); +    audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); + +    if (pa->stream) { +        pa_stream_unref (pa->stream); +        pa->stream = NULL; +    } + +    audio_pt_fini (&pa->pt, AUDIO_FUNC); +    g_free (pa->pcm_buf); +    pa->pcm_buf = NULL; +} + +static void qpa_fini_in (HWVoiceIn *hw) +{ +    void *ret; +    PAVoiceIn *pa = (PAVoiceIn *) hw; + +    audio_pt_lock (&pa->pt, AUDIO_FUNC); +    pa->done = 1; +    audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); +    audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); + +    if (pa->stream) { +        pa_stream_unref (pa->stream); +        pa->stream = NULL; +    } + +    audio_pt_fini (&pa->pt, AUDIO_FUNC); +    g_free (pa->pcm_buf); +    pa->pcm_buf = NULL; +} + +static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ +    PAVoiceOut *pa = (PAVoiceOut *) hw; +    pa_operation *op; +    pa_cvolume v; +    paaudio *g = pa->g; + +#ifdef PA_CHECK_VERSION    /* macro is present in 0.9.16+ */ +    pa_cvolume_init (&v);  /* function is present in 0.9.13+ */ +#endif + +    switch (cmd) { +    case VOICE_VOLUME: +        { +            SWVoiceOut *sw; +            va_list ap; + +            va_start (ap, cmd); +            sw = va_arg (ap, SWVoiceOut *); +            va_end (ap); + +            v.channels = 2; +            v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.l) / UINT32_MAX; +            v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.r) / UINT32_MAX; + +            pa_threaded_mainloop_lock (g->mainloop); + +            op = pa_context_set_sink_input_volume (g->context, +                pa_stream_get_index (pa->stream), +                &v, NULL, NULL); +            if (!op) +                qpa_logerr (pa_context_errno (g->context), +                            "set_sink_input_volume() failed\n"); +            else +                pa_operation_unref (op); + +            op = pa_context_set_sink_input_mute (g->context, +                pa_stream_get_index (pa->stream), +               sw->vol.mute, NULL, NULL); +            if (!op) { +                qpa_logerr (pa_context_errno (g->context), +                            "set_sink_input_mute() failed\n"); +            } else { +                pa_operation_unref (op); +            } + +            pa_threaded_mainloop_unlock (g->mainloop); +        } +    } +    return 0; +} + +static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ +    PAVoiceIn *pa = (PAVoiceIn *) hw; +    pa_operation *op; +    pa_cvolume v; +    paaudio *g = pa->g; + +#ifdef PA_CHECK_VERSION +    pa_cvolume_init (&v); +#endif + +    switch (cmd) { +    case VOICE_VOLUME: +        { +            SWVoiceIn *sw; +            va_list ap; + +            va_start (ap, cmd); +            sw = va_arg (ap, SWVoiceIn *); +            va_end (ap); + +            v.channels = 2; +            v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.l) / UINT32_MAX; +            v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * sw->vol.r) / UINT32_MAX; + +            pa_threaded_mainloop_lock (g->mainloop); + +            /* FIXME: use the upcoming "set_source_output_{volume,mute}" */ +            op = pa_context_set_source_volume_by_index (g->context, +                pa_stream_get_device_index (pa->stream), +                &v, NULL, NULL); +            if (!op) { +                qpa_logerr (pa_context_errno (g->context), +                            "set_source_volume() failed\n"); +            } else { +                pa_operation_unref(op); +            } + +            op = pa_context_set_source_mute_by_index (g->context, +                pa_stream_get_index (pa->stream), +                sw->vol.mute, NULL, NULL); +            if (!op) { +                qpa_logerr (pa_context_errno (g->context), +                            "set_source_mute() failed\n"); +            } else { +                pa_operation_unref (op); +            } + +            pa_threaded_mainloop_unlock (g->mainloop); +        } +    } +    return 0; +} + +/* common */ +static PAConf glob_conf = { +    .samples = 4096, +}; + +static void *qpa_audio_init (void) +{ +    paaudio *g = g_malloc(sizeof(paaudio)); +    g->conf = glob_conf; +    g->mainloop = NULL; +    g->context = NULL; + +    g->mainloop = pa_threaded_mainloop_new (); +    if (!g->mainloop) { +        goto fail; +    } + +    g->context = pa_context_new (pa_threaded_mainloop_get_api (g->mainloop), +                                 g->conf.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) { +        qpa_logerr (pa_context_errno (g->context), +                    "pa_context_connect() failed\n"); +        goto fail; +    } + +    pa_threaded_mainloop_lock (g->mainloop); + +    if (pa_threaded_mainloop_start (g->mainloop) < 0) { +        goto unlock_and_fail; +    } + +    for (;;) { +        pa_context_state_t state; + +        state = pa_context_get_state (g->context); + +        if (state == PA_CONTEXT_READY) { +            break; +        } + +        if (!PA_CONTEXT_IS_GOOD (state)) { +            qpa_logerr (pa_context_errno (g->context), +                        "Wrong context state\n"); +            goto unlock_and_fail; +        } + +        /* Wait until the context is ready */ +        pa_threaded_mainloop_wait (g->mainloop); +    } + +    pa_threaded_mainloop_unlock (g->mainloop); + +    return g; + +unlock_and_fail: +    pa_threaded_mainloop_unlock (g->mainloop); +fail: +    AUD_log (AUDIO_CAP, "Failed to initialize PA context"); +    qpa_audio_fini(g); +    return NULL; +} + +static void qpa_audio_fini (void *opaque) +{ +    paaudio *g = opaque; + +    if (g->mainloop) { +        pa_threaded_mainloop_stop (g->mainloop); +    } + +    if (g->context) { +        pa_context_disconnect (g->context); +        pa_context_unref (g->context); +    } + +    if (g->mainloop) { +        pa_threaded_mainloop_free (g->mainloop); +    } + +    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, +    .run_out  = qpa_run_out, +    .write    = qpa_write, +    .ctl_out  = qpa_ctl_out, + +    .init_in  = qpa_init_in, +    .fini_in  = qpa_fini_in, +    .run_in   = qpa_run_in, +    .read     = qpa_read, +    .ctl_in   = qpa_ctl_in +}; + +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, +    .can_be_default = 1, +    .max_voices_out = INT_MAX, +    .max_voices_in  = INT_MAX, +    .voice_size_out = sizeof (PAVoiceOut), +    .voice_size_in  = sizeof (PAVoiceIn), +    .ctl_caps       = VOICE_VOLUME_CAP +}; diff --git a/audio/rate_template.h b/audio/rate_template.h new file mode 100644 index 00000000..bd4b1c76 --- /dev/null +++ b/audio/rate_template.h @@ -0,0 +1,111 @@ +/* + * QEMU Mixing engine + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * Copyright (c) 1998 Fabrice Bellard + * + * 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. + */ + +/* + * Processed signed long samples from ibuf to obuf. + * Return number of samples processed. + */ +void NAME (void *opaque, struct st_sample *ibuf, struct st_sample *obuf, +           int *isamp, int *osamp) +{ +    struct rate *rate = opaque; +    struct st_sample *istart, *iend; +    struct st_sample *ostart, *oend; +    struct st_sample ilast, icur, out; +#ifdef FLOAT_MIXENG +    mixeng_real t; +#else +    int64_t t; +#endif + +    ilast = rate->ilast; + +    istart = ibuf; +    iend = ibuf + *isamp; + +    ostart = obuf; +    oend = obuf + *osamp; + +    if (rate->opos_inc == (1ULL + UINT_MAX)) { +        int i, n = *isamp > *osamp ? *osamp : *isamp; +        for (i = 0; i < n; i++) { +            OP (obuf[i].l, ibuf[i].l); +            OP (obuf[i].r, ibuf[i].r); +        } +        *isamp = n; +        *osamp = n; +        return; +    } + +    while (obuf < oend) { + +        /* Safety catch to make sure we have input samples.  */ +        if (ibuf >= iend) { +            break; +        } + +        /* read as many input samples so that ipos > opos */ + +        while (rate->ipos <= (rate->opos >> 32)) { +            ilast = *ibuf++; +            rate->ipos++; +            /* See if we finished the input buffer yet */ +            if (ibuf >= iend) { +                goto the_end; +            } +        } + +        icur = *ibuf; + +        /* interpolate */ +#ifdef FLOAT_MIXENG +#ifdef RECIPROCAL +        t = (rate->opos & UINT_MAX) * (1.f / UINT_MAX); +#else +        t = (rate->opos & UINT_MAX) / (mixeng_real) UINT_MAX; +#endif +        out.l = (ilast.l * (1.0 - t)) + icur.l * t; +        out.r = (ilast.r * (1.0 - t)) + icur.r * t; +#else +        t = rate->opos & 0xffffffff; +        out.l = (ilast.l * ((int64_t) UINT_MAX - t) + icur.l * t) >> 32; +        out.r = (ilast.r * ((int64_t) UINT_MAX - t) + icur.r * t) >> 32; +#endif + +        /* output sample & increment position */ +        OP (obuf->l, out.l); +        OP (obuf->r, out.r); +        obuf += 1; +        rate->opos += rate->opos_inc; +    } + +the_end: +    *isamp = ibuf - istart; +    *osamp = obuf - ostart; +    rate->ilast = ilast; +} + +#undef NAME +#undef OP diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c new file mode 100644 index 00000000..1140f2ea --- /dev/null +++ b/audio/sdlaudio.c @@ -0,0 +1,466 @@ +/* + * QEMU SDL audio driver + * + * Copyright (c) 2004-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. + */ +#include <SDL.h> +#include <SDL_thread.h> +#include "qemu-common.h" +#include "audio.h" + +#ifndef _WIN32 +#ifdef __sun__ +#define _POSIX_PTHREAD_SEMANTICS 1 +#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) +#include <pthread.h> +#endif +#endif + +#define AUDIO_CAP "sdl" +#include "audio_int.h" + +typedef struct SDLVoiceOut { +    HWVoiceOut hw; +    int live; +    int rpos; +    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; +} glob_sdl; +typedef struct SDLAudioState SDLAudioState; + +static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...) +{ +    va_list ap; + +    va_start (ap, fmt); +    AUD_vlog (AUDIO_CAP, fmt, ap); +    va_end (ap); + +    AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ()); +} + +static int sdl_lock (SDLAudioState *s, const char *forfn) +{ +    if (SDL_LockMutex (s->mutex)) { +        sdl_logerr ("SDL_LockMutex for %s failed\n", forfn); +        return -1; +    } +    return 0; +} + +static int sdl_unlock (SDLAudioState *s, const char *forfn) +{ +    if (SDL_UnlockMutex (s->mutex)) { +        sdl_logerr ("SDL_UnlockMutex for %s failed\n", forfn); +        return -1; +    } +    return 0; +} + +static int sdl_post (SDLAudioState *s, const char *forfn) +{ +    if (SDL_SemPost (s->sem)) { +        sdl_logerr ("SDL_SemPost for %s failed\n", forfn); +        return -1; +    } +    return 0; +} + +static int sdl_wait (SDLAudioState *s, const char *forfn) +{ +    if (SDL_SemWait (s->sem)) { +        sdl_logerr ("SDL_SemWait for %s failed\n", forfn); +        return -1; +    } +    return 0; +} + +static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn) +{ +    if (sdl_unlock (s, forfn)) { +        return -1; +    } + +    return sdl_post (s, forfn); +} + +static int aud_to_sdlfmt (audfmt_e fmt) +{ +    switch (fmt) { +    case AUD_FMT_S8: +        return AUDIO_S8; + +    case AUD_FMT_U8: +        return AUDIO_U8; + +    case AUD_FMT_S16: +        return AUDIO_S16LSB; + +    case AUD_FMT_U16: +        return AUDIO_U16LSB; + +    default: +        dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO +        abort (); +#endif +        return AUDIO_U8; +    } +} + +static int sdl_to_audfmt(int sdlfmt, audfmt_e *fmt, int *endianness) +{ +    switch (sdlfmt) { +    case AUDIO_S8: +        *endianness = 0; +        *fmt = AUD_FMT_S8; +        break; + +    case AUDIO_U8: +        *endianness = 0; +        *fmt = AUD_FMT_U8; +        break; + +    case AUDIO_S16LSB: +        *endianness = 0; +        *fmt = AUD_FMT_S16; +        break; + +    case AUDIO_U16LSB: +        *endianness = 0; +        *fmt = AUD_FMT_U16; +        break; + +    case AUDIO_S16MSB: +        *endianness = 1; +        *fmt = AUD_FMT_S16; +        break; + +    case AUDIO_U16MSB: +        *endianness = 1; +        *fmt = AUD_FMT_U16; +        break; + +    default: +        dolog ("Unrecognized SDL audio format %d\n", sdlfmt); +        return -1; +    } + +    return 0; +} + +static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) +{ +    int status; +#ifndef _WIN32 +    int err; +    sigset_t new, old; + +    /* Make sure potential threads created by SDL don't hog signals.  */ +    err = sigfillset (&new); +    if (err) { +        dolog ("sdl_open: sigfillset failed: %s\n", strerror (errno)); +        return -1; +    } +    err = pthread_sigmask (SIG_BLOCK, &new, &old); +    if (err) { +        dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err)); +        return -1; +    } +#endif + +    status = SDL_OpenAudio (req, obt); +    if (status) { +        sdl_logerr ("SDL_OpenAudio failed\n"); +    } + +#ifndef _WIN32 +    err = pthread_sigmask (SIG_SETMASK, &old, NULL); +    if (err) { +        dolog ("sdl_open: pthread_sigmask (restore) failed: %s\n", +               strerror (errno)); +        /* We have failed to restore original signal mask, all bets are off, +           so exit the process */ +        exit (EXIT_FAILURE); +    } +#endif +    return status; +} + +static void sdl_close (SDLAudioState *s) +{ +    if (s->initialized) { +        sdl_lock (s, "sdl_close"); +        s->exit = 1; +        sdl_unlock_and_post (s, "sdl_close"); +        SDL_PauseAudio (1); +        SDL_CloseAudio (); +        s->initialized = 0; +    } +} + +static void sdl_callback (void *opaque, Uint8 *buf, int len) +{ +    SDLVoiceOut *sdl = opaque; +    SDLAudioState *s = &glob_sdl; +    HWVoiceOut *hw = &sdl->hw; +    int samples = len >> hw->info.shift; + +    if (s->exit) { +        return; +    } + +    while (samples) { +        int to_mix, decr; + +        /* dolog ("in callback samples=%d\n", samples); */ +        sdl_wait (s, "sdl_callback"); +        if (s->exit) { +            return; +        } + +        if (sdl_lock (s, "sdl_callback")) { +            return; +        } + +        if (audio_bug (AUDIO_FUNC, sdl->live < 0 || sdl->live > hw->samples)) { +            dolog ("sdl->live=%d hw->samples=%d\n", +                   sdl->live, hw->samples); +            return; +        } + +        if (!sdl->live) { +            goto again; +        } + +        /* dolog ("in callback live=%d\n", live); */ +        to_mix = audio_MIN (samples, sdl->live); +        decr = to_mix; +        while (to_mix) { +            int chunk = audio_MIN (to_mix, hw->samples - hw->rpos); +            struct st_sample *src = hw->mix_buf + hw->rpos; + +            /* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */ +            hw->clip (buf, src, chunk); +            sdl->rpos = (sdl->rpos + chunk) % hw->samples; +            to_mix -= chunk; +            buf += chunk << hw->info.shift; +        } +        samples -= decr; +        sdl->live -= decr; +        sdl->decr += decr; + +    again: +        if (sdl_unlock (s, "sdl_callback")) { +            return; +        } +    } +    /* dolog ("done len=%d\n", len); */ +} + +static int sdl_write_out (SWVoiceOut *sw, void *buf, int len) +{ +    return audio_pcm_sw_write (sw, buf, len); +} + +static int sdl_run_out (HWVoiceOut *hw, int live) +{ +    int decr; +    SDLVoiceOut *sdl = (SDLVoiceOut *) hw; +    SDLAudioState *s = &glob_sdl; + +    if (sdl_lock (s, "sdl_run_out")) { +        return 0; +    } + +    if (sdl->decr > live) { +        ldebug ("sdl->decr %d live %d sdl->live %d\n", +                sdl->decr, +                live, +                sdl->live); +    } + +    decr = audio_MIN (sdl->decr, live); +    sdl->decr -= decr; + +    sdl->live = live - decr; +    hw->rpos = sdl->rpos; + +    if (sdl->live > 0) { +        sdl_unlock_and_post (s, "sdl_run_out"); +    } +    else { +        sdl_unlock (s, "sdl_run_out"); +    } +    return decr; +} + +static void sdl_fini_out (HWVoiceOut *hw) +{ +    (void) hw; + +    sdl_close (&glob_sdl); +} + +static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, +                        void *drv_opaque) +{ +    SDLVoiceOut *sdl = (SDLVoiceOut *) hw; +    SDLAudioState *s = &glob_sdl; +    SDL_AudioSpec req, obt; +    int endianness; +    int err; +    audfmt_e effective_fmt; +    struct audsettings obt_as; + +    req.freq = as->freq; +    req.format = aud_to_sdlfmt (as->fmt); +    req.channels = as->nchannels; +    req.samples = conf.nb_samples; +    req.callback = sdl_callback; +    req.userdata = sdl; + +    if (sdl_open (&req, &obt)) { +        return -1; +    } + +    err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness); +    if (err) { +        sdl_close (s); +        return -1; +    } + +    obt_as.freq = obt.freq; +    obt_as.nchannels = obt.channels; +    obt_as.fmt = effective_fmt; +    obt_as.endianness = endianness; + +    audio_pcm_init_info (&hw->info, &obt_as); +    hw->samples = obt.samples; + +    s->initialized = 1; +    s->exit = 0; +    SDL_PauseAudio (0); +    return 0; +} + +static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ +    (void) hw; + +    switch (cmd) { +    case VOICE_ENABLE: +        SDL_PauseAudio (0); +        break; + +    case VOICE_DISABLE: +        SDL_PauseAudio (1); +        break; +    } +    return 0; +} + +static void *sdl_audio_init (void) +{ +    SDLAudioState *s = &glob_sdl; +    if (s->driver_created) { +        sdl_logerr("Can't create multiple sdl backends\n"); +        return NULL; +    } + +    if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { +        sdl_logerr ("SDL failed to initialize audio subsystem\n"); +        return NULL; +    } + +    s->mutex = SDL_CreateMutex (); +    if (!s->mutex) { +        sdl_logerr ("Failed to create SDL mutex\n"); +        SDL_QuitSubSystem (SDL_INIT_AUDIO); +        return NULL; +    } + +    s->sem = SDL_CreateSemaphore (0); +    if (!s->sem) { +        sdl_logerr ("Failed to create SDL semaphore\n"); +        SDL_DestroyMutex (s->mutex); +        SDL_QuitSubSystem (SDL_INIT_AUDIO); +        return NULL; +    } + +    s->driver_created = true; +    return s; +} + +static void sdl_audio_fini (void *opaque) +{ +    SDLAudioState *s = opaque; +    sdl_close (s); +    SDL_DestroySemaphore (s->sem); +    SDL_DestroyMutex (s->mutex); +    SDL_QuitSubSystem (SDL_INIT_AUDIO); +    s->driver_created = false; +} + +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, +    .run_out  = sdl_run_out, +    .write    = sdl_write_out, +    .ctl_out  = sdl_ctl_out, +}; + +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, +    .can_be_default = 1, +    .max_voices_out = 1, +    .max_voices_in  = 0, +    .voice_size_out = sizeof (SDLVoiceOut), +    .voice_size_in  = 0 +}; diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c new file mode 100644 index 00000000..42ae4a45 --- /dev/null +++ b/audio/spiceaudio.c @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * maintained by Gerd Hoffmann <kraxel@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/hw.h" +#include "qemu/error-report.h" +#include "qemu/timer.h" +#include "ui/qemu-spice.h" + +#define AUDIO_CAP "spice" +#include "audio.h" +#include "audio_int.h" + +#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 +#define LINE_OUT_SAMPLES (480 * 4) +#else +#define LINE_OUT_SAMPLES (256 * 4) +#endif + +#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 +#define LINE_IN_SAMPLES (480 * 4) +#else +#define LINE_IN_SAMPLES (256 * 4) +#endif + +typedef struct SpiceRateCtl { +    int64_t               start_ticks; +    int64_t               bytes_sent; +} SpiceRateCtl; + +typedef struct SpiceVoiceOut { +    HWVoiceOut            hw; +    SpicePlaybackInstance sin; +    SpiceRateCtl          rate; +    int                   active; +    uint32_t              *frame; +    uint32_t              *fpos; +    uint32_t              fsize; +} SpiceVoiceOut; + +typedef struct SpiceVoiceIn { +    HWVoiceIn             hw; +    SpiceRecordInstance   sin; +    SpiceRateCtl          rate; +    int                   active; +    uint32_t              samples[LINE_IN_SAMPLES]; +} SpiceVoiceIn; + +static const SpicePlaybackInterface playback_sif = { +    .base.type          = SPICE_INTERFACE_PLAYBACK, +    .base.description   = "playback", +    .base.major_version = SPICE_INTERFACE_PLAYBACK_MAJOR, +    .base.minor_version = SPICE_INTERFACE_PLAYBACK_MINOR, +}; + +static const SpiceRecordInterface record_sif = { +    .base.type          = SPICE_INTERFACE_RECORD, +    .base.description   = "record", +    .base.major_version = SPICE_INTERFACE_RECORD_MAJOR, +    .base.minor_version = SPICE_INTERFACE_RECORD_MINOR, +}; + +static void *spice_audio_init (void) +{ +    if (!using_spice) { +        return NULL; +    } +    return &spice_audio_init; +} + +static void spice_audio_fini (void *opaque) +{ +    /* nothing */ +} + +static void rate_start (SpiceRateCtl *rate) +{ +    memset (rate, 0, sizeof (*rate)); +    rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate) +{ +    int64_t now; +    int64_t ticks; +    int64_t bytes; +    int64_t samples; + +    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +    ticks = now - rate->start_ticks; +    bytes = muldiv64 (ticks, info->bytes_per_second, get_ticks_per_sec ()); +    samples = (bytes - rate->bytes_sent) >> info->shift; +    if (samples < 0 || samples > 65536) { +        error_report("Resetting rate control (%" PRId64 " samples)", samples); +        rate_start (rate); +        samples = 0; +    } +    rate->bytes_sent += samples << info->shift; +    return samples; +} + +/* playback */ + +static int line_out_init(HWVoiceOut *hw, struct audsettings *as, +                         void *drv_opaque) +{ +    SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); +    struct audsettings settings; + +#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 +    settings.freq       = spice_server_get_best_playback_rate(NULL); +#else +    settings.freq       = SPICE_INTERFACE_PLAYBACK_FREQ; +#endif +    settings.nchannels  = SPICE_INTERFACE_PLAYBACK_CHAN; +    settings.fmt        = AUD_FMT_S16; +    settings.endianness = AUDIO_HOST_ENDIANNESS; + +    audio_pcm_init_info (&hw->info, &settings); +    hw->samples = LINE_OUT_SAMPLES; +    out->active = 0; + +    out->sin.base.sif = &playback_sif.base; +    qemu_spice_add_interface (&out->sin.base); +#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 +    spice_server_set_playback_rate(&out->sin, settings.freq); +#endif +    return 0; +} + +static void line_out_fini (HWVoiceOut *hw) +{ +    SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + +    spice_server_remove_interface (&out->sin.base); +} + +static int line_out_run (HWVoiceOut *hw, int live) +{ +    SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); +    int rpos, decr; +    int samples; + +    if (!live) { +        return 0; +    } + +    decr = rate_get_samples (&hw->info, &out->rate); +    decr = audio_MIN (live, decr); + +    samples = decr; +    rpos = hw->rpos; +    while (samples) { +        int left_till_end_samples = hw->samples - rpos; +        int len = audio_MIN (samples, left_till_end_samples); + +        if (!out->frame) { +            spice_server_playback_get_buffer (&out->sin, &out->frame, &out->fsize); +            out->fpos = out->frame; +        } +        if (out->frame) { +            len = audio_MIN (len, out->fsize); +            hw->clip (out->fpos, hw->mix_buf + rpos, len); +            out->fsize -= len; +            out->fpos  += len; +            if (out->fsize == 0) { +                spice_server_playback_put_samples (&out->sin, out->frame); +                out->frame = out->fpos = NULL; +            } +        } +        rpos = (rpos + len) % hw->samples; +        samples -= len; +    } +    hw->rpos = rpos; +    return decr; +} + +static int line_out_write (SWVoiceOut *sw, void *buf, int len) +{ +    return audio_pcm_sw_write (sw, buf, len); +} + +static int line_out_ctl (HWVoiceOut *hw, int cmd, ...) +{ +    SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + +    switch (cmd) { +    case VOICE_ENABLE: +        if (out->active) { +            break; +        } +        out->active = 1; +        rate_start (&out->rate); +        spice_server_playback_start (&out->sin); +        break; +    case VOICE_DISABLE: +        if (!out->active) { +            break; +        } +        out->active = 0; +        if (out->frame) { +            memset (out->fpos, 0, out->fsize << 2); +            spice_server_playback_put_samples (&out->sin, out->frame); +            out->frame = out->fpos = NULL; +        } +        spice_server_playback_stop (&out->sin); +        break; +    case VOICE_VOLUME: +        { +#if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2)) +            SWVoiceOut *sw; +            va_list ap; +            uint16_t vol[2]; + +            va_start (ap, cmd); +            sw = va_arg (ap, SWVoiceOut *); +            va_end (ap); + +            vol[0] = sw->vol.l / ((1ULL << 16) + 1); +            vol[1] = sw->vol.r / ((1ULL << 16) + 1); +            spice_server_playback_set_volume (&out->sin, 2, vol); +            spice_server_playback_set_mute (&out->sin, sw->vol.mute); +#endif +            break; +        } +    } + +    return 0; +} + +/* record */ + +static int line_in_init(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +{ +    SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); +    struct audsettings settings; + +#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 +    settings.freq       = spice_server_get_best_record_rate(NULL); +#else +    settings.freq       = SPICE_INTERFACE_RECORD_FREQ; +#endif +    settings.nchannels  = SPICE_INTERFACE_RECORD_CHAN; +    settings.fmt        = AUD_FMT_S16; +    settings.endianness = AUDIO_HOST_ENDIANNESS; + +    audio_pcm_init_info (&hw->info, &settings); +    hw->samples = LINE_IN_SAMPLES; +    in->active = 0; + +    in->sin.base.sif = &record_sif.base; +    qemu_spice_add_interface (&in->sin.base); +#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 +    spice_server_set_record_rate(&in->sin, settings.freq); +#endif +    return 0; +} + +static void line_in_fini (HWVoiceIn *hw) +{ +    SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + +    spice_server_remove_interface (&in->sin.base); +} + +static int line_in_run (HWVoiceIn *hw) +{ +    SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); +    int num_samples; +    int ready; +    int len[2]; +    uint64_t delta_samp; +    const uint32_t *samples; + +    if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in (hw))) { +        return 0; +    } + +    delta_samp = rate_get_samples (&hw->info, &in->rate); +    num_samples = audio_MIN (num_samples, delta_samp); + +    ready = spice_server_record_get_samples (&in->sin, in->samples, num_samples); +    samples = in->samples; +    if (ready == 0) { +        static const uint32_t silence[LINE_IN_SAMPLES]; +        samples = silence; +        ready = LINE_IN_SAMPLES; +    } + +    num_samples = audio_MIN (ready, num_samples); + +    if (hw->wpos + num_samples > hw->samples) { +        len[0] = hw->samples - hw->wpos; +        len[1] = num_samples - len[0]; +    } else { +        len[0] = num_samples; +        len[1] = 0; +    } + +    hw->conv (hw->conv_buf + hw->wpos, samples, len[0]); + +    if (len[1]) { +        hw->conv (hw->conv_buf, samples + len[0], len[1]); +    } + +    hw->wpos = (hw->wpos + num_samples) % hw->samples; + +    return num_samples; +} + +static int line_in_read (SWVoiceIn *sw, void *buf, int size) +{ +    return audio_pcm_sw_read (sw, buf, size); +} + +static int line_in_ctl (HWVoiceIn *hw, int cmd, ...) +{ +    SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + +    switch (cmd) { +    case VOICE_ENABLE: +        if (in->active) { +            break; +        } +        in->active = 1; +        rate_start (&in->rate); +        spice_server_record_start (&in->sin); +        break; +    case VOICE_DISABLE: +        if (!in->active) { +            break; +        } +        in->active = 0; +        spice_server_record_stop (&in->sin); +        break; +    case VOICE_VOLUME: +        { +#if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2)) +            SWVoiceIn *sw; +            va_list ap; +            uint16_t vol[2]; + +            va_start (ap, cmd); +            sw = va_arg (ap, SWVoiceIn *); +            va_end (ap); + +            vol[0] = sw->vol.l / ((1ULL << 16) + 1); +            vol[1] = sw->vol.r / ((1ULL << 16) + 1); +            spice_server_record_set_volume (&in->sin, 2, vol); +            spice_server_record_set_mute (&in->sin, sw->vol.mute); +#endif +            break; +        } +    } + +    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, +    .run_out  = line_out_run, +    .write    = line_out_write, +    .ctl_out  = line_out_ctl, + +    .init_in  = line_in_init, +    .fini_in  = line_in_fini, +    .run_in   = line_in_run, +    .read     = line_in_read, +    .ctl_in   = line_in_ctl, +}; + +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, +    .max_voices_out = 1, +    .max_voices_in  = 1, +    .voice_size_out = sizeof (SpiceVoiceOut), +    .voice_size_in  = sizeof (SpiceVoiceIn), +#if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2)) +    .ctl_caps       = VOICE_VOLUME_CAP +#endif +}; + +void qemu_spice_audio_init (void) +{ +    spice_audio_driver.can_be_default = 1; +} diff --git a/audio/wavaudio.c b/audio/wavaudio.c new file mode 100644 index 00000000..c586020c --- /dev/null +++ b/audio/wavaudio.c @@ -0,0 +1,292 @@ +/* + * QEMU WAV audio driver + * + * Copyright (c) 2004-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. + */ +#include "hw/hw.h" +#include "qemu/timer.h" +#include "audio.h" + +#define AUDIO_CAP "wav" +#include "audio_int.h" + +typedef struct WAVVoiceOut { +    HWVoiceOut hw; +    FILE *f; +    int64_t old_ticks; +    void *pcm_buf; +    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; +    int rpos, decr, samples; +    uint8_t *dst; +    struct st_sample *src; +    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +    int64_t ticks = now - wav->old_ticks; +    int64_t bytes = +        muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ()); + +    if (bytes > INT_MAX) { +        samples = INT_MAX >> hw->info.shift; +    } +    else { +        samples = bytes >> hw->info.shift; +    } + +    wav->old_ticks = now; +    decr = audio_MIN (live, samples); +    samples = decr; +    rpos = hw->rpos; +    while (samples) { +        int left_till_end_samples = hw->samples - rpos; +        int convert_samples = audio_MIN (samples, left_till_end_samples); + +        src = hw->mix_buf + rpos; +        dst = advance (wav->pcm_buf, rpos << hw->info.shift); + +        hw->clip (dst, src, convert_samples); +        if (fwrite (dst, convert_samples << hw->info.shift, 1, wav->f) != 1) { +            dolog ("wav_run_out: fwrite of %d bytes failed\nReaons: %s\n", +                   convert_samples << hw->info.shift, strerror (errno)); +        } + +        rpos = (rpos + convert_samples) % hw->samples; +        samples -= convert_samples; +        wav->total_samples += convert_samples; +    } + +    hw->rpos = rpos; +    return decr; +} + +static int wav_write_out (SWVoiceOut *sw, void *buf, int len) +{ +    return audio_pcm_sw_write (sw, buf, len); +} + +/* VICE code: Store number as little endian. */ +static void le_store (uint8_t *buf, uint32_t val, int len) +{ +    int i; +    for (i = 0; i < len; i++) { +        buf[i] = (uint8_t) (val & 0xff); +        val >>= 8; +    } +} + +static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, +                        void *drv_opaque) +{ +    WAVVoiceOut *wav = (WAVVoiceOut *) hw; +    int bits16 = 0, stereo = 0; +    uint8_t hdr[] = { +        0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, +        0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, +        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; + +    stereo = wav_as.nchannels == 2; +    switch (wav_as.fmt) { +    case AUD_FMT_S8: +    case AUD_FMT_U8: +        bits16 = 0; +        break; + +    case AUD_FMT_S16: +    case AUD_FMT_U16: +        bits16 = 1; +        break; + +    case AUD_FMT_S32: +    case AUD_FMT_U32: +        dolog ("WAVE files can not handle 32bit formats\n"); +        return -1; +    } + +    hdr[34] = bits16 ? 0x10 : 0x08; + +    wav_as.endianness = 0; +    audio_pcm_init_info (&hw->info, &wav_as); + +    hw->samples = 1024; +    wav->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); +    if (!wav->pcm_buf) { +        dolog ("Could not allocate buffer (%d bytes)\n", +               hw->samples << hw->info.shift); +        return -1; +    } + +    le_store (hdr + 22, hw->info.nchannels, 2); +    le_store (hdr + 24, hw->info.freq, 4); +    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"); +    if (!wav->f) { +        dolog ("Failed to open wave file `%s'\nReason: %s\n", +               conf->wav_path, strerror (errno)); +        g_free (wav->pcm_buf); +        wav->pcm_buf = NULL; +        return -1; +    } + +    if (fwrite (hdr, sizeof (hdr), 1, wav->f) != 1) { +        dolog ("wav_init_out: failed to write header\nReason: %s\n", +               strerror(errno)); +        return -1; +    } +    return 0; +} + +static void wav_fini_out (HWVoiceOut *hw) +{ +    WAVVoiceOut *wav = (WAVVoiceOut *) hw; +    uint8_t rlen[4]; +    uint8_t dlen[4]; +    uint32_t datalen = wav->total_samples << hw->info.shift; +    uint32_t rifflen = datalen + 36; + +    if (!wav->f) { +        return; +    } + +    le_store (rlen, rifflen, 4); +    le_store (dlen, datalen, 4); + +    if (fseek (wav->f, 4, SEEK_SET)) { +        dolog ("wav_fini_out: fseek to rlen failed\nReason: %s\n", +               strerror(errno)); +        goto doclose; +    } +    if (fwrite (rlen, 4, 1, wav->f) != 1) { +        dolog ("wav_fini_out: failed to write rlen\nReason: %s\n", +               strerror (errno)); +        goto doclose; +    } +    if (fseek (wav->f, 32, SEEK_CUR)) { +        dolog ("wav_fini_out: fseek to dlen failed\nReason: %s\n", +               strerror (errno)); +        goto doclose; +    } +    if (fwrite (dlen, 4, 1, wav->f) != 1) { +        dolog ("wav_fini_out: failed to write dlen\nReaons: %s\n", +               strerror (errno)); +        goto doclose; +    } + + doclose: +    if (fclose (wav->f))  { +        dolog ("wav_fini_out: fclose %p failed\nReason: %s\n", +               wav->f, strerror (errno)); +    } +    wav->f = NULL; + +    g_free (wav->pcm_buf); +    wav->pcm_buf = NULL; +} + +static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ +    (void) hw; +    (void) cmd; +    return 0; +} + +static WAVConf glob_conf = { +    .settings.freq      = 44100, +    .settings.nchannels = 2, +    .settings.fmt       = AUD_FMT_S16, +    .wav_path           = "qemu.wav" +}; + +static void *wav_audio_init (void) +{ +    WAVConf *conf = g_malloc(sizeof(WAVConf)); +    *conf = glob_conf; +    return conf; +} + +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, +    .run_out  = wav_run_out, +    .write    = wav_write_out, +    .ctl_out  = wav_ctl_out, +}; + +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, +    .can_be_default = 0, +    .max_voices_out = 1, +    .max_voices_in  = 0, +    .voice_size_out = sizeof (WAVVoiceOut), +    .voice_size_in  = 0 +}; diff --git a/audio/wavcapture.c b/audio/wavcapture.c new file mode 100644 index 00000000..86e90562 --- /dev/null +++ b/audio/wavcapture.c @@ -0,0 +1,194 @@ +#include "hw/hw.h" +#include "monitor/monitor.h" +#include "qemu/error-report.h" +#include "audio.h" + +typedef struct { +    FILE *f; +    int bytes; +    char *path; +    int freq; +    int bits; +    int nchannels; +    CaptureVoiceOut *cap; +} WAVState; + +/* VICE code: Store number as little endian. */ +static void le_store (uint8_t *buf, uint32_t val, int len) +{ +    int i; +    for (i = 0; i < len; i++) { +        buf[i] = (uint8_t) (val & 0xff); +        val >>= 8; +    } +} + +static void wav_notify (void *opaque, audcnotification_e cmd) +{ +    (void) opaque; +    (void) cmd; +} + +static void wav_destroy (void *opaque) +{ +    WAVState *wav = opaque; +    uint8_t rlen[4]; +    uint8_t dlen[4]; +    uint32_t datalen = wav->bytes; +    uint32_t rifflen = datalen + 36; +    Monitor *mon = cur_mon; + +    if (wav->f) { +        le_store (rlen, rifflen, 4); +        le_store (dlen, datalen, 4); + +        if (fseek (wav->f, 4, SEEK_SET)) { +            monitor_printf (mon, "wav_destroy: rlen fseek failed\nReason: %s\n", +                            strerror (errno)); +            goto doclose; +        } +        if (fwrite (rlen, 4, 1, wav->f) != 1) { +            monitor_printf (mon, "wav_destroy: rlen fwrite failed\nReason %s\n", +                            strerror (errno)); +            goto doclose; +        } +        if (fseek (wav->f, 32, SEEK_CUR)) { +            monitor_printf (mon, "wav_destroy: dlen fseek failed\nReason %s\n", +                            strerror (errno)); +            goto doclose; +        } +        if (fwrite (dlen, 1, 4, wav->f) != 4) { +            monitor_printf (mon, "wav_destroy: dlen fwrite failed\nReason %s\n", +                            strerror (errno)); +            goto doclose; +        } +    doclose: +        if (fclose (wav->f)) { +            error_report("wav_destroy: fclose failed: %s", strerror(errno)); +        } +    } + +    g_free (wav->path); +} + +static void wav_capture (void *opaque, void *buf, int size) +{ +    WAVState *wav = opaque; + +    if (fwrite (buf, size, 1, wav->f) != 1) { +        monitor_printf (cur_mon, "wav_capture: fwrite error\nReason: %s", +                        strerror (errno)); +    } +    wav->bytes += size; +} + +static void wav_capture_destroy (void *opaque) +{ +    WAVState *wav = opaque; + +    AUD_del_capture (wav->cap, wav); +} + +static void wav_capture_info (void *opaque) +{ +    WAVState *wav = opaque; +    char *path = wav->path; + +    monitor_printf (cur_mon, "Capturing audio(%d,%d,%d) to %s: %d bytes\n", +                    wav->freq, wav->bits, wav->nchannels, +                    path ? path : "<not available>", wav->bytes); +} + +static struct capture_ops wav_capture_ops = { +    .destroy = wav_capture_destroy, +    .info = wav_capture_info +}; + +int wav_start_capture (CaptureState *s, const char *path, int freq, +                       int bits, int nchannels) +{ +    Monitor *mon = cur_mon; +    WAVState *wav; +    uint8_t hdr[] = { +        0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, +        0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, +        0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, +        0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 +    }; +    struct audsettings as; +    struct audio_capture_ops ops; +    int stereo, bits16, shift; +    CaptureVoiceOut *cap; + +    if (bits != 8 && bits != 16) { +        monitor_printf (mon, "incorrect bit count %d, must be 8 or 16\n", bits); +        return -1; +    } + +    if (nchannels != 1 && nchannels != 2) { +        monitor_printf (mon, "incorrect channel count %d, must be 1 or 2\n", +                        nchannels); +        return -1; +    } + +    stereo = nchannels == 2; +    bits16 = bits == 16; + +    as.freq = freq; +    as.nchannels = 1 << stereo; +    as.fmt = bits16 ? AUD_FMT_S16 : AUD_FMT_U8; +    as.endianness = 0; + +    ops.notify = wav_notify; +    ops.capture = wav_capture; +    ops.destroy = wav_destroy; + +    wav = g_malloc0 (sizeof (*wav)); + +    shift = bits16 + stereo; +    hdr[34] = bits16 ? 0x10 : 0x08; + +    le_store (hdr + 22, as.nchannels, 2); +    le_store (hdr + 24, freq, 4); +    le_store (hdr + 28, freq << shift, 4); +    le_store (hdr + 32, 1 << shift, 2); + +    wav->f = fopen (path, "wb"); +    if (!wav->f) { +        monitor_printf (mon, "Failed to open wave file `%s'\nReason: %s\n", +                        path, strerror (errno)); +        g_free (wav); +        return -1; +    } + +    wav->path = g_strdup (path); +    wav->bits = bits; +    wav->nchannels = nchannels; +    wav->freq = freq; + +    if (fwrite (hdr, sizeof (hdr), 1, wav->f) != 1) { +        monitor_printf (mon, "Failed to write header\nReason: %s\n", +                        strerror (errno)); +        goto error_free; +    } + +    cap = AUD_add_capture (&as, &ops, wav); +    if (!cap) { +        monitor_printf (mon, "Failed to add audio capture\n"); +        goto error_free; +    } + +    wav->cap = cap; +    s->opaque = wav; +    s->ops = wav_capture_ops; +    return 0; + +error_free: +    g_free (wav->path); +    if (fclose (wav->f)) { +        monitor_printf (mon, "Failed to close wave file\nReason: %s\n", +                        strerror (errno)); +    } +    g_free (wav); +    return -1; +}  | 
