diff options
author | Scott Murray <scott.murray@konsulko.com> | 2017-05-22 18:05:21 -0400 |
---|---|---|
committer | Scott Murray <scott.murray@konsulko.com> | 2017-05-23 18:53:26 -0400 |
commit | 4a134c89fcd4afabb10aa32120495b8259bd0c41 (patch) | |
tree | b8896295efd56165d1122e45acf733225447c857 /radio_output.c |
Rework to add and use a binding for radio control
A radio binding has been added in the new binding directory, and the
application has been reworked to use it. The binding uses a modified
version of the rtl_fm code used in the qtmultimedia radio plugin that
was previously used, and some new code has been added to output to
PulseAudio using the asynchronous API to ensure compatibility with
stream corking. The rtl_fm code has been enhanced to add seeking
support, and the application has been tweaked to use it.
Bug-AGL: SPEC-581
Change-Id: I011e98374accc2cad2b36c93ac800948ee51f2aa
Signed-off-by: Scott Murray <scott.murray@konsulko.com>
Diffstat (limited to 'radio_output.c')
-rw-r--r-- | radio_output.c | 294 |
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; +} |