]> git.rkrishnan.org Git - pihpsdr.git/commitdiff
Updated ALSA audio to low-latency for CW.
authorc vw <dl1ycf@darc.de>
Fri, 11 Sep 2020 10:30:05 +0000 (12:30 +0200)
committerc vw <dl1ycf@darc.de>
Fri, 11 Sep 2020 10:30:05 +0000 (12:30 +0200)
audio.c
receiver.h

diff --git a/audio.c b/audio.c
index 3c4581a6eccb6d855651e37b109bc527d0c4d6af..2f41ae689960dffcbbce042426ceeb93feac5f4a 100644 (file)
--- a/audio.c
+++ b/audio.c
@@ -141,6 +141,7 @@ g_print("audio_open_output: using format %s (%s)\n",snd_pcm_format_name(formats[
   }
 
   rx->local_audio_buffer_offset=0;
+  rx->local_audio_cw=0;
   switch(rx->local_audio_format) {
     case SND_PCM_FORMAT_S16_LE:
 g_print("audio_open_output: local_audio_buffer: size=%d sample=%ld\n",rx->local_audio_buffer_size,sizeof(gint16));
@@ -304,24 +305,49 @@ g_print("audio_close_input: free mic buffer\n");
 }
 
 //
-// This is for writing a CW side tone. It is essentially
-// a copy of audio_write using the active receiver.
-// Note that audio_write must be switched off for the
-// active_receiver when transmitting.
+// This is for writing a CW side tone.
+// To keep sidetone latencies low, only use 256 samples of the
+// RX audio buffer, and slightly shorten periods of silence
+// as long as the delay is too large.
 //
 int cw_audio_write(float sample){
   snd_pcm_sframes_t delay;
   long rc;
-  long trim;
   float *float_buffer;
   gint32 *long_buffer;
   gint16 *short_buffer;
+  static int count=0;
+  int    short_audio_buffer_size;
        
   RECEIVER *rx = active_receiver;
  
   g_mutex_lock(&rx->local_audio_mutex);
   if(rx->playback_handle!=NULL && rx->local_audio_buffer!=NULL) {
 
+    //
+    // For CW side tone, use short audio buffers
+    //
+    short_audio_buffer_size=rx->local_audio_buffer_size;
+    if (short_audio_buffer_size > 256) short_audio_buffer_size = 256;
+
+    if (rx->local_audio_cw == 0) {
+      //
+      // first invocation of cw_audio_write e.g. after a RX/TX transition:
+      // clear audio buffer local to pihpsdr, rewind ALSA buffer
+      // if contains too many samples
+      //
+      rx->local_audio_cw=1;
+      rx->local_audio_buffer_offset=0;
+      if (snd_pcm_delay(rx->playback_handle, &delay) == 0) {
+         if (delay > 1024) snd_pcm_rewind(rx->playback_handle, delay-1024);
+      }
+    }
+
+    if (sample != 0.0) count=0;  // count upwards during silence
+
+    //
+    // Put sample into buffer
+    //
     switch(rx->local_audio_format) {
       case SND_PCM_FORMAT_S16_LE:
         short_buffer=(gint16 *)rx->local_audio_buffer;
@@ -341,29 +367,30 @@ int cw_audio_write(float sample){
     }
     rx->local_audio_buffer_offset++;
 
-    if(rx->local_audio_buffer_offset>=rx->local_audio_buffer_size) {
-
-      trim=0;
-
-      int max_delay=rx->local_audio_buffer_size*4;
-      if(snd_pcm_delay(rx->playback_handle,&delay)==0) {
-        if(delay>max_delay) {
-          trim=delay-max_delay;
-g_print("audio delay=%ld trim=%ld audio_buffer_size=%d\n",delay,trim,rx->local_audio_buffer_size);
-        }
+    if (++count >= 16) {
+      //
+      // We have just seen 16 zero samples, and we are not running low
+      // in the ALSA output buffer --> skip one sample
+      // This keeps the output buffer at the low water mark for
+      // optimizing latency.
+      //
+      if (snd_pcm_delay(rx->playback_handle,&delay) == 0) {
+        if (delay >= 1024) rx->local_audio_buffer_offset--;
       }
+      count=0;
+    }
 
-      if(trim<rx->local_audio_buffer_size) {
-        if ((rc = snd_pcm_writei (rx->playback_handle, rx->local_audio_buffer, rx->local_audio_buffer_size-trim)) != rx->local_audio_buffer_size-trim) {
-          if(rc<0) {
-            if(rc==-EPIPE) {
-              if ((rc = snd_pcm_prepare (rx->playback_handle)) < 0) {
-                g_print("audio_write: cannot prepare audio interface for use %ld (%s)\n", rc, snd_strerror (rc));
-                g_mutex_unlock(&rx->local_audio_mutex);
-                return rc;
-              } else {
-                // ignore short write
-              }
+    if(rx->local_audio_buffer_offset>=short_audio-buffer_size) {
+
+      if ((rc = snd_pcm_writei (rx->playback_handle, rx->local_audio_buffer, rx->local_audio_buffer_offset)) != rx->local_audio_buffer_offset) {
+        if(rc<0) {
+          if(rc==-EPIPE) {
+            if ((rc = snd_pcm_prepare (rx->playback_handle)) < 0) {
+              g_print("audio_write: cannot prepare audio interface for use %ld (%s)\n", rc, snd_strerror (rc));
+              g_mutex_unlock(&rx->local_audio_mutex);
+              return rc;
+            } else {
+              // ignore short write
             }
           }
         }
@@ -406,6 +433,38 @@ int audio_write(RECEIVER *rx,float left_sample,float right_sample) {
   g_mutex_lock(&rx->local_audio_mutex);
 
   if(rx->playback_handle!=NULL && rx->local_audio_buffer!=NULL) {
+
+    if (rx->local_audio_cw == 1) {
+      //
+      // we come from a CWTX-RX transition where we kept the ALSA audio buffer
+      // at the low-water mark. So with this call, send one bunch of silence
+      // to do a partial re-fill.
+      //
+      rx->local_audio_cw=0;
+      switch(rx->local_audio_format) {
+        case SND_PCM_FORMAT_S16_LE:
+          bzero(rx->local_audio_buffer,2*sizeof(gint16)*rx->local_audio_buffer_size);
+          break;
+        case SND_PCM_FORMAT_S32_LE:
+          bzero(rx->local_audio_buffer,2*sizeof(gint32)*rx->local_audio_buffer_size);
+          break;
+        case SND_PCM_FORMAT_FLOAT_LE:
+          bzero(rx->local_audio_buffer,2*sizeof(gfloat)*rx->local_audio_buffer_size);
+          break;
+      }
+      rx->local_audio_buffer_offset=0;
+      //
+      // In principle, this should be done after *all* TX/RX transition
+      // unless in duplex mode, since the ALSA buffers are drained in this case.
+      //
+      if(snd_pcm_delay(rx->playback_handle,&delay)==0) {
+        if (delay < 1024) {
+          snd_pcm_writei (rx->playback_handle, rx->local_audio_buffer, rx->local_audio_buffer_size);
+          //g_print("Audio output buffer partially refilled, delay was %ld\n", delay);
+        }
+      }
+    }
+    
     switch(rx->local_audio_format) {
       case SND_PCM_FORMAT_S16_LE:
         short_buffer=(gint16 *)rx->local_audio_buffer;
index 01db004159144c5518c89c133a516feed41a5db1..0551adccd82209cc712008e1bd0bd64e84789947 100644 (file)
@@ -119,18 +119,13 @@ typedef struct _receiver {
   gchar *audio_name;
 #ifdef PORTAUDIO
   PaStream *playback_handle;
-  // In the PORTAUDIO module, the local audio buffer is
-  // a ring buffer so we need two ring buffer
-  // pointers here.
-  // The local_audio_cw flag switches between
-  // high-latency (RX) and low-latency (CW TX) mode.
-  gint local_audio_buffer_inpt;
-  gint local_audio_buffer_outpt;
-  gint local_audio_cw;  
+  gint local_audio_buffer_inpt;    // pointer in audio ring-buffer
+  gint local_audio_buffer_outpt;   // pointer in audio ring-buffer
 #else
   snd_pcm_t *playback_handle;
   snd_pcm_format_t local_audio_format;
 #endif
+  gint local_audio_cw;             // flag for latency switching
   gint local_audio_buffer_size;
   gint local_audio_buffer_offset;
   void *local_audio_buffer;