From a75ef85cf8e0f6644d9332eb999c3452a6e04b59 Mon Sep 17 00:00:00 2001 From: DL1YCF Date: Thu, 3 Jun 2021 11:48:12 +0200 Subject: [PATCH] Re-worked audio "buffer filling" control --- audio.c | 301 +++++++++++++++++++++++++------------------------- newhpsdrsim.c | 3 +- portaudio.c | 82 ++++++-------- receiver.h | 2 - 4 files changed, 188 insertions(+), 200 deletions(-) diff --git a/audio.c b/audio.c index 5ed0755..9c10a46 100644 --- a/audio.c +++ b/audio.c @@ -23,6 +23,27 @@ #ifndef PORTAUDIO +// +// Some important parameters +// Note that we keep the playback buffers at half-filling so +// we can use a larger latency there. +// +// +// while it is kept above out_low_water +// +static const int inp_latency = 125000; +static const int out_latency = 200000; + +static const int mic_buffer_size = 256; +static const int out_buffer_size = 256; + +static const int out_buflen = 48*(out_latency/1000); // Length of ALSA buffer +static const int out_cw_border = 1536; // separates CW-TX from other buffer fillings + +static const int cw_mid_water = 1024; // target buffer filling for CW +static const int cw_low_water = 896; // low water mark for CW +static const int cw_high_water = 1152; // high water mark for CW + #include #include @@ -49,7 +70,6 @@ #include "vfo.h" int audio = 0; -int mic_buffer_size = 720; // samples (both left and right) static GMutex audio_mutex; static snd_pcm_t *record_handle=NULL; @@ -90,14 +110,13 @@ int audio_open_output(RECEIVER *rx) { unsigned int rate=48000; unsigned int channels=2; int soft_resample=1; - unsigned int latency=125000; if(rx->audio_name==NULL) { rx->local_audio=0; return -1; } -g_print("audio_open_output: rx=%d %s buffer_size=%d\n",rx->id,rx->audio_name,rx->local_audio_buffer_size); +g_print("%s: rx=%d %s buffer_size=%d\n",__FUNCTION__,rx->id,rx->audio_name,out_buffer_size); int i; char hw[128]; @@ -110,55 +129,55 @@ g_print("audio_open_output: rx=%d %s buffer_size=%d\n",rx->id,rx->audio_name,rx- } hw[i]='\0'; -g_print("audio_open_output: hw=%s\n",hw); +g_print("%s: hw=%s\n", __FUNCTION__, hw); for(i=0;ilocal_audio_mutex); if ((err = snd_pcm_open (&rx->playback_handle, hw, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { - g_print("audio_open_output: cannot open audio device %s (%s)\n", + g_print("%s: cannot open audio device %s (%s)\n", + __FUNCTION__, hw, snd_strerror (err)); g_mutex_unlock(&rx->local_audio_mutex); return err; } -g_print("audio_open_output: handle=%p\n",rx->playback_handle); +g_print("%s: handle=%p\n",__FUNCTION__,rx->playback_handle); -g_print("audio_open_output: trying format %s (%s)\n",snd_pcm_format_name(formats[i]),snd_pcm_format_description(formats[i])); - if ((err = snd_pcm_set_params (rx->playback_handle,formats[i],SND_PCM_ACCESS_RW_INTERLEAVED,channels,rate,soft_resample,latency)) < 0) { - g_print("audio_open_output: snd_pcm_set_params failed: %s\n",snd_strerror(err)); +g_print("%s: trying format %s (%s)\n",__FUNCTION__,snd_pcm_format_name(formats[i]),snd_pcm_format_description(formats[i])); + if ((err = snd_pcm_set_params (rx->playback_handle,formats[i],SND_PCM_ACCESS_RW_INTERLEAVED,channels,rate,soft_resample,out_latency)) < 0) { + g_print("%s: snd_pcm_set_params failed: %s\n",__FUNCTION__,snd_strerror(err)); g_mutex_unlock(&rx->local_audio_mutex); audio_close_output(rx); continue; } else { -g_print("audio_open_output: using format %s (%s)\n",snd_pcm_format_name(formats[i]),snd_pcm_format_description(formats[i])); +g_print("%s: using format %s (%s)\n",__FUNCTION__,snd_pcm_format_name(formats[i]),snd_pcm_format_description(formats[i])); rx->local_audio_format=formats[i]; break; } } if(i>=FORMATS) { - g_print("audio_open_output: cannot find usable format\n"); + g_print("%s: cannot find usable format\n", __FUNCTION__); return err; } 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)); - rx->local_audio_buffer=g_new(gint16,2*rx->local_audio_buffer_size); +g_print("%s: local_audio_buffer: size=%d sample=%ld\n",__FUNCTION__,out_buffer_size,sizeof(gint16)); + rx->local_audio_buffer=g_new(gint16,2*out_buffer_size); break; case SND_PCM_FORMAT_S32_LE: -g_print("audio_open_output: local_audio_buffer: size=%d sample=%ld\n",rx->local_audio_buffer_size,sizeof(gint32)); - rx->local_audio_buffer=g_new(gint32,2*rx->local_audio_buffer_size); +g_print("%s: local_audio_buffer: size=%d sample=%ld\n",__FUNCTION__,out_buffer_size,sizeof(gint32)); + rx->local_audio_buffer=g_new(gint32,2*out_buffer_size); break; case SND_PCM_FORMAT_FLOAT_LE: -g_print("audio_open_output: local_audio_buffer: size=%d sample=%ld\n",rx->local_audio_buffer_size,sizeof(gfloat)); - rx->local_audio_buffer=g_new(gfloat,2*rx->local_audio_buffer_size); +g_print("%s: local_audio_buffer: size=%d sample=%ld\n",__FUNCTION__,out_buffer_size,sizeof(gfloat)); + rx->local_audio_buffer=g_new(gfloat,2*out_buffer_size); break; } - g_print("audio_open_output: rx=%d audio_device=%d handle=%p buffer=%p size=%d\n",rx->id,rx->audio_device,rx->playback_handle,rx->local_audio_buffer,rx->local_audio_buffer_size); + g_print("%s: rx=%d audio_device=%d handle=%p buffer=%p size=%d\n",__FUNCTION__,rx->id,rx->audio_device,rx->playback_handle,rx->local_audio_buffer,out_buffer_size); g_mutex_unlock(&rx->local_audio_mutex); return 0; @@ -169,7 +188,6 @@ int audio_open_input() { unsigned int rate=48000; unsigned int channels=1; int soft_resample=1; - unsigned int latency=125000; char hw[64]; int i; @@ -178,11 +196,9 @@ int audio_open_input() { return -1; } -g_print("audio_open_input: %s\n",transmitter->microphone_name); +g_print("%s: %s\n",__FUNCTION__, transmitter->microphone_name); - mic_buffer_size = 256; - - g_print("audio_open_input: mic_buffer_size=%d\n",mic_buffer_size); + g_print("%s: mic_buffer_size=%d\n",__FUNCTION__, mic_buffer_size); i=0; while(i<63 && transmitter->microphone_name[i]!=' ') { hw[i]=transmitter->microphone_name[i]; @@ -191,58 +207,59 @@ g_print("audio_open_input: %s\n",transmitter->microphone_name); hw[i]='\0'; - g_print("audio_open_input: hw=%s\n",hw); + g_print("%s: hw=%s\n", __FUNCTION__, hw); for(i=0;i=FORMATS) { - g_print("audio_open_input: cannot find usable format\n"); + g_print("%s: cannot find usable format\n", __FUNCTION__); g_mutex_unlock(&audio_mutex); audio_close_input(); return err; } -g_print("audio_open_input: format=%d\n",record_audio_format); +g_print("%s: format=%d\n",__FUNCTION__,record_audio_format); switch(record_audio_format) { case SND_PCM_FORMAT_S16_LE: -g_print("audio_open_input: mic_buffer: size=%d channels=%d sample=%ld bytes\n",mic_buffer_size,channels,sizeof(gint16)); +g_print("%s: mic_buffer: size=%d channels=%d sample=%ld bytes\n",__FUNCTION__,mic_buffer_size,channels,sizeof(gint16)); mic_buffer=g_new(gint16,mic_buffer_size); break; case SND_PCM_FORMAT_S32_LE: -g_print("audio_open_input: mic_buffer: size=%d channels=%d sample=%ld bytes\n",mic_buffer_size,channels,sizeof(gint32)); +g_print("%s: mic_buffer: size=%d channels=%d sample=%ld bytes\n",__FUNCTION__,mic_buffer_size,channels,sizeof(gint32)); mic_buffer=g_new(gint32,mic_buffer_size); break; case SND_PCM_FORMAT_FLOAT_LE: -g_print("audio_open_input: mic_buffer: size=%d channels=%d sample=%ld bytes\n",mic_buffer_size,channels,sizeof(gfloat)); +g_print("%s: mic_buffer: size=%d channels=%d sample=%ld bytes\n",__FUNCTION__,mic_buffer_size,channels,sizeof(gfloat)); mic_buffer=g_new(gfloat,mic_buffer_size); break; } -g_print("audio_open_input: allocating ring buffer\n"); +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) { @@ -251,7 +268,7 @@ g_print("audio_open_input: allocating ring buffer\n"); return -1; } -g_print("audio_open_input: creating mic_read_thread\n"); +g_print("%s: creating mic_read_thread\n", __FUNCTION__); GError *error; mic_read_thread_id = g_thread_try_new("microphone",mic_read_thread,NULL,&error); if(!mic_read_thread_id ) { @@ -266,7 +283,7 @@ g_print("audio_open_input: creating mic_read_thread\n"); } void audio_close_output(RECEIVER *rx) { -g_print("audio_close_output: rx=%d handle=%p buffer=%p\n",rx->id,rx->playback_handle,rx->local_audio_buffer); +g_print("%s: rx=%d handle=%p buffer=%p\n",__FUNCTION__,rx->id,rx->playback_handle,rx->local_audio_buffer); g_mutex_lock(&rx->local_audio_mutex); if(rx->playback_handle!=NULL) { snd_pcm_close (rx->playback_handle); @@ -280,21 +297,21 @@ g_print("audio_close_output: rx=%d handle=%p buffer=%p\n",rx->id,rx->playback_ha } void audio_close_input() { -g_print("audio_close_input\n"); +g_print("%s: enter\n",__FUNCTION__); running=FALSE; g_mutex_lock(&audio_mutex); if(mic_read_thread_id!=NULL) { -g_print("audio_close_input: wait for thread to complete\n"); +g_print("%s: wait for thread to complete\n", __FUNCTION__); g_thread_join(mic_read_thread_id); mic_read_thread_id=NULL; } if(record_handle!=NULL) { -g_print("audio_close_input: snd_pcm_close\n"); +g_print("%s: snd_pcm_close\n", __FUNCTION__); snd_pcm_close (record_handle); record_handle=NULL; } if(mic_buffer!=NULL) { -g_print("audio_close_input: free mic buffer\n"); +g_print("%s: free mic buffer\n", __FUNCTION__); g_free(mic_buffer); mic_buffer=NULL; } @@ -306,17 +323,9 @@ g_print("audio_close_input: free mic buffer\n"); // // 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. -// -// There are three parameters to control the latency: -// -// short_audio_buffer_size length of piHPSDR's audio buffer (default: 256) -// delay low water mark stretch pauses if delay falls below (default: 896) -// delay high water mark shorten pauses if delay exceeds (default: 1152) +// To keep sidetone latencies low, we keep the ALSA buffer +// at low filling, between cw_low_water and cw_high_water. // -// By default, it is tried to keep the delay in the range 1024 +/- 128 // Note that when sending the buffer, delay "jumps" by the buffer size // @@ -327,35 +336,19 @@ int cw_audio_write(RECEIVER *rx, float sample){ gint32 *long_buffer; gint16 *short_buffer; static int count=0; - int short_audio_buffer_size; g_mutex_lock(&rx->local_audio_mutex); if(rx->playback_handle!=NULL && rx->local_audio_buffer!=NULL) { - if (rx->local_audio_cw == 0 && cw_keyer_sidetone_volume > 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 - // - // if the keyer side tone volume is zero, then we need not care - // about side tone latency at all. - // - 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 (snd_pcm_delay(rx->playback_handle, &delay) == 0) { + if (delay > out_cw_border) { + // + // This happens when we come here for the first time after a + // RX/TX transision. Rewind until we are at target filling for CW + // + snd_pcm_rewind(rx->playback_handle, delay-cw_mid_water); + count=0; } - count=0; - } - - short_audio_buffer_size=rx->local_audio_buffer_size; - if (rx->local_audio_cw) { - // - // For CW side tone, use short audio buffers - // - if (short_audio_buffer_size > 256) short_audio_buffer_size = 256; - if (sample != 0.0) count=0; // count upwards during silence } // @@ -380,19 +373,21 @@ int cw_audio_write(RECEIVER *rx, float sample){ } rx->local_audio_buffer_offset++; - if (++count >= 16 && rx->local_audio_cw) { + if (sample != 0.0) count=0; // count upwards during silence + if (++count >= 16) { + count=0; // // We have just seen 16 zero samples, so this is the right place // to adjust the buffer filling. // If buffer gets too full ==> skip the sample - // If buffer gets too lempty ==> insert zero sample + // If buffer gets too empty ==> insert zero sample // if (snd_pcm_delay(rx->playback_handle,&delay) == 0) { - if (delay > 1152) { + if (delay > cw_high_water && rx->local_audio_buffer_offset > 0) { // delete the last sample - rx->local_audio_buffer_offset--; + rx->local_audio_buffer_offset--; } - if ((delay < 896) && (rx->local_audio_buffer_offset < short_audio_buffer_size)) { + if ((delay < cw_low_water) && (rx->local_audio_buffer_offset < out_buffer_size)) { // insert another zero sample switch(rx->local_audio_format) { case SND_PCM_FORMAT_S16_LE: @@ -414,23 +409,28 @@ int cw_audio_write(RECEIVER *rx, float sample){ rx->local_audio_buffer_offset++; } } - count=0; } - if(rx->local_audio_buffer_offset>=short_audio_buffer_size) { + if(rx->local_audio_buffer_offset>=out_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 = snd_pcm_writei (rx->playback_handle, rx->local_audio_buffer, out_buffer_size)) != out_buffer_size) { 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 - } + switch (rc) { + case -EPIPE: + if ((rc = snd_pcm_prepare (rx->playback_handle)) < 0) { + g_print("%s: cannot prepare audio interface for use %ld (%s)\n", __FUNCTION__, rc, snd_strerror (rc)); + rx->local_audio_buffer_offset=0; + g_mutex_unlock(&rx->local_audio_mutex); + return rc; + } + break; + default: + g_print("%s: write error: %s\n", __FUNCTION__, snd_strerror(rc)); + break; } - } + } else { + g_print("%s: short write lost=%d\n", __FUNCTION__, out_buffer_size - rc); + } } rx->local_audio_buffer_offset=0; } @@ -447,7 +447,6 @@ int cw_audio_write(RECEIVER *rx, float sample){ int audio_write(RECEIVER *rx,float left_sample,float right_sample) { snd_pcm_sframes_t delay; long rc; - long trim; int txmode=get_tx_mode(); float *float_buffer; gint32 *long_buffer; @@ -471,37 +470,6 @@ int audio_write(RECEIVER *rx,float left_sample,float right_sample) { 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; @@ -521,33 +489,62 @@ int audio_write(RECEIVER *rx,float left_sample,float right_sample) { } rx->local_audio_buffer_offset++; - if(rx->local_audio_buffer_offset>=rx->local_audio_buffer_size) { - - trim=0; + if(rx->local_audio_buffer_offset>=out_buffer_size) { - 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 (snd_pcm_delay(rx->playback_handle, &delay) == 0) { + if (delay < out_cw_border) { + // + // upon first occurence, or after a TX/RX transition, the buffer + // is empty (delay == 0), if we just come from CW TXing, delay is below + // out_cw_border as well. + // ACTION: fill buffer completely with silence to start output, then + // rewind until half-filling. Just filling by half does nothing, + // ALSA just does not start playing until the buffer is nearly full. + // + void *silence=NULL; + size_t len; + int num=(out_buflen - delay); + switch(rx->local_audio_format) { + case SND_PCM_FORMAT_S16_LE: + silence=g_new(gint16,2*num); + len=2*num*sizeof(gint16); + break; + case SND_PCM_FORMAT_S32_LE: + silence=g_new(gint32,2*num); + len=2*num*sizeof(gint32); + break; + case SND_PCM_FORMAT_FLOAT_LE: + silence=g_new(float,2*num); + len=2*num*sizeof(float); + break; + } + if (silence) { + memset(silence, 0, len); + snd_pcm_writei (rx->playback_handle, silence, num); + snd_pcm_rewind (rx->playback_handle, out_buflen/2); + g_free(silence); + } + } } - if(trimlocal_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_writei (rx->playback_handle, rx->local_audio_buffer, out_buffer_size)) != out_buffer_size) { + if(rc<0) { + switch (rc) { + case -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_print("%s: cannot prepare audio interface for use %ld (%s)\n", __FUNCTION__, rc, snd_strerror (rc)); rx->local_audio_buffer_offset=0; g_mutex_unlock(&rx->local_audio_mutex); return rc; } - } else { - // ignore short write - } + break; + default: + g_print("%s: write error: %s\n", __FUNCTION__, snd_strerror(rc)); + break; } - } + } else { + g_print("%s: short write lost=%d\n", __FUNCTION__, out_buffer_size - rc); + } } rx->local_audio_buffer_offset=0; } @@ -564,10 +561,11 @@ static void *mic_read_thread(gpointer arg) { gfloat sample; int i; -g_print("mic_read_thread: mic_buffer_size=%d\n",mic_buffer_size); -g_print("mic_read_thread: snd_pcm_start\n"); +g_print("%s: mic_buffer_size=%d\n",__FUNCTION__,mic_buffer_size); +g_print("%s: snd_pcm_start\n", __FUNCTION__); if ((rc = snd_pcm_start (record_handle)) < 0) { - g_print("mic_read_thread: cannot start audio interface for use (%s)\n", + g_print("%s: cannot start audio interface for use (%s)\n", + __FUNCTION__, snd_strerror (rc)); return NULL; } @@ -577,11 +575,12 @@ g_print("mic_read_thread: snd_pcm_start\n"); if ((rc = snd_pcm_readi (record_handle, mic_buffer, mic_buffer_size)) != mic_buffer_size) { if(running) { if(rc<0) { - g_print("mic_read_thread: read from audio interface failed (%s)\n", + g_print("%s: read from audio interface failed (%s)\n", + __FUNCTION__, snd_strerror (rc)); //running=FALSE; } else { - g_print("mic_read_thread: read %d\n",rc); + g_print("%s: read %d\n",__FUNCTION__,rc); } } } else { @@ -630,7 +629,7 @@ g_print("mic_read_thread: snd_pcm_start\n"); } } } -g_print("mic_read_thread: exiting\n"); +g_print("%s: exiting\n", __FUNCTION__); return NULL; } @@ -665,7 +664,7 @@ void audio_get_cards() { char *device_id; int card = -1; -g_print("audio_get_cards\n"); +g_print("%s\n", __FUNCTION__); g_mutex_init(&audio_mutex); n_input_devices=0; diff --git a/newhpsdrsim.c b/newhpsdrsim.c index f20823a..5bcc8fe 100644 --- a/newhpsdrsim.c +++ b/newhpsdrsim.c @@ -1184,7 +1184,8 @@ void *audio_thread(void *data) { } // -// The microphone thread just sends silence +// The microphone thread just sends silence, that is +// a "zeroed" mic frame every 1.333 msec // void *mic_thread(void *data) { int sock; diff --git a/portaudio.c b/portaudio.c index 9ef19cc..2bb6644 100644 --- a/portaudio.c +++ b/portaudio.c @@ -76,8 +76,11 @@ int n_output_devices=0; #define MY_AUDIO_BUFFER_SIZE 256 #define MY_RING_BUFFER_SIZE 9600 -#define MY_RING_LOW_WATER 800 -#define MY_RING_HIGH_WATER 8800 +#define MY_RING_LOW_WATER 1000 +#define MY_RING_HIGH_WATER 8600 +#define MY_CW_LOW_WATER 512 +#define MY_CW_HIGH_WATER 768 +#define MY_CW_MID_WATER 640 // // Ring buffer for "local microphone" samples stored locally here. @@ -264,10 +267,7 @@ int pa_out_cb(const void *inputBuffer, void *outputBuffer, unsigned long framesP // // Mutex protection: if the buffer is non-NULL it cannot vanish // util callback is completed - // DEBUG: report water mark - //avail = rx->local_audio_buffer_inpt - rx->local_audio_buffer_outpt; - //if (avail < 0) avail += MY_RING_BUFFER_SIZE; - //g_print("%s: AVAIL=%d\n", __FUNCTION__, avail); + // newpt=rx->local_audio_buffer_outpt; for (i=0; i< framesPerBuffer; i++) { if (rx->local_audio_buffer_inpt == newpt) { @@ -409,7 +409,6 @@ int audio_open_output(RECEIVER *rx) rx->local_audio_buffer=g_new(float,MY_RING_BUFFER_SIZE); rx->local_audio_buffer_inpt=0; rx->local_audio_buffer_outpt=0; - rx->local_audio_cw=0; if (rx->local_audio_buffer == NULL) { g_print("%s: allocate buffer failed\n", __FUNCTION__); @@ -528,16 +527,6 @@ int audio_write (RECEIVER *rx, float left, float right) g_mutex_lock(&rx->local_audio_mutex); if (rx->playstream != NULL && buffer != NULL) { - if (rx->local_audio_cw == 1) { - // - // We come from a TX->RX transition: - // Clear buffer and insert half a buffer length of silence - // - rx->local_audio_cw=0; - bzero(buffer, sizeof(float)*(MY_RING_BUFFER_SIZE/2)); - rx->local_audio_buffer_inpt=MY_RING_BUFFER_SIZE/2; - rx->local_audio_buffer_outpt=0; - } avail = rx->local_audio_buffer_inpt - rx->local_audio_buffer_outpt; if (avail < 0) avail += MY_RING_BUFFER_SIZE; if (avail < MY_RING_LOW_WATER) { @@ -546,18 +535,22 @@ int audio_write (RECEIVER *rx, float left, float right) // and with audio hardware whose "48000 Hz" are a little fasterthan the "48000 Hz" of // the SDR will very slowly drain the buffer. We recover from this by brutally // inserting half a buffer's length of silence. - // Note that this also happens the first time we arrive here, and after a TX/RX - // transition if RX audio has been shut down during TX. - // When coming from a TX/RX transition while in CW mode, the buffer will - // *always* be quite empty. + // + // This is not always an "error" to be reported and necessarily happens in three cases: + // a) we come here for the first time + // b) we come from a TX/RX transition in non-CW mode, and no duplex + // c) we come from a TX/RX transition in CW mode + // + // In case a) and b) the buffer will be empty, in c) the buffer will contain "few" samples + // because of the "CW audio low latency" strategy. // oldpt=rx->local_audio_buffer_inpt; - for (i=0; i< MY_RING_BUFFER_SIZE/2; i++) { + for (i=0; i< MY_RING_BUFFER_SIZE/2 -avail; i++) { buffer[oldpt++]=0.0; if (oldpt >= MY_RING_BUFFER_SIZE) oldpt=0; } rx->local_audio_buffer_inpt=oldpt; - //g_print("audio_write: buffer was nearly empty, inserted silence\n"); + //g_print("%s: buffer was nearly empty, inserted silence.\n", __FUNCTION__); } if (avail > MY_RING_HIGH_WATER) { // @@ -568,10 +561,10 @@ int audio_write (RECEIVER *rx, float left, float right) // deleting half a buffer size of audio, such that the next overrun is in the distant // future. // - oldpt=rx->local_audio_buffer_inpt-MY_RING_BUFFER_SIZE/2; + oldpt=rx->local_audio_buffer_inpt -avail + MY_RING_BUFFER_SIZE/2; if (oldpt < 0) oldpt += MY_RING_BUFFER_SIZE; rx->local_audio_buffer_inpt=oldpt; - g_print("audio_write: buffer was nearly full, deleted audio\n"); + g_print("%s: buffer was nearly full, deleted audio\n", __FUNCTION__); } // // put sample into ring buffer @@ -608,33 +601,30 @@ int cw_audio_write(RECEIVER *rx, float sample) { g_mutex_lock(&rx->local_audio_mutex); if (rx->playstream != NULL && rx->local_audio_buffer != NULL) { - if (rx->local_audio_cw == 0 && cw_keyer_sidetone_volume > 0) { + avail = rx->local_audio_buffer_inpt - rx->local_audio_buffer_outpt; + if (avail < 0) avail += MY_RING_BUFFER_SIZE; + if (avail > MY_RING_LOW_WATER) { // // First time producing CW audio after RX/TX transition: - // empty audio buffer and insert 512 samples of silence + // empty audio buffer and insert *a little bit of* silence // - rx->local_audio_cw=1; - bzero(rx->local_audio_buffer, 512*sizeof(float)); - rx->local_audio_buffer_inpt=512; + bzero(rx->local_audio_buffer, MY_CW_MID_WATER*sizeof(float)); + rx->local_audio_buffer_inpt=MY_CW_MID_WATER; rx->local_audio_buffer_outpt=0; + avail=MY_CW_MID_WATER; count=0; } adjust=0; - if (rx->local_audio_cw) { - count++; - if (sample != 0.0) count=0; - if (count >= 16) { - count=0; - // - // We arrive here if we have seen 16 zero samples in a row. - // First look how many samples there are in the ring buffer - // - avail = rx->local_audio_buffer_inpt - rx->local_audio_buffer_outpt; - if (avail < 0) avail += MY_RING_BUFFER_SIZE; - if (avail > 768) adjust=2; // too full: skip one sample - if (avail < 512) adjust=1; // too empty: insert one sample - } - } + if (sample != 0.0) count=0; + if (++count >= 16) { + count=0; + // + // We arrive here if we have seen 16 zero samples in a row. + // First look how many samples there are in the ring buffer + // + if (avail > MY_CW_HIGH_WATER) adjust=2; // too full: skip one sample + if (avail < MY_CW_LOW_WATER ) adjust=1; // too empty: insert one sample + } switch (adjust) { case 0: // @@ -654,7 +644,7 @@ int cw_audio_write(RECEIVER *rx, float sample) { case 1: // // buffer becomes too empty, and we just saw 16 samples of silence: - // insert two samples of silence. No check on "buffer full" needed + // insert two samples of silence. No check on "buffer full" necessary. // oldpt=rx->local_audio_buffer_inpt; rx->local_audio_buffer[oldpt++]=0.0; diff --git a/receiver.h b/receiver.h index 93aee97..654cbc0 100644 --- a/receiver.h +++ b/receiver.h @@ -125,13 +125,11 @@ typedef struct _receiver { gint local_audio_buffer_inpt; // pointer in audio ring-buffer gint local_audio_buffer_outpt; // pointer in audio ring-buffer float *local_audio_buffer; - gint local_audio_cw; // flag for latency switching #endif #ifdef ALSA snd_pcm_t *playback_handle; snd_pcm_format_t local_audio_format; void *local_audio_buffer; // different formats possible, so void* - gint local_audio_cw; // flag for latency switching #endif #ifdef PULSEAUDIO pa_simple *playstream; -- 2.45.2