Camera1中我们可以通过onPreviewFrame接口直接获取到默认为NV21格式的预览数据, 如下图注释所示,还可以通过调用setPreviewFormat方法要求Camera返回YV12格式的预览数据。
那么在Camera2的架构中,我们要如何获取NV21或者YV12格式的预览数据呢?
在之前的文章Android Camera2详解中描述到,要获取每一帧的预览数据,我们需要ImageReader这个类的帮助:
val imageReader = ImageReader(width, height, format, maxImages)
可以看到有一个format的参数可以指定,遗憾的是NV21格式是不支持的,进入ImageReader的构造方法中可以看到:
if (format == ImageFormat.NV21) {
throw new IllegalArgumentException( "NV21 format is not supported");
}
NV21
查看文档关于NV21的描述,发现在Camera2中官方建议使用YUV_420_888。
YUV_420_888是一种Y:U:V按4:1:1的比例进行采样的格式,也就是说其中每一个UV都被四个Y共享, 888表示每一个分量都是8bits
NV21和YV12都是这种YUV_420的采样格式,只是其中U,V分量的排列不一样。
NV21:先排Y, 再VU交替排列, 码流:YYYY YYYY VU VU
YV12:先排Y, 再排V, 最后排列U, 码流:YYYY YYYY VV UU
虽然YUV存在444,422,420等不同的采样方式,但是深入ImageReader源码后,发现只能使用YUV_420_888,另外两种会抛出UnsupportedOperationException异常。
在ImageReader的实例中,参数format设置为YUV_420_888,并注册数据回调后,对于每一帧预览,我们将拿到一个包装对象Image。
ImageReader.OnImageAvailableListener {
val image = it.acquireLatestImage
//...
}
如何从这个Image对象中获取具体的YUV byte[]数据呢?
在YUV_420_888这种格式下拿到的Image对象,存在以下几点规则:
- Y,U,V的数据是分别存储在3个plane中的;
- plane#0为Y分量,plane#1为U分量,plane#2为V分量;
- Y-plane的pixelStride一定为1,而U-plane和V-plane的pixelStride则不固定(所谓pixelStride是指连续的码流中有效位的偏移,1表示数据是紧凑的,连续有效,中间不存在无效数据)
- 每一个plane都有一个rowStride,表示一行数据的长度,rowStride >= imageWidth,这就是有些图片存在绿边的原因;
- U-plane的rowStride一定等于V-plane的rowStride
知道了相关的规则,就可以开始撸代码了。
首先将Y,U,V分量的数据依次提取出来:
/**
* take YUV data from image, output data format-> YYYYYYYYUUVV
*/
private fun readYuvDataToBuffer(image: Image, data: ByteArray): Boolean {
if (image.format != ImageFormat.YUV_420_888) {
throw IllegalArgumentException("only support ImageFormat.YUV_420_888 for mow")
}
val imageWidth = image.width
val imageHeight = image.height
val planes = image.planes
var offset = 0
for (plane in planes.indices) {
val buffer = planes[plane].buffer ?: return false
val rowStride = planes[plane].rowStride
val pixelStride = planes[plane].pixelStride
val planeWidth = if (plane == 0) imageWidth else imageWidth / 2
val planeHeight = if (plane == 0) imageHeight else imageHeight / 2
if (pixelStride == 1 && rowStride == planeWidth) {
buffer.get(data, offset, planeWidth * planeHeight)
offset += planeWidth * planeHeight
} else {
// Copy pixels one by one respecting pixelStride and rowStride
val rowData = ByteArray(rowStride)
var colOffset: Int
for (row in 0 until planeHeight - 1) {
colOffset = 0
buffer.get(rowData, 0, rowStride)
for (col in 0 until planeWidth) {
data[offset++] = rowData[colOffset]
colOffset += pixelStride
}
}
// Last row is special in some devices:
// may not contain the full |rowStride| bytes of data
colOffset = 0
buffer.get(rowData, 0, min(rowStride, buffer.remaining()))
for (col in 0 until planeWidth) {
data[offset++] = rowData[colOffset]
colOffset += pixelStride
}
}
}
return true
}
然后按照所需要的格式要求进行调整Y,U,V分量的排列:
/**
* take YUV image from image.
*/
fun readYuvDataToBuffer(image: Image, format:Int, data: ByteArray): Boolean {
require(!(format != ImageFormat.NV21 && format != ImageFormat.YV12)) {
"output only support ImageFormat.NV21 and ImageFormat.YV12 for now"
}
val result = readYuvDataToBuffer(image, data)
if (!result) {
return false
}
// data(YU12): YYYY YYYY UU VV
if (format == ImageFormat.NV21) {
// convert to: YYYY YYYY VU VU
val size = data.size
val uv = ByteArray(size / 3)
var uOffset = size / 6 * 4
var vOffset = size / 6 * 5
for (i in 0 until uv.size - 1 step 2) {
uv[i] = data[vOffset++]
uv[i + 1] = data[uOffset++]
}
val uvOffset = size / 3 * 2
for (i in uvOffset until size) {
data[i] = uv[i - uvOffset]
}
} else if (format == ImageFormat.YV12) {
// convert to: YYYY YYYY VV UU
val size = data.size
val tmp = ByteArray(size / 6)
val uOffset = size / 6 * 4
val vOffset = size / 6 * 5
System.arraycopy(data, uOffset, tmp, 0, tmp.size)
System.arraycopy(data, vOffset, data, uOffset, tmp.size)
System.arraycopy(tmp, 0, data, vOffset, tmp.size)
}
return true
}
YV12
ImageReader的format是可以直接指定为YV12的,这种格式下又是如何从Image对象中提取YUV byte[]数据的呢?
通过阅读源码我们得知,其实在应用层设置YV12或者YUV_420_888,最后被映射到framework层的都是同一个东西。所以我们还是要通过3个plane来分别获取Y,U,V分量的数据。
只不过从目前我测试的一些设备上来看,Y,U,V3个分量的pixelStride总是为1,不过官方文档并没有指明这一点,说明实际情况还是要具体平台具体对待的。
PS:YUV的采样方式有很多种,同一种采样方式还存在不同的排列方式,导致YUV格式五花八门的, 比如:NV21, NV12, YV12, YU12, YUY2…
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。