/* * QEMU SDL audio driver * * Copyright (c) 2004-2005 Vassili Karpov (malc) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "qemu/osdep.h" #include <SDL.h> #include <SDL_thread.h> #include "qemu/module.h" #include "audio.h" #ifndef _WIN32 #ifdef __sun__ #define _POSIX_PTHREAD_SEMANTICS 1 #elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) #include <pthread.h> #endif #endif #define AUDIO_CAP "sdl" #include "audio_int.h" typedef struct SDLVoiceOut { HWVoiceOut hw; int exit; int initialized; Audiodev *dev; SDL_AudioDeviceID devid; } SDLVoiceOut; typedef struct SDLVoiceIn { HWVoiceIn hw; int exit; int initialized; Audiodev *dev; SDL_AudioDeviceID devid; } SDLVoiceIn; static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...) { va_list ap; va_start (ap, fmt); AUD_vlog (AUDIO_CAP, fmt, ap); va_end (ap); AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ()); } static int aud_to_sdlfmt (AudioFormat fmt) { switch (fmt) { case AUDIO_FORMAT_S8: return AUDIO_S8; case AUDIO_FORMAT_U8: return AUDIO_U8; case AUDIO_FORMAT_S16: return AUDIO_S16LSB; case AUDIO_FORMAT_U16: return AUDIO_U16LSB; case AUDIO_FORMAT_S32: return AUDIO_S32LSB; /* no unsigned 32-bit support in SDL */ case AUDIO_FORMAT_F32: return AUDIO_F32LSB; default: dolog ("Internal logic error: Bad audio format %d\n", fmt); #ifdef DEBUG_AUDIO abort (); #endif return AUDIO_U8; } } static int sdl_to_audfmt(int sdlfmt, AudioFormat *fmt, int *endianness) { switch (sdlfmt) { case AUDIO_S8: *endianness = 0; *fmt = AUDIO_FORMAT_S8; break; case AUDIO_U8: *endianness = 0; *fmt = AUDIO_FORMAT_U8; break; case AUDIO_S16LSB: *endianness = 0; *fmt = AUDIO_FORMAT_S16; break; case AUDIO_U16LSB: *endianness = 0; *fmt = AUDIO_FORMAT_U16; break; case AUDIO_S16MSB: *endianness = 1; *fmt = AUDIO_FORMAT_S16; break; case AUDIO_U16MSB: *endianness = 1; *fmt = AUDIO_FORMAT_U16; break; case AUDIO_S32LSB: *endianness = 0; *fmt = AUDIO_FORMAT_S32; break; case AUDIO_S32MSB: *endianness = 1; *fmt = AUDIO_FORMAT_S32; break; case AUDIO_F32LSB: *endianness = 0; *fmt = AUDIO_FORMAT_F32; break; case AUDIO_F32MSB: *endianness = 1; *fmt = AUDIO_FORMAT_F32; break; default: dolog ("Unrecognized SDL audio format %d\n", sdlfmt); return -1; } return 0; } static SDL_AudioDeviceID sdl_open(SDL_AudioSpec *req, SDL_AudioSpec *obt, int rec) { SDL_AudioDeviceID devid; #ifndef _WIN32 int err; sigset_t new, old; /* Make sure potential threads created by SDL don't hog signals. */ err = sigfillset (&new); if (err) { dolog ("sdl_open: sigfillset failed: %s\n", strerror (errno)); return 0; } err = pthread_sigmask (SIG_BLOCK, &new, &old); if (err) { dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err)); return 0; } #endif devid = SDL_OpenAudioDevice(NULL, rec, req, obt, 0); if (!devid) { sdl_logerr("SDL_OpenAudioDevice for %s failed\n", rec ? "recording" : "playback"); } #ifndef _WIN32 err = pthread_sigmask (SIG_SETMASK, &old, NULL); if (err) { dolog ("sdl_open: pthread_sigmask (restore) failed: %s\n", strerror (errno)); /* We have failed to restore original signal mask, all bets are off, so exit the process */ exit (EXIT_FAILURE); } #endif return devid; } static void sdl_close_out(SDLVoiceOut *sdl) { if (sdl->initialized) { SDL_LockAudioDevice(sdl->devid); sdl->exit = 1; SDL_UnlockAudioDevice(sdl->devid); SDL_PauseAudioDevice(sdl->devid, 1); sdl->initialized = 0; } if (sdl->devid) { SDL_CloseAudioDevice(sdl->devid); sdl->devid = 0; } } static void sdl_callback_out(void *opaque, Uint8 *buf, int len) { SDLVoiceOut *sdl = opaque; HWVoiceOut *hw = &sdl->hw; if (!sdl->exit) { /* dolog("callback_out: len=%d avail=%zu\n", len, hw->pending_emul); */ while (hw->pending_emul && len) { size_t write_len; ssize_t start = (ssize_t)hw->pos_emul - hw->pending_emul; if (start < 0) { start += hw->size_emul; } assert(start >= 0 && start < hw->size_emul); write_len = MIN(MIN(hw->pending_emul, len), hw->size_emul - start); memcpy(buf, hw->buf_emul + start, write_len); hw->pending_emul -= write_len; len -= write_len; buf += write_len; } } /* clear remaining buffer that we couldn't fill with data */ if (len) { audio_pcm_info_clear_buf(&hw->info, buf, len / hw->info.bytes_per_frame); } } static void sdl_close_in(SDLVoiceIn *sdl) { if (sdl->initialized) { SDL_LockAudioDevice(sdl->devid); sdl->exit = 1; SDL_UnlockAudioDevice(sdl->devid); SDL_PauseAudioDevice(sdl->devid, 1); sdl->initialized = 0; } if (sdl->devid) { SDL_CloseAudioDevice(sdl->devid); sdl->devid = 0; } } static void sdl_callback_in(void *opaque, Uint8 *buf, int len) { SDLVoiceIn *sdl = opaque; HWVoiceIn *hw = &sdl->hw; if (sdl->exit) { return; } /* dolog("callback_in: len=%d pending=%zu\n", len, hw->pending_emul); */ while (hw->pending_emul < hw->size_emul && len) { size_t read_len = MIN(len, MIN(hw->size_emul - hw->pos_emul, hw->size_emul - hw->pending_emul)); memcpy(hw->buf_emul + hw->pos_emul, buf, read_len); hw->pending_emul += read_len; hw->pos_emul = (hw->pos_emul + read_len) % hw->size_emul; len -= read_len; buf += read_len; } } #define SDL_WRAPPER_FUNC(name, ret_type, args_decl, args, dir) \ static ret_type glue(sdl_, name)args_decl \ { \ ret_type ret; \ glue(SDLVoice, dir) *sdl = (glue(SDLVoice, dir) *)hw; \ \ SDL_LockAudioDevice(sdl->devid); \ ret = glue(audio_generic_, name)args; \ SDL_UnlockAudioDevice(sdl->devid); \ \ return ret; \ } #define SDL_WRAPPER_VOID_FUNC(name, args_decl, args, dir) \ static void glue(sdl_, name)args_decl \ { \ glue(SDLVoice, dir) *sdl = (glue(SDLVoice, dir) *)hw; \ \ SDL_LockAudioDevice(sdl->devid); \ glue(audio_generic_, name)args; \ SDL_UnlockAudioDevice(sdl->devid); \ } SDL_WRAPPER_FUNC(get_buffer_out, void *, (HWVoiceOut *hw, size_t *size), (hw, size), Out) SDL_WRAPPER_FUNC(put_buffer_out, size_t, (HWVoiceOut *hw, void *buf, size_t size), (hw, buf, size), Out) SDL_WRAPPER_FUNC(write, size_t, (HWVoiceOut *hw, void *buf, size_t size), (hw, buf, size), Out) SDL_WRAPPER_FUNC(read, size_t, (HWVoiceIn *hw, void *buf, size_t size), (hw, buf, size), In) SDL_WRAPPER_FUNC(get_buffer_in, void *, (HWVoiceIn *hw, size_t *size), (hw, size), In) SDL_WRAPPER_VOID_FUNC(put_buffer_in, (HWVoiceIn *hw, void *buf, size_t size), (hw, buf, size), In) #undef SDL_WRAPPER_FUNC #undef SDL_WRAPPER_VOID_FUNC static void sdl_fini_out(HWVoiceOut *hw) { SDLVoiceOut *sdl = (SDLVoiceOut *)hw; sdl_close_out(sdl); } static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) { SDLVoiceOut *sdl = (SDLVoiceOut *)hw; SDL_AudioSpec req, obt; int endianness; int err; AudioFormat effective_fmt; Audiodev *dev = drv_opaque; AudiodevSdlPerDirectionOptions *spdo = dev->u.sdl.out; struct audsettings obt_as; req.freq = as->freq; req.format = aud_to_sdlfmt (as->fmt); req.channels = as->nchannels; /* * This is wrong. SDL samples are QEMU frames. The buffer size will be * the requested buffer size multiplied by the number of channels. */ req.samples = audio_buffer_samples( qapi_AudiodevSdlPerDirectionOptions_base(spdo), as, 11610); req.callback = sdl_callback_out; req.userdata = sdl; sdl->dev = dev; sdl->devid = sdl_open(&req, &obt, 0); if (!sdl->devid) { return -1; } err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness); if (err) { sdl_close_out(sdl); return -1; } obt_as.freq = obt.freq; obt_as.nchannels = obt.channels; obt_as.fmt = effective_fmt; obt_as.endianness = endianness; audio_pcm_init_info (&hw->info, &obt_as); hw->samples = (spdo->has_buffer_count ? spdo->buffer_count : 4) * obt.samples; sdl->initialized = 1; sdl->exit = 0; return 0; } static void sdl_enable_out(HWVoiceOut *hw, bool enable) { SDLVoiceOut *sdl = (SDLVoiceOut *)hw; SDL_PauseAudioDevice(sdl->devid, !enable); } static void sdl_fini_in(HWVoiceIn *hw) { SDLVoiceIn *sdl = (SDLVoiceIn *)hw; sdl_close_in(sdl); } static int sdl_init_in(HWVoiceIn *hw, audsettings *as, void *drv_opaque) { SDLVoiceIn *sdl = (SDLVoiceIn *)hw; SDL_AudioSpec req, obt; int endianness; int err; AudioFormat effective_fmt; Audiodev *dev = drv_opaque; AudiodevSdlPerDirectionOptions *spdo = dev->u.sdl.in; struct audsettings obt_as; req.freq = as->freq; req.format = aud_to_sdlfmt(as->fmt); req.channels = as->nchannels; /* SDL samples are QEMU frames */ req.samples = audio_buffer_frames( qapi_AudiodevSdlPerDirectionOptions_base(spdo), as, 11610); req.callback = sdl_callback_in; req.userdata = sdl; sdl->dev = dev; sdl->devid = sdl_open(&req, &obt, 1); if (!sdl->devid) { return -1; } err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness); if (err) { sdl_close_in(sdl); return -1; } obt_as.freq = obt.freq; obt_as.nchannels = obt.channels; obt_as.fmt = effective_fmt; obt_as.endianness = endianness; audio_pcm_init_info(&hw->info, &obt_as); hw->samples = (spdo->has_buffer_count ? spdo->buffer_count : 4) * obt.samples; hw->size_emul = hw->samples * hw->info.bytes_per_frame; hw->buf_emul = g_malloc(hw->size_emul); hw->pos_emul = hw->pending_emul = 0; sdl->initialized = 1; sdl->exit = 0; return 0; } static void sdl_enable_in(HWVoiceIn *hw, bool enable) { SDLVoiceIn *sdl = (SDLVoiceIn *)hw; SDL_PauseAudioDevice(sdl->devid, !enable); } static void *sdl_audio_init(Audiodev *dev) { if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { sdl_logerr ("SDL failed to initialize audio subsystem\n"); return NULL; } return dev; } static void sdl_audio_fini (void *opaque) { SDL_QuitSubSystem (SDL_INIT_AUDIO); } static struct audio_pcm_ops sdl_pcm_ops = { .init_out = sdl_init_out, .fini_out = sdl_fini_out, /* wrapper for audio_generic_write */ .write = sdl_write, /* wrapper for audio_generic_get_buffer_out */ .get_buffer_out = sdl_get_buffer_out, /* wrapper for audio_generic_put_buffer_out */ .put_buffer_out = sdl_put_buffer_out, .enable_out = sdl_enable_out, .init_in = sdl_init_in, .fini_in = sdl_fini_in, /* wrapper for audio_generic_read */ .read = sdl_read, /* wrapper for audio_generic_get_buffer_in */ .get_buffer_in = sdl_get_buffer_in, /* wrapper for audio_generic_put_buffer_in */ .put_buffer_in = sdl_put_buffer_in, .enable_in = sdl_enable_in, }; static struct audio_driver sdl_audio_driver = { .name = "sdl", .descr = "SDL http://www.libsdl.org", .init = sdl_audio_init, .fini = sdl_audio_fini, .pcm_ops = &sdl_pcm_ops, .can_be_default = 1, .max_voices_out = INT_MAX, .max_voices_in = INT_MAX, .voice_size_out = sizeof(SDLVoiceOut), .voice_size_in = sizeof(SDLVoiceIn), }; static void register_audio_sdl(void) { audio_driver_register(&sdl_audio_driver); } type_init(register_audio_sdl);