如何从RTP包中,抽取完整H264帧,以便用于解码器解码(MediaCodec)?
How to extract full H264 frame from RTP package for MediaCodec decode?
BE.java:
package abc.camera;
public class BE {
public static int getuint8(byte[] data, int offset) {
return ((int)data[offset]) & 0xff;
}
public static int getuint16(byte[] data, int offset) {
int ret;
ret = ((int) data[offset] & 0xff) << 8;
ret += ((int)data[offset + 1]) & 0xff;
return ret;
}
public static int getuint32(byte[] data, int offset) {
int ret;
ret = ((int) data[offset] & 0xff) << 24;
ret += ((int) data[offset + 1] & 0xff) << 16;
ret += ((int) data[offset + 2] & 0xff) << 8;
ret += ((int) data[offset + 3] & 0xff);
return ret;
}
public static long getuint64(byte[] data, int offset) {
long ret;
ret = ((long) data[offset] & 0xff) << 56;
ret += ((long) data[offset + 1] & 0xff) << 48;
ret += ((long) data[offset + 2] & 0xff) << 40;
ret += ((long) data[offset + 3] & 0xff) << 32;
ret += ((long) data[offset + 4] & 0xff) << 24;
ret += ((long) data[offset + 5] & 0xff) << 16;
ret += ((long) data[offset + 6] & 0xff) << 8;
ret += ((long) data[offset + 7] & 0xff);
return ret;
}
public static void setuint8(byte data[], int offset, int value) {
data[offset+0] = (byte)(value & 0xff);
}
public static void setuint16(byte data[], int offset, int value) {
data[offset+1] = (byte)(value & 0xff);
value >>= 8;
data[offset+0] = (byte)(value & 0xff);
}
public static void setuint32(byte data[], int offset, int value) {
data[offset+3] = (byte)(value & 0xff);
value >>= 8;
data[offset+2] = (byte)(value & 0xff);
value >>= 8;
data[offset+1] = (byte)(value & 0xff);
value >>= 8;
data[offset+0] = (byte)(value & 0xff);
}
public static void setuint64(byte data[], int offset, long value) {
data[offset+7] = (byte)(value & 0xff);
value >>= 8;
data[offset+6] = (byte)(value & 0xff);
value >>= 8;
data[offset+5] = (byte)(value & 0xff);
value >>= 8;
data[offset+4] = (byte)(value & 0xff);
value >>= 8;
data[offset+3] = (byte)(value & 0xff);
value >>= 8;
data[offset+2] = (byte)(value & 0xff);
value >>= 8;
data[offset+1] = (byte)(value & 0xff);
value >>= 8;
data[offset+0] = (byte)(value & 0xff);
}
public static short[] byteArray2shortArray(byte in[], short out[]) {
int len = in.length / 2;
if (out == null) out = new short[len];
int p = 0;
short val;
for (int a = 0; a < len; a++) {
val = (short)((((short)in[p++]) & 0xff) << 8);
val += (((short)in[p++]) & 0xff);
out[a] = val;
}
return out;
}
public static int[] byteArray2intArray(byte in[], int out[]) {
int len = in.length / 4;
if (out == null) out = new int[len];
int p = 0;
int val;
for (int a = 0; a < len; a++) {
val = (((int)in[p++]) & 0xff) << 24;
val += (((int)in[p++]) & 0xff) << 16;
val += (((int)in[p++]) & 0xff) << 8;
val += (((int)in[p++]) & 0xff);
out[a] = val;
}
return out;
}
public static byte[] shortArray2byteArray(short in[], byte out[]) {
int len = in.length;
if (out == null) out = new byte[len * 2];
int p = 0;
short val;
for (int a = 0; a < len; a++) {
val = in[a];
out[p++] = (byte) ((val & 0xff00) >>> 8);
out[p++] = (byte) (val & 0xff);
}
return out;
}
public static byte[] intArray2byteArray(int in[], byte out[]) {
int len = in.length;
if (out == null) out = new byte[len * 4];
int p = 0;
int val;
for (int a = 0; a < len; a++) {
val = in[a];
out[p++] = (byte) ((val & 0xff000000) >>> 8);
out[p++] = (byte) ((val & 0xff0000) >> 8);
out[p++] = (byte) ((val & 0xff00) >> 8);
out[p++] = (byte) (val & 0xff);
}
return out;
}
}
RTPH264.java
package abc.h264;
import java.util.ArrayList;
import java.util.Arrays;
public class RTPH264 {
private static final int mtu = 1440;
private byte partial[] = new byte[0];
private int lastseqnum = -1;
private int last_timestamp = -1;
private int find_best_length(byte data[], int offset, int length) {
for (int a = 1; a < length - 3; a++) {
if (data[offset + a] == 0 && data[offset + a + 1] == 0 && data[offset + a + 2] == 1)
return a;
}
return length;
}
public boolean NeedDecode(byte rtp[]) {
if (rtp.length < 1500) return false;
int fu_header_len = 12;
int extension = (rtp[0] >> 4) & 1;
if (extension > 0) {
int extLen = (rtp[14] << 8) + rtp[15];
fu_header_len += (extLen + 1) * 4;
}
int type = rtp[fu_header_len] & 0x1f;
return type == 28;
}
public static int getTimestamp(byte[] data, int off) {
return BE.getuint32(data, 4 + off);
}
public static int getSeqnum(byte[] data, int off) {
return BE.getuint16(data, 2 + off);
}
public byte[] decode(byte rtp[], int length) {
int fu_header_len = 12;
if (length < 12 + 2) return null;
int extension = (rtp[0] >> 4) & 1;
if (extension > 0) {
if (length < fu_header_len + 4)
return null;
int extLen = (rtp[14] << 8) + rtp[15];
fu_header_len += (extLen + 1) * 4;
}
int h264Length = length - fu_header_len;
if (h264Length == 0) {
return null;
}
int type = rtp[fu_header_len] & 0x1f;
if (partial == null) {
partial = new byte[0];
}
byte[] ret = null;
if (type == 28) {
int thistimestamp = getTimestamp(rtp, 0);
if (last_timestamp == -1) last_timestamp = thistimestamp;
if (thistimestamp != last_timestamp && partial.length > 0) {
ret = partial;
partial = new byte[0];
}
if ((rtp[13] & 0x80) == 0x80) {
int nri = rtp[12] & 0x60;
type = rtp[13] & 0x1f;
partial = Arrays.copyOf(partial, partial.length + 5);
partial[partial.length - 2] = 1;
partial[partial.length - 1] = (byte) (nri + type);
lastseqnum = getSeqnum(rtp, 0);
} else {
int thisseqnum = getSeqnum(rtp, 0);
if (thisseqnum != lastseqnum + 1) {
partial = null;
lastseqnum = -1;
last_timestamp = -1;
return null;
}
lastseqnum = thisseqnum;
}
last_timestamp = thistimestamp;
int partialLength = partial.length;
h264Length -= 2;
partial = Arrays.copyOf(partial, partial.length + h264Length);
System.arraycopy(rtp, fu_header_len + 2, partial, partialLength, h264Length);
} else {
ret = new byte[h264Length + 3];
ret[0] = 0;
ret[1] = 0;
ret[2] = 1;
System.arraycopy(rtp, fu_header_len, ret, 3, h264Length);
}
return ret;
}
boolean leak = false;
int leak_len = 0;
public byte[][] decode2(byte rtp[]) {
ArrayList<byte[]> packets = new ArrayList<byte[]>();
int start = 2;
if (leak) {
if (leak_len > rtp.length) {
int partialLength = partial.length;
partial = Arrays.copyOf(partial, partial.length + rtp.length);
System.arraycopy(rtp, 0, partial, partialLength, rtp.length);
return null;
} else {
int partialLength = partial.length;
partial = Arrays.copyOf(partial, partial.length + leak_len);
System.arraycopy(rtp, 0, partial, partialLength, leak_len);
byte[] full = partial;
packets.add(full);
partial = new byte[0];
start = leak_len;
leak_len = 0;
leak = false;
}
}
while (start < rtp.length) {
start++;
if (rtp[start - 3] == 0 && rtp[start - 2] == 0 && rtp[start - 1] == 1 && rtp[start] == -32) {
int len = (rtp[start + 1] << 8) + (rtp[start + 2] & 0xff);
start += 3;
if (start + len > rtp.length) {
leak_len = rtp.length - start;
len = rtp.length - start;
int partialLength = partial.length;
partial = Arrays.copyOf(partial, partial.length + len);
System.arraycopy(rtp, start, partial, partialLength, len);
leak = true;
break;
} else
{
byte[] full = new byte[len];
System.arraycopy(rtp, start, full, 0, len);
packets.add(full);
start += len;
}
}
}
return packets.toArray(new byte[0][0]);
}
public byte[] decodePS(byte rtp[]) {
int start = 3;
while (start < rtp.length) {
if (rtp[start - 3] == 0 && rtp[start - 2] == 0 && rtp[start - 1] == 1 && rtp[start] == -70) {
int len = (rtp[start + 1] << 8) + rtp[start + 2];
start += 3;
if (start + len > rtp.length) {
leak_len = rtp.length - start;
len = rtp.length - start;
int partialLength = partial.length;
partial = Arrays.copyOf(partial, partial.length + len);
System.arraycopy(rtp, start, partial, partialLength, len);
leak = true;
break;
} else
{
byte[] full = new byte[len];
System.arraycopy(rtp, start, full, 0, len);
start += len;
}
}
}
return null;
}
}
用法:
private RTPH264 rtp = new RTPH264();
byte[] frame = rtp.decode(pBuffer, dwBufSize);
if (frame != null) {
Bitmap bitmap = decodeVideo(frame, frame.length);
}
frame就是一个完整的H264帧,可以用于MediaCodec解码,下面代码就可以在界面上看到视频了!!
下面decode初始化如下:
decodec = MediaCodec.createDecoderByType("video/avc");
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 1920, 1080);
mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
decodec.configure(mediaFormat, svPreview.getHolder().getSurface(), null, 0);
decodec.start();
private void decodeVideo(byte[] buf, int size) {
ByteBuffer[] inputBuffers = decodec.getInputBuffers();
int inputBufferIndex = decodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(buf, 0, size);
decodec.queueInputBuffer(inputBufferIndex, 0, size, System.nanoTime() / 1000, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = decodec.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
decodec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = decodec.dequeueOutputBuffer(bufferInfo, 0);
}
}