首页 > 应用开发 > IT综合 > Android短视频系统硬编码—实现音视频编码(二)
短视频系统视频的编码和上篇文章的短视频系统音频的编码也大同小异。摄像头的数据回调时间并不是确定的,就算你设置了摄像头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
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70002045/viewspace-2779320/ ,如需转载,请注明出处,否则将追究法律责任。