概述
在看本篇文章之前请务必先查看这面三篇文章:
第一篇:Android音视频录制概述
第二篇Android音视频录制(1)——Surface录制
第三篇Android音视频录制(2)——Buffer录制
全关键帧录制顾名思义,就是视频所有帧都是关键帧(I帧),毫无疑问,全I帧的视频肯定会比正常录制的视频要大很多,但是为什么需要全I帧录制的视频?原因就是,大部分音视频app录制完视频之后都要对视频进行编辑吧,但是如果不是全I帧录制的视频文件,编辑起来会非常困难,而全I帧视频肯定很容易编辑。比如你要做一个时光倒流的功能,或者你要对视频加特效,哪怕最简单的,视频播放拖动进度(精确seek而非关键帧seek),全I帧视频肯定比普通视频流畅很多很多。所以全关键帧录制对于录制后的视频编辑是非常重要的。
那根本原因是什么?
我举一个例子,视频的seek操作:首先要知道视频的seek分两种,一种是精确seek,一种是关键帧seek(目前绝大部分的播放器都是关键帧seek)。
假设一个视频是3秒,视频帧为10帧/秒,关键帧间隔为1秒,那么这个视频第1帧为关键帧,第11帧为关键帧,第21帧为关键帧,比如我现在需要做一个seek操作,seek到1.3秒,如果采用的是关键帧seek,它会seek到第11帧(也有可能是第21帧,这个是看播放器是根据哪个原则来seek,一般有这三种关键帧seek原则,向下取关键帧/向上取关键帧/最近关键帧),而精确seek的话,是seek到13帧,这就是关键帧seek和精确seek的区别。但涉及到视频编辑的app无一例外,精确seek才是最符合产品设计的。
所以如果我对于非全关键帧的视频我要seek到1.3秒位置,应该如何做?
首先去到最近的关键帧,第11帧,然后根据H264的编码原理,一直根据这个关键帧解码到第13帧,这个过程非常非常耗时,无论你采用多么高级的解码器,多么高深的算法,这个过程都几乎不可优化。但是如果你这个视频是全关键帧录制的就不同了,直接seek到第13帧,用不着任何的解码,所以这个过程会非常的流畅。尤其遇到这样的产品需求的时候:拖动进度条,视频画面随进度滚动。如果产品需要精确seek,而且视频又不是全关键帧,这个时候就GG了。
所以很多的视频编辑都会设计到视频帧的重编解码,采用关键帧,会使这个重编解码的过程高效很多。
说了那么多,那么我下面就说说怎样录制全关键帧视频。再次说明,要看此篇文章,请务必先查看上面说的三篇文章。
全关键帧录制——buffer录制
1,配置关键帧间隔为0
在buffer录制中,我在编码器的prepared函数中有这样的一段代码:首先配置编码器的时候需要配置关键帧间隔为0
if(!mIsAllKeyFrame) {
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); //单位是 秒
}else{
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0);//设置为0
}
2,输入buffer的时候请求关键帧
在编码器的input()buffer时候,需要请求关键帧,需要在dequeueInputBuffer操作之前
if(mIsAllKeyFrame){
requestKeyFrame();
}
请求关键帧代码:
@TargetApi(19)
protected void requestKeyFrame() {
if (mIsAllKeyFrame){
try {
Bundle reqKeyCmd = new Bundle();
reqKeyCmd.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
mMediaCodec.setParameters(reqKeyCmd);
} catch (Exception e) {
}
}
}
3,输出编码器buffer的时候也要请求关键帧
请求关键帧操作需要在dequeueOutputBuffer之前
if(mIsAllKeyFrame){
requestKeyFrame();
}
这样子我们得到的视频就是关键帧视频了。
全关键帧视频录制——Surface录制
1,Surface录制的时候也一样,配置编码器的时候设置关键帧间隔为0
if(!mIsAllKeyFrame) {
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
}else{
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0);//设置全关键帧
}
2,EGL绘制前需要请求关键帧,绘制后也需要请求关键帧(这里不同于buffer录制)
//egl 绘制
public void render(float[] surfaceTextureMatrix, float[] mvpMatrix) {
if(isAllKeyFrame()){
requestKeyFrame();
}
mRenderer.draw(surfaceTextureMatrix, mvpMatrix);
if(isAllKeyFrame()){
requestKeyFrame();
}
}
3,同样是在输出的时候需要请求关键帧,这个操作和buffer录制一样,都需要在dequeueOutputBuffer之前操作。
@Override
public void output(boolean isEos) {
if(isAllKeyFrame()){
requestKeyFrame();
}
super.output(isEos);
}
至此,关键帧视频录制的已经讲完了,欢迎小伙伴们的留言!