engine/engine/client/snd_pulse.c

229 lines
5.7 KiB
C

#include "quakedef.h"
#ifdef AUDIO_PULSE
#if 0
#include <pulse/simple.h>
#else
typedef struct pa_simple pa_simple;
typedef enum pa_stream_direction {PA_STREAM_PLAYBACK=1} pa_stream_direction_t;
typedef enum pa_sample_format {
PA_SAMPLE_U8,
PA_SAMPLE_ALAW,
PA_SAMPLE_ULAW,
PA_SAMPLE_S16LE,
PA_SAMPLE_S16BE,
PA_SAMPLE_FLOAT32LE,
PA_SAMPLE_FLOAT32BE,
PA_SAMPLE_S32LE,
PA_SAMPLE_S32BE,
PA_SAMPLE_S24LE,
PA_SAMPLE_S24BE,
PA_SAMPLE_S24_32LE,
PA_SAMPLE_S24_32BE,
PA_SAMPLE_MAX,
PA_SAMPLE_INVALID = -1
} pa_sample_format_t;
typedef struct pa_sample_spec {
pa_sample_format_t format;
uint32_t rate;
uint8_t channels;
} pa_sample_spec;
typedef struct pa_channel_map pa_channel_map;
typedef struct pa_buffer_attr pa_buffer_attr;
typedef uint64_t pa_usec_t;
#if __BYTE_ORDER == __BIG_ENDIAN
#define PA_SAMPLE_FLOAT32 PA_SAMPLE_FLOAT32BE
#define PA_SAMPLE_S16NE PA_SAMPLE_S16BE
#else
#define PA_SAMPLE_FLOAT32 PA_SAMPLE_FLOAT32LE
#define PA_SAMPLE_S16NE PA_SAMPLE_S16LE
#endif
#endif
static pa_simple *(*qpa_simple_new)(const char *server,const char *name,pa_stream_direction_t dir, const char *dev, const char *stream_name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_buffer_attr *attr, int *error);
static pa_usec_t (*qpa_simple_get_latency)(pa_simple *s, int *error);
static int (*qpa_simple_write)(pa_simple *s, const void *data, size_t bytes, int *error);
static void (*qpa_simple_free)(pa_simple *s);
static qboolean Pulse_Init(void)
{
static qboolean tried;
static void *pulsemodule;
static dllfunction_t funcs[] =
{
{(void**)&qpa_simple_new, "pa_simple_new"},
{(void**)&qpa_simple_get_latency, "pa_simple_get_latency"},
{(void**)&qpa_simple_write, "pa_simple_write"},
{(void**)&qpa_simple_free, "pa_simple_free"},
{NULL, NULL}
};
if (COM_CheckParm("-nopulse"))
return false;
if (!tried)
{
tried = true;
pulsemodule = Sys_LoadLibrary("libpulse-simple.so.0", funcs);
}
return pulsemodule!=NULL;
}
static unsigned int Pulse_GetDMAPos(soundcardinfo_t *sc)
{
sc->sn.samplepos = sc->snd_sent / sc->sn.samplebytes;
return sc->sn.samplepos;
}
static void Pulse_Submit(soundcardinfo_t *sc, int start, int end)
{
}
static void Pulse_Shutdown(soundcardinfo_t *sc)
{
sc->selfpainting = false;
if (sc->thread)
Sys_WaitOnThread(sc->thread);
sc->thread = NULL;
*sc->name = '\0';
}
static void *Pulse_Lock(soundcardinfo_t *sc, unsigned int *sampidx)
{
return sc->sn.buffer;
}
static void Pulse_Unlock(soundcardinfo_t *sc, void *buffer)
{
}
static int Pulse_Thread(void *arg)
{
char buffer[256];
soundcardinfo_t *sc = arg;
void *cond = sc->handle;
int err = 0;
int showlatency = 64;
pa_simple *pulse;
pa_sample_spec ss;
ss.rate = sc->sn.speed;
switch(sc->sn.sampleformat)
{
case QSF_INVALID:
case QSF_EXTERNALMIXER:
case QSF_S8: //no signed 8bit formats here
ss.format = PA_SAMPLE_INVALID;
break;
case QSF_U8:
ss.format = PA_SAMPLE_U8;
break;
case QSF_S16:
ss.format = PA_SAMPLE_S16NE;
break;
case QSF_F32:
ss.format = PA_SAMPLE_FLOAT32;
break;
}
ss.channels = sc->sn.numchannels;
pulse = qpa_simple_new( NULL, // Use the default server.
FULLENGINENAME, // Our application's name.
PA_STREAM_PLAYBACK,
NULL, // Use the default device.
"Game Audio", // Description of our stream.
&ss, // Our sample format.
NULL, // Use default channel map
NULL, // Use default buffering attributes.
NULL // Ignore error code.
);
if (pulse)
sc->selfpainting = true; //its going!
Sys_LockConditional(cond);
Sys_ConditionSignal(cond);
Sys_UnlockConditional(cond);
while(sc->selfpainting)
{
sc->sn.buffer = buffer;
sc->sn.samples = sizeof(buffer)/sc->sn.samplebytes;
sc->samplequeue = sc->sn.samples;
S_MixerThread(sc);
sc->snd_sent += sc->sn.samplebytes*sc->samplequeue;
if (qpa_simple_write(pulse, buffer, sc->sn.samplebytes*sc->samplequeue, &err) < 0)
{
Con_Printf("pa_simple_write failed\n");
sc->selfpainting = false; //some sort of error
}
if (showlatency > 0)
if (--showlatency == 0)
{ //we delay this print so that we have a chance of finding out the real value
pa_usec_t latency = qpa_simple_get_latency(pulse, &err);
Con_Printf("PulseAudio latency is about %.3f seconds\n", latency/1000000.0);
}
}
if (pulse)
qpa_simple_free(pulse);
return 0;
}
static qboolean Pulse_InitCard(soundcardinfo_t *sc, const char *snddev)
{ //FIXME: implement snd_multipledevices somehow.
if (!Pulse_Init())
return false;
sc->inactive_sound = true; //linux sound devices always play sound, even when we're not the active app...
sc->sn.samplebytes = 4;
sc->sn.sampleformat = QSF_F32;
sc->sn.buffer = NULL;
sc->sn.samplepos = 0;
sc->Submit = Pulse_Submit;
sc->GetDMAPos = Pulse_GetDMAPos;
sc->Lock = Pulse_Lock;
sc->Unlock = Pulse_Unlock;
sc->Shutdown = Pulse_Shutdown;
sc->handle = Sys_CreateConditional();
Sys_LockConditional(sc->handle);
sc->thread = Sys_CreateThread("pulse", Pulse_Thread, sc, THREADP_HIGHEST, 0);
if (sc->thread)
{
if (!Sys_ConditionWait(sc->handle))
sc->selfpainting = false;
//thread is up and running now.
}
Sys_UnlockConditional(sc->handle);
Sys_DestroyConditional(sc->handle);
if (!sc->selfpainting)
{ //err, thread signalled itself to die?
Pulse_Shutdown(sc);
return false;
}
return true;
}
#define SDRVNAME "Pulse"
static qboolean QDECL Pulse_Enumerate(void (QDECL *cb) (const char *drivername, const char *devicecode, const char *readablename))
{
if (!Pulse_Init())
return true; //sucessfully enumerated no devices
return false; //not implemented (we'll get a default device only)
}
sounddriver_t Pulse_Output =
{
SDRVNAME,
Pulse_InitCard,
Pulse_Enumerate
};
#endif