Media Player Classic - HC 源代码分析 14:PIN连接过程中推模式和拉模式区别

本文详细介绍了在DirectShow中实现拉模式PIN连接的过程,特别是输入PIN的实现。通过CPullPin类,讲解了如何协商分配器、获取IAsyncReader接口以及数据读取线程的工作原理。在CPullPin类中,Connect方法负责分配器协商,而Process方法处理数据获取,通过SyncReadAligned从输出PIN读取数据并交付给下游Filter。
摘要由CSDN通过智能技术生成

 前面有两篇文章讲解了PIN连接过程中需要做2件事情:

  1. 媒体类型的协商
  2. 分配器的协商

推模式和拉模式关于媒体类型的协商调用的接口都一样,主要的区别还是在分配器的协商。

前面讲解的分配器的协商过程是推模式的,今天我们来看看拉模式的分配器协商过程。

如果我们要实现拉模式的PIN连接,则输出PIN和输入PIN不能从CBaseOuputPin和CBaseInputPin继承。因为这两个类是为推模式连接设计的:

  • CBaseInputPin实现了IMemInputPin接口。(IMemInputPin接口专门针对推模式设计的)
  • CBaseOuputPin没有实现IAsyncReader接口。(IAsyncReader接口专门针对拉模式设计的)

那要实现拉模式的PIN连接,输入和输出PIN需要如何实现呢?

由于拉模式的数据读取线程是由输入PIN创建的,所以输出PIN比较简单,只要继承自CBasePin类和IAsyncReader接口就行。由于没有找到相关PIN的实现代码,这里就只是简单介绍一下。我们重点看一下拉模式输入PIN的实现方式:

为了方便大家实现拉模式的输入PIN,DirectShow为大家创建了一个帮助类:CPullPin。这个类没有从CBasePin继承,也没有实现IPin接口。它在内部通过调用上游连接的输出PIN的IAsyncReader接口获取数据。CPullPin内部有很多方法是和IPin同名的,因此它是你实现的输入Pin的一个帮助类。

接下来我大概讲解一下这个类的使用方法:

  1. 创建一个新类CMyPullPin继承自CPullPin类,创建另外一个新类CMyBasePin继承自CBasePin类。声明一个CMyPullPin类的实例作为CMyBasePin类的成员变量。
  2. 重新实现CBasePin::CheckConnect 方法,并在此方法中调用 CPullPin::Connect方法。CPullPin::Connect方法会负责分配器的协商和连接输出PIN的IAsyncReader接口的查询。
  3. 重新实现CBasePin::BreakConnect方法,并在此方法中调用 CPullPin::Disconnect方法。
  4. 重新实现CBasePin::Active方法,并在此方法中调用CPullPin::Active方法。CPullPin::Active方法会创建一个线程用来从上游的FIlter中获取数据。当PIN连接成功,你可以指定读取数据的方式为同步模式还是异步模式。
  5. 重新实现CBasePin::Inactive方法,并在此方法中调用CPullPin::Inactive方法。CPullPin::Inactive方法会关闭数据读取线程。
  6. 在CMyPullPin类中实现纯虚CPullPin::Receive方法,并在实现中处理接收到的数据以及将处理完的数据交付给下游Filter.
  7. 设置开始、停止的位置或者Seek数据流,调用CPullPin::Seek方法。这个方法会暂停数据线程并且清空调用缓存数据。
  8. 在CMyPullPin类中实现纯虚函数CPullPin::EndOfStream, CPullPin::BeginFlush, CPullPin::EndFlushCPullPin::OnError

CPullPin类的声明:

class CPullPin : public CAMThread
{
    IAsyncReader*       m_pReader;
    REFERENCE_TIME      m_tStart;
    REFERENCE_TIME      m_tStop;
    REFERENCE_TIME      m_tDuration;
    BOOL                m_bSync;

    enum ThreadMsg {
	TM_Pause,       // stop pulling and wait for next message
	TM_Start,       // start pulling
	TM_Exit,        // stop and exit
    };

    ThreadMsg m_State;

    // override pure thread proc from CAMThread
    DWORD ThreadProc(void);

    // running pull method (check m_bSync)
    void Process(void);

    // clean up any cancelled i/o after a flush
    void CleanupCancelled(void);

    // suspend thread from pulling, eg during seek
    HRESULT PauseThread();

    // start thread pulling - create thread if necy
    HRESULT StartThread();

    // stop and close thread
    HRESULT StopThread();

    // called from ProcessAsync to queue and collect requests
    HRESULT QueueSample(
		__inout REFERENCE_TIME& tCurrent,
		REFERENCE_TIME tAlignStop,
		BOOL bDiscontinuity);

    HRESULT CollectAndDeliver(
		REFERENCE_TIME tStart,
		REFERENCE_TIME tStop);

    HRESULT DeliverSample(
		IMediaSample* pSample,
		REFERENCE_TIME tStart,
		REFERENCE_TIME tStop);

protected:
    IMemAllocator *     m_pAlloc;

public:
    CPullPin();
    virtual ~CPullPin();

    // returns S_OK if successfully connected to an IAsyncReader interface
    // from this object
    // Optional allocator should be proposed as a preferred allocator if
    // necessary
    // bSync is TRUE if we are to use sync reads instead of the
    // async methods.
    HRESULT Connect(IUnknown* pUnk, IMemAllocator* pAlloc, BOOL bSync);

    // disconnect any connection made in Connect
    HRESULT Disconnect();

    // agree an allocator using RequestAllocator - optional
    // props param specifies your requirements (non-zero fields).
    // returns an error code if fail to match requirements.
    // optional IMemAllocator interface is offered as a preferred allocator
    // but no error occurs if it can't be met.
    virtual HRESULT DecideAllocator(
		IMemAllocator* pAlloc,
		__inout_opt ALLOCATOR_PROPERTIES * pProps);

    // set start and stop position. if active, will start immediately at
    // the new position. Default is 0 to duration
    HRESULT Seek(REFERENCE_TIME tStart, REFERENCE_TIME tStop);

    // return the total duration
    HRESULT Duration(__out REFERENCE_TIME* ptDuration);

    // start pulling data
    HRESULT Active(void);

    // stop pulling data
    HRESULT Inactive(void);

    // helper functions
    LONGLONG AlignDown(LONGLONG ll, LONG lAlign) {
	// aligning downwards is just truncation
	return ll & ~(lAlign-1);
    };

    LONGLONG AlignUp(LONGLONG ll, LONG lAlign) {
	// align up: round up to next boundary
	return (ll + (lAlign -1)) & ~(lAlign -1);
    };

    // GetReader returns the (addrefed) IAsyncReader interface
    // for SyncRead etc
    IAsyncReader* GetReader() {
	m_pReader->AddRef();
	return m_pReader;
    };

    // -- pure --

    // override this to handle data arrival
    // return value other than S_OK will stop data
    virtual HRESULT Receive(IMediaSample*) PURE;

    // override this to handle end-of-stream
    virtual HRESULT EndOfStream(void) PURE;

    // called on runtime errors that will have caused pulling
    // to stop
    // these errors are all returned from the upstream filter, who
    // will have already reported any errors to the filtergraph.
    virtual void OnError(HRESULT hr) PURE;

    // flush this pin and all downstream
    virtual HRESULT BeginFlush() PURE;
    virtual HRESULT EndFlush() PURE;

};

接下来我们重点讲解一下这个类是如何协商分配器和获取数据的。

上面讲解CPullPin 类的使用时第二步中提到,CBasePin的CheckConnect 方法会调用CPullPin的Connect方法,此方法会负责分配器的协商以及IAsyncReader接口的获取。具体实现代码如下:

HRESULT
CPullPin::Connect(IUnknown* pUnk, IMemAllocator* pAlloc, BOOL bSync)
{
    CAutoLock lock(&m_AccessLock);

    if (m_pReader) {
	return VFW_E_ALREADY_CONNECTED;
    }

    HRESULT hr = pUnk->QueryInterface(IID_IAsyncReader, (void**)&m_pReader);
    if (FAILED(hr)) {
	return(hr);
    }

    hr = DecideAllocator(pAlloc, NULL);
    if (FAILED(hr)) {
	Disconnect();
	return hr;
    }

    LONGLONG llTotal, llAvail;
    hr = m_pReader->Length(&llTotal, &llAvail);
    if (FAILED(hr)) {
	Disconnect();
	return hr;
    }

    // convert from file position to reference time
    m_tDuration = llTotal * UNITS;
    m_tStop = m_tDuration;
    m_tStart = 0;
    m_bSync = bSync;
    return S_OK;
}

从 实现可知,首先获取了IAsyncReader接口,然后调用DecideAllocator接口来实现分配器的协商。我接下来看看DecideAllocator的源码实现:

HRESULT
CPullPin::DecideAllocator(
    IMemAllocator * pAlloc,
    __inout_opt ALLOCATOR_PROPERTIES * pProps)
{
    ALLOCATOR_PROPERTIES *pRequest;
    ALLOCATOR_PROPERTIES Request;
    if (pProps == NULL) {
	Request.cBuffers = 3;
	Request.cbBuffer = 64*1024;
	Request.cbAlign = 0;
	Request.cbPrefix = 0;
	pRequest = &Request;
    } else {
	pRequest = pProps;
    }
    HRESULT hr = m_pReader->RequestAllocator(
		    pAlloc,
		    pRequest,
		    &m_pAlloc);
    return hr;
}

从实现可知,最终分配器的协商交由连接的输出PIN的IAsyncReader接口RequestAllocator方法。

我们接下来看看拉模式数据的读取方式,CPullPin拉数据线程的源码如下:

DWORD
CPullPin::ThreadProc(void)
{
    for (;;) {
	DWORD cmd = GetRequest();
	switch(cmd) {
	case TM_Exit:
	    Reply(S_OK);
	    return 0;

	case TM_Pause:
	    // we are paused already
	    Reply(S_OK);
	    break;

	case TM_Start:
	    Reply(S_OK);
	    Process();
	    break;
	}

	// at this point, there should be no outstanding requests on the
	// upstream filter.
	// We should force begin/endflush to ensure that this is true.
	// !!!Note that we may currently be inside a BeginFlush/EndFlush pair
	// on another thread, but the premature EndFlush will do no harm now
	// that we are idle.
	m_pReader->BeginFlush();
	CleanupCancelled();
	m_pReader->EndFlush();
    }
}

从实现可知,此方法调用的主要方法为Process();

Process方法源码如下:

void
CPullPin::Process(void)
{

	    IMediaSample* pSample;

	    hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0);

	    LONGLONG tStopThis = tCurrent + (pSample->GetSize() * UNITS);
	    pSample->SetTime(&tCurrent, &tStopThis);
	    tCurrent = tStopThis;

	    if (bDiscontinuity) {
		pSample->SetDiscontinuity(TRUE);
		bDiscontinuity = FALSE;
	    }

	    hr = m_pReader->SyncReadAligned(pSample);
	    hr = DeliverSample(pSample, tStart, tStop);
		return;

}

此方法代码有点长,所以我就只留下比较重要的几行,可以大概了解下整个数据获取的过程。

  1. hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0);//从分配器获取到一个Sampler对象。
  2.  hr = m_pReader->SyncReadAligned(pSample);//从输出Pin读取数据到Sampler对象。
  3. hr = DeliverSample(pSample, tStart, tStop);//处理Sampler数据,并交付给下游Filter.

我们接下来看看DeliverSample的实现代码:

HRESULT
CPullPin::DeliverSample(
    IMediaSample* pSample,
    REFERENCE_TIME tStart,
    REFERENCE_TIME tStop
    )
{
    // fix up sample if past actual stop (for sector alignment)
    REFERENCE_TIME t1, t2;
    if (S_OK == pSample->GetTime(&t1, &t2)) {
        if (t2 > tStop) {
            t2 = tStop;
        }

        // adjust times to be relative to (aligned) start time
        t1 -= tStart;
        t2 -= tStart;
        HRESULT hr = pSample->SetTime(&t1, &t2);
        if (FAILED(hr)) {
            return hr;
        }
    }

    HRESULT hr = Receive(pSample);
    pSample->Release();
    return hr;
}

由代码实现可知,此函数将Sampler对象交给Receive函数处理,然后就释放掉这个Sampler 对象了。

所以具体的Sampler处理还是由派生自CPullPin的CMyPullPin类(我们自己实现的类)决定。

总结:以上就是拉模式的分配器协商过程和数据处理过程。推模式的分配器协商过程在输出CBaseOutputPin的CompleteConnect函数中实现。拉模式的分配器协商过程在CBasePin的CheckConnect函数中实现,此函数会在ReceiveConnection函数中被调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值