From: John Melton G0ORX Date: Wed, 9 Dec 2020 18:53:01 +0000 (+0000) Subject: Changed audio to use pulse audio X-Git-Url: https://git.rkrishnan.org/components/vdrive//%22%22.?a=commitdiff_plain;h=295a737acd0245b5b1167119183b816517faf98b;p=pihpsdr.git Changed audio to use pulse audio --- diff --git a/Makefile b/Makefile index 261934a..a900f01 100644 --- a/Makefile +++ b/Makefile @@ -135,7 +135,6 @@ GPIO_SOURCES= \ switch_menu.c \ i2c_controller.c \ i2c_controller_menu.c - GPIO_HEADERS= \ configure.h \ i2c.h \ @@ -144,7 +143,6 @@ GPIO_HEADERS= \ switch_menu.h \ i2c_controller.h \ i2c_controller_menu.h - GPIO_OBJS= \ configure.o \ i2c.o \ @@ -192,8 +190,8 @@ endif GTKINCLUDES=`pkg-config --cflags gtk+-3.0` GTKLIBS=`pkg-config --libs gtk+-3.0` -AUDIO_LIBS=-lasound -#AUDIO_LIBS=-lsoundio +AUDIO_LIBS=-lpulse-simple -lpulse -lpulse-mainloop-glib +//AUDIO_LIBS=-lasound CFLAGS= -g -Wno-deprecated-declarations -O3 OPTIONS=$(SMALL_SCREEN_OPTIONS) $(MIDI_OPTIONS) $(PURESIGNAL_OPTIONS) $(REMOTE_OPTIONS) $(USBOZY_OPTIONS) \ @@ -214,7 +212,7 @@ COMPILE=$(CC) $(CFLAGS) $(OPTIONS) $(INCLUDES) PROGRAM=pihpsdr SOURCES= \ -audio.c \ +pulseaudio.c \ band.c \ discovered.c \ discovery.c \ @@ -284,7 +282,7 @@ actions.c HEADERS= \ -audio.h \ +pulseaudio.h \ agc.h \ alex.h \ band.h \ @@ -355,7 +353,7 @@ actions.h OBJS= \ -audio.o \ +pulseaudio.o \ band.o \ discovered.o \ discovery.o \ diff --git a/audio.c b/audio.c index 931bb14..bcf38a6 100644 --- a/audio.c +++ b/audio.c @@ -569,7 +569,7 @@ void audio_get_cards() { char *device_id; int card = -1; -g_print("audio_get_cards\n"); +g_print("%s\n",__FUNCTION__); n_input_devices=0; n_output_devices=0; diff --git a/audio.h b/audio.h index 234c184..398b8c2 100644 --- a/audio.h +++ b/audio.h @@ -36,13 +36,15 @@ extern int n_input_devices; extern AUDIO_DEVICE input_devices[MAX_AUDIO_DEVICES]; extern int n_output_devices; extern AUDIO_DEVICE output_devices[MAX_AUDIO_DEVICES]; +extern GMutex audio_mutex; +extern gint local_microphone_buffer_size; extern int audio_open_input(); extern void audio_close_input(); extern int audio_open_output(RECEIVER *rx); extern void audio_close_output(RECEIVER *rx); extern int audio_write(RECEIVER *rx,float left_sample,float right_sample); -extern int cw_audio_write(float sample); +extern int cw_audio_write(RECEIVER *rx,float sample); extern void audio_get_cards(); char * audio_get_error_string(int err); float audio_get_next_mic_sample(); diff --git a/main.c b/main.c index ac59cd9..07bc002 100644 --- a/main.c +++ b/main.c @@ -176,10 +176,14 @@ gboolean main_delete (GtkWidget *widget) { static int init(void *data) { char wisdom_directory[1024]; - fprintf(stderr,"init\n"); + g_print("%s\n",__FUNCTION__); audio_get_cards(); + // wait for get_cards to complete + //g_mutex_lock(&audio_mutex); + //g_mutex_unlock(&audio_mutex); + cursor_arrow=gdk_cursor_new(GDK_ARROW); cursor_watch=gdk_cursor_new(GDK_WATCH); diff --git a/pulseaudio.c b/pulseaudio.c new file mode 100644 index 0000000..c08783c --- /dev/null +++ b/pulseaudio.c @@ -0,0 +1,396 @@ +#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; +} + diff --git a/receiver.c b/receiver.c index c6e1ec0..7d93db8 100644 --- a/receiver.c +++ b/receiver.c @@ -897,7 +897,7 @@ fprintf(stderr,"create_pure_signal_receiver: id=%d buffer_size=%d\n",id,buffer_s rx->agc_slope=35.0; rx->agc_hang_threshold=0.0; - rx->playback_handle=NULL; + //rx->playback_handle=NULL; rx->local_audio_buffer=NULL; rx->local_audio_buffer_size=2048; rx->local_audio=0; @@ -1031,7 +1031,7 @@ fprintf(stderr,"create_receiver: id=%d default adc=%d\n",rx->id, rx->adc); rx->agc_slope=35.0; rx->agc_hang_threshold=0.0; - rx->playback_handle=NULL; + //rx->playback_handle=NULL; rx->local_audio=0; g_mutex_init(&rx->local_audio_mutex); rx->local_audio_buffer=NULL; diff --git a/receiver.h b/receiver.h index 41a7cb0..1132c0f 100644 --- a/receiver.h +++ b/receiver.h @@ -23,7 +23,9 @@ #ifdef PORTAUDIO #include "portaudio.h" #else -#include +#include +#include +//#include #endif enum _audio_t { @@ -120,12 +122,14 @@ typedef struct _receiver { #ifdef PORTAUDIO PaStream *playback_handle; #else - snd_pcm_t *playback_handle; - snd_pcm_format_t local_audio_format; + pa_simple* playstream; + gboolean output_started; +// snd_pcm_t *playback_handle; +// snd_pcm_format_t local_audio_format; #endif gint local_audio_buffer_size; gint local_audio_buffer_offset; - void *local_audio_buffer; + float *local_audio_buffer; GMutex local_audio_mutex; gint low_latency; diff --git a/rx_menu.c b/rx_menu.c index 911ca05..394afc6 100644 --- a/rx_menu.c +++ b/rx_menu.c @@ -366,7 +366,7 @@ void rx_menu(GtkWidget *parent) { output=gtk_combo_box_text_new(); for(i=0;iaudio_name!=NULL) { if(strcmp(active_receiver->audio_name,output_devices[i].name)==0) { gtk_combo_box_set_active(GTK_COMBO_BOX(output),i); diff --git a/transmitter.c b/transmitter.c index 7a2d3b3..a51a22c 100644 --- a/transmitter.c +++ b/transmitter.c @@ -1223,7 +1223,7 @@ void add_mic_sample(TRANSMITTER *tx,float mic_sample) { // side tone ramp=cwramp48[cw_shape]; cwsample=0.00197 * getNextSideToneSample() * cw_keyer_sidetone_volume * ramp; - cw_audio_write(cwsample); + cw_audio_write(active_receiver,cwsample); cw_shape_buffer48[tx->samples]=ramp; // // In the new protocol, we MUST maintain a constant flow of audio samples to the radio diff --git a/tx_menu.c b/tx_menu.c index b1f95ac..a6edc0a 100644 --- a/tx_menu.c +++ b/tx_menu.c @@ -285,7 +285,7 @@ void tx_menu(GtkWidget *parent) { input=gtk_combo_box_text_new(); for(i=0;imicrophone_name!=NULL) { if(strcmp(transmitter->microphone_name,input_devices[i].name)==0) { gtk_combo_box_set_active(GTK_COMBO_BOX(input),i);