2016-10-05 88 views
1

我有一個使用DirectShow.NET的攝像頭控件。我創建了一個自定義控件來顯示視頻並從網絡攝像頭捕捉圖像。我在另一個WPF窗口中使用該自定義控件。我在自定義控件中有一個函數public Bitmap CaptureImage()來抽象出一點DirectShow編程,並簡單地返回一個Bitmap。由於圖像相對較大(1920x1080),IVMRWindowlessControl9GetCurrentImage()功能需要相當長的時間來處理(2-3秒)。我已經瀏覽了我的代碼,可以確認此調用是唯一需要很長時間才能處理的調用。在後臺運行IVMRWindowlessControl9.GetCurrentImage()

因此,我的主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的方法,但似乎任何時候我異步進行此調用,都會產生此錯誤:

Unable to cast COM object of type 'DirectShowLib.VideoMixingRenderer9' to interface type 'DirectShowLib.IVMRWindowlessControl9'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{8F537D09-F85E-4414-B23B-502E54C79927}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

錯誤總是發生在這條線時:

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

調用CaptureImage()同步產生的正常結果。圖像被捕獲,一切按預期工作。但是,切換到使用任何類型的異步功能會導致該錯誤。

回答

2

這裏有兩個問題。首先,API的原始緩慢性是設計行爲。 MSDN mentions this爲:

However, frequent calls to this method will degrade video playback performance.

視頻內存可能是讀後衛很慢 - 這是2-3次處理問題,而不是圖像的分辨率本身。壞消息是,即使從後臺線程輪詢快照也很有可能影響視覺流。

此方法是和旨在採取零星的快照,特別是。那些由用戶交互式發起的,不是自動化的。需要更加密集和自動化的應用程序,以及那些不影響視覺饋送快照的應用程序應該在將視頻發送到視頻內存之前攔截該視頻源(有選項可供選擇,最流行但笨拙的是使用Sample Grabber)。

其次,您可能會遇到.NET線程問題described in this question,這會觸發上述異常。通過偷偷違反COM線程規則並在公寓之間傳遞接口指針,很容易在本地代碼開發中使用相同的接口指針。由於CLR在你的代碼和COM對象之間增加了一箇中間層來進行額外的安全檢查,因爲COM線程規則被強制執行,你不能再使用來自後臺線程的COM對象/接口。

我想你要麼不得不忍受長時間的凍結與直接的API調用相關,或者添加有助於繞過凍結的本地代碼開發(特別是,例如,在發送到視頻內存之前捕獲幀的輔助過濾器和同時爲.NET調用者實現助手功能以支持後臺線程調用)。大概你也可以在幫助程序庫池中執行所有DirectShow相關的東西,這些線程解決了後臺線程調用者的問題,但在這種情況下,你很可能需要從你的UI線程移動這個代碼 - 我不需要如果有的話,經常想一些人的事情。

+0

很好的解釋 - 你可以擴展一下「最流行但笨拙的是採樣採樣」的說法嗎?爲什麼樣本抓取方式笨拙,什麼是更好的方法? –

+0

@Roman R.你能否提供一些關於如何爲DirectShow相關代碼實現MTA線程池的更多細節?我無法在MTA線程中打開一個WPF窗口,因爲它要求它的線程是STA。我一直在試圖實現你提到的方法,但似乎無法提出一個可行的解決方案。 –

+0

@MikeDinescu:我認爲這超出了這個問題的範圍,我在稍後的一段時間做了一個註釋說明。 –