一般而言,使用MediaCodec配合OpenGLES编码摄像头数据是常规做法。如果要限制帧率,最简单的方法是限制OpenGLES的绘制间隔时间。
假设使用GLSurfaceView进行绘制,设置RenderMode为RENDERMODE_WHEN_DIRTY,在SurfaceTexture监听onFrameAvailable()来通知GLSurfaceView绘制时,一般手机能达到60FPS。
按照上面的说法,例如要限制到30FPS,只需要在onFrameAvailable()或者onDrawFrame()中加入sleep(),每次绘制后睡眠一定时间,这样预览画面和实际编码出来的MP4将会是同样的30FPS。
注:使用
Camera设定预览帧率貌似不起作用,起码在我的华为Mate9 Pro上没有效果。
但是,我希望可以做到预览画面帧率不变,编码时限制FPS,就需要额外启动线程来处理。
由于MediaCodec中使用InputSurface编码数据时需要运行在GL线程,一般的做法都是额外启动线程->初始化EGL环境->编码。在这基础上,只要我们限制通知MediaCodec编码的频率,就可以限制编码出来的数据的FPS。
下面是自己demo中的部分代码:
@Override
public void onDrawFrame(GL10 gl) {
if (!mMediaRecorder.isInit()) {
mMediaRecorder.setEglContext(EGL14.eglGetCurrentContext());
mMediaRecorder.setTextureId(mCameraDrawable.getTextureId());
mMediaRecorder.init();
}
if (!mMediaRecorder.isStart()) {
mMediaRecorder.startRecord();
new Thread() {
@Override
public void run() {
int fps = 20;
long fpsTime = 1000 / fps;
long time = System.currentTimeMillis();
long diffTime = 0;
while (true) {
LogUtils.i("start");
final SurfaceTexture surfaceTexture = mCameraDrawable.getSurfaceTexture();
surfaceTexture.getTransformMatrix(mTextureMartix);
mMediaRecorder.record(surfaceTexture.getTimestamp(), mTextureMartix);
diffTime = System.currentTimeMillis() - time;
if (diffTime < fpsTime) {
try {
sleep(fpsTime - diffTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!mMediaRecorder.isStart()){
break;
}
time = System.currentTimeMillis();
}
}
}.start();
}
mCameraDrawable.draw();
mFpsPrinter.printFPS();
}
上述代码中新起的线程就是控制MediaCodec编码频率的。值得一提的是,MediaCodec使用InputSurface编码时,都是需要通过SurfaceTexture来获取每一帧的时间戳。过去我曾经陷入过迷思,不知道如何控制时间戳来控制帧率。后来想想,其实在编译每一帧时获取其对应的时间戳即可。
编码时加速、减速视频
假设我们想保持帧率不变,但是实际编码出来的MP4是加速或减速过的视频,可以通过修改上面提到的时间戳效果。比如,将输入的SurfaceTexture.getTimestamp()乘以2,再传递给EGLExt.eglPresentationTimeANDROID(),最终的效果就是整个视频是加快一倍的;减速同理,除以指定系数即可。