我有一个使用 DSPACK 组件库用 Delphi 6 编写的 DirectShow 推送源视频过滤器。只要使用过滤器的 Skype 客户端不是5.x 或更新版本,过滤器就可以在 Skype 通话期间正常运行。在 5.x 中,Skype 客户端变得非常缓慢,直到它挂起,然后我得到了各种严重的崩溃,包括数据执行保护警告和典型的 Microsoft“这个程序已经崩溃”对话框。有时它会立即崩溃,有时它会在通话大约 30 秒或更长时间后崩溃。
我还可以在以下情况下运行视频过滤器而不会出错:
- 在视频过滤器预览窗口中使用 Skype 5.x,您在选择要与 Skype 一起使用的视频设备时看到该窗口(不是在通话中,而是在视频选项选择对话框页面上)。
- Skype 4.x 客户端(在通话中完美运行)
- 图表编辑
- 一个 DSPACK TVideoWindow 实例
- 使用网络摄像头源的其他程序
我在网上做了一些研究,确实发现了很多关于 Skype 5.x 和崩溃的投诉。我阅读的主题建议下载 5.7 beta。我试过了,但没有帮助。它运行得更好一些,但仍然崩溃。
作为一个简单的测试,我改变了我的 FillBuffer() 方法,只提供一个我在启动时加载的静态位图,而不是我通常中继到 Skype 的外部视频流。它仍然崩溃。此外,我什至尝试运行设置了 FastMM4 的推送源过滤器 DLL,以对每个 FillBuffer() 调用和将媒体样本传递到下游引脚的调用进行全内存扫描。没有任何错误。
由于 Skype 显然可以与其他网络摄像头驱动程序一起使用,否则会在网络上引起强烈抗议,我的过滤器可能会做什么它不喜欢的事情?
更新:经过进一步测试,我遇到了一些奇怪的事情。最初,我的过滤器中的 GetMediaType() 调用有 4 种格式。我将其降低为 1 种格式:24 位,压缩设置为 BI_RGB,因为这是我从外部接收然后传递给 Skype 的。登录后,我立即开始从 Skype 快速失败,它是 DirectShow 过滤器扫描,并且失败发生在我的 GetStreamCaps() 调用期间。由于 Skype 有反调试代码,我煞费苦心地将跟踪消息添加到每行之后的 GetStreamCaps() 调用中,并发现它发生在我第一次尝试访问它的媒体格式变量时(见下文)。看来我无法访问 Skype 传递给我的 DirectShow 过滤器的内存区域。为什么只提供 1 种媒体格式而不是之前的 4 种格式会使故障发生得更快是未知数。
这纯粹是我的猜测,但是否有可能在 Skype 和我的过滤器之间发生某种奇怪的内存区域访问权限错误?事实上,在我开始通话之前,Skype 偶尔会报告数据执行保护错误,以及其他一般崩溃,这让我想知道是否发生了一些奇怪的事情。当您尝试写入标记为代码块的区域时,会发生 DEP 错误。就好像 Skype 传递给我的指针指向某个我无法写入的陌生或受保护的地方。
概括地说,现在每次 Skype 在调用 GetStreamCaps() 时访问我的 DirectShow 过滤器时,在我开始呼叫之前,或者我什至能够访问视频设备选择屏幕之前,错误都会发生 100%。这是相关的代码片段:
function TBCPushPinDesktop.GetStreamCaps(iIndex: Integer; out ppmt: PAMMediaType; out pSCC): HResult;
var
pvi:PVIDEOINFOHEADER;
begin
ppmt := CreateMediaType(@Fmt);
pvi:=PVIDEOINFOHEADER(ppmt.pbFormat);
// Error occurs at THIS statement, the first attempt to write to the memory area
// provided by Skype.
pvi.bmiHeader.biCompression := BI_RGB;
.... SNIP ....
end;
更新 2:我的代码有问题,但我不知道是什么。Graph Edit 不像 Skype 那样调用 GetStreamCaps()。我添加了更多跟踪语句,结果发现在上面的代码中,DSPACK CreateMediaType() 调用返回的媒体类型对象有一个 NIL pbFormat 字段,因此可以解释快速失败。如果有人知道我需要做什么才能获得正确配置的 pbFormat 字段,请告诉我。下面是 DSPACK 中执行 CreateMediaType() 操作的代码:
// this also comes in useful when using the IEnumMediaTypes interface so
// that you can copy a media type, you can do nearly the same by creating
// a CMediaType object but as soon as it goes out of scope the destructor
// will delete the memory it allocated (this takes a copy of the memory)
function CreateMediaType(pSrc: PAMMediaType): PAMMediaType;
var pMediaType: PAMMediaType;
begin
ASSERT(pSrc<>nil);
// Allocate a block of memory for the media type
pMediaType := CoTaskMemAlloc(sizeof(TAMMediaType));
if (pMediaType = nil) then
begin
result := nil;
exit;
end;
// Copy the variable length format block
CopyMediaType(pMediaType,pSrc);
result := pMediaType;
end;
//----------------------------------------------------------------------------
// Copies a task-allocated AM_MEDIA_TYPE structure.
//----------------------------------------------------------------------------
procedure CopyMediaType(pmtTarget: PAMMediaType; pmtSource: PAMMediaType);
begin
// We'll leak if we copy onto one that already exists - there's one
// case we can check like that - copying to itself.
ASSERT(pmtSource <> pmtTarget);
//pmtTarget^ := pmtSource^;
move(pmtSource^, pmtTarget^, SizeOf(TAMMediaType));
if (pmtSource.cbFormat <> 0) then
begin
ASSERT(pmtSource.pbFormat <> nil);
pmtTarget.pbFormat := CoTaskMemAlloc(pmtSource.cbFormat);
if (pmtTarget.pbFormat = nil) then
pmtTarget.cbFormat := 0
else
CopyMemory(pmtTarget.pbFormat, pmtSource.pbFormat, pmtTarget.cbFormat);
end;
if (pmtTarget.pUnk <> nil) then pmtTarget.pUnk._AddRef;
end;