视频编码实例
短视频系统视频的编码和上篇文章的短视频系统音频的编码也大同小异。摄像头的数据回调时间并不是确定的,就算你设置了摄像头FPS范围为30-30帧,它也不会每秒就一定给你30帧数据。Android摄像头的数据回调,受光线的影响非常严重,这是由HAL层的3A算法决定的,你可以将自动曝光补偿、自动白平光等等给关掉,这样你才有可能得到稳定的帧率。
而我们录制并编码视频的时候,肯定是希望得到一个固定帧率的短视频系统视频。所以在短视频系统视频录制并进行编码的过程中,需要自己想些法子,让帧率固定下来。最简单也是最有效的做法就是,按照固定时间编码,如果没有新的摄像头数据回调来就用上一帧的数据。
参考代码如下:
private String mime="video/avc"; //编码的MIME
private int rate=256000; //波特率,256kb
private int frameRate=24; //帧率,24帧
private int frameInterval=1; //关键帧一秒一关键帧
//和音频编码一样,设置编码格式,获取编码器实例
MediaFormat format=MediaFormat.createVideoFormat(mime,width,height);
format.setInteger(MediaFormat.KEY_BIT_RATE,rate);
format.setInteger(MediaFormat.KEY_FRAME_RATE,frameRate);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,frameInterval);
//这里需要注意,为了简单这里是写了个固定的ColorFormat
//实际上,并不是所有的手机都支持COLOR_FormatYUV420Planar颜色空间
//所以正确的做法应该是,获取当前设备支持的颜色空间,并从中选取
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
mEnc=MediaCodec.createEncoderByType(mime);
mEnc.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
//同样,准备好了后,开始编码器
mEnc.start();
//编码器正确开始后,在子线程中循环编码,固定码率的话,就是一个循环加上线程休眠的时间固定
//流程和音频编码一样,取出空盒子,往空盒子里面加原料,放回盒子到原处,
//盒子中原料被加工,取出盒子,从盒子里面取出成品,放回盒子到原处
int index=mEnc.dequeueInputBuffer(-1);
if(index>=0){
if(hasNewData){
if(yuv==null){
yuv=new byte[width*height*3/2];
}
//把传入的rgba数据转成yuv的数据,转换在网上也是一大堆,不够下面还是一起贴上吧
rgbaToYuv(data,width,height,yuv);
}
ByteBuffer buffer=getInputBuffer(index);
buffer.clear();
buffer.put(yuv);
//把盒子和原料一起放回到传送带上原来的位置
mEnc.queueInputBuffer(index,0,yuv.length,timeStep,0);
}
MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo();
//尝试取出加工好的数据,和音频编码一样,do while和while都行,觉得怎么好怎么写
int outIndex=mEnc.dequeueOutputBuffer(mInfo,0);
while (outIndex>=0){
ByteBuffer outBuf=getOutputBuffer(outIndex);
byte[] temp=new byte[mInfo.size];
outBuf.get(temp);
if(mInfo.flags==MediaCodec.BUFFER_FLAG_CODEC_CONFIG){
//把编码信息保存下来,关键帧上要用
mHeadInfo=new byte[temp.length];
mHeadInfo=temp;
}else if(mInfo.flags%8==MediaCodec.BUFFER_FLAG_KEY_FRAME){
//关键帧比普通帧是多了个帧头的,保存了编码的信息
byte[] keyframe = new byte[temp.length + mHeadInfo.length];
System.arraycopy(mHeadInfo, 0, keyframe, 0, mHeadInfo.length);
System.arraycopy(temp, 0, keyframe, mHeadInfo.length, temp.length);
Log.e(TAG,"other->"+mInfo.flags);
//写入文件
fos.write(keyframe,0,keyframe.length);
}else if(mInfo.flags==MediaCodec.BUFFER_FLAG_END_OF_STREAM){
//结束的时候应该发送结束信号,在这里处理
}else{
//写入文件
fos.write(temp,0,temp.length);
}
mEnc.releaseOutputBuffer(outIndex,false);
outIndex=mEnc.dequeueOutputBuffer(mInfo,0);
}
//数据的来源,GL处理好后,readpix出来的RGBA数据喂进来,
public void feedData(final byte[] data, final long timeStep){
hasNewData=true;
nowFeedData=data;
nowTimeStep=timeStep;
}
//RGBA转YUV的方法,这是最简单粗暴的方式,在使用的时候,一般不会选择在Java层,用这种方式做转换
private void rgbaToYuv(byte[] rgba,int width,int height,byte[] yuv){
final int frameSize = width * height;
int yIndex = 0;
int uIndex = frameSize;
int vIndex = frameSize + frameSize/4;
int R, G, B, Y, U, V;
int index = 0;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
index = j * width + i;
if(rgba[index*4]>127||rgba[index*4]<-128){
Log.e("color","-->"+rgba[index*4]);
}
R = rgba[index*4]&0xFF;
G = rgba[index*4+1]&0xFF;
B = rgba[index*4+2]&0xFF;
Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
yuv[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
if (j % 2 == 0 && index % 2 == 0) {
yuv[uIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
yuv[vIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));
}
}
}
}
对于短视频系统其他格式的音频视频编解码也大同小异了,只要MediaCodec支持就好。
————————————————
声明:声明:本文由云豹科技转发自CSDN【湖广午王】博客,如有侵权请联系作者删除
原文链接:https://blog.csdn.net/junzia/article/details/54018671