rgba 转 yv420p x86 极速转换,1080p 800 fps

本文介绍了一种使用x86汇编语言优化RGB到YV420颜色空间转换的方法,通过避免内存频繁交互和利用汇编指令简化计算流程,实现了630-900fps的高性能转换速度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    最近项目中涉及到颜色控件转换,开始使用ffmpeg的 scale 功能,感觉方便快捷。 不过 测试发现 1080p图像在 i5 2400 上只有150-160fps。具体到实时24fps的视频中,颜色转换,就消耗了一个核的1/6的性能(i54核,windows统计是4%,)。从这个数据预估仅仅是颜色转换 这个使用率太高,应该有优化空间。从网上查询研究的结果看,有yv420转rgb的,能到600fps(http://blog.youkuaiyun.com/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次加法操作,所以能比查表节省指令。

第一步:公式转化为 整数计算:

      

y = (unsigned char)( ( 66 * r + 129 * g +  25 * b + 128) >> 8) + 16  ;            
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

     

在Java中将RGBA(红、绿、蓝、透明度)颜色空间转换为YUV420P(逐行扫描,主要用于图像压缩)通常涉及到色彩模型的数学变换。由于RGB和YUV都是常见的色彩空间,但它们之间的映射不是线性的,所以需要一些公式来完成这个转换。 这里提供一个简化的步骤概述: 1. 分离Alpha通道(如果存在):如果RGBA有Alpha通道,首先提取出来并单独处理,因为YUV不包含透明度信息。 ```java float alpha = rgba.getAlpha(); Color colorWithoutAlpha = new Color(rgba.getRed(), rgba.getGreen(), rgba.getBlue()); ``` 2. 将RGB转换为亮度Y和色差UV:对于每个像素(R, G, B),分别计算亮度Y和色差分量U/V。常见的公式是: - Y = 0.299 * R + 0.587 * G + 0.114 * B - V = 0.596 * (B - Y) - 0.1687 * (G - Y) - U = 0.439 * (R - Y) - 0.3114 * (B - Y) 3. YUV420P格式的处理:YUV420P采用逐行扫描的方式,其中U和V数据通常是每四个Y样本中的一对。因此,你需要将UV数据进行适当的插值或打包成对应的4x4网格(例如,对于一个宽高比为16:9的图像,宽度上每8个Y样本会有一个UV块)。 4. 结果存储:将转换后的Y、U、V数组分别存储到YUV420P的数据结构中,比如`byte[]`数组。 以下是伪代码示例: ```java // 假设rgba为ARGBInt数组,width和height为图像尺寸 int[] yuv = new int[width * height]; int yIndex = 0; for (int i = 0; i < height; i++) { for (int j = 0; j < width / 2; j++) { // 只处理半宽,因为YUV420P是奇数像素 int r = rgba[j * 4]; int g = rgba[j * 4 + 1]; int b = rgba[j * 4 + 2]; float y = calculateY(r, g, b); float u = calculateU(r, g, b); float v = calculateV(r, g, b); yuv[yIndex++] = (int) Math.round(y); // Y if ((i + j) % 2 == 0) { // 每两个Y样本有一个UV块 // 对UV进行插值或其他处理,然后存储到yuv数组对应位置 } } } ``` 请注意,以上代码是一个简化版本,实际的颜色转换可能会更复杂,包括考虑浮点精度和色彩空间间的校准系数等。如果你打算在生产环境中使用,建议参考相关的图像处理库,如Apache Commons Imaging或JTransforms。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值