在公司产品的应用中图像固定是352×288的大小,但可以根据网络的状况调整码流速率。我先用的300k的码流进行测试,统计的时间花费如下:
I帧数: 2 P帧数: 96 无编码帧数: 2
dectime(ms) =8737.00, fps = 11.45, length(bytes) = 1210522
time_decode:3619.00 time_out:5107.00
time_decode 是指解码I帧,P帧等花的时间,time_out是指将解码后的yuv420转换成rgb565花的时间。可以看到,格式转换所花的时间居然比真正解码花的时间还多。狂汗,干脆别叫解码器,该叫格式转换器得了。这个转换部分太刺眼了,首先解决掉它。
先简化相应的c代码。xvid采用的是查表法,这已经是最快的算法,没什么好改的。我只是根据我们产品的特殊情况减少了一些参数。
#define MK_RGB565(R,G,B) \
((MAX(0,MIN(255, R)) << 8) & 0xf800) | \
((MAX(0,MIN(255, G)) << 3) & 0x07e0) | \
((MAX(0,MIN(255, B)) >> 3) & 0x001f)
void
yv12_to_rgb565_c(uint8_t * x_ptr,uint8_t * y_ptr,int32_t * t_ptr)
{
int16_t x, y;
uint8_t * u_ptr = y_ptr + 176672;
uint8_t * v_ptr = u_ptr + 49984;
for (y = 0; y < 288; y+=2)
{
int16_t r[2], g[2], b[2];
r[0] = r[1] = g[0] = g[1] = b[0] = b[1] = 0;
for (x = 0; x < 352; x+=2)
{
int rgb_y;
int b_u0 = B_U_tab[ u_ptr[0] ]; //1
int g_uv0 = G_U_tab[ u_ptr[0] ] + G_V_tab[ v_ptr[0] ]; //2
int r_v0 = R_V_tab[ v_ptr[0] ]; //2
rgb_y = RGB_Y_tab[ y_ptr[0] ]; //1
b[0] = (b[0] & 0x7) + ((rgb_y + b_u0) >> SCALEBITS_OUT); //2
g[0] = (g[0] & 0x7) + ((rgb_y - g_uv0) >> SCALEBITS_OUT); //2
r[0] = (r[0] & 0x7) + ((rgb_y + r_v0) >> SCALEBITS_OUT); //2
*(uint16_t *) x_ptr = MK_RGB565(r[0], g[0], b[0]); //20-100
rgb_y = RGB_Y_tab[ y_ptr[1] ];
b[0] = (b[0] & 0x7) + ((rgb_y + b_u0) >> SCALEBITS_OUT);
g[0] = (g[0] & 0x7) + ((rgb_y - g_uv0) >> SCALEBITS_OUT);
r[0] = (r[0] & 0x7) + ((rgb_y + r_v0) >> SCALEBITS_OUT);
b[0] = ((rgb_y + b_u0) >> SCALEBITS_OUT);
g[0] = ((rgb_y - g_uv0) >> SCALEBITS_OUT);
r[0] = ((rgb_y + r_v0) >> SCALEBITS_OUT);
*(uint16_t *) (x_ptr+2) = MK_RGB565(r[0], g[0], b[0]);
rgb_y = RGB_Y_tab[ y_ptr[480] ];
b[1] = (b[1] & 0x7) + ((rgb_y + b_u0) >> SCALEBITS_OUT);
g[1] = (g[1] & 0x7) + ((rgb_y - g_uv0) >> SCALEBITS_OUT);
r[1] = (r[1] & 0x7) + ((rgb_y + r_v0) >> SCALEBITS_OUT);
*(uint16_t *) (x_ptr+704) = MK_RGB565(r[1], g[1], b[1]);
rgb_y = RGB_Y_tab[ y_ptr[481] ];
b[1] = (b[1] & 0x7) + ((rgb_y + b_u0) >> SCALEBITS_OUT);
g[1] = (g[1] & 0x7) + ((rgb_y - g_uv0) >> SCALEBITS_OUT);
r[1] = (r[1] & 0x7) + ((rgb_y + r_v0) >> SCALEBITS_OUT);
*(uint16_t *) (x_ptr+706)= MK_RGB565(r[1], g[1], b[1]);
x_ptr += 4;
y_ptr += 2;
u_ptr += 1;
v_ptr += 1;
}
x_ptr += 704;
y_ptr += 608;
u_ptr += 64;
v_ptr += 64;
}
}
这里主要简化了函数的参数,原来的函数有10个输入参数,
void yv12_to_rgb565_c(uint8_t * x_ptr,int x_stride,uint8_t * y_src,uint8_t * v_src,uint8_t * u_src, int y_stride, int uv_stride,int width,int height,int vflip); 其中像x_stride参数在我的应用中是固定的,直接省略掉,通过分配连续的缓冲空间将 y_src,v_src,u_src三个数据指针简化成一个。这样做的目的是使函数对应的汇编代码简单。
从汇编级优化可真是头痛。还好ADS编译器调试时可以显示c代码相应的汇编代码。直接copy一份出来在它的基础上修改就简单多了。
写汇编代码最头痛的是寄存器不够用。arm虽然有37个寄存器,但能使用的很少(r0-r12,r14) 。绞尽脑汁,压榨每一寸空间。x循环内执行次数最多,所以里面的临时变量都要用寄存器存。r[2], g[2], b[2]都是16位的,只需3个寄存器就可以存下。不常用的可以存入堆栈。
r0查找表地址 r10 y数据 r10 u数据 r11 v数据 r6 bu r7 gu r8 gv r9 RGB_Y
r3 b[0] r4 g[0] r5 r[0]
上面已经将寄存器用完了,其它的变量如x, y 等都只能用堆栈,使用前出栈,使用后压栈。
上面c代码的后面注释了每步操作所花的指令条数。很明显MK_RGB565花的指令太多了。更进一步发现,MAX(0,MIN(255, R)) 最耗时间。
下面是编译器给的MAX(0,MIN(255, R)) 汇编代码
30014e24 [0xe1dd60f8] * ldrsh r6,[r13,#8] ;从内存中载入数据很慢
30014e28 [0xe35600ff] cmp r6,#0xff
30014e2c [0xca000004] bgt 0x30014e44 ; (yv12_to_rgb565_c + 0xc8) ;跳转
30014e30 [0xe1dd60f8] ldrsh r6,[r13,#8]
30014e34 [0xe3560000] cmp r6,#0
30014e38 [0xaa000001] bge 0x30014e44 ; (yv12_to_rgb565_c + 0xc8) ;跳转
30014e3c [0xe3a06000] mov r6,#0
30014e40 [0xea000005] b 0x30014e5c ; (yv12_to_rgb565_c + 0xe0)
跳转时会清除流水线上后继的5条指令。而编译器给的代码中很不恰当的使用了很多跳转指令,严重的损害了速度。
改进跳转的一种代码如下
cmp r12,#0xff
bgt point2
cmp r12, #0x0
blt point1
point11
.............
............
point1
mov r12, #0x0
b point11
point2
mov r12, #0xff
b point11
统计数据发现,超出0-255范围而发生跳转的情况不到1/10 。在这个代码中 如果不超过范围,执行 MAX(0,MIN(255, R)) 只需要 4条指令 ,如果超出范围 则增加到 10-15条。
后来再看了cmp指令的用法,将代码改进如下:
cmp r11,#0xff
movgt r11,#0xff
cmp r11,#0
movlt r11,#0
这样不管是否超范围,执行MAX(0,MIN(255, R)) 都只需 4条指令。
尽管改进了代码,计算一个点仍需要执行三次MAX(0,MIN(255, R)) ,也就是12条指令,仍然占据了超过一半的指令。也许有其它办法解决,比如 如果能事先限制输入的数不超过0-255范围,也就不用计算MAX(0,MIN(255, R)) 了, 或者 不管范围直接计算,检查计算出的结果不正确时才检查输入的数的范围。我想了好久都没想出好的办法。
还有,开始时我想避免使用比较指令而将一个32位数限制在0-255范围,论坛上The One老大给了一个很好的算法,但是与上面后来的方法比起来,使用的指令更多,有点得不偿失。怪我学艺不精提出了这个刁钻的问题,还是很感谢老大的帮助。
还要注意指令的顺序问题。
比如:
ldr r1,[r0] ldr r1,[r0]
ldr r2,[r0,#1] add r4,r4,r1
ldr r3,[r0,#2] ldr r2,[r0,#1]
add r4,r4,r1 add r4,r4,r2
add r4,r4,r2 ldr r3,[r0,#2]
and r4,r4,r3 and r4,r4,r3
同样的功能,左边比右边要好。因为CPU的流水线有好几级,也要充分利用。
使用所有能想到的方法后,结果是明显的。采用优化后的代码后,测试时间如下:
dectime(ms) =6048.00, fps = 16.53, length(bytes) = 1210522
time_decode:3617.00 time_out:2417.00
time_out的时间缩小了一半还多。
现在200M的主频下300k的码流达到了16.5帧/秒 ,改成300M的CPU后应该能达到25帧/秒。
不过头头说300k的码流太简单,要用1M的码流。唉,只有继续优化其它部分。其它部分还有 idct, vlc解码,还有数据的搬运部分都有比较大的优化空间。
posted on 2006-06-10 02:56 zgf的blog 阅读(3385)
评论(7) 编辑 收藏