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

@ -471,7 +471,7 @@ void *sdr_open(const char __attribute__((__unused__)) *audiodev, double *tx_freq
#ifdef HAVE_UHD
if (sdr_config->uhd) {
rc = uhd_open(sdr_config->channel, sdr_config->device_args, sdr_config->stream_args, sdr_config->tune_args, sdr_config->tx_antenna, sdr_config->rx_antenna, sdr_config->clock_source, tx_center_frequency, rx_center_frequency, sdr_config->lo_offset, sdr_config->samplerate, sdr_config->tx_gain, sdr_config->rx_gain, sdr_config->bandwidth, sdr_config->uhd_tx_timestamps);
rc = uhd_open(sdr_config->channel, sdr_config->device_args, sdr_config->stream_args, sdr_config->tune_args, sdr_config->tx_antenna, sdr_config->rx_antenna, sdr_config->clock_source, tx_center_frequency, rx_center_frequency, sdr_config->lo_offset, sdr_config->samplerate, sdr_config->tx_gain, sdr_config->rx_gain, sdr_config->bandwidth, sdr_config->timestamps);
if (rc)
goto error;
}
@ -479,7 +479,7 @@ void *sdr_open(const char __attribute__((__unused__)) *audiodev, double *tx_freq
#ifdef HAVE_SOAPY
if (sdr_config->soapy) {
rc = soapy_open(sdr_config->channel, sdr_config->device_args, sdr_config->stream_args, sdr_config->tune_args, sdr_config->tx_antenna, sdr_config->rx_antenna, sdr_config->clock_source, tx_center_frequency, rx_center_frequency, sdr_config->lo_offset, sdr_config->samplerate, sdr_config->tx_gain, sdr_config->rx_gain, sdr_config->bandwidth);
rc = soapy_open(sdr_config->channel, sdr_config->device_args, sdr_config->stream_args, sdr_config->tune_args, sdr_config->tx_antenna, sdr_config->rx_antenna, sdr_config->clock_source, tx_center_frequency, rx_center_frequency, sdr_config->lo_offset, sdr_config->samplerate, sdr_config->tx_gain, sdr_config->rx_gain, sdr_config->bandwidth, sdr_config->timestamps);
if (rc)
goto error;
}
@ -997,10 +997,11 @@ int sdr_get_tosend(void *inst, int latspl)
#endif
if (count < 0)
return count;
/* rounding down, so we never overfill */
count /= sdr->oversample;
if (sdr->threads) {
/* subtract what we have in write buffer, because this is not jent sent to the SDR */
/* subtract what we have in write buffer, because this is not jet sent to the SDR */
int fill;
fill = (sdr->thread_write.in - sdr->thread_write.out + sdr->thread_write.buffer_size) % sdr->thread_write.buffer_size;

View File

@ -41,6 +41,7 @@ void sdr_config_init(double lo_offset)
sdr_config->stream_args = "";
sdr_config->tune_args = "";
sdr_config->lo_offset = lo_offset;
sdr_config->timestamps = 1;
got_init = 1;
}
@ -92,10 +93,8 @@ void sdr_config_print_help(void)
printf(" Replace transmitted IQ data by given wave file.\n");
printf(" --sdr-swap-links\n");
printf(" Swap RX and TX frequencies for loopback tests over the air.\n");
#ifdef HAVE_UHD
printf(" --sdr-uhd-tx-timestamps\n");
printf(" Use TX timestamps on UHD device. (May not work with some devices!)\n");
#endif
printf(" --sdr-timestamps 1 | 0\n");
printf(" Use TX timestamps on UHD device. (default = %d)\n", sdr_config->timestamps);
}
void sdr_config_print_hotkeys(void)
@ -124,7 +123,7 @@ void sdr_config_print_hotkeys(void)
#define OPT_READ_IQ_RX_WAVE 1516
#define OPT_READ_IQ_TX_WAVE 1517
#define OPT_SDR_SWAP_LINKS 1518
#define OPT_SDR_UHD_TX_TS 1519
#define OPT_SDR_TIMESTAMPS 1519
void sdr_config_add_options(void)
{
@ -147,7 +146,7 @@ void sdr_config_add_options(void)
option_add(OPT_READ_IQ_RX_WAVE, "read-iq-rx-wave", 1);
option_add(OPT_READ_IQ_TX_WAVE, "read-iq-tx-wave", 1);
option_add(OPT_SDR_SWAP_LINKS, "sdr-swap-links", 0);
option_add(OPT_SDR_UHD_TX_TS, "sdr-uhd-tx-timestamps", 0);
option_add(OPT_SDR_TIMESTAMPS, "sdr-timestamps", 1);
}
int sdr_config_handle_options(int short_option, int argi, char **argv)
@ -222,8 +221,8 @@ int sdr_config_handle_options(int short_option, int argi, char **argv)
case OPT_SDR_SWAP_LINKS:
sdr_config->swap_links = 1;
break;
case OPT_SDR_UHD_TX_TS:
sdr_config->uhd_tx_timestamps = 1;
case OPT_SDR_TIMESTAMPS:
sdr_config->timestamps = atoi(argv[argi]);
break;
default:
return -EINVAL;
@ -257,4 +256,3 @@ int sdr_configure(int samplerate)
return 1;
}

View File

@ -19,7 +19,7 @@ typedef struct sdr_config {
const char *read_iq_tx_wave;
const char *read_iq_rx_wave;
int swap_links; /* swap DL and UL frequency */
int uhd_tx_timestamps; /* use UHD time stamps */
int timestamps; /* use time stamps when transmitting */
} sdr_config_t;
extern sdr_config_t *sdr_config;

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;

View File

@ -1,5 +1,5 @@
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);
int soapy_start(void);
void soapy_close(void);
int soapy_send(float *buff, int num);

View File

@ -28,9 +28,6 @@
#include "../libdebug/debug.h"
#include "../liboptions/options.h"
/* use to TX time stamp */
//#define TX_TIMESTAMP
extern int sdr_rx_overflow;
static uhd_usrp_handle usrp = NULL;
@ -50,14 +47,14 @@ static time_t tx_time_secs = 0;
static double tx_time_fract_sec = 0.0;
static int tx_timestamps;
int uhd_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 _tx_timestamps)
int uhd_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)
{
uhd_error error;
double got_frequency, got_rate, got_gain, got_bandwidth;
char got_antenna[64], got_clock[64];
samplerate = rate;
tx_timestamps = _tx_timestamps;
tx_timestamps = timestamps;
PDEBUG(DUHD, DEBUG_INFO, "Using device args \"%s\"\n", _device_args);
PDEBUG(DUHD, DEBUG_INFO, "Using stream args \"%s\"\n", _stream_args);
@ -564,7 +561,7 @@ int uhd_send(float *buff, int num)
/* increment time stamp */
tx_time_fract_sec += (double)count / samplerate;
while (tx_time_fract_sec >= 1.0) {
if (tx_time_fract_sec >= 1.0) {
tx_time_secs++;
tx_time_fract_sec -= 1.0;
}
@ -584,6 +581,8 @@ int uhd_receive(float *buff, int max)
void *buffs_ptr[1];
size_t got = 0, count;
uhd_error error;
bool has_time_spec;
int rc;
while (1) {
if (max < (int)rx_samps_per_buff) {
@ -600,8 +599,24 @@ int uhd_receive(float *buff, int max)
break;
}
if (count) {
/* get time stamp of received RX packet */
uhd_rx_metadata_time_spec(rx_metadata, &rx_time_secs, &rx_time_fract_sec);
if (tx_timestamps) {
/* get time stamp of received RX packet */
rc = uhd_rx_metadata_has_time_spec(rx_metadata, &has_time_spec);
if (rc == 0 && has_time_spec)
rc = uhd_rx_metadata_time_spec(rx_metadata, &rx_time_secs, &rx_time_fract_sec);
if (rc < 0 || !has_time_spec) {
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");
tx_timestamps = 0;
}
}
if (!tx_timestamps) {
/* increment time stamp */
rx_time_fract_sec += (double)count / samplerate;
if (rx_time_fract_sec >= 1.0) {
rx_time_secs++;
rx_time_fract_sec -= 1.0;
}
}
/* commit received data to buffer */
got += count;
buff += count * 2;
@ -641,8 +656,10 @@ int uhd_get_tosend(int latspl)
/* we check how advance our transmitted time stamp is */
advance = ((double)tx_time_secs + tx_time_fract_sec) - ((double)rx_time_secs + rx_time_fract_sec);
/* in case of underrun: */
if (advance < 0)
if (advance < 0) {
PDEBUG(DSOAPY, DEBUG_ERROR, "SDR TX underrun, seems we are too slow. Use lower SDR sample rate.\n");
advance = 0;
}
tosend = latspl - (int)(advance * samplerate);
if (tosend < 0)
tosend = 0;

View File

@ -1,5 +1,5 @@
int uhd_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 _tx_timestamps);
int uhd_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);
int uhd_start(void);
void uhd_close(void);
int uhd_send(float *buff, int num);