package org.jcodec.codecs.mpeg12;
import org.jcodec.common.io.NIOUtils;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.List;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Pulls frames from MPEG elementary stream
*
* @author The JCodec project
*
*/
public class SegmentReader {
private ReadableByteChannel channel;
private ByteBuffer buf;
protected int curMarker;
private int fetchSize;
protected boolean done;
private long pos;
private int bytesInMarker;
private int bufferIncrement = 1 << 15; // 32k
public SegmentReader(ReadableByteChannel channel, int fetchSize) throws IOException {
this.channel = channel;
this.fetchSize = fetchSize;
buf = NIOUtils.fetchFromChannel(channel, 4);
pos = buf.remaining();
curMarker = buf.getInt();
bytesInMarker = 4;
}
// FOR TESTCASES
public int getBufferIncrement() {
return bufferIncrement;
}
// FOR TESTCASES
public void setBufferIncrement(int bufferIncrement) {
this.bufferIncrement = bufferIncrement;
}
public static enum State {
MORE_DATA, DONE, STOP
}
/**
* Reads one full segment till the next marker. Will read as much data as
* the provided buffer fits, if the provided buffer doesn't fit all data
* will return MORE_DATA.
*
* @param out
* @return
* @throws IOException
*/
public final State readToNextMarkerPartial(ByteBuffer out) throws IOException {
if (done)
return State.STOP;
int skipOneMarker = curMarker >= 0x100 && curMarker <= 0x1ff ? 1 : 0;
int written = out.position();
do {
while (buf.hasRemaining()) {
if (curMarker >= 0x100 && curMarker <= 0x1ff) {
if (skipOneMarker == 0) {
return State.DONE;
}
--skipOneMarker;
}
if (!out.hasRemaining())
return State.MORE_DATA;
out.put((byte) (curMarker >>> 24));
curMarker = (curMarker << 8) | (buf.get() & 0xff);
}
buf = NIOUtils.fetchFromChannel(channel, fetchSize);
pos += buf.remaining();
} while (buf.hasRemaining());
written = out.position() - written;
if (written > 0 && curMarker >= 0x100 && curMarker <= 0x1ff)
return State.DONE;
for (; bytesInMarker > 0 && out.hasRemaining();) {
out.put((byte) (curMarker >>> 24));
curMarker = (curMarker << 8);
--bytesInMarker;
if (curMarker >= 0x100 && curMarker <= 0x1ff)
return State.DONE;
}
if (bytesInMarker == 0) {
done = true;
return State.STOP;
} else {
return State.MORE_DATA;
}
}
/**
* Reads one full segment till the next marker. Will allocate the necessary
* buffer to hold the full segment. Internally uses a growing collection of
* smaller buffers since the segment size is intitially unkwnown.
*
* @return
* @throws IOException
*/
public ByteBuffer readToNextMarkerNewBuffer() throws IOException {
if (done)
return null;
List<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
readToNextMarkerBuffers(buffers);
return NIOUtils.combineBuffers(buffers);
}
public void readToNextMarkerBuffers(List<ByteBuffer> buffers) throws IOException {
State state;
do {
ByteBuffer curBuffer = ByteBuffer.allocate(bufferIncrement);
state = readToNextMarkerPartial(curBuffer);
curBuffer.flip();
buffers.add(curBuffer);
} while (state == State.MORE_DATA);
}
public final boolean readToNextMarker(ByteBuffer out) throws IOException {
State state = readToNextMarkerPartial(out);
if(state == State.MORE_DATA)
throw new BufferOverflowException();
return state == State.DONE;
}
public final boolean skipToMarker() throws IOException {
if (done)
return false;
do {
while (buf.hasRemaining()) {
curMarker = (curMarker << 8) | (buf.get() & 0xff);
if (curMarker >= 0x100 && curMarker <= 0x1ff) {
return true;
}
}
buf = NIOUtils.fetchFromChannel(channel, fetchSize);
pos += buf.remaining();
} while (buf.hasRemaining());
done = true;
return false;
}
public final boolean read(ByteBuffer out, int length) throws IOException {
if (done)
return false;
do {
while (buf.hasRemaining()) {
if (length-- == 0)
return true;
out.put((byte) (curMarker >>> 24));
curMarker = (curMarker << 8) | (buf.get() & 0xff);
}
buf = NIOUtils.fetchFromChannel(channel, fetchSize);
pos += buf.remaining();
} while (buf.hasRemaining());
out.putInt(curMarker);
done = true;
return false;
}
public final long curPos() {
return pos - buf.remaining() - 4;
}
}