Bugfix and refactoring of linux output stream driver

This commit is contained in:
Silvano Seva 2022-07-29 14:20:08 +02:00
parent 44abedce4c
commit 26048b90d1
1 changed files with 170 additions and 99 deletions

View File

@ -18,131 +18,189 @@
* along with this program; if not, see <http://www.gnu.org/licenses/> * * along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/ ***************************************************************************/
#include <assert.h>
#include <interfaces/audio_stream.h> #include <interfaces/audio_stream.h>
#include <pulse/error.h>
#include <pulse/simple.h>
#include <pulse/pulseaudio.h> #include <pulse/pulseaudio.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include <pthread.h>
#include <stddef.h> #include <stddef.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h> #include <stdio.h>
// Expand opaque pa_simple struct // Expand opaque pa_simple struct
struct pa_simple { struct pa_simple
{
pa_threaded_mainloop *mainloop; pa_threaded_mainloop *mainloop;
pa_context *context; pa_context *context;
pa_stream *stream; pa_stream *stream;
pa_stream_direction_t direction; pa_stream_direction_t direction;
const void *read_data;
const void *read_data; size_t read_index;
size_t read_index, read_length; size_t read_length;
int operation_success;
int operation_success;
}; };
static int priority = PRIO_BEEP; // Current priority static enum BufMode bufMode; // Buffer operation mode
static bool running = false; // Stream is running static enum AudioPriority priority = PRIO_BEEP; // Priority level
static pa_simple *p = NULL; // Pulseaudio instance static bool running = false; // Stream is running
static int error = 0; // Error code static size_t bufLen = 0; // Total buffer length
static enum BufMode buf_mode; // Buffer operation mode static stream_sample_t *playBuf = NULL; // Buffer being reproduced
static stream_sample_t *buf = NULL; // Playback buffer static stream_sample_t *idleBuf = NULL; // Idle buffer available to be filled
static size_t buf_len = 0; // Buffer length static pa_simple *paInstance = NULL; // Pulseaudio instance
static bool first_half_active = true; // Circular addressing mode flag static size_t remaining = 0;
static size_t offset = 0; // Playback offset static pthread_cond_t barrier;
static pthread_mutex_t mutex;
static void buf_circ_write_cb(pa_stream *s, size_t length, void *userdata) static void buf_circ_write_cb(pa_stream* s, size_t length, void* userdata)
{ {
(void) userdata; (void) userdata;
size_t remaining = 0;
if (!s || length <= 0) if((s == NULL) || (length <= 0))
return; return;
if (offset >= buf_len / 2) if(length > remaining)
first_half_active = false;
remaining = buf_len - offset;
// We can play all the rest of the buffer
if (length > remaining)
{ {
pa_stream_write(s, buf + offset, remaining, NULL, 0, PA_SEEK_RELATIVE); // We can play all the rest of the buffer
pa_stream_write(s, playBuf, remaining * sizeof(stream_sample_t),
if(first_half_active == true) NULL, 0, PA_SEEK_RELATIVE);
first_half_active = false; remaining = 0;
else
first_half_active = true;
offset = 0;
} }
else else
{ {
pa_stream_write(s, buf + offset, length, NULL, 0, PA_SEEK_RELATIVE); pa_stream_write(s, playBuf, length, NULL, 0, PA_SEEK_RELATIVE);
offset += length;
if (remaining > length)
remaining -= length;
else
remaining = 0;
}
// All data in playBuffer has been sent
if(remaining == 0)
{
// Reload counter
remaining = bufLen/2;
pthread_mutex_lock(&mutex);
// Swap idle and play buffers
stream_sample_t *tmp = idleBuf;
playBuf = idleBuf;
idleBuf = tmp;
// Unlock waiting threads
pthread_cond_signal(&barrier);
pthread_mutex_unlock(&mutex);
} }
} }
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 buffer, 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");
/* The Sample format to use */ if(destination != SINK_SPK)
static pa_sample_spec ss = { return -1;
.format = PA_SAMPLE_S16LE,
.rate = 0,
.channels = 1
};
ss.rate = sampleRate; // Check if an output stream is already opened and, in case, handle
// priority.
// Check if an output stream is already opened and, in case, handle priority.
if(running) if(running)
{ {
if((int) prio < priority) return -1; // Lower priority, reject. if(prio < priority) return -1; // Lower priority, reject.
if((int) prio > priority) outputStream_stop(0); // Higher priority, takes over. if(prio > priority) outputStream_stop(0); // Higher priority, takes over.
outputStream_sync(0, false); // Same priority, wait. while(running) ; // Same priority, wait.
} }
// Assign priority and set stream as running // Assign priority and set stream as running
priority = prio; running = true;
running = true; priority = prio;
buf_mode = mode; bufMode = mode;
buf = buffer; playBuf = buffer;
buf_len = length; idleBuf = buffer + (length/2);
first_half_active = true; bufLen = length;
remaining = length/2;
if (!p) int paError = 0;
bool success = true;
if(paInstance == NULL)
{ {
// Create a new playback stream // Stream data sample format
if (!(p = pa_simple_new(NULL, "OpenRTX", PA_STREAM_PLAYBACK, NULL, "Audio out", &ss, NULL, NULL, &error))) { static pa_sample_spec spec;
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); spec.format = PA_SAMPLE_S16LE;
return -1; spec.rate = 0;
spec.channels = 1;
spec.rate = sampleRate;
paInstance = pa_simple_new(NULL, "OpenRTX", PA_STREAM_PLAYBACK, NULL,
"Audio out", &spec, NULL, NULL, &paError);
if(paInstance == NULL)
{
fprintf(stderr, __FILE__ ": pa_simple_new() failed: %s\n",
pa_strerror(paError));
success = false;
}
else
{
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&barrier, NULL);
} }
} }
if (mode == BUF_LINEAR) switch(mode)
{ {
if (pa_simple_write(p, buf, length, &error) < 0) { case BUF_LINEAR:
fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); if(pa_simple_write(paInstance, buffer, length, &paError) < 0)
return -1; success = false;
break;
case BUF_CIRC_DOUBLE:
{
if(paInstance->stream == NULL)
{
success = false;
break;
}
// Register write callback
pa_stream_set_write_callback(paInstance->stream, buf_circ_write_cb,
NULL);
// Set minimal prebuffering
const pa_buffer_attr attr =
{
.fragsize = -1,
.maxlength = -1,
.minreq = -1,
.prebuf = 320,
.tlength = -1,
};
pa_stream_set_buffer_attr(paInstance->stream, &attr, NULL, NULL);
// Get maximum pulse buffer size
size_t wsize = pa_stream_writable_size(paInstance->stream);
if(wsize > (length / 2))
wsize = length / 2;
// Start writing loop
pa_stream_write(paInstance->stream, playBuf, wsize, NULL, 0,
PA_SEEK_RELATIVE);
} }
break;
} }
else if (mode == BUF_CIRC_DOUBLE)
if(success == false)
{ {
assert(p->stream && "Invalid PulseAudio Stream!"); running = false;
priority = PRIO_BEEP;
if (pa_simple_write(p, buf, length / 2, &error) < 0) { return -1;
fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error));
return -1;
}
offset = length / 2;
// Register write callback
pa_stream_set_write_callback(p->stream, buf_circ_write_cb, NULL);
} }
return 0; return 0;
@ -152,13 +210,16 @@ stream_sample_t *outputStream_getIdleBuffer(const streamId id)
{ {
(void) id; (void) id;
if (buf_mode == BUF_CIRC_DOUBLE) stream_sample_t *ptr = NULL;
if(bufMode == BUF_CIRC_DOUBLE)
{ {
// Return half of the buffer not currently being read pthread_mutex_lock(&mutex);
return (!first_half_active) ? buf : buf + buf_len / 2; ptr = idleBuf;
pthread_mutex_unlock(&mutex);
} }
else
return NULL; return ptr;
} }
bool outputStream_sync(const streamId id, const bool bufChanged) bool outputStream_sync(const streamId id, const bool bufChanged)
@ -166,16 +227,16 @@ bool outputStream_sync(const streamId id, const bool bufChanged)
(void) id; (void) id;
(void) bufChanged; (void) bufChanged;
/* Make sure that every single sample was played */ if(bufMode == BUF_CIRC_DOUBLE)
if (pa_simple_drain(p, &error) < 0) { {
fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); pthread_mutex_lock(&mutex);
return false; pthread_cond_wait(&barrier, &mutex);
pthread_mutex_unlock(&mutex);
} }
if (buf_mode == BUF_LINEAR) //
{ // TODO syncronisation barrrier also for linear buffer mode
running = false; //
}
return true; return true;
} }
@ -184,18 +245,28 @@ void outputStream_stop(const streamId id)
{ {
(void) id; (void) id;
/* Make sure that every single sample was played */ int error = 0;
if (pa_simple_flush(p, &error) < 0) { if (pa_simple_flush(paInstance, &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));
} }
running = false; running = false;
priority = PRIO_BEEP;
} }
void outputStream_terminate(const streamId id) void outputStream_terminate(const streamId id)
{ {
(void) id; (void) id;
if (p) running = false;
pa_simple_free(p); priority = PRIO_BEEP;
if(paInstance != NULL)
{
pa_simple_free(paInstance);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&barrier);
}
} }