/*
* 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 org.geogebra.desktop.sound.mp3transform;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
/**
* This class is responsible for parsing an MPEG audio bitstream.
*/
public final class Bitstream {
/**
* Synchronization control constant for the initial synchronization to the
* start of a frame.
*/
static final byte INITIAL_SYNC = 0;
/**
* Synchronization control constant for non-initial frame synchronizations.
*/
static final byte STRICT_SYNC = 1;
/**
* Maximum size of the frame buffer. max. 1730 bytes per frame: 144 *
* 384kbit/s / 32000 Hz + 2 Bytes CRC
*/
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 final byte[] frameBytes = 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 getBits()
*/
private int bitIndex;
private int syncWord;
private boolean singleChMode;
private static 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[] syncBuffer = new byte[4];
private byte[] rawID3v2 = null;
private boolean firstFrame = true;
public Bitstream(InputStream in) {
source = new PushbackInputStream(in, BUFFER_INT_SIZE * 4);
loadID3v2();
closeFrame();
}
private void loadID3v2() {
int size = -1;
try {
// read ID3v2 header
source.mark(10);
size = readID3v2Header();
} catch (IOException e) {
// ignore
} finally {
try {
// unread ID3v2 header
source.reset();
} catch (IOException e) {
// ignore
}
}
// load ID3v2 tags.
try {
if (size > 0) {
rawID3v2 = new byte[size];
this.readBytes(rawID3v2, 0, rawID3v2.length);
}
} catch (IOException e) {
// ignore
}
}
/**
* 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() throws IOException {
byte[] buff = new byte[4];
int size = -10;
readBytes(buff, 0, 3);
if (buff[0] == 'I' && buff[1] == 'D' && buff[2] == '3') {
readBytes(buff, 0, 3);
readBytes(buff, 0, 4);
size = (buff[0] << 21) + (buff[1] << 14) + (buff[2] << 7) + buff[3];
}
return size + 10;
}
/**
* 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.
* @throws IOException
*/
public Header readFrame() throws IOException {
try {
Header result = readNextFrame();
if (firstFrame) {
result.parseVBR(frameBytes);
firstFrame = false;
}
return result;
} catch (EOFException e) {
return null;
}
}
private Header readNextFrame() throws IOException {
if (frameSize == -1) {
while (true) {
boolean ok = header.readHeader(this);
if (ok) {
break;
}
closeFrame();
}
}
return header;
}
void unreadFrame() throws IOException {
if (wordPointer == -1 && bitIndex == -1 && frameSize > 0) {
source.unread(frameBytes, 0, frameSize);
}
}
public void closeFrame() {
frameSize = -1;
wordPointer = -1;
bitIndex = -1;
}
/**
* Determines if the next 4 bytes of the stream represent a frame header.
*/
boolean isSyncCurrentPosition(int syncMode) throws IOException {
int read = readBytes(syncBuffer, 0, 4);
int headerString = ((syncBuffer[0] << 24) & 0xFF000000)
| ((syncBuffer[1] << 16) & 0x00FF0000)
| ((syncBuffer[2] << 8) & 0x0000FF00)
| ((syncBuffer[3] << 0) & 0x000000FF);
try {
source.unread(syncBuffer, 0, read);
} catch (IOException ex) {
// ignore
}
if (read == 0) {
return true;
} else if (read == 4) {
return isSyncMark(headerString, syncMode, syncWord);
} else {
return false;
}
}
/**
* 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.
*
* @param syncMode
*/
int syncHeader(byte syncMode) throws IOException {
boolean sync;
int headerString;
// read additional 2 bytes
int bytesRead = readBytes(syncBuffer, 0, 3);
if (bytesRead != 3) {
throw new EOFException();
}
headerString = ((syncBuffer[0] << 16) & 0x00FF0000)
| ((syncBuffer[1] << 8) & 0x0000FF00)
| ((syncBuffer[2] << 0) & 0x000000FF);
do {
headerString <<= 8;
if (readBytes(syncBuffer, 3, 1) != 1) {
throw new EOFException();
}
headerString |= (syncBuffer[3] & 0x000000FF);
sync = isSyncMark(headerString, syncMode, syncWord);
} while (!sync);
return headerString;
}
private boolean isSyncMark(int headerString, int syncMode, int word) {
boolean sync = false;
if (syncMode == INITIAL_SYNC) {
sync = ((headerString & 0xFFE00000) == 0xFFE00000); // SZD: MPEG 2.5
} else {
sync = ((headerString & 0xFFF80C00) == word) && (((headerString
& 0x000000C0) == 0x000000C0) == singleChMode);
}
// 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 readFrameData(int byteSize) throws IOException {
int numread = 0;
numread = readFully(frameBytes, 0, byteSize);
frameSize = byteSize;
wordPointer = -1;
bitIndex = -1;
return numread;
}
/**
* Parses the data previously read with readFrameData().
*/
void parseFrame() {
// Convert bytes read to int
int b = 0;
byte[] byteRead = frameBytes;
int byteSize = frameSize;
for (int k = 0; k < byteSize; k = k + 4) {
byte b0 = 0;
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 <= numberOfBits <= 16)
*/
int getBits(int numberOfBits) {
int returnValue = 0;
int sum = bitIndex + numberOfBits;
// TODO There is a problem here, wordpointer could be -1 ?!
if (wordPointer < 0) {
System.out.println("wordPointer < 0");
wordPointer = 0;
}
if (sum <= 32) {
// all bits contained in *wordpointer
returnValue = (frameBuffer[wordPointer] >>> (32 - sum))
& BITMASK[numberOfBits];
bitIndex += numberOfBits;
if (bitIndex == 32) {
bitIndex = 0;
wordPointer++;
}
return returnValue;
}
int right = (frameBuffer[wordPointer] & 0x0000FFFF);
wordPointer++;
int left = (frameBuffer[wordPointer] & 0xFFFF0000);
returnValue = ((right << 16) & 0xFFFF0000)
| ((left >>> 16) & 0x0000FFFF);
returnValue >>>= 48 - sum;
returnValue &= BITMASK[numberOfBits];
bitIndex = sum - 32;
return returnValue;
}
/**
* Set the word we want to sync the header to. In Big-Endian byte order
*/
void setSyncWord(int s) {
syncWord = s & 0xFFFFFF3F;
singleChMode = ((s & 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 Exception
* 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 IOException {
// TODO does not in fact throw an exception, probably return not
// required
int read = 0;
while (len > 0) {
int bytesRead = source.read(b, offs, len);
if (bytesRead == -1) {
while (len-- > 0) {
b[offs++] = 0;
}
break;
}
read = read + bytesRead;
offs += bytesRead;
len -= bytesRead;
}
return read;
}
/**
* Simlar to readFully, but doesn't throw exception when EOF is reached.
*/
private int readBytes(byte[] b, int offs, int len) throws IOException {
int totalBytesRead = 0;
while (len > 0) {
int bytesRead = source.read(b, offs, len);
if (bytesRead == -1) {
break;
}
totalBytesRead += bytesRead;
offs += bytesRead;
len -= bytesRead;
}
return totalBytesRead;
}
}