package com.o3dr.android.client.utils.video; import android.annotation.TargetApi; import android.media.MediaCodec; import android.os.Build; import android.util.Log; import java.nio.ByteBuffer; import java.util.Locale; /** * Created by Fredia Huya-Kouadio on 6/1/15. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) class NaluChunkAssembler { private static final String TAG = NaluChunkAssembler.class.getSimpleName(); private final NaluChunk assembledNaluChunk; private final NaluChunk paramsNaluChunk; private final NaluChunk eosNaluChunk; /** * Stores the sps data so it can be concatenate with the pps data. */ private final static int SPS_BUFFER_INDEX = 0; private boolean isSpsSet = false; /** * Stores the pps data so it can be concatenate with the sps data. */ private final static int PPS_BUFFER_INDEX = 1; private boolean isPpsSet = false; NaluChunkAssembler(){ this.assembledNaluChunk = new NaluChunk(1, 1024 * 1024, NaluChunk.START_CODE); this.paramsNaluChunk = new NaluChunk(2, 256, NaluChunk.START_CODE); this.paramsNaluChunk.type = 78; this.paramsNaluChunk.flags = MediaCodec.BUFFER_FLAG_CODEC_CONFIG; this.eosNaluChunk = new NaluChunk(1, 0, null); this.eosNaluChunk.flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM; } void reset(){ isSpsSet = false; isPpsSet = false; this.assembledNaluChunk.payloads[0].reset(); this.paramsNaluChunk.payloads[0].reset(); this.paramsNaluChunk.payloads[1].reset(); } private boolean areParametersSet() { return isSpsSet && isPpsSet; } NaluChunk getEndOfStream(){ return eosNaluChunk; } private int prevSeq = -1; private int naluCounter = 0; private final static long DELTA_PRESENTATION_TIME = 42000L; NaluChunk getParametersSet(){ if(areParametersSet()) return paramsNaluChunk; return null; } NaluChunk assembleNALUChunk(byte[] buffer, int bufferLength) { //The first 12 bytes are the rtp header. final byte nalHeaderByte = buffer[12]; final int forbiddenBit = (nalHeaderByte & 0x80) >> 7; if (forbiddenBit != 0) { Log.w(TAG, "Forbidden bit is set, indicating possible errors."); return null; } long rtpTimestamp = 0; rtpTimestamp |= (buffer[4] & 0xffl) << 24; rtpTimestamp |= (buffer[5] & 0xffl) << 16; rtpTimestamp |= (buffer[6] & 0xffl) << 8; rtpTimestamp |= (buffer[7] & 0xffl); final int sequenceNumber = ((buffer[2] & 0xff) << 8) | (buffer[3] & 0xff); final int nalType = nalHeaderByte & 0x1f; if (nalType <= 0) { Log.d(TAG, "Undefined nal type: " + nalType); return null; } //DEBUG LOGIC if(prevSeq != -1){ final int expectedSeq = prevSeq + 1; if(sequenceNumber != expectedSeq){ Log.v(TAG, String.format(Locale.US, "Sequence number is out of order: %d != %d", expectedSeq, sequenceNumber)); } } prevSeq = sequenceNumber; //DEBUG LOGIC if (nalType <= 23) { //Single nal unit packet. final int payloadOffset = 12; final int payloadLength = bufferLength - payloadOffset; switch (nalType) { case 7: //SPS parameters set. case 8: //PPS parameters set. { ByteBuffer naluData; if (nalType == NaluChunk.SPS_NAL_TYPE) { naluData = paramsNaluChunk.payloads[SPS_BUFFER_INDEX]; isSpsSet = true; } else { naluData = paramsNaluChunk.payloads[PPS_BUFFER_INDEX]; isPpsSet = true; } naluData.reset(); naluData.put(buffer, payloadOffset, payloadLength); if (areParametersSet()) { paramsNaluChunk.sequenceNumber = sequenceNumber; paramsNaluChunk.presentationTime = 0; return paramsNaluChunk; } return null; } default: if (!areParametersSet()) return null; ByteBuffer assembledNaluBuffer = assembledNaluChunk.payloads[0]; assembledNaluBuffer.reset(); assembledNaluBuffer.put(buffer, payloadOffset, payloadLength); assembledNaluChunk.type = nalType; assembledNaluChunk.sequenceNumber = sequenceNumber; assembledNaluChunk.flags = 0; assembledNaluChunk.presentationTime = naluCounter++ * DELTA_PRESENTATION_TIME; return assembledNaluChunk; } } if (nalType == 28) { //Fragmentation unit if (!areParametersSet()) return null; final int payloadOffset = 14; final int payloadLength = bufferLength - payloadOffset; final int fuIndicatorByte = nalHeaderByte; final int fuHeaderByte = buffer[13]; final int fuNalType = fuHeaderByte & 0x1f; final int startBit = (fuHeaderByte & 0x80) >> 7; final int endBit = (fuHeaderByte & 0x40) >> 6; if (startBit == 1) { ByteBuffer assembledNaluBuffer = assembledNaluChunk.payloads[0]; assembledNaluBuffer.reset(); assembledNaluBuffer.put((byte) ((fuIndicatorByte & 0xe0) | fuNalType)); assembledNaluBuffer.put(buffer, payloadOffset, payloadLength); boolean isConfig = fuNalType == 7 || fuNalType == 8; assembledNaluChunk.sequenceNumber = sequenceNumber; assembledNaluChunk.type = fuNalType; assembledNaluChunk.flags = isConfig ? MediaCodec.BUFFER_FLAG_CODEC_CONFIG : 0; // assembledNaluChunk.presentationTime = rtpTimestamp; return null; } else { if (sequenceNumber - 1 != assembledNaluChunk.sequenceNumber) { return null; } ByteBuffer assembledNaluBuffer = assembledNaluChunk.payloads[0]; assembledNaluBuffer.put(buffer, payloadOffset, payloadLength); assembledNaluChunk.sequenceNumber = sequenceNumber; if (endBit == 1) { assembledNaluChunk.presentationTime = naluCounter++ * DELTA_PRESENTATION_TIME; return assembledNaluChunk; } else { return null; } } } return null; } }