Status Update
Comments
da...@gmail.com <da...@gmail.com> #2
Hi Doudera,
Thank you for reporting this issue. It is working as intended. This is because the state of CameraControl follows Camera. Once the Camera is closed, all settings will be restored, just like the settings of the focus area and zoom level. The app can restore the torch state by resetting torch at the time of activity onResume.
da...@gmail.com <da...@gmail.com> #3
thank you for the fast reply. The behaviour is unexpected to me because if you have the same code as before and just add logging like this:
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
.apply {
Log.d(TAG, "Enabling torch after binding") // <-- called everytime it returns to Activity so next statement should be applied
cameraControl.enableTorch(true)
}
The log is always called even when returning to Activity so I suppose it should be applied. Why it is not applied when it is called?
Unfortunately, I cannot make it work even with your hint. I tried to add camera?.cameraControl?.enableTorch(true) to onResume() but without any effect. Can you please describe more how to reset torch state onResume().
As you said the same happens with setZoomRatio() etc.
Thank you, Martin
da...@gmail.com <da...@gmail.com> #4
Hi Doudera, Thank you for the detail description. It sounds like some problem in the code. I will investigate it more.
wu...@google.com <wu...@google.com> #5
From the log, it shows the camera state
-> open -> enable torch -> close -> open
The torch was gone because it restarted the camera.
It needs to investigate more about why it open/close so frequently.
da...@gmail.com <da...@gmail.com> #6
In CameraXBasic, it looks like the "cameraProvider.unbindAll()" call in CameraFragment.bindCameraUseCases() method will cause the camera close.
Since camera operation is in another thread, a timing causes the enableTorch() failed because the camera is in closed state at that moment.
It can be detected by checking the returned ListenableFuture result from cameraControl.enableTorch()
020-03-29 12:34:56.139 4946-4946/com.android.example.cameraxbasic E/CameraXBasic: enableTorch
2020-03-29 12:34:56.225 4946-5035/com.android.example.cameraxbasic D/Camera: Transitioning camera internal state: OPENED --> CLOSING
2020-03-29 12:34:56.226 4946-4946/com.android.example.cameraxbasic E/CameraXBasic: enableTorch fail by java.util.concurrent.ExecutionException: androidx.camera.core.CameraControl$OperationCanceledException: Camera is not active.
java.util.concurrent.ExecutionException: androidx.camera.core.CameraControl$OperationCanceledException: Camera is not active.
at androidx.concurrent.futures.AbstractResolvableFuture.getDoneValue(AbstractResolvableFuture.java:518)
at androidx.concurrent.futures.AbstractResolvableFuture.get(AbstractResolvableFuture.java:475)
at androidx.concurrent.futures.CallbackToFutureAdapter$SafeFuture.get(CallbackToFutureAdapter.java:199)
at com.android.example.cameraxbasic.fragments.CameraFragment$bindCameraUseCases$1$2$1.run(CameraFragment.kt:310)
at android.os.Handler.handleCallback(Handler.java:907)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:216)
at android.app.ActivityThread.main(ActivityThread.java:7476)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:549)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:968)
Caused by: androidx.camera.core.CameraControl$OperationCanceledException: Camera is not active.
at androidx.camera.camera2.internal.TorchControl.setActive(TorchControl.java:112)
at androidx.camera.camera2.internal.Camera2CameraControl.setActive(Camera2CameraControl.java:135)
at androidx.camera.camera2.internal.Camera2CameraImpl.tryRemoveOnlineUseCases(Camera2CameraImpl.java:727)
at androidx.camera.camera2.internal.Camera2CameraImpl.lambda$removeOnlineUseCase$13$Camera2CameraImpl(Camera2CameraImpl.java:699)
at androidx.camera.camera2.internal.-$$Lambda$Camera2CameraImpl$5wc8VkOCNW87m5eLEZfzgWUl-nY.run(Unknown Source:4)
at android.os.Handler.handleCallback(Handler.java:907)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:216)
at android.os.HandlerThread.run(HandlerThread.java:67)
It is a defect that CameraX should guarantee the order of the camera request.
Another topic is that why camera will be closed when resuming the CameraFragment.
CameraX is lifecycle aware, if UseCases is bound and pause/resume the page, CameraX will restart those UseCases automatically. So actually it doesn't have to unbind and rebind those UseCases. Unbind and rebind UseCases causes it open camera twice and meet this timing issue. In CameraXBasic, it will unbind and rebind UseCases in onViewCreated().
It is easier to be done in an Activity, just put the bindToLifecycle(...) code in the Activity.onCreate(). For Fragment, maybe Fragment.onAttach() is something like Activity.onCreate, but not pretty sure if it is practical. Or alternatively, it has to unbind all use case in a paired callback such like Fragment.onDestroyView() to prevent Camera from being automatically opened when resuming.
wu...@google.com <wu...@google.com> #7
Branch: androidx-master-dev
commit 76ad175d0c681ddc6a1b86ff35b180f97049da0d
Author: leo huang <leohuang@google.com>
Date: Sat Jul 11 11:15:41 2020
Fix CameraControl.enableTorch is not working
The issue is caused by a race condition. Camera2CameraControl#setActive(true) is called on the main thread, but #setActive(false) is called on the camera thread. It will cause the control to be in a wrong state and not work properly.
The solution is to post all operations to camera thread to ensure the API calls are executed in the correct order.
But it must update the "LiveData" in advance on the main thread to maintain an existing behavior, i.e. user can immediately obtain the new LiveData value through continuous API calls on the main thread. However, trying to maintain this behavior will conflict with the above solution. There are 2 issues that need to be corrected to ensure the final LiveData value is correct.
(1) LiveData that was updated earlier on the main thread may reset by "setActive(false)" executed on the camera thread. This issue can be resolved by updating LiveData again in the posted camera operation since the tasks on the camera thread always have correct order.
(2) Before updating LiveData on the main thread, it cannot directly check the "active state" of CameraControl because the "active state" is only correct on the camera thread. The solution is to create a synchronized "use count" that can be queried on all threads. In this way, "use count" represents whether CameraControl accepts new requests, "active state" represents whether the requests are sent to Camera.
"Relnote: Fix the CameraControl unable to work by a race condition"
Bug: 152333890
Bug: 160928870
Bug: 160714166
Test: ./gradlew camera:camera-camera2:connectedAndroidTest; ./gradlew camera:camera-camera2:test
Change-Id: I2279f7bda1a9edf90af0c46b2db749d59821e0cc
M camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/CameraControlDeviceTest.java
M camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlTest.java
M camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ZoomControlDeviceTest.java
M camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControl.java
M camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
M camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/FocusMeteringControl.java
M camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/TorchControl.java
M camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZoomControl.java
M camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/TorchControlTest.java
M camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ZoomControlTest.java
le...@google.com <le...@google.com> #8
Hi,
Thanks for the feedback. QualitySelector.isQualitySupported() can only know whether the device support this quality, but the final selected quality is still constrained by UseCase combinations when
For example, assume isQualitySupported(UHD) returns true on a device. When trying to bind use cases with QualitySelector only contains UHD, you may get below results for different combinations
- Preview + VideoCapture(UHD) => success
- Preview + ImageCapture + VideoCapture(UHD) => fail
- Preview + ImageCapture + VideoCapture(FHD) => success
In your case, I think you want to know in advance what qualities are supported under a certain use cases combination (ex: Preview + VideoCapture). However, there is not yet a convenient API to query about this. Currently you can create a VideoCapture with a single quality (QualitySelector.from(quality)) and then bind the use cases you want, use try-catch on ProcessCameraProvider.bindToLifecycle() to know if the combination is supported (ProcessCameraProvider.bindToLifecycle() will throw an exception if the combination is not supported).
However, this method will open camera when the combination is supported. It would be better to have an API that only checks for support without opening camera. There is a bug related to such API requirement. See
Let us know if you have any questions.
Leo
da...@gmail.com <da...@gmail.com> #9
Pixels appear to be capable of taking photos while recording 4k video and we're still seeing 4k resolution not being used on them when we try to use QualitySelector with UHD as the main choice and FHD as a fallback. It's likely that what you're describing does occur on some devices but I don't think it explains the QualitySelector issue we're seeing on Pixels.
da...@gmail.com <da...@gmail.com> #10
If you try our app from
le...@google.com <le...@google.com> #11
This sounds like a bug. I will investigate it and get back to you.
Thanks
le...@google.com <le...@google.com>
le...@google.com <le...@google.com> #12
The issue happens when combining ImageCapture + VideoCapture.
In a FULL level camera device, there are two possible combinations for Preview + VideoCapture + ImageCapture. (refer to
- (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
- (PRIV, PREVIEW) + (PRIV, PREVIEW) + (JPEG, MAXIMUM)
MAXIMUM > RECORD > PREVIEW
In the current CameraX settings, ImageCapture has higher surface occupancy priority than VideoCapture by default. That means ImageCapture has higher priority to choose resolution than VideoCapture. When QualitySelector contains multiple qualities as candidates, such as
QualitySelector.from(Quality.UHD, FallbackStrategy.lowerQualityOrHigherThan(Quality.UHD)))
It means VideoCapture can accept UHD, FHD, HD, SD. CameraX auto-resolution mechanism will then adopt combination (2) because ImageCapture has higher priority. i.e. ImageCapture gets MAXIMUM size and VideoCapture gets Preview size (FHD).
For
QualitySelector.from(Quality.UHD)
Since there is only one option UHD (RECORD size) for VideoCapture, combination (2) can't be met and combination (1) is adopted. i.e. both ImageCapture and VideoCapture gets RECORD size.
will have a solution to fix this.
ap...@google.com <ap...@google.com> #13
Branch: androidx-main
commit 76d3d536e73a8c119534d7fbb2110e29bacc3706
Author: leo huang <leohuang@google.com>
Date: Tue May 03 00:54:01 2022
Increase VideoCapture surface occupancy priority over ImageCapture
Root Cause:
In a FULL (or higher) hardware level camera device, there are two possible combinations for Preview + VideoCapture + ImageCapture
(1) (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
(2) (PRIV, PREVIEW) + (PRIV, PREVIEW) + (JPEG, MAXIMUM)
In the current code, ImageCapture has higher surface occupancy priority than VideoCapture by default. When QualitySelector contains multiple qualities as candidates, such as using QualitySelector.from(Quality.UHD, FallbackStrategy.lowerQualityOrHigherThan(Quality.UHD))) as the bug mentioned. The CameraX auto-resolution mechanism will give ImageCapture higher priority than VideoCapture to select the largest resolution it can use. So combination (2) is adopted and VideoCapture obtains PREVIEW size, i.e VideoCapture gets FHD but not UHD.
Solution:
For scenario using VideoCapture + ImageCapture, it makes sense to give VideoCapture higher surface occupancy priority than ImageCapture. Then combination (1) can be adopted for the issue case.
The CL also removes impossible resolutions from prioritized resolution list to prevent auto-resolution mechanism from incorrectly assuming that VideoCapture might use them. This ensure ImageCapture can still get the MAXIMUM size when VideoCapture only requests a surface resolution under PREVIEW size for combination (2).
Relnote: "Fixed QualitySelector fails to record a UHD video when a fallback strategy is enabled. The issue happens when VideoCapture is bound with ImageCapture and Preview on a FULL or higher hardware level camera device. A fallback strategy of QualitySelector causes VideoCapture incorrectly to get a FHD resolution. UHD resolution is actually supported for this use case combination and should be adopted."
Bug:
Test: ./gradlew camera:camera-video:testDebug
Change-Id: I0e788060512feef5fa934d7f35374da218ffe8e8
M camera/camera-camera2/build.gradle
M camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
M camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java
M camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
M camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
Description
When we create a QualitySelector with the following code, it doesn't use UHD even when UHD is supported:
We had to implement a workaround for this in our app:
This works but led to a regression (a crash) because
camera
isnull
when the app is being opened immediately to video mode via the standard Android video capture intent. We'll try some other approaches to using QualitySector but it doesn't seem thatFallbackStrategy
works correctly at the moment.Our app is open source if for some reason you can't reproduce this yourselves.