xref: /qemu/audio/wavaudio.c (revision 1de7afc984b49af164e2619e6850b9732b173b34)
185571bc7Sbellard /*
21d14ffa9Sbellard  * QEMU WAV audio driver
385571bc7Sbellard  *
41d14ffa9Sbellard  * Copyright (c) 2004-2005 Vassili Karpov (malc)
585571bc7Sbellard  *
685571bc7Sbellard  * Permission is hereby granted, free of charge, to any person obtaining a copy
785571bc7Sbellard  * of this software and associated documentation files (the "Software"), to deal
885571bc7Sbellard  * in the Software without restriction, including without limitation the rights
985571bc7Sbellard  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1085571bc7Sbellard  * copies of the Software, and to permit persons to whom the Software is
1185571bc7Sbellard  * furnished to do so, subject to the following conditions:
1285571bc7Sbellard  *
1385571bc7Sbellard  * The above copyright notice and this permission notice shall be included in
1485571bc7Sbellard  * all copies or substantial portions of the Software.
1585571bc7Sbellard  *
1685571bc7Sbellard  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1785571bc7Sbellard  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1885571bc7Sbellard  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1985571bc7Sbellard  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2085571bc7Sbellard  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2185571bc7Sbellard  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2285571bc7Sbellard  * THE SOFTWARE.
2385571bc7Sbellard  */
2487ecb68bSpbrook #include "hw/hw.h"
25*1de7afc9SPaolo Bonzini #include "qemu/timer.h"
2687ecb68bSpbrook #include "audio.h"
2785571bc7Sbellard 
281d14ffa9Sbellard #define AUDIO_CAP "wav"
291d14ffa9Sbellard #include "audio_int.h"
30fb065187Sbellard 
311d14ffa9Sbellard typedef struct WAVVoiceOut {
321d14ffa9Sbellard     HWVoiceOut hw;
3327acf660SJuan Quintela     FILE *f;
34fb065187Sbellard     int64_t old_ticks;
35fb065187Sbellard     void *pcm_buf;
36fb065187Sbellard     int total_samples;
371d14ffa9Sbellard } WAVVoiceOut;
3885571bc7Sbellard 
3985571bc7Sbellard static struct {
401ea879e5Smalc     struct audsettings settings;
4185571bc7Sbellard     const char *wav_path;
4285571bc7Sbellard } conf = {
431a40d5e2SJuan Quintela     .settings.freq      = 44100,
441a40d5e2SJuan Quintela     .settings.nchannels = 2,
451a40d5e2SJuan Quintela     .settings.fmt       = AUD_FMT_S16,
461a40d5e2SJuan Quintela     .wav_path           = "qemu.wav"
4785571bc7Sbellard };
4885571bc7Sbellard 
49bdff253cSmalc static int wav_run_out (HWVoiceOut *hw, int live)
5085571bc7Sbellard {
511d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
52bdff253cSmalc     int rpos, decr, samples;
5385571bc7Sbellard     uint8_t *dst;
541ea879e5Smalc     struct st_sample *src;
5574475455SPaolo Bonzini     int64_t now = qemu_get_clock_ns (vm_clock);
5685571bc7Sbellard     int64_t ticks = now - wav->old_ticks;
574f4cc0efSmalc     int64_t bytes =
584f4cc0efSmalc         muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
5985571bc7Sbellard 
601d14ffa9Sbellard     if (bytes > INT_MAX) {
611d14ffa9Sbellard         samples = INT_MAX >> hw->info.shift;
621d14ffa9Sbellard     }
631d14ffa9Sbellard     else {
641d14ffa9Sbellard         samples = bytes >> hw->info.shift;
651d14ffa9Sbellard     }
6685571bc7Sbellard 
677372f88dSbellard     wav->old_ticks = now;
6885571bc7Sbellard     decr = audio_MIN (live, samples);
6985571bc7Sbellard     samples = decr;
7085571bc7Sbellard     rpos = hw->rpos;
7185571bc7Sbellard     while (samples) {
7285571bc7Sbellard         int left_till_end_samples = hw->samples - rpos;
7385571bc7Sbellard         int convert_samples = audio_MIN (samples, left_till_end_samples);
7485571bc7Sbellard 
751d14ffa9Sbellard         src = hw->mix_buf + rpos;
761d14ffa9Sbellard         dst = advance (wav->pcm_buf, rpos << hw->info.shift);
7785571bc7Sbellard 
7885571bc7Sbellard         hw->clip (dst, src, convert_samples);
7927acf660SJuan Quintela         if (fwrite (dst, convert_samples << hw->info.shift, 1, wav->f) != 1) {
8027acf660SJuan Quintela             dolog ("wav_run_out: fwrite of %d bytes failed\nReaons: %s\n",
8127acf660SJuan Quintela                    convert_samples << hw->info.shift, strerror (errno));
8227acf660SJuan Quintela         }
8385571bc7Sbellard 
8485571bc7Sbellard         rpos = (rpos + convert_samples) % hw->samples;
8585571bc7Sbellard         samples -= convert_samples;
8685571bc7Sbellard         wav->total_samples += convert_samples;
8785571bc7Sbellard     }
8885571bc7Sbellard 
8985571bc7Sbellard     hw->rpos = rpos;
901d14ffa9Sbellard     return decr;
9185571bc7Sbellard }
9285571bc7Sbellard 
931d14ffa9Sbellard static int wav_write_out (SWVoiceOut *sw, void *buf, int len)
9485571bc7Sbellard {
951d14ffa9Sbellard     return audio_pcm_sw_write (sw, buf, len);
9685571bc7Sbellard }
9785571bc7Sbellard 
9885571bc7Sbellard /* VICE code: Store number as little endian. */
9985571bc7Sbellard static void le_store (uint8_t *buf, uint32_t val, int len)
10085571bc7Sbellard {
10185571bc7Sbellard     int i;
10285571bc7Sbellard     for (i = 0; i < len; i++) {
10385571bc7Sbellard         buf[i] = (uint8_t) (val & 0xff);
10485571bc7Sbellard         val >>= 8;
10585571bc7Sbellard     }
10685571bc7Sbellard }
10785571bc7Sbellard 
1081ea879e5Smalc static int wav_init_out (HWVoiceOut *hw, struct audsettings *as)
10985571bc7Sbellard {
1101d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
111c0fe3827Sbellard     int bits16 = 0, stereo = 0;
11285571bc7Sbellard     uint8_t hdr[] = {
11385571bc7Sbellard         0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56,
11485571bc7Sbellard         0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00,
11585571bc7Sbellard         0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04,
11685571bc7Sbellard         0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00
11785571bc7Sbellard     };
1181ea879e5Smalc     struct audsettings wav_as = conf.settings;
11985571bc7Sbellard 
120c0fe3827Sbellard     (void) as;
1211d14ffa9Sbellard 
122c0fe3827Sbellard     stereo = wav_as.nchannels == 2;
123c0fe3827Sbellard     switch (wav_as.fmt) {
12485571bc7Sbellard     case AUD_FMT_S8:
12585571bc7Sbellard     case AUD_FMT_U8:
1261d14ffa9Sbellard         bits16 = 0;
12785571bc7Sbellard         break;
12885571bc7Sbellard 
12985571bc7Sbellard     case AUD_FMT_S16:
13085571bc7Sbellard     case AUD_FMT_U16:
13185571bc7Sbellard         bits16 = 1;
13285571bc7Sbellard         break;
133f941aa25Sths 
134f941aa25Sths     case AUD_FMT_S32:
135f941aa25Sths     case AUD_FMT_U32:
136f941aa25Sths         dolog ("WAVE files can not handle 32bit formats\n");
137f941aa25Sths         return -1;
13885571bc7Sbellard     }
13985571bc7Sbellard 
14085571bc7Sbellard     hdr[34] = bits16 ? 0x10 : 0x08;
141c0fe3827Sbellard 
142d929eba5Sbellard     wav_as.endianness = 0;
143d929eba5Sbellard     audio_pcm_init_info (&hw->info, &wav_as);
144c0fe3827Sbellard 
145c0fe3827Sbellard     hw->samples = 1024;
146c0fe3827Sbellard     wav->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
1471d14ffa9Sbellard     if (!wav->pcm_buf) {
148c0fe3827Sbellard         dolog ("Could not allocate buffer (%d bytes)\n",
149c0fe3827Sbellard                hw->samples << hw->info.shift);
15085571bc7Sbellard         return -1;
1511d14ffa9Sbellard     }
15285571bc7Sbellard 
1531d14ffa9Sbellard     le_store (hdr + 22, hw->info.nchannels, 2);
1541d14ffa9Sbellard     le_store (hdr + 24, hw->info.freq, 4);
155c0fe3827Sbellard     le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4);
156c0fe3827Sbellard     le_store (hdr + 32, 1 << (bits16 + stereo), 2);
15785571bc7Sbellard 
15827acf660SJuan Quintela     wav->f = fopen (conf.wav_path, "wb");
15985571bc7Sbellard     if (!wav->f) {
1601d14ffa9Sbellard         dolog ("Failed to open wave file `%s'\nReason: %s\n",
16185571bc7Sbellard                conf.wav_path, strerror (errno));
1627267c094SAnthony Liguori         g_free (wav->pcm_buf);
1637372f88dSbellard         wav->pcm_buf = NULL;
16485571bc7Sbellard         return -1;
16585571bc7Sbellard     }
16685571bc7Sbellard 
16727acf660SJuan Quintela     if (fwrite (hdr, sizeof (hdr), 1, wav->f) != 1) {
16827acf660SJuan Quintela         dolog ("wav_init_out: failed to write header\nReason: %s\n",
16927acf660SJuan Quintela                strerror(errno));
17027acf660SJuan Quintela         return -1;
17127acf660SJuan Quintela     }
17285571bc7Sbellard     return 0;
17385571bc7Sbellard }
17485571bc7Sbellard 
1751d14ffa9Sbellard static void wav_fini_out (HWVoiceOut *hw)
17685571bc7Sbellard {
1771d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
17885571bc7Sbellard     uint8_t rlen[4];
17985571bc7Sbellard     uint8_t dlen[4];
18050903530Sbellard     uint32_t datalen = wav->total_samples << hw->info.shift;
18150903530Sbellard     uint32_t rifflen = datalen + 36;
18285571bc7Sbellard 
183c0fe3827Sbellard     if (!wav->f) {
18485571bc7Sbellard         return;
1851d14ffa9Sbellard     }
18685571bc7Sbellard 
18785571bc7Sbellard     le_store (rlen, rifflen, 4);
18885571bc7Sbellard     le_store (dlen, datalen, 4);
18985571bc7Sbellard 
19027acf660SJuan Quintela     if (fseek (wav->f, 4, SEEK_SET)) {
19127acf660SJuan Quintela         dolog ("wav_fini_out: fseek to rlen failed\nReason: %s\n",
19227acf660SJuan Quintela                strerror(errno));
19327acf660SJuan Quintela         goto doclose;
19427acf660SJuan Quintela     }
19527acf660SJuan Quintela     if (fwrite (rlen, 4, 1, wav->f) != 1) {
19627acf660SJuan Quintela         dolog ("wav_fini_out: failed to write rlen\nReason: %s\n",
19727acf660SJuan Quintela                strerror (errno));
19827acf660SJuan Quintela         goto doclose;
19927acf660SJuan Quintela     }
20027acf660SJuan Quintela     if (fseek (wav->f, 32, SEEK_CUR)) {
20127acf660SJuan Quintela         dolog ("wav_fini_out: fseek to dlen failed\nReason: %s\n",
20227acf660SJuan Quintela                strerror (errno));
20327acf660SJuan Quintela         goto doclose;
20427acf660SJuan Quintela     }
20527acf660SJuan Quintela     if (fwrite (dlen, 4, 1, wav->f) != 1) {
20627acf660SJuan Quintela         dolog ("wav_fini_out: failed to write dlen\nReaons: %s\n",
20727acf660SJuan Quintela                strerror (errno));
20827acf660SJuan Quintela         goto doclose;
20927acf660SJuan Quintela     }
21085571bc7Sbellard 
21127acf660SJuan Quintela  doclose:
21227acf660SJuan Quintela     if (fclose (wav->f))  {
21327acf660SJuan Quintela         dolog ("wav_fini_out: fclose %p failed\nReason: %s\n",
21427acf660SJuan Quintela                wav->f, strerror (errno));
21527acf660SJuan Quintela     }
21685571bc7Sbellard     wav->f = NULL;
2177372f88dSbellard 
2187267c094SAnthony Liguori     g_free (wav->pcm_buf);
2197372f88dSbellard     wav->pcm_buf = NULL;
22085571bc7Sbellard }
22185571bc7Sbellard 
2221d14ffa9Sbellard static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...)
22385571bc7Sbellard {
22485571bc7Sbellard     (void) hw;
22585571bc7Sbellard     (void) cmd;
22685571bc7Sbellard     return 0;
22785571bc7Sbellard }
22885571bc7Sbellard 
22985571bc7Sbellard static void *wav_audio_init (void)
23085571bc7Sbellard {
23185571bc7Sbellard     return &conf;
23285571bc7Sbellard }
23385571bc7Sbellard 
23485571bc7Sbellard static void wav_audio_fini (void *opaque)
23585571bc7Sbellard {
2361d14ffa9Sbellard     (void) opaque;
23785571bc7Sbellard     ldebug ("wav_fini");
23885571bc7Sbellard }
23985571bc7Sbellard 
2408869defeSblueswir1 static struct audio_option wav_options[] = {
24198f9f48cSmalc     {
24298f9f48cSmalc         .name  = "FREQUENCY",
2432700efa3SJuan Quintela         .tag   = AUD_OPT_INT,
2442700efa3SJuan Quintela         .valp  = &conf.settings.freq,
24598f9f48cSmalc         .descr = "Frequency"
24698f9f48cSmalc     },
24798f9f48cSmalc     {
24898f9f48cSmalc         .name  = "FORMAT",
2492700efa3SJuan Quintela         .tag   = AUD_OPT_FMT,
2502700efa3SJuan Quintela         .valp  = &conf.settings.fmt,
25198f9f48cSmalc         .descr = "Format"
25298f9f48cSmalc     },
25398f9f48cSmalc     {
25498f9f48cSmalc         .name  = "DAC_FIXED_CHANNELS",
2552700efa3SJuan Quintela         .tag   = AUD_OPT_INT,
2562700efa3SJuan Quintela         .valp  = &conf.settings.nchannels,
25798f9f48cSmalc         .descr = "Number of channels (1 - mono, 2 - stereo)"
25898f9f48cSmalc     },
25998f9f48cSmalc     {
26098f9f48cSmalc         .name  = "PATH",
2612700efa3SJuan Quintela         .tag   = AUD_OPT_STR,
2622700efa3SJuan Quintela         .valp  = &conf.wav_path,
26398f9f48cSmalc         .descr = "Path to wave file"
26498f9f48cSmalc     },
2652700efa3SJuan Quintela     { /* End of list */ }
26685571bc7Sbellard };
26785571bc7Sbellard 
26835f4b58cSblueswir1 static struct audio_pcm_ops wav_pcm_ops = {
2691dd3e4d1SJuan Quintela     .init_out = wav_init_out,
2701dd3e4d1SJuan Quintela     .fini_out = wav_fini_out,
2711dd3e4d1SJuan Quintela     .run_out  = wav_run_out,
2721dd3e4d1SJuan Quintela     .write    = wav_write_out,
2731dd3e4d1SJuan Quintela     .ctl_out  = wav_ctl_out,
2741d14ffa9Sbellard };
2751d14ffa9Sbellard 
2761d14ffa9Sbellard struct audio_driver wav_audio_driver = {
277bee37f32SJuan Quintela     .name           = "wav",
278bee37f32SJuan Quintela     .descr          = "WAV renderer http://wikipedia.org/wiki/WAV",
279bee37f32SJuan Quintela     .options        = wav_options,
280bee37f32SJuan Quintela     .init           = wav_audio_init,
281bee37f32SJuan Quintela     .fini           = wav_audio_fini,
282bee37f32SJuan Quintela     .pcm_ops        = &wav_pcm_ops,
283bee37f32SJuan Quintela     .can_be_default = 0,
284bee37f32SJuan Quintela     .max_voices_out = 1,
285bee37f32SJuan Quintela     .max_voices_in  = 0,
286bee37f32SJuan Quintela     .voice_size_out = sizeof (WAVVoiceOut),
287bee37f32SJuan Quintela     .voice_size_in  = 0
28885571bc7Sbellard };
289