comparison src/audio/pulse/SDL_pulseaudio.c @ 3939:42e83d81224b SDL-1.2

Committed PulseAudio driver. Thanks, Stephan!
author Ryan C. Gordon <icculus@icculus.org>
date Sun, 13 May 2007 23:12:46 +0000
parents
children a1b03ba2fcd0
comparison
equal deleted inserted replaced
3938:345908fb5442 3939:42e83d81224b
1 /*
2 SDL - Simple DirectMedia Layer
3 Copyright (C) 1997-2007 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 Stéphan Kochen
20 stephan@kochen.nl
21
22 Based on parts of the ALSA and ESounD output drivers.
23 */
24 #include "SDL_config.h"
25
26 /* Allow access to an PulseAudio network stream mixing buffer */
27
28 #include <sys/types.h>
29 #include <unistd.h>
30 #include <signal.h>
31 #include <errno.h>
32 #include <pulse/simple.h>
33
34 #include "SDL_timer.h"
35 #include "SDL_audio.h"
36 #include "../SDL_audiomem.h"
37 #include "../SDL_audio_c.h"
38 #include "../SDL_audiodev_c.h"
39 #include "SDL_pulseaudio.h"
40
41 #ifdef SDL_AUDIO_DRIVER_PULSE_DYNAMIC
42 #include "SDL_name.h"
43 #include "SDL_loadso.h"
44 #else
45 #define SDL_NAME(X) X
46 #endif
47
48 /* The tag name used by the driver */
49 #define PULSE_DRIVER_NAME "pulse"
50
51 /* Audio driver functions */
52 static int PULSE_OpenAudio(_THIS, SDL_AudioSpec *spec);
53 static void PULSE_WaitAudio(_THIS);
54 static void PULSE_PlayAudio(_THIS);
55 static Uint8 *PULSE_GetAudioBuf(_THIS);
56 static void PULSE_CloseAudio(_THIS);
57
58 #ifdef SDL_AUDIO_DRIVER_PULSE_DYNAMIC
59
60 static const char *pulse_library = SDL_AUDIO_DRIVER_PULSE_DYNAMIC;
61 static void *pulse_handle = NULL;
62 static int pulse_loaded = 0;
63
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 static struct {
91 const char *name;
92 void **func;
93 } pulse_functions[] = {
94 { "pa_simple_new",
95 (void **)&SDL_NAME(pa_simple_new) },
96 { "pa_simple_free",
97 (void **)&SDL_NAME(pa_simple_free) },
98 { "pa_simple_drain",
99 (void **)&SDL_NAME(pa_simple_drain) },
100 { "pa_simple_write",
101 (void **)&SDL_NAME(pa_simple_write) },
102 { "pa_channel_map_init_auto",
103 (void **)&SDL_NAME(pa_channel_map_init_auto) },
104 };
105
106 static void UnloadPulseLibrary()
107 {
108 if ( pulse_loaded ) {
109 SDL_UnloadObject(pulse_handle);
110 pulse_handle = NULL;
111 pulse_loaded = 0;
112 }
113 }
114
115 static int LoadPulseLibrary(void)
116 {
117 int i, retval = -1;
118
119 pulse_handle = SDL_LoadObject(pulse_library);
120 if ( pulse_handle ) {
121 pulse_loaded = 1;
122 retval = 0;
123 for ( i=0; i<SDL_arraysize(pulse_functions); ++i ) {
124 *pulse_functions[i].func = SDL_LoadFunction(pulse_handle, pulse_functions[i].name);
125 if ( !*pulse_functions[i].func ) {
126 retval = -1;
127 UnloadPulseLibrary();
128 break;
129 }
130 }
131 }
132 return retval;
133 }
134
135 #else
136
137 static void UnloadPulseLibrary()
138 {
139 return;
140 }
141
142 static int LoadPulseLibrary(void)
143 {
144 return 0;
145 }
146
147 #endif /* SDL_AUDIO_DRIVER_PULSE_DYNAMIC */
148
149 /* Audio driver bootstrap functions */
150
151 static int Audio_Available(void)
152 {
153 pa_sample_spec paspec;
154 pa_simple *connection;
155 int available;
156
157 available = 0;
158 if ( LoadPulseLibrary() < 0 ) {
159 return available;
160 }
161
162 /* Connect with a dummy format. */
163 paspec.format = PA_SAMPLE_U8;
164 paspec.rate = 11025;
165 paspec.channels = 1;
166 connection = SDL_NAME(pa_simple_new)(
167 SDL_getenv("PASERVER"), /* server */
168 "Test stream", /* application name */
169 PA_STREAM_PLAYBACK, /* playback mode */
170 SDL_getenv("PADEVICE"), /* device on the server */
171 "Simple DirectMedia Layer", /* stream description */
172 &paspec, /* sample format spec */
173 NULL, /* channel map */
174 NULL, /* buffering attributes */
175 NULL /* error code */
176 );
177 if ( connection != NULL ) {
178 available = 1;
179 SDL_NAME(pa_simple_free)(connection);
180 }
181
182 UnloadPulseLibrary();
183 return(available);
184 }
185
186 static void Audio_DeleteDevice(SDL_AudioDevice *device)
187 {
188 SDL_free(device->hidden);
189 SDL_free(device);
190 UnloadPulseLibrary();
191 }
192
193 static SDL_AudioDevice *Audio_CreateDevice(int devindex)
194 {
195 SDL_AudioDevice *this;
196
197 /* Initialize all variables that we clean on shutdown */
198 LoadPulseLibrary();
199 this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
200 if ( this ) {
201 SDL_memset(this, 0, (sizeof *this));
202 this->hidden = (struct SDL_PrivateAudioData *)
203 SDL_malloc((sizeof *this->hidden));
204 }
205 if ( (this == NULL) || (this->hidden == NULL) ) {
206 SDL_OutOfMemory();
207 if ( this ) {
208 SDL_free(this);
209 }
210 return(0);
211 }
212 SDL_memset(this->hidden, 0, (sizeof *this->hidden));
213
214 /* Set the function pointers */
215 this->OpenAudio = PULSE_OpenAudio;
216 this->WaitAudio = PULSE_WaitAudio;
217 this->PlayAudio = PULSE_PlayAudio;
218 this->GetAudioBuf = PULSE_GetAudioBuf;
219 this->CloseAudio = PULSE_CloseAudio;
220
221 this->free = Audio_DeleteDevice;
222
223 return this;
224 }
225
226 AudioBootStrap PULSE_bootstrap = {
227 PULSE_DRIVER_NAME, "PulseAudio",
228 Audio_Available, Audio_CreateDevice
229 };
230
231 /* This function waits until it is possible to write a full sound buffer */
232 static void PULSE_WaitAudio(_THIS)
233 {
234 /* Check to see if the thread-parent process is still alive */
235 { static int cnt = 0;
236 /* Note that this only works with thread implementations
237 that use a different process id for each thread.
238 */
239 if (parent && (((++cnt)%10) == 0)) { /* Check every 10 loops */
240 if ( kill(parent, 0) < 0 ) {
241 this->enabled = 0;
242 }
243 }
244 }
245 }
246
247 static void PULSE_PlayAudio(_THIS)
248 {
249 /* Write the audio data */
250 if ( SDL_NAME(pa_simple_write)(stream, mixbuf, mixlen, NULL) != 0 )
251 {
252 this->enabled = 0;
253 }
254 }
255
256 static Uint8 *PULSE_GetAudioBuf(_THIS)
257 {
258 return(mixbuf);
259 }
260
261 static void PULSE_CloseAudio(_THIS)
262 {
263 if ( mixbuf != NULL ) {
264 SDL_FreeAudioMem(mixbuf);
265 mixbuf = NULL;
266 }
267 if ( stream != NULL ) {
268 SDL_NAME(pa_simple_drain)(stream, NULL);
269 SDL_NAME(pa_simple_free)(stream);
270 stream = NULL;
271 }
272 }
273
274 /* Try to get the name of the program */
275 static char *get_progname(void)
276 {
277 char *progname = NULL;
278 #ifdef __LINUX__
279 FILE *fp;
280 static char temp[BUFSIZ];
281
282 SDL_snprintf(temp, SDL_arraysize(temp), "/proc/%d/cmdline", getpid());
283 fp = fopen(temp, "r");
284 if ( fp != NULL ) {
285 if ( fgets(temp, sizeof(temp)-1, fp) ) {
286 progname = SDL_strrchr(temp, '/');
287 if ( progname == NULL ) {
288 progname = temp;
289 } else {
290 progname = progname+1;
291 }
292 }
293 fclose(fp);
294 }
295 #endif
296 return(progname);
297 }
298
299 static int PULSE_OpenAudio(_THIS, SDL_AudioSpec *spec)
300 {
301 Uint16 test_format;
302 pa_sample_spec paspec;
303 pa_buffer_attr paattr;
304 pa_channel_map pacmap;
305
306 paspec.format = PA_SAMPLE_INVALID;
307 for ( test_format = SDL_FirstAudioFormat(spec->format); test_format; ) {
308 switch ( test_format ) {
309 case AUDIO_U8:
310 paspec.format = PA_SAMPLE_U8;
311 break;
312 case AUDIO_S16LSB:
313 paspec.format = PA_SAMPLE_S16LE;
314 break;
315 case AUDIO_S16MSB:
316 paspec.format = PA_SAMPLE_S16BE;
317 break;
318 }
319 if ( paspec.format != PA_SAMPLE_INVALID )
320 break;
321 }
322 if (paspec.format == PA_SAMPLE_INVALID ) {
323 SDL_SetError("Couldn't find any suitable audio formats");
324 return(-1);
325 }
326 spec->format = test_format;
327
328 paspec.channels = spec->channels;
329 paspec.rate = spec->freq;
330
331 /* Calculate the final parameters for this audio specification */
332 SDL_CalculateAudioSpec(spec);
333
334 /* Allocate mixing buffer */
335 mixlen = spec->size;
336 mixbuf = (Uint8 *)SDL_AllocAudioMem(mixlen);
337 if ( mixbuf == NULL ) {
338 return(-1);
339 }
340 SDL_memset(mixbuf, spec->silence, spec->size);
341
342 /* Reduced prebuffering compared to the defaults. */
343 paattr.tlength = mixlen;
344 paattr.minreq = mixlen;
345 paattr.fragsize = mixlen;
346 paattr.prebuf = mixlen;
347 paattr.maxlength = mixlen * 4;
348
349 /* The SDL ALSA output hints us that we use Windows' channel mapping */
350 /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
351 SDL_NAME(pa_channel_map_init_auto)(
352 &pacmap, spec->channels, PA_CHANNEL_MAP_WAVEEX);
353
354 /* Connect to the PulseAudio server */
355 stream = SDL_NAME(pa_simple_new)(
356 SDL_getenv("PASERVER"), /* server */
357 get_progname(), /* application name */
358 PA_STREAM_PLAYBACK, /* playback mode */
359 SDL_getenv("PADEVICE"), /* device on the server */
360 "Simple DirectMedia Layer", /* stream description */
361 &paspec, /* sample format spec */
362 &pacmap, /* channel map */
363 &paattr, /* buffering attributes */
364 NULL /* error code */
365 );
366 if ( stream == NULL ) {
367 PULSE_CloseAudio(this);
368 SDL_SetError("Could not connect to PulseAudio");
369 return(-1);
370 }
371
372 /* Get the parent process id (we're the parent of the audio thread) */
373 parent = getpid();
374
375 return(0);
376 }
377