qemu/audio/wavaudio.c
<<
>>
Prefs
   1/*
   2 * QEMU WAV audio driver
   3 *
   4 * Copyright (c) 2004-2005 Vassili Karpov (malc)
   5 *
   6 * Permission is hereby granted, free of charge, to any person obtaining a copy
   7 * of this software and associated documentation files (the "Software"), to deal
   8 * in the Software without restriction, including without limitation the rights
   9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 * copies of the Software, and to permit persons to whom the Software is
  11 * furnished to do so, subject to the following conditions:
  12 *
  13 * The above copyright notice and this permission notice shall be included in
  14 * all copies or substantial portions of the Software.
  15 *
  16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22 * THE SOFTWARE.
  23 */
  24#include "hw/hw.h"
  25#include "qemu-timer.h"
  26#include "audio.h"
  27
  28#define AUDIO_CAP "wav"
  29#include "audio_int.h"
  30
  31typedef struct WAVVoiceOut {
  32    HWVoiceOut hw;
  33    QEMUFile *f;
  34    int64_t old_ticks;
  35    void *pcm_buf;
  36    int total_samples;
  37} WAVVoiceOut;
  38
  39static struct {
  40    struct audsettings settings;
  41    const char *wav_path;
  42} conf = {
  43    .settings.freq      = 44100,
  44    .settings.nchannels = 2,
  45    .settings.fmt       = AUD_FMT_S16,
  46    .wav_path           = "qemu.wav"
  47};
  48
  49static int wav_run_out (HWVoiceOut *hw, int live)
  50{
  51    WAVVoiceOut *wav = (WAVVoiceOut *) hw;
  52    int rpos, decr, samples;
  53    uint8_t *dst;
  54    struct st_sample *src;
  55    int64_t now = qemu_get_clock (vm_clock);
  56    int64_t ticks = now - wav->old_ticks;
  57    int64_t bytes =
  58        muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
  59
  60    if (bytes > INT_MAX) {
  61        samples = INT_MAX >> hw->info.shift;
  62    }
  63    else {
  64        samples = bytes >> hw->info.shift;
  65    }
  66
  67    wav->old_ticks = now;
  68    decr = audio_MIN (live, samples);
  69    samples = decr;
  70    rpos = hw->rpos;
  71    while (samples) {
  72        int left_till_end_samples = hw->samples - rpos;
  73        int convert_samples = audio_MIN (samples, left_till_end_samples);
  74
  75        src = hw->mix_buf + rpos;
  76        dst = advance (wav->pcm_buf, rpos << hw->info.shift);
  77
  78        hw->clip (dst, src, convert_samples);
  79        qemu_put_buffer (wav->f, dst, convert_samples << hw->info.shift);
  80
  81        rpos = (rpos + convert_samples) % hw->samples;
  82        samples -= convert_samples;
  83        wav->total_samples += convert_samples;
  84    }
  85
  86    hw->rpos = rpos;
  87    return decr;
  88}
  89
  90static int wav_write_out (SWVoiceOut *sw, void *buf, int len)
  91{
  92    return audio_pcm_sw_write (sw, buf, len);
  93}
  94
  95/* VICE code: Store number as little endian. */
  96static void le_store (uint8_t *buf, uint32_t val, int len)
  97{
  98    int i;
  99    for (i = 0; i < len; i++) {
 100        buf[i] = (uint8_t) (val & 0xff);
 101        val >>= 8;
 102    }
 103}
 104
 105static int wav_init_out (HWVoiceOut *hw, struct audsettings *as)
 106{
 107    WAVVoiceOut *wav = (WAVVoiceOut *) hw;
 108    int bits16 = 0, stereo = 0;
 109    uint8_t hdr[] = {
 110        0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56,
 111        0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00,
 112        0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04,
 113        0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00
 114    };
 115    struct audsettings wav_as = conf.settings;
 116
 117    (void) as;
 118
 119    stereo = wav_as.nchannels == 2;
 120    switch (wav_as.fmt) {
 121    case AUD_FMT_S8:
 122    case AUD_FMT_U8:
 123        bits16 = 0;
 124        break;
 125
 126    case AUD_FMT_S16:
 127    case AUD_FMT_U16:
 128        bits16 = 1;
 129        break;
 130
 131    case AUD_FMT_S32:
 132    case AUD_FMT_U32:
 133        dolog ("WAVE files can not handle 32bit formats\n");
 134        return -1;
 135    }
 136
 137    hdr[34] = bits16 ? 0x10 : 0x08;
 138
 139    wav_as.endianness = 0;
 140    audio_pcm_init_info (&hw->info, &wav_as);
 141
 142    hw->samples = 1024;
 143    wav->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
 144    if (!wav->pcm_buf) {
 145        dolog ("Could not allocate buffer (%d bytes)\n",
 146               hw->samples << hw->info.shift);
 147        return -1;
 148    }
 149
 150    le_store (hdr + 22, hw->info.nchannels, 2);
 151    le_store (hdr + 24, hw->info.freq, 4);
 152    le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4);
 153    le_store (hdr + 32, 1 << (bits16 + stereo), 2);
 154
 155    wav->f = qemu_fopen (conf.wav_path, "wb");
 156    if (!wav->f) {
 157        dolog ("Failed to open wave file `%s'\nReason: %s\n",
 158               conf.wav_path, strerror (errno));
 159        qemu_free (wav->pcm_buf);
 160        wav->pcm_buf = NULL;
 161        return -1;
 162    }
 163
 164    qemu_put_buffer (wav->f, hdr, sizeof (hdr));
 165    return 0;
 166}
 167
 168static void wav_fini_out (HWVoiceOut *hw)
 169{
 170    WAVVoiceOut *wav = (WAVVoiceOut *) hw;
 171    uint8_t rlen[4];
 172    uint8_t dlen[4];
 173    uint32_t datalen = wav->total_samples << hw->info.shift;
 174    uint32_t rifflen = datalen + 36;
 175
 176    if (!wav->f) {
 177        return;
 178    }
 179
 180    le_store (rlen, rifflen, 4);
 181    le_store (dlen, datalen, 4);
 182
 183    qemu_fseek (wav->f, 4, SEEK_SET);
 184    qemu_put_buffer (wav->f, rlen, 4);
 185
 186    qemu_fseek (wav->f, 32, SEEK_CUR);
 187    qemu_put_buffer (wav->f, dlen, 4);
 188
 189    qemu_fclose (wav->f);
 190    wav->f = NULL;
 191
 192    qemu_free (wav->pcm_buf);
 193    wav->pcm_buf = NULL;
 194}
 195
 196static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...)
 197{
 198    (void) hw;
 199    (void) cmd;
 200    return 0;
 201}
 202
 203static void *wav_audio_init (void)
 204{
 205    return &conf;
 206}
 207
 208static void wav_audio_fini (void *opaque)
 209{
 210    (void) opaque;
 211    ldebug ("wav_fini");
 212}
 213
 214static struct audio_option wav_options[] = {
 215    {
 216        .name  = "FREQUENCY",
 217        .tag   = AUD_OPT_INT,
 218        .valp  = &conf.settings.freq,
 219        .descr = "Frequency"
 220    },
 221    {
 222        .name  = "FORMAT",
 223        .tag   = AUD_OPT_FMT,
 224        .valp  = &conf.settings.fmt,
 225        .descr = "Format"
 226    },
 227    {
 228        .name  = "DAC_FIXED_CHANNELS",
 229        .tag   = AUD_OPT_INT,
 230        .valp  = &conf.settings.nchannels,
 231        .descr = "Number of channels (1 - mono, 2 - stereo)"
 232    },
 233    {
 234        .name  = "PATH",
 235        .tag   = AUD_OPT_STR,
 236        .valp  = &conf.wav_path,
 237        .descr = "Path to wave file"
 238    },
 239    { /* End of list */ }
 240};
 241
 242static struct audio_pcm_ops wav_pcm_ops = {
 243    .init_out = wav_init_out,
 244    .fini_out = wav_fini_out,
 245    .run_out  = wav_run_out,
 246    .write    = wav_write_out,
 247    .ctl_out  = wav_ctl_out,
 248};
 249
 250struct audio_driver wav_audio_driver = {
 251    .name           = "wav",
 252    .descr          = "WAV renderer http://wikipedia.org/wiki/WAV",
 253    .options        = wav_options,
 254    .init           = wav_audio_init,
 255    .fini           = wav_audio_fini,
 256    .pcm_ops        = &wav_pcm_ops,
 257    .can_be_default = 0,
 258    .max_voices_out = 1,
 259    .max_voices_in  = 0,
 260    .voice_size_out = sizeof (WAVVoiceOut),
 261    .voice_size_in  = 0
 262};
 263