Windows远程桌面实现之三(电脑内部声音采集,录音采集,摄像头视频采集)

来源:互联网 发布:vb中default是什么意思 编辑:程序博客网 时间:2024/05/17 05:04

                                                                                                  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,
只好看声卡驱动是否有“混音”这一选项,如果有,则需要用到这里的录音采集的办法,因为“混音”是在声卡的录音设备里边的。

再上边说到摄像头数据采集, 我们很多时候除了采集摄像头视频,还需要采集话筒声音。
因此这种情况下,我们也需要用到录音采集的办法。
原创粉丝点击