You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Camera2 API方向设置异常:Android 9下图像始终旋转-90度

解决Camera2+ImageReader获取JPEG图像方向异常(Android 9+)的问题

我来帮你拆解问题根源,再给出针对性的解决办法:

问题核心原因

你的代码里用了TEMPLATE_PREVIEW模板创建捕获请求,同时设置JPEG_ORIENTATION,但在Android 9及以上版本,这个设置不会对预览流的JPEG输出生效

  • TEMPLATE_PREVIEW是为实时预览设计的,默认使用YUV格式,即便你把ImageReader设为JPEG,系统会做格式转换,但会忽略JPEG_ORIENTATION参数(Android 5的系统逻辑不同,所以能正常工作)。
  • JPEG_ORIENTATION仅对TEMPLATE_STILL_CAPTURE模板的捕获请求有效——这个模板是专门用于静态JPEG拍照的,系统会严格处理JPEG的Exif方向信息。

另外,你直接用BitmapFactory.decodeByteArray解码时,BitmapFactory默认不会自动应用Exif中的方向信息,这也会加剧显示方向错误的问题。

解决方案一:改用正确的捕获模板(适合持续获取JPEG图像的场景)

如果你需要持续获取JPEG格式图像,建议改用TEMPLATE_STILL_CAPTURE模板创建重复捕获请求,同时结合相机传感器方向计算正确的JPEG_ORIENTATION值:

步骤1:先获取相机传感器方向

在打开相机前,先拿到当前相机的传感器方向:

val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameraId = "你的相机ID(比如后置相机ID)"
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!

步骤2:修改捕获请求模板与方向设置

TEMPLATE_PREVIEW替换为TEMPLATE_STILL_CAPTURE,并根据屏幕方向动态计算JPEG方向:

override fun onOpened(camera: CameraDevice) {
    val builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) // 改用拍照模板
    // 根据屏幕旋转角度计算正确的JPEG方向
    val displayRotation = windowManager.defaultDisplay.rotation
    val jpegOrientation = calculateJpegOrientation(sensorOrientation, displayRotation)
    
    val request = builder.apply {
        addTarget(imageReader.surface) // 确保目标是ImageReader的Surface
        set(CaptureRequest.JPEG_ORIENTATION, jpegOrientation)
        set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range(15, 15))
    }.build()
    camera.createCaptureSession(mutableListOf(imageReader.surface), SessionStateCallback(request), null)
}

// 计算JPEG方向的工具方法
private fun calculateJpegOrientation(sensorOrientation: Int, displayRotation: Int): Int {
    return when (displayRotation) {
        Surface.ROTATION_0 -> sensorOrientation
        Surface.ROTATION_90 -> (sensorOrientation + 270) % 360
        Surface.ROTATION_180 -> (sensorOrientation + 180) % 360
        Surface.ROTATION_270 -> (sensorOrientation + 90) % 360
        else -> sensorOrientation
    }
}

解决方案二:改用YUV格式+高效旋转(适合实时处理场景)

如果需要更高帧率,JPEG格式的开销太大,建议改用YUV_420_888格式的ImageReader,然后用RenderScript处理旋转——比Java层Bitmap操作快得多:

步骤1:创建YUV格式的ImageReader

imageReader = ImageReader.newInstance(1920, 1080, ImageFormat.YUV_420_888, 2) // 增加缓存数量提升流畅度

步骤2:用RenderScript处理图像旋转

RenderScript是Android原生的高性能计算框架,处理图像旋转的速度远快于Java层Bitmap操作:

// 初始化RenderScript
val rs = RenderScript.create(this)
val rotationScript = ScriptIntrinsicRotate.create(rs, Element.U8_4(rs))

private val imageAvailableListener = { imageReader: ImageReader ->
    val image = imageReader.acquireNextImage() ?: return@let
    try {
        // 将YUV图像转换为RGBA格式(RenderScript旋转需要RGBA输入)
        val rgbaBitmap = convertYuvToRgba(image)
        // 设置旋转角度(根据实际需求替换,比如90度)
        rotationScript.setRotate(90f)
        val input = Allocation.createFromBitmap(rs, rgbaBitmap)
        val output = Allocation.createTyped(rs, input.type)
        
        rotationScript.forEach(input, output)
        output.copyTo(rgbaBitmap)
        
        view.showCameraData(rgbaBitmap)
    } finally {
        image.close()
    }
}

// YUV转RGBA的工具方法
private fun convertYuvToRgba(image: Image): Bitmap {
    val yPlane = image.planes[0]
    val uPlane = image.planes[1]
    val vPlane = image.planes[2]
    
    val yBuffer = yPlane.buffer
    val uBuffer = uPlane.buffer
    val vBuffer = vPlane.buffer
    
    val ySize = yBuffer.remaining()
    val uSize = uBuffer.remaining()
    val vSize = vBuffer.remaining()
    
    val nv21 = ByteArray(ySize + uSize + vSize)
    yBuffer.get(nv21, 0, ySize)
    vBuffer.get(nv21, ySize, vSize)
    uBuffer.get(nv21, ySize + vSize, uSize)
    
    val bitmap = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888)
    val yuvImage = YuvImage(nv21, ImageFormat.NV21, image.width, image.height, null)
    val outputStream = ByteArrayOutputStream()
    yuvImage.compressToJpeg(Rect(0, 0, image.width, image.height), 100, outputStream)
    val jpegBytes = outputStream.toByteArray()
    return BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
}

额外注意事项

  • 如果你坚持用JPEG格式,解码时要手动读取Exif方向并旋转:可以用ExifInterface读取JPEG的方向信息,再调用Bitmap.createBitmap旋转,但这种方法在高分辨率下速度很慢,不适合实时场景。
  • 确保捕获请求的目标Surface是imageReader.surface,不要和预览用的SurfaceView/TextureView的Surface混用,否则会导致冲突。

内容的提问来源于stack exchange,提问作者Timur

火山引擎 最新活动