package org.jcodec.containers.flv;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import org.jcodec.common.Codec;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.LongArrayList;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.TrackType;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.Packet;
import org.jcodec.containers.flv.FLVTag.Type;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Demuxer frontend for FLV, track based wrapper
*
* @author The JCodec project
*
*/
public class FLVTrackDemuxer {
private static final int MAX_CRAWL_DISTANCE_SEC = 10;
private FLVReader demuxer;
private FLVDemuxerTrack video;
private FLVDemuxerTrack audio;
private LinkedList<FLVTag> packets;
private SeekableByteChannel _in;
public static class FLVDemuxerTrack implements SeekableDemuxerTrack {
private Type type;
private int curFrame;
private Codec codec;
private LongArrayList framePositions;
private byte[] codecPrivate;
private FLVTrackDemuxer demuxer;
public FLVDemuxerTrack(FLVTrackDemuxer demuxer, Type type) throws IOException {
this.framePositions = LongArrayList.createLongArrayList();
this.demuxer = demuxer;
this.type = type;
FLVTag frame = demuxer.nextFrameI(type, false);
codec = frame.getTagHeader().getCodec();
}
@Override
public Packet nextFrame() throws IOException {
FLVTag frame = demuxer.nextFrameI(type, true);
framePositions.add(frame.getPosition());
return toPacket(frame);
}
public Packet prevFrame() throws IOException {
FLVTag frame = demuxer.prevFrameI(type, true);
// framePositions.add(nextFrameI.getPosition());
return toPacket(frame);
}
public Packet pickFrame() throws IOException {
FLVTag frame = demuxer.nextFrameI(type, false);
// framePositions.add(nextFrameI.getPosition());
return toPacket(frame);
}
private Packet toPacket(FLVTag frame) {
return null;
}
@Override
public DemuxerTrackMeta getMeta() {
TrackType t = type == Type.VIDEO ? TrackType.VIDEO : TrackType.AUDIO;
return new DemuxerTrackMeta(t, codec, 0, null, 0, ByteBuffer.wrap(codecPrivate), null, null);
}
@Override
public boolean gotoFrame(long i) throws IOException {
if (i >= framePositions.size())
return false;
demuxer.resetToPosition(framePositions.get((int) i));
return true;
}
@Override
public boolean gotoSyncFrame(long i) {
throw new RuntimeException();
}
@Override
public long getCurFrame() {
return curFrame;
}
@Override
public void seek(double second) throws IOException {
demuxer.seekI(second);
}
}
public FLVTrackDemuxer(SeekableByteChannel _in) throws IOException {
this.packets = new LinkedList<FLVTag>();
this._in = _in;
_in.setPosition(0);
demuxer = new FLVReader(_in);
video = new FLVDemuxerTrack(this, Type.VIDEO);
audio = new FLVDemuxerTrack(this, Type.AUDIO);
}
private void resetToPosition(long position) throws IOException {
_in.setPosition(position);
demuxer.reset();
packets.clear();
}
private void seekI(double second) throws IOException {
packets.clear();
FLVTag base;
while ((base = demuxer.readNextPacket()) != null && base.getPtsD() == 0)
;
_in.setPosition(base.getPosition() + 0x100000);
demuxer.reposition();
FLVTag off = demuxer.readNextPacket();
int byteRate = (int) ((off.getPosition() - base.getPosition()) / (off.getPtsD() - base.getPtsD()));
long offset = base.getPosition() + (long) ((second - base.getPtsD()) * byteRate);
_in.setPosition(offset);
demuxer.reposition();
// TODO: the implementation is incorrect
// 5 reposition attempts
for (int i = 0; i < 5; ++i) {
FLVTag pkt = demuxer.readNextPacket();
double distance = second - pkt.getPtsD();
if (distance > 0 && distance < MAX_CRAWL_DISTANCE_SEC) {
// Read to the right frame
System.out.println("Crawling forward: " + distance);
FLVTag testPkt;
while ((testPkt = demuxer.readNextPacket()) != null && testPkt.getPtsD() < second)
;
if (testPkt != null)
packets.add(pkt);
return;
} else if (distance < 0 && distance > -MAX_CRAWL_DISTANCE_SEC) {
// Read back to the frame
System.out.println("Overshoot by: " + (-distance));
_in.setPosition(pkt.getPosition() + (long) ((distance - 1) * byteRate));
demuxer.reposition();
}
}
}
private FLVTag nextFrameI(Type type, boolean remove) throws IOException {
for (Iterator<FLVTag> it = packets.iterator(); it.hasNext();) {
FLVTag pkt = it.next();
if (pkt.getType() == type) {
if (remove)
it.remove();
return pkt;
}
}
FLVTag pkt;
while ((pkt = demuxer.readNextPacket()) != null && pkt.getType() != type)
packets.add(pkt);
if (!remove)
packets.add(pkt);
return pkt;
}
private FLVTag prevFrameI(Type type, boolean remove) throws IOException {
for (ListIterator<FLVTag> it = packets.listIterator(); it.hasPrevious();) {
FLVTag pkt = it.previous();
if (pkt.getType() == type) {
if (remove)
it.remove();
return pkt;
}
}
FLVTag pkt;
while ((pkt = demuxer.readPrevPacket()) != null && pkt.getType() != type)
packets.add(0, pkt);
if (!remove)
packets.add(0, pkt);
return pkt;
}
public DemuxerTrack[] getTracks() {
return new DemuxerTrack[] { video, audio };
}
public DemuxerTrack getVideoTrack() {
return video;
}
public DemuxerTrack getAudioTrack() {
return video;
}
}