Add sound manager initial source code
[staging/soundmanager.git] / sample / radio_qml / binding / radio_output.c
diff --git a/sample/radio_qml/binding/radio_output.c b/sample/radio_qml/binding/radio_output.c
new file mode 100644 (file)
index 0000000..a49687b
--- /dev/null
@@ -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;
+}