Skip to content

Commit

Permalink
PulseAudio: Added multiple device support, other cleanups.
Browse files Browse the repository at this point in the history
Thanks to Dominik Frizel for most of the effort on this!

Fixes Bugzilla #2730.
  • Loading branch information
icculus committed Mar 18, 2015
1 parent 73feb8c commit 9a83151
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 92 deletions.
237 changes: 146 additions & 91 deletions src/audio/pulseaudio/SDL_pulseaudio.c
Expand Up @@ -26,6 +26,7 @@
Stéphan Kochen: stephan .a.t. kochen.nl
*/
#include "../../SDL_internal.h"
#include "SDL_assert.h"

#if SDL_AUDIO_DRIVER_PULSEAUDIO

Expand All @@ -38,7 +39,6 @@
#include <sys/types.h>
#include <errno.h>
#include <pulse/pulseaudio.h>
#include <pulse/simple.h>

#include "SDL_timer.h"
#include "SDL_audio.h"
Expand Down Expand Up @@ -66,10 +66,6 @@ static SDL_INLINE int PA_STREAM_IS_GOOD(pa_stream_state_t x) {


static const char *(*PULSEAUDIO_pa_get_library_version) (void);
static pa_simple *(*PULSEAUDIO_pa_simple_new) (const char *, const char *,
pa_stream_direction_t, const char *, const char *, const pa_sample_spec *,
const pa_channel_map *, const pa_buffer_attr *, int *);
static void (*PULSEAUDIO_pa_simple_free) (pa_simple *);
static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto) (
pa_channel_map *, unsigned, pa_channel_map_def_t);
static const char * (*PULSEAUDIO_pa_strerror) (int);
Expand All @@ -87,6 +83,7 @@ static pa_context * (*PULSEAUDIO_pa_context_new) (pa_mainloop_api *,
const char *);
static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *,
pa_context_flags_t, const pa_spawn_api *);
static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_list)(pa_context *, pa_sink_info_cb_t, void *);
static pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *);
static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *);
static void (*PULSEAUDIO_pa_context_unref) (pa_context *);
Expand Down Expand Up @@ -179,8 +176,6 @@ static int
load_pulseaudio_syms(void)
{
SDL_PULSEAUDIO_SYM(pa_get_library_version);
SDL_PULSEAUDIO_SYM(pa_simple_new);
SDL_PULSEAUDIO_SYM(pa_simple_free);
SDL_PULSEAUDIO_SYM(pa_mainloop_new);
SDL_PULSEAUDIO_SYM(pa_mainloop_get_api);
SDL_PULSEAUDIO_SYM(pa_mainloop_iterate);
Expand All @@ -190,6 +185,7 @@ load_pulseaudio_syms(void)
SDL_PULSEAUDIO_SYM(pa_operation_unref);
SDL_PULSEAUDIO_SYM(pa_context_new);
SDL_PULSEAUDIO_SYM(pa_context_connect);
SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list);
SDL_PULSEAUDIO_SYM(pa_context_get_state);
SDL_PULSEAUDIO_SYM(pa_context_disconnect);
SDL_PULSEAUDIO_SYM(pa_context_unref);
Expand All @@ -206,28 +202,96 @@ load_pulseaudio_syms(void)
return 0;
}

static SDL_INLINE int
squashVersion(const int major, const int minor, const int patch)
{
return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF);
}

/* Workaround for older pulse: pa_context_new() must have non-NULL appname */
static const char *
getAppName(void)
{
const char *verstr = PULSEAUDIO_pa_get_library_version();
if (verstr != NULL) {
int maj, min, patch;
if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) {
if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) {
return NULL; /* 0.9.15+ handles NULL correctly. */
}
}
}
return "SDL Application"; /* oh well. */
}

/* Check to see if we can connect to PulseAudio */
static SDL_bool
CheckPulseAudioAvailable()
static void
DisconnectFromPulseServer(pa_mainloop *mainloop, pa_context *context)
{
pa_simple *s;
pa_sample_spec ss;

ss.format = PA_SAMPLE_S16NE;
ss.channels = 1;
ss.rate = 22050;

s = PULSEAUDIO_pa_simple_new(NULL, "SDL", PA_STREAM_PLAYBACK, NULL,
"Test", &ss, NULL, NULL, NULL);
if (s) {
PULSEAUDIO_pa_simple_free(s);
return SDL_TRUE;
} else {
return SDL_FALSE;
if (context) {
PULSEAUDIO_pa_context_disconnect(context);
PULSEAUDIO_pa_context_unref(context);
}
if (mainloop != NULL) {
PULSEAUDIO_pa_mainloop_free(mainloop);
}
}

static int
ConnectToPulseServer_Internal(pa_mainloop **_mainloop, pa_context **_context)
{
pa_mainloop *mainloop = NULL;
pa_context *context = NULL;
pa_mainloop_api *mainloop_api = NULL;
int state = 0;

*_mainloop = NULL;
*_context = NULL;

/* Set up a new main loop */
if (!(mainloop = PULSEAUDIO_pa_mainloop_new())) {
return SDL_SetError("pa_mainloop_new() failed");
}

*_mainloop = mainloop;

mainloop_api = PULSEAUDIO_pa_mainloop_get_api(mainloop);
SDL_assert(mainloop_api); /* this never fails, right? */

context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName());
if (!context) {
return SDL_SetError("pa_context_new() failed");
}
*_context = context;

/* Connect to the PulseAudio server */
if (PULSEAUDIO_pa_context_connect(context, NULL, 0, NULL) < 0) {
return SDL_SetError("Could not setup connection to PulseAudio");
}

do {
if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) < 0) {
return SDL_SetError("pa_mainloop_iterate() failed");
}
state = PULSEAUDIO_pa_context_get_state(context);
if (!PA_CONTEXT_IS_GOOD(state)) {
return SDL_SetError("Could not connect to PulseAudio");
}
} while (state != PA_CONTEXT_READY);

return 0; /* connected and ready! */
}

static int
ConnectToPulseServer(pa_mainloop **_mainloop, pa_context **_context)
{
const int retval = ConnectToPulseServer_Internal(_mainloop, _context);
if (retval < 0) {
DisconnectFromPulseServer(*_mainloop, *_context);
}
return retval;
}


/* This function waits until it is possible to write a full sound buffer */
static void
PULSEAUDIO_WaitDevice(_THIS)
Expand Down Expand Up @@ -307,43 +371,12 @@ PULSEAUDIO_CloseDevice(_THIS)
PULSEAUDIO_pa_stream_unref(this->hidden->stream);
this->hidden->stream = NULL;
}
if (this->hidden->context != NULL) {
PULSEAUDIO_pa_context_disconnect(this->hidden->context);
PULSEAUDIO_pa_context_unref(this->hidden->context);
this->hidden->context = NULL;
}
if (this->hidden->mainloop != NULL) {
PULSEAUDIO_pa_mainloop_free(this->hidden->mainloop);
this->hidden->mainloop = NULL;
}
DisconnectFromPulseServer(this->hidden->mainloop, this->hidden->context);
SDL_free(this->hidden);
this->hidden = NULL;
}
}


static SDL_INLINE int
squashVersion(const int major, const int minor, const int patch)
{
return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF);
}

/* Workaround for older pulse: pa_context_new() must have non-NULL appname */
static const char *
getAppName(void)
{
const char *verstr = PULSEAUDIO_pa_get_library_version();
if (verstr != NULL) {
int maj, min, patch;
if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) {
if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) {
return NULL; /* 0.9.15+ handles NULL correctly. */
}
}
}
return "SDL Application"; /* oh well. */
}

static int
PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
{
Expand Down Expand Up @@ -442,42 +475,16 @@ PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
paattr.minreq = h->mixlen;
#endif

if (ConnectToPulseServer(&h->mainloop, &h->context) < 0) {
PULSEAUDIO_CloseDevice(this);
return SDL_SetError("Could not connect to PulseAudio server");
}

/* The SDL ALSA output hints us that we use Windows' channel mapping */
/* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->spec.channels,
PA_CHANNEL_MAP_WAVEEX);

/* Set up a new main loop */
if (!(h->mainloop = PULSEAUDIO_pa_mainloop_new())) {
PULSEAUDIO_CloseDevice(this);
return SDL_SetError("pa_mainloop_new() failed");
}

h->mainloop_api = PULSEAUDIO_pa_mainloop_get_api(h->mainloop);
h->context = PULSEAUDIO_pa_context_new(h->mainloop_api, getAppName());
if (!h->context) {
PULSEAUDIO_CloseDevice(this);
return SDL_SetError("pa_context_new() failed");
}

/* Connect to the PulseAudio server */
if (PULSEAUDIO_pa_context_connect(h->context, NULL, 0, NULL) < 0) {
PULSEAUDIO_CloseDevice(this);
return SDL_SetError("Could not setup connection to PulseAudio");
}

do {
if (PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
PULSEAUDIO_CloseDevice(this);
return SDL_SetError("pa_mainloop_iterate() failed");
}
state = PULSEAUDIO_pa_context_get_state(h->context);
if (!PA_CONTEXT_IS_GOOD(state)) {
PULSEAUDIO_CloseDevice(this);
return SDL_SetError("Could not connect to PulseAudio");
}
} while (state != PA_CONTEXT_READY);

h->stream = PULSEAUDIO_pa_stream_new(
h->context,
"Simple DirectMedia Layer", /* stream description */
Expand All @@ -490,7 +497,7 @@ PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
return SDL_SetError("Could not set up PulseAudio stream");
}

if (PULSEAUDIO_pa_stream_connect_playback(h->stream, NULL, &paattr, flags,
if (PULSEAUDIO_pa_stream_connect_playback(h->stream, devname, &paattr, flags,
NULL, NULL) < 0) {
PULSEAUDIO_CloseDevice(this);
return SDL_SetError("Could not connect PulseAudio stream");
Expand All @@ -512,6 +519,50 @@ PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
return 0;
}

typedef struct
{
uint8_t last;
SDL_AddAudioDevice addfn;
} sink_struct;

static void
get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata)
{
sink_struct *a = (sink_struct *) userdata;
a->last = is_last;
if (i) {
a->addfn(i->name);
}
}

static void
PULSEAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
{
pa_mainloop *mainloop = NULL;
pa_context *context = NULL;

if (ConnectToPulseServer(&mainloop, &context) < 0) {
return;
}

if (!iscapture) {
sink_struct a;
a.last = 0;
a.addfn = addfn;
pa_operation* o = PULSEAUDIO_pa_context_get_sink_info_list(context,
get_sink_info_callback, &a);
while (!a.last) {
if (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_CANCELLED) {
break;
}
if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1, NULL) < 0) {
break;
}
}
}

DisconnectFromPulseServer(mainloop, context);
}

static void
PULSEAUDIO_Deinitialize(void)
Expand All @@ -522,29 +573,33 @@ PULSEAUDIO_Deinitialize(void)
static int
PULSEAUDIO_Init(SDL_AudioDriverImpl * impl)
{
pa_mainloop *mainloop = NULL;
pa_context *context = NULL;

if (LoadPulseAudioLibrary() < 0) {
return 0;
}

if (!CheckPulseAudioAvailable()) {
if (ConnectToPulseServer(&mainloop, &context) < 0) {
UnloadPulseAudioLibrary();
return 0;
}

DisconnectFromPulseServer(mainloop, context);

/* Set the function pointers */
impl->DetectDevices = PULSEAUDIO_DetectDevices;
impl->OpenDevice = PULSEAUDIO_OpenDevice;
impl->PlayDevice = PULSEAUDIO_PlayDevice;
impl->WaitDevice = PULSEAUDIO_WaitDevice;
impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf;
impl->CloseDevice = PULSEAUDIO_CloseDevice;
impl->WaitDone = PULSEAUDIO_WaitDone;
impl->Deinitialize = PULSEAUDIO_Deinitialize;
impl->OnlyHasDefaultOutputDevice = 1;

return 1; /* this audio target is available. */
}


AudioBootStrap PULSEAUDIO_bootstrap = {
"pulseaudio", "PulseAudio", PULSEAUDIO_Init, 0
};
Expand Down
1 change: 0 additions & 1 deletion src/audio/pulseaudio/SDL_pulseaudio.h
Expand Up @@ -34,7 +34,6 @@ struct SDL_PrivateAudioData
{
/* pulseaudio structures */
pa_mainloop *mainloop;
pa_mainloop_api *mainloop_api;
pa_context *context;
pa_stream *stream;

Expand Down

0 comments on commit 9a83151

Please sign in to comment.