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) { 177*8a7816c4SThomas Huth SDL_LockAudio(); 17885571bc7Sbellard s->exit = 1; 179*8a7816c4SThomas 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; 19285571bc7Sbellard 19385571bc7Sbellard if (s->exit) { 19485571bc7Sbellard return; 19585571bc7Sbellard } 19685571bc7Sbellard 19785571bc7Sbellard while (samples) { 1981d14ffa9Sbellard int to_mix, decr; 19985571bc7Sbellard 200ff541499Smalc /* dolog ("in callback samples=%d\n", samples); */ 20185571bc7Sbellard 202bcf19777SThomas Huth if (s->exit || !sdl->live) { 203bcf19777SThomas Huth break; 204bcf19777SThomas Huth } 205ff541499Smalc 206ff541499Smalc /* dolog ("in callback live=%d\n", live); */ 207ff541499Smalc to_mix = audio_MIN (samples, sdl->live); 208ff541499Smalc decr = to_mix; 209ff541499Smalc while (to_mix) { 210ff541499Smalc int chunk = audio_MIN (to_mix, hw->samples - hw->rpos); 211ff541499Smalc struct st_sample *src = hw->mix_buf + hw->rpos; 212ff541499Smalc 213ff541499Smalc /* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */ 214ff541499Smalc hw->clip (buf, src, chunk); 215bcf19777SThomas Huth hw->rpos = (hw->rpos + chunk) % hw->samples; 216ff541499Smalc to_mix -= chunk; 217ff541499Smalc buf += chunk << hw->info.shift; 218ff541499Smalc } 21985571bc7Sbellard samples -= decr; 220ff541499Smalc sdl->live -= decr; 2211d14ffa9Sbellard sdl->decr += decr; 22285571bc7Sbellard } 223ff541499Smalc /* dolog ("done len=%d\n", len); */ 224bcf19777SThomas Huth 225bcf19777SThomas Huth /* SDL2 does not clear the remaining buffer for us, so do it on our own */ 226bcf19777SThomas Huth if (samples) { 227bcf19777SThomas Huth memset(buf, 0, samples << hw->info.shift); 228bcf19777SThomas Huth } 229ff541499Smalc } 23085571bc7Sbellard 2311d14ffa9Sbellard static int sdl_write_out (SWVoiceOut *sw, void *buf, int len) 23285571bc7Sbellard { 2331d14ffa9Sbellard return audio_pcm_sw_write (sw, buf, len); 2341d14ffa9Sbellard } 2351d14ffa9Sbellard 236bdff253cSmalc static int sdl_run_out (HWVoiceOut *hw, int live) 2371d14ffa9Sbellard { 238bdff253cSmalc int decr; 2391d14ffa9Sbellard SDLVoiceOut *sdl = (SDLVoiceOut *) hw; 2401d14ffa9Sbellard 241*8a7816c4SThomas Huth SDL_LockAudio(); 2421d14ffa9Sbellard 243ff541499Smalc if (sdl->decr > live) { 244ff541499Smalc ldebug ("sdl->decr %d live %d sdl->live %d\n", 245ff541499Smalc sdl->decr, 246ff541499Smalc live, 247ff541499Smalc sdl->live); 248ff541499Smalc } 249ff541499Smalc 250ff541499Smalc decr = audio_MIN (sdl->decr, live); 251ff541499Smalc sdl->decr -= decr; 252ff541499Smalc 253bcf19777SThomas Huth sdl->live = live; 2541d14ffa9Sbellard 255*8a7816c4SThomas Huth SDL_UnlockAudio(); 256*8a7816c4SThomas Huth 2571d14ffa9Sbellard return decr; 2581d14ffa9Sbellard } 2591d14ffa9Sbellard 2601d14ffa9Sbellard static void sdl_fini_out (HWVoiceOut *hw) 2611d14ffa9Sbellard { 2621d14ffa9Sbellard (void) hw; 2631d14ffa9Sbellard 26485571bc7Sbellard sdl_close (&glob_sdl); 26585571bc7Sbellard } 26685571bc7Sbellard 2675706db1dSKővágó, Zoltán static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, 2685706db1dSKővágó, Zoltán void *drv_opaque) 26985571bc7Sbellard { 2701d14ffa9Sbellard SDLVoiceOut *sdl = (SDLVoiceOut *) hw; 27185571bc7Sbellard SDLAudioState *s = &glob_sdl; 27285571bc7Sbellard SDL_AudioSpec req, obt; 2734ff9786cSStefan Weil int endianness; 2741d14ffa9Sbellard int err; 2751d14ffa9Sbellard audfmt_e effective_fmt; 2761ea879e5Smalc struct audsettings obt_as; 27785571bc7Sbellard 278c0fe3827Sbellard req.freq = as->freq; 2796c557ab9SSerge Ziryukin req.format = aud_to_sdlfmt (as->fmt); 280c0fe3827Sbellard req.channels = as->nchannels; 28185571bc7Sbellard req.samples = conf.nb_samples; 28285571bc7Sbellard req.callback = sdl_callback; 28385571bc7Sbellard req.userdata = sdl; 28485571bc7Sbellard 2851d14ffa9Sbellard if (sdl_open (&req, &obt)) { 28685571bc7Sbellard return -1; 2871d14ffa9Sbellard } 28885571bc7Sbellard 2894ff9786cSStefan Weil err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness); 2901d14ffa9Sbellard if (err) { 2911d14ffa9Sbellard sdl_close (s); 2921d14ffa9Sbellard return -1; 2931d14ffa9Sbellard } 2941d14ffa9Sbellard 295c0fe3827Sbellard obt_as.freq = obt.freq; 296c0fe3827Sbellard obt_as.nchannels = obt.channels; 297c0fe3827Sbellard obt_as.fmt = effective_fmt; 2984ff9786cSStefan Weil obt_as.endianness = endianness; 299c0fe3827Sbellard 300d929eba5Sbellard audio_pcm_init_info (&hw->info, &obt_as); 301c0fe3827Sbellard hw->samples = obt.samples; 30285571bc7Sbellard 30385571bc7Sbellard s->initialized = 1; 30485571bc7Sbellard s->exit = 0; 30585571bc7Sbellard SDL_PauseAudio (0); 30685571bc7Sbellard return 0; 30785571bc7Sbellard } 30885571bc7Sbellard 3091d14ffa9Sbellard static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...) 31085571bc7Sbellard { 31185571bc7Sbellard (void) hw; 31285571bc7Sbellard 31385571bc7Sbellard switch (cmd) { 31485571bc7Sbellard case VOICE_ENABLE: 31585571bc7Sbellard SDL_PauseAudio (0); 31685571bc7Sbellard break; 31785571bc7Sbellard 31885571bc7Sbellard case VOICE_DISABLE: 31985571bc7Sbellard SDL_PauseAudio (1); 32085571bc7Sbellard break; 32185571bc7Sbellard } 32285571bc7Sbellard return 0; 32385571bc7Sbellard } 32485571bc7Sbellard 32585571bc7Sbellard static void *sdl_audio_init (void) 32685571bc7Sbellard { 32785571bc7Sbellard SDLAudioState *s = &glob_sdl; 32881ebb07cSKővágó, Zoltán if (s->driver_created) { 32981ebb07cSKővágó, Zoltán sdl_logerr("Can't create multiple sdl backends\n"); 33081ebb07cSKővágó, Zoltán return NULL; 33181ebb07cSKővágó, Zoltán } 33285571bc7Sbellard 33385571bc7Sbellard if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { 3341d14ffa9Sbellard sdl_logerr ("SDL failed to initialize audio subsystem\n"); 33585571bc7Sbellard return NULL; 33685571bc7Sbellard } 33785571bc7Sbellard 33881ebb07cSKővágó, Zoltán s->driver_created = true; 33985571bc7Sbellard return s; 34085571bc7Sbellard } 34185571bc7Sbellard 34285571bc7Sbellard static void sdl_audio_fini (void *opaque) 34385571bc7Sbellard { 34485571bc7Sbellard SDLAudioState *s = opaque; 34585571bc7Sbellard sdl_close (s); 34685571bc7Sbellard SDL_QuitSubSystem (SDL_INIT_AUDIO); 34781ebb07cSKővágó, Zoltán s->driver_created = false; 34885571bc7Sbellard } 34985571bc7Sbellard 3501d14ffa9Sbellard static struct audio_option sdl_options[] = { 35198f9f48cSmalc { 35298f9f48cSmalc .name = "SAMPLES", 3532700efa3SJuan Quintela .tag = AUD_OPT_INT, 3542700efa3SJuan Quintela .valp = &conf.nb_samples, 35598f9f48cSmalc .descr = "Size of SDL buffer in samples" 35698f9f48cSmalc }, 3572700efa3SJuan Quintela { /* End of list */ } 35885571bc7Sbellard }; 35985571bc7Sbellard 36035f4b58cSblueswir1 static struct audio_pcm_ops sdl_pcm_ops = { 3611dd3e4d1SJuan Quintela .init_out = sdl_init_out, 3621dd3e4d1SJuan Quintela .fini_out = sdl_fini_out, 3631dd3e4d1SJuan Quintela .run_out = sdl_run_out, 3641dd3e4d1SJuan Quintela .write = sdl_write_out, 3651dd3e4d1SJuan Quintela .ctl_out = sdl_ctl_out, 3661d14ffa9Sbellard }; 3671d14ffa9Sbellard 368d3893a39SGerd Hoffmann static struct audio_driver sdl_audio_driver = { 369bee37f32SJuan Quintela .name = "sdl", 370bee37f32SJuan Quintela .descr = "SDL http://www.libsdl.org", 371bee37f32SJuan Quintela .options = sdl_options, 372bee37f32SJuan Quintela .init = sdl_audio_init, 373bee37f32SJuan Quintela .fini = sdl_audio_fini, 374bee37f32SJuan Quintela .pcm_ops = &sdl_pcm_ops, 375bee37f32SJuan Quintela .can_be_default = 1, 376bee37f32SJuan Quintela .max_voices_out = 1, 377bee37f32SJuan Quintela .max_voices_in = 0, 378bee37f32SJuan Quintela .voice_size_out = sizeof (SDLVoiceOut), 379bee37f32SJuan Quintela .voice_size_in = 0 38085571bc7Sbellard }; 381d3893a39SGerd Hoffmann 382d3893a39SGerd Hoffmann static void register_audio_sdl(void) 383d3893a39SGerd Hoffmann { 384d3893a39SGerd Hoffmann audio_driver_register(&sdl_audio_driver); 385d3893a39SGerd Hoffmann } 386d3893a39SGerd Hoffmann type_init(register_audio_sdl); 387