SoapySDR uses time stamps to prevent gaps/overflows in transmit stream

A common option for both UHD and SoapySDR allows to turn off time stamps.
This commit is contained in:
Andreas Eversberg
2021-09-15 09:21:13 +02:00
parent 35ed2d5138
commit 8a1c5a1a5b
7 changed files with 128 additions and 46 deletions

View File

@@ -17,12 +17,31 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* how time stamp process works:
*
* TX and RX time stamps are not valid in the beginning.
*
* If a first chunk is received from SDR, RX time becomes valid. The duration
* of the received chunk is added to the RX time stamp, so it becomes the time
* of the next expected chunk.
*
* If a RX time stamp is valid and first chunk is to be transmitted (tosend()
* is called), TX time stamp becomes valid and is set to RX time stamp, but
* advanced by the duration of the latency (latspl). tosend() always returns
* the number of samples that are needed, to make TX time stamp advance RX time
* stamp by given latency.
*
* If chunk is transmitted to SDR, the TX time stamp is advanced by the
* duration of the transmitted chunk.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <SoapySDR/Device.h>
#include <SoapySDR/Formats.h>
#include "soapy.h"
@@ -36,8 +55,13 @@ SoapySDRStream *rxStream = NULL;
SoapySDRStream *txStream = NULL;
static int tx_samps_per_buff, rx_samps_per_buff;
static double samplerate;
static uint64_t rx_count = 0;
static uint64_t tx_count = 0;
static pthread_mutex_t timestamp_mutex;
static int use_time_stamps;
static int rx_valid = 0;
static long long rx_timeNs = 0;
static int tx_valid = 0;
static long long tx_timeNs = 0;
static long long Ns_per_sample;
static int parse_args(SoapySDRKwargs *args, const char *_args_string)
{
@@ -63,7 +87,7 @@ static int parse_args(SoapySDRKwargs *args, const char *_args_string)
return 0;
}
int soapy_open(size_t channel, const char *_device_args, const char *_stream_args, const char *_tune_args, const char *tx_antenna, const char *rx_antenna, const char *clock_source, double tx_frequency, double rx_frequency, double lo_offset, double rate, double tx_gain, double rx_gain, double bandwidth)
int soapy_open(size_t channel, const char *_device_args, const char *_stream_args, const char *_tune_args, const char *tx_antenna, const char *rx_antenna, const char *clock_source, double tx_frequency, double rx_frequency, double lo_offset, double rate, double tx_gain, double rx_gain, double bandwidth, int timestamps)
{
double got_frequency, got_rate, got_gain, got_bandwidth;
const char *got_antenna, *got_clock;
@@ -73,6 +97,12 @@ int soapy_open(size_t channel, const char *_device_args, const char *_stream_arg
SoapySDRKwargs tune_args;
int rc;
use_time_stamps = timestamps;
if (use_time_stamps && (1000000000LL % (long long)rate)) {
PDEBUG(DSOAPY, DEBUG_ERROR, "The given sample duration is not a multiple of a nano second. I.e. we can't divide 1000,000,000 by sample rate of %.0f. Please choose a different sample rate for time stamp support!\n", rate);
use_time_stamps = 0;
}
Ns_per_sample = 1000000000LL / (long long)rate;
samplerate = rate;
/* parsing ARGS */
@@ -394,6 +424,13 @@ int soapy_open(size_t channel, const char *_device_args, const char *_stream_arg
}
}
/* create mutex for time stamp protection */
rc = pthread_mutex_init(&timestamp_mutex, NULL);
if (rc < 0) {
PDEBUG(DSOAPY, DEBUG_ERROR, "Mutex init failed!\n");
return rc;
}
return 0;
}
@@ -430,6 +467,7 @@ void soapy_close(void)
if (sdr) {
SoapySDRDevice_unmake(sdr);
sdr = NULL;
pthread_mutex_destroy(&timestamp_mutex);
}
}
@@ -444,20 +482,28 @@ int soapy_send(float *buff, int num)
chunk = num;
if (chunk > tx_samps_per_buff)
chunk = tx_samps_per_buff;
/* create tx metadata */
/* write TX stream */
buffs_ptr[0] = buff;
count = SoapySDRDevice_writeStream(sdr, txStream, buffs_ptr, chunk, &flags, 0, 1000000);
if (use_time_stamps)
flags |= SOAPY_SDR_HAS_TIME;
count = SoapySDRDevice_writeStream(sdr, txStream, buffs_ptr, chunk, &flags, tx_timeNs, 1000000);
if (count <= 0) {
PDEBUG(DUHD, DEBUG_ERROR, "Failed to write to TX streamer (error=%d)\n", count);
break;
}
/* process TX time stamp */
if (!tx_valid)
PDEBUG(DSOAPY, DEBUG_ERROR, "SDR TX: tosend() was not called before, prease fix!\n");
else {
pthread_mutex_lock(&timestamp_mutex);
tx_timeNs += count * Ns_per_sample;
pthread_mutex_unlock(&timestamp_mutex);
}
/* increment transmit counters */
sent += count;
buff += count * 2;
num -= count;
}
/* increment tx counter */
tx_count += sent;
return sent;
}
@@ -480,6 +526,23 @@ int soapy_receive(float *buff, int max)
buffs_ptr[0] = buff;
count = SoapySDRDevice_readStream(sdr, rxStream, buffs_ptr, rx_samps_per_buff, &flags, &timeNs, 0);
if (count > 0) {
if (!use_time_stamps || !(flags & SOAPY_SDR_HAS_TIME)) {
if (use_time_stamps) {
PDEBUG(DSOAPY, DEBUG_ERROR, "SDR RX: No time stamps available. This may cuse little gaps and problems with time slot based networks, like C-Netz.\n");
use_time_stamps = 0;
}
timeNs = rx_timeNs;
}
/* process RX time stamp */
if (!rx_valid) {
rx_timeNs = timeNs;
rx_valid = 1;
}
pthread_mutex_lock(&timestamp_mutex);
if (rx_timeNs != timeNs)
PDEBUG(DSOAPY, DEBUG_ERROR, "SDR RX overflow, seems we are too slow. Use lower SDR sample rate.\n");
rx_timeNs = timeNs + count * Ns_per_sample;
pthread_mutex_unlock(&timestamp_mutex);
/* commit received data to buffer */
got += count;
buff += count * 2;
@@ -489,8 +552,6 @@ int soapy_receive(float *buff, int max)
break;
}
}
/* update current rx time */
rx_count += got;
return got;
}
@@ -500,24 +561,29 @@ int soapy_get_tosend(int latspl)
{
int tosend;
/* we need the rx time stamp to determine how much data is already sent in advance */
if (rx_count == 0)
/* if no RX time stamp is set, we must wait until we receive a valid time stamp */
if (!rx_valid)
return 0;
/* if we have not yet sent any data, we set initial tx time stamp */
if (tx_count == 0)
tx_count = rx_count;
/* RX time stamp is valid the first time, set the TX time stamp in advance */
if (!tx_valid) {
tx_timeNs = rx_timeNs + latspl * Ns_per_sample;
tx_valid = 1;
return 0;
}
/* we check how advance our transmitted time stamp is */
tosend = latspl - (tx_count - rx_count);
/* in case of underrun: */
pthread_mutex_lock(&timestamp_mutex);
tosend = latspl - (tx_timeNs - rx_timeNs) / Ns_per_sample;
pthread_mutex_unlock(&timestamp_mutex);
/* in case of underrun */
if (tosend > latspl) {
// It is normal that we have underruns, prior initial filling of buffer.
// FIXME: better solution to detect underrun
// PDEBUG(DSOAPY, DEBUG_ERROR, "SDR TX underrun!\n");
tosend = 0;
tx_count = rx_count;
PDEBUG(DSOAPY, DEBUG_ERROR, "SDR TX underrun, seems we are too slow. Use lower SDR sample rate.\n");
tosend = latspl;
}
/* race condition and routing errors may cause TX time stamps to be in advance of slightly more than latspl */
if (tosend < 0)
tosend = 0;