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 */ 240b8fa32fSMarkus Armbruster 256086a565SPeter Maydell #include "qemu/osdep.h" 269f059ecaSbellard #include <SDL.h> 279f059ecaSbellard #include <SDL_thread.h> 280b8fa32fSMarkus Armbruster #include "qemu/module.h" 2987ecb68bSpbrook #include "audio.h" 3085571bc7Sbellard 31e784ba70Sths #ifndef _WIN32 32e784ba70Sths #ifdef __sun__ 33e784ba70Sths #define _POSIX_PTHREAD_SEMANTICS 1 34c5e97233Sblueswir1 #elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) 359b4c14c3Sblueswir1 #include <pthread.h> 36e784ba70Sths #endif 37e784ba70Sths #endif 38e784ba70Sths 391d14ffa9Sbellard #define AUDIO_CAP "sdl" 401d14ffa9Sbellard #include "audio_int.h" 41fb065187Sbellard 421d14ffa9Sbellard typedef struct SDLVoiceOut { 431d14ffa9Sbellard HWVoiceOut hw; 441d14ffa9Sbellard } SDLVoiceOut; 4585571bc7Sbellard 46b1d8e52eSblueswir1 static struct SDLAudioState { 4785571bc7Sbellard int exit; 4885571bc7Sbellard int initialized; 4981ebb07cSKővágó, Zoltán bool driver_created; 5057dea553SKővágó, Zoltán Audiodev *dev; 5185571bc7Sbellard } glob_sdl; 5285571bc7Sbellard typedef struct SDLAudioState SDLAudioState; 5385571bc7Sbellard 541d14ffa9Sbellard static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...) 5585571bc7Sbellard { 561d14ffa9Sbellard va_list ap; 571d14ffa9Sbellard 581d14ffa9Sbellard va_start (ap, fmt); 591d14ffa9Sbellard AUD_vlog (AUDIO_CAP, fmt, ap); 601d14ffa9Sbellard va_end (ap); 611d14ffa9Sbellard 621d14ffa9Sbellard AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ()); 6385571bc7Sbellard } 6485571bc7Sbellard 6585bc5852SKővágó, Zoltán static int aud_to_sdlfmt (AudioFormat fmt) 6685571bc7Sbellard { 671d14ffa9Sbellard switch (fmt) { 6885bc5852SKővágó, Zoltán case AUDIO_FORMAT_S8: 691d14ffa9Sbellard return AUDIO_S8; 701d14ffa9Sbellard 7185bc5852SKővágó, Zoltán case AUDIO_FORMAT_U8: 721d14ffa9Sbellard return AUDIO_U8; 731d14ffa9Sbellard 7485bc5852SKővágó, Zoltán case AUDIO_FORMAT_S16: 751d14ffa9Sbellard return AUDIO_S16LSB; 761d14ffa9Sbellard 7785bc5852SKővágó, Zoltán case AUDIO_FORMAT_U16: 781d14ffa9Sbellard return AUDIO_U16LSB; 791d14ffa9Sbellard 80ed2a4a79SKővágó, Zoltán case AUDIO_FORMAT_S32: 81ed2a4a79SKővágó, Zoltán return AUDIO_S32LSB; 82ed2a4a79SKővágó, Zoltán 83ed2a4a79SKővágó, Zoltán /* no unsigned 32-bit support in SDL */ 84ed2a4a79SKővágó, Zoltán 85ed2a4a79SKővágó, Zoltán case AUDIO_FORMAT_F32: 86ed2a4a79SKővágó, Zoltán return AUDIO_F32LSB; 87ed2a4a79SKővágó, Zoltán 8885571bc7Sbellard default: 891d14ffa9Sbellard dolog ("Internal logic error: Bad audio format %d\n", fmt); 901d14ffa9Sbellard #ifdef DEBUG_AUDIO 911d14ffa9Sbellard abort (); 921d14ffa9Sbellard #endif 931d14ffa9Sbellard return AUDIO_U8; 9485571bc7Sbellard } 9585571bc7Sbellard } 9685571bc7Sbellard 9785bc5852SKővágó, Zoltán static int sdl_to_audfmt(int sdlfmt, AudioFormat *fmt, int *endianness) 9885571bc7Sbellard { 991d14ffa9Sbellard switch (sdlfmt) { 1001d14ffa9Sbellard case AUDIO_S8: 1014ff9786cSStefan Weil *endianness = 0; 10285bc5852SKővágó, Zoltán *fmt = AUDIO_FORMAT_S8; 1031d14ffa9Sbellard break; 1041d14ffa9Sbellard 1051d14ffa9Sbellard case AUDIO_U8: 1064ff9786cSStefan Weil *endianness = 0; 10785bc5852SKővágó, Zoltán *fmt = AUDIO_FORMAT_U8; 1081d14ffa9Sbellard break; 1091d14ffa9Sbellard 1101d14ffa9Sbellard case AUDIO_S16LSB: 1114ff9786cSStefan Weil *endianness = 0; 11285bc5852SKővágó, Zoltán *fmt = AUDIO_FORMAT_S16; 1131d14ffa9Sbellard break; 1141d14ffa9Sbellard 1151d14ffa9Sbellard case AUDIO_U16LSB: 1164ff9786cSStefan Weil *endianness = 0; 11785bc5852SKővágó, Zoltán *fmt = AUDIO_FORMAT_U16; 1181d14ffa9Sbellard break; 1191d14ffa9Sbellard 1201d14ffa9Sbellard case AUDIO_S16MSB: 1214ff9786cSStefan Weil *endianness = 1; 12285bc5852SKővágó, Zoltán *fmt = AUDIO_FORMAT_S16; 1231d14ffa9Sbellard break; 1241d14ffa9Sbellard 1251d14ffa9Sbellard case AUDIO_U16MSB: 1264ff9786cSStefan Weil *endianness = 1; 12785bc5852SKővágó, Zoltán *fmt = AUDIO_FORMAT_U16; 1281d14ffa9Sbellard break; 1291d14ffa9Sbellard 130ed2a4a79SKővágó, Zoltán case AUDIO_S32LSB: 131ed2a4a79SKővágó, Zoltán *endianness = 0; 132ed2a4a79SKővágó, Zoltán *fmt = AUDIO_FORMAT_S32; 133ed2a4a79SKővágó, Zoltán break; 134ed2a4a79SKővágó, Zoltán 135ed2a4a79SKővágó, Zoltán case AUDIO_S32MSB: 136ed2a4a79SKővágó, Zoltán *endianness = 1; 137ed2a4a79SKővágó, Zoltán *fmt = AUDIO_FORMAT_S32; 138ed2a4a79SKővágó, Zoltán break; 139ed2a4a79SKővágó, Zoltán 140ed2a4a79SKővágó, Zoltán case AUDIO_F32LSB: 141ed2a4a79SKővágó, Zoltán *endianness = 0; 142ed2a4a79SKővágó, Zoltán *fmt = AUDIO_FORMAT_F32; 143ed2a4a79SKővágó, Zoltán break; 144ed2a4a79SKővágó, Zoltán 145ed2a4a79SKővágó, Zoltán case AUDIO_F32MSB: 146ed2a4a79SKővágó, Zoltán *endianness = 1; 147ed2a4a79SKővágó, Zoltán *fmt = AUDIO_FORMAT_F32; 148ed2a4a79SKővágó, Zoltán break; 149ed2a4a79SKővágó, Zoltán 15085571bc7Sbellard default: 1511d14ffa9Sbellard dolog ("Unrecognized SDL audio format %d\n", sdlfmt); 1521d14ffa9Sbellard return -1; 15385571bc7Sbellard } 1541d14ffa9Sbellard 1551d14ffa9Sbellard return 0; 15685571bc7Sbellard } 15785571bc7Sbellard 15885571bc7Sbellard static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) 15985571bc7Sbellard { 16085571bc7Sbellard int status; 161e784ba70Sths #ifndef _WIN32 162d087bb3eSmalc int err; 163e784ba70Sths sigset_t new, old; 164e784ba70Sths 165e784ba70Sths /* Make sure potential threads created by SDL don't hog signals. */ 166d087bb3eSmalc err = sigfillset (&new); 167d087bb3eSmalc if (err) { 168d087bb3eSmalc dolog ("sdl_open: sigfillset failed: %s\n", strerror (errno)); 16960592eddSmalc return -1; 170d087bb3eSmalc } 171d087bb3eSmalc err = pthread_sigmask (SIG_BLOCK, &new, &old); 172d087bb3eSmalc if (err) { 173d087bb3eSmalc dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err)); 174d087bb3eSmalc return -1; 175d087bb3eSmalc } 176e784ba70Sths #endif 17785571bc7Sbellard 17885571bc7Sbellard status = SDL_OpenAudio (req, obt); 17985571bc7Sbellard if (status) { 1801d14ffa9Sbellard sdl_logerr ("SDL_OpenAudio failed\n"); 18185571bc7Sbellard } 182e784ba70Sths 183e784ba70Sths #ifndef _WIN32 184d087bb3eSmalc err = pthread_sigmask (SIG_SETMASK, &old, NULL); 185d087bb3eSmalc if (err) { 186d087bb3eSmalc dolog ("sdl_open: pthread_sigmask (restore) failed: %s\n", 187d087bb3eSmalc strerror (errno)); 188d087bb3eSmalc /* We have failed to restore original signal mask, all bets are off, 189d087bb3eSmalc so exit the process */ 190d087bb3eSmalc exit (EXIT_FAILURE); 191d087bb3eSmalc } 192e784ba70Sths #endif 19385571bc7Sbellard return status; 19485571bc7Sbellard } 19585571bc7Sbellard 19685571bc7Sbellard static void sdl_close (SDLAudioState *s) 19785571bc7Sbellard { 19885571bc7Sbellard if (s->initialized) { 1998a7816c4SThomas Huth SDL_LockAudio(); 20085571bc7Sbellard s->exit = 1; 2018a7816c4SThomas Huth SDL_UnlockAudio(); 20285571bc7Sbellard SDL_PauseAudio (1); 20385571bc7Sbellard SDL_CloseAudio (); 20485571bc7Sbellard s->initialized = 0; 20585571bc7Sbellard } 20685571bc7Sbellard } 20785571bc7Sbellard 20885571bc7Sbellard static void sdl_callback (void *opaque, Uint8 *buf, int len) 20985571bc7Sbellard { 2101d14ffa9Sbellard SDLVoiceOut *sdl = opaque; 21185571bc7Sbellard SDLAudioState *s = &glob_sdl; 2121d14ffa9Sbellard HWVoiceOut *hw = &sdl->hw; 21385571bc7Sbellard 214*bcce2ea5SVolker Rümelin if (!s->exit) { 21585571bc7Sbellard 216ff69c481SVolker Rümelin /* dolog("callback: len=%d avail=%zu\n", len, hw->pending_emul); */ 21785571bc7Sbellard 218ff718767SKővágó, Zoltán while (hw->pending_emul && len) { 219ff718767SKővágó, Zoltán size_t write_len; 220*bcce2ea5SVolker Rümelin ssize_t start = (ssize_t)hw->pos_emul - hw->pending_emul; 221ff718767SKővágó, Zoltán if (start < 0) { 222ff718767SKővágó, Zoltán start += hw->size_emul; 223ff541499Smalc } 224ff718767SKővágó, Zoltán assert(start >= 0 && start < hw->size_emul); 2259399ef16SThomas Huth 226ff718767SKővágó, Zoltán write_len = MIN(MIN(hw->pending_emul, len), 227ff718767SKővágó, Zoltán hw->size_emul - start); 228bcf19777SThomas Huth 229ff718767SKővágó, Zoltán memcpy(buf, hw->buf_emul + start, write_len); 230ff718767SKővágó, Zoltán hw->pending_emul -= write_len; 231ff718767SKővágó, Zoltán len -= write_len; 232ff718767SKővágó, Zoltán buf += write_len; 233ff718767SKővágó, Zoltán } 234*bcce2ea5SVolker Rümelin } 235ff718767SKővágó, Zoltán 236ff718767SKővágó, Zoltán /* clear remaining buffer that we couldn't fill with data */ 237ff718767SKővágó, Zoltán if (len) { 238ff718767SKővágó, Zoltán memset(buf, 0, len); 239bcf19777SThomas Huth } 240ff541499Smalc } 24185571bc7Sbellard 242ef26632eSVolker Rümelin #define SDL_WRAPPER_FUNC(name, ret_type, args_decl, args) \ 243ff718767SKővágó, Zoltán static ret_type glue(sdl_, name)args_decl \ 244ff718767SKővágó, Zoltán { \ 245ff718767SKővágó, Zoltán ret_type ret; \ 246ff718767SKővágó, Zoltán \ 247ff718767SKővágó, Zoltán SDL_LockAudio(); \ 248ff718767SKővágó, Zoltán ret = glue(audio_generic_, name)args; \ 249ff718767SKővágó, Zoltán SDL_UnlockAudio(); \ 250ef26632eSVolker Rümelin \ 251ff718767SKővágó, Zoltán return ret; \ 252ff541499Smalc } 253ff541499Smalc 254ff718767SKővágó, Zoltán SDL_WRAPPER_FUNC(get_buffer_out, void *, (HWVoiceOut *hw, size_t *size), 255ef26632eSVolker Rümelin (hw, size)) 256fdc8c5f4SVolker Rümelin SDL_WRAPPER_FUNC(put_buffer_out, size_t, 257ef26632eSVolker Rümelin (HWVoiceOut *hw, void *buf, size_t size), (hw, buf, size)) 258ff718767SKővágó, Zoltán SDL_WRAPPER_FUNC(write, size_t, 259ef26632eSVolker Rümelin (HWVoiceOut *hw, void *buf, size_t size), (hw, buf, size)) 260ff718767SKővágó, Zoltán #undef SDL_WRAPPER_FUNC 2611d14ffa9Sbellard 2621d14ffa9Sbellard static void sdl_fini_out (HWVoiceOut *hw) 2631d14ffa9Sbellard { 2641d14ffa9Sbellard (void) hw; 2651d14ffa9Sbellard 26685571bc7Sbellard sdl_close (&glob_sdl); 26785571bc7Sbellard } 26885571bc7Sbellard 2695706db1dSKővágó, Zoltán static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, 2705706db1dSKővágó, Zoltán void *drv_opaque) 27185571bc7Sbellard { 2721d14ffa9Sbellard SDLVoiceOut *sdl = (SDLVoiceOut *) hw; 27385571bc7Sbellard SDLAudioState *s = &glob_sdl; 27485571bc7Sbellard SDL_AudioSpec req, obt; 2754ff9786cSStefan Weil int endianness; 2761d14ffa9Sbellard int err; 27785bc5852SKővágó, Zoltán AudioFormat effective_fmt; 2785a0926c2SVolker Rümelin AudiodevSdlPerDirectionOptions *spdo = s->dev->u.sdl.out; 2791ea879e5Smalc struct audsettings obt_as; 28085571bc7Sbellard 281c0fe3827Sbellard req.freq = as->freq; 2826c557ab9SSerge Ziryukin req.format = aud_to_sdlfmt (as->fmt); 283c0fe3827Sbellard req.channels = as->nchannels; 2845a0926c2SVolker Rümelin /* 2855a0926c2SVolker Rümelin * This is wrong. SDL samples are QEMU frames. The buffer size will be 2865a0926c2SVolker Rümelin * the requested buffer size multiplied by the number of channels. 2875a0926c2SVolker Rümelin */ 2885a0926c2SVolker Rümelin req.samples = audio_buffer_samples( 2895a0926c2SVolker Rümelin qapi_AudiodevSdlPerDirectionOptions_base(spdo), as, 11610); 29085571bc7Sbellard req.callback = sdl_callback; 29185571bc7Sbellard req.userdata = sdl; 29285571bc7Sbellard 2931d14ffa9Sbellard if (sdl_open (&req, &obt)) { 29485571bc7Sbellard return -1; 2951d14ffa9Sbellard } 29685571bc7Sbellard 2974ff9786cSStefan Weil err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness); 2981d14ffa9Sbellard if (err) { 2991d14ffa9Sbellard sdl_close (s); 3001d14ffa9Sbellard return -1; 3011d14ffa9Sbellard } 3021d14ffa9Sbellard 303c0fe3827Sbellard obt_as.freq = obt.freq; 304c0fe3827Sbellard obt_as.nchannels = obt.channels; 305c0fe3827Sbellard obt_as.fmt = effective_fmt; 3064ff9786cSStefan Weil obt_as.endianness = endianness; 307c0fe3827Sbellard 308d929eba5Sbellard audio_pcm_init_info (&hw->info, &obt_as); 3095a0926c2SVolker Rümelin hw->samples = (spdo->has_buffer_count ? spdo->buffer_count : 4) * 3105a0926c2SVolker Rümelin obt.samples; 31185571bc7Sbellard 31285571bc7Sbellard s->initialized = 1; 31385571bc7Sbellard s->exit = 0; 31485571bc7Sbellard return 0; 31585571bc7Sbellard } 31685571bc7Sbellard 317571a8c52SKővágó, Zoltán static void sdl_enable_out(HWVoiceOut *hw, bool enable) 31885571bc7Sbellard { 319571a8c52SKővágó, Zoltán SDL_PauseAudio(!enable); 32085571bc7Sbellard } 32185571bc7Sbellard 32271830221SKővágó, Zoltán static void *sdl_audio_init(Audiodev *dev) 32385571bc7Sbellard { 32485571bc7Sbellard SDLAudioState *s = &glob_sdl; 32581ebb07cSKővágó, Zoltán if (s->driver_created) { 32681ebb07cSKővágó, Zoltán sdl_logerr("Can't create multiple sdl backends\n"); 32781ebb07cSKővágó, Zoltán return NULL; 32881ebb07cSKővágó, Zoltán } 32985571bc7Sbellard 33085571bc7Sbellard if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { 3311d14ffa9Sbellard sdl_logerr ("SDL failed to initialize audio subsystem\n"); 33285571bc7Sbellard return NULL; 33385571bc7Sbellard } 33485571bc7Sbellard 33581ebb07cSKővágó, Zoltán s->driver_created = true; 33657dea553SKővágó, Zoltán s->dev = dev; 33785571bc7Sbellard return s; 33885571bc7Sbellard } 33985571bc7Sbellard 34085571bc7Sbellard static void sdl_audio_fini (void *opaque) 34185571bc7Sbellard { 34285571bc7Sbellard SDLAudioState *s = opaque; 34385571bc7Sbellard sdl_close (s); 34485571bc7Sbellard SDL_QuitSubSystem (SDL_INIT_AUDIO); 34581ebb07cSKővágó, Zoltán s->driver_created = false; 34657dea553SKővágó, Zoltán s->dev = NULL; 34785571bc7Sbellard } 34885571bc7Sbellard 34935f4b58cSblueswir1 static struct audio_pcm_ops sdl_pcm_ops = { 3501dd3e4d1SJuan Quintela .init_out = sdl_init_out, 3511dd3e4d1SJuan Quintela .fini_out = sdl_fini_out, 352fdc8c5f4SVolker Rümelin /* wrapper for audio_generic_write */ 353ff718767SKővágó, Zoltán .write = sdl_write, 354fdc8c5f4SVolker Rümelin /* wrapper for audio_generic_get_buffer_out */ 355ff718767SKővágó, Zoltán .get_buffer_out = sdl_get_buffer_out, 356fdc8c5f4SVolker Rümelin /* wrapper for audio_generic_put_buffer_out */ 357fdc8c5f4SVolker Rümelin .put_buffer_out = sdl_put_buffer_out, 358571a8c52SKővágó, Zoltán .enable_out = sdl_enable_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 .init = sdl_audio_init, 365bee37f32SJuan Quintela .fini = sdl_audio_fini, 366bee37f32SJuan Quintela .pcm_ops = &sdl_pcm_ops, 367bee37f32SJuan Quintela .can_be_default = 1, 368bee37f32SJuan Quintela .max_voices_out = 1, 369bee37f32SJuan Quintela .max_voices_in = 0, 370bee37f32SJuan Quintela .voice_size_out = sizeof (SDLVoiceOut), 371bee37f32SJuan Quintela .voice_size_in = 0 37285571bc7Sbellard }; 373d3893a39SGerd Hoffmann 374d3893a39SGerd Hoffmann static void register_audio_sdl(void) 375d3893a39SGerd Hoffmann { 376d3893a39SGerd Hoffmann audio_driver_register(&sdl_audio_driver); 377d3893a39SGerd Hoffmann } 378d3893a39SGerd Hoffmann type_init(register_audio_sdl); 379