如何并行化这个for循环以快速转换YUV422到RGB888?

时间:2015-03-30 12:47:58

标签: c++ multithreading video ffmpeg v4l2

我正在使用v4l2 api从Microsoft Lifecam获取图像,然后通过TCP将这些图像传输到远程计算机。我也使用ffmpeg API将视频帧编码为MPEG2VIDEO。这些录制的视频播放速度太快,这可能是因为没有捕获足够的帧并且由于FPS设置不正确。

以下是将YUV422源转换为RGB888图像的代码。这段代码片段是我代码中的瓶颈,因为它需要将近100到150毫秒才能执行,这意味着我无法以1280 x 720分辨率记录超过6 - 10 FPS。 CPU使用率也是100%。

for (int line = 0; line < image_height; line++) {
    for (int column = 0; column < image_width; column++) {
        *dst++ = CLAMP((double)*py + 1.402*((double)*pv - 128.0));                                                  // R - first byte           
        *dst++ = CLAMP((double)*py - 0.344*((double)*pu - 128.0) - 0.714*((double)*pv - 128.0));    // G - next byte
        *dst++ = CLAMP((double)*py + 1.772*((double)*pu - 128.0));                                                            // B - next byte

        vid_frame->data[0][line * frame->linesize[0] + column] = *py; 

        // increment py, pu, pv here

    }

&#39; DST&#39;然后压缩为jpeg并通过TCP和&#39; vid_frame&#39;保存到磁盘。

如何使这段代码片段更快,以便在目前的5-6 FPS下,以1280x720分辨率获得至少30 FPS?

我尝试使用p_thread在三个线程之间并行化for循环,处理每个线程中三分之一的行。

for (int line = 0; line < image_height/3; line++) // thread 1
for (int line = image_height/3; line < 2*image_height/3; line++) // thread 2
for (int line = 2*image_height/3; line < image_height; line++) // thread 3

这让我每帧只有20-30毫秒的微小改进。 并行化这种循环的最佳方法是什么?我可以使用GPU计算或类似OpenMP吗?说吐出大约100个线程进行计算?

与Microsoft USB Lifecam相比,笔记本电脑摄像头的帧速率也更高。

以下是其他细节:

  • Ubuntu 12.04,ffmpeg 2.6
  • AMG-A8四核处理器,内存为6GB
  • 编码器设置:
    • 编解码器:AV_CODEC_ID_MPEG2VIDEO
    • 比特率:4000000
    • time_base:(AVRational){1,20}
    • pix_fmt:AV_PIX_FMT_YUV420P
    • gop:10
    • max_b_frames:1

6 个答案:

答案 0 :(得分:1)

如果您关心的只是fps而不是每帧ms(延迟),则另一个选项是每帧一个单独的线程。

线程不是提高速度的唯一选择。您也可以执行整数运算而不是浮点运算。 SIMD是一种选择。使用像sws_scale这样的现有库可能会为您提供最佳性能。

请确保您正在编译-O3(或-Os)。

确保禁用调试符号。

在循环外移动重复操作,例如

// compiler cant optimize this because another thread could change frame->linesize[0]
    int row = line * frame->linesize[0]; 
    for (int column = 0; column < image_width; column++) {
            ...
            vid_frame->data[0][row + column] = *py; 

您可以预先计算表,因此循环中没有数学运算:

init() {
for(int py = 0; py <= 255 ; ++py)
for(int pv = 0; pv <= 255 ; ++pv)
    ytable[pv][py] =  CLAMP(pv + 1.402*(py - 128.0)); 
}    

for (int column = 0; column < image_width; column++) {
        *dst++ = ytable[*pv][*py];

仅举几个选项。

答案 1 :(得分:1)

我认为除非你想重新发明痛苦的轮子,否则使用预先存在的选项(ffmpeg&#39; libswscale或ffmpeg&#fscmpeg&#39的缩放过滤器,gstreamer&#39;缩放插件等)是一个更好的选择

但是如果你想因任何原因重新发明轮子,请显示你使用的代码。例如,线程启动很昂贵,因此您需要在测量循环时间之前创建线程,并在帧到帧之间重用线程。更好的是帧线程,但这增加了延迟。这通常没问题,但取决于您的使用案例。更重要的是,不要编写C代码,学习编写x86程序集(simd),所有前面提到的库都使用simd进行此类转换,并且它会给你3-4倍的加速(因为它允许你做4-8个像素而不是每次迭代1次。

答案 2 :(得分:0)

您可以构建x行块并在单独的线程中转换每个块

答案 3 :(得分:0)

  • 不要混用整数和浮点运算!

    char x;
    char y=((double)x*1.5); /* ouch casting double<->int is slow! */
    char z=(x*3)>>1;        /* fixed point arithmetic rulez */
    
  • 使用SIMD(尽管如果输入和输出数据都正确对齐,这会更容易......例如,使用RGB8888作为输出)

  • 使用openMP

一种不需要任何处理编码的替代方案,只需使用一个框架来完成整个处理,该框架在整个流水线中进行适当的时间戳(从图像采集时开始),并且希望能够优化以处理大数据。例如gstreamer

答案 4 :(得分:0)

这样的事情不起作用吗?

#pragma omp parallel for
for (int line = 0; line < image_height; line++) {
    for (int column = 0; column < image_width; column++) {
        dst[ ( image_width*line + column )*3    ] = CLAMP((double)*py + 1.402*((double)*pv - 128.0));                                                  // R - first byte           
        dst[ ( image_width*line + column )*3 + 1] = CLAMP((double)*py - 0.344*((double)*pu - 128.0) - 0.714*((double)*pv - 128.0));    // G - next byte
        dst[ ( image_width*line + column )*3 + 2] = CLAMP((double)*py + 1.772*((double)*pu - 128.0));                                                            // B - next byte

        vid_frame->data[0][line * frame->linesize[0] + column] = *py; 

        // increment py, pu, pv here

    }

当然,您还必须相应地处理增量py,py,pv部分。

答案 5 :(得分:0)

通常仅使用整数变量来执行像素格式的变换。 它允许防止浮点和整数变量之间的转换。 此外,它允许更有效地使用现代CPU的SIMD扩展。 例如,这是转换YUV到BGR的代码:

const int Y_ADJUST = 16; 
const int UV_ADJUST = 128;
const int YUV_TO_BGR_AVERAGING_SHIFT = 13;
const int YUV_TO_BGR_ROUND_TERM = 1 << (YUV_TO_BGR_AVERAGING_SHIFT - 1); 
const int Y_TO_RGB_WEIGHT = int(1.164*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);
const int U_TO_BLUE_WEIGHT = int(2.018*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);
const int U_TO_GREEN_WEIGHT = -int(0.391*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);
const int V_TO_GREEN_WEIGHT = -int(0.813*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);
const int V_TO_RED_WEIGHT = int(1.596*(1 << YUV_TO_BGR_AVERAGING_SHIFT) + 0.5);

inline int RestrictRange(int value, int min = 0, int max = 255)
{
    return value < min ? min : (value > max ?  max : value);
}

inline int YuvToBlue(int y, int u)
{
    return RestrictRange((Y_TO_RGB_WEIGHT*(y - Y_ADJUST) + 
        U_TO_BLUE_WEIGHT*(u - UV_ADJUST) + 
        YUV_TO_BGR_ROUND_TERM) >> YUV_TO_BGR_AVERAGING_SHIFT);
}

inline int YuvToGreen(int y, int u, int v)
{
    return RestrictRange((Y_TO_RGB_WEIGHT*(y - Y_ADJUST) + 
        U_TO_GREEN_WEIGHT*(u - UV_ADJUST) + 
        V_TO_GREEN_WEIGHT*(v - UV_ADJUST) + 
        YUV_TO_BGR_ROUND_TERM) >> YUV_TO_BGR_AVERAGING_SHIFT);
}

inline int YuvToRed(int y, int v)
{
    return RestrictRange((Y_TO_RGB_WEIGHT*(y - Y_ADJUST) + 
        V_TO_RED_WEIGHT*(v - UV_ADJUST) + 
        YUV_TO_BGR_ROUND_TERM) >> YUV_TO_BGR_AVERAGING_SHIFT);
}

此处的代码(http://simd.sourceforge.net/)。此外,还有针对不同SIMD优化的代码。