CVcam与CVCamStream实现类

2021-10-26  本文已影响0人  Charles_linzc

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的基础类。

image.png
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, 
            &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;   //真开始时间
        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需要做下面的几件事:

  1. 从sample buffer获取新的sample地址,
  2. 填充sample数据
  3. 设置sample的媒体开始时间和结束时间。
  4. 设置同步点状态。
    除了以上的方法外,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都给了很好的实现例子。

上一篇下一篇

猜你喜欢

热点阅读