diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b79036..525b6b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ FILE(GLOB HEADERS include/*.h include/*.hpp) if(NOT RTLSDR_FOUND) list(REMOVE_ITEM SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/rtl_sdr_fm.cpp) + list(REMOVE_ITEM SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/rtl_sdr_tcp.c) endif(NOT RTLSDR_FOUND) configure_file("src/git_ver.c.in" "${CMAKE_CURRENT_BINARY_DIR}/git_ver.c" @ONLY) diff --git a/include/dsd.h b/include/dsd.h index 70d0638..eb89975 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -372,6 +372,9 @@ typedef struct int rtl_bandwidth; int rtl_started; long int rtl_rms; + int rtltcp_enabled; + char rtltcp_hostname[1024]; + int rtltcp_portno; int monitor_input_audio; int analog_only; int pulse_raw_rate_in; @@ -1707,6 +1710,21 @@ void rtlsdr_sighandler(); void rtl_dev_tune(dsd_opts * opts, long int frequency); long int rtl_return_rms(); void rtl_clean_queue(); + +//rtl_tcp client (rtl_sdr_tcp.c) +int rtltcp_open(const char *hostname, int portno); +int rtlsdr_or_tcp_close(rtlsdr_dev_t *dev); +int rtlsdr_or_tcp_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len); +int rtlsdr_or_tcp_cancel_async(rtlsdr_dev_t *dev); +int rtlsdr_or_tcp_reset_buffer(rtlsdr_dev_t *dev); +int rtlsdr_or_tcp_get_tuner_gains(rtlsdr_dev_t *dev, int *gains); +int rtlsdr_or_tcp_set_center_freq(rtlsdr_dev_t *dev, uint32_t freq); +int rtlsdr_or_tcp_set_sample_rate(rtlsdr_dev_t *dev, uint32_t rate); +int rtlsdr_or_tcp_set_direct_sampling(rtlsdr_dev_t *dev, int on); +int rtlsdr_or_tcp_set_offset_tuning(rtlsdr_dev_t *dev, int on); +int rtlsdr_or_tcp_set_tuner_gain_mode(rtlsdr_dev_t *dev, int manual); +int rtlsdr_or_tcp_set_tuner_gain(rtlsdr_dev_t *dev, int gain); +int rtlsdr_or_tcp_set_freq_correction(rtlsdr_dev_t *dev, int ppm); #endif diff --git a/src/dsd_main.c b/src/dsd_main.c index 6b050b1..f5d2b1a 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -695,6 +695,9 @@ opts->rtlsdr_center_freq = 850000000; //set to an initial value (if user is using a channel map, then they won't need to specify anything other than -i rtl if desired) opts->rtl_started = 0; opts->rtl_rms = 0; //root means square power level on rtl input signal + opts->rtltcp_enabled = 0; //1 when the rtl input is an rtl_tcp server instead of a local dongle + sprintf (opts->rtltcp_hostname, "%s", "localhost"); + opts->rtltcp_portno = 1234; //rtl_tcp default port //end RTL user options opts->pulse_raw_rate_in = 48000; opts->pulse_raw_rate_out = 48000;// @@ -1406,6 +1409,7 @@ printf (" pulse:6 or pulse:virtual_sink2.monitor for pulse audio signal input on virtual_sink2 (see -O) \n"); printf (" rtl for rtl dongle (Default Values -- see below)\n"); printf (" rtl:dev:freq:gain:ppm:bw:sq:vol for rtl dongle (see below)\n"); + printf (" rtltcp:host:port:freq:gain:ppm:bw:sq:vol for rtl dongle on an rtl_tcp server (see below)\n"); printf (" tcp for tcp client SDR++/GNURadio Companion/Other (Port 7355)\n"); printf (" tcp:192.168.7.5:7355 for custom address and port \n"); printf (" filename.bin for OP25/FME capture bin files\n"); @@ -1477,6 +1481,11 @@ printf (" vol RTL-SDR Sample 'Volume' Multiplier (default = 2)(1,2,3)\n"); printf (" Example: dsd-fme -fs -i rtl -C cap_plus_channel.csv -T\n"); printf (" Example: dsd-fme -fp -i rtl:0:851.375M:22:-2:24:0:2\n"); + printf (" Usage: rtltcp:host:port:freq:gain:ppm:bw:sq:vol\n"); + printf (" host rtl_tcp Server Hostname or IP Address (default = localhost)\n"); + printf (" port rtl_tcp Server Port (default = 1234)\n"); + printf (" freq, gain, ppm, bw, sq, vol as above\n"); + printf (" Example: dsd-fme -fs -i rtltcp:192.168.1.10:1234:450M:26:-2:8 -C connect_plus_chan.csv -T\n"); printf ("\n"); printf ("Decoder options:\n"); printf (" -fa Auto Detection\n"); @@ -3281,9 +3290,24 @@ if (curr != NULL) ; //continue else goto RTLEND; //end early with preset values - curr = strtok(NULL, ":"); //rtl device number "-D" - if (curr != NULL) opts.rtl_dev_index = atoi (curr); - else goto RTLEND; + if (strcmp (curr, "rtltcp") == 0) //rtl_tcp server instead of a local dongle + { + opts.rtltcp_enabled = 1; + + curr = strtok(NULL, ":"); //rtl_tcp hostname + if (curr != NULL) snprintf (opts.rtltcp_hostname, sizeof(opts.rtltcp_hostname), "%s", curr); + else goto RTLEND; + + curr = strtok(NULL, ":"); //rtl_tcp port + if (curr != NULL) opts.rtltcp_portno = atoi (curr); + else goto RTLEND; + } + else + { + curr = strtok(NULL, ":"); //rtl device number "-D" + if (curr != NULL) opts.rtl_dev_index = atoi (curr); + else goto RTLEND; + } curr = strtok(NULL, ":"); //rtl freq "-c" if (curr != NULL) opts.rtlsdr_center_freq = (uint32_t)atofs(curr); @@ -3326,6 +3350,8 @@ RTLEND: + if (opts.rtltcp_enabled == 1) goto RTLTCPEND; //no local dongle to look for + device_count = rtlsdr_get_device_count(); if (!device_count) { @@ -3353,10 +3379,15 @@ } + RTLTCPEND: + if (opts.rtl_volume_multiplier > 3 || opts.rtl_volume_multiplier < 0) opts.rtl_volume_multiplier = 1; //I wonder if you could flip polarity by using -1 - fprintf (stderr, "Dev %d ", opts.rtl_dev_index); + if (opts.rtltcp_enabled == 1) + fprintf (stderr, "rtl_tcp %s:%d ", opts.rtltcp_hostname, opts.rtltcp_portno); + else + fprintf (stderr, "Dev %d ", opts.rtl_dev_index); fprintf (stderr, "Freq %d ", opts.rtlsdr_center_freq); fprintf (stderr, "Gain %d ", opts.rtl_gain_value); fprintf (stderr, "PPM %d ", opts.rtlsdr_ppm_error); diff --git a/src/rtl_sdr_fm.cpp b/src/rtl_sdr_fm.cpp index 06796d8..8fe4981 100644 --- a/src/rtl_sdr_fm.cpp +++ b/src/rtl_sdr_fm.cpp @@ -567,7 +567,7 @@ static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) static void *dongle_thread_fn(void *arg) { struct dongle_state *s = static_cast(arg); - rtlsdr_read_async(s->dev, rtlsdr_callback, s, 0, s->buf_len); + rtlsdr_or_tcp_read_async(s->dev, rtlsdr_callback, s, 0, s->buf_len); return 0; } @@ -604,17 +604,17 @@ int nearest_gain(rtlsdr_dev_t *dev, int target_gain) { int i, r, err1, err2, count, nearest; int* gains; - r = rtlsdr_set_tuner_gain_mode(dev, 1); + r = rtlsdr_or_tcp_set_tuner_gain_mode(dev, 1); if (r < 0) { fprintf (stderr, "WARNING: Failed to enable manual gain.\n"); return r; } - count = rtlsdr_get_tuner_gains(dev, NULL); + count = rtlsdr_or_tcp_get_tuner_gains(dev, NULL); if (count <= 0) { return 0; } gains = static_cast(malloc(sizeof(int) * count)); - count = rtlsdr_get_tuner_gains(dev, gains); + count = rtlsdr_or_tcp_get_tuner_gains(dev, gains); nearest = gains[0]; for (i=0; ifreq_now = (s->freq_now + 1) % s->freq_len; optimal_settings(s->freqs[s->freq_now], demod.rate_in); - rtlsdr_set_center_freq(dongle.dev, dongle.freq); + rtlsdr_or_tcp_set_center_freq(dongle.dev, dongle.freq); dongle.mute = BUFFER_DUMP; } return 0; @@ -1012,7 +1012,7 @@ static void *socket_thread_fn(void *arg) { new_freq = chars_to_int(buffer); dongle.freq = new_freq; optimal_settings(new_freq, demod.rate_in); - rtlsdr_set_center_freq(dongle.dev, dongle.freq); + rtlsdr_or_tcp_set_center_freq(dongle.dev, dongle.freq); fprintf (stderr, "\nTuning to: %d [Hz] \n", new_freq); } @@ -1027,7 +1027,7 @@ static void *socket_thread_fn(void *arg) { void rtlsdr_sighandler() { fprintf (stderr, "Signal caught, exiting!\n"); - rtlsdr_cancel_async(dongle.dev); + rtlsdr_or_tcp_cancel_async(dongle.dev); } void open_rtlsdr_stream(dsd_opts *opts) @@ -1080,14 +1080,23 @@ void open_rtlsdr_stream(dsd_opts *opts) ACTUAL_BUF_LENGTH = lcm_post[demod.post_downsample] * DEFAULT_BUF_LENGTH; - r = rtlsdr_open(&dongle.dev, (uint32_t)dongle.dev_index); - if (r < 0) + if (opts->rtltcp_enabled == 1) { - fprintf (stderr, "Failed to open rtlsdr device %d.\n", dongle.dev_index); - exit(1); - } else { - fprintf (stderr, "Using RTLSDR Device Index: %d. \n", dongle.dev_index); - } + if (rtltcp_open(opts->rtltcp_hostname, opts->rtltcp_portno) < 0) + //rtltcp_open prints its own error messages + exit(1); + } + else + { + r = rtlsdr_open(&dongle.dev, (uint32_t)dongle.dev_index); + if (r < 0) + { + fprintf (stderr, "Failed to open rtlsdr device %d.\n", dongle.dev_index); + exit(1); + } else { + fprintf (stderr, "Using RTLSDR Device Index: %d. \n", dongle.dev_index); + } + } if (demod.deemph) { demod.deemph_a = (int)round(1.0/((1.0-exp(-1.0/(demod.rate_out * 75e-6))))); @@ -1119,7 +1128,7 @@ void open_rtlsdr_stream(dsd_opts *opts) void cleanup_rtlsdr_stream() { fprintf (stderr, "cleaning up...\n"); - rtlsdr_cancel_async(dongle.dev); + rtlsdr_or_tcp_cancel_async(dongle.dev); pthread_join(dongle.thread, NULL); safe_cond_signal(&demod.ready, &demod.ready_m); pthread_join(demod.thread, NULL); @@ -1132,7 +1141,7 @@ void cleanup_rtlsdr_stream() output_cleanup(&output); controller_cleanup(&controller); - rtlsdr_close(dongle.dev); + rtlsdr_or_tcp_close(dongle.dev); } //original for safe keeping @@ -1241,7 +1250,7 @@ void rtl_dev_tune(dsd_opts * opts, long int frequency) for (int i = 0; i < 20; i++) { int r = -1; - r = rtlsdr_set_center_freq(dongle.dev, dongle.freq); + r = rtlsdr_or_tcp_set_center_freq(dongle.dev, dongle.freq); if (r < 0) fprintf (stderr, " (RETRY: Failed to set Center Frequency %u). \n", dongle.freq); else break; diff --git a/src/rtl_sdr_tcp.c b/src/rtl_sdr_tcp.c new file mode 100644 index 0000000..ec68656 --- /dev/null +++ b/src/rtl_sdr_tcp.c @@ -0,0 +1,247 @@ +/*------------------------------------------------------------------------------- + * rtl_sdr_tcp.c + * rtl_tcp client for the RTL-SDR input: samples arrive on a TCP socket, and tuner commands are sent back on it + *-----------------------------------------------------------------------------*/ + +#include "dsd.h" +#include +#include +#include +#include +#include +#include +#include + +//MSG_NOSIGNAL is missing before macOS 11; SO_NOSIGPIPE in rtltcp_open keeps send from raising SIGPIPE there +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +//rtlsdr_read_async's default block size, in bytes +#define RTLTCP_BUF_LENGTH (16 * 32 * 512) + +//rtl_tcp state: socket, tuner type, and a mutex that keeps commands from different threads from interleaving +//NOTE: Keeping these as static global is not great. They really should be in some kind of opaque handle +// like rtl_tcp_dev_t or something. For applications that only use one device at a time, this is fine. +// But if we ever move to supporting multiple simultaneous RTL tuners, this will need to change. +static int rtltcp_sock = -1; +static int rtltcp_tuner_type = RTLSDR_TUNER_UNKNOWN; //from the server's greeting +static pthread_mutex_t rtltcp_m = PTHREAD_MUTEX_INITIALIZER; + +//rtl_tcp command bytes, each named after the librtlsdr function the server calls with the parameter +enum +{ + RTLTCP_SET_CENTER_FREQ = 0x01, + RTLTCP_SET_SAMPLE_RATE = 0x02, + RTLTCP_SET_TUNER_GAIN_MODE = 0x03, + RTLTCP_SET_TUNER_GAIN = 0x04, + RTLTCP_SET_FREQ_CORRECTION = 0x05, + RTLTCP_SET_DIRECT_SAMPLING = 0x09, + RTLTCP_SET_OFFSET_TUNING = 0x0a +}; + +//send one rtl_tcp command: a command byte followed by a 32-bit big-endian parameter. +//A partly sent command would misalign every later one, so a failed send ends the connection. +static int rtltcp_command(uint8_t cmd, uint32_t param) +{ + //encode command and parameter to send to rtl_tcp + unsigned char buf[5] = {cmd, (unsigned char)(param >> 24), (unsigned char)(param >> 16), (unsigned char)(param >> 8), (unsigned char)param}; + + size_t sent = 0; + pthread_mutex_lock(&rtltcp_m); + while (sent < 5) { + ssize_t n = send(rtltcp_sock, buf + sent, 5 - sent, MSG_NOSIGNAL); + if (n < 0 && errno == EINTR) { + continue; + } + if (n <= 0) { + break; + } + sent += n; + } + + //a failed or partial send ends the connection + if (sent < 5) { + shutdown(rtltcp_sock, SHUT_RDWR); + } + pthread_mutex_unlock(&rtltcp_m); + return sent < 5 ? -1 : 0; +} + +//connect to an rtl_tcp server over IPv4 or IPv6, read its greeting, and store the socket in rtltcp_sock; prints the outcome and returns 0, or -1 on failure +int rtltcp_open(const char *hostname, int portno) +{ + struct addrinfo hints, *res, *ai; + char portstr[16]; + unsigned char header[12]; + int sock = -1; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + snprintf(portstr, sizeof(portstr), "%d", portno); + if (getaddrinfo(hostname, portstr, &hints, &res) != 0) { + fprintf (stderr, "Failed to resolve rtl_tcp server %s.\n", hostname); + return -1; + } + + //try each resolved address in turn until one connects + for (ai = res; ai != NULL; ai = ai->ai_next) { + //socket creation + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) { + continue; + } + + //connect + if (connect(sock, ai->ai_addr, ai->ai_addrlen) == 0) { + break; + } + + //close socket and retry with different address + close(sock); + sock = -1; + } + + freeaddrinfo(res); + if (sock < 0) { + fprintf (stderr, "Failed to connect to rtl_tcp server %s:%d.\n", hostname, portno); + return -1; + } + +#ifdef SO_NOSIGPIPE + //macOS and the BSDs: a send to a closed connection returns an error instead of raising SIGPIPE + int one = 1; + setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); +#endif + + //the greeting is "RTL0", the tuner type, and the number of tuner gain steps + if (recv(sock, header, 12, MSG_WAITALL) != 12 || memcmp(header, "RTL0", 4) != 0) { + fprintf (stderr, "%s:%d did not send an rtl_tcp greeting.\n", hostname, portno); + close(sock); + return -1; + } + + //socket is valid and ready to use + rtltcp_sock = sock; + + //store tuner type for later use in rtltcp_get_tuner_gains + rtltcp_tuner_type = ((uint32_t)header[4] << 24) | ((uint32_t)header[5] << 16) | ((uint32_t)header[6] << 8) | header[7]; + fprintf (stderr, "Using rtl_tcp server %s:%d. \n", hostname, portno); + return 0; +} + +//rtlsdr_get_tuner_gains for the server's tuner: copies its gain steps, in tenths of a dB, into gains unless gains is NULL, and returns the number of steps +static int rtltcp_get_tuner_gains(int *gains) +{ + //rtl_tcp advertises only the gain count, not the values; librtlsdr's gain query requires a local device. + //Unfortunately this means we have to copy librtlsdr's tables here to select the nearest valid gain given the remote device. + //It would be better if rtl_tcp told us the actual list of supported gains, but it doesn't. + static const int e4k[] = { -10, 15, 40, 65, 90, 115, 140, 165, 190, 215, 240, 290, 340, 420 }; + static const int fc0012[] = { -99, -40, 71, 179, 192 }; + static const int fc0013[] = { -99, -73, -65, -63, -60, -58, -54, 58, 61, 63, 65, 67, 68, 70, 71, 179, 181, 182, 184, 186, 188, 191, 197 }; + static const int r82xx[] = { 0, 9, 14, 27, 37, 77, 87, 125, 144, 157, 166, 197, 207, 229, 254, 280, 297, 328, 338, 364, 372, 386, 402, 421, 434, 439, 445, 480, 496 }; + static const int none[] = { 0 }; + const int *table = none; + int count = 1; + + //different tuners have different possible gains + switch (rtltcp_tuner_type) { + case RTLSDR_TUNER_E4000: table = e4k; count = sizeof(e4k) / sizeof(int); break; + case RTLSDR_TUNER_FC0012: table = fc0012; count = sizeof(fc0012) / sizeof(int); break; + case RTLSDR_TUNER_FC0013: table = fc0013; count = sizeof(fc0013) / sizeof(int); break; + case RTLSDR_TUNER_R820T: + case RTLSDR_TUNER_R828D: table = r82xx; count = sizeof(r82xx) / sizeof(int); break; + } + + if (gains != NULL) { + memcpy(gains, table, count * sizeof(int)); + } + return count; +} + +//close the rtl_tcp connection; returns close's result +static int rtltcp_close(void) +{ + int r = close(rtltcp_sock); + rtltcp_sock = -1; + return r; +} + +//pass rtl_tcp samples to cb until the connection closes or exitflag is set, in the same block size that rtlsdr_read_async delivers by default +static int rtltcp_read_async(rtlsdr_read_async_cb_t cb, void *ctx) +{ + static unsigned char buf[RTLTCP_BUF_LENGTH]; + while (!exitflag && recv(rtltcp_sock, buf, RTLTCP_BUF_LENGTH, MSG_WAITALL) == RTLTCP_BUF_LENGTH) { + cb(buf, RTLTCP_BUF_LENGTH, ctx); + } + + //we are finished (for a variety of reasons). ensure exitflag is set + if (!exitflag) { + fprintf (stderr, "rtl_tcp server closed the connection.\n"); + exitflag = 1; + } + return 0; +} + +//Each rtlsdr_or_tcp_* function takes the arguments of the librtlsdr function it is named after. +//While an rtl_tcp server is connected, the call goes to the server; otherwise it goes to librtlsdr. +int rtlsdr_or_tcp_close(rtlsdr_dev_t *dev) +{ + return rtltcp_sock >= 0 ? rtltcp_close() : rtlsdr_close(dev); +} + +int rtlsdr_or_tcp_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len) +{ + return rtltcp_sock >= 0 ? rtltcp_read_async(cb, ctx) : rtlsdr_read_async(dev, cb, ctx, buf_num, buf_len); +} + +int rtlsdr_or_tcp_cancel_async(rtlsdr_dev_t *dev) +{ + return rtltcp_sock >= 0 ? shutdown(rtltcp_sock, SHUT_RDWR) : rtlsdr_cancel_async(dev); +} + +int rtlsdr_or_tcp_reset_buffer(rtlsdr_dev_t *dev) +{ + return rtltcp_sock >= 0 ? 0 : rtlsdr_reset_buffer(dev); +} + +int rtlsdr_or_tcp_get_tuner_gains(rtlsdr_dev_t *dev, int *gains) +{ + return rtltcp_sock >= 0 ? rtltcp_get_tuner_gains(gains) : rtlsdr_get_tuner_gains(dev, gains); +} + +int rtlsdr_or_tcp_set_center_freq(rtlsdr_dev_t *dev, uint32_t freq) +{ + return rtltcp_sock >= 0 ? rtltcp_command(RTLTCP_SET_CENTER_FREQ, freq) : rtlsdr_set_center_freq(dev, freq); +} + +int rtlsdr_or_tcp_set_sample_rate(rtlsdr_dev_t *dev, uint32_t rate) +{ + return rtltcp_sock >= 0 ? rtltcp_command(RTLTCP_SET_SAMPLE_RATE, rate) : rtlsdr_set_sample_rate(dev, rate); +} + +int rtlsdr_or_tcp_set_direct_sampling(rtlsdr_dev_t *dev, int on) +{ + return rtltcp_sock >= 0 ? rtltcp_command(RTLTCP_SET_DIRECT_SAMPLING, on) : rtlsdr_set_direct_sampling(dev, on); +} + +int rtlsdr_or_tcp_set_offset_tuning(rtlsdr_dev_t *dev, int on) +{ + return rtltcp_sock >= 0 ? rtltcp_command(RTLTCP_SET_OFFSET_TUNING, on) : rtlsdr_set_offset_tuning(dev, on); +} + +int rtlsdr_or_tcp_set_tuner_gain_mode(rtlsdr_dev_t *dev, int manual) +{ + return rtltcp_sock >= 0 ? rtltcp_command(RTLTCP_SET_TUNER_GAIN_MODE, manual) : rtlsdr_set_tuner_gain_mode(dev, manual); +} + +int rtlsdr_or_tcp_set_tuner_gain(rtlsdr_dev_t *dev, int gain) +{ + return rtltcp_sock >= 0 ? rtltcp_command(RTLTCP_SET_TUNER_GAIN, gain) : rtlsdr_set_tuner_gain(dev, gain); +} + +int rtlsdr_or_tcp_set_freq_correction(rtlsdr_dev_t *dev, int ppm) +{ + return rtltcp_sock >= 0 ? rtltcp_command(RTLTCP_SET_FREQ_CORRECTION, ppm) : rtlsdr_set_freq_correction(dev, ppm); +}