• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2     SDL - Simple DirectMedia Layer
3     Copyright (C) 1997-2012 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/pulseaudio.h>
33 #include <pulse/simple.h>
34 
35 #include "SDL_timer.h"
36 #include "SDL_audio.h"
37 #include "../SDL_audiomem.h"
38 #include "../SDL_audio_c.h"
39 #include "../SDL_audiodev_c.h"
40 #include "../../../include/SDL_video.h"  /* for SDL_WM_GetCaption(). */
41 #include "SDL_pulseaudio.h"
42 
43 #ifdef SDL_AUDIO_DRIVER_PULSE_DYNAMIC
44 #include "SDL_name.h"
45 #include "SDL_loadso.h"
46 #else
47 #define SDL_NAME(X)	X
48 #endif
49 
50 /* The tag name used by the driver */
51 #define PULSE_DRIVER_NAME	"pulse"
52 
53 /* Audio driver functions */
54 static int PULSE_OpenAudio(_THIS, SDL_AudioSpec *spec);
55 static void PULSE_WaitAudio(_THIS);
56 static void PULSE_PlayAudio(_THIS);
57 static Uint8 *PULSE_GetAudioBuf(_THIS);
58 static void PULSE_CloseAudio(_THIS);
59 static void PULSE_WaitDone(_THIS);
60 static void PULSE_SetCaption(_THIS, const char *str);
61 
62 #ifdef SDL_AUDIO_DRIVER_PULSE_DYNAMIC
63 
64 static const char *pulse_library = SDL_AUDIO_DRIVER_PULSE_DYNAMIC;
65 static void *pulse_handle = NULL;
66 static int pulse_loaded = 0;
67 
68 static pa_simple* (*SDL_NAME(pa_simple_new))(
69 	const char *server,
70 	const char *name,
71 	pa_stream_direction_t dir,
72 	const char *dev,
73 	const char *stream_name,
74 	const pa_sample_spec *ss,
75 	const pa_channel_map *map,
76 	const pa_buffer_attr *attr,
77 	int *error
78 );
79 static void (*SDL_NAME(pa_simple_free))(pa_simple *s);
80 
81 static pa_channel_map* (*SDL_NAME(pa_channel_map_init_auto))(
82 	pa_channel_map *m,
83 	unsigned channels,
84 	pa_channel_map_def_t def
85 );
86 
87 static pa_mainloop * (*SDL_NAME(pa_mainloop_new))(void);
88 static pa_mainloop_api * (*SDL_NAME(pa_mainloop_get_api))(pa_mainloop *m);
89 static int (*SDL_NAME(pa_mainloop_iterate))(pa_mainloop *m, int block, int *retval);
90 static void (*SDL_NAME(pa_mainloop_free))(pa_mainloop *m);
91 
92 static pa_operation_state_t (*SDL_NAME(pa_operation_get_state))(pa_operation *o);
93 static void (*SDL_NAME(pa_operation_cancel))(pa_operation *o);
94 static void (*SDL_NAME(pa_operation_unref))(pa_operation *o);
95 
96 static pa_context * (*SDL_NAME(pa_context_new))(
97 	pa_mainloop_api *m, const char *name);
98 static int (*SDL_NAME(pa_context_connect))(
99 	pa_context *c, const char *server,
100 	pa_context_flags_t flags, const pa_spawn_api *api);
101 static pa_context_state_t (*SDL_NAME(pa_context_get_state))(pa_context *c);
102 static void (*SDL_NAME(pa_context_disconnect))(pa_context *c);
103 static void (*SDL_NAME(pa_context_unref))(pa_context *c);
104 
105 static pa_stream * (*SDL_NAME(pa_stream_new))(pa_context *c,
106 	const char *name, const pa_sample_spec *ss, const pa_channel_map *map);
107 static int (*SDL_NAME(pa_stream_connect_playback))(pa_stream *s, const char *dev,
108 	const pa_buffer_attr *attr, pa_stream_flags_t flags,
109 	pa_cvolume *volume, pa_stream *sync_stream);
110 static pa_stream_state_t (*SDL_NAME(pa_stream_get_state))(pa_stream *s);
111 static size_t (*SDL_NAME(pa_stream_writable_size))(pa_stream *s);
112 static int (*SDL_NAME(pa_stream_write))(pa_stream *s, const void *data, size_t nbytes,
113 	pa_free_cb_t free_cb, int64_t offset, pa_seek_mode_t seek);
114 static pa_operation * (*SDL_NAME(pa_stream_drain))(pa_stream *s,
115 	pa_stream_success_cb_t cb, void *userdata);
116 static int (*SDL_NAME(pa_stream_disconnect))(pa_stream *s);
117 static void (*SDL_NAME(pa_stream_unref))(pa_stream *s);
118 static pa_operation* (*SDL_NAME(pa_context_set_name))(pa_context *c,
119 	const char *name, pa_context_success_cb_t cb, void *userdata);
120 
121 static struct {
122 	const char *name;
123 	void **func;
124 } pulse_functions[] = {
125 	{ "pa_simple_new",
126 		(void **)&SDL_NAME(pa_simple_new)		},
127 	{ "pa_simple_free",
128 		(void **)&SDL_NAME(pa_simple_free)		},
129 	{ "pa_channel_map_init_auto",
130 		(void **)&SDL_NAME(pa_channel_map_init_auto)	},
131 	{ "pa_mainloop_new",
132 		(void **)&SDL_NAME(pa_mainloop_new)		},
133 	{ "pa_mainloop_get_api",
134 		(void **)&SDL_NAME(pa_mainloop_get_api)		},
135 	{ "pa_mainloop_iterate",
136 		(void **)&SDL_NAME(pa_mainloop_iterate)		},
137 	{ "pa_mainloop_free",
138 		(void **)&SDL_NAME(pa_mainloop_free)		},
139 	{ "pa_operation_get_state",
140 		(void **)&SDL_NAME(pa_operation_get_state)	},
141 	{ "pa_operation_cancel",
142 		(void **)&SDL_NAME(pa_operation_cancel)		},
143 	{ "pa_operation_unref",
144 		(void **)&SDL_NAME(pa_operation_unref)		},
145 	{ "pa_context_new",
146 		(void **)&SDL_NAME(pa_context_new)		},
147 	{ "pa_context_connect",
148 		(void **)&SDL_NAME(pa_context_connect)		},
149 	{ "pa_context_get_state",
150 		(void **)&SDL_NAME(pa_context_get_state)	},
151 	{ "pa_context_disconnect",
152 		(void **)&SDL_NAME(pa_context_disconnect)	},
153 	{ "pa_context_unref",
154 		(void **)&SDL_NAME(pa_context_unref)		},
155 	{ "pa_stream_new",
156 		(void **)&SDL_NAME(pa_stream_new)		},
157 	{ "pa_stream_connect_playback",
158 		(void **)&SDL_NAME(pa_stream_connect_playback)	},
159 	{ "pa_stream_get_state",
160 		(void **)&SDL_NAME(pa_stream_get_state)		},
161 	{ "pa_stream_writable_size",
162 		(void **)&SDL_NAME(pa_stream_writable_size)	},
163 	{ "pa_stream_write",
164 		(void **)&SDL_NAME(pa_stream_write)		},
165 	{ "pa_stream_drain",
166 		(void **)&SDL_NAME(pa_stream_drain)		},
167 	{ "pa_stream_disconnect",
168 		(void **)&SDL_NAME(pa_stream_disconnect)	},
169 	{ "pa_stream_unref",
170 		(void **)&SDL_NAME(pa_stream_unref)		},
171 	{ "pa_context_set_name",
172 		(void **)&SDL_NAME(pa_context_set_name)		},
173 };
174 
UnloadPulseLibrary()175 static void UnloadPulseLibrary()
176 {
177 	if ( pulse_loaded ) {
178 		SDL_UnloadObject(pulse_handle);
179 		pulse_handle = NULL;
180 		pulse_loaded = 0;
181 	}
182 }
183 
LoadPulseLibrary(void)184 static int LoadPulseLibrary(void)
185 {
186 	int i, retval = -1;
187 
188 	pulse_handle = SDL_LoadObject(pulse_library);
189 	if ( pulse_handle ) {
190 		pulse_loaded = 1;
191 		retval = 0;
192 		for ( i=0; i<SDL_arraysize(pulse_functions); ++i ) {
193 			*pulse_functions[i].func = SDL_LoadFunction(pulse_handle, pulse_functions[i].name);
194 			if ( !*pulse_functions[i].func ) {
195 				retval = -1;
196 				UnloadPulseLibrary();
197 				break;
198 			}
199 		}
200 	}
201 	return retval;
202 }
203 
204 #else
205 
UnloadPulseLibrary()206 static void UnloadPulseLibrary()
207 {
208 	return;
209 }
210 
LoadPulseLibrary(void)211 static int LoadPulseLibrary(void)
212 {
213 	return 0;
214 }
215 
216 #endif /* SDL_AUDIO_DRIVER_PULSE_DYNAMIC */
217 
218 /* Audio driver bootstrap functions */
219 
Audio_Available(void)220 static int Audio_Available(void)
221 {
222 	pa_sample_spec paspec;
223 	pa_simple *connection;
224 	int available;
225 
226 	available = 0;
227 	if ( LoadPulseLibrary() < 0 ) {
228 		return available;
229 	}
230 
231 	/* Connect with a dummy format. */
232 	paspec.format = PA_SAMPLE_U8;
233 	paspec.rate = 11025;
234 	paspec.channels = 1;
235 	connection = SDL_NAME(pa_simple_new)(
236 		NULL,                        /* server */
237 		"Test stream",               /* application name */
238 		PA_STREAM_PLAYBACK,          /* playback mode */
239 		NULL,                        /* device on the server */
240 		"Simple DirectMedia Layer",  /* stream description */
241 		&paspec,                     /* sample format spec */
242 		NULL,                        /* channel map */
243 		NULL,                        /* buffering attributes */
244 		NULL                         /* error code */
245 	);
246 	if ( connection != NULL ) {
247 		available = 1;
248 		SDL_NAME(pa_simple_free)(connection);
249 	}
250 
251 	UnloadPulseLibrary();
252 	return(available);
253 }
254 
Audio_DeleteDevice(SDL_AudioDevice * device)255 static void Audio_DeleteDevice(SDL_AudioDevice *device)
256 {
257 	SDL_free(device->hidden->caption);
258 	SDL_free(device->hidden);
259 	SDL_free(device);
260 	UnloadPulseLibrary();
261 }
262 
Audio_CreateDevice(int devindex)263 static SDL_AudioDevice *Audio_CreateDevice(int devindex)
264 {
265 	SDL_AudioDevice *this;
266 
267 	/* Initialize all variables that we clean on shutdown */
268 	LoadPulseLibrary();
269 	this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
270 	if ( this ) {
271 		SDL_memset(this, 0, (sizeof *this));
272 		this->hidden = (struct SDL_PrivateAudioData *)
273 				SDL_malloc((sizeof *this->hidden));
274 	}
275 	if ( (this == NULL) || (this->hidden == NULL) ) {
276 		SDL_OutOfMemory();
277 		if ( this ) {
278 			SDL_free(this);
279 		}
280 		return(0);
281 	}
282 	SDL_memset(this->hidden, 0, (sizeof *this->hidden));
283 
284 	/* Set the function pointers */
285 	this->OpenAudio = PULSE_OpenAudio;
286 	this->WaitAudio = PULSE_WaitAudio;
287 	this->PlayAudio = PULSE_PlayAudio;
288 	this->GetAudioBuf = PULSE_GetAudioBuf;
289 	this->CloseAudio = PULSE_CloseAudio;
290 	this->WaitDone = PULSE_WaitDone;
291 	this->SetCaption = PULSE_SetCaption;
292 
293 	this->free = Audio_DeleteDevice;
294 
295 	return this;
296 }
297 
298 AudioBootStrap PULSE_bootstrap = {
299 	PULSE_DRIVER_NAME, "PulseAudio",
300 	Audio_Available, Audio_CreateDevice
301 };
302 
303 /* This function waits until it is possible to write a full sound buffer */
PULSE_WaitAudio(_THIS)304 static void PULSE_WaitAudio(_THIS)
305 {
306 	int size;
307 	while(1) {
308 		if (SDL_NAME(pa_context_get_state)(context) != PA_CONTEXT_READY ||
309 		    SDL_NAME(pa_stream_get_state)(stream) != PA_STREAM_READY ||
310 		    SDL_NAME(pa_mainloop_iterate)(mainloop, 1, NULL) < 0) {
311 			this->enabled = 0;
312 			return;
313 		}
314 		size = SDL_NAME(pa_stream_writable_size)(stream);
315 		if (size >= mixlen)
316 			return;
317 	}
318 }
319 
PULSE_PlayAudio(_THIS)320 static void PULSE_PlayAudio(_THIS)
321 {
322 	/* Write the audio data */
323 	if (SDL_NAME(pa_stream_write)(stream, mixbuf, mixlen, NULL, 0LL, PA_SEEK_RELATIVE) < 0)
324 		this->enabled = 0;
325 }
326 
PULSE_GetAudioBuf(_THIS)327 static Uint8 *PULSE_GetAudioBuf(_THIS)
328 {
329 	return(mixbuf);
330 }
331 
PULSE_CloseAudio(_THIS)332 static void PULSE_CloseAudio(_THIS)
333 {
334 	if ( mixbuf != NULL ) {
335 		SDL_FreeAudioMem(mixbuf);
336 		mixbuf = NULL;
337 	}
338 	if ( stream != NULL ) {
339 		SDL_NAME(pa_stream_disconnect)(stream);
340 		SDL_NAME(pa_stream_unref)(stream);
341 		stream = NULL;
342 	}
343 	if (context != NULL) {
344 		SDL_NAME(pa_context_disconnect)(context);
345 		SDL_NAME(pa_context_unref)(context);
346 		context = NULL;
347 	}
348 	if (mainloop != NULL) {
349 		SDL_NAME(pa_mainloop_free)(mainloop);
350 		mainloop = NULL;
351 	}
352 }
353 
354 /* Try to get the name of the program */
get_progname(void)355 static char *get_progname(void)
356 {
357 #ifdef __LINUX__
358 	char *progname = NULL;
359 	FILE *fp;
360 	static char temp[BUFSIZ];
361 
362 	SDL_snprintf(temp, SDL_arraysize(temp), "/proc/%d/cmdline", getpid());
363 	fp = fopen(temp, "r");
364 	if ( fp != NULL ) {
365 		if ( fgets(temp, sizeof(temp)-1, fp) ) {
366 			progname = SDL_strrchr(temp, '/');
367 			if ( progname == NULL ) {
368 				progname = temp;
369 			} else {
370 				progname = progname+1;
371 			}
372 		}
373 		fclose(fp);
374 	}
375 	return(progname);
376 #elif defined(__NetBSD__)
377 	return getprogname();
378 #else
379 	return("unknown");
380 #endif
381 }
382 
caption_set_complete(pa_context * c,int success,void * userdata)383 static void caption_set_complete(pa_context *c, int success, void *userdata)
384 {
385 	/* no-op. */
386 }
387 
PULSE_SetCaption(_THIS,const char * str)388 static void PULSE_SetCaption(_THIS, const char *str)
389 {
390 	SDL_free(this->hidden->caption);
391 	if ((str == NULL) || (*str == '\0')) {
392 		str = get_progname();  /* set a default so SOMETHING shows up. */
393 	}
394 	this->hidden->caption = SDL_strdup(str);
395 	if (context != NULL) {
396 		SDL_NAME(pa_context_set_name)(context, this->hidden->caption,
397 		                              caption_set_complete, 0);
398 	}
399 }
400 
stream_drain_complete(pa_stream * s,int success,void * userdata)401 static void stream_drain_complete(pa_stream *s, int success, void *userdata)
402 {
403 	/* no-op. */
404 }
405 
PULSE_WaitDone(_THIS)406 static void PULSE_WaitDone(_THIS)
407 {
408 	pa_operation *o;
409 
410 	o = SDL_NAME(pa_stream_drain)(stream, stream_drain_complete, NULL);
411 	if (!o)
412 		return;
413 
414 	while (SDL_NAME(pa_operation_get_state)(o) != PA_OPERATION_DONE) {
415 		if (SDL_NAME(pa_context_get_state)(context) != PA_CONTEXT_READY ||
416 		    SDL_NAME(pa_stream_get_state)(stream) != PA_STREAM_READY ||
417 		    SDL_NAME(pa_mainloop_iterate)(mainloop, 1, NULL) < 0) {
418 			SDL_NAME(pa_operation_cancel)(o);
419 			break;
420 		}
421 	}
422 	SDL_NAME(pa_operation_unref)(o);
423 }
424 
PULSE_OpenAudio(_THIS,SDL_AudioSpec * spec)425 static int PULSE_OpenAudio(_THIS, SDL_AudioSpec *spec)
426 {
427 	int             state;
428 	Uint16          test_format;
429 	pa_sample_spec  paspec;
430 	pa_buffer_attr  paattr;
431 	pa_channel_map  pacmap;
432 	pa_stream_flags_t flags = 0;
433 
434 	paspec.format = PA_SAMPLE_INVALID;
435 	for ( test_format = SDL_FirstAudioFormat(spec->format); test_format; ) {
436 		switch ( test_format ) {
437 			case AUDIO_U8:
438 				paspec.format = PA_SAMPLE_U8;
439 				break;
440 			case AUDIO_S16LSB:
441 				paspec.format = PA_SAMPLE_S16LE;
442 				break;
443 			case AUDIO_S16MSB:
444 				paspec.format = PA_SAMPLE_S16BE;
445 				break;
446 		}
447 		if ( paspec.format != PA_SAMPLE_INVALID )
448 			break;
449 		test_format = SDL_NextAudioFormat();
450 	}
451 	if (paspec.format == PA_SAMPLE_INVALID ) {
452 		SDL_SetError("Couldn't find any suitable audio formats");
453 		return(-1);
454 	}
455 	spec->format = test_format;
456 
457 	paspec.channels = spec->channels;
458 	paspec.rate = spec->freq;
459 
460 	/* Calculate the final parameters for this audio specification */
461 #ifdef PA_STREAM_ADJUST_LATENCY
462 	spec->samples /= 2; /* Mix in smaller chunck to avoid underruns */
463 #endif
464 	SDL_CalculateAudioSpec(spec);
465 
466 	/* Allocate mixing buffer */
467 	mixlen = spec->size;
468 	mixbuf = (Uint8 *)SDL_AllocAudioMem(mixlen);
469 	if ( mixbuf == NULL ) {
470 		return(-1);
471 	}
472 	SDL_memset(mixbuf, spec->silence, spec->size);
473 
474 	/* Reduced prebuffering compared to the defaults. */
475 #ifdef PA_STREAM_ADJUST_LATENCY
476 	paattr.tlength = mixlen * 4; /* 2x original requested bufsize */
477 	paattr.prebuf = -1;
478 	paattr.maxlength = -1;
479 	paattr.minreq = mixlen; /* -1 can lead to pa_stream_writable_size()
480 				   >= mixlen never becoming true */
481 	flags = PA_STREAM_ADJUST_LATENCY;
482 #else
483 	paattr.tlength = mixlen*2;
484 	paattr.prebuf = mixlen*2;
485 	paattr.maxlength = mixlen*2;
486 	paattr.minreq = mixlen;
487 #endif
488 
489 	/* The SDL ALSA output hints us that we use Windows' channel mapping */
490 	/* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
491 	SDL_NAME(pa_channel_map_init_auto)(
492 		&pacmap, spec->channels, PA_CHANNEL_MAP_WAVEEX);
493 
494 	/* Set up a new main loop */
495 	if (!(mainloop = SDL_NAME(pa_mainloop_new)())) {
496 		PULSE_CloseAudio(this);
497 		SDL_SetError("pa_mainloop_new() failed");
498 		return(-1);
499 	}
500 
501 	if (this->hidden->caption == NULL) {
502 		char *title = NULL;
503 		SDL_WM_GetCaption(&title, NULL);
504 		PULSE_SetCaption(this, title);
505 	}
506 
507 	mainloop_api = SDL_NAME(pa_mainloop_get_api)(mainloop);
508 	if (!(context = SDL_NAME(pa_context_new)(mainloop_api,
509 	                                         this->hidden->caption))) {
510 		PULSE_CloseAudio(this);
511 		SDL_SetError("pa_context_new() failed");
512 		return(-1);
513 	}
514 
515 	/* Connect to the PulseAudio server */
516 	if (SDL_NAME(pa_context_connect)(context, NULL, 0, NULL) < 0) {
517 		PULSE_CloseAudio(this);
518 		SDL_SetError("Could not setup connection to PulseAudio");
519 		return(-1);
520 	}
521 
522 	do {
523 		if (SDL_NAME(pa_mainloop_iterate)(mainloop, 1, NULL) < 0) {
524 			PULSE_CloseAudio(this);
525 			SDL_SetError("pa_mainloop_iterate() failed");
526 			return(-1);
527 		}
528 		state = SDL_NAME(pa_context_get_state)(context);
529 		if (!PA_CONTEXT_IS_GOOD(state)) {
530 			PULSE_CloseAudio(this);
531 			SDL_SetError("Could not connect to PulseAudio");
532 			return(-1);
533 		}
534 	} while (state != PA_CONTEXT_READY);
535 
536 	stream = SDL_NAME(pa_stream_new)(
537 		context,
538 		"Simple DirectMedia Layer",  /* stream description */
539 		&paspec,                     /* sample format spec */
540 		&pacmap                      /* channel map */
541 	);
542 	if ( stream == NULL ) {
543 		PULSE_CloseAudio(this);
544 		SDL_SetError("Could not setup PulseAudio stream");
545 		return(-1);
546 	}
547 
548 	if (SDL_NAME(pa_stream_connect_playback)(stream, NULL, &paattr, flags,
549 			NULL, NULL) < 0) {
550 		PULSE_CloseAudio(this);
551 		SDL_SetError("Could not connect PulseAudio stream");
552 		return(-1);
553 	}
554 
555 	do {
556 		if (SDL_NAME(pa_mainloop_iterate)(mainloop, 1, NULL) < 0) {
557 			PULSE_CloseAudio(this);
558 			SDL_SetError("pa_mainloop_iterate() failed");
559 			return(-1);
560 		}
561 		state = SDL_NAME(pa_stream_get_state)(stream);
562 		if (!PA_STREAM_IS_GOOD(state)) {
563 			PULSE_CloseAudio(this);
564 			SDL_SetError("Could not create to PulseAudio stream");
565 			return(-1);
566 		}
567 	} while (state != PA_STREAM_READY);
568 
569 	return(0);
570 }
571