natario1 / CameraView

📸 A well documented, high-level Android interface that makes capturing pictures and videos easy, addressing all of the common issues and needs. Real-time filters, gestures, watermarks, frame processing, RAW, output of any size.
https://natario1.github.io/CameraView
Other
4.96k stars 938 forks source link

Bug? Thread safety and overlay usage #1230

Open pierre-the-fighter-pilot opened 1 year ago

pierre-the-fighter-pilot commented 1 year ago

Describe the bug

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

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)
pierre-the-fighter-pilot commented 1 year ago

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.

pierre-the-fighter-pilot commented 1 year ago

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

pierre-the-fighter-pilot commented 1 year ago

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