/* * 11/19/04 1.0 moved to LGPL. * * 11/17/04 Uncomplete frames discarded. E.B, javalayer@javazoom.net * * 12/05/03 ID3v2 tag returned. E.B, javalayer@javazoom.net * * 12/12/99 Based on Ibitstream. Exceptions thrown on errors, * Temporary removed seek functionality. mdm@techie.com * * 02/12/99 : Java Conversion by E.B , javalayer@javazoom.net * * 04/14/97 : Added function prototypes for new syncing and seeking * mechanisms. Also made this file portable. Changes made by Jeff Tsay * * @(#) ibitstream.h 1.5, last edit: 6/15/94 16:55:34 * @(#) Copyright (C) 1993, 1994 Tobias Bading (bading@cs.tu-berlin.de) * @(#) Berlin University of Technology *----------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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 Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *---------------------------------------------------------------------- */ package javazoom.jl.decoder; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; import java.io.BufferedInputStream; /** * The <code>Bistream</code> class is responsible for parsing * an MPEG audio bitstream. * <p/> * <b>REVIEW:</b> much of the parsing currently occurs in the * various decoders. This should be moved into this class and associated * inner classes. */ public final class Bitstream implements BitstreamErrors { /** * Synchronization control constant for the initial * synchronization to the start of a frame. */ static byte INITIAL_SYNC = 0; /** * Synchronization control constant for non-initial frame * synchronizations. */ static byte STRICT_SYNC = 1; // max. 1730 bytes per frame: 144 * 384kbit/s / 32000 Hz + 2 Bytes CRC /** * Maximum size of the frame buffer. */ private static final int BUFFER_INT_SIZE = 1730; /** * The frame buffer that holds the data for the current frame. */ private final int[] framebuffer = new int[BUFFER_INT_SIZE]; /** * Number of valid bytes in the frame buffer. */ private int framesize; /** * The bytes read from the stream. */ private byte[] frame_bytes = new byte[BUFFER_INT_SIZE * 4]; /** * Index into <code>framebuffer</code> where the next bits are * retrieved. */ private int wordpointer; /** * Number (0-31, from MSB to LSB) of next bit for get_bits() */ private int bitindex; /** * The current specified syncword */ private int syncword; private boolean single_ch_mode; //private int current_frame_number; //private int last_frame_number; private final int bitmask[] = {0, // dummy 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, 0x0001FFFF}; private final PushbackInputStream source; private final Header header = new Header(); private final byte syncbuf[] = new byte[4]; private Crc16[] crc = new Crc16[1]; private boolean firstframe = true; /** * Construct a IBitstream that reads data from a * given InputStream. * * @param source The InputStream to read from. */ public Bitstream(InputStream source) { this.source = new PushbackInputStream( new BufferedInputStream(source, 30000), BUFFER_INT_SIZE); skipID3v2(); firstframe = true; closeFrame(); } /** * Find ID3v2 tag header and skip the tag. * * @return size of ID3v2 frames + header * @author JavaZOOM */ @SuppressWarnings({"ResultOfMethodCallIgnored"}) private void skipID3v2() { byte[] id3header = new byte[4]; try { source.read(id3header, 0, 3); // Look for ID3v2 if ((id3header[0] == 'I') && (id3header[1] == 'D') && (id3header[2] == '3')) { //skip some flags source.skip(3); //read the size source.read(id3header, 0, 4); int size = (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]); if (size > 0) source.skip(size); } else { source.unread(id3header, 0, 3); } } catch (IOException e) { e.printStackTrace(); } } /** * Close the Bitstream. */ public void close() { try { source.close(); } catch (IOException e) { e.printStackTrace(); } } /** * Reads and parses the next frame from the input source. * * @return the Header describing details of the frame read, * or null if the end of the stream has been reached. */ public Header readFrame() throws BitstreamException { Header result = null; try { result = readNextFrame(); // E.B, Parse VBR (if any) first frame. if (firstframe) { if (result.checkHeader(frame_bytes)) { closeFrame(); firstframe = false; return readFrame(); } firstframe = false; } } catch (BitstreamException ex) { if ((ex.getErrorCode() == INVALIDFRAME)) { // Try to skip this frame. try { closeFrame(); result = readNextFrame(); } catch (BitstreamException e) { if ((e.getErrorCode() != STREAM_EOF)) { // wrap original exception so stack trace is maintained. throw newBitstreamException(e.getErrorCode(), e); } } } else if ((ex.getErrorCode() != STREAM_EOF)) { // wrap original exception so stack trace is maintained. throw newBitstreamException(ex.getErrorCode(), ex); } } return result; } /** * Read next MP3 frame. * * @return MP3 frame header. * @throws BitstreamException */ private Header readNextFrame() throws BitstreamException { if (framesize == -1) { nextFrame(); } return header; } /** * Read next MP3 frame. * * @throws BitstreamException */ private void nextFrame() throws BitstreamException { // entire frame is read by the header class. header.readHeader(this, crc); } /** * Unreads the bytes read from the frame. * * @throws BitstreamException */ // REVIEW: add new error codes for this. public void unreadFrame() throws BitstreamException { if (wordpointer == -1 && bitindex == -1 && (framesize > 0)) { try { source.unread(frame_bytes, 0, framesize); } catch (IOException ex) { throw newBitstreamException(STREAM_ERROR); } } } /** * Close MP3 frame. */ public void closeFrame() { framesize = -1; wordpointer = -1; bitindex = -1; } /** * Determines if the next 4 bytes of the stream represent a * frame header. */ public boolean isSyncCurrentPosition(int syncmode) throws BitstreamException { int read = readBytes(syncbuf, 0, 4); int headerstring = ((syncbuf[0] << 24) & 0xFF000000) | ((syncbuf[1] << 16) & 0x00FF0000) | ((syncbuf[2] << 8) & 0x0000FF00) | ((syncbuf[3]) & 0x000000FF); try { source.unread(syncbuf, 0, 4); } catch (IOException e) { e.printStackTrace(); } switch (read) { case 0: return true; case 4: return isSyncMark(headerstring, syncmode, syncword); default: return false; } } // REVIEW: this class should provide inner classes to // parse the frame contents. Eventually, readBits will // be removed. public int readBits(int n) { return get_bits(n); } public int readCheckedBits(int n) { // REVIEW: implement CRC check. return get_bits(n); } protected BitstreamException newBitstreamException(int errorcode) { return new BitstreamException(errorcode, null); } protected BitstreamException newBitstreamException(int errorcode, Throwable throwable) { return new BitstreamException(errorcode, throwable); } /** * Get next 32 bits from bitstream. * They are stored in the headerstring. * syncmod allows Synchro flag ID * The returned value is False at the end of stream. */ public int syncHeader(byte syncmode) throws BitstreamException { boolean sync; int headerstring; // read additional 2 bytes int bytesRead = readBytes(syncbuf, 0, 3); if (bytesRead != 3) throw new BitstreamException(STREAM_EOF, null); headerstring = ((syncbuf[0] << 16) & 0x00FF0000) | ((syncbuf[1] << 8) & 0x0000FF00) | ((syncbuf[2]) & 0x000000FF); do { headerstring <<= 8; if (readBytes(syncbuf, 3, 1) != 1) throw new BitstreamException(STREAM_EOF, null); headerstring |= (syncbuf[3] & 0x000000FF); sync = isSyncMark(headerstring, syncmode, syncword); } while (!sync); return headerstring; } public boolean isSyncMark(int headerstring, int syncmode, int word) { boolean sync; if (syncmode == INITIAL_SYNC) { sync = ((headerstring & 0xFFE00000) == 0xFFE00000); // SZD: MPEG 2.5 } else { sync = (headerstring & 0xFFF80C00) == word && (headerstring & 0x000000C0) == 0x000000C0 == single_ch_mode; } // filter out invalid sample rate if (sync) sync = (((headerstring >>> 10) & 3) != 3); // filter out invalid layer if (sync) sync = (((headerstring >>> 17) & 3) != 0); // filter out invalid version if (sync) sync = (((headerstring >>> 19) & 3) != 1); return sync; } /** * Reads the data for the next frame. The frame is not parsed * until parse frame is called. */ int read_frame_data(int bytesize) throws BitstreamException { int numread; numread = readFully(frame_bytes, 0, bytesize); framesize = bytesize; wordpointer = -1; bitindex = -1; return numread; } /** * Parses the data previously read with read_frame_data(). */ void parse_frame() throws BitstreamException { // Convert Bytes read to int int b = 0; byte[] byteread = frame_bytes; int bytesize = framesize; for (int k = 0; k < bytesize; k = k + 4) { byte b0; byte b1 = 0; byte b2 = 0; byte b3 = 0; b0 = byteread[k]; if (k + 1 < bytesize) b1 = byteread[k + 1]; if (k + 2 < bytesize) b2 = byteread[k + 2]; if (k + 3 < bytesize) b3 = byteread[k + 3]; framebuffer[b++] = ((b0 << 24) & 0xFF000000) | ((b1 << 16) & 0x00FF0000) | ((b2 << 8) & 0x0000FF00) | (b3 & 0x000000FF); } wordpointer = 0; bitindex = 0; } /** * Read bits from buffer into the lower bits of an unsigned int. * The LSB contains the latest read bit of the stream. * (1 <= number_of_bits <= 16) */ public int get_bits(int number_of_bits) { int returnvalue; int sum = bitindex + number_of_bits; // E.B // There is a problem here, wordpointer could be -1 ?! if (wordpointer < 0) wordpointer = 0; // E.B : End. if (sum <= 32) { // all bits contained in *wordpointer returnvalue = (framebuffer[wordpointer] >>> (32 - sum)) & bitmask[number_of_bits]; // returnvalue = (wordpointer[0] >> (32 - sum)) & bitmask[number_of_bits]; if ((bitindex += number_of_bits) == 32) { bitindex = 0; wordpointer++; // added by me! } return returnvalue; } int Right = (framebuffer[wordpointer] & 0x0000FFFF); wordpointer++; int Left = (framebuffer[wordpointer] & 0xFFFF0000); returnvalue = ((Right << 16) & 0xFFFF0000) | ((Left >>> 16) & 0x0000FFFF); returnvalue >>>= 48 - sum; // returnvalue >>= 16 - (number_of_bits - (32 - bitindex)) returnvalue &= bitmask[number_of_bits]; bitindex = sum - 32; return returnvalue; } /** * Set the word we want to sync the header to. * In Big-Endian byte order */ void setSyncword(int syncword0) { syncword = syncword0 & 0xFFFFFF3F; single_ch_mode = ((syncword0 & 0x000000C0) == 0x000000C0); } /** * Reads the exact number of bytes from the source * input stream into a byte array. * * @param b The byte array to read the specified number * of bytes into. * @param offs The index in the array where the first byte * read should be stored. * @param len the number of bytes to read. * @throws BitstreamException is thrown if the specified * number of bytes could not be read from the stream. */ private int readFully(byte[] b, int offs, int len) throws BitstreamException { int nRead = 0; try { while (len > 0) { int bytesread = source.read(b, offs, len); if (bytesread == -1) { while (len-- > 0) { b[offs++] = 0; } break; //throw newBitstreamException(UNEXPECTED_EOF, new EOFException()); } nRead = nRead + bytesread; offs += bytesread; len -= bytesread; } } catch (IOException ex) { throw newBitstreamException(STREAM_ERROR, ex); } return nRead; } /** * Simlar to readFully, but doesn't throw exception when * EOF is reached. */ private int readBytes(byte[] b, int offs, int len) throws BitstreamException { int totalBytesRead = 0; try { while (len > 0) { int bytesread = source.read(b, offs, len); if (bytesread == -1) { break; } totalBytesRead += bytesread; offs += bytesread; len -= bytesread; } } catch (IOException ex) { throw new BitstreamException(STREAM_ERROR, ex); } return totalBytesRead; } public long getPosition() { try { return source.available(); } catch (IOException e) { e.printStackTrace(); } return 0; } }