src/audio/pulse/SDL_pulseaudio.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 21 Sep 2009 09:27:08 +0000
branchSDL-1.2
changeset 4216 5b99971a27b4
parent 4211 3ce5bfddbaf6
child 4344 14f95e514408
permissions -rw-r--r--
Fixed bug #698

Hans de Goede 2009-02-13 01:10:52 PST

Since the new "glitch free" version of pulseaudio (used in Fedora 10 amongst
others), the sound of SDL using apps (like a simple playmus call) has been
crackling.

While looking in to fixing this I noticed that the current pulseaudio code in
SDL uses pa_simple. However pa_simple uses a thread to pump pulseaudio events
and ipc, given that SDL already has its own thread for audio handling this is
clearly suboptimal, leading to unnecessary context switching IPC, etc. Also
pa_simple does not allow one to implement the WaitAudio() callback for SDL
audiodrivers properly.

Given that my work is mostly a rewrite (although some original pieces remain)
I'm attaching the new .c and .h file, as that is easier to review then the huge
diff.

Let me know if you also want the diff.

This new version has the following features:
-no longer use an additional thread next to the SDL sound thread
-do not crackle with glitch free audio
-when used with a newer pulse, which does glitch free audio, the total latency
is
the same as with the alsa driver
-proper WaitAudio() implementation, saving another mixlen worth of latency
-adds a WaitDone() implementation

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