diff --git a/utils/aplay/Makefile.am b/utils/aplay/Makefile.am index e585a7e82..a453af4d8 100644 --- a/utils/aplay/Makefile.am +++ b/utils/aplay/Makefile.am @@ -7,8 +7,8 @@ bin_PROGRAMS = bluealsa-aplay bluealsa_aplay_SOURCES = \ ../../src/shared/dbus-client.c \ - ../../src/shared/ffb.c \ ../../src/shared/log.c \ + ring-buffer.c \ alsa-pcm.c \ dbus.c \ aplay.c diff --git a/utils/aplay/aplay.c b/utils/aplay/aplay.c index 00cf95c50..c6e5313b3 100644 --- a/utils/aplay/aplay.c +++ b/utils/aplay/aplay.c @@ -29,8 +29,8 @@ #include "shared/dbus-client.h" #include "shared/defs.h" -#include "shared/ffb.h" #include "shared/log.h" +#include "ring-buffer.h" #include "alsa-pcm.h" #include "dbus.h" @@ -310,18 +310,22 @@ static void *pcm_worker_routine(struct pcm_worker *w) { snd_pcm_format_t pcm_format = bluealsa_get_snd_pcm_format(&w->ba_pcm); ssize_t pcm_format_size = snd_pcm_format_size(pcm_format, 1); size_t pcm_1s_samples = w->ba_pcm.sampling * w->ba_pcm.channels; - ffb_t buffer = { 0 }; + + /* store 50 ms of PCM data in the ring buffer */ + /* will be adjusted to one period size when pcm is openend */ + size_t ring_buff_bytes = pcm_1s_samples * pcm_format_size * 50 / 1000; + ring_buff_t buffer = { 0 }; + unsigned char *read_buff = malloc(ring_buff_bytes); /* Cancellation should be possible only in the carefully selected place * in order to prevent memory leaks and resources not being released. */ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pthread_cleanup_push(PTHREAD_CLEANUP(pcm_worker_routine_exit), w); - pthread_cleanup_push(PTHREAD_CLEANUP(ffb_free), &buffer); + pthread_cleanup_push(PTHREAD_CLEANUP(ring_buff_free), &buffer); - /* create buffer big enough to hold 100 ms of PCM data */ - if (ffb_init(&buffer, pcm_1s_samples / 10, pcm_format_size) == -1) { - error("Couldn't create PCM buffer: %s", strerror(errno)); + if (ring_buff_init(&buffer, ring_buff_bytes) == -1) { + error("Couldn't create PCM ring buffer: %s", strerror(errno)); goto fail; } @@ -370,7 +374,7 @@ static void *pcm_worker_routine(struct pcm_worker *w) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pcm_max_read_len = pcm_max_read_len_init; pause_counter = pause_bytes = 0; - ffb_rewind(&buffer); + ring_buff_rewind(&buffer); if (w->pcm != NULL) { snd_pcm_close(w->pcm); w->pcm = NULL; @@ -384,9 +388,7 @@ static void *pcm_worker_routine(struct pcm_worker *w) { if (pfds[0].revents & POLLHUP) break; - #define MIN(a,b) a < b ? a : b - size_t _in = MIN(pcm_max_read_len, ffb_blen_in(&buffer)); - if ((ret = read(w->ba_pcm_fd, buffer.tail, _in)) == -1) { + if ((ret = ring_buff_write(&buffer, w->ba_pcm_fd)) == -1) { if (errno == EINTR) continue; error("PCM FIFO read error: %s", strerror(errno)); @@ -439,6 +441,8 @@ static void *pcm_worker_routine(struct pcm_worker *w) { pcm_max_read_len = period_size * w->ba_pcm.channels * pcm_format_size; pcm_open_retries = 0; + ring_buff_resize(&buffer, snd_pcm_frames_to_bytes(w->pcm, period_size)); + if (verbose >= 2) { printf("Used configuration for %s:\n" " PCM buffer time: %u us (%zu bytes)\n" @@ -460,12 +464,12 @@ static void *pcm_worker_routine(struct pcm_worker *w) { w->active = true; timeout = 500; - ffb_seek(&buffer, ret / pcm_format_size); + /* read the max possible number of bytes from the ring buffer in the continuous read buffer + and calculate the overall number of frames in the read buffer */ + size_t read_bytes = ring_buff_read(&buffer, read_buff, ring_buff_bytes); + snd_pcm_sframes_t frames = read_bytes / w->ba_pcm.channels / pcm_format_size; - /* calculate the overall number of frames in the buffer */ - snd_pcm_sframes_t frames = ffb_len_out(&buffer) / w->ba_pcm.channels; - - if ((frames = snd_pcm_writei(w->pcm, buffer.data, frames)) < 0) + if ((frames = snd_pcm_writei(w->pcm, read_buff, frames)) < 0) switch (-frames) { case EPIPE: debug("An underrun has occurred"); @@ -479,8 +483,7 @@ static void *pcm_worker_routine(struct pcm_worker *w) { } /* move leftovers to the beginning and reposition tail */ - ffb_shift(&buffer, frames * w->ba_pcm.channels); - + ring_buff_shift(&buffer, frames * w->ba_pcm.channels * pcm_format_size); } fail: diff --git a/utils/aplay/ring-buffer.c b/utils/aplay/ring-buffer.c new file mode 100644 index 000000000..58d8fc2ef --- /dev/null +++ b/utils/aplay/ring-buffer.c @@ -0,0 +1,186 @@ +#include "ring-buffer.h" + +#include +#include +#include +#include +#include +#include + +int ring_buff_init(ring_buff_t *ring_buff, size_t size) { + void *ptr; + if ((ptr = (unsigned char*)realloc(ring_buff->data, size)) == NULL) + return -1; + + ring_buff->data = ptr; + ring_buff->write_pos = 0; + ring_buff->read_pos = 0; + ring_buff->size = size; + ring_buff->full = false; + + return 0; +} + +void ring_buff_free(ring_buff_t *ring_buff) { + if (ring_buff->data == NULL) + return; + free(ring_buff->data); + ring_buff->data = NULL; + ring_buff->write_pos = 0; + ring_buff->read_pos = 0; +} + +int ring_buff_resize(ring_buff_t *ring_buff, size_t new_size) { + if (ring_buff->size == new_size) + return 0; + + printf("Resizing Ring Buffer from %u to %u bytes\n", ring_buff->size, new_size); + + unsigned char *new_buff = (unsigned char*)malloc(new_size); + size_t new_write_pos; + bool new_full; + + if (new_size >= ring_buff_size(ring_buff)) { + // copy all + + if (ring_buff->write_pos >= ring_buff->read_pos && !ring_buff->full) { + // 1 copy + size_t copy_len = ring_buff->write_pos - ring_buff->read_pos; + memcpy(new_buff, &ring_buff->data[ring_buff->read_pos], copy_len); + } else { + // 2 copies + size_t copy_len1 = ring_buff->size - ring_buff->read_pos; + size_t copy_len2 = ring_buff->write_pos; + + memcpy(new_buff, &ring_buff->data[ring_buff->read_pos], copy_len1); + memcpy(&new_buff[copy_len1], ring_buff->data, copy_len2); + } + + new_write_pos = ring_buff_size(ring_buff); + new_full = false; + } else { + // copy most recent new_size bytes + + if ((ring_buff->write_pos >= ring_buff->read_pos && !ring_buff->full) || ring_buff->write_pos >= new_size) { + // 1 copy + size_t copy_len = new_size; // bc new_size < write_pos - read_pos + size_t copy_from = ring_buff->write_pos - copy_len; // most recent bytes + memcpy(new_buff, &ring_buff->data[copy_from], copy_len); + } else { + // 2 copies + size_t copy_len2 = ring_buff->write_pos; + size_t copy_len1 = new_size - copy_len2; + + size_t copy_from = ring_buff->size - copy_len1; + + memcpy(new_buff, &ring_buff->data[copy_from], copy_len1); + memcpy(&new_buff[copy_len1], ring_buff->data, copy_len2); + } + + new_write_pos = 0; + new_full = true; + } + + unsigned char *old_buff = ring_buff->data; + ring_buff->data = new_buff; + ring_buff->write_pos = new_write_pos; + ring_buff->read_pos = 0; + ring_buff->size = new_size; + ring_buff->full = new_full; + + free(old_buff); + + return 1; +} + + +bool ring_buff_is_full(ring_buff_t *ring_buff) { + return ring_buff->full; +} + +bool ring_buff_is_empty(ring_buff_t *ring_buff) { + return !ring_buff->full && ring_buff->write_pos == ring_buff->read_pos; +} + + +size_t ring_buff_capacity(ring_buff_t *ring_buff) { + return ring_buff->size; +} + +size_t ring_buff_size(ring_buff_t *ring_buff) { + size_t size = ring_buff->size; + + if (!ring_buff->full) { + if (ring_buff->write_pos >= ring_buff->read_pos) { + size = ring_buff->write_pos - ring_buff->read_pos; + } else { + size = ring_buff->size + ring_buff->write_pos - ring_buff->read_pos; + } + } + + return size; +} + +void ring_buff_rewind(ring_buff_t *ring_buff) { + ring_buff->write_pos = 0; + ring_buff->read_pos = 0; + ring_buff->full = false; +} + +ssize_t ring_buff_write(ring_buff_t *ring_buff, int fd) { + size_t len = ring_buff->size - ring_buff->write_pos; + size_t readable = ring_buff_size(ring_buff); + + // set filedescriptor to non blocking mode + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + size_t totalRead = 0; + int s; + + while((s = read(fd, &ring_buff->data[ring_buff->write_pos], len)) > 0) { + ring_buff->write_pos = (ring_buff->write_pos + s) % ring_buff->size; + len = ring_buff->size - ring_buff->write_pos; + totalRead += s; + } + + if (s == -1 && errno != EAGAIN) { + return -1; + } + + ring_buff->full = ring_buff->full || (readable + totalRead >= ring_buff->size); + + if (ring_buff->full) { + ring_buff->read_pos = ring_buff->write_pos; + } + + return totalRead; +} + +size_t ring_buff_read(ring_buff_t *ring_buff, unsigned char *buf, size_t max) { + if (max <= 0) + return 0; + + if (max > ring_buff_size(ring_buff)) { + max = ring_buff_size(ring_buff); + } + + if (ring_buff->read_pos + max <= ring_buff->size) { // read all at once + memcpy(buf, &ring_buff->data[ring_buff->read_pos], max); + } else { // read 2 times + size_t read = ring_buff->size - ring_buff->read_pos; + + memcpy(buf, &ring_buff->data[ring_buff->read_pos], read); + memcpy(&buf[read], ring_buff->data, max - read); + } + + return max; +} + +void ring_buff_shift(ring_buff_t *ring_buff, size_t bytes) { + if (bytes == 0) + return; + + ring_buff->read_pos = (ring_buff->read_pos + bytes) % ring_buff->size; + ring_buff->full = false; +} diff --git a/utils/aplay/ring-buffer.h b/utils/aplay/ring-buffer.h new file mode 100644 index 000000000..a6c6c6275 --- /dev/null +++ b/utils/aplay/ring-buffer.h @@ -0,0 +1,35 @@ +#ifndef BLUEALSA_SHARED_RING_BUFFER_H_ +#define BLUEALSA_SHARED_RING_BUFFER_H_ + +#include +#include +#include +#include + +typedef struct { + unsigned char *data; + size_t write_pos; + size_t read_pos; + size_t size; + bool full; +} ring_buff_t; + +int ring_buff_init(ring_buff_t *ring_buff, size_t size); +void ring_buff_free(ring_buff_t *ring_buff); + +int ring_buff_resize(ring_buff_t *ring_buff, size_t new_size); + +bool ring_buff_is_full(ring_buff_t *ring_buff); +bool ring_buff_is_empty(ring_buff_t *ring_buff); + +size_t ring_buff_capacity(ring_buff_t *ring_buff); +size_t ring_buff_size(ring_buff_t *ring_buff); + +void ring_buff_rewind(ring_buff_t *ring_buff); + +ssize_t ring_buff_write(ring_buff_t *ring_buff, int fd); +size_t ring_buff_read(ring_buff_t *ring_buff, unsigned char *buf, size_t max); + +void ring_buff_shift(ring_buff_t *ring_buff, size_t bytes); + +#endif