/* * This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com> * Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/) */ /* * @(#)CircularBuffer.java 1.21 03/01/23 * * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package com.sun.media.sound; //$$fb todo: real synchronization, i.e. multiple reads, but write exclusive //$$fb todo: shouldn't use a sample counter that keeps on counting /** * Represents a circular buffer. Also handles sign and byte order coversions. * * @version 1.21, 03/01/23 * @author Kara Kytle */ class CircularBuffer { boolean convertSign = false; boolean convertByteOrder = false; private byte[] array; // ay: Upgraded to longs because int was overflowing after a few // hours and throwing OutOfMemoryError ! private long bytesWritten = 0; private long bytesRead = 0; private long end = -1; /** * Creates a circular buffer with the indicated capacity in bytes. */ CircularBuffer(int size, boolean convertSign, boolean convertByteOrder) { array = new byte[size]; this.convertSign = convertSign; this.convertByteOrder = convertByteOrder; } /** * Writes len bytes to the circular buffer. * Does *not* block. Returns the number of * bytes actually written. */ synchronized int write(byte[] b, int off, int len) { if (Printer.verbose) Printer.verbose("CircularBuffer: write: b: " + b + " off: " + off + " len: " + len); // check for end of stream if (end >= 0 || len == 0) { return 0; } // this is how many bytes we can write to the circular buffer. int totalBytesToWrite = Math.min(len, bytesAvailableToWrite()); // this is how many bytes we have left to write. int bytesRemainingToWrite = totalBytesToWrite; // this is the number of bytes to write right now int bytesToWrite; while (bytesRemainingToWrite > 0) { bytesToWrite = bytesRemainingToWrite; int writeIndex = getWriteIndex(); // write up to the end of the circular buffer bytesToWrite = Math.min(bytesToWrite, (array.length - writeIndex)); System.arraycopy(b, off, array, writeIndex, bytesToWrite); //$$fb fix the bug that the array passed to SourceDataLine.write() gets modified // if the data has to be converted for the native format // do any sign and byte order conversions if (convertSign) { Toolkit.getUnsigned8(array, writeIndex, bytesToWrite); } else if (convertByteOrder) { Toolkit.getByteSwapped(array, writeIndex, bytesToWrite - (bytesToWrite % 2)); } bytesWritten += bytesToWrite; bytesRemainingToWrite -= bytesToWrite; off += bytesToWrite; if (bytesToWrite == 0) { Thread.yield(); } } if (Printer.verbose) Printer.verbose("CircularBuffer: write: returning: " + totalBytesToWrite); return totalBytesToWrite; } /** * Reads len bytes from the circular buffer. * Does *not* block. Returns the number of * bytes actually read. */ synchronized int read(byte[] b, int off, int len) { if (Printer.verbose) Printer.verbose("CircularBuffer: read: b: " + b + " off: " + off + " len: " + len); // check for end of stream if (end >= 0) { if (bytesRead >= bytesWritten) { return -1; } } // this is how many bytes we can read from the circular buffer. int totalBytesToRead = Math.min(len, bytesAvailableToRead()); // this is how many bytes we have left to read. int bytesRemainingToRead = totalBytesToRead; // this is the number of bytes to read right now int bytesToRead; while (bytesRemainingToRead > 0) { bytesToRead = bytesRemainingToRead; // read up to the end of the circular buffer bytesToRead = Math.min(bytesToRead, (array.length - getReadIndex())); System.arraycopy(array, getReadIndex(), b, off, bytesToRead); bytesRead += bytesToRead; bytesRemainingToRead -= bytesToRead; off += bytesToRead; if (bytesToRead == 0) { Thread.yield(); } } if (Printer.verbose) Printer.verbose("CircularBuffer: read: returning: " + totalBytesToRead); return totalBytesToRead; } /** * This will write the current data over old data if the amount of new data * exceeds the amount of available space in the circular buffer. It also * handles the case where the amount of data to be written exceeds the total * length of the buffer, though this generally is *bad*. * The number of bytes written can be thought of as len, always, because this * method always writes data to the end of the requested dataset, even if it * must dump earlier data to do so. * * Returns the number of data bytes dumped in the process (*not* the number of * bytes written). * * $$kk: 08.04.99: we can dump data here. should have overflow event / exception / * notification mechanism? */ synchronized int writeover(byte[] b, int off, int len) { int bytesDumped = 0; int totalLen = getByteLength(); // if the len requested is greater than the total buffer size, adjust // the offset and flush the circular buffer; we'll just write the last // totalLen bytes. if (len > totalLen) { bytesDumped = len - totalLen; off += bytesDumped; len = totalLen; flush(); } else if (len > bytesAvailableToWrite()) { // if the len requested is greater than the number of bytes available // for writing, skip the difference. bytesDumped = len - bytesAvailableToWrite(); skip(bytesDumped); } // now write the data into the circular buffer write(b, off, len); // return the number of bytes dumped return bytesDumped; } /** * Clear the buffer by marking everything as read. */ synchronized void flush() { bytesRead = bytesWritten; } /* * Drain the buffer */ void drain() { long targetBytesWritten = bytesWritten; if (Printer.debug) Printer.debug("CircularBuffer.drain(): bytesWritten="+bytesWritten +" bytesRead="+bytesRead +" bytesAvailableToRead()="+bytesAvailableToRead() +" getByteLength()="+getByteLength()); //$$fb 2001-10-09: add a counter so that it does not block forever // part of fix for bug #4517739: Using JMF to playback sound clips blocks virtual machine int maxIterations=2000/5; // 2 seconds while (bytesRead < targetBytesWritten && (maxIterations--)>0) { try { Thread.sleep(5); } catch (InterruptedException e) { } } } /** * Skips len bytes. Return the number of bytes actually skipped. */ synchronized int skip(int len) { // this is how many bytes we can read from the circular buffer. int bytesToSkip = Math.min(len, bytesAvailableToRead()); bytesRead += bytesToSkip; return bytesToSkip; } /** * Mark the end of the circular buffer. After all bytes written * at this time have been read, the circular buffer returns -1 for * all read calls. */ synchronized void markEnd() { end = bytesWritten; } /** * Obtains the number of bytes in the buffer available for reading. */ /*private*/ int bytesAvailableToRead() { return (int) (bytesWritten - bytesRead); } /** * Obtains the number of bytes in the buffer available for writing. */ /*private*/ int bytesAvailableToWrite() { return ( (end >= 0) ? 0 : (array.length - bytesAvailableToRead()) ); } /** * Obtains the length in bytes of the circular buffer. */ int getByteLength() { return (array.length); } private int getReadIndex() { return (int) (bytesRead % array.length); } private int getWriteIndex() { return (int) (bytesWritten % array.length); } } // class CircularBuffer