package org.jcodec.codecs.h264; import static java.util.Arrays.asList; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; 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.SeqParameterSet; import org.jcodec.codecs.h264.io.model.SliceHeader; import org.jcodec.codecs.h264.io.model.SliceType; import org.jcodec.codecs.h264.io.write.SliceHeaderWriter; import org.jcodec.codecs.h264.mp4.AvcCBox; import org.jcodec.common.IntArrayList; import org.jcodec.common.io.BitReader; import org.jcodec.common.io.BitWriter; import org.jcodec.common.io.FileChannelWrapper; import org.jcodec.common.io.NIOUtils; import org.jcodec.common.io.SeekableByteChannel; import org.jcodec.common.model.Size; import org.jcodec.containers.mp4.boxes.Box; import org.jcodec.containers.mp4.boxes.Box.LeafBox; import org.jcodec.containers.mp4.boxes.NodeBox; import org.jcodec.containers.mp4.boxes.SampleEntry; import org.jcodec.containers.mp4.boxes.VideoSampleEntry; import org.jcodec.containers.mp4.muxer.MP4Muxer; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * @author The JCodec project * */ public class H264Utils { private static SliceHeaderReader shr = new SliceHeaderReader(); private static SliceHeaderWriter shw = new SliceHeaderWriter(); public static ByteBuffer nextNALUnit(ByteBuffer buf) { skipToNALUnit(buf); return gotoNALUnit(buf); } public static final void skipToNALUnit(ByteBuffer buf) { if (!buf.hasRemaining()) return; int val = 0xffffffff; while (buf.hasRemaining()) { val <<= 8; val |= (buf.get() & 0xff); if ((val & 0xffffff) == 1) { buf.position(buf.position()); break; } } } /** * Finds next Nth H.264 bitstream NAL unit (0x00000001) and returns the data * that preceeds it as a ByteBuffer slice * * Segment byte order is always little endian * * TODO: emulation prevention * * @param buf * @return */ public static final ByteBuffer gotoNALUnit(ByteBuffer buf) { if (!buf.hasRemaining()) return null; int from = buf.position(); ByteBuffer result = buf.slice(); result.order(ByteOrder.BIG_ENDIAN); int val = 0xffffffff; while (buf.hasRemaining()) { val <<= 8; val |= (buf.get() & 0xff); if ((val & 0xffffff) == 1) { buf.position(buf.position() - (val == 1 ? 4 : 3)); result.limit(buf.position() - from); break; } } return result; } public static final void unescapeNAL(ByteBuffer _buf) { if (_buf.remaining() < 2) return; ByteBuffer _in = _buf.duplicate(); ByteBuffer out = _buf.duplicate(); byte p1 = _in.get(); out.put(p1); byte p2 = _in.get(); out.put(p2); while (_in.hasRemaining()) { byte b = _in.get(); if (p1 != 0 || p2 != 0 || b != 3) out.put(b); p1 = p2; p2 = b; } _buf.limit(out.position()); } public static final void escapeNALinplace(ByteBuffer src) { int[] loc = searchEscapeLocations(src); int old = src.limit(); src.limit(src.limit() + loc.length); for (int newPos = src.limit() - 1, oldPos = old - 1, locIdx = loc.length - 1; newPos >= src .position(); newPos--, oldPos--) { src.put(newPos, src.get(oldPos)); if (locIdx >= 0 && loc[locIdx] == oldPos) { newPos--; src.put(newPos, (byte) 3); locIdx--; } } } private static int[] searchEscapeLocations(ByteBuffer src) { IntArrayList points = IntArrayList.createIntArrayList(); ByteBuffer search = src.duplicate(); short p = search.getShort(); while (search.hasRemaining()) { byte b = search.get(); if (p == 0 && (b & ~3) == 0) { points.add(search.position() - 1); p = 3; } p <<= 8; p |= b & 0xff; } int[] array = points.toArray(); return array; } public static final void escapeNAL(ByteBuffer src, ByteBuffer dst) { byte p1 = src.get(), p2 = src.get(); dst.put(p1); dst.put(p2); while (src.hasRemaining()) { byte b = src.get(); if (p1 == 0 && p2 == 0 && (b & 0xff) <= 3) { dst.put((byte) 3); p1 = p2; p2 = 3; } dst.put(b); p1 = p2; p2 = b; } } public static List<ByteBuffer> splitMOVPacket(ByteBuffer buf, AvcCBox avcC) { List<ByteBuffer> result = new ArrayList<ByteBuffer>(); int nls = avcC.getNalLengthSize(); ByteBuffer dup = buf.duplicate(); while (dup.remaining() >= nls) { int len = readLen(dup, nls); if (len == 0) break; result.add(NIOUtils.read(dup, len)); } return result; } private static int readLen(ByteBuffer dup, int nls) { switch (nls) { case 1: return dup.get() & 0xff; case 2: return dup.getShort() & 0xffff; case 3: return ((dup.getShort() & 0xffff) << 8) | (dup.get() & 0xff); case 4: return dup.getInt(); default: throw new IllegalArgumentException("NAL Unit length size can not be " + nls); } } /** * Encodes AVC frame in ISO BMF format. Takes Annex B format. * * Scans the packet for each NAL Unit starting with 00 00 00 01 and replaces * this 4 byte sequence with 4 byte integer representing this NAL unit * length. * * @param avcFrame * AVC frame encoded in Annex B NAL unit format */ public static void encodeMOVPacketInplace(ByteBuffer avcFrame) { ByteBuffer dup = avcFrame.duplicate(); ByteBuffer d1 = avcFrame.duplicate(); for (int tot = d1.position();;) { ByteBuffer buf = H264Utils.nextNALUnit(dup); if (buf == null) break; d1.position(tot); d1.putInt(buf.remaining()); tot += buf.remaining() + 4; } } /** * Encodes AVC frame in ISO BMF format. Takes Annex B format. * * Scans the packet for each NAL Unit starting with 00 00 00 01 and replaces * this 4 byte sequence with 4 byte integer representing this NAL unit * length. * * @param avcFrame * AVC frame encoded in Annex B NAL unit format */ public static ByteBuffer encodeMOVPacket(ByteBuffer avcFrame) { ByteBuffer dup = avcFrame.duplicate(); List<ByteBuffer> list = new ArrayList<ByteBuffer>(); ByteBuffer buf; int totalLen = 0; while ((buf = H264Utils.nextNALUnit(dup)) != null) { list.add(buf); totalLen += buf.remaining(); } ByteBuffer result = ByteBuffer.allocate(list.size() * 4 + totalLen); for (ByteBuffer byteBuffer : list) { result.putInt(byteBuffer.remaining()); result.put(byteBuffer); } result.flip(); return result; } /** * Decodes AVC packet in ISO BMF format into Annex B format. * * Replaces NAL unit size integers with 00 00 00 01 start codes. If the * space allows the transformation is done inplace. * * @param result */ public static ByteBuffer decodeMOVPacket(ByteBuffer result, AvcCBox avcC) { if (avcC.getNalLengthSize() == 4) { decodeMOVPacketInplace(result, avcC); return result; } return joinNALUnits(splitMOVPacket(result, avcC)); } /** * Decodes AVC packet in ISO BMF format into Annex B format. * * Inplace replaces NAL unit size integers with 00 00 00 01 start codes. * * @param result */ public static void decodeMOVPacketInplace(ByteBuffer result, AvcCBox avcC) { if (avcC.getNalLengthSize() != 4) throw new IllegalArgumentException("Can only inplace decode AVC MOV packet with nal_length_size = 4."); ByteBuffer dup = result.duplicate(); while (dup.remaining() >= 4) { int size = dup.getInt(); dup.position(dup.position() - 4); dup.putInt(1); dup.position(dup.position() + size); } } /** * Wipes AVC parameter sets ( SPS/PPS ) from the packet * * @param in * AVC frame encoded in Annex B NAL unit format * @param out * Buffer where packet without PS will be put * @param spsList * Storage for leading SPS structures ( can be null, then all * leading SPSs are discarded ). * @param ppsList * Storage for leading PPS structures ( can be null, then all * leading PPSs are discarded ). */ public static void wipePS(ByteBuffer _in, ByteBuffer out, List<ByteBuffer> spsList, List<ByteBuffer> ppsList) { ByteBuffer dup = _in.duplicate(); while (dup.hasRemaining()) { ByteBuffer buf = H264Utils.nextNALUnit(dup); if (buf == null) break; NALUnit nu = NALUnit.read(buf.duplicate()); if (nu.type == NALUnitType.PPS) { if (ppsList != null) ppsList.add(NIOUtils.duplicate(buf)); } else if (nu.type == NALUnitType.SPS) { if (spsList != null) spsList.add(NIOUtils.duplicate(buf)); } else if (out != null) { out.putInt(1); out.put(buf); } } if (out != null) out.flip(); } /** * Wipes AVC parameter sets ( SPS/PPS ) from the packet ( inplace operation * ) * * @param in * AVC frame encoded in Annex B NAL unit format * @param spsList * Storage for leading SPS structures ( can be null, then all * leading SPSs are discarded ). * @param ppsList * Storage for leading PPS structures ( can be null, then all * leading PPSs are discarded ). */ public static void wipePSinplace(ByteBuffer _in, Collection<ByteBuffer> spsList, Collection<ByteBuffer> ppsList) { 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.PPS) { if (ppsList != null) ppsList.add(NIOUtils.duplicate(buf)); _in.position(dup.position()); } else if (nu.type == NALUnitType.SPS) { if (spsList != null) spsList.add(NIOUtils.duplicate(buf)); _in.position(dup.position()); } else if (nu.type == NALUnitType.IDR_SLICE || nu.type == NALUnitType.NON_IDR_SLICE) break; } } public static AvcCBox createAvcC(SeqParameterSet sps, PictureParameterSet pps, int nalLengthSize) { ByteBuffer serialSps = ByteBuffer.allocate(512); sps.write(serialSps); serialSps.flip(); H264Utils.escapeNALinplace(serialSps); ByteBuffer serialPps = ByteBuffer.allocate(512); pps.write(serialPps); serialPps.flip(); H264Utils.escapeNALinplace(serialPps); AvcCBox avcC = AvcCBox.createAvcCBox(sps.profile_idc, 0, sps.level_idc, nalLengthSize, asList(serialSps), asList(serialPps)); return avcC; } public static AvcCBox createAvcCFromList(List<SeqParameterSet> initSPS, List<PictureParameterSet> initPPS, int nalLengthSize) { List<ByteBuffer> serialSps = saveSPS(initSPS); List<ByteBuffer> serialPps = savePPS(initPPS); SeqParameterSet sps = initSPS.get(0); return AvcCBox.createAvcCBox(sps.profile_idc, 0, sps.level_idc, nalLengthSize, serialSps, serialPps); } /** * @param initPPS * @return */ public static List<ByteBuffer> savePPS(List<PictureParameterSet> initPPS) { List<ByteBuffer> serialPps = new ArrayList<ByteBuffer>(); for (PictureParameterSet pps : initPPS) { ByteBuffer bb1 = ByteBuffer.allocate(512); pps.write(bb1); bb1.flip(); H264Utils.escapeNALinplace(bb1); serialPps.add(bb1); } return serialPps; } /** * @param initSPS * @return */ public static List<ByteBuffer> saveSPS(List<SeqParameterSet> initSPS) { List<ByteBuffer> serialSps = new ArrayList<ByteBuffer>(); for (SeqParameterSet sps : initSPS) { ByteBuffer bb1 = ByteBuffer.allocate(512); sps.write(bb1); bb1.flip(); H264Utils.escapeNALinplace(bb1); serialSps.add(bb1); } return serialSps; } /** * Creates a MP4 sample entry given AVC/H.264 codec private. * * @param codecPrivate * Array containing AnnexB delimited (00 00 00 01) SPS/PPS NAL * units. * @return MP4 sample entry */ public static SampleEntry createMOVSampleEntryFromBytes(ByteBuffer codecPrivate) { List<ByteBuffer> rawSPS = getRawSPS(codecPrivate.duplicate()); List<ByteBuffer> rawPPS = getRawPPS(codecPrivate.duplicate()); return createMOVSampleEntryFromSpsPpsList(rawSPS, rawPPS, 4); } public static SampleEntry createMOVSampleEntryFromSpsPpsList(List<ByteBuffer> spsList, List<ByteBuffer> ppsList, int nalLengthSize) { AvcCBox avcC = createAvcCFromPS(spsList, ppsList, nalLengthSize); return createMOVSampleEntryFromAvcC(avcC); } /** * Creates a MP4 sample entry given AVC/H.264 codec private. * * @param codecPrivate * Array containing AnnexB delimited (00 00 00 01) SPS/PPS NAL * units. * @return MP4 sample entry */ public static AvcCBox createAvcCFromBytes(ByteBuffer codecPrivate) { List<ByteBuffer> rawSPS = getRawSPS(codecPrivate.duplicate()); List<ByteBuffer> rawPPS = getRawPPS(codecPrivate.duplicate()); return createAvcCFromPS(rawSPS, rawPPS, 4); } public static AvcCBox createAvcCFromPS(List<ByteBuffer> spsList, List<ByteBuffer> ppsList, int nalLengthSize) { SeqParameterSet sps = readSPS(NIOUtils.duplicate(spsList.get(0))); return AvcCBox.createAvcCBox(sps.profile_idc, 0, sps.level_idc, nalLengthSize, spsList, ppsList); } public static SampleEntry createMOVSampleEntryFromAvcC(AvcCBox avcC) { SeqParameterSet sps = SeqParameterSet.read(avcC.getSpsList().get(0).duplicate()); int codedWidth = (sps.pic_width_in_mbs_minus1 + 1) << 4; int codedHeight = SeqParameterSet.getPicHeightInMbs(sps) << 4; SampleEntry se = MP4Muxer.videoSampleEntry("avc1", getPicSize(sps), "JCodec"); se.add(avcC); return se; } public static SampleEntry createMOVSampleEntryFromSpsPps(SeqParameterSet initSPS, PictureParameterSet initPPS, int nalLengthSize) { ByteBuffer bb1 = ByteBuffer.allocate(512), bb2 = ByteBuffer.allocate(512); initSPS.write(bb1); initPPS.write(bb2); bb1.flip(); bb2.flip(); return createMOVSampleEntryFromBuffer(bb1, bb2, nalLengthSize); } public static SampleEntry createMOVSampleEntryFromBuffer(ByteBuffer sps, ByteBuffer pps, int nalLengthSize) { return createMOVSampleEntryFromSpsPpsList(Arrays.asList(new ByteBuffer[] { sps }), Arrays.asList(new ByteBuffer[] { pps }), nalLengthSize); } public static boolean iFrame(ByteBuffer _data) { ByteBuffer data = _data.duplicate(); SliceHeaderReader shr = new SliceHeaderReader(); ByteBuffer segment; while ((segment = H264Utils.nextNALUnit(data)) != null) { NALUnitType type = NALUnit.read(segment).type; if (type == NALUnitType.IDR_SLICE || type == NALUnitType.NON_IDR_SLICE) { unescapeNAL(segment); BitReader reader = BitReader.createBitReader(segment); SliceHeader part1 = shr.readPart1(reader); return part1.slice_type == SliceType.I; } } return false; } public static boolean isByteBufferIDRSlice(ByteBuffer _data) { ByteBuffer data = _data.duplicate(); ByteBuffer segment; while ((segment = H264Utils.nextNALUnit(data)) != null) { if (NALUnit.read(segment).type == NALUnitType.IDR_SLICE) return true; } return false; } public static boolean idrSlice(List<ByteBuffer> _data) { for (ByteBuffer segment : _data) { if (NALUnit.read(segment.duplicate()).type == NALUnitType.IDR_SLICE) return true; } return false; } public static void saveRawFrame(ByteBuffer data, AvcCBox avcC, File f) throws IOException { SeekableByteChannel raw = NIOUtils.writableChannel(f); saveStreamParams(avcC, raw); raw.write(data.duplicate()); raw.close(); } public static void saveStreamParams(AvcCBox avcC, SeekableByteChannel raw) throws IOException { ByteBuffer bb = ByteBuffer.allocate(1024); for (ByteBuffer byteBuffer : avcC.getSpsList()) { raw.write(ByteBuffer.wrap(new byte[] { 0, 0, 0, 1, 0x67 })); H264Utils.escapeNAL(byteBuffer.duplicate(), bb); bb.flip(); raw.write(bb); bb.clear(); } for (ByteBuffer byteBuffer : avcC.getPpsList()) { raw.write(ByteBuffer.wrap(new byte[] { 0, 0, 0, 1, 0x68 })); H264Utils.escapeNAL(byteBuffer.duplicate(), bb); bb.flip(); raw.write(bb); bb.clear(); } } public static List<ByteBuffer> splitFrame(ByteBuffer frame) { ArrayList<ByteBuffer> result = new ArrayList<ByteBuffer>(); ByteBuffer segment; while ((segment = H264Utils.nextNALUnit(frame)) != null) { result.add(segment); } return result; } /** * Joins buffers containing individual NAL units into a single AnnexB * delimited buffer. Each NAL unit will be separated with 00 00 00 01 * markers. Allocates a new byte buffer and writes data into it. * * @param nalUnits * @param out */ public static ByteBuffer joinNALUnits(List<ByteBuffer> nalUnits) { int size = 0; for (ByteBuffer nal : nalUnits) { size += 4 + nal.remaining(); } ByteBuffer allocate = ByteBuffer.allocate(size); joinNALUnitsToBuffer(nalUnits, allocate); return allocate; } /** * Joins buffers containing individual NAL units into a single AnnexB * delimited buffer. Each NAL unit will be separated with 00 00 00 01 * markers. * * @param nalUnits * @param out */ public static void joinNALUnitsToBuffer(List<ByteBuffer> nalUnits, ByteBuffer out) { for (ByteBuffer nal : nalUnits) { out.putInt(1); out.put(nal.duplicate()); } } public static ByteBuffer getAvcCData(AvcCBox avcC) { ByteBuffer bb = ByteBuffer.allocate(2048); avcC.doWrite(bb); bb.flip(); return bb; } public static AvcCBox parseAVCC(VideoSampleEntry vse) { Box lb = NodeBox.findFirst(vse, Box.class, "avcC"); if (lb instanceof AvcCBox) return (AvcCBox) lb; else { return parseAVCCFromBuffer(((LeafBox) lb).getData().duplicate()); } } public static ByteBuffer saveCodecPrivate(List<ByteBuffer> spsList, List<ByteBuffer> ppsList) { int totalCodecPrivateSize = 0; for (ByteBuffer byteBuffer : spsList) { totalCodecPrivateSize += byteBuffer.remaining() + 5; } for (ByteBuffer byteBuffer : ppsList) { totalCodecPrivateSize += byteBuffer.remaining() + 5; } ByteBuffer bb = ByteBuffer.allocate(totalCodecPrivateSize); for (ByteBuffer byteBuffer : spsList) { bb.putInt(1); bb.put((byte) 0x67); bb.put(byteBuffer.duplicate()); } for (ByteBuffer byteBuffer : ppsList) { bb.putInt(1); bb.put((byte) 0x68); bb.put(byteBuffer.duplicate()); } bb.flip(); return bb; } public static ByteBuffer avcCToAnnexB(AvcCBox avcC) { return saveCodecPrivate(avcC.getSpsList(), avcC.getPpsList()); } public static AvcCBox parseAVCCFromBuffer(ByteBuffer bb) { return AvcCBox.parseAvcCBox(bb); } public static ByteBuffer writeSPS(SeqParameterSet sps, int approxSize) { ByteBuffer output = ByteBuffer.allocate(approxSize + 8); sps.write(output); output.flip(); H264Utils.escapeNALinplace(output); return output; } public static SeqParameterSet readSPS(ByteBuffer data) { ByteBuffer input = NIOUtils.duplicate(data); H264Utils.unescapeNAL(input); SeqParameterSet sps = SeqParameterSet.read(input); return sps; } public static ByteBuffer writePPS(PictureParameterSet pps, int approxSize) { ByteBuffer output = ByteBuffer.allocate(approxSize + 8); pps.write(output); output.flip(); H264Utils.escapeNALinplace(output); return output; } public static PictureParameterSet readPPS(ByteBuffer data) { ByteBuffer input = NIOUtils.duplicate(data); H264Utils.unescapeNAL(input); PictureParameterSet pps = PictureParameterSet.read(input); return pps; } public static PictureParameterSet findPPS(List<PictureParameterSet> ppss, int id) { for (PictureParameterSet pps : ppss) { if (pps.pic_parameter_set_id == id) return pps; } return null; } public static SeqParameterSet findSPS(List<SeqParameterSet> spss, int id) { for (SeqParameterSet sps : spss) { if (sps.seq_parameter_set_id == id) return sps; } return null; } public abstract static class SliceHeaderTweaker { protected List<SeqParameterSet> sps; protected List<PictureParameterSet> pps; protected abstract void tweak(SliceHeader sh); public SliceHeader run(ByteBuffer is, ByteBuffer os, NALUnit nu) { ByteBuffer nal = os.duplicate(); H264Utils.unescapeNAL(is); BitReader reader = BitReader.createBitReader(is); SliceHeader sh = shr.readPart1(reader); PictureParameterSet pp = findPPS(pps, sh.pic_parameter_set_id); return part2(is, os, nu, findSPS(sps, pp.pic_parameter_set_id), pp, nal, reader, sh); } public SliceHeader runSpsPps(ByteBuffer is, ByteBuffer os, NALUnit nu, SeqParameterSet sps, PictureParameterSet pps) { ByteBuffer nal = os.duplicate(); H264Utils.unescapeNAL(is); BitReader reader = BitReader.createBitReader(is); SliceHeader sh = shr.readPart1(reader); return part2(is, os, nu, sps, pps, nal, reader, sh); } private SliceHeader part2(ByteBuffer is, ByteBuffer os, NALUnit nu, SeqParameterSet sps, PictureParameterSet pps, ByteBuffer nal, BitReader reader, SliceHeader sh) { BitWriter writer = new BitWriter(os); shr.readPart2(sh, nu, sps, pps, reader); tweak(sh); shw.write(sh, nu.type == NALUnitType.IDR_SLICE, nu.nal_ref_idc, writer); if (pps.entropy_coding_mode_flag) copyDataCABAC(is, os, reader, writer); else copyDataCAVLC(is, os, reader, writer); nal.limit(os.position()); H264Utils.escapeNALinplace(nal); os.position(nal.limit()); return sh; } private void copyDataCAVLC(ByteBuffer is, ByteBuffer os, BitReader reader, BitWriter writer) { int wLeft = 8 - writer.curBit(); if (wLeft != 0) writer.writeNBit(reader.readNBit(wLeft), wLeft); writer.flush(); // Copy with shift int shift = reader.curBit(); if (shift != 0) { int mShift = 8 - shift; int inp = reader.readNBit(mShift); reader.stop(); while (is.hasRemaining()) { int out = inp << shift; inp = is.get() & 0xff; out |= inp >> mShift; os.put((byte) out); } os.put((byte) (inp << shift)); } else { reader.stop(); os.put(is); } } private void copyDataCABAC(ByteBuffer is, ByteBuffer os, BitReader reader, BitWriter writer) { long bp = reader.curBit(); if (bp != 0) { long rem = reader.readNBit(8 - (int) bp); if ((1 << (8 - bp)) - 1 != rem) throw new RuntimeException("Invalid CABAC padding"); } if (writer.curBit() != 0) writer.writeNBit(0xff, 8 - writer.curBit()); writer.flush(); reader.stop(); os.put(is); } } public static Size getPicSize(SeqParameterSet sps) { int w = (sps.pic_width_in_mbs_minus1 + 1) << 4; int h = SeqParameterSet.getPicHeightInMbs(sps) << 4; if (sps.frame_cropping_flag) { w -= (sps.frame_crop_left_offset + sps.frame_crop_right_offset) << sps.chroma_format_idc.compWidth[1]; h -= (sps.frame_crop_top_offset + sps.frame_crop_bottom_offset) << sps.chroma_format_idc.compHeight[1]; } return new Size(w, h); } public static List<SeqParameterSet> readSPSFromBufferList(List<ByteBuffer> spsList) { List<SeqParameterSet> result = new ArrayList<SeqParameterSet>(); for (ByteBuffer byteBuffer : spsList) { result.add(readSPS(NIOUtils.duplicate(byteBuffer))); } return result; } public static List<PictureParameterSet> readPPSFromBufferList(List<ByteBuffer> ppsList) { List<PictureParameterSet> result = new ArrayList<PictureParameterSet>(); for (ByteBuffer byteBuffer : ppsList) { result.add(readPPS(NIOUtils.duplicate(byteBuffer))); } return result; } public static List<ByteBuffer> writePPSList(List<PictureParameterSet> allPps) { List<ByteBuffer> result = new ArrayList<ByteBuffer>(); for (PictureParameterSet pps : allPps) { result.add(writePPS(pps, 64)); } return result; } public static List<ByteBuffer> writeSPSList(List<SeqParameterSet> allSps) { List<ByteBuffer> result = new ArrayList<ByteBuffer>(); for (SeqParameterSet sps : allSps) { result.add(writeSPS(sps, 256)); } return result; } public static void dumpFrame(FileChannelWrapper ch, SeqParameterSet[] values, PictureParameterSet[] values2, List<ByteBuffer> nalUnits) throws IOException { for (int i = 0; i < values.length; i++) { SeqParameterSet sps = values[i]; NIOUtils.writeInt(ch, 1); NIOUtils.writeByte(ch, (byte) 0x67); ch.write(writeSPS(sps, 128)); } for (int i = 0; i < values2.length; i++) { PictureParameterSet pps = values2[i]; NIOUtils.writeInt(ch, 1); NIOUtils.writeByte(ch, (byte) 0x68); ch.write(writePPS(pps, 256)); } for (ByteBuffer byteBuffer : nalUnits) { NIOUtils.writeInt(ch, 1); ch.write(byteBuffer.duplicate()); } } public static void toNAL(ByteBuffer codecPrivate, SeqParameterSet sps, PictureParameterSet pps) { ByteBuffer bb1 = ByteBuffer.allocate(512), bb2 = ByteBuffer.allocate(512); sps.write(bb1); pps.write(bb2); bb1.flip(); bb2.flip(); putNAL(codecPrivate, bb1, 0x67); putNAL(codecPrivate, bb2, 0x68); } public static void toNALList(ByteBuffer codecPrivate, List<ByteBuffer> spsList2, List<ByteBuffer> ppsList2) { for (ByteBuffer byteBuffer : spsList2) putNAL(codecPrivate, byteBuffer, 0x67); for (ByteBuffer byteBuffer : ppsList2) putNAL(codecPrivate, byteBuffer, 0x68); } private static void putNAL(ByteBuffer codecPrivate, ByteBuffer byteBuffer, int nalType) { ByteBuffer dst = ByteBuffer.allocate(byteBuffer.remaining() * 2); escapeNAL(byteBuffer, dst); dst.flip(); codecPrivate.putInt(1); codecPrivate.put((byte) nalType); codecPrivate.put(dst); } /** * Parses a list of SPS NAL units out of the codec private array. * * @param codecPrivate * An AnnexB formatted set of SPS/PPS NAL units. * @return A list of ByteBuffers containing PPS NAL units. */ public static List<ByteBuffer> getRawPPS(ByteBuffer codecPrivate) { return getRawNALUnitsOfType(codecPrivate, NALUnitType.PPS); } /** * Parses a list of SPS NAL units out of the codec private array. * * @param codecPrivate * An AnnexB formatted set of SPS/PPS NAL units. * @return A list of ByteBuffers containing SPS NAL units. */ public static List<ByteBuffer> getRawSPS(ByteBuffer codecPrivate) { return getRawNALUnitsOfType(codecPrivate, NALUnitType.SPS); } public static List<ByteBuffer> getRawNALUnitsOfType(ByteBuffer codecPrivate, NALUnitType type) { List<ByteBuffer> result = new ArrayList<ByteBuffer>(); for (ByteBuffer bb : splitFrame(codecPrivate.duplicate())) { NALUnit nu = NALUnit.read(bb); if (nu.type == type) { result.add(bb); } } return result; } }