Mercurial > sdl-ios-xcode
comparison src/audio/pulseaudio/SDL_pulseaudio.c @ 2271:60b4c52a7906
Ported PulseAudio target from 1.2 to 1.3 interfaces, and added it to the trunk.
Fixes Bugzilla #439.
author | Ryan C. Gordon <icculus@icculus.org> |
---|---|
date | Mon, 20 Aug 2007 01:02:37 +0000 |
parents | |
children | 25a87553a59d |
comparison
equal
deleted
inserted
replaced
2270:d5a11262f067 | 2271:60b4c52a7906 |
---|---|
1 /* | |
2 SDL - Simple DirectMedia Layer | |
3 Copyright (C) 1997-2006 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 | |
23 /* | |
24 The PulseAudio target for SDL 1.3 is based on the 1.3 arts target, with | |
25 the appropriate parts replaced with the 1.2 PulseAudio target code. This | |
26 was the cleanest way to move it to 1.3. The 1.2 target was written by | |
27 Stéphan Kochen: stephan .a.t. kochen.nl | |
28 */ | |
29 | |
30 #include "SDL_config.h" | |
31 | |
32 /* Allow access to a raw mixing buffer */ | |
33 | |
34 #ifdef HAVE_SIGNAL_H | |
35 #include <signal.h> | |
36 #endif | |
37 #include <unistd.h> | |
38 #include <sys/types.h> | |
39 #include <errno.h> | |
40 #include <pulse/simple.h> | |
41 | |
42 #include "SDL_timer.h" | |
43 #include "SDL_audio.h" | |
44 #include "../SDL_audiomem.h" | |
45 #include "../SDL_audio_c.h" | |
46 #include "SDL_pulseaudio.h" | |
47 | |
48 #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC | |
49 #include "SDL_name.h" | |
50 #include "SDL_loadso.h" | |
51 #else | |
52 #define SDL_NAME(X) X | |
53 #endif | |
54 | |
55 /* The tag name used by pulse audio */ | |
56 #define PULSEAUDIO_DRIVER_NAME "pulseaudio" | |
57 | |
58 #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC | |
59 | |
60 static const char *pulse_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; | |
61 static void *pulse_handle = NULL; | |
62 | |
63 /* !!! FIXME: I hate this SDL_NAME clutter...it makes everything so messy! */ | |
64 static pa_simple* (*SDL_NAME(pa_simple_new))( | |
65 const char *server, | |
66 const char *name, | |
67 pa_stream_direction_t dir, | |
68 const char *dev, | |
69 const char *stream_name, | |
70 const pa_sample_spec *ss, | |
71 const pa_channel_map *map, | |
72 const pa_buffer_attr *attr, | |
73 int *error | |
74 ); | |
75 static void (*SDL_NAME(pa_simple_free))(pa_simple *s); | |
76 static int (*SDL_NAME(pa_simple_drain))(pa_simple *s, int *error); | |
77 static int (*SDL_NAME(pa_simple_write))( | |
78 pa_simple *s, | |
79 const void *data, | |
80 size_t length, | |
81 int *error | |
82 ); | |
83 static pa_channel_map* (*SDL_NAME(pa_channel_map_init_auto))( | |
84 pa_channel_map *m, | |
85 unsigned channels, | |
86 pa_channel_map_def_t def | |
87 ); | |
88 | |
89 | |
90 #define SDL_PULSEAUDIO_SYM(x) { #x, (void **) (char *) &SDL_NAME(x) } | |
91 static struct { | |
92 const char *name; | |
93 void **func; | |
94 } pulse_functions[] = { | |
95 /* *INDENT-OFF* */ | |
96 SDL_PULSEAUDIO_SYM(pa_simple_new), | |
97 SDL_PULSEAUDIO_SYM(pa_simple_free), | |
98 SDL_PULSEAUDIO_SYM(pa_simple_drain), | |
99 SDL_PULSEAUDIO_SYM(pa_simple_write), | |
100 SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto), | |
101 /* *INDENT-ON* */ | |
102 }; | |
103 #undef SDL_PULSEAUDIO_SYM | |
104 | |
105 static void | |
106 UnloadPulseLibrary() | |
107 { | |
108 if (pulse_handle != NULL) { | |
109 SDL_UnloadObject(pulse_handle); | |
110 pulse_handle = NULL; | |
111 } | |
112 } | |
113 | |
114 static int | |
115 LoadPulseLibrary(void) | |
116 { | |
117 int i, retval = -1; | |
118 | |
119 if (pulse_handle == NULL) { | |
120 pulse_handle = SDL_LoadObject(pulse_library); | |
121 if (pulse_handle != NULL) { | |
122 retval = 0; | |
123 for (i = 0; i < SDL_arraysize(pulse_functions); ++i) { | |
124 *pulse_functions[i].func = | |
125 SDL_LoadFunction(pulse_handle, pulse_functions[i].name); | |
126 if (!*pulse_functions[i].func) { | |
127 retval = -1; | |
128 UnloadPulseLibrary(); | |
129 break; | |
130 } | |
131 } | |
132 } | |
133 } | |
134 | |
135 return retval; | |
136 } | |
137 | |
138 #else | |
139 | |
140 static void | |
141 UnloadPulseLibrary() | |
142 { | |
143 return; | |
144 } | |
145 | |
146 static int | |
147 LoadPulseLibrary(void) | |
148 { | |
149 return 0; | |
150 } | |
151 | |
152 #endif /* SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */ | |
153 | |
154 /* This function waits until it is possible to write a full sound buffer */ | |
155 static void | |
156 PULSEAUDIO_WaitDevice(_THIS) | |
157 { | |
158 Sint32 ticks; | |
159 | |
160 /* Check to see if the thread-parent process is still alive */ | |
161 { | |
162 static int cnt = 0; | |
163 /* Note that this only works with thread implementations | |
164 that use a different process id for each thread. | |
165 */ | |
166 /* Check every 10 loops */ | |
167 if (this->hidden->parent && (((++cnt) % 10) == 0)) { | |
168 if (kill(this->hidden->parent, 0) < 0) { | |
169 this->enabled = 0; | |
170 } | |
171 } | |
172 } | |
173 | |
174 /* Use timer for general audio synchronization */ | |
175 ticks = | |
176 ((Sint32) (this->hidden->next_frame - SDL_GetTicks())) - FUDGE_TICKS; | |
177 if (ticks > 0) { | |
178 SDL_Delay(ticks); | |
179 } | |
180 } | |
181 | |
182 static void | |
183 PULSEAUDIO_PlayDevice(_THIS) | |
184 { | |
185 /* Write the audio data */ | |
186 if ( SDL_NAME(pa_simple_write)(this->hidden->stream, this->hidden->mixbuf, | |
187 this->hidden->mixlen, NULL) != 0 ) | |
188 { | |
189 this->enabled = 0; | |
190 } | |
191 } | |
192 | |
193 static void | |
194 PULSEAUDIO_WaitDone(_THIS) | |
195 { | |
196 SDL_NAME(pa_simple_drain)(this->hidden->stream, NULL); | |
197 } | |
198 | |
199 | |
200 static Uint8 * | |
201 PULSEAUDIO_GetDeviceBuf(_THIS) | |
202 { | |
203 return (this->hidden->mixbuf); | |
204 } | |
205 | |
206 | |
207 static void | |
208 PULSEAUDIO_CloseDevice(_THIS) | |
209 { | |
210 if (this->hidden != NULL) { | |
211 if (this->hidden->mixbuf != NULL) { | |
212 SDL_FreeAudioMem(this->hidden->mixbuf); | |
213 this->hidden->mixbuf = NULL; | |
214 } | |
215 if (this->hidden->stream) { | |
216 SDL_NAME(pa_simple_drain)(this->hidden->stream, NULL); | |
217 SDL_NAME(pa_simple_free)(this->hidden->stream); | |
218 this->hidden->stream = NULL; | |
219 } | |
220 SDL_free(this->hidden); | |
221 this->hidden = NULL; | |
222 } | |
223 } | |
224 | |
225 | |
226 /* !!! FIXME: this could probably be expanded. */ | |
227 /* Try to get the name of the program */ | |
228 static char *get_progname(void) | |
229 { | |
230 char *progname = NULL; | |
231 #ifdef __LINUX__ | |
232 FILE *fp; | |
233 static char temp[BUFSIZ]; | |
234 | |
235 SDL_snprintf(temp, SDL_arraysize(temp), "/proc/%d/cmdline", getpid()); | |
236 fp = fopen(temp, "r"); | |
237 if ( fp != NULL ) { | |
238 if ( fgets(temp, sizeof(temp)-1, fp) ) { | |
239 progname = SDL_strrchr(temp, '/'); | |
240 if ( progname == NULL ) { | |
241 progname = temp; | |
242 } else { | |
243 progname = progname+1; | |
244 } | |
245 } | |
246 fclose(fp); | |
247 } | |
248 #endif | |
249 return(progname); | |
250 } | |
251 | |
252 | |
253 static int | |
254 PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture) | |
255 { | |
256 Uint16 test_format; | |
257 pa_sample_spec paspec; | |
258 pa_buffer_attr paattr; | |
259 pa_channel_map pacmap; | |
260 | |
261 /* Initialize all variables that we clean on shutdown */ | |
262 this->hidden = (struct SDL_PrivateAudioData *) | |
263 SDL_malloc((sizeof *this->hidden)); | |
264 if (this->hidden == NULL) { | |
265 SDL_OutOfMemory(); | |
266 return 0; | |
267 } | |
268 SDL_memset(this->hidden, 0, (sizeof *this->hidden)); | |
269 | |
270 paspec.format = PA_SAMPLE_INVALID; | |
271 | |
272 /* Try for a closest match on audio format */ | |
273 for (test_format = SDL_FirstAudioFormat(this->spec.format); | |
274 (paspec.format == PA_SAMPLE_INVALID) && test_format;) { | |
275 #ifdef DEBUG_AUDIO | |
276 fprintf(stderr, "Trying format 0x%4.4x\n", test_format); | |
277 #endif | |
278 switch ( test_format ) { | |
279 case AUDIO_U8: | |
280 paspec.format = PA_SAMPLE_U8; | |
281 break; | |
282 case AUDIO_S16LSB: | |
283 paspec.format = PA_SAMPLE_S16LE; | |
284 break; | |
285 case AUDIO_S16MSB: | |
286 paspec.format = PA_SAMPLE_S16BE; | |
287 break; | |
288 default: | |
289 paspec.format = PA_SAMPLE_INVALID; | |
290 break; | |
291 } | |
292 if (paspec.format == PA_SAMPLE_INVALID) { | |
293 test_format = SDL_NextAudioFormat(); | |
294 } | |
295 } | |
296 if (paspec.format == PA_SAMPLE_INVALID) { | |
297 PULSEAUDIO_CloseDevice(this); | |
298 SDL_SetError("Couldn't find any hardware audio formats"); | |
299 return 0; | |
300 } | |
301 this->spec.format = test_format; | |
302 | |
303 /* Calculate the final parameters for this audio specification */ | |
304 SDL_CalculateAudioSpec(&this->spec); | |
305 | |
306 /* Allocate mixing buffer */ | |
307 this->hidden->mixlen = this->spec.size; | |
308 this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen); | |
309 if (this->hidden->mixbuf == NULL) { | |
310 PULSEAUDIO_CloseDevice(this); | |
311 SDL_OutOfMemory(); | |
312 return 0; | |
313 } | |
314 SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size); | |
315 | |
316 paspec.channels = this->spec.channels; | |
317 paspec.rate = this->spec.freq; | |
318 | |
319 /* Reduced prebuffering compared to the defaults. */ | |
320 paattr.tlength = this->hidden->mixlen; | |
321 paattr.minreq = this->hidden->mixlen; | |
322 paattr.fragsize = this->hidden->mixlen; | |
323 paattr.prebuf = this->hidden->mixlen; | |
324 paattr.maxlength = this->hidden->mixlen * 4; | |
325 | |
326 /* The SDL ALSA output hints us that we use Windows' channel mapping */ | |
327 /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */ | |
328 SDL_NAME(pa_channel_map_init_auto)( | |
329 &pacmap, this->spec.channels, PA_CHANNEL_MAP_WAVEEX); | |
330 | |
331 /* Connect to the PulseAudio server */ | |
332 this->hidden->stream = SDL_NAME(pa_simple_new)( | |
333 SDL_getenv("PASERVER"), /* server */ | |
334 get_progname(), /* application name */ | |
335 PA_STREAM_PLAYBACK, /* playback mode */ | |
336 SDL_getenv("PADEVICE"), /* device on the server */ | |
337 "Simple DirectMedia Layer", /* stream description */ | |
338 &paspec, /* sample format spec */ | |
339 &pacmap, /* channel map */ | |
340 &paattr, /* buffering attributes */ | |
341 NULL /* error code */ | |
342 ); | |
343 if ( this->hidden->stream == NULL ) { | |
344 PULSEAUDIO_CloseDevice(this); | |
345 SDL_SetError("Could not connect to PulseAudio"); | |
346 return(-1); | |
347 } | |
348 | |
349 /* Get the parent process id (we're the parent of the audio thread) */ | |
350 this->hidden->parent = getpid(); | |
351 | |
352 /* We're ready to rock and roll. :-) */ | |
353 return 1; | |
354 } | |
355 | |
356 | |
357 static void | |
358 PULSEAUDIO_Deinitialize(void) | |
359 { | |
360 UnloadPulseLibrary(); | |
361 } | |
362 | |
363 | |
364 static int | |
365 PULSEAUDIO_Init(SDL_AudioDriverImpl * impl) | |
366 { | |
367 if (LoadPulseLibrary() < 0) { | |
368 return 0; | |
369 } | |
370 | |
371 /* Set the function pointers */ | |
372 impl->OpenDevice = PULSEAUDIO_OpenDevice; | |
373 impl->PlayDevice = PULSEAUDIO_PlayDevice; | |
374 impl->WaitDevice = PULSEAUDIO_WaitDevice; | |
375 impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf; | |
376 impl->CloseDevice = PULSEAUDIO_CloseDevice; | |
377 impl->WaitDone = PULSEAUDIO_WaitDone; | |
378 impl->Deinitialize = PULSEAUDIO_Deinitialize; | |
379 impl->OnlyHasDefaultOutputDevice = 1; | |
380 | |
381 return 1; | |
382 } | |
383 | |
384 | |
385 AudioBootStrap PULSEAUDIO_bootstrap = { | |
386 PULSEAUDIO_DRIVER_NAME, "PulseAudio", PULSEAUDIO_Init, 0 | |
387 }; | |
388 | |
389 /* vi: set ts=4 sw=4 expandtab: */ |