package org.jcodec.movtool.streaming.tracks; import java.lang.IllegalStateException; import java.lang.System; import java.lang.ThreadLocal; import java.lang.IllegalArgumentException; import static java.util.Arrays.binarySearch; import static org.jcodec.codecs.mpeg12.MPEGConst.PICTURE_START_CODE; import static org.jcodec.codecs.mpeg12.MPEGUtil.nextSegment; import static org.jcodec.movtool.streaming.tracks.MPEGToAVCTranscoder.createTranscoder; import static org.jcodec.movtool.streaming.tracks.Transcode2AVCTrack.createCodecMeta; import org.jcodec.codecs.h264.H264Encoder; import org.jcodec.codecs.h264.encode.H264FixedRateControl; import org.jcodec.codecs.mpeg12.MPEGConst; import org.jcodec.codecs.mpeg12.MPEGDecoder; import org.jcodec.codecs.mpeg12.bitstream.PictureHeader; import org.jcodec.common.logging.Logger; import org.jcodec.common.model.Size; import org.jcodec.movtool.streaming.CodecMeta; import org.jcodec.movtool.streaming.VirtualPacket; import org.jcodec.movtool.streaming.VirtualTrack; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Virtual movie track that transcodes ProRes to AVC on the fly. * * @author The JCodec project * */ public class Mpeg2AVCTrack implements VirtualTrack { private final int frameSize; protected VirtualTrack src; private CodecMeta se; private ThreadLocal<MPEGToAVCTranscoder> transcoders; int mbW; int mbH; int scaleFactor; int thumbWidth; int thumbHeight; private GOP gop; private GOP prevGop; private VirtualPacket _nextPacket; protected void checkFourCC(VirtualTrack srcTrack) { String fourcc = srcTrack.getCodecMeta().getFourcc(); if (!"m2v1".equals(fourcc)) throw new IllegalArgumentException("Input track is not ProRes"); } protected int selectScaleFactor(Size frameDim) { return frameDim.getWidth() >= 960 ? 2 : (frameDim.getWidth() > 480 ? 1 : 0); } public Mpeg2AVCTrack(VirtualTrack src) throws IOException { this.transcoders = new ThreadLocal<MPEGToAVCTranscoder>(); checkFourCC(src); this.src = src; H264FixedRateControl rc = new H264FixedRateControl(MPEGToAVCTranscoder.TARGET_RATE); H264Encoder encoder = new H264Encoder(rc); _nextPacket = src.nextPacket(); Size frameDim = new MPEGDecoder().getCodecMeta(_nextPacket.getData()).getSize(); 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); int _frameSize = rc.calcFrameSize(mbW * mbH); _frameSize += _frameSize >> 4; this.frameSize = _frameSize; } @Override public CodecMeta getCodecMeta() { return se; } @Override public VirtualPacket nextPacket() throws IOException { if (_nextPacket == null) return null; if (_nextPacket.isKeyframe()) { prevGop = gop; gop = new GOP(this, _nextPacket.getFrameNo(), prevGop); if (prevGop != null) prevGop.setNextGop(gop); } VirtualPacket ret = gop.addPacket(_nextPacket); _nextPacket = src.nextPacket(); return ret; } private static class GOP { private List<VirtualPacket> packets; private ByteBuffer[] data; private int frameNo; private GOP nextGop; private GOP prevGop; private List<ByteBuffer> leadingB; private Mpeg2AVCTrack track; public GOP(Mpeg2AVCTrack track, int frameNo, GOP prevGop) { this.packets = new ArrayList<VirtualPacket>(); this.track = track; this.frameNo = frameNo; this.prevGop = prevGop; } public void setNextGop(GOP gop) { this.nextGop = gop; } public VirtualPacket addPacket(VirtualPacket pkt) { packets.add(pkt); return new TranscodePacket(pkt, this, packets.size() - 1, track.frameSize); } private synchronized void transcode() throws IOException { if (data != null) return; // System.out.println("Transcoding GOP: " + frameNo); data = new ByteBuffer[packets.size()]; for (int tr = 0; tr < 2; tr++) { try { MPEGToAVCTranscoder t = track.transcoders.get(); if (t == null) { t = createTranscoder(track.scaleFactor); track.transcoders.set(t); } carryLeadingBOver(); double[] pts = collectPts(packets); for (int numRefs = 0, i = 0; i < packets.size(); i++) { VirtualPacket pkt = packets.get(i); ByteBuffer pktData = pkt.getData(); int picType = getPicType(pktData.duplicate()); if (picType != MPEGConst.BiPredictiveCoded) { ++numRefs; } else if (numRefs < 2) { continue; } ByteBuffer buf = ByteBuffer.allocate(track.frameSize); data[i] = t.transcodeFrame(pktData, buf, i == 0, binarySearch(pts, pkt.getPts())); } if (nextGop != null) { nextGop.leadingB = new ArrayList<ByteBuffer>(); pts = collectPts(nextGop.packets); for (int numRefs = 0, i = 0; i < nextGop.packets.size(); i++) { VirtualPacket pkt = nextGop.packets.get(i); ByteBuffer pktData = pkt.getData(); int picType = getPicType(pktData.duplicate()); if (picType != MPEGConst.BiPredictiveCoded) ++numRefs; if (numRefs >= 2) break; ByteBuffer buf = ByteBuffer.allocate(track.frameSize); nextGop.leadingB .add(t.transcodeFrame(pktData, buf, i == 0, binarySearch(pts, pkt.getPts()))); } } break; } catch (Throwable t) { Logger.error("Error transcoding gop: " + t.getMessage() + ", retrying."); } } } private double[] collectPts(List<VirtualPacket> packets2) { double[] pts = new double[packets2.size()]; for (int i = 0; i < pts.length; i++) pts[i] = packets2.get(i).getPts(); Arrays.sort(pts); return pts; } private synchronized void carryLeadingBOver() { if (leadingB != null) { for (int i = 0; i < leadingB.size(); i++) { data[i] = leadingB.get(i); } } } public ByteBuffer getData(int i) throws IOException { transcode(); if (data[i] == null && prevGop != null) { prevGop.transcode(); carryLeadingBOver(); } return data[i]; } } public static int getPicType(ByteBuffer buf) { ByteBuffer segment; while ((segment = nextSegment(buf)) != null) { int code = segment.getInt() & 0xff; if (code == PICTURE_START_CODE) { PictureHeader ph = PictureHeader.read(segment); return ph.picture_coding_type; } } return -1; } private static class TranscodePacket extends VirtualPacketWrapper { private GOP gop; private int index; private int frameSize; public TranscodePacket(VirtualPacket nextPacket, GOP gop, int index, int frameSize) { super(nextPacket); this.gop = gop; this.index = index; this.frameSize = frameSize; } @Override public int getDataLen() { return frameSize; } @Override public ByteBuffer getData() throws IOException { return gop.getData(index); } } @Override public void close() throws IOException { src.close(); } @Override public VirtualEdit[] getEdits() { return src.getEdits(); } @Override public int getPreferredTimescale() { return src.getPreferredTimescale(); } }