版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zxd_Android/article/details/81436669
视频编解码,编的是什么码?解的又是什么码?有没有想过?现在主流的就是H264码流,Android 采集摄像头原始帧数据
这篇博客讲解的是如何从摄像头从提取YUV画面色值,然后由MediaCodec进行编码压缩,最后生成的就是H264码流,我们先了解下H264码流格式。
可以看到一个个NALU单元组成了H264码流,NALU单元又包含头数据部分和帧数据部分。
每一个头开始都包含0x 00000001或者0x000001.
我选取了上面讲的采集摄像头画面进行编码后的H264文件,打开其字节文件,码流格式数据如下
可以看到手机编码后的码流每个NAL开头起始码为0x00000001
所以我要做的工作就是提取出每一个NAL单元,然后送给MediaCodec进行解码。
提取出NAL 单元的代码函数如下
private byte[] getNALU() {
try {
int curpos = 0;
//一般NAL不超过100000字节
byte[] bb = new byte[100000];
//先读取4个字节
rf.read(bb, 0, 4);
//判断是否是0x00000001开头
if (findStartCode4(bb, 0)) {
curpos = 4;
} else {
rf.seek(0);
rf.read(bb, 0, 3);
//判断是否是0x000001开头
if (findStartCode3(bb, 0)) {
curpos = 3;
}
}
//标志是否找到NAL单元开头
boolean findNALStartCode = false;
//下一个NAL单元的开始位置
int nextNalStartPos = 0;
//找到适合标记开头的长度
int reWind = 0;
while (!findNALStartCode) {
int hex = rf.read();
if (curpos >= bb.length) {
break;
}
bb[curpos++] = (byte) hex;
if (hex == -1) {
nextNalStartPos = curpos;
}
if (findStartCode4(bb, curpos - 4)) {
findNALStartCode = true;
reWind = 4;
nextNalStartPos = curpos - reWind;
} else if (findStartCode3(bb, curpos - 3)) {
findNALStartCode = true;
reWind = 3;
nextNalStartPos = curpos - reWind;
}
}
byte[] nal = new byte[nextNalStartPos];
System.arraycopy(bb, 0, nal, 0, nextNalStartPos);
long pos = rf.getFilePointer();
long setPos = pos - reWind;
//退回rewind长度字节
rf.seek(setPos);
return nal;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//find match "00 00 00 01"
private boolean findStartCode4(byte[] bb, int offSet) {
if (offSet < 0) {
return false;
}
if (bb[offSet] == 0 && bb[offSet + 1] == 0 && bb[offSet + 2] == 0 && bb[offSet + 3] == 1) {
return true;
}
return false;
}
//find match "00 00 01"
private boolean findStartCode3(byte[] bb, int offSet) {
if (offSet <= 0) {
return false;
}
if (bb[offSet] == 0 && bb[offSet + 1] == 0 && bb[offSet + 2] == 1) {
return true;
}
return false;
}
封装其数据,读取每一NAL单元
/**
* 读取每一帧数据
* @param buffer
* @return
*/
public int readSampleData(ByteBuffer buffer) {
byte[] nal = getNALU();
buffer.put(nal);
return nal.length;
}
MediaCodec开启解码线程,和 Android MediaCodec,MediaExtractor解码播放MP4文件中解码一样,将NAL单元数据送给解码器即可。
/**
* 解析播放H264码流
*/
private class DecoderH264Thread extends Thread {
long pts = 0;
@Override
public void run() {
super.run();
while (!isDecodeFinish) {
int inputIndex = mediaCodec.dequeueInputBuffer(-1);
if (inputIndex >= 0) {
ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inputIndex);
int sampSize = DecodeH264File.getInstance().readSampleData(byteBuffer);
long time = computePresentationTime();
if (sampSize > 0 && time > 0) {
mediaCodec.queueInputBuffer(inputIndex, 0, sampSize, time, 0);
try {
sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
BufferInfo bufferInfo = new BufferInfo();
int outIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
if (outIndex >= 0) {
mediaCodec.releaseOutputBuffer(outIndex, true);
}
}
}
}
好了,到这里结束了,有什么不明白的,欢迎留言~~
CSDN代码下载地址
https://download.csdn.net/download/zxd_android/10584840
GitHub
https://github.com/zxd1991/AndroidMedia
如果你觉得有用,给star,谢谢哈。