xref: /qemu/audio/wavaudio.c (revision 98f9f48ccb1276d2fc3bcf4592cc1febea796e4b)
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"
2587ecb68bSpbrook #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;
33fb065187Sbellard     QEMUFile *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 
491d14ffa9Sbellard static int wav_run_out (HWVoiceOut *hw)
5085571bc7Sbellard {
511d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
5285571bc7Sbellard     int rpos, live, decr, samples;
5385571bc7Sbellard     uint8_t *dst;
541ea879e5Smalc     struct st_sample *src;
5585571bc7Sbellard     int64_t now = qemu_get_clock (vm_clock);
5685571bc7Sbellard     int64_t ticks = now - wav->old_ticks;
571d14ffa9Sbellard     int64_t bytes = (ticks * hw->info.bytes_per_second) / ticks_per_sec;
5885571bc7Sbellard 
591d14ffa9Sbellard     if (bytes > INT_MAX) {
601d14ffa9Sbellard         samples = INT_MAX >> hw->info.shift;
611d14ffa9Sbellard     }
621d14ffa9Sbellard     else {
631d14ffa9Sbellard         samples = bytes >> hw->info.shift;
641d14ffa9Sbellard     }
6585571bc7Sbellard 
661d14ffa9Sbellard     live = audio_pcm_hw_get_live_out (hw);
671d14ffa9Sbellard     if (!live) {
681d14ffa9Sbellard         return 0;
691d14ffa9Sbellard     }
7085571bc7Sbellard 
717372f88dSbellard     wav->old_ticks = now;
7285571bc7Sbellard     decr = audio_MIN (live, samples);
7385571bc7Sbellard     samples = decr;
7485571bc7Sbellard     rpos = hw->rpos;
7585571bc7Sbellard     while (samples) {
7685571bc7Sbellard         int left_till_end_samples = hw->samples - rpos;
7785571bc7Sbellard         int convert_samples = audio_MIN (samples, left_till_end_samples);
7885571bc7Sbellard 
791d14ffa9Sbellard         src = hw->mix_buf + rpos;
801d14ffa9Sbellard         dst = advance (wav->pcm_buf, rpos << hw->info.shift);
8185571bc7Sbellard 
8285571bc7Sbellard         hw->clip (dst, src, convert_samples);
831d14ffa9Sbellard         qemu_put_buffer (wav->f, dst, convert_samples << hw->info.shift);
8485571bc7Sbellard 
8585571bc7Sbellard         rpos = (rpos + convert_samples) % hw->samples;
8685571bc7Sbellard         samples -= convert_samples;
8785571bc7Sbellard         wav->total_samples += convert_samples;
8885571bc7Sbellard     }
8985571bc7Sbellard 
9085571bc7Sbellard     hw->rpos = rpos;
911d14ffa9Sbellard     return decr;
9285571bc7Sbellard }
9385571bc7Sbellard 
941d14ffa9Sbellard static int wav_write_out (SWVoiceOut *sw, void *buf, int len)
9585571bc7Sbellard {
961d14ffa9Sbellard     return audio_pcm_sw_write (sw, buf, len);
9785571bc7Sbellard }
9885571bc7Sbellard 
9985571bc7Sbellard /* VICE code: Store number as little endian. */
10085571bc7Sbellard static void le_store (uint8_t *buf, uint32_t val, int len)
10185571bc7Sbellard {
10285571bc7Sbellard     int i;
10385571bc7Sbellard     for (i = 0; i < len; i++) {
10485571bc7Sbellard         buf[i] = (uint8_t) (val & 0xff);
10585571bc7Sbellard         val >>= 8;
10685571bc7Sbellard     }
10785571bc7Sbellard }
10885571bc7Sbellard 
1091ea879e5Smalc static int wav_init_out (HWVoiceOut *hw, struct audsettings *as)
11085571bc7Sbellard {
1111d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
112c0fe3827Sbellard     int bits16 = 0, stereo = 0;
11385571bc7Sbellard     uint8_t hdr[] = {
11485571bc7Sbellard         0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56,
11585571bc7Sbellard         0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00,
11685571bc7Sbellard         0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04,
11785571bc7Sbellard         0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00
11885571bc7Sbellard     };
1191ea879e5Smalc     struct audsettings wav_as = conf.settings;
12085571bc7Sbellard 
121c0fe3827Sbellard     (void) as;
1221d14ffa9Sbellard 
123c0fe3827Sbellard     stereo = wav_as.nchannels == 2;
124c0fe3827Sbellard     switch (wav_as.fmt) {
12585571bc7Sbellard     case AUD_FMT_S8:
12685571bc7Sbellard     case AUD_FMT_U8:
1271d14ffa9Sbellard         bits16 = 0;
12885571bc7Sbellard         break;
12985571bc7Sbellard 
13085571bc7Sbellard     case AUD_FMT_S16:
13185571bc7Sbellard     case AUD_FMT_U16:
13285571bc7Sbellard         bits16 = 1;
13385571bc7Sbellard         break;
134f941aa25Sths 
135f941aa25Sths     case AUD_FMT_S32:
136f941aa25Sths     case AUD_FMT_U32:
137f941aa25Sths         dolog ("WAVE files can not handle 32bit formats\n");
138f941aa25Sths         return -1;
13985571bc7Sbellard     }
14085571bc7Sbellard 
14185571bc7Sbellard     hdr[34] = bits16 ? 0x10 : 0x08;
142c0fe3827Sbellard 
143d929eba5Sbellard     wav_as.endianness = 0;
144d929eba5Sbellard     audio_pcm_init_info (&hw->info, &wav_as);
145c0fe3827Sbellard 
146c0fe3827Sbellard     hw->samples = 1024;
147c0fe3827Sbellard     wav->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
1481d14ffa9Sbellard     if (!wav->pcm_buf) {
149c0fe3827Sbellard         dolog ("Could not allocate buffer (%d bytes)\n",
150c0fe3827Sbellard                hw->samples << hw->info.shift);
15185571bc7Sbellard         return -1;
1521d14ffa9Sbellard     }
15385571bc7Sbellard 
1541d14ffa9Sbellard     le_store (hdr + 22, hw->info.nchannels, 2);
1551d14ffa9Sbellard     le_store (hdr + 24, hw->info.freq, 4);
156c0fe3827Sbellard     le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4);
157c0fe3827Sbellard     le_store (hdr + 32, 1 << (bits16 + stereo), 2);
15885571bc7Sbellard 
159e70332b3Sbellard     wav->f = qemu_fopen (conf.wav_path, "wb");
16085571bc7Sbellard     if (!wav->f) {
1611d14ffa9Sbellard         dolog ("Failed to open wave file `%s'\nReason: %s\n",
16285571bc7Sbellard                conf.wav_path, strerror (errno));
1637372f88dSbellard         qemu_free (wav->pcm_buf);
1647372f88dSbellard         wav->pcm_buf = NULL;
16585571bc7Sbellard         return -1;
16685571bc7Sbellard     }
16785571bc7Sbellard 
16885571bc7Sbellard     qemu_put_buffer (wav->f, hdr, sizeof (hdr));
16985571bc7Sbellard     return 0;
17085571bc7Sbellard }
17185571bc7Sbellard 
1721d14ffa9Sbellard static void wav_fini_out (HWVoiceOut *hw)
17385571bc7Sbellard {
1741d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
17585571bc7Sbellard     uint8_t rlen[4];
17685571bc7Sbellard     uint8_t dlen[4];
17750903530Sbellard     uint32_t datalen = wav->total_samples << hw->info.shift;
17850903530Sbellard     uint32_t rifflen = datalen + 36;
17985571bc7Sbellard 
180c0fe3827Sbellard     if (!wav->f) {
18185571bc7Sbellard         return;
1821d14ffa9Sbellard     }
18385571bc7Sbellard 
18485571bc7Sbellard     le_store (rlen, rifflen, 4);
18585571bc7Sbellard     le_store (dlen, datalen, 4);
18685571bc7Sbellard 
18785571bc7Sbellard     qemu_fseek (wav->f, 4, SEEK_SET);
18885571bc7Sbellard     qemu_put_buffer (wav->f, rlen, 4);
18985571bc7Sbellard 
19085571bc7Sbellard     qemu_fseek (wav->f, 32, SEEK_CUR);
19185571bc7Sbellard     qemu_put_buffer (wav->f, dlen, 4);
19285571bc7Sbellard 
193e70332b3Sbellard     qemu_fclose (wav->f);
19485571bc7Sbellard     wav->f = NULL;
1957372f88dSbellard 
1967372f88dSbellard     qemu_free (wav->pcm_buf);
1977372f88dSbellard     wav->pcm_buf = NULL;
19885571bc7Sbellard }
19985571bc7Sbellard 
2001d14ffa9Sbellard static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...)
20185571bc7Sbellard {
20285571bc7Sbellard     (void) hw;
20385571bc7Sbellard     (void) cmd;
20485571bc7Sbellard     return 0;
20585571bc7Sbellard }
20685571bc7Sbellard 
20785571bc7Sbellard static void *wav_audio_init (void)
20885571bc7Sbellard {
20985571bc7Sbellard     return &conf;
21085571bc7Sbellard }
21185571bc7Sbellard 
21285571bc7Sbellard static void wav_audio_fini (void *opaque)
21385571bc7Sbellard {
2141d14ffa9Sbellard     (void) opaque;
21585571bc7Sbellard     ldebug ("wav_fini");
21685571bc7Sbellard }
21785571bc7Sbellard 
2188869defeSblueswir1 static struct audio_option wav_options[] = {
219*98f9f48cSmalc     {
220*98f9f48cSmalc         .name  = "FREQUENCY",
2212700efa3SJuan Quintela         .tag   = AUD_OPT_INT,
2222700efa3SJuan Quintela         .valp  = &conf.settings.freq,
223*98f9f48cSmalc         .descr = "Frequency"
224*98f9f48cSmalc     },
225*98f9f48cSmalc     {
226*98f9f48cSmalc         .name  = "FORMAT",
2272700efa3SJuan Quintela         .tag   = AUD_OPT_FMT,
2282700efa3SJuan Quintela         .valp  = &conf.settings.fmt,
229*98f9f48cSmalc         .descr = "Format"
230*98f9f48cSmalc     },
231*98f9f48cSmalc     {
232*98f9f48cSmalc         .name  = "DAC_FIXED_CHANNELS",
2332700efa3SJuan Quintela         .tag   = AUD_OPT_INT,
2342700efa3SJuan Quintela         .valp  = &conf.settings.nchannels,
235*98f9f48cSmalc         .descr = "Number of channels (1 - mono, 2 - stereo)"
236*98f9f48cSmalc     },
237*98f9f48cSmalc     {
238*98f9f48cSmalc         .name  = "PATH",
2392700efa3SJuan Quintela         .tag   = AUD_OPT_STR,
2402700efa3SJuan Quintela         .valp  = &conf.wav_path,
241*98f9f48cSmalc         .descr = "Path to wave file"
242*98f9f48cSmalc     },
2432700efa3SJuan Quintela     { /* End of list */ }
24485571bc7Sbellard };
24585571bc7Sbellard 
24635f4b58cSblueswir1 static struct audio_pcm_ops wav_pcm_ops = {
2471dd3e4d1SJuan Quintela     .init_out = wav_init_out,
2481dd3e4d1SJuan Quintela     .fini_out = wav_fini_out,
2491dd3e4d1SJuan Quintela     .run_out  = wav_run_out,
2501dd3e4d1SJuan Quintela     .write    = wav_write_out,
2511dd3e4d1SJuan Quintela     .ctl_out  = wav_ctl_out,
2521d14ffa9Sbellard };
2531d14ffa9Sbellard 
2541d14ffa9Sbellard struct audio_driver wav_audio_driver = {
255bee37f32SJuan Quintela     .name           = "wav",
256bee37f32SJuan Quintela     .descr          = "WAV renderer http://wikipedia.org/wiki/WAV",
257bee37f32SJuan Quintela     .options        = wav_options,
258bee37f32SJuan Quintela     .init           = wav_audio_init,
259bee37f32SJuan Quintela     .fini           = wav_audio_fini,
260bee37f32SJuan Quintela     .pcm_ops        = &wav_pcm_ops,
261bee37f32SJuan Quintela     .can_be_default = 0,
262bee37f32SJuan Quintela     .max_voices_out = 1,
263bee37f32SJuan Quintela     .max_voices_in  = 0,
264bee37f32SJuan Quintela     .voice_size_out = sizeof (WAVVoiceOut),
265bee37f32SJuan Quintela     .voice_size_in  = 0
26685571bc7Sbellard };
267