natario1/CameraView

Bug? Thread safety and overlay usage

pierre-the-fighter-pilot opened this issue · 3 comments

Describe the bug

Please add a clear description of what the bug is, and fill the list below.

  • CameraView version: 2.7.2
  • Reproducible in official demo app: unknown. The issue is random, increasing the max recording time could presumably replicate the issue.
  • Device / Android version: OnePlus 7, Android 12
  • I have read the FAQ page: yes

To Reproduce

Record a long video snapshot with overlay (lots of them, with views (mostly TextViews) changing, but no animation).
Observe a random crash (closure of the activity).

There seems to be a race condition related to the drawing of overlay views. Is there a restriction as to which thread can be used to make changes to the overlay views?

Expected behavior

Error handling preventing the loss of the video being recorded.

XML layout

Part of the XML layout with the CameraView declaration, so we can read its attributes.

  <com.otaliastudios.cameraview.CameraView
      android:id="@+id/camera_view"
      android:keepScreenOn="true"
      app:cameraHdr="on"
      app:cameraDrawHardwareOverlays="true"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"

      app:cameraAudio="stereo"
      app:cameraAudioBitRate="0"
      app:cameraVideoCodec="deviceDefault"
      app:cameraVideoMaxSize="0"
      app:cameraVideoMaxDuration="0"
      app:cameraVideoBitRate="0"
      app:cameraPreviewFrameRate="30"
      app:cameraPreviewFrameRateExact="false">

    <TextureView
        android:id="@+id/replay_video_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_drawOnPreview="true"
        app:layout_drawOnVideoSnapshot="true" />

    <ImageView
        android:id="@+id/replay_transition"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_drawOnPreview="true"
        app:layout_drawOnVideoSnapshot="true" />

    <include
        android:id="@+id/overlay_scoreboard_group"
        layout="@layout/overlay_scoreboard_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_drawOnPreview="false"
        app:layout_drawOnVideoSnapshot="true" />

    <include
        android:id="@+id/overlay_shootout_group"
        layout="@layout/overlay_shootout_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_drawOnPreview="false"
        app:layout_drawOnVideoSnapshot="true" />

  </com.otaliastudios.cameraview.CameraView>

Screenshots

No screenshot. Activity randomly shutting down

Logs

This was the original log when I first observed the issue:

2023-02-27 14:02:58.883 16371-16371 AndroidRuntime          com.cyrp.hockeytime                  D  Shutting down VM
2023-02-27 14:02:58.905 16371-16544 AndroidRuntime          com.cyrp.hockeytime                  E  FATAL EXCEPTION: VideoEncoder
                                                                                                    Process: com.cyrp.hockeytime, PID: 16371
                                                                                                    java.lang.IllegalStateException: No recording in progress, forgot to call #beginRecording()?
                                                                                                    	at android.graphics.RenderNode.endRecording(RenderNode.java:431)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21672)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.View.draw(View.java:22534)
                                                                                                    	at android.view.ViewGroup.drawChild(ViewGroup.java:4608)
                                                                                                    	at com.otaliastudios.cameraview.overlay.OverlayLayout.doDrawChild(OverlayLayout.java:180)
                                                                                                    	at com.otaliastudios.cameraview.overlay.OverlayLayout.drawChild(OverlayLayout.java:169)
                                                                                                    	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4361)
                                                                                                    	at com.otaliastudios.cameraview.overlay.OverlayLayout.drawOn(OverlayLayout.java:151)
                                                                                                    	at com.otaliastudios.cameraview.overlay.OverlayDrawer.draw(OverlayDrawer.java:74)
                                                                                                    	at com.otaliastudios.cameraview.video.encoding.TextureMediaEncoder.onFrame(TextureMediaEncoder.java:218)
                                                                                                    	at com.otaliastudios.cameraview.video.encoding.TextureMediaEncoder.onEvent(TextureMediaEncoder.java:146)
                                                                                                    	at com.otaliastudios.cameraview.video.encoding.MediaEncoder$3.run(MediaEncoder.java:244)
                                                                                                    	at android.os.Handler.handleCallback(Handler.java:938)
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                                                    	at android.os.Looper.loopOnce(Looper.java:233)
                                                                                                    	at android.os.Looper.loop(Looper.java:344)
                                                                                                    	at android.os.HandlerThread.run(HandlerThread.java:67)
2023-02-27 14:02:58.905 16371-16371 AndroidRuntime          com.cyrp.hockeytime                  E  FATAL EXCEPTION: main
                                                                                                    Process: com.cyrp.hockeytime, PID: 16371
                                                                                                    java.lang.IllegalStateException: Recording currently in progress - missing #endRecording() call?
                                                                                                    	at android.graphics.RenderNode.beginRecording(RenderNode.java:399)
                                                                                                    	at android.view.View.getDrawableRenderNode(View.java:23108)
                                                                                                    	at android.view.View.drawBackground(View.java:23042)
                                                                                                    	at android.view.View.draw(View.java:22801)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21668)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:534)
                                                                                                    	at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:540)
                                                                                                    	at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:620)
                                                                                                    	at android.view.ViewRootImpl.draw(ViewRootImpl.java:4743)
                                                                                                    	at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4447)
                                                                                                    	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3529)
                                                                                                    	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2305)
                                                                                                    	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9133)
                                                                                                    	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1232)
                                                                                                    	at android.view.Choreographer.doCallbacks(Choreographer.java:1029)
                                                                                                    	at android.view.Choreographer.doFrame(Choreographer.java:934)
                                                                                                    	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1217)
                                                                                                    	at android.os.Handler.handleCallback(Handler.java:938)
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                                                    	at android.os.Looper.loopOnce(Looper.java:233)
                                                                                                    	at android.os.Looper.loop(Looper.java:344)
                                                                                                    	at android.app.ActivityThread.main(ActivityThread.java:8212)
                                                                                                    	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584)
                                                                                                    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034)
2023-02-27 14:02:58.905 16371-16371 Process                 com.cyrp.hockeytime                  I  Sending signal. PID: 16371 SIG: 9
---------------------------- PROCESS ENDED (16371) for package com.cyrp.hockeytime ----------------------------

It seemed as though this was a race condition around the "mConfig.overlayDrawer.draw(mConfig.overlayTarget);"
call in TextureMediaEncoder.java, so I put a synchronized scope around it. It reduced the issue quite a bit but not completely. This is the new log I have:

2023-03-18 20:19:51.602  3251-3251  AndroidRuntime          com.cyrp.hockeystudio                E  FATAL EXCEPTION: main
                                                                                                    Process: com.cyrp.hockeystudio, PID: 3251
                                                                                                    java.lang.IllegalStateException: No recording in progress, forgot to call #beginRecording()?
                                                                                                    	at android.graphics.RenderNode.endRecording(RenderNode.java:431)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21672)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                    	at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:534)
                                                                                                    	at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:540)
                                                                                                    	at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:620)
                                                                                                    	at android.view.ViewRootImpl.draw(ViewRootImpl.java:4743)
                                                                                                    	at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4447)
                                                                                                    	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3529)
                                                                                                    	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2305)
                                                                                                    	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9133)
                                                                                                    	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1232)
                                                                                                    	at android.view.Choreographer.doCallbacks(Choreographer.java:1029)
                                                                                                    	at android.view.Choreographer.doFrame(Choreographer.java:934)
                                                                                                    	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1217)
                                                                                                    	at android.os.Handler.handleCallback(Handler.java:938)
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                                                    	at android.os.Looper.loopOnce(Looper.java:233)
                                                                                                    	at android.os.Looper.loop(Looper.java:344)
                                                                                                    	at android.app.ActivityThread.main(ActivityThread.java:8212)
                                                                                                    	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584)
                                                                                                    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034)

Here is an example of what my app does:

https://youtu.be/O8EqaRZQaGA

The transitions are managed internally without using Android animations. Of note is the crashes happen at any random time and in fact, usually happen when only the clock timer changes.

It seems the bug was on my side with an update to the overlay view done outside the UI thread

After ensuring all my UI and overlay UI code runs on the UI thread, I still experience the issue as originally described. I confirm that there is a race condition in the drawing/rendering of the overlay causing the main thread to issue an IllegalStateException in android.graphics.RenderNode.endRecording (RenderNode.java)
logcat4.txt