最近在做一个项目需要将RGB888转换为RGB565,用C语言转换的代码很简单,这是从ffmpeg中摘抄的代码

static inline void rgb24to16_c(const uint8_t *src, uint8_t *dst, int src_size)
{
uint16_t *d = (uint16_t *)dst;
const uint8_t *s = src;
const uint8_t *end = s + src_size; while (s < end) {
const int r = *s++;
const int g = *s++;
const int b = *s++;
*d++ = (b >> 3) | ((g & 0xFC) << 3) | ((r & 0xF8) << 8);
}
}

这个项目需要转换的数据量不多,用C语言进行转换的CPU开销完全可以接受。但我并不满足于此,项目中使用的芯片支持NEON指令加速,所以为什么不用呢?

简单搜索一番发现国很少有相关的文章,最后还是去外面找了一些有用的资料,在学习NEON指令和寻找的过程中发现ARM官网已经提供了例程,那我正好可以偷懒。

这里给出ARM官网的例程链接,防止有人无法无访问,这里也给出源码

RGB888转RGB565

https://developer.arm.com/documentation/den0018/a/NEON-Code-Examples-with-Optimization/Converting-color-depth/Converting-from-RGB888-to-RGB565

uint8_t *src = image_src;
uint16_t *dst = image_dst;
int count = PIXEL_NUMBER; while (count >= 8) {
uint8x8x3_t vsrc;
uint16x8_t vdst; vsrc = vld3_u8(src); vdst = vshll_n_u8(vsrc.val[0], 8);
vdst = vsriq_n_u16(vdst, vshll_n_u8(vsrc.val[1], 8), 5);
vdst = vsriq_n_u16(vdst, vshll_n_u8(vsrc.val[2], 8), 11); vst1q_u16(dst, vdst); dst += 8;
src += 8*3;
count -= 8;
}

RGB565转RGB888

https://developer.arm.com/documentation/den0018/a/NEON-Code-Examples-with-Optimization/Converting-color-depth/Converting-from-RGB565-to-RGB888

uint16_t *src = image_src;
uint8_t *dst = image_dst;
int count = PIXEL_NUMBER; while (count >= 8) {
uint16x8_t vsrc;
uint8x8x3_t vdst; vsrc = vld1q_u16(src); vdst.val[0] = vshrn_n_u16(vreinterpretq_u16_u8(vshrq_n_u8(vreinterpretq_u8_u16(vsrc), 3)), 5);
vdst.val[1] = vshl_n_u8(vshrn_n_u16(vsrc, 5) ,2);
vdst.val[2] = vmovn_u16(vshlq_n_u16(vsrc, 3)); vst3_u8(dst, vdst); dst += 8*3;
src += 8;
count -= 8;
}

NEON的头文件是 arm_neon.h,编译需要加 -mcpu=cortex-a7 -mfloat-abi=softfp -mfpu=neon-vfpv4,其中-mpu参数按自己的芯片填写,我这里用的是一款A7架构芯片,主频800MHz。

接下来就是传统项目,性能测试,随机生成数据的 RGB888 图片转换为 RGB565 图片,重复1000次并计时,测试代码如下

#include <arm_neon.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h> void rgb888_to_rgb565(uint8_t *in, uint8_t *out, int h, int v)
{
uint16_t *d = (uint16_t *)out;
const uint8_t *s = in;
const uint8_t *end = s + h * v * 3; while (s < end) {
const int r = *s++;
const int g = *s++;
const int b = *s++;
*d++ = (b >> 3) | ((g & 0xFC) << 3) | ((r & 0xF8) << 8);
}
} void rgb888_to_rgb565_neon(uint8_t *in, uint8_t *out, int h, int v)
{
uint8_t *src = in;
uint16_t *dst = (uint16_t *)out;
int count = h * v; if (count % 8 != 0) {
printf("pixel number must align with 8\n");
return;
} while (count >= 8) {
uint8x8x3_t vsrc;
uint16x8_t vdst; vsrc = vld3_u8(src); vdst = vshll_n_u8(vsrc.val[0], 8);
vdst = vsriq_n_u16(vdst, vshll_n_u8(vsrc.val[1], 8), 5);
vdst = vsriq_n_u16(vdst, vshll_n_u8(vsrc.val[2], 8), 11); vst1q_u16(dst, vdst); dst += 8;
src += 8*3;
count -= 8;
}
} #define WIDTH 320
#define HEIGHT 180 // #define WIDTH 640
// #define HEIGHT 360 // #define WIDTH 960
// #define HEIGHT 540 // #define WIDTH 1280
// #define HEIGHT 720 // #define WIDTH 1600
// #define HEIGHT 900 // #define WIDTH 1920
// #define HEIGHT 1080 #define LOOP 1000 uint8_t rgb888[WIDTH * HEIGHT * 3];
uint16_t rgb565[WIDTH * HEIGHT];
int main(int argc, char **argv)
{
int32_t i;
for (i = 0; i < WIDTH * HEIGHT * 3; i++) {
rgb888[i] = rand() & 0xFF;
} struct timeval tv1;
struct timeval tv2;
double td = 0; // ms printf("size %d x %d, loop %d\n", WIDTH, HEIGHT, LOOP); gettimeofday(&tv1, NULL);
for (i = 0; i < LOOP; i++) {
rgb888_to_rgb565(rgb888, (uint8_t *)rgb565, WIDTH, HEIGHT);
}
gettimeofday(&tv2, NULL);
td = ((double)tv2.tv_sec * 1000.0 + (double)tv2.tv_usec / 1000.0) - ((double)tv1.tv_sec * 1000.0 + (double)tv1.tv_usec / 1000.0);
printf("time c: %f ms\n", td); gettimeofday(&tv1, NULL);
for (i = 0; i < LOOP; i++) {
rgb888_to_rgb565_neon(rgb888, (uint8_t *)rgb565, WIDTH, HEIGHT);
}
gettimeofday(&tv2, NULL);
td = ((double)tv2.tv_sec * 1000.0 + (double)tv2.tv_usec / 1000.0) - ((double)tv1.tv_sec * 1000.0 + (double)tv1.tv_usec / 1000.0);
printf("time neon: %f ms\n", td); return 0;
}

来看一下耗时:

size 320 x 180, loop 1000
time c: 1049.035889 ms
time neon: 405.596924 ms size 640 x 360, loop 1000
time c: 3948.885986 ms
time neon: 2150.033203 ms size 960 x 540, loop 1000
time c: 9026.308838 ms
time neon: 5033.337891 ms size 1280 x 720, loop 1000
time c: 16550.081055 ms
time neon: 8756.577881 ms size 1600 x 900, loop 1000
time c: 25366.738037 ms
time neon: 13618.843994 ms size 1920 x 1080, loop 1000
time c: 37058.665039 ms
time neon: 20064.520996 ms

对于RGB888转RGB565来说,NEON指令的性能大约是C语言的两倍。其他NEON指令的性能未做测试,以上测试内容仅供参考。

最后给出ARM官方的参考文档

NEON指令开发指南

https://developer.arm.com/documentation/den0018/a

NEON指令查询

https://developer.arm.com/architectures/instruction-sets/intrinsics/#f:@navigationhierarchiessimdisa=[Neon]

使用NEON指令加速RGB888和RGB565的相互转换的更多相关文章

  1. (二十三)ARM平台NEON指令的编译和优化

    ARM平台NEON指令的编译和优化 本文介绍了ARM平台基于ARM v7-A架构的ARM Cortex-A系列处理器(Cortex-A5, Cortex-A7,Cortex-A8, Cortex-A9 ...

  2. BMP RGB888转RGB565 +上下翻转+缩放

      典型的BMP图像文件由四部分组成: (1) 位图头文件数据结构,它包含BMP图像文件的类型.文件大小和位图起始位置等信息: typedef struct tagBITMAPFILEHEADER { ...

  3. neon指令,注意事项

    1. vbic_s8 (int8x8_t a, int8x8_t b) 是  ~(ai & bi),一开始理解成  (~ai )& bi 导致出错 2.uint8x8_t vqshrn ...

  4. linux kernel态下使用NEON对算法进行加速

    ARM处理器从cortex系列开始集成NEON处理单元,该单元可以简单理解为协处理器,专门为矩阵运算等算法设计,特别适用于图像.视频.音频处理等场景,应用也很广泛. 本文先对NEON处理单元进行简要介 ...

  5. 【linux】ARM板子开启浮点和neon加速

    参考 1. ARM平台NEON指令的编译和优化; 2. 交叉编译器 arm-linux-gnueabi 和 arm-linux-gnueabihf 的区别; 3. https://blog.csdn. ...

  6. NEON简介【转】

    转自:http://blog.csdn.net/fengbingchun/article/details/38020265 版权声明:本文为博主原创文章,未经博主允许不得转载. “ARM Advanc ...

  7. ARM NEON 编程系列2 - 基本指令集

    ARM NEON 编程系列2 - 基本指令集 前言 本系列博文用于介绍ARM CPU下NEON指令优化. 博文github地址:github 相关代码github地址:github NEON指令集 主 ...

  8. ARM NEON编程系列1-导论

    ARM NEON 编程系列1 - 导论 前言 本系列博文用于介绍ARM CPU下NEON指令优化. 博文github地址:github 相关代码github地址:github NEON历史 ARM处理 ...

  9. NEON简单介绍

    个128位四字寄存器Q0-Q15,32个64位双字寄存器D0-D31,两个寄存器是重叠的,在使用的时候须要特别注意,不小心就会被覆盖掉. NEON的数据类型:无符号整数.有符号整数.未指定类型的整数. ...

随机推荐

  1. LuoguP2575 高手过招(博弈论)

    空格数变吗?不变呀 阶梯博弈阶梯数变吗?不变呀 那这不就阶梯博弈,每行一栋楼,爬完\(mex\)就可以了吗? #include <iostream> #include <cstdio ...

  2. 移动端实现HTML5 mp3录音踩坑指南:系统播放音量变小、一些机型录音断断续续 之 MediaRecorder和AudioWorklet的终极对决

    目录 H5录音见坑填坑 采用MediaRecorder采集音频 音频格式:WebM和PCM 从WebM封装容器中提取PCM数据 录音的兼容性 困扰已久的H5录音时系统播放音量变小的问题 H5录音见坑填 ...

  3. 树莓派4B无屏幕连接Wi-Fi/启用ssh/创建用户

    前边总得说点什么 最近每次在Win10上写代码需要启动Redis,残血Redis For Windows有卡死系统的bug.由于主机内存不大够用(已经扩到顶了),开虚拟机运行Redis更别提了..想起 ...

  4. Taurus.MVC 微服务框架 入门开发教程:项目集成:5、统一的日志管理。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...

  5. mysql存储过程的创建和调用

    描述:存储过程就是具有名字的一段代码,用来完成一个特定的功能.创建的存储过程保存在数据库的数据词典中. --创建一个名为GreetWorld的存储过程,拼接两个值 CREATE PROCEDURE G ...

  6. 从C过渡到C++(1)——GNU/Linux

    从C过渡到C++(1)--GNU/Linux 目录 从C过渡到C++(1)--GNU/Linux 大名鼎鼎的GNU/Linux GNU GNU的组成 一点补充 MinGW 运行时库 额外的内容 Min ...

  7. 【java】学习路线12-内部类的使用事项

    //内部类只能在其外部类当中使用//局部内部类:定义在方法里面//如果内部类和外部类有重名,就近原则在内部类中优先访问内部类.//如果想访问宿主类的同名成员,使用OuterClass.this.xxx ...

  8. Filebeat Nginx Module 自定义字段

    Filebeat Nginx Module 自定义字段 一.修改/usr/local/nginx/conf/nginx.conf中 log_format access '$remote_addr - ...

  9. 学习ASP.NET Core Blazor编程系列二——第一个Blazor应用程序(下)

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  10. 来点基础的练习题吧,看见CSDN这类基础的代码不多

    来点基础的练习题吧,看见CSDN这类基础的代码不多 //正三角形 void ex03(){ int i,k=0, rows, space; printf("请输入三角形的层次:") ...