package info.guardianproject.iocipher.camera.encoders; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import org.jcodec.codecs.h264.H264Encoder; import org.jcodec.codecs.h264.H264Utils; import org.jcodec.common.AudioFormat; 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.containers.mp4.muxer.PCMMP4MuxerTrack; import org.jcodec.scale.ColorUtil; import org.jcodec.scale.Transform; /** * * This file contains code from the IOCipher Camera Library "CipherCam". * * For more information about IOCipher, see https://guardianproject.info/code/iocipher * and this sample library: https://github.com/n8fr8/IOCipherCameraExample * * IOCipher Camera Sample is distributed under this license (aka the 3-clause BSD license) * * Some of this class was originally part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * @author n8fr8, The JCodec project * */ public class ImageToH264MP4Encoder { private SeekableByteChannel ch; private Picture toEncode; private Transform transform; private H264Encoder encoder; private ArrayList<ByteBuffer> spsList; private ArrayList<ByteBuffer> ppsList; private FramesMP4MuxerTrack outTrack; private ByteBuffer _out; private int frameNo; private MP4Muxer muxer; private PCMMP4MuxerTrack audioTrack; private AudioFormat af; public ImageToH264MP4Encoder(SeekableByteChannel ch, AudioFormat af) throws IOException { this.ch = ch; this.af = af; // Muxer that will store the encoded frames muxer = new MP4Muxer(ch, Brand.MP4); // Add video track to muxer outTrack = muxer.addTrack(TrackType.VIDEO, 25); // Create an instance of encoder encoder = new H264Encoder(); // Transform to convert between RGB and YUV transform = ColorUtil.getTransform(ColorSpace.RGB, encoder.getSupportedColorSpaces()[0]); // Encoder extra data ( SPS, PPS ) to be stored in a special place of // MP4 spsList = new ArrayList<ByteBuffer>(); ppsList = new ArrayList<ByteBuffer>(); if (af != null) audioTrack = muxer.addPCMAudioTrack(af); } public void addFrame(Picture pic) throws IOException { if (toEncode == null) { toEncode = Picture.create(pic.getWidth(), pic.getHeight(), encoder.getSupportedColorSpaces()[0]); } if (_out == null) { // Allocate a buffer big enough to hold output frames _out = ByteBuffer.allocate(pic.getWidth() * pic.getHeight() * 6); } // Perform conversion transform.transform(pic, toEncode); // Encode image into H.264 frame, the result is stored in '_out' buffer _out.clear(); ByteBuffer result = encoder.encodeFrame(toEncode, _out); // Based on the frame above form correct MP4 packet spsList.clear(); ppsList.clear(); H264Utils.wipePS(result, spsList, ppsList); H264Utils.encodeMOVPacket(result); // Add packet to video track outTrack.addFrame(new MP4Packet(result, frameNo, 25, 1, frameNo, true, null, frameNo, 0)); frameNo++; } public void addAudio (ByteBuffer buffer) throws IOException { audioTrack.addSamples(buffer); } public void finish() throws IOException { // Push saved SPS/PPS to a special storage in MP4 outTrack.addSampleEntry(H264Utils.createMOVSampleEntry(spsList, ppsList, 4)); audioTrack.addSampleEntry(MP4Muxer.audioSampleEntry(af)); // Write MP4 header and finalize recording muxer.writeHeader(); NIOUtils.closeQuietly(ch); } }