Julian Steenbakker

hotfix: cherry pick updated yuvtorgb converter, fix resource not released

@@ -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,37 +19,72 @@ import java.nio.ByteBuffer @@ -23,37 +19,72 @@ 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 + try {
40 val yuvBuffer = YuvByteBuffer(image, yuvBits) 41 val yuvBuffer = YuvByteBuffer(image, yuvBits)
41 yuvBits = yuvBuffer.buffer 42 yuvBits = yuvBuffer.buffer
42 43
43 if (needCreateAllocations(image, yuvBuffer)) { 44 if (needCreateAllocations(image, yuvBuffer)) {
  45 + createAllocations(image, yuvBuffer)
  46 + }
  47 +
  48 + yuvBuffer.buffer.get(bytes)
  49 + @Suppress("DEPRECATION")
  50 + inputAllocation!!.copyFrom(bytes)
  51 +
  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 + }
  61 + }
  62 +
  63 + private fun needCreateAllocations(image: Image, yuvBuffer: YuvByteBuffer): Boolean {
  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")
44 val yuvType = Type.Builder(rs, Element.U8(rs)) 72 val yuvType = Type.Builder(rs, Element.U8(rs))
45 .setX(image.width) 73 .setX(image.width)
46 .setY(image.height) 74 .setY(image.height)
47 .setYuvFormat(yuvBuffer.type) 75 .setYuvFormat(yuvBuffer.type)
  76 + @Suppress("DEPRECATION")
48 inputAllocation = Allocation.createTyped( 77 inputAllocation = Allocation.createTyped(
49 rs, 78 rs,
50 yuvType.create(), 79 yuvType.create(),
51 Allocation.USAGE_SCRIPT 80 Allocation.USAGE_SCRIPT
52 ) 81 )
53 bytes = ByteArray(yuvBuffer.buffer.capacity()) 82 bytes = ByteArray(yuvBuffer.buffer.capacity())
  83 + @Suppress("DEPRECATION")
54 val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs)) 84 val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs))
55 .setX(image.width) 85 .setX(image.width)
56 .setY(image.height) 86 .setY(image.height)
  87 + @Suppress("DEPRECATION")
57 outputAllocation = Allocation.createTyped( 88 outputAllocation = Allocation.createTyped(
58 rs, 89 rs,
59 rgbaType.create(), 90 rgbaType.create(),
@@ -61,21 +92,11 @@ import java.nio.ByteBuffer @@ -61,21 +92,11 @@ import java.nio.ByteBuffer
61 ) 92 )
62 } 93 }
63 94
64 - yuvBuffer.buffer.get(bytes)  
65 - inputAllocation!!.copyFrom(bytes)  
66 -  
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)  
72 - }  
73 -  
74 - 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()) 95 + @Suppress("DEPRECATION")
  96 + fun release() {
  97 + inputAllocation?.destroy()
  98 + outputAllocation?.destroy()
  99 + scriptYuvToRgb.destroy()
  100 + rs.destroy()
80 } 101 }
81 } 102 }