diff options
Diffstat (limited to 'binding/radio_impl_kingfisher.c')
-rw-r--r-- | binding/radio_impl_kingfisher.c | 158 |
1 files changed, 122 insertions, 36 deletions
diff --git a/binding/radio_impl_kingfisher.c b/binding/radio_impl_kingfisher.c index dcd3077..dc59916 100644 --- a/binding/radio_impl_kingfisher.c +++ b/binding/radio_impl_kingfisher.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017,2018 Konsulko Group + * Copyright (C) 2017-2019 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,17 +23,23 @@ #include <fcntl.h> #include <sys/stat.h> #include <json-c/json.h> +#include <gst/gst.h> #include <afb/afb-binding.h> #include "radio_impl.h" #define SI_NODE "/sys/firmware/devicetree/base/si468x@0/compatible" -#define SI_INIT "/usr/bin/si_init" #define SI_CTL "/usr/bin/si_ctl" #define SI_CTL_CMDLINE_MAXLEN 128 #define SI_CTL_OUTPUT_MAXLEN 128 +#define GST_PIPELINE_LEN 256 + +// Flag to enable using GST_STATE_READY instead of GST_STATE_PAUSED to trigger +// Wireplumber policy mechanism. Hopefully temporary. +#define WIREPLUMBER_WORKAROUND + // Structure to describe FM band plans, all values in Hz. typedef struct { char *name; @@ -51,6 +57,7 @@ static fm_band_plan_t known_fm_band_plans[5] = { }; static unsigned int bandplan = 0; +static bool corking = false; static bool present = false; static uint32_t current_frequency; static int scan_valid_snr_threshold = 128; @@ -58,7 +65,7 @@ static int scan_valid_rssi_threshold = 128; static bool scanning = false; // stream state - +static GstElement *pipeline; static bool running; static void (*freq_callback)(uint32_t, void*); @@ -67,6 +74,36 @@ static void *freq_callback_data; static uint32_t kf_get_min_frequency(radio_band_t band); static void kf_scan_stop(void); +static gboolean handle_message(GstBus *bus, GstMessage *msg, __attribute__((unused)) void *ptr) +{ + GstState state; + + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_REQUEST_STATE) { + gst_message_parse_request_state(msg, &state); + + if (state == GST_STATE_PAUSED) { + corking = true; + + // NOTE: Explicitly using PAUSED here, this case currently + // is separate from the general PAUSED/READY issue wrt + // Wireplumber policy. + gst_element_set_state(pipeline, GST_STATE_PAUSED); + } else if (state == GST_STATE_PLAYING) { + corking = false; + + gst_element_set_state(pipeline, GST_STATE_PLAYING); + } + } + + return TRUE; +} + +static void *gstreamer_loop_thread(void *ptr) +{ + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return NULL; +} + static int kf_init(void) { GKeyFile* conf_file; @@ -75,6 +112,8 @@ static int kf_init(void) char *value_str; char cmd[SI_CTL_CMDLINE_MAXLEN]; int rc; + char gst_pipeline_str[GST_PIPELINE_LEN]; + pthread_t thread_id; if(present) return 0; @@ -83,9 +122,7 @@ static int kf_init(void) if(stat(SI_NODE, &statbuf) != 0) return -1; - // Check for Cogent's si_init script and si_ctl utility - if(stat(SI_INIT, &statbuf) != 0) - return -1; + // Check for Cogent's si_ctl utility if(stat(SI_CTL, &statbuf) != 0) return -1; @@ -129,7 +166,7 @@ static int kf_init(void) "scan_valid_snr_threshold", &error); if(!error) { - AFB_INFO("Scan valid SNR level set to %d", n); + AFB_API_INFO(afbBindingV3root, "Scan valid SNR level set to %d", n); scan_valid_snr_threshold = n; } @@ -139,20 +176,14 @@ static int kf_init(void) "scan_valid_rssi_threshold", &error); if(!error) { - AFB_INFO("Scan valid SNR level set to %d", n); + AFB_API_INFO(afbBindingV3root, "Scan valid SNR level set to %d", n); scan_valid_rssi_threshold = n; } g_key_file_free(conf_file); } - rc = system(SI_INIT); - if(rc != 0) { - AFB_ERROR("si_init failed, rc = %d", rc); - return -1; - } - - AFB_INFO("Using FM Bandplan: %s", known_fm_band_plans[bandplan].name); + AFB_API_INFO(afbBindingV3root, "Using FM Bandplan: %s", known_fm_band_plans[bandplan].name); current_frequency = kf_get_min_frequency(BAND_FM); snprintf(cmd, sizeof(cmd), @@ -164,10 +195,40 @@ static int kf_init(void) current_frequency / 1000); rc = system(cmd); if(rc != 0) { - AFB_ERROR("%s failed, rc = %d", SI_CTL, rc); + AFB_API_ERROR(afbBindingV3root, "%s failed, rc = %d", SI_CTL, rc); return -1; } + // Initialize GStreamer + gst_init(NULL, NULL); + + // Use PipeWire output + rc = snprintf(gst_pipeline_str, + GST_PIPELINE_LEN, + "alsasrc device=hw:radio ! queue ! audioconvert ! audioresample ! pwaudiosink stream-properties=\"p,media.role=Multimedia\""); + if(rc >= GST_PIPELINE_LEN) { + AFB_API_ERROR(afbBindingV3root, "pipeline string too long"); + return -1; + } + pipeline = gst_parse_launch(gst_pipeline_str, NULL); + if(!pipeline) { + AFB_API_ERROR(afbBindingV3root, "pipeline construction failed!"); + return -1; + } + + // Start pipeline in paused state +#ifdef WIREPLUMBER_WORKAROUND + gst_element_set_state(pipeline, GST_STATE_READY); +#else + gst_element_set_state(pipeline, GST_STATE_PAUSED); +#endif + + gst_bus_add_watch(gst_element_get_bus(pipeline), (GstBusFunc) handle_message, NULL); + + rc = pthread_create(&thread_id, NULL, gstreamer_loop_thread, NULL); + if(rc != 0) + return rc; + present = true; return 0; } @@ -295,30 +356,54 @@ static uint32_t kf_get_frequency_step(radio_band_t band) return ret; } +static bool kf_get_corking_state(void) +{ + return corking; +} + static void kf_start(void) { if(!present) return; - if (running) - return; - - /* TODO: start the stream in pipewire */ - - running = true; + if(!running || corking) { + // Start pipeline + gst_element_set_state(pipeline, GST_STATE_PLAYING); + running = true; + corking = false; + } } static void kf_stop(void) { - if (!present) - return; - - if (!running) - return; - - /* TODO: stop the stream in pipewire */ - - running = false; + GstEvent *event; + + if(present && running) { + // Stop pipeline + running = false; + +#ifdef WIREPLUMBER_WORKAROUND + // NOTE: Using NULL here instead of READY, as it seems to trigger + // some odd behavior in the pipeline; alsasrc does not seem to + // stop, and things get hung up on restart as there are a bunch + // of "old" samples that seemingly confuse pwaudiosink. Going + // to NULL state seems to tear down things enough to avoid + // whatever happens. + gst_element_set_state(pipeline, GST_STATE_NULL); +#else + gst_element_set_state(pipeline, GST_STATE_PAUSED); +#endif + corking = false; + +#ifndef WIREPLUMBER_WORKAROUND + // Flush pipeline + // This seems required to avoid stutters on starts after a stop + event = gst_event_new_flush_start(); + gst_element_send_event(GST_ELEMENT(pipeline), event); + event = gst_event_new_flush_stop(TRUE); + gst_element_send_event(GST_ELEMENT(pipeline), event); +#endif + } } static void kf_scan_start(radio_scan_direction_t direction, @@ -339,19 +424,19 @@ static void kf_scan_start(radio_scan_direction_t direction, scanning = true; snprintf(cmd, - SI_CTL_CMDLINE_MAXLEN, - "%s /dev/i2c-12 0x65 -l %s", - SI_CTL, direction == SCAN_FORWARD ? "up" : "down"); + SI_CTL_CMDLINE_MAXLEN, + "%s /dev/i2c-12 0x65 -l %s", + SI_CTL, direction == SCAN_FORWARD ? "up" : "down"); fp = popen(cmd, "r"); if(fp == NULL) { - AFB_ERROR("Could not run: %s!", cmd); + AFB_API_ERROR(afbBindingV3root, "Could not run: %s!", cmd); return; } // Look for "Frequency:" in output while(fgets(line, SI_CTL_OUTPUT_MAXLEN, fp) != NULL) { if(strncmp("Frequency:", line, 10) == 0) { new_frequency = atoi(line + 10); - //AFB_DEBUG("%s: got new_frequency = %d", __FUNCTION__, new_frequency); + //AFB_API_DEBUG(afbBindingV3root, "%s: got new_frequency = %d", __FUNCTION__, new_frequency); break; } } @@ -413,6 +498,7 @@ radio_impl_ops_t kf_impl_ops = { .get_min_frequency = kf_get_min_frequency, .get_max_frequency = kf_get_max_frequency, .get_frequency_step = kf_get_frequency_step, + .get_corking_state = kf_get_corking_state, .start = kf_start, .stop = kf_stop, .scan_start = kf_scan_start, |