可用資源拆包與避坑指南)
簡(jiǎn)介DirectShow_Win10親測(cè)可用是一份面向Windows 10平臺(tái)多媒體開(kāi)發(fā)者的DirectShow學(xué)習(xí)與開(kāi)發(fā)資源包適合具備一定C與COM編程基礎(chǔ)、希望構(gòu)建播放器、視頻捕獲或流媒體應(yīng)用的開(kāi)發(fā)者。資源圍繞DirectShow框架展開(kāi)涵蓋過(guò)濾器圖工作原理、源/轉(zhuǎn)換/渲染過(guò)濾器類(lèi)型、CoCreateInstance等COM接口調(diào)用方式以及在Win10下的安裝配置與兼容性要點(diǎn)并延伸至GraphEdit調(diào)試、HRESULT錯(cuò)誤處理與自定義過(guò)濾器開(kāi)發(fā)等實(shí)踐內(nèi)容。壓縮包為rar格式大小約1.29MB上游未提供文件總數(shù)與類(lèi)型明細(xì)但內(nèi)容以頭文件、庫(kù)文件及配套說(shuō)明為主可直接集成到Visual Studio項(xiàng)目中配合DirectX SDK使用。目前已有915人學(xué)習(xí)下載可作為快速搭建DirectShow開(kāi)發(fā)環(huán)境、理解過(guò)濾器圖機(jī)制與排查兼容性問(wèn)題的參考材料。1. DirectShow 在 Win10 上到底還能不能跑一次親測(cè)可用的資源拆包如果你最近在 Win10 上編譯過(guò)老的多媒體項(xiàng)目大概率見(jiàn)過(guò)這個(gè)場(chǎng)景代碼里#include dshow.h一加鏈接strmiids.lib一掛編譯直接報(bào)一堆unresolved external symbol或者運(yùn)行時(shí)CoCreateInstance返回0x80040154類(lèi)未注冊(cè)。DirectShow 這套微軟的老牌流媒體框架從 DirectX 9 時(shí)代一路活到 Win10官方文檔還在但真到 Win10 上跑起來(lái)坑比想象中多。這份「DirectShow_Win10親測(cè)可用」資源核心價(jià)值就是把這套框架在 Win10 環(huán)境下的可用狀態(tài)、依賴(lài)組件和最小可運(yùn)行工程整理清楚了省去你自己一個(gè)個(gè)試 SDK 版本、試注冊(cè)表、試 COM 初始化的時(shí)間。它適合兩類(lèi)人一是維護(hù)老音視頻采集/播放項(xiàng)目的工程師二是想用 DirectShow 做攝像頭采集、濾鏡開(kāi)發(fā)但被環(huán)境卡住的新手。下面我按「資源是什么 → 怎么用 → 坑在哪」的順序把這份包拆開(kāi)講。2. DirectShow 在 Win10 的運(yùn)行時(shí)依賴(lài)SDK、COM 與注冊(cè)表三件事2.1 為什么 Win10 上 DirectShow 不是「裝上就能用」DirectShow 在 Win10 里并沒(méi)有被移除quartz.dll、qedit.dll這些核心組件依然躺在C:\Windows\System32下但微軟從 Windows SDK 8.0 之后就不再單獨(dú)提供 DirectShow 的完整頭文件和庫(kù)的「一鍵安裝」了。很多人的第一反應(yīng)是去裝老版本的 DirectX SDK比如 June 2010結(jié)果和 Win10 自帶的 SDK 沖突dshow.h版本對(duì)不上鏈接期直接翻車(chē)。常見(jiàn)做法是不裝老 DirectX SDK直接用 Windows SDK 里自帶的 DirectShow 頭文件但需要手動(dòng)確認(rèn)幾個(gè)關(guān)鍵文件是否齊全。我一般會(huì)先檢查這幾個(gè)路徑# 檢查 Windows SDK 中 DirectShow 相關(guān)文件是否存在 dir C:\Program Files (x86)\Windows Kits\10\Include\*\um\dshow.h dir C:\Program Files (x86)\Windows Kits\10\Lib\*\um\x64\strmiids.lib dir C:\Program Files (x86)\Windows Kits\10\Lib\*\um\x64\quartz.lib邏輯說(shuō)明dshow.h是 DirectShow 的主頭文件strmiids.lib提供接口 GUIDquartz.lib提供CoCreateInstance需要的類(lèi)廠標(biāo)識(shí)。參數(shù)說(shuō)明*代表 SDK 版本號(hào)目錄比如10.0.19041.0x64 換成 x86 就是 32 位庫(kù)。如果這三個(gè)文件都在說(shuō)明 SDK 層面沒(méi)問(wèn)題不用再折騰老 DirectX SDK。2.2 COM 初始化與線(xiàn)程模型別在錯(cuò)誤的地方調(diào) CoInitializeDirectShow 完全基于 COM所以每個(gè)使用它的線(xiàn)程都必須先調(diào)CoInitializeEx。新手最容易犯的錯(cuò)是在主線(xiàn)程初始化了然后在工作線(xiàn)程里直接調(diào)IGraphBuilder結(jié)果返回CO_E_NOTINITIALIZED。正確做法是每個(gè)線(xiàn)程各自初始化并且注意套間模型。// 每個(gè)使用 DirectShow 的線(xiàn)程都要獨(dú)立初始化 COM HRESULT hr CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { // RPC_E_CHANGED_MODE 表示該線(xiàn)程已用其他模型初始化過(guò) if (hr ! RPC_E_CHANGED_MODE) { return hr; } } // ... 在這里創(chuàng)建 IGraphBuilder、ICaptureGraphBuilder2 等 CoUninitialize(); // 與 CoInitializeEx 配對(duì)邏輯說(shuō)明COINIT_APARTMENTTHREADED是 STA 模型DirectShow 的很多濾鏡尤其是視頻渲染器要求 STA。參數(shù)說(shuō)明如果返回RPC_E_CHANGED_MODE說(shuō)明當(dāng)前線(xiàn)程已經(jīng)被初始化成 MTA 了這時(shí)不要重復(fù)初始化也不要CoUninitialize直接繼續(xù)用即可但要注意跨套間調(diào)用可能帶來(lái)的封送開(kāi)銷(xiāo)。這個(gè)細(xì)節(jié)在 Win10 上尤其重要因?yàn)?Win10 對(duì) COM 套間的檢查比 Win7 嚴(yán)格。2.3 注冊(cè)表里要確認(rèn)的 Filter 注冊(cè)狀態(tài)DirectShow 的濾鏡分兩類(lèi)一類(lèi)是系統(tǒng)自帶的注冊(cè)在HKEY_CLASSES_ROOT\CLSID下另一類(lèi)是你自己寫(xiě)的需要用regsvr32注冊(cè)。Win10 上如果發(fā)現(xiàn)CoCreateInstance返回0x80040154八成是濾鏡沒(méi)注冊(cè)或者注冊(cè)到了錯(cuò)誤的位數(shù)32 位濾鏡注冊(cè)到 64 位注冊(cè)表視圖。# 以管理員身份注冊(cè) 32 位濾鏡注意用 SysWOW64 下的 regsvr32 C:\Windows\SysWOW64\regsvr32.exe YourFilter.dll # 注冊(cè) 64 位濾鏡 C:\Windows\System32\regsvr32.exe YourFilter.dll # 查詢(xún)某個(gè) CLSID 是否注冊(cè) reg query HKCR\CLSID\{你的CLSID} /s邏輯說(shuō)明Win10 的注冊(cè)表有 WOW64 重定向32 位程序看到的HKCR\CLSID和 64 位程序看到的是同一邏輯視圖但物理位置不同。參數(shù)說(shuō)明SysWOW64下的regsvr32是 32 位版本System32下的是 64 位版本別搞反。如果查詢(xún)不到 CLSID說(shuō)明注冊(cè)失敗常見(jiàn)原因是 DLL 依賴(lài)的運(yùn)行時(shí)庫(kù)缺失用Dependencies工具查一下。3. 最小可運(yùn)行工程從零搭一個(gè) Win10 下的攝像頭預(yù)覽程序3.1 工程配置頭文件、庫(kù)、字符集三處對(duì)齊在 Visual Studio 里新建一個(gè)空 C 控制臺(tái)工程然后按下面三步配置。很多人編譯不過(guò)不是代碼問(wèn)題是工程配置沒(méi)對(duì)齊。第一步包含目錄加上 SDK 的Include\*\um和Include\*\shared。第二步庫(kù)目錄加上Lib\*\um\x64或 x86。第三步在鏈接器輸入里加上strmiids.lib quartz.lib ole32.lib oleaut32.lib。字符集建議用 Unicode因?yàn)?DirectShow 的IGraphBuilder::RenderFile在 Win10 上對(duì)寬字符路徑支持更穩(wěn)。// 最小攝像頭預(yù)覽枚舉設(shè)備 - 建圖 - 渲染 #include dshow.h #include iostream #pragma comment(lib, strmiids.lib) #pragma comment(lib, quartz.lib) #pragma comment(lib, ole32.lib) int main() { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); ICreateDevEnum* pDevEnum nullptr; IEnumMoniker* pEnum nullptr; // 創(chuàng)建系統(tǒng)設(shè)備枚舉器 CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)pDevEnum); // 枚舉視頻采集設(shè)備類(lèi)別 pDevEnum-CreateClassEnumerator(CLSID_VideoInputDeviceCategory, pEnum, 0); IMoniker* pMoniker nullptr; if (pEnum pEnum-Next(1, pMoniker, NULL) S_OK) { IPropertyBag* pBag nullptr; pMoniker-BindToStorage(0, 0, IID_IPropertyBag, (void**)pBag); VARIANT var; VariantInit(var); pBag-Read(LFriendlyName, var, 0); std::wcout L找到設(shè)備: var.bstrVal std::endl; VariantClear(var); pBag-Release(); pMoniker-Release(); } // 后續(xù)用 IGraphBuilder ICaptureGraphBuilder2 建圖此處省略 if (pEnum) pEnum-Release(); if (pDevEnum) pDevEnum-Release(); CoUninitialize(); return 0; }邏輯說(shuō)明CLSID_SystemDeviceEnum是系統(tǒng)設(shè)備枚舉器的類(lèi)標(biāo)識(shí)CLSID_VideoInputDeviceCategory限定只枚舉攝像頭。參數(shù)說(shuō)明CreateClassEnumerator的第三個(gè)參數(shù)為 0 表示返回所有設(shè)備如果設(shè)為CDEF_DEVMON_FILTER則只返回已注冊(cè)的濾鏡。這段代碼能跑通說(shuō)明 COM 和枚舉器都正常接下來(lái)才是建圖。3.2 用 CaptureGraphBuilder2 建圖順序錯(cuò)了就黑屏建圖的順序很關(guān)鍵先CoCreateInstance拿IGraphBuilder再拿ICaptureGraphBuilder2然后SetFiltergraph把兩者綁定最后RenderStream。如果先RenderStream再SetFiltergraphWin10 上大概率黑屏或者返回E_INVALIDARG。IGraphBuilder* pGraph nullptr; ICaptureGraphBuilder2* pBuilder nullptr; CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)pGraph); CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)pBuilder); pBuilder-SetFiltergraph(pGraph); // 必須先綁定 // 假設(shè) pCap 是已經(jīng)創(chuàng)建好的采集濾鏡 // pBuilder-RenderStream(PIN_CATEGORY_PREVIEW, MEDIATYPE_Video, pCap, NULL, NULL);邏輯說(shuō)明SetFiltergraph把 GraphBuilder 和 CaptureGraphBuilder2 關(guān)聯(lián)起來(lái)后續(xù)RenderStream才能正確找到渲染器。參數(shù)說(shuō)明PIN_CATEGORY_PREVIEW表示預(yù)覽引腳PIN_CATEGORY_CAPTURE表示采集引腳Win10 上有些攝像頭只暴露 capture 不暴露 preview這時(shí)要改用 capture 引腳并手動(dòng)接一個(gè)Smart Tee濾鏡分流。3.3 消息循環(huán)與事件處理別讓窗口卡死DirectShow 的圖運(yùn)行起來(lái)后需要處理IMediaEvent消息否則窗口會(huì)假死。常見(jiàn)做法是在主消息循環(huán)里用IMediaEvent::GetEvent輪詢(xún)或者用IMediaEventEx::SetNotifyWindow把事件轉(zhuǎn)發(fā)到窗口過(guò)程。IMediaEventEx* pEvent nullptr; pGraph-QueryInterface(IID_IMediaEventEx, (void**)pEvent); pEvent-SetNotifyWindow((OAHWND)hWnd, WM_GRAPHNOTIFY, 0); // 在窗口過(guò)程中處理 case WM_GRAPHNOTIFY: { long evCode; LONG_PTR param1, param2; while (pEvent-GetEvent(evCode, param1, param2, 0) S_OK) { if (evCode EC_COMPLETE) { /* 播放結(jié)束 */ } pEvent-FreeEventParams(evCode, param1, param2); } break; }邏輯說(shuō)明SetNotifyWindow把 DirectShow 事件投遞到指定窗口避免阻塞主線(xiàn)程。參數(shù)說(shuō)明WM_GRAPHNOTIFY是自定義消息號(hào)GetEvent的最后一個(gè)參數(shù) 0 表示同步獲取如果設(shè)為INFINITE會(huì)阻塞。Win10 上如果發(fā)現(xiàn)事件收不到檢查窗口句柄是否有效以及是否在正確的線(xiàn)程創(chuàng)建了圖。4. 避坑與排查Win10 下 DirectShow 最常見(jiàn)的五個(gè)翻車(chē)現(xiàn)場(chǎng)4.1 現(xiàn)象編譯報(bào)dshow.h找不到或鏈接報(bào)strmiids.lib缺失原因Windows SDK 安裝時(shí)沒(méi)有勾選「Desktop development with C」里的 Windows SDK 組件或者只裝了 UWP 部分的 SDK。解決打開(kāi) Visual Studio Installer修改當(dāng)前 VS 版本在「單個(gè)組件」里搜索「Windows SDK」確保對(duì)應(yīng)版本的 SDK 已勾選并且安裝路徑下確實(shí)有um\dshow.h。如果還是找不到檢查工程屬性里的「Windows SDK 版本」是否選了一個(gè)沒(méi)裝全的版本。4.2 現(xiàn)象CoCreateInstance返回0x80040154類(lèi)未注冊(cè)原因目標(biāo)濾鏡沒(méi)有注冊(cè)或者注冊(cè)到了錯(cuò)誤的注冊(cè)表視圖。解決先用reg query確認(rèn) CLSID 是否存在如果不存在用對(duì)應(yīng)位數(shù)的regsvr32重新注冊(cè)如果注冊(cè)成功但依然報(bào)錯(cuò)用Dependencies工具查 DLL 的依賴(lài)是否齊全Win10 上常見(jiàn)缺msvcp140.dll或vcruntime140.dll。4.3 現(xiàn)象攝像頭預(yù)覽黑屏但RenderStream返回S_OK原因Win10 的隱私設(shè)置里攝像頭權(quán)限沒(méi)開(kāi)或者攝像頭被其他程序占用。解決打開(kāi)「設(shè)置 → 隱私 → 攝像頭」確認(rèn)「允許應(yīng)用訪(fǎng)問(wèn)你的攝像頭」已開(kāi)啟然后檢查是否有其他程序如 Teams、Zoom正在占用攝像頭。如果權(quán)限和占用都沒(méi)問(wèn)題嘗試改用PIN_CATEGORY_CAPTURE加Smart Tee的方式建圖。4.4 現(xiàn)象程序運(yùn)行一段時(shí)間后卡死CPU 占用飆升原因IMediaEvent沒(méi)有及時(shí)FreeEventParams導(dǎo)致事件隊(duì)列堆積或者消息循環(huán)里做了阻塞操作。解決確保每次GetEvent成功后都調(diào)用FreeEventParams把耗時(shí)操作放到獨(dú)立線(xiàn)程主線(xiàn)程只處理窗口消息和事件輪詢(xún)。Win10 上還可以用任務(wù)管理器看quartz.dll的線(xiàn)程狀態(tài)確認(rèn)是否有死鎖。4.5 現(xiàn)象32 位程序在 64 位 Win10 上注冊(cè)濾鏡成功但運(yùn)行時(shí)找不到原因32 位程序讀取的是HKCR\Wow6432Node\CLSID而 64 位regsvr32注冊(cè)到了HKCR\CLSID。解決用C:\Windows\SysWOW64\regsvr32.exe重新注冊(cè) 32 位濾鏡然后用reg query HKCR\Wow6432Node\CLSID\{你的CLSID}確認(rèn)。如果還是不行檢查濾鏡 DLL 本身是不是 32 位的用dumpbin /headers看 machine 字段。5. 進(jìn)階技巧用 GraphEdit 驗(yàn)證濾鏡鏈路與性能調(diào)優(yōu)5.1 用 GraphEdit 快速驗(yàn)證濾鏡是否可用GraphEdit 是 Windows SDK 里自帶的一個(gè)小工具在C:\Program Files (x86)\Windows Kits\10\bin\*\x64\graphedit.exe部分版本需要單獨(dú)裝 SDK 工具。它的價(jià)值在于不用寫(xiě)一行代碼就能把攝像頭、解碼器、渲染器拖到一起看鏈路能不能通。我一般在新環(huán)境上先跑 GraphEdit確認(rèn)系統(tǒng)濾鏡都正常再回去調(diào)代碼。如果 GraphEdit 里都連不通代碼里更不可能通。操作步驟打開(kāi) GraphEdit → 點(diǎn)「Graph → Insert Filters」→ 在「Video Capture Sources」里選攝像頭 → 在「DirectShow Filters」里選Video Renderer→ 把兩個(gè)濾鏡的引腳連起來(lái) → 點(diǎn)播放。如果能看到畫(huà)面說(shuō)明系統(tǒng)層面沒(méi)問(wèn)題如果報(bào)錯(cuò)錯(cuò)誤碼會(huì)直接顯示在彈窗里比代碼里調(diào)試快得多。5.2 性能調(diào)優(yōu)緩沖區(qū)、幀率和色彩空間DirectShow 默認(rèn)的采集緩沖區(qū)可能偏小Win10 上高分辨率攝像頭容易丟幀。常見(jiàn)做法是通過(guò)IAMBufferNegotiation接口調(diào)整緩沖區(qū)數(shù)量或者通過(guò)IAMStreamConfig設(shè)置幀率和色彩空間。參數(shù)默認(rèn)值建議值說(shuō)明緩沖區(qū)數(shù)量13~5太少丟幀太多增加延遲幀率攝像頭默認(rèn)與處理能力匹配太高會(huì)占滿(mǎn) CPU色彩空間YUY2MJPG 或 NV12MJPG 省帶寬NV12 省 CPU分辨率640x480按需1080p 以上建議用 MJPG// 通過(guò) IAMStreamConfig 設(shè)置幀率和分辨率 IAMStreamConfig* pConfig nullptr; pCap-QueryInterface(IID_IAMStreamConfig, (void**)pConfig); int count, size; pConfig-GetNumberOfCapabilities(count, size); VIDEO_STREAM_CONFIG_CAPS caps; AM_MEDIA_TYPE* pmt; for (int i 0; i count; i) { pConfig-GetStreamCaps(i, pmt, (BYTE*)caps); if (pmt-subtype MEDIASUBTYPE_MJPG caps.MaxOutputSize.cx 1280) { pConfig-SetFormat(pmt); // 選中 MJPG 1280 寬 break; } DeleteMediaType(pmt); }邏輯說(shuō)明GetStreamCaps枚舉攝像頭支持的所有格式SetFormat應(yīng)用選中的格式。參數(shù)說(shuō)明MEDIASUBTYPE_MJPG是動(dòng)態(tài) JPEG適合高分辨率低帶寬場(chǎng)景MEDIASUBTYPE_NV12是 YUV 4:2:0適合需要 CPU 處理的場(chǎng)景。Win10 上如果發(fā)現(xiàn)設(shè)置格式后返回E_INVALIDARG說(shuō)明攝像頭不支持該格式回退到默認(rèn)即可。5.3 一個(gè)我踩過(guò)的坑別在 Win10 上混用老 DirectX SDK 和 Windows SDK早期我為了圖省事在 Win10 上同時(shí)裝了 DirectX SDK June 2010 和 Windows SDK結(jié)果dshow.h被老 SDK 覆蓋編譯出來(lái)的程序在 Win10 上跑著跑著就崩。后來(lái)查了半天才發(fā)現(xiàn)是頭文件版本沖突。從那以后我每次配新環(huán)境都強(qiáng)制先檢查dshow.h的路徑優(yōu)先級(jí)確保只引用 Windows SDK 里的版本。具體做法是在工程屬性里把 Windows SDK 的包含目錄移到 DirectX SDK 前面或者干脆不裝老 DirectX SDK。這個(gè)習(xí)慣幫我省了不少后悔藥。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取