当然你可以直接用现成的虚拟摄像头软件实现这个功能。不过当初我开发这个插件的原因是,需要在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函数完成注册。
主逻辑
在头文件中,我们需要声明两个类
代码语言:javascript复制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类的实例
代码语言:javascript复制m_paStreams = (CSourceStream **) new CVCamStream*[1];
m_paStreams[0] = new CVCamStream(phr, this, L"Flex COM");
在实现COM接口的QueryInterface函数中,我们调用了CVCamStream类的QueryInterface
代码语言:javascript复制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);
}
定义媒体类型
代码语言:javascript复制HRESULT CVCamStream::GetMediaType(int iPosition, CMediaType *pmt)
在这个函数中,我们配置了媒体的具体的格式参数,比如24位RGB格式,图像的宽高等等。 另外还需要对GetStreamCaps函数进行实现,配置媒体的格式。
代码语言:javascript复制HRESULT STDMETHODCALLTYPE CVCamStream::GetStreamCaps(int iIndex, AM_MEDIA_TYPE **pmt, BYTE *pSCC)
捕获桌面
系统会调用FillBuffer函数,在这个函数中,我们将捕获到的数据填充到缓冲里面,Direct Show会处理剩下的事情。
代码语言:javascript复制HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
捕获桌面只需要用到一个函数CopyScreenToBitmap
代码语言:javascript复制HANDLE hDib = CopyScreenToBitmap(&ScreenRect, pData, (BITMAPINFO *)&(pVih->bmiHeader), m_hCursor);
if (hDib) DeleteObject(hDib);
pData是我们定义的一个指针,通过下面的代码,我们的pData就指向了缓存,数据填充到pData指向的内存中。
代码语言:javascript复制BYTE *pData;
pms->GetPointer(&pData);
进阶
实际产品会有很多需求,光实现捕获桌面是远远不够的,我们需要对这个捕获进行控制,比如捕获制定区域,停止捕获,恢复捕获等等。那么就涉及到和COM进行通讯了。 我们可以通过VS的窗口设计器创建一个windows窗口,然后提供一个用户操作界面。
如何响应这个窗口的用户操作呢? 通过windows消息
代码语言:javascript复制INT_PTR CALLBACK WindowMessage(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
当然最关键是需要在Flash产品的程序里面唤起这个窗口,需要用的socket编程。
代码语言:javascript复制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