package org.jcodec.api; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import org.jcodec.api.specific.AVCMP4Adaptor; import org.jcodec.api.specific.ContainerAdaptor; import org.jcodec.common.Codec; import org.jcodec.common.DemuxerTrackMeta; import org.jcodec.common.Format; import org.jcodec.common.JCodecUtil; import org.jcodec.common.SeekableDemuxerTrack; import org.jcodec.common.io.FileChannelWrapper; import org.jcodec.common.io.NIOUtils; import org.jcodec.common.io.SeekableByteChannel; import org.jcodec.common.model.Packet; import org.jcodec.common.model.Picture; import org.jcodec.containers.mp4.demuxer.MP4Demuxer; public class FrameGrab { private SeekableDemuxerTrack videoTrack; private ContainerAdaptor decoder; private final ThreadLocal buffers; public static FrameGrab createFrameGrab(SeekableByteChannel _in) throws IOException, JCodecException { SeekableDemuxerTrack videoTrack_; ByteBuffer header = ByteBuffer.allocate(65536); _in.read(header); header.flip(); Format detectFormat = JCodecUtil.detectFormatBuffer(header); if (detectFormat == null) throw new UnsupportedFormatException("Could not detect the format of the input video."); if (Format.MOV == detectFormat) { MP4Demuxer d1 = MP4Demuxer.createMP4Demuxer(_in); videoTrack_ = (SeekableDemuxerTrack)d1.getVideoTrack(); } else { if (Format.MPEG_PS == detectFormat) throw new UnsupportedFormatException("MPEG PS is temporarily unsupported."); if (Format.MPEG_TS == detectFormat) throw new UnsupportedFormatException("MPEG TS is temporarily unsupported."); throw new UnsupportedFormatException("Container format is not supported by JCodec"); } FrameGrab fg = new FrameGrab(videoTrack_, detectDecoder(videoTrack_)); fg.decodeLeadingFrames(); return fg; } public FrameGrab(SeekableDemuxerTrack videoTrack, ContainerAdaptor decoder) { this.videoTrack = videoTrack; this.decoder = decoder; this.buffers = new ThreadLocal<>(); } private SeekableDemuxerTrack sdt() throws JCodecException { if (!(this.videoTrack instanceof SeekableDemuxerTrack)) throw new JCodecException("Not a seekable track"); return this.videoTrack; } public FrameGrab seekToSecondPrecise(double second) throws IOException, JCodecException { sdt().seek(second); decodeLeadingFrames(); return this; } public FrameGrab seekToFramePrecise(int frameNumber) throws IOException, JCodecException { sdt().gotoFrame((long)frameNumber); decodeLeadingFrames(); return this; } public FrameGrab seekToSecondSloppy(double second) throws IOException, JCodecException { sdt().seek(second); goToPrevKeyframe(); return this; } public FrameGrab seekToFrameSloppy(int frameNumber) throws IOException, JCodecException { sdt().gotoFrame((long)frameNumber); goToPrevKeyframe(); return this; } private void goToPrevKeyframe() throws IOException, JCodecException { sdt().gotoFrame((long)detectKeyFrame((int)sdt().getCurFrame())); } private void decodeLeadingFrames() throws IOException, JCodecException { SeekableDemuxerTrack sdt = sdt(); int curFrame = (int)sdt.getCurFrame(); int keyFrame = detectKeyFrame(curFrame); sdt.gotoFrame((long)keyFrame); Packet frame = sdt.nextFrame(); if (this.decoder == null) this.decoder = detectDecoder(sdt); while (frame.getFrameNo() < (long)curFrame) { this.decoder.decodeFrame(frame, getBuffer()); frame = sdt.nextFrame(); } sdt.gotoFrame((long)curFrame); } private byte[][] getBuffer() { byte[][] buf = this.buffers.get(); if (buf == null) { buf = this.decoder.allocatePicture(); this.buffers.set(buf); } return buf; } private int detectKeyFrame(int start) throws IOException { int[] seekFrames = this.videoTrack.getMeta().getSeekFrames(); if (seekFrames == null) return start; int prev = seekFrames[0]; for (int i = 1; i < seekFrames.length && seekFrames[i] <= start; i++) prev = seekFrames[i]; return prev; } private static ContainerAdaptor detectDecoder(SeekableDemuxerTrack videoTrack) throws JCodecException { DemuxerTrackMeta meta = videoTrack.getMeta(); if (Codec.H264 == meta.getCodec()) return new AVCMP4Adaptor(meta); throw new UnsupportedFormatException("Codec is not supported"); } public PictureWithMetadata getNativeFrameWithMetadata() throws IOException { Packet frame = this.videoTrack.nextFrame(); if (frame == null) return null; Picture picture = this.decoder.decodeFrame(frame, getBuffer()); return new PictureWithMetadata(picture, frame.getPtsD(), frame.getDurationD(), this.videoTrack.getMeta().getOrientation()); } public Picture getNativeFrame() throws IOException { Packet frame = this.videoTrack.nextFrame(); if (frame == null) return null; return this.decoder.decodeFrame(frame, getBuffer()); } public static Picture getFrameAtSec(File file, double second) throws IOException, JCodecException { FileChannelWrapper ch = null; try { ch = NIOUtils.readableChannel(file); return createFrameGrab(ch).seekToSecondPrecise(second).getNativeFrame(); } finally { NIOUtils.closeQuietly(ch); } } public static Picture getFrameFromChannelAtSec(SeekableByteChannel file, double second) throws JCodecException, IOException { return createFrameGrab(file).seekToSecondPrecise(second).getNativeFrame(); } public static Picture getFrameFromFile(File file, int frameNumber) throws IOException, JCodecException { FileChannelWrapper ch = null; try { ch = NIOUtils.readableChannel(file); return createFrameGrab(ch).seekToFramePrecise(frameNumber).getNativeFrame(); } finally { NIOUtils.closeQuietly(ch); } } public static Picture getFrameFromChannel(SeekableByteChannel file, int frameNumber) throws JCodecException, IOException { return createFrameGrab(file).seekToFramePrecise(frameNumber).getNativeFrame(); } public static Picture getNativeFrameAtFrame(SeekableDemuxerTrack vt, ContainerAdaptor decoder, int frameNumber) throws IOException, JCodecException { return new FrameGrab(vt, decoder).seekToFramePrecise(frameNumber).getNativeFrame(); } public static Picture getNativeFrameAtSec(SeekableDemuxerTrack vt, ContainerAdaptor decoder, double second) throws IOException, JCodecException { return new FrameGrab(vt, decoder).seekToSecondPrecise(second).getNativeFrame(); } public static Picture getNativeFrameSloppy(SeekableDemuxerTrack vt, ContainerAdaptor decoder, int frameNumber) throws IOException, JCodecException { return new FrameGrab(vt, decoder).seekToFrameSloppy(frameNumber).getNativeFrame(); } public static Picture getNativeFrameAtSecSloppy(SeekableDemuxerTrack vt, ContainerAdaptor decoder, double second) throws IOException, JCodecException { return new FrameGrab(vt, decoder).seekToSecondSloppy(second).getNativeFrame(); } public MediaInfo getMediaInfo() { return this.decoder.getMediaInfo(); } public SeekableDemuxerTrack getVideoTrack() { return this.videoTrack; } public ContainerAdaptor getDecoder() { return this.decoder; } }