【问题标题】:How to parallelize this for loop for rapidly converting YUV422 to RGB888?如何并行化此 for 循环以快速将 YUV422 转换为 RGB888?
【发布时间】:2015-04-16 15:18:36
【问题描述】:

我正在使用 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

    }

'dst' 然后被压缩为 jpeg 并通过 TCP 发送,'vid_frame' 被保存到磁盘。

与目前的 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 RAM
  • 编码器设置:
    • 编解码器:AV_CODEC_ID_MPEG2VIDEO
    • 比特率:4000000
    • time_base: (AVRational){1, 20}
    • pix_fmt: AV_PIX_FMT_YUV420P
    • gop: 10
    • max_b_frames: 1

【问题讨论】:

  • 如果您能负担更多的带宽/内存来使用 RGBA8888 而不是 RGB888,那么它会容易得多。
  • 也许 libswscale 可以为您快速完成...

标签: c++ multithreading video ffmpeg v4l2


【解决方案1】:

如果您只关心 fps 而不是每帧毫秒数(延迟),那么另一种选择是每帧单独线程。

线程不是提高速度的唯一选择。您还可以执行整数运算,而不是浮点数。 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];

仅举几个选项。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 即使一个块也会占用 100% 的 CPU 内核,所以假设我跨 100 个线程构建 100 个块,它真的会有帮助吗?
      • 我会在您的系统中创建两倍于 CPU 的线程数。并且不要为每个新图像帧创建新线程,因为这需要时间。使用线程池。
      【解决方案4】:
      • 不要混合整数和浮点运算!

        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

      【讨论】:

        【解决方案5】:

        这样的东西行不通吗?

        #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 部分。

        【讨论】:

          【解决方案6】:

          通常只使用整数变量来执行像素格式的转换。 它允许防止浮点和整数变量之间的转换。 它还允许更有效地使用现代 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 优化的代码。

          【讨论】:

            猜你喜欢
            • 2023-04-01
            • 2021-02-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-12-09
            • 2020-05-20
            • 1970-01-01
            • 2015-01-22
            相关资源
            最近更新 更多