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:
@@ -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(×tamp_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(×tamp_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(×tamp_mutex);
|
||||
tx_timeNs += count * Ns_per_sample;
|
||||
pthread_mutex_unlock(×tamp_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(×tamp_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(×tamp_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(×tamp_mutex);
|
||||
tosend = latspl - (tx_timeNs - rx_timeNs) / Ns_per_sample;
|
||||
pthread_mutex_unlock(×tamp_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;
|
||||
|
||||
|
Reference in New Issue
Block a user