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




