/* * 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.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; /** * The Bistream class is responsible for parsing * an MPEG audio bitstream. * * REVIEW: 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 = 433; /** * 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 framebuffer 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; /** * Audio header position in stream. */ private int header_pos = 0; /** * */ 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 byte[] rawid3v2 = null; private boolean firstframe = true; /** * Construct a IBitstream that reads data from a * given InputStream. * * @param in The InputStream to read from. */ public Bitstream(InputStream in) { if (in==null) throw new NullPointerException("in"); in = new BufferedInputStream(in); loadID3v2(in); firstframe = true; //source = new PushbackInputStream(in, 1024); source = new PushbackInputStream(in, BUFFER_INT_SIZE*4); closeFrame(); //current_frame_number = -1; //last_frame_number = -1; } /** * Return position of the first audio header. * @return size of ID3v2 tag frames. */ public int header_pos() { return header_pos; } /** * Load ID3v2 frames. * @param in MP3 InputStream. * @author JavaZOOM */ private void loadID3v2(InputStream in) { int size = -1; try { // Read ID3v2 header (10 bytes). in.mark(10); size = readID3v2Header(in); header_pos = size; } catch (IOException e) {} finally { try { // Unread ID3v2 header (10 bytes). in.reset(); } catch (IOException e) {} } // Load ID3v2 tags. try { if (size > 0) { rawid3v2 = new byte[size]; in.read(rawid3v2,0,rawid3v2.length); } } catch (IOException e) {} } /** * Parse ID3v2 tag header to find out size of ID3v2 frames. * @param in MP3 InputStream * @return size of ID3v2 frames + header * @throws IOException * @author JavaZOOM */ private int readID3v2Header(InputStream in) throws IOException { byte[] id3header = new byte[4]; int size = -10; in.read(id3header,0,3); // Look for ID3v2 if ( (id3header[0]=='I') && (id3header[1]=='D') && (id3header[2]=='3')) { in.read(id3header,0,3); int majorVersion = id3header[0]; int revision = id3header[1]; in.read(id3header,0,4); size = (int) (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]); } return (size+10); } /** * Return raw ID3v2 frames + header. * @return ID3v2 InputStream or null if ID3v2 frames are not available. */ public InputStream getRawID3v2() { if (rawid3v2 == null) return null; else { ByteArrayInputStream bain = new ByteArrayInputStream(rawid3v2); return bain; } } /** * Close the Bitstream. * @throws BitstreamException */ public void close() throws BitstreamException { try { source.close(); } catch (IOException ex) { throw newBitstreamException(STREAM_ERROR, ex); } } /** * 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 == true) { result.parseVBR(frame_bytes); firstframe = false; } } catch (BitstreamException ex) { if ((ex.getErrorCode()==INVALIDFRAME)) { // Try to skip this frame. //System.out.println("INVALIDFRAME"); 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.read_header(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] << 0) & 0x000000FF); try { source.unread(syncbuf, 0, read); } catch (IOException ex) { } boolean sync = false; switch (read) { case 0: sync = true; break; case 4: sync = isSyncMark(headerstring, syncmode, syncword); break; } return sync; } // 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. */ int syncHeader(byte syncmode) throws BitstreamException { boolean sync; int headerstring; // read additional 2 bytes int bytesRead = readBytes(syncbuf, 0, 3); if (bytesRead!=3) throw newBitstreamException(STREAM_EOF, null); headerstring = ((syncbuf[0] << 16) & 0x00FF0000) | ((syncbuf[1] << 8) & 0x0000FF00) | ((syncbuf[2] << 0) & 0x000000FF); do { headerstring <<= 8; if (readBytes(syncbuf, 3, 1)!=1) throw newBitstreamException(STREAM_EOF, null); headerstring |= (syncbuf[3] & 0x000000FF); sync = isSyncMark(headerstring, syncmode, syncword); } while (!sync); //current_frame_number++; //if (last_frame_number < current_frame_number) last_frame_number = current_frame_number; return headerstring; } public boolean isSyncMark(int headerstring, int syncmode, int word) { boolean sync = false; if (syncmode == INITIAL_SYNC) { //sync = ((headerstring & 0xFFF00000) == 0xFFF00000); 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 = 0; 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; // Check ID3v1 TAG (True only if last frame). //for (int t=0;t<(byteread.length)-2;t++) //{ // if ((byteread[t]=='T') && (byteread[t+1]=='A') && (byteread[t+2]=='G')) // { // System.out.println("ID3v1 detected at offset "+t); // throw newBitstreamException(INVALIDFRAME, null); // } //} for (int k=0;k>> (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; } // E.B : Check that ? //((short[])&returnvalue)[0] = ((short[])wordpointer + 1)[0]; //wordpointer++; // Added by me! //((short[])&returnvalue + 1)[0] = ((short[])wordpointer)[0]; 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 set_syncword(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. * * @exception 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 newBitstreamException(STREAM_ERROR, ex); } return totalBytesRead; } }