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

Android IoT相机:如何将三个YUV缓冲区合并为单个数组?

解决方案:YUV缓冲区合并与快照实现

一、合并Y/U/V ByteBuffer为NV21格式字节数组

要使用YuvImage,需要将回调中的三个ByteBuffer合并为NV21格式的字节数组(YUV420SP的一种,结构为Y平面 + VU交织平面),具体步骤如下:

1. 获取帧信息与分量长度

首先从videoFrameInfo中拿到画面宽高,计算各分量的字节长度:

val width = videoFrameInfo?.width ?: 0
val height = videoFrameInfo?.height ?: 0
val yLength = width * height // Y分量总长度
val uvLength = yLength / 4   // U/V分量各占Y的1/4
val nv21Length = yLength + uvLength * 2 // NV21总长度 = Y + V + U

2. 分配数组并复制数据

创建对应长度的字节数组,依次复制Y、V、U分量,注意V和U需要交替写入(NV21要求VU交织):

val nv21ByteArray = ByteArray(nv21Length)

// 复制Y分量到数组前半部分
y.position(0)
y.get(nv21ByteArray, 0, yLength)

// 复制V和U分量,交替写入数组后半部分
v.position(0)
u.position(0)
var currentIndex = yLength
for (i in 0 until uvLength) {
    nv21ByteArray[currentIndex++] = v.get()
    nv21ByteArray[currentIndex++] = u.get()
}

二、使用YuvImage转JPEG并保存

得到NV21数组后,就可以用YuvImage的compressToJpeg方法生成JPEG数据,再通过作用域存储API保存到外部存储:

// 创建YuvImage实例
val yuvImage = YuvImage(
    nv21ByteArray,
    ImageFormat.NV21,
    width,
    height,
    null // 裁剪参数,不需要则传null
)

// 压缩为JPEG字节流
val outputStream = ByteArrayOutputStream()
yuvImage.compressToJpeg(Rect(0, 0, width, height), 80, outputStream) // 80为压缩质量(0-100)
val jpegData = outputStream.toByteArray()

// 通过作用域存储保存到外部存储(以公共图片目录为例)
val contentValues = ContentValues().apply {
    put(MediaStore.Images.Media.DISPLAY_NAME, "camera_snapshot_${System.currentTimeMillis()}.jpg")
    put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
    put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/TuyaCamera")
}
val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
uri?.let {
    context.contentResolver.openOutputStream(it)?.use { os ->
        os.write(jpegData)
    }
}

三、方案合理性与替代方案

现有方案的合理性

用YuvImage实现是非常适合快照场景的:

  • 原生API,无需引入第三方库,兼容性好(从Android 1开始支持);
  • 代码简洁,开发成本低;
  • 单帧快照的性能完全够用,不会有明显卡顿。

替代方案

如果需要更高性能或更灵活的处理,可以考虑以下方案:

  • RenderScript硬件加速:使用ScriptIntrinsicYuvToRGB将YUV转换为RGB,再压缩为JPEG。优点是利用GPU加速,处理速度更快;缺点是代码稍复杂,需要管理RenderScript上下文。
  • OpenGL ES渲染:将YUV数据上传为纹理,渲染到帧缓冲区后读取像素数据,再压缩保存。适合需要同时处理渲染和快照的场景,但代码复杂度较高。
  • 第三方库(如libyuv):编译libyuv的SO库,直接调用其YUV转JPEG的接口。支持更多YUV格式,性能优异,但会增加APK体积。

SDK文档相关翻译(视频截图部分)

由于Android 10及以上版本引入的作用域存储机制,原有的接收绝对文件路径的snapshot方法无法正常工作。开发者需自行实现截图功能:通过注册帧数据回调获取YUV420SP(NV21)格式的原始帧数据,再自行将数据转换为JPEG格式并保存到外部存储。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 05:35:27