Android 采集过程注意
Camera.addCallbackBuffer(byte[] data)
其中data的大小要紧密与采集数据的格式相关.- 如采集到的数据格式yuv422i,那么data的大小应该为width * height * 2.
Camera.setPreviewCallback(Camera.PreviewCallback cb)
- 每次调用
onPreviewFrame(...)
的末尾在添加一次Camera.addCallbackBuffer(byte[] data)
Android H264硬编码过程
- 生成编码器并且设置相关参数
public synchronized void open() {
//YUV420P的大小关系
byte[] yuv420 = new byte[mWidth * mHeight * 3 / 2];
//生成编码器并且设置相关参数
mediaCodec = MediaCodec.createEncoderByType("video/avc");
//编码格式参数设置,如果对yuv数据进行旋转以后,注意mWidth,
//mHeight的在90度或270度会颠倒,不设置正确的话对端会花屏
MediaFormat mediaFormat = MediaFormat.createVideoFormat(
"video/avc", mWidth, mHeight);
//设置码率,码率越低,失真越厉害
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
/**设置编码输入缓存大小,默认输入数据为yuv420大小,即1.5倍的宽高积,
如果使用的是其他格式,如yuv422那么就要手动设置大小,不然塞数据时
会报BufferOverflowException异常*/
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,
mWidth * mHeight * 3 / 2);
//设置帧率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
/**设置颜色格式(I420,YV12,NV21等)
如:MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar*/
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
//设置发送I帧的时间间隔
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//单位:s(秒)
//完成配置,启动
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAT_ENCODE);
mediaCodec.start();
}
- 进行编码
public synchronized int encode(byte[] in, int offset, byte[] out, int length) {
int pos = 0;
byte[] inBuf = in;
int l = length;
/**由于Android 摄像头默认采集的数据是NV21格式,所以要
转成MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
让编码器支持.*/
NV21toYUV420SemiPlannr(in, offset, yuv420, mWidth, mHeight);
try {
/**
注意此处获取inputBuffer和outputBuffer的方法,在android LOLLIPOP之后
的版本要修改inputBuffer = MediaCodec.getInputBuffer(index);
*/
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
/**
解码的时候,如果此处TIME_OUT非0会有个大坑,很多机子在这句卡死
*/
int inputBufferIndex = mediaCodec.dequeueInputBuffer(TIME_OUT);
if(inputBufferIndex >= 0){
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(inBuf, offset, l);
/**此处getMyTime()函数维护一个递增的时间戳
据说此处的第四个参数不传,第一个I帧以后,
mediaCodec.dequeueOutputBuffer()一直返回-1,
-1对应原生代码的再试一遍的意思*/
mediaCodec.queueInputBuffer(inputBufferIndex, 0, l, getMyTime(), 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
/**
此处的TIME_OUT是否要传,待研究.
*/
int outBufferIndex = mediaCodec.dequeueInputBuffer(bufferInfo, TIME_OUT);
//待理解继续
}
}
(待补充)
Android H264硬解码过程
- 生成MediaCodec对象并且设置好参数
//H264解码器
codec = MediaCodec.createDecoderByType("video/avc");
MeidaFormat mediaFormat = MediaFormat.createVideoFormat(
"video/avc", width, height);
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width * height);
codec.configure(mediaFormat, surface, null, 0);
codec.start();
- 进行解码并且播放
public void decodeAndPlayBack(byte[] in, int offset, int length) {
//获取喂数据的ByteBuffer数组
ByteBuffer[] inputBuffers = codec.getInputBuffers();
/**以下特别注意,TIME_OUT建议设置成0,设置非0很多机子
出现卡死在此句代码,设置成0的代价只是丢帧*/
int inputBuffersIndex = codec.dequeueInputBuffer(TIME_OUT);
if(inputBuffersIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBuffersIndex];
inputBuffer.clear();
inputBuffer.put(in, offset, length);
//填充好数据以后,提交通知解码器解码,这几个参数待研究
codec.queueInputBuffer(inputBuffersIndex, 0, length, 0, 0);
}
//释放缓存空间
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >=0) {
codec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
}
}