From 351ba508f3b96462d40283073cdc6b1b50853b9f Mon Sep 17 00:00:00 2001 From: c vw Date: Tue, 18 May 2021 12:31:46 +0200 Subject: [PATCH] From John's repo --- pulseaudio.c | 397 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 pulseaudio.c diff --git a/pulseaudio.c b/pulseaudio.c new file mode 100644 index 0000000..2435907 --- /dev/null +++ b/pulseaudio.c @@ -0,0 +1,397 @@ +#include +#include +#include +#include + +#include "radio.h" +#include "receiver.h" +#include "transmitter.h" +#include "audio.h" +#include "mode.h" +#include "new_protocol.h" +#include "old_protocol.h" +#ifdef SOAPYSDR +#include "soapy_protocol.h" +#endif + +int n_input_devices; +AUDIO_DEVICE input_devices[MAX_AUDIO_DEVICES]; +int n_output_devices; +AUDIO_DEVICE output_devices[MAX_AUDIO_DEVICES]; + +// +// Ring buffer for "local microphone" samples +// NOTE: lead large buffer for some "loopback" devices which produce +// samples in large chunks if fed from digimode programs. +// +#define MICRINGLEN 6000 +static float *mic_ring_buffer=NULL; +static int mic_ring_read_pt=0; +static int mic_ring_write_pt=0; + +static pa_glib_mainloop *main_loop; +static pa_mainloop_api *main_loop_api; +static pa_operation *op; +static pa_context *pa_ctx; +static pa_simple* microphone_stream; +static gint local_microphone_buffer_offset; +static float *local_microphone_buffer; +static GThread *mic_read_thread_id; +static gboolean running; + +gint local_microphone_buffer_size=720; +GMutex audio_mutex; + +static void source_list_cb(pa_context *context,const pa_source_info *s,int eol,void *data) { + int i; + if(eol>0) { + for(i=0;iname)+1); + strncpy(input_devices[n_input_devices].name,s->name,strlen(s->name)); + input_devices[n_input_devices].description=g_new0(char,strlen(s->description)+1); + strncpy(input_devices[n_input_devices].description,s->description,strlen(s->description)); + input_devices[n_input_devices].index=s->index; + n_input_devices++; + } +} + +static void sink_list_cb(pa_context *context,const pa_sink_info *s,int eol,void *data) { + int i; + if(eol>0) { + for(i=0;iname)+1); + strncpy(output_devices[n_output_devices].name,s->name,strlen(s->name)); + output_devices[n_output_devices].description=g_new0(char,strlen(s->description)+1); + strncpy(output_devices[n_output_devices].description,s->description,strlen(s->description)); + output_devices[n_output_devices].index=s->index; + n_output_devices++; + } +} + +static void state_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + + state = pa_context_get_state(c); + +g_print("%s: %d\n",__FUNCTION__,state); + switch (state) { + // There are just here for reference + case PA_CONTEXT_UNCONNECTED: +g_print("audio: state_cb: PA_CONTEXT_UNCONNECTED\n"); + break; + case PA_CONTEXT_CONNECTING: +g_print("audio: state_cb: PA_CONTEXT_CONNECTING\n"); + break; + case PA_CONTEXT_AUTHORIZING: +g_print("audio: state_cb: PA_CONTEXT_AUTHORIZING\n"); + break; + case PA_CONTEXT_SETTING_NAME: +g_print("audio: state_cb: PA_CONTEXT_SETTING_NAME\n"); + break; + case PA_CONTEXT_FAILED: +g_print("audio: state_cb: PA_CONTEXT_FAILED\n"); + g_mutex_unlock(&audio_mutex); + break; + case PA_CONTEXT_TERMINATED: +g_print("audio: state_cb: PA_CONTEXT_TERMINATED\n"); + g_mutex_unlock(&audio_mutex); + break; + case PA_CONTEXT_READY: +g_print("audio: state_cb: PA_CONTEXT_READY\n"); +// get a list of the output devices + n_input_devices=0; + n_output_devices=0; + op = pa_context_get_sink_info_list(pa_ctx,sink_list_cb,NULL); + break; + default: + g_print("audio: state_cb: unknown state %d\n",state); + break; + } +} + +void audio_get_cards() { + g_mutex_init(&audio_mutex); + g_mutex_lock(&audio_mutex); + main_loop=pa_glib_mainloop_new(NULL); + main_loop_api=pa_glib_mainloop_get_api(main_loop); + pa_ctx=pa_context_new(main_loop_api,"linhpsdr"); + pa_context_connect(pa_ctx,NULL,0,NULL); + pa_context_set_state_callback(pa_ctx, state_cb, NULL); +} + +int audio_open_output(RECEIVER *rx) { + int result=0; + pa_sample_spec sample_spec; + int err; + + if(rx->audio_name==NULL) { + result=-1; + } else { + g_mutex_lock(&rx->local_audio_mutex); + sample_spec.rate=48000; + sample_spec.channels=2; + sample_spec.format=PA_SAMPLE_FLOAT32NE; + + + char stream_id[16]; + sprintf(stream_id,"RX-%d",rx->id); + + rx->playstream=pa_simple_new(NULL, // Use the default server. + "piHPSDR", // Our application's name. + PA_STREAM_PLAYBACK, + rx->audio_name, + stream_id, // Description of our stream. + &sample_spec, // Our sample format. + NULL, // Use default channel map + NULL, // Use default buffering attributes. + &err // error code if returns NULL + ); + + if(rx->playstream!=NULL) { + rx->local_audio_buffer_offset=0; + rx->local_audio_buffer=g_new0(float,2*rx->local_audio_buffer_size); + g_print("%s: allocated local_audio_buffer %p size %ld bytes\n",__FUNCTION__,rx->local_audio_buffer,2*rx->local_audio_buffer_size*sizeof(float)); + } else { + result=-1; + g_print("%s: pa-simple_new failed: err=%d\n",__FUNCTION__,err); + } + g_mutex_unlock(&rx->local_audio_mutex); + } + + return result; +} + +static void *mic_read_thread(gpointer arg) { + int rc; + int err; + + g_print("%s: running=%d\n",__FUNCTION__,running); + while(running) { + g_mutex_lock(&audio_mutex); + if(local_microphone_buffer==NULL) { + running=0; + } else { + rc=pa_simple_read(microphone_stream, + local_microphone_buffer, + local_microphone_buffer_size*sizeof(float), + &err); + if(rc<0) { + running=0; + g_print("%s: returned %d error=%d (%s)\n",__FUNCTION__,rc,err,pa_strerror(err)); + } else { + gint newpt; + for(gint i=0;imicrophone_name==NULL) { + return -1; + } + + g_mutex_lock(&audio_mutex); + + + pa_buffer_attr attr; + attr.maxlength = (uint32_t) -1; + attr.tlength = (uint32_t) -1; + attr.prebuf = (uint32_t) -1; + attr.minreq = (uint32_t) -1; + attr.fragsize = 512; + + + sample_spec.rate=48000; + sample_spec.channels=1; + sample_spec.format=PA_SAMPLE_FLOAT32NE; + + microphone_stream=pa_simple_new(NULL, // Use the default server. + "piHPSDR", // Our application's name. + PA_STREAM_RECORD, + transmitter->microphone_name, + "TX", // Description of our stream. + &sample_spec, // Our sample format. + NULL, // Use default channel map + //NULL, + &attr, // Use default buffering attributes. + NULL // Ignore error code. + ); + + if(microphone_stream!=NULL) { + local_microphone_buffer_offset=0; + local_microphone_buffer=g_new0(float,local_microphone_buffer_size); + + g_print("%s: allocating ring buffer\n",__FUNCTION__); + mic_ring_buffer=(float *) g_new(float,MICRINGLEN); + mic_ring_read_pt = mic_ring_write_pt=0; + if (mic_ring_buffer == NULL) { + audio_close_input(); + return -1; + } + + running=TRUE; + g_print("%s: PULSEAUDIO mic_read_thread\n",__FUNCTION__); + mic_read_thread_id=g_thread_new("mic_thread",mic_read_thread,NULL); + if(!mic_read_thread_id ) { + g_print("%s: g_thread_new failed on mic_read_thread\n",__FUNCTION__); + g_free(local_microphone_buffer); + local_microphone_buffer=NULL; + running=FALSE; + result=-1; + } + } else { + result=-1; + } + g_mutex_unlock(&audio_mutex); + + return result; +} + +void audio_close_output(RECEIVER *rx) { + g_mutex_lock(&rx->local_audio_mutex); + if(rx->playstream!=NULL) { + pa_simple_free(rx->playstream); + rx->playstream=NULL; + } + if(rx->local_audio_buffer!=NULL) { + g_free(rx->local_audio_buffer); + rx->local_audio_buffer=NULL; + } + g_mutex_unlock(&rx->local_audio_mutex); +} + +void audio_close_input() { + g_mutex_lock(&audio_mutex); + if(microphone_stream!=NULL) { + pa_simple_free(microphone_stream); + microphone_stream=NULL; + g_free(local_microphone_buffer); + local_microphone_buffer=NULL; + } + g_mutex_unlock(&audio_mutex); +} + +// +// Utility function for retrieving mic samples +// from ring buffer +// +float audio_get_next_mic_sample() { + int newpt; + float sample; + + if ((mic_ring_buffer == NULL) || (mic_ring_read_pt == mic_ring_write_pt)) { + // no buffer, or nothing in buffer: insert silence + g_print("%s: no samples\n",__FUNCTION__); + sample=0.0; + } else { + // the "existence" of the ring buffer is now guaranteed for 1 msec, + // see audio_close_input(), + newpt = mic_ring_read_pt+1; + if (newpt == MICRINGLEN) newpt=0; + sample=mic_ring_buffer[mic_ring_read_pt]; + // atomic update of read pointer + mic_ring_read_pt=newpt; + } + return sample; +} + +int cw_audio_write(RECEIVER *rx,float sample) { + int result=0; + int rc; + int err; + + g_mutex_lock(&rx->local_audio_mutex); + if(rx->local_audio_buffer==NULL) { + rx->local_audio_buffer_offset=0; + rx->local_audio_buffer=g_new0(float,2*rx->local_audio_buffer_size); + } + + rx->local_audio_buffer[rx->local_audio_buffer_offset*2]=sample; + rx->local_audio_buffer[(rx->local_audio_buffer_offset*2)+1]=sample; + rx->local_audio_buffer_offset++; + if(rx->local_audio_buffer_offset>=rx->local_audio_buffer_size) { + rc=pa_simple_write(rx->playstream, + rx->local_audio_buffer, + rx->local_audio_buffer_size*sizeof(float)*2, + &err); + if(rc!=0) { + fprintf(stderr,"audio_write failed err=%d\n",err); + } + rx->local_audio_buffer_offset=0; + } + g_mutex_unlock(&rx->local_audio_mutex); + + return result; +} + +int audio_write(RECEIVER *rx,float left_sample,float right_sample) { + int result=0; + int rc; + int err; + + g_mutex_lock(&rx->local_audio_mutex); + if(rx->local_audio_buffer==NULL) { + rx->local_audio_buffer_offset=0; + rx->local_audio_buffer=g_new0(float,2*rx->local_audio_buffer_size); + } + + rx->local_audio_buffer[rx->local_audio_buffer_offset*2]=left_sample; + rx->local_audio_buffer[(rx->local_audio_buffer_offset*2)+1]=right_sample; + rx->local_audio_buffer_offset++; + if(rx->local_audio_buffer_offset>=rx->local_audio_buffer_size) { + rc=pa_simple_write(rx->playstream, + rx->local_audio_buffer, + rx->local_audio_buffer_size*sizeof(float)*2, + &err); + if(rc!=0) { + fprintf(stderr,"audio_write failed err=%d\n",err); + } + rx->local_audio_buffer_offset=0; + } + g_mutex_unlock(&rx->local_audio_mutex); + + return result; +} + -- 2.45.2