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:
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