diff options
author | Scott Murray <scottm@ghidorah.spiteful.org> | 2018-05-18 11:48:09 -0400 |
---|---|---|
committer | Scott Murray <scottm@ghidorah.spiteful.org> | 2018-05-28 19:40:50 -0400 |
commit | e65da04f8451c1166a414fdb58acfe01c63e4f94 (patch) | |
tree | ca8688de99c1642f30cd45aed39a34d7e580124c | |
parent | a714f867ef283ce6de606789aeda2fc17b644fac (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 <scottm@ghidorah.spiteful.org>
-rw-r--r-- | binding/CMakeLists.txt | 28 | ||||
-rw-r--r-- | binding/radio_impl_rtlsdr.c | 270 | ||||
-rw-r--r-- | binding/radio_output_pulse.c (renamed from binding/radio_output.c) | 0 | ||||
-rw-r--r-- | binding/rtl_fm_helper.c | 242 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 3 | ||||
-rw-r--r-- | conf.d/wgt/config.xml.in | 4 |
6 files changed, 487 insertions, 60 deletions
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt index 321022a..d279945 100644 --- a/binding/CMakeLists.txt +++ b/binding/CMakeLists.txt @@ -1,5 +1,6 @@ ########################################################################### # Copyright 2015, 2016, 2017 IoT.bzh +# Copyright (C) 2018 Konsulko Group # # author: Fulup Ar Foll <fulup@iot.bzh> # contrib: Romain Forlot <romain.forlot@iot.bzh> @@ -23,11 +24,8 @@ PROJECT_TARGET_ADD(radio-binding) # Define project Targets set(radio_SOURCES radio-binding.c - radio_output.c radio_impl_kingfisher.c - radio_impl_rtlsdr.c - rtl_fm.c - convenience/convenience.c) + radio_impl_rtlsdr.c) add_library(${TARGET_NAME} MODULE ${radio_SOURCES}) @@ -45,3 +43,25 @@ PROJECT_TARGET_ADD(radio-binding) # installation directory INSTALL(TARGETS ${TARGET_NAME} LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR}) + +# Add helper program target +PROJECT_TARGET_ADD(rtl_fm_helper) + + # Define project targets + set(helper_SOURCES + ${TARGET_NAME}.c + rtl_fm.c + convenience/convenience.c + radio_output.c) + PKG_CHECK_MODULES(SOUND REQUIRED libpulse-simple) + + add_executable(${TARGET_NAME} ${helper_SOURCES}) + + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + LABELS "EXECUTABLE" + OUTPUT_NAME ${TARGET_NAME} + ) + + PKG_CHECK_MODULES(RTLSDR REQUIRED librtlsdr) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + ${RTLSDR_LIBRARIES} ${SOUND_LIBRARIES} ${link_libraries} m) 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; } diff --git a/binding/radio_output.c b/binding/radio_output_pulse.c index a49687b..a49687b 100644 --- a/binding/radio_output.c +++ b/binding/radio_output_pulse.c diff --git a/binding/rtl_fm_helper.c b/binding/rtl_fm_helper.c new file mode 100644 index 0000000..9405ae2 --- /dev/null +++ b/binding/rtl_fm_helper.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2018 Konsulko Group + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <glib.h> + +#include "rtl_fm.h" +#include "radio_output.h" + +#ifdef DEBUG +#define LOG_FILE "/tmp/helper.log" +FILE *_log; +#define LOG(...) \ + do { \ + if(!_log) _log = fopen(LOG_FILE, "w"); \ + fprintf(_log, __VA_ARGS__); \ + fflush(_log); \ + } while(0) +#else +#define LOG(...) do { } while(0) +#endif + +// Structure to describe FM band plans, all values in Hz. +typedef struct { + const char *name; + uint32_t min; + uint32_t max; + uint32_t step; +} fm_band_plan_t; + +static fm_band_plan_t known_fm_band_plans[5] = { + { .name = "US", .min = 87900000, .max = 107900000, .step = 200000 }, + { .name = "JP", .min = 76000000, .max = 95000000, .step = 100000 }, + { .name = "EU", .min = 87500000, .max = 108000000, .step = 50000 }, + { .name = "ITU-1", .min = 87500000, .max = 108000000, .step = 50000 }, + { .name = "ITU-2", .min = 87900000, .max = 107900000, .step = 50000 } +}; + +static unsigned int bandplan; +static char line[64]; + +static void rtl_output_callback(int16_t *result, int result_len, void *ctx) +{ + radio_output_write((char*) result, result_len * 2); +} + +static void rtl_freq_callback(uint32_t freq, void *ctx) +{ + // Note, need to flush output to ensure parent sees it + printf("F=%u\n", freq); + fflush(stdout); + LOG("F=%u\n", freq); +} + +static void rtl_scan_callback(uint32_t freq, void *ctx) +{ + // Note, need to flush output to ensure parent sees it + printf("S=%u\n", freq); + fflush(stdout); + LOG("S=%u\n", freq); +} + +static void read_config(void) +{ + GKeyFile* conf_file; + int conf_file_present = 0; + char *value_str; + + // Load settings from configuration file if it exists + conf_file = g_key_file_new(); + if(conf_file && + g_key_file_load_from_dirs(conf_file, + "AGL.conf", + (const gchar**) g_get_system_config_dirs(), + 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, + "radio", + "fmbandplan", + NULL); + if(value_str) { + unsigned int i; + for(i = 0; + i < sizeof(known_fm_band_plans) / sizeof(fm_band_plan_t); + i++) { + if(!strcasecmp(value_str, known_fm_band_plans[i].name)) { + bandplan = i; + break; + } + } + } + } + fprintf(stderr, "Using FM Bandplan: %s\n", known_fm_band_plans[bandplan].name); + + 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); + } + + 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); + } + + g_key_file_free(conf_file); + } +} + +int main(int argc, char *argv[]) +{ + int rc; + bool detect = false; + bool done = false; + bool started = false; + uint32_t frequency; + + LOG("started\n"); + + read_config(); + frequency = known_fm_band_plans[bandplan].min; + + if(argc == 2 && strcmp(argv[1], "--detect") == 0) { + detect = true; + } + + rc = rtl_fm_init(frequency, 200000, 48000, rtl_output_callback, NULL); + if(rc < 0) { + fprintf(stderr, "No RTL USB adapter?\n"); + exit(1); + } + if(detect) { + rtl_fm_cleanup(); + exit(0); + } + + rtl_fm_set_freq_callback(rtl_freq_callback, NULL); + + while(!done) { + LOG("Reading command\n"); + fgets(line, sizeof(line), stdin); + if(line[0] == '\0' || line[0] == '\n') + continue; + if(strcmp(line, "START\n") == 0) { + LOG("START received\n"); + if(!started) { + LOG("Starting\n"); + radio_output_start(); + rtl_fm_start(); + started = true; + } + } else if(strcmp(line, "STOP\n") == 0) { + LOG("STOP received\n"); + if(started) { + LOG("Stopping\n"); + radio_output_stop(); + rtl_fm_stop(); + started = false; + } + } else if(strncmp(line, "F=", 2) == 0) { + uint32_t n; + if(sscanf(line, "F=%u\n", &n) == 1) { + LOG("F=%d received\n", n); + rtl_fm_scan_stop(); + rtl_fm_set_freq(n); + } + } else if(strcmp(line, "S=UP\n") == 0) { + LOG("S=UP received\n"); + if(!started) + continue; + LOG("Calling rtl_fm_scan_start\n"); + rtl_fm_scan_start(0, + rtl_scan_callback, + NULL, + known_fm_band_plans[bandplan].step, + known_fm_band_plans[bandplan].min, + known_fm_band_plans[bandplan].max); + } else if(strcmp(line, "S=DOWN\n") == 0) { + LOG("S=DOWN received\n"); + if(!started) + continue; + LOG("Calling rtl_fm_scan_start\n"); + rtl_fm_scan_start(1, + rtl_scan_callback, + NULL, + known_fm_band_plans[bandplan].step, + known_fm_band_plans[bandplan].min, + known_fm_band_plans[bandplan].max); + } else if(strcmp(line, "S=STOP\n") == 0) { + LOG("S=STOP received\n"); + if(started) { + LOG("Calling rtl_fm_scan_stop\n"); + rtl_fm_scan_stop(); + } + } else if(line[0] == 'q' || line[0] == 'Q') + break; + } + if(started) { + radio_output_stop(); + rtl_fm_stop(); + } + rtl_fm_cleanup(); + LOG("done"); + return 0; +} diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake index 95cee56..4fa7666 100644 --- a/conf.d/cmake/config.cmake +++ b/conf.d/cmake/config.cmake @@ -1,5 +1,6 @@ ########################################################################### # Copyright 2015, 2016, 2017 IoT.bzh +# Copyright (C) 2018 Konsulko Group # # author: Fulup Ar Foll <fulup@iot.bzh> # @@ -69,8 +70,6 @@ set (PKG_REQUIRED_LIST json-c libsystemd>=222 afb-daemon - librtlsdr - libpulse-simple glib-2.0 gobject-2.0 ) diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in index 605374a..1ce4ff9 100644 --- a/conf.d/wgt/config.xml.in +++ b/conf.d/wgt/config.xml.in @@ -12,6 +12,10 @@ <param name="urn:AGL:permission::public:no-htdocs" value="required" /> </feature> + <feature name="urn:AGL:widget:file-properties"> + <param name="bin/rtl_fm_helper" value="executable" /> + </feature> + <feature name="urn:AGL:widget:provided-api"> <param name="radio" value="ws" /> </feature> |