當然你可以直接用現成的虛擬攝像頭軟件實現這個功能。不過當初我開發這個插件的原因是,需要在Flash產品裏面共享桌面,如果此時需要引導用戶安裝一個第三方的虛擬攝像頭體驗不好,所以公司希望我自己開發一個虛擬攝像頭,一鍵安裝減少用戶的使用門檻。所謂的虛擬攝像頭實際上在windows系統上註冊了一個特殊dll,這個dll是一個COM組件。
虛擬攝像頭需要用到Direct Show編程。
下載Direct Show開發代碼
裏面有如下的文件夾,我只需要用到第一個文件夾裏面的代碼—— baseclasses
baseclasses
capture
common
dmo
dvd
filters
misc
players
vmr9
創建工程
打開Visual Studio ,新建一個win32 Dll項目。 打開屬性頁,在VC++ 目錄一欄中的庫目錄裏面添加剛纔的baseclasses的路徑,這樣我們就能在項目中引用這個目錄裏的代碼了。 在C/C++屬性頁裏面的附加庫目錄裏面也把baseclasses的路徑填入。 在dll.cpp中,我們需要把Filter註冊成COM組件。 分別需要調用AMovieSetupRegisterServer函數、CreateComObject函數以及IFilterMapper2接口的RegisterFilter函數完成註冊。
主邏輯
在頭文件中,我們需要聲明兩個類
class CVCamStream;
class CVCam : public CSource
{
public:
//////////////////////////////////////////////////////////////////////////
// IUnknown
//////////////////////////////////////////////////////////////////////////
static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
IFilterGraph *GetGraph() {return m_pGraph;}
static int cx, cy;
static HANDLE SocketThread;
static SOCKET ClientSocket;
private:
CVCam(LPUNKNOWN lpunk, HRESULT *phr);
};
class CVCamStream : public CSourceStream, public IAMDroppedFrames,public IAMStreamConfig, public IKsPropertySet
{
public:
COM組件只需要實現CUnknown接口即可。我們繼承了Direct Show的CSource類,那麼就已經實現了這個接口。 CVCamStream類用來實現圖像數據的輸出。 在CVCam的構造函數裏面我們創建CVCamStream類的實例
m_paStreams = (CSourceStream **) new CVCamStream*[1];
m_paStreams[0] = new CVCamStream(phr, this, L"Flex COM");
在實現COM接口的QueryInterface函數中,我們調用了CVCamStream類的QueryInterface
HRESULT CVCam::QueryInterface(REFIID riid, void **ppv)
{
//Forward request for IAMStreamConfig & IKsPropertySet to the pin
if(riid == _uuidof(IAMStreamConfig) ||
riid == _uuidof(IAMDroppedFrames) ||
riid == _uuidof(IKsPropertySet))
return m_paStreams[0]->QueryInterface(riid, ppv);
else
return CSource::QueryInterface(riid, ppv);
}
定義媒體類型
HRESULT CVCamStream::GetMediaType(int iPosition, CMediaType *pmt)
在這個函數中,我們配置了媒體的具體的格式參數,比如24位RGB格式,圖像的寬高等等。 另外還需要對GetStreamCaps函數進行實現,配置媒體的格式。
HRESULT STDMETHODCALLTYPE CVCamStream::GetStreamCaps(int iIndex, AM_MEDIA_TYPE **pmt, BYTE *pSCC)
捕獲桌面
系統會調用FillBuffer函數,在這個函數中,我們將捕獲到的數據填充到緩衝裏面,Direct Show會處理剩下的事情。
HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
捕獲桌面只需要用到一個函數CopyScreenToBitmap
HANDLE hDib = CopyScreenToBitmap(&ScreenRect, pData, (BITMAPINFO *)&(pVih->bmiHeader), m_hCursor);
if (hDib) DeleteObject(hDib);
pData是我們定義的一個指針,通過下面的代碼,我們的pData就指向了緩存,數據填充到pData指向的內存中。
BYTE *pData;
pms->GetPointer(&pData);
進階
實際產品會有很多需求,光實現捕獲桌面是遠遠不夠的,我們需要對這個捕獲進行控制,比如捕獲制定區域,停止捕獲,恢復捕獲等等。那麼就涉及到和COM進行通訊了。 我們可以通過VS的窗口設計器創建一個windows窗口,然後提供一個用戶操作界面。 如何響應這個窗口的用戶操作呢? 通過windows消息
INT_PTR CALLBACK WindowMessage(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
當然最關鍵是需要在Flash產品的程序裏面喚起這個窗口,需要用的socket編程。
SOCKET Listen_Sock = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN serverAddr;
ZeroMemory((char *)&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(1234);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(Listen_Sock, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
listen(Listen_Sock, 5);
C++的socket編程十分的繁瑣,其他語言都會進行封裝,讓開發變的十分便利。 在一個線程裏面寫個死循環進行讀取socket的數據,這是比較初級的多線程阻塞式的socket編程。對於我們這個程序是綽綽有餘了。畢竟不是服務器,不需要面對併發的問題。