diff --git a/platform/drivers/audio/outputStream_linux.c b/platform/drivers/audio/outputStream_linux.c index 5efc857b..ec4b5168 100644 --- a/platform/drivers/audio/outputStream_linux.c +++ b/platform/drivers/audio/outputStream_linux.c @@ -18,131 +18,189 @@ * along with this program; if not, see * ***************************************************************************/ -#include #include -#include -#include #include +#include +#include +#include #include +#include +#include #include // Expand opaque pa_simple struct -struct pa_simple { +struct pa_simple +{ pa_threaded_mainloop *mainloop; - pa_context *context; - pa_stream *stream; + pa_context *context; + pa_stream *stream; pa_stream_direction_t direction; - - const void *read_data; - size_t read_index, read_length; - - int operation_success; + const void *read_data; + size_t read_index; + size_t 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 size_t offset = 0; // Playback offset +static enum BufMode bufMode; // Buffer operation mode +static enum AudioPriority priority = PRIO_BEEP; // Priority level +static bool running = false; // Stream is running +static size_t bufLen = 0; // Total buffer length +static stream_sample_t *playBuf = NULL; // Buffer being reproduced +static stream_sample_t *idleBuf = NULL; // Idle buffer available to be filled +static pa_simple *paInstance = NULL; // Pulseaudio instance +static size_t remaining = 0; +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; - size_t remaining = 0; - if (!s || length <= 0) + if((s == NULL) || (length <= 0)) return; - if (offset >= buf_len / 2) - first_half_active = false; - - remaining = buf_len - offset; - - // We can play all the rest of the buffer - if (length > remaining) + if(length > remaining) { - pa_stream_write(s, buf + offset, remaining, NULL, 0, PA_SEEK_RELATIVE); - - if(first_half_active == true) - first_half_active = false; - else - first_half_active = true; - - offset = 0; + // We can play all the rest of the buffer + pa_stream_write(s, playBuf, remaining * sizeof(stream_sample_t), + NULL, 0, PA_SEEK_RELATIVE); + remaining = 0; } else { - pa_stream_write(s, buf + offset, length, NULL, 0, PA_SEEK_RELATIVE); - offset += length; + pa_stream_write(s, playBuf, length, NULL, 0, PA_SEEK_RELATIVE); + + 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, const enum AudioPriority prio, - stream_sample_t * const buffer, + 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"); - /* The Sample format to use */ - static pa_sample_spec ss = { - .format = PA_SAMPLE_S16LE, - .rate = 0, - .channels = 1 - }; + if(destination != SINK_SPK) + return -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((int) prio < priority) return -1; // Lower priority, reject. - if((int) prio > priority) outputStream_stop(0); // Higher priority, takes over. - outputStream_sync(0, false); // Same priority, wait. + if(prio < priority) return -1; // Lower priority, reject. + if(prio > priority) outputStream_stop(0); // Higher priority, takes over. + while(running) ; // Same priority, wait. } // Assign priority and set stream as running - priority = prio; - running = true; - buf_mode = mode; - buf = buffer; - buf_len = length; - first_half_active = true; + running = true; + priority = prio; + bufMode = mode; + playBuf = buffer; + idleBuf = buffer + (length/2); + bufLen = length; + remaining = length/2; - if (!p) + int paError = 0; + bool success = true; + + if(paInstance == NULL) { - // 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; + // Stream data sample format + static pa_sample_spec spec; + spec.format = PA_SAMPLE_S16LE; + 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) { - fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); - return -1; + case BUF_LINEAR: + if(pa_simple_write(paInstance, buffer, length, &paError) < 0) + 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!"); - - if (pa_simple_write(p, buf, length / 2, &error) < 0) { - 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); + running = false; + priority = PRIO_BEEP; + return -1; } return 0; @@ -152,13 +210,16 @@ stream_sample_t *outputStream_getIdleBuffer(const streamId 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 - return (!first_half_active) ? buf : buf + buf_len / 2; + pthread_mutex_lock(&mutex); + ptr = idleBuf; + pthread_mutex_unlock(&mutex); } - else - return NULL; + + return ptr; } 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) bufChanged; - /* Make sure that every single sample was played */ - if (pa_simple_drain(p, &error) < 0) { - fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); - return false; + if(bufMode == BUF_CIRC_DOUBLE) + { + pthread_mutex_lock(&mutex); + pthread_cond_wait(&barrier, &mutex); + pthread_mutex_unlock(&mutex); } - if (buf_mode == BUF_LINEAR) - { - running = false; - } + // + // TODO syncronisation barrrier also for linear buffer mode + // return true; } @@ -184,18 +245,28 @@ void outputStream_stop(const streamId id) { (void) id; - /* Make sure that every single sample was played */ - if (pa_simple_flush(p, &error) < 0) { - fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); + int error = 0; + if (pa_simple_flush(paInstance, &error) < 0) + { + 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) id; - if (p) - pa_simple_free(p); + running = false; + priority = PRIO_BEEP; + + if(paInstance != NULL) + { + pa_simple_free(paInstance); + pthread_mutex_destroy(&mutex); + pthread_cond_destroy(&barrier); + } }