diff options
Diffstat (limited to 'binding/radio_impl_kingfisher.c')
-rw-r--r-- | binding/radio_impl_kingfisher.c | 526 |
1 files changed, 0 insertions, 526 deletions
diff --git a/binding/radio_impl_kingfisher.c b/binding/radio_impl_kingfisher.c deleted file mode 100644 index e906140..0000000 --- a/binding/radio_impl_kingfisher.c +++ /dev/null @@ -1,526 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <stdbool.h> -#include <string.h> -#include <glib.h> -#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_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; - 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 = 0; -static bool corking; -static bool present; -static bool initialized; -static uint32_t current_frequency; -static int scan_valid_snr_threshold = 128; -static int scan_valid_rssi_threshold = 128; -static bool scanning; - -// stream state -static GstElement *pipeline; -static bool running; - -static void (*freq_callback)(uint32_t, void*); -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_probe(void) -{ - struct stat statbuf; - - if(present) - return 0; - - // Check for Kingfisher SI468x devicetree node - if(stat(SI_NODE, &statbuf) != 0) - return -1; - - // Check for Cogent's si_ctl utility - if(stat(SI_CTL, &statbuf) != 0) - return -1; - - present = true; - return 0; -} - -static int kf_init(void) -{ - GKeyFile* conf_file; - bool conf_file_present = false; - 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 -1; - - if(initialized) - return 0; - - // 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 = true; - - // 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; - } - } - } - } - - 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_valid_snr_threshold", - &error); - if(!error) { - AFB_API_INFO(afbBindingV3root, "Scan valid SNR level set to %d", n); - scan_valid_snr_threshold = n; - } - - error = NULL; - n = g_key_file_get_integer(conf_file, - "radio", - "scan_valid_rssi_threshold", - &error); - if(!error) { - AFB_API_INFO(afbBindingV3root, "Scan valid SNR level set to %d", n); - scan_valid_rssi_threshold = n; - } - - g_key_file_free(conf_file); - } - - 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), - "%s /dev/i2c-12 0x65 -b fm -p %s -t %d -u %d -c %d", - SI_CTL, - known_fm_band_plans[bandplan].name, - scan_valid_snr_threshold, - scan_valid_rssi_threshold, - current_frequency / 1000); - rc = system(cmd); - if(rc != 0) { - 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, - "pipewiresrc stream-properties=\"p,node.target=alsa:pcm:radio:0:capture\" ! " - "audio/x-raw,format=F32LE,channels=2 ! " - "pipewiresink 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; - - initialized = true; - return 0; -} - -static void kf_set_output(const char *output) -{ -} - -static uint32_t kf_get_frequency(void) -{ - return current_frequency; -} - -static void kf_set_frequency(uint32_t frequency) -{ - char cmd[SI_CTL_CMDLINE_MAXLEN]; - int rc; - - if(!initialized) - return; - - if(scanning) - return; - - if(frequency < known_fm_band_plans[bandplan].min || - frequency > known_fm_band_plans[bandplan].max) - return; - - kf_scan_stop(); - snprintf(cmd, sizeof(cmd), "%s /dev/i2c-12 0x65 -c %d", SI_CTL, frequency / 1000); - rc = system(cmd); - if(rc == 0) - current_frequency = frequency; - - if(freq_callback) - freq_callback(current_frequency, freq_callback_data); -} - -static void kf_set_frequency_callback(radio_freq_callback_t callback, - void *data) -{ - freq_callback = callback; - freq_callback_data = data; -} - -static char * kf_get_rds_info(void) { - char cmd[SI_CTL_CMDLINE_MAXLEN]; - char line[SI_CTL_OUTPUT_MAXLEN]; - char * rds = NULL; - FILE *fp; - - if (scanning) - goto done; - - snprintf(cmd, sizeof(cmd), "%s /dev/i2c-12 0x65 -m", SI_CTL); - fp = popen(cmd, "r"); - if(fp == NULL) { - fprintf(stderr, "Could not run: %s!\n", cmd); - goto done; - } - /* Look for "Name:" in output */ - while (fgets(line, sizeof(line), fp) != NULL) { - - char* nS = strstr(line, "Name:"); - char * end; - if (!nS) - continue; - - end = nS+strlen("Name:"); - /* remove the trailing '\n' */ - end[strlen(end)-1] = '\0'; - - rds = strdup(end); - break; - } - - /* Make sure si_ctl has finished */ - pclose(fp); - -done: - return rds; -} - -static radio_band_t kf_get_band(void) -{ - return BAND_FM; -} - -static void kf_set_band(radio_band_t band) -{ - // We only support FM, so do nothing -} - -static int kf_band_supported(radio_band_t band) -{ - if(band == BAND_FM) - return 1; - return 0; -} - -static uint32_t kf_get_min_frequency(radio_band_t band) -{ - return known_fm_band_plans[bandplan].min; -} - -static uint32_t kf_get_max_frequency(radio_band_t band) -{ - return known_fm_band_plans[bandplan].max; -} - -static uint32_t kf_get_frequency_step(radio_band_t band) -{ - uint32_t ret = 0; - - switch (band) { - case BAND_AM: - ret = 1000; // 1 kHz - break; - case BAND_FM: - ret = known_fm_band_plans[bandplan].step; - break; - default: - break; - } - return ret; -} - -static bool kf_get_corking_state(void) -{ - return corking; -} - -static void kf_start(void) -{ - if(!initialized) - return; - - if(!running || corking) { - // Start pipeline - gst_element_set_state(pipeline, GST_STATE_PLAYING); - running = true; - corking = false; - } -} - -static void kf_stop(void) -{ - GstEvent *event; - - if(initialized && 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 pipewiresink. 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, - radio_scan_callback_t callback, - void *data) -{ - int rc; - char cmd[SI_CTL_CMDLINE_MAXLEN]; - char line[SI_CTL_OUTPUT_MAXLEN]; - uint32_t new_frequency = 0; - FILE *fp; - - if(!initialized) - return; - - if(!running || scanning) - return; - - scanning = true; - snprintf(cmd, - 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_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_API_DEBUG(afbBindingV3root, "%s: got new_frequency = %d", __FUNCTION__, new_frequency); - break; - } - } - - // Make sure si_ctl has finished - rc = pclose(fp); - if(rc != 0) { - // Make sure we reset to original frequency, the Si4689 seems - // to auto-mute sometimes on failed scans, this hopefully works - // around that. - new_frequency = 0; - } - - if(new_frequency) { - current_frequency = new_frequency * 1000; - - // Push up the new frequency - // This is more efficient than calling kf_set_frequency and calling - // out to si_ctl again. - if(freq_callback) - freq_callback(current_frequency, freq_callback_data); - } else { - // Assume no station found, go back to starting frequency - kf_set_frequency(current_frequency); - } - - // Push up scan state - if(callback) - callback(current_frequency, data); - - scanning = false; -} - -static void kf_scan_stop(void) -{ - // ATM, it's not straightforward to stop a scan since we're using the si_ctl utility... -} - -static radio_stereo_mode_t kf_get_stereo_mode(void) -{ - return STEREO; -} - -static void kf_set_stereo_mode(radio_stereo_mode_t mode) -{ - // We only support stereo, so do nothing -} - -radio_impl_ops_t kf_impl_ops = { - .name = "Kingfisher Si4689", - .probe = kf_probe, - .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, - .get_band = kf_get_band, - .set_band = kf_set_band, - .band_supported = kf_band_supported, - .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, - .scan_stop = kf_scan_stop, - .get_stereo_mode = kf_get_stereo_mode, - .set_stereo_mode = kf_set_stereo_mode, - .get_rds_info = kf_get_rds_info -}; |