There is no time stamp handling. It is just TX'ing in advance and synchronous to received RX rate.pull/1/head
parent
17b6124122
commit
438ce08add
@ -0,0 +1,281 @@ |
||||
/* SoapySDR device access
|
||||
* |
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include <stdlib.h> |
||||
#include <stdint.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <math.h> |
||||
#include <SoapySDR/Device.h> |
||||
#include <SoapySDR/Formats.h> |
||||
#include "soapy.h" |
||||
#include "debug.h" |
||||
|
||||
static SoapySDRDevice *sdr = NULL; |
||||
SoapySDRStream *rxStream = NULL; |
||||
SoapySDRStream *txStream = NULL; |
||||
static int tx_samps_per_buff, rx_samps_per_buff; |
||||
static double samplerate; |
||||
static long long rx_count = 0; |
||||
static long long tx_count = 0; |
||||
|
||||
int soapy_open(const char *device_args, double tx_frequency, double rx_frequency, double rate, double rx_gain, double tx_gain) |
||||
{ |
||||
double got_frequency, got_rate, got_gain; |
||||
size_t channel = 0; |
||||
char *arg_string = strdup(device_args), *key, *val; |
||||
SoapySDRKwargs args; |
||||
|
||||
samplerate = rate; |
||||
|
||||
/* create SoapySDR device */ |
||||
PDEBUG(DUHD, DEBUG_INFO, "Creating SoapySDR with args \"%s\"...\n", arg_string); |
||||
memset(&args, 0, sizeof(args)); |
||||
while (arg_string && *arg_string) { |
||||
key = arg_string; |
||||
val = strchr(key, '='); |
||||
if (!val) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Error parsing SDR args: No '=' after key\n"); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
val++; |
||||
arg_string = strchr(val, ','); |
||||
if (arg_string) |
||||
*arg_string++ = '\0'; |
||||
SoapySDRKwargs_set(&args, key, val); |
||||
} |
||||
sdr = SoapySDRDevice_make(&args); |
||||
if (!sdr) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to create SoapySDR\n"); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
|
||||
/* set rate */ |
||||
if (SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_TX, channel, rate) != 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX rate to %.0f Hz\n", rate); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
if (SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_RX, channel, rate) != 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX rate to %.0f Hz\n", rate); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
|
||||
/* see what rate actually is */ |
||||
got_rate = SoapySDRDevice_getSampleRate(sdr, SOAPY_SDR_TX, channel); |
||||
if (got_rate != rate) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given TX rate %.0f Hz is not supported, try %0.f Hz\n", rate, got_rate); |
||||
soapy_close(); |
||||
return -EINVAL; |
||||
} |
||||
got_rate = SoapySDRDevice_getSampleRate(sdr, SOAPY_SDR_RX, channel); |
||||
if (got_rate != rate) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given RX rate %.0f Hz is not supported, try %0.f Hz\n", rate, got_rate); |
||||
soapy_close(); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
/* set gain */ |
||||
if (SoapySDRDevice_setGain(sdr, SOAPY_SDR_TX, channel, tx_gain) != 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX gain to %.0f\n", tx_gain); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
if (SoapySDRDevice_setGain(sdr, SOAPY_SDR_RX, channel, rx_gain) != 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX gain to %.0f\n", rx_gain); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
|
||||
/* see what gain actually is */ |
||||
got_gain = SoapySDRDevice_getGain(sdr, SOAPY_SDR_TX, channel); |
||||
if (got_gain != tx_gain) { |
||||
PDEBUG(DUHD, DEBUG_NOTICE, "Given TX gain %.0f is not supported, we use %0.f\n", tx_gain, got_gain); |
||||
tx_gain = got_gain; |
||||
} |
||||
got_gain = SoapySDRDevice_getGain(sdr, SOAPY_SDR_RX, channel); |
||||
if (got_gain != rx_gain) { |
||||
PDEBUG(DUHD, DEBUG_NOTICE, "Given RX gain %.3f is not supported, we use %.3f\n", rx_gain, got_gain); |
||||
rx_gain = got_gain; |
||||
} |
||||
|
||||
/* set frequency */ |
||||
if (SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_TX, channel, tx_frequency, NULL) != 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX frequency to %.0f Hz\n", tx_frequency); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
if (SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_RX, channel, rx_frequency, NULL) != 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX frequency to %.0f Hz\n", rx_frequency); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
|
||||
/* see what frequency actually is */ |
||||
got_frequency = SoapySDRDevice_getFrequency(sdr, SOAPY_SDR_TX, channel); |
||||
if (got_frequency != tx_frequency) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given TX frequency %.0f Hz is not supported, try %0.f Hz\n", tx_frequency, got_frequency); |
||||
soapy_close(); |
||||
return -EINVAL; |
||||
} |
||||
got_frequency = SoapySDRDevice_getFrequency(sdr, SOAPY_SDR_RX, channel); |
||||
if (got_frequency != rx_frequency) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given RX frequency %.0f Hz is not supported, try %0.f Hz\n", rx_frequency, got_frequency); |
||||
soapy_close(); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
/* set up streamer */ |
||||
if (SoapySDRDevice_setupStream(sdr, &txStream, SOAPY_SDR_TX, SOAPY_SDR_CF32, &channel, 1, NULL) != 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX streamer args\n"); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
if (SoapySDRDevice_setupStream(sdr, &rxStream, SOAPY_SDR_RX, SOAPY_SDR_CF32, &channel, 1, NULL) != 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX streamer args\n"); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
|
||||
/* get buffer sizes */ |
||||
tx_samps_per_buff = SoapySDRDevice_getStreamMTU(sdr, txStream); |
||||
if (tx_samps_per_buff == 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get TX streamer sample buffer\n"); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
rx_samps_per_buff = SoapySDRDevice_getStreamMTU(sdr, rxStream); |
||||
if (rx_samps_per_buff == 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get RX streamer sample buffer\n"); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
|
||||
/* enable rx stream */ |
||||
if (SoapySDRDevice_activateStream(sdr, rxStream, 0, 0, 0) != 0) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to issue RX stream command\n"); |
||||
soapy_close(); |
||||
return -EIO; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
void soapy_close(void) |
||||
{ |
||||
PDEBUG(DUHD, DEBUG_DEBUG, "Clean up UHD\n"); |
||||
if (txStream) { |
||||
SoapySDRDevice_closeStream(sdr, txStream); |
||||
txStream = NULL; |
||||
} |
||||
if (rxStream) { |
||||
SoapySDRDevice_deactivateStream(sdr, rxStream, 0, 0); |
||||
SoapySDRDevice_closeStream(sdr, rxStream); |
||||
rxStream = NULL; |
||||
} |
||||
if (sdr) { |
||||
SoapySDRDevice_unmake(sdr); |
||||
sdr = NULL; |
||||
} |
||||
} |
||||
|
||||
int soapy_send(float *buff, int num) |
||||
{ |
||||
const void *buffs_ptr[1]; |
||||
int chunk; |
||||
int sent = 0, count; |
||||
int flags = 0; |
||||
|
||||
while (num) { |
||||
chunk = num; |
||||
if (chunk > tx_samps_per_buff) |
||||
chunk = tx_samps_per_buff; |
||||
/* create tx metadata */ |
||||
buffs_ptr[0] = buff; |
||||
count = SoapySDRDevice_writeStream(sdr, txStream, buffs_ptr, chunk, &flags, 0, 0); |
||||
if (count <= 0) |
||||
break; |
||||
|
||||
/* increment tx counter */ |
||||
tx_count += count; |
||||
|
||||
sent += count; |
||||
buff += count * 2; |
||||
num -= count; |
||||
} |
||||
|
||||
return sent; |
||||
} |
||||
|
||||
/* read what we got, return 0, if buffer is empty, otherwise return the number of samples */ |
||||
int soapy_receive(float *buff, int max) |
||||
{ |
||||
void *buffs_ptr[1]; |
||||
int got = 0, count; |
||||
long long timeNs; |
||||
int flags = 0; |
||||
|
||||
while (1) { |
||||
if (max < rx_samps_per_buff) { |
||||
PDEBUG(DUHD, DEBUG_ERROR, "SDR rx buffer overflow!\n"); |
||||
break; |
||||
} |
||||
/* read RX stream */ |
||||
buffs_ptr[0] = buff; |
||||
count = SoapySDRDevice_readStream(sdr, rxStream, buffs_ptr, rx_samps_per_buff, &flags, &timeNs, 0); |
||||
if (count > 0) { |
||||
/* update current rx time */ |
||||
rx_count += count; |
||||
/* commit received data to buffer */ |
||||
got += count; |
||||
buff += count * 2; |
||||
max -= count; |
||||
} else { |
||||
/* got nothing this time */ |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return got; |
||||
} |
||||
|
||||
/* estimate current unsent number of samples */ |
||||
int soapy_get_inbuffer(void) |
||||
{ |
||||
long long advance; |
||||
|
||||
/* we need the rx time stamp to determine how much data is already sent in advance */ |
||||
if (rx_count == 0) |
||||
return -EAGAIN; |
||||
|
||||
/* if we have not yet sent any data, we set initial tx time stamp */ |
||||
if (tx_count == 0) |
||||
tx_count = rx_count; |
||||
|
||||
/* we check how advance our transmitted time stamp is */ |
||||
advance = tx_count - rx_count; |
||||
/* in case of underrun: */ |
||||
if (advance < 0) |
||||
advance = 0; |
||||
|
||||
return advance; |
||||
} |
||||
|
@ -0,0 +1,7 @@ |
||||
|
||||
int soapy_open(const char *device_args, double tx_frequency, double rx_frequency, double rate, double rx_gain, double tx_gain); |
||||
void soapy_close(void); |
||||
int soapy_send(float *buff, int num); |
||||
int soapy_receive(float *buff, int max); |
||||
int soapy_get_inbuffer(void); |
||||
|
Loading…
Reference in new issue