/* * Copyright (c) 2011 Michael Zucchi * * This file is part of jjmpeg, a java binding to ffmpeg's libraries. * * jjmpeg is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * jjmpeg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with jjmpeg. If not, see <http://www.gnu.org/licenses/>. */ package au.notzed.jjmpeg.io; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.nio.ByteBuffer; import au.notzed.jjmpeg.AVCodec; import au.notzed.jjmpeg.AVCodecContext; import au.notzed.jjmpeg.AVFormatContext; import au.notzed.jjmpeg.AVFrame; import au.notzed.jjmpeg.AVPacket; import au.notzed.jjmpeg.AVPlane; import au.notzed.jjmpeg.AVRational; import au.notzed.jjmpeg.AVStream; import au.notzed.jjmpeg.PixelFormat; import au.notzed.jjmpeg.SwsContext; import au.notzed.jjmpeg.exception.AVDecodingError; import au.notzed.jjmpeg.exception.AVIOException; import au.notzed.jjmpeg.exception.AVInvalidCodecException; import au.notzed.jjmpeg.exception.AVInvalidStreamException; /** * High level interface for scanning video frames. * * This will deprecated in the future once JJMediaReader has the seeking stuff * in it. * * @author notzed */ @Deprecated public class JJVideoScanner { AVFormatContext format; AVCodecContext codecContext = null; int videoStream = -1; AVCodec codec; int swidth; int sheight; SwsContext scale; int height; int width; PixelFormat fmt; // timebase int tb_Num; int tb_Den; // start pts long startpts; // start ms long startms; // duration (estimated?) long duration; long durationms; // long seekid = -1; // AVFrame iframe; AVFrame oframe; AVPacket packet; /** * Use JJMediaReader instead. * * @param name * @throws AVInvalidStreamException * @throws AVIOException * @throws AVInvalidCodecException * @deprecated */ @Deprecated public JJVideoScanner(String name) throws AVInvalidStreamException, AVIOException, AVInvalidCodecException { // AVFormatContext.registerAll(); format = AVFormatContext.openInputFile(name); if (format.findStreamInfo() < 0) { throw new AVInvalidStreamException("No streams found"); } // find first video stream AVStream stream = null; int nstreams = format.getNBStreams(); for (int i = 0; i < nstreams; i++) { AVStream s = format.getStreamAt(i); codecContext = s.getCodec(); if (codecContext.getCodecType() == AVCodecContext.AVMEDIA_TYPE_VIDEO) { videoStream = i; stream = s; break; } } if (stream == null) { throw new AVInvalidStreamException("No video stream"); } System.out.printf("codec size %dx%d\n", codecContext.getWidth(), codecContext.getHeight()); System.out.println("codec id = " + codecContext.getCodecID()); // find decoder for the video stream codec = AVCodec.findDecoder(codecContext.getCodecID()); if (codec == null) { throw new AVInvalidCodecException("No video stream"); } System.out.println("opening codec"); codecContext.open(codec); System.out.println("pixel format: " + codecContext.getPixFmt()); iframe = AVFrame.create(); packet = AVPacket.create(); height = codecContext.getHeight(); width = codecContext.getWidth(); fmt = codecContext.getPixFmt(); swidth = width; sheight = height; oframe = AVFrame.create(PixelFormat.PIX_FMT_BGR24, swidth, sheight); scale = SwsContext.create(width, height, fmt, swidth, sheight, PixelFormat.PIX_FMT_BGR24, SwsContext.SWS_BILINEAR); AVRational tb = stream.getTimeBase(); tb_Num = tb.getNum(); tb_Den = tb.getDen(); startpts = stream.getStartTime(); startms = AVRational.starSlash(startpts * 1000, tb_Num, tb_Den); duration = stream.getDuration(); durationms = AVRational.starSlash(duration * 1000, tb_Num, tb_Den); System.out.println("startpts = " + startpts); System.out.println("startms = " + startms); System.out.println("duration = " + duration); System.out.println("durationms = " + durationms); } public void dispose() { scale.dispose(); oframe.dispose(); iframe.dispose(); packet.dispose(); codec.dispose(); codecContext.dispose(); format.dispose(); } /** * Set output rendered size. * * If this is changed, must re-call createImage(), getOutputFrame(), etc. * * @param swidth * @param sheight */ public void setSize(int swidth, int sheight) { oframe.dispose(); scale.dispose(); this.swidth = swidth; this.sheight = sheight; oframe = AVFrame.create(PixelFormat.PIX_FMT_BGR24, swidth, sheight); scale = SwsContext.create(width, height, fmt, swidth, sheight, PixelFormat.PIX_FMT_BGR24, SwsContext.SWS_BILINEAR); } public int getWidth() { return width; } public int getHeight() { return height; } public PixelFormat getPixelFormat() { return fmt; } /** * Retrieve duration of sequence, in milliseconds. * * @return */ public long getDurationMS() { return durationms; } /** * Get duration in timebase units (i.e. frames?) * * @return */ public long getDuration() { return duration; } /** * Convert the 'pts' provided to milliseconds relative to the start of the * video stream. * * @param pts * @return */ public long convertPTS(long pts) { return AVRational.starSlash(pts * 1000, tb_Num, tb_Den) - startms; } /** * Allocate an image suitable for readFrame() * * @return */ public BufferedImage createImage() { return new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); } /** * Frame used for scaled/format converted output. * * Can be used directly if using readFrame(target) * * @return */ public AVFrame getOutputFrame() { return oframe; } /** * Get raw format for images. * * @return */ public AVFormatContext getFormat() { return format; } long pts; /** * Retrieve (calculated) pts of the last frame decoded. * * Well be -1 at EOF * * @return */ public long getPTS() { return pts; } /** * Internal read the next decoded video frame. * * @return * @throws AVDecodingError */ AVFrame readAVFrame() throws AVDecodingError { pts = -1; while (format.readFrame(packet) >= 0) { try { // is this from the video stream? if (packet.getStreamIndex() == videoStream) { // decode video frame boolean frameFinished = codecContext.decodeVideo(iframe, packet); if (frameFinished) { pts = packet.getDTS(); // If seeking, attempt to get to the exact frame if (seekid != -1 && pts < seekid) { continue; } seekid = -1; return iframe; } } } finally { packet.freePacket(); } } return null; } /** * Read a raw frame into a scaled/format converted target frame. * * Target should be this.getFrame() or a copy of it. * * @param target * @return the internal pts. Use convertPTS() to convert to milliseconds. * @throws AVDecodingError */ public long readFrame(AVFrame target) throws AVDecodingError { // read packets until a whole frame is decoded AVFrame frame = readAVFrame(); if (frame != null) { scale.scale(frame, 0, height, target); return pts; } return -1; } /** * Read a frame into the target image. * * Image should be allocated using createFrame() * * @param dst * @return the internal pts. Use convertPTS() to convert to milliseconds. * @throws AVDecodingError */ public long readFrame(BufferedImage dst) throws AVDecodingError { long pt = readFrame(oframe); if (pt != -1) { AVPlane splane = oframe.getPlaneAt(0, PixelFormat.PIX_FMT_BGR24, swidth, sheight); byte[] data = ((DataBufferByte) dst.getRaster().getDataBuffer()) .getData(); splane.data.get(data, 0, Math.min(data.length, splane.data.capacity())); splane.data.rewind(); } return pt; } /** * Read the raw byte-byffer for the frame. * * Data will be in the input format. * * @return * @throws AVDecodingError */ public ByteBuffer readFrame() throws AVDecodingError { AVFrame af = readAVFrame(); if (af != null) { AVPlane splane = af.getPlaneAt(0, fmt, width, height); return splane.data; } else { return null; } } /** * Attempt to seek to the nearest millisecond. * * The next frame read should match the stamp. * * This only seeks to key frames * * @param stamp * @throws AVIOException */ public void seekMS(long stamp) throws AVIOException { int res; res = format.seekFile(-1, 0, stamp * 1000, stamp * 1000, 0); if (res < 0) { throw new AVIOException(res, "Cannot seek"); } codecContext.flushBuffers(); } /** * Seek in stream units * * @param stamp * @throws AVIOException */ public void seek(long stamp) throws AVIOException { int res; res = format.seekFile(videoStream, 0, stamp, stamp, 0); if (res < 0) { throw new AVIOException(res, "Cannot seek"); } seekid = stamp; codecContext.flushBuffers(); } }