使用Surface输入的MediaCodec:后台录制

10
我正在开发一个视频编码应用程序,希望在托管 Activity 进入后台或屏幕关闭/打开时防止其停止。我的编码器架构源于优秀的 CameraToMpegTest 示例,增加了将相机帧显示到 GLSurfaceView 上(请参见下面的 Github 链接)。我目前正在执行后台录制,采用两种状态的解决方案: - 当托管 Activity 在前台时,每次调用 GLSurfaceView.Renderer 的 onDrawFrame 时编码一个视频帧。这样可以在突发情况下访问 GLSurfaceView 的 EGL 状态,以免阻塞其他排队到渲染器线程的事件。 - 当托管 Activity 进入后台时,停止 onDrawFrame 编码,并在另一个后台线程中循环编码帧。此模式与 CameraToMpegTest 示例相同。
然而,如果屏幕关闭了,GLSurfaceView的EGLContext会丢失,并且需要重新调用onSurfaceCreated。在这种情况下,我们必须重新创建连接到MediaCodec输入Surface的EGL窗口表面。不幸的是,第二次调用eglCreateWindowSurface 会产生如下问题:
E/libEGL(18839): EGLNativeWindowType 0x7a931098 already connected to another API

在调用之前,我释放与Android Surface相关的所有EGL资源

有没有一种方法可以交换与MediaCodec输入Surface相关联的EGLSurface?

我的测试应用程序的完整源代码在Github上。主要活动

更新 我将在基于MediaCodec和MediaMuxer类的Android视频sdk中应用此处所学到的教训。希望它能帮助你!


1
MediaCodec 不应受到后台运行的影响,甚至不应意识到它在后台运行。例如,在 Android 4.4 中添加的 screenrecord 命令就可以愉快地在后台运行。事实上,它正在编码任何东西,这意味着它仍在接收输入数据,所以我的猜测是某些东西正在影响 Camera。我不明白为什么会导致预览 Surface 是空白的,而预览 byte[] 却有真实数据。 - fadden
更新了我的问题。我现在能够实现从后台录制(无GLSurfaceView显示)切换到前台录制(GLSurfaceView显示),除非在此期间发生屏幕关闭/打开事件... - dbro
屏幕的开/关会导致GLSurfaceView的EGLContext丢失,因此我们必须创建一个新的CodecInputSurface.mEGLSurface,以共享新的EGLContext。然而,我们在断开MediaCodec输入Surface时遇到了麻烦,以避免在eglCreateWindowSurface上出现“已连接到另一个API”的错误。这是否可能呢? - dbro
快速想法:http://developer.android.com/reference/android/opengl/GLSurfaceView.html#setPreserveEGLContextOnPause(boolean) - fadden
我应该说的是:在调用eglMakeCurrent(nothing)时,请确保您在正确的线程中。如果上下文+线程在线程#1中处于当前状态,则在线程#2中调用eglMakeCurrent(nothing)将不会释放线程和表面。如果线程的当前上下文与对象中的内容匹配,则您知道自己在正确的位置。(除非您在两个不同的线程中使相同的上下文成为当前上下文,否则您将处于困境之中。) - fadden
显示剩余5条评论
1个回答

10

首先简述背景...

当您调用eglCreateWindowSurface()时,Android的EGL包装器会在传递的Surface上调用native_window_api_connect()。这最终转化为BufferQueue生产者连接调用,这意味着此EGL表面现在是Surface的唯一图形缓冲区来源。

EGL表面保持连接到Surface,直到销毁EGL表面。销毁时,surface析构函数调用native_window_api_disconnect()以将EGL表面与BufferQueue断开连接。EGL表面是引用计数的,当将表面传递给eglMakeCurrent()时,引用计数会增加,因此要销毁表面必须执行两个操作:

  1. 必须调用eglDestroySurface()
  2. EGL表面不能在任何线程中处于“当前状态”

第二点需要使用另一个EGL表面(或EGL_NO_SURFACE)调用eglMakeCurrent(),或在之前使用表面的任何线程上调用eglReleaseThread()。一种快速确认是否已完成此操作的方法是,在使表面成为当前状态和非当前状态之前添加日志记录,并通过使用adb logcat -v threadtime查看logcat输出来比较线程ID。还可以使用EGL查询如eglGetCurrentSurface(EGL_DRAW)来确认您正在使当前线程进行未关联的操作。

如果EGL表面没有被销毁,它将不会与Surface断开连接,并且尝试连接新的生产者(通过使用新的EGL表面调用eglCreateWindowSurface)将被拒绝并显示“已连接”消息。
更新:我的实现现在可在Grafika测试项目中获取。如果您安装了此工具,请选择“显示+捕获相机”,开始录制,切换电源,然后停止录制,您应该会得到一个中间长时间暂停的完整视频。您可以退出,选择“播放视频”,并选择“camera-test.mp4”进行查看。

1
太棒了!Grafika真是美丽动人。看到所有的移动部件都被很好地模块化,让我的手艺充满了蜡笔和胶水的味道。 - dbro
在ContinuousCaptureActivity.java(https://github.com/google/grafika/blob/master/src/com/android/grafika/ContinuousCaptureActivity.java)中,当onPause时,您首先释放mDisplaySurface,然后释放mEglCore。 mDisplaySurface.release()最后将调用EGL14.eglDestroySurface,但此时您尚未确认“current”问题。 eglDestroySurface会成功吗? - dragonfly
我不确定你所说的“未确认'当前'问题”的意思。正如答案中所指出的,这些资源是引用计数的;当前状态会增加一个额外的引用。因此,当调用eglDestroySurface()并且它不再处于当前状态时,表面将被丢弃。这段代码确实存在一个错误--https://github.com/google/grafika/issues/24--但更多的是关于我在`onResume()`中没有做好的事情。 - fadden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接