Windows遠程桌面實現之三(電腦內部聲音採集,錄音採集,攝像頭視頻採集)

                                                                                                  by fanxiushu 2017-08-09 轉載或引用請註明原始作者

一,攝像頭視頻採集


這裏提到的攝像頭數據採集,好像跟遠程桌面實現沒半毛錢關係,
其實本身確實跟遠程桌面沒半毛錢關係,但是當實現了遠程桌面整套東西之後發現,
把採集的桌面數據當成視頻數據,與攝像頭採集的視頻數據是如此相似,
以至於只要把攝像頭採集的數據格式跟之前採集到的桌面數據格式對接,基本就能實現攝像頭數據的遠程訪問。
與我在CSDN公佈的電腦桌面屏幕採集接口(下載地址: http://download.csdn.net/detail/fanxiushu/9910360)。
struct dp_rect_t
{
    RECT   rc;             ///發生變化的矩形框
    /////
    char*  line_buffer;    ///矩形框數據起始地址
    int    line_bytes;     ///每行(矩形框width對應)的數據長度
    int    line_nextpos;   ///從0開始,第N行的數據地址: line_buffer + N*line_nextpos 。
    int    line_count;     ///等於矩形框高度 height
};

struct dp_frame_t
{
    int        cx;          ///屏幕寬度
    int        cy;          ///屏幕高度
    int        line_bytes;  ///每個掃描行的實際數據長度
    int        line_stride; ///每個掃描行的4字節對齊的數據長度
    int        bitcount;    ///8.16.24.32 位深度, 8位是256調色板; 16位是555格式的圖像

    int        length;      ///屏幕數據長度 line_stride*cy
    char*      buffer;      ///屏幕數據
    /////
    int        rc_count;    ///變化區域個數
    dp_rect_t* rc_array;    ///變化區域

    ///
    dp_cursor_t* cursor;    ////鼠標相關信息
    ///
    void*      param;       ///
};

當採集到每幀桌面圖像數據,就會調用一個傳遞 dp_frame_t 參數的回調函數,dp_frame_t 裏邊包含了這一幀圖像數據的所有信息。
因此攝像頭採集的數據符合這個接口,就能無縫的與遠程桌面對接,無需額外對攝像頭採集的數據再另外來套代碼做壓縮做網絡傳輸。
如下圖所示,就是利用了遠程桌面這一套軟件來搭載攝像頭視頻和音頻。


使用DirectShow來實現攝像頭視頻數據的採集。
首先調用 ICreateDevEnum 枚舉電腦中所有存在的攝像頭設備,
僞代碼如下:
   hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&DevEnum);
   CComPtr<IEnumMoniker> pEM;
   hr = DevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEM, 0);
   IMoniker* pM;
   while (pEM->Next(1, &pM, &fetch) == S_OK) {
            查找你想綁定的設備,調用
            hr = pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&deviceFilter);  //綁定到這個設備,獲取到 deviceFilter接口用於接下來的處理
   }

接着創建管理接口 IGraphBuilder,用於負責管理攝像頭數據流
   hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
        IID_IGraphBuilder, (void**)&graphBuilder);
接着獲取控制接口,用於攝像頭運行,停止等控制,
   IMEdiaControl* control;
   graphBuilder->QueryInterface(IID_IMediaControl, (void**)&control);

至於DSHOW,微軟當初的設計思路,應該是想把流媒體諸如視頻之類的從最初的設備輸入到最終的render(顯示),
中間可以經過多個filter的過濾,每個filter可以有一個或者多個輸入PIN和輸出PIN,
某個filter的輸入PIN連接前一個filter或者鏈接到輸入設備的輸出PIN, 輸出PIN則連接到下一個filter或者render的輸入PIN。
雖然看起來是挺靈活的,實際上做起來麻煩挺多,這裏就不說這種思路的好處和壞處了。
因爲這裏我們只要求採集到從攝像頭輸入的原始視頻數據就可了,其他的我們自己會處理(也就是用不着dshow摻和了)。
要採集數據,根據dshow的特點,我們實現一個filter就可以了,爲了更簡單,直接使用微軟提供的 ISampleGrabber 接口,
雖然MSDN上說這個接口被遺棄了,但是在windows各個平臺還是能用,而且也沒找到更好更加簡單的替代接口來採集數據。
首先創建 SampleGrabler對象並且獲取 ISampleGrabber接口。
   IBaseFilter* sampleFilter;
   hr = CoCreateInstance( CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
        IID_IBaseFilter, (LPVOID *)&sampleFilter);
   ISampleGrabber* sampleGrabber;
   hr = sampleFilter->QueryInterface(IID_ISampleGrabber, (void**)&sampleGrabber);
  
添加設備Filter和Sample的filter到GraphBuilder對象中,
    hr = graphBuilder->AddFilter(sampleFilter, L"GrabFilter");
    hr = graphBuilder->AddFilter( deviceFilter, L"DeviceFilter"); // deviceFilter 是上邊綁定的攝像頭設備Filter。
接着查詢攝像頭的輸出PIN(攝像頭只有輸出PIN)和SampleGrabber的輸入PIN,經過各自配置之後,把這兩個PIN連接起來。
   IPin* capturePin;  //攝像頭的輸出PIN
   IPin* samplePin;  //採集接口的輸入PIN
   deviceFilter->EnumPins(&pEnum);  pEnum->Next(1, &capturePin, NULL);
   sampleFilter->EnumPins(&pEnum);  pEnum->Next(1, &samplePin, NULL);
然後從攝像頭設備獲取配置信息,
    IAMStreamConfig* config;
    AM_MEDIA_TYPE* found_fmt; /// 從配置中獲取一個攝像頭格式。
    hr = capturePin->QueryInterface(IID_IAMStreamConfig, (void**)&config);
    然後把此格式設置到 SampleGrabber中,
    hr = sampleGrabber->SetMediaType(found_fmt); //這樣兩個PIN對接時候,才能採集到你需要的視頻數據格式。

爲了從SampleGrabber獲取視頻數據,更重要的需要給sampleGrabber設置一個 ISampleGrabberCB 回調接口,
這樣在 ISampleGrabberCB 的BufferCB函數中就能實時的截獲到攝像頭的視頻數據。

最後把capturePin和samplePin 連接起來,整個流程就完成了,
     hr = graphBuilder->Connect(capturePin, samplePin);

如果啓動攝像頭調用 control->Run,如果停止設備調用 control->Stop
啓動攝像頭之後,
ISampleGrabberCB接口的回調函數BufferCB就會被調用,實時的採集攝像頭的視頻數據源。
就爲了採集攝像頭數據,搞得來這麼多繁瑣的步驟,可見dshow的博大精深?!
 
///////////////////

二,電腦內部聲音採集:

實現遠程桌面時候,最多的時候是強調的是如何抓取桌面屏幕數據,然後壓縮傳輸,
而對電腦內部聲音的採集,似乎在遠程桌面實現中需不需要這個功能都無所謂。
可是當我們採用壓縮率和壓縮質量很高的圖像壓縮算法,比如H264等,使得在遠程桌面中觀看視頻或者玩遊戲成爲可能,
這個時候,對電腦內部的聲音採集和傳輸就顯得十分的必要了,否則看到的視頻是啞劇,玩的遊戲也是靜音。

記得很早前實現了虛擬聲卡的驅動(源代碼下載地址: http://download.csdn.net/download/fanxiushu/4975753,)
當初是爲某個公司實現的,主要目的就是爲了採集電腦內部的聲音。
當時的做法是開發了一塊虛擬聲卡驅動,然後安裝到電腦上,然後再把這塊虛擬聲卡設置成系統默認的聲卡,
這樣電腦的聲音都進入了這塊虛擬聲卡,然後在虛擬聲卡驅動內部實現中,
把所有輸入的聲音數據,再轉發到這塊虛擬聲卡的輸出PIN(也就是這塊虛擬聲卡對應的錄音設備),
然後在應用層打開這塊虛擬聲卡的錄音設備,再把截取到的聲音數據
一份再發給真實的聲卡發出聲音,另一份則作爲截獲到的電腦內部聲音數據進行其他處理。
這種做法顯得有些麻煩,因爲在應用層得來回切換聲卡設備,一不留神就搞錯。
但是好在通用性很強,能在 WINXP,WIN7,等各種平臺運行,
尤其是在老的winXP系統,本身不支持聲卡硬件混音的電腦上,這幾乎成了最好的辦法。

回到win7以上的系統來,如果您的電腦是運行win7以上的系統,你會有更加簡單辦法來截取電腦內部聲音。
這同樣得益於windows平臺支持, 否則也要非常麻煩的實現虛擬聲卡來截取電腦內部聲音。
win7以上系統,微軟提供了一個叫 WASAPI 的聲音相關組件,這個組件的其他不談,其中對我們非常有用的,就是WASAPI支持聲音迴環。
簡單說,就是通過WASAPI可以截取電腦內部聲音,而且對於不支持聲卡硬件混音的電腦同樣適用。
WASAPI同樣是以COM組件方式提供,但是比起dshow截獲攝像頭視頻的代碼簡化了不少。

大致僞代碼如下。
    IMMDeviceEnumerator *pEnumerator;
    IMMDevice *pDevice ;
    IAudioClient *pAudioClient ;
    IAudioCaptureClient *pCaptureClient ;
    首先創建 MMDeviceEnumerator 對象,用於枚舉當前電腦的設備
    hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
                 (void **)&pEnumerator);
   
    hr = pEnumerator->GetDefaultAudioEndpoint( eRender, eConsole, &pDevice); //然後獲取當前默認的聲卡設備
    hr = pDevice->Activate( __uuidof(IAudioClient), CLSCTX_ALL, NULL, (void **)&pAudioClient); //獲取活動的聲音客戶端
   
    hr = pAudioClient->GetMixFormat(&pwfx); //獲取當前設備的默認聲音數據格式,然後在做些調整
    pwfx->... //對格式做調整,主要目的是滿足自己想截獲什麼格式的聲音數據。
   
   然後進行初始化 ,
   hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, pwfx, NULL);
   AUDCLNT_STREAMFLAGS_LOOPBACK參數表示,我們要把它初始化成截獲電腦內部聲音。
  最後獲取到 IAudioCaptureClient 接口 用於在循環中截獲聲音數據。
   hr = pAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&pCaptureClient);

以上初始化完成之後,我們只要在循環中,不斷調用
IAudioCaptureClient 相關函數,就能截獲到聲音數據。
大致如下:
      while (!quit) {
        //
        sleep(sleep_msec); /// 停留一段時間
        ///
        packetLength = 0;
        HRESULT hr = pCaptureClient->GetNextPacketSize(&packetLength);
        while (packetLength != 0) {
            BYTE* pData = 0;
            UINT32 num = 0; DWORD flags = 0;
            hr = pCaptureClient->GetBuffer(&pData, &num, &flags, NULL, NULL);
            .......pData就是截獲到的聲音數據。
            .........
           hr = pCaptureClient->ReleaseBuffer(num);
            hr = pCaptureClient->GetNextPacketSize(&packetLength);
        }
       ......
    }

////////////////////////////////////////////////////
三,錄音數據採集
這個就是打開聲卡的錄音設備,然後直接利用標準的WIN32 API函數就能讀取到錄音數據。
錄音採集可以使用waveOut方式,或者採用DirectSound方式,相對來說,都比較簡單。
因此也就不再贅述具體代碼流程。

只是補充的是,在遠程桌面實現中,如上邊的 ”採集電腦內部聲音“ 所說,如果你運行的是win7以上的系統,
可以直接利用 WASAPI 來採集電腦內部聲音,  如果你的系統是winxp,很不幸,沒有wasapi,
只好看聲卡驅動是否有“混音”這一選項,如果有,則需要用到這裏的錄音採集的辦法,因爲“混音”是在聲卡的錄音設備裏邊的。

再上邊說到攝像頭數據採集, 我們很多時候除了採集攝像頭視頻,還需要採集話筒聲音。
因此這種情況下,我們也需要用到錄音採集的辦法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章