blob: 2ae872f7513a18db7dedc4742ede874a326cabcc [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.extensions.internal.sessionprocessor
import android.content.Context
import android.graphics.ImageFormat
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraManager
import android.hardware.camera2.TotalCaptureResult
import android.media.Image
import android.media.ImageReader
import android.media.ImageWriter
import android.os.Handler
import android.os.HandlerThread
import android.util.Size
import android.view.Surface
import androidx.camera.camera2.Camera2Config
import androidx.camera.core.CameraXThreads
import androidx.camera.extensions.impl.PreviewImageProcessorImpl
import androidx.camera.extensions.impl.ProcessResultImpl
import androidx.camera.testing.Camera2Util
import androidx.camera.testing.CameraUtil
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import java.util.concurrent.Executor
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@LargeTest
@SdkSuppress(minSdkVersion = 29) // Extensions supported on API 29+
@RunWith(AndroidJUnit4::class)
class PreviewProcessorTest {
@get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
)
private lateinit var surfaceTexture: SurfaceTexture
private lateinit var previewSurface: Surface
private lateinit var previewProcessor: PreviewProcessor
private val context = ApplicationProvider.getApplicationContext<Context>()
private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
private lateinit var backgroundThread: HandlerThread
private lateinit var backgroundHandler: Handler
private lateinit var fakePreviewImageProcessorImpl: FakePreviewImageProcessorImpl
companion object {
const val CAMERA_ID = "0"
const val WIDTH = 640
const val HEIGHT = 480
const val MAX_IMAGES = 6
}
@Before
fun setUp() {
Assume.assumeTrue(CameraUtil.deviceHasCamera())
backgroundThread = HandlerThread(
CameraXThreads.TAG + "preview_processor_test"
)
backgroundThread.start()
backgroundHandler = Handler(backgroundThread.looper)
surfaceTexture = SurfaceTexture(0)
surfaceTexture.setDefaultBufferSize(WIDTH, HEIGHT)
surfaceTexture.detachFromGLContext()
previewSurface = Surface(surfaceTexture)
fakePreviewImageProcessorImpl = FakePreviewImageProcessorImpl()
previewProcessor = PreviewProcessor(
fakePreviewImageProcessorImpl, previewSurface, Size(WIDTH, HEIGHT)
)
}
@After
fun tearDown() {
if (::backgroundThread.isInitialized) {
backgroundThread.quitSafely()
}
if (::surfaceTexture.isInitialized) {
surfaceTexture.release()
}
if (::previewSurface.isInitialized) {
previewSurface.release()
}
if (::previewProcessor.isInitialized) {
previewProcessor.close()
}
if (::fakePreviewImageProcessorImpl.isInitialized) {
fakePreviewImageProcessorImpl.close()
}
}
@Test
fun canOutputToPreview(): Unit = runBlocking {
val cameraDevice = Camera2Util.openCameraDevice(cameraManager, CAMERA_ID, backgroundHandler)
val imageReaderYuv = ImageReader.newInstance(
WIDTH, HEIGHT, ImageFormat.YUV_420_888, MAX_IMAGES
)
val session = Camera2Util.openCaptureSession(
cameraDevice,
arrayListOf(imageReaderYuv.surface),
backgroundHandler
)
previewProcessor.start()
imageReaderYuv.setOnImageAvailableListener({
val image = it.acquireNextImage()
previewProcessor.notifyImage(createImageReference(image))
}, backgroundHandler)
Camera2Util.startRepeating(cameraDevice, session, arrayListOf(imageReaderYuv.surface)) {
previewProcessor.notifyCaptureResult(it)
}
val previewUpdateDeferred = CompletableDeferred<Boolean>()
surfaceTexture.setOnFrameAvailableListener {
previewUpdateDeferred.complete(true)
}
withTimeout(3000) {
assertTrue(previewUpdateDeferred.await())
}
}
@Test
fun canCloseProcessor(): Unit = runBlocking {
val cameraDevice = Camera2Util.openCameraDevice(cameraManager, CAMERA_ID, backgroundHandler)
val imageReaderYuv = ImageReader.newInstance(
WIDTH, HEIGHT, ImageFormat.YUV_420_888, MAX_IMAGES
)
val session = Camera2Util.openCaptureSession(
cameraDevice,
arrayListOf(imageReaderYuv.surface),
backgroundHandler
)
previewProcessor.start()
imageReaderYuv.setOnImageAvailableListener({
val image = it.acquireNextImage()
previewProcessor.notifyImage(createImageReference(image))
}, backgroundHandler)
Camera2Util.startRepeating(cameraDevice, session, arrayListOf(imageReaderYuv.surface)) {
previewProcessor.notifyCaptureResult(it)
}
val previewUpdateDeferred = CompletableDeferred<Boolean>()
surfaceTexture.setOnFrameAvailableListener {
previewUpdateDeferred.complete(true)
}
withTimeout(3000) {
previewUpdateDeferred.await()
}
previewProcessor.close()
// close the preview surface to see if closing causes any issues.
surfaceTexture.release()
previewSurface.release()
// Delay a little while to see if the close() causes any issue.
delay(1000)
}
private fun createImageReference(image: Image): ImageReference {
return object : ImageReference {
private var refCount = 1
override fun increment(): Boolean {
if (refCount <= 0) return false
refCount++
return true
}
override fun decrement(): Boolean {
if (refCount <= 0) return false
refCount--
if (refCount <= 0) {
image.close()
}
return true
}
override fun get(): Image? {
return image
}
}
}
private class FakePreviewImageProcessorImpl : PreviewImageProcessorImpl {
private var imageWriter: ImageWriter? = null
override fun process(image: Image?, result: TotalCaptureResult?) {
val emptyImage = imageWriter!!.dequeueInputImage()
imageWriter!!.queueInputImage(emptyImage)
}
override fun process(
image: Image?,
result: TotalCaptureResult?,
resultCallback: ProcessResultImpl?,
executor: Executor?
) {
val blankImage = imageWriter!!.dequeueInputImage()
imageWriter!!.queueInputImage(blankImage)
}
override fun onOutputSurface(surface: Surface, imageFormat: Int) {
imageWriter = ImageWriter.newInstance(surface, 2)
}
override fun onResolutionUpdate(size: Size) {
}
override fun onImageFormatUpdate(imageFormat: Int) {
}
fun close() {
imageWriter?.close()
}
}
}