mirror of https://github.com/merbanan/rtl_433.git
1807 lines
52 KiB
C
1807 lines
52 KiB
C
/** @file
|
|
SDR input from RTLSDR or SoapySDR.
|
|
|
|
Copyright (C) 2018 Christian Zuckschwerdt
|
|
based on code
|
|
Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de>
|
|
Copyright (C) 2014 by Kyle Keen <keenerd@gmail.com>
|
|
Copyright (C) 2016 by Robert X. Seger
|
|
|
|
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 2 of the License, or
|
|
(at your option) any later version.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include "sdr.h"
|
|
#include "r_util.h"
|
|
#include "optparse.h"
|
|
#include "logger.h"
|
|
#include "fatal.h"
|
|
#include "compat_pthread.h"
|
|
#ifdef RTLSDR
|
|
#include <rtl-sdr.h>
|
|
#if defined(__linux__) && (defined(__GNUC__) || defined(__clang__))
|
|
// not available in rtlsdr 0.5.3, allow weak link for Linux
|
|
int __attribute__((weak)) rtlsdr_set_bias_tee(rtlsdr_dev_t *dev, int on);
|
|
#endif
|
|
#ifdef LIBUSB1
|
|
#include <libusb.h> /* libusb_error_name(), libusb_strerror() */
|
|
#endif
|
|
#endif
|
|
#ifdef SOAPYSDR
|
|
#include <SoapySDR/Version.h>
|
|
#include <SoapySDR/Device.h>
|
|
#include <SoapySDR/Formats.h>
|
|
#include <SoapySDR/Logger.h>
|
|
#if (SOAPY_SDR_API_VERSION < 0x00080000)
|
|
#define SoapySDR_free(ptr) free(ptr)
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef _MSC_VER
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
|
|
#undef _WIN32_WINNT
|
|
#define _WIN32_WINNT 0x0600 /* Needed to pull in 'struct sockaddr_storage' */
|
|
#endif
|
|
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#define SHUT_RDWR SD_BOTH
|
|
#define perror(str) ws2_perror(str)
|
|
|
|
static void ws2_perror (const char *str)
|
|
{
|
|
if (str && *str)
|
|
fprintf(stderr, "%s: ", str);
|
|
fprintf(stderr, "Winsock error %d.\n", WSAGetLastError());
|
|
}
|
|
#else
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
|
|
#define SOCKET int
|
|
#define INVALID_SOCKET (-1)
|
|
#define closesocket(x) close(x)
|
|
#endif
|
|
|
|
#define GAIN_STR_MAX_SIZE 64
|
|
|
|
struct sdr_dev {
|
|
SOCKET rtl_tcp;
|
|
uint32_t rtl_tcp_freq; ///< last known center frequency, rtl_tcp only.
|
|
uint32_t rtl_tcp_rate; ///< last known sample rate, rtl_tcp only.
|
|
|
|
#ifdef SOAPYSDR
|
|
SoapySDRDevice *soapy_dev;
|
|
SoapySDRStream *soapy_stream;
|
|
double fullScale;
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
rtlsdr_dev_t *rtlsdr_dev;
|
|
sdr_event_cb_t rtlsdr_cb;
|
|
void *rtlsdr_cb_ctx;
|
|
#endif
|
|
|
|
char *dev_info;
|
|
|
|
int running;
|
|
uint8_t *buffer; ///< sdr data buffer current and past frames
|
|
size_t buffer_size; ///< sdr data buffer overall size (num * len)
|
|
size_t buffer_pos; ///< sdr data buffer next write position
|
|
|
|
int sample_size;
|
|
int sample_signed;
|
|
|
|
uint32_t sample_rate;
|
|
uint32_t center_frequency;
|
|
|
|
#ifdef THREADS
|
|
pthread_t thread;
|
|
pthread_mutex_t lock; ///< lock for exit_acquire
|
|
int exit_acquire;
|
|
|
|
// acquire thread args
|
|
sdr_event_cb_t async_cb;
|
|
void *async_ctx;
|
|
uint32_t buf_num;
|
|
uint32_t buf_len;
|
|
#endif
|
|
};
|
|
|
|
/* rtl_tcp helpers */
|
|
|
|
#pragma pack(push, 1)
|
|
struct rtl_tcp_info {
|
|
char magic[4]; // "RTL0"
|
|
uint32_t tuner_number; // big endian
|
|
uint32_t tuner_gain_count; // big endian
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
static int rtltcp_open(sdr_dev_t **out_dev, char const *dev_query, int verbose)
|
|
{
|
|
UNUSED(verbose);
|
|
char const *host = "localhost";
|
|
char const *port = "1234";
|
|
char hostport[280]; // 253 chars DNS name plus extra chars
|
|
|
|
char *param = arg_param(dev_query); // strip scheme
|
|
hostport[0] = '\0';
|
|
if (param) {
|
|
snprintf(hostport, sizeof(hostport), "%s", param);
|
|
}
|
|
hostport_param(hostport, &host, &port);
|
|
|
|
print_logf(LOG_CRITICAL, "SDR", "rtl_tcp input from %s port %s", host, port);
|
|
|
|
#ifdef _WIN32
|
|
WSADATA wsa;
|
|
|
|
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
|
|
perror("WSAStartup()");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
struct addrinfo hints, *res, *res0;
|
|
int ret;
|
|
SOCKET sock;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = 0;
|
|
hints.ai_flags = AI_ADDRCONFIG;
|
|
|
|
ret = getaddrinfo(host, port, &hints, &res0);
|
|
if (ret) {
|
|
print_log(LOG_ERROR, __func__, gai_strerror(ret));
|
|
return -1;
|
|
}
|
|
sock = INVALID_SOCKET;
|
|
for (res = res0; res; res = res->ai_next) {
|
|
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
|
if (sock >= 0) {
|
|
ret = connect(sock, res->ai_addr, res->ai_addrlen);
|
|
if (ret == -1) {
|
|
perror("connect");
|
|
closesocket(sock);
|
|
sock = INVALID_SOCKET;
|
|
}
|
|
else
|
|
break; // success
|
|
}
|
|
}
|
|
freeaddrinfo(res0);
|
|
if (sock == INVALID_SOCKET) {
|
|
perror("socket");
|
|
return -1;
|
|
}
|
|
|
|
//int const value_one = 1;
|
|
//ret = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&value_one, sizeof(value_one));
|
|
//if (ret < 0)
|
|
// fprintf(stderr, "rtl_tcp TCP_NODELAY failed\n");
|
|
|
|
struct rtl_tcp_info info;
|
|
ret = recv(sock, (char *)&info, sizeof (info), 0);
|
|
if (ret != 12) {
|
|
print_logf(LOG_ERROR, __func__, "Bad rtl_tcp header (%d)", ret);
|
|
return -1;
|
|
}
|
|
if (strncmp(info.magic, "RTL0", 4)) {
|
|
info.tuner_number = 0; // terminate magic
|
|
print_logf(LOG_ERROR, __func__, "Bad rtl_tcp header magic \"%s\"", info.magic);
|
|
return -1;
|
|
}
|
|
|
|
unsigned tuner_number = ntohl(info.tuner_number);
|
|
//int tuner_gain_count = ntohl(info.tuner_gain_count);
|
|
|
|
char const *tuner_names[] = { "Unknown", "E4000", "FC0012", "FC0013", "FC2580", "R820T", "R828D" };
|
|
char const *tuner_name = tuner_number > sizeof (tuner_names) ? "Invalid" : tuner_names[tuner_number];
|
|
|
|
print_logf(LOG_CRITICAL, "SDR", "rtl_tcp connected to %s:%s (Tuner: %s)", host, port, tuner_name);
|
|
|
|
sdr_dev_t *dev = calloc(1, sizeof(sdr_dev_t));
|
|
if (!dev) {
|
|
WARN_CALLOC("rtltcp_open()");
|
|
return -1; // NOTE: returns error on alloc failure.
|
|
}
|
|
#ifdef THREADS
|
|
pthread_mutex_init(&dev->lock, NULL);
|
|
#endif
|
|
|
|
dev->rtl_tcp = sock;
|
|
dev->sample_size = sizeof(uint8_t) * 2; // CU8
|
|
dev->sample_signed = 0;
|
|
|
|
*out_dev = dev;
|
|
return 0;
|
|
}
|
|
|
|
static int rtltcp_close(SOCKET sock)
|
|
{
|
|
int ret = shutdown(sock, SHUT_RDWR);
|
|
if (ret == -1) {
|
|
perror("shutdown");
|
|
return -1;
|
|
}
|
|
|
|
ret = closesocket(sock);
|
|
if (ret == -1) {
|
|
perror("close");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rtltcp_read_loop(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)
|
|
{
|
|
size_t buffer_size = (size_t)buf_num * buf_len;
|
|
if (dev->buffer_size != buffer_size) {
|
|
free(dev->buffer);
|
|
dev->buffer = malloc(buffer_size);
|
|
if (!dev->buffer) {
|
|
WARN_MALLOC("rtltcp_read_loop()");
|
|
return -1; // NOTE: returns error on alloc failure.
|
|
}
|
|
dev->buffer_size = buffer_size;
|
|
dev->buffer_pos = 0;
|
|
}
|
|
|
|
dev->running = 1;
|
|
do {
|
|
if (dev->buffer_pos + buf_len > buffer_size)
|
|
dev->buffer_pos = 0;
|
|
uint8_t *buffer = &dev->buffer[dev->buffer_pos];
|
|
dev->buffer_pos += buf_len;
|
|
|
|
unsigned n_read = 0;
|
|
int r;
|
|
|
|
do {
|
|
r = recv(dev->rtl_tcp, &buffer[n_read], buf_len - n_read, MSG_WAITALL);
|
|
if (r <= 0)
|
|
break;
|
|
n_read += r;
|
|
//fprintf(stderr, "readStream ret=%d (of %u)\n", r, n_read);
|
|
} while (n_read < buf_len);
|
|
//fprintf(stderr, "readStream ret=%d (read %u)\n", r, n_read);
|
|
|
|
if (r < 0) {
|
|
print_logf(LOG_WARNING, __func__, "sync read failed. %d", r);
|
|
}
|
|
if (n_read == 0) {
|
|
perror("rtl_tcp");
|
|
dev->running = 0;
|
|
}
|
|
|
|
#ifdef THREADS
|
|
pthread_mutex_lock(&dev->lock);
|
|
#endif
|
|
uint32_t sample_rate = dev->sample_rate;
|
|
uint32_t center_frequency = dev->center_frequency;
|
|
#ifdef THREADS
|
|
pthread_mutex_unlock(&dev->lock);
|
|
#endif
|
|
sdr_event_t ev = {
|
|
.ev = SDR_EV_DATA,
|
|
.sample_rate = sample_rate,
|
|
.center_frequency = center_frequency,
|
|
.buf = buffer,
|
|
.len = n_read,
|
|
};
|
|
#ifdef THREADS
|
|
pthread_mutex_lock(&dev->lock);
|
|
int exit_acquire = dev->exit_acquire;
|
|
pthread_mutex_unlock(&dev->lock);
|
|
if (exit_acquire) {
|
|
break; // do not deliver any more events
|
|
}
|
|
#endif
|
|
if (n_read > 0) // prevent a crash in callback
|
|
cb(&ev, ctx);
|
|
|
|
} while (dev->running);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#pragma pack(push, 1)
|
|
struct command {
|
|
unsigned char cmd;
|
|
unsigned int param;
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
// rtl_tcp API
|
|
#define RTLTCP_SET_FREQ 0x01
|
|
#define RTLTCP_SET_SAMPLE_RATE 0x02
|
|
#define RTLTCP_SET_GAIN_MODE 0x03
|
|
#define RTLTCP_SET_GAIN 0x04
|
|
#define RTLTCP_SET_FREQ_CORRECTION 0x05
|
|
#define RTLTCP_SET_IF_TUNER_GAIN 0x06
|
|
#define RTLTCP_SET_TEST_MODE 0x07
|
|
#define RTLTCP_SET_AGC_MODE 0x08
|
|
#define RTLTCP_SET_DIRECT_SAMPLING 0x09
|
|
#define RTLTCP_SET_OFFSET_TUNING 0x0a
|
|
#define RTLTCP_SET_RTL_XTAL 0x0b
|
|
#define RTLTCP_SET_TUNER_XTAL 0x0c
|
|
#define RTLTCP_SET_TUNER_GAIN_BY_ID 0x0d
|
|
#define RTLTCP_SET_BIAS_TEE 0x0e
|
|
|
|
static int rtltcp_command(sdr_dev_t *dev, char cmd, int param)
|
|
{
|
|
struct command command;
|
|
command.cmd = cmd;
|
|
command.param = htonl(param);
|
|
|
|
return sizeof(command) == send(dev->rtl_tcp, (const char*) &command, sizeof(command), 0) ? 0 : -1;
|
|
}
|
|
|
|
/* RTL-SDR helpers */
|
|
|
|
#ifdef RTLSDR
|
|
|
|
static int sdr_open_rtl(sdr_dev_t **out_dev, char const *dev_query, int verbose)
|
|
{
|
|
uint32_t device_count = rtlsdr_get_device_count();
|
|
if (!device_count) {
|
|
print_log(LOG_CRITICAL, "SDR", "No supported devices found.");
|
|
return -1;
|
|
}
|
|
|
|
if (verbose)
|
|
print_logf(LOG_NOTICE, "SDR", "Found %u device(s)", device_count);
|
|
|
|
int dev_index = 0;
|
|
// select rtlsdr device by serial (-d :<serial>)
|
|
if (dev_query && *dev_query == ':') {
|
|
dev_index = rtlsdr_get_index_by_serial(&dev_query[1]);
|
|
if (dev_index < 0) {
|
|
if (verbose)
|
|
print_logf(LOG_ERROR, "SDR", "Could not find device with serial '%s' (err %d)",
|
|
&dev_query[1], dev_index);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// select rtlsdr device by number (-d <n>)
|
|
else if (dev_query) {
|
|
dev_index = atoi(dev_query);
|
|
// check if 0 is a parsing error?
|
|
if (dev_index < 0) {
|
|
// select first available rtlsdr device
|
|
dev_index = 0;
|
|
dev_query = NULL;
|
|
}
|
|
}
|
|
|
|
char vendor[256] = "n/a", product[256] = "n/a", serial[256] = "n/a";
|
|
int r = -1;
|
|
sdr_dev_t *dev = calloc(1, sizeof(sdr_dev_t));
|
|
if (!dev) {
|
|
WARN_CALLOC("sdr_open_rtl()");
|
|
return -1; // NOTE: returns error on alloc failure.
|
|
}
|
|
#ifdef THREADS
|
|
pthread_mutex_init(&dev->lock, NULL);
|
|
#endif
|
|
|
|
for (uint32_t i = dev_query ? dev_index : 0;
|
|
//cast quiets -Wsign-compare; if dev_index were < 0, would have returned -1 above
|
|
i < (dev_query ? (unsigned)dev_index + 1 : device_count);
|
|
i++) {
|
|
rtlsdr_get_device_usb_strings(i, vendor, product, serial);
|
|
|
|
if (verbose)
|
|
print_logf(LOG_NOTICE, "SDR", "trying device %u: %s, %s, SN: %s",
|
|
i, vendor, product, serial);
|
|
|
|
r = rtlsdr_open(&dev->rtlsdr_dev, i);
|
|
if (r < 0) {
|
|
if (verbose)
|
|
print_logf(LOG_ERROR, __func__, "Failed to open rtlsdr device #%u.", i);
|
|
}
|
|
else {
|
|
if (verbose)
|
|
print_logf(LOG_CRITICAL, "SDR", "Using device %u: %s, %s, SN: %s, \"%s\"",
|
|
i, vendor, product, serial, rtlsdr_get_device_name(i));
|
|
dev->sample_size = sizeof(uint8_t) * 2; // CU8
|
|
dev->sample_signed = 0;
|
|
|
|
size_t info_len = 41 + strlen(vendor) + strlen(product) + strlen(serial);
|
|
dev->dev_info = malloc(info_len);
|
|
if (!dev->dev_info)
|
|
FATAL_MALLOC("sdr_open_rtl");
|
|
snprintf(dev->dev_info, info_len, "{\"vendor\":\"%s\", \"product\":\"%s\", \"serial\":\"%s\"}",
|
|
vendor, product, serial);
|
|
break;
|
|
}
|
|
}
|
|
if (r < 0) {
|
|
free(dev);
|
|
if (verbose)
|
|
print_log(LOG_ERROR, __func__, "Unable to open a device");
|
|
}
|
|
else {
|
|
*out_dev = dev;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int rtlsdr_find_tuner_gain(sdr_dev_t *dev, int centigain, int verbose)
|
|
{
|
|
/* Get allowed gains */
|
|
int gains_count = rtlsdr_get_tuner_gains(dev->rtlsdr_dev, NULL);
|
|
if (gains_count < 0) {
|
|
if (verbose)
|
|
print_log(LOG_WARNING, __func__, "Unable to get exact gains");
|
|
return centigain;
|
|
}
|
|
if (gains_count < 1) {
|
|
if (verbose)
|
|
print_log(LOG_WARNING, __func__, "No exact gains");
|
|
return centigain;
|
|
}
|
|
if (gains_count > 29) {
|
|
print_log(LOG_ERROR, __func__, "Unexpected gain count, notify maintainers please!");
|
|
return centigain;
|
|
}
|
|
// We known the maximum nunmber of gains is 29.
|
|
// Let's not waste an alloc
|
|
int gains[29] = {0};
|
|
rtlsdr_get_tuner_gains(dev->rtlsdr_dev, gains);
|
|
|
|
/* Find allowed gain */
|
|
for (int i = 0; i < gains_count; ++i) {
|
|
if (centigain <= gains[i]) {
|
|
centigain = gains[i];
|
|
break;
|
|
}
|
|
}
|
|
if (centigain > gains[gains_count - 1]) {
|
|
centigain = gains[gains_count - 1];
|
|
}
|
|
|
|
return centigain;
|
|
}
|
|
|
|
static void rtlsdr_read_cb(unsigned char *iq_buf, uint32_t len, void *ctx)
|
|
{
|
|
sdr_dev_t *dev = ctx;
|
|
|
|
//fprintf(stderr, "rtlsdr_read_cb enter...\n");
|
|
#ifdef THREADS
|
|
pthread_mutex_lock(&dev->lock);
|
|
int exit_acquire = dev->exit_acquire;
|
|
pthread_mutex_unlock(&dev->lock);
|
|
if (exit_acquire) {
|
|
// we get one more call after rtlsdr_cancel_async(),
|
|
// it then takes a full second until rtlsdr_read_async() ends.
|
|
//fprintf(stderr, "rtlsdr_read_cb stopping...\n");
|
|
return; // do not deliver any more events
|
|
}
|
|
#endif
|
|
|
|
if (dev->buffer_pos + len > dev->buffer_size)
|
|
dev->buffer_pos = 0;
|
|
uint8_t *buffer = &dev->buffer[dev->buffer_pos];
|
|
dev->buffer_pos += len;
|
|
|
|
// NOTE: we need to copy the buffer, it might go away on cancel_async
|
|
memcpy(buffer, iq_buf, len);
|
|
|
|
#ifdef THREADS
|
|
pthread_mutex_lock(&dev->lock);
|
|
#endif
|
|
uint32_t sample_rate = dev->sample_rate;
|
|
uint32_t center_frequency = dev->center_frequency;
|
|
#ifdef THREADS
|
|
pthread_mutex_unlock(&dev->lock);
|
|
#endif
|
|
sdr_event_t ev = {
|
|
.ev = SDR_EV_DATA,
|
|
.sample_rate = sample_rate,
|
|
.center_frequency = center_frequency,
|
|
.buf = buffer,
|
|
.len = len,
|
|
};
|
|
//fprintf(stderr, "rtlsdr_read_cb cb...\n");
|
|
if (len > 0) // prevent a crash in callback
|
|
dev->rtlsdr_cb(&ev, dev->rtlsdr_cb_ctx);
|
|
//fprintf(stderr, "rtlsdr_read_cb cb done.\n");
|
|
// NOTE: we actually need to copy the buffer to prevent it going away on cancel_async
|
|
}
|
|
|
|
static int rtlsdr_read_loop(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)
|
|
{
|
|
size_t buffer_size = (size_t)buf_num * buf_len;
|
|
if (dev->buffer_size != buffer_size) {
|
|
free(dev->buffer);
|
|
dev->buffer = malloc(buffer_size);
|
|
if (!dev->buffer) {
|
|
WARN_MALLOC("rtlsdr_read_loop()");
|
|
return -1; // NOTE: returns error on alloc failure.
|
|
}
|
|
dev->buffer_size = buffer_size;
|
|
dev->buffer_pos = 0;
|
|
}
|
|
|
|
int r = 0;
|
|
|
|
dev->rtlsdr_cb = cb;
|
|
dev->rtlsdr_cb_ctx = ctx;
|
|
|
|
dev->running = 1;
|
|
|
|
r = rtlsdr_read_async(dev->rtlsdr_dev, rtlsdr_read_cb, dev, buf_num, buf_len);
|
|
// rtlsdr_read_async() returns possible error codes from:
|
|
// if (!dev) return -1;
|
|
// if (RTLSDR_INACTIVE != dev->async_status) return -2;
|
|
// r = libusb_submit_transfer(dev->xfer[i]);
|
|
// r = libusb_handle_events_timeout_completed(dev->ctx, &tv,
|
|
// r = libusb_cancel_transfer(dev->xfer[i]);
|
|
// We can safely assume it's an libusb error.
|
|
if (r < 0) {
|
|
#ifdef LIBUSB1
|
|
print_logf(LOG_ERROR, __func__, "%s: %s! "
|
|
"Check your RTL-SDR dongle, USB cables, and power supply.",
|
|
libusb_error_name(r), libusb_strerror(r));
|
|
#else
|
|
print_logf(LOG_ERROR, __func__, "LIBUSB_ERROR: %d! "
|
|
"Check your RTL-SDR dongle, USB cables, and power supply.",
|
|
r);
|
|
#endif
|
|
dev->running = 0;
|
|
}
|
|
print_log(LOG_DEBUG, __func__, "rtlsdr_read_async done");
|
|
|
|
return r;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* SoapySDR helpers */
|
|
|
|
#ifdef SOAPYSDR
|
|
|
|
static int soapysdr_set_bandwidth(SoapySDRDevice *dev, uint32_t bandwidth)
|
|
{
|
|
int r;
|
|
r = (int)SoapySDRDevice_setBandwidth(dev, SOAPY_SDR_RX, 0, (double)bandwidth);
|
|
uint32_t applied_bw = 0;
|
|
if (r != 0) {
|
|
print_log(LOG_WARNING, "SDR", "Failed to set bandwidth.");
|
|
}
|
|
else if (bandwidth > 0) {
|
|
applied_bw = (uint32_t)SoapySDRDevice_getBandwidth(dev, SOAPY_SDR_RX, 0);
|
|
if (applied_bw)
|
|
print_logf(LOG_NOTICE, "SDR", "Bandwidth parameter %u Hz resulted in %u Hz.", bandwidth, applied_bw);
|
|
else
|
|
print_logf(LOG_NOTICE, "SDR", "Set bandwidth parameter %u Hz.", bandwidth);
|
|
}
|
|
else {
|
|
print_logf(LOG_NOTICE, "SDR", "Bandwidth set to automatic resulted in %u Hz.", applied_bw);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int soapysdr_direct_sampling(SoapySDRDevice *dev, int on)
|
|
{
|
|
int r = 0;
|
|
char const *value;
|
|
if (on == 0)
|
|
value = "0";
|
|
else if (on == 1)
|
|
value = "1";
|
|
else if (on == 2)
|
|
value = "2";
|
|
else
|
|
return -1;
|
|
SoapySDRDevice_writeSetting(dev, "direct_samp", value);
|
|
char *set_value = SoapySDRDevice_readSetting(dev, "direct_samp");
|
|
|
|
if (set_value == NULL) {
|
|
print_log(LOG_ERROR, __func__, "Failed to set direct sampling moden");
|
|
return r;
|
|
}
|
|
int set_num = atoi(set_value);
|
|
if (set_num == 0) {
|
|
print_log(LOG_CRITICAL, "SDR", "Direct sampling mode disabled.");}
|
|
else if (set_num == 1) {
|
|
print_log(LOG_CRITICAL, "SDR", "Enabled direct sampling mode, input 1/I.");}
|
|
else if (set_num == 2) {
|
|
print_log(LOG_CRITICAL, "SDR", "Enabled direct sampling mode, input 2/Q.");}
|
|
else if (set_num == 3) {
|
|
print_log(LOG_CRITICAL, "SDR", "Enabled no-mod direct sampling mode.");}
|
|
SoapySDR_free(set_value);
|
|
return r;
|
|
}
|
|
|
|
static int soapysdr_offset_tuning(SoapySDRDevice *dev)
|
|
{
|
|
int r = 0;
|
|
SoapySDRDevice_writeSetting(dev, "offset_tune", "true");
|
|
char *set_value = SoapySDRDevice_readSetting(dev, "offset_tune");
|
|
|
|
if (strcmp(set_value, "true") != 0) {
|
|
/* TODO: detection of failure modes
|
|
if (r == -2)
|
|
print_log(LOG_WARNING, __func__, "Failed to set offset tuning: tuner doesn't support offset tuning!");
|
|
else if (r == -3)
|
|
print_log(LOG_WARNING, __func__, "Failed to set offset tuning: direct sampling not combinable with offset tuning!");
|
|
else
|
|
*/
|
|
print_log(LOG_WARNING, __func__, "Failed to set offset tuning.");
|
|
}
|
|
else {
|
|
print_log(LOG_CRITICAL, "SDR", "Offset tuning mode enabled.");
|
|
}
|
|
SoapySDR_free(set_value);
|
|
return r;
|
|
}
|
|
|
|
static int soapysdr_auto_gain(SoapySDRDevice *dev, int verbose)
|
|
{
|
|
int r = 0;
|
|
|
|
r = SoapySDRDevice_hasGainMode(dev, SOAPY_SDR_RX, 0);
|
|
if (r) {
|
|
r = SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, 0, 1);
|
|
if (r != 0) {
|
|
print_log(LOG_WARNING, __func__, "Failed to enable automatic gain.");
|
|
}
|
|
else {
|
|
if (verbose)
|
|
print_log(LOG_CRITICAL, "SDR", "Tuner set to automatic gain.");
|
|
}
|
|
}
|
|
|
|
// Per-driver hacks TODO: clean this up
|
|
char *driver = SoapySDRDevice_getDriverKey(dev);
|
|
if (strcmp(driver, "HackRF") == 0) {
|
|
// HackRF has three gains LNA, VGA, and AMP, setting total distributes amongst, 116.0 dB seems to work well,
|
|
// even though it logs HACKRF_ERROR_INVALID_PARAM? https://github.com/rxseger/rx_tools/issues/9
|
|
// Total gain is distributed amongst all gains, 116 = 37,65,1; the LNA is OK (<40) but VGA is out of range (65 > 62)
|
|
// TODO: generic means to set all gains, of any SDR? string parsing LNA=#,VGA=#,AMP=#?
|
|
r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, "LNA", 40.); // max 40
|
|
if (r != 0) {
|
|
print_log(LOG_WARNING, __func__, "Failed to set LNA tuner gain.");
|
|
}
|
|
r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, "VGA", 20.); // max 65
|
|
if (r != 0) {
|
|
print_log(LOG_WARNING, __func__, "Failed to set VGA tuner gain.");
|
|
}
|
|
r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, "AMP", 0.); // on or off
|
|
if (r != 0) {
|
|
print_log(LOG_WARNING, __func__, "Failed to set AMP tuner gain.");
|
|
}
|
|
|
|
}
|
|
SoapySDR_free(driver);
|
|
// otherwise leave unset, hopefully the driver has good defaults
|
|
|
|
return r;
|
|
}
|
|
|
|
static int soapysdr_gain_str_set(SoapySDRDevice *dev, char const *gain_str, int verbose)
|
|
{
|
|
if (!gain_str || !*gain_str || strlen(gain_str) >= GAIN_STR_MAX_SIZE)
|
|
return -1;
|
|
|
|
int r = 0;
|
|
|
|
// Disable automatic gain
|
|
r = SoapySDRDevice_hasGainMode(dev, SOAPY_SDR_RX, 0);
|
|
if (r) {
|
|
r = SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, 0, 0);
|
|
if (r != 0) {
|
|
print_log(LOG_WARNING, __func__, "Failed to disable automatic gain.");
|
|
}
|
|
else {
|
|
if (verbose)
|
|
print_log(LOG_NOTICE, "SDR", "Tuner set to manual gain.");
|
|
}
|
|
}
|
|
|
|
if (strchr(gain_str, '=')) {
|
|
char gain_cpy[GAIN_STR_MAX_SIZE];
|
|
snprintf(gain_cpy, sizeof(gain_cpy), "%s", gain_str);
|
|
char *gain_p = gain_cpy;
|
|
// Set each gain individually (more control)
|
|
char *name;
|
|
char *value;
|
|
while (getkwargs(&gain_p, &name, &value)) {
|
|
double num = atof(value);
|
|
if (verbose)
|
|
print_logf(LOG_NOTICE, "SDR", "Setting gain element %s: %f dB", name, num);
|
|
r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, name, num);
|
|
if (r != 0) {
|
|
print_logf(LOG_WARNING, __func__, "setGainElement(%s, %f) failed: %d", name, num, r);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Set overall gain and let SoapySDR distribute amongst components
|
|
double value = atof(gain_str);
|
|
r = SoapySDRDevice_setGain(dev, SOAPY_SDR_RX, 0, value);
|
|
if (r != 0) {
|
|
print_log(LOG_WARNING, __func__, "Failed to set tuner gain.");
|
|
}
|
|
else {
|
|
if (verbose)
|
|
print_logf(LOG_NOTICE, __func__, "Tuner gain set to %0.2f dB.", value);
|
|
}
|
|
// read back and print each individual gain element
|
|
if (verbose) {
|
|
size_t len = 0;
|
|
char **gains = SoapySDRDevice_listGains(dev, SOAPY_SDR_RX, 0, &len);
|
|
fprintf(stderr, "Gain elements: ");
|
|
for (size_t i = 0; i < len; ++i) {
|
|
double gain = SoapySDRDevice_getGain(dev, SOAPY_SDR_RX, 0);
|
|
fprintf(stderr, "%s=%g ", gains[i], gain);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
SoapySDRStrings_clear(&gains, len);
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static void soapysdr_show_device_info(SoapySDRDevice *dev)
|
|
{
|
|
size_t len = 0, i = 0;
|
|
char *hwkey;
|
|
SoapySDRKwargs args;
|
|
char **antennas;
|
|
char **gains;
|
|
char **frequencies;
|
|
SoapySDRRange *frequencyRanges;
|
|
SoapySDRRange *rates;
|
|
SoapySDRRange *bandwidths;
|
|
double fullScale;
|
|
char **stream_formats;
|
|
char *native_stream_format;
|
|
|
|
int direction = SOAPY_SDR_RX;
|
|
size_t channel = 0;
|
|
|
|
hwkey = SoapySDRDevice_getHardwareKey(dev);
|
|
fprintf(stderr, "Using device %s: ", hwkey);
|
|
SoapySDR_free(hwkey);
|
|
|
|
args = SoapySDRDevice_getHardwareInfo(dev);
|
|
for (i = 0; i < args.size; ++i) {
|
|
fprintf(stderr, "%s=%s ", args.keys[i], args.vals[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
SoapySDRKwargs_clear(&args);
|
|
|
|
antennas = SoapySDRDevice_listAntennas(dev, direction, channel, &len);
|
|
fprintf(stderr, "Found %zu antenna(s): ", len);
|
|
for (i = 0; i < len; ++i) {
|
|
fprintf(stderr, "%s ", antennas[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
SoapySDRStrings_clear(&antennas, len);
|
|
|
|
gains = SoapySDRDevice_listGains(dev, direction, channel, &len);
|
|
fprintf(stderr, "Found %zu gain(s): ", len);
|
|
for (i = 0; i < len; ++i) {
|
|
SoapySDRRange gainRange = SoapySDRDevice_getGainRange(dev, direction, channel);
|
|
fprintf(stderr, "%s %.0f - %.0f (step %.0f) ", gains[i], gainRange.minimum, gainRange.maximum, gainRange.step);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
SoapySDRStrings_clear(&gains, len);
|
|
|
|
frequencies = SoapySDRDevice_listFrequencies(dev, direction, channel, &len);
|
|
fprintf(stderr, "Found %zu frequencies: ", len);
|
|
for (i = 0; i < len; ++i) {
|
|
fprintf(stderr, "%s ", frequencies[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
SoapySDRStrings_clear(&frequencies, len);
|
|
|
|
frequencyRanges = SoapySDRDevice_getFrequencyRange(dev, direction, channel, &len);
|
|
fprintf(stderr, "Found %zu frequency range(s): ", len);
|
|
for (i = 0; i < len; ++i) {
|
|
fprintf(stderr, "%.0f - %.0f (step %.0f) ", frequencyRanges[i].minimum, frequencyRanges[i].maximum, frequencyRanges[i].step);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
SoapySDR_free(frequencyRanges);
|
|
|
|
rates = SoapySDRDevice_getSampleRateRange(dev, direction, channel, &len);
|
|
fprintf(stderr, "Found %zu sample rate range(s): ", len);
|
|
for (i = 0; i < len; ++i) {
|
|
if (rates[i].minimum == rates[i].maximum)
|
|
fprintf(stderr, "%.0f ", rates[i].minimum);
|
|
else
|
|
fprintf(stderr, "%.0f - %.0f (step %.0f) ", rates[i].minimum, rates[i].maximum, rates[i].step);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
SoapySDR_free(rates);
|
|
|
|
bandwidths = SoapySDRDevice_getBandwidthRange(dev, direction, channel, &len);
|
|
fprintf(stderr, "Found %zu bandwidth range(s): ", len);
|
|
for (i = 0; i < len; ++i) {
|
|
fprintf(stderr, "%.0f - %.0f (step %.0f) ", bandwidths[i].minimum, bandwidths[i].maximum, bandwidths[i].step);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
SoapySDR_free(bandwidths);
|
|
|
|
double bandwidth = SoapySDRDevice_getBandwidth(dev, direction, channel);
|
|
fprintf(stderr, "Found current bandwidth %.0f\n", bandwidth);
|
|
|
|
stream_formats = SoapySDRDevice_getStreamFormats(dev, direction, channel, &len);
|
|
fprintf(stderr, "Found %zu stream format(s): ", len);
|
|
for (i = 0; i < len; ++i) {
|
|
fprintf(stderr, "%s ", stream_formats[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
SoapySDRStrings_clear(&stream_formats, len);
|
|
|
|
native_stream_format = SoapySDRDevice_getNativeStreamFormat(dev, direction, channel, &fullScale);
|
|
fprintf(stderr, "Found native stream format: %s (full scale: %.1f)\n", native_stream_format, fullScale);
|
|
SoapySDR_free(native_stream_format);
|
|
}
|
|
|
|
static int sdr_open_soapy(sdr_dev_t **out_dev, char const *dev_query, int verbose)
|
|
{
|
|
if (verbose)
|
|
SoapySDR_setLogLevel(SOAPY_SDR_DEBUG);
|
|
|
|
sdr_dev_t *dev = calloc(1, sizeof(sdr_dev_t));
|
|
if (!dev) {
|
|
WARN_CALLOC("sdr_open_soapy()");
|
|
return -1; // NOTE: returns error on alloc failure.
|
|
}
|
|
#ifdef THREADS
|
|
pthread_mutex_init(&dev->lock, NULL);
|
|
#endif
|
|
|
|
dev->soapy_dev = SoapySDRDevice_makeStrArgs(dev_query);
|
|
if (!dev->soapy_dev) {
|
|
if (verbose)
|
|
print_logf(LOG_ERROR, __func__, "Failed to open sdr device matching '%s'.", dev_query);
|
|
free(dev);
|
|
return -1;
|
|
}
|
|
|
|
if (verbose)
|
|
soapysdr_show_device_info(dev->soapy_dev);
|
|
|
|
// select a stream format, in preference order: native CU8, CS8, CS16, forced CS16
|
|
// stream_formats = SoapySDRDevice_getStreamFormats(dev->soapy_dev, SOAPY_SDR_RX, 0, &len);
|
|
char *native_format = SoapySDRDevice_getNativeStreamFormat(dev->soapy_dev, SOAPY_SDR_RX, 0, &dev->fullScale);
|
|
char const *selected_format;
|
|
if (!strcmp(SOAPY_SDR_CU8, native_format)) {
|
|
// actually not supported by SoapySDR
|
|
selected_format = SOAPY_SDR_CU8;
|
|
dev->sample_size = sizeof(uint8_t); // CU8
|
|
dev->sample_signed = 0;
|
|
}
|
|
// else if (!strcmp(SOAPY_SDR_CS8, native_format)) {
|
|
// // TODO: CS8 needs conversion to CU8
|
|
// // e.g. RTL-SDR (8 bit), scale is 128.0
|
|
// selected_format = SOAPY_SDR_CS8;
|
|
// dev->sample_size = sizeof(int8_t) * 2; // CS8
|
|
// dev->sample_signed = 1;
|
|
// }
|
|
else if (!strcmp(SOAPY_SDR_CS16, native_format)) {
|
|
// e.g. LimeSDR-mini (12 bit), native scale is 2048.0
|
|
// e.g. SDRplay RSP1A (14 bit), native scale is 32767.0
|
|
selected_format = SOAPY_SDR_CS16;
|
|
dev->sample_size = sizeof(int16_t) * 2; // CS16
|
|
dev->sample_signed = 1;
|
|
}
|
|
else {
|
|
// force CS16
|
|
selected_format = SOAPY_SDR_CS16;
|
|
dev->sample_size = sizeof(int16_t) * 2; // CS16
|
|
dev->sample_signed = 1;
|
|
dev->fullScale = 32768.0; // assume max for SOAPY_SDR_CS16
|
|
}
|
|
SoapySDR_free(native_format);
|
|
|
|
SoapySDRKwargs args = SoapySDRDevice_getHardwareInfo(dev->soapy_dev);
|
|
size_t info_len = 2;
|
|
for (size_t i = 0; i < args.size; ++i) {
|
|
info_len += strlen(args.keys[i]) + strlen(args.vals[i]) + 6;
|
|
}
|
|
char *p = dev->dev_info = malloc(info_len);
|
|
if (!dev->dev_info)
|
|
FATAL_MALLOC("sdr_open_soapy");
|
|
for (size_t i = 0; i < args.size; ++i) {
|
|
p += sprintf(p, "%s\"%s\":\"%s\"", i ? "," : "{", args.keys[i], args.vals[i]);
|
|
}
|
|
sprintf(p, "}");
|
|
SoapySDRKwargs_clear(&args);
|
|
|
|
SoapySDRKwargs stream_args = {0};
|
|
int r;
|
|
#if SOAPY_SDR_API_VERSION >= 0x00080000
|
|
// API version 0.8
|
|
#undef SoapySDRDevice_setupStream
|
|
dev->soapy_stream = SoapySDRDevice_setupStream(dev->soapy_dev, SOAPY_SDR_RX, selected_format, NULL, 0, &stream_args);
|
|
r = dev->soapy_stream == NULL;
|
|
#else
|
|
// API version 0.7
|
|
r = SoapySDRDevice_setupStream(dev->soapy_dev, &dev->soapy_stream, SOAPY_SDR_RX, selected_format, NULL, 0, &stream_args);
|
|
#endif
|
|
if (r != 0) {
|
|
if (verbose)
|
|
print_log(LOG_ERROR, __func__, "Failed to setup sdr device");
|
|
free(dev);
|
|
return -3;
|
|
}
|
|
|
|
*out_dev = dev;
|
|
return 0;
|
|
}
|
|
|
|
// the buffer sizes can't be proven to be correct
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
|
|
#pragma GCC diagnostic ignored "-Wanalyzer-allocation-size"
|
|
|
|
static int soapysdr_read_loop(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)
|
|
{
|
|
size_t buffer_size = (size_t)buf_num * buf_len;
|
|
if (dev->buffer_size != buffer_size) {
|
|
free(dev->buffer);
|
|
dev->buffer = malloc(buffer_size);
|
|
if (!dev->buffer) {
|
|
WARN_MALLOC("soapysdr_read_loop()");
|
|
return -1; // NOTE: returns error on alloc failure.
|
|
}
|
|
dev->buffer_size = buffer_size;
|
|
dev->buffer_pos = 0;
|
|
}
|
|
|
|
size_t buf_elems = buf_len / dev->sample_size;
|
|
|
|
dev->running = 1;
|
|
do {
|
|
if (dev->buffer_pos + buf_len > buffer_size)
|
|
dev->buffer_pos = 0;
|
|
int16_t *buffer = (void *)&dev->buffer[dev->buffer_pos];
|
|
dev->buffer_pos += buf_len;
|
|
|
|
void *buffs[] = {buffer};
|
|
int flags = 0;
|
|
long long timeNs = 0;
|
|
long timeoutUs = 1000000; // 1 second
|
|
unsigned n_read = 0, i;
|
|
int r;
|
|
|
|
do {
|
|
buffs[0] = &buffer[n_read * 2];
|
|
r = SoapySDRDevice_readStream(dev->soapy_dev, dev->soapy_stream, buffs, buf_elems - n_read, &flags, &timeNs, timeoutUs);
|
|
if (r < 0)
|
|
break;
|
|
n_read += r; // r is number of elements read, elements=complex pairs, so buffer length is twice
|
|
//fprintf(stderr, "readStream ret=%d, flags=%d, timeNs=%lld (%zu - %u)\n", r, flags, timeNs, buf_elems, n_read);
|
|
} while (n_read < buf_elems);
|
|
//fprintf(stderr, "readStream ret=%u (%u), flags=%d, timeNs=%lld\n", n_read, buf_len, flags, timeNs);
|
|
if (r < 0) {
|
|
if (r == SOAPY_SDR_OVERFLOW) {
|
|
fprintf(stderr, "O");
|
|
fflush(stderr);
|
|
continue;
|
|
}
|
|
print_logf(LOG_WARNING, __func__, "sync read failed. %d", r);
|
|
}
|
|
|
|
// convert to CS16 or CU8 if needed
|
|
// if converting CS8 to CU8 -- vectorized with -O3
|
|
//for (i = 0; i < n_read * 2; ++i)
|
|
// cu8buf[i] = (int8_t)cu8buf[i] + 128;
|
|
|
|
// TODO: SoapyRemote doesn't scale properly when reading (local) CS16 from (remote) CS8
|
|
// rescale cs16 buffer
|
|
if (dev->fullScale >= 2047.0 && dev->fullScale <= 2048.0) {
|
|
for (i = 0; i < n_read * 2; ++i)
|
|
buffer[i] *= 16; // prevent left shift of negative value
|
|
}
|
|
else if (dev->fullScale < 32767.0) {
|
|
int upscale = 32768 / dev->fullScale;
|
|
for (i = 0; i < n_read * 2; ++i)
|
|
buffer[i] *= upscale;
|
|
}
|
|
|
|
#ifdef THREADS
|
|
pthread_mutex_lock(&dev->lock);
|
|
#endif
|
|
uint32_t sample_rate = dev->sample_rate;
|
|
uint32_t center_frequency = dev->center_frequency;
|
|
#ifdef THREADS
|
|
pthread_mutex_unlock(&dev->lock);
|
|
#endif
|
|
sdr_event_t ev = {
|
|
.ev = SDR_EV_DATA,
|
|
.sample_rate = sample_rate,
|
|
.center_frequency = center_frequency,
|
|
.buf = buffer,
|
|
.len = n_read * dev->sample_size,
|
|
};
|
|
#ifdef THREADS
|
|
pthread_mutex_lock(&dev->lock);
|
|
int exit_acquire = dev->exit_acquire;
|
|
pthread_mutex_unlock(&dev->lock);
|
|
if (exit_acquire) {
|
|
break; // do not deliver any more events
|
|
}
|
|
#endif
|
|
if (n_read > 0) // prevent a crash in callback
|
|
cb(&ev, ctx);
|
|
|
|
} while (dev->running);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
#endif
|
|
|
|
/* Public API */
|
|
|
|
int sdr_open(sdr_dev_t **out_dev, char const *dev_query, int verbose)
|
|
{
|
|
if (dev_query && !strncmp(dev_query, "rtl_tcp", 7))
|
|
return rtltcp_open(out_dev, dev_query, verbose);
|
|
|
|
#if !defined(RTLSDR) && !defined(SOAPYSDR)
|
|
if (verbose)
|
|
print_log(LOG_ERROR, __func__, "No input drivers (RTL-SDR or SoapySDR) compiled in.");
|
|
return -1;
|
|
#endif
|
|
|
|
/* Open RTLSDR by default or if index or serial given, if available */
|
|
if (!dev_query || *dev_query == ':' || (*dev_query >= '0' && *dev_query <= '9')) {
|
|
#ifdef RTLSDR
|
|
return sdr_open_rtl(out_dev, dev_query, verbose);
|
|
#else
|
|
print_log(LOG_ERROR, __func__, "No input driver for RTL-SDR compiled in.");
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
#ifdef SOAPYSDR
|
|
UNUSED(soapysdr_set_bandwidth);
|
|
UNUSED(soapysdr_direct_sampling);
|
|
UNUSED(soapysdr_offset_tuning);
|
|
|
|
/* Open SoapySDR otherwise, if available */
|
|
return sdr_open_soapy(out_dev, dev_query, verbose);
|
|
#endif
|
|
print_log(LOG_ERROR, __func__, "No input driver for SoapySDR compiled in.");
|
|
|
|
return -1;
|
|
}
|
|
|
|
int sdr_close(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
int ret = sdr_stop(dev);
|
|
|
|
if (dev->rtl_tcp)
|
|
ret = rtltcp_close(dev->rtl_tcp);
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev)
|
|
ret = SoapySDRDevice_unmake(dev->soapy_dev);
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev)
|
|
ret = rtlsdr_close(dev->rtlsdr_dev);
|
|
#endif
|
|
|
|
#ifdef THREADS
|
|
pthread_mutex_destroy(&dev->lock);
|
|
#endif
|
|
|
|
free(dev->dev_info);
|
|
free(dev->buffer);
|
|
free(dev);
|
|
return ret;
|
|
}
|
|
|
|
char const *sdr_get_dev_info(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return NULL;
|
|
|
|
return dev->dev_info;
|
|
}
|
|
|
|
int sdr_get_sample_size(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return 0;
|
|
|
|
return dev->sample_size;
|
|
}
|
|
|
|
int sdr_get_sample_signed(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return 0;
|
|
|
|
return dev->sample_signed;
|
|
}
|
|
|
|
int sdr_set_center_freq(sdr_dev_t *dev, uint32_t freq, int verbose)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
#ifdef THREADS
|
|
if (pthread_equal(dev->thread, pthread_self())) {
|
|
fprintf(stderr, "%s: must not be called from acquire callback!\n", __func__);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
int r = -1;
|
|
|
|
if (dev->rtl_tcp) {
|
|
dev->rtl_tcp_freq = freq;
|
|
r = rtltcp_command(dev, RTLTCP_SET_FREQ, freq);
|
|
}
|
|
|
|
#ifdef SOAPYSDR
|
|
SoapySDRKwargs args = {0};
|
|
if (dev->soapy_dev) {
|
|
r = SoapySDRDevice_setFrequency(dev->soapy_dev, SOAPY_SDR_RX, 0, (double)freq, &args);
|
|
}
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev) {
|
|
r = rtlsdr_set_center_freq(dev->rtlsdr_dev, freq);
|
|
print_logf(LOG_DEBUG, "SDR", "rtlsdr_set_center_freq %u = %d", freq, r);
|
|
}
|
|
#endif
|
|
|
|
if (verbose) {
|
|
if (r < 0)
|
|
print_log(LOG_WARNING, __func__, "Failed to set center freq.");
|
|
else
|
|
print_logf(LOG_NOTICE, "SDR", "Tuned to %s.", nice_freq(sdr_get_center_freq(dev)));
|
|
}
|
|
|
|
#ifdef THREADS
|
|
pthread_mutex_lock(&dev->lock);
|
|
#endif
|
|
dev->center_frequency = freq;
|
|
#ifdef THREADS
|
|
pthread_mutex_unlock(&dev->lock);
|
|
#endif
|
|
|
|
return r;
|
|
}
|
|
|
|
uint32_t sdr_get_center_freq(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return 0;
|
|
|
|
if (dev->rtl_tcp)
|
|
return dev->rtl_tcp_freq;
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev)
|
|
return (uint32_t)SoapySDRDevice_getFrequency(dev->soapy_dev, SOAPY_SDR_RX, 0);
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev)
|
|
return rtlsdr_get_center_freq(dev->rtlsdr_dev);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sdr_set_freq_correction(sdr_dev_t *dev, int ppm, int verbose)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
#ifdef THREADS
|
|
if (pthread_equal(dev->thread, pthread_self())) {
|
|
fprintf(stderr, "%s: must not be called from acquire callback!\n", __func__);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
int r = -1;
|
|
|
|
if (dev->rtl_tcp)
|
|
r = rtltcp_command(dev, RTLTCP_SET_FREQ_CORRECTION, ppm);
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev)
|
|
r = SoapySDRDevice_setFrequencyComponent(dev->soapy_dev, SOAPY_SDR_RX, 0, "CORR", (double)ppm, NULL);
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev) {
|
|
r = rtlsdr_set_freq_correction(dev->rtlsdr_dev, ppm);
|
|
if (r == -2)
|
|
r = 0; // -2 is not an error code
|
|
}
|
|
#endif
|
|
|
|
if (verbose) {
|
|
if (r < 0)
|
|
print_log(LOG_WARNING, __func__, "Failed to set frequency correction.");
|
|
else
|
|
print_logf(LOG_NOTICE, "SDR", "Frequency correction set to %d ppm.", ppm);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int sdr_set_auto_gain(sdr_dev_t *dev, int verbose)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
#ifdef THREADS
|
|
if (pthread_equal(dev->thread, pthread_self())) {
|
|
fprintf(stderr, "%s: must not be called from acquire callback!\n", __func__);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
int r = -1;
|
|
|
|
if (dev->rtl_tcp)
|
|
r = rtltcp_command(dev, RTLTCP_SET_GAIN_MODE, 0);
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev)
|
|
r = soapysdr_auto_gain(dev->soapy_dev, verbose);
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev)
|
|
r = rtlsdr_set_tuner_gain_mode(dev->rtlsdr_dev, 0);
|
|
#endif
|
|
|
|
if (verbose) {
|
|
if (r < 0)
|
|
print_log(LOG_WARNING, __func__, "Failed to enable automatic gain.");
|
|
else
|
|
print_log(LOG_NOTICE, "SDR", "Tuner gain set to Auto.");
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int sdr_set_tuner_gain(sdr_dev_t *dev, char const *gain_str, int verbose)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
#ifdef THREADS
|
|
if (pthread_equal(dev->thread, pthread_self())) {
|
|
fprintf(stderr, "%s: must not be called from acquire callback!\n", __func__);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
int r = -1;
|
|
|
|
if (!gain_str || !*gain_str) {
|
|
/* Enable automatic gain */
|
|
return sdr_set_auto_gain(dev, verbose);
|
|
}
|
|
|
|
#ifdef SOAPYSDR
|
|
/* Enable manual gain */
|
|
if (dev->soapy_dev)
|
|
return soapysdr_gain_str_set(dev->soapy_dev, gain_str, verbose);
|
|
#endif
|
|
|
|
int gain = (int)(atof(gain_str) * 10); /* tenths of a dB */
|
|
if (gain == 0) {
|
|
/* Enable automatic gain */
|
|
return sdr_set_auto_gain(dev, verbose);
|
|
}
|
|
|
|
if (dev->rtl_tcp) {
|
|
return rtltcp_command(dev, RTLTCP_SET_GAIN_MODE, 1)
|
|
|| rtltcp_command(dev, RTLTCP_SET_GAIN, gain);
|
|
}
|
|
|
|
#ifdef RTLSDR
|
|
/* Enable manual gain */
|
|
r = rtlsdr_set_tuner_gain_mode(dev->rtlsdr_dev, 1);
|
|
if (verbose)
|
|
if (r < 0)
|
|
print_log(LOG_WARNING, __func__, "Failed to enable manual gain.");
|
|
|
|
/* Set the tuner gain */
|
|
gain = rtlsdr_find_tuner_gain(dev, gain, verbose);
|
|
|
|
/* Fix for FitiPower FC0012: set gain to minimum before desired value */
|
|
if (rtlsdr_get_tuner_type(dev->rtlsdr_dev) == RTLSDR_TUNER_FC0012) {
|
|
int minGain = -99;
|
|
minGain = rtlsdr_find_tuner_gain(dev, minGain, verbose);
|
|
|
|
r = rtlsdr_set_tuner_gain(dev->rtlsdr_dev, minGain);
|
|
if (verbose) {
|
|
if (r < 0)
|
|
print_log(LOG_WARNING, __func__, "Failed to set initial gain.");
|
|
else
|
|
print_logf(LOG_NOTICE, "SDR", "Set initial gain for FC0012 to %f dB.", minGain / 10.0);
|
|
}
|
|
}
|
|
|
|
r = rtlsdr_set_tuner_gain(dev->rtlsdr_dev, gain);
|
|
if (verbose) {
|
|
if (r < 0)
|
|
print_log(LOG_WARNING, __func__, "Failed to set tuner gain.");
|
|
else
|
|
print_logf(LOG_NOTICE, "SDR", "Tuner gain set to %f dB.", gain / 10.0);
|
|
}
|
|
#endif
|
|
|
|
return r;
|
|
}
|
|
|
|
int sdr_set_antenna(sdr_dev_t *dev, char const *antenna_str, int verbose)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
POSSIBLY_UNUSED(verbose);
|
|
int r = -1;
|
|
|
|
if (!antenna_str)
|
|
return 0;
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev) {
|
|
r = SoapySDRDevice_setAntenna(dev->soapy_dev, SOAPY_SDR_RX, 0, antenna_str);
|
|
|
|
if (verbose) {
|
|
if (r < 0)
|
|
print_log(LOG_WARNING, __func__, "Failed to set antenna.");
|
|
|
|
// report the antenna that is actually used
|
|
char *antenna = SoapySDRDevice_getAntenna(dev->soapy_dev, SOAPY_SDR_RX, 0);
|
|
print_logf(LOG_NOTICE, "SDR", "Antenna set to '%s'.", antenna);
|
|
free(antenna);
|
|
}
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
// currently only SoapySDR supports devices with multiple antennas
|
|
print_log(LOG_WARNING, __func__, "Antenna selection only available for SoapySDR devices");
|
|
|
|
return r;
|
|
}
|
|
|
|
int sdr_set_sample_rate(sdr_dev_t *dev, uint32_t rate, int verbose)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
#ifdef THREADS
|
|
if (pthread_equal(dev->thread, pthread_self())) {
|
|
fprintf(stderr, "%s: must not be called from acquire callback!\n", __func__);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
int r = -1;
|
|
|
|
if (dev->rtl_tcp) {
|
|
dev->rtl_tcp_rate = rate;
|
|
r = rtltcp_command(dev, RTLTCP_SET_SAMPLE_RATE, rate);
|
|
}
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev)
|
|
r = SoapySDRDevice_setSampleRate(dev->soapy_dev, SOAPY_SDR_RX, 0, (double)rate);
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev)
|
|
r = rtlsdr_set_sample_rate(dev->rtlsdr_dev, rate);
|
|
#endif
|
|
|
|
if (verbose) {
|
|
if (r < 0)
|
|
print_log(LOG_WARNING, __func__, "Failed to set sample rate.");
|
|
else
|
|
print_logf(LOG_NOTICE, "SDR", "Sample rate set to %u S/s.", sdr_get_sample_rate(dev)); // Unfortunately, doesn't return real rate
|
|
}
|
|
|
|
#ifdef THREADS
|
|
pthread_mutex_lock(&dev->lock);
|
|
#endif
|
|
dev->sample_rate = rate;
|
|
#ifdef THREADS
|
|
pthread_mutex_unlock(&dev->lock);
|
|
#endif
|
|
|
|
return r;
|
|
}
|
|
|
|
uint32_t sdr_get_sample_rate(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return 0;
|
|
|
|
if (dev->rtl_tcp)
|
|
return dev->rtl_tcp_rate;
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev)
|
|
return (uint32_t)SoapySDRDevice_getSampleRate(dev->soapy_dev, SOAPY_SDR_RX, 0);
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev)
|
|
return rtlsdr_get_sample_rate(dev->rtlsdr_dev);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sdr_apply_settings(sdr_dev_t *dev, char const *sdr_settings, int verbose)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
POSSIBLY_UNUSED(verbose);
|
|
int r = 0;
|
|
|
|
if (!sdr_settings || !*sdr_settings)
|
|
return 0;
|
|
|
|
if (dev->rtl_tcp) {
|
|
while (sdr_settings && *sdr_settings) {
|
|
char const *val = NULL;
|
|
// This mirrors the settings of SoapyRTLSDR
|
|
if (kwargs_match(sdr_settings, "direct_samp", &val)) {
|
|
int direct_sampling = atoiv(val, 1);
|
|
r = rtltcp_command(dev, RTLTCP_SET_DIRECT_SAMPLING, direct_sampling);
|
|
}
|
|
else if (kwargs_match(sdr_settings, "offset_tune", &val)) {
|
|
int offset_tuning = atobv(val, 1);
|
|
r = rtltcp_command(dev, RTLTCP_SET_OFFSET_TUNING, offset_tuning);
|
|
}
|
|
else if (kwargs_match(sdr_settings, "digital_agc", &val)) {
|
|
int digital_agc = atobv(val, 1);
|
|
r = rtltcp_command(dev, RTLTCP_SET_AGC_MODE, digital_agc);
|
|
}
|
|
else if (kwargs_match(sdr_settings, "biastee", &val)) {
|
|
int biastee = atobv(val, 1);
|
|
r = rtltcp_command(dev, RTLTCP_SET_BIAS_TEE, biastee);
|
|
}
|
|
else {
|
|
print_logf(LOG_ERROR, __func__, "Unknown rtl_tcp setting: %s", sdr_settings);
|
|
return -1;
|
|
}
|
|
sdr_settings = kwargs_skip(sdr_settings);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev) {
|
|
SoapySDRKwargs settings = SoapySDRKwargs_fromString(sdr_settings);
|
|
for (size_t i = 0; i < settings.size; ++i) {
|
|
const char *key = settings.keys[i];
|
|
const char *value = settings.vals[i];
|
|
if (!key) {
|
|
continue;
|
|
}
|
|
if (verbose) {
|
|
print_logf(LOG_NOTICE, "SDR", "Setting %s to %s", key, value);
|
|
}
|
|
if (!strcmp(key, "antenna")) {
|
|
if (SoapySDRDevice_setAntenna(dev->soapy_dev, SOAPY_SDR_RX, 0, value) != 0) {
|
|
r = -1;
|
|
print_logf(LOG_WARNING, __func__, "Antenna setting failed: %s", SoapySDRDevice_lastError());
|
|
}
|
|
}
|
|
else if (!strcmp(key, "bandwidth")) {
|
|
uint32_t f_value = atouint32_metric(value, "-t bandwidth= ");
|
|
if (SoapySDRDevice_setBandwidth(dev->soapy_dev, SOAPY_SDR_RX, 0, (double)f_value) != 0) {
|
|
r = -1;
|
|
print_logf(LOG_WARNING, __func__, "Bandwidth setting failed: %s", SoapySDRDevice_lastError());
|
|
}
|
|
}
|
|
else {
|
|
if (SoapySDRDevice_writeSetting(dev->soapy_dev, key, value) != 0) {
|
|
r = -1;
|
|
print_logf(LOG_WARNING, __func__, "sdr setting failed: %s", SoapySDRDevice_lastError());
|
|
}
|
|
}
|
|
}
|
|
SoapySDRKwargs_clear(&settings);
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev) {
|
|
while (sdr_settings && *sdr_settings) {
|
|
char const *val = NULL;
|
|
// This mirrors the settings of SoapyRTLSDR
|
|
if (kwargs_match(sdr_settings, "direct_samp", &val)) {
|
|
int direct_sampling = atoiv(val, 1);
|
|
r = rtlsdr_set_direct_sampling(dev->rtlsdr_dev, direct_sampling);
|
|
}
|
|
else if (kwargs_match(sdr_settings, "offset_tune", &val)) {
|
|
int offset_tuning = atobv(val, 1);
|
|
r = rtlsdr_set_offset_tuning(dev->rtlsdr_dev, offset_tuning);
|
|
}
|
|
else if (kwargs_match(sdr_settings, "digital_agc", &val)) {
|
|
int digital_agc = atobv(val, 1);
|
|
r = rtlsdr_set_agc_mode(dev->rtlsdr_dev, digital_agc);
|
|
}
|
|
else if (kwargs_match(sdr_settings, "biastee", &val)) {
|
|
#if defined(__linux__) && (defined(__GNUC__) || defined(__clang__))
|
|
// check weak link for Linux with older rtlsdr
|
|
if (!rtlsdr_set_bias_tee) {
|
|
print_log(LOG_ERROR, __func__, "This librtlsdr version does not support biastee setting");
|
|
return -1;
|
|
}
|
|
#endif
|
|
int biastee = atobv(val, 1);
|
|
r = rtlsdr_set_bias_tee(dev->rtlsdr_dev, biastee);
|
|
}
|
|
else {
|
|
print_logf(LOG_ERROR, __func__, "Unknown RTLSDR setting: %s", sdr_settings);
|
|
return -1;
|
|
}
|
|
sdr_settings = kwargs_skip(sdr_settings);
|
|
}
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
print_log(LOG_WARNING, __func__, "sdr settings not available."); // no open device
|
|
|
|
return -1;
|
|
}
|
|
|
|
int sdr_activate(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev) {
|
|
if (SoapySDRDevice_activateStream(dev->soapy_dev, dev->soapy_stream, 0, 0, 0) != 0) {
|
|
print_log(LOG_ERROR, __func__, "Failed to activate stream");
|
|
exit(1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sdr_deactivate(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev) {
|
|
SoapySDRDevice_deactivateStream(dev->soapy_dev, dev->soapy_stream, 0, 0);
|
|
SoapySDRDevice_closeStream(dev->soapy_dev, dev->soapy_stream);
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sdr_reset(sdr_dev_t *dev, int verbose)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
int r = 0;
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev)
|
|
r = rtlsdr_reset_buffer(dev->rtlsdr_dev);
|
|
#endif
|
|
|
|
if (verbose) {
|
|
if (r < 0)
|
|
print_log(LOG_WARNING, __func__, "Failed to reset buffers.");
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int sdr_start_sync(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
if (buf_num == 0)
|
|
buf_num = SDR_DEFAULT_BUF_NUMBER;
|
|
if (buf_len == 0)
|
|
buf_len = SDR_DEFAULT_BUF_LENGTH;
|
|
|
|
if (dev->rtl_tcp)
|
|
return rtltcp_read_loop(dev, cb, ctx, buf_num, buf_len);
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev)
|
|
return soapysdr_read_loop(dev, cb, ctx, buf_num, buf_len);
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev)
|
|
return rtlsdr_read_loop(dev, cb, ctx, buf_num, buf_len);
|
|
#endif
|
|
|
|
return -1;
|
|
}
|
|
|
|
int sdr_stop_sync(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
if (dev->rtl_tcp) {
|
|
dev->running = 0;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef SOAPYSDR
|
|
if (dev->soapy_dev) {
|
|
dev->running = 0;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef RTLSDR
|
|
if (dev->rtlsdr_dev) {
|
|
dev->running = 0;
|
|
return rtlsdr_cancel_async(dev->rtlsdr_dev);
|
|
}
|
|
#endif
|
|
|
|
return -1;
|
|
}
|
|
|
|
#ifdef SOAPYSDR
|
|
static void soapysdr_log_handler(const SoapySDRLogLevel level, const char *message)
|
|
{
|
|
// Our log levels are compatible with SoapySDR.
|
|
print_log((log_level_t)level, "SoapySDR", message);
|
|
}
|
|
#endif
|
|
|
|
void sdr_redirect_logging(void)
|
|
{
|
|
#ifdef SOAPYSDR
|
|
SoapySDR_registerLogHandler(soapysdr_log_handler);
|
|
#endif
|
|
}
|
|
|
|
/* threading */
|
|
|
|
#ifdef THREADS
|
|
static THREAD_RETURN THREAD_CALL acquire_thread(void *arg)
|
|
{
|
|
sdr_dev_t *dev = arg;
|
|
print_log(LOG_DEBUG, __func__, "acquire_thread enter...");
|
|
|
|
int r = sdr_start_sync(dev, dev->async_cb, dev->async_ctx, dev->buf_num, dev->buf_len);
|
|
// if (cfg->verbosity > 1)
|
|
print_log(LOG_DEBUG, __func__, "acquire_thread async stop...");
|
|
|
|
if (r < 0) {
|
|
print_logf(LOG_ERROR, "SDR", "async read failed (%d).", r);
|
|
}
|
|
|
|
// sdr_event_t ev = {
|
|
// .ev = SDR_EV_QUIT,
|
|
// };
|
|
// dev->async_cb(&ev, dev->async_ctx);
|
|
|
|
print_log(LOG_DEBUG, __func__, "acquire_thread done...");
|
|
return (THREAD_RETURN)(intptr_t)r;
|
|
}
|
|
|
|
int sdr_start(sdr_dev_t *dev, sdr_event_cb_t async_cb, void *async_ctx, uint32_t buf_num, uint32_t buf_len)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
dev->async_cb = async_cb;
|
|
dev->async_ctx = async_ctx;
|
|
dev->buf_num = buf_num;
|
|
dev->buf_len = buf_len;
|
|
|
|
#ifndef _WIN32
|
|
// Block all signals from the worker thread
|
|
sigset_t sigset;
|
|
sigset_t oldset;
|
|
sigfillset(&sigset);
|
|
pthread_sigmask(SIG_SETMASK, &sigset, &oldset);
|
|
#endif
|
|
int r = pthread_create(&dev->thread, NULL, acquire_thread, dev);
|
|
#ifndef _WIN32
|
|
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
|
|
#endif
|
|
if (r) {
|
|
fprintf(stderr, "%s: error in pthread_create, rc: %d\n", __func__, r);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int sdr_stop(sdr_dev_t *dev)
|
|
{
|
|
if (!dev)
|
|
return -1;
|
|
|
|
if (pthread_equal(dev->thread, pthread_self())) {
|
|
fprintf(stderr, "%s: must not be called from acquire callback!\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
print_log(LOG_DEBUG, __func__, "EXITING...");
|
|
pthread_mutex_lock(&dev->lock);
|
|
if (dev->exit_acquire) {
|
|
pthread_mutex_unlock(&dev->lock);
|
|
print_log(LOG_DEBUG, __func__, "Already exiting.");
|
|
return 0;
|
|
}
|
|
dev->exit_acquire = 1; // for rtl_tcp and SoapySDR
|
|
sdr_stop_sync(dev); // for rtlsdr
|
|
pthread_mutex_unlock(&dev->lock);
|
|
|
|
print_log(LOG_DEBUG, __func__, "JOINING...");
|
|
int r = pthread_join(dev->thread, NULL);
|
|
if (r) {
|
|
fprintf(stderr, "%s: error in pthread_join, rc: %d\n", __func__, r);
|
|
}
|
|
|
|
print_log(LOG_DEBUG, __func__, "EXITED.");
|
|
return r;
|
|
}
|
|
#else
|
|
int sdr_start(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)
|
|
{
|
|
UNUSED(dev);
|
|
return -1;
|
|
}
|
|
int sdr_stop(sdr_dev_t *dev)
|
|
{
|
|
UNUSED(dev);
|
|
return -1;
|
|
}
|
|
#endif
|