A circular buffer is a fixed amount of memory area that can be used in a circular fashion. That means data can be stored from the beginning of the buffer to the end and then repeat the process. The process is same for the retrieval operation. Here, the reading and writing operations are supposed to be done from different threads. In this case, there arises two conditions. One is to check whether there is enough space in the buffer to perform a write operation. If not, the condition is called a buffer overrun. The second is to check whether there is enough data to be read. If not, the condition is called a buffer underrun.
This implementation of a circular buffer handles both buffer underruns and buffer overruns. Data can be written to the buffer from one thread and the same can be read from another thread. Two indices are used to track the position of both read and write positions inside the buffer. Following are the assumptions made in this implementation of a circular buffer.
- The size of the circular buffer is fixed.
- The circular buffer is supposed to handle a known amount of data, however large it may be. This means that the reading thread will read all the data that is written by the writing thread, and the writing thread will continue writing till the known amount of data is reached. Otherwise, either the read or the write function will infinitely wait for the data or space respectively.
Before a read or write operation is done, it will check for the availability of data or space using these indices. After a read/write operation, these indices are updated according to the amount of data that is read or written. The following code shows a read operation.
BOOL CCircularBuffer::ReadBuffer(PBYTE pBuff, LONG lSize)
if(GetBufferArea() >= lSize)
if( ( m_ReadPtr + lSize) > CIRCULAR_BUFFER_SIZE )
lDist = CIRCULAR_BUFFER_SIZE - m_ReadPtr;
CopyMemory(pBuff, (m_pBuffer + m_ReadPtr), lDist);
CopyMemory(pBuff + lDist, m_pBuffer, lSize - lDist);
m_ReadPtr = lSize - lDist;
CopyMemory(pBuff, (m_pBuffer + m_ReadPtr), lSize);
m_ReadPtr += lSize;
A lock is used for getting a mutually exclusive access to the read and write indices. The function
GetBufferArea()will calculate the amount of data available in the buffer. If sufficient data is not available, it will wait till it is available. If anyone can suggest a more efficient method, they are most welcome. In the same way, the write method uses a function named
GetBufferSpace()which will return the amount of space available in the buffer.
InitBuffer() should be called before the buffer can be used. This function allocates a fixed amount of memory for the circular buffer.
Here, the requirement is to burn a set of files to a CD. In order to burn a CD, an ISO9660 image is to be created. This image is then written to a blank CD. What is done is, the image is created and is written to a circular buffer from one thread, and a CD burning thread will read the date from the circular buffer and writes to a blank CD. The ISO image is built using a two pass method. After the first pass, the size of the image is known and hence the writing thread is aware of the size of the image so that the thread can keep reading and burning till the known size is reached.
In this case, the size of the circular buffer is 100 MB. First, the image building thread is started and the main thread will wait for some time, and then the writing thread is started. This is because once the burning is started, there should be an uninterrupted flow of data, otherwise some CD-RW which doesn’t have Buffer-Underrun-Protection feature will fail to burn the entire image before it is completed.
The use of the above described circular buffer is found very effective in the above case.
Video stream player
There is a given amount of MPEG data available. The MPEG data is not a file, rather it is a stream. The circular buffer has been used here in an efficient way. The MPEG stream is played using Microsoft DirectShow. Here, the data is read from the source and is written to the circular buffer from one thread. Another thread will read the data from the buffer and play the stream.
The downloadable source files contain two header files. One is the Lock.h which is the declaration of the class
CLock, which is used for getting an exclusive access to some of the shared variables. Include the header file named CircularBuffer.h into your project, and declare an object of the class
CCircularBuffer and use that object as described above.