xref: /qemu/audio/sdlaudio.c (revision 57dea5533238cda04a07c3b49452f5cb315358b0)
185571bc7Sbellard /*
21d14ffa9Sbellard  * QEMU SDL 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"
259f059ecaSbellard #include <SDL.h>
269f059ecaSbellard #include <SDL_thread.h>
2787ecb68bSpbrook #include "qemu-common.h"
2887ecb68bSpbrook #include "audio.h"
2985571bc7Sbellard 
30e784ba70Sths #ifndef _WIN32
31e784ba70Sths #ifdef __sun__
32e784ba70Sths #define _POSIX_PTHREAD_SEMANTICS 1
33c5e97233Sblueswir1 #elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
349b4c14c3Sblueswir1 #include <pthread.h>
35e784ba70Sths #endif
36e784ba70Sths #endif
37e784ba70Sths 
381d14ffa9Sbellard #define AUDIO_CAP "sdl"
391d14ffa9Sbellard #include "audio_int.h"
40fb065187Sbellard 
411d14ffa9Sbellard typedef struct SDLVoiceOut {
421d14ffa9Sbellard     HWVoiceOut hw;
431d14ffa9Sbellard     int live;
441d14ffa9Sbellard     int decr;
451d14ffa9Sbellard } SDLVoiceOut;
4685571bc7Sbellard 
47b1d8e52eSblueswir1 static struct SDLAudioState {
4885571bc7Sbellard     int exit;
4985571bc7Sbellard     int initialized;
5081ebb07cSKővágó, Zoltán     bool driver_created;
51*57dea553SKővágó, Zoltán     Audiodev *dev;
5285571bc7Sbellard } glob_sdl;
5385571bc7Sbellard typedef struct SDLAudioState SDLAudioState;
5485571bc7Sbellard 
551d14ffa9Sbellard static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...)
5685571bc7Sbellard {
571d14ffa9Sbellard     va_list ap;
581d14ffa9Sbellard 
591d14ffa9Sbellard     va_start (ap, fmt);
601d14ffa9Sbellard     AUD_vlog (AUDIO_CAP, fmt, ap);
611d14ffa9Sbellard     va_end (ap);
621d14ffa9Sbellard 
631d14ffa9Sbellard     AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ());
6485571bc7Sbellard }
6585571bc7Sbellard 
6685bc5852SKővágó, Zoltán static int aud_to_sdlfmt (AudioFormat fmt)
6785571bc7Sbellard {
681d14ffa9Sbellard     switch (fmt) {
6985bc5852SKővágó, Zoltán     case AUDIO_FORMAT_S8:
701d14ffa9Sbellard         return AUDIO_S8;
711d14ffa9Sbellard 
7285bc5852SKővágó, Zoltán     case AUDIO_FORMAT_U8:
731d14ffa9Sbellard         return AUDIO_U8;
741d14ffa9Sbellard 
7585bc5852SKővágó, Zoltán     case AUDIO_FORMAT_S16:
761d14ffa9Sbellard         return AUDIO_S16LSB;
771d14ffa9Sbellard 
7885bc5852SKővágó, Zoltán     case AUDIO_FORMAT_U16:
791d14ffa9Sbellard         return AUDIO_U16LSB;
801d14ffa9Sbellard 
8185571bc7Sbellard     default:
821d14ffa9Sbellard         dolog ("Internal logic error: Bad audio format %d\n", fmt);
831d14ffa9Sbellard #ifdef DEBUG_AUDIO
841d14ffa9Sbellard         abort ();
851d14ffa9Sbellard #endif
861d14ffa9Sbellard         return AUDIO_U8;
8785571bc7Sbellard     }
8885571bc7Sbellard }
8985571bc7Sbellard 
9085bc5852SKővágó, Zoltán static int sdl_to_audfmt(int sdlfmt, AudioFormat *fmt, int *endianness)
9185571bc7Sbellard {
921d14ffa9Sbellard     switch (sdlfmt) {
931d14ffa9Sbellard     case AUDIO_S8:
944ff9786cSStefan Weil         *endianness = 0;
9585bc5852SKővágó, Zoltán         *fmt = AUDIO_FORMAT_S8;
961d14ffa9Sbellard         break;
971d14ffa9Sbellard 
981d14ffa9Sbellard     case AUDIO_U8:
994ff9786cSStefan Weil         *endianness = 0;
10085bc5852SKővágó, Zoltán         *fmt = AUDIO_FORMAT_U8;
1011d14ffa9Sbellard         break;
1021d14ffa9Sbellard 
1031d14ffa9Sbellard     case AUDIO_S16LSB:
1044ff9786cSStefan Weil         *endianness = 0;
10585bc5852SKővágó, Zoltán         *fmt = AUDIO_FORMAT_S16;
1061d14ffa9Sbellard         break;
1071d14ffa9Sbellard 
1081d14ffa9Sbellard     case AUDIO_U16LSB:
1094ff9786cSStefan Weil         *endianness = 0;
11085bc5852SKővágó, Zoltán         *fmt = AUDIO_FORMAT_U16;
1111d14ffa9Sbellard         break;
1121d14ffa9Sbellard 
1131d14ffa9Sbellard     case AUDIO_S16MSB:
1144ff9786cSStefan Weil         *endianness = 1;
11585bc5852SKővágó, Zoltán         *fmt = AUDIO_FORMAT_S16;
1161d14ffa9Sbellard         break;
1171d14ffa9Sbellard 
1181d14ffa9Sbellard     case AUDIO_U16MSB:
1194ff9786cSStefan Weil         *endianness = 1;
12085bc5852SKővágó, Zoltán         *fmt = AUDIO_FORMAT_U16;
1211d14ffa9Sbellard         break;
1221d14ffa9Sbellard 
12385571bc7Sbellard     default:
1241d14ffa9Sbellard         dolog ("Unrecognized SDL audio format %d\n", sdlfmt);
1251d14ffa9Sbellard         return -1;
12685571bc7Sbellard     }
1271d14ffa9Sbellard 
1281d14ffa9Sbellard     return 0;
12985571bc7Sbellard }
13085571bc7Sbellard 
13185571bc7Sbellard static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt)
13285571bc7Sbellard {
13385571bc7Sbellard     int status;
134e784ba70Sths #ifndef _WIN32
135d087bb3eSmalc     int err;
136e784ba70Sths     sigset_t new, old;
137e784ba70Sths 
138e784ba70Sths     /* Make sure potential threads created by SDL don't hog signals.  */
139d087bb3eSmalc     err = sigfillset (&new);
140d087bb3eSmalc     if (err) {
141d087bb3eSmalc         dolog ("sdl_open: sigfillset failed: %s\n", strerror (errno));
14260592eddSmalc         return -1;
143d087bb3eSmalc     }
144d087bb3eSmalc     err = pthread_sigmask (SIG_BLOCK, &new, &old);
145d087bb3eSmalc     if (err) {
146d087bb3eSmalc         dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err));
147d087bb3eSmalc         return -1;
148d087bb3eSmalc     }
149e784ba70Sths #endif
15085571bc7Sbellard 
15185571bc7Sbellard     status = SDL_OpenAudio (req, obt);
15285571bc7Sbellard     if (status) {
1531d14ffa9Sbellard         sdl_logerr ("SDL_OpenAudio failed\n");
15485571bc7Sbellard     }
155e784ba70Sths 
156e784ba70Sths #ifndef _WIN32
157d087bb3eSmalc     err = pthread_sigmask (SIG_SETMASK, &old, NULL);
158d087bb3eSmalc     if (err) {
159d087bb3eSmalc         dolog ("sdl_open: pthread_sigmask (restore) failed: %s\n",
160d087bb3eSmalc                strerror (errno));
161d087bb3eSmalc         /* We have failed to restore original signal mask, all bets are off,
162d087bb3eSmalc            so exit the process */
163d087bb3eSmalc         exit (EXIT_FAILURE);
164d087bb3eSmalc     }
165e784ba70Sths #endif
16685571bc7Sbellard     return status;
16785571bc7Sbellard }
16885571bc7Sbellard 
16985571bc7Sbellard static void sdl_close (SDLAudioState *s)
17085571bc7Sbellard {
17185571bc7Sbellard     if (s->initialized) {
1728a7816c4SThomas Huth         SDL_LockAudio();
17385571bc7Sbellard         s->exit = 1;
1748a7816c4SThomas Huth         SDL_UnlockAudio();
17585571bc7Sbellard         SDL_PauseAudio (1);
17685571bc7Sbellard         SDL_CloseAudio ();
17785571bc7Sbellard         s->initialized = 0;
17885571bc7Sbellard     }
17985571bc7Sbellard }
18085571bc7Sbellard 
18185571bc7Sbellard static void sdl_callback (void *opaque, Uint8 *buf, int len)
18285571bc7Sbellard {
1831d14ffa9Sbellard     SDLVoiceOut *sdl = opaque;
18485571bc7Sbellard     SDLAudioState *s = &glob_sdl;
1851d14ffa9Sbellard     HWVoiceOut *hw = &sdl->hw;
1861d14ffa9Sbellard     int samples = len >> hw->info.shift;
1879399ef16SThomas Huth     int to_mix, decr;
18885571bc7Sbellard 
1899399ef16SThomas Huth     if (s->exit || !sdl->live) {
19085571bc7Sbellard         return;
19185571bc7Sbellard     }
19285571bc7Sbellard 
1939399ef16SThomas Huth     /* dolog ("in callback samples=%d live=%d\n", samples, sdl->live); */
19485571bc7Sbellard 
195ff541499Smalc     to_mix = audio_MIN(samples, sdl->live);
196ff541499Smalc     decr = to_mix;
197ff541499Smalc     while (to_mix) {
198ff541499Smalc         int chunk = audio_MIN(to_mix, hw->samples - hw->rpos);
199ff541499Smalc         struct st_sample *src = hw->mix_buf + hw->rpos;
200ff541499Smalc 
201ff541499Smalc         /* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */
202ff541499Smalc         hw->clip(buf, src, chunk);
203bcf19777SThomas Huth         hw->rpos = (hw->rpos + chunk) % hw->samples;
204ff541499Smalc         to_mix -= chunk;
205ff541499Smalc         buf += chunk << hw->info.shift;
206ff541499Smalc     }
20785571bc7Sbellard     samples -= decr;
208ff541499Smalc     sdl->live -= decr;
2091d14ffa9Sbellard     sdl->decr += decr;
2109399ef16SThomas Huth 
211ff541499Smalc     /* dolog ("done len=%d\n", len); */
212bcf19777SThomas Huth 
213bcf19777SThomas Huth     /* SDL2 does not clear the remaining buffer for us, so do it on our own */
214bcf19777SThomas Huth     if (samples) {
215bcf19777SThomas Huth         memset(buf, 0, samples << hw->info.shift);
216bcf19777SThomas Huth     }
217ff541499Smalc }
21885571bc7Sbellard 
2191d14ffa9Sbellard static int sdl_write_out (SWVoiceOut *sw, void *buf, int len)
22085571bc7Sbellard {
2211d14ffa9Sbellard     return audio_pcm_sw_write (sw, buf, len);
2221d14ffa9Sbellard }
2231d14ffa9Sbellard 
224bdff253cSmalc static int sdl_run_out (HWVoiceOut *hw, int live)
2251d14ffa9Sbellard {
226bdff253cSmalc     int decr;
2271d14ffa9Sbellard     SDLVoiceOut *sdl = (SDLVoiceOut *) hw;
2281d14ffa9Sbellard 
2298a7816c4SThomas Huth     SDL_LockAudio();
2301d14ffa9Sbellard 
231ff541499Smalc     if (sdl->decr > live) {
232ff541499Smalc         ldebug ("sdl->decr %d live %d sdl->live %d\n",
233ff541499Smalc                 sdl->decr,
234ff541499Smalc                 live,
235ff541499Smalc                 sdl->live);
236ff541499Smalc     }
237ff541499Smalc 
238ff541499Smalc     decr = audio_MIN (sdl->decr, live);
239ff541499Smalc     sdl->decr -= decr;
240ff541499Smalc 
241bcf19777SThomas Huth     sdl->live = live;
2421d14ffa9Sbellard 
2438a7816c4SThomas Huth     SDL_UnlockAudio();
2448a7816c4SThomas Huth 
2451d14ffa9Sbellard     return decr;
2461d14ffa9Sbellard }
2471d14ffa9Sbellard 
2481d14ffa9Sbellard static void sdl_fini_out (HWVoiceOut *hw)
2491d14ffa9Sbellard {
2501d14ffa9Sbellard     (void) hw;
2511d14ffa9Sbellard 
25285571bc7Sbellard     sdl_close (&glob_sdl);
25385571bc7Sbellard }
25485571bc7Sbellard 
2555706db1dSKővágó, Zoltán static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as,
2565706db1dSKővágó, Zoltán                         void *drv_opaque)
25785571bc7Sbellard {
2581d14ffa9Sbellard     SDLVoiceOut *sdl = (SDLVoiceOut *) hw;
25985571bc7Sbellard     SDLAudioState *s = &glob_sdl;
26085571bc7Sbellard     SDL_AudioSpec req, obt;
2614ff9786cSStefan Weil     int endianness;
2621d14ffa9Sbellard     int err;
26385bc5852SKővágó, Zoltán     AudioFormat effective_fmt;
2641ea879e5Smalc     struct audsettings obt_as;
26585571bc7Sbellard 
266c0fe3827Sbellard     req.freq = as->freq;
2676c557ab9SSerge Ziryukin     req.format = aud_to_sdlfmt (as->fmt);
268c0fe3827Sbellard     req.channels = as->nchannels;
269*57dea553SKővágó, Zoltán     req.samples = audio_buffer_samples(s->dev->u.sdl.out, as, 11610);
27085571bc7Sbellard     req.callback = sdl_callback;
27185571bc7Sbellard     req.userdata = sdl;
27285571bc7Sbellard 
2731d14ffa9Sbellard     if (sdl_open (&req, &obt)) {
27485571bc7Sbellard         return -1;
2751d14ffa9Sbellard     }
27685571bc7Sbellard 
2774ff9786cSStefan Weil     err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness);
2781d14ffa9Sbellard     if (err) {
2791d14ffa9Sbellard         sdl_close (s);
2801d14ffa9Sbellard         return -1;
2811d14ffa9Sbellard     }
2821d14ffa9Sbellard 
283c0fe3827Sbellard     obt_as.freq = obt.freq;
284c0fe3827Sbellard     obt_as.nchannels = obt.channels;
285c0fe3827Sbellard     obt_as.fmt = effective_fmt;
2864ff9786cSStefan Weil     obt_as.endianness = endianness;
287c0fe3827Sbellard 
288d929eba5Sbellard     audio_pcm_init_info (&hw->info, &obt_as);
289c0fe3827Sbellard     hw->samples = obt.samples;
29085571bc7Sbellard 
29185571bc7Sbellard     s->initialized = 1;
29285571bc7Sbellard     s->exit = 0;
29385571bc7Sbellard     SDL_PauseAudio (0);
29485571bc7Sbellard     return 0;
29585571bc7Sbellard }
29685571bc7Sbellard 
2971d14ffa9Sbellard static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...)
29885571bc7Sbellard {
29985571bc7Sbellard     (void) hw;
30085571bc7Sbellard 
30185571bc7Sbellard     switch (cmd) {
30285571bc7Sbellard     case VOICE_ENABLE:
30385571bc7Sbellard         SDL_PauseAudio (0);
30485571bc7Sbellard         break;
30585571bc7Sbellard 
30685571bc7Sbellard     case VOICE_DISABLE:
30785571bc7Sbellard         SDL_PauseAudio (1);
30885571bc7Sbellard         break;
30985571bc7Sbellard     }
31085571bc7Sbellard     return 0;
31185571bc7Sbellard }
31285571bc7Sbellard 
31371830221SKővágó, Zoltán static void *sdl_audio_init(Audiodev *dev)
31485571bc7Sbellard {
31585571bc7Sbellard     SDLAudioState *s = &glob_sdl;
31681ebb07cSKővágó, Zoltán     if (s->driver_created) {
31781ebb07cSKővágó, Zoltán         sdl_logerr("Can't create multiple sdl backends\n");
31881ebb07cSKővágó, Zoltán         return NULL;
31981ebb07cSKővágó, Zoltán     }
32085571bc7Sbellard 
32185571bc7Sbellard     if (SDL_InitSubSystem (SDL_INIT_AUDIO)) {
3221d14ffa9Sbellard         sdl_logerr ("SDL failed to initialize audio subsystem\n");
32385571bc7Sbellard         return NULL;
32485571bc7Sbellard     }
32585571bc7Sbellard 
32681ebb07cSKővágó, Zoltán     s->driver_created = true;
327*57dea553SKővágó, Zoltán     s->dev = dev;
32885571bc7Sbellard     return s;
32985571bc7Sbellard }
33085571bc7Sbellard 
33185571bc7Sbellard static void sdl_audio_fini (void *opaque)
33285571bc7Sbellard {
33385571bc7Sbellard     SDLAudioState *s = opaque;
33485571bc7Sbellard     sdl_close (s);
33585571bc7Sbellard     SDL_QuitSubSystem (SDL_INIT_AUDIO);
33681ebb07cSKővágó, Zoltán     s->driver_created = false;
337*57dea553SKővágó, Zoltán     s->dev = NULL;
33885571bc7Sbellard }
33985571bc7Sbellard 
34035f4b58cSblueswir1 static struct audio_pcm_ops sdl_pcm_ops = {
3411dd3e4d1SJuan Quintela     .init_out = sdl_init_out,
3421dd3e4d1SJuan Quintela     .fini_out = sdl_fini_out,
3431dd3e4d1SJuan Quintela     .run_out  = sdl_run_out,
3441dd3e4d1SJuan Quintela     .write    = sdl_write_out,
3451dd3e4d1SJuan Quintela     .ctl_out  = sdl_ctl_out,
3461d14ffa9Sbellard };
3471d14ffa9Sbellard 
348d3893a39SGerd Hoffmann static struct audio_driver sdl_audio_driver = {
349bee37f32SJuan Quintela     .name           = "sdl",
350bee37f32SJuan Quintela     .descr          = "SDL http://www.libsdl.org",
351bee37f32SJuan Quintela     .init           = sdl_audio_init,
352bee37f32SJuan Quintela     .fini           = sdl_audio_fini,
353bee37f32SJuan Quintela     .pcm_ops        = &sdl_pcm_ops,
354bee37f32SJuan Quintela     .can_be_default = 1,
355bee37f32SJuan Quintela     .max_voices_out = 1,
356bee37f32SJuan Quintela     .max_voices_in  = 0,
357bee37f32SJuan Quintela     .voice_size_out = sizeof (SDLVoiceOut),
358bee37f32SJuan Quintela     .voice_size_in  = 0
35985571bc7Sbellard };
360d3893a39SGerd Hoffmann 
361d3893a39SGerd Hoffmann static void register_audio_sdl(void)
362d3893a39SGerd Hoffmann {
363d3893a39SGerd Hoffmann     audio_driver_register(&sdl_audio_driver);
364d3893a39SGerd Hoffmann }
365d3893a39SGerd Hoffmann type_init(register_audio_sdl);
366