diff options
Diffstat (limited to 'src/radio_output_gstreamer.c')
-rw-r--r-- | src/radio_output_gstreamer.c | 231 |
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; +} |