马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
相机录像时预览流和录像流各跑一遍 sensor 输出,这部分 CPU 开销挺扎眼。ARM Neon 指令集能把图像拷贝的活儿并行起来,省下不少时间。这篇记录用 Neon 完成一帧 NV12 数据拷贝的完整实现,也顺带回答“两个流能不能只拷一份数据”这个问题。
一、背景:录像时的双流开销
Camera apk 在普通相机模式做录像操作时,一般是同时请求两个流,也就是预览流和录像流。当两个流输出的图像格式和分辨率完全相同的时候,能不能用一个流拷贝出另一个流的数据,从而省掉 sensor 输出被处理两次的开销?
我们都知道平时用的 memcpy() 是 CPU 一件件搬的,图像数据一大就非常耗 CPU,那有没有办法把 copy 操作优化一下,让 CPU 占用小一点?
二、用 Neon 实现 NV12 拷贝
这里介绍用 ARM Neon 指令集实现输出拷贝的优化。下面以具体实现来看,Neon 指令到底怎么完成一帧 NV12 图像数据的拷贝。
Android 源码里本身就支持 Neon,只要在用到的模块里引用并调用就可以了,实现如下:
- #include <arm_neon.h> //neon指令相关数据结构和接口定义
- void NeonMemcpy(void *dest, const void *src, size_t size)
- {
- uint8_t* dst8 = static_cast<uint8_t*>(dest);
- const uint8_t* src8 = static_cast<const uint8_t*>(src);
-
- //小数据直接拷贝
- if (size < 64) {
- for (; size > 0; size--) {
- *dst8++ = *src8++;
- }
- return;
- }
-
- // 64字节对齐预处理
- const uintptr_t mask = 0x3F;
-
- const uintptr_t misalign = reinterpret_cast<uintptr_t>(src8) & mask;
- if (misalign > 0) {
- const size_t align_bytes = 64 - misalign;
- for (size_t i = 0; i < align_bytes; i++) {
- *dst8++ = *src8++;
- }
- size -= align_bytes;
- }
- // 主拷贝循环(每次64字节)
- const size_t chunks = size >> 6; // 64字节块数
- for (size_t i = 0; i < chunks; i++) {
- // 使用基本NEON指令替代vld1q_u8_x4
- uint8x16_t data0 = vld1q_u8(src8); //从src8加载16个8位数据到data0
- uint8x16_t data1 = vld1q_u8(src8 + 16);
- uint8x16_t data2 = vld1q_u8(src8 + 32);
- uint8x16_t data3 = vld1q_u8(src8 + 48);
-
- vst1q_u8(dst8, data0); //将data0中的16个8位数据存储到dst8中
- vst1q_u8(dst8 + 16, data1);
- vst1q_u8(dst8 + 32, data2);
- vst1q_u8(dst8 + 48, data3);
-
- src8 += 64;
- dst8 += 64;
- }
- // 处理剩余数据(16字节块)
- size &= 63;
- const size_t vec16_count = size >> 4;
- for (size_t i = 0; i < vec16_count; i++) {
- vst1q_u8(dst8, vld1q_u8(src8));
- src8 += 16;
- dst8 += 16;
- size -= 16;
- }
- // 尾部字节处理
- for (; size > 0; size--) {
- *dst8++ = *src8++;
- }
- }
复制代码
三、这段代码具体在搬什么
- 小于 64 字节的数据直接走字节循环拷贝,不进 SIMD 流程
- 先做 64 字节对齐预处理:让源地址对齐到 64 字节边界,不够的部分先用单字节补齐
- 主拷贝循环一次搬 64 字节,vld1q_u8 连续取四段 16 字节,再 vst1q_u8 分四次写回
- 剩下的按 16 字节块处理,最后不足 16 字节的尾巴再逐字节收尾
四、落地时留意两件事
- 头文件引用的是 arm_neon.h,Neon 相关的数据结构和接口定义都在这里面
- 主循环用的是基本 NEON 指令来替代 vld1q_u8_x4,通用性更好
这套 NeonMemcpy 可以直接用在预览流和录像流的那一层,少搬一遍 sensor 数据,同样思路换个图像格式也能照搬。
编辑提示:要接入的话先看 Android 源码里的 Neon 支持范围,按模块引用&调用,改完记得和 memcpy 实测一下耗时再定。
|