9

I've implemented a new sample, here is a link which describes new CameraX api from Google codelabs, but TextureView doesn't show anything and throw next exception:

OpenGLRenderer: [SurfaceTexture-0-7609-1] dequeueImage: SurfaceTexture is not attached to a View

Another camera samples such as a Camera2 and the native camera app work fine I used an emulator with API level Q beta 3

class CameraXFragment : Fragment(), TextureView.SurfaceTextureListener {

    companion object {
        fun newInstance(): Fragment = CameraXFragment()
    }

    private val REQUEST_CODE_PERMISSIONS = 10
    private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_camera, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewFinder.surfaceTextureListener = this
    }

    private fun startCamera() {
        CameraX.unbindAll()

        val previewConfig = PreviewConfig.Builder().apply {
            setTargetAspectRatio(Rational(1, 1))
            setTargetResolution(Size(320, 320))
        }.build()

        val preview = Preview(previewConfig)
        preview.setOnPreviewOutputUpdateListener {
            viewFinder.surfaceTexture = it.surfaceTexture
            updateTransform()
        }

        val imageCaptureConfig = ImageCaptureConfig.Builder()
                .apply {
                    setTargetAspectRatio(Rational(1, 1))
                    setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
                }.build()

        val imageCapture = ImageCapture(imageCaptureConfig)
        captureButton.setOnClickListener {
            val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "${System.currentTimeMillis()}.jpg")
            imageCapture.takePicture(file,
                    object : ImageCapture.OnImageSavedListener {
                        override fun onError(error: ImageCapture.UseCaseError, message: String, t: Throwable?) {
                            t?.printStackTrace()
                        }

                        override fun onImageSaved(file: File) {
                            val msg = "Photo capture succeeded: ${file.absolutePath}"
                            Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
                        }
                    })
        }

        CameraX.bindToLifecycle(this, preview, imageCapture)
    }

    private fun updateTransform() {
        val matrix = Matrix()
        val centerX = viewFinder.width / 2f
        val centerY = viewFinder.height / 2f
        val rotationDegrees = when (viewFinder.display.rotation) {
            Surface.ROTATION_0 -> 0
            Surface.ROTATION_90 -> 90
            Surface.ROTATION_180 -> 180
            Surface.ROTATION_270 -> 270
            else -> return
        }
        matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)
        viewFinder.setTransform(matrix)
    }

    override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
    }

    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
    }

    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
        return true
    }

    override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
        if (allPermissionsGranted()) {
            viewFinder.post { startCamera() }
        } else {
            requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }
        viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
            updateTransform()
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                viewFinder.post { startCamera() }
            } else {
                Toast.makeText(requireContext(), "Permissions are not granted", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun allPermissionsGranted(): Boolean {
        for (permission in REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(requireContext(), permission) != PackageManager.PERMISSION_GRANTED) {
                return false
            }
        }
        return true
    }
}
Ramesh R
  • 7,009
  • 4
  • 25
  • 38
Max
  • 123
  • 3
  • 8

5 Answers5

19

The TextureView needs to be removed and re-added from the parent view for the SurfaceTexture to be attached. This is because TextureView internally creates its own SurfaceTexture once it is attached to the view hierarchy, and that internal SurfaceTexture only gets correctly detached once the parent TextureView is removed from the view hierarchy. You should change preview.setOnPreviewOutputUpdateListener to:

preview.setOnPreviewOutputUpdateListener {
    val parent = viewFinder.parent as ViewGroup
    parent.removeView(viewFinder)
    viewFinder.surfaceTexture = it.surfaceTexture
    parent.addView(viewFinder, 0)
    updateTransform()
}

It looks like you may have copied the code from the codelab, which has now been updated to include the view re-attachment. The official sample also implements this view re-attachment.

Oscar Wahltinez
  • 1,155
  • 3
  • 12
  • 24
5

I faced the same problem when following codeLabs. I locked the screen then turned it on again and suddenly it worked normally, the capture function worked as well. I don't have any idea about this situation, but you can try this way as a work around. I'm using Q beta 3 in Pixel 3.

PS: You can just trigger onStop and onStart event for the Activity (for example: press home and open app again), the live preview will work. In my opinion, I think this problem related to the CameraX.bindToLifecycle.

Tuan Nguyen
  • 131
  • 6
  • Your solution works well! Definitely a problem inside the bindToLifecycle () method, when I commented out this method, I didn't get any exceptions in Logcat, but the preview didn't work either – Max May 10 '19 at 15:44
  • 1
    Because the ```bindToLifeCycle()``` is a part of CameraX flow, to detect when the preview should start or stop base on the activity/fragment life cycle, so we absolutely need it. You can refer the cameraX document or watch the CameraX session in Google IO 2019: https://youtu.be/kuv8uK-5CLY to grab more information. We have nothing to do except waiting for Google to fix this problem :). – Tuan Nguyen May 11 '19 at 16:15
4

The Kotlin code of Oscar Wahltinez in Java:

ViewGroup parent = (ViewGroup) textureView.getParent();
parent.removeView(textureView);
parent.addView(textureView, 0);
SurfaceTexture surfaceTexture = previewOutput.getSurfaceTexture();
textureView.setSurfaceTexture(surfaceTexture);
Ahwar
  • 1,746
  • 16
  • 30
1

In addition to this answer. I solved my issue by removing application level hardwareAccelerated="false" line from AndroidManifest.xml file.

0

This code is worked for me

  val parent = viewFinder.parent as ViewGroup
  parent.removeView(viewFinder)
  parent.addView(viewFinder, 0)
  val surfaceTexture: SurfaceTexture = it.surfaceTexture
  viewFinder.setSurfaceTexture(surfaceTexture)
terng03412
  • 13
  • 2