summaryrefslogtreecommitdiffstats
path: root/src/radio_output_gstreamer.c
diff options
context:
space:
mode:
authorScott Murray <scott.murray@konsulko.com>2023-01-03 00:51:50 -0500
committerScott Murray <scott.murray@konsulko.com>2023-01-19 00:37:11 +0000
commit7f26a2d06410fd3a2768612b9c9daf869778e480 (patch)
tree4dfffa78e14c8eadb42a731ae15cc87137e17723 /src/radio_output_gstreamer.c
parent6191be9c1e4b628f60ccaeabcef2aaa9de00800b (diff)
Repurpose into gRPC servicequillback_17.0.0quillback/17.0.017.0.0
Repurpose repository into a spiritual successor of the previous binding. The backend code is retained behind a new gRPC API defined in protos/radio.proto. The simpler synchronous gRPC API had been used for expediency, this may warrant revisiting to rework into an async or callback API based server instead. As well, authentication has been left until some consensus on an approach can be worked out. Bug-AGL: SPEC-4665 Signed-off-by: Scott Murray <scott.murray@konsulko.com> Change-Id: I28b122ce6e0ecfc7504aa08b90394cb1b9e22976 (cherry picked from commit dd23c157bdba1b25bbb50cdb99a60aa597735f43)
Diffstat (limited to 'src/radio_output_gstreamer.c')
-rw-r--r--src/radio_output_gstreamer.c231
1 files changed, 231 insertions, 0 deletions
diff --git a/src/radio_output_gstreamer.c b/src/radio_output_gstreamer.c
new file mode 100644
index 0000000..e098d2d
--- /dev/null
+++ b/src/radio_output_gstreamer.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2018, 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 <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <gst/gst.h>
+
+#include "radio_output.h"
+#include "rtl_fm.h"
+
+// Flag to enable using GST_STATE_READY instead of GST_STATE_PAUSED to trigger
+// Wireplumber policy mechanism. Hopefully temporary.
+#define WIREPLUMBER_WORKAROUND
+
+// Output buffer
+static unsigned int extra;
+static int16_t extra_buf[1];
+static unsigned char *output_buf;
+
+// GStreamer state
+static GstElement *pipeline, *appsrc;
+static bool running;
+
+int radio_output_open()
+{
+ unsigned int rate = 24000;
+ GstElement *queue, *convert, *sink, *resample;
+ char *p;
+
+ if(pipeline)
+ return 0;
+
+ // Initialize GStreamer
+#ifdef DEBUG
+ unsigned int argc = 2;
+ char **argv = malloc(2 * sizeof(char*));
+ argv[0] = strdup("test");
+ argv[1] = strdup("--gst-debug-level=5");
+ gst_init(&argc, &argv);
+#else
+ gst_init(NULL, NULL);
+#endif
+
+ // Setup pipeline
+ // NOTE: With our use of the simple buffer pushing mode, there seems to
+ // be no need for a mainloop, so currently not instantiating one.
+ pipeline = gst_pipeline_new("pipeline");
+ appsrc = gst_element_factory_make("appsrc", "source");
+ queue = gst_element_factory_make("queue", "queue");
+ convert = gst_element_factory_make("audioconvert", "convert");
+ resample = gst_element_factory_make("audioresample", "resample");
+ sink = gst_element_factory_make("pipewiresink", "sink");
+ if(!(pipeline && appsrc && queue && convert && resample && sink)) {
+ fprintf(stderr, "pipeline element construction failed!\n");
+ }
+ g_object_set(G_OBJECT(appsrc), "caps",
+ gst_caps_new_simple("audio/x-raw",
+ "format", G_TYPE_STRING, "S16LE",
+ "rate", G_TYPE_INT, rate,
+ "channels", G_TYPE_INT, 2,
+ "layout", G_TYPE_STRING, "interleaved",
+ "channel-mask", G_TYPE_UINT64, 3,
+ NULL), NULL);
+ gst_util_set_object_arg(sink, "stream-properties", "p,media.role=Multimedia");
+
+ if((p = getenv("RADIO_OUTPUT"))) {
+ fprintf(stderr, "Using output device %s\n", p);
+ g_object_set(sink, "device", p, NULL);
+ }
+ gst_bin_add_many(GST_BIN(pipeline), appsrc, queue, convert, resample, sink, NULL);
+ gst_element_link_many(appsrc, queue, convert, resample, sink, NULL);
+ //gst_bin_add_many(GST_BIN(pipeline), appsrc, convert, resample, sink, NULL);
+ //gst_element_link_many(appsrc, convert, resample, sink, NULL);
+
+ // Set up appsrc
+ // NOTE: Radio seems like it matches the use case the "is-live" property
+ // is for, but setting it seems to require a lot more work with
+ // respect to latency settings to make the pipeline work smoothly.
+ // For now, leave it unset since the result seems to work
+ // reasonably well.
+ g_object_set(G_OBJECT(appsrc),
+ "stream-type", 0,
+ "format", GST_FORMAT_TIME,
+ NULL);
+
+ // 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
+
+ // Set up output buffer
+ extra = 0;
+ output_buf = malloc(sizeof(unsigned char) * RTL_FM_MAXIMUM_BUF_LENGTH);
+
+ return 0;
+}
+
+int radio_output_start(void)
+{
+ int rc = 0;
+
+ if(!pipeline) {
+ rc = radio_output_open();
+ if(rc)
+ return rc;
+ }
+
+ // Start pipeline
+ running = true;
+ gst_element_set_state(pipeline, GST_STATE_PLAYING);
+
+ return rc;
+}
+
+void radio_output_stop(void)
+{
+ GstEvent *event;
+
+ if(pipeline && running) {
+ // Stop pipeline
+ running = false;
+#ifdef WIREPLUMBER_WORKAROUND
+ gst_element_set_state(pipeline, GST_STATE_READY);
+#else
+ gst_element_set_state(pipeline, GST_STATE_PAUSED);
+#endif
+
+ // 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);
+ }
+}
+
+void radio_output_suspend(int state)
+{
+ // Placeholder
+}
+
+void radio_output_close(void)
+{
+ radio_output_stop();
+
+ if(pipeline) {
+ // Tear down pipeline
+ gst_element_set_state(pipeline, GST_STATE_NULL);
+ gst_object_unref(GST_OBJECT(pipeline));
+ pipeline = NULL;
+ running = false;
+ }
+
+ free(output_buf);
+ output_buf = NULL;
+}
+
+ssize_t radio_output_write(void *buf, int len)
+{
+ ssize_t rc = -EINVAL;
+ size_t n = len;
+ int samples = len / 2;
+ unsigned char *p;
+ GstBuffer *buffer;
+ GstFlowReturn ret;
+
+ if(!(pipeline && buf)) {
+ return rc;
+ }
+
+ // Don't bother pushing samples if output hasn't started
+ if(!running)
+ return 0;
+
+ /*
+ * Handle the rtl_fm code giving us an odd number of samples.
+ * This extra buffer copying approach is not particularly efficient,
+ * but works for now. It looks feasible to hack in something in the
+ * demod and output thread routines in rtl_fm.c to handle it there
+ * if more performance is required.
+ */
+ p = output_buf;
+ if(extra) {
+ memcpy(output_buf, extra_buf, sizeof(int16_t));
+ if((extra + samples) % 2) {
+ // We still have an extra sample, n remains the same, store the extra
+ memcpy(output_buf + sizeof(int16_t), buf, n - 2);
+ memcpy(extra_buf, ((unsigned char*) buf) + n - 2, sizeof(int16_t));
+ } else {
+ // We have an even number of samples, no extra
+ memcpy(output_buf + sizeof(int16_t), buf, n);
+ n += 2;
+ extra = 0;
+ }
+ } else if(samples % 2) {
+ // We have an extra sample, store it, and decrease n
+ n -= 2;
+ memcpy(output_buf + sizeof(int16_t), buf, n);
+ memcpy(extra_buf, ((unsigned char*) buf) + n, sizeof(int16_t));
+ extra = 1;
+ } else {
+ p = buf;
+ }
+
+ // Push buffer into pipeline
+ buffer = gst_buffer_new_allocate(NULL, n, NULL);
+ gst_buffer_fill(buffer, 0, p, n);
+ g_signal_emit_by_name(appsrc, "push-buffer", buffer, &ret);
+ gst_buffer_unref(buffer);
+ rc = n;
+
+ return rc;
+}