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: */