xref: /qemu/audio/wavaudio.c (revision 87776ab72b02e3c99a042ab7a0a378bc457cc069)
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  */
246086a565SPeter Maydell #include "qemu/osdep.h"
25*87776ab7SPaolo Bonzini #include "qemu/host-utils.h"
261de7afc9SPaolo Bonzini #include "qemu/timer.h"
2787ecb68bSpbrook #include "audio.h"
2885571bc7Sbellard 
291d14ffa9Sbellard #define AUDIO_CAP "wav"
301d14ffa9Sbellard #include "audio_int.h"
31fb065187Sbellard 
321d14ffa9Sbellard typedef struct WAVVoiceOut {
331d14ffa9Sbellard     HWVoiceOut hw;
3427acf660SJuan Quintela     FILE *f;
35fb065187Sbellard     int64_t old_ticks;
36fb065187Sbellard     void *pcm_buf;
37fb065187Sbellard     int total_samples;
381d14ffa9Sbellard } WAVVoiceOut;
3985571bc7Sbellard 
40f2dcc6ceSKővágó, Zoltán typedef struct {
411ea879e5Smalc     struct audsettings settings;
4285571bc7Sbellard     const char *wav_path;
43f2dcc6ceSKővágó, Zoltán } WAVConf;
4485571bc7Sbellard 
45bdff253cSmalc static int wav_run_out (HWVoiceOut *hw, int live)
4685571bc7Sbellard {
471d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
48bdff253cSmalc     int rpos, decr, samples;
4985571bc7Sbellard     uint8_t *dst;
501ea879e5Smalc     struct st_sample *src;
51bc72ad67SAlex Bligh     int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
5285571bc7Sbellard     int64_t ticks = now - wav->old_ticks;
534f4cc0efSmalc     int64_t bytes =
5473bcb24dSRutuja Shah         muldiv64(ticks, hw->info.bytes_per_second, NANOSECONDS_PER_SECOND);
5585571bc7Sbellard 
561d14ffa9Sbellard     if (bytes > INT_MAX) {
571d14ffa9Sbellard         samples = INT_MAX >> hw->info.shift;
581d14ffa9Sbellard     }
591d14ffa9Sbellard     else {
601d14ffa9Sbellard         samples = bytes >> hw->info.shift;
611d14ffa9Sbellard     }
6285571bc7Sbellard 
637372f88dSbellard     wav->old_ticks = now;
6485571bc7Sbellard     decr = audio_MIN (live, samples);
6585571bc7Sbellard     samples = decr;
6685571bc7Sbellard     rpos = hw->rpos;
6785571bc7Sbellard     while (samples) {
6885571bc7Sbellard         int left_till_end_samples = hw->samples - rpos;
6985571bc7Sbellard         int convert_samples = audio_MIN (samples, left_till_end_samples);
7085571bc7Sbellard 
711d14ffa9Sbellard         src = hw->mix_buf + rpos;
721d14ffa9Sbellard         dst = advance (wav->pcm_buf, rpos << hw->info.shift);
7385571bc7Sbellard 
7485571bc7Sbellard         hw->clip (dst, src, convert_samples);
7527acf660SJuan Quintela         if (fwrite (dst, convert_samples << hw->info.shift, 1, wav->f) != 1) {
7627acf660SJuan Quintela             dolog ("wav_run_out: fwrite of %d bytes failed\nReaons: %s\n",
7727acf660SJuan Quintela                    convert_samples << hw->info.shift, strerror (errno));
7827acf660SJuan Quintela         }
7985571bc7Sbellard 
8085571bc7Sbellard         rpos = (rpos + convert_samples) % hw->samples;
8185571bc7Sbellard         samples -= convert_samples;
8285571bc7Sbellard         wav->total_samples += convert_samples;
8385571bc7Sbellard     }
8485571bc7Sbellard 
8585571bc7Sbellard     hw->rpos = rpos;
861d14ffa9Sbellard     return decr;
8785571bc7Sbellard }
8885571bc7Sbellard 
891d14ffa9Sbellard static int wav_write_out (SWVoiceOut *sw, void *buf, int len)
9085571bc7Sbellard {
911d14ffa9Sbellard     return audio_pcm_sw_write (sw, buf, len);
9285571bc7Sbellard }
9385571bc7Sbellard 
9485571bc7Sbellard /* VICE code: Store number as little endian. */
9585571bc7Sbellard static void le_store (uint8_t *buf, uint32_t val, int len)
9685571bc7Sbellard {
9785571bc7Sbellard     int i;
9885571bc7Sbellard     for (i = 0; i < len; i++) {
9985571bc7Sbellard         buf[i] = (uint8_t) (val & 0xff);
10085571bc7Sbellard         val >>= 8;
10185571bc7Sbellard     }
10285571bc7Sbellard }
10385571bc7Sbellard 
1045706db1dSKővágó, Zoltán static int wav_init_out(HWVoiceOut *hw, struct audsettings *as,
1055706db1dSKővágó, Zoltán                         void *drv_opaque)
10685571bc7Sbellard {
1071d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
108c0fe3827Sbellard     int bits16 = 0, stereo = 0;
10985571bc7Sbellard     uint8_t hdr[] = {
11085571bc7Sbellard         0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56,
11185571bc7Sbellard         0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00,
11285571bc7Sbellard         0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04,
11385571bc7Sbellard         0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00
11485571bc7Sbellard     };
115f2dcc6ceSKővágó, Zoltán     WAVConf *conf = drv_opaque;
116f2dcc6ceSKővágó, Zoltán     struct audsettings wav_as = conf->settings;
11785571bc7Sbellard 
118c0fe3827Sbellard     stereo = wav_as.nchannels == 2;
119c0fe3827Sbellard     switch (wav_as.fmt) {
12085571bc7Sbellard     case AUD_FMT_S8:
12185571bc7Sbellard     case AUD_FMT_U8:
1221d14ffa9Sbellard         bits16 = 0;
12385571bc7Sbellard         break;
12485571bc7Sbellard 
12585571bc7Sbellard     case AUD_FMT_S16:
12685571bc7Sbellard     case AUD_FMT_U16:
12785571bc7Sbellard         bits16 = 1;
12885571bc7Sbellard         break;
129f941aa25Sths 
130f941aa25Sths     case AUD_FMT_S32:
131f941aa25Sths     case AUD_FMT_U32:
132f941aa25Sths         dolog ("WAVE files can not handle 32bit formats\n");
133f941aa25Sths         return -1;
13485571bc7Sbellard     }
13585571bc7Sbellard 
13685571bc7Sbellard     hdr[34] = bits16 ? 0x10 : 0x08;
137c0fe3827Sbellard 
138d929eba5Sbellard     wav_as.endianness = 0;
139d929eba5Sbellard     audio_pcm_init_info (&hw->info, &wav_as);
140c0fe3827Sbellard 
141c0fe3827Sbellard     hw->samples = 1024;
142c0fe3827Sbellard     wav->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
1431d14ffa9Sbellard     if (!wav->pcm_buf) {
144c0fe3827Sbellard         dolog ("Could not allocate buffer (%d bytes)\n",
145c0fe3827Sbellard                hw->samples << hw->info.shift);
14685571bc7Sbellard         return -1;
1471d14ffa9Sbellard     }
14885571bc7Sbellard 
1491d14ffa9Sbellard     le_store (hdr + 22, hw->info.nchannels, 2);
1501d14ffa9Sbellard     le_store (hdr + 24, hw->info.freq, 4);
151c0fe3827Sbellard     le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4);
152c0fe3827Sbellard     le_store (hdr + 32, 1 << (bits16 + stereo), 2);
15385571bc7Sbellard 
154f2dcc6ceSKővágó, Zoltán     wav->f = fopen (conf->wav_path, "wb");
15585571bc7Sbellard     if (!wav->f) {
1561d14ffa9Sbellard         dolog ("Failed to open wave file `%s'\nReason: %s\n",
157f2dcc6ceSKővágó, Zoltán                conf->wav_path, strerror (errno));
1587267c094SAnthony Liguori         g_free (wav->pcm_buf);
1597372f88dSbellard         wav->pcm_buf = NULL;
16085571bc7Sbellard         return -1;
16185571bc7Sbellard     }
16285571bc7Sbellard 
16327acf660SJuan Quintela     if (fwrite (hdr, sizeof (hdr), 1, wav->f) != 1) {
16427acf660SJuan Quintela         dolog ("wav_init_out: failed to write header\nReason: %s\n",
16527acf660SJuan Quintela                strerror(errno));
16627acf660SJuan Quintela         return -1;
16727acf660SJuan Quintela     }
16885571bc7Sbellard     return 0;
16985571bc7Sbellard }
17085571bc7Sbellard 
1711d14ffa9Sbellard static void wav_fini_out (HWVoiceOut *hw)
17285571bc7Sbellard {
1731d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
17485571bc7Sbellard     uint8_t rlen[4];
17585571bc7Sbellard     uint8_t dlen[4];
17650903530Sbellard     uint32_t datalen = wav->total_samples << hw->info.shift;
17750903530Sbellard     uint32_t rifflen = datalen + 36;
17885571bc7Sbellard 
179c0fe3827Sbellard     if (!wav->f) {
18085571bc7Sbellard         return;
1811d14ffa9Sbellard     }
18285571bc7Sbellard 
18385571bc7Sbellard     le_store (rlen, rifflen, 4);
18485571bc7Sbellard     le_store (dlen, datalen, 4);
18585571bc7Sbellard 
18627acf660SJuan Quintela     if (fseek (wav->f, 4, SEEK_SET)) {
18727acf660SJuan Quintela         dolog ("wav_fini_out: fseek to rlen failed\nReason: %s\n",
18827acf660SJuan Quintela                strerror(errno));
18927acf660SJuan Quintela         goto doclose;
19027acf660SJuan Quintela     }
19127acf660SJuan Quintela     if (fwrite (rlen, 4, 1, wav->f) != 1) {
19227acf660SJuan Quintela         dolog ("wav_fini_out: failed to write rlen\nReason: %s\n",
19327acf660SJuan Quintela                strerror (errno));
19427acf660SJuan Quintela         goto doclose;
19527acf660SJuan Quintela     }
19627acf660SJuan Quintela     if (fseek (wav->f, 32, SEEK_CUR)) {
19727acf660SJuan Quintela         dolog ("wav_fini_out: fseek to dlen failed\nReason: %s\n",
19827acf660SJuan Quintela                strerror (errno));
19927acf660SJuan Quintela         goto doclose;
20027acf660SJuan Quintela     }
20127acf660SJuan Quintela     if (fwrite (dlen, 4, 1, wav->f) != 1) {
20227acf660SJuan Quintela         dolog ("wav_fini_out: failed to write dlen\nReaons: %s\n",
20327acf660SJuan Quintela                strerror (errno));
20427acf660SJuan Quintela         goto doclose;
20527acf660SJuan Quintela     }
20685571bc7Sbellard 
20727acf660SJuan Quintela  doclose:
20827acf660SJuan Quintela     if (fclose (wav->f))  {
20927acf660SJuan Quintela         dolog ("wav_fini_out: fclose %p failed\nReason: %s\n",
21027acf660SJuan Quintela                wav->f, strerror (errno));
21127acf660SJuan Quintela     }
21285571bc7Sbellard     wav->f = NULL;
2137372f88dSbellard 
2147267c094SAnthony Liguori     g_free (wav->pcm_buf);
2157372f88dSbellard     wav->pcm_buf = NULL;
21685571bc7Sbellard }
21785571bc7Sbellard 
2181d14ffa9Sbellard static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...)
21985571bc7Sbellard {
22085571bc7Sbellard     (void) hw;
22185571bc7Sbellard     (void) cmd;
22285571bc7Sbellard     return 0;
22385571bc7Sbellard }
22485571bc7Sbellard 
225f2dcc6ceSKővágó, Zoltán static WAVConf glob_conf = {
226f2dcc6ceSKővágó, Zoltán     .settings.freq      = 44100,
227f2dcc6ceSKővágó, Zoltán     .settings.nchannels = 2,
228f2dcc6ceSKővágó, Zoltán     .settings.fmt       = AUD_FMT_S16,
229f2dcc6ceSKővágó, Zoltán     .wav_path           = "qemu.wav"
230f2dcc6ceSKővágó, Zoltán };
231f2dcc6ceSKővágó, Zoltán 
23285571bc7Sbellard static void *wav_audio_init (void)
23385571bc7Sbellard {
234f2dcc6ceSKővágó, Zoltán     WAVConf *conf = g_malloc(sizeof(WAVConf));
235f2dcc6ceSKővágó, Zoltán     *conf = glob_conf;
236f2dcc6ceSKővágó, Zoltán     return conf;
23785571bc7Sbellard }
23885571bc7Sbellard 
23985571bc7Sbellard static void wav_audio_fini (void *opaque)
24085571bc7Sbellard {
24185571bc7Sbellard     ldebug ("wav_fini");
242f2dcc6ceSKővágó, Zoltán     g_free(opaque);
24385571bc7Sbellard }
24485571bc7Sbellard 
2458869defeSblueswir1 static struct audio_option wav_options[] = {
24698f9f48cSmalc     {
24798f9f48cSmalc         .name  = "FREQUENCY",
2482700efa3SJuan Quintela         .tag   = AUD_OPT_INT,
249f2dcc6ceSKővágó, Zoltán         .valp  = &glob_conf.settings.freq,
25098f9f48cSmalc         .descr = "Frequency"
25198f9f48cSmalc     },
25298f9f48cSmalc     {
25398f9f48cSmalc         .name  = "FORMAT",
2542700efa3SJuan Quintela         .tag   = AUD_OPT_FMT,
255f2dcc6ceSKővágó, Zoltán         .valp  = &glob_conf.settings.fmt,
25698f9f48cSmalc         .descr = "Format"
25798f9f48cSmalc     },
25898f9f48cSmalc     {
25998f9f48cSmalc         .name  = "DAC_FIXED_CHANNELS",
2602700efa3SJuan Quintela         .tag   = AUD_OPT_INT,
261f2dcc6ceSKővágó, Zoltán         .valp  = &glob_conf.settings.nchannels,
26298f9f48cSmalc         .descr = "Number of channels (1 - mono, 2 - stereo)"
26398f9f48cSmalc     },
26498f9f48cSmalc     {
26598f9f48cSmalc         .name  = "PATH",
2662700efa3SJuan Quintela         .tag   = AUD_OPT_STR,
267f2dcc6ceSKővágó, Zoltán         .valp  = &glob_conf.wav_path,
26898f9f48cSmalc         .descr = "Path to wave file"
26998f9f48cSmalc     },
2702700efa3SJuan Quintela     { /* End of list */ }
27185571bc7Sbellard };
27285571bc7Sbellard 
27335f4b58cSblueswir1 static struct audio_pcm_ops wav_pcm_ops = {
2741dd3e4d1SJuan Quintela     .init_out = wav_init_out,
2751dd3e4d1SJuan Quintela     .fini_out = wav_fini_out,
2761dd3e4d1SJuan Quintela     .run_out  = wav_run_out,
2771dd3e4d1SJuan Quintela     .write    = wav_write_out,
2781dd3e4d1SJuan Quintela     .ctl_out  = wav_ctl_out,
2791d14ffa9Sbellard };
2801d14ffa9Sbellard 
2811d14ffa9Sbellard struct audio_driver wav_audio_driver = {
282bee37f32SJuan Quintela     .name           = "wav",
283bee37f32SJuan Quintela     .descr          = "WAV renderer http://wikipedia.org/wiki/WAV",
284bee37f32SJuan Quintela     .options        = wav_options,
285bee37f32SJuan Quintela     .init           = wav_audio_init,
286bee37f32SJuan Quintela     .fini           = wav_audio_fini,
287bee37f32SJuan Quintela     .pcm_ops        = &wav_pcm_ops,
288bee37f32SJuan Quintela     .can_be_default = 0,
289bee37f32SJuan Quintela     .max_voices_out = 1,
290bee37f32SJuan Quintela     .max_voices_in  = 0,
291bee37f32SJuan Quintela     .voice_size_out = sizeof (WAVVoiceOut),
292bee37f32SJuan Quintela     .voice_size_in  = 0
29385571bc7Sbellard };
294