colorchord/sound_pulse.c
2015-01-06 22:51:39 -05:00

373 lines
9.2 KiB
C

//This file is really rough. Full duplex doesn't seem to work hardly at all.
#include "sound.h"
#include "os_generic.h"
#include "parameters.h"
#include <stdlib.h>
#include <pulse/simple.h>
#include <pulse/pulseaudio.h>
#include <pulse/error.h>
#include <stdio.h>
#include <string.h>
#define BUFFERSETS 3
//from http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/Samples/AsyncPlayback/
//also http://maemo.org/api_refs/5.0/5.0-final/pulseaudio/pacat_8c-example.html
struct SoundDriverPulse
{
void (*CloseFn)( struct SoundDriverPulse * object );
int (*SoundStateFn)( struct SoundDriverPulse * object );
SoundCBType callback;
int channelsPlay;
int spsPlay;
int channelsRec;
int spsRec;
const char * sourceName;
og_thread_t thread;
pa_stream * play;
pa_stream * rec;
pa_context * pa_ctx;
pa_mainloop *pa_ml;
int pa_ready;
int buffer;
//More fields may exist on a per-sound-driver basis
};
void CloseSoundPulse( struct SoundDriverPulse * r );
int SoundStatePulse( struct SoundDriverPulse * soundobject )
{
return ((soundobject->play)?1:0) | ((soundobject->rec)?2:0);
}
void CloseSoundPulse( struct SoundDriverPulse * r )
{
if( r )
{
if( r->play )
{
pa_stream_unref (r->play);
r->play = 0;
}
if( r->rec )
{
pa_stream_unref (r->rec);
r->rec = 0;
}
OGUSleep(2000);
OGCancelThread( r->thread );
free( r );
}
}
static void * SoundThread( void * v )
{
struct SoundDriverPulse * r = (struct SoundDriverPulse*)v;
while(1)
{
pa_mainloop_iterate( r->pa_ml, 1, NULL );
}
return 0;
}
/*
int i;
int error;
struct SoundDriverPulse * r = (struct SoundDriverPulse*)v;
float * bufr[BUFFERSETS];
float * bufp[BUFFERSETS];
for(i = 0; i < BUFFERSETS; i++ )
{
bufr[i] = malloc( r->buffer * sizeof(float) * r->channelsRec );
bufp[i] = malloc( r->buffer * sizeof(float) * r->channelsPlay );
}
while( r->play || r->rec )
{
i = (i+1)%BUFFERSETS;
if( r->rec )
{
if (pa_stream_read(r->rec, bufr[i], r->buffer * sizeof(float) * r->channelsRec, &error) < 0) {
fprintf(stderr, __FILE__": pa_stream_write() failed: %s\n", pa_strerror(error));
pa_stream_unref( r->play );
r->rec = 0;
}
}
int playbacksamples = 0;
r->callback( bufp[i], bufr[i], r->buffer, &playbacksamples, (struct SoundDriver*)r );
playbacksamples *= sizeof( float ) * r->channelsPlay;
if( r->play )
{
if (pa_stream_write(r->play, bufp[i], playbacksamples, NULL, 0LL, PA_SEEK_RELATIVE) < 0) {
fprintf(stderr, __FILE__": pa_stream_write() failed: %s\n", pa_strerror(error));
pa_stream_unref( r->play );
r->play = 0;
}
}
}
}*/
static void stream_request_cb(pa_stream *s, size_t length, void *userdata) {
// pa_usec_t usec;
struct SoundDriverPulse * r = (struct SoundDriverPulse*)userdata;
if( r->rec )
{
return;
}
/*
//Neat: You might want this:
pa_stream_get_latency(s,&usec,&neg);
if (sampleoffs*2 + length > sizeof(sampledata))
{
sampleoffs = 0;
}
if (length > sizeof(sampledata)) {
length = sizeof(sampledata);
}
*/
// pa_stream_write(s, &sampledata[sampleoffs], length, NULL, 0LL, PA_SEEK_RELATIVE);
int playbacksamples = 0;
float bufr[r->buffer*r->channelsRec];
float bufp[r->buffer*r->channelsPlay];
r->callback( bufp, bufr, r->buffer, &playbacksamples, (struct SoundDriver*)r );
//playbacksamples *= sizeof( float ) * r->channelsPlay;
pa_stream_write(r->play, &bufp, playbacksamples, NULL, 0LL, PA_SEEK_RELATIVE);
}
static void stream_record_cb(pa_stream *s, size_t length, void *userdata) {
// pa_usec_t usec;
// int neg;
struct SoundDriverPulse * r = (struct SoundDriverPulse*)userdata;
/* pa_stream_get_latency(s,&usec,&neg);
printf(" latency %8d us\n",(int)usec);
if (sampleoffs*2 + length > sizeof(sampledata))
{
sampleoffs = 0;
}
if (length > sizeof(sampledata)) {
length = sizeof(sampledata);
}*/
int playbacksamples = 0;
float * bufr;
if (pa_stream_peek(r->rec, (void*)&bufr, &length) < 0) {
fprintf(stderr, ("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(r->pa_ctx)));
return;
}
float * buffer;
buffer = pa_xmalloc(length);
memcpy(buffer, bufr, length);
pa_stream_drop(r->rec);
float bufp[length*r->channelsPlay];
r->callback( bufp, buffer, length/sizeof(float)/r->channelsRec, &playbacksamples, (struct SoundDriver*)r );
//playbacksamples *= sizeof( float ) * r->channelsPlay;
pa_xfree( buffer );
if( r->play )
pa_stream_write(r->play, &bufp, playbacksamples*sizeof(float)*r->channelsPlay, NULL, 0LL, PA_SEEK_RELATIVE);
}
static void stream_underflow_cb(pa_stream *s, void *userdata) {
// We increase the latency by 50% if we get 6 underflows and latency is under 2s
// This is very useful for over the network playback that can't handle low latencies
printf("underflow\n");
// underflows++;
/* if (underflows >= 6 && latency < 2000000) {
latency = (latency*3)/2;
bufattr.maxlength = pa_usec_to_bytes(latency,&ss);
bufattr.tlength = pa_usec_to_bytes(latency,&ss);
pa_stream_set_buffer_attr(s, &bufattr, NULL, NULL);
underflows = 0;
printf("latency increased to %d\n", latency);
}*/
}
void pa_state_cb(pa_context *c, void *userdata) {
pa_context_state_t state;
int *pa_ready = userdata;
state = pa_context_get_state(c);
switch (state) {
// These are just here for reference
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
default:
break;
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
*pa_ready = 2;
break;
case PA_CONTEXT_READY:
*pa_ready = 1;
break;
}
}
void * InitSoundPulse( SoundCBType cb )
{
static pa_buffer_attr bufattr;
static pa_sample_spec ss;
int error;
pa_mainloop_api *pa_mlapi;
struct SoundDriverPulse * r = malloc( sizeof( struct SoundDriverPulse ) );
r->pa_ml = pa_mainloop_new();
pa_mlapi = pa_mainloop_get_api(r->pa_ml);
const char * title = GetParameterS( "title", "PA Test" );
r->pa_ctx = pa_context_new(pa_mlapi, title );
pa_context_connect(r->pa_ctx, NULL, 0, NULL);
//TODO: pa_context_set_state_callback
r->CloseFn = CloseSoundPulse;
r->SoundStateFn = SoundStatePulse;
r->callback = cb;
r->spsPlay = GetParameterI( "samplerate", 44100 );
r->channelsPlay = GetParameterI( "channels", 2 );
r->spsRec = r->spsPlay;
r->channelsRec = r->channelsPlay;
r->sourceName = GetParameterS( "sourcename", NULL );
r->play = 0;
r->rec = 0;
r->buffer = GetParameterI( "buffer", 1024 );
printf ("Pulse: from: %s (%s) / %dx%d (%d)\n", r->sourceName, title, r->spsPlay, r->channelsPlay, r->buffer );
memset( &ss, 0, sizeof( ss ) );
ss.format = PA_SAMPLE_FLOAT32NE;
ss.rate = r->spsPlay;
ss.channels = r->channelsPlay;
r->pa_ready = 0;
pa_context_set_state_callback(r->pa_ctx, pa_state_cb, &r->pa_ready);
while (r->pa_ready == 0)
{
pa_mainloop_iterate(r->pa_ml, 1, NULL);
}
int bufbytes = r->buffer * sizeof(float) * r->channelsRec;
if( GetParameterI( "play", 1 ) )
{
if (!(r->play = pa_stream_new(r->pa_ctx, "Play", &ss, NULL))) {
error = -3; //XXX ??? TODO
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
goto fail;
}
pa_stream_set_underflow_callback(r->play, stream_underflow_cb, NULL);
pa_stream_set_write_callback(r->play, stream_request_cb, r );
bufattr.fragsize = (uint32_t)-1;
bufattr.maxlength = bufbytes*3; //XXX TODO Consider making this -1
bufattr.minreq = 0;
bufattr.prebuf = (uint32_t)-1;
bufattr.tlength = bufbytes*3;
int ret = pa_stream_connect_playback(r->play, NULL, &bufattr,
// PA_STREAM_INTERPOLATE_TIMING
// |PA_STREAM_ADJUST_LATENCY //Some servers don't like the adjust_latency flag.
// |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
0, NULL, NULL );
printf( "Play stream.\n" );
if( ret < 0 )
{
fprintf(stderr, __FILE__": (PLAY) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret));
goto fail;
}
}
if( GetParameterI( "rec", 1 ) )
{
if (!(r->rec = pa_stream_new(r->pa_ctx, "Record", &ss, NULL))) {
error = -3; //XXX ??? TODO
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
goto fail;
}
pa_stream_set_read_callback(r->rec, stream_record_cb, r );
bufattr.fragsize = bufbytes;
bufattr.maxlength = (uint32_t)-1;//(uint32_t)-1; //XXX: Todo, should this be low?
bufattr.minreq = bufbytes;
bufattr.prebuf = (uint32_t)-1;
bufattr.tlength = bufbytes*3;
printf( "Source: %s\n", r->sourceName );
int ret = pa_stream_connect_record(r->rec, r->sourceName, &bufattr, 0
// |PA_STREAM_INTERPOLATE_TIMING
|PA_STREAM_ADJUST_LATENCY //Some servers don't like the adjust_latency flag.
// |PA_STREAM_AUTO_TIMING_UPDATE
// 0
);
printf( "Got handle: %d\n", ret );
if( ret < 0 )
{
fprintf(stderr, __FILE__": (REC) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret));
goto fail;
}
}
printf( "Pulse initialized.\n" );
// SoundThread( r );
r->thread = OGCreateThread( SoundThread, r );
return r;
fail:
if( r )
{
if( r->play ) pa_xfree (r->play);
if( r->rec ) pa_xfree (r->rec);
free( r );
}
return 0;
}
EXECUTE_AT_BOOT( PulseSoundReg, RegSound( 11, "PULSE", InitSoundPulse ) );