From 6e70d2b8b821622ae81796a6f1d3f7f3881610a2 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Thu, 3 Jan 2019 01:26:11 -0500 Subject: Add audio role open/close support Rework things so that the 4A audio role is only opened while playing, and closed when stopped. Further work will need to be done to handle the possibility of the output device changing on subsequent opens, both the RTL SDR and Kingfisher case have complications around doing so: - The RTL SDR helper application needs to be enhanced to add an output setting command. Killing it and starting it again would also work, but likely will add noticeable UI latency. - On the Kingfisher, the binding currently has no control over the output used for the loopback, as it is being set up down in the soft-mixer based on HAL values. Change-Id: I4aa83c937972ec5d91f7b78421a11148c7fe0afc Signed-off-by: Scott Murray (cherry picked from commit f1d5902c92eafd3aad62e29298502603a9b56d81) --- binding/radio-binding.c | 147 +++++++++++++++++++++------------------- binding/radio_impl.h | 4 +- binding/radio_impl_kingfisher.c | 47 ++----------- binding/radio_impl_rtlsdr.c | 76 +++++++++++++++------ 4 files changed, 143 insertions(+), 131 deletions(-) diff --git a/binding/radio-binding.c b/binding/radio-binding.c index 5f67567..490af5b 100644 --- a/binding/radio-binding.c +++ b/binding/radio-binding.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,67 @@ static void scan_callback(uint32_t frequency, void *data) afb_event_push(scan_event, json_object_get(jresp)); } +static int set_role_state(bool state, char **output) +{ + int rc; + json_object *response = NULL; + json_object *jsonData = json_object_new_object(); + + json_object_object_add(jsonData, "action", json_object_new_string(state ? "open" : "close")); + rc = afb_service_call_sync("ahl-4a", "radio", jsonData, &response); + if (rc < 0) { + AFB_ERROR("Failed to %s 4A 'radio' role", state ? "open" : "close"); + goto failed; + } + + if(!state) { + // No need to look at the response on close + goto done; + } + + // Handle response on open + json_object *valJson = NULL; + json_object *val = NULL; + rc = json_object_object_get_ex(response, "response", &valJson); + if (rc == 0) { + AFB_ERROR("Reply from 4A is missing a 'response' field"); + rc = -1; + goto failed_malformed; + } + + rc = json_object_object_get_ex(valJson, "device_uri", &val); + if (rc == 0) { + AFB_ERROR("Reply from 4A is missing a 'device_uri' field"); + rc = -1; + goto failed_malformed; + } + + const char *jres_pcm = json_object_get_string(val); + char * p = strchr(jres_pcm, ':'); + + if (p == NULL) { + AFB_ERROR("Unable to parse 'device_uri' value field"); + rc = -1; + goto failed_malformed; + } + + if (output) { + if (asprintf(output, "hw:%s", p + 1) < 0) { + AFB_ERROR("Insufficient memory"); + rc = -1; + goto failed_malformed; + } + } +done: + rc = 0; + +failed_malformed: + json_object_put(response); + +failed: + return rc; +} + /* * Binding verb handlers */ @@ -306,7 +368,13 @@ static void frequency_step(struct afb_req request) */ static void start(struct afb_req request) { - radio_impl_ops->start(); + char *output = NULL; + + if(set_role_state(true, &output) == 0) { + radio_impl_ops->set_output(output); + radio_impl_ops->start(); + free(output); + } afb_req_success(request, NULL, NULL); } @@ -319,6 +387,7 @@ static void start(struct afb_req request) static void stop(struct afb_req request) { radio_impl_ops->stop(); + set_role_state(false, NULL); afb_req_success(request, NULL, NULL); } @@ -487,89 +556,29 @@ static const struct afb_verb_v2 verbs[]= { static int init() { - int rc; - char *output = NULL; - -#ifdef HAVE_4A_FRAMEWORK - json_object *response = NULL; - json_object *jsonData = json_object_new_object(); - - json_object_object_add(jsonData, "action", json_object_new_string("open")); - rc = afb_service_call_sync("ahl-4a", "get_roles", NULL, &response); - if (rc < 0) { - AFB_ERROR("Failed to query 4A about roles"); - goto failed; - } - AFB_NOTICE("4A: available roles are '%s'", json_object_get_string(response)); - json_object_put(response); - - rc = afb_service_call_sync("ahl-4a", "radio", jsonData, &response); - if (rc < 0) { - AFB_ERROR("Failed to query 'radio' role to 4A"); - goto failed; - } - - json_object *valJson = NULL; - json_object *val = NULL; - - rc = json_object_object_get_ex(response, "response", &valJson); - if (rc == 0) { - AFB_ERROR("Reply from 4A is missing a 'response' field"); - goto failed_malformed; - } - - rc = json_object_object_get_ex(valJson, "device_uri", &val); - if (rc == 0) { - AFB_ERROR("Reply from 4A is missing a 'device_uri' field"); - goto failed_malformed; - } - - const char *jres_pcm = json_object_get_string(val); - char * p = strchr(jres_pcm, ':'); - - if (p == NULL) { - AFB_ERROR("Unable to parse 'device_uri' value field"); - rc = -1; - goto failed_malformed; - } - - if (asprintf(&output, "hw:%s", p + 1) < 0) { - AFB_ERROR("Insufficient memory"); - rc = -1; - goto failed_malformed; - } -#endif /* HAVE_4A_FRAMEWORK */ - - // Initialize event structures - freq_event = afb_daemon_make_event("frequency"); - scan_event = afb_daemon_make_event("station_found"); - // Look for RTL-SDR USB adapter radio_impl_ops = &rtlsdr_impl_ops; - rc = radio_impl_ops->init(output); + int rc = radio_impl_ops->init(); if(rc != 0) { // Look for Kingfisher Si4689 radio_impl_ops = &kf_impl_ops; - rc = radio_impl_ops->init(output); + rc = radio_impl_ops->init(); } if (rc != 0) { AFB_ERROR("No radio device found, exiting"); - goto failed; } - if(rc == 0) { AFB_NOTICE("%s found\n", radio_impl_ops->name); radio_impl_ops->set_frequency_callback(freq_callback, NULL); + } else { + return rc; } - free(output); -#ifdef HAVE_4A_FRAMEWORK -failed_malformed: - json_object_put(response); -#endif /* HAVE_4A_FRAMEWORK */ + // Initialize event structures + freq_event = afb_daemon_make_event("frequency"); + scan_event = afb_daemon_make_event("station_found"); -failed: - return rc; + return 0; } const struct afb_binding_v2 afbBindingV2 = { diff --git a/binding/radio_impl.h b/binding/radio_impl.h index 49efe24..0216f69 100644 --- a/binding/radio_impl.h +++ b/binding/radio_impl.h @@ -41,7 +41,9 @@ typedef enum { typedef struct { char *name; - int (*init)(const char *output); + int (*init)(void); + + void (*set_output)(const char *output); uint32_t (*get_frequency)(void); diff --git a/binding/radio_impl_kingfisher.c b/binding/radio_impl_kingfisher.c index 7775d5b..8bfc6ff 100644 --- a/binding/radio_impl_kingfisher.c +++ b/binding/radio_impl_kingfisher.c @@ -77,7 +77,7 @@ static void *freq_callback_data; static uint32_t kf_get_min_frequency(radio_band_t band); static void kf_scan_stop(void); -static int kf_init(const char *output) +static int kf_init(void) { GKeyFile* conf_file; int conf_file_present = 0; @@ -85,10 +85,6 @@ static int kf_init(const char *output) char *value_str; char cmd[SI_CTL_CMDLINE_MAXLEN]; int rc; -#ifndef HAVE_4A_FRAMEWORK - char output_sink_opt[GST_SINK_OPT_LEN]; - char gst_pipeline_str[GST_PIPELINE_LEN]; -#endif /* !HAVE_4A_FRAMEWORK */ if(present) return 0; @@ -182,46 +178,14 @@ static int kf_init(const char *output) return -1; } -#ifndef HAVE_4A_FRAMEWORK - // Initialize GStreamer - gst_init(NULL, NULL); - - if(output) { - AFB_INFO("Using output device %s", output); - rc = snprintf(output_sink_opt, GST_SINK_OPT_LEN, " device=%s", output); - if(rc >= GST_SINK_OPT_LEN) { - AFB_ERROR("output device string too long"); - return -1; - } - } else { - output_sink_opt[0] = '\0'; - } - // NOTE: If muting without pausing is desired, it can likely be done - // by adding a "volume" element to the pipeline before the sink, - // and setting the volume to 0 to mute. - // Use PulseAudio output for compatibility with audiomanager / module_router - rc = snprintf(gst_pipeline_str, - sizeof(gst_pipeline_str), - "alsasrc device=hw:radio ! queue ! audioconvert ! audioresample ! pulsesink stream-properties=\"props,media.role=radio\""); - - if(rc >= GST_PIPELINE_LEN) { - AFB_ERROR("pipeline string too long"); - return -1; - } - pipeline = gst_parse_launch(gst_pipeline_str, NULL); - if(!pipeline) { - AFB_ERROR("pipeline construction failed!"); - return -1; - } - - // Start pipeline in paused state - gst_element_set_state(pipeline, GST_STATE_PAUSED); -#endif /* !HAVE_4A_FRAMEWORK */ - present = true; return 0; } +static void kf_set_output(const char *output) +{ +} + static uint32_t kf_get_frequency(void) { return current_frequency; @@ -488,6 +452,7 @@ static void kf_set_stereo_mode(radio_stereo_mode_t mode) radio_impl_ops_t kf_impl_ops = { .name = "Kingfisher Si4689", .init = kf_init, + .set_output = kf_set_output, .get_frequency = kf_get_frequency, .set_frequency = kf_set_frequency, .set_frequency_callback = kf_set_frequency_callback, diff --git a/binding/radio_impl_rtlsdr.c b/binding/radio_impl_rtlsdr.c index c6b0ea9..109d3f3 100644 --- a/binding/radio_impl_rtlsdr.c +++ b/binding/radio_impl_rtlsdr.c @@ -51,6 +51,7 @@ static fm_band_plan_t known_fm_band_plans[5] = { }; static unsigned int bandplan; +static char *helper_output; static pid_t helper_pid; static int helper_in; static int helper_out; @@ -127,7 +128,7 @@ static pid_t popen2(char *command, int *in_fd, int *out_fd) return pid; } -static int rtlsdr_init(const char *output) +static int rtlsdr_init(void) { GKeyFile *conf_file; char *value_str; @@ -185,31 +186,20 @@ static int rtlsdr_init(const char *output) return -1; } - if(output) { - // Indicate desired output to helper - AFB_INFO("Setting RADIO_OUTPUT=%s", output); - setenv("RADIO_OUTPUT", output, 1); - } - - // 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; } +static void rtlsdr_set_output(const char *output) +{ + // Save output for later use + if(helper_output) + free(helper_output); + helper_output = strdup(output); +} + static uint32_t rtlsdr_get_frequency(void) { return current_frequency; @@ -309,6 +299,48 @@ static uint32_t rtlsdr_get_frequency_step(radio_band_t band) return ret; } +static int rtlsdr_start_helper(void) +{ + char *rootdir; + char *helper_path; + static bool helper_started = false; + + if(!present) + return -1; + + if(helper_started) + return 0; + + rootdir = getenv("AFM_APP_INSTALL_DIR"); + if(!rootdir) + return -1; + + if(helper_output) { + // Indicate desired output to helper + AFB_INFO("Setting RADIO_OUTPUT=%s", helper_output); + setenv("RADIO_OUTPUT", helper_output, 1); + } + + // Run helper + helper_path = malloc(HELPER_MAX); + if(!helper_path) + return -ENOMEM; + 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); + helper_started = true; + free(helper_path); + + return 0; +} + static void rtlsdr_start(void) { if(!present) @@ -317,6 +349,9 @@ static void rtlsdr_start(void) if(active) return; + if(rtlsdr_start_helper() < 0) + return; + ssize_t rc; char cmd[HELPER_CMD_MAXLEN]; @@ -437,6 +472,7 @@ static void rtlsdr_set_stereo_mode(radio_stereo_mode_t mode) radio_impl_ops_t rtlsdr_impl_ops = { .name = "RTL-SDR USB adapter", .init = rtlsdr_init, + .set_output = rtlsdr_set_output, .get_frequency = rtlsdr_get_frequency, .set_frequency = rtlsdr_set_frequency, .set_frequency_callback = rtlsdr_set_frequency_callback, -- cgit 1.2.3-korg