]>
Commit | Line | Data |
---|---|---|
85571bc7 | 1 | /* |
1d14ffa9 FB |
2 | * QEMU SDL audio driver |
3 | * | |
4 | * Copyright (c) 2004-2005 Vassili Karpov (malc) | |
5 | * | |
85571bc7 FB |
6 | * 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 | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in | |
14 | * all copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
22 | * THE SOFTWARE. | |
23 | */ | |
6086a565 | 24 | #include "qemu/osdep.h" |
9f059eca FB |
25 | #include <SDL.h> |
26 | #include <SDL_thread.h> | |
87ecb68b PB |
27 | #include "qemu-common.h" |
28 | #include "audio.h" | |
85571bc7 | 29 | |
e784ba70 TS |
30 | #ifndef _WIN32 |
31 | #ifdef __sun__ | |
32 | #define _POSIX_PTHREAD_SEMANTICS 1 | |
c5e97233 | 33 | #elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) |
9b4c14c3 | 34 | #include <pthread.h> |
e784ba70 | 35 | #endif |
e784ba70 TS |
36 | #endif |
37 | ||
1d14ffa9 FB |
38 | #define AUDIO_CAP "sdl" |
39 | #include "audio_int.h" | |
85571bc7 | 40 | |
bcf19777 TH |
41 | #define USE_SEMAPHORE (SDL_MAJOR_VERSION < 2) |
42 | ||
1d14ffa9 FB |
43 | typedef struct SDLVoiceOut { |
44 | HWVoiceOut hw; | |
45 | int live; | |
bcf19777 | 46 | #if USE_SEMAPHORE |
ff541499 | 47 | int rpos; |
bcf19777 | 48 | #endif |
1d14ffa9 FB |
49 | int decr; |
50 | } SDLVoiceOut; | |
85571bc7 FB |
51 | |
52 | static struct { | |
53 | int nb_samples; | |
54 | } conf = { | |
1a40d5e2 | 55 | .nb_samples = 1024 |
85571bc7 FB |
56 | }; |
57 | ||
b1d8e52e | 58 | static struct SDLAudioState { |
85571bc7 | 59 | int exit; |
bcf19777 | 60 | #if USE_SEMAPHORE |
85571bc7 FB |
61 | SDL_mutex *mutex; |
62 | SDL_sem *sem; | |
bcf19777 | 63 | #endif |
85571bc7 | 64 | int initialized; |
81ebb07c | 65 | bool driver_created; |
85571bc7 FB |
66 | } glob_sdl; |
67 | typedef struct SDLAudioState SDLAudioState; | |
68 | ||
1d14ffa9 | 69 | static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...) |
85571bc7 | 70 | { |
1d14ffa9 FB |
71 | va_list ap; |
72 | ||
73 | va_start (ap, fmt); | |
74 | AUD_vlog (AUDIO_CAP, fmt, ap); | |
75 | va_end (ap); | |
76 | ||
77 | AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ()); | |
85571bc7 FB |
78 | } |
79 | ||
1d14ffa9 | 80 | static int sdl_lock (SDLAudioState *s, const char *forfn) |
85571bc7 | 81 | { |
bcf19777 | 82 | #if USE_SEMAPHORE |
85571bc7 | 83 | if (SDL_LockMutex (s->mutex)) { |
1d14ffa9 | 84 | sdl_logerr ("SDL_LockMutex for %s failed\n", forfn); |
85571bc7 FB |
85 | return -1; |
86 | } | |
bcf19777 TH |
87 | #else |
88 | SDL_LockAudio(); | |
89 | #endif | |
90 | ||
85571bc7 FB |
91 | return 0; |
92 | } | |
93 | ||
1d14ffa9 | 94 | static int sdl_unlock (SDLAudioState *s, const char *forfn) |
85571bc7 | 95 | { |
bcf19777 | 96 | #if USE_SEMAPHORE |
85571bc7 | 97 | if (SDL_UnlockMutex (s->mutex)) { |
1d14ffa9 | 98 | sdl_logerr ("SDL_UnlockMutex for %s failed\n", forfn); |
85571bc7 FB |
99 | return -1; |
100 | } | |
bcf19777 TH |
101 | #else |
102 | SDL_UnlockAudio(); | |
103 | #endif | |
104 | ||
85571bc7 FB |
105 | return 0; |
106 | } | |
107 | ||
1d14ffa9 | 108 | static int sdl_post (SDLAudioState *s, const char *forfn) |
85571bc7 | 109 | { |
bcf19777 | 110 | #if USE_SEMAPHORE |
85571bc7 | 111 | if (SDL_SemPost (s->sem)) { |
1d14ffa9 | 112 | sdl_logerr ("SDL_SemPost for %s failed\n", forfn); |
85571bc7 FB |
113 | return -1; |
114 | } | |
bcf19777 TH |
115 | #endif |
116 | ||
85571bc7 FB |
117 | return 0; |
118 | } | |
119 | ||
bcf19777 | 120 | #if USE_SEMAPHORE |
1d14ffa9 | 121 | static int sdl_wait (SDLAudioState *s, const char *forfn) |
85571bc7 FB |
122 | { |
123 | if (SDL_SemWait (s->sem)) { | |
1d14ffa9 | 124 | sdl_logerr ("SDL_SemWait for %s failed\n", forfn); |
85571bc7 FB |
125 | return -1; |
126 | } | |
127 | return 0; | |
128 | } | |
bcf19777 | 129 | #endif |
85571bc7 | 130 | |
1d14ffa9 | 131 | static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn) |
85571bc7 | 132 | { |
1d14ffa9 | 133 | if (sdl_unlock (s, forfn)) { |
85571bc7 | 134 | return -1; |
1d14ffa9 | 135 | } |
85571bc7 | 136 | |
1d14ffa9 | 137 | return sdl_post (s, forfn); |
85571bc7 FB |
138 | } |
139 | ||
6c557ab9 | 140 | static int aud_to_sdlfmt (audfmt_e fmt) |
85571bc7 | 141 | { |
85571bc7 | 142 | switch (fmt) { |
1d14ffa9 | 143 | case AUD_FMT_S8: |
1d14ffa9 FB |
144 | return AUDIO_S8; |
145 | ||
146 | case AUD_FMT_U8: | |
1d14ffa9 FB |
147 | return AUDIO_U8; |
148 | ||
149 | case AUD_FMT_S16: | |
1d14ffa9 FB |
150 | return AUDIO_S16LSB; |
151 | ||
152 | case AUD_FMT_U16: | |
1d14ffa9 FB |
153 | return AUDIO_U16LSB; |
154 | ||
85571bc7 | 155 | default: |
1d14ffa9 FB |
156 | dolog ("Internal logic error: Bad audio format %d\n", fmt); |
157 | #ifdef DEBUG_AUDIO | |
158 | abort (); | |
159 | #endif | |
160 | return AUDIO_U8; | |
85571bc7 FB |
161 | } |
162 | } | |
163 | ||
4ff9786c | 164 | static int sdl_to_audfmt(int sdlfmt, audfmt_e *fmt, int *endianness) |
85571bc7 | 165 | { |
1d14ffa9 FB |
166 | switch (sdlfmt) { |
167 | case AUDIO_S8: | |
4ff9786c | 168 | *endianness = 0; |
1d14ffa9 FB |
169 | *fmt = AUD_FMT_S8; |
170 | break; | |
171 | ||
172 | case AUDIO_U8: | |
4ff9786c | 173 | *endianness = 0; |
1d14ffa9 FB |
174 | *fmt = AUD_FMT_U8; |
175 | break; | |
176 | ||
177 | case AUDIO_S16LSB: | |
4ff9786c | 178 | *endianness = 0; |
1d14ffa9 FB |
179 | *fmt = AUD_FMT_S16; |
180 | break; | |
181 | ||
182 | case AUDIO_U16LSB: | |
4ff9786c | 183 | *endianness = 0; |
1d14ffa9 FB |
184 | *fmt = AUD_FMT_U16; |
185 | break; | |
186 | ||
187 | case AUDIO_S16MSB: | |
4ff9786c | 188 | *endianness = 1; |
1d14ffa9 FB |
189 | *fmt = AUD_FMT_S16; |
190 | break; | |
191 | ||
192 | case AUDIO_U16MSB: | |
4ff9786c | 193 | *endianness = 1; |
1d14ffa9 FB |
194 | *fmt = AUD_FMT_U16; |
195 | break; | |
196 | ||
85571bc7 | 197 | default: |
1d14ffa9 FB |
198 | dolog ("Unrecognized SDL audio format %d\n", sdlfmt); |
199 | return -1; | |
85571bc7 | 200 | } |
1d14ffa9 FB |
201 | |
202 | return 0; | |
85571bc7 FB |
203 | } |
204 | ||
205 | static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) | |
206 | { | |
207 | int status; | |
e784ba70 | 208 | #ifndef _WIN32 |
d087bb3e | 209 | int err; |
e784ba70 TS |
210 | sigset_t new, old; |
211 | ||
212 | /* Make sure potential threads created by SDL don't hog signals. */ | |
d087bb3e | 213 | err = sigfillset (&new); |
214 | if (err) { | |
215 | dolog ("sdl_open: sigfillset failed: %s\n", strerror (errno)); | |
60592edd | 216 | return -1; |
d087bb3e | 217 | } |
218 | err = pthread_sigmask (SIG_BLOCK, &new, &old); | |
219 | if (err) { | |
220 | dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err)); | |
221 | return -1; | |
222 | } | |
e784ba70 | 223 | #endif |
85571bc7 FB |
224 | |
225 | status = SDL_OpenAudio (req, obt); | |
226 | if (status) { | |
1d14ffa9 | 227 | sdl_logerr ("SDL_OpenAudio failed\n"); |
85571bc7 | 228 | } |
e784ba70 TS |
229 | |
230 | #ifndef _WIN32 | |
d087bb3e | 231 | err = pthread_sigmask (SIG_SETMASK, &old, NULL); |
232 | if (err) { | |
233 | dolog ("sdl_open: pthread_sigmask (restore) failed: %s\n", | |
234 | strerror (errno)); | |
235 | /* We have failed to restore original signal mask, all bets are off, | |
236 | so exit the process */ | |
237 | exit (EXIT_FAILURE); | |
238 | } | |
e784ba70 | 239 | #endif |
85571bc7 FB |
240 | return status; |
241 | } | |
242 | ||
243 | static void sdl_close (SDLAudioState *s) | |
244 | { | |
245 | if (s->initialized) { | |
1d14ffa9 | 246 | sdl_lock (s, "sdl_close"); |
85571bc7 | 247 | s->exit = 1; |
1d14ffa9 | 248 | sdl_unlock_and_post (s, "sdl_close"); |
85571bc7 FB |
249 | SDL_PauseAudio (1); |
250 | SDL_CloseAudio (); | |
251 | s->initialized = 0; | |
252 | } | |
253 | } | |
254 | ||
255 | static void sdl_callback (void *opaque, Uint8 *buf, int len) | |
256 | { | |
1d14ffa9 | 257 | SDLVoiceOut *sdl = opaque; |
85571bc7 | 258 | SDLAudioState *s = &glob_sdl; |
1d14ffa9 FB |
259 | HWVoiceOut *hw = &sdl->hw; |
260 | int samples = len >> hw->info.shift; | |
85571bc7 FB |
261 | |
262 | if (s->exit) { | |
263 | return; | |
264 | } | |
265 | ||
266 | while (samples) { | |
1d14ffa9 | 267 | int to_mix, decr; |
85571bc7 | 268 | |
ff541499 | 269 | /* dolog ("in callback samples=%d\n", samples); */ |
bcf19777 | 270 | #if USE_SEMAPHORE |
ff541499 | 271 | sdl_wait (s, "sdl_callback"); |
272 | if (s->exit) { | |
273 | return; | |
274 | } | |
275 | ||
276 | if (sdl_lock (s, "sdl_callback")) { | |
277 | return; | |
278 | } | |
279 | ||
280 | if (audio_bug (AUDIO_FUNC, sdl->live < 0 || sdl->live > hw->samples)) { | |
281 | dolog ("sdl->live=%d hw->samples=%d\n", | |
282 | sdl->live, hw->samples); | |
283 | return; | |
284 | } | |
285 | ||
286 | if (!sdl->live) { | |
287 | goto again; | |
1d14ffa9 | 288 | } |
bcf19777 TH |
289 | #else |
290 | if (s->exit || !sdl->live) { | |
291 | break; | |
292 | } | |
293 | #endif | |
85571bc7 | 294 | |
ff541499 | 295 | /* dolog ("in callback live=%d\n", live); */ |
296 | to_mix = audio_MIN (samples, sdl->live); | |
297 | decr = to_mix; | |
298 | while (to_mix) { | |
299 | int chunk = audio_MIN (to_mix, hw->samples - hw->rpos); | |
300 | struct st_sample *src = hw->mix_buf + hw->rpos; | |
301 | ||
302 | /* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */ | |
303 | hw->clip (buf, src, chunk); | |
bcf19777 | 304 | #if USE_SEMAPHORE |
ff541499 | 305 | sdl->rpos = (sdl->rpos + chunk) % hw->samples; |
bcf19777 TH |
306 | #else |
307 | hw->rpos = (hw->rpos + chunk) % hw->samples; | |
308 | #endif | |
ff541499 | 309 | to_mix -= chunk; |
310 | buf += chunk << hw->info.shift; | |
311 | } | |
85571bc7 | 312 | samples -= decr; |
ff541499 | 313 | sdl->live -= decr; |
1d14ffa9 | 314 | sdl->decr += decr; |
85571bc7 | 315 | |
bcf19777 | 316 | #if USE_SEMAPHORE |
ff541499 | 317 | again: |
318 | if (sdl_unlock (s, "sdl_callback")) { | |
319 | return; | |
320 | } | |
bcf19777 | 321 | #endif |
85571bc7 | 322 | } |
ff541499 | 323 | /* dolog ("done len=%d\n", len); */ |
bcf19777 TH |
324 | |
325 | #if (SDL_MAJOR_VERSION >= 2) | |
326 | /* SDL2 does not clear the remaining buffer for us, so do it on our own */ | |
327 | if (samples) { | |
328 | memset(buf, 0, samples << hw->info.shift); | |
329 | } | |
330 | #endif | |
85571bc7 FB |
331 | } |
332 | ||
1d14ffa9 | 333 | static int sdl_write_out (SWVoiceOut *sw, void *buf, int len) |
85571bc7 | 334 | { |
1d14ffa9 FB |
335 | return audio_pcm_sw_write (sw, buf, len); |
336 | } | |
337 | ||
bdff253c | 338 | static int sdl_run_out (HWVoiceOut *hw, int live) |
1d14ffa9 | 339 | { |
bdff253c | 340 | int decr; |
1d14ffa9 FB |
341 | SDLVoiceOut *sdl = (SDLVoiceOut *) hw; |
342 | SDLAudioState *s = &glob_sdl; | |
343 | ||
3fd7f635 | 344 | if (sdl_lock (s, "sdl_run_out")) { |
1d14ffa9 FB |
345 | return 0; |
346 | } | |
347 | ||
ff541499 | 348 | if (sdl->decr > live) { |
349 | ldebug ("sdl->decr %d live %d sdl->live %d\n", | |
350 | sdl->decr, | |
351 | live, | |
352 | sdl->live); | |
353 | } | |
354 | ||
355 | decr = audio_MIN (sdl->decr, live); | |
356 | sdl->decr -= decr; | |
357 | ||
bcf19777 | 358 | #if USE_SEMAPHORE |
ff541499 | 359 | sdl->live = live - decr; |
360 | hw->rpos = sdl->rpos; | |
bcf19777 TH |
361 | #else |
362 | sdl->live = live; | |
363 | #endif | |
1d14ffa9 FB |
364 | |
365 | if (sdl->live > 0) { | |
3fd7f635 | 366 | sdl_unlock_and_post (s, "sdl_run_out"); |
1d14ffa9 FB |
367 | } |
368 | else { | |
3fd7f635 | 369 | sdl_unlock (s, "sdl_run_out"); |
1d14ffa9 FB |
370 | } |
371 | return decr; | |
372 | } | |
373 | ||
374 | static void sdl_fini_out (HWVoiceOut *hw) | |
375 | { | |
376 | (void) hw; | |
377 | ||
85571bc7 FB |
378 | sdl_close (&glob_sdl); |
379 | } | |
380 | ||
5706db1d KZ |
381 | static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, |
382 | void *drv_opaque) | |
85571bc7 | 383 | { |
1d14ffa9 | 384 | SDLVoiceOut *sdl = (SDLVoiceOut *) hw; |
85571bc7 FB |
385 | SDLAudioState *s = &glob_sdl; |
386 | SDL_AudioSpec req, obt; | |
4ff9786c | 387 | int endianness; |
1d14ffa9 FB |
388 | int err; |
389 | audfmt_e effective_fmt; | |
1ea879e5 | 390 | struct audsettings obt_as; |
85571bc7 | 391 | |
c0fe3827 | 392 | req.freq = as->freq; |
6c557ab9 | 393 | req.format = aud_to_sdlfmt (as->fmt); |
c0fe3827 | 394 | req.channels = as->nchannels; |
85571bc7 | 395 | req.samples = conf.nb_samples; |
85571bc7 FB |
396 | req.callback = sdl_callback; |
397 | req.userdata = sdl; | |
398 | ||
1d14ffa9 FB |
399 | if (sdl_open (&req, &obt)) { |
400 | return -1; | |
401 | } | |
402 | ||
4ff9786c | 403 | err = sdl_to_audfmt(obt.format, &effective_fmt, &endianness); |
1d14ffa9 FB |
404 | if (err) { |
405 | sdl_close (s); | |
85571bc7 | 406 | return -1; |
1d14ffa9 | 407 | } |
85571bc7 | 408 | |
c0fe3827 FB |
409 | obt_as.freq = obt.freq; |
410 | obt_as.nchannels = obt.channels; | |
411 | obt_as.fmt = effective_fmt; | |
4ff9786c | 412 | obt_as.endianness = endianness; |
c0fe3827 | 413 | |
d929eba5 | 414 | audio_pcm_init_info (&hw->info, &obt_as); |
c0fe3827 | 415 | hw->samples = obt.samples; |
85571bc7 FB |
416 | |
417 | s->initialized = 1; | |
418 | s->exit = 0; | |
419 | SDL_PauseAudio (0); | |
420 | return 0; | |
421 | } | |
422 | ||
1d14ffa9 | 423 | static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...) |
85571bc7 FB |
424 | { |
425 | (void) hw; | |
426 | ||
427 | switch (cmd) { | |
428 | case VOICE_ENABLE: | |
429 | SDL_PauseAudio (0); | |
430 | break; | |
431 | ||
432 | case VOICE_DISABLE: | |
433 | SDL_PauseAudio (1); | |
434 | break; | |
435 | } | |
436 | return 0; | |
437 | } | |
438 | ||
439 | static void *sdl_audio_init (void) | |
440 | { | |
441 | SDLAudioState *s = &glob_sdl; | |
81ebb07c KZ |
442 | if (s->driver_created) { |
443 | sdl_logerr("Can't create multiple sdl backends\n"); | |
444 | return NULL; | |
445 | } | |
85571bc7 FB |
446 | |
447 | if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { | |
1d14ffa9 | 448 | sdl_logerr ("SDL failed to initialize audio subsystem\n"); |
85571bc7 FB |
449 | return NULL; |
450 | } | |
451 | ||
bcf19777 | 452 | #if USE_SEMAPHORE |
85571bc7 FB |
453 | s->mutex = SDL_CreateMutex (); |
454 | if (!s->mutex) { | |
1d14ffa9 | 455 | sdl_logerr ("Failed to create SDL mutex\n"); |
85571bc7 FB |
456 | SDL_QuitSubSystem (SDL_INIT_AUDIO); |
457 | return NULL; | |
458 | } | |
459 | ||
460 | s->sem = SDL_CreateSemaphore (0); | |
461 | if (!s->sem) { | |
1d14ffa9 | 462 | sdl_logerr ("Failed to create SDL semaphore\n"); |
85571bc7 FB |
463 | SDL_DestroyMutex (s->mutex); |
464 | SDL_QuitSubSystem (SDL_INIT_AUDIO); | |
465 | return NULL; | |
466 | } | |
bcf19777 | 467 | #endif |
85571bc7 | 468 | |
81ebb07c | 469 | s->driver_created = true; |
85571bc7 FB |
470 | return s; |
471 | } | |
472 | ||
473 | static void sdl_audio_fini (void *opaque) | |
474 | { | |
475 | SDLAudioState *s = opaque; | |
476 | sdl_close (s); | |
bcf19777 | 477 | #if USE_SEMAPHORE |
85571bc7 FB |
478 | SDL_DestroySemaphore (s->sem); |
479 | SDL_DestroyMutex (s->mutex); | |
bcf19777 | 480 | #endif |
85571bc7 | 481 | SDL_QuitSubSystem (SDL_INIT_AUDIO); |
81ebb07c | 482 | s->driver_created = false; |
85571bc7 FB |
483 | } |
484 | ||
1d14ffa9 | 485 | static struct audio_option sdl_options[] = { |
98f9f48c | 486 | { |
487 | .name = "SAMPLES", | |
488 | .tag = AUD_OPT_INT, | |
489 | .valp = &conf.nb_samples, | |
490 | .descr = "Size of SDL buffer in samples" | |
491 | }, | |
2700efa3 | 492 | { /* End of list */ } |
1d14ffa9 FB |
493 | }; |
494 | ||
35f4b58c | 495 | static struct audio_pcm_ops sdl_pcm_ops = { |
1dd3e4d1 JQ |
496 | .init_out = sdl_init_out, |
497 | .fini_out = sdl_fini_out, | |
498 | .run_out = sdl_run_out, | |
499 | .write = sdl_write_out, | |
500 | .ctl_out = sdl_ctl_out, | |
85571bc7 FB |
501 | }; |
502 | ||
1d14ffa9 | 503 | struct audio_driver sdl_audio_driver = { |
bee37f32 JQ |
504 | .name = "sdl", |
505 | .descr = "SDL http://www.libsdl.org", | |
506 | .options = sdl_options, | |
507 | .init = sdl_audio_init, | |
508 | .fini = sdl_audio_fini, | |
509 | .pcm_ops = &sdl_pcm_ops, | |
510 | .can_be_default = 1, | |
511 | .max_voices_out = 1, | |
512 | .max_voices_in = 0, | |
513 | .voice_size_out = sizeof (SDLVoiceOut), | |
514 | .voice_size_in = 0 | |
85571bc7 | 515 | }; |