package org.jcodec.player.filters; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.locks.ReentrantLock; import org.jcodec.common.JCodecUtil; import org.jcodec.common.VideoDecoder; import org.jcodec.common.model.Frame; import org.jcodec.common.model.Packet; import org.jcodec.common.model.Picture; import org.jcodec.common.model.Rational; import org.jcodec.common.model.RationalLarge; import org.jcodec.common.model.Size; import org.jcodec.common.model.TapeTimecode; import org.jcodec.common.tools.Debug; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * @author The JCodec project * */ public class JCodecVideoSource implements VideoSource { private ThreadLocal<VideoDecoder> decoders = new ThreadLocal<VideoDecoder>(); private ExecutorService tp; private List<ByteBuffer> drain = new ArrayList<ByteBuffer>(); private MediaInfo.VideoInfo mi; private PacketSource src; private ReentrantLock seekLock = new ReentrantLock(); private TreeSet<Packet> reordering; public JCodecVideoSource(PacketSource src) throws IOException { Debug.println("Creating video source"); this.src = src; mi = (MediaInfo.VideoInfo) src.getMediaInfo(); reordering = createReordering(); tp = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setPriority(Thread.MIN_PRIORITY); return thread; } }); } private TreeSet<Packet> createReordering() { return new TreeSet<Packet>(new Comparator<Packet>() { public int compare(Packet o1, Packet o2) { return o1.getPts() > o2.getPts() ? 1 : (o1.getPts() == o2.getPts() ? 0 : -1); } }); } static int cnt = 0; @Override public Frame decode(int[][] buf) throws IOException { seekLock.lock(); Packet nextPacket; try { nextPacket = selectNextPacket(); } finally { seekLock.unlock(); } if (nextPacket == null) return null; final Future<Picture> job = tp.submit(new FrameCallable(nextPacket, buf)); Frame frm = new FutureFrame(job, new RationalLarge(nextPacket.getPts(), nextPacket.getTimescale()), new RationalLarge(nextPacket.getDuration(), nextPacket.getTimescale()), mi.getPAR(), (int) nextPacket.getFrameNo(), nextPacket.getTapeTimecode(), null); return frm; } public Packet selectNextPacket() throws IOException { fillReordering(); if (reordering.size() == 0) return null; Packet first = reordering.first(); if (first != null) reordering.remove(first); return first; } private void fillReordering() throws IOException { while (reordering.size() < 5) { Packet pkt = pickNextPacket(); if (pkt == null) break; reordering.add(pkt); } } public Packet pickNextPacket() throws IOException { ByteBuffer buffer; synchronized (drain) { if (drain.size() == 0) { drain.add(allocateBuffer()); } buffer = drain.remove(0); buffer.rewind(); } return src.getPacket(buffer); } public class FutureFrame extends Frame { private Future<Picture> job; public FutureFrame(Future<Picture> job, RationalLarge pts, RationalLarge duration, Rational pixelAspect, int frameNo, TapeTimecode tapeTimecode, List<String> messages) { super(null, pts, duration, pixelAspect, frameNo, tapeTimecode, messages); this.job = job; } @Override public boolean isAvailable() { return job.isDone(); } @Override public Picture getPic() { try { return job.get(); } catch (Exception e) { throw new RuntimeException(e); } } } class FrameCallable implements Callable<Picture> { private Packet pkt; private int[][] out; public FrameCallable(Packet pkt, int[][] out) { this.pkt = pkt; this.out = out; } public Picture call() { VideoDecoder decoder = decoders.get(); if (decoder == null) { decoder = JCodecUtil.getVideoDecoder(mi.getFourcc()); decoders.set(decoder); } Picture pic = decoder.decodeFrame(pkt.getData(), out); synchronized (drain) { drain.add(pkt.getData()); } return pic; } } @Override public boolean drySeek(RationalLarge sec) throws IOException { return src.drySeek(sec); } @Override public void seek(RationalLarge sec) throws IOException { seekLock.lock(); try { clearReordering(); src.seek(sec); fillReordering(); Iterator<Packet> it = reordering.iterator(); while (it.hasNext()) { if (it.next().getPtsR().lessThen(sec)) it.remove(); else break; } } finally { seekLock.unlock(); } } private void clearReordering() { TreeSet<Packet> old = reordering; reordering = createReordering(); for (Packet packet : old) { drain.add(packet.getData()); } } private ByteBuffer allocateBuffer() { Size dim = mi.getDim(); return ByteBuffer.allocate(dim.getWidth() * dim.getHeight() * 2); } public MediaInfo.VideoInfo getMediaInfo() throws IOException { return (MediaInfo.VideoInfo) src.getMediaInfo(); } public void close() throws IOException { src.close(); } @Override public void gotoFrame(int frame) { clearReordering(); src.gotoFrame(frame); } }