202 lines
7.2 KiB
Java
202 lines
7.2 KiB
Java
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<byte[][]> 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;
|
|
}
|
|
}
|