ARM的NEON是類似於X86的SSE2的一種優化的指令集,主要就是為了實現SIMD全稱Single Instruction Multiple Data,單指令多數據流,能夠復制多個操作數,並把它們打包在大型寄存器的一組指令集。簡單來說就是處理一些算法的時候,可以並行處理,大大提高了效率。
在Android手機上大部分都是ARM架構的,我們開啟NEON后就可以使用這些指令集了,當然可以使用匯編,但是也為我們提供了封裝好的方法,總結一下數據以及函數的解析規律。
NEON 的矢量數據類型
數據類型有下列的模式,首先是不是數組的
<type><size>x<number_of_lanes>_t
1
例如
int16x4_t 代表4個16bit的數據,也就是4個short
float32x4_t 代表4個32bit的float數據
然后是數組類型
<type><size>x<number_of_lanes>x<length_of_array>_t
1
2
類似於這樣
struct int16x4x2_t{
int16x4_t val[2];
} <var_name>;
1
2
3
4
然后我發現還有一個poly的類型,好像多用在有限域的中,密碼學等方面,這個暫時不管吧,基本算法也用不到。
函數指令解釋
關於函數的指令解釋,參考這篇文章:https://blog.csdn.net/xiongtiancheng/article/details/77103810
再參考這篇文字速查:https://blog.csdn.net/billbliss/article/details/78924636
方便觀看我就直接轉載過來了。
arm neon 指令分類:
正常指令(q)
正常指令可對上述任意向量類型執行運算,並生成大小相同且類型通常與操作數向量相同的結果向量。
長指令(l)
長指令對雙字向量操作數執行運算,並生成四字向量結果。 所生成的元素通常是操作數元素寬度的兩倍,並屬於同一類型。
寬指令(w)
寬指令對一個雙字向量操作數和一個四字向量操作數執行運算。 此類指令生成四字向量結果。 所生成的元素和第一個操作數的元素是第二個操作數元素寬度的兩倍。
窄指令(n)
窄指令對四字向量操作數執行運算,並生成雙字向量結果。 所生成的元素通常是操作數元素寬度的一半。
飽和指令(q)
通過在 V 和指令助記符之間使用 Q 前綴可以指定飽和指令。
上述的指令分類區別我感覺就是操作對象與返回對象的區別了。
數據類型 x 的飽和范圍 (s 就是signed,有符號的意思,u就是unsigned,無符號的意思)
s8 –2^7 <= x < 2^7
s16 –2^15 <= x < 2^15
s32 –2^31 <= x < 2^31
s64 –2^63 <= x < 2^63
u8 0 <= x < 2^8
u16 0 <= x < 2^16
u32 0 <= x < 2^32
u64 0 <= x < 2^64
neon的寄存器:
有16個128位四字到寄存器Q0-Q15,32個64位雙子寄存器D0-D31,兩個寄存器是重疊的。
arm_neon.h 中的函數介紹
例如
int8x8_t vadd_s8 (int8x8_t __a, int8x8_t __b);
v是向量操作,可以認為就是neon函數,add 是相加,s8表示結果是s8類型(向量)
int8x16_t vaddq_s8 (int8x16_t __a, int8x16_t __b);
v是向量操作,可以認為就是neon函數,add 是相加,后面的q代表正常指令,s8表示結果是s8類型(向量)
int8x8_t vqadd_s8 (int8x8_t __a, int8x8_t __b);
v是向量操作,可以認為就是neon函數,后面的q代表飽和指令,add 是相加,s8表示結果是s8類型(向量)
int8x8_t vshr_n_s8 (int8x8_t __a, const int __b);
v是向量操作,可以認為就是neon函數,shr是右移位,n表示參數中有個基本數據類型,也就是不是向量或指針,s8表示結果是s8類型(向量)
int8_t vget_lane_s8 (int8x8_t __a, const int __b);
v是向量操作,可以認為就是neon函數,shr是右移位,lane表示操作向量中的某個元素,s8表示結果是s8類型(向量)
int8x8_t vget_high_s8 (int8x16_t __a); //ri = a(i+4);
v是向量操作,可以認為就是neon函數,get是取值,high表示取高64位,s8表示結果是s8類型(向量)
int8x8_t vget_low_s8 (int8x16_t __a); //ri = ai;
v是向量操作,可以認為就是neon函數,get是取值,low表示取低64為,s8表示結果是s8類型(向量)
綜上所述,可以總結函數的定義如下
v<noen函數前綴>q<飽和操作>ops<具體操作>tyep<指令類型 q,l,w,n>_flag<標識 n,lane,high or low>_dtype<返回值類型或參數類型>
1
arm_neon.h 支持的操作
add 加法
mul 乘法
sub 減法
mla 乘加
mls 乘減
ceq 比較,類似與 ==
cge 比較,類似與 >=
cle 比較,類似與 <=
cgt 比較,類似與 >
clt 比較,類似與 <
tst 做與運算后,判斷是否等於0 ,ri = (ai & bi != 0) ? 1…1:0…0;
abd 兩個向量相減后的絕對值,vabd -> ri = |ai - bi|;
max 求最大值,ri = ai >= bi ? ai : bi;
min 求最小值,ri = ai >= bi ? bi : ai;
shl 左移位, ri = ai << b;
shr 右移位, ri = ai >> b;
abs 求絕對值,ri = |ai|;
neg 取反,ri = -ai;
mvn 按位取反,ri = ~ai;
and 與運算,ri = ai & bi;
orr 或運算,ri = ai | bi;
eor 異或運算,ri = ai ^ bi;
cls 計算連續相同的位數
get 取值,從向量中取出一個值,所謂的向量可以認為是一個數組,給數組中的某個元素賦值
set 賦值,給向量中賦值
dup 構造一個向量,並賦上初始值,ri = a;
combine 合並操作,把兩個向量合並
mov 改變數據類型,數據范圍,比如把u8 變成u16,或者u16變成u8
zip 壓縮操作
uzp 解壓操作
ld1 加載數據,給定的buffer 指針中拷貝數據,注意是ld后面的是數字1,而不是字母l
st1 拷貝數據,將neon數據類型拷貝到指定buffer中
實例分析
這里分析一下從yuv422轉yuv420P的用法。
void yuy2_to_yuv420p(const uint8_t * const srcSlice[], const int srcStride[],
int imageWidth, int imageHeight, uint8_t * const dst[],
const int dstStride[]) {
//ffmpeg兩個AVFrame的的數據轉換
//yuy2的數據
const uint8_t *yuy2Line = (const uint8_t *) srcSlice[0];
//每行數據個數
const int yuy2Pitch = srcStride[0];
//目標數據的y指針
uint8_t *yLine = (uint8_t *) dst[0];
//目標數據每行數據量,下面同理
const int yPitch = dstStride[0];
uint8_t *uLine = (uint8_t *) dst[1];
const int uPitch = dstStride[1];
uint8_t *vLine = (uint8_t *) dst[2];
const int vPitch = dstStride[2];
const unsigned int linePairs = imageHeight / 2;
const unsigned int groups = imageWidth / 32;
const unsigned int tails = imageWidth & 31;
const unsigned int tailPairs = tails / 2;
for (unsigned int row = 0; row < linePairs; row++) {
const uint8_t *yuy2Ptr = (const uint8_t *) yuy2Line;
uint8_t *yPtr = yLine;
uint8_t *uPtr = uLine;
uint8_t *vPtr = vLine;
//每次讀取32個像素填充到數據到dst
for (unsigned int i = 0; i < groups; i++) {
//yvyu yuyv
//取16*4個字節 也就是16個yuyv數據,32個像素
//分為4個通道存儲,將yuyv分離
//val[0] y0 y2 y4 y6 y8 y10 y12 y14 y16 y18 y20 y22 y24 y26 y28 y30
//val[2] y1 y3 y5 y7 y9 y11 y13 y15 y17 y19 y21 y23 y25 y27 y29 y31
//val[1] u0....
//val[3] v0...
const uint8x16x4_t srcYUYV = vld4q_u8(yuy2Ptr);
uint8x16x2_t srcY;
//16個y
srcY.val[0] = srcYUYV.val[0];
//另外16個y
srcY.val[1] = srcYUYV.val[2];
//回推到yPtr,所有的y,兩個向量交替回推
vst2q_u8(yPtr, srcY);
//回推到uPtr,所有的u
vst1q_u8(uPtr, srcYUYV.val[1]);
//回推到uPtr,所有的v
vst1q_u8(vPtr, srcYUYV.val[3]);
yuy2Ptr += 64;
yPtr += 32;
uPtr += 16;
vPtr += 16;
}
//上面的玄循環結束之后
//變成類似 yPtr yyyy ... 0 2
// uPtr uu .. 1
// vPtr vv .. 3
//對32求余后剩余的數據也填充進去,一次兩個像素
for (unsigned int i = 0; i < tailPairs; i++) {
yPtr[0] = yuy2Ptr[0];
yPtr[1] = yuy2Ptr[2];
*(uPtr++) = yuy2Ptr[1];
*(vPtr++) = yuy2Ptr[3];
yuy2Ptr += 4;
yPtr += 2;
}
//奇數寬高的處理,奇數yuv我也不太清楚是怎么排列的,基本是沒見過奇數
//看這里的處理是再保留一個y和一個uv,舍去一個y
if (tails & 1) {
yPtr[0] = yuy2Ptr[0];
uPtr[0] = yuy2Ptr[1];
vPtr[0] = yuy2Ptr[3];
}
//接着處理第二行,一個循環處理兩行數據
yuy2Line += yuy2Pitch;
yLine += yPitch;
uLine += uPitch;
vLine += vPitch;
yuy2Ptr = (const uint8_t *) yuy2Line;
yPtr = yLine;
//處理第二行數據,奇數行保留yuv,偶數行只保留Y。。這里我指的是像素的行數,從1開始
//下面的處理和上面類似
for (unsigned int i = 0; i < groups; i++) {
const uint8x16x4_t srcYUYV = vld4q_u8(yuy2Ptr);
uint8x16x2_t srcY;
srcY.val[0] = srcYUYV.val[0];
srcY.val[1] = srcYUYV.val[2];
vst2q_u8(yPtr, srcY);
yuy2Ptr += 64;
yPtr += 32;
}
for (unsigned int i = 0; i < tailPairs; i++) {
yPtr[0] = yuy2Ptr[0];
yPtr[1] = yuy2Ptr[2];
yuy2Ptr += 4;
yPtr += 2;
}
if (tails & 1)
yPtr[0] = yuy2Ptr[0];
yuy2Line += yuy2Pitch;
yLine += yPitch;
}
//高度奇數,再處理最后一行
if (imageHeight & 1) {
const uint8_t *yuy2Ptr = (const uint8_t *) yuy2Line;
uint8_t *yPtr = yLine;
uint8_t *uPtr = uLine;
uint8_t *vPtr = vLine;
for (unsigned int i = 0; i < groups; i++) {
const uint8x16x4_t srcYUYV = vld4q_u8(yuy2Ptr);
uint8x16x2_t srcY;
srcY.val[0] = srcYUYV.val[0];
srcY.val[1] = srcYUYV.val[2];
vst2q_u8(yPtr, srcY);
vst1q_u8(uPtr, srcYUYV.val[1]);
vst1q_u8(vPtr, srcYUYV.val[3]);
yuy2Ptr += 64;
yPtr += 32;
uPtr += 16;
vPtr += 16;
}
for (unsigned int i = 0; i < tailPairs; i++) {
yPtr[0] = yuy2Ptr[0];
yPtr[1] = yuy2Ptr[2];
*(uPtr++) = yuy2Ptr[1];
*(vPtr++) = yuy2Ptr[3];
yuy2Ptr += 4;
yPtr += 2;
}
if (tails & 1) {
yPtr[0] = yuy2Ptr[0];
uPtr[0] = yuy2Ptr[1];
vPtr[0] = yuy2Ptr[3];
}
}
}
————————————————
版權聲明:本文為CSDN博主「JabamiLight」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/a568478312/article/details/80937797