最近项目中涉及到颜色控件转换,开始使用ffmpeg的 scale 功能,感觉方便快捷。 不过 测试发现 1080p图像在 i5 2400 上只有150-160fps。具体到实时24fps的视频中,颜色转换,就消耗了一个核的1/6的性能(i54核,windows统计是4%,)。从这个数据预估仅仅是颜色转换 这个使用率太高,应该有优化空间。从网上查询研究的结果看,有yv420转rgb的,能到600fps(http://blog.csdn.net/mikedai/article/details/79073530)。虽然过程相反,从复杂度看应该差不多。 实际项目中会多路并行使用,所以是cpu越低越好。考虑优化可以x86汇编优化,和gpu加速两种。x86相对容易,gpu复杂但是能消耗更少的cpu。不过从该文章的测试结果看,gpu方案综合 并不比cpu高多少。在600 fps下,cpu使用率已经极少到可以忽略。所以以下是基于x86汇编的方案,gpu方案目前没有时间尝试了,。
根据目前经验,转换优化:
1、可以从数学计算转化为查表方式,能提高不少性能。
2、c++调用sse指令函数。
3、直接汇编。
直接上结果吧(x64平台,汇编需要单独成文件):
方法1、发现效果并不好,大约和ffmpeg的方法持平。
方法2、能提高到200+fps。
通过对应汇编代码发现,sse指令函数,每一条都额外消耗了3-5条汇编指令。所以直接使用汇编预计可以提高3-5倍。
方法3、汇编提高到630-900 fps,计算机环境有波动 所以每次测试结果有些差别。(ps 经过查看原始公式,应该还有优化空间,特别是 UV 的转换。)
未使用查表方式:因为多次查表,涉及到内存多次访问,效率上预计有很大影响。而直接寄存器计算,可以避免内存频繁交互。同时,汇编指令(_mm_maddubs_epi16 / pmaddubsw)能一次完成 2乘法和1次加法操作,所以能比查表节省指令。
第一步:公式转化为 整数计算:
u = (unsigned char)( ( -38 * r - 74 * g + 112 * b + 128) >> 8) + 128 ;
v = (unsigned char)( ( 112 * r - 94 * g - 18 * b + 128) >> 8) + 128 ;
第二步:将公式再转化为可直接乘法和加法完成的方式:
y=(unsigned char)( 66 * r + 129 * g + 25 * b + 16*255)>>8 ;
u=(unsigned char)(-38*r - 74*g + 112*b + 127*255)>>8;
v=(unsigned char)(112*r - 94*g - 18*b + 127*255)>>8;
这样变换之后,一次y或者u或者v转换只需要 括号内计算一次乘法加操作 和一次水平相加,加上一次右移操作3条指令完成。
一次pmaddubsw 指令执行 66 * r + 129 * g,和25 * b + 16*255,需要将这两个结果再相加,所以需要一次水平相加操作(_mm_hadd_epi16)
这样一次计算占据4字节xmm寄存器,xmm可以计算16字节,所以一次可以同时进行4像素rgba计算。
c 调用接口(图像宽高为16字节整数倍图像):
void rgba2yv420_asm(unsigned char *pRgb, unsigned char *pYv420 , int nWidth, int nHeight);
汇编.asm文件:
.data
ycof db 66, 127, 25, 16, 66, 127, 25, 16, 66, 127, 25, 16, 66, 127, 25, 16
ucof db -19, -37, 56, 64, -19, -37, 56, 64, -19, -37, 56, 64, -19, -37, 56, 64
vcof db 56, -47, -9, 64, 56, -47, -9, 64, 56, -47, -9, 64, 56, -47, -9, 64
uv_stuff_mask_low db 0, 1, 2, 3, 8, 9, 10, 11, -1,-1,-1,-1, -1,-1,-1,-1
uv_stuff_mask_high db -1,-1,-1,-1, -1,-1,-1,-1, 0, 1, 2, 3, 8, 9, 10, 11
; nStep db 8
.code
rgba2yv420_asm PROC
mov rax, -1 ;return error
;step
cmp r8, 1920
je step128
cmp r8, 1080
je step8
cmp r8, 720
je step16
jp finish ;return error
step128:
mov rsi, 256
jp start
step16:
mov rsi, 64
jp start
step8:
mov rsi, 32
start:
push r12
push r13
push r14
push r15
;init regs
pxor xmm0, xmm0
pxor xmm1, xmm1
pxor xmm2, xmm2 ;mask
pxor xmm3, xmm3 ;mask
pxor xmm4, xmm4
pxor xmm5, xmm5 ;shift
pxor xmm6, xmm6 ;shift
pxor xmm7, xmm7
pxor xmm8, xmm8
pxor xmm9, xmm9
pxor xmm10, xmm10
movdqa xmm13, xmmword ptr [ycof];
movdqa xmm14, xmmword ptr [ucof];
movdqa xmm15, xmmword ptr [vcof];
mov r10, rcx ; rgba input
mov r13, rdx ; yv420 output
mov eax, 8
mov ebx, 7
movd xmm5, eax ; >>8
movd xmm6, ebx ; >>7
movdqa xmm2, xmmword ptr [uv_stuff_mask_low] ; mask low
movdqa xmm3, xmmword ptr [uv_stuff_mask_high] ; mask high
mov r11, r8 ; w
mov r14, r13 ;output u
imul r11, r9 ; h
add r14, r11 ;U OUT = Y + w*h
mov r15, r14 ;output v
shr r11, 2
add r15, r11 ;V OUT = U + w*h/4
;input params
xor rax, rax
mov r11, r9 ;h, R9
hloop:
xor rdi, rdi ;w = 0, R8
prefetch_Y:
prefetchnta [rsi][r10] ;prefetch 128 pixels
xor r12, r12
wloop:
movdqa xmm0, [r10+r12]
movdqa xmm1, [r10+r12+16]
pmaddubsw xmm0, xmm13 ;_mm_maddubs_epi16
pmaddubsw xmm1, xmm13 ;_mm_maddubs_epi16
phaddw xmm0, xmm1 ;_mm_hadd_epi16
PSRAW xmm0, xmm5 ;_mm_sra_epi16 >>8
PACKSSWB xmm0, xmm0 ;_mm_packs_epi16
;PACKUSWB xmm0, xmm0 ;_mm_packs_epi16
movq qword ptr [r13+rdi], xmm0 ;output low 64 bit
add rdi, 8 ; w++
cmp rax, 1
je Endof_uv
; UV LOOPS
movdqa xmm0, [r10+r12] ; reload 16 bytes 4 pixel
movdqa xmm1, [r10+r12+16] ; reload 16 bytes
pshufb xmm0, xmm2 ;_mm_shuffle_epi8 skip 1 pixel 4->2 pixel
pshufb xmm1, xmm3 ;_mm_shuffle_epi8 ;
paddw xmm0, xmm1 ;_mm_add_epi16
movdqa xmm1, xmm0
;u
pmaddubsw xmm0, xmm14 ;_mm_maddubs_epi16
phaddw xmm0, xmm7 ;_mm_hadd_epi16 0
PSRAW xmm0, xmm6 ;_mm_sra_epi16 >>7
PACKUSWB xmm0, xmm0 ;_mm_packs_epi16
;v
pmaddubsw xmm1, xmm15 ;_mm_maddubs_epi16
phaddw xmm1, xmm7 ;_mm_hadd_epi16 0
PSRAW xmm1, xmm6 ;_mm_sra_epi16 >>7
PACKUSWB xmm1, xmm1 ;_mm_packs_epi16
movd dword ptr [r14], xmm0 ;output low 32 bit U
movd dword ptr [r15], xmm1 ;output low 32 bit V
add r14, 4 ;U += 4
add r15, 4 ;V += 4
Endof_uv:
add r12, 32
cmp r12, rsi
jne wloop
add r10, rsi
cmp rdi, r8
jne prefetch_Y
xor rax, 1
add r13, r8
dec r11 ; h++
jne hloop
xor rax, rax ;return 0
pop r15
pop r14
pop r13
pop r12
emms ;reset float register
finish:
ret
rgba2yv420_asm ENDP
END