近期换部门,从事之前从未接触过的Android音视频开发,主要涉及到USB摄像头调用、libyuv处理Nv21图像、直播推流等功能,对应的库有【UVCCamera】、【libyuv】等,刚接触没经验也没人带挺难搞的,而且网上资料很凌乱,所以,开此篇总结&汇总一下近期的研究,兴许可以帮助到别人,本人亦是新手,文中如有不正确的地方,欢迎指出点评。
一、libyuv入门
先简单说明一下,不管是Android手机的Camera,或是外接的UVCCamera(免驱摄像头),它们获取到的yuv图像格式都是nv21格式的,针对业务,我们可能需要对摄像头获取到的图像进行各种处理,如:镜像、旋转、缩放、裁剪等。
1、yuv概念
总的来说,我们要做的yuv数据处理,无非就是针对各种图像格式下yuv数据(byte[])的转换、调整。举个例子:
- NV21:安卓的模式。存储顺序是先存Y,再存U,再VU交替存储,格式为:YYYYVUVUVU。
- I420:又叫YU12,安卓的模式。存储顺序是先存Y,再存U,最后存V,格式为YYYYUUUVVV。
可以看到,NV21与I420(都属于YUV420)之间的差别在于U和V的存储位置,所以,NV21要转换成I420,就必须把NV21中的U和V调整为I420的方式存储即可,其他格式之间的转换以此类推。
2、libyuv概念
libyuv是Google开源的yuv图像处理库,实现对各种yuv数据之间的转换,包括数据转换,裁剪,缩放,旋转。尽管libyuv对yuv数据处理的核心进行了封装,但还是要求开发者对各种格式的区别有所了解,这样才能正常调用对应方法,进行转换。在使用这个库之前,如有时间,建议先去了解下yuv的相关知识,相关的文章推荐如下:
《音视频基础知识---像素格式YUV》
3、libyuv核心方法
通过git下载下来的libyuv源码目录,有几个文件需要我们了解下,分别是:
// 格式转换(NV21、NV12、I420等格式互转)
libyuv\include\libyuv\convert_from.h
// 图像处理(镜像、旋转、缩放、裁剪)
libyuv\include\libyuv\planar_functions.h
libyuv\include\libyuv\rotate.h
libyuv\include\libyuv\scale.h
libyuv\include\libyuv\convert.h
以上的几个头文件中声明了libyuv对yuv数据处理的一些函数,我们后续需要使用到这些函数来处理yuv数据的转换和修改。
二、libyuv进阶
通过上面的入门内容与资料,应该对yuv与libyuv有比较表面的理解了,但要完全理解透还是得靠自己再多看看其他资料才行,下面直接使用libyuv这个库,实现一些实际的代码逻辑,完全干货分享,如有错误请不吝赐教。
1、yuv转换格式
因为libyuv对于图像的处理基本上都是针对i420格式的,所以,不管摄像头获取到的图像格式如何,都需要在进行图像处理之前转换成i420格式才行。这里整理了比较常用的nv21与i420、nv12与i420互转的cpp代码实现:
nv21是Android摄像头获取到的图像格式。 nv12是iOS摄像头获取到的图像格式。
// nv21 --> i420
void nv21ToI420(jbyte *src_nv21_data, jint width, jint height, jbyte *src_i420_data) {
jint src_y_size = width * height;
jint src_u_size = (width >> 1) * (height >> 1);
jbyte *src_nv21_y_data = src_nv21_data;
jbyte *src_nv21_vu_data = src_nv21_data + src_y_size;
jbyte *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_y_size;
jbyte *src_i420_v_data = src_i420_data + src_y_size + src_u_size;
libyuv::NV21ToI420((const uint8 *) src_nv21_y_data, width,
(const uint8 *) src_nv21_vu_data, width,
(uint8 *) src_i420_y_data, width,
(uint8 *) src_i420_u_data, width >> 1,
(uint8 *) src_i420_v_data, width >> 1,
width, height);
}
// i420 --> nv21
void i420ToNv21(jbyte *src_i420_data, jint width, jint height, jbyte *src_nv21_data) {
jint src_y_size = width * height;
jint src_u_size = (width >> 1) * (height >> 1);
jbyte *src_nv21_y_data = src_nv21_data;
jbyte *src_nv21_uv_data = src_nv21_data + src_y_size;
jbyte *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_y_size;
jbyte *src_i420_v_data = src_i420_data + src_y_size + src_u_size;
libyuv::I420ToNV21(
(const uint8 *) src_i420_y_data, width,
(const uint8 *) src_i420_u_data, width >> 1,
(const uint8 *) src_i420_v_data, width >> 1,
(uint8 *) src_nv21_y_data, width,
(uint8 *) src_nv21_uv_data, width,
width, height);
}
// nv12 --> i420
void nv12ToI420(jbyte *Src_data, jint src_width, jint src_height, jbyte *Dst_data) {
// NV12 video size
jint NV12_Size = src_width * src_height * 3 / 2;
jint NV12_Y_Size = src_width * src_height;
// YUV420 video size
jint I420_Size = src_width * src_height * 3 / 2;
jint I420_Y_Size = src_width * src_height;
jint I420_U_Size = (src_width >> 1)*(src_height >> 1);
jint I420_V_Size = I420_U_Size;
// src: buffer address of Y channel and UV channel
jbyte *Y_data_Src = Src_data;
jbyte *UV_data_Src = Src_data + NV12_Y_Size;
jint src_stride_y = src_width;
jint src_stride_uv = src_width;
//dst: buffer address of Y channel、U channel and V channel
jbyte *Y_data_Dst = Dst_data;
jbyte *U_data_Dst = Dst_data + I420_Y_Size;
jbyte *V_data_Dst = Dst_data + I420_Y_Size + I420_U_Size;
jint Dst_Stride_Y = src_width;
jint Dst_Stride_U = src_width >> 1;
jint Dst_Stride_V = Dst_Stride_U;
libyuv::NV12ToI420((const uint8 *) Y_data_Src, src_stride_y,
(const uint8 *) UV_data_Src, src_stride_uv,
(uint8 *) Y_data_Dst, Dst_Stride_Y,
(uint8 *) U_data_Dst, Dst_Stride_U,
(uint8 *) V_data_Dst, Dst_Stride_V,
src_width, src_height);
}
// i420 --> nv12
void i420ToNv12(jbyte *src_i420_data, jint width, jint height, jbyte *src_nv12_data) {
jint src_y_size = width * height;
jint src_u_size = (width >> 1) * (height >> 1);
jbyte *src_nv12_y_data = src_nv12_data;
jbyte *src_nv12_uv_data = src_nv12_data + src_y_size;
jbyte *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_y_size;
jbyte *src_i420_v_data = src_i420_data + src_y_size + src_u_size;
libyuv::I420ToNV12(
(const uint8 *) src_i420_y_data, width,
(const uint8 *) src_i420_u_data, width >> 1,
(const uint8 *) src_i420_v_data, width >> 1,
(uint8 *) src_nv12_y_data, width,
(uint8 *) src_nv12_uv_data, width,
width, height);
}
2、yuv处理图像
针对常见的图像处理,在这里也整理了一些,主要包括 镜像、旋转、缩放、剪裁。 要注意的是,所有的图像处理,都是基于i420数据格式的!
// 镜像
void mirrorI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data) {
jint src_i420_y_size = width * height;
// jint src_i420_u_size = (width >> 1) * (height >> 1);
jint src_i420_u_size = src_i420_y_size >> 2;
jbyte *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;
jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;
jbyte *dst_i420_y_data = dst_i420_data;
jbyte *dst_i420_u_data = dst_i420_data + src_i420_y_size;
jbyte *dst_i420_v_data = dst_i420_data + src_i420_y_size + src_i420_u_size;
libyuv::I420Mirror((const uint8 *) src_i420_y_data, width,
(const uint8 *) src_i420_u_data, width >> 1,
(const uint8 *) src_i420_v_data, width >> 1,
(uint8 *) dst_i420_y_data, width,
(uint8 *) dst_i420_u_data, width >> 1,
(uint8 *) dst_i420_v_data, width >> 1,
width, height);
}
// 旋转
void rotateI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data, jint degree) {
jint src_i420_y_size = width * height;
jint src_i420_u_size = (width >> 1) * (height >> 1);
jbyte *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;
jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;
jbyte *dst_i420_y_data = dst_i420_data;
jbyte *dst_i420_u_data = dst_i420_data + src_i420_y_size;
jbyte *dst_i420_v_data = dst_i420_data + src_i420_y_size + src_i420_u_size;
//要注意这里的width和height在旋转之后是相反的
if (degree == libyuv::kRotate90 || degree == libyuv::kRotate270) {
libyuv::I420Rotate((const uint8 *) src_i