CVcam代表的時(shí)capture Filter,模擬硬件設(shè)備钾挟。下面的代碼時(shí)CVCam在obs-vcam中的實(shí)現(xiàn):
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的實(shí)現(xiàn)比較簡單,繼承自CSource, 只需要實(shí)現(xiàn)構(gòu)建方法CVCam::CVCam和代理查詢方法CVCam::NonDelegatingQueryInterface荸百, 其它的需要方法已經(jīng)有CSource提供稽坤,或者在頭文件中定義了。
CSource是source filter的基礎(chǔ)類渔伯,下圖是它的繼承鏈顶霞,CUnknown 實(shí)現(xiàn)了COM的IUNKnown接口,以及轉(zhuǎn)為directshows設(shè)計(jì)的代理查詢機(jī)制咱旱,CBasefilter是所有filter的基礎(chǔ)類确丢。
CVCam構(gòu)造函數(shù)調(diào)用CSource構(gòu)造函數(shù)绷耍,設(shè)置filter的名稱,所屬對象鲜侥,clsid褂始。然后調(diào)用鎖定filter狀態(tài),防止其它方法修改(以外被其它程序調(diào)用,方法結(jié)束后會(huì)自動(dòng)釋放)描函,然后為cvcam創(chuàng)建一個(gè)CVCamStream實(shí)例崎苗,它代表Output pin,并把它加入隊(duì)列里舀寓。m_paStreams實(shí)在csource里聲明的pin數(shù)組胆数。
NonDelegatingQueryInterface時(shí)directshow提供的機(jī)制,用于COM對象的聚合場景. 在這里互墓,我們通過實(shí)現(xiàn)NonDelegatingQueryInterface可以為client提供同一的查詢接口必尼,通過VCAM的查詢接口就可以查詢CVCamStream信息(對應(yīng)的souce filter的 output pin)。
CVCamStream類實(shí)現(xiàn)
CVCamStream的實(shí)現(xiàn)比CVCam復(fù)雜篡撵,它代表的是虛擬攝像機(jī)的輸出端判莉。所有的output pin都繼承自CSourceStream。
CVCamStream作為output pin育谬,在Format 協(xié)商時(shí)需要調(diào)用兩個(gè)方法:
GetMediaType : 從outputpin獲得media type信息券盅。
CheckMediaType: 檢查是否output pin 接受一個(gè)給定的media type.
一個(gè)output pin支持一種或多種Media類型,再協(xié)商的時(shí)候膛檀,下游的input pin 需要從output pin獲得支持的media類型锰镀,這一需求通過GetMediaType來實(shí)現(xiàn)。GetMediaType有兩個(gè)方法:
一個(gè)只支持一個(gè)參數(shù)咖刃,當(dāng)output pin僅支持一種媒體類型泳炉,僅override這個(gè)方法就可以。
virtual HRESULT GetMediaType(
CMediaType *pMediaType
);
另外一個(gè)支持兩個(gè)參數(shù)僵缺,當(dāng)output pin支持多個(gè)媒體類型是胡桃,需要override這個(gè)方法。
virtual HRESULT GetMediaType(
int iPosition,
CMediaType *pMediaType
);
當(dāng)需要實(shí)現(xiàn)第二種GetMediaType方法時(shí)磕潮,同時(shí)需要實(shí)現(xiàn)CheckMediaType翠胰,檢查摸一個(gè)媒體類型是否接受。
以下時(shí)obs-vcam的實(shí)現(xiàn):
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 結(jié)構(gòu)體描述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定義了媒體的通用分類: 視頻轻纪、聲音油额、字節(jié)流等;子類型定義了通用分類下更信息的類型刻帚。AM_MEDIA_TYPE還包含一個(gè)長度可變的數(shù)據(jù)區(qū)域潦嘶,pbFormat指向它,它包含了更確切的格式信息崇众,被稱為format block掂僵。通常用formattype類確定格式信息的類型。更多詳細(xì)的信息可以參考!About Media Types顷歌。
CMediaType管理media type, 它繼承自AM_MEDIA_TYPE锰蓬,它增加了很多方法操作AM_MEDIA_TYPE。
在GetMediaType方法中有兩個(gè)參數(shù)眯漩,第一個(gè)表示meidaType在格式列表的index芹扭,第二個(gè)表示返回的指向mediatype的指針。對于虛擬攝像頭赦抖,這里直接重新構(gòu)造mediaType(而不是直接讀确朊恪)。方法中先聲明一個(gè)類型VIDEOINFOHEADER的指針摹芙,代表AM_MEDIA_TYPE的format block空間。format block包含的一個(gè)BITMAPINFOHEADER ,描述圖像的顏色和為維度信息宛瞄。
填充完mediatype信息后浮禾,便返回成功信息。
當(dāng)getmediatype使用兩個(gè)參數(shù)格式的時(shí)候份汗,ouput pin需要重載CheckMediaType方法盈电,下面時(shí)對應(yīng)的方法:
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主要驗(yàn)證mediaType是否可一個(gè)被pin out接受。在這個(gè)方法里杯活,分別驗(yàn)證了主類型匆帚,子類型,媒體信息格式旁钧,平均幀率吸重,以及有效的高寬值。
除了以上兩個(gè)方法歪今,CSourceStream還包含幾個(gè)重要的方法需要實(shí)現(xiàn)嚎幸。
CBaseOutputPin::DecideBufferSize方法用于設(shè)置 sample buffers的大小(一個(gè)block的媒體數(shù)據(jù)).
HRESULT CVCamStream::DecideBufferSize(IMemAllocator *pAlloc,
ALLOCATOR_PROPERTIES *pProperties)
{
CAutoLock cAutoLock(m_pFilter->pStateLock()); // 鎖定狀態(tài)
HRESULT hr = NOERROR;
VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)m_mt.Format(); //獲取媒體類型信息
pProperties->cBuffers = 1; //設(shè)置buffer個(gè)數(shù)
pProperties->cbBuffer = pvi->bmiHeader.biSizeImage; //buffer大小等于一個(gè)bitmap的大小
ALLOCATOR_PROPERTIES Actual;
hr = pAlloc->SetProperties(pProperties, &Actual); //設(shè)置buffer屬性
if (FAILED(hr)) return hr;
if (Actual.cbBuffer < pProperties->cbBuffer) return E_FAIL;
return NOERROR;
}
pAlloc指向allocator(sample buffer的管理器),ppropInputRequest 指向 ALLOCATOR_PROPERTIES結(jié)構(gòu)信息寄猩,它包含了input pin的buffer需求嫉晶。ALLOCATOR_PROPERTIES的結(jié)構(gòu)如下:
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示例中,一個(gè)allacator 的buffer個(gè)數(shù)為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是最重要的一個(gè)方法箍铭,它代表著虛擬攝像頭如何填充media sample用來向調(diào)用者提供數(shù)據(jù), 下面代碼是Vivek vcam的實(shí)現(xiàn):
//////////////////////////////////////////////////////////////////////////
// This is the routine where we create the data being output by the Virtual
// Camera device.
//////////////////////////////////////////////////////////////////////////
HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
{
REFERENCE_TIME rtNow; //聲明媒體時(shí)間
REFERENCE_TIME avgFrameTime = ((VIDEOINFOHEADER*)m_mt.pbFormat)->AvgTimePerFrame; //平均幀時(shí)間
rtNow = m_rtLastTime; //獲取當(dāng)前媒體時(shí)間, m_rtLastTime用來標(biāo)記下一次的sample其實(shí)時(shí)間
m_rtLastTime += avgFrameTime; //播放完本sample的時(shí)間
pms->SetTime(&rtNow, &m_rtLastTime);//設(shè)置sample的起始播放時(shí)間和結(jié)束播放時(shí)間
pms->SetSyncPoint(TRUE);
BYTE *pData; //聲明數(shù)據(jù)指針
long lDataLen; //聲明數(shù)據(jù)長度
pms->GetPointer(&pData);
lDataLen = pms->GetSize();
for(int i = 0; i < lDataLen; ++i)
pData[i] = rand();
return NOERROR;
} // FillBuffer
Vivek的實(shí)現(xiàn)是隨機(jī)的生成圖像椎镣,IMediaSample 用來設(shè)置和獲取media sample的屬性诈火。 Media Sample 是一個(gè)包含了一段媒體數(shù)據(jù)的COM對象, 它支持在filter之間使用共享內(nèi)存衣陶。
如上柄瑰,filBuffer函數(shù)先獲取媒體的時(shí)間,計(jì)算當(dāng)前sample的媒體起始時(shí)間和結(jié)束時(shí)間剪况,然后通過IMediaSample設(shè)置教沾。SetSyncPoint函數(shù)用來設(shè)置當(dāng)前sample的起始時(shí)間是同步點(diǎn),由生成函數(shù)的的filter來設(shè)置译断,這里虛擬攝像頭需要設(shè)置它授翻;設(shè)置規(guī)則參考MediaSample::SetSyncPoint method. pms->GetPointer(&pData) 從buffer中獲取一個(gè)用于讀寫media sample的指針,獲取完后開始填充它孙咪】疤疲可以看到,在vivek的實(shí)現(xiàn)中翎蹈,只是簡單的使用隨機(jī)函數(shù)生成數(shù)據(jù)淮菠。
相比Vivek的實(shí)現(xiàn),OBS-Vcam 對這個(gè)函數(shù)的實(shí)現(xiàn)就要復(fù)雜一些荤堪,因?yàn)樗臄?shù)據(jù)是來在與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中獲取一個(gè)sample數(shù)據(jù)塊的指針。
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獲取要傳輸?shù)膕ample的格式信息
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) { //循環(huán)從queue中獲取數(shù)據(jù)澄阳,并填充的buffer獲得的sampleblock中
if (get_times >= get_timeout || queue.header->state != OutputReady) //如果超出最大獲取次數(shù)或狀態(tài)不能與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; //真開始時(shí)間
duration = time_perframe; //frame的時(shí)常
} else { //如果沒有獲得數(shù)據(jù)拥知,返回一個(gè)空幀,所有值為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; //幀技術(shù)時(shí)間
prev_end_ts = end_time;
pms->SetTime(&start_time, &end_time);
pms->SetSyncPoint(TRUE);
return NOERROR;
}
OBS-Vcan通過queue與OBS通信獲取幀數(shù)據(jù)碎赢,然后填充給buffer低剔。 填充邏輯和vivek的差不多,唯一不同的在于計(jì)算媒體時(shí)間上肮塞,以及處理異常襟齿。
總的來書,fillbuffer需要做下面的幾件事:
- 從sample buffer獲取新的sample地址峦嗤,
- 填充sample數(shù)據(jù)
- 設(shè)置sample的媒體開始時(shí)間和結(jié)束時(shí)間蕊唐。
- 設(shè)置同步點(diǎn)狀態(tài)。
除了以上的方法外烁设,CVCamStream在實(shí)現(xiàn)output pin的過程中替梨,同時(shí)可以負(fù)載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會(huì)使用工作線程來與下游filter通信钓试,所以可以在線程開始于結(jié)束時(shí)進(jìn)行資源的初始化和釋放。
CVCamStream除了繼承CSourceStream類以外副瀑,還實(shí)現(xiàn)了IAMStreamConfig接口:
//////////////////////////////////////////////////////////////////////////
// IAMStreamConfig
//////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE SetFormat(AM_MEDIA_TYPE *pmt); //設(shè)置格式
HRESULT STDMETHODCALLTYPE GetFormat(AM_MEDIA_TYPE **ppmt); //獲取當(dāng)前或prefered的輸出格式
HRESULT STDMETHODCALLTYPE GetNumberOfCapabilities(int *piCount, int *piSize); //output pin 支持的格式數(shù)量
HRESULT STDMETHODCALLTYPE GetStreamCaps(int iIndex, AM_MEDIA_TYPE **pmt,
BYTE *pSCC);// 獲取支持的格式集合
IAMStreamConfig 用來設(shè)置capture 的輸出格式弓熏。應(yīng)用程序可以使用這個(gè)接口設(shè)置格式屬性,例如幀速率糠睡,sample rate等挽鞠。
下面時(shí)OBS-vcam 設(shè)置Format的實(shí)現(xiàn):
HRESULT STDMETHODCALLTYPE CVCamStream::SetFormat(AM_MEDIA_TYPE *pmt)
{
if (pmt == nullptr)
return E_FAIL;
if (parent->GetState() != State_Stopped) //僅當(dāng)filter為停止?fàn)顟B(tài)時(shí)才允許設(shè)置
return E_FAIL;
if (CheckMediaType((CMediaType *)pmt) != S_OK) //檢查pmt的格式是否在output pin支持的范圍內(nèi)
return E_FAIL;
VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)(pmt->pbFormat);
m_mt.SetFormat(m_mt.Format(), sizeof(VIDEOINFOHEADER));//更新當(dāng)前pin的格式信息
format_list.push_front(struct format(pvi->bmiHeader.biWidth,
pvi->bmiHeader.biHeight, pvi->AvgTimePerFrame)); //將新格式加入format list列表中
IPin* pin;
ConnectedTo(&pin); //重連pin, 讓新設(shè)置起效
if (pin){
IFilterGraph *pGraph = parent->GetGraph();
pGraph->Reconnect(this);
}
return S_OK;
}
m_mt 來源于CBasePin, 用來維護(hù)當(dāng)前pin鏈接的媒體類型信息狈孔。format_list時(shí)CVCamStream維護(hù)的一個(gè)foramt數(shù)組信认,它可以用ListSupportFormat函數(shù)驚醒初始化:
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先驗(yàn)證新的format均抽,如果正常存入當(dāng)前format列表嫁赏,然后更新當(dāng)前format, 重連pin和filter.
GetFormat和GetNumberOfCapabilities函數(shù)比較簡單,一個(gè)返回當(dāng)前的media type油挥, 一個(gè)獲取支持格式的數(shù)量潦蝇。,如下:
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稍微復(fù)雜一點(diǎn)深寥,需要根據(jù)支持format的index構(gòu)造media type和VIDEO_STREAM_CONFIG_CAPS結(jié)構(gòu)體:
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); //創(chuàng)建media type,
DECLARE_PTR(VIDEOINFOHEADER, pvi, (*pmt)->pbFormat); //創(chuàng)建結(jié)構(gòu)體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); //創(chuàng)建結(jié)構(gòu)體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同時(shí)實(shí)現(xiàn)了IAMStreamConfig接口攘乒,用于對output pin對應(yīng)的設(shè)備屬性進(jìn)行操作:
//////////////////////////////////////////////////////////////////////////
// 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方法主要用來根據(jù)屬性set的GUID和屬性的ID設(shè)置屬性值,但是對于虛擬攝像頭惋鹅,我們沒有特別的屬性需要設(shè)置:
HRESULT CVCamStream::Set(REFGUID guidPropSet, DWORD dwID, void *pInstanceData,
DWORD cbInstanceData, void *pPropData, DWORD cbPropData)
{// Set: Cannot set any properties.
return E_NOTIMPL;
}
Get方法用于獲取屬性则酝,在實(shí)現(xiàn)中,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用來確定是否支持某個(gè)屬性集合闰集,它返回一個(gè)支持flag, 這里總返回Get, 表示只能獲取堤魁,不能設(shè)置:
// 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;
}
兩個(gè)vcam對IKsPropertySet的實(shí)現(xiàn)都是一樣的,對于新的虛擬接口可以直接拿來用返十。
最后,vcam在Notify的實(shí)現(xiàn)中椭微,直接返回錯(cuò)誤洞坑,用于忽略來自于下游的質(zhì)量消息。
//
// Notify
// Ignore quality management messages sent from the downstream filter
STDMETHODIMP CVCamStream::Notify(IBaseFilter * pSender, Quality q)
{
return E_NOTIMPL;
} // Notify
可以看到蝇率,對于新寫一個(gè)虛擬攝像頭來說迟杂,需要詳細(xì)的考慮支持的媒體類型,和如何填寫數(shù)據(jù)本慕;其它的方法排拷,兩個(gè)vcam都給了很好的實(shí)現(xiàn)例子。