javazoom.jl.decoder.Bitstream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sound Show documentation
Show all versions of sound Show documentation
Etyl's default sound module
The newest version!
/*
* 11/19/04 1.0 moved to LGPL.
*
* 11/17/04 Uncomplete frames discarded. E.B, [email protected]
*
* 12/05/03 ID3v2 tag returned. E.B, [email protected]
*
* 12/12/99 Based on Ibitstream. Exceptions thrown on errors,
* Temporary removed seek functionality. [email protected]
*
* 02/12/99 : Java Conversion by E.B , [email protected]
*
* 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 ([email protected])
* @(#) 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;
}
/**
* Similar 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;
}
}