xref: /qemu/audio/wavaudio.c (revision 509035303d2dc77e96c857bedf3de472440ef76b)
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  */
2485571bc7Sbellard #include "vl.h"
2585571bc7Sbellard 
261d14ffa9Sbellard #define AUDIO_CAP "wav"
271d14ffa9Sbellard #include "audio_int.h"
28fb065187Sbellard 
291d14ffa9Sbellard typedef struct WAVVoiceOut {
301d14ffa9Sbellard     HWVoiceOut hw;
31fb065187Sbellard     QEMUFile *f;
32fb065187Sbellard     int64_t old_ticks;
33fb065187Sbellard     void *pcm_buf;
34fb065187Sbellard     int total_samples;
351d14ffa9Sbellard } WAVVoiceOut;
3685571bc7Sbellard 
3785571bc7Sbellard static struct {
38c0fe3827Sbellard     audsettings_t settings;
3985571bc7Sbellard     const char *wav_path;
4085571bc7Sbellard } conf = {
41c0fe3827Sbellard     {
42c0fe3827Sbellard         44100,
43c0fe3827Sbellard         2,
44c0fe3827Sbellard         AUD_FMT_S16
45c0fe3827Sbellard     },
46c0fe3827Sbellard     "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;
5485571bc7Sbellard     st_sample_t *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);
841d14ffa9Sbellard         mixeng_clear (src, convert_samples);
8585571bc7Sbellard 
8685571bc7Sbellard         rpos = (rpos + convert_samples) % hw->samples;
8785571bc7Sbellard         samples -= convert_samples;
8885571bc7Sbellard         wav->total_samples += convert_samples;
8985571bc7Sbellard     }
9085571bc7Sbellard 
9185571bc7Sbellard     hw->rpos = rpos;
921d14ffa9Sbellard     return decr;
9385571bc7Sbellard }
9485571bc7Sbellard 
951d14ffa9Sbellard static int wav_write_out (SWVoiceOut *sw, void *buf, int len)
9685571bc7Sbellard {
971d14ffa9Sbellard     return audio_pcm_sw_write (sw, buf, len);
9885571bc7Sbellard }
9985571bc7Sbellard 
10085571bc7Sbellard /* VICE code: Store number as little endian. */
10185571bc7Sbellard static void le_store (uint8_t *buf, uint32_t val, int len)
10285571bc7Sbellard {
10385571bc7Sbellard     int i;
10485571bc7Sbellard     for (i = 0; i < len; i++) {
10585571bc7Sbellard         buf[i] = (uint8_t) (val & 0xff);
10685571bc7Sbellard         val >>= 8;
10785571bc7Sbellard     }
10885571bc7Sbellard }
10985571bc7Sbellard 
110c0fe3827Sbellard static int wav_init_out (HWVoiceOut *hw, audsettings_t *as)
11185571bc7Sbellard {
1121d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
113c0fe3827Sbellard     int bits16 = 0, stereo = 0;
11485571bc7Sbellard     uint8_t hdr[] = {
11585571bc7Sbellard         0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56,
11685571bc7Sbellard         0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00,
11785571bc7Sbellard         0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04,
11885571bc7Sbellard         0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00
11985571bc7Sbellard     };
120c0fe3827Sbellard     audsettings_t wav_as = conf.settings;
12185571bc7Sbellard 
122c0fe3827Sbellard     (void) as;
1231d14ffa9Sbellard 
124c0fe3827Sbellard     stereo = wav_as.nchannels == 2;
125c0fe3827Sbellard     switch (wav_as.fmt) {
12685571bc7Sbellard     case AUD_FMT_S8:
12785571bc7Sbellard     case AUD_FMT_U8:
1281d14ffa9Sbellard         bits16 = 0;
12985571bc7Sbellard         break;
13085571bc7Sbellard 
13185571bc7Sbellard     case AUD_FMT_S16:
13285571bc7Sbellard     case AUD_FMT_U16:
13385571bc7Sbellard         bits16 = 1;
13485571bc7Sbellard         break;
13585571bc7Sbellard     }
13685571bc7Sbellard 
13785571bc7Sbellard     hdr[34] = bits16 ? 0x10 : 0x08;
138c0fe3827Sbellard 
139c0fe3827Sbellard     audio_pcm_init_info (&hw->info, &wav_as, audio_need_to_swap_endian (0));
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 
15485571bc7Sbellard     wav->f = fopen (conf.wav_path, "wb");
15585571bc7Sbellard     if (!wav->f) {
1561d14ffa9Sbellard         dolog ("Failed to open wave file `%s'\nReason: %s\n",
15785571bc7Sbellard                conf.wav_path, strerror (errno));
1587372f88dSbellard         qemu_free (wav->pcm_buf);
1597372f88dSbellard         wav->pcm_buf = NULL;
16085571bc7Sbellard         return -1;
16185571bc7Sbellard     }
16285571bc7Sbellard 
16385571bc7Sbellard     qemu_put_buffer (wav->f, hdr, sizeof (hdr));
16485571bc7Sbellard     return 0;
16585571bc7Sbellard }
16685571bc7Sbellard 
1671d14ffa9Sbellard static void wav_fini_out (HWVoiceOut *hw)
16885571bc7Sbellard {
1691d14ffa9Sbellard     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
17085571bc7Sbellard     uint8_t rlen[4];
17185571bc7Sbellard     uint8_t dlen[4];
172*50903530Sbellard     uint32_t datalen = wav->total_samples << hw->info.shift;
173*50903530Sbellard     uint32_t rifflen = datalen + 36;
17485571bc7Sbellard 
175c0fe3827Sbellard     if (!wav->f) {
17685571bc7Sbellard         return;
1771d14ffa9Sbellard     }
17885571bc7Sbellard 
17985571bc7Sbellard     le_store (rlen, rifflen, 4);
18085571bc7Sbellard     le_store (dlen, datalen, 4);
18185571bc7Sbellard 
18285571bc7Sbellard     qemu_fseek (wav->f, 4, SEEK_SET);
18385571bc7Sbellard     qemu_put_buffer (wav->f, rlen, 4);
18485571bc7Sbellard 
18585571bc7Sbellard     qemu_fseek (wav->f, 32, SEEK_CUR);
18685571bc7Sbellard     qemu_put_buffer (wav->f, dlen, 4);
18785571bc7Sbellard 
18885571bc7Sbellard     fclose (wav->f);
18985571bc7Sbellard     wav->f = NULL;
1907372f88dSbellard 
1917372f88dSbellard     qemu_free (wav->pcm_buf);
1927372f88dSbellard     wav->pcm_buf = NULL;
19385571bc7Sbellard }
19485571bc7Sbellard 
1951d14ffa9Sbellard static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...)
19685571bc7Sbellard {
19785571bc7Sbellard     (void) hw;
19885571bc7Sbellard     (void) cmd;
19985571bc7Sbellard     return 0;
20085571bc7Sbellard }
20185571bc7Sbellard 
20285571bc7Sbellard static void *wav_audio_init (void)
20385571bc7Sbellard {
20485571bc7Sbellard     return &conf;
20585571bc7Sbellard }
20685571bc7Sbellard 
20785571bc7Sbellard static void wav_audio_fini (void *opaque)
20885571bc7Sbellard {
2091d14ffa9Sbellard     (void) opaque;
21085571bc7Sbellard     ldebug ("wav_fini");
21185571bc7Sbellard }
21285571bc7Sbellard 
2131d14ffa9Sbellard struct audio_option wav_options[] = {
214c0fe3827Sbellard     {"FREQUENCY", AUD_OPT_INT, &conf.settings.freq,
215c0fe3827Sbellard      "Frequency", NULL, 0},
216c0fe3827Sbellard 
217c0fe3827Sbellard     {"FORMAT", AUD_OPT_FMT, &conf.settings.fmt,
218c0fe3827Sbellard      "Format", NULL, 0},
219c0fe3827Sbellard 
220c0fe3827Sbellard     {"DAC_FIXED_CHANNELS", AUD_OPT_INT, &conf.settings.nchannels,
221c0fe3827Sbellard      "Number of channels (1 - mono, 2 - stereo)", NULL, 0},
222c0fe3827Sbellard 
2231d14ffa9Sbellard     {"PATH", AUD_OPT_STR, &conf.wav_path,
2241d14ffa9Sbellard      "Path to wave file", NULL, 0},
2251d14ffa9Sbellard     {NULL, 0, NULL, NULL, NULL, 0}
22685571bc7Sbellard };
22785571bc7Sbellard 
2281d14ffa9Sbellard struct audio_pcm_ops wav_pcm_ops = {
2291d14ffa9Sbellard     wav_init_out,
2301d14ffa9Sbellard     wav_fini_out,
2311d14ffa9Sbellard     wav_run_out,
2321d14ffa9Sbellard     wav_write_out,
2331d14ffa9Sbellard     wav_ctl_out,
2341d14ffa9Sbellard 
2351d14ffa9Sbellard     NULL,
2361d14ffa9Sbellard     NULL,
2371d14ffa9Sbellard     NULL,
2381d14ffa9Sbellard     NULL,
2391d14ffa9Sbellard     NULL
2401d14ffa9Sbellard };
2411d14ffa9Sbellard 
2421d14ffa9Sbellard struct audio_driver wav_audio_driver = {
2431d14ffa9Sbellard     INIT_FIELD (name           = ) "wav",
2441d14ffa9Sbellard     INIT_FIELD (descr          = )
2451d14ffa9Sbellard     "WAV renderer http://wikipedia.org/wiki/WAV",
2461d14ffa9Sbellard     INIT_FIELD (options        = ) wav_options,
2471d14ffa9Sbellard     INIT_FIELD (init           = ) wav_audio_init,
2481d14ffa9Sbellard     INIT_FIELD (fini           = ) wav_audio_fini,
2491d14ffa9Sbellard     INIT_FIELD (pcm_ops        = ) &wav_pcm_ops,
2501d14ffa9Sbellard     INIT_FIELD (can_be_default = ) 0,
2511d14ffa9Sbellard     INIT_FIELD (max_voices_out = ) 1,
2521d14ffa9Sbellard     INIT_FIELD (max_voices_in  = ) 0,
2531d14ffa9Sbellard     INIT_FIELD (voice_size_out = ) sizeof (WAVVoiceOut),
2541d14ffa9Sbellard     INIT_FIELD (voice_size_in  = ) 0
25585571bc7Sbellard };
256