使⽤libyuv对YUV数据进⾏缩放,旋转,镜像,裁剪等操作
1.背景
在Android做过⾃定义Camera的朋友应该都知道,我们可以通过public void onPreviewFrame(byte[] data, Camera camera)回调中获取摄像头采集到的每⼀帧的数据,但是这个byte[] data的数据格式YUV的,并不能直接给我们进⾏使⽤,那么该通过什么样的⽅法对这个YUV数据进⾏处理呢?
2.YUV数据格式介绍
2.Libyuv库的介绍
其实对于YUV数据的处理,Google已经开源了⼀个叫做libyuv的库专门⽤于YUV数据的处理。
2.1 什么是libyuv
libyuv是Google开源的实现各种YUV与RGB之间相互转换、旋转、缩放的库。它是跨平台的,可在Windows、Linux、Mac、Android 等操作系统,x86、x64、arm架构上进⾏编译运⾏,⽀持SSE、AVX、NEON等SIMD指令加速。
2.2 Android上如何使⽤Libyuv
libyuv.png
如果⽆法下载的话,也可以从我⽂章最后的demo中去进⾏拷贝。新键Android项⽬,并且创建的时候勾选项include C++ Support,也就是改android项⽬⽀持C,C++的编译,如果对于Android Stuido如何⽀持C,C++编译不清楚的,请⾃⾏百度⾕歌,这⾥就不多细说。项⽬创建之后将下载的libyuv库直接拷贝到src/main/cpp⽬录下
libyuv.png
修改⽂件,并在src/main/cpp下创建YuvJni.cpp⽂件,修改如下
cmake_minimum_required(VERSION 3.4.1)
include_directories(src/main/cpp/libyuv/include)
add_subdirectory(src/main/cpp/libyuv ./build)
aux_source_directory(src/main/cpp SRC_FILE)
add_library(yuvutil SHARED ${SRC_FILE})
find_library(log-lib log)
target_link_libraries(yuvutil ${log-lib} yuv)
创建⽂件YuvUtil.java,在这⾥我添加了三个⽅法进⾏yuv数据的操作
public class YuvUtil {
static {
System.loadLibrary("yuvutil");
}
/**
* YUV数据的基本的处理
*
* @param src 原始数据
* @param width 原始的宽
* @param height 原始的⾼
* @param dst 输出数据
* @param dst_width 输出的宽
* @param dst_height 输出的⾼
* @param mode 压缩模式。这⾥为0,1,2,3 速度由快到慢,质量由低到⾼,⼀般⽤0就好了,因为0的速度最快
* @param degree 旋转的⾓度,90,180和270三种
* @param isMirror 是否镜像,⼀般只有270的时候才需要镜像
**/
public static native void compressYUV(byte[] src, int width, int height, byte[] dst, int dst_width, int dst_height, int mode, int degree, boolean isMirror);
/**
* yuv数据的裁剪操作
*
* @param src 原始数据
* @param width 原始的宽
* @param height 原始的⾼
* @param dst 输出数据
* @param dst_width 输出的宽
* @param dst_height 输出的⾼
* @param left 裁剪的x的开始位置,必须为偶数,否则显⽰会有问题
* @param top 裁剪的y的开始位置,必须为偶数,否则显⽰会有问题
**/
public static native void cropYUV(byte[] src, int width, int height, byte[] dst, int dst_width, int dst_height, int left, int top);
/**
* 将I420转化为NV21
*
* @param i420Src 原始I420数据
* @param nv21Src 转化后的NV21数据
* @param width 输出的宽
* @param width 输出的⾼
**/
public static native void yuvI420ToNV21(byte[] i420Src, byte[] nv21Src, int width, int height);
}
同时在前⾯创建的YuvJni.cpp⽂件中添加对应的⽅法
extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_compressYUV(JNIEnv *env, jclass type,
jbyteArray src_, jint width,
jint height, jbyteArray dst_,
jint dst_width, jint dst_height,
jint mode, jint degree,
jboolean isMirror) {
}
extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_cropYUV(JNIEnv *env, jclass type, jbyteArray src_, jint width,
jint height, jbyteArray dst_, jint dst_width, jint dst_height,
jint left, jint top) {
}
extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_yuvI420ToNV21(JNIEnv *env, jclass type, jbyteArray i420Src,
jbyteArray nv21Src,
jint width, jint height) {
}
3.使⽤Libyuv库进⾏YUV数据的操作
接下来就是要libyuv对yuv数据进⾏缩放,旋转,镜像,裁剪等操作。在libyuv的实际使⽤过程中,更多的是⽤于直播推流前对Camera 采集到的YUV数据进⾏处理的操作。对如今,Camera的预览⼀般采⽤的是1080p,并且摄像头采集到的数据是旋转之后的,⼀般来说后置摄像头旋转了90度,前置摄像头旋转了270度并且⽔平镜像。在下⾯的例⼦中,就对Camera返回的yuv数据进⾏相关的处理操作。
3.1 NV21转化为I420
对于如何获取Camera返回的YUV数据,不是本篇⽂章的重点,不了解的请⾃⾏百度⾕歌。因为Camera返回的YUV数据只能是NV21和YV12两种,⽽libyuv的缩放旋转镜像的操作需要的是I420的数据格式,那么第⼀步就是将NV21(例⼦中Camera返回数据格式设置的是NV21)转化为I420了。⽅法如下:
#include "libyuv.h"
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);
}
⾸先我们必须得先导⼊libyuv(#include "libyuv.h"),在这⾥我们⽤到的是libyuv::NV21ToI420⽅法,我们来看下它传参
// Convert NV21 to I420. Same as NV12 but u and v pointers swapped.
LIBYUV_API
int NV21ToI420(const uint8* src_y,
int src_stride_y,
const uint8* src_vu,
int src_stride_vu,
uint8* dst_y,
int dst_stride_y,
uint8* dst_u,
int dst_stride_u,
uint8* dst_v,
int dst_stride_v,
int width,
int height) {
return X420ToI420(src_y, src_stride_y, src_stride_y, src_vu, src_stride_vu,
dst_y, dst_stride_y, dst_v, dst_stride_v, dst_u,
dst_stride_u, width, height);
}
⾸先第⼀个参数src_y指的是NV21数据中的Y的数据,我们知道NV21的数据格式是YYYYYYYY VUVU,
同时NV21的数据⼤⼩是width height3/2,可以知道Y的数据⼤⼩是width height,⽽V和U均为width height/4。第⼆个参数src_stride_y表⽰的是Y的数组⾏间距,在这⾥很容易知道是width。以此类推src_vu和src_stride_vu也可以相对应的知道了。对于后⾯的参数
dst_y,dst_stride_y,dst_u,dst_stride_u,dst_v ,dst_stride_v表⽰分别表⽰的是输出的I420数据的YUV三个分量的数据,最后的width和height也就是我们设置的Camera的预览的width和height了。
3.2 I420数据的缩放和旋转
经过上⾯的NV21转化为I420操作之后,我们就可以对I420数据进⾏后续的缩放和旋转的操作,它们的传参跟上⾯的NV21ToI420是类似的,这⾥就不具体的介绍了。缩放的⽅法
void scaleI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data, jint dst_width,
jint dst_height, jint mode) {
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;
jint dst_i420_y_size = dst_width * dst_height;
jint dst_i420_u_size = (dst_width >> 1) * (dst_height >> 1);
jbyte *dst_i420_y_data = dst_i420_data;
jbyte *dst_i420_u_data = dst_i420_data + dst_i420_y_size;
jbyte *dst_i420_v_data = dst_i420_data + dst_i420_y_size + dst_i420_u_size;
libyuv::I420Scale((const uint8 *) src_i420_y_data, width,
(const uint8 *) src_i420_u_data, width >> 1,
(const uint8 *) src_i420_v_data, width >> 1,
width, height,
(uint8 *) dst_i420_y_data, dst_width,
(uint8 *) dst_i420_u_data, dst_width >> 1,
(uint8 *) dst_i420_v_data, dst_width >> 1,
dst_width, dst_height,
(libyuv::FilterMode) mode);
}
值得注意的是,这边有⼀个缩放的模式选择 (libyuv::FilterMode),它的值分别有0,1,2,3四种,代表不同的缩放模式,在我实际的使⽤过程中,0的缩放速度是最快的,且远远快与其他的3种,并且就缩放的效果来看,以我的⾁眼观察,看不出有什么区别,这⾥为了保证速度,⼀般⽤FilterMode.kFilterNone就好了
typedef enum FilterMode {
kFilterNone = 0, // Point sample; Fastest.
kFilterLinear = 1, // Filter horizontally only.
kFilterBilinear = 2, // Faster than box, but lower quality scaling down.
kFilterBox = 3 // Highest quality.
} FilterModeEnum;
旋转的⽅法如下,不过在这⾥要注意的是,因为Camera输出的数据是需要进⾏90度或者是270的旋转,那么要注意的就是旋转之后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;
if (degree == libyuv::kRotate90 || degree == libyuv::kRotate270) {
libyuv::I420Rotate((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, height,
cmake如何使用(uint8 *) dst_i420_u_data, height >> 1,
(uint8 *) dst_i420_v_data, height >> 1,
width, height,
(libyuv::RotationMode) degree);
}
}
3.3 libyuv其他的⼀些操作
libyuv的操作不仅仅是上⾯的这些,它还有镜像,裁剪的⼀些操作,同时还有⼀些其他数据格式的转化和对于的操作。包括rgba与yuv数据的转化等。在⽂章中,镜像和裁剪的操作就不加以叙述了,在demo之中我已经加⼊了进去了。
4.最后
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论