Implemented circular buffer mode in linux output stream driver.

This commit is contained in:
Niccolò Izzo 2022-07-23 16:15:30 +02:00 committed by Silvano Seva
parent 081b19e52c
commit 58c1c3bbd6
2 changed files with 78 additions and 26 deletions

View File

@ -106,6 +106,7 @@ void inputStream_stop(streamId id);
* @param prio: priority of the requester. * @param prio: priority of the requester.
* @param buf: buffer containing the audio samples. * @param buf: buffer containing the audio samples.
* @param length: length of the buffer, in elements. * @param length: length of the buffer, in elements.
* @param mode: operation mode of the buffer
* @param sampleRate: sample rate in Hz. * @param sampleRate: sample rate in Hz.
* @return a unique identifier for the stream or -1 if the stream could not be * @return a unique identifier for the stream or -1 if the stream could not be
* opened. * opened.
@ -138,7 +139,7 @@ stream_sample_t *outputStream_getIdleBuffer(const streamId id);
* synchronisation point, the function returns immediately. * synchronisation point, the function returns immediately.
* *
* @param id: stream identifier. * @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 * been written to the idle section of the data buffer. This field is valid
* only in circular double buffered mode. * only in circular double buffered mode.
* @return true if execution was effectively blocked, false if stream is not * @return true if execution was effectively blocked, false if stream is not

View File

@ -22,23 +22,62 @@
#include <interfaces/audio_stream.h> #include <interfaces/audio_stream.h>
#include <pulse/error.h> #include <pulse/error.h>
#include <pulse/simple.h> #include <pulse/simple.h>
#include <pulse/pulseaudio.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
static int priority = PRIO_BEEP; // Current priority // Expand opaque pa_simple struct
static bool running = false; // Stream is running struct pa_simple {
pa_simple *s = NULL; // Pulseaudio instance pa_threaded_mainloop *mainloop;
int error; // Error code 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, streamId outputStream_start(const enum AudioSink destination,
const enum AudioPriority prio, const enum AudioPriority prio,
stream_sample_t * const buf, stream_sample_t * const buffer,
const size_t length, const size_t length,
const enum BufMode mode, const enum BufMode mode,
const uint32_t sampleRate) const uint32_t sampleRate)
{ {
assert(destination == SINK_SPK && "Only speaker sink was implemented!\n"); 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 */ /* The Sample format to use */
static pa_sample_spec ss = { static pa_sample_spec ss = {
@ -60,27 +99,33 @@ streamId outputStream_start(const enum AudioSink destination,
// Assign priority and set stream as running // Assign priority and set stream as running
priority = prio; priority = prio;
running = true; running = true;
buf_mode = mode;
buf = buffer;
buf_len = length;
first_half_active = true;
if (!s) if (!p)
{ {
if (!(s = pa_simple_new(NULL, // Create a new playback stream
"OpenRTX", if (!(p = pa_simple_new(NULL, "OpenRTX", PA_STREAM_PLAYBACK, NULL, "Audio out", &ss, NULL, NULL, &error))) {
PA_STREAM_PLAYBACK,
NULL,
"playback",
&ss,
NULL,
NULL,
&error)))
{
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
return -1; return -1;
} }
} }
if (pa_simple_write(s, buf, length, &error) < 0) { if (mode == BUF_LINEAR)
fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); {
return -1; 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; return 0;
@ -90,7 +135,13 @@ stream_sample_t *outputStream_getIdleBuffer(const streamId id)
{ {
(void) 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) bool outputStream_sync(const streamId id, const bool bufChanged)
@ -99,7 +150,7 @@ bool outputStream_sync(const streamId id, const bool bufChanged)
(void) bufChanged; (void) bufChanged;
/* Make sure that every single sample was played */ /* 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)); fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error));
return false; return false;
} }
@ -114,7 +165,7 @@ void outputStream_stop(const streamId id)
(void) id; (void) id;
/* Make sure that every single sample was played */ /* 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)); fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error));
} }
@ -125,6 +176,6 @@ void outputStream_terminate(const streamId id)
{ {
(void) id; (void) id;
if (s) if (p)
pa_simple_free(s); pa_simple_free(p);
} }