Circular buffer / Ringbuffer

De wikijoan
Salta a la navegació Salta a la cerca

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