org.jcodec.movtool.streaming.tracks.avc.AVCClipTrack Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jcodec Show documentation
Show all versions of jcodec Show documentation
Pure Java implementation of video/audio codecs and formats
package org.jcodec.movtool.streaming.tracks.avc;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
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.Frame;
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.codecs.h264.mp4.AvcCBox;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
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;
/**
* 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 int frameSize;
private SeqParameterSet encSPS;
private PictureParameterSet encPPS;
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 = new H264Encoder(rc);
avcC = H264Utils.parseAVCC(codecMeta.getCodecPrivate());
SeqParameterSet sps = H264Utils.readSPS(NIOUtils.duplicate(avcC.getSpsList().get(0)));
mbW = sps.pic_width_in_mbs_minus1 + 1;
mbH = H264Utils.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;
avcC.getSpsList().add(H264Utils.writeSPS(encSPS, 128));
avcC.getPpsList().add(H264Utils.writePPS(encPPS, 20));
se = new VideoCodecMeta("avc1", H264Utils.getAvcCData(avcC), codecMeta.getSize(), codecMeta.getPasp());
frameSize = rc.calcFrameSize(mbW * mbH);
frameSize += frameSize >> 4;
}
protected List getGop(VirtualTrack src, int from) throws IOException {
VirtualPacket packet = src.nextPacket();
List head = new ArrayList();
while (packet != null && packet.getFrameNo() < from) {
if (packet.isKeyframe())
head.clear();
head.add(packet);
packet = src.nextPacket();
}
List tail = new ArrayList();
while (packet != null && !packet.isKeyframe()) {
tail.add(packet);
packet = src.nextPacket();
}
List gop = new ArrayList();
GopTranscoder tr = new GopTranscoder(head, tail);
for (int i = 0; i < tail.size(); i++)
gop.add(new TranscodePacket(tail.get(i), tr, i));
gop.add(packet);
return gop;
}
public class GopTranscoder {
private List tail;
private List head;
private List result;
public GopTranscoder(List head, List tail) {
this.head = head;
this.tail = tail;
}
public List transcode() throws IOException {
H264Decoder decoder = new H264Decoder();
decoder.addSps(avcC.getSpsList());
decoder.addPps(avcC.getPpsList());
Picture buf = Picture.create(mbW << 4, mbH << 4, ColorSpace.YUV420);
Frame dec = null;
for (VirtualPacket virtualPacket : head) {
dec = decoder.decodeFrame(H264Utils.splitMOVPacket(virtualPacket.getData(), avcC), buf.getData());
}
H264Encoder encoder = new H264Encoder(rc);
ByteBuffer tmp = ByteBuffer.allocate(frameSize);
List result = new ArrayList();
for (VirtualPacket pkt : tail) {
dec = decoder.decodeFrame(H264Utils.splitMOVPacket(pkt.getData(), avcC), buf.getData());
tmp.clear();
ByteBuffer res = encoder.encodeFrame(dec, tmp);
ByteBuffer out = ByteBuffer.allocate(frameSize);
processFrame(res, 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) {
ByteBuffer sp = out.duplicate();
out.putInt(0);
nu.write(out);
st.run(buf, out, nu, encSPS, encPPS);
sp.putInt(out.position() - sp.position() - 4);
}
}
if (out.remaining() >= 5) {
out.putInt(out.remaining() - 4);
new NALUnit(NALUnitType.FILLER_DATA, 0).write(out);
}
out.clear();
}
public synchronized List getResult() throws IOException {
if (result == null)
result = transcode();
return result;
}
}
@Override
public CodecMeta getCodecMeta() {
return se;
}
public class TranscodePacket extends VirtualPacketWrapper {
private GopTranscoder tr;
private int off;
public TranscodePacket(VirtualPacket src, GopTranscoder tr, int off) {
super(src);
this.tr = tr;
this.off = off;
}
@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;
}
}
}