xref: /openbmc/qemu/audio/sdlaudio.c (revision 9399ef16)
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 
4785571bc7Sbellard static struct {
4885571bc7Sbellard     int nb_samples;
4985571bc7Sbellard } conf = {
501a40d5e2SJuan Quintela     .nb_samples = 1024
5185571bc7Sbellard };
5285571bc7Sbellard 
53b1d8e52eSblueswir1 static struct SDLAudioState {
5485571bc7Sbellard     int exit;
5585571bc7Sbellard     int initialized;
5681ebb07cSKővágó, Zoltán     bool driver_created;
5785571bc7Sbellard } glob_sdl;
5885571bc7Sbellard typedef struct SDLAudioState SDLAudioState;
5985571bc7Sbellard 
601d14ffa9Sbellard static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...)
6185571bc7Sbellard {
621d14ffa9Sbellard     va_list ap;
631d14ffa9Sbellard 
641d14ffa9Sbellard     va_start (ap, fmt);
651d14ffa9Sbellard     AUD_vlog (AUDIO_CAP, fmt, ap);
661d14ffa9Sbellard     va_end (ap);
671d14ffa9Sbellard 
681d14ffa9Sbellard     AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ());
6985571bc7Sbellard }
7085571bc7Sbellard 
716c557ab9SSerge Ziryukin static int aud_to_sdlfmt (audfmt_e fmt)
7285571bc7Sbellard {
731d14ffa9Sbellard     switch (fmt) {
741d14ffa9Sbellard     case AUD_FMT_S8:
751d14ffa9Sbellard         return AUDIO_S8;
761d14ffa9Sbellard 
771d14ffa9Sbellard     case AUD_FMT_U8:
781d14ffa9Sbellard         return AUDIO_U8;
791d14ffa9Sbellard 
801d14ffa9Sbellard     case AUD_FMT_S16:
811d14ffa9Sbellard         return AUDIO_S16LSB;
821d14ffa9Sbellard 
831d14ffa9Sbellard     case AUD_FMT_U16:
841d14ffa9Sbellard         return AUDIO_U16LSB;
851d14ffa9Sbellard 
8685571bc7Sbellard     default:
871d14ffa9Sbellard         dolog ("Internal logic error: Bad audio format %d\n", fmt);
881d14ffa9Sbellard #ifdef DEBUG_AUDIO
891d14ffa9Sbellard         abort ();
901d14ffa9Sbellard #endif
911d14ffa9Sbellard         return AUDIO_U8;
9285571bc7Sbellard     }
9385571bc7Sbellard }
9485571bc7Sbellard 
954ff9786cSStefan Weil static int sdl_to_audfmt(int sdlfmt, audfmt_e *fmt, int *endianness)
9685571bc7Sbellard {
971d14ffa9Sbellard     switch (sdlfmt) {
981d14ffa9Sbellard     case AUDIO_S8:
994ff9786cSStefan Weil         *endianness = 0;
1001d14ffa9Sbellard         *fmt = AUD_FMT_S8;
1011d14ffa9Sbellard         break;
1021d14ffa9Sbellard 
1031d14ffa9Sbellard     case AUDIO_U8:
1044ff9786cSStefan Weil         *endianness = 0;
1051d14ffa9Sbellard         *fmt = AUD_FMT_U8;
1061d14ffa9Sbellard         break;
1071d14ffa9Sbellard 
1081d14ffa9Sbellard     case AUDIO_S16LSB:
1094ff9786cSStefan Weil         *endianness = 0;
1101d14ffa9Sbellard         *fmt = AUD_FMT_S16;
1111d14ffa9Sbellard         break;
1121d14ffa9Sbellard 
1131d14ffa9Sbellard     case AUDIO_U16LSB:
1144ff9786cSStefan Weil         *endianness = 0;
1151d14ffa9Sbellard         *fmt = AUD_FMT_U16;
1161d14ffa9Sbellard         break;
1171d14ffa9Sbellard 
1181d14ffa9Sbellard     case AUDIO_S16MSB:
1194ff9786cSStefan Weil         *endianness = 1;
1201d14ffa9Sbellard         *fmt = AUD_FMT_S16;
1211d14ffa9Sbellard         break;
1221d14ffa9Sbellard 
1231d14ffa9Sbellard     case AUDIO_U16MSB:
1244ff9786cSStefan Weil         *endianness = 1;
1251d14ffa9Sbellard         *fmt = AUD_FMT_U16;
1261d14ffa9Sbellard         break;
1271d14ffa9Sbellard 
12885571bc7Sbellard     default:
1291d14ffa9Sbellard         dolog ("Unrecognized SDL audio format %d\n", sdlfmt);
1301d14ffa9Sbellard         return -1;
13185571bc7Sbellard     }
1321d14ffa9Sbellard 
1331d14ffa9Sbellard     return 0;
13485571bc7Sbellard }
13585571bc7Sbellard 
13685571bc7Sbellard static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt)
13785571bc7Sbellard {
13885571bc7Sbellard     int status;
139e784ba70Sths #ifndef _WIN32
140d087bb3eSmalc     int err;
141e784ba70Sths     sigset_t new, old;
142e784ba70Sths 
143e784ba70Sths     /* Make sure potential threads created by SDL don't hog signals.  */
144d087bb3eSmalc     err = sigfillset (&new);
145d087bb3eSmalc     if (err) {
146d087bb3eSmalc         dolog ("sdl_open: sigfillset failed: %s\n", strerror (errno));
14760592eddSmalc         return -1;
148d087bb3eSmalc     }
149d087bb3eSmalc     err = pthread_sigmask (SIG_BLOCK, &new, &old);
150d087bb3eSmalc     if (err) {
151d087bb3eSmalc         dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err));
152d087bb3eSmalc         return -1;
153d087bb3eSmalc     }
154e784ba70Sths #endif
15585571bc7Sbellard 
15685571bc7Sbellard     status = SDL_OpenAudio (req, obt);
15785571bc7Sbellard     if (status) {
1581d14ffa9Sbellard         sdl_logerr ("SDL_OpenAudio failed\n");
15985571bc7Sbellard     }
160e784ba70Sths 
161e784ba70Sths #ifndef _WIN32
162d087bb3eSmalc     err = pthread_sigmask (SIG_SETMASK, &old, NULL);
163d087bb3eSmalc     if (err) {
164d087bb3eSmalc         dolog ("sdl_open: pthread_sigmask (restore) failed: %s\n",
165d087bb3eSmalc                strerror (errno));
166d087bb3eSmalc         /* We have failed to restore original signal mask, all bets are off,
167d087bb3eSmalc            so exit the process */
168d087bb3eSmalc         exit (EXIT_FAILURE);
169d087bb3eSmalc     }
170e784ba70Sths #endif
17185571bc7Sbellard     return status;
17285571bc7Sbellard }
17385571bc7Sbellard 
17485571bc7Sbellard static void sdl_close (SDLAudioState *s)
17585571bc7Sbellard {
17685571bc7Sbellard     if (s->initialized) {
1778a7816c4SThomas Huth         SDL_LockAudio();
17885571bc7Sbellard         s->exit = 1;
1798a7816c4SThomas Huth         SDL_UnlockAudio();
18085571bc7Sbellard         SDL_PauseAudio (1);
18185571bc7Sbellard         SDL_CloseAudio ();
18285571bc7Sbellard         s->initialized = 0;
18385571bc7Sbellard     }
18485571bc7Sbellard }
18585571bc7Sbellard 
18685571bc7Sbellard static void sdl_callback (void *opaque, Uint8 *buf, int len)
18785571bc7Sbellard {
1881d14ffa9Sbellard     SDLVoiceOut *sdl = opaque;
18985571bc7Sbellard     SDLAudioState *s = &glob_sdl;
1901d14ffa9Sbellard     HWVoiceOut *hw = &sdl->hw;
1911d14ffa9Sbellard     int samples = len >> hw->info.shift;
192*9399ef16SThomas Huth     int to_mix, decr;
19385571bc7Sbellard 
194*9399ef16SThomas Huth     if (s->exit || !sdl->live) {
19585571bc7Sbellard         return;
19685571bc7Sbellard     }
19785571bc7Sbellard 
198*9399ef16SThomas Huth     /* dolog ("in callback samples=%d live=%d\n", samples, sdl->live); */
19985571bc7Sbellard 
200ff541499Smalc     to_mix = audio_MIN(samples, sdl->live);
201ff541499Smalc     decr = to_mix;
202ff541499Smalc     while (to_mix) {
203ff541499Smalc         int chunk = audio_MIN(to_mix, hw->samples - hw->rpos);
204ff541499Smalc         struct st_sample *src = hw->mix_buf + hw->rpos;
205ff541499Smalc 
206ff541499Smalc         /* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */
207ff541499Smalc         hw->clip(buf, src, chunk);
208bcf19777SThomas Huth         hw->rpos = (hw->rpos + chunk) % hw->samples;
209ff541499Smalc         to_mix -= chunk;
210ff541499Smalc         buf += chunk << hw->info.shift;
211ff541499Smalc     }
21285571bc7Sbellard     samples -= decr;
213ff541499Smalc     sdl->live -= decr;
2141d14ffa9Sbellard     sdl->decr += decr;
215*9399ef16SThomas Huth 
216ff541499Smalc     /* dolog ("done len=%d\n", len); */
217bcf19777SThomas Huth 
218bcf19777SThomas Huth     /* SDL2 does not clear the remaining buffer for us, so do it on our own */
219bcf19777SThomas Huth     if (samples) {
220bcf19777SThomas Huth         memset(buf, 0, samples << hw->info.shift);
221bcf19777SThomas Huth     }
222ff541499Smalc }
22385571bc7Sbellard 
2241d14ffa9Sbellard static int sdl_write_out (SWVoiceOut *sw, void *buf, int len)
22585571bc7Sbellard {
2261d14ffa9Sbellard     return audio_pcm_sw_write (sw, buf, len);
2271d14ffa9Sbellard }
2281d14ffa9Sbellard 
229bdff253cSmalc static int sdl_run_out (HWVoiceOut *hw, int live)
2301d14ffa9Sbellard {
231bdff253cSmalc     int decr;
2321d14ffa9Sbellard     SDLVoiceOut *sdl = (SDLVoiceOut *) hw;
2331d14ffa9Sbellard 
2348a7816c4SThomas Huth     SDL_LockAudio();
2351d14ffa9Sbellard 
236ff541499Smalc     if (sdl->decr > live) {
237ff541499Smalc         ldebug ("sdl->decr %d live %d sdl->live %d\n",
238ff541499Smalc                 sdl->decr,
239ff541499Smalc                 live,
240ff541499Smalc                 sdl->live);
241ff541499Smalc     }
242ff541499Smalc 
243ff541499Smalc     decr = audio_MIN (sdl->decr, live);
244ff541499Smalc     sdl->decr -= decr;
245ff541499Smalc 
246bcf19777SThomas Huth     sdl->live = live;
2471d14ffa9Sbellard 
2488a7816c4SThomas Huth     SDL_UnlockAudio();
2498a7816c4SThomas Huth 
2501d14ffa9Sbellard     return decr;
2511d14ffa9Sbellard }
2521d14ffa9Sbellard 
2531d14ffa9Sbellard static void sdl_fini_out (HWVoiceOut *hw)
2541d14ffa9Sbellard {
2551d14ffa9Sbellard     (void) hw;
2561d14ffa9Sbellard 
25785571bc7Sbellard     sdl_close (&glob_sdl);
25885571bc7Sbellard }
25985571bc7Sbellard 
2605706db1dSKővágó, Zoltán static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as,
2615706db1dSKővágó, Zoltán                         void *drv_opaque)
26285571bc7Sbellard {
2631d14ffa9Sbellard     SDLVoiceOut *sdl = (SDLVoiceOut *) hw;
26485571bc7Sbellard     SDLAudioState *s = &glob_sdl;
26585571bc7Sbellard     SDL_AudioSpec req, obt;
2664ff9786cSStefan Weil     int endianness;
2671d14ffa9Sbellard     int err;
2681d14ffa9Sbellard     audfmt_e effective_fmt;
2691ea879e5Smalc     struct audsettings obt_as;
27085571bc7Sbellard 
271c0fe3827Sbellard     req.freq = as->freq;
2726c557ab9SSerge Ziryukin     req.format = aud_to_sdlfmt (as->fmt);
273c0fe3827Sbellard     req.channels = as->nchannels;
27485571bc7Sbellard     req.samples = conf.nb_samples;
27585571bc7Sbellard     req.callback = sdl_callback;
27685571bc7Sbellard     req.userdata = sdl;
27785571bc7Sbellard 
2781d14ffa9Sbellard     if (sdl_open (&req, &obt)) {
27985571bc7Sbellard         return -1;
2801d14ffa9Sbellard     }
28185571bc7Sbellard 
2824ff9786cSStefan Weil     err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness);
2831d14ffa9Sbellard     if (err) {
2841d14ffa9Sbellard         sdl_close (s);
2851d14ffa9Sbellard         return -1;
2861d14ffa9Sbellard     }
2871d14ffa9Sbellard 
288c0fe3827Sbellard     obt_as.freq = obt.freq;
289c0fe3827Sbellard     obt_as.nchannels = obt.channels;
290c0fe3827Sbellard     obt_as.fmt = effective_fmt;
2914ff9786cSStefan Weil     obt_as.endianness = endianness;
292c0fe3827Sbellard 
293d929eba5Sbellard     audio_pcm_init_info (&hw->info, &obt_as);
294c0fe3827Sbellard     hw->samples = obt.samples;
29585571bc7Sbellard 
29685571bc7Sbellard     s->initialized = 1;
29785571bc7Sbellard     s->exit = 0;
29885571bc7Sbellard     SDL_PauseAudio (0);
29985571bc7Sbellard     return 0;
30085571bc7Sbellard }
30185571bc7Sbellard 
3021d14ffa9Sbellard static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...)
30385571bc7Sbellard {
30485571bc7Sbellard     (void) hw;
30585571bc7Sbellard 
30685571bc7Sbellard     switch (cmd) {
30785571bc7Sbellard     case VOICE_ENABLE:
30885571bc7Sbellard         SDL_PauseAudio (0);
30985571bc7Sbellard         break;
31085571bc7Sbellard 
31185571bc7Sbellard     case VOICE_DISABLE:
31285571bc7Sbellard         SDL_PauseAudio (1);
31385571bc7Sbellard         break;
31485571bc7Sbellard     }
31585571bc7Sbellard     return 0;
31685571bc7Sbellard }
31785571bc7Sbellard 
31885571bc7Sbellard static void *sdl_audio_init (void)
31985571bc7Sbellard {
32085571bc7Sbellard     SDLAudioState *s = &glob_sdl;
32181ebb07cSKővágó, Zoltán     if (s->driver_created) {
32281ebb07cSKővágó, Zoltán         sdl_logerr("Can't create multiple sdl backends\n");
32381ebb07cSKővágó, Zoltán         return NULL;
32481ebb07cSKővágó, Zoltán     }
32585571bc7Sbellard 
32685571bc7Sbellard     if (SDL_InitSubSystem (SDL_INIT_AUDIO)) {
3271d14ffa9Sbellard         sdl_logerr ("SDL failed to initialize audio subsystem\n");
32885571bc7Sbellard         return NULL;
32985571bc7Sbellard     }
33085571bc7Sbellard 
33181ebb07cSKővágó, Zoltán     s->driver_created = true;
33285571bc7Sbellard     return s;
33385571bc7Sbellard }
33485571bc7Sbellard 
33585571bc7Sbellard static void sdl_audio_fini (void *opaque)
33685571bc7Sbellard {
33785571bc7Sbellard     SDLAudioState *s = opaque;
33885571bc7Sbellard     sdl_close (s);
33985571bc7Sbellard     SDL_QuitSubSystem (SDL_INIT_AUDIO);
34081ebb07cSKővágó, Zoltán     s->driver_created = false;
34185571bc7Sbellard }
34285571bc7Sbellard 
3431d14ffa9Sbellard static struct audio_option sdl_options[] = {
34498f9f48cSmalc     {
34598f9f48cSmalc         .name  = "SAMPLES",
3462700efa3SJuan Quintela         .tag   = AUD_OPT_INT,
3472700efa3SJuan Quintela         .valp  = &conf.nb_samples,
34898f9f48cSmalc         .descr = "Size of SDL buffer in samples"
34998f9f48cSmalc     },
3502700efa3SJuan Quintela     { /* End of list */ }
35185571bc7Sbellard };
35285571bc7Sbellard 
35335f4b58cSblueswir1 static struct audio_pcm_ops sdl_pcm_ops = {
3541dd3e4d1SJuan Quintela     .init_out = sdl_init_out,
3551dd3e4d1SJuan Quintela     .fini_out = sdl_fini_out,
3561dd3e4d1SJuan Quintela     .run_out  = sdl_run_out,
3571dd3e4d1SJuan Quintela     .write    = sdl_write_out,
3581dd3e4d1SJuan Quintela     .ctl_out  = sdl_ctl_out,
3591d14ffa9Sbellard };
3601d14ffa9Sbellard 
361d3893a39SGerd Hoffmann static struct audio_driver sdl_audio_driver = {
362bee37f32SJuan Quintela     .name           = "sdl",
363bee37f32SJuan Quintela     .descr          = "SDL http://www.libsdl.org",
364bee37f32SJuan Quintela     .options        = sdl_options,
365bee37f32SJuan Quintela     .init           = sdl_audio_init,
366bee37f32SJuan Quintela     .fini           = sdl_audio_fini,
367bee37f32SJuan Quintela     .pcm_ops        = &sdl_pcm_ops,
368bee37f32SJuan Quintela     .can_be_default = 1,
369bee37f32SJuan Quintela     .max_voices_out = 1,
370bee37f32SJuan Quintela     .max_voices_in  = 0,
371bee37f32SJuan Quintela     .voice_size_out = sizeof (SDLVoiceOut),
372bee37f32SJuan Quintela     .voice_size_in  = 0
37385571bc7Sbellard };
374d3893a39SGerd Hoffmann 
375d3893a39SGerd Hoffmann static void register_audio_sdl(void)
376d3893a39SGerd Hoffmann {
377d3893a39SGerd Hoffmann     audio_driver_register(&sdl_audio_driver);
378d3893a39SGerd Hoffmann }
379d3893a39SGerd Hoffmann type_init(register_audio_sdl);
380