Merge "Add more tests for VideoCapture.setTargetRotation" into androidx-main
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index f4f9d38..a7a8ed9 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -219,9 +219,11 @@
@Test
fun getMetadataRotation_when_setTargetRotation() {
// Arrange.
- // Just set one Surface.ROTATION_90 to verify the function work or not.
- val targetRotation = Surface.ROTATION_90
- videoCapture.targetRotation = targetRotation
+ // Set Surface.ROTATION_90 for the 1st recording and update to Surface.ROTATION_180
+ // for the 2nd recording.
+ val targetRotation1 = Surface.ROTATION_90
+ val targetRotation2 = Surface.ROTATION_180
+ videoCapture.targetRotation = targetRotation1
val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
latchForVideoSaved = CountDownLatch(1)
@@ -235,10 +237,32 @@
completeVideoRecording(videoCapture, file)
// Verify.
- verifyMetadataRotation(getExpectedRotation(videoCapture).metadataRotation, file)
+ val (videoContentRotation, metadataRotation) = getExpectedRotation(videoCapture)
+ verifyMetadataRotation(metadataRotation, file)
// Cleanup.
file.delete()
+
+ // Arrange: Prepare for 2nd recording
+ val file2 = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+ latchForVideoSaved = CountDownLatch(1)
+ latchForVideoRecording = CountDownLatch(5)
+
+ // Act: Update targetRotation.
+ videoCapture.targetRotation = targetRotation2
+ completeVideoRecording(videoCapture, file2)
+
+ // Verify.
+ val metadataRotation2 = cameraInfo.getSensorRotationDegrees(targetRotation2).let {
+ if (videoCapture.node != null) {
+ // If effect is enabled, the rotation should eliminate the video content rotation.
+ it - videoContentRotation
+ } else it
+ }
+ verifyMetadataRotation(metadataRotation2, file2)
+
+ // Cleanup.
+ file2.delete()
}
@Test
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
index d0d5d70..c640507 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
@@ -36,8 +36,10 @@
import androidx.camera.core.AspectRatio.RATIO_4_3
import androidx.camera.core.CameraEffect
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
-import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA
+import androidx.camera.core.CameraSelector.DEFAULT_FRONT_CAMERA
import androidx.camera.core.CameraSelector.LENS_FACING_BACK
+import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
import androidx.camera.core.CameraXConfig
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.UseCase
@@ -50,12 +52,16 @@
import androidx.camera.core.impl.Observable
import androidx.camera.core.impl.StreamSpec
import androidx.camera.core.impl.Timebase
+import androidx.camera.core.impl.utils.CameraOrientationUtil.surfaceRotationToDegrees
import androidx.camera.core.impl.utils.CompareSizesByArea
import androidx.camera.core.impl.utils.TransformUtils.rectToSize
import androidx.camera.core.impl.utils.TransformUtils.rotateSize
+import androidx.camera.core.impl.utils.TransformUtils.within360
import androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import androidx.camera.core.internal.CameraUseCaseAdapter
+import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.CameraXUtil
import androidx.camera.testing.EncoderProfilesUtil.PROFILES_1080P
import androidx.camera.testing.EncoderProfilesUtil.PROFILES_2160P
import androidx.camera.testing.EncoderProfilesUtil.PROFILES_480P
@@ -67,8 +73,6 @@
import androidx.camera.testing.EncoderProfilesUtil.RESOLUTION_QHD
import androidx.camera.testing.EncoderProfilesUtil.RESOLUTION_QVGA
import androidx.camera.testing.EncoderProfilesUtil.RESOLUTION_VGA
-import androidx.camera.testing.CameraUtil
-import androidx.camera.testing.CameraXUtil
import androidx.camera.testing.fakes.FakeAppConfig
import androidx.camera.testing.fakes.FakeCamera
import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
@@ -90,6 +94,7 @@
import androidx.camera.video.internal.encoder.VideoEncoderInfo
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import java.util.Collections
import java.util.concurrent.TimeUnit
import org.junit.After
@@ -707,6 +712,15 @@
}
@Test
+ fun setTargetRotationInBuilder_rotationIsChanged() {
+ // Act.
+ val videoCapture = createVideoCapture(targetRotation = Surface.ROTATION_180)
+
+ // Assert.
+ assertThat(videoCapture.targetRotation).isEqualTo(Surface.ROTATION_180)
+ }
+
+ @Test
fun setTargetRotation_rotationIsChanged() {
// Arrange.
val videoCapture = createVideoCapture()
@@ -789,11 +803,92 @@
verify(listener).onTransformationInfoUpdate(any())
}
+ // Test setTargetRotation with common back and front camera properties and various conditions.
@Test
- fun setTargetRotation_transformationInfoUpdated() {
+ fun setTargetRotation_backCameraInitial0_transformationInfoUpdated() {
+ testSetTargetRotation_transformationInfoUpdated(
+ lensFacing = LENS_FACING_BACK,
+ sensorRotationDegrees = 90,
+ initialTargetRotation = Surface.ROTATION_0,
+ )
+ }
+
+ @Test
+ fun setTargetRotation_backCameraInitial90_transformationInfoUpdated() {
+ testSetTargetRotation_transformationInfoUpdated(
+ lensFacing = LENS_FACING_BACK,
+ sensorRotationDegrees = 90,
+ initialTargetRotation = Surface.ROTATION_90,
+ )
+ }
+
+ @Test
+ fun setTargetRotation_frontCameraInitial0_transformationInfoUpdated() {
+ testSetTargetRotation_transformationInfoUpdated(
+ lensFacing = LENS_FACING_FRONT,
+ sensorRotationDegrees = 270,
+ initialTargetRotation = Surface.ROTATION_0,
+ )
+ }
+
+ @Test
+ fun setTargetRotation_frontCameraInitial90_transformationInfoUpdated() {
+ testSetTargetRotation_transformationInfoUpdated(
+ lensFacing = LENS_FACING_FRONT,
+ sensorRotationDegrees = 270,
+ initialTargetRotation = Surface.ROTATION_90,
+ )
+ }
+
+ @Test
+ fun setTargetRotation_withEffectBackCameraInitial0_transformationInfoUpdated() {
+ testSetTargetRotation_transformationInfoUpdated(
+ lensFacing = LENS_FACING_BACK,
+ sensorRotationDegrees = 90,
+ effect = createFakeEffect(),
+ initialTargetRotation = Surface.ROTATION_0,
+ )
+ }
+
+ @Test
+ fun setTargetRotation_withEffectBackCameraInitial90_transformationInfoUpdated() {
+ testSetTargetRotation_transformationInfoUpdated(
+ lensFacing = LENS_FACING_BACK,
+ sensorRotationDegrees = 90,
+ effect = createFakeEffect(),
+ initialTargetRotation = Surface.ROTATION_90,
+ )
+ }
+
+ @Test
+ fun setTargetRotation_withEffectFrontCameraInitial0_transformationInfoUpdated() {
+ testSetTargetRotation_transformationInfoUpdated(
+ lensFacing = LENS_FACING_FRONT,
+ sensorRotationDegrees = 270,
+ effect = createFakeEffect(),
+ initialTargetRotation = Surface.ROTATION_90,
+ )
+ }
+
+ @Test
+ fun setTargetRotation_withEffectFrontCameraInitial90_transformationInfoUpdated() {
+ testSetTargetRotation_transformationInfoUpdated(
+ lensFacing = LENS_FACING_FRONT,
+ sensorRotationDegrees = 270,
+ effect = createFakeEffect(),
+ initialTargetRotation = Surface.ROTATION_90,
+ )
+ }
+
+ private fun testSetTargetRotation_transformationInfoUpdated(
+ lensFacing: Int = LENS_FACING_BACK,
+ sensorRotationDegrees: Int = 0,
+ effect: CameraEffect? = null,
+ initialTargetRotation: Int = Surface.ROTATION_0,
+ ) {
// Arrange.
- setupCamera()
- createCameraUseCaseAdapter()
+ setupCamera(lensFacing = lensFacing, sensorRotation = sensorRotationDegrees)
+ createCameraUseCaseAdapter(lensFacing = lensFacing)
var transformationInfo: SurfaceRequest.TransformationInfo? = null
val videoOutput = createVideoOutput(
surfaceRequestListener = { surfaceRequest, _ ->
@@ -804,19 +899,59 @@
}
}
)
- val videoCapture = createVideoCapture(videoOutput, targetRotation = Surface.ROTATION_90)
+ val videoCapture = createVideoCapture(videoOutput, targetRotation = initialTargetRotation)
// Act.
+ effect?.apply { cameraUseCaseAdapter.setEffects(listOf(this)) }
addAndAttachUseCases(videoCapture)
+ shadowOf(Looper.getMainLooper()).idle()
// Assert.
- assertThat(transformationInfo!!.rotationDegrees).isEqualTo(270)
+ var videoContentDegrees: Int
+ var metadataDegrees: Int
+ cameraInfo.getSensorRotationDegrees(initialTargetRotation).let {
+ if (effect != null) {
+ // If effect is enabled, the rotation is applied on video content but not metadata.
+ videoContentDegrees = it
+ metadataDegrees = 0
+ } else {
+ videoContentDegrees = 0
+ metadataDegrees = it
+ }
+ }
+ assertThat(transformationInfo!!.rotationDegrees).isEqualTo(metadataDegrees)
- // Act.
- videoCapture.targetRotation = Surface.ROTATION_180
+ // Act: Test all 4 rotation degrees.
+ for (targetRotation in listOf(
+ Surface.ROTATION_0,
+ Surface.ROTATION_90,
+ Surface.ROTATION_180,
+ Surface.ROTATION_270
+ )) {
+ videoCapture.targetRotation = targetRotation
+ shadowOf(Looper.getMainLooper()).idle()
- // Assert.
- assertThat(transformationInfo!!.rotationDegrees).isEqualTo(180)
+ // Assert.
+ val requiredDegrees = cameraInfo.getSensorRotationDegrees(targetRotation)
+ val expectedDegrees = if (effect != null) {
+ // If effect is enabled, the rotation should eliminate the video content rotation.
+ within360(requiredDegrees - videoContentDegrees)
+ } else {
+ requiredDegrees
+ }
+ val message = "lensFacing = $lensFacing" +
+ ", sensorRotationDegrees = $sensorRotationDegrees" +
+ ", initialTargetRotation = $initialTargetRotation" +
+ ", targetRotation = ${surfaceRotationToDegrees(targetRotation)}" +
+ ", effect = ${effect != null}" +
+ ", videoContentDegrees = $videoContentDegrees" +
+ ", metadataDegrees = $metadataDegrees" +
+ ", requiredDegrees = $requiredDegrees" +
+ ", expectedDegrees = $expectedDegrees" +
+ ", transformationInfo.rotationDegrees = " + transformationInfo!!.rotationDegrees
+ assertWithMessage(message).that(transformationInfo!!.rotationDegrees)
+ .isEqualTo(expectedDegrees)
+ }
}
@Test
@@ -1063,9 +1198,11 @@
shadowOf(Looper.getMainLooper()).idle()
}
- private fun createCameraUseCaseAdapter() {
+ private fun createCameraUseCaseAdapter(lensFacing: Int = LENS_FACING_BACK) {
+ val cameraSelector = if (lensFacing == LENS_FACING_FRONT) DEFAULT_FRONT_CAMERA
+ else DEFAULT_BACK_CAMERA
cameraUseCaseAdapter =
- CameraUtil.createCameraUseCaseAdapter(context, CameraSelector.DEFAULT_BACK_CAMERA)
+ CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
}
private fun createVideoCapture(
@@ -1115,13 +1252,14 @@
private fun setupCamera(
cameraId: String = CAMERA_ID_0,
+ lensFacing: Int = LENS_FACING_BACK,
sensorRotation: Int = 0,
hasTransform: Boolean = true,
supportedResolutions: Map<Int, List<Size>> = CAMERA_0_SUPPORTED_RESOLUTION_MAP,
profiles: Map<Int, EncoderProfilesProxy> = CAMERA_0_PROFILES,
timebase: Timebase = Timebase.UPTIME,
) {
- cameraInfo = FakeCameraInfoInternal(cameraId, sensorRotation, LENS_FACING_BACK).apply {
+ cameraInfo = FakeCameraInfoInternal(cameraId, sensorRotation, lensFacing).apply {
supportedResolutions.forEach { (format, resolutions) ->
setSupportedResolutions(format, resolutions)
}