mirror of
https://github.com/Ryujinx/SDL.git
synced 2025-01-12 12:15:29 +00:00
PulseAudio: Added multiple device support, other cleanups.
Thanks to Dominik Frizel for most of the effort on this! Fixes Bugzilla #2730.
This commit is contained in:
parent
73feb8c042
commit
9a83151e9c
|
@ -26,6 +26,7 @@
|
||||||
Stéphan Kochen: stephan .a.t. kochen.nl
|
Stéphan Kochen: stephan .a.t. kochen.nl
|
||||||
*/
|
*/
|
||||||
#include "../../SDL_internal.h"
|
#include "../../SDL_internal.h"
|
||||||
|
#include "SDL_assert.h"
|
||||||
|
|
||||||
#if SDL_AUDIO_DRIVER_PULSEAUDIO
|
#if SDL_AUDIO_DRIVER_PULSEAUDIO
|
||||||
|
|
||||||
|
@ -38,7 +39,6 @@
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <pulse/pulseaudio.h>
|
#include <pulse/pulseaudio.h>
|
||||||
#include <pulse/simple.h>
|
|
||||||
|
|
||||||
#include "SDL_timer.h"
|
#include "SDL_timer.h"
|
||||||
#include "SDL_audio.h"
|
#include "SDL_audio.h"
|
||||||
|
@ -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 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) (
|
static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto) (
|
||||||
pa_channel_map *, unsigned, pa_channel_map_def_t);
|
pa_channel_map *, unsigned, pa_channel_map_def_t);
|
||||||
static const char * (*PULSEAUDIO_pa_strerror) (int);
|
static const char * (*PULSEAUDIO_pa_strerror) (int);
|
||||||
|
@ -87,6 +83,7 @@ static pa_context * (*PULSEAUDIO_pa_context_new) (pa_mainloop_api *,
|
||||||
const char *);
|
const char *);
|
||||||
static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *,
|
static int (*PULSEAUDIO_pa_context_connect) (pa_context *, const char *,
|
||||||
pa_context_flags_t, const pa_spawn_api *);
|
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 pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *);
|
||||||
static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *);
|
static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *);
|
||||||
static void (*PULSEAUDIO_pa_context_unref) (pa_context *);
|
static void (*PULSEAUDIO_pa_context_unref) (pa_context *);
|
||||||
|
@ -179,8 +176,6 @@ static int
|
||||||
load_pulseaudio_syms(void)
|
load_pulseaudio_syms(void)
|
||||||
{
|
{
|
||||||
SDL_PULSEAUDIO_SYM(pa_get_library_version);
|
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_new);
|
||||||
SDL_PULSEAUDIO_SYM(pa_mainloop_get_api);
|
SDL_PULSEAUDIO_SYM(pa_mainloop_get_api);
|
||||||
SDL_PULSEAUDIO_SYM(pa_mainloop_iterate);
|
SDL_PULSEAUDIO_SYM(pa_mainloop_iterate);
|
||||||
|
@ -190,6 +185,7 @@ load_pulseaudio_syms(void)
|
||||||
SDL_PULSEAUDIO_SYM(pa_operation_unref);
|
SDL_PULSEAUDIO_SYM(pa_operation_unref);
|
||||||
SDL_PULSEAUDIO_SYM(pa_context_new);
|
SDL_PULSEAUDIO_SYM(pa_context_new);
|
||||||
SDL_PULSEAUDIO_SYM(pa_context_connect);
|
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_get_state);
|
||||||
SDL_PULSEAUDIO_SYM(pa_context_disconnect);
|
SDL_PULSEAUDIO_SYM(pa_context_disconnect);
|
||||||
SDL_PULSEAUDIO_SYM(pa_context_unref);
|
SDL_PULSEAUDIO_SYM(pa_context_unref);
|
||||||
|
@ -206,28 +202,96 @@ load_pulseaudio_syms(void)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SDL_INLINE int
|
||||||
/* Check to see if we can connect to PulseAudio */
|
squashVersion(const int major, const int minor, const int patch)
|
||||||
static SDL_bool
|
|
||||||
CheckPulseAudioAvailable()
|
|
||||||
{
|
{
|
||||||
pa_simple *s;
|
return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF);
|
||||||
pa_sample_spec ss;
|
}
|
||||||
|
|
||||||
ss.format = PA_SAMPLE_S16NE;
|
/* Workaround for older pulse: pa_context_new() must have non-NULL appname */
|
||||||
ss.channels = 1;
|
static const char *
|
||||||
ss.rate = 22050;
|
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. */
|
||||||
|
}
|
||||||
|
|
||||||
s = PULSEAUDIO_pa_simple_new(NULL, "SDL", PA_STREAM_PLAYBACK, NULL,
|
static void
|
||||||
"Test", &ss, NULL, NULL, NULL);
|
DisconnectFromPulseServer(pa_mainloop *mainloop, pa_context *context)
|
||||||
if (s) {
|
{
|
||||||
PULSEAUDIO_pa_simple_free(s);
|
if (context) {
|
||||||
return SDL_TRUE;
|
PULSEAUDIO_pa_context_disconnect(context);
|
||||||
} else {
|
PULSEAUDIO_pa_context_unref(context);
|
||||||
return SDL_FALSE;
|
}
|
||||||
|
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 */
|
/* This function waits until it is possible to write a full sound buffer */
|
||||||
static void
|
static void
|
||||||
PULSEAUDIO_WaitDevice(_THIS)
|
PULSEAUDIO_WaitDevice(_THIS)
|
||||||
|
@ -307,43 +371,12 @@ PULSEAUDIO_CloseDevice(_THIS)
|
||||||
PULSEAUDIO_pa_stream_unref(this->hidden->stream);
|
PULSEAUDIO_pa_stream_unref(this->hidden->stream);
|
||||||
this->hidden->stream = NULL;
|
this->hidden->stream = NULL;
|
||||||
}
|
}
|
||||||
if (this->hidden->context != NULL) {
|
DisconnectFromPulseServer(this->hidden->mainloop, this->hidden->context);
|
||||||
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;
|
|
||||||
}
|
|
||||||
SDL_free(this->hidden);
|
SDL_free(this->hidden);
|
||||||
this->hidden = NULL;
|
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
|
static int
|
||||||
PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
|
PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
|
||||||
{
|
{
|
||||||
|
@ -442,42 +475,16 @@ PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
|
||||||
paattr.minreq = h->mixlen;
|
paattr.minreq = h->mixlen;
|
||||||
#endif
|
#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 */
|
/* The SDL ALSA output hints us that we use Windows' channel mapping */
|
||||||
/* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
|
/* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */
|
||||||
PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->spec.channels,
|
PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->spec.channels,
|
||||||
PA_CHANNEL_MAP_WAVEEX);
|
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->stream = PULSEAUDIO_pa_stream_new(
|
||||||
h->context,
|
h->context,
|
||||||
"Simple DirectMedia Layer", /* stream description */
|
"Simple DirectMedia Layer", /* stream description */
|
||||||
|
@ -490,7 +497,7 @@ PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
|
||||||
return SDL_SetError("Could not set up PulseAudio stream");
|
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) {
|
NULL, NULL) < 0) {
|
||||||
PULSEAUDIO_CloseDevice(this);
|
PULSEAUDIO_CloseDevice(this);
|
||||||
return SDL_SetError("Could not connect PulseAudio stream");
|
return SDL_SetError("Could not connect PulseAudio stream");
|
||||||
|
@ -512,6 +519,50 @@ PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
|
||||||
return 0;
|
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
|
static void
|
||||||
PULSEAUDIO_Deinitialize(void)
|
PULSEAUDIO_Deinitialize(void)
|
||||||
|
@ -522,16 +573,22 @@ PULSEAUDIO_Deinitialize(void)
|
||||||
static int
|
static int
|
||||||
PULSEAUDIO_Init(SDL_AudioDriverImpl * impl)
|
PULSEAUDIO_Init(SDL_AudioDriverImpl * impl)
|
||||||
{
|
{
|
||||||
|
pa_mainloop *mainloop = NULL;
|
||||||
|
pa_context *context = NULL;
|
||||||
|
|
||||||
if (LoadPulseAudioLibrary() < 0) {
|
if (LoadPulseAudioLibrary() < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CheckPulseAudioAvailable()) {
|
if (ConnectToPulseServer(&mainloop, &context) < 0) {
|
||||||
UnloadPulseAudioLibrary();
|
UnloadPulseAudioLibrary();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisconnectFromPulseServer(mainloop, context);
|
||||||
|
|
||||||
/* Set the function pointers */
|
/* Set the function pointers */
|
||||||
|
impl->DetectDevices = PULSEAUDIO_DetectDevices;
|
||||||
impl->OpenDevice = PULSEAUDIO_OpenDevice;
|
impl->OpenDevice = PULSEAUDIO_OpenDevice;
|
||||||
impl->PlayDevice = PULSEAUDIO_PlayDevice;
|
impl->PlayDevice = PULSEAUDIO_PlayDevice;
|
||||||
impl->WaitDevice = PULSEAUDIO_WaitDevice;
|
impl->WaitDevice = PULSEAUDIO_WaitDevice;
|
||||||
|
@ -539,12 +596,10 @@ PULSEAUDIO_Init(SDL_AudioDriverImpl * impl)
|
||||||
impl->CloseDevice = PULSEAUDIO_CloseDevice;
|
impl->CloseDevice = PULSEAUDIO_CloseDevice;
|
||||||
impl->WaitDone = PULSEAUDIO_WaitDone;
|
impl->WaitDone = PULSEAUDIO_WaitDone;
|
||||||
impl->Deinitialize = PULSEAUDIO_Deinitialize;
|
impl->Deinitialize = PULSEAUDIO_Deinitialize;
|
||||||
impl->OnlyHasDefaultOutputDevice = 1;
|
|
||||||
|
|
||||||
return 1; /* this audio target is available. */
|
return 1; /* this audio target is available. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AudioBootStrap PULSEAUDIO_bootstrap = {
|
AudioBootStrap PULSEAUDIO_bootstrap = {
|
||||||
"pulseaudio", "PulseAudio", PULSEAUDIO_Init, 0
|
"pulseaudio", "PulseAudio", PULSEAUDIO_Init, 0
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,7 +34,6 @@ struct SDL_PrivateAudioData
|
||||||
{
|
{
|
||||||
/* pulseaudio structures */
|
/* pulseaudio structures */
|
||||||
pa_mainloop *mainloop;
|
pa_mainloop *mainloop;
|
||||||
pa_mainloop_api *mainloop_api;
|
|
||||||
pa_context *context;
|
pa_context *context;
|
||||||
pa_stream *stream;
|
pa_stream *stream;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue