You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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
}

注意事项

  1. 线程处理:ImageReader的回调在非UI线程,更新UI必须用runOnUiThread切换线程。
  2. 内存管理:必须调用image.close()释放Image资源,否则会快速耗尽内存。
  3. 尺寸匹配:ImageReader的宽高必须和Camera预览尺寸、编码器尺寸完全一致,避免帧拉伸。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 17:20:54