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 "qemu/osdep.h"
  25#include "hw/hw.h"
  26#include "qemu/timer.h"
  27#include "audio.h"
  28
  29#define AUDIO_CAP "wav"
  30#include "audio_int.h"
  31
  32typedef struct WAVVoiceOut {
  33    HWVoiceOut hw;
  34    FILE *f;
  35    int64_t old_ticks;
  36    void *pcm_buf;
  37    int total_samples;
  38} WAVVoiceOut;
  39
  40typedef struct {
  41    struct audsettings settings;
  42    const char *wav_path;
  43} WAVConf;
  44
  45static int wav_run_out (HWVoiceOut *hw, int live)
  46{
  47    WAVVoiceOut *wav = (WAVVoiceOut *) hw;
  48    int rpos, decr, samples;
  49    uint8_t *dst;
  50    struct st_sample *src;
  51    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
  52    int64_t ticks = now - wav->old_ticks;
  53    int64_t bytes =
  54        muldiv64(ticks, hw->info.bytes_per_second, NANOSECONDS_PER_SECOND);
  55
  56    if (bytes > INT_MAX) {
  57        samples = INT_MAX >> hw->info.shift;
  58    }
  59    else {
  60        samples = bytes >> hw->info.shift;
  61    }
  62
  63    wav->old_ticks = now;
  64    decr = audio_MIN (live, samples);
  65    samples = decr;
  66    rpos = hw->rpos;
  67    while (samples) {
  68        int left_till_end_samples = hw->samples - rpos;
  69        int convert_samples = audio_MIN (samples, left_till_end_samples);
  70
  71        src = hw->mix_buf + rpos;
  72        dst = advance (wav->pcm_buf, rpos << hw->info.shift);
  73
  74        hw->clip (dst, src, convert_samples);
  75        if (fwrite (dst, convert_samples << hw->info.shift, 1, wav->f) != 1) {
  76            dolog ("wav_run_out: fwrite of %d bytes failed\nReaons: %s\n",
  77                   convert_samples << hw->info.shift, strerror (errno));
  78        }
  79
  80        rpos = (rpos + convert_samples) % hw->samples;
  81        samples -= convert_samples;
  82        wav->total_samples += convert_samples;
  83    }
  84
  85    hw->rpos = rpos;
  86    return decr;
  87}
  88
  89static int wav_write_out (SWVoiceOut *sw, void *buf, int len)
  90{
  91    return audio_pcm_sw_write (sw, buf, len);
  92}
  93
  94/* VICE code: Store number as little endian. */
  95static void le_store (uint8_t *buf, uint32_t val, int len)
  96{
  97    int i;
  98    for (i = 0; i < len; i++) {
  99        buf[i] = (uint8_t) (val & 0xff);
 100        val >>= 8;
 101    }
 102}
 103
 104static int wav_init_out(HWVoiceOut *hw, struct audsettings *as,
 105                        void *drv_opaque)
 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    WAVConf *conf = drv_opaque;
 116    struct audsettings wav_as = conf->settings;
 117
 118    stereo = wav_as.nchannels == 2;
 119    switch (wav_as.fmt) {
 120    case AUD_FMT_S8:
 121    case AUD_FMT_U8:
 122        bits16 = 0;
 123        break;
 124
 125    case AUD_FMT_S16:
 126    case AUD_FMT_U16:
 127        bits16 = 1;
 128        break;
 129
 130    case AUD_FMT_S32:
 131    case AUD_FMT_U32:
 132        dolog ("WAVE files can not handle 32bit formats\n");
 133        return -1;
 134    }
 135
 136    hdr[34] = bits16 ? 0x10 : 0x08;
 137
 138    wav_as.endianness = 0;
 139    audio_pcm_init_info (&hw->info, &wav_as);
 140
 141    hw->samples = 1024;
 142    wav->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
 143    if (!wav->pcm_buf) {
 144        dolog ("Could not allocate buffer (%d bytes)\n",
 145               hw->samples << hw->info.shift);
 146        return -1;
 147    }
 148
 149    le_store (hdr + 22, hw->info.nchannels, 2);
 150    le_store (hdr + 24, hw->info.freq, 4);
 151    le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4);
 152    le_store (hdr + 32, 1 << (bits16 + stereo), 2);
 153
 154    wav->f = fopen (conf->wav_path, "wb");
 155    if (!wav->f) {
 156        dolog ("Failed to open wave file `%s'\nReason: %s\n",
 157               conf->wav_path, strerror (errno));
 158        g_free (wav->pcm_buf);
 159        wav->pcm_buf = NULL;
 160        return -1;
 161    }
 162
 163    if (fwrite (hdr, sizeof (hdr), 1, wav->f) != 1) {
 164        dolog ("wav_init_out: failed to write header\nReason: %s\n",
 165               strerror(errno));
 166        return -1;
 167    }
 168    return 0;
 169}
 170
 171static void wav_fini_out (HWVoiceOut *hw)
 172{
 173    WAVVoiceOut *wav = (WAVVoiceOut *) hw;
 174    uint8_t rlen[4];
 175    uint8_t dlen[4];
 176    uint32_t datalen = wav->total_samples << hw->info.shift;
 177    uint32_t rifflen = datalen + 36;
 178
 179    if (!wav->f) {
 180        return;
 181    }
 182
 183    le_store (rlen, rifflen, 4);
 184    le_store (dlen, datalen, 4);
 185
 186    if (fseek (wav->f, 4, SEEK_SET)) {
 187        dolog ("wav_fini_out: fseek to rlen failed\nReason: %s\n",
 188               strerror(errno));
 189        goto doclose;
 190    }
 191    if (fwrite (rlen, 4, 1, wav->f) != 1) {
 192        dolog ("wav_fini_out: failed to write rlen\nReason: %s\n",
 193               strerror (errno));
 194        goto doclose;
 195    }
 196    if (fseek (wav->f, 32, SEEK_CUR)) {
 197        dolog ("wav_fini_out: fseek to dlen failed\nReason: %s\n",
 198               strerror (errno));
 199        goto doclose;
 200    }
 201    if (fwrite (dlen, 4, 1, wav->f) != 1) {
 202        dolog ("wav_fini_out: failed to write dlen\nReaons: %s\n",
 203               strerror (errno));
 204        goto doclose;
 205    }
 206
 207 doclose:
 208    if (fclose (wav->f))  {
 209        dolog ("wav_fini_out: fclose %p failed\nReason: %s\n",
 210               wav->f, strerror (errno));
 211    }
 212    wav->f = NULL;
 213
 214    g_free (wav->pcm_buf);
 215    wav->pcm_buf = NULL;
 216}
 217
 218static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...)
 219{
 220    (void) hw;
 221    (void) cmd;
 222    return 0;
 223}
 224
 225static WAVConf glob_conf = {
 226    .settings.freq      = 44100,
 227    .settings.nchannels = 2,
 228    .settings.fmt       = AUD_FMT_S16,
 229    .wav_path           = "qemu.wav"
 230};
 231
 232static void *wav_audio_init (void)
 233{
 234    WAVConf *conf = g_malloc(sizeof(WAVConf));
 235    *conf = glob_conf;
 236    return conf;
 237}
 238
 239static void wav_audio_fini (void *opaque)
 240{
 241    ldebug ("wav_fini");
 242    g_free(opaque);
 243}
 244
 245static struct audio_option wav_options[] = {
 246    {
 247        .name  = "FREQUENCY",
 248        .tag   = AUD_OPT_INT,
 249        .valp  = &glob_conf.settings.freq,
 250        .descr = "Frequency"
 251    },
 252    {
 253        .name  = "FORMAT",
 254        .tag   = AUD_OPT_FMT,
 255        .valp  = &glob_conf.settings.fmt,
 256        .descr = "Format"
 257    },
 258    {
 259        .name  = "DAC_FIXED_CHANNELS",
 260        .tag   = AUD_OPT_INT,
 261        .valp  = &glob_conf.settings.nchannels,
 262        .descr = "Number of channels (1 - mono, 2 - stereo)"
 263    },
 264    {
 265        .name  = "PATH",
 266        .tag   = AUD_OPT_STR,
 267        .valp  = &glob_conf.wav_path,
 268        .descr = "Path to wave file"
 269    },
 270    { /* End of list */ }
 271};
 272
 273static struct audio_pcm_ops wav_pcm_ops = {
 274    .init_out = wav_init_out,
 275    .fini_out = wav_fini_out,
 276    .run_out  = wav_run_out,
 277    .write    = wav_write_out,
 278    .ctl_out  = wav_ctl_out,
 279};
 280
 281struct audio_driver wav_audio_driver = {
 282    .name           = "wav",
 283    .descr          = "WAV renderer http://wikipedia.org/wiki/WAV",
 284    .options        = wav_options,
 285    .init           = wav_audio_init,
 286    .fini           = wav_audio_fini,
 287    .pcm_ops        = &wav_pcm_ops,
 288    .can_be_default = 0,
 289    .max_voices_out = 1,
 290    .max_voices_in  = 0,
 291    .voice_size_out = sizeof (WAVVoiceOut),
 292    .voice_size_in  = 0
 293};
 294