Media Player Classic - HC 源代码分析 13:媒体类型协商过程中CheckMediaType函数详解

本文深入探讨 DirectShow 的 CBasePin 类及其派生类中 CheckMediaType 函数的作用和实现。该函数用于验证输入的媒体类型是否符合PIN的要求,其具体实现通常在PIN的派生类中,如CSourceStream和CRendererInputPin。在输入PIN中,CheckMediaType函数的最终处理通常由包含它的Filter完成,如CMpcVideoRenderer。通过对源代码的分析,揭示了DirectShow中媒体类型协商和连接的内部机制。

 前面在讲解媒体类型协商的过程中提到过CheckMediaType这个函数,当时只是说明了这个函数是用来检查输入的媒体类型是否符合要求,今天我们就来重点了解一下这个函数。

我们首先看一下CBasePin类中关于CheckMediaType函数的声明:

class  AM_NOVTABLE CBasePin : public CUnknown, public IPin, public IQualityControl
{

protected:

    // used to agree a media type for a pin connection

    // given a specific media type, attempt a connection (includes
    // checking that the type is acceptable to this pin)
    HRESULT
    AttemptConnection(
        IPin* pReceivePin,      // connect to this pin
        const CMediaType* pmt   // using this type
    );

    // try all the media types in this enumerator - for each that
    // we accept, try to connect using ReceiveConnection.
    HRESULT TryMediaTypes(
                        IPin *pReceivePin,          // connect to this pin
                        __in_opt const CMediaType *pmt,  // proposed type from Connect
                        IEnumMediaTypes *pEnum);    // try this enumerator

    // establish a connection with a suitable mediatype. Needs to
    // propose a media type if the pmt pointer is null or partially
    // specified - use TryMediaTypes on both our and then the other pin's
    // enumerator until we find one that works.
    HRESULT AgreeMediaType(
                        IPin *pReceivePin,      // connect to this pin
                        const CMediaType *pmt);      // proposed type from Connect

public:

    CBasePin(
        __in_opt LPCTSTR pObjectName,         // Object description
        __in CBaseFilter *pFilter,       // Owning filter who knows about pins
        __in CCritSec *pLock,            // Object who implements the lock
        __inout HRESULT *phr,               // General OLE return code
        __in_opt LPCWSTR pName,              // Pin name for us
        PIN_DIRECTION dir);         // Either PINDIR_INPUT or PINDIR_OUTPUT
#ifdef UNICODE
    CBasePin(
        __in_opt LPCSTR pObjectName,         // Object description
        __in CBaseFilter *pFilter,       // Owning filter who knows about pins
        __in CCritSec *pLock,            // Object who implements the lock
        __inout HRESULT *phr,               // General OLE return code
        __in_opt LPCWSTR pName,              // Pin name for us
        PIN_DIRECTION dir);         // Either PINDIR_INPUT or PINDIR_OUTPUT
#endif
    virtual ~CBasePin();

    //省略部分接口

    // check if the pin can support this specific proposed type and format
    virtual HRESULT CheckMediaType(const CMediaType *) PURE;

    //省略部分接口
};

 由于CBasePin类的声明比较长,所以我删除了大部分代码,只留下了CheckMediaType函数的声明。从声明我们可以看出,这个函数是纯虚函数,没有具体实现,那这个函数在哪里实现的呢?

我们带着这个疑问再来看看CBasePin的几个派生类的源码:

CBaseOutputPin类声明:

class  AM_NOVTABLE CBaseOutputPin : public CBasePin
{

protected:

    IMemAllocator *m_pAllocator;
    IMemInputPin *m_pInputPin;        // interface on the downstreaminput pin
                                      // set up in CheckConnect when we connect.

public:

    CBaseOutputPin(
        __in_opt LPCTSTR pObjectName,
        __in CBaseFilter *pFilter,
        __in CCritSec *pLock,
        __inout HRESULT *phr,
        __in_opt LPCWSTR pName);
#ifdef UNICODE
    CBaseOutputPin(
        __in_opt LPCSTR pObjectName,
        __in CBaseFilter *pFilter,
        __in CCritSec *pLock,
        __inout HRESULT *phr,
        __in_opt LPCWSTR pName);
#endif
    // override CompleteConnect() so we can negotiate an allocator
    virtual HRESULT CompleteConnect(IPin *pReceivePin);

    // negotiate the allocator and its buffer size/count and other properties
    // Calls DecideBufferSize to set properties
    virtual HRESULT DecideAllocator(IMemInputPin * pPin, __deref_out IMemAllocator ** pAlloc);

    // override this to set the buffer size and count. Return an error
    // if the size/count is not to your liking.
    // The allocator properties passed in are those requested by the
    // input pin - use eg the alignment and prefix members if you have
    // no preference on these.
    virtual HRESULT DecideBufferSize(
        IMemAllocator * pAlloc,
        __inout ALLOCATOR_PROPERTIES * ppropInputRequest
    ) PURE;

    // returns an empty sample buffer from the allocator
    virtual HRESULT GetDeliveryBuffer(__deref_out IMediaSample ** ppSample,
                                      __in_opt REFERENCE_TIME * pStartTime,
                                      __in_opt REFERENCE_TIME * pEndTime,
                                      DWORD dwFlags);

    // deliver a filled-in sample to the connected input pin
    // note - you need to release it after calling this. The receiving
    // pin will addref the sample if it needs to hold it beyond the
    // call.
    virtual HRESULT Deliver(IMediaSample *);

    // override this to control the connection
    virtual HRESULT InitAllocator(__deref_out IMemAllocator **ppAlloc);
    HRESULT CheckConnect(IPin *pPin);
    HRESULT BreakConnect();

    // override to call Commit and Decommit
    HRESULT Active(void);
    HRESULT Inactive(void);

    // we have a default handling of EndOfStream which is to return
    // an error, since this should be called on input pins only
    STDMETHODIMP EndOfStream(void);

    // called from elsewhere in our filter to pass EOS downstream to
    // our connected input pin
    virtual HRESULT DeliverEndOfStream(void);

    // same for Begin/EndFlush - we handle Begin/EndFlush since it
    // is an error on an output pin, and we have Deliver methods to
    // call the methods on the connected pin
    STDMETHODIMP BeginFlush(void);
    STDMETHODIMP EndFlush(void);
    virtual HRESULT DeliverBeginFlush(void);
    virtual HRESULT DeliverEndFlush(void);

    // deliver NewSegment to connected pin - you will need to
    // override this if you queue any data in your output pin.
    virtual HRESULT DeliverNewSegment(
                        REFERENCE_TIME tStart,
                        REFERENCE_TIME tStop,
                        double dRate);

    //================================================================================
    // IQualityControl methods
    //================================================================================

    // All inherited from CBasePin and not overridden here.
    // STDMETHODIMP Notify(IBaseFilter * pSender, Quality q);
    // STDMETHODIMP SetSink(IQualityControl * piqc);
};

从类声明可知CBaseOutputPin没有对CheckMediaType函数进行重写。需要从CBaseOutputPin派生的其他类实现这个函数。

我们再看看CSourceStream类的声明(此类从CBaseOutputPin派生):

class CSourceStream : public CAMThread, public CBaseOutputPin {
public:

    CSourceStream(__in_opt LPCTSTR pObjectName,
                  __inout HRESULT *phr,
                  __inout CSource *pms,
                  __in_opt LPCWSTR pName);
#ifdef UNICODE
    CSourceStream(__in_opt LPCSTR pObjectName,
                  __inout HRESULT *phr,
                  __inout CSource *pms,
                  __in_opt LPCWSTR pName);
#endif
    virtual ~CSourceStream(void);  // virtual destructor ensures derived class destructors are called too.

protected:

    CSource *m_pFilter;	// The parent of this stream

    // *
    // * Data Source
    // *
    // * The following three functions: FillBuffer, OnThreadCreate/Destroy, are
    // * called from within the ThreadProc. They are used in the creation of
    // * the media samples this pin will provide
    // *

    // Override this to provide the worker thread a means
    // of processing a buffer
    virtual HRESULT FillBuffer(IMediaSample *pSamp) PURE;

    // Called as the thread is created/destroyed - use to perform
    // jobs such as start/stop streaming mode
    // If OnThreadCreate returns an error the thread will exit.
    virtual HRESULT OnThreadCreate(void) {return NOERROR;};
    virtual HRESULT OnThreadDestroy(void) {return NOERROR;};
    virtual HRESULT OnThreadStartPlay(void) {return NOERROR;};

    // *
    // * Worker Thread
    // *

    HRESULT Active(void);    // Starts up the worker thread
    HRESULT Inactive(void);  // Exits the worker thread.

public:
    // thread commands
    enum Command {CMD_INIT, CMD_PAUSE, CMD_RUN, CMD_STOP, CMD_EXIT};
    HRESULT Init(void) { return CallWorker(CMD_INIT); }
    HRESULT Exit(void) { return CallWorker(CMD_EXIT); }
    HRESULT Run(void) { return CallWorker(CMD_RUN); }
    HRESULT Pause(void) { return CallWorker(CMD_PAUSE); }
    HRESULT Stop(void) { return CallWorker(CMD_STOP); }

protected:
    Command GetRequest(void) { return (Command) CAMThread::GetRequest(); }
    BOOL    CheckRequest(Command *pCom) { return CAMThread::CheckRequest( (DWORD *) pCom); }

    // override these if you want to add thread commands
    virtual DWORD ThreadProc(void);  		// the thread function

    virtual HRESULT DoBufferProcessingLoop(void);    // the loop executed whilst running


    // *
    // * AM_MEDIA_TYPE support
    // *

    // If you support more than one media type then override these 2 functions
    virtual HRESULT CheckMediaType(const CMediaType *pMediaType);
    virtual HRESULT GetMediaType(int iPosition, __inout CMediaType *pMediaType);  // List pos. 0-n

    // If you support only one type then override this fn.
    // This will only be called by the default implementations
    // of CheckMediaType and GetMediaType(int, CMediaType*)
    // You must override this fn. or the above 2!
    virtual HRESULT GetMediaType(__inout CMediaType *pMediaType) {return E_UNEXPECTED;}

    STDMETHODIMP QueryId(
        __deref_out LPWSTR * Id
    );
};

我们可以看到此类才真正实现了CheckMediaType函数。

我们再来看看此类中这个函数的具体实现代码:

// Do we support this type? Provides the default support for 1 type.
HRESULT CSourceStream::CheckMediaType(const CMediaType *pMediaType) {

    CAutoLock lock(m_pFilter->pStateLock());

    CMediaType mt;
    GetMediaType(&mt);

    if (mt == *pMediaType) {
        return NOERROR;
    }

    return E_FAIL;
}

实现比较简单,就是取到此类默认支持的媒体类型,然后和传入的媒体类型比较,如果相同就返回成功,否则返回失败。

前面我们看了输出PIN关于CheckMediaType函数的实现情况,我们接下来看看关于输入PIN的。

CBaseInputPin类的声明:

class AM_NOVTABLE CBaseInputPin : public CBasePin,
                                  public IMemInputPin
{

protected:

    IMemAllocator *m_pAllocator;    // Default memory allocator

    // allocator is read-only, so received samples
    // cannot be modified (probably only relevant to in-place
    // transforms
    BYTE m_bReadOnly;

    // in flushing state (between BeginFlush and EndFlush)
    // if TRUE, all Receives are returned with S_FALSE
    BYTE m_bFlushing;

    // Sample properties - initalized in Receive
    AM_SAMPLE2_PROPERTIES m_SampleProps;

public:

    CBaseInputPin(
        __in_opt LPCTSTR pObjectName,
        __in CBaseFilter *pFilter,
        __in CCritSec *pLock,
        __inout HRESULT *phr,
        __in_opt LPCWSTR pName);
#ifdef UNICODE
    CBaseInputPin(
        __in_opt LPCSTR pObjectName,
        __in CBaseFilter *pFilter,
        __in CCritSec *pLock,
        __inout HRESULT *phr,
        __in_opt LPCWSTR pName);
#endif
    virtual ~CBaseInputPin();

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv);

    // return the allocator interface that this input pin
    // would like the output pin to use
    STDMETHODIMP GetAllocator(__deref_out IMemAllocator ** ppAllocator);

    // tell the input pin which allocator the output pin is actually
    // going to use.
    STDMETHODIMP NotifyAllocator(
                    IMemAllocator * pAllocator,
                    BOOL bReadOnly);

    // do something with this media sample
    STDMETHODIMP Receive(IMediaSample *pSample);

    // do something with these media samples
    STDMETHODIMP ReceiveMultiple (
        __in_ecount(nSamples) IMediaSample **pSamples,
        long nSamples,
        __out long *nSamplesProcessed);

    // See if Receive() blocks
    STDMETHODIMP ReceiveCanBlock();

    // Default handling for BeginFlush - call at the beginning
    // of your implementation (makes sure that all Receive calls
    // fail). After calling this, you need to free any queued data
    // and then call downstream.
    STDMETHODIMP BeginFlush(void);

    // default handling for EndFlush - call at end of your implementation
    // - before calling this, ensure that there is no queued data and no thread
    // pushing any more without a further receive, then call downstream,
    // then call this method to clear the m_bFlushing flag and re-enable
    // receives
    STDMETHODIMP EndFlush(void);

    // this method is optional (can return E_NOTIMPL).
    // default implementation returns E_NOTIMPL. Override if you have
    // specific alignment or prefix needs, but could use an upstream
    // allocator
    STDMETHODIMP GetAllocatorRequirements(__out ALLOCATOR_PROPERTIES*pProps);

    // Release the pin's allocator.
    HRESULT BreakConnect();

    // helper method to check the read-only flag
    BOOL IsReadOnly() {
        return m_bReadOnly;
    };

    // helper method to see if we are flushing
    BOOL IsFlushing() {
        return m_bFlushing;
    };

    //  Override this for checking whether it's OK to process samples
    //  Also call this from EndOfStream.
    virtual HRESULT CheckStreaming();

    // Pass a Quality notification on to the appropriate sink
    HRESULT PassNotify(Quality& q);


    //================================================================================
    // IQualityControl methods (from CBasePin)
    //================================================================================

    STDMETHODIMP Notify(IBaseFilter * pSender, Quality q);

    // no need to override:
    // STDMETHODIMP SetSink(IQualityControl * piqc);


    // switch the pin to inactive state - may already be inactive
    virtual HRESULT Inactive(void);

    // Return sample properties pointer
    AM_SAMPLE2_PROPERTIES * SampleProps() {
        ASSERT(m_SampleProps.cbData != 0);
        return &m_SampleProps;
    }

};

从声明了解到这个类也没有实现CheckMediaType函数,我们只能继续从派生类中查找了。

CRendererInputPin类的声明(继承自CBaseInputPin类):

class CRendererInputPin : public CBaseInputPin
{
protected:

    CBaseRenderer *m_pRenderer;

public:

    CRendererInputPin(__inout CBaseRenderer *pRenderer,
                      __inout HRESULT *phr,
                      __in_opt LPCWSTR Name);

    // Overriden from the base pin classes

    HRESULT BreakConnect();
    HRESULT CompleteConnect(IPin *pReceivePin);
    HRESULT SetMediaType(const CMediaType *pmt);
    HRESULT CheckMediaType(const CMediaType *pmt);
    HRESULT Active();
    HRESULT Inactive();

    // Add rendering behaviour to interface functions

    STDMETHODIMP QueryId(__deref_out LPWSTR *Id);
    STDMETHODIMP EndOfStream();
    STDMETHODIMP BeginFlush();
    STDMETHODIMP EndFlush();
    STDMETHODIMP Receive(IMediaSample *pMediaSample);

    // Helper
    IMemAllocator inline *Allocator() const
    {
        return m_pAllocator;
    }
};

可以看到此类有对CheckMediaType的实现,我们再来看看具体的实现代码:

// Will the filter accept this media type

HRESULT CRendererInputPin::CheckMediaType(const CMediaType *pmt)
{
    return m_pRenderer->CheckMediaType(pmt);
}

从实现可知它将CheckMeidaType函数转交给了Filter对象。所以具体的媒体类型检查是到Filter当中。

我们再来看看CMpcVideoRenderer这个Filter中关于此函数的实现情况:

CMpcVideoRenderer类的继承情况如下:

CMpcVideoRenderer-->CBaseVideoRenderer2-->CBaseRenderer-->CBaseFilter.

CheckMeidaType这个函数就是从CBaseRenderer类开始声明的。只是这个类里定义的函数为纯虚函数。

CBaseRenderer类声明如下:

class CBaseRenderer : public CBaseFilter
{
protected:

    friend class CRendererInputPin;

    //省略部分代码
   
    // Derived classes MUST override these
    virtual HRESULT DoRenderSample(IMediaSample *pMediaSample) PURE;
    virtual HRESULT CheckMediaType(const CMediaType *) PURE;

    // Helper
    void WaitForReceiveToComplete();
};

我看了CBaseVideoRenderer2类的声明,没有对CheckMeidaType的实现,所以具体的实现就交给了CMpcVideoRenderer类对象。

CMpcVideoRenderer类声明如下:

class __declspec(uuid("71F080AA-8661-4093-B15E-4F6903E77D0A"))
	CMpcVideoRenderer
	: public CBaseVideoRenderer2
	, public IKsPropertySet
	, public IMFGetService
	, public IBasicVideo2
	, public IVideoWindow
	, public ISpecifyPropertyPages
	, public IVideoRenderer
	, public ISubRender
	, public CExFilterConfigImpl
	, public ID3DFullscreenControl
{
private:
public:
	CMpcVideoRenderer(LPUNKNOWN pUnk, HRESULT* phr);
	~CMpcVideoRenderer();

	void NewSegment(REFERENCE_TIME startTime);
	long CalcImageSize(CMediaType& mt, bool redefine_mt);

	// CBaseRenderer
	HRESULT CheckMediaType(const CMediaType *pmt) override;
	HRESULT SetMediaType(const CMediaType *pmt) override;
	HRESULT DoRenderSample(IMediaSample* pSample) override;
	HRESULT Receive(IMediaSample* pMediaSample) override;

	HRESULT BeginFlush() override;
	HRESULT EndFlush() override;

	void UpdateDisplayInfo();
	void OnDisplayModeChange(const bool bReset = false);
	void OnWindowMove();

	DECLARE_IUNKNOWN
	STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);

	// IMediaFilter
	STDMETHODIMP Run(REFERENCE_TIME rtStart) override;
	STDMETHODIMP Pause() override;
	STDMETHODIMP Stop() override;

	// IKsPropertySet
	// IMFGetService
	// IDispatch
	// IBasicVideo
	// IBasicVideo2
	// IVideoWindow
	// ISpecifyPropertyPages
	// IVideoRenderer
	// ISubRender
	// IExFilterConfig
	// ID3DFullscreenControl
private:
};

从类声明可知,此类实现了CheckMeidaType函数,我们再来看看具体的实现。

HRESULT CMpcVideoRenderer::CheckMediaType(const CMediaType* pmt)
{
	CheckPointer(pmt, E_POINTER);
	CheckPointer(pmt->pbFormat, E_POINTER);

	if (pmt->majortype == MEDIATYPE_Video && (pmt->formattype == FORMAT_VideoInfo2 || pmt->formattype == FORMAT_VideoInfo)) {
		for (const auto& sudPinType : sudPinTypesIn) {
			if (pmt->subtype == *sudPinType.clsMinorType) {
				CAutoLock cRendererLock(&m_RendererLock);

				if (!m_VideoProcessor->VerifyMediaType(pmt)) {
					return VFW_E_UNSUPPORTED_VIDEO;
				}

				return S_OK;
			}
		}
	}

	return E_FAIL;
}

具体的实现就不讲解了,就是对媒体类型的验证。

总结:从输入PIN的媒体类型检查函数可知,虽然媒体类型的检查在输入PIN的ReceiveConnection中发起,但是最终的实现由输入PIN转给了对应包含它的Filter上。发现这种现象以后,我也看了输入PIN的其他几个实现函数(CompleteConnect、SetMediaType、Receive等),也是同样的实现方式。由此可知PIN只是负责暴露对外的连接接口,具体的业务逻辑都是转交给了Filter来实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值