filerf: add tx, multi-channel, open via device string and test

This commits extends the file-based RF device as follows:

* open device via device string
* add tx to file
* add multi-channel support (multiple files)
* add rf_file_test.c to for testing
This commit is contained in:
Robert Falkenberg 2021-11-16 15:32:34 +01:00
parent 57f84d4ca4
commit f3d144dd59
8 changed files with 846 additions and 62 deletions

View File

@ -59,7 +59,7 @@ if(RF_FOUND)
list(APPEND SOURCES_RF rf_zmq_imp.c rf_zmq_imp_tx.c rf_zmq_imp_rx.c)
endif (ZEROMQ_FOUND)
list(APPEND SOURCES_RF rf_file_imp.c rf_file_imp_rx.c)
list(APPEND SOURCES_RF rf_file_imp.c rf_file_imp_tx.c rf_file_imp_rx.c)
add_library(srsran_rf_object OBJECT ${SOURCES_RF})
set_property(TARGET srsran_rf_object PROPERTY POSITION_INDEPENDENT_CODE 1)
@ -100,5 +100,9 @@ if(RF_FOUND)
#add_test(rf_zmq_test rf_zmq_test)
endif (ZEROMQ_FOUND)
add_executable(rf_file_test rf_file_test.c)
target_link_libraries(rf_file_test srsran_rf)
add_test(rf_file_test rf_file_test)
INSTALL(TARGETS srsran_rf DESTINATION ${LIBRARY_DIR})
endif(RF_FOUND)

View File

@ -324,4 +324,5 @@ static rf_dev_t* available_devices[] = {
#ifdef ENABLE_DUMMY_DEV
&dev_dummy,
#endif
&dev_file,
NULL};

View File

@ -16,6 +16,7 @@
#include "rf_file_imp.h"
#include "rf_file_imp_trx.h"
#include "rf_helper.h"
#include <errno.h>
#include <math.h>
#include <srsran/phy/common/phy_common.h>
#include <srsran/phy/common/timestamp.h>
@ -35,18 +36,18 @@ typedef struct {
uint32_t base_srate;
uint32_t decim_factor; // decimation factor between base_srate used on transport on radio's rate
double rx_gain;
uint32_t tx_freq_mhz[SRSRAN_MAX_PORTS];
uint32_t rx_freq_mhz[SRSRAN_MAX_PORTS];
bool tx_used;
uint32_t tx_freq_mhz[SRSRAN_MAX_CHANNELS];
uint32_t rx_freq_mhz[SRSRAN_MAX_CHANNELS];
bool tx_off;
char id[RF_PARAM_LEN];
// FILEs
rf_file_tx_t transmitter[SRSRAN_MAX_PORTS];
rf_file_rx_t receiver[SRSRAN_MAX_PORTS];
char id[PARAM_LEN_SHORT];
rf_file_tx_t transmitter[SRSRAN_MAX_CHANNELS];
rf_file_rx_t receiver[SRSRAN_MAX_CHANNELS];
bool close_files;
// Various sample buffers
cf_t* buffer_decimation[SRSRAN_MAX_PORTS];
cf_t* buffer_decimation[SRSRAN_MAX_CHANNELS];
cf_t* buffer_tx;
// Rx timestamp
@ -55,6 +56,7 @@ typedef struct {
pthread_mutex_t tx_config_mutex;
pthread_mutex_t rx_config_mutex;
pthread_mutex_t decim_mutex;
pthread_mutex_t rx_gain_mutex;
} rf_file_handler_t;
/*
@ -63,6 +65,31 @@ typedef struct {
static void update_rates(rf_file_handler_t* handler, double srate);
void rf_file_info(char* id, const char* format, ...)
{
#if VERBOSE
struct timeval t;
gettimeofday(&t, NULL);
va_list args;
va_start(args, format);
printf("[%s@%02ld.%06ld] ", id ? id : "file", t.tv_sec % 10, t.tv_usec);
vprintf(format, args);
va_end(args);
#else /* VERBOSE */
// Do nothing
#endif /* VERBOSE */
}
void rf_file_error(char* id, const char* format, ...)
{
struct timeval t;
gettimeofday(&t, NULL);
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
static inline int update_ts(void* h, uint64_t* ts, int nsamples, const char* dir)
{
int ret = SRSRAN_ERROR;
@ -74,6 +101,8 @@ static inline int update_ts(void* h, uint64_t* ts, int nsamples, const char* dir
srsran_timestamp_t _ts = {};
srsran_timestamp_init_uint64(&_ts, *ts, handler->base_srate);
rf_file_info(
handler->id, " -> next %s time after %d samples: %d + %.3f\n", dir, nsamples, _ts.full_secs, _ts.frac_secs);
ret = SRSRAN_SUCCESS;
}
@ -138,11 +167,76 @@ int rf_file_open(char* args, void** h)
int rf_file_open_multi(char* args, void** h, uint32_t nof_channels)
{
perror("Cannot open file-based RF as regular device. Use rf_file_open_file() instead.");
return SRSRAN_ERROR_INVALID_COMMAND;
int ret = SRSRAN_ERROR;
FILE* rx_files[SRSRAN_MAX_CHANNELS] = {NULL};
FILE* tx_files[SRSRAN_MAX_CHANNELS] = {NULL};
if (h && nof_channels < SRSRAN_MAX_CHANNELS) {
uint32_t base_srate = FILE_BASERATE_DEFAULT_HZ;
// parse args
if (args && strlen(args)) {
// base_srate
parse_uint32(args, "base_srate", -1, &base_srate);
} else {
fprintf(stderr, "[file] Error: RF device args are required for file-based no-RF module\n");
goto clean_exit;
}
for (int i = 0; i < nof_channels; i++) {
// rx_file
char rx_file[RF_PARAM_LEN] = {};
parse_string(args, "rx_file", i, rx_file);
// tx_file
char tx_file[RF_PARAM_LEN] = {};
parse_string(args, "tx_file", i, tx_file);
// initialize transmitter
if (strlen(tx_file) != 0) {
tx_files[i] = fopen(tx_file, "wb");
if (tx_files[i] == NULL) {
fprintf(stderr, "[file] Error: opening tx_file%d: %s; %s\n", i, tx_file, strerror(errno));
goto clean_exit;
}
}
// initialize receiver
if (strlen(rx_file) != 0) {
rx_files[i] = fopen(rx_file, "rb");
if (rx_files[i] == NULL) {
fprintf(stderr, "[file] Error: opening rx_file%d: %s; %s\n", i, rx_file, strerror(errno));
goto clean_exit;
}
}
}
// defer further initialization to open_file method
ret = rf_file_open_file(h, rx_files, tx_files, nof_channels, base_srate);
if (ret != SRSRAN_SUCCESS) {
goto clean_exit;
}
// add flag to close all files when closing device
rf_file_handler_t* handler = (rf_file_handler_t*)(*h);
handler->close_files = true;
return ret;
}
clean_exit:
for (int i = 0; i < nof_channels; i++) {
if (rx_files[i] != NULL) {
fclose(rx_files[i]);
}
if (tx_files[i] != NULL) {
fclose(tx_files[i]);
}
}
return ret;
}
int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_channels, uint32_t base_srate)
int rf_file_open_file(void** h, FILE** rx_files, FILE** tx_files, uint32_t nof_channels, uint32_t base_srate)
{
int ret = SRSRAN_ERROR;
@ -151,13 +245,12 @@ int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_c
rf_file_handler_t* handler = (rf_file_handler_t*)malloc(sizeof(rf_file_handler_t));
if (!handler) {
perror("malloc");
fprintf(stderr, "malloc: %s\n", strerror(errno));
return SRSRAN_ERROR;
}
memset(handler, 0, sizeof(rf_file_handler_t));
*h = handler;
handler->base_srate = FILE_BASERATE_DEFAULT_HZ; // Sample rate for 100 PRB cell
handler->rx_gain = 0.0;
handler->base_srate = base_srate;
handler->info.max_rx_gain = FILE_MAX_GAIN_DB;
handler->info.min_rx_gain = FILE_MIN_GAIN_DB;
handler->info.max_tx_gain = FILE_MAX_GAIN_DB;
@ -171,17 +264,21 @@ int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_c
rx_opts.id = handler->id;
if (pthread_mutex_init(&handler->tx_config_mutex, NULL)) {
perror("Mutex init");
fprintf(stderr, "Mutex init: %s\n", strerror(errno));
}
if (pthread_mutex_init(&handler->rx_config_mutex, NULL)) {
perror("Mutex init");
fprintf(stderr, "Mutex init: %s\n", strerror(errno));
}
if (pthread_mutex_init(&handler->decim_mutex, NULL)) {
perror("Mutex init");
fprintf(stderr, "Mutex init: %s\n", strerror(errno));
}
if (pthread_mutex_init(&handler->rx_gain_mutex, NULL)) {
fprintf(stderr, "Mutex init: %s\n", strerror(errno));
}
// base_srate
handler->base_srate = base_srate;
pthread_mutex_lock(&handler->rx_gain_mutex);
handler->rx_gain = 0.0;
pthread_mutex_unlock(&handler->rx_gain_mutex);
// id
// TODO: set some meaningful ID in handler->id
@ -195,29 +292,30 @@ int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_c
// Create channels
for (int i = 0; i < handler->nof_channels; i++) {
if (rx_files != NULL) {
if (rx_files != NULL && rx_files[i] != NULL) {
rx_opts.file = rx_files[i];
if (rf_file_rx_open(&handler->receiver[i], rx_opts) != SRSRAN_SUCCESS) {
fprintf(stderr, "[file] Error: opening receiver\n");
goto clean_exit;
}
} else {
fprintf(stdout, "[file] %s Rx file not specified. Disabling receiver.\n", handler->id);
// no rx_files provided
fprintf(stdout, "[file] %s rx channel %d not specified. Disabling receiver.\n", handler->id, i);
}
if (tx_files != NULL) {
if (tx_files != NULL && tx_files[i] != NULL) {
tx_opts.file = tx_files[i];
// TX is not implemented yet
// if(rf_file_tx_open(&handler->transmitter[i], tx_opts) != SRSRAN_SUCCESS) {
// fprintf(stderr, "[file] Error: opening transmitter\n");
// goto clean_exit;
// }
if (rf_file_tx_open(&handler->transmitter[i], tx_opts) != SRSRAN_SUCCESS) {
fprintf(stderr, "[file] Error: opening transmitter\n");
goto clean_exit;
}
} else {
// no tx_files provided
fprintf(stdout, "[file] %s Tx file not specified. Disabling transmitter.\n", handler->id);
fprintf(stdout, "[file] %s tx channel %d not specified. Disabling transmitter.\n", handler->id, i);
handler->tx_off = true;
}
if (!handler->transmitter[i].running && !handler->receiver[i].running) {
fprintf(stderr, "[file] Error: Neither Tx file nor Rx file specified.\n");
fprintf(stderr, "[file] Error: Neither tx nor rx specificed for channel %d.\n", i);
goto clean_exit;
}
}
@ -249,9 +347,15 @@ int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_c
int rf_file_close(void* h)
{
rf_file_handler_t* handler = (rf_file_handler_t*)h;
rf_file_info(handler->id, "Closing ...\n");
for (int i = 0; i < handler->nof_channels; i++) {
rf_file_tx_close(&handler->transmitter[i]);
rf_file_rx_close(&handler->receiver[i]);
}
for (uint32_t i = 0; i < handler->nof_channels; i++) {
if (handler->buffer_decimation[i]) {
free(handler->buffer_decimation[i]);
@ -265,6 +369,18 @@ int rf_file_close(void* h)
pthread_mutex_destroy(&handler->tx_config_mutex);
pthread_mutex_destroy(&handler->rx_config_mutex);
pthread_mutex_destroy(&handler->decim_mutex);
pthread_mutex_destroy(&handler->rx_gain_mutex);
if (handler->close_files) {
for (int i = 0; i < handler->nof_channels; i++) {
if (handler->receiver[i].file != NULL) {
fclose(handler->receiver[i].file);
}
if (handler->transmitter[i].file != NULL) {
fclose(handler->transmitter[i].file);
}
}
}
// Free all
free(handler);
@ -412,13 +528,11 @@ void rf_file_get_time(void* h, time_t* secs, double* frac_secs)
int rf_file_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs)
{
void* data_multi[SRSRAN_MAX_PORTS] = {NULL};
data_multi[0] = data;
return rf_file_recv_with_time_multi(h, data_multi, nsamples, blocking, secs, frac_secs);
return rf_file_recv_with_time_multi(h, &data, nsamples, blocking, secs, frac_secs);
}
int rf_file_recv_with_time_multi(void* h,
void* data[SRSRAN_MAX_PORTS],
void** data,
uint32_t nsamples,
bool blocking,
time_t* secs,
@ -431,23 +545,28 @@ int rf_file_recv_with_time_multi(void* h,
// Map ports to data buffers according to the selected frequencies
pthread_mutex_lock(&handler->rx_config_mutex);
cf_t* buffers[SRSRAN_MAX_PORTS] = {}; // Buffer pointers, NULL if unmatched
for (uint32_t i = 0; i < handler->nof_channels; i++) {
bool mapped = false;
bool mapped[SRSRAN_MAX_CHANNELS] = {}; // Mapped mask, set to true when the physical channel is used
cf_t* buffers[SRSRAN_MAX_CHANNELS] = {}; // Buffer pointers, NULL if unmatched
// Find first matching frequency
for (uint32_t j = 0; j < handler->nof_channels && !mapped; j++) {
// Traverse all channels, break if mapped
if (buffers[j] == NULL && rf_file_rx_match_freq(&handler->receiver[j], handler->rx_freq_mhz[i])) {
// Available buffer and matched frequency with receiver
buffers[j] = (cf_t*)data[i];
mapped = true;
// For each logical channel...
for (uint32_t logical = 0; logical < handler->nof_channels; logical++) {
bool unmatched = true;
// For each physical channel...
for (uint32_t physical = 0; physical < handler->nof_channels; physical++) {
// Consider a match if the physical channel is NOT mapped and the frequency match
if (!mapped[physical] && rf_file_rx_match_freq(&handler->receiver[physical], handler->rx_freq_mhz[logical])) {
// Not mapped and matched frequency with receiver
buffers[physical] = (cf_t*)data[logical];
mapped[physical] = true;
unmatched = false;
break;
}
}
// If no matching frequency found; set data to zeros
if (!mapped && data[i]) {
memset(data[i], 0, sizeof(cf_t) * nsamples);
if (unmatched) {
srsran_vec_zero(data[logical], nsamples);
}
}
pthread_mutex_unlock(&handler->rx_config_mutex);
@ -460,6 +579,8 @@ int rf_file_recv_with_time_multi(void* h,
uint32_t nbytes = NSAMPLES2NBYTES(nsamples * decim_factor);
uint32_t nsamples_baserate = nsamples * decim_factor;
rf_file_info(handler->id, "Rx %d samples (%d B)\n", nsamples, nbytes);
// set timestamp for this reception
if (secs != NULL && frac_secs != NULL) {
srsran_timestamp_t ts = {};
@ -488,10 +609,19 @@ int rf_file_recv_with_time_multi(void* h,
srsran_timestamp_t ts_tx = {}, ts_rx = {};
srsran_timestamp_init_uint64(&ts_tx, handler->transmitter[0].nsamples, handler->base_srate);
srsran_timestamp_init_uint64(&ts_rx, handler->next_rx_ts, handler->base_srate);
rf_file_info(handler->id, " - next rx time: %d + %.3f\n", ts_rx.full_secs, ts_rx.frac_secs);
rf_file_info(handler->id, " - next tx time: %d + %.3f\n", ts_tx.full_secs, ts_tx.frac_secs);
// check for tx gap if we're also transmitting on this radio
for (int i = 0; i < handler->nof_channels; i++) {
if (handler->transmitter[i].running) {
rf_file_tx_align(&handler->transmitter[i], handler->next_rx_ts + nsamples_baserate);
}
}
// copy from rx buffer as many samples as requested into provided buffer
bool completed = false;
int32_t count[SRSRAN_MAX_PORTS] = {};
bool completed = false;
int32_t count[SRSRAN_MAX_CHANNELS] = {};
while (!completed) {
uint32_t completed_count = 0;
@ -523,6 +653,7 @@ int rf_file_recv_with_time_multi(void* h,
// Check if all channels are completed
completed = (completed_count == handler->nof_channels);
}
rf_file_info(handler->id, " - read %d samples.\n", NBYTES2NSAMPLES(nbytes));
// decimate if needed
if (decim_factor != 1) {
@ -538,14 +669,24 @@ int rf_file_recv_with_time_multi(void* h,
for (int j = 0; j < decim_factor; j++, n++) {
avg += ptr[n];
}
dst[i] = avg;
dst[i] = avg; // divide by decim_factor later via scale
}
rf_file_info(handler->id,
" - re-adjust bytes due to %dx decimation %d --> %d samples)\n",
decim_factor,
nsamples_baserate,
nsamples);
}
}
}
// Set gain
pthread_mutex_lock(&handler->rx_gain_mutex);
float scale = srsran_convert_dB_to_amplitude(handler->rx_gain);
pthread_mutex_unlock(&handler->rx_gain_mutex);
// scale shall also incorporate decim_factor
scale = scale / decim_factor;
for (uint32_t c = 0; c < handler->nof_channels; c++) {
if (buffers[c]) {
srsran_vec_sc_prod_cfc(buffers[c], scale, buffers[c], nsamples);
@ -589,9 +730,127 @@ int rf_file_send_timed_multi(void* h,
bool is_start_of_burst,
bool is_end_of_burst)
{
// Not implemented
fprintf(stderr, "Error: rf_file_send_timed_multi not implemented.\n");
return SRSRAN_ERROR;
int ret = SRSRAN_ERROR;
if (h && data && nsamples > 0) {
rf_file_handler_t* handler = (rf_file_handler_t*)h;
// Map ports to data buffers according to the selected frequencies
pthread_mutex_lock(&handler->tx_config_mutex);
bool mapped[SRSRAN_MAX_CHANNELS] = {}; // Mapped mask, set to true when the physical channel is used
cf_t* buffers[SRSRAN_MAX_CHANNELS] = {}; // Buffer pointers, NULL if unmatched or zero transmission
// For each logical channel...
for (uint32_t logical = 0; logical < handler->nof_channels; logical++) {
// For each physical channel...
for (uint32_t physical = 0; physical < handler->nof_channels; physical++) {
// Consider a match if the physical channel is NOT mapped and the frequency match
if (!mapped[physical] &&
rf_file_tx_match_freq(&handler->transmitter[physical], handler->tx_freq_mhz[logical])) {
// Not mapped and matched frequency with receiver
buffers[physical] = (cf_t*)data[logical];
mapped[physical] = true;
break;
}
}
}
pthread_mutex_unlock(&handler->tx_config_mutex);
// Protect the access to decim_factor since is a shared variable
pthread_mutex_lock(&handler->decim_mutex);
uint32_t decim_factor = handler->decim_factor;
pthread_mutex_unlock(&handler->decim_mutex);
uint32_t nbytes = NSAMPLES2NBYTES(nsamples);
uint32_t nsamples_baseband = nsamples * decim_factor;
uint32_t nbytes_baseband = NSAMPLES2NBYTES(nsamples_baseband);
if (nbytes_baseband > FILE_MAX_BUFFER_SIZE) {
fprintf(stderr, "Error: trying to transmit too many samples (%d > %zu).\n", nbytes, FILE_MAX_BUFFER_SIZE);
goto clean_exit;
}
rf_file_info(handler->id, "Tx %d samples (%d B)\n", nsamples, nbytes);
// return if transmitter is switched off
if (handler->tx_off) {
return SRSRAN_SUCCESS;
}
// check if this is a tx in the future
if (has_time_spec) {
rf_file_info(handler->id, " - tx time: %d + %.3f\n", secs, frac_secs);
srsran_timestamp_t ts = {};
srsran_timestamp_init(&ts, secs, frac_secs);
uint64_t tx_ts = srsran_timestamp_uint64(&ts, handler->base_srate);
int num_tx_gap_samples = 0;
for (int i = 0; i < handler->nof_channels; i++) {
if (handler->transmitter[i].running) {
num_tx_gap_samples = rf_file_tx_align(&handler->transmitter[i], tx_ts);
}
}
if (num_tx_gap_samples < 0) {
fprintf(stderr,
"[file] Error: tx time is %.3f ms in the past (%" PRIu64 " < %" PRIu64 ")\n",
-1000.0 * num_tx_gap_samples / handler->base_srate,
tx_ts,
handler->transmitter[0].nsamples);
goto clean_exit;
}
}
// Send base-band samples
for (int i = 0; i < handler->nof_channels; i++) {
if (buffers[i] != NULL) {
// Select buffer pointer depending on interpolation
cf_t* buf = (decim_factor != 1) ? handler->buffer_tx : buffers[i];
// Interpolate if required
if (decim_factor != 1) {
rf_file_info(handler->id,
" - re-adjust bytes due to %dx interpolation %d --> %d samples)\n",
decim_factor,
nsamples,
nsamples_baseband);
int n = 0;
cf_t* src = buffers[i];
for (int k = 0; k < nsamples; k++) {
// perform zero order hold
for (int j = 0; j < decim_factor; j++, n++) {
buf[n] = src[k];
}
}
if (nsamples_baseband != n) {
fprintf(stderr,
"Number of tx samples (%d) does not match with number of interpolated samples (%d)\n",
nsamples_baseband,
n);
goto clean_exit;
}
}
int n = rf_file_tx_baseband(&handler->transmitter[i], buf, nsamples_baseband);
if (n == SRSRAN_ERROR) {
goto clean_exit;
}
} else {
int n = rf_file_tx_zeros(&handler->transmitter[i], nsamples_baseband);
if (n == SRSRAN_ERROR) {
goto clean_exit;
}
}
}
}
ret = SRSRAN_SUCCESS;
clean_exit:
return ret;
}
#endif

View File

@ -95,12 +95,8 @@ SRSRAN_API void rf_file_get_time(void* h, time_t* secs, double* frac_secs);
SRSRAN_API int
rf_file_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs);
SRSRAN_API int rf_file_recv_with_time_multi(void* h,
void* data[SRSRAN_MAX_PORTS],
uint32_t nsamples,
bool blocking,
time_t* secs,
double* frac_secs);
SRSRAN_API int
rf_file_recv_with_time_multi(void* h, void** data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs);
SRSRAN_API int rf_file_send_timed(void* h,
void* data,

View File

@ -21,7 +21,7 @@ int rf_file_rx_open(rf_file_rx_t* q, rf_file_opts_t opts)
if (q) {
// Zero object
bzero(q, sizeof(rf_file_rx_t));
memset(q, 0, sizeof(rf_file_rx_t));
// Copy id
strncpy(q->id, opts.id, FILE_ID_STRLEN - 1);
@ -62,7 +62,7 @@ clean_exit:
int rf_file_rx_baseband(rf_file_rx_t* q, cf_t* buffer, uint32_t nsamples)
{
uint32_t sample_sz = sizeof(cf_t);
uint32_t sample_sz = sizeof(cf_t);
int ret = fread(buffer, sample_sz, nsamples, q->file);
if (ret > 0) {
@ -83,6 +83,7 @@ bool rf_file_rx_match_freq(rf_file_rx_t* q, uint32_t freq_hz)
void rf_file_rx_close(rf_file_rx_t* q)
{
rf_file_info(q->id, "Closing ...\n");
q->running = false;
if (q->temp_buffer) {

View File

@ -42,7 +42,8 @@ typedef struct {
pthread_mutex_t mutex;
cf_t* zeros;
void* temp_buffer_convert;
uint32_t frequency_hz_mhz;
uint32_t frequency_mhz;
int32_t sample_offset;
} rf_file_tx_t;
typedef struct {
@ -68,8 +69,29 @@ typedef struct {
/*
* Common functions
*/
SRSRAN_API void rf_file_info(char* id, const char* format, ...);
SRSRAN_API void rf_file_error(char* id, const char* format, ...);
SRSRAN_API int rf_file_handle_error(char* id, const char* text);
/*
* Transmitter functions
*/
SRSRAN_API int rf_file_tx_open(rf_file_tx_t* q, rf_file_opts_t opts);
SRSRAN_API int rf_file_tx_align(rf_file_tx_t* q, uint64_t ts);
SRSRAN_API int rf_file_tx_baseband(rf_file_tx_t* q, cf_t* buffer, uint32_t nsamples);
SRSRAN_API int rf_file_tx_get_nsamples(rf_file_tx_t* q);
SRSRAN_API int rf_file_tx_zeros(rf_file_tx_t* q, uint32_t nsamples);
SRSRAN_API bool rf_file_tx_match_freq(rf_file_tx_t* q, uint32_t freq_hz);
SRSRAN_API void rf_file_tx_close(rf_file_tx_t* q);
/*
* Receiver functions
*/

View File

@ -0,0 +1,189 @@
/**
*
* \section COPYRIGHT
*
* Copyright 2013-2021 Software Radio Systems Limited
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the distribution.
*
*/
#include "rf_file_imp_trx.h"
#include <errno.h>
#include <inttypes.h>
#include <srsran/config.h>
#include <srsran/phy/utils/vector.h>
#include <stdlib.h>
#include <string.h>
int rf_file_tx_open(rf_file_tx_t* q, rf_file_opts_t opts)
{
int ret = SRSRAN_ERROR;
if (q) {
// Zero object
memset(q, 0, sizeof(rf_file_tx_t));
// Copy id
strncpy(q->id, opts.id, FILE_ID_STRLEN - 1);
q->id[FILE_ID_STRLEN - 1] = '\0';
// Assign file
q->file = opts.file;
// Configure formats
q->sample_format = opts.sample_format;
q->frequency_mhz = opts.frequency_mhz;
q->temp_buffer_convert = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE);
if (!q->temp_buffer_convert) {
fprintf(stderr, "Error: allocating tx buffer\n");
goto clean_exit;
}
if (pthread_mutex_init(&q->mutex, NULL)) {
fprintf(stderr, "Error: creating mutex\n");
goto clean_exit;
}
q->zeros = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE);
if (!q->zeros) {
fprintf(stderr, "Error: allocating zeros\n");
goto clean_exit;
}
memset(q->zeros, 0, FILE_MAX_BUFFER_SIZE);
q->running = true;
ret = SRSRAN_SUCCESS;
}
clean_exit:
return ret;
}
static int _rf_file_tx_baseband(rf_file_tx_t* q, cf_t* buffer, uint32_t nsamples)
{
int n = SRSRAN_ERROR;
// convert samples if necessary
void* buf = (buffer) ? buffer : q->zeros;
uint32_t sample_sz = sizeof(cf_t);
if (q->sample_format == FILERF_TYPE_SC16) {
buf = q->temp_buffer_convert;
sample_sz = 2 * sizeof(short);
srsran_vec_convert_fi((float*)buffer, INT16_MAX, (short*)q->temp_buffer_convert, 2 * nsamples);
}
size_t ret = fwrite(buf, (size_t)sample_sz, (size_t)nsamples, q->file);
if (ret < (size_t)nsamples) {
rf_file_error(q->id,
"[file] Error: transmitter expected %d bytes and sent %d. %s.\n",
NSAMPLES2NBYTES(nsamples),
ret,
strerror(errno));
n = SRSRAN_ERROR;
goto clean_exit;
}
// Increment sample counter
q->nsamples += nsamples;
n = nsamples;
clean_exit:
return n;
}
int rf_file_tx_align(rf_file_tx_t* q, uint64_t ts)
{
pthread_mutex_lock(&q->mutex);
int64_t nsamples = (int64_t)ts - (int64_t)q->nsamples;
if (nsamples > 0) {
rf_file_info(q->id, " - Detected Tx gap of %d samples.\n", nsamples);
_rf_file_tx_baseband(q, q->zeros, (uint32_t)nsamples);
}
pthread_mutex_unlock(&q->mutex);
return (int)nsamples;
}
int rf_file_tx_baseband(rf_file_tx_t* q, cf_t* buffer, uint32_t nsamples)
{
int n;
pthread_mutex_lock(&q->mutex);
if (q->sample_offset > 0) {
_rf_file_tx_baseband(q, q->zeros, (uint32_t)q->sample_offset);
q->sample_offset = 0;
} else if (q->sample_offset < 0) {
n = SRSRAN_MIN(-q->sample_offset, nsamples);
buffer += n;
nsamples -= n;
q->sample_offset += n;
if (nsamples == 0) {
return n;
}
}
n = _rf_file_tx_baseband(q, buffer, nsamples);
pthread_mutex_unlock(&q->mutex);
return n;
}
int rf_file_tx_get_nsamples(rf_file_tx_t* q)
{
pthread_mutex_lock(&q->mutex);
int ret = q->nsamples;
pthread_mutex_unlock(&q->mutex);
return ret;
}
int rf_file_tx_zeros(rf_file_tx_t* q, uint32_t nsamples)
{
pthread_mutex_lock(&q->mutex);
rf_file_info(q->id, " - Tx %d Zeros.\n", nsamples);
_rf_file_tx_baseband(q, q->zeros, (uint32_t)nsamples);
pthread_mutex_unlock(&q->mutex);
return (int)nsamples;
}
bool rf_file_tx_match_freq(rf_file_tx_t* q, uint32_t freq_hz)
{
bool ret = false;
if (q) {
ret = (q->frequency_mhz == 0 || q->frequency_mhz == freq_hz);
}
return ret;
}
void rf_file_tx_close(rf_file_tx_t* q)
{
rf_file_info(q->id, "Closing ...\n");
pthread_mutex_lock(&q->mutex);
q->running = false;
pthread_mutex_unlock(&q->mutex);
pthread_mutex_destroy(&q->mutex);
if (q->zeros) {
free(q->zeros);
}
if (q->temp_buffer_convert) {
free(q->temp_buffer_convert);
}
q->file = NULL;
}

View File

@ -0,0 +1,312 @@
/**
*
* \section COPYRIGHT
*
* Copyright 2013-2021 Software Radio Systems Limited
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the distribution.
*
*/
#include "rf_file_imp.h"
#include "srsran/common/tsan_options.h"
#include "srsran/phy/common/timestamp.h"
#include "srsran/phy/utils/debug.h"
#include <complex.h>
#include <pthread.h>
#include <srsran/phy/common/phy_common.h>
#include <srsran/phy/utils/vector.h>
#include <stdlib.h>
#define PRINT_SAMPLES 0
#define COMPARE_BITS 0
#define COMPARE_EPSILON (1e-6f)
#define NOF_RX_ANT 4
#define NUM_SF (500)
#define SF_LEN (1920)
#define RF_BUFFER_SIZE (SF_LEN * NUM_SF)
#define TX_OFFSET_MS (4)
static cf_t ue_rx_buffer[NOF_RX_ANT][RF_BUFFER_SIZE];
static cf_t enb_tx_buffer[NOF_RX_ANT][RF_BUFFER_SIZE];
static cf_t enb_rx_buffer[NOF_RX_ANT][RF_BUFFER_SIZE];
static srsran_rf_t ue_radio, enb_radio;
pthread_t rx_thread;
void* ue_rx_thread_function(void* args)
{
char rf_args[RF_PARAM_LEN];
strncpy(rf_args, (char*)args, RF_PARAM_LEN - 1);
rf_args[RF_PARAM_LEN - 1] = 0;
// sleep(1);
printf("opening rx device with args=%s\n", rf_args);
if (srsran_rf_open_devname(&ue_radio, "file", rf_args, NOF_RX_ANT)) {
fprintf(stderr, "Error opening rf\n");
exit(-1);
}
// receive 5 subframes at once (i.e. mimic initial rx that receives one slot)
uint32_t num_slots = NUM_SF / 5;
uint32_t num_samps_per_slot = SF_LEN * 5;
uint32_t num_rxed_samps = 0;
for (uint32_t i = 0; i < num_slots; ++i) {
void* data_ptr[SRSRAN_MAX_PORTS] = {NULL};
for (uint32_t c = 0; c < NOF_RX_ANT; c++) {
data_ptr[c] = &ue_rx_buffer[c][i * num_samps_per_slot];
}
num_rxed_samps += srsran_rf_recv_with_time_multi(&ue_radio, data_ptr, num_samps_per_slot, true, NULL, NULL);
}
printf("received %d samples.\n", num_rxed_samps);
printf("closing ue rx device\n");
srsran_rf_close(&ue_radio);
return NULL;
}
void enb_tx_function(const char* tx_args, bool timed_tx)
{
char rf_args[RF_PARAM_LEN];
strncpy(rf_args, tx_args, RF_PARAM_LEN - 1);
rf_args[RF_PARAM_LEN - 1] = 0;
printf("opening tx device with args=%s\n", rf_args);
if (srsran_rf_open_devname(&enb_radio, "file", rf_args, NOF_RX_ANT)) {
fprintf(stderr, "Error opening rf\n");
exit(-1);
}
// generate random tx data
for (int c = 0; c < NOF_RX_ANT; c++) {
for (int i = 0; i < RF_BUFFER_SIZE; i++) {
enb_tx_buffer[c][i] = ((float)rand() / (float)RAND_MAX) + _Complex_I * ((float)rand() / (float)RAND_MAX);
}
}
// send data subframe per subframe
uint32_t num_txed_samples = 0;
// initial transmission without ts
void* data_ptr[SRSRAN_MAX_PORTS] = {NULL};
for (int c = 0; c < NOF_RX_ANT; c++) {
data_ptr[c] = &enb_tx_buffer[c][num_txed_samples];
}
int ret = srsran_rf_send_multi(&enb_radio, (void**)data_ptr, SF_LEN, true, true, false);
num_txed_samples += SF_LEN;
// from here on, all transmissions are timed relative to the last rx time
srsran_timestamp_t rx_time, tx_time;
for (uint32_t i = 0; i < NUM_SF - ((timed_tx) ? TX_OFFSET_MS : 1); ++i) {
// first recv samples
for (int c = 0; c < NOF_RX_ANT; c++) {
data_ptr[c] = enb_rx_buffer[c];
}
srsran_rf_recv_with_time_multi(&enb_radio, data_ptr, SF_LEN, true, &rx_time.full_secs, &rx_time.frac_secs);
// prepare data buffer
for (int c = 0; c < NOF_RX_ANT; c++) {
data_ptr[c] = &enb_tx_buffer[c][num_txed_samples];
}
if (timed_tx) {
// timed tx relative to receive time (this will cause a cap in the rx'ed samples at the UE resulting in 3 zero
// subframes)
srsran_timestamp_copy(&tx_time, &rx_time);
srsran_timestamp_add(&tx_time, 0, TX_OFFSET_MS * 1e-3);
ret = srsran_rf_send_timed_multi(
&enb_radio, (void**)data_ptr, SF_LEN, tx_time.full_secs, tx_time.frac_secs, true, true, false);
} else {
// normal tx
ret = srsran_rf_send_multi(&enb_radio, (void**)data_ptr, SF_LEN, true, true, false);
}
if (ret != SRSRAN_SUCCESS) {
fprintf(stderr, "Error sending data\n");
exit(-1);
}
num_txed_samples += SF_LEN;
}
printf("transmitted %d samples in %d subframes\n", num_txed_samples, NUM_SF);
printf("closing tx device\n");
srsran_rf_close(&enb_radio);
}
int run_test(const char* rx_args, const char* tx_args, bool timed_tx)
{
int ret = SRSRAN_ERROR;
// make sure we can receive in slots
if (NUM_SF % 5 != 0) {
fprintf(stderr, "number of subframes must be multiple of 5\n");
goto exit;
}
// write to file(s)
enb_tx_function(tx_args, timed_tx);
// read from file(s)
ue_rx_thread_function((void*)rx_args);
// channel-wise comparison
for (int c = 0; c < NOF_RX_ANT; c++) {
// subframe-wise compare tx'ed and rx'ed data (stop 3 subframes earlier for timed tx)
for (uint32_t i = 0; i < NUM_SF - (timed_tx ? 3 : 0); ++i) {
uint32_t sf_offet = 0;
if (timed_tx && i >= 1) {
// for timed transmission, the enb inserts 3 zero subframes after the first untimed tx
sf_offet = (TX_OFFSET_MS - 1) * SF_LEN;
}
#if PRINT_SAMPLES
// print first 10 samples for each SF
printf("enb_tx_buffer sf%d:\n", i);
srsran_vec_fprint_c(stdout, &enb_tx_buffer[c][i * SF_LEN], 10);
printf("ue_rx_buffer sf%d:\n", i);
srsran_vec_fprint_c(stdout, &ue_rx_buffer[c][sf_offet + i * SF_LEN], 10);
#endif
#if COMPARE_BITS
int d = memcmp(&ue_rx_buffer[sf_offet + i * SF_LEN], &enb_tx_buffer[i * SF_LEN], SF_LEN);
if (d) {
d = d > 0 ? d : -d;
fprintf(stderr, "data mismatch in subframe %d, sample %d\n", i, d);
printf("enb_tx_buffer sf%d:\n", i);
srsran_vec_fprint_c(stdout, &enb_tx_buffer[i * SF_LEN + d], 10);
printf("ue_rx_buffer sf%d:\n", i);
srsran_vec_fprint_c(stdout, &ue_rx_buffer[sf_offet + i * SF_LEN + d], 10);
goto exit;
}
#else
srsran_vec_sub_ccc(&ue_rx_buffer[c][sf_offet + i * SF_LEN],
&enb_tx_buffer[c][i * SF_LEN],
&ue_rx_buffer[c][sf_offet + i * SF_LEN],
SF_LEN);
uint32_t max_ix = srsran_vec_max_abs_ci(&ue_rx_buffer[c][sf_offet + i * SF_LEN], SF_LEN);
if (cabsf(ue_rx_buffer[c][sf_offet + i * SF_LEN + max_ix]) > COMPARE_EPSILON) {
fprintf(stderr, "data mismatch in subframe %d\n", i);
goto exit;
}
#endif
}
}
ret = SRSRAN_SUCCESS;
exit:
return ret;
}
int param_test(const char* args_param, const int num_channels)
{
char rf_args[RF_PARAM_LEN] = {};
strncpy(rf_args, (char*)args_param, RF_PARAM_LEN - 1);
rf_args[RF_PARAM_LEN - 1] = 0;
printf("opening tx device with args=%s\n", rf_args);
if (srsran_rf_open_devname(&enb_radio, "file", rf_args, num_channels)) {
fprintf(stderr, "Error opening rf\n");
return SRSRAN_ERROR;
}
srsran_rf_close(&enb_radio);
return SRSRAN_SUCCESS;
}
void create_file(const char* filename)
{
FILE* f = fopen(filename, "w");
fclose(f);
}
int main()
{
// create files for testing
create_file("rx_file0");
create_file("rx_file1");
create_file("rx_file2");
create_file("rx_file3");
// two RX files
if (param_test("rx_file=rx_file0,"
"rx_file1=rx_file1",
2)) {
fprintf(stderr, "Param test failed!\n");
return SRSRAN_ERROR;
}
// multiple RX files, no channel index provided
if (param_test("rx_file=rx_file0,"
"rx_file=rx_file1,"
"rx_file=rx_file2,"
"rx_file=rx_file3,"
"base_srate=1.92e6",
4)) {
fprintf(stderr, "Param test failed!\n");
return SRSRAN_ERROR;
}
// one RX, one TX and all generic options
if (param_test("rx_file0=rx_file0,"
"tx_file0=tx_file0,"
"base_srate=1.92e6",
1)) {
fprintf(stderr, "Param test failed!\n");
return SRSRAN_ERROR;
}
// two RX, two TX
if (param_test("rx_file0=rx_file0,"
"rx_file1=rx_file1,"
"tx_file0=tx_file0,"
"tx_file1=tx_file1",
2)) {
fprintf(stderr, "Param test failed!\n");
return SRSRAN_ERROR;
}
#if NOF_RX_ANT == 1
// single tx, single rx with continuous transmissions (no decimation, no timed tx)
if (run_test("rx_file=tx_file0,base_srate=1.92e6", "tx_file=tx_file0,base_srate=1.92e6", false) != SRSRAN_SUCCESS) {
fprintf(stderr, "Single tx, single rx test failed (no decimation, no timed tx)!\n");
return -1;
}
#endif
// up to 4 trx radios with continous tx (no decimation, no timed tx)
if (run_test("rx_file=tx_file0,rx_file=tx_file1,rx_file=tx_file2,rx_file=tx_file3,base_srate=1.92e6",
"tx_file=tx_file0,tx_file=tx_file1,tx_file=tx_file2,tx_file=tx_file3,base_srate=1.92e6",
false) != SRSRAN_SUCCESS) {
fprintf(stderr, "Multi TRx radio test failed (no decimation, no timed tx)!\n");
return -1;
}
// up to 4 trx radios with continous tx (with decimation, no timed tx)
if (run_test("rx_file=tx_file0,rx_file=tx_file1,rx_file=tx_file2,rx_file=tx_file3",
"tx_file=tx_file0,tx_file=tx_file1,tx_file=tx_file2,tx_file=tx_file3",
false) != SRSRAN_SUCCESS) {
fprintf(stderr, "Multi TRx radio test failed (with decimation, no timed tx)!\n");
return -1;
}
// up to 4 trx radios with continous tx (with decimation, timed tx)
if (run_test("rx_file=tx_file0,rx_file=tx_file1,rx_file=tx_file2,rx_file=tx_file3",
"tx_file=tx_file0,tx_file=tx_file1,tx_file=tx_file2,tx_file=tx_file3",
true) != SRSRAN_SUCCESS) {
fprintf(stderr, "Two TRx radio test failed (with decimation, timed tx)!\n");
return -1;
}
fprintf(stdout, "Test passed!\n");
return SRSRAN_SUCCESS;
}