package com.robonobo.plugin.mp3;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Super simple mp3 file parser. Should be quite efficient.
*
* Given readable byte channel, each call to nextFrame() will try to read an Mp3Frame.
* The process is lossless, so unknown frames are still returned as such, so the output == input
*
* @see http://www.multiweb.cz/twoinches/MP3inside.htm
* @author ray
*
*/
public class Mp3Parser {
Log log = LogFactory.getLog(getClass());
ReadableByteChannel in;
ByteBuffer buffer = ByteBuffer.allocate(4096);
ByteBuffer frameBuffer = ByteBuffer.allocate(4096);
// Mp3Frame nextFrame = new Mp3Frame(frameBuffer);
long realByteOffset = 0;
long frameOffset = 0;
long timeOffset = 0;
public Mp3Parser(ReadableByteChannel in) {
this.in = in;
}
public void seek(long offset) throws IOException {
ByteBuffer b = ByteBuffer.allocate(1);
buffer.clear();
int read = 0;
int totalRead=0;
while(read!=-1 && totalRead<offset) {
read=in.read(b);
totalRead+=read;
}
}
public Frame nextFrame() throws IOException {
// read more data into the buffer,
in.read(buffer);
buffer.limit(buffer.position());
// rewind to start for the next batch of parsing
buffer.rewind();
Frame nextFrame = findNextFrame();
if(nextFrame != null) {
// set the frame duration explicitly as the number of bytes copied
nextFrame.frameLength = copyToFrameBuffer();
// update the byte offset by the amount of data copied
realByteOffset+=nextFrame.frameLength;
frameOffset++;
if(nextFrame instanceof Mp3Frame)
timeOffset+=((Mp3Frame)nextFrame).getTimeLength();
//log.debug("Next frame: " + nextFrame);
}
return nextFrame;
}
protected Frame findNextFrame() {
Frame nextFrame = null;
byte b;
int passed = 0;
try {
while(buffer.remaining()>0) {
b = (byte)buffer.get();
switch(passed) {
case 0:
if((b & 0xFF) == 0xFF) { // all bits are 1
// potentially start of header, keep checking
passed=1;
nextFrame = new Mp3Frame(frameBuffer);
}
break;
case 1:
if((b >>> 5 & 0x7) == 0x7) { // first three bits are 111
passed=2;
((Mp3Frame)nextFrame).version = (b & 0x18) >>> 3; // 0b00011000
((Mp3Frame)nextFrame).layer = (b & 0x6) >>> 1; // 0b00000110
((Mp3Frame)nextFrame).crc = ((b & 0x1) == 1) ? true : false; // 0b00000001
((Mp3Frame)nextFrame).byteOffset = realByteOffset;
((Mp3Frame)nextFrame).frameOffset = frameOffset;
((Mp3Frame)nextFrame).timeOffset = timeOffset;
} else {
passed=0;
nextFrame = null;
}
break;
case 2:
if((b >>> 2 & 0x3F ) != 0x3F) { // first six bits are NOT 111111
// seems genuine
passed=3;
((Mp3Frame)nextFrame).bitrate = (b & 0xF0) >>> 4; // 0b11110000
((Mp3Frame)nextFrame).samplingRate = (b &0xC) >>> 2; // 0b00001100
((Mp3Frame)nextFrame).padding = ((b & 0x2) >>> 1 == 1)? true : false; // 0b00000010
((Mp3Frame)nextFrame).priv = (b & 0x1) == 1 ? true : false; // 0b00000001
} else {
passed=0;
nextFrame = null;
}
break;
case 3:
((Mp3Frame)nextFrame).channel = (b & 0xC0) >>> 6; // 0b11000000
((Mp3Frame)nextFrame).modeext = (b & 0x30) >>> 4; // 0b00110000
((Mp3Frame)nextFrame).copyright = ((b & 0x8) >>> 3 == 1) ? true:false; // 0b00001000
((Mp3Frame)nextFrame).original = ((b & 0x4) >>> 2 == 1) ? true:false; //0b00000100
((Mp3Frame)nextFrame).emphasis = (b & 0x3);
// if this is the first frame, then everything before this is unknown data
if(buffer.position()>4) {
buffer.position(buffer.position()-4);
passed=0; // parse this bit again
return new UnknownFrame(frameBuffer,realByteOffset,frameOffset,timeOffset);
} else {
passed=4;
}
break;
case 4:
if(nextFrame instanceof Mp3Frame) {
// if(((Mp3Frame)nextFrame).getCalculatedFrameLength()<4) {
// int i = 0;
// }
// we are in data mode
// when we reach frameSize, return nextFrame
if(buffer.position()==((Mp3Frame)nextFrame).getCalculatedFrameLength()) {
passed=0;
return nextFrame;
}
}
}
}
}
catch(BufferUnderflowException e) {
int i = 0;
if(passed!=4 && buffer.position()>0) // if it has not been recognized as an mp3 frame, and there is data, send a unknown frame
return new UnknownFrame(frameBuffer,realByteOffset,frameOffset,timeOffset);
}
// if we have read some bytes, but found not header, return as a unknown frame
if(buffer.position()>0)
return new UnknownFrame(frameBuffer,realByteOffset,frameOffset,timeOffset);
return nextFrame;
}
protected int copyToFrameBuffer() {
int pos = buffer.position();
int lim = buffer.limit();
buffer.rewind();
buffer.limit(pos);
frameBuffer.clear();
frameBuffer.limit(pos);
frameBuffer.put(buffer);
frameBuffer.rewind();
// restore previous pos and limit
buffer.position(pos);
buffer.limit(lim);
// get rid of read data
buffer.compact();
return pos;
// pos is now at the end of the data, so we can write immediately
}
}