在后台运行IVMRWindowlessControl9.GetCurrentImage()

本文关键字:GetCurrentImage IVMRWindowlessControl9 后台 运行 | 更新日期: 2023-09-27 18:11:46

我有一个使用DirectShow.NET的网络摄像头控件。我创建了一个自定义控件来显示视频和从网络摄像头捕获图像。我在另一个WPF窗口中使用该自定义控件。我在自定义控件中有一个函数public Bitmap CaptureImage()来抽象掉一点DirectShow编程,并简单地返回一个Bitmap。由于图像比较大(1920x1080), IVMRWindowlessControl9GetCurrentImage()函数需要相当长的时间来处理(2-3秒)。我已经遍历了我的代码,可以确认这个调用是唯一一个需要很长时间来处理的调用。

因此,我的主WPF窗口中的GUI线程挂起,导致它在几秒钟内没有响应,所以如果我想在捕获图像时显示进度旋转器,它将保持冻结状态。

以下是CaptureImage()的代码:

public Bitmap CaptureImage()
{
  if (!IsCapturing)
    return null;
  this.mediaControl.Stop();
  IntPtr currentImage = IntPtr.Zero;
  Bitmap bmp = null;
  try
  {
    int hr = this.windowlessControl.GetCurrentImage(out currentImage);
    DsError.ThrowExceptionForHR(hr);
    if (currentImage != IntPtr.Zero)
    {
      BitmapInfoHeader bih = new BitmapInfoHeader();
      Marshal.PtrToStructure(currentImage, bih);
      ...
      // Irrelevant code removed 
      ...
      bmp = new Bitmap(bih.Width, bih.Height, stride, pixelFormat, new IntPtr(currentImage.ToInt64() + Marshal.SizeOf(bih)));
      bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
    }
  }
  catch (Exception ex)
  {
    MessageBox.Show("Failed to capture image:" + ex.Message);
  }
  finally
  {
    Marshal.FreeCoTaskMem(currentImage);
  }
  return bmp;
}

为了解决这个问题,我已经尝试将其作为后台任务运行,如下所示:

public async void CaptureImageAsync()
{
  try
  {
    await Task.Run(() =>
    {
      CaptureImage();
    });
  }
  catch(Exception ex)
  {
    MessageBox.Show(ex.Message);
  }
}

我尝试了多种方法来做到这一点,包括使用BackgroundWorker s,但似乎任何时候我异步地进行这个调用,它会产生这个错误:

无法强制转换'DirectShowLib '类型的COM对象。VideoMixingRenderer9"到接口类型'DirectShowLib.IVMRWindowlessControl9'。这操作失败,因为在COM组件上调用了QueryInterface用于IID为'{8F537D09-F85E-4414-B23B-502E54C79927}'的接口没有这样的接口支持(Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

错误总是发生在这一行:

int hr = this.windowlessControl.GetCurrentImage(out currentImage);

同步调用CaptureImage()产生正常结果。图像被捕获,一切都按预期工作。但是,切换到使用任何类型的异步功能都会产生该错误。

在后台运行IVMRWindowlessControl9.GetCurrentImage()

这里有两个问题。首先,API最初的缓慢是设计行为。MSDN将其命名为:

但是,频繁调用此方法会降低视频播放性能。

视频内存在读回时可能相当慢——这是2-3秒的处理问题,而不是图像本身的分辨率。坏消息是,很有可能从后台线程轮询快照也会影响视觉流。

这个方法过去和现在都是用来拍摄零星的快照,特别是那些由用户交互发起的快照,而不是自动的。需要更密集和自动化的应用程序,以及那些不影响视觉提要快照的应用程序,应该在提要发送到显存之前拦截它(有很多选项可以做到这一点,最流行但笨拙的是使用Sample Grabber)。

第二,你可能会碰到这个问题中描述的。net线程问题,它会触发上述异常。在本机代码开发中,通过偷偷违反COM线程规则并在公寓之间传递接口指针,很容易使用相同的接口指针。由于CLR在你的代码和COM对象之间添加了一个中间层,做额外的安全检查,你不能再从后台线程操作COM对象/接口,因为COM线程规则被强制执行。

我想你要么不得不忍受与直接API调用相关的长时间冻结,要么添加有助于绕过冻结的本机代码开发(特别是,例如,在发送到视频内存之前捕获帧的helper过滤器,同时为。net调用者实现支持后台线程调用的helper功能)。大概你也可以做所有的DirectShow相关的东西在一个帮助MTA线程池中解决你的后台线程调用者的问题,但在这种情况下,你很可能需要从你的UI线程,这是STA移动这些代码-我不认为这是人们经常做的事情,如果有的话。