Ring buffer streaming in general - how to implement

1997-06-19, Jarno Elonen <elonen@iki.fi> | URN:NBN:fi-fe20031153

This is a small note I wrote in 1997 while implementing a ring buffer. I couldn't find any good tutorial with suitably detailed instructions (e.g. source code in C) from the web back then - hope this fixes the lack. :)

Notations

LP = Load Pointer = Arrow down CP = Consume Pointer = Arrow up

Loaded, not usable area (always 1 byte) = Light gray box
Loaded and usable area = Dark gray box

RBE = ring buffer end address (first byte which may not be consumed)
RBS = ring buffer start address (first byte which MAY be consumed)

The principle: CP never equals LP: there's always a 1 byte gap.
(Another way is to have an extra variable to tell wether the buffer is full or empty when LP and CP point to the same address. This method is used in the sample code below.)

Initialization

initial state of ring buffer

The first byte of the stream must be read in to the gap between CP and LP before starting the actual streaming.

Possible situations during buffering and consuming

[case 1] Consume pointer in the beginning
(CP=RBS)
[case 2] Most usual case
(CP>RBS, LP<EBS)
[case 3] Wrap around
[case 4] Buffer empty (LP=CP+1)
[case 5] Buffer full (CP=LP+1)

Update cycle in C-like pseudo code

if (CP==RBE && LP!=RBS)
  CP = RBS

if (LP==RBE && CP!=RBS)
  LP = RBS

if (LP>CP)
{
  maxLoadBytes = RBE - LP
  maxConsumeBytes = LP - CP - 1
}
else
{
  maxLoadBytes = CP - LP -1
  maxConsumeBytes = RBE - CP
  // Or if the consumer knows how to wrap around:
  // maxConsumeBytes = (RBE - CP) + (LP - RBS) - 1
...and after consuming or loading:
  LP += maxLoadBytes
  CP += maxConsumeBytes

Implementation in C++

Here's a small C++ ring buffer class:

class Ring_Buffer
{
public:
	Ring_Buffer( void* buffer, unsigned int buffer_size )
	{
        m_buff = (unsigned char*)buffer;
		m_buff_end = m_buff + buffer_size;
		m_load_ptr = m_consume_ptr = m_buff;
		m_max_load = buffer_size;
		m_max_consume = m_data_in_buffer = 0;
		Update_State();
	}

	// Try to add data to the buffer. After the call, 'len' contains
	// the amount of bytes actually buffered.
	void Buffer_Data( const void* data, unsigned int& len )
	{
		if ( len > (unsigned int)m_max_load )
			len = (unsigned int)m_max_load;
		memcpy( m_load_ptr, data, len );
		m_load_ptr += len;
		m_data_in_buffer += len;
		Update_State();
	}

	// Request 'len' bytes from the buffer. After the call,
	// 'len' contains the amount of bytes actually copied.
	void Get_Data( void* outData, unsigned int& len )
	{
		if ( len > (unsigned int)m_max_consume )
			len = (unsigned int)m_max_consume;
		memcpy( outData, m_consume_ptr, len );
		m_consume_ptr += len;
		m_data_in_buffer -= len;
		Update_State();
	}

	// Tries to skip len bytes. After the call,
	// 'len' contains the realized skip.
	void SkipData( unsigned int& len )
	{
		unsigned int requestedSkip = len;
		for ( int i=0; i<2; ++i ) // This may wrap  so try it twice
		{
			int skip = (int)len;
			if ( skip > m_max_consume )
				skip = m_max_consume;
			m_consume_ptr += skip;
			m_data_in_buffer -= skip;
			len -= skip;
			Update_State();
		}
		len = requestedSkip - len;
	}

	// The amount of data the buffer can currently receive on one Buffer_Data() call.
	inline unsigned int Free_Space()    { return (unsigned int)m_max_load; }

	// The total amount of data in the buffer. Note that it may not be continuous: you may need
	// two successive calls to Get_Data() to get it all.
	inline unsigned int Buffered_Bytes() { return (unsigned int)m_data_in_buffer; }

private:

	void Update_State()
	{
		if (m_consume_ptr == m_buff_end)	m_consume_ptr = m_buff;
		if (m_load_ptr == m_buff_end)		m_load_ptr = m_buff;

		if (m_load_ptr == m_consume_ptr)
		{
			if ( m_data_in_buffer > 0 )
			{
				m_max_load = 0;
				m_max_consume = m_buff_end - m_consume_ptr;
			}
			else
			{
				m_max_load = m_buff_end - m_load_ptr;
				m_max_consume = 0;
			}
		}
		else if ( m_load_ptr > m_consume_ptr )
		{
			m_max_load = m_buff_end - m_load_ptr;
			m_max_consume = m_load_ptr - m_consume_ptr;
		}
		else
		{
			m_max_load = m_consume_ptr - m_load_ptr;
			m_max_consume = m_buff_end - m_consume_ptr;
		}
	}

	unsigned char *m_load_ptr, *m_consume_ptr;
	unsigned char *m_buff_end;
	unsigned char *m_buff;
	int m_max_load, m_max_consume, m_data_in_buffer;
};

Public Domain Dedication This work is dedicated to the Public Domain.

Back to mics tips