package org.jcodec.codecs.h264; import org.jcodec.api.NotSupportedException; import org.jcodec.codecs.h264.decode.SliceHeaderReader; 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.RefPicMarking; import org.jcodec.codecs.h264.io.model.RefPicMarking.InstrType; import org.jcodec.codecs.h264.io.model.SeqParameterSet; import org.jcodec.codecs.h264.io.model.SliceHeader; import org.jcodec.common.Demuxer; import org.jcodec.common.DemuxerTrack; import org.jcodec.common.DemuxerTrackMeta; import org.jcodec.common.IntObjectMap; import org.jcodec.common.io.BitReader; import org.jcodec.common.model.Packet; import org.jcodec.common.model.Packet.FrameType; 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 * * Extracts H.264 frames out H.264 Elementary stream ( according to Annex B ) * * @author The JCodec project * */ public class BufferH264ES implements DemuxerTrack, Demuxer { private ByteBuffer bb; private SliceHeaderReader shr; private IntObjectMap<PictureParameterSet> pps; private IntObjectMap<SeqParameterSet> sps; // POC and framenum detection private int prevFrameNumOffset; private int prevFrameNum; private int prevPicOrderCntMsb; private int prevPicOrderCntLsb; private int frameNo; public BufferH264ES(ByteBuffer bb) { this.pps = new IntObjectMap<PictureParameterSet>(); this.sps = new IntObjectMap<SeqParameterSet>(); this.bb = bb; this.shr = new SliceHeaderReader(); this.frameNo = 0; } @Override public Packet nextFrame() { ByteBuffer result = bb.duplicate(); NALUnit prevNu = null; SliceHeader prevSh = null; while (true) { bb.mark(); ByteBuffer buf = H264Utils.nextNALUnit(bb); if (buf == null) break; // NIOUtils.skip(buf, 4); NALUnit nu = NALUnit.read(buf); if (nu.type == NALUnitType.IDR_SLICE || nu.type == NALUnitType.NON_IDR_SLICE) { SliceHeader sh = readSliceHeader(buf, nu); if (prevNu != null && prevSh != null && !sameFrame(prevNu, nu, prevSh, sh)) { bb.reset(); break; } prevSh = sh; prevNu = nu; } else if (nu.type == NALUnitType.PPS) { PictureParameterSet read = PictureParameterSet.read(buf); pps.put(read.pic_parameter_set_id, read); } else if (nu.type == NALUnitType.SPS) { SeqParameterSet read = SeqParameterSet.read(buf); sps.put(read.seq_parameter_set_id, read); } } result.limit(bb.position()); return prevSh == null ? null : detectPoc(result, prevNu, prevSh); } private SliceHeader readSliceHeader(ByteBuffer buf, NALUnit nu) { BitReader br = BitReader.createBitReader(buf); SliceHeader sh = shr.readPart1(br); PictureParameterSet pp = pps.get(sh.pic_parameter_set_id); shr.readPart2(sh, nu, sps.get(pp.seq_parameter_set_id), pp, br); return sh; } private boolean sameFrame(NALUnit nu1, NALUnit nu2, SliceHeader sh1, SliceHeader sh2) { if (sh1.pic_parameter_set_id != sh2.pic_parameter_set_id) return false; if (sh1.frame_num != sh2.frame_num) return false; SeqParameterSet sps = sh1.sps; if ((sps.pic_order_cnt_type == 0 && sh1.pic_order_cnt_lsb != sh2.pic_order_cnt_lsb)) return false; if ((sps.pic_order_cnt_type == 1 && (sh1.delta_pic_order_cnt[0] != sh2.delta_pic_order_cnt[0] || sh1.delta_pic_order_cnt[1] != sh2.delta_pic_order_cnt[1]))) return false; if (((nu1.nal_ref_idc == 0 || nu2.nal_ref_idc == 0) && nu1.nal_ref_idc != nu2.nal_ref_idc)) return false; if (((nu1.type == NALUnitType.IDR_SLICE) != (nu2.type == NALUnitType.IDR_SLICE))) return false; if (sh1.idr_pic_id != sh2.idr_pic_id) return false; return true; } private Packet detectPoc(ByteBuffer result, NALUnit nu, SliceHeader sh) { int maxFrameNum = 1 << (sh.sps.log2_max_frame_num_minus4 + 4); if (detectGap(sh, maxFrameNum)) { issueNonExistingPic(sh, maxFrameNum); } int absFrameNum = updateFrameNumber(sh.frame_num, maxFrameNum, detectMMCO5(sh.refPicMarkingNonIDR)); int poc = 0; if (nu.type == NALUnitType.NON_IDR_SLICE) { poc = calcPoc(absFrameNum, nu, sh); } return new Packet(result, absFrameNum, 1, 1, frameNo++, nu.type == NALUnitType.IDR_SLICE ? FrameType.KEY : FrameType.INTER, null, poc); } private int updateFrameNumber(int frameNo, int maxFrameNum, boolean mmco5) { int frameNumOffset; if (prevFrameNum > frameNo) frameNumOffset = prevFrameNumOffset + maxFrameNum; else frameNumOffset = prevFrameNumOffset; int absFrameNum = frameNumOffset + frameNo; prevFrameNum = mmco5 ? 0 : frameNo; prevFrameNumOffset = frameNumOffset; return absFrameNum; } private void issueNonExistingPic(SliceHeader sh, int maxFrameNum) { int nextFrameNum = (prevFrameNum + 1) % maxFrameNum; // refPictureManager.addNonExisting(nextFrameNum); prevFrameNum = nextFrameNum; } private boolean detectGap(SliceHeader sh, int maxFrameNum) { return sh.frame_num != prevFrameNum && sh.frame_num != ((prevFrameNum + 1) % maxFrameNum); } private int calcPoc(int absFrameNum, NALUnit nu, SliceHeader sh) { if (sh.sps.pic_order_cnt_type == 0) { return calcPOC0(nu, sh); } else if (sh.sps.pic_order_cnt_type == 1) { return calcPOC1(absFrameNum, nu, sh); } else { return calcPOC2(absFrameNum, nu, sh); } } private int calcPOC2(int absFrameNum, NALUnit nu, SliceHeader sh) { if (nu.nal_ref_idc == 0) return 2 * absFrameNum - 1; else return 2 * absFrameNum; } private int calcPOC1(int absFrameNum, NALUnit nu, SliceHeader sh) { if (sh.sps.num_ref_frames_in_pic_order_cnt_cycle == 0) absFrameNum = 0; if (nu.nal_ref_idc == 0 && absFrameNum > 0) absFrameNum = absFrameNum - 1; int expectedDeltaPerPicOrderCntCycle = 0; for (int i = 0; i < sh.sps.num_ref_frames_in_pic_order_cnt_cycle; i++) expectedDeltaPerPicOrderCntCycle += sh.sps.offsetForRefFrame[i]; int expectedPicOrderCnt; if (absFrameNum > 0) { int picOrderCntCycleCnt = (absFrameNum - 1) / sh.sps.num_ref_frames_in_pic_order_cnt_cycle; int frameNumInPicOrderCntCycle = (absFrameNum - 1) % sh.sps.num_ref_frames_in_pic_order_cnt_cycle; expectedPicOrderCnt = picOrderCntCycleCnt * expectedDeltaPerPicOrderCntCycle; for (int i = 0; i <= frameNumInPicOrderCntCycle; i++) expectedPicOrderCnt = expectedPicOrderCnt + sh.sps.offsetForRefFrame[i]; } else { expectedPicOrderCnt = 0; } if (nu.nal_ref_idc == 0) expectedPicOrderCnt = expectedPicOrderCnt + sh.sps.offset_for_non_ref_pic; return expectedPicOrderCnt + sh.delta_pic_order_cnt[0]; } private int calcPOC0(NALUnit nu, SliceHeader sh) { int pocCntLsb = sh.pic_order_cnt_lsb; int maxPicOrderCntLsb = 1 << (sh.sps.log2_max_pic_order_cnt_lsb_minus4 + 4); // TODO prevPicOrderCntMsb should be wrapped!! int picOrderCntMsb; if ((pocCntLsb < prevPicOrderCntLsb) && ((prevPicOrderCntLsb - pocCntLsb) >= (maxPicOrderCntLsb / 2))) picOrderCntMsb = prevPicOrderCntMsb + maxPicOrderCntLsb; else if ((pocCntLsb > prevPicOrderCntLsb) && ((pocCntLsb - prevPicOrderCntLsb) > (maxPicOrderCntLsb / 2))) picOrderCntMsb = prevPicOrderCntMsb - maxPicOrderCntLsb; else picOrderCntMsb = prevPicOrderCntMsb; if (nu.nal_ref_idc != 0) { prevPicOrderCntMsb = picOrderCntMsb; prevPicOrderCntLsb = pocCntLsb; } return picOrderCntMsb + pocCntLsb; } private boolean detectMMCO5(RefPicMarking refPicMarkingNonIDR) { if (refPicMarkingNonIDR == null) return false; RefPicMarking.Instruction[] instructions = refPicMarkingNonIDR.getInstructions(); for (int i = 0; i < instructions.length; i++) { RefPicMarking.Instruction instr = instructions[i]; if (instr.getType() == InstrType.CLEAR) { return true; } } return false; } public SeqParameterSet[] getSps() { return sps.values(new SeqParameterSet[0]); } public PictureParameterSet[] getPps() { return pps.values(new PictureParameterSet[0]); } @Override public DemuxerTrackMeta getMeta() { return null; } @Override public void close() throws IOException { // TODO Auto-generated method stub } @Override public List<? extends DemuxerTrack> getTracks() { return getVideoTracks(); } @Override public List<? extends DemuxerTrack> getVideoTracks() { List<DemuxerTrack> tracks = new ArrayList<DemuxerTrack>(); tracks.add(this); return tracks; } @Override public List<? extends DemuxerTrack> getAudioTracks() { List<DemuxerTrack> tracks = new ArrayList<DemuxerTrack>(); return tracks; } }