Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Ring Buffer for Aplay #459

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion utils/aplay/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 20 additions & 17 deletions utils/aplay/aplay.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -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));
Expand Down Expand Up @@ -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"
Expand All @@ -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");
Expand All @@ -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:
Expand Down
186 changes: 186 additions & 0 deletions utils/aplay/ring-buffer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#include "ring-buffer.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>

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;
}
35 changes: 35 additions & 0 deletions utils/aplay/ring-buffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef BLUEALSA_SHARED_RING_BUFFER_H_
#define BLUEALSA_SHARED_RING_BUFFER_H_

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>

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