All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jcodec.movtool.streaming.tracks.avc.AVCClipTrack Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
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;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy