1 /*
2 SDL - Simple DirectMedia Layer
3 Copyright (C) 1997-2009 Sam Lantinga
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 Sam Lantinga
20 slouken@libsdl.org
21 */
22 #include "SDL_config.h"
23
24 /* Allow access to a raw mixing buffer */
25
26 #include "SDL.h"
27 #include "SDL_audio_c.h"
28 #include "SDL_audiomem.h"
29 #include "SDL_sysaudio.h"
30
31 #ifdef __OS2__
32 /* We'll need the DosSetPriority() API! */
33 #define INCL_DOSPROCESS
34 #include <os2.h>
35 #endif
36
37 /* Available audio drivers */
38 static AudioBootStrap *bootstrap[] = {
39 #if SDL_AUDIO_DRIVER_PULSE
40 &PULSE_bootstrap,
41 #endif
42 #if SDL_AUDIO_DRIVER_ALSA
43 &ALSA_bootstrap,
44 #endif
45 #if SDL_AUDIO_DRIVER_BSD
46 &BSD_AUDIO_bootstrap,
47 #endif
48 #if SDL_AUDIO_DRIVER_OSS
49 &DSP_bootstrap,
50 &DMA_bootstrap,
51 #endif
52 #if SDL_AUDIO_DRIVER_QNXNTO
53 &QNXNTOAUDIO_bootstrap,
54 #endif
55 #if SDL_AUDIO_DRIVER_SUNAUDIO
56 &SUNAUDIO_bootstrap,
57 #endif
58 #if SDL_AUDIO_DRIVER_DMEDIA
59 &DMEDIA_bootstrap,
60 #endif
61 #if SDL_AUDIO_DRIVER_ARTS
62 &ARTS_bootstrap,
63 #endif
64 #if SDL_AUDIO_DRIVER_ESD
65 &ESD_bootstrap,
66 #endif
67 #if SDL_AUDIO_DRIVER_NAS
68 &NAS_bootstrap,
69 #endif
70 #if SDL_AUDIO_DRIVER_DSOUND
71 &DSOUND_bootstrap,
72 #endif
73 #if SDL_AUDIO_DRIVER_WAVEOUT
74 &WAVEOUT_bootstrap,
75 #endif
76 #if SDL_AUDIO_DRIVER_PAUD
77 &Paud_bootstrap,
78 #endif
79 #if SDL_AUDIO_DRIVER_BAUDIO
80 &BAUDIO_bootstrap,
81 #endif
82 #if SDL_AUDIO_DRIVER_COREAUDIO
83 &COREAUDIO_bootstrap,
84 #endif
85 #if SDL_AUDIO_DRIVER_SNDMGR
86 &SNDMGR_bootstrap,
87 #endif
88 #if SDL_AUDIO_DRIVER_MINT
89 &MINTAUDIO_GSXB_bootstrap,
90 &MINTAUDIO_MCSN_bootstrap,
91 &MINTAUDIO_STFA_bootstrap,
92 &MINTAUDIO_XBIOS_bootstrap,
93 &MINTAUDIO_DMA8_bootstrap,
94 #endif
95 #if SDL_AUDIO_DRIVER_DISK
96 &DISKAUD_bootstrap,
97 #endif
98 #if SDL_AUDIO_DRIVER_DUMMY
99 &DUMMYAUD_bootstrap,
100 #endif
101 #if SDL_AUDIO_DRIVER_DC
102 &DCAUD_bootstrap,
103 #endif
104 #if SDL_AUDIO_DRIVER_NDS
105 &NDSAUD_bootstrap,
106 #endif
107 #if SDL_AUDIO_DRIVER_MMEAUDIO
108 &MMEAUDIO_bootstrap,
109 #endif
110 #if SDL_AUDIO_DRIVER_DART
111 &DART_bootstrap,
112 #endif
113 #if SDL_AUDIO_DRIVER_EPOCAUDIO
114 &EPOCAudio_bootstrap,
115 #endif
116 #if SDL_AUDIO_DRIVER_ANDROID
117 &ANDROIDAUD_bootstrap,
118 #endif
119 NULL
120 };
121 SDL_AudioDevice *current_audio = NULL;
122
123 /* Various local functions */
124 int SDL_AudioInit(const char *driver_name);
125 void SDL_AudioQuit(void);
126
127 /* The general mixing thread function */
SDL_RunAudio(void * audiop)128 int SDLCALL SDL_RunAudio(void *audiop)
129 {
130 SDL_AudioDevice *audio = (SDL_AudioDevice *)audiop;
131 Uint8 *stream;
132 int stream_len;
133 void *udata;
134 void (SDLCALL *fill)(void *userdata,Uint8 *stream, int len);
135 int silence;
136
137 /* Perform any thread setup */
138 if ( audio->ThreadInit ) {
139 audio->ThreadInit(audio);
140 }
141 audio->threadid = SDL_ThreadID();
142
143 /* Set up the mixing function */
144 fill = audio->spec.callback;
145 udata = audio->spec.userdata;
146
147 if ( audio->convert.needed ) {
148 if ( audio->convert.src_format == AUDIO_U8 ) {
149 silence = 0x80;
150 } else {
151 silence = 0;
152 }
153 stream_len = audio->convert.len;
154 } else {
155 silence = audio->spec.silence;
156 stream_len = audio->spec.size;
157 }
158
159 #ifdef __OS2__
160 /* Increase the priority of this thread to make sure that
161 the audio will be continuous all the time! */
162 #ifdef USE_DOSSETPRIORITY
163 if (SDL_getenv("SDL_USE_TIMECRITICAL_AUDIO"))
164 {
165 #ifdef DEBUG_BUILD
166 printf("[SDL_RunAudio] : Setting priority to TimeCritical+0! (TID%d)\n", SDL_ThreadID());
167 #endif
168 DosSetPriority(PRTYS_THREAD, PRTYC_TIMECRITICAL, 0, 0);
169 }
170 else
171 {
172 #ifdef DEBUG_BUILD
173 printf("[SDL_RunAudio] : Setting priority to ForegroundServer+0! (TID%d)\n", SDL_ThreadID());
174 #endif
175 DosSetPriority(PRTYS_THREAD, PRTYC_FOREGROUNDSERVER, 0, 0);
176 }
177 #endif
178 #endif
179
180 /* Loop, filling the audio buffers */
181 while ( audio->enabled ) {
182
183 /* Fill the current buffer with sound */
184 if ( audio->convert.needed ) {
185 if ( audio->convert.buf ) {
186 stream = audio->convert.buf;
187 } else {
188 continue;
189 }
190 } else {
191 stream = audio->GetAudioBuf(audio);
192 if ( stream == NULL ) {
193 stream = audio->fake_stream;
194 }
195 }
196
197 SDL_memset(stream, silence, stream_len);
198
199 if ( ! audio->paused ) {
200 SDL_mutexP(audio->mixer_lock);
201 (*fill)(udata, stream, stream_len);
202 SDL_mutexV(audio->mixer_lock);
203 }
204
205 /* Convert the audio if necessary */
206 if ( audio->convert.needed ) {
207 SDL_ConvertAudio(&audio->convert);
208 stream = audio->GetAudioBuf(audio);
209 if ( stream == NULL ) {
210 stream = audio->fake_stream;
211 }
212 SDL_memcpy(stream, audio->convert.buf,
213 audio->convert.len_cvt);
214 }
215
216 /* Ready current buffer for play and change current buffer */
217 if ( stream != audio->fake_stream ) {
218 audio->PlayAudio(audio);
219 }
220
221 /* Wait for an audio buffer to become available */
222 if ( stream == audio->fake_stream ) {
223 SDL_Delay((audio->spec.samples*1000)/audio->spec.freq);
224 } else {
225 audio->WaitAudio(audio);
226 }
227 }
228
229 /* Wait for the audio to drain.. */
230 if ( audio->WaitDone ) {
231 audio->WaitDone(audio);
232 }
233
234 #ifdef __OS2__
235 #ifdef DEBUG_BUILD
236 printf("[SDL_RunAudio] : Task exiting. (TID%d)\n", SDL_ThreadID());
237 #endif
238 #endif
239 return(0);
240 }
241
SDL_LockAudio_Default(SDL_AudioDevice * audio)242 static void SDL_LockAudio_Default(SDL_AudioDevice *audio)
243 {
244 if ( audio->thread && (SDL_ThreadID() == audio->threadid) ) {
245 return;
246 }
247 SDL_mutexP(audio->mixer_lock);
248 }
249
SDL_UnlockAudio_Default(SDL_AudioDevice * audio)250 static void SDL_UnlockAudio_Default(SDL_AudioDevice *audio)
251 {
252 if ( audio->thread && (SDL_ThreadID() == audio->threadid) ) {
253 return;
254 }
255 SDL_mutexV(audio->mixer_lock);
256 }
257
SDL_ParseAudioFormat(const char * string)258 static Uint16 SDL_ParseAudioFormat(const char *string)
259 {
260 Uint16 format = 0;
261
262 switch (*string) {
263 case 'U':
264 ++string;
265 format |= 0x0000;
266 break;
267 case 'S':
268 ++string;
269 format |= 0x8000;
270 break;
271 default:
272 return 0;
273 }
274 switch (SDL_atoi(string)) {
275 case 8:
276 string += 1;
277 format |= 8;
278 break;
279 case 16:
280 string += 2;
281 format |= 16;
282 if ( SDL_strcmp(string, "LSB") == 0
283 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
284 || SDL_strcmp(string, "SYS") == 0
285 #endif
286 ) {
287 format |= 0x0000;
288 }
289 if ( SDL_strcmp(string, "MSB") == 0
290 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
291 || SDL_strcmp(string, "SYS") == 0
292 #endif
293 ) {
294 format |= 0x1000;
295 }
296 break;
297 default:
298 return 0;
299 }
300 return format;
301 }
302
SDL_AudioInit(const char * driver_name)303 int SDL_AudioInit(const char *driver_name)
304 {
305 SDL_AudioDevice *audio;
306 int i = 0, idx;
307
308 /* Check to make sure we don't overwrite 'current_audio' */
309 if ( current_audio != NULL ) {
310 SDL_AudioQuit();
311 }
312
313 /* Select the proper audio driver */
314 audio = NULL;
315 idx = 0;
316 #if SDL_AUDIO_DRIVER_ESD
317 if ( (driver_name == NULL) && (SDL_getenv("ESPEAKER") != NULL) ) {
318 /* Ahem, we know that if ESPEAKER is set, user probably wants
319 to use ESD, but don't start it if it's not already running.
320 This probably isn't the place to do this, but... Shh! :)
321 */
322 for ( i=0; bootstrap[i]; ++i ) {
323 if ( SDL_strcasecmp(bootstrap[i]->name, "esd") == 0 ) {
324 #ifdef HAVE_PUTENV
325 const char *esd_no_spawn;
326
327 /* Don't start ESD if it's not running */
328 esd_no_spawn = getenv("ESD_NO_SPAWN");
329 if ( esd_no_spawn == NULL ) {
330 putenv("ESD_NO_SPAWN=1");
331 }
332 #endif
333 if ( bootstrap[i]->available() ) {
334 audio = bootstrap[i]->create(0);
335 break;
336 }
337 #ifdef HAVE_UNSETENV
338 if ( esd_no_spawn == NULL ) {
339 unsetenv("ESD_NO_SPAWN");
340 }
341 #endif
342 }
343 }
344 }
345 #endif /* SDL_AUDIO_DRIVER_ESD */
346 if ( audio == NULL ) {
347 if ( driver_name != NULL ) {
348 #if 0 /* This will be replaced with a better driver selection API */
349 if ( SDL_strrchr(driver_name, ':') != NULL ) {
350 idx = atoi(SDL_strrchr(driver_name, ':')+1);
351 }
352 #endif
353 for ( i=0; bootstrap[i]; ++i ) {
354 if (SDL_strcasecmp(bootstrap[i]->name, driver_name) == 0) {
355 if ( bootstrap[i]->available() ) {
356 audio=bootstrap[i]->create(idx);
357 break;
358 }
359 }
360 }
361 } else {
362 for ( i=0; bootstrap[i]; ++i ) {
363 if ( bootstrap[i]->available() ) {
364 audio = bootstrap[i]->create(idx);
365 if ( audio != NULL ) {
366 break;
367 }
368 }
369 }
370 }
371 if ( audio == NULL ) {
372 SDL_SetError("No available audio device");
373 #if 0 /* Don't fail SDL_Init() if audio isn't available.
374 SDL_OpenAudio() will handle it at that point. *sigh*
375 */
376 return(-1);
377 #endif
378 }
379 }
380 current_audio = audio;
381 if ( current_audio ) {
382 current_audio->name = bootstrap[i]->name;
383 if ( !current_audio->LockAudio && !current_audio->UnlockAudio ) {
384 current_audio->LockAudio = SDL_LockAudio_Default;
385 current_audio->UnlockAudio = SDL_UnlockAudio_Default;
386 }
387 }
388 return(0);
389 }
390
SDL_AudioDriverName(char * namebuf,int maxlen)391 char *SDL_AudioDriverName(char *namebuf, int maxlen)
392 {
393 if ( current_audio != NULL ) {
394 SDL_strlcpy(namebuf, current_audio->name, maxlen);
395 return(namebuf);
396 }
397 return(NULL);
398 }
399
SDL_OpenAudio(SDL_AudioSpec * desired,SDL_AudioSpec * obtained)400 int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)
401 {
402 SDL_AudioDevice *audio;
403 const char *env;
404
405 /* Start up the audio driver, if necessary */
406 if ( ! current_audio ) {
407 if ( (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) ||
408 (current_audio == NULL) ) {
409 return(-1);
410 }
411 }
412 audio = current_audio;
413
414 if (audio->opened) {
415 SDL_SetError("Audio device is already opened");
416 return(-1);
417 }
418
419 /* Verify some parameters */
420 if ( desired->freq == 0 ) {
421 env = SDL_getenv("SDL_AUDIO_FREQUENCY");
422 if ( env ) {
423 desired->freq = SDL_atoi(env);
424 }
425 }
426 if ( desired->freq == 0 ) {
427 /* Pick some default audio frequency */
428 desired->freq = 22050;
429 }
430 if ( desired->format == 0 ) {
431 env = SDL_getenv("SDL_AUDIO_FORMAT");
432 if ( env ) {
433 desired->format = SDL_ParseAudioFormat(env);
434 }
435 }
436 if ( desired->format == 0 ) {
437 /* Pick some default audio format */
438 desired->format = AUDIO_S16;
439 }
440 if ( desired->channels == 0 ) {
441 env = SDL_getenv("SDL_AUDIO_CHANNELS");
442 if ( env ) {
443 desired->channels = (Uint8)SDL_atoi(env);
444 }
445 }
446 if ( desired->channels == 0 ) {
447 /* Pick a default number of channels */
448 desired->channels = 2;
449 }
450 switch ( desired->channels ) {
451 case 1: /* Mono */
452 case 2: /* Stereo */
453 case 4: /* surround */
454 case 6: /* surround with center and lfe */
455 break;
456 default:
457 SDL_SetError("1 (mono) and 2 (stereo) channels supported");
458 return(-1);
459 }
460 if ( desired->samples == 0 ) {
461 env = SDL_getenv("SDL_AUDIO_SAMPLES");
462 if ( env ) {
463 desired->samples = (Uint16)SDL_atoi(env);
464 }
465 }
466 if ( desired->samples == 0 ) {
467 /* Pick a default of ~46 ms at desired frequency */
468 int samples = (desired->freq / 1000) * 46;
469 int power2 = 1;
470 while ( power2 < samples ) {
471 power2 *= 2;
472 }
473 desired->samples = power2;
474 }
475 if ( desired->callback == NULL ) {
476 SDL_SetError("SDL_OpenAudio() passed a NULL callback");
477 return(-1);
478 }
479
480 #if SDL_THREADS_DISABLED
481 /* Uses interrupt driven audio, without thread */
482 #else
483 /* Create a semaphore for locking the sound buffers */
484 audio->mixer_lock = SDL_CreateMutex();
485 if ( audio->mixer_lock == NULL ) {
486 SDL_SetError("Couldn't create mixer lock");
487 SDL_CloseAudio();
488 return(-1);
489 }
490 #endif /* SDL_THREADS_DISABLED */
491
492 /* Calculate the silence and size of the audio specification */
493 SDL_CalculateAudioSpec(desired);
494
495 /* Open the audio subsystem */
496 SDL_memcpy(&audio->spec, desired, sizeof(audio->spec));
497 audio->convert.needed = 0;
498 audio->enabled = 1;
499 audio->paused = 1;
500
501 audio->opened = audio->OpenAudio(audio, &audio->spec)+1;
502
503 if ( ! audio->opened ) {
504 SDL_CloseAudio();
505 return(-1);
506 }
507
508 /* If the audio driver changes the buffer size, accept it */
509 if ( audio->spec.samples != desired->samples ) {
510 desired->samples = audio->spec.samples;
511 SDL_CalculateAudioSpec(desired);
512 }
513
514 /* Allocate a fake audio memory buffer */
515 audio->fake_stream = SDL_AllocAudioMem(audio->spec.size);
516 if ( audio->fake_stream == NULL ) {
517 SDL_CloseAudio();
518 SDL_OutOfMemory();
519 return(-1);
520 }
521
522 /* See if we need to do any conversion */
523 if ( obtained != NULL ) {
524 SDL_memcpy(obtained, &audio->spec, sizeof(audio->spec));
525 } else if ( desired->freq != audio->spec.freq ||
526 desired->format != audio->spec.format ||
527 desired->channels != audio->spec.channels ) {
528 /* Build an audio conversion block */
529 if ( SDL_BuildAudioCVT(&audio->convert,
530 desired->format, desired->channels,
531 desired->freq,
532 audio->spec.format, audio->spec.channels,
533 audio->spec.freq) < 0 ) {
534 SDL_CloseAudio();
535 return(-1);
536 }
537 if ( audio->convert.needed ) {
538 audio->convert.len = (int) ( ((double) audio->spec.size) /
539 audio->convert.len_ratio );
540 audio->convert.buf =(Uint8 *)SDL_AllocAudioMem(
541 audio->convert.len*audio->convert.len_mult);
542 if ( audio->convert.buf == NULL ) {
543 SDL_CloseAudio();
544 SDL_OutOfMemory();
545 return(-1);
546 }
547 }
548 }
549
550 /* Start the audio thread if necessary */
551 switch (audio->opened) {
552 case 1:
553 /* Start the audio thread */
554 #if (defined(__WIN32__) && !defined(_WIN32_WCE)) && !defined(HAVE_LIBC) && !defined(__SYMBIAN32__)
555 #undef SDL_CreateThread
556 audio->thread = SDL_CreateThread(SDL_RunAudio, audio, NULL, NULL);
557 #else
558 audio->thread = SDL_CreateThread(SDL_RunAudio, audio);
559 #endif
560 if ( audio->thread == NULL ) {
561 SDL_CloseAudio();
562 SDL_SetError("Couldn't create audio thread");
563 return(-1);
564 }
565 break;
566
567 default:
568 /* The audio is now playing */
569 break;
570 }
571
572 return(0);
573 }
574
SDL_GetAudioStatus(void)575 SDL_audiostatus SDL_GetAudioStatus(void)
576 {
577 SDL_AudioDevice *audio = current_audio;
578 SDL_audiostatus status;
579
580 status = SDL_AUDIO_STOPPED;
581 if ( audio && audio->enabled ) {
582 if ( audio->paused ) {
583 status = SDL_AUDIO_PAUSED;
584 } else {
585 status = SDL_AUDIO_PLAYING;
586 }
587 }
588 return(status);
589 }
590
SDL_PauseAudio(int pause_on)591 void SDL_PauseAudio (int pause_on)
592 {
593 SDL_AudioDevice *audio = current_audio;
594
595 if ( audio ) {
596 audio->paused = pause_on;
597 }
598 }
599
SDL_LockAudio(void)600 void SDL_LockAudio (void)
601 {
602 SDL_AudioDevice *audio = current_audio;
603
604 /* Obtain a lock on the mixing buffers */
605 if ( audio && audio->LockAudio ) {
606 audio->LockAudio(audio);
607 }
608 }
609
SDL_UnlockAudio(void)610 void SDL_UnlockAudio (void)
611 {
612 SDL_AudioDevice *audio = current_audio;
613
614 /* Release lock on the mixing buffers */
615 if ( audio && audio->UnlockAudio ) {
616 audio->UnlockAudio(audio);
617 }
618 }
619
SDL_CloseAudio(void)620 void SDL_CloseAudio (void)
621 {
622 SDL_QuitSubSystem(SDL_INIT_AUDIO);
623 }
624
SDL_AudioQuit(void)625 void SDL_AudioQuit(void)
626 {
627 SDL_AudioDevice *audio = current_audio;
628
629 if ( audio ) {
630 audio->enabled = 0;
631 if ( audio->thread != NULL ) {
632 SDL_WaitThread(audio->thread, NULL);
633 }
634 if ( audio->mixer_lock != NULL ) {
635 SDL_DestroyMutex(audio->mixer_lock);
636 }
637 if ( audio->fake_stream != NULL ) {
638 SDL_FreeAudioMem(audio->fake_stream);
639 }
640 if ( audio->convert.needed ) {
641 SDL_FreeAudioMem(audio->convert.buf);
642
643 }
644 if ( audio->opened ) {
645 audio->CloseAudio(audio);
646 audio->opened = 0;
647 }
648 /* Free the driver data */
649 audio->free(audio);
650 current_audio = NULL;
651 }
652 }
653
654 #define NUM_FORMATS 6
655 static int format_idx;
656 static int format_idx_sub;
657 static Uint16 format_list[NUM_FORMATS][NUM_FORMATS] = {
658 { AUDIO_U8, AUDIO_S8, AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U16LSB, AUDIO_U16MSB },
659 { AUDIO_S8, AUDIO_U8, AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U16LSB, AUDIO_U16MSB },
660 { AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_U8, AUDIO_S8 },
661 { AUDIO_S16MSB, AUDIO_S16LSB, AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_U8, AUDIO_S8 },
662 { AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U8, AUDIO_S8 },
663 { AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_S16MSB, AUDIO_S16LSB, AUDIO_U8, AUDIO_S8 },
664 };
665
SDL_FirstAudioFormat(Uint16 format)666 Uint16 SDL_FirstAudioFormat(Uint16 format)
667 {
668 for ( format_idx=0; format_idx < NUM_FORMATS; ++format_idx ) {
669 if ( format_list[format_idx][0] == format ) {
670 break;
671 }
672 }
673 format_idx_sub = 0;
674 return(SDL_NextAudioFormat());
675 }
676
SDL_NextAudioFormat(void)677 Uint16 SDL_NextAudioFormat(void)
678 {
679 if ( (format_idx == NUM_FORMATS) || (format_idx_sub == NUM_FORMATS) ) {
680 return(0);
681 }
682 return(format_list[format_idx][format_idx_sub++]);
683 }
684
SDL_CalculateAudioSpec(SDL_AudioSpec * spec)685 void SDL_CalculateAudioSpec(SDL_AudioSpec *spec)
686 {
687 switch (spec->format) {
688 case AUDIO_U8:
689 spec->silence = 0x80;
690 break;
691 default:
692 spec->silence = 0x00;
693 break;
694 }
695 spec->size = (spec->format&0xFF)/8;
696 spec->size *= spec->channels;
697 spec->size *= spec->samples;
698 }
699
SDL_Audio_SetCaption(const char * caption)700 void SDL_Audio_SetCaption(const char *caption)
701 {
702 if ((current_audio) && (current_audio->SetCaption)) {
703 current_audio->SetCaption(current_audio, caption);
704 }
705 }
706
707