RedApparat / Fotoapparat

Making Camera for Android more friendly. 📸
Apache License 2.0
3.81k stars 406 forks source link

toSingle() or pendingResult.await() cause camera to freeze #264

Closed Raenar4k closed 6 years ago

Raenar4k commented 6 years ago

What are you trying to achieve or the steps to reproduce?

I am trying to use fotoapparat Rx adapters to work with the result or get result directly from pendingResult.await() method. Both are causing camera to freeze.

I've reproduced this issue in sample project, using version from master branch or from 2.2.0 tag. You can use either method and take a photo from back camera to reproduce this.

Here im using await() method:

    private fun takePicture(): () -> Unit = {
        val photo = fotoapparat
                .autoFocus()
                .takePicture()
                .toBitmap(scaled(scaleFactor = 0.25f))
                .await()

        Log.i(LOGGING_TAG, "New photo captured. Bitmap length: ${photo.bitmap.byteCount}")

        val imageView = findViewById<ImageView>(R.id.result)

        imageView.setImageBitmap(photo.bitmap)
        imageView.rotation = (-photo.rotationDegrees).toFloat()
    }

And here is the example same as in fotoapparat-adapters readme, using rxjava2:

dependencies {
    implementation project(':fotoapparat')

    implementation project(':fotoapparat-adapters:rxjava2')
    implementation "io.reactivex.rxjava2:rxjava:2.1.12"

    implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"

    implementation "com.android.support:appcompat-v7:${versions.android.support}"
}
    private fun takePicture(): () -> Unit = {
        fotoapparat.takePicture()
                .toBitmap()
                .toObservable()
                .subscribe { photo ->
                    Log.i(LOGGING_TAG, "New photo captured. Bitmap length: ${photo.bitmap.byteCount}")

                    val imageView = findViewById<ImageView>(R.id.result)

                    imageView.setImageBitmap(photo.bitmap)
                    imageView.rotation = (-photo.rotationDegrees).toFloat()
                }
    }

Using toFlowable() or toObservable() produces same result.

How did you initialize FA?

Sample project looks like this (shortened):

        fotoapparat = Fotoapparat(
                context = this,
                view = cameraView,
                focusView = focusView,
                logger = logcat(),
                lensPosition = back(),
                cameraConfiguration = CameraConfiguration(
                        previewResolution = firstAvailable(
                                wideRatio(highestResolution()),
                                standardRatio(highestResolution())
                        ),
                        previewFpsRange = highestFps(),
                        flashMode = off(),
                        focusMode = firstAvailable(
                                continuousFocusPicture(),
                                autoFocus()
                        ),
                        frameProcessor = {
                            // Do something with the preview frame
                        }
                ),
                cameraErrorCallback = { Log.e(LOGGING_TAG, "Camera error: ", it) }
        )

Context:

Diolor commented 6 years ago

Docs from await: Blocks current thread until result is available.. So please use it in a thread.

Regarding toObservable looks like you also block main thread too and you call takePicture from that (main) thread.

So you need to use different threads:

.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
Raenar4k commented 6 years ago

Ah, my bad! Thanks for quick response. Yep, it works alright, when triggered in non UI thread.

I did expect it to block, but return some result eventually.
As to why callback never gets delivered: ~onPictureTaken() callback is called on main thread. takePicture method is also executed on main thread and ~it seems to block thread until callback is called~ -> callback can not be called since the thread is blocked by takePicture method -> and so it is never returned~ Update: read explanation below. Blocking is done by future.get() method

Diolor commented 6 years ago

When main thread is blocked because it's waiting the result no other operation can be executed e.g. taking picture itself internally.

Raenar4k commented 6 years ago

Now it makes sense to me! Thanks :)