Skip to content

Instantly share code, notes, and snippets.

@tripulse
Last active November 14, 2021 14:20
Show Gist options
  • Save tripulse/f4de9f7600ce563680dab4122b2b287a to your computer and use it in GitHub Desktop.
Save tripulse/f4de9f7600ce563680dab4122b2b287a to your computer and use it in GitHub Desktop.
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <threads.h>
/**
* @brief A single unit of data in a opusbuffer, that is capable of
* containing a contigous sequence of bytes being arbitrarily large.
*/
struct opuspacket {
void* data;
size_t size;
size_t capacity;
struct opuspacket* next;
};
/**
* @brief Mutually exclusive, thread safe, resizable, circular buffer
* implemented as a linked list for storing OPUS packets.
*/
struct opusbuffer {
struct opuspacket* list;
struct opuspacket* read;
struct opuspacket* write;
size_t length;
size_t capacity;
mtx_t lock;
};
struct opusbuffer* opusbuffer_create(size_t capacity)
{
if(capacity == 0)
return NULL;
struct opusbuffer* buffer = malloc(sizeof(struct opusbuffer));
if(buffer == NULL)
return NULL;
int ret;
ret = mtx_init(&buffer->lock, mtx_plain);
if(ret != thrd_success)
{
free(buffer);
return NULL;
}
// why calloc() instead of malloc + memset?
// because it gurantess that allocated memory will be zero initialised.
// it takes the benefit that kernel usually provides zero-init memory,
// so it doesn't do any manual memory setting on its own depending
// on the platform.
buffer->capacity = capacity;
buffer->list = calloc(1, sizeof(struct opusbuffer));
if(!buffer->list)
{
free(buffer);
return NULL;
}
struct opuspacket* packet = buffer->list;
for(size_t i = 1; i < buffer->capacity; ++i) {
packet->next = calloc(1, sizeof(struct opusbuffer));
if(!packet->next)
{
buffer->capacity = i;
break;
}
packet = packet->next; // move to next one.
}
// circular linked list is like a "mala", each bead is a node.
// and you connect two ends of it, to make it a closed shape.
//
// https://en.wikipedia.org/wiki/Japamala
packet->next = buffer->list;
buffer->read = buffer->list;
buffer->write = buffer->list;
buffer->length = 0;
return buffer;
}
int opusbuffer_append(struct opusbuffer* buffer, void* data, size_t size)
{
mtx_lock(&buffer->lock);
if(buffer->length == buffer->capacity) {
struct opuspacket* mid = calloc(1, sizeof(struct opuspacket));
struct opuspacket* end = buffer->write->next;
if(!mid)
{
mtx_unlock(&buffer->lock);
return -ENOMEM;
}
// connect middle part in between the two,
// which will be our current pointer and
// increment the capacity if it succeeds.
buffer->write->next = mid;
buffer->write = mid;
mid->next = end;
++buffer->capacity;
}
if(buffer->write->capacity < size)
{
buffer->write->data = realloc(buffer->write->data, size);
if(!buffer->write->data)
{
mtx_unlock(&buffer->lock);
return -ENOMEM;
}
}
memcpy(buffer->write->data, data, size);
buffer->write->size = size;
buffer->write = buffer->write->next;
++buffer->length;
mtx_unlock(&buffer->lock);
return 0;
}
int opusbuffer_get_acq(struct opusbuffer* buffer, void** data, size_t* size)
{
int ret;
ret = mtx_trylock(&buffer->lock);
if(ret == thrd_busy)
return -EAGAIN;
if(!buffer->length)
{
mtx_unlock(&buffer->lock);
return -EAGAIN;
}
*data = buffer->read->data;
*size = buffer->read->size;
return 0;
}
void opusbuffer_get_rel(struct opusbuffer* buffer)
{
buffer->read = buffer->read->next;
--buffer->length;
mtx_unlock(&buffer->lock);
}
void opusbuffer_free(struct opusbuffer* buffer)
{
struct opuspacket* packet = buffer->list;
for(size_t i = 0; i < buffer->capacity; ++i)
{
struct opuspacket* nxt = packet->next;
free(packet);
packet = nxt;
}
mtx_destroy(&buffer->lock);
free(buffer);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment