基于Android Camera2Video实现,如何获取摄像头帧为Bitmap?
解决Camera2Video中获取摄像头帧并转换为Bitmap的方案
不要从编码后的数据流(比如EncoderWrapper输出的压缩ByteBuffer)中提取帧——编码后的是H.264/H.265这类压缩格式,解码会额外增加性能开销,最优方案是在Camera2将帧发送给编码器之前,直接获取原始的摄像头帧数据。
推荐方案:用ImageReader捕获原始帧
步骤1:配置Camera2时添加ImageReader作为输出目标
在Camera2的初始化流程中,创建一个ImageReader,将其Surface与编码器的Surface一起加入CaptureRequest的输出列表,这样Camera会同时把帧发送给编码器和ImageReader:
// 匹配编码器的尺寸(width/height和EncoderWrapper初始化时的参数一致) val imageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2) // 设置帧可用回调 imageReader.setOnImageAvailableListener({ reader -> val image = reader.acquireLatestImage() ?: return@setOnImageAvailableListener // 将YUV格式的Image转换为Bitmap val bitmap = convertYuvImageToBitmap(image) // 这里处理你的Bitmap:比如显示到ImageView、做图像识别等 // 注意:如果要更新UI,需要切换到主线程 runOnUiThread { yourImageView.setImageBitmap(bitmap) } // 必须关闭Image,否则会造成内存泄漏 image.close() }, Handler(Looper.getMainLooper())) // 将ImageReader的Surface和编码器Surface加入CaptureRequest val captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD) captureRequestBuilder.addTarget(encoderWrapper.getInputSurface()) // 原编码器Surface captureRequestBuilder.addTarget(imageReader.surface()) // 新增的ImageReader Surface // 提交CaptureRequest到CameraSession cameraSession.setRepeatingRequest(captureRequestBuilder.build(), null, null)
步骤2:实现YUV到Bitmap的转换
Camera输出的Image默认是YUV_420_888格式,需要转换为Bitmap,这里提供两种实现方式:
方式1:通过YuvImage压缩为JPEG再解码(简单易用)
private fun convertYuvImageToBitmap(image: Image): Bitmap { val planes = image.planes val yBuffer = planes[0].buffer val uBuffer = planes[1].buffer val vBuffer = planes[2].buffer val ySize = yBuffer.remaining() val uSize = uBuffer.remaining() val vSize = vBuffer.remaining() // 把YUV_420_888转成NV21格式 val nv21ByteArray = ByteArray(ySize + uSize + vSize) yBuffer.get(nv21ByteArray, 0, ySize) vBuffer.get(nv21ByteArray, ySize, vSize) uBuffer.get(nv21ByteArray, ySize + vSize, uSize) // 用YuvImage压缩为JPEG,再解码成Bitmap val yuvImage = YuvImage(nv21ByteArray, 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) }
方式2:用RenderScript加速转换(无压缩损耗,性能更好)
如果需要避免JPEG压缩的画质损失,可以用RenderScript直接转换YUV到RGBA:
private fun convertYuvToBitmapWithRenderScript(image: Image, rs: RenderScript): 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 yStride = yPlane.rowStride val uvStride = uPlane.rowStride val uvPixelStride = uPlane.pixelStride val bitmap = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888) val allocation = Allocation.createFromBitmap(rs, bitmap) val scriptYuvToRgba = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)) scriptYuvToRgba.setInput(Allocation.createSized(rs, Element.U8(rs), yBuffer.remaining() + uBuffer.remaining() + vBuffer.remaining())) scriptYuvToRgba.forEach(allocation) allocation.copyTo(bitmap) return bitmap }
注意事项
- 线程处理:ImageReader的回调在非UI线程,更新UI必须用
runOnUiThread切换线程。 - 内存管理:必须调用
image.close()释放Image资源,否则会快速耗尽内存。 - 尺寸匹配:ImageReader的宽高必须和Camera预览尺寸、编码器尺寸完全一致,避免帧拉伸。
内容的提问来源于stack exchange,提问作者Ashutosh
相关产品推荐
相关产品推荐

