hotfix: cherry pick updated yuvtorgb converter, fix resource not released
Showing
2 changed files
with
63 additions
and
41 deletions
| @@ -153,6 +153,7 @@ class MobileScanner( | @@ -153,6 +153,7 @@ class MobileScanner( | ||
| 153 | 153 | ||
| 154 | bmResult.recycle() | 154 | bmResult.recycle() |
| 155 | imageProxy.close() | 155 | imageProxy.close() |
| 156 | + imageFormat.release() | ||
| 156 | 157 | ||
| 157 | mobileScannerCallback( | 158 | mobileScannerCallback( |
| 158 | barcodeMap, | 159 | barcodeMap, |
| @@ -2,18 +2,14 @@ package dev.steenbakker.mobile_scanner.utils | @@ -2,18 +2,14 @@ package dev.steenbakker.mobile_scanner.utils | ||
| 2 | 2 | ||
| 3 | import android.content.Context | 3 | import android.content.Context |
| 4 | import android.graphics.Bitmap | 4 | import android.graphics.Bitmap |
| 5 | -import android.graphics.ImageFormat | ||
| 6 | import android.media.Image | 5 | import android.media.Image |
| 7 | -import android.os.Build | ||
| 8 | import android.renderscript.Allocation | 6 | import android.renderscript.Allocation |
| 9 | import android.renderscript.Element | 7 | import android.renderscript.Element |
| 10 | import android.renderscript.RenderScript | 8 | import android.renderscript.RenderScript |
| 11 | import android.renderscript.ScriptIntrinsicYuvToRGB | 9 | import android.renderscript.ScriptIntrinsicYuvToRGB |
| 12 | import android.renderscript.Type | 10 | import android.renderscript.Type |
| 13 | -import androidx.annotation.RequiresApi | ||
| 14 | import java.nio.ByteBuffer | 11 | import java.nio.ByteBuffer |
| 15 | 12 | ||
| 16 | - | ||
| 17 | /** | 13 | /** |
| 18 | * Helper class used to efficiently convert a [Media.Image] object from | 14 | * Helper class used to efficiently convert a [Media.Image] object from |
| 19 | * YUV_420_888 format to an RGB [Bitmap] object. | 15 | * YUV_420_888 format to an RGB [Bitmap] object. |
| @@ -23,59 +19,84 @@ import java.nio.ByteBuffer | @@ -23,59 +19,84 @@ import java.nio.ByteBuffer | ||
| 23 | * The [yuvToRgb] method is able to achieve the same FPS as the CameraX image | 19 | * The [yuvToRgb] method is able to achieve the same FPS as the CameraX image |
| 24 | * analysis use case at the default analyzer resolution, which is 30 FPS with | 20 | * analysis use case at the default analyzer resolution, which is 30 FPS with |
| 25 | * 640x480 on a Pixel 3 XL device. | 21 | * 640x480 on a Pixel 3 XL device. |
| 26 | - */class YuvToRgbConverter(context: Context) { | 22 | + */ |
| 23 | +/// TODO: Upgrade to implementation without deprecated android.renderscript, but with same or better performance. See https://github.com/juliansteenbakker/mobile_scanner/issues/1142 | ||
| 24 | +class YuvToRgbConverter(context: Context) { | ||
| 25 | + @Suppress("DEPRECATION") | ||
| 27 | private val rs = RenderScript.create(context) | 26 | private val rs = RenderScript.create(context) |
| 27 | + @Suppress("DEPRECATION") | ||
| 28 | private val scriptYuvToRgb = | 28 | private val scriptYuvToRgb = |
| 29 | ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)) | 29 | ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)) |
| 30 | 30 | ||
| 31 | - // Do not add getters/setters functions to these private variables | ||
| 32 | - // because yuvToRgb() assume they won't be modified elsewhere | ||
| 33 | private var yuvBits: ByteBuffer? = null | 31 | private var yuvBits: ByteBuffer? = null |
| 34 | private var bytes: ByteArray = ByteArray(0) | 32 | private var bytes: ByteArray = ByteArray(0) |
| 33 | + @Suppress("DEPRECATION") | ||
| 35 | private var inputAllocation: Allocation? = null | 34 | private var inputAllocation: Allocation? = null |
| 35 | + @Suppress("DEPRECATION") | ||
| 36 | private var outputAllocation: Allocation? = null | 36 | private var outputAllocation: Allocation? = null |
| 37 | 37 | ||
| 38 | @Synchronized | 38 | @Synchronized |
| 39 | fun yuvToRgb(image: Image, output: Bitmap) { | 39 | fun yuvToRgb(image: Image, output: Bitmap) { |
| 40 | - val yuvBuffer = YuvByteBuffer(image, yuvBits) | ||
| 41 | - yuvBits = yuvBuffer.buffer | 40 | + try { |
| 41 | + val yuvBuffer = YuvByteBuffer(image, yuvBits) | ||
| 42 | + yuvBits = yuvBuffer.buffer | ||
| 42 | 43 | ||
| 43 | - if (needCreateAllocations(image, yuvBuffer)) { | ||
| 44 | - val yuvType = Type.Builder(rs, Element.U8(rs)) | ||
| 45 | - .setX(image.width) | ||
| 46 | - .setY(image.height) | ||
| 47 | - .setYuvFormat(yuvBuffer.type) | ||
| 48 | - inputAllocation = Allocation.createTyped( | ||
| 49 | - rs, | ||
| 50 | - yuvType.create(), | ||
| 51 | - Allocation.USAGE_SCRIPT | ||
| 52 | - ) | ||
| 53 | - bytes = ByteArray(yuvBuffer.buffer.capacity()) | ||
| 54 | - val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs)) | ||
| 55 | - .setX(image.width) | ||
| 56 | - .setY(image.height) | ||
| 57 | - outputAllocation = Allocation.createTyped( | ||
| 58 | - rs, | ||
| 59 | - rgbaType.create(), | ||
| 60 | - Allocation.USAGE_SCRIPT | ||
| 61 | - ) | ||
| 62 | - } | 44 | + if (needCreateAllocations(image, yuvBuffer)) { |
| 45 | + createAllocations(image, yuvBuffer) | ||
| 46 | + } | ||
| 63 | 47 | ||
| 64 | - yuvBuffer.buffer.get(bytes) | ||
| 65 | - inputAllocation!!.copyFrom(bytes) | 48 | + yuvBuffer.buffer.get(bytes) |
| 49 | + @Suppress("DEPRECATION") | ||
| 50 | + inputAllocation!!.copyFrom(bytes) | ||
| 66 | 51 | ||
| 67 | - // Convert NV21 or YUV_420_888 format to RGB | ||
| 68 | - inputAllocation!!.copyFrom(bytes) | ||
| 69 | - scriptYuvToRgb.setInput(inputAllocation) | ||
| 70 | - scriptYuvToRgb.forEach(outputAllocation) | ||
| 71 | - outputAllocation!!.copyTo(output) | 52 | + @Suppress("DEPRECATION") |
| 53 | + scriptYuvToRgb.setInput(inputAllocation) | ||
| 54 | + @Suppress("DEPRECATION") | ||
| 55 | + scriptYuvToRgb.forEach(outputAllocation) | ||
| 56 | + @Suppress("DEPRECATION") | ||
| 57 | + outputAllocation!!.copyTo(output) | ||
| 58 | + } catch (e: Exception) { | ||
| 59 | + throw IllegalStateException("Failed to convert YUV to RGB", e) | ||
| 60 | + } | ||
| 72 | } | 61 | } |
| 73 | 62 | ||
| 74 | private fun needCreateAllocations(image: Image, yuvBuffer: YuvByteBuffer): Boolean { | 63 | private fun needCreateAllocations(image: Image, yuvBuffer: YuvByteBuffer): Boolean { |
| 75 | - return (inputAllocation == null || // the very 1st call | ||
| 76 | - inputAllocation!!.type.x != image.width || // image size changed | ||
| 77 | - inputAllocation!!.type.y != image.height || | ||
| 78 | - inputAllocation!!.type.yuv != yuvBuffer.type || // image format changed | ||
| 79 | - bytes.size == yuvBuffer.buffer.capacity()) | 64 | + @Suppress("DEPRECATION") |
| 65 | + return inputAllocation?.type?.x != image.width || | ||
| 66 | + inputAllocation?.type?.y != image.height || | ||
| 67 | + inputAllocation?.type?.yuv != yuvBuffer.type | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + private fun createAllocations(image: Image, yuvBuffer: YuvByteBuffer) { | ||
| 71 | + @Suppress("DEPRECATION") | ||
| 72 | + val yuvType = Type.Builder(rs, Element.U8(rs)) | ||
| 73 | + .setX(image.width) | ||
| 74 | + .setY(image.height) | ||
| 75 | + .setYuvFormat(yuvBuffer.type) | ||
| 76 | + @Suppress("DEPRECATION") | ||
| 77 | + inputAllocation = Allocation.createTyped( | ||
| 78 | + rs, | ||
| 79 | + yuvType.create(), | ||
| 80 | + Allocation.USAGE_SCRIPT | ||
| 81 | + ) | ||
| 82 | + bytes = ByteArray(yuvBuffer.buffer.capacity()) | ||
| 83 | + @Suppress("DEPRECATION") | ||
| 84 | + val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs)) | ||
| 85 | + .setX(image.width) | ||
| 86 | + .setY(image.height) | ||
| 87 | + @Suppress("DEPRECATION") | ||
| 88 | + outputAllocation = Allocation.createTyped( | ||
| 89 | + rs, | ||
| 90 | + rgbaType.create(), | ||
| 91 | + Allocation.USAGE_SCRIPT | ||
| 92 | + ) | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + @Suppress("DEPRECATION") | ||
| 96 | + fun release() { | ||
| 97 | + inputAllocation?.destroy() | ||
| 98 | + outputAllocation?.destroy() | ||
| 99 | + scriptYuvToRgb.destroy() | ||
| 100 | + rs.destroy() | ||
| 80 | } | 101 | } |
| 81 | } | 102 | } |
-
Please register or login to post a comment