Android 端处理 YUV 数据 - Libyuv 的编译与使用

news/2024/11/7 21:15:41/

前言

在 Android 系统上, Camera 输出的图像一般为 NV21(YUV420SP 系列) 格式, 当我们想进行录像处理时, 会面临两个问题

问题 1

图像的旋转问题

  • 后置镜头: 需要旋转 90°
  • 前置镜头: 需要旋转 270° 然后再进行镜像处理

问题 2

处理好镜头的旋转后, 当我们尝试使用 MediaCodec 进行 H.264 的硬编时, 便会发现偏色的问题

这是因为 MediaCodec 的 COLOR_FormatYUV420SemiPlanar 格式为 NV12, 并非是 NV21, 虽然都是 YUV420SP 系列, 但他们的排列不同, 都是先存储 Y 的数据, NV21 是 vu 交替存储, NV12 是 uv 交替存储

NV21: yyyy yyyy vu vu
NV12: yyyy yyyy uv uv

为了解决这个问题, 对于这个问题网上有很多的解决思路, 我们可以在 Java 层使用进行数据操作, 不过经过测试之后发现, 在 Samsung S7 Edge 上, 录制 1080p

  • 旋转与镜像: 20mm
  • NV21 转 NV12: 16mm

消耗时长约为 40mm, 这也仅仅是勉强能够进行 25 帧的录制, 在使用 opencv 进行人脸识别或滤镜处理时, 能够感觉到明显的卡顿感

libyuv 便是 google 为了解决移动端 NV21 数据处理不便所提供的开源库, 它提供了旋转, 裁剪, 镜像, 缩放等功能

接下来看看 libyuv 的编译与使用

一. 环境

操作系统

MacOS Mojave version 10.14.5

Libyuv

git clone https://chromium.googlesource.com/libyuv/libyuv

NDK 版本

NDK16

cmake 版本

➜  ~ cmake -version
cmake version 3.14.5

二. 编译脚本

从 libyuv 的源码中, 可以看到 libyuv 已经提供了 CMakeLists.txt, 因此我们可以直接通过 cmake 生成 Makefile, 然后通过 make 对 Makefile 进行编译

ARCH=arm
ANDROID_ARCH_ABI=armeabi-v7a
NDK_PATH=/Users/sharrychoo/Library/Android/ndk/android-ndk-r16b
PREFIX=`pwd`/android/${ARCH}/${CPU}# cmake 传参
cmake -G"Unix Makefiles" \-DANDROID_NDK=${NDK_PATH} \-DCMAKE_TOOLCHAIN_FILE=${NDK_PATH}/build/cmake/android.toolchain.cmake \-DANDROID_ABI=${ANDROID_ARCH_ABI} \-DANDROID_NATIVE_API_LEVE=16 \-DCMAKE_INSTALL_PREFIX=${PREFIX} \-DANDROID_ARM_NEON=TRUE \..# 生成动态库
make 
make install

输出的 so 库

三. 代码编写

我们将 so 库和头文件拷贝到 AS 中, 便可以进行代码的编写了, 这里编写一个 Libyuv 的工具类, 方便后续使用

一) Java 代码

这里以 NV21 转 I420 为例

/*** 处理 YUV 的工具类** @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>* @version 1.0* @since 2019-07-23*/
public class LibyuvUtil {static {System.loadLibrary("smedia-camera");}/*** 将 NV21 转 I420*/public static native void convertNV21ToI420(byte[] src, byte[] dst, int width, int height);......
}

二) native 实现

这里以将 NV21 转 I420 为例

namespace libyuv_util {void convertI420ToNV12(JNIEnv *env, jclass, jbyteArray i420_src, jbyteArray nv12_dst, int width,int height) {jbyte *src = env->GetByteArrayElements(i420_src, NULL);jbyte *dst = env->GetByteArrayElements(nv12_dst, NULL);// 执行转换 I420 -> NV12 的转换LibyuvUtil::I420ToNV12(src, dst, width, height);// 释放资源env->ReleaseByteArrayElements(i420_src, src, 0);env->ReleaseByteArrayElements(nv12_dst, dst, 0);}}void LibyuvUtil::NV21ToI420(jbyte *src, jbyte *dst, int width, int height) {// NV21 参数jint src_y_size = width * height;jbyte *src_y = src;jbyte *src_vu = src + src_y_size;// I420 参数jint dst_y_size = width * height;jint dst_u_size = dst_y_size >> 2;jbyte *dst_y = dst;jbyte *dst_u = dst + dst_y_size;jbyte *dst_v = dst + dst_y_size + dst_u_size;/*** <pre>* int NV21ToI420(const uint8_t* src_y,*          int src_stride_y,*          const uint8_t* src_vu,*          int src_stride_vu,*          uint8_t* dst_y,*          int dst_stride_y,*          uint8_t* dst_u,*          int dst_stride_u,*          uint8_t* dst_v,*          int dst_stride_v,*          int width,*          int height);* </pre>* <p>* stride 为颜色分量的跨距: 它描述一行像素中, 该颜色分量所占的 byte 数目, YUV 每个通道均为 1byte(8bit)* <p>* stride_y: Y 是最全的, 一行中有 width 个像素, 也就有 width 个 Y* stride_u: YUV420 的采样为 Y:U:V = 4:1:1, 从整体的存储来看, 一个 Y 分量的数目为 U/V 的四倍* 但从一行上来看, width 个 Y, 它会用到 width/2 个 U* stride_v: 同 stride_u 的分析方式*/libyuv::NV21ToI420((uint8_t *) src_y, width,(uint8_t *) src_vu, width,(uint8_t *) dst_y, width,(uint8_t *) dst_u, width >> 1,(uint8_t *) dst_v, width >> 1,width, height);
}

可以看到方法的调用也非常的简单, 只需要传入相关参数即可, 其中有个非常重要的参数, stride 跨距, 它描述一行像素中, 该颜色分量所占的 byte 数目

  • YUV420 系列
    • NV21
      • Y: 跨距为 width
      • VU: 跨距为 width
    • I420P(YU12):
      • Y: 跨距为 width
      • U: 跨距为 width/2
      • V: 跨距为 width/2
  • ABGR: 跨距为 4 *width

总结

通过 libyuv 进行旋转镜像转码等操作, 其时长如下

  • 旋转镜像: 5~8mm
  • NV21 转 NV12: 0~3mm

可以看到比起 java 代码, 几乎快了 3 倍, 这已经能够满足流畅录制的需求了

最后

如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。谢谢!


 


http://www.ppmy.cn/news/654893.html

相关文章

NAS组建日记(二):为了玩得自由,还是DIY个NAS机吧

NAS组建日记&#xff08;二&#xff09;&#xff1a;为了玩得自由&#xff0c;还是DIY个NAS机吧 NAS组建日记&#xff08;二&#xff09;&#xff1a;为了玩得自由&#xff0c;还是DIY个NAS机吧 从立冬到正月的数码 18-03-2821:05 现在照片视频越来越大&#xff0c;越来越多&a…

市面上的开发板总览

开发板品牌角度 树莓派3B 4B博通芯片? ARMv8 cortex-A72 Pico树莓派RP2040 ARMv6-M cortex-m0 Rock Pi瑞芯微rk3328 ARMv8 cortex-A53香橙派 ZERO全志H2 ARMv7 cortex-A7全志H616 ARMv8 cortex-A57 樱桃派 PC-V3S全志V3s ARMv7 cortex-A7 荔枝派 Zero全志V3s ARMv7 cort…

游戏蓝牙耳机哪款适合女孩子?小巧高颜值无线蓝牙耳机推荐

虽然蓝牙耳机的到来给人们的生活带来了很多的便利&#xff0c;但是蓝牙耳机的使用&#xff0c;还有一个我们绝对不能忽视的好处&#xff1a;健康&#xff01;常讲电话的人一定有这种经历&#xff0c;手持电话贴着耳朵讲&#xff0c;不仅手和脖子容易引起酸痛&#xff0c;讲久了…

NVIDIA Jetson Nano主机的autoware的学习与demo运行-第1章-操作环境的搭建

操作环境的搭建 计算机平台介绍 NVIDIA 在2019年NVIDIA GPU技术大会&#xff08;GTC&#xff09;上发布了Jetson Nano开发套件&#xff0c;这是一款售价99美元的计算机&#xff0c;现在可供嵌入式设计人员&#xff0c;研究人员和DIY制作商使用&#xff0c;在紧凑&#xff0c;易…

嵌入式优秀资源网址整理

## 嵌入式相关开源项目、库、资料------持续更新中 在产品开发的过程&#xff0c;难免会遇到各种各样的问题&#xff0c;为了方便大家快速查找。 学习初期最难找的就是找学习资料了&#xff0c;本贴精心汇总了一些嵌入式相关资源&#xff0c;包括但不限于编程语言、单片机、开…

智能座舱域控制器技术发展趋势分析

已剪辑自: https://mp.weixin.qq.com/s/ajmpg7ThTUBerLvb2Bng9g 提到座舱域控制器用的主控SoC芯片&#xff0c;大家第一个会想到应该就是高通的SA8155P 。目前&#xff0c;在主机厂新上市的中高端车型中&#xff0c;其座舱的主控SoC芯片多是采用高通的SA8155P&#xff0c; SA8…

有关嵌入式的 github

目录 嵌入式相关开源项目、库、资料------持续更新中1、OS2、实用库/框架3、GUI相关4、物联网、智能家居5、实例/项目/软硬结合6、学习资料/资源/工具/网站7、一些芯片原厂代码仓库 嵌入式相关开源项目、库、资料------持续更新中 学习初期最难找的就是找学习资料了&#xff0…

一季度SSG营收占比7%,能否充当联想“新触角”?

8月11日&#xff0c;国内PC产业巨头联想公布了截至2021年6月底的2021/22财年第一季业绩。 就基本面数据&#xff0c;联想营收获得了20.7%的增长&#xff0c;净利方面也实现119%的大涨。 新一季财报的发出&#xff0c;也收获了资本市场的青睐&#xff0c;8月11日午间&#xff…