// file: $isip/class/java/AudioCircularBuffer/AudioCircularBuffer.java // version: $Id: AudioCircularBuffer.java,v 1.6 2004/07/21 16:15:35 // stanley Exp $ // // import java's libraries. // import java.lang.*; import java.nio.BufferOverflowException; /** * Implements the circular buffer producer/consumer model for any java * object */ public class AudioCircularBuffer { //------------------------------------------------------------------------ // // public constants // //------------------------------------------------------------------------ /** * The default size for a circular object buffer. */ public final static int DEFAULT_SIZE = 1024; /** * A buffer that will grow as things are added. */ public final static int INFINITE_SIZE = -1; //----------------------------------------------------------------------- // // protected data // //----------------------------------------------------------------------- /** * The circular buffer. The actual capacity of the buffer is one * less than the actual length of the buffer so that an empty and * a full buffer can be distinguished. an empty buffer will have * the readpostion and the writeposition equal to each other. a * full buffer will have the writeposition one less than the * readpostion. There are two important indexes into the buffer: * the readposition, and the writeposition. the objects available * to be read go from the readposition to the writeposition, * wrapping around the end of the buffer. the space available for * writing goes from the write position to one less than the * readposition, wrapping around the end of the buffer. */ protected Object[] buffer_d; /** * Index of the first object available to be read. */ protected volatile int read_position_d = 0; /** * Index of the first object available to be written. */ protected volatile int write_position_d = 0; /** * This gives the current position of the pointer. Initially it is * set to be same as that of the read pointer. */ protected volatile int curr_position_d = 0; /** * If this buffer is infinite (should resize itself when full) */ protected volatile boolean infinite_d = false; /** * True if a write to a full buffer should block until the buffer * has room, false if the write method should throw an ioexception */ protected boolean blocking_write_d = true; /** * True when no more input is coming into this buffer. At that * point reading from the buffer may return null if the buffer * is empty, otherwise a read will block until an Object is available. */ protected boolean input_done_d = false; //------------------------------------------------------------------------- // // public methods // //------------------------------------------------------------------------- /** * Constructor for the class. Create a new buffer with a default * capacity. Writing to a full buffer will block until space is * available rather than throw an exception. */ public AudioCircularBuffer() { this (DEFAULT_SIZE, true); } /** * Constructor for the class. Create a new buffer with given * capacity. Writing to a full buffer will block until space is * available rather than throw an exception.
Note that the * buffer may reserve some Objects for special purposes and * capacity number of Objects may not be able to be written to the * buffer.
Note that if the buffer is of INFINITE_SIZE it * will neither block or throw exceptions, but rather grow without * bound. * * @param size_a desired capacity of the buffer in Objects or * AudioCircularBuffer.INFINITE_SIZE. */ public AudioCircularBuffer(int size_a) { this (size_a, true); } /** * Constructor for the class. Create a new buffer with a default * capacity and given blocking behavior. * * @param blocking_write_a true writing to a full buffer should * block until space is available, false if an exception should be * thrown instead. */ public AudioCircularBuffer(boolean blocking_write_a) { this (DEFAULT_SIZE, blocking_write_a); } /** * Constructor for the class. Create a new buffer with the given * capacity and blocking behavior. Note that the buffer may * reserve some Objects for special purposes and capacity number * of Objects may not be able to be written to the buffer. Note * that if the buffer is of INFINITE_SIZE it will neither block or * throw exceptions, but rather grow without bound. * * @param size_a desired capacity of the buffer in Objects or * AudioCircularBuffer.INFINITE_SIZE. * @param blocking_write_a true writing to a full buffer should * block until space is available, false if an exception should be * thrown instead. */ public AudioCircularBuffer(int size_a, boolean blocking_write_a) { if (size_a == INFINITE_SIZE) { // if the size_a is infinite size set the circular buffer // length to default size // buffer_d = new Object[DEFAULT_SIZE]; infinite_d = true; } else { // else set the size to the user defined size // buffer_d = new Object[size_a]; infinite_d = false; } this.blocking_write_d = blocking_write_a; } //------------------------------------------------------------------------- // // class-specific public methods: // get methods // //------------------------------------------------------------------------- /** * Get an Object from the circular buffer. The offset gives the * distance of the index from the current pointer. * * @param o_a an object * @param offset_a the index number from the current pointer. * * @return integer value containing the available objects to read * in the circular buffer. */ public boolean getElement(Object o_a, int offset_a) { if ((offset_a > 0 && offset_a > getNumForward()) || (offset_a < 0 && offset_a < (-1 * getNumBackward()))) { System.out.println("Exception in getElement() method"); return false; } // get the index whose contents should be read // int index = curr_position_d + offset_a; // get the capacity of the circular buffer // int capacity = getCapacity(); while(index > (capacity - 1)) { index = index - capacity; } // assign the value at that index to the Object // o_a = buffer_d[index]; return true; } /** * Get an array of Objects from the circular buffer. The offset * gives the distance of the index from the current pointer. * * @param buff_a an array of objects * @param num_elem_a the number of elements that should be got * starting from the offset_a * @param offset_a the index number from the current pointer. * * @return integer value containing the available objects to read * in the circular buffer. */ public boolean getElement(Object[] buff_a, int num_elem_a, int offset_a) { buff_a = new Object[num_elem_a]; // loop over until you get the number of elements requested by // the user starting from the offset_a // for (int i = 0; i < num_elem_a; i++) { getElement(buff_a[i], offset_a); offset_a++; } return true; } /** * Get number of Objects that are available to be read.
* Note that the number of Objects available plus the number * of Objects free may not add up to the capacity of this * buffer, as the buffer may reserve some space for other * purposes. * * @return integer value containing the available objects to read * in the circular buffer. */ public int getNumElements(){ synchronized (this){ return available(); } } /** * Get number of valid Objects in front of the current pointer * (This doesnot include the current pointer). * * @return integer value indicating the number of Objects that * are before the current pointer. */ public int getNumForward() { int num = 0; // if the circular buffer is empty return zero // if (isEmpty()) { num = 0; } // if the current pointer and the read pointer are at the // same position // else if (curr_position_d == read_position_d) { num = getNumElements() - 1; } else { int capacity = getCapacity(); num = (write_position_d - curr_position_d + capacity) % capacity; } return num; } /** * Get number of valid Objects behind (lag of) the current pointer * (This doesnot include the current pointer). * * @return integer value indicating the number of Objects that * are behind the current pointer. */ public int getNumBackward() { int num = 0; // if the circular buffer is empty return zero // if (isEmpty()) { num = 0; } // if the current pointer and the write pointer are at the // same position // else if (curr_position_d == write_position_d) { num = getNumElements() -1; } else { int capacity = getCapacity(); num = (curr_position_d - read_position_d + capacity) % capacity; } return num; } /** * Get the capacity of this buffer.
Note that the number * of Objects available plus the number of Objects free may * not add up to the capacity of this buffer, as the buffer * may reserve some space for other purposes. * * @return the size in Objects of this buffer */ public int getCapacity() { synchronized (this) { return buffer_d.length; } } //----------------------------------------------------------------------- // // class-specific other public methods: // // //------------------------------------------------------------------------ /** * Make this buffer ready for reuse. The contents of the * buffer will be cleared and the streams associated with this * buffer will be reopened if they had been closed. * * @return void */ public boolean clear() { synchronized (this) { read_position_d = 0; write_position_d = 0; curr_position_d = 0; input_done_d = false; } return true; } /** * Get the number of Objects this buffer has free for writing. *
Note that the number of Objects available plus the number * of Objects free may not add up to the capacity of this buffer, * as the buffer may reserve some space for other purposes. * * @return the available space in Objects of this buffer */ public int getSpaceLeft() { synchronized (this) { return spaceLeft(); } } /** * Check whether the circular buffer is full or not. This * function internally calls the spaceleft() method and if the * spaceleft is zero then returns true. * * @return a boolean value indicating whether the circular buffer * is empty or not. */ public boolean isFull() { // if the circular buffer is empty return false // if (isEmpty()) { return false; } // if the space left in the circular buffer is zero then // return true // else if (spaceLeft() == 0) { return true; } return false; } /** * Check whether the buffer is empty. If the read and the write * pointer are both zero then the circular buffer is empty. * * @return a boolean value indicating whether the circular buffer * is empty or not. */ public boolean isEmpty() { // if the read and the write pointer are the same then there // is no data to be read inbetween them. So the circular // buffer is empty. // if (read_position_d == write_position_d) { return true; } else { return false; } } /** * This function is used to indicate the status of the circular * buffer. It indicates whether the producer is still writing to * the buffer. * * @return returns a boolean value indicating whether the producer * is still writing to the buffer or not. */ public boolean isDone() { return input_done_d; } /** * Get a single Object from this buffer. This method should be * called by the consumer. This method will block until a Object * is available or no more objects are available. * * @return The Object read, or null if there are no more objects * * @throws InterruptedException if the thread is interrupted while * waiting. */ public Object read() throws InterruptedException { while (true) { synchronized (this) { int available = available(); if (available > 0) { Object result = buffer_d[read_position_d]; read_position_d++; if (read_position_d == buffer_d.length) { read_position_d = 0; } read_position_d = curr_position_d; return result; } else if (input_done_d) { return null; } } Thread.sleep(100); } } /** * Get Objects into an array from this buffer. This method should * be called by the consumer. This method will block until some * input is available, or there is no more input. * * @param buf_a destination buffer. * * @return the number of Objects read, or -1 there will be no more * objects available. */ public int read(Object[] buf_a) { return read(buf_a, 0, buf_a.length); } /** * Get Objects into a portion of an array from this buffer. This * method should be called by the consumer. This method will * return zero if there is nothing available to read. * * @param buf_a destination buffer. * @param off_a offset at which to start storing Objects. * @param len_a maximum number of Objects to read. * * @return the number of Objects read, or -1 there will be no more * objects available. */ public int read(Object[] buf_a, int off_a, int len_a) { synchronized (this) { // get the available number of data to be read // int available = available(); if (available > 0) { // get the minimum of the two arguments // int length = Math.min(len_a, available); int firstLen = Math.min(length, buffer_d.length - read_position_d); int secondLen = length - firstLen; System.arraycopy(buffer_d, read_position_d, buf_a, off_a, firstLen); if (secondLen > 0) { System.arraycopy(buffer_d, 0, buf_a, off_a+firstLen, secondLen); read_position_d = secondLen; } else { read_position_d += length; } // if the read pointer is equal to the buffer length, // it means the read pointer has reached the end of // the circular buffer and so set the read pointer to // the beginning of the buffer. // if (read_position_d == buffer_d.length) { read_position_d = 0; } // set the read pointer to current pointer // curr_position_d = read_position_d; return length; } else if (available == 0) { return 0; } } return -1; } /** * Skip Objects. This method should be used by the consumer when * it does not care to examine some number of Objects. This * method will block until some Objects are available, or there * will be no more Objects available. * * @param n_a the number of Objects to skip * * @return the number of Objects actually skipped * * @throws IllegalArgumentException if n is negative. * @throws InterruptedException if the thread is inturrupted while * waiting. */ public long skip(long n_a) throws InterruptedException, IllegalArgumentException { while (true) { synchronized (this) { // get the available number of data to be read // int available = available(); if (available > 0) { // get the minimum of the two arguments // int length = Math.min((int)n_a, available); int firstLen = Math.min(length, buffer_d.length - read_position_d); int secondLen = length - firstLen; if (secondLen > 0) { read_position_d = secondLen; } else { read_position_d += length; } // if the read pointer is equal to the buffer // length, it means the read pointer has reached // the end of the circular buffer and so set the // read pointer to the beginning of the buffer. // if (read_position_d == buffer_d.length) { read_position_d = 0; } return length; // if the recording is zero and there are no // available data then return zero // } else if (input_done_d) { return 0; } } Thread.sleep(100); } } /** * This method should be used by the producer to signal to the * consumer that the producer is done producing objects and that * the consumer should stop asking for objects once it has used up * buffered objects. Once the producer has signaled that it is * done, further write() invocations will cause an * IllegalStateException to be thrown. Calling done() multiple * times, however, has no effect. */ public boolean done() { synchronized (this) { // set the input_done_d to true indicating the end of // recording // input_done_d = true; } return true; } /** * Fill this buffer with array of Objects. This method should be * called by the producer. If the buffer allows blocking writes, * this method will block until all the data has been written * rather than throw a BufferOverflowException. * * @param buf_a Array of Objects to be written * * @throws BufferOverflowException if buffer does not allow * blocking writes and the buffer is full. If the exception is * thrown, no data will have been written since the buffer was set * to be non-blocking. * @throws IllegalStateException if done() has been called. * @throws InterruptedException if the write is interrupted. */ public void write(Object[] buf_a) throws BufferOverflowException, IllegalStateException, InterruptedException { write(buf_a, 0, buf_a.length); } /** * Fill this buffer with a portion of an array of Objects. This * method should be called by the producer. If the buffer allows * blocking writes, this method will block until all the data has * been written rather than throw an IOException. * * @param buf_a array of Objects * @param off_a offset from which to start writing Objects * @param len_a number of Objects to write * * @throws BufferOverflowException if buffer does not allow * blocking writes and the buffer is full. If the exception is * thrown, no data will have been written since the buffer was set * to be non-blocking. * @throws IllegalStateException if done() has been called. * @throws InterruptedException if the write is interrupted. */ public void write(Object[] buf_a, int off_a, int len_a) throws BufferOverflowException, IllegalStateException, InterruptedException { while (len_a > 0) { synchronized (AudioCircularBuffer.this) { int spaceLeft = spaceLeft(); // if the length of the circular buffer is infinite // and the spaceleft is less than what needs to be // written then resize the buffer // while (infinite_d && spaceLeft < len_a) { resize(); spaceLeft = spaceLeft(); } // if not blocking write and the spaceleft is less // than the length of the buffer to be written // if (!blocking_write_d && spaceLeft < len_a) throw new BufferOverflowException(); int realLen = Math.min(len_a, spaceLeft); int firstLen = Math.min(realLen, buffer_d.length - write_position_d); int secondLen = Math.min(realLen - firstLen, buffer_d.length - read_position_d - 1); int written = firstLen + secondLen; // array copy the buffer into the circular buffer // if (firstLen > 0) { System.arraycopy(buf_a, off_a, buffer_d, write_position_d, firstLen); } if (secondLen > 0) { System.arraycopy(buf_a, off_a + firstLen, buffer_d, 0, secondLen); write_position_d = secondLen; } else { write_position_d += written; } if (write_position_d == buffer_d.length) { write_position_d = 0; } off_a += written; len_a -= written; } // if more data needs to be written and there is no space // left in the circular buffer then sleep for sometime // hoping that the data from the circular buffer will be // written and there will be space to write the data to // the circular buffer // if (len_a > 0) { Thread.sleep(100); } } } /** * Add a single Object to this buffer. This method should be * called by the producer. If the buffer allows blocking writes, * this method will block until all the data has been written * rather than throw an IOException. * * @param o_a object to be written. * * @throws BufferOverflowException if buffer does not allow * blocking writes and the buffer is full. If the exception is * thrown, no data will have been written since the buffer was set * to be non-blocking. * @throws IllegalStateException if done() has been called. * @throws InterruptedException if the write is interrupted. */ public void write(Object o_a) throws BufferOverflowException, IllegalStateException, InterruptedException { boolean written = false; while (!written) { synchronized (AudioCircularBuffer.this) { if (input_done_d) throw new IllegalStateException("AudioCircularBuffer.done() has been called," + "AudioCircularBuffer.write() failed."); int spaceLeft = spaceLeft(); // if the length of the circular buffer is infinite // and the spaceleft is less than one then resize the // circular buffer // while (infinite_d && spaceLeft < 1) { resize(); spaceLeft = spaceLeft(); } // if the circular buffer is full then throw an buffer // overflow exception // if (!blocking_write_d && spaceLeft < 1) throw new BufferOverflowException(); // if space is left then write to the circular buffer // if (spaceLeft > 0) { buffer_d[write_position_d] = o_a; write_position_d++; if (write_position_d == buffer_d.length) { write_position_d = 0; } written = true; } } // if data couldnot be written sleep for some time and try // again // if (!written) { Thread.sleep(100); } } } //------------------------------------------------------------------------- // // class-specific public methods: // position methods // //------------------------------------------------------------------------- /** * Check whether the buffer is empty. If the read and the write * pointer are both zero then the circular buffer is empty. * * @return a boolean value indicating whether the circular buffer * is empty or not. */ public boolean resetCurr() { // reset the current pointer position to that of the read // pointer's position // curr_position_d = read_position_d; return true; } /** * move the current pointer to the desired index. * * @param offset_a the offset which gives the number of indeces * the current pointer should be moved. * * @return a boolean value indicating whether the circular buffer * is empty or not. */ public boolean seekCurr(int offset_a) { long capacity = getCapacity(); // current pointer should reside between read pointer and // write pointer // if ((offset_a > 0 && offset_a > getNumForward()) || (offset_a < 0 && offset_a < (-1 * getNumBackward()))) { System.out.println("Exception in seekCurr method"); return false; } // compute // curr_position_d += offset_a; if (curr_position_d > capacity - 1) { curr_position_d -= capacity; } else if ((long)curr_position_d < 0) { curr_position_d += capacity; } return true; } /** * Move the read read pointer forward. The offset is given by the * num_read_a * * @param num_read_a the offset that gives the number of indeces * the read pointer should be moved. * * @return a boolean value indicating the status. */ public boolean setRead(int num_read_a) { // read pointer can't move backward // if (num_read_a < 0) { System.out.println("The read pointer cannot be moved backwards."); return false; } if (getNumElements() == num_read_a) { // reset the circular buffer to be empty // clear(); } else { // get capacity // int capacity = getCapacity(); // compute // int tmp_read_idx = read_position_d + num_read_a; if (tmp_read_idx > capacity - 1) { tmp_read_idx -= capacity; } // move current pointer if necessary to make sure it is // still in the proper range // if (getNumBackward() < num_read_a) { curr_position_d = tmp_read_idx; } // move the read pointer // read_position_d = tmp_read_idx; } return true; } //------------------------------------------------------------------------ // // class-specific public methods: // error handler code // //------------------------------------------------------------------------ /** * Does the error handling whenever an exception gets thrown. It * prints the exception and also prints the trace of methods from * where the error originated. * * @param e an object of exception class * * @return a boolean value indicating status */ public boolean errorHandler(Exception e) { // print the Exception that occurred // System.out.println("Exception in ISIPAudioInterface: " + e); // print the the trace of methods from where the error // originated // e.printStackTrace(); // exit gracefully // return true; } //----------------------------------------------------------------------- // // debug method for this class // //----------------------------------------------------------------------- /** * A method used to print out debug information for this class * * @return string to represent this object */ public String toString() { // define a string // String result = "The capacity of the circular buffer " + getCapacity() +"\n"; result += "The read pointer position " + read_position_d + "\n"; result += "The write pointer position " + write_position_d + "\n"; result += "The current pointer position " + curr_position_d + "\n"; result += "The bytes available to be read " + getNumElements() + "\n"; result += "The spaceleft on the circular buffer " + getSpaceLeft() + "\n"; return result; } //------------------------------------------------------------------------- // // class-specific other private methods: // // //------------------------------------------------------------------------- /** * double the size of the buffer. * * @return void */ private void resize() { // create new buffer // Object[] newBuffer = new Object[buffer_d.length * 2]; int available = available(); if (read_position_d <= write_position_d) { // any space between the read and the first write needs to // be saved. In this case it is all in one piece. // int length = write_position_d - read_position_d; System.arraycopy(buffer_d, read_position_d, newBuffer, 0, length); } else { int length1 = buffer_d.length - read_position_d; System.arraycopy(buffer_d, read_position_d, newBuffer, 0, length1); int length2 = write_position_d; System.arraycopy(buffer_d, 0, newBuffer, length1, length2); } // point the buffer to new buffer // buffer_d = newBuffer; read_position_d = 0; write_position_d = available; } /** * Space available in the buffer which can be written. It can be * noted that the space of the write pointer is not taken into * account. * * @return integer value that returns the spaceleft in the * circular buffer. */ private int spaceLeft() { if (write_position_d < read_position_d) { // any space between the first write and the read except // one Object is available. In this case it is all in one // piece. // return (read_position_d - write_position_d - 1); } else { // space at the beginning and end. // return ((buffer_d.length - 1) - (write_position_d - read_position_d)); } } /** * Objects available for reading. * * @return integer value of the number of elements that need to be * read from the circular buffer. */ private int available() { if (read_position_d <= write_position_d) { // any space between the first read and the first write is // available. In this case i is all in one piece. // return (write_position_d - read_position_d); } else { // space at the beginning and end. // return (buffer_d.length - (read_position_d - write_position_d)); } } //------------------------------------------------------------------------- // // main diagnostic method for this class // //------------------------------------------------------------------------- /** * Diagnostic method for this class * * @param args arguments from the command line */ public static void main (String[] args) { // declare a circular buffer // AudioCircularBuffer cb_d = new AudioCircularBuffer(100); // create a Object buffer // Object [] temp_1 = new Object[99]; // assign values to the elements in the buffer // for (int i = 0; i < 99; i++) { Integer temp = new Integer(i); temp_1[i] = (Object) temp; } try { // write the buffer to the circular buffer // cb_d.write(temp_1); } catch (Exception e) { System.out.println("EXCEPTION DURING WRITE."); } // print the debug information // System.out.println(cb_d); // check whether the circular buffer is full // if(cb_d.isFull()) { System.out.println("The circular buffer is full."); } Object [] temp_2 = new Object[99]; // read from the circular buffer // cb_d.read(temp_2); // print the debug information // System.out.println(cb_d); // check if the circular buffer is empty // if(cb_d.isEmpty()) { System.out.println("The circular buffer is empty."); } } }