package org.jcodec.movtool.streaming.tracks.avc;
import java.lang.IllegalStateException;
import java.lang.System;
import org.jcodec.codecs.h264.H264Decoder;
import org.jcodec.codecs.h264.H264Encoder;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.h264.H264Utils.SliceHeaderTweaker;
import org.jcodec.codecs.h264.encode.H264FixedRateControl;
import org.jcodec.codecs.h264.io.model.NALUnit;
import org.jcodec.codecs.h264.io.model.NALUnitType;
import org.jcodec.codecs.h264.io.model.PictureParameterSet;
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
import org.jcodec.codecs.h264.io.model.SliceHeader;
import org.jcodec.common.VideoEncoder.EncodedFrame;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture8Bit;
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.movtool.streaming.tracks.ClipTrack;
import org.jcodec.movtool.streaming.tracks.VirtualPacketWrapper;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Clips AVC track replacing the remainder of a GOP at cut point with I-frames
*
* @author The JCodec project
*
*/
public class AVCClipTrack extends ClipTrack {
// private AvcCBox avcC;
private H264FixedRateControl rc;
private int mbW;
private int mbH;
private VideoCodecMeta se;
private final int frameSize;
private SeqParameterSet encSPS;
private PictureParameterSet encPPS;
private ByteBuffer codecPrivate;
public AVCClipTrack(VirtualTrack src, int frameFrom, int frameTo) {
super(src, frameFrom, frameTo);
VideoCodecMeta codecMeta = (VideoCodecMeta) src.getCodecMeta();
if (!"avc1".equals(codecMeta.getFourcc()))
throw new RuntimeException("Not an AVC source track");
rc = new H264FixedRateControl(1024);
H264Encoder encoder = getEncoder();
ByteBuffer codecPrivate = codecMeta.getCodecPrivate();
codecPrivate.reset();
this.codecPrivate = codecPrivate;
List<ByteBuffer> rawSPS = H264Utils.getRawSPS(codecPrivate);
List<ByteBuffer> rawPPS = H264Utils.getRawPPS(codecPrivate);
SeqParameterSet sps = H264Utils.readSPS(rawSPS.get(0));
mbW = sps.pic_width_in_mbs_minus1 + 1;
mbH = SeqParameterSet.getPicHeightInMbs(sps);
encSPS = encoder.initSPS(H264Utils.getPicSize(sps));
encSPS.seq_parameter_set_id = 1;
encPPS = encoder.initPPS();
encPPS.seq_parameter_set_id = 1;
encPPS.pic_parameter_set_id = 1;
encSPS.profile_idc = sps.profile_idc;
encSPS.level_idc = sps.level_idc;
encSPS.frame_mbs_only_flag = sps.frame_mbs_only_flag;
encSPS.frame_crop_bottom_offset = sps.frame_crop_bottom_offset;
encSPS.frame_crop_left_offset = sps.frame_crop_left_offset;
encSPS.frame_crop_right_offset = sps.frame_crop_right_offset;
encSPS.frame_crop_top_offset = sps.frame_crop_top_offset;
encSPS.vuiParams = sps.vuiParams;
rawSPS.add(H264Utils.writeSPS(encSPS, 128));
rawPPS.add(H264Utils.writePPS(encPPS, 20));
se = VideoCodecMeta.createVideoCodecMeta("avc1", H264Utils.saveCodecPrivate(rawSPS, rawPPS), codecMeta.getSize(), codecMeta.getPasp());
int _frameSize = rc.calcFrameSize(mbW * mbH);
_frameSize += _frameSize >> 4;
this.frameSize = _frameSize;
}
private H264Encoder getEncoder() {
H264Encoder encoder = new H264Encoder(rc);
encoder.setKeyInterval(1);
return encoder;
}
@Override
protected List<VirtualPacket> getGop(VirtualTrack src, int from) throws IOException {
VirtualPacket packet = src.nextPacket();
List<VirtualPacket> head = new ArrayList<VirtualPacket>();
while (packet != null && packet.getFrameNo() < from) {
if (packet.isKeyframe())
head.clear();
head.add(packet);
packet = src.nextPacket();
}
List<VirtualPacket> tail = new ArrayList<VirtualPacket>();
while (packet != null && !packet.isKeyframe()) {
tail.add(packet);
packet = src.nextPacket();
}
List<VirtualPacket> gop = new ArrayList<VirtualPacket>();
GopTranscoder tr = new GopTranscoder(this, head, tail, getEncoder());
for (int i = 0; i < tail.size(); i++)
gop.add(new TranscodePacket(tail.get(i), tr, i, frameSize));
gop.add(packet);
return gop;
}
public static class GopTranscoder {
private List<VirtualPacket> tail;
private List<VirtualPacket> head;
private List<ByteBuffer> result;
private AVCClipTrack track;
private H264Encoder encoder;
public GopTranscoder(AVCClipTrack track, List<VirtualPacket> head, List<VirtualPacket> tail,
H264Encoder encoder) {
this.track = track;
this.head = head;
this.tail = tail;
this.encoder = encoder;
}
public List<ByteBuffer> transcode() throws IOException {
H264Decoder decoder = H264Decoder.createH264DecoderFromCodecPrivate(track.codecPrivate);
Picture8Bit buf = Picture8Bit.create(track.mbW << 4, track.mbH << 4, ColorSpace.YUV420J);
Picture8Bit dec = null;
for (VirtualPacket virtualPacket : head) {
dec = decoder.decodeFrame8Bit(virtualPacket.getData(), buf.getData());
}
ByteBuffer tmp = ByteBuffer.allocate(track.frameSize);
List<ByteBuffer> result = new ArrayList<ByteBuffer>();
for (VirtualPacket pkt : tail) {
dec = decoder.decodeFrame8Bit(pkt.getData(), buf.getData());
tmp.clear();
EncodedFrame res = encoder.encodeFrame8Bit(dec, tmp);
ByteBuffer out = ByteBuffer.allocate(track.frameSize);
processFrame(res.getData(), out);
result.add(out);
}
return result;
}
private void processFrame(ByteBuffer _in, ByteBuffer out) {
SliceHeaderTweaker st = new H264Utils.SliceHeaderTweaker() {
@Override
protected void tweak(SliceHeader sh) {
sh.pic_parameter_set_id = 1;
}
};
ByteBuffer dup = _in.duplicate();
while (dup.hasRemaining()) {
ByteBuffer buf = H264Utils.nextNALUnit(dup);
if (buf == null)
break;
NALUnit nu = NALUnit.read(buf);
if (nu.type == NALUnitType.IDR_SLICE || nu.type == NALUnitType.NON_IDR_SLICE) {
out.putInt(1);
nu.write(out);
st.runSpsPps(buf, out, nu, track.encSPS, track.encPPS);
}
}
if (out.remaining() >= 5) {
out.putInt(1);
new NALUnit(NALUnitType.FILLER_DATA, 0).write(out);
}
out.clear();
}
public synchronized List<ByteBuffer> getResult() throws IOException {
if (result == null)
result = transcode();
return result;
}
}
@Override
public CodecMeta getCodecMeta() {
return se;
}
public static class TranscodePacket extends VirtualPacketWrapper {
private GopTranscoder tr;
private int off;
private int frameSize;
public TranscodePacket(VirtualPacket src, GopTranscoder tr, int off, int frameSize) {
super(src);
this.tr = tr;
this.off = off;
this.frameSize = frameSize;
}
@Override
public ByteBuffer getData() throws IOException {
return NIOUtils.duplicate(tr.getResult().get(off));
}
@Override
public int getDataLen() throws IOException {
return frameSize;
}
@Override
public boolean isKeyframe() {
return true;
}
}
}