mirror of https://github.com/proxmox/mirror_qemu
333 lines
7.8 KiB
C
333 lines
7.8 KiB
C
/*
|
|
* QEMU SDL audio output driver
|
|
*
|
|
* Copyright (c) 2004 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 <SDL.h>
|
|
#include <SDL_thread.h>
|
|
#include "vl.h"
|
|
|
|
#include "audio/audio_int.h"
|
|
|
|
typedef struct SDLVoice {
|
|
HWVoice hw;
|
|
} SDLVoice;
|
|
|
|
#define dolog(...) AUD_log ("sdl", __VA_ARGS__)
|
|
#ifdef DEBUG
|
|
#define ldebug(...) dolog (__VA_ARGS__)
|
|
#else
|
|
#define ldebug(...)
|
|
#endif
|
|
|
|
#define QC_SDL_SAMPLES "QEMU_SDL_SAMPLES"
|
|
|
|
#define errstr() SDL_GetError ()
|
|
|
|
static struct {
|
|
int nb_samples;
|
|
} conf = {
|
|
1024
|
|
};
|
|
|
|
struct SDLAudioState {
|
|
int exit;
|
|
SDL_mutex *mutex;
|
|
SDL_sem *sem;
|
|
int initialized;
|
|
} glob_sdl;
|
|
typedef struct SDLAudioState SDLAudioState;
|
|
|
|
static void sdl_hw_run (HWVoice *hw)
|
|
{
|
|
(void) hw;
|
|
}
|
|
|
|
static int sdl_lock (SDLAudioState *s)
|
|
{
|
|
if (SDL_LockMutex (s->mutex)) {
|
|
dolog ("SDL_LockMutex failed\nReason: %s\n", errstr ());
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sdl_unlock (SDLAudioState *s)
|
|
{
|
|
if (SDL_UnlockMutex (s->mutex)) {
|
|
dolog ("SDL_UnlockMutex failed\nReason: %s\n", errstr ());
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sdl_post (SDLAudioState *s)
|
|
{
|
|
if (SDL_SemPost (s->sem)) {
|
|
dolog ("SDL_SemPost failed\nReason: %s\n", errstr ());
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sdl_wait (SDLAudioState *s)
|
|
{
|
|
if (SDL_SemWait (s->sem)) {
|
|
dolog ("SDL_SemWait failed\nReason: %s\n", errstr ());
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sdl_unlock_and_post (SDLAudioState *s)
|
|
{
|
|
if (sdl_unlock (s))
|
|
return -1;
|
|
|
|
return sdl_post (s);
|
|
}
|
|
|
|
static int sdl_hw_write (SWVoice *sw, void *buf, int len)
|
|
{
|
|
int ret;
|
|
SDLAudioState *s = &glob_sdl;
|
|
sdl_lock (s);
|
|
ret = pcm_hw_write (sw, buf, len);
|
|
sdl_unlock_and_post (s);
|
|
return ret;
|
|
}
|
|
|
|
static int AUD_to_sdlfmt (audfmt_e fmt, int *shift)
|
|
{
|
|
*shift = 0;
|
|
switch (fmt) {
|
|
case AUD_FMT_S8: return AUDIO_S8;
|
|
case AUD_FMT_U8: return AUDIO_U8;
|
|
case AUD_FMT_S16: *shift = 1; return AUDIO_S16LSB;
|
|
case AUD_FMT_U16: *shift = 1; return AUDIO_U16LSB;
|
|
default:
|
|
dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
static int sdl_to_audfmt (int fmt)
|
|
{
|
|
switch (fmt) {
|
|
case AUDIO_S8: return AUD_FMT_S8;
|
|
case AUDIO_U8: return AUD_FMT_U8;
|
|
case AUDIO_S16LSB: return AUD_FMT_S16;
|
|
case AUDIO_U16LSB: return AUD_FMT_U16;
|
|
default:
|
|
dolog ("Internal logic error: Unrecognized SDL audio format %d\n"
|
|
"Aborting\n", fmt);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt)
|
|
{
|
|
int status;
|
|
|
|
status = SDL_OpenAudio (req, obt);
|
|
if (status) {
|
|
dolog ("SDL_OpenAudio failed\nReason: %s\n", errstr ());
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static void sdl_close (SDLAudioState *s)
|
|
{
|
|
if (s->initialized) {
|
|
sdl_lock (s);
|
|
s->exit = 1;
|
|
sdl_unlock_and_post (s);
|
|
SDL_PauseAudio (1);
|
|
SDL_CloseAudio ();
|
|
s->initialized = 0;
|
|
}
|
|
}
|
|
|
|
static void sdl_callback (void *opaque, Uint8 *buf, int len)
|
|
{
|
|
SDLVoice *sdl = opaque;
|
|
SDLAudioState *s = &glob_sdl;
|
|
HWVoice *hw = &sdl->hw;
|
|
int samples = len >> hw->shift;
|
|
|
|
if (s->exit) {
|
|
return;
|
|
}
|
|
|
|
while (samples) {
|
|
int to_mix, live, decr;
|
|
|
|
/* dolog ("in callback samples=%d\n", samples); */
|
|
sdl_wait (s);
|
|
if (s->exit) {
|
|
return;
|
|
}
|
|
|
|
sdl_lock (s);
|
|
live = pcm_hw_get_live (hw);
|
|
if (live <= 0)
|
|
goto again;
|
|
|
|
/* dolog ("in callback live=%d\n", live); */
|
|
to_mix = audio_MIN (samples, live);
|
|
decr = to_mix;
|
|
while (to_mix) {
|
|
int chunk = audio_MIN (to_mix, hw->samples - hw->rpos);
|
|
st_sample_t *src = hw->mix_buf + hw->rpos;
|
|
|
|
/* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */
|
|
hw->clip (buf, src, chunk);
|
|
memset (src, 0, chunk * sizeof (st_sample_t));
|
|
hw->rpos = (hw->rpos + chunk) % hw->samples;
|
|
to_mix -= chunk;
|
|
buf += chunk << hw->shift;
|
|
}
|
|
samples -= decr;
|
|
pcm_hw_dec_live (hw, decr);
|
|
|
|
again:
|
|
sdl_unlock (s);
|
|
}
|
|
/* dolog ("done len=%d\n", len); */
|
|
}
|
|
|
|
static void sdl_hw_fini (HWVoice *hw)
|
|
{
|
|
ldebug ("sdl_hw_fini %d fixed=%d\n",
|
|
glob_sdl.initialized, audio_conf.fixed_format);
|
|
sdl_close (&glob_sdl);
|
|
}
|
|
|
|
static int sdl_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt)
|
|
{
|
|
SDLVoice *sdl = (SDLVoice *) hw;
|
|
SDLAudioState *s = &glob_sdl;
|
|
SDL_AudioSpec req, obt;
|
|
int shift;
|
|
|
|
ldebug ("sdl_hw_init %d freq=%d fixed=%d\n",
|
|
s->initialized, freq, audio_conf.fixed_format);
|
|
|
|
if (nchannels != 2) {
|
|
dolog ("Bogus channel count %d\n", nchannels);
|
|
return -1;
|
|
}
|
|
|
|
req.freq = freq;
|
|
req.format = AUD_to_sdlfmt (fmt, &shift);
|
|
req.channels = nchannels;
|
|
req.samples = conf.nb_samples;
|
|
shift <<= nchannels == 2;
|
|
|
|
req.callback = sdl_callback;
|
|
req.userdata = sdl;
|
|
|
|
if (sdl_open (&req, &obt))
|
|
return -1;
|
|
|
|
hw->freq = obt.freq;
|
|
hw->fmt = sdl_to_audfmt (obt.format);
|
|
hw->nchannels = obt.channels;
|
|
hw->bufsize = obt.samples << shift;
|
|
|
|
s->initialized = 1;
|
|
s->exit = 0;
|
|
SDL_PauseAudio (0);
|
|
return 0;
|
|
}
|
|
|
|
static int sdl_hw_ctl (HWVoice *hw, int cmd, ...)
|
|
{
|
|
(void) hw;
|
|
|
|
switch (cmd) {
|
|
case VOICE_ENABLE:
|
|
SDL_PauseAudio (0);
|
|
break;
|
|
|
|
case VOICE_DISABLE:
|
|
SDL_PauseAudio (1);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void *sdl_audio_init (void)
|
|
{
|
|
SDLAudioState *s = &glob_sdl;
|
|
conf.nb_samples = audio_get_conf_int (QC_SDL_SAMPLES, conf.nb_samples);
|
|
|
|
if (SDL_InitSubSystem (SDL_INIT_AUDIO)) {
|
|
dolog ("SDL failed to initialize audio subsystem\nReason: %s\n",
|
|
errstr ());
|
|
return NULL;
|
|
}
|
|
|
|
s->mutex = SDL_CreateMutex ();
|
|
if (!s->mutex) {
|
|
dolog ("Failed to create SDL mutex\nReason: %s\n", errstr ());
|
|
SDL_QuitSubSystem (SDL_INIT_AUDIO);
|
|
return NULL;
|
|
}
|
|
|
|
s->sem = SDL_CreateSemaphore (0);
|
|
if (!s->sem) {
|
|
dolog ("Failed to create SDL semaphore\nReason: %s\n", errstr ());
|
|
SDL_DestroyMutex (s->mutex);
|
|
SDL_QuitSubSystem (SDL_INIT_AUDIO);
|
|
return NULL;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static void sdl_audio_fini (void *opaque)
|
|
{
|
|
SDLAudioState *s = opaque;
|
|
sdl_close (s);
|
|
SDL_DestroySemaphore (s->sem);
|
|
SDL_DestroyMutex (s->mutex);
|
|
SDL_QuitSubSystem (SDL_INIT_AUDIO);
|
|
}
|
|
|
|
struct pcm_ops sdl_pcm_ops = {
|
|
sdl_hw_init,
|
|
sdl_hw_fini,
|
|
sdl_hw_run,
|
|
sdl_hw_write,
|
|
sdl_hw_ctl
|
|
};
|
|
|
|
struct audio_output_driver sdl_output_driver = {
|
|
"sdl",
|
|
sdl_audio_init,
|
|
sdl_audio_fini,
|
|
&sdl_pcm_ops,
|
|
1,
|
|
1,
|
|
sizeof (SDLVoice)
|
|
};
|