summaryrefslogtreecommitdiffstats
path: root/binding/radio_impl_kingfisher.c
diff options
context:
space:
mode:
Diffstat (limited to 'binding/radio_impl_kingfisher.c')
-rw-r--r--binding/radio_impl_kingfisher.c158
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,