From 58c1c3bbd64f5cd9e1c99bf6b016432c179f2dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Izzo?= Date: Sat, 23 Jul 2022 16:15:30 +0200 Subject: [PATCH] Implemented circular buffer mode in linux output stream driver. --- openrtx/include/interfaces/audio_stream.h | 3 +- platform/drivers/audio/outputStream_linux.c | 101 +++++++++++++++----- 2 files changed, 78 insertions(+), 26 deletions(-) diff --git a/openrtx/include/interfaces/audio_stream.h b/openrtx/include/interfaces/audio_stream.h index fba36fa6..05d3f83c 100644 --- a/openrtx/include/interfaces/audio_stream.h +++ b/openrtx/include/interfaces/audio_stream.h @@ -106,6 +106,7 @@ void inputStream_stop(streamId id); * @param prio: priority of the requester. * @param buf: buffer containing the audio samples. * @param length: length of the buffer, in elements. + * @param mode: operation mode of the buffer * @param sampleRate: sample rate in Hz. * @return a unique identifier for the stream or -1 if the stream could not be * opened. @@ -138,7 +139,7 @@ stream_sample_t *outputStream_getIdleBuffer(const streamId id); * synchronisation point, the function returns immediately. * * @param id: stream identifier. - * @param bufChanged: if true, notifies the strem handler that new data has + * @param bufChanged: if true, notifies the stream handler that new data has * been written to the idle section of the data buffer. This field is valid * only in circular double buffered mode. * @return true if execution was effectively blocked, false if stream is not diff --git a/platform/drivers/audio/outputStream_linux.c b/platform/drivers/audio/outputStream_linux.c index 273b69b6..b7c3b3f5 100644 --- a/platform/drivers/audio/outputStream_linux.c +++ b/platform/drivers/audio/outputStream_linux.c @@ -22,23 +22,62 @@ #include #include #include +#include #include #include -static int priority = PRIO_BEEP; // Current priority -static bool running = false; // Stream is running -pa_simple *s = NULL; // Pulseaudio instance -int error; // Error code +// Expand opaque pa_simple struct +struct pa_simple { + pa_threaded_mainloop *mainloop; + pa_context *context; + pa_stream *stream; + pa_stream_direction_t direction; + + const void *read_data; + size_t read_index, read_length; + + int operation_success; +}; + +static int priority = PRIO_BEEP; // Current priority +static bool running = false; // Stream is running +static pa_simple *p = NULL; // Pulseaudio instance +static int error = 0; // Error code +static enum BufMode buf_mode; // Buffer operation mode +static stream_sample_t *buf = NULL; // Playback buffer +static size_t buf_len = 0; // Buffer length +static bool first_half_active = true; // Circular addressing mode flag + +static void buf_circ_write_cb(pa_stream *s, size_t length, void *userdata) +{ + (void) userdata; + // TODO: We can play length more bytes of data + // Start playback of the other half + stream_sample_t *active_buf = (first_half_active) ? + buf : buf + buf_len / 2; + first_half_active = !first_half_active; + size_t active_buf_len = buf_len / 2; + + if (!s || length <= 0) + return; + + if (pa_stream_begin_write(s, (void **) &active_buf, &active_buf_len) < 0) + { + fprintf(stderr, "pa_stream_begin_write() failed: %s", pa_strerror(pa_context_errno(p->context))); + return; + } + + pa_stream_write(s, active_buf, active_buf_len, NULL, 0, PA_SEEK_RELATIVE); +} streamId outputStream_start(const enum AudioSink destination, const enum AudioPriority prio, - stream_sample_t * const buf, + stream_sample_t * const buffer, const size_t length, const enum BufMode mode, const uint32_t sampleRate) { assert(destination == SINK_SPK && "Only speaker sink was implemented!\n"); - assert(mode == BUF_LINEAR && "Only linear buffering was implemented!\n"); /* The Sample format to use */ static pa_sample_spec ss = { @@ -60,27 +99,33 @@ streamId outputStream_start(const enum AudioSink destination, // Assign priority and set stream as running priority = prio; running = true; + buf_mode = mode; + buf = buffer; + buf_len = length; + first_half_active = true; - if (!s) + if (!p) { - if (!(s = pa_simple_new(NULL, - "OpenRTX", - PA_STREAM_PLAYBACK, - NULL, - "playback", - &ss, - NULL, - NULL, - &error))) - { + // Create a new playback stream + if (!(p = pa_simple_new(NULL, "OpenRTX", PA_STREAM_PLAYBACK, NULL, "Audio out", &ss, NULL, NULL, &error))) { fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); return -1; } } - if (pa_simple_write(s, buf, length, &error) < 0) { - fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); - return -1; + if (mode == BUF_LINEAR) + { + if (pa_simple_write(p, buf, length, &error) < 0) { + fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); + return -1; + } + } + else if (mode == BUF_CIRC_DOUBLE) + { + assert(p->stream && "Invalid PulseAudio Stream!"); + + // Register write callback + pa_stream_set_write_callback(p->stream, buf_circ_write_cb, NULL); } return 0; @@ -90,7 +135,13 @@ stream_sample_t *outputStream_getIdleBuffer(const streamId id) { (void) id; - return NULL; + if (buf_mode == BUF_CIRC_DOUBLE) + { + // Return half of the buffer not currently being read + return (!first_half_active) ? buf : buf + buf_len / 2; + } + else + return NULL; } bool outputStream_sync(const streamId id, const bool bufChanged) @@ -99,7 +150,7 @@ bool outputStream_sync(const streamId id, const bool bufChanged) (void) bufChanged; /* Make sure that every single sample was played */ - if (pa_simple_drain(s, &error) < 0) { + if (pa_simple_drain(p, &error) < 0) { fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); return false; } @@ -114,7 +165,7 @@ void outputStream_stop(const streamId id) (void) id; /* Make sure that every single sample was played */ - if (pa_simple_flush(s, &error) < 0) { + if (pa_simple_flush(p, &error) < 0) { fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); } @@ -125,6 +176,6 @@ void outputStream_terminate(const streamId id) { (void) id; - if (s) - pa_simple_free(s); + if (p) + pa_simple_free(p); }