/* * Copyright (c) 2012 Michael Zucchi * * This programme is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This programme 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this programme. If not, see <http://www.gnu.org/licenses/>. */ package au.notzed.fxperiment; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.logging.Level; import java.util.logging.Logger; import javafx.application.Platform; import javafx.scene.image.ImageView; import javafx.scene.image.PixelFormat; import javafx.scene.image.WritableImage; import au.notzed.jjmpeg.AVPlane; import au.notzed.jjmpeg.exception.AVIOException; import au.notzed.jjmpeg.exception.AVInvalidCodecException; import au.notzed.jjmpeg.exception.AVInvalidStreamException; import au.notzed.jjmpeg.io.JJMediaReader; import au.notzed.jjmpeg.io.JJMediaReader.JJReaderVideo; /** * * @author notzed */ public class VideoThread extends Thread { final static String tag = "videoThread"; String path; boolean cancelled = false; WritableImage image; int width; int height; // For use by parent ImageView imageView; public VideoThread(String path) { this.path = path; } /** * too lazy for callback stuff, override this in subclass for state * transition * * @param image */ public void imageCreated(WritableImage image) { } public void cancel() throws InterruptedException { cancelled = true; interrupt(); // join(); } ByteBuffer frameCopy; @Override public void run() { int framecount = 2; // If we only have one frame (i.e. a still image), just run once and // quit. while (!cancelled && framecount > 1) { long start = -1; long pts0 = -1; JJMediaReader mr = null; try { mr = new JJMediaReader(path); JJReaderVideo vs = mr.openFirstVideoStream(); // width = vs.getWidth(); // height = vs.getHeight(); width = 256; height = 144; if (image == null) { image = new WritableImage(width, height); Platform.runLater(new Runnable() { @Override public void run() { imageCreated(image); } }); frameCopy = ByteBuffer.allocateDirect(width * height * 4) .order(ByteOrder.nativeOrder()); } vs.setOutputFormat(au.notzed.jjmpeg.PixelFormat.PIX_FMT_BGRA, width, height); framecount = 0; while (!cancelled && mr.readFrame() != null) { try { AVPlane data = vs.getOutputFrame().getPlaneAt(0, au.notzed.jjmpeg.PixelFormat.PIX_FMT_ARGB, width, height); framecount++; // So ... we need to copy the frame I guess (or do some // go to sleep thing until the gui thread returns) synchronized (frameCopy) { frameCopy.put(data.data); data.data.rewind(); frameCopy.rewind(); } Platform.runLater(new Runnable() { @Override public void run() { // TODO: race on thread exit synchronized (frameCopy) { image.getPixelWriter() .setPixels( 0, 0, width, height, PixelFormat .getIntArgbPreInstance(), frameCopy.asIntBuffer(), width); } } }); long pts = vs.convertPTS(mr.getPTS()); long now = System.currentTimeMillis(); if (start == -1) { start = now; pts0 = pts; } else { long diff; diff = (pts - pts0) - (now - start); diff = Math.min(diff, 1000); if (diff > 0) sleep(diff); } } catch (InterruptedException ex) { break; } } } catch (AVInvalidStreamException ex) { Logger.getLogger(tag).log(Level.SEVERE, null, ex); } catch (AVIOException ex) { Logger.getLogger(tag).log(Level.SEVERE, null, ex); } catch (AVInvalidCodecException ex) { Logger.getLogger(tag).log(Level.SEVERE, null, ex); } finally { if (mr != null) mr.dispose(); } } } }