2

我有一个 Delphi 6 应用程序,它以每秒 25 帧的速度实时将位图发送到 DirectShow DLL。DirectShow DLL 也是我的代码,也是使用 DSPACK DirectShow 组件套件在 Delphi 6 中编写的。我有一个简单的代码块,它通过位图中的每个像素修改图像的亮度和对比度,如果设置了某个标志,否则位图被推出未修改的 DirectShow DLL(推送源视频过滤器)。该代码曾经位于主应用程序中,然后我将其移至 DirectShow DLL 中。当它在主应用程序中时,它运行良好。我可以按预期看到位图中的变化。但是,既然代码驻留在 DirectShow DLL 中,它就有以下问题:

  1. 当下面的代码块处于活动状态时,DirectShow DLL 真的很慢。我有一个四核 i5,它真的很慢。我还可以看到 CPU 消耗的大幅飙升。相比之下,在主应用程序中运行的相同代码在旧的单核 P4 上运行良好。它确实在那台旧机器上明显地击中了 CPU,但视频很流畅,没有任何问题。图像尺寸仅为 352 x 288 像素。

  2. 我没有看到可见位图的预期变化。我可以跟踪 DirectShow DLL 中的代码并查看代码正确更改的每个像素的数值,但图形编辑 ActiveMovie 窗口中的可视图像看起来完全没有变化。

  3. 如果我停用我可以实时执行的代码,ActiveMovie 窗口会显示像玻璃一样光滑的视频,在 CPU 几乎没有接触的情况下完美呈现。如果我重新激活代码,视频现在真的很不稳定,可能每秒只显示 1 到 2 帧,并且在显示第一帧之前有很长的延迟,并且 CPU 会出现峰值。不完全,但比我预期的要多得多。

我尝试使用包括范围检查、溢出检查等在内的所有内容编译 DirectShow DLL,并且在运行时没有警告或错误。然后我尝试以最快的速度进行编译,但它仍然存在上面列出的完全相同的问题。确实出了点问题,我不知道是什么。请注意,我确实在修改位图之前锁定了画布,并在完成后将其解锁。如果不是因为我上面提到的“一切都在”编译运行,我会说它感觉就像每次像素计算都会引发 FPU 异常并默默吞噬,但正如我所说,没有错误或异常正在发生。

更新:我将其放在这里,以便嵌入在 Roman R 的评论之一中的解决方案清晰可见。在访问 ScanLine 属性之前我没有将PixelFormat属性设置为pf24Bit的问题。正如 Roman 建议的那样,不这样做必须使 TBitmap 代码创建位图的临时副本。一旦我添加了下面的代码行,问题就消失了,更改不可见和软页面错误都消失了。这是一个隐蔽的问题,因为受影响的唯一对象是用于访问 ScanLine 属性的指针,因为(假设)它包含指向位图临时副本的指针。这就是为什么后续的 TextOut() 调用仍然有效的原因,因为它在位图的原始副本上工作。

clip.PixelFormat := pf24bit; // The missing code line that fixes the problem.

这是我一直提到的代码块:

function IntToByte(i: Integer): Byte;
begin
 if i > 255 then
   Result := 255
 else if i < 0 then
   Result := 0
 else
   Result := i;
end;

// ---------------------------------------------------------------

procedure brightnessTurboBoost(var clip: TBitmap; rangeExpansionPowerOf2: integer; shiftValue: Byte);
var
   p0: PByte;
   x,y: Integer;
begin
   if (rangeExpansionPowerOf2 = 0) and (shiftValue = 0) then
       exit; // These parameter settings will not change the pixel values.

   for y := 0 to clip.Height-1 do
   begin
       p0 := clip.scanline[y];

       // Can't just do the whole buffer as a big block of bytes since the
       //  individual scan lines may be padded for CPU alignment.
       for x := 0 to (clip.Width - 1) * 3 do
       begin
           if rangeExpansionPowerOf2 >= 1 then
               p0^ := IntToByte((p0^ shl rangeExpansionPowerOf2) + shiftValue)
           else
               p0^ := IntToByte(p0^ + shiftValue);

           Inc(p0);
       end;
   end;
end;
4

1 回答 1

3

关于这个代码片段有几件事要说。

  1. 首先,您正在使用类的Scanline属性TBitmap。我已经很多年没有与 Delphi 脱节了,所以我可能错了,但我的印象Scanline是它实际上并不是一个瘦访问器,是吗?它可能在内部隐藏了会极大影响性能的东西,例如“如果他想访问图像的位,那么我们必须先将其转换为 DIB,然后再返回指针”。所以一个看起来如此简单的东西可能看起来是一个杀手。

  2. 内循环体中的“if rangeExpansionPowerOf2 >= 1 then”?你真的不想一直比较这个。要么创建两个单独的函数,要么复制整个循环,而不是在零和非零 rangeExpansionPowerOf2 的两个版本中,如果只执行一次。

  3. “for ... to (clip.Width - 1) * 3 do”我不太确定 Delphi 是否优化了上边界评估以使其仅进行一次。您可能对每个像素进行三次乘法运算,而您只能对整个图像执行一次。

  4. 对于顶级性能IntToByte,绝对在 MMX 中实现,以避免 ifs 并一次处理多个字节。

正如您所说,图像只有 352x288,我怀疑 #1 正在破坏性能。

于 2012-01-06T18:37:12.540 回答