Circular buffer / Ringbuffer
Contingut
Què és un ringbuffer
És un buffer circular, és a dir, allà on acaba comença. S'utilitza amb streaming i aplicacions d'audio i video. Imaginem que volem bolcar el buffer que ve de la targeta de so (del micro) al buffer de la nostra aplicació, i això passa a cada callback. Utilitzant un ringbuffer ens evitem de reposicionar l'index i d'esborrar el contingut. El contingut es va matxacant automàticament.
En l'article de la wikipedia s'entén bé, i hi ha un exemple que funciona i s'entén de com opera el ringbuffer:
/* Circular buffer example, keeps one slot open */ // $ g++ -Wall -o ringbuffer ringbuffer.cpp // $ ./ringbuffer // 20 // 21 // 22 // 23 // 24 // 25 // 26 // 27 // 28 // 29 #include <stdio.h> #include <malloc.h> /* Opaque buffer element type. This would be defined by the application. */ typedef struct { int value; } ElemType; /* Circular buffer object */ typedef struct { int size; /* maximum number of elements */ int start; /* index of oldest element */ int end; /* index at which to write new element */ ElemType *elems; /* vector of elements */ } CircularBuffer; void cbInit(CircularBuffer *cb, int size) { cb->size = size + 1; /* include empty elem */ cb->start = 0; cb->end = 0; cb->elems = (ElemType *)calloc(cb->size, sizeof(ElemType)); } void cbFree(CircularBuffer *cb) { free(cb->elems); /* OK if null */ } int cbIsFull(CircularBuffer *cb) { return (cb->end + 1) % cb->size == cb->start; } int cbIsEmpty(CircularBuffer *cb) { return cb->end == cb->start; } /* Write an element, overwriting oldest element if buffer is full. App can choose to avoid the overwrite by checking cbIsFull(). */ void cbWrite(CircularBuffer *cb, ElemType *elem) { cb->elems[cb->end] = *elem; cb->end = (cb->end + 1) % cb->size; if (cb->end == cb->start) cb->start = (cb->start + 1) % cb->size; /* full, overwrite */ } /* Read oldest element. App must ensure !cbIsEmpty() first. */ void cbRead(CircularBuffer *cb, ElemType *elem) { *elem = cb->elems[cb->start]; cb->start = (cb->start + 1) % cb->size; } int main(int argc, char **argv) { CircularBuffer cb; ElemType elem = {0}; int testBufferSize = 10; /* arbitrary size */ cbInit(&cb, testBufferSize); /* Fill buffer with test elements 3 times */ for (elem.value = 0; elem.value < 3 * testBufferSize; ++ elem.value) cbWrite(&cb, &elem); /* Remove and print all elements */ while (!cbIsEmpty(&cb)) { cbRead(&cb, &elem); printf("%d\n", elem.value); } cbFree(&cb); return 0; }
El problema dels ringbuffer és que si l'index de End i de Start coincideixen, no sabem si el buffer està ple o buit. Per solucionar-ho hi ha varis mètodes, i el codi anterior respon al mètode Always keep one slot open, que vol dir que sempre deixem una cel.la sense ocupar, i així podem saber quan el buffer està ple i quan està buit.
JACK i Ringbuffer
Harry Haaren:
A quick tutorial on basic Jack ringbuffer usage. Ringbuffers are an easy way to exchange data from one thread to another in a realtime safe way. This means that no thread will block when reading or writing, and hence you use ringbuffers in a real-time thread.
Functions jack_ringbuffer_t * jack_ringbuffer_create (size_t sz) void jack_ringbuffer_free (jack_ringbuffer_t *rb) void jack_ringbuffer_get_read_vector (const jack_ringbuffer_t *rb, jack_ringbuffer_data_t *vec) void jack_ringbuffer_get_write_vector (const jack_ringbuffer_t *rb, jack_ringbuffer_data_t *vec) size_t jack_ringbuffer_read (jack_ringbuffer_t *rb, char *dest, size_t cnt) size_t jack_ringbuffer_peek (jack_ringbuffer_t *rb, char *dest, size_t cnt) void jack_ringbuffer_read_advance (jack_ringbuffer_t *rb, size_t cnt) size_t jack_ringbuffer_read_space (const jack_ringbuffer_t *rb) int jack_ringbuffer_mlock (jack_ringbuffer_t *rb) void jack_ringbuffer_reset (jack_ringbuffer_t *rb) size_t jack_ringbuffer_write (jack_ringbuffer_t *rb, const char *src, size_t cnt) void jack_ringbuffer_write_advance (jack_ringbuffer_t *rb, size_t cnt) size_t jack_ringbuffer_write_space (const jack_ringbuffer_t *rb)
Veiem que es pot fer tot allò que s'espera d'un buffer, com ara llegir, escriure, resetejar, alliberar.
main.cpp
$ g++ main.cpp `pkg-config --cflags --libs jack`
/* Copyright (C) 2011 Harry van Haaren <harryhaaren@gmail.com> This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ // compile with: g++ main.cpp `pkg-config --cflags --libs jack` #include <iostream> #include <jack/jack.h> #include <jack/ringbuffer.h> // a global pointer to a ring buffer (usually a class will own this pointer) jack_ringbuffer_t *buffer = 0; int write(int i) { // get amount of space we can write int availableWrite = jack_ringbuffer_write_space(buffer); if (availableWrite >= sizeof(int)) { // tell it to write data, keep track of how much was written int written = jack_ringbuffer_write( buffer, (const char*) &i , sizeof(int) ); // ensure we wrote an entire event if (written != sizeof(int) ) { std::cout << "ERROR! didn't write full integer!" << std::endl; } } else { std::cout << "ERROR! RingBuffer FULL! Skipping..." <<std::endl; } } int process(jack_nframes_t nframes, void* ) { // check if there's anything to read int availableRead = jack_ringbuffer_read_space(buffer); if ( availableRead >= sizeof(int) ) { // create int to read value into int tempInt; // read from the buffer int result = jack_ringbuffer_read(buffer, (char*)&tempInt, sizeof(int)); if ( result != sizeof(int) ) { std::cout << "RtQueue::pull() WARNING! didn't read full event!" << std::endl; return -1; } std::cout << "Jack thread says int = " << tempInt << std::endl; } return 0; } int main() { std::cout << "Ring buffer tutorial" << std::endl; // create an instance of a ringbuffer that will hold up to 20 integers, // let the pointer point to it buffer = jack_ringbuffer_create( 20 * sizeof(int)); // lock the buffer into memory, this is *NOT* realtime safe, do it before // using the buffer! int res = jack_ringbuffer_mlock(buffer); // check if we've locked the memory successfully if ( res ) { std::cout << "Error locking memory!" << std::endl; return -1; } // create a JACK client, register the process callback and activate jack_client_t* client = jack_client_open ( "RingbufferDemo", JackNullOption , 0 , 0 ); jack_set_process_callback (client, process , 0); jack_activate(client); for ( int i = 0; i < 1000; i++) { // write an event, then pause a while, JACK will get a go and then // we'll write another event... etc write(i); sleep(1); } return 0; }
$ ./a.out Ring buffer tutorial Jack thread says int = 0 Jack thread says int = 1 Jack thread says int = 2 Jack thread says int = 3 ...
creat per Joan Quintana Compte, febrer 2012