我正在制作一个视频流应用程序,使视频比特率适应可用的上行链路带宽,我希望它能动态地改变视频分辨率,以便在较低的比特率上没有那么多的压缩伪像。虽然我通过发布MediaCodec
并在abortCaptures()
上调用stopRepeating()
和CameraCaptureSession
然后为新分辨率配置所有内容来实现这一点,但这会导致流中断非常明显 - 在我的测试中至少半秒钟。
类似于this,当相机不支持所需的分辨率时,我使用OpenGL来缩放图像。我使用两个表面初始化捕获会话 - 一个用于预览用户(使用TextureView
),另一个用于编码器,即MediaCodec的输入表面直接或我的OpenGL纹理表面。
这可以通过使用MediaCodec.createPersistentInputSurface()
来解决,因为我将能够在分辨率变化中重用这个缩放器实例,并且不必对捕获会话做任何事情,因为就摄像机而言,没有发生表面变化,但是它仅在API 23之后可用,我也需要此实现来支持API 21。
然后还有表面无效和重新创建的问题。例如,当用户按下后退按钮时,活动及其包含的TextureView
将被破坏,从而使预览表面无效。然后当用户再次导航到该活动时,会创建一个新的TextureView
,我需要开始在其中显示预览,而不会给缩放器/编码器看到的流引入任何延迟。
所以,我的问题一般来说:如何在CameraCaptureSession
中更改输出表面的集合,或重新创建CameraCaptureSession
,同时尽可能少地引入视频流?
事实证明,OpenGL上下文实际上是包含纹理的内容,包括相机提供帧的纹理,它与任何特定的输出目的地都没有关联。所以我能够让我的视频缩放器在初始化后更改其输出表面,如下所示:
...
}else if(inputMessage.what==MSG_CHANGE_SURFACE){
// Detach the current thread from the context, as a precaution
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
checkEglError("eglMakeCurrent 1");
// Destroy the old EGL surface and release its Android counterpart
// the old surface belongs to the previous, now released, MediaCodec instance
EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
checkEglError("eglDestroySurface");
surface.release(); // surface is a field that holds the current MediaCodec encoder input surface
surface=(Surface) inputMessage.obj;
dstW=inputMessage.arg1; // these are used in glViewport and the fragment shader
dstH=inputMessage.arg2;
// Create a new EGL surface for the new MediaCodec instance
int[] surfaceAttribs={
EGL14.EGL_NONE
};
mEGLSurface=EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], surface, surfaceAttribs, 0);
checkEglError("eglCreateWindowSurface");
// Make it current for the current thread
EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
checkEglError("eglMakeCurrent 2");
// That's it, any subsequent draw calls will render to the new surface
}
使用这种方法,不需要重新初始化CameraCaptureSession
,因为相机输出的表面组没有变化。