fix: Fix VideoPipeline crash on Samsung (Disable USAGE_GPU_SAMPLED_IMAGE ImageReader) (#2555)
				
					
				
			* fix: Fix VideoPipeline crash on Samsung (`USAGE_GPU_SAMPLED_IMAGE` not supported) * Format code
This commit is contained in:
		@@ -9,7 +9,6 @@ import android.os.Build
 | 
				
			|||||||
import android.util.Log
 | 
					import android.util.Log
 | 
				
			||||||
import android.view.Surface
 | 
					import android.view.Surface
 | 
				
			||||||
import androidx.annotation.Keep
 | 
					import androidx.annotation.Keep
 | 
				
			||||||
import androidx.annotation.RequiresApi
 | 
					 | 
				
			||||||
import com.facebook.jni.HybridData
 | 
					import com.facebook.jni.HybridData
 | 
				
			||||||
import com.facebook.proguard.annotations.DoNotStrip
 | 
					import com.facebook.proguard.annotations.DoNotStrip
 | 
				
			||||||
import com.mrousavy.camera.frameprocessor.Frame
 | 
					import com.mrousavy.camera.frameprocessor.Frame
 | 
				
			||||||
@@ -79,15 +78,12 @@ class VideoPipeline(
 | 
				
			|||||||
      val format = getImageReaderFormat()
 | 
					      val format = getImageReaderFormat()
 | 
				
			||||||
      Log.i(TAG, "Using ImageReader round-trip (format: #$format)")
 | 
					      Log.i(TAG, "Using ImageReader round-trip (format: #$format)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      imageReader = ImageReader.newInstance(width, height, format, MAX_IMAGES)
 | 
				
			||||||
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
 | 
					      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
 | 
				
			||||||
        Log.i(TAG, "Using API 29 for GPU ImageReader...")
 | 
					        Log.i(TAG, "Using ImageWriter with custom format (#$format)...")
 | 
				
			||||||
        val usageFlags = getRecommendedHardwareBufferFlags()
 | 
					 | 
				
			||||||
        Log.i(TAG, "Using ImageReader flags: $usageFlags")
 | 
					 | 
				
			||||||
        imageReader = ImageReader.newInstance(width, height, format, MAX_IMAGES, usageFlags)
 | 
					 | 
				
			||||||
        imageWriter = ImageWriter.newInstance(glSurface, MAX_IMAGES, format)
 | 
					        imageWriter = ImageWriter.newInstance(glSurface, MAX_IMAGES, format)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        Log.i(TAG, "Using legacy API for CPU ImageReader...")
 | 
					        Log.i(TAG, "Using ImageWriter with default format...")
 | 
				
			||||||
        imageReader = ImageReader.newInstance(width, height, format, MAX_IMAGES)
 | 
					 | 
				
			||||||
        imageWriter = ImageWriter.newInstance(glSurface, MAX_IMAGES)
 | 
					        imageWriter = ImageWriter.newInstance(glSurface, MAX_IMAGES)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      imageReader!!.setOnImageAvailableListener({ reader ->
 | 
					      imageReader!!.setOnImageAvailableListener({ reader ->
 | 
				
			||||||
@@ -185,56 +181,6 @@ class VideoPipeline(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Get the recommended HardwareBuffer flags for creating ImageReader instances with.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * Tries to use [HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE] if possible, [HardwareBuffer.USAGE_CPU_READ_OFTEN]
 | 
					 | 
				
			||||||
   * or a combination of both flags if CPU access is needed ([enableFrameProcessor]), and [0] otherwise.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  @RequiresApi(Build.VERSION_CODES.Q)
 | 
					 | 
				
			||||||
  @Suppress("LiftReturnOrAssignment")
 | 
					 | 
				
			||||||
  private fun getRecommendedHardwareBufferFlags(): Long {
 | 
					 | 
				
			||||||
    val cpuFlag = HardwareBuffer.USAGE_CPU_READ_OFTEN
 | 
					 | 
				
			||||||
    val gpuFlag = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
 | 
					 | 
				
			||||||
    val bothFlags = gpuFlag or cpuFlag
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (format == PixelFormat.NATIVE) {
 | 
					 | 
				
			||||||
      // We don't need CPU access, so we can use GPU optimized buffers
 | 
					 | 
				
			||||||
      if (supportsHardwareBufferFlags(gpuFlag)) {
 | 
					 | 
				
			||||||
        // We support GPU Buffers directly and
 | 
					 | 
				
			||||||
        Log.i(TAG, "GPU HardwareBuffers are supported!")
 | 
					 | 
				
			||||||
        return gpuFlag
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        // no flags are supported - fall back to default
 | 
					 | 
				
			||||||
        return 0
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      // We are using YUV or RGB formats, so we need CPU access on the Frame
 | 
					 | 
				
			||||||
      if (supportsHardwareBufferFlags(bothFlags)) {
 | 
					 | 
				
			||||||
        // We support both CPU and GPU flags!
 | 
					 | 
				
			||||||
        Log.i(TAG, "GPU + CPU HardwareBuffers are supported!")
 | 
					 | 
				
			||||||
        return bothFlags
 | 
					 | 
				
			||||||
      } else if (supportsHardwareBufferFlags(cpuFlag)) {
 | 
					 | 
				
			||||||
        // We only support a CPU read flag, that's fine
 | 
					 | 
				
			||||||
        Log.i(TAG, "CPU HardwareBuffers are supported!")
 | 
					 | 
				
			||||||
        return cpuFlag
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        // no flags are supported - fall back to default
 | 
					 | 
				
			||||||
        return 0
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @RequiresApi(Build.VERSION_CODES.Q)
 | 
					 | 
				
			||||||
  private fun supportsHardwareBufferFlags(flags: Long): Boolean {
 | 
					 | 
				
			||||||
    val hardwareBufferFormat = format.toHardwareBufferFormat()
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      return HardwareBuffer.isSupported(width, height, hardwareBufferFormat, 1, flags)
 | 
					 | 
				
			||||||
    } catch (_: Throwable) {
 | 
					 | 
				
			||||||
      return false
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private external fun getInputTextureId(): Int
 | 
					  private external fun getInputTextureId(): Int
 | 
				
			||||||
  private external fun onBeforeFrame()
 | 
					  private external fun onBeforeFrame()
 | 
				
			||||||
  private external fun onFrame(transformMatrix: FloatArray)
 | 
					  private external fun onFrame(transformMatrix: FloatArray)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,6 @@ import android.graphics.ImageFormat
 | 
				
			|||||||
import android.util.Log
 | 
					import android.util.Log
 | 
				
			||||||
import com.mrousavy.camera.core.InvalidTypeScriptUnionError
 | 
					import com.mrousavy.camera.core.InvalidTypeScriptUnionError
 | 
				
			||||||
import com.mrousavy.camera.core.PixelFormatNotSupportedError
 | 
					import com.mrousavy.camera.core.PixelFormatNotSupportedError
 | 
				
			||||||
import com.mrousavy.camera.utils.HardwareBufferUtils
 | 
					 | 
				
			||||||
import com.mrousavy.camera.utils.ImageFormatUtils
 | 
					import com.mrousavy.camera.utils.ImageFormatUtils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum class PixelFormat(override val unionValue: String) : JSUnionValue {
 | 
					enum class PixelFormat(override val unionValue: String) : JSUnionValue {
 | 
				
			||||||
@@ -20,11 +19,6 @@ enum class PixelFormat(override val unionValue: String) : JSUnionValue {
 | 
				
			|||||||
      else -> throw PixelFormatNotSupportedError(this.unionValue)
 | 
					      else -> throw PixelFormatNotSupportedError(this.unionValue)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun toHardwareBufferFormat(): Int {
 | 
					 | 
				
			||||||
    val imageFormat = toImageFormat()
 | 
					 | 
				
			||||||
    return HardwareBufferUtils.getHardwareBufferFormat(imageFormat)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  companion object : JSUnionValue.Companion<PixelFormat> {
 | 
					  companion object : JSUnionValue.Companion<PixelFormat> {
 | 
				
			||||||
    private const val TAG = "PixelFormat"
 | 
					    private const val TAG = "PixelFormat"
 | 
				
			||||||
    fun fromImageFormat(imageFormat: Int): PixelFormat =
 | 
					    fun fromImageFormat(imageFormat: Int): PixelFormat =
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,36 +0,0 @@
 | 
				
			|||||||
package com.mrousavy.camera.utils
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.graphics.ImageFormat
 | 
					 | 
				
			||||||
import android.hardware.HardwareBuffer
 | 
					 | 
				
			||||||
import android.media.ImageReader
 | 
					 | 
				
			||||||
import android.os.Build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class HardwareBufferUtils {
 | 
					 | 
				
			||||||
  companion object {
 | 
					 | 
				
			||||||
    fun getHardwareBufferFormat(imageFormat: Int): Int {
 | 
					 | 
				
			||||||
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
 | 
					 | 
				
			||||||
        // Dynamically create an ImageReader with the target ImageFormat, and then
 | 
					 | 
				
			||||||
        // get it's HardwareBuffer format to see what it uses underneath.
 | 
					 | 
				
			||||||
        val imageReader = ImageReader.newInstance(1, 1, imageFormat, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
 | 
					 | 
				
			||||||
        val format = imageReader.hardwareBufferFormat
 | 
					 | 
				
			||||||
        imageReader.close()
 | 
					 | 
				
			||||||
        return format
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (imageFormat == ImageFormat.PRIVATE) {
 | 
					 | 
				
			||||||
        // PRIVATE formats are opaque, their actual equivalent HardwareBuffer format is unknown.
 | 
					 | 
				
			||||||
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
 | 
					 | 
				
			||||||
          // We can assume that YUV 4:2:0 or RGB is used.
 | 
					 | 
				
			||||||
          return HardwareBuffer.YCBCR_420_888
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          // Maybe assume we are on RGB if we're not on API R or above...
 | 
					 | 
				
			||||||
          return HardwareBuffer.RGB_888
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // According to PublicFormat.cpp in Android's codebase, the formats map 1:1 anyways..
 | 
					 | 
				
			||||||
      // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/ui/PublicFormat.cpp
 | 
					 | 
				
			||||||
      return imageFormat
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user