/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2013, Geomatys * * This library 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; * version 2.1 of the License. * * This library 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. */ package org.geotoolkit.display3d.phase; import com.jogamp.opengl.GL; import com.jogamp.opengl.util.awt.AWTGLReadBufferUtil; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.*; import java.util.logging.Level; import org.geotoolkit.display3d.Map3D; import org.geotoolkit.display3d.utils.TransformRGBtoYUV420; import org.jcodec.codecs.h264.H264Encoder; import org.jcodec.codecs.h264.H264Utils; import org.jcodec.common.NIOUtils; import org.jcodec.common.SeekableByteChannel; import org.jcodec.common.model.ColorSpace; import org.jcodec.common.model.Picture; import org.jcodec.containers.mp4.Brand; import org.jcodec.containers.mp4.MP4Packet; import org.jcodec.containers.mp4.TrackType; import org.jcodec.containers.mp4.muxer.FramesMP4MuxerTrack; import org.jcodec.containers.mp4.muxer.MP4Muxer; import org.jcodec.scale.AWTUtil; import org.jcodec.scale.Transform; /** * @author Thomas Rouby (Geomatys) */ public class VideoWriterPhase implements Phase { private final File videoFile; private final SeekableByteChannel ch; private final MP4Muxer muxer; private final FramesMP4MuxerTrack outTrack; private final ByteBuffer _out; private final H264Encoder encoder; private final Transform transform; private final int fps; private final List<ByteBuffer> spsList = new ArrayList<>(); private final List<ByteBuffer> ppsList = new ArrayList<>(); private Picture toEncode; private int frameNo; private boolean record = false; // private long videoStart = 0l; private Map3D map; private final int width; private final int height; public VideoWriterPhase(File fileToWrite, int width, int height, int fps) throws IOException { final File videoDirectory = fileToWrite.getParentFile(); if (!videoDirectory.exists()){ videoDirectory.mkdirs(); } this.videoFile = fileToWrite; this.width = width; this.height = height; this.fps = fps; this.ch = NIOUtils.writableFileChannel(fileToWrite); this.transform = new TransformRGBtoYUV420(0,0); muxer = new MP4Muxer(ch, Brand.MP4); this.outTrack = muxer.addTrackForCompressed(TrackType.VIDEO, fps); // second is FPS this.encoder = new H264Encoder(); this._out = ByteBuffer.allocate(width * height * 6); } public int getFPS(){ return fps; } public int getFrameNo(){ return frameNo; } public int getWidth(){ return width; } public int getHeight(){ return height; } @Override public void setMap(Map3D map) { this.map = map; } @Override public Map3D getMap() { return this.map; } @Override public void update(GL gl) { if (record){ // 2.0.2 method not valid in 2.0-rc11 AWTGLReadBufferUtil glReadBuffer = new AWTGLReadBufferUtil(gl.getGLProfile(), false); BufferedImage bufferedImage = glReadBuffer.readPixelsToBufferedImage(gl, true); // 2.0-rc11 method // final BufferedImage bufferedImage = Screenshot.readToBufferedImage(this.getMap().getCamera().getWidth(), this.getMap().getCamera().getHeight(), false); try { this.encodeImage(convertToType(bufferedImage, BufferedImage.TYPE_3BYTE_BGR)); } catch (Exception ex) { stopRecord(); if (this.getMap() != null) { this.getMap().getMonitor().exceptionOccured(ex, Level.WARNING); } else { System.out.println(ex.getMessage()); ex.printStackTrace(System.err); } } } } public void encodeImage(BufferedImage bi) throws IOException { if (toEncode == null) { toEncode = Picture.create(getWidth(), getHeight(), ColorSpace.YUV420); } // Perform conversion for (int i = 0; i < 3; i++) Arrays.fill(toEncode.getData()[i], 0); transform.transform(AWTUtil.fromBufferedImage(bi), toEncode); // Encode image into H.264 frame, the result is stored in '_out' buffer and return _out.clear(); ByteBuffer result = encoder.encodeFrame(_out, toEncode); // Based on the frame above form correct MP4 packet spsList.clear(); ppsList.clear(); H264Utils.encodeMOVPacket(result, spsList, ppsList); // Add packet to video track outTrack.addFrame(new MP4Packet(result, frameNo, fps, 1, frameNo, true, null, frameNo, 0)); frameNo++; } public void saveVideo() throws IOException { // Push saved SPS/PPS to a special storage in MP4 outTrack.addSampleEntry(H264Utils.createMOVSampleEntry(spsList, ppsList)); // Write MP4 header and finalize recording muxer.writeHeader(); NIOUtils.closeQuietly(ch); } public File getVideoFile() { return this.videoFile; } private BufferedImage convertToType(BufferedImage sourceImage, int targetType) { BufferedImage image; // if the source image is already the target type, return the source image if (sourceImage.getType() == targetType) { image = sourceImage; } // otherwise create a new image of the target type and draw the new image else { image = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), targetType); image.getGraphics().drawImage(sourceImage, 0, 0, null); } return image; } public void startRecord(){ record = true; } public void stopRecord(){ record = false; } }