CVcam與CVCamStream實(shí)現(xiàn)類

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ǔ)類确丢。

image.png

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, 
            &timestamp);

        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需要做下面的幾件事:

  1. 從sample buffer獲取新的sample地址峦嗤,
  2. 填充sample數(shù)據(jù)
  3. 設(shè)置sample的媒體開始時(shí)間和結(jié)束時(shí)間蕊唐。
  4. 設(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)例子。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锅尘,一起剝皮案震驚了整個(gè)濱河市监氢,隨后出現(xiàn)的幾起案子布蔗,更是在濱河造成了極大的恐慌,老刑警劉巖浪腐,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纵揍,死亡現(xiàn)場離奇詭異,居然都是意外死亡议街,警方通過查閱死者的電腦和手機(jī)泽谨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來特漩,“玉大人吧雹,你說我怎么就攤上這事⊥可恚” “怎么了雄卷?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長访得。 經(jīng)常有香客問我龙亲,道長,這世上最難降的妖魔是什么悍抑? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任鳄炉,我火速辦了婚禮,結(jié)果婚禮上搜骡,老公的妹妹穿的比我還像新娘拂盯。我一直安慰自己,他們只是感情好记靡,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布谈竿。 她就那樣靜靜地躺著,像睡著了一般摸吠。 火紅的嫁衣襯著肌膚如雪空凸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天寸痢,我揣著相機(jī)與錄音呀洲,去河邊找鬼。 笑死啼止,一個(gè)胖子當(dāng)著我的面吹牛道逗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播献烦,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼滓窍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了巩那?” 一聲冷哼從身側(cè)響起吏夯,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤此蜈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后锦亦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舶替,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年杠园,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了顾瞪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抛蚁,死狀恐怖陈醒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞧甩,我是刑警寧澤钉跷,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站肚逸,受9級特大地震影響爷辙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜朦促,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一膝晾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧务冕,春花似錦血当、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至箩退,卻和暖如春离熏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戴涝。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工撤奸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喊括。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像矢棚,于是被迫代替她去往敵國和親郑什。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 下面是OBS-VirtualCam項(xiàng)目結(jié)構(gòu)與Vivek‘s VCam項(xiàng)目結(jié)構(gòu)的對比蒲肋,OBS-VirtualCam比...
    Charles_linzc閱讀 1,730評論 0 0
  • Windows COM使用注冊表來注冊COM組件蘑拯, director fitler遵從COM開發(fā)規(guī)范钝满,所以也需要注...
    Charles_linzc閱讀 726評論 0 0
  • 本文內(nèi)容翻譯自微軟開發(fā)文檔[https://docs.microsoft.com/zh-cn/windows/wi...
    axiuluo70閱讀 253評論 0 0
  • 本文是對此文[https://docs.microsoft.com/zh-cn/windows/win32/dir...
    離原春草閱讀 1,348評論 0 2
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者,不喜歡去冒險(xiǎn)申窘,但是人生放棄了冒險(xiǎn)弯蚜,也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 6,059評論 0 4