编写DirectShow Filters—线程和关键区

来源:互联网 发布:高晓松和罗振宇 知乎 编辑:程序博客网 时间:2024/05/20 17:25

seeker

本章描述了dshow filters 的线程,这些步骤可以让你在自定义filter 中避免系统崩溃和死锁。
在本章中的例子使用伪码为演示你需要去编写的代码。假定自定义filter使用从Directshow基类继承的类。
1. CMyInputPin::继承自CBaseInputPin
2. CMyOutputPin: 继承自CBaseOutputPin
3. CMyFilter:继承自CBaseFilter
4. CMyInputAllocator: input pin的allocator,继承自CMemAllocator。不是每个filter都需要自定义allocator,对于许多的Filter,CMemAllocator足够了。
一、 流线程和应用程序线程
任何Directshow 应用程序至少包含两个线程:应用程序线程,和一个或多个流线程。sample在流线程传递,应用程序线程上改变状态。主流线程通过source或parser filter建立。其它filter建立工作线程传递sample,这些也被认为是流线程。
在应用程序线程上和流线程上调用的函数:
1.  流线程:IMemInputPin::Receive, IMemInputPin::ReceiveMultiple, IPIn::EndOfStream, IMemAllocator::GetBuffer
2. 应用程序线程:IMediaFilter::Pause, IMediaFilter::Run, IMediaFilter::Stop, IMediaSeeking::SetPositions, Ipin::BeginFlush, Ipin::ENdFlush
3. 二者:IPin::NewSegment.
当应用程序线程等待用户输入时分离的流线程同意数据通过graph流动。多线程是有问题的,可是,一个filter建立源当它暂停时(在应用程序线程),在一个流方法中使用,当停止时(在APP线程)销毁。如果你不小心,流线程可能试图在它们被销毁之后去使用资源。解决方案是使用临界区保存资源,用状态改变同步流方法。
filter需要一个临界区保护FILTER状态,对于临界区CBaseFilter类有一个成员变量,CBaseFilter::m_pLock。临界区叫做filter lock,在流线程中每个输入PIN需要一个临界区来保护资源,这个临界区叫做流lock,必须在继承的PIN类中声明它们,最简单的是使用CCritSec类,它包装了一个widnow CRTICAL_SECTION对象并且可能使用CAutoLock类锁定,CCritSec类也提供一些有用调试函数。
当一个filter停止或flush时,必须用流线程同步APP线程,为了避免死锁,必须首先解锁流线程,流线程由几个原因被锁定
1) 等待得到一个在IMemAllocator::GetBuffer中的sample,因为allocator的所有Sample都在使用
2) 等待另一个FILTER从一个流方法返回,如RECEIVE
3) 等待它所属流函数的一个内部,一些资源开始可
4) renderer filter下一个sampler的表现时间
5) 当暂停时renderer filter等待RECEVIE方法
因此,filter停止或flush,它必须如下操作:
1) 释放某种原因正在 holding的任何SAMPLE,为了解锁GetBuffer
2) 尽快从各种流方法返回,如果流函数等待一个资源,必须立即停止等待
3) 在RECEIVE中开始拒绝sample,所以任何流线程不访问任何资源
4) stop方法必须反提交所有allocator
Flush/stop都发生在应用程序线程,filter停止响应IMediaControl::Stop,filter graph manager假定stop命令在upstream顺序,从renderer开始并且向后直到source filter,stop命令完成发生在filter的CBaseFilter::STop。当这个函数返回,FILTER应该在一个停止状态。
flushing典型发生在一个seek命令上,一个flush命令从source或parser filter发生,穿过downstream,flush在两个状态发生:Ipin::BeginFlush通知一个filter丢弃所有未决和到来的数据,Ipin::EndFlush通知filter再次接收数据,flush需要两个状态因为BeginFlush在APP线程调用,在这期间流线程继续传递数据。因此,一些sample可以在BeginFlush调用之后到达。filter可以丢弃这些,任何sample在EndFlush之后到达的是新产生的,并且应该被传递。
本文下面包含的代码例子显示如何实现最重要filter方法,如Pause,receive 等,为了避免死锁和竞争条件。每个filter需要不同的需求。你将需要采用这些例子到你自己的特定的filter。
注意:CTransformFilter和CTransInPlaceFilter基类处理许多本文描述的知识点。如果你写一个transform filter,并且filter不等待在流方向中的事件,或者保持sample在Receive外部,这些基类已经足够了。
二、 暂停
所有filter 状态变化必须要保存filter锁。在Pause 方法中,要创建任何filter 需要的资源
HRESULT CMyFilter::Pause()
{
    CAutoLock lock_it(m_pLock);

    /* Create filter resources. */

    return CBaseFilter::Pause();
}
CBaseFilterCBaseFilter::Pause 方法设置在filter 正确的(State_Paused)状态,然后调用在filter 上相连接的每个pin 上的CBasePin::Active 方法。Active 方法通知pin filter 开始处于激活状态了。如果pin 创建资源,从Active 方法重载,如下:
HRESULT CMyInputPin::Active()
{
    // You do not need to hold the filter lock here. It is already held in Pause.

    /* Create pin resources. */

    return CBaseInputPin::Active()
}
三、 接收和传递Sample
下面的伪码演示了如何实现IMemInput:: Receive 方法:
HRESULT CMyInputPin::Receive(IMediaSample *pSample)
{
    CAutoLock cObjectLock(&m_csReceive);

    // Perhaps the filter needs to wait on something.
    WaitForSingleObject(m_hSomeEventThatReceiveNeedsToWaitOn, INFINITE);

    // Before using resources, make sure it is safe to proceed. Do not
    // continue if the base-class method returns anything besides S_OK.
    hr = CBaseInputPin::Receive(pSample);
    if (hr != S_OK)
    {
        return hr;
    }

    /* It is safe to use resources allocated in Active and Pause. */

    // Deliver sample(s), via your output pin(s).
    for (each output pin)
        pOutputPin->Deliver(pSample);

    return hr;
}
Receive 方法设置了一个streaming 锁,不是filter 锁。Filter 在开始处理数据之前可能需要等待其他事件发生,这里就调用了WaitForSingleObject。当然并不是所有的filter 都需要这样做。CBaseInputPin::Receive 方法验证一些基本的streaming 条件。如果filter 停止它返回VFW_E_WRONG_STATE,如果filter flushing 该方法返回S_FALSE,如果没有返回S_OK 就表示Receive 方法应该立即返回,不能处理sample。
在sample 被处理后,就调用CBaseOutputPin::Deliver方法向下传递。这个帮助函数调用在downstream input pin上的IMemINputPin::Receive。一个filter可能传递sample到几个pins。
四、 传递end of stream
当一个input pin 接收一个end of stream通知,它传播这个调用downstream。从input pin接收数据的任何downstream filter也应该接收到end of stream通知。再次,用流锁而不是filter锁。如果filter的有仍没传递的未决数据,filter应该现在传递它,在它发送end of stream通知之前。它应该在end of stream之后不发送任何数据。
HRESULT CMyInputPin::EndOfStream()
{
    CAutoLock lock_it(&m_csReceive);

    /* If the pin has not delivered all of the data in the stream
       (based on what it received previously), do so now.  */

    // Propagate EndOfStream call downstream, via your output pin(s).
    for (each output pin)
    {   
        hr = pOutputPin->DeliverEndOfStream();
    }
    return S_OK;
}
CBaseOutputPin::DeliverEndOfStream 调用了在downstream input pin上的IPin::EndOfStream 方法。
五、 flushing数据
下面的伪代码演示如何实现IPin::BeginFlush 方法:
HRESULT CMyInputPin::BeginFlush()
{
    CAutoLock lock_it(m_pLock);
  
    // First, make sure the Receive method will fail from now on.
    HRESULT hr = CBaseInputPin::BeginFlush();
   
    // Force downstream filters to release samples. If our Receive method
    // is blocked in GetBuffer or Deliver, this will unblock it.
    for (each output pin)
    {
        hr = pOutputPin->DeliverBeginFlush();
    }

    // Unblock our Receive method if it is waiting on an event.
    SetEvent(m_hSomeEventThatReceiveNeedsToWaitOn);

    // At this point, the Receive method can't be blocked. Make sure
    // it finishes, by taking the streaming lock. (Not necessary if this
    // is the last step.)
    {
        CAutoLock lock_2(&m_csReceive);

        /* Now it's safe to do anything that would crash or hang
           if Receive were executing. */
    }
    return hr;
}
当开始Flushing 时,BeginFlush 方法使用了filter 锁,此锁序列化状态改变。使用streaming 锁不是很安全,因为flush 发生在应用程序线程,并且有可能此时streaming 线程正处于Receive 方法中。Pin 要保证Receive 方法没有被阻塞,否则,后续调用Receive 方法都会失败。CBaseInputPin::BeginFlush 设置了一个内部标志,CBaseInputPin::m_bFlushing。当这个标志为TRUE,Receive 方法就会失败。
通过传递Beginflush 调用downstream,pin 保证所有downstream filter 释放它们的samples
并且从Receive 方法中返回。这就保证了input pin 不会阻塞在GetBuffer 和Receive 方法中。如果你的pin 的Receive 方法正在等待一个事件(如得到资源),BeginFlush 方法设置事件来强制结束这种等待。在这点上,保证Receive 方法能够返回,m_bFlushing 标志就阻止Receive 方法调用。
对于一些filter ,所有BeginFlush需要这么做,EndFlush 方法通知filter 可以重新接收数据了。其它的filter可能需要使用在BeginFlush中的变量或资源,也可以在Receive中使用。在这种情况下,filter应该首先保存流锁。确保在这之前的任何先前步骤不这么做,因为可能引起一个死锁。
Endflush 方法保持filter 锁。然后传播调用downstream。
HRESULT CMyInputPin::EndFlush()
{
    CAutoLock lock_it(m_pLock);
    for (each output pin)
        hr = pOutputPin->DeliverEndFlush();
    return CBaseInputPin::EndFlush();
}
CBaseInputPin::EndFlush 重新设置m_bFlushing 标志为FALSE。它允许 Receive 方法开始再次接收数据,这必须在EndFlush中的最后步骤,因为pin必须不接收任何sample直到flushing完全并且所有的downstream filter被通知。
六、 停止
Stop 方法必须解锁 Receive 方法并且decommit filter 的allocator。反提交一个allocator强制任何未决GetBuffer调用返回。解锁等待sample的upstream filter。Stop 方法使用了filter 锁,然后调用了CBaseFilter::Stop,这个stop 方法调用了filter pins上的CBasePin::Inactive 方法。
HRESULT CMyFilter::Stop()
{
    CAutoLock lock_it(m_pLock);
    // Inactivate all the pins, to protect the filter resources.
    hr = CBaseFilter::Stop();

    /* Safe to destroy filter resources used by the streaming thread. */

    return hr;
}
重载输入PIN的Inactive方法
HRESULT CMyInputPin::Inactive()
{
    // You do not need to hold the filter lock here.
    // It is already locked in Stop.

    // Unblock Receive.
    SetEvent(m_hSomeEventThatReceiveNeedsToWaitOn);

    // Make sure Receive will fail.
    // This also decommits the allocator.
    HRESULT hr = CBaseInputPin::Inactive();

    // Make sure Receive has completed, and is not using resources.
    {
        CAutoLock c(&m_csReceive);

        /* It is now safe to destroy filter resources used by the
           streaming thread. */
    }
    return hr;
}
七、 得到buffer
如果你的filter有一个自定义allocator,使用了filter资源,那么GetBuffer 方法应该保存streaming 锁,和其它的流方法一样。
HRESULT CMyInputAllocator::GetBuffer(
    IMediaSample **ppBuffer,
    REFERENCE_TIME *pStartTime,
    REFERENCE_TIME *pEndTime,
    DWORD dwFlags)
{
    CAutoLock cObjectLock(&m_csReceive);

    /* Use resources. */

    return CMemAllocator::GetBuffer(ppBuffer, pStartTime, pEndTime, dwFlags);   
}
八、 流线程和filter graph manager
当filter graph manager停止graph,它要等待所有的streaming 线程停止:
1. filter必须从不从流线程上调用在filter graph manager上的方法。
filter graph manager使用一个关键区来同步它所属的操作。如果一个流线程试图去保存这个关键区,它可能引起一个死锁。例如:支持另一个线程停止这个Graph。这个线程使filter graph锁定并且等待你的Filter停止传递数据。如果你的filter等待这个锁,它将从不停止,引起一个死锁。
2. filter必须从不从流线程上调用在filter graph manager上的AddRef/QueryInterface。
如果filter有一个在filter graph manager上的引用计数(通过AddRef / QueryInterface),它可能成为最后对象来保持一个引用计数。当filter调用Release,filter graph manager销毁它自身。在它的cleanup rountine内部,filter graph manager试图停止这个graph,引起它等待流线程退出。可是,它在流线程内部等待,这样流线程不能退出。结果是一个死锁。
九、 filter线程总结
Streaming 线程调用的函数:
IMemInputPin::Receive
IMemInputPin::ReceiveMultiple
IPin::EndOfStream
IPin::NewSegment
IMemAllocator::GetBuffer
应用程序调用的函数:
状态改变: IBaseFilter::JoinFilterGraph, IMediaFilter::Pause, IMediaFilter::Run, IMediaFilter::Stop, IQualityControl::SetSink.
参考时钟: IMediaFilter::GetSyncSource, IMediaFilter::SetSyncSource.
:Pin 的操作IBaseFilter::FindPin, IPin::Connect, IPin::ConnectedTo, IPin::ConnectionMediaType,
IPin::Disconnect, IPin::ReceiveConnection.
内存的分配: IMemInputPin::GetAllocator, IMemInputPin::NotifyAllocator.
Flushing: IPin::BeginFlush, IPin::EndFlush
此列表是不全部的。当你实现一个filter,必须考虑哪个方法改变filter状态,哪个方法执行流操作。

原创粉丝点击