1 |
/* |
/* |
2 |
* QEMU SDL audio output driver |
* QEMU SDL audio driver |
3 |
* |
* |
4 |
* Copyright (c) 2004 Vassili Karpov (malc) |
* Copyright (c) 2004-2005 Vassili Karpov (malc) |
5 |
* |
* |
6 |
* Permission is hereby granted, free of charge, to any person obtaining a copy |
* Permission is hereby granted, free of charge, to any person obtaining a copy |
7 |
* of this software and associated documentation files (the "Software"), to deal |
* of this software and associated documentation files (the "Software"), to deal |
8 |
* in the Software without restriction, including without limitation the rights |
* in the Software without restriction, including without limitation the rights |
25 |
#include <SDL_thread.h> |
#include <SDL_thread.h> |
26 |
#include "vl.h" |
#include "vl.h" |
27 |
|
|
28 |
#include "audio/audio_int.h" |
#define AUDIO_CAP "sdl" |
29 |
|
#include "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" |
|
30 |
|
|
31 |
#define errstr() SDL_GetError () |
typedef struct SDLVoiceOut { |
32 |
|
HWVoiceOut hw; |
33 |
|
int live; |
34 |
|
int rpos; |
35 |
|
int decr; |
36 |
|
} SDLVoiceOut; |
37 |
|
|
38 |
static struct { |
static struct { |
39 |
int nb_samples; |
int nb_samples; |
49 |
} glob_sdl; |
} glob_sdl; |
50 |
typedef struct SDLAudioState SDLAudioState; |
typedef struct SDLAudioState SDLAudioState; |
51 |
|
|
52 |
static void sdl_hw_run (HWVoice *hw) |
static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...) |
53 |
{ |
{ |
54 |
(void) hw; |
va_list ap; |
55 |
|
|
56 |
|
va_start (ap, fmt); |
57 |
|
AUD_vlog (AUDIO_CAP, fmt, ap); |
58 |
|
va_end (ap); |
59 |
|
|
60 |
|
AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ()); |
61 |
} |
} |
62 |
|
|
63 |
static int sdl_lock (SDLAudioState *s) |
static int sdl_lock (SDLAudioState *s, const char *forfn) |
64 |
{ |
{ |
65 |
if (SDL_LockMutex (s->mutex)) { |
if (SDL_LockMutex (s->mutex)) { |
66 |
dolog ("SDL_LockMutex failed\nReason: %s\n", errstr ()); |
sdl_logerr ("SDL_LockMutex for %s failed\n", forfn); |
67 |
return -1; |
return -1; |
68 |
} |
} |
69 |
return 0; |
return 0; |
70 |
} |
} |
71 |
|
|
72 |
static int sdl_unlock (SDLAudioState *s) |
static int sdl_unlock (SDLAudioState *s, const char *forfn) |
73 |
{ |
{ |
74 |
if (SDL_UnlockMutex (s->mutex)) { |
if (SDL_UnlockMutex (s->mutex)) { |
75 |
dolog ("SDL_UnlockMutex failed\nReason: %s\n", errstr ()); |
sdl_logerr ("SDL_UnlockMutex for %s failed\n", forfn); |
76 |
return -1; |
return -1; |
77 |
} |
} |
78 |
return 0; |
return 0; |
79 |
} |
} |
80 |
|
|
81 |
static int sdl_post (SDLAudioState *s) |
static int sdl_post (SDLAudioState *s, const char *forfn) |
82 |
{ |
{ |
83 |
if (SDL_SemPost (s->sem)) { |
if (SDL_SemPost (s->sem)) { |
84 |
dolog ("SDL_SemPost failed\nReason: %s\n", errstr ()); |
sdl_logerr ("SDL_SemPost for %s failed\n", forfn); |
85 |
return -1; |
return -1; |
86 |
} |
} |
87 |
return 0; |
return 0; |
88 |
} |
} |
89 |
|
|
90 |
static int sdl_wait (SDLAudioState *s) |
static int sdl_wait (SDLAudioState *s, const char *forfn) |
91 |
{ |
{ |
92 |
if (SDL_SemWait (s->sem)) { |
if (SDL_SemWait (s->sem)) { |
93 |
dolog ("SDL_SemWait failed\nReason: %s\n", errstr ()); |
sdl_logerr ("SDL_SemWait for %s failed\n", forfn); |
94 |
return -1; |
return -1; |
95 |
} |
} |
96 |
return 0; |
return 0; |
97 |
} |
} |
98 |
|
|
99 |
static int sdl_unlock_and_post (SDLAudioState *s) |
static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn) |
100 |
{ |
{ |
101 |
if (sdl_unlock (s)) |
if (sdl_unlock (s, forfn)) { |
102 |
return -1; |
return -1; |
103 |
|
} |
104 |
|
|
105 |
return sdl_post (s); |
return sdl_post (s, forfn); |
|
} |
|
|
|
|
|
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; |
|
106 |
} |
} |
107 |
|
|
108 |
static int AUD_to_sdlfmt (audfmt_e fmt, int *shift) |
static int aud_to_sdlfmt (audfmt_e fmt, int *shift) |
109 |
{ |
{ |
|
*shift = 0; |
|
110 |
switch (fmt) { |
switch (fmt) { |
111 |
case AUD_FMT_S8: return AUDIO_S8; |
case AUD_FMT_S8: |
112 |
case AUD_FMT_U8: return AUDIO_U8; |
*shift = 0; |
113 |
case AUD_FMT_S16: *shift = 1; return AUDIO_S16LSB; |
return AUDIO_S8; |
114 |
case AUD_FMT_U16: *shift = 1; return AUDIO_U16LSB; |
|
115 |
|
case AUD_FMT_U8: |
116 |
|
*shift = 0; |
117 |
|
return AUDIO_U8; |
118 |
|
|
119 |
|
case AUD_FMT_S16: |
120 |
|
*shift = 1; |
121 |
|
return AUDIO_S16LSB; |
122 |
|
|
123 |
|
case AUD_FMT_U16: |
124 |
|
*shift = 1; |
125 |
|
return AUDIO_U16LSB; |
126 |
|
|
127 |
default: |
default: |
128 |
dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt); |
dolog ("Internal logic error: Bad audio format %d\n", fmt); |
129 |
exit (EXIT_FAILURE); |
#ifdef DEBUG_AUDIO |
130 |
|
abort (); |
131 |
|
#endif |
132 |
|
return AUDIO_U8; |
133 |
} |
} |
134 |
} |
} |
135 |
|
|
136 |
static int sdl_to_audfmt (int fmt) |
static int sdl_to_audfmt (int sdlfmt, audfmt_e *fmt, int *endianess) |
137 |
{ |
{ |
138 |
switch (fmt) { |
switch (sdlfmt) { |
139 |
case AUDIO_S8: return AUD_FMT_S8; |
case AUDIO_S8: |
140 |
case AUDIO_U8: return AUD_FMT_U8; |
*endianess = 0; |
141 |
case AUDIO_S16LSB: return AUD_FMT_S16; |
*fmt = AUD_FMT_S8; |
142 |
case AUDIO_U16LSB: return AUD_FMT_U16; |
break; |
143 |
|
|
144 |
|
case AUDIO_U8: |
145 |
|
*endianess = 0; |
146 |
|
*fmt = AUD_FMT_U8; |
147 |
|
break; |
148 |
|
|
149 |
|
case AUDIO_S16LSB: |
150 |
|
*endianess = 0; |
151 |
|
*fmt = AUD_FMT_S16; |
152 |
|
break; |
153 |
|
|
154 |
|
case AUDIO_U16LSB: |
155 |
|
*endianess = 0; |
156 |
|
*fmt = AUD_FMT_U16; |
157 |
|
break; |
158 |
|
|
159 |
|
case AUDIO_S16MSB: |
160 |
|
*endianess = 1; |
161 |
|
*fmt = AUD_FMT_S16; |
162 |
|
break; |
163 |
|
|
164 |
|
case AUDIO_U16MSB: |
165 |
|
*endianess = 1; |
166 |
|
*fmt = AUD_FMT_U16; |
167 |
|
break; |
168 |
|
|
169 |
default: |
default: |
170 |
dolog ("Internal logic error: Unrecognized SDL audio format %d\n" |
dolog ("Unrecognized SDL audio format %d\n", sdlfmt); |
171 |
"Aborting\n", fmt); |
return -1; |
|
exit (EXIT_FAILURE); |
|
172 |
} |
} |
173 |
|
|
174 |
|
return 0; |
175 |
} |
} |
176 |
|
|
177 |
static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) |
static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) |
180 |
|
|
181 |
status = SDL_OpenAudio (req, obt); |
status = SDL_OpenAudio (req, obt); |
182 |
if (status) { |
if (status) { |
183 |
dolog ("SDL_OpenAudio failed\nReason: %s\n", errstr ()); |
sdl_logerr ("SDL_OpenAudio failed\n"); |
184 |
} |
} |
185 |
return status; |
return status; |
186 |
} |
} |
188 |
static void sdl_close (SDLAudioState *s) |
static void sdl_close (SDLAudioState *s) |
189 |
{ |
{ |
190 |
if (s->initialized) { |
if (s->initialized) { |
191 |
sdl_lock (s); |
sdl_lock (s, "sdl_close"); |
192 |
s->exit = 1; |
s->exit = 1; |
193 |
sdl_unlock_and_post (s); |
sdl_unlock_and_post (s, "sdl_close"); |
194 |
SDL_PauseAudio (1); |
SDL_PauseAudio (1); |
195 |
SDL_CloseAudio (); |
SDL_CloseAudio (); |
196 |
s->initialized = 0; |
s->initialized = 0; |
199 |
|
|
200 |
static void sdl_callback (void *opaque, Uint8 *buf, int len) |
static void sdl_callback (void *opaque, Uint8 *buf, int len) |
201 |
{ |
{ |
202 |
SDLVoice *sdl = opaque; |
SDLVoiceOut *sdl = opaque; |
203 |
SDLAudioState *s = &glob_sdl; |
SDLAudioState *s = &glob_sdl; |
204 |
HWVoice *hw = &sdl->hw; |
HWVoiceOut *hw = &sdl->hw; |
205 |
int samples = len >> hw->shift; |
int samples = len >> hw->info.shift; |
206 |
|
|
207 |
if (s->exit) { |
if (s->exit) { |
208 |
return; |
return; |
209 |
} |
} |
210 |
|
|
211 |
while (samples) { |
while (samples) { |
212 |
int to_mix, live, decr; |
int to_mix, decr; |
213 |
|
|
214 |
/* dolog ("in callback samples=%d\n", samples); */ |
/* dolog ("in callback samples=%d\n", samples); */ |
215 |
sdl_wait (s); |
sdl_wait (s, "sdl_callback"); |
216 |
if (s->exit) { |
if (s->exit) { |
217 |
return; |
return; |
218 |
} |
} |
219 |
|
|
220 |
sdl_lock (s); |
if (sdl_lock (s, "sdl_callback")) { |
221 |
live = pcm_hw_get_live (hw); |
return; |
222 |
if (live <= 0) |
} |
223 |
|
|
224 |
|
if (audio_bug (AUDIO_FUNC, sdl->live < 0 || sdl->live > hw->samples)) { |
225 |
|
dolog ("sdl->live=%d hw->samples=%d\n", |
226 |
|
sdl->live, hw->samples); |
227 |
|
return; |
228 |
|
} |
229 |
|
|
230 |
|
if (!sdl->live) { |
231 |
goto again; |
goto again; |
232 |
|
} |
233 |
|
|
234 |
/* dolog ("in callback live=%d\n", live); */ |
/* dolog ("in callback live=%d\n", live); */ |
235 |
to_mix = audio_MIN (samples, live); |
to_mix = audio_MIN (samples, sdl->live); |
236 |
decr = to_mix; |
decr = to_mix; |
237 |
while (to_mix) { |
while (to_mix) { |
238 |
int chunk = audio_MIN (to_mix, hw->samples - hw->rpos); |
int chunk = audio_MIN (to_mix, hw->samples - hw->rpos); |
240 |
|
|
241 |
/* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */ |
/* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */ |
242 |
hw->clip (buf, src, chunk); |
hw->clip (buf, src, chunk); |
243 |
memset (src, 0, chunk * sizeof (st_sample_t)); |
mixeng_clear (src, chunk); |
244 |
hw->rpos = (hw->rpos + chunk) % hw->samples; |
sdl->rpos = (sdl->rpos + chunk) % hw->samples; |
245 |
to_mix -= chunk; |
to_mix -= chunk; |
246 |
buf += chunk << hw->shift; |
buf += chunk << hw->info.shift; |
247 |
} |
} |
248 |
samples -= decr; |
samples -= decr; |
249 |
pcm_hw_dec_live (hw, decr); |
sdl->live -= decr; |
250 |
|
sdl->decr += decr; |
251 |
|
|
252 |
again: |
again: |
253 |
sdl_unlock (s); |
if (sdl_unlock (s, "sdl_callback")) { |
254 |
|
return; |
255 |
|
} |
256 |
} |
} |
257 |
/* dolog ("done len=%d\n", len); */ |
/* dolog ("done len=%d\n", len); */ |
258 |
} |
} |
259 |
|
|
260 |
static void sdl_hw_fini (HWVoice *hw) |
static int sdl_write_out (SWVoiceOut *sw, void *buf, int len) |
261 |
{ |
{ |
262 |
ldebug ("sdl_hw_fini %d fixed=%d\n", |
return audio_pcm_sw_write (sw, buf, len); |
263 |
glob_sdl.initialized, audio_state.fixed_format); |
} |
264 |
|
|
265 |
|
static int sdl_run_out (HWVoiceOut *hw) |
266 |
|
{ |
267 |
|
int decr, live; |
268 |
|
SDLVoiceOut *sdl = (SDLVoiceOut *) hw; |
269 |
|
SDLAudioState *s = &glob_sdl; |
270 |
|
|
271 |
|
if (sdl_lock (s, "sdl_callback")) { |
272 |
|
return 0; |
273 |
|
} |
274 |
|
|
275 |
|
live = audio_pcm_hw_get_live_out (hw); |
276 |
|
|
277 |
|
if (sdl->decr > live) { |
278 |
|
ldebug ("sdl->decr %d live %d sdl->live %d\n", |
279 |
|
sdl->decr, |
280 |
|
live, |
281 |
|
sdl->live); |
282 |
|
} |
283 |
|
|
284 |
|
decr = audio_MIN (sdl->decr, live); |
285 |
|
sdl->decr -= decr; |
286 |
|
|
287 |
|
sdl->live = live - decr; |
288 |
|
hw->rpos = sdl->rpos; |
289 |
|
|
290 |
|
if (sdl->live > 0) { |
291 |
|
sdl_unlock_and_post (s, "sdl_callback"); |
292 |
|
} |
293 |
|
else { |
294 |
|
sdl_unlock (s, "sdl_callback"); |
295 |
|
} |
296 |
|
return decr; |
297 |
|
} |
298 |
|
|
299 |
|
static void sdl_fini_out (HWVoiceOut *hw) |
300 |
|
{ |
301 |
|
(void) hw; |
302 |
|
|
303 |
sdl_close (&glob_sdl); |
sdl_close (&glob_sdl); |
304 |
} |
} |
305 |
|
|
306 |
static int sdl_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) |
static int sdl_init_out (HWVoiceOut *hw, int freq, int nchannels, audfmt_e fmt) |
307 |
{ |
{ |
308 |
SDLVoice *sdl = (SDLVoice *) hw; |
SDLVoiceOut *sdl = (SDLVoiceOut *) hw; |
309 |
SDLAudioState *s = &glob_sdl; |
SDLAudioState *s = &glob_sdl; |
310 |
SDL_AudioSpec req, obt; |
SDL_AudioSpec req, obt; |
311 |
int shift; |
int shift; |
312 |
|
int endianess; |
313 |
ldebug ("sdl_hw_init %d freq=%d fixed=%d\n", |
int err; |
314 |
s->initialized, freq, audio_state.fixed_format); |
audfmt_e effective_fmt; |
315 |
|
|
316 |
if (nchannels != 2) { |
if (nchannels != 2) { |
317 |
dolog ("Bogus channel count %d\n", nchannels); |
dolog ("Can not init DAC. Bogus channel count %d\n", nchannels); |
318 |
return -1; |
return -1; |
319 |
} |
} |
320 |
|
|
321 |
req.freq = freq; |
req.freq = freq; |
322 |
req.format = AUD_to_sdlfmt (fmt, &shift); |
req.format = aud_to_sdlfmt (fmt, &shift); |
323 |
req.channels = nchannels; |
req.channels = nchannels; |
324 |
req.samples = conf.nb_samples; |
req.samples = conf.nb_samples; |
325 |
shift <<= nchannels == 2; |
shift <<= nchannels == 2; |
327 |
req.callback = sdl_callback; |
req.callback = sdl_callback; |
328 |
req.userdata = sdl; |
req.userdata = sdl; |
329 |
|
|
330 |
if (sdl_open (&req, &obt)) |
if (sdl_open (&req, &obt)) { |
331 |
|
return -1; |
332 |
|
} |
333 |
|
|
334 |
|
err = sdl_to_audfmt (obt.format, &effective_fmt, &endianess); |
335 |
|
if (err) { |
336 |
|
sdl_close (s); |
337 |
return -1; |
return -1; |
338 |
|
} |
339 |
|
|
340 |
hw->freq = obt.freq; |
audio_pcm_init_info ( |
341 |
hw->fmt = sdl_to_audfmt (obt.format); |
&hw->info, |
342 |
hw->nchannels = obt.channels; |
obt.freq, |
343 |
|
obt.channels, |
344 |
|
effective_fmt, |
345 |
|
audio_need_to_swap_endian (endianess) |
346 |
|
); |
347 |
hw->bufsize = obt.samples << shift; |
hw->bufsize = obt.samples << shift; |
348 |
|
|
349 |
s->initialized = 1; |
s->initialized = 1; |
352 |
return 0; |
return 0; |
353 |
} |
} |
354 |
|
|
355 |
static int sdl_hw_ctl (HWVoice *hw, int cmd, ...) |
static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...) |
356 |
{ |
{ |
357 |
(void) hw; |
(void) hw; |
358 |
|
|
371 |
static void *sdl_audio_init (void) |
static void *sdl_audio_init (void) |
372 |
{ |
{ |
373 |
SDLAudioState *s = &glob_sdl; |
SDLAudioState *s = &glob_sdl; |
|
conf.nb_samples = audio_get_conf_int (QC_SDL_SAMPLES, conf.nb_samples); |
|
374 |
|
|
375 |
if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { |
if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { |
376 |
dolog ("SDL failed to initialize audio subsystem\nReason: %s\n", |
sdl_logerr ("SDL failed to initialize audio subsystem\n"); |
|
errstr ()); |
|
377 |
return NULL; |
return NULL; |
378 |
} |
} |
379 |
|
|
380 |
s->mutex = SDL_CreateMutex (); |
s->mutex = SDL_CreateMutex (); |
381 |
if (!s->mutex) { |
if (!s->mutex) { |
382 |
dolog ("Failed to create SDL mutex\nReason: %s\n", errstr ()); |
sdl_logerr ("Failed to create SDL mutex\n"); |
383 |
SDL_QuitSubSystem (SDL_INIT_AUDIO); |
SDL_QuitSubSystem (SDL_INIT_AUDIO); |
384 |
return NULL; |
return NULL; |
385 |
} |
} |
386 |
|
|
387 |
s->sem = SDL_CreateSemaphore (0); |
s->sem = SDL_CreateSemaphore (0); |
388 |
if (!s->sem) { |
if (!s->sem) { |
389 |
dolog ("Failed to create SDL semaphore\nReason: %s\n", errstr ()); |
sdl_logerr ("Failed to create SDL semaphore\n"); |
390 |
SDL_DestroyMutex (s->mutex); |
SDL_DestroyMutex (s->mutex); |
391 |
SDL_QuitSubSystem (SDL_INIT_AUDIO); |
SDL_QuitSubSystem (SDL_INIT_AUDIO); |
392 |
return NULL; |
return NULL; |
404 |
SDL_QuitSubSystem (SDL_INIT_AUDIO); |
SDL_QuitSubSystem (SDL_INIT_AUDIO); |
405 |
} |
} |
406 |
|
|
407 |
struct pcm_ops sdl_pcm_ops = { |
static struct audio_option sdl_options[] = { |
408 |
sdl_hw_init, |
{"SAMPLES", AUD_OPT_INT, &conf.nb_samples, |
409 |
sdl_hw_fini, |
"Size of SDL buffer in samples", NULL, 0}, |
410 |
sdl_hw_run, |
{NULL, 0, NULL, NULL, NULL, 0} |
411 |
sdl_hw_write, |
}; |
412 |
sdl_hw_ctl |
|
413 |
|
static struct audio_pcm_ops sdl_pcm_ops = { |
414 |
|
sdl_init_out, |
415 |
|
sdl_fini_out, |
416 |
|
sdl_run_out, |
417 |
|
sdl_write_out, |
418 |
|
sdl_ctl_out, |
419 |
|
|
420 |
|
NULL, |
421 |
|
NULL, |
422 |
|
NULL, |
423 |
|
NULL, |
424 |
|
NULL |
425 |
}; |
}; |
426 |
|
|
427 |
struct audio_output_driver sdl_output_driver = { |
struct audio_driver sdl_audio_driver = { |
428 |
"sdl", |
INIT_FIELD (name = ) "sdl", |
429 |
sdl_audio_init, |
INIT_FIELD (descr = ) "SDL http://www.libsdl.org", |
430 |
sdl_audio_fini, |
INIT_FIELD (options = ) sdl_options, |
431 |
&sdl_pcm_ops, |
INIT_FIELD (init = ) sdl_audio_init, |
432 |
1, |
INIT_FIELD (fini = ) sdl_audio_fini, |
433 |
1, |
INIT_FIELD (pcm_ops = ) &sdl_pcm_ops, |
434 |
sizeof (SDLVoice) |
INIT_FIELD (can_be_default = ) 1, |
435 |
|
INIT_FIELD (max_voices_out = ) 1, |
436 |
|
INIT_FIELD (max_voices_in = ) 0, |
437 |
|
INIT_FIELD (voice_size_out = ) sizeof (SDLVoiceOut), |
438 |
|
INIT_FIELD (voice_size_in = ) 0 |
439 |
}; |
}; |