diff --git a/engine/client/snd_pulse.c b/engine/client/snd_pulse.c new file mode 100644 index 000000000..b4e3da5e6 --- /dev/null +++ b/engine/client/snd_pulse.c @@ -0,0 +1,228 @@ +#include "quakedef.h" +#ifdef HAVE_MIXER + +#if 0 +#include +#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