將桌面捕獲到虛擬攝像頭 原

當然你可以直接用現成的虛擬攝像頭軟件實現這個功能。不過當初我開發這個插件的原因是,需要在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編程。對於我們這個程序是綽綽有餘了。畢竟不是服務器,不需要面對併發的問題。

源碼

https://github.com/langhuihui/FlexCOM

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章