使用NEON指令加速RGB888和RGB565的相互转换
最近在做一个项目需要将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;
}
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指令加速RGB888和RGB565的相互转换的更多相关文章
- (二十三)ARM平台NEON指令的编译和优化
		
ARM平台NEON指令的编译和优化 本文介绍了ARM平台基于ARM v7-A架构的ARM Cortex-A系列处理器(Cortex-A5, Cortex-A7,Cortex-A8, Cortex-A9 ...
 - BMP RGB888转RGB565 +上下翻转+缩放
		
典型的BMP图像文件由四部分组成: (1) 位图头文件数据结构,它包含BMP图像文件的类型.文件大小和位图起始位置等信息: typedef struct tagBITMAPFILEHEADER { ...
 - neon指令,注意事项
		
1. vbic_s8 (int8x8_t a, int8x8_t b) 是 ~(ai & bi),一开始理解成 (~ai )& bi 导致出错 2.uint8x8_t vqshrn ...
 - linux kernel态下使用NEON对算法进行加速
		
ARM处理器从cortex系列开始集成NEON处理单元,该单元可以简单理解为协处理器,专门为矩阵运算等算法设计,特别适用于图像.视频.音频处理等场景,应用也很广泛. 本文先对NEON处理单元进行简要介 ...
 - 【linux】ARM板子开启浮点和neon加速
		
参考 1. ARM平台NEON指令的编译和优化; 2. 交叉编译器 arm-linux-gnueabi 和 arm-linux-gnueabihf 的区别; 3. https://blog.csdn. ...
 - NEON简介【转】
		
转自:http://blog.csdn.net/fengbingchun/article/details/38020265 版权声明:本文为博主原创文章,未经博主允许不得转载. “ARM Advanc ...
 - ARM NEON 编程系列2 - 基本指令集
		
ARM NEON 编程系列2 - 基本指令集 前言 本系列博文用于介绍ARM CPU下NEON指令优化. 博文github地址:github 相关代码github地址:github NEON指令集 主 ...
 - ARM NEON编程系列1-导论
		
ARM NEON 编程系列1 - 导论 前言 本系列博文用于介绍ARM CPU下NEON指令优化. 博文github地址:github 相关代码github地址:github NEON历史 ARM处理 ...
 - NEON简单介绍
		
个128位四字寄存器Q0-Q15,32个64位双字寄存器D0-D31,两个寄存器是重叠的,在使用的时候须要特别注意,不小心就会被覆盖掉. NEON的数据类型:无符号整数.有符号整数.未指定类型的整数. ...
 
随机推荐
- LuoguP1922 女仆咖啡厅桌游吧 (树形动态规划)
			
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> ...
 - 【原创】JDK 9-17新功能30分钟详解-语法篇-var
			
JDK 9-17新功能30分钟详解-语法篇-var 介绍 JDK 10 JDK 10新增了新的关键字--var,官方文档说作用是: Enhance the Java Language to exten ...
 - 十周周末总结    MySQL的介绍与使用
			
python 十周周末总结 MySQL的介绍与使用 MySQL字符编码与配置文件 查看数据库的基本信息(用户,字符编码) /s windos下MySQL默认的配置文件 my_default.ini 修 ...
 - Android Kotlin Annotation Processer
			
Annotation Processer 注解处理器(Annotation Processer)是javac内置的注解处理工具,可以在编译时处理注解,让我们自己做相应的处理.比如生成重复度很高的代码, ...
 - D - Distinct Trio
			
D - Distinct Trio 题意:求三个数个各不相同的数目. 题解:正面考虑比较困难,可以反向思考,在总值上减去不符合的即可 #include<bits/stdc++.h> usi ...
 - 搭建docker镜像仓库(一):使用registry搭建本地镜像仓库
			
目录 一.系统环境 二.前言 三.使用registry搭建私有镜像仓库 3.1 环境介绍 3.2 k8smaster节点配置镜像仓库 3.3 k8sworker1节点配置从私有仓库上传和拉取镜像 3. ...
 - Taurus.MVC-Java 版本打包上传到Maven中央仓库(详细过程):1、JIRA账号注册
			
文章目录: Taurus.MVC-Java 版本打包上传到Maven中央仓库(详细过程):1.JIRA账号注册 Taurus.MVC-Java 版本打包上传到Maven中央仓库(详细过程):2.PGP ...
 - React Native入门  Enable live Reload
			
在开发项目时,有时一点点小修改就需要重新编译,打包,安装,效率比较低 RN 提供了一种实时重载 (Enable live Reload)的方式,来实现快速的调试开发,修改保存后会立刻载真机或模拟器中显 ...
 - python自动化测试系列教程
			
随着互联网产品更新迭代加快,Web 开发和测试的需求也越来越大.很难想象,如果阿里的双 11.京东的 618,这些庞大繁杂的系统,由工程师们一个个手动测试,将会是一个怎样费时费力.成本巨大的工程. 也 ...
 - 跟羽夏学 Ghidra ——导航
			
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...