package org.jcodec.movtool.streaming.tracks; import java.lang.IllegalStateException; import java.lang.System; import org.jcodec.codecs.h264.H264Encoder; import org.jcodec.codecs.h264.H264Utils; import org.jcodec.codecs.h264.encode.H264FixedRateControl; import org.jcodec.codecs.h264.mp4.AvcCBox; import org.jcodec.common.VideoDecoder; import org.jcodec.common.logging.Logger; import org.jcodec.common.model.ColorSpace; import org.jcodec.common.model.Picture8Bit; import org.jcodec.common.model.Rect; import org.jcodec.common.model.Size; import org.jcodec.movtool.streaming.CodecMeta; import org.jcodec.movtool.streaming.VideoCodecMeta; import org.jcodec.movtool.streaming.VirtualPacket; import org.jcodec.movtool.streaming.VirtualTrack; import org.jcodec.scale.ColorUtil; import org.jcodec.scale.Transform8Bit; import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.lang.ThreadLocal; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Virtual movie track that transcodes the source video format to AVC on the * fly. * * @author The JCodec project * */ public abstract class Transcode2AVCTrack implements VirtualTrack { private static final int TARGET_RATE = 1024; private int frameSize; protected VirtualTrack src; private CodecMeta se; private ThreadLocal<Transcoder> transcoders; private int mbW; private int mbH; private int scaleFactor; private int thumbWidth; private int thumbHeight; protected abstract int selectScaleFactor(Size frameDim); protected abstract VideoDecoder getDecoder(int scaleFactor); protected abstract void checkFourCC(VirtualTrack proresTrack); public Transcode2AVCTrack(VirtualTrack src, Size frameDim) { this.transcoders = new ThreadLocal<Transcoder>(); checkFourCC(src); this.src = src; H264FixedRateControl rc = new H264FixedRateControl(TARGET_RATE); H264Encoder encoder = new H264Encoder(rc); scaleFactor = selectScaleFactor(frameDim); thumbWidth = frameDim.getWidth() >> scaleFactor; thumbHeight = (frameDim.getHeight() >> scaleFactor) & ~1; mbW = (thumbWidth + 15) >> 4; mbH = (thumbHeight + 15) >> 4; se = createCodecMeta(src, encoder, thumbWidth, thumbHeight); frameSize = rc.calcFrameSize(mbW * mbH); frameSize += frameSize >> 4; } public static VideoCodecMeta createCodecMeta(VirtualTrack src, H264Encoder encoder, int thumbWidth, int thumbHeight) { VideoCodecMeta codecMeta = (VideoCodecMeta) src.getCodecMeta(); AvcCBox createAvcC = H264Utils.createAvcC(encoder.initSPS(new Size(thumbWidth, thumbHeight)), encoder.initPPS(), 4); return VideoCodecMeta.createVideoCodecMeta("avc1", H264Utils.getAvcCData(createAvcC), new Size(thumbWidth, thumbHeight), codecMeta.getPasp()); } @Override public CodecMeta getCodecMeta() { return se; } @Override public VirtualPacket nextPacket() throws IOException { VirtualPacket nextPacket = src.nextPacket(); if (nextPacket == null) return null; return new TranscodePacket(this, nextPacket); } private static class TranscodePacket extends VirtualPacketWrapper { private Transcode2AVCTrack track; public TranscodePacket(Transcode2AVCTrack track, VirtualPacket nextPacket) { super(nextPacket); this.track = track; } @Override public int getDataLen() { return track.frameSize; } @Override public ByteBuffer getData() throws IOException { Transcoder t = track.transcoders.get(); if (t == null) { t = new Transcoder(track); track.transcoders.set(t); } ByteBuffer buf = ByteBuffer.allocate(track.frameSize); ByteBuffer data = src.getData(); return t.transcodeFrame(data, buf); } } static class Transcoder { private VideoDecoder decoder; private H264Encoder encoder; private Picture8Bit pic0; private Picture8Bit pic1; private Transform8Bit transform; private H264FixedRateControl rc; private Transcode2AVCTrack track; public Transcoder(Transcode2AVCTrack track) { this.track = track; rc = new H264FixedRateControl(TARGET_RATE); this.decoder = track.getDecoder(track.scaleFactor); this.encoder = new H264Encoder(rc); pic0 = Picture8Bit.create(track.mbW << 4, (track.mbH + 1) << 4, ColorSpace.YUV444); } public ByteBuffer transcodeFrame(ByteBuffer src, ByteBuffer dst) throws IOException { if (src == null) return null; Picture8Bit decoded = decoder.decodeFrame8Bit(src, pic0.getData()); if (pic1 == null) { pic1 = Picture8Bit.create(decoded.getWidth(), decoded.getHeight(), encoder.getSupportedColorSpaces()[0]); transform = ColorUtil.getTransform8Bit(decoded.getColor(), encoder.getSupportedColorSpaces()[0]); } transform.transform(decoded, pic1); pic1.setCrop(new Rect(0, 0, track.thumbWidth, track.thumbHeight)); int rate = TARGET_RATE; do { try { encoder.encodeFrame8Bit(pic1, dst); break; } catch (BufferOverflowException ex) { Logger.warn("Abandon frame, buffer too small: " + dst.capacity()); rate -= 10; rc.setRate(rate); } } while (rate > 10); rc.setRate(TARGET_RATE); H264Utils.encodeMOVPacketInplace(dst); return dst; } } @Override public void close() throws IOException { src.close(); } @Override public VirtualEdit[] getEdits() { return src.getEdits(); } @Override public int getPreferredTimescale() { return src.getPreferredTimescale(); } }