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
相关产品推荐
相关产品推荐

