CVcam与CVCamStream实现类
CVcam代表的时capture Filter,模拟硬件设备。下面的代码时CVCam在obs-vcam中的实现:
CVCam::CVCam(LPUNKNOWN lpunk, HRESULT *phr, const GUID id, int mode) :
CSource(NAME("OBS Virtual CAM"), lpunk, id)
{
ASSERT(phr);
CAutoLock cAutoLock(&m_cStateLock);
m_paStreams = (CSourceStream **) new CVCamStream*[1];
stream = new CVCamStream(phr, this, L"Video", mode);
m_paStreams[0] = stream;
}
HRESULT CVCam::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
if (riid == _uuidof(IAMStreamConfig) || riid == _uuidof(IKsPropertySet))
return m_paStreams[0]->QueryInterface(riid, ppv);
else
return CSource::NonDelegatingQueryInterface(riid, ppv);
}
可以看到filter的实现比较简单,继承自CSource, 只需要实现构建方法CVCam::CVCam和代理查询方法CVCam::NonDelegatingQueryInterface, 其它的需要方法已经有CSource提供,或者在头文件中定义了。
CSource是source filter的基础类,下图是它的继承链,CUnknown 实现了COM的IUNKnown接口,以及转为directshows设计的代理查询机制,CBasefilter是所有filter的基础类。
CVCam构造函数调用CSource构造函数,设置filter的名称,所属对象,clsid。然后调用锁定filter状态,防止其它方法修改(以外被其它程序调用,方法结束后会自动释放),然后为cvcam创建一个CVCamStream实例,它代表Output pin,并把它加入队列里。m_paStreams实在csource里声明的pin数组。
NonDelegatingQueryInterface时directshow提供的机制,用于COM对象的聚合场景. 在这里,我们通过实现NonDelegatingQueryInterface可以为client提供同一的查询接口,通过VCAM的查询接口就可以查询CVCamStream信息(对应的souce filter的 output pin)。
CVCamStream类实现
CVCamStream的实现比CVCam复杂,它代表的是虚拟摄像机的输出端。所有的output pin都继承自CSourceStream。
CVCamStream作为output pin,在Format 协商时需要调用两个方法:
GetMediaType : 从outputpin获得media type信息。
CheckMediaType: 检查是否output pin 接受一个给定的media type.
一个output pin支持一种或多种Media类型,再协商的时候,下游的input pin 需要从output pin获得支持的media类型,这一需求通过GetMediaType来实现。GetMediaType有两个方法:
一个只支持一个参数,当output pin仅支持一种媒体类型,仅override这个方法就可以。
virtual HRESULT GetMediaType(
CMediaType *pMediaType
);
另外一个支持两个参数,当output pin支持多个媒体类型是,需要override这个方法。
virtual HRESULT GetMediaType(
int iPosition,
CMediaType *pMediaType
);
当需要实现第二种GetMediaType方法时,同时需要实现CheckMediaType,检查摸一个媒体类型是否接受。
以下时obs-vcam的实现:
HRESULT CVCamStream::GetMediaType(int iPosition,CMediaType *pmt)
{
if (format_list.size() == 0)
ListSupportFormat(); //填充格式列表
if (iPosition < 0 || iPosition > format_list.size()-1) //异常的格式列表
return E_INVALIDARG;
DECLARE_PTR(VIDEOINFOHEADER, pvi,
pmt->AllocFormatBuffer(sizeof(VIDEOINFOHEADER)));
ZeroMemory(pvi, sizeof(VIDEOINFOHEADER)); // 为format block分配空间,类型为VIDEOINFOHEADER,并让pvi指向它。
//填充format block,BITMAPINFOHEADER(bmiHeader)包含颜色和维度信息。
pvi->bmiHeader.biWidth = format_list[iPosition].width; //帧宽度
pvi->bmiHeader.biHeight = format_list[iPosition].height; //帧高度
pvi->AvgTimePerFrame = format_list[iPosition].time_per_frame; //帧速率
pvi->bmiHeader.biCompression = MAKEFOURCC('Y', 'U', 'Y', '2'); //for compressed video and YUV formats
pvi->bmiHeader.biBitCount = 16; //Specifies the number of bits per pixel
pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); //the number of bytes required by the structure.
pvi->bmiHeader.biPlanes = 1;
pvi->bmiHeader.biSizeImage = pvi->bmiHeader.biWidth *
pvi->bmiHeader.biHeight * 2; //image 大小
pvi->bmiHeader.biClrImportant = 0; //color重要性
SetRectEmpty(&(pvi->rcSource)); //A [RECT](https://docs.microsoft.com/en-us/windows/desktop/api/windef/ns-windef-rect) structure that specifies the source video window
SetRectEmpty(&(pvi->rcTarget)); //pecifies the destination video window.
pmt->SetType(&MEDIATYPE_Video);
pmt->SetFormatType(&FORMAT_VideoInfo);
pmt->SetTemporalCompression(FALSE);
pmt->SetSubtype(&MEDIASUBTYPE_YUY2);
pmt->SetSampleSize(pvi->bmiHeader.biSizeImage);
return NOERROR;
}
在directshow中,使用AM_MEDIA_TYPE 结构体描述media sample.
typedef struct _AMMediaType {
GUID majortype;
GUID subtype;
BOOL bFixedSizeSamples;
BOOL bTemporalCompression;
ULONG lSampleSize;
GUID formattype;
IUnknown *pUnk;
ULONG cbFormat;
BYTE *pbFormat;
} AM_MEDIA_TYPE
majortype, subtype用来描述media 类型的主类型和子类型,媒体类型的介绍参考Media Types. Major type定义了媒体的通用分类: 视频、声音、字节流等;子类型定义了通用分类下更信息的类型。AM_MEDIA_TYPE还包含一个长度可变的数据区域,pbFormat指向它,它包含了更确切的格式信息,被称为format block。通常用formattype类确定格式信息的类型。更多详细的信息可以参考!About Media Types。
CMediaType管理media type, 它继承自AM_MEDIA_TYPE,它增加了很多方法操作AM_MEDIA_TYPE。
在GetMediaType方法中有两个参数,第一个表示meidaType在格式列表的index,第二个表示返回的指向mediatype的指针。对于虚拟摄像头,这里直接重新构造mediaType(而不是直接读取)。方法中先声明一个类型VIDEOINFOHEADER的指针,代表AM_MEDIA_TYPE的format block空间。format block包含的一个BITMAPINFOHEADER ,描述图像的颜色和为维度信息。
填充完mediatype信息后,便返回成功信息。
当getmediatype使用两个参数格式的时候,ouput pin需要重载CheckMediaType方法,下面时对应的方法:
HRESULT CVCamStream::CheckMediaType(const CMediaType *pMediaType)
{
if (pMediaType == nullptr)
return E_FAIL;
VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)(pMediaType->Format());
const GUID* type = pMediaType->Type();
const GUID* info = pMediaType->FormatType();
const GUID* subtype = pMediaType->Subtype();
if (*type != MEDIATYPE_Video)
return E_INVALIDARG;
if (*info != FORMAT_VideoInfo)
return E_INVALIDARG;
if (*subtype != MEDIASUBTYPE_YUY2)
return E_INVALIDARG;
if (pvi->AvgTimePerFrame < 166666 || pvi->AvgTimePerFrame >1000000)
return E_INVALIDARG;
if (ValidateResolution(pvi->bmiHeader.biWidth, pvi->bmiHeader.biHeight))
return S_OK;
return E_INVALIDARG;
}
CheckMediaType主要验证mediaType是否可一个被pin out接受。在这个方法里,分别验证了主类型,子类型,媒体信息格式,平均帧率,以及有效的高宽值。
除了以上两个方法,CSourceStream还包含几个重要的方法需要实现。
CBaseOutputPin::DecideBufferSize方法用于设置 sample buffers的大小(一个block的媒体数据).
HRESULT CVCamStream::DecideBufferSize(IMemAllocator *pAlloc,
ALLOCATOR_PROPERTIES *pProperties)
{
CAutoLock cAutoLock(m_pFilter->pStateLock()); // 锁定状态
HRESULT hr = NOERROR;
VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)m_mt.Format(); //获取媒体类型信息
pProperties->cBuffers = 1; //设置buffer个数
pProperties->cbBuffer = pvi->bmiHeader.biSizeImage; //buffer大小等于一个bitmap的大小
ALLOCATOR_PROPERTIES Actual;
hr = pAlloc->SetProperties(pProperties, &Actual); //设置buffer属性
if (FAILED(hr)) return hr;
if (Actual.cbBuffer < pProperties->cbBuffer) return E_FAIL;
return NOERROR;
}
pAlloc指向allocator(sample buffer的管理器),ppropInputRequest 指向 ALLOCATOR_PROPERTIES结构信息,它包含了input pin的buffer需求。ALLOCATOR_PROPERTIES的结构如下:
typedef struct _AllocatorProperties {
long cBuffers; //Number of buffers created by the allocator.
long cbBuffer; //Size of each buffer in bytes, excluding any prefix.
long cbAlign; //Alignment of the buffer
long cbPrefix; //Each buffer is preceded by a prefix of this many bytes
} ALLOCATOR_PROPERTIES;
在OBS-vcam示例中,一个allacator 的buffer个数为1, 大小等于一帧的大小。
SetMediaType主要用来更新output pin的媒体类型:
//This is called when the output format has been negotiated
HRESULT CVCamStream::SetMediaType(const CMediaType *pmt)
{
DECLARE_PTR(VIDEOINFOHEADER, pvi, pmt->Format());
HRESULT hr = CSourceStream::SetMediaType(pmt);
return hr;
}
而fillBuffer是最重要的一个方法,它代表着虚拟摄像头如何填充media sample用来向调用者提供数据, 下面代码是Vivek vcam的实现:
//////////////////////////////////////////////////////////////////////////
// This is the routine where we create the data being output by the Virtual
// Camera device.
//////////////////////////////////////////////////////////////////////////
HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
{
REFERENCE_TIME rtNow; //声明媒体时间
REFERENCE_TIME avgFrameTime = ((VIDEOINFOHEADER*)m_mt.pbFormat)->AvgTimePerFrame; //平均帧时间
rtNow = m_rtLastTime; //获取当前媒体时间, m_rtLastTime用来标记下一次的sample其实时间
m_rtLastTime += avgFrameTime; //播放完本sample的时间
pms->SetTime(&rtNow, &m_rtLastTime);//设置sample的起始播放时间和结束播放时间
pms->SetSyncPoint(TRUE);
BYTE *pData; //声明数据指针
long lDataLen; //声明数据长度
pms->GetPointer(&pData);
lDataLen = pms->GetSize();
for(int i = 0; i < lDataLen; ++i)
pData[i] = rand();
return NOERROR;
} // FillBuffer
Vivek的实现是随机的生成图像,IMediaSample 用来设置和获取media sample的属性。 Media Sample 是一个包含了一段媒体数据的COM对象, 它支持在filter之间使用共享内存。
如上,filBuffer函数先获取媒体的时间,计算当前sample的媒体起始时间和结束时间,然后通过IMediaSample设置。SetSyncPoint函数用来设置当前sample的起始时间是同步点,由生成函数的的filter来设置,这里虚拟摄像头需要设置它;设置规则参考MediaSample::SetSyncPoint method. pms->GetPointer(&pData) 从buffer中获取一个用于读写media sample的指针,获取完后开始填充它。可以看到,在vivek的实现中,只是简单的使用随机函数生成数据。
相比Vivek的实现,OBS-Vcam 对这个函数的实现就要复杂一些,因为他的数据是来在与OBS的流。
HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
{
HRESULT hr;
bool get_sample = false;
int get_times = 0;
uint64_t timestamp = 0;
uint64_t current_time = 0;
REFERENCE_TIME start_time = 0;
REFERENCE_TIME end_time = 0;
REFERENCE_TIME duration = 0;
hr = pms->GetPointer((BYTE**)&dst); //从buffer中获取一个sample数据块的指针。
if (system_start_time <= 0)
system_start_time = get_current_time();
else
current_time = get_current_time(system_start_time);
if (!queue.hwnd) { //obs queue操作
if (shared_queue_open(&queue, queue_mode)) {
shared_queue_get_video_format(queue_mode, &format, &frame_width,
&frame_height, &time_perframe); //通过QUEUE获取要传输的sample的格式信息
SetConvertContext();
SetGetTimeout();
sync_timeout = 0;
}
}
if (sync_timeout <= 0) {
SetSyncTimeout();
}
else if (prev_end_ts >current_time) {
sleepto(prev_end_ts, system_start_time);
}
else if (current_time - prev_end_ts > sync_timeout) {
if(queue.header)
share_queue_init_index(&queue);
else
prev_end_ts = current_time;
}
while (queue.header && !get_sample) { //循环从queue中获取数据,并填充的buffer获得的sampleblock中
if (get_times >= get_timeout || queue.header->state != OutputReady) //如果超出最大获取次数或状态不能与ready就推出
break;
get_sample = shared_queue_get_video(&queue, &scale_info, dst,
×tamp);
if (!get_sample) {
Sleep(SLEEP_DURATION);
get_times++;
}
}
if (get_sample && !obs_start_ts) {
obs_start_ts = timestamp;
dshow_start_ts = prev_end_ts;
}
if (get_sample) {
start_time = dshow_start_ts + (timestamp - obs_start_ts) / 100; //真开始时间
duration = time_perframe; //frame的时常
} else { //如果没有获得数据,返回一个空帧,所有值为127
int size = pms->GetActualDataLength();
memset(dst, 127, size);
start_time = prev_end_ts;
duration = ((VIDEOINFOHEADER*)m_mt.pbFormat)->AvgTimePerFrame;
}
if (queue.header && queue.header->state == OutputStop || get_times > 20) {
shared_queue_read_close(&queue, &scale_info);
dshow_start_ts = 0;
obs_start_ts = 0;
sync_timeout = 0;
}
end_time = start_time + duration; //帧技术时间
prev_end_ts = end_time;
pms->SetTime(&start_time, &end_time);
pms->SetSyncPoint(TRUE);
return NOERROR;
}
OBS-Vcan通过queue与OBS通信获取帧数据,然后填充给buffer。 填充逻辑和vivek的差不多,唯一不同的在于计算媒体时间上,以及处理异常。
总的来书,fillbuffer需要做下面的几件事:
- 从sample buffer获取新的sample地址,
- 填充sample数据
- 设置sample的媒体开始时间和结束时间。
- 设置同步点状态。
除了以上的方法外,CVCamStream在实现output pin的过程中,同时可以负载CSourceStream提供的对工作线程的初始化与释放方法:
HRESULT CVCamStream::OnThreadCreate()
{
prev_end_ts = 0;
obs_start_ts = 0;
dshow_start_ts = 0;
system_start_time = 0;
return NOERROR;
}
HRESULT CVCamStream::OnThreadDestroy()
{
if (queue.header)
shared_queue_read_close(&queue, &scale_info);
return NOERROR;
}
CVCamStream会使用工作线程来与下游filter通信,所以可以在线程开始于结束时进行资源的初始化和释放。
CVCamStream除了继承CSourceStream类以外,还实现了IAMStreamConfig接口:
//////////////////////////////////////////////////////////////////////////
// IAMStreamConfig
//////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE SetFormat(AM_MEDIA_TYPE *pmt); //设置格式
HRESULT STDMETHODCALLTYPE GetFormat(AM_MEDIA_TYPE **ppmt); //获取当前或prefered的输出格式
HRESULT STDMETHODCALLTYPE GetNumberOfCapabilities(int *piCount, int *piSize); //output pin 支持的格式数量
HRESULT STDMETHODCALLTYPE GetStreamCaps(int iIndex, AM_MEDIA_TYPE **pmt,
BYTE *pSCC);// 获取支持的格式集合
IAMStreamConfig 用来设置capture 的输出格式。应用程序可以使用这个接口设置格式属性,例如帧速率,sample rate等。
下面时OBS-vcam 设置Format的实现:
HRESULT STDMETHODCALLTYPE CVCamStream::SetFormat(AM_MEDIA_TYPE *pmt)
{
if (pmt == nullptr)
return E_FAIL;
if (parent->GetState() != State_Stopped) //仅当filter为停止状态时才允许设置
return E_FAIL;
if (CheckMediaType((CMediaType *)pmt) != S_OK) //检查pmt的格式是否在output pin支持的范围内
return E_FAIL;
VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)(pmt->pbFormat);
m_mt.SetFormat(m_mt.Format(), sizeof(VIDEOINFOHEADER));//更新当前pin的格式信息
format_list.push_front(struct format(pvi->bmiHeader.biWidth,
pvi->bmiHeader.biHeight, pvi->AvgTimePerFrame)); //将新格式加入format list列表中
IPin* pin;
ConnectedTo(&pin); //重连pin, 让新设置起效
if (pin){
IFilterGraph *pGraph = parent->GetGraph();
pGraph->Reconnect(this);
}
return S_OK;
}
m_mt 来源于CBasePin, 用来维护当前pin链接的媒体类型信息。format_list时CVCamStream维护的一个foramt数组,它可以用ListSupportFormat函数惊醒初始化:
bool CVCamStream::ListSupportFormat()
{
if (format_list.size() > 0)
format_list.empty();
format_list.push_back(struct format(1920, 1080, 333333));
format_list.push_back(struct format(1280, 720, 333333));
format_list.push_back(struct format(960, 540, 333333));
format_list.push_back(struct format(640, 360, 333333));
return true;
}
可以很容易看明白,SetFormat先验证新的format,如果正常存入当前format列表,然后更新当前format, 重连pin和filter.
GetFormat和GetNumberOfCapabilities函数比较简单,一个返回当前的media type, 一个获取支持格式的数量。,如下:
HRESULT STDMETHODCALLTYPE CVCamStream::GetFormat(AM_MEDIA_TYPE **ppmt)
{
*ppmt = CreateMediaType(&m_mt);
return S_OK;
}
HRESULT STDMETHODCALLTYPE CVCamStream::GetNumberOfCapabilities(int *piCount,
int *piSize)
{
if (format_list.size() == 0)
ListSupportFormat();
*piCount = format_list.size();
*piSize = sizeof(VIDEO_STREAM_CONFIG_CAPS);
return S_OK;
}
GetStreamCaps稍微复杂一点,需要根据支持format的index构造media type和VIDEO_STREAM_CONFIG_CAPS结构体:
HRESULT STDMETHODCALLTYPE CVCamStream::GetStreamCaps(int iIndex,
AM_MEDIA_TYPE **pmt, BYTE *pSCC)
{
if (format_list.size() == 0)
ListSupportFormat();
if (iIndex < 0 || iIndex > format_list.size() - 1)
return E_INVALIDARG;
*pmt = CreateMediaType(&m_mt); //创建media type,
DECLARE_PTR(VIDEOINFOHEADER, pvi, (*pmt)->pbFormat); //创建结构体VIDEOINFOHEADER指针 pvi
//填充pvi
pvi->bmiHeader.biWidth = format_list[iIndex].width;
pvi->bmiHeader.biHeight = format_list[iIndex].height;
pvi->AvgTimePerFrame = format_list[iIndex].time_per_frame;
pvi->AvgTimePerFrame = 333333;
pvi->bmiHeader.biCompression = MAKEFOURCC('Y', 'U', 'Y', '2');
pvi->bmiHeader.biBitCount = 16;
pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pvi->bmiHeader.biPlanes = 1;
pvi->bmiHeader.biSizeImage = pvi->bmiHeader.biWidth *
pvi->bmiHeader.biHeight * 2;
pvi->bmiHeader.biClrImportant = 0;
SetRectEmpty(&(pvi->rcSource));
SetRectEmpty(&(pvi->rcTarget));
//填充Media type
(*pmt)->majortype = MEDIATYPE_Video;
(*pmt)->subtype = MEDIASUBTYPE_YUY2;
(*pmt)->formattype = FORMAT_VideoInfo;
(*pmt)->bTemporalCompression = FALSE;
(*pmt)->bFixedSizeSamples = FALSE;
(*pmt)->lSampleSize = pvi->bmiHeader.biSizeImage;
(*pmt)->cbFormat = sizeof(VIDEOINFOHEADER);
DECLARE_PTR(VIDEO_STREAM_CONFIG_CAPS, pvscc, pSCC); //创建结构体VIDEO_STREAM_CONFIG_CAPS指针 pvscc
//填充pvscc
pvscc->guid = FORMAT_VideoInfo;
pvscc->VideoStandard = AnalogVideo_None;
pvscc->InputSize.cx = pvi->bmiHeader.biWidth;
pvscc->InputSize.cy = pvi->bmiHeader.biHeight;
pvscc->MinCroppingSize.cx = pvi->bmiHeader.biWidth;
pvscc->MinCroppingSize.cy = pvi->bmiHeader.biHeight;
pvscc->MaxCroppingSize.cx = pvi->bmiHeader.biWidth;
pvscc->MaxCroppingSize.cy = pvi->bmiHeader.biHeight;
pvscc->CropGranularityX = pvi->bmiHeader.biWidth;
pvscc->CropGranularityY = pvi->bmiHeader.biHeight;
pvscc->CropAlignX = 0;
pvscc->CropAlignY = 0;
pvscc->MinOutputSize.cx = pvi->bmiHeader.biWidth;
pvscc->MinOutputSize.cy = pvi->bmiHeader.biHeight;
pvscc->MaxOutputSize.cx = pvi->bmiHeader.biWidth;
pvscc->MaxOutputSize.cy = pvi->bmiHeader.biHeight;
pvscc->OutputGranularityX = 0;
pvscc->OutputGranularityY = 0;
pvscc->StretchTapsX = 0;
pvscc->StretchTapsY = 0;
pvscc->ShrinkTapsX = 0;
pvscc->ShrinkTapsY = 0;
pvscc->MinFrameInterval = pvi->AvgTimePerFrame;
pvscc->MaxFrameInterval = pvi->AvgTimePerFrame;
pvscc->MinBitsPerSecond = pvi->bmiHeader.biWidth * pvi->bmiHeader.biHeight
* 2 * 8 * (10000000 / pvi->AvgTimePerFrame);
pvscc->MaxBitsPerSecond = pvi->bmiHeader.biWidth * pvi->bmiHeader.biHeight
* 2 * 8 * (10000000 / pvi->AvgTimePerFrame);
return S_OK;
}
CVCamStream同时实现了IAMStreamConfig接口,用于对output pin对应的设备属性进行操作:
//////////////////////////////////////////////////////////////////////////
// IKsPropertySet
//////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE Set(REFGUID guidPropSet, DWORD dwID, void *pInstanceData, DWORD cbInstanceData, void *pPropData, DWORD cbPropData);
HRESULT STDMETHODCALLTYPE Get(REFGUID guidPropSet, DWORD dwPropID, void *pInstanceData,DWORD cbInstanceData, void *pPropData, DWORD cbPropData, DWORD *pcbReturned);
HRESULT STDMETHODCALLTYPE QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport);
Set方法主要用来根据属性set的GUID和属性的ID设置属性值,但是对于虚拟摄像头,我们没有特别的属性需要设置:
HRESULT CVCamStream::Set(REFGUID guidPropSet, DWORD dwID, void *pInstanceData,
DWORD cbInstanceData, void *pPropData, DWORD cbPropData)
{// Set: Cannot set any properties.
return E_NOTIMPL;
}
Get方法用于获取属性,在实现中,vcam返回pin的类型:
// Get: Return the pin category (our only property).
HRESULT CVCamStream::Get(
REFGUID guidPropSet, // Which property set.
DWORD dwPropID, // Which property in that set.
void *pInstanceData, // Instance data (ignore).
DWORD cbInstanceData, // Size of the instance data (ignore).
void *pPropData, // Buffer to receive the property data.
DWORD cbPropData, // Size of the buffer.
DWORD *pcbReturned // Return the size of the property.
)
{
if (guidPropSet != AMPROPSETID_Pin) return E_PROP_SET_UNSUPPORTED;
if (dwPropID != AMPROPERTY_PIN_CATEGORY) return E_PROP_ID_UNSUPPORTED;
if (pPropData == NULL && pcbReturned == NULL) return E_POINTER;
if (pcbReturned) *pcbReturned = sizeof(GUID);
if (pPropData == NULL) return S_OK; // Caller just wants to know the size.
if (cbPropData < sizeof(GUID)) return E_UNEXPECTED;// The buffer is too small.
*(GUID *)pPropData = PIN_CATEGORY_CAPTURE;
return S_OK;
}
QuerySupported用来确定是否支持某个属性集合,它返回一个支持flag, 这里总返回Get, 表示只能获取,不能设置:
// QuerySupported: Query whether the pin supports the specified property.
HRESULT CVCamStream::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
if (guidPropSet != AMPROPSETID_Pin) return E_PROP_SET_UNSUPPORTED;
if (dwPropID != AMPROPERTY_PIN_CATEGORY) return E_PROP_ID_UNSUPPORTED;
// We support getting this property, but not setting it.
if (pTypeSupport) *pTypeSupport = KSPROPERTY_SUPPORT_GET;
return S_OK;
}
两个vcam对IKsPropertySet的实现都是一样的,对于新的虚拟接口可以直接拿来用。
最后,vcam在Notify的实现中,直接返回错误,用于忽略来自于下游的质量消息。
//
// Notify
// Ignore quality management messages sent from the downstream filter
STDMETHODIMP CVCamStream::Notify(IBaseFilter * pSender, Quality q)
{
return E_NOTIMPL;
} // Notify
可以看到,对于新写一个虚拟摄像头来说,需要详细的考虑支持的媒体类型,和如何填写数据;其它的方法,两个vcam都给了很好的实现例子。