summaryrefslogtreecommitdiffstats
path: root/radio_output.c
diff options
context:
space:
mode:
Diffstat (limited to 'radio_output.c')
-rw-r--r--radio_output.c294
1 files changed, 294 insertions, 0 deletions
diff --git a/radio_output.c b/radio_output.c
new file mode 100644
index 0000000..a49687b
--- /dev/null
+++ b/radio_output.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2017 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 <string.h>
+#include <errno.h>
+#include <pulse/pulseaudio.h>
+
+#include "radio_output.h"
+#include "rtl_fm.h"
+
+static pa_threaded_mainloop *mainloop;
+static pa_context *context;
+static pa_stream *stream;
+
+static unsigned int extra;
+static int16_t extra_buf[1];
+static unsigned char *output_buf;
+
+static void pa_context_state_cb(pa_context *c, void *data) {
+ pa_operation *o;
+
+ assert(c);
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ case PA_CONTEXT_READY:
+ break;
+ case PA_CONTEXT_TERMINATED:
+ pa_threaded_mainloop_stop(mainloop);
+ break;
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "PA connection failed: %s\n",
+ pa_strerror(pa_context_errno(c)));
+ pa_threaded_mainloop_stop(mainloop);
+ break;
+ }
+ pa_threaded_mainloop_signal(mainloop, 0);
+}
+
+int radio_output_open(void)
+{
+ pa_context *c;
+ pa_mainloop_api *mapi;
+ char *client;
+
+ if(context)
+ return 0;
+
+ if (!(mainloop = pa_threaded_mainloop_new())) {
+ fprintf(stderr, "pa_mainloop_new() failed.\n");
+ return -1;
+ }
+
+ pa_threaded_mainloop_set_name(mainloop, "pa_mainloop");
+ mapi = pa_threaded_mainloop_get_api(mainloop);
+
+ client = pa_xstrdup("radio");
+ if (!(c = pa_context_new(mapi, client))) {
+ fprintf(stderr, "pa_context_new() failed.\n");
+ goto exit;
+ }
+
+ pa_context_set_state_callback(c, pa_context_state_cb, NULL);
+ if (pa_context_connect(c, NULL, 0, NULL) < 0) {
+ fprintf(stderr, "pa_context_connect(): %s", pa_strerror(pa_context_errno(c)));
+ goto exit;
+ }
+
+ if (pa_threaded_mainloop_start(mainloop) < 0) {
+ fprintf(stderr, "pa_mainloop_run() failed.\n");
+ goto exit;
+ }
+
+ context = c;
+
+ extra = 0;
+ output_buf = malloc(sizeof(unsigned char) * RTL_FM_MAXIMUM_BUF_LENGTH);
+
+ return 0;
+
+exit:
+ if (c)
+ pa_context_unref(c);
+
+ if (mainloop)
+ pa_threaded_mainloop_free(mainloop);
+
+ pa_xfree(client);
+ return -1;
+}
+
+int radio_output_start(void)
+{
+ int error = 0;
+ pa_sample_spec *spec;
+
+ if(stream)
+ return 0;
+
+ if(!context) {
+ error = radio_output_open();
+ if(error != 0)
+ return error;
+ }
+
+ while(pa_context_get_state(context) != PA_CONTEXT_READY)
+ pa_threaded_mainloop_wait(mainloop);
+
+ spec = (pa_sample_spec*) calloc(1, sizeof(pa_sample_spec));
+ spec->format = PA_SAMPLE_S16LE;
+ spec->rate = 24000;
+ spec->channels = 2;
+ if (!pa_sample_spec_valid(spec)) {
+ fprintf(stderr, "%s\n",
+ pa_strerror(pa_context_errno(context)));
+ return -1;
+ }
+
+ pa_threaded_mainloop_lock(mainloop);
+ pa_proplist *props = pa_proplist_new();
+ pa_proplist_sets(props, PA_PROP_MEDIA_ROLE, "radio");
+ stream = pa_stream_new_with_proplist(context, "radio-output", spec, 0, props);
+ if(!stream) {
+ fprintf(stderr, "Error creating stream %s\n",
+ pa_strerror(pa_context_errno(context)));
+ pa_proplist_free(props);
+ free(spec);
+ pa_threaded_mainloop_unlock(mainloop);
+ return -1;
+ }
+ pa_proplist_free(props);
+ free(spec);
+
+ if(pa_stream_connect_playback(stream,
+ NULL,
+ NULL,
+ (pa_stream_flags_t) 0,
+ NULL,
+ NULL) < 0) {
+ fprintf(stderr, "Error connecting to PulseAudio : %s\n",
+ pa_strerror(pa_context_errno(context)));
+ pa_stream_unref(stream);
+ stream = NULL;
+ pa_threaded_mainloop_unlock(mainloop);
+ return -1;
+ }
+
+ pa_threaded_mainloop_unlock(mainloop);
+
+ while(pa_stream_get_state(stream) != PA_STREAM_READY)
+ pa_threaded_mainloop_wait(mainloop);
+
+ return error;
+}
+
+void radio_output_stop(void)
+{
+ if(stream) {
+ pa_threaded_mainloop_lock(mainloop);
+
+ pa_stream_set_state_callback(stream, 0, 0);
+ pa_stream_set_write_callback(stream, 0, 0);
+ pa_stream_set_underflow_callback(stream, 0, 0);
+ pa_stream_set_overflow_callback(stream, 0, 0);
+ pa_stream_set_latency_update_callback(stream, 0, 0);
+
+ pa_operation *o = pa_stream_flush(stream, NULL, NULL);
+ if(o)
+ pa_operation_unref(o);
+
+ pa_stream_disconnect(stream);
+ pa_stream_unref(stream);
+ stream = NULL;
+
+ pa_threaded_mainloop_unlock(mainloop);
+ }
+}
+
+void radio_output_suspend(int state)
+{
+ if(stream) {
+ pa_stream_cork(stream, state, NULL, NULL);
+ }
+}
+
+void radio_output_close(void)
+{
+ radio_output_stop();
+
+ if(context) {
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+ context = NULL;
+ }
+
+ if(mainloop) {
+ pa_threaded_mainloop_stop(mainloop);
+ pa_threaded_mainloop_free(mainloop);
+ mainloop = NULL;
+ }
+
+ free(output_buf);
+ output_buf = NULL;
+}
+
+int radio_output_write(void *buf, int len)
+{
+ int rc = -EINVAL;
+ int error;
+ size_t n = len;
+ size_t avail;
+ int samples = len / 2;
+ void *p;
+
+ if(!stream) {
+ return -1;
+ }
+
+ if(!buf) {
+ fprintf(stderr, "Error: buf == null!\n");
+ return rc;
+ }
+
+ pa_threaded_mainloop_lock(mainloop);
+
+ avail = pa_stream_writable_size(stream);
+ if(avail < n) {
+ /*
+ * NOTE: Definitely room for improvement here,but for now just
+ * check for the no space case that happens when the
+ * stream is corked.
+ */
+ if(!avail) {
+ rc = 0;
+ goto exit;
+ }
+ }
+
+ /*
+ * Handle the rtl_fm code giving us an odd number of samples, which
+ * PA does not like. 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;
+ }
+
+ if ((rc = pa_stream_write(stream, p, n, NULL, 0, PA_SEEK_RELATIVE)) < 0) {
+ fprintf(stderr, "Error writing %d bytes to PulseAudio : %s\n",
+ n, pa_strerror(pa_context_errno(context)));
+ }
+exit:
+ pa_threaded_mainloop_unlock(mainloop);
+
+ return rc;
+}