美文网首页
CVcam与CVCamStream实现类

CVcam与CVCamStream实现类

作者: Charles_linzc | 来源:发表于2021-10-26 16:13 被阅读0次

    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都给了很好的实现例子。

    相关文章

      网友评论

          本文标题:CVcam与CVCamStream实现类

          本文链接:https://www.haomeiwen.com/subject/tdqsaltx.html