package org.jcodec.api;
import org.jcodec.api.specific.AVCMP4Adaptor;
import org.jcodec.api.specific.ContainerAdaptor;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.JCodecUtil;
import org.jcodec.common.Format;
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;
import java.io.File;
import java.io.IOException;
import java.lang.NullPointerException;
import java.lang.ThreadLocal;
import java.nio.ByteBuffer;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Extracts frames from a movie into uncompressed images suitable for
* processing.
*
* Supports going to random points inside of a movie ( seeking ) by frame number
* of by second.
*
* NOTE: Supports only AVC ( H.264 ) in MP4 ( ISO BMF, QuickTime ) at this
* point.
*
* @author The JCodec project
*
*/
@Deprecated
public class FrameGrab {
private SeekableDemuxerTrack videoTrack;
private ContainerAdaptor decoder;
private ThreadLocal<int[][]> buffers;
private static int _detectKeyFrame(DemuxerTrack videoTrack, int start) throws IOException {
int[] seekFrames = videoTrack.getMeta().getSeekFrames();
if (seekFrames == null)
return start;
int prev = seekFrames[0];
for (int i = 1; i < seekFrames.length; i++) {
if (seekFrames[i] > start)
break;
prev = seekFrames[i];
}
return prev;
}
private static ContainerAdaptor _detectDecoder(SeekableDemuxerTrack sdt, Packet frame) throws JCodecException {
DemuxerTrackMeta meta = sdt.getMeta();
switch (meta.getCodec()) {
case H264:
return new AVCMP4Adaptor(meta);
default:
throw new UnsupportedFormatException("Codec is not supported");
}
}
static ContainerAdaptor detectDecoder(SeekableDemuxerTrack sdt) throws IOException, JCodecException {
int curFrame = (int) sdt.getCurFrame();
int keyFrame = _detectKeyFrame(sdt, curFrame);
sdt.gotoFrame(keyFrame);
Packet frame = sdt.nextFrame();
ContainerAdaptor decoder = _detectDecoder(sdt, frame);
return decoder;
}
public static FrameGrab createFrameGrab(SeekableByteChannel _in) throws IOException, JCodecException {
ByteBuffer header = ByteBuffer.allocate(65536);
_in.read(header);
header.flip();
Format detectFormat = JCodecUtil.detectFormatBuffer(header);
SeekableDemuxerTrack videoTrack = null;
switch (detectFormat) {
case MOV:
MP4Demuxer d1 = new MP4Demuxer(_in);
videoTrack = (SeekableDemuxerTrack)d1.getVideoTrack();
break;
case MPEG_PS:
throw new UnsupportedFormatException("MPEG PS is temporarily unsupported.");
case MPEG_TS:
throw new UnsupportedFormatException("MPEG TS is temporarily unsupported.");
default:
throw new UnsupportedFormatException("Container format is not supported by JCodec");
}
return new FrameGrab(videoTrack, detectDecoder(videoTrack));
}
public FrameGrab(SeekableDemuxerTrack videoTrack, ContainerAdaptor decoder) {
if (decoder == null || videoTrack == null) {
throw new NullPointerException();
}
this.buffers = new ThreadLocal<int[][]>();
this.videoTrack = videoTrack;
this.decoder = decoder;
}
private SeekableDemuxerTrack sdt() throws JCodecException {
if (!(videoTrack instanceof SeekableDemuxerTrack))
throw new JCodecException("Not a seekable track");
return (SeekableDemuxerTrack) videoTrack;
}
/**
* Position frame grabber to a specific second in a movie. As a result the
* next decoded frame will be precisely at the requested second.
*
* WARNING: potentially very slow. Use only when you absolutely need precise
* seek. Tries to seek to exactly the requested second and for this it might
* have to decode a sequence of frames from the closes key frame. Depending
* on GOP structure this may be as many as 500 frames.
*
* @param second
* @return
* @throws IOException
* @throws JCodecException
*/
public FrameGrab seekToSecondPrecise(double second) throws IOException, JCodecException {
sdt().seek(second);
decodeLeadingFrames();
return this;
}
/**
* Position frame grabber to a specific frame in a movie. As a result the
* next decoded frame will be precisely the requested frame number.
*
* WARNING: potentially very slow. Use only when you absolutely need precise
* seek. Tries to seek to exactly the requested frame and for this it might
* have to decode a sequence of frames from the closes key frame. Depending
* on GOP structure this may be as many as 500 frames.
*
* @param frameNumber
* @return
* @throws IOException
* @throws JCodecException
*/
public FrameGrab seekToFramePrecise(int frameNumber) throws IOException, JCodecException {
sdt().gotoFrame(frameNumber);
decodeLeadingFrames();
return this;
}
/**
* Position frame grabber to a specific second in a movie.
*
* Performs a sloppy seek, meaning that it may actually not seek to exact
* second requested, instead it will seek to the closest key frame
*
* NOTE: fast, as it just seeks to the closest previous key frame and
* doesn't try to decode frames in the middle
*
* @param second
* @return
* @throws IOException
* @throws JCodecException
*/
public FrameGrab seekToSecondSloppy(double second) throws IOException, JCodecException {
sdt().seek(second);
goToPrevKeyframe();
return this;
}
/**
* Position frame grabber to a specific frame in a movie
*
* Performs a sloppy seek, meaning that it may actually not seek to exact
* frame requested, instead it will seek to the closest key frame
*
* NOTE: fast, as it just seeks to the closest previous key frame and
* doesn't try to decode frames in the middle
*
* @param frameNumber
* @return
* @throws IOException
* @throws JCodecException
*/
public FrameGrab seekToFrameSloppy(int frameNumber) throws IOException, JCodecException {
sdt().gotoFrame(frameNumber);
goToPrevKeyframe();
return this;
}
private void goToPrevKeyframe() throws IOException, JCodecException {
sdt().gotoFrame(detectKeyFrame((int) sdt().getCurFrame()));
}
private void decodeLeadingFrames() throws IOException, JCodecException {
SeekableDemuxerTrack sdt = sdt();
int curFrame = (int) sdt.getCurFrame();
int keyFrame = detectKeyFrame(curFrame);
sdt.gotoFrame(keyFrame);
Packet frame = sdt.nextFrame();
while (frame.getFrameNo() < curFrame) {
decoder.decodeFrame(frame, getBuffer());
frame = sdt.nextFrame();
}
sdt.gotoFrame(curFrame);
}
private int[][] getBuffer() {
int[][] buf = buffers.get();
if (buf == null) {
buf = decoder.allocatePicture();
buffers.set(buf);
}
return buf;
}
private int detectKeyFrame(int start) throws IOException {
int[] seekFrames = videoTrack.getMeta().getSeekFrames();
if (seekFrames == null)
return start;
int prev = seekFrames[0];
for (int i = 1; i < seekFrames.length; i++) {
if (seekFrames[i] > start)
break;
prev = seekFrames[i];
}
return prev;
}
/**
* Get frame at current position in JCodec native image
*
* @return
* @throws IOException
*/
public Picture getNativeFrame() throws IOException {
Packet frame = videoTrack.nextFrame();
if (frame == null)
return null;
return decoder.decodeFrame(frame, getBuffer());
}
/**
* Gets info about the media
*
* @return
*/
public MediaInfo getMediaInfo() {
return decoder.getMediaInfo();
}
/**
* Get frame at a specified frame number as JCodec image
*
* @param file
* @param second
* @return
* @throws IOException
* @throws JCodecException
*/
public static Picture getFrameFromChannel(SeekableByteChannel file, int frameNumber)
throws JCodecException, IOException {
return createFrameGrab(file).seekToFramePrecise(frameNumber).getNativeFrame();
}
/**
* Get frame at a specified frame number as JCodec image
*
* @param file
* @param second
* @return
* @throws IOException
* @throws JCodecException
*/
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);
}
}
/**
* Get frame at a specified second as JCodec image
*
* @param file
* @param second
* @return
* @throws IOException
* @throws JCodecException
*/
public static Picture getFrameAtSecFromChannel(SeekableByteChannel file, double second)
throws JCodecException, IOException {
return createFrameGrab(file).seekToSecondPrecise(second).getNativeFrame();
}
/**
* Get frame at a specified second as JCodec image
*
* @param file
* @param second
* @return
* @throws IOException
* @throws JCodecException
*/
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 SeekableDemuxerTrack getVideoTrack() {
return videoTrack;
}
public ContainerAdaptor getDecoder() {
return decoder;
}
}