]> git.rkrishnan.org Git - pihpsdr.git/commitdiff
Added pulseaudio audio module from John's repo
authorc vw <dl1ycf@darc.de>
Sun, 23 May 2021 08:32:17 +0000 (10:32 +0200)
committerc vw <dl1ycf@darc.de>
Sun, 23 May 2021 08:32:17 +0000 (10:32 +0200)
pulseaudio.c [new file with mode: 0644]

diff --git a/pulseaudio.c b/pulseaudio.c
new file mode 100644 (file)
index 0000000..2435907
--- /dev/null
@@ -0,0 +1,397 @@
+#include <gtk/gtk.h>
+#include <pulse/pulseaudio.h>
+#include <pulse/glib-mainloop.h>
+#include <pulse/simple.h>
+
+#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;i<n_input_devices;i++) {
+      g_print("Input: %d: %s (%s)\n",input_devices[i].index,input_devices[i].name,input_devices[i].description);
+    }
+    g_mutex_unlock(&audio_mutex);
+  } else if(n_input_devices<MAX_AUDIO_DEVICES) {
+    input_devices[n_input_devices].name=g_new0(char,strlen(s->name)+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;i<n_output_devices;i++) {
+      g_print("Output: %d: %s (%s)\n",output_devices[i].index,output_devices[i].name,output_devices[i].description);
+    }
+    op=pa_context_get_source_info_list(pa_ctx,source_list_cb,NULL);
+  } else if(n_output_devices<MAX_AUDIO_DEVICES) {
+    output_devices[n_output_devices].name=g_new0(char,strlen(s->name)+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;i<local_microphone_buffer_size;i++) {
+          gfloat sample=local_microphone_buffer[i];
+          switch(protocol) {
+            case ORIGINAL_PROTOCOL:
+            case NEW_PROTOCOL:
+#ifdef SOAPYSDR
+            case SOAPYSDR_PROTOCOL:
+#endif
+              //
+              // put sample into ring buffer
+              //
+              if (mic_ring_buffer != NULL) {
+                // the "existence" of the ring buffer is now guaranteed for 1 msec,
+                // see audio_close_input().
+                newpt=mic_ring_write_pt +1;
+                if (newpt == MICRINGLEN) newpt=0;
+                if (newpt != mic_ring_read_pt) {
+                  // buffer space available, do the write
+                  mic_ring_buffer[mic_ring_write_pt]=sample;
+                  // atomic update of mic_ring_write_pt
+                  mic_ring_write_pt=newpt;
+                }
+              }
+              break;
+            default:
+              break;
+          }
+        }
+      }
+    }
+    g_mutex_unlock(&audio_mutex);
+  }
+  g_print("%s: exit\n",__FUNCTION__);
+}
+
+int audio_open_input() {
+  pa_sample_spec sample_spec;
+  int result=0;
+  int err;
+
+  if(transmitter->microphone_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;
+}
+