aboutsummaryrefslogtreecommitdiffstats
path: root/binding/radio_impl_rtlsdr.c
diff options
context:
space:
mode:
authorScott Murray <scottm@ghidorah.spiteful.org>2018-05-18 11:48:09 -0400
committerScott Murray <scott.murray@konsulko.com>2018-06-05 21:17:02 -0400
commit127da1228b47486de500424e4a2ab3f0f5f0ec4a (patch)
tree31d4047e89b6c7f5a2151d2d8660fdd3c64cceef /binding/radio_impl_rtlsdr.c
parenta0b0ea9312d41a6f48d3a8e92d1d12f69a714bcd (diff)
Split rtlsdr code into standalone helper
To avoid the GPL licensed code in rtl_fm.c making all of the binding GPL, rework things to wrap it with a simple standalone helper executable that is driven via stdin/stdout. While this could potentially be done by running the original unmodified rtl_fm utility itself, it would be impossible to implement scanning with that approach without some compromising of playback latency and quality. The current helper implementation is simple enough that replacing it with an alternate one should be relatively straightforward if that is desired. Change-Id: If83b834da3999f5807d1453524ae72b1c3559c90 Signed-off-by: Scott Murray <scott.murray@konsulko.com>
Diffstat (limited to 'binding/radio_impl_rtlsdr.c')
-rw-r--r--binding/radio_impl_rtlsdr.c270
1 files changed, 216 insertions, 54 deletions
diff --git a/binding/radio_impl_rtlsdr.c b/binding/radio_impl_rtlsdr.c
index 22d627e..7fd4d69 100644
--- a/binding/radio_impl_rtlsdr.c
+++ b/binding/radio_impl_rtlsdr.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 Konsulko Group
+ * Copyright (C) 2017,2018 Konsulko Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,18 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/stat.h>
+#include <errno.h>
#include <glib.h>
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
#include "radio_impl.h"
-#include "radio_output.h"
-#include "rtl_fm.h"
+
+#define HELPER_NAME "rtl_fm_helper"
+#define HELPER_MAX PATH_MAX + 64
// Structure to describe FM band plans, all values in Hz.
typedef struct {
@@ -42,27 +49,91 @@ static fm_band_plan_t known_fm_band_plans[5] = {
};
static unsigned int bandplan;
+static pid_t helper_pid;
+static int helper_in;
+static int helper_out;
+static pthread_mutex_t helper_mutex = PTHREAD_MUTEX_INITIALIZER;
static bool present;
static bool active;
+static bool scanning;
static uint32_t current_frequency;
+static radio_freq_callback_t freq_callback;
+static void *freq_callback_data;
static uint32_t rtlsdr_get_min_frequency(radio_band_t band);
+static void rtlsdr_set_frequency(uint32_t frequency);
static void rtlsdr_scan_stop(void);
-static void rtl_output_callback(int16_t *result, int result_len, void *ctx)
+/*
+ * Bi-directional popen implementation
+ * Based on one of the versions given in:
+ *
+ * https://stackoverflow.com/questions/12778672/killing-process-that-has-been-created-with-popen2
+ */
+static pid_t popen2(char *command, int *in_fd, int *out_fd)
{
- if(active)
- radio_output_write((char*) result, result_len * 2);
+ int pin[2], pout[2];
+ pid_t pid;
+
+ if(out_fd != NULL) {
+ if(pipe(pin) != 0)
+ return -1;
+ }
+ if(in_fd != NULL) {
+ if (pipe(pout) != 0) {
+ if(out_fd != NULL) {
+ close(pin[0]);
+ close(pin[1]);
+ }
+ return -1;
+ }
+ }
+
+ pid = fork();
+ if(pid < 0) {
+ if(out_fd != NULL) {
+ close(pin[0]);
+ close(pin[1]);
+ }
+ if(in_fd != NULL) {
+ close(pout[0]);
+ close(pout[1]);
+ }
+ return pid;
+ }
+ if(pid == 0) {
+ if(out_fd != NULL) {
+ close(pin[1]);
+ dup2(pin[0], 0);
+ }
+ if(in_fd != NULL) {
+ close(pout[0]);
+ dup2(pout[1], 1);
+ }
+ execlp(command, command, NULL);
+ perror("Error:");
+ exit(1);
+ }
+ if(in_fd != NULL) {
+ close(pout[1]);
+ *in_fd = pout[0];
+ }
+ if(out_fd != NULL) {
+ close(pin[0]);
+ *out_fd = pin[1];
+ }
+ return pid;
}
static int rtlsdr_init(void)
{
- GKeyFile* conf_file;
- int conf_file_present = 0;
+ GKeyFile *conf_file;
char *value_str;
+ char *rootdir;
+ char *helper_path;
if(present)
- return -1;
+ return 0;
// Load settings from configuration file if it exists
conf_file = g_key_file_new();
@@ -73,7 +144,6 @@ static int rtlsdr_init(void)
NULL,
G_KEY_FILE_KEEP_COMMENTS,
NULL) == TRUE) {
- conf_file_present = 1;
// Set band plan if it is specified
value_str = g_key_file_get_string(conf_file,
@@ -92,42 +162,43 @@ static int rtlsdr_init(void)
}
}
}
- fprintf(stderr, "Using FM Bandplan: %s\n", known_fm_band_plans[bandplan].name);
+ // Start off with minimum bandplan frequency
current_frequency = rtlsdr_get_min_frequency(BAND_FM);
- if(rtl_fm_init(current_frequency, 200000, 48000, rtl_output_callback, NULL) < 0) {
- return -1;
- }
- if(conf_file_present) {
- GError *error = NULL;
- int n;
-
- // Allow over-riding scanning parameters just in case a demo
- // setup needs to do so to work reliably.
- n = g_key_file_get_integer(conf_file,
- "radio",
- "scan_squelch_level",
- &error);
- if(!error) {
- fprintf(stderr, "Scanning squelch level set to %d\n", n);
- rtl_fm_scan_set_squelch_level(n);
- }
+ rootdir = getenv("AFM_APP_INSTALL_DIR");
+ if(!rootdir)
+ return -1;
- error = NULL;
- n = g_key_file_get_integer(conf_file,
- "radio",
- "scan_squelch_limit",
- &error);
- if(!error) {
- fprintf(stderr, "Scanning squelch limit set to %d\n", n);
- rtl_fm_scan_set_squelch_limit(n);
- }
+ // Run helper to detect adapter
+ helper_path = malloc(HELPER_MAX);
+ if(!helper_path)
+ return -ENOMEM;
+ if(snprintf(helper_path, HELPER_MAX, "%s/bin/%s --detect", rootdir, HELPER_NAME) == HELPER_MAX) {
+ AFB_ERROR("Could not create command for \"%s --detect\"", HELPER_NAME);
+ return -EINVAL;
+ }
+ if(system(helper_path) != 0) {
+ free(helper_path);
+ return -1;
+ }
- g_key_file_free(conf_file);
+ // Run helper
+ if(snprintf(helper_path, PATH_MAX, "%s/bin/%s", rootdir, HELPER_NAME) == PATH_MAX) {
+ AFB_ERROR("Could not create path to %s", HELPER_NAME);
+ return -EINVAL;
}
+ helper_pid = popen2(helper_path, &helper_out, &helper_in);
+ if(helper_pid < 0) {
+ AFB_ERROR("Could not run %s!", HELPER_NAME);
+ return -1;
+ }
+ AFB_DEBUG("%s started", HELPER_NAME);
+ free(helper_path);
present = true;
+ rtlsdr_set_frequency(current_frequency);
+
return 0;
}
@@ -138,6 +209,12 @@ static uint32_t rtlsdr_get_frequency(void)
static void rtlsdr_set_frequency(uint32_t frequency)
{
+ char cmd[64];
+ char output[128];
+ bool found = false;
+ int rc;
+ uint32_t n;
+
if(!present)
return;
@@ -145,19 +222,45 @@ static void rtlsdr_set_frequency(uint32_t frequency)
frequency > known_fm_band_plans[bandplan].max)
return;
- rtlsdr_scan_stop();
+ if(scanning)
+ rtlsdr_scan_stop();
+
current_frequency = frequency;
- rtl_fm_set_freq(frequency);
+ sprintf(cmd, "F=%u\n", frequency);
+ pthread_mutex_lock(&helper_mutex);
+ rc = write(helper_in, cmd, strlen(cmd));
+ if(rc < 0) {
+ pthread_mutex_unlock(&helper_mutex);
+ return;
+ }
+ while(!found) {
+ rc = read(helper_out, output, 128);
+ if(rc <= 0)
+ break;
+ if(rc == 128)
+ rc = 127;
+ output[rc] = '\0';
+ if(output[0] == 'F') {
+ if(sscanf(output, "F=%u\n", &n) == 1) {
+ if(freq_callback)
+ freq_callback(n, freq_callback_data);
+ found = true;
+ }
+ }
+ }
+ pthread_mutex_unlock(&helper_mutex);
}
static void rtlsdr_set_frequency_callback(radio_freq_callback_t callback,
void *data)
{
- rtl_fm_set_freq_callback(callback, data);
+ freq_callback = callback;
+ freq_callback_data = data;
}
static radio_band_t rtlsdr_get_band(void)
{
+ // We only support FM
return BAND_FM;
}
@@ -206,10 +309,15 @@ static void rtlsdr_start(void)
return;
if(!active) {
- if(radio_output_start() != 0)
+ int rc;
+ char cmd[64];
+
+ sprintf(cmd, "START\n");
+ pthread_mutex_lock(&helper_mutex);
+ rc = write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
+ if(rc < 0)
return;
-
- rtl_fm_start();
active = true;
}
}
@@ -220,10 +328,14 @@ static void rtlsdr_stop(void)
return;
if(active) {
- active = false;
- radio_output_stop();
- rtl_fm_stop();
+ int rc;
+ char cmd[64];
+ active = false;
+ sprintf(cmd, "STOP\n");
+ pthread_mutex_lock(&helper_mutex);
+ write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
}
}
@@ -231,21 +343,71 @@ static void rtlsdr_scan_start(radio_scan_direction_t direction,
radio_scan_callback_t callback,
void *data)
{
- rtl_fm_scan_start(direction == SCAN_FORWARD ? 0 : 1,
- callback,
- data,
- rtlsdr_get_frequency_step(BAND_FM),
- rtlsdr_get_min_frequency(BAND_FM),
- rtlsdr_get_max_frequency(BAND_FM));
+ int rc;
+ char cmd[64];
+ char output[128];
+ bool found = false;
+ uint32_t n;
+
+ if(!active || scanning)
+ return;
+
+ scanning = true;
+ sprintf(cmd, "S=%s\n", direction == SCAN_FORWARD ? "UP" : "DOWN");
+ pthread_mutex_lock(&helper_mutex);
+ if(!scanning) {
+ pthread_mutex_unlock(&helper_mutex);
+ return;
+ }
+ rc = write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
+ if(rc < 0)
+ return;
+ while(!found) {
+ pthread_mutex_lock(&helper_mutex);
+ if(!scanning) {
+ pthread_mutex_unlock(&helper_mutex);
+ break;
+ }
+ rc = read(helper_out, output, 128);
+ pthread_mutex_unlock(&helper_mutex);
+ if(rc <= 0)
+ break;
+ if(rc == 128)
+ rc = 127;
+ output[rc] = '\0';
+ if(output[0] == 'F') {
+ if(sscanf(output, "F=%u\n", &n) == 1) {
+ current_frequency = n;
+ if(freq_callback)
+ freq_callback(n, freq_callback_data);
+ }
+ }
+ if(output[0] == 'S') {
+ if(sscanf(output, "S=%u\n", &n) == 1) {
+ if(callback)
+ callback(n, data);
+ found = true;
+ scanning = false;
+ }
+ }
+ }
}
static void rtlsdr_scan_stop(void)
{
- rtl_fm_scan_stop();
+ char cmd[64];
+
+ sprintf(cmd, "S=STOP\n");
+ pthread_mutex_lock(&helper_mutex);
+ scanning = false;
+ write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
}
static radio_stereo_mode_t rtlsdr_get_stereo_mode(void)
{
+ // We only support stereo
return STEREO;
}