Skip to content

Commit

Permalink
Camera2Video: Fix stability issues due to race conditions
Browse files Browse the repository at this point in the history
The app is not waiting for rendering / encoding operations to complete
before the preview fragment ends. This leads to exceptions in render /
encode, and an app crash.

Bug: 307767427
Test: Ran Camera2Video down various paths, observed no crashes.
Change-Id: I1896fa7753ddf8c1e9022fffb1719fdd8ed7ee43
  • Loading branch information
borgerag committed Oct 25, 2023
1 parent 1ccae7d commit 270fd2b
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ class HardwarePipeline(width: Int, height: Int, fps: Int, filterOn: Boolean, tra
override fun destroyWindowSurface() {
renderHandler.sendMessage(renderHandler.obtainMessage(
RenderHandler.MSG_DESTROY_WINDOW_SURFACE))
renderHandler.waitDestroyWindowSurface()
}

override fun setPreviewSize(previewSize: Size) {
Expand Down Expand Up @@ -415,11 +416,13 @@ class HardwarePipeline(width: Int, height: Int, fps: Int, filterOn: Boolean, tra
override fun clearFrameListener() {
renderHandler.sendMessage(renderHandler.obtainMessage(
RenderHandler.MSG_CLEAR_FRAME_LISTENER))
renderHandler.waitClearFrameListener()
}

override fun cleanup() {
renderHandler.sendMessage(renderHandler.obtainMessage(
RenderHandler.MSG_CLEANUP))
renderHandler.waitCleanup()
}

private class ShaderProgram(id: Int,
Expand Down Expand Up @@ -513,9 +516,9 @@ class HardwarePipeline(width: Int, height: Int, fps: Int, filterOn: Boolean, tra
private var eglDisplay = EGL14.EGL_NO_DISPLAY
private var eglContext = EGL14.EGL_NO_CONTEXT
private var eglConfig: EGLConfig? = null
private var eglRenderSurface: EGLSurface? = null
private var eglEncoderSurface: EGLSurface? = null
private var eglWindowSurface: EGLSurface? = null
private var eglRenderSurface: EGLSurface = EGL14.EGL_NO_SURFACE
private var eglEncoderSurface: EGLSurface = EGL14.EGL_NO_SURFACE
private var eglWindowSurface: EGLSurface = EGL14.EGL_NO_SURFACE
private var vertexShader = 0
private var cameraToRenderFragmentShader = 0
private var renderToPreviewFragmentShader = 0
Expand All @@ -526,6 +529,9 @@ class HardwarePipeline(width: Int, height: Int, fps: Int, filterOn: Boolean, tra
private var renderToEncodeShaderProgram: ShaderProgram? = null

private val cvResourcesCreated = ConditionVariable(false)
private val cvDestroyWindowSurface = ConditionVariable(false)
private val cvClearFrameListener = ConditionVariable(false)
private val cvCleanup = ConditionVariable(false)

public fun startRecording() {
currentlyRecording = true
Expand Down Expand Up @@ -865,7 +871,12 @@ class HardwarePipeline(width: Int, height: Int, fps: Int, filterOn: Boolean, tra

private fun destroyWindowSurface() {
EGL14.eglDestroySurface(eglDisplay, eglWindowSurface)
eglWindowSurface = null
eglWindowSurface = EGL14.EGL_NO_SURFACE
cvDestroyWindowSurface.open()
}

public fun waitDestroyWindowSurface() {
cvDestroyWindowSurface.block()
}

private fun copyTexture(texId: Int, texture: SurfaceTexture, viewportRect: Rect,
Expand Down Expand Up @@ -964,36 +975,54 @@ class HardwarePipeline(width: Int, height: Int, fps: Int, filterOn: Boolean, tra

private fun clearFrameListener() {
cameraTexture.setOnFrameAvailableListener(null)
cvClearFrameListener.open()
}

public fun waitClearFrameListener() {
cvClearFrameListener.block()
}

private fun cleanup() {
EGL14.eglDestroySurface(eglDisplay, eglEncoderSurface)
eglEncoderSurface = null
eglEncoderSurface = EGL14.EGL_NO_SURFACE
EGL14.eglDestroySurface(eglDisplay, eglRenderSurface)
eglRenderSurface = null
eglRenderSurface = EGL14.EGL_NO_SURFACE

cameraTexture.release()

EGL14.eglDestroyContext(eglDisplay, eglContext)

eglDisplay = EGL14.EGL_NO_DISPLAY
eglContext = EGL14.EGL_NO_CONTEXT

cvCleanup.open()
}

public fun waitCleanup() {
cvCleanup.block()
}

@Suppress("UNUSED_PARAMETER")
private fun onFrameAvailableImpl(surfaceTexture: SurfaceTexture) {
if (eglContext == EGL14.EGL_NO_CONTEXT) {
return
}

/** The camera API does not update the tex image. Do so here. */
cameraTexture.updateTexImage()

/** Copy from the camera texture to the render texture */
if (eglRenderSurface != null) {
if (eglRenderSurface != EGL14.EGL_NO_SURFACE) {
copyCameraToRender()
}

/** Copy from the render texture to the TextureView */
if (eglWindowSurface != null) {
if (eglWindowSurface != EGL14.EGL_NO_SURFACE) {
copyRenderToPreview()
}

/** Copy to the encoder surface if we're currently recording. */
if (eglEncoderSurface != null && currentlyRecording) {
if (eglEncoderSurface != EGL14.EGL_NO_SURFACE && currentlyRecording) {
copyRenderToEncode()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,11 @@ class PreviewFragment : Fragment() {

delay(CameraActivity.ANIMATION_SLOW_MILLIS)

pipeline.cleanup()

Log.d(TAG, "Recording stopped. Output file: $outputFile")
encoder.shutdown()

pipeline.cleanup()

// Broadcasts the media file to the rest of the system
MediaScannerConnection.scanFile(
requireView().context, arrayOf(outputFile.absolutePath), null, null)
Expand Down Expand Up @@ -512,6 +512,8 @@ class PreviewFragment : Fragment() {

override fun onDestroy() {
super.onDestroy()
pipeline.clearFrameListener()
pipeline.cleanup()
cameraThread.quitSafely()
encoderSurface.release()
}
Expand Down

0 comments on commit 270fd2b

Please sign in to comment.