DirectShow SDK笔记【关于DirectShow(3)】

【续前一篇文章】

    4.3 Filter States
    Filter 有三种状态,停止,暂停,运行。暂停状态是为了在 Graph Cue Data, 使得运行命令可以立即响应。 Filter Graph Manager 控制着所有状态的转换。当应用程序调用 IMediaControl Run, Pause, Stop 方法时, Filter Graph Manager 就调用所有 Filter 的相应 IMediaFilter 方法。停止,运行状态的切换总是要经过暂停,因此,当应用程序对停止的 Graph 调用 RUN 命令时, Filter 图表管理器在 Run 之前首先要暂停。对于大多数的 Filter 来说, Running Paused 状态是一样的。考虑如下的 Filter Graph:
    Source -> Transform -> Renderer
    假定 Source Filter 不是实时源。当 Source Filter 暂停,它创建一个线程生成新的数据并尽可能快的写入到 Media Samples. 线程通过调用 Transform Filter 的输入 PIN 上的 IMemInputPin Receive 方法把数据向下推。 Transform Filter 接收到在 Source Filter 的线程中的 Sample. 它可能用一个工作线程把 Sample 递送给 Renderer ,但是通常是在同一个线程完成。此时 Renderer 暂停,它等待接收 Sample. 在接收到一个 Sample 后,它就阻塞线程并不定的保存数据。如果是视频 Renderer, Sample 作为张贴图像显示,如果必要就重画图像。
    现在,数据流完全准备好进行提交。如果 Graph 保持暂停,在第一帧 Sample Sample 就堆积在 Graph, 直到每个 Filter 都阻塞在 Receive GetBuffer. 但是不会有数据丢失。一旦 Source 线程非阻塞,它简单从阻塞的点恢复。
    Source Filter Transform Filter 忽略从暂停到运行的变换,它们简单的尽可能快的继续处理数据。当 Render 开始运行,它就开始提交 Sample. 首先提交的就是当它阻塞时保存的 Sample. 然后,每次都接收到新 Sample. 计算 Sample 的提交时间。(细节可参考 Time and Clocks in DirectShow ), Render 会保存每个 Sample 直到提交时间,到点后再提交 Sample. 当它等待提交时间时,线程也阻塞在 Receive 方法,或者在工作线程用队列接收 Sample. Renderer 向上的所有 Filter 都不会陷于时间安排。
    实时源(比如捕捉设备)与普通结构有些不同。在实时源中,不合适提前准备任何数据。应用程序可能暂停 Graph ,在运行前等待比较长的时间。 Graph 不应该提交过时 Sample. 因此,暂停时实时源不会产生数据,只有运行时才有。为了把事件通知给 Filter Graph Manager, Source Filter IMediaFilter 方法 GetState 返回 VFW_S_CANT_CUE 。此返回值表示 Filter 已经转换到暂停状态,即使 Renderer 没有接收到任何数据。
    当一个 Filter 停止时,它拒绝发送给它的任何 Samples Source Filter 关闭他们的 Stream 线程,其他 Filter 也关闭他们创建的工作线程, Pin Decommit 他们的内存分配器。
 
    4.3.1   State Transitions
    Filter Graph Manager 按照逆流的方向来切换 Filter 的状态,从 Renderer Filter Source Filter ,这种方式可以防止死锁和 Sample 的丢失。最关键的状态切换是暂停和停止之间。
    ·从停止到暂停,当 Filter 暂停时就准备好了从连接 Filter 接收 Sample Source Filter 是最后一个暂停的。它创建 Streaming 线程发送 Sample ,因为下游的 Filter 的状态都已经切换到暂停了,所以,所有的 Filter 都可以接收 Sample 。只有当 Graph 所有的 Renderer 都接收到 Sample Filter Graph Manager 才算完成了状态的切换 ( 除了前面讲的实时源例外 )
    ·从暂停到停止。当 Filter 停止时它要释放它拥有的所有的 Sample 以使得上一级 Filter 中的 IMemAllocator::GetBuffer 调用脱离阻塞。如果 Filter Receive 函数中等待资源,它也会停止等待,并从 Receive 返回,以使调用 Filter 解锁。因此,当 Filter Graph Manager 停止连接的上一级 Filter 时, Filter 就不会在 GetBuffer Receive 函数中阻塞,也就能响应停止命令。上级 Filter 在得到停止命令前可以会递送一些额外 Sample 。但是下级 Filter 可能拒绝他们,因为他们已经停止。
    4.4 Pull Model
IMemInputPin 接口中,上级 Filter 决定发送什么样数据,然后将数据推给下 Filter 。但对某些 Filter ,拉模式更适合。现在,下 Filter 向上 Filter 请求数据。数据依然是从上 级到下级,从输出 Pin 到输入 Pin ,但是由下 Filter 发动数据流动。这种类型连接采用的是 IAsyncReader 接口。
   拉模式的典型应用是文件回放。例如在 AVI 文件的回放 Graph 中, Async File Source Filter 就执行通用文件读写操作,然后将数据以字节流的方式(不带格式信息)发送给下 Filter AVI Splitter 读取 AVI 头并把数据流分析为视频、音频流。 AVI Splitter 能比 Async File Source Filter 更好的决定它需要什么类型的数据,因此它用 IMemInputPin 代替 IAsyncReader 接口。
    为了从输出 PIN 请求数据,输入 PIN 调用如下一种方法:
    · IAsyncReader::Request
    · IAsyncReader::SyncRead
    · IAsyncReader::SyncReadAligned
    第一个方法是异步的,支持多个重叠读写。其他是同步的。
    理论上,任何 Filter 可以支持 IAsyncReader ,但是实际上它被设计来连接 Source Filter Parser Filter Parser 的功能与推模式中的 Source Filter 非常相同。当它暂停时,创建一个数据流线程从 IAsyncReader 连接拉数据,并推向下一级。输出 PIN 使用 IMemInputPin Graph 的余下部分使用标准的推模式。
 
5. Event Notification in DirectShow
 
    5.1 Overview of Event Notification
    某个事件发生时,比如数据流结束,产生一个错误,提交流失败等, Filter 就给 Filter Graph Manager 发送一个事件通知。 Filter Graph Manager 自己处理部分事件,另一部分交给应用程序处理。如果 Filter Graph Manager 没有处理事件,它就把事件通知放入到一个队列中。图表管理器也可以将自己的事件通知放进队列中。
    应用程序以事件队列获取事件并根据事件类型进行处理。 DirectShow 中的事件通知和 Windows 的消息机制差不多。应用程序也可以取消 Filter Graph Manager 特定事件的默认行为。 Filter Graph Manager 就直接把事件放入队列,留给应用程序来处理。
 
    5.2 Retrieving Events
    Filter Graph Manager 暴露了三个接口处理事件通知:
    · IMediaEventSink  Filter 用这个接口来 Post 事件。
    · IMediaEvent       应用程序利用这个接口来从队列中查询消息。
    · IMediaEventEx     IMediaEvent 的扩展。
    Filter 通过 IMediaEventSink::Notify 方法向 Filter Graph Manager 提交事件。事件通知包括一个事件 Code ,这个 Code 不仅仅代表了事件的类型,还包含两个 DWORD 类型的参数用来传递一些其他的信息。根据不同的事件 Code ,附加信息可能是指针、返回码、参考时间或其他信息。获取事件 Code 和参数的全部列表可参考 Event Notification Codes.
    应用程序通过调用 IMediaEvent::GetEvent 从事件队列中获取事件。如果有事件发生,该函数就返回一个事件码和两个参数,如果没有事件,则一直阻塞直到有事件发生和超过某个时间。调用 GetEvent 函数后,应用程序必须调用 IMediaEvent::FreeEventParams 来释放事件码所关联的资源。例如,参数可能是 Filter Graph 分配的 BSTR 内存。下面的代码演示了如何从事件队列中提取事件:
long    evCode, param1, param2;
HRESULT hr;
while (hr = pEvent->GetEvent(&evCode, &param1, &param2, 0), SUCCEEDED(hr))
{
    switch(evCode)
    {
        // Call application-defined functions for each
        // type of event that you want to handle.
    }
    hr = pEvent->FreeEventParams(evCode, param1, param2);
}
    为了重载 Filter Graph Manager 对事件的缺省处理,可以用某个事件码做参数调用 IMediaEvent CancelDefaultHandling 方法。这样就可以屏蔽 Filter Graph Manager 对某个事件码的处理了。如果要恢复图表管理器对该事件码的缺省处理,可以调用 RestoreDefaultHandling 方法。如果 Filter Graph 对某个事件没有缺省的处理,调用这两个函数是不起作用的。
 
    5.3 Learning When an Event Occurs
    为了处理 DirectShow 事件,应用程序需要一种机制来知道什么时候队列中有等待的事件。 Filter Graph Manager 提供了两种方法 :
    ·窗口通知: Filter Graph Manager 发送开发者自己定义的窗口消息
    ·事件信号:如果队列中有 DirectShow 事件,就用事件信号通知应用程序,如果队列为空就重新设置事件信号。
    应用程序可以使用这两种技术。窗口通知通常更简单。
 
    5.3.1   Window Notification
    建立窗口通知可调用 IMediaEventEx::SetNotifyWindow 方法,并指定一个私有消息。应用程序可使用从 WM_APP 0XBFFF 之间的消息值作为私有消息。只要 Filter Graph Manager 把一个新事件放入到队列时,就向目标窗口发送消息。应用程序从消息队列响应消息。
下面的代码演示了如何利用消息通知:
#define WM_GRAPHNOTIFY WM_APP+1    // Private message.
pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
    消息是一个普通的窗口消息,是从 DirectShow 的事件通知队列单独提交的。这种方法的好处多数程序已经实现了消息循环。因此,我们可以合并 DirectShow 的事件通知而不需要过多额外工作。下面的代码说明了如何响应消息通知的框架。完整的例子可参考 Responding to Events.
LRESULT CALLBACK WindowProc( HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    switch (msg)
    {
        case WM_GRAPHNOTIFY:
            HandleEvent(); // Application-defined function.
            break;
        // Handle other Windows messages here too.
    }
    return (DefWindowProc(hwnd, msg, wParam, lParam));
}
    由于事件通知和窗口的消息循环都是异步的,因此,当你的应用程序处理消息的时候,队列中或许有 N 个事件等待处理。同样,如果事件失效,它也可能从事件队列清除掉。因此,在你调用 GetEvent 的时候,一定要循环调用,直到返回一个错误码,这表明队列是空的。
    当你释放 IMediaEventEx 指针时,你可以调用 SetNotifyWindow 来取消事件通知,记住此时要给这个函数传递一个 NULL 指针。在你的事件处理程序中,在调用 GetEvent 之前一定要检查 IMediaEventEx 指针是否为空,这样就可以避免错误。因为有时在释放 IMediaEventEx 指针后可能会得到通知。
 
    5.3.2   Event Signaling
    Filter Graph Manager 里有一个手动设置的 Event 内核对象,用来反映事件队列的状态。如果队列中有等待处理的事件, Event 就处于通知状态,如果队列是空的, IMediaEvent::GetEvent 函数调用就会重置该 Event 对象。应用程序可利用此事件来判断队列的状态。
    应用程序可以调用 IMediaEvent::GetEventHandle 获得 Event 内核对象的句柄,然后就可以调用 WaitForMultipleObjects 来等待事件的发生,如果 Event 被通知了,就可以调用 GetEvent 来获得 DirectShow 的事件。下面的代码演示了事件信号。返回事件句柄,再等待 1000 毫秒。如果事件变为信号状态,调用 GetEvent 返回事件 Code 和参数。获取到 EC_COMPLETE 事件 Code 时表示回放结束中止循环:
HANDLE hEvent;
long    evCode, param1, param2;
BOOLEAN bDone = FALSE;
HRESULT hr = S_OK;
hr = pEvent->GetEventHandle((OAEVENT*)&hEvent);
if (FAILED(hr)
{
    /* Insert failure-handling code here. */
}
while(!bDone)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(hEvent, 100))
    {
        while (hr = pEvent->GetEvent(&evCode, &param1, &param2, 0), SUCCEEDED(hr))
        {
            printf("Event code: %#04x/n Params: %d, %d/n", evCode, param1, param2);
            pEvent->FreeEventParams(evCode, param1, param2);
              switch (evCode)
             {
              case EC_COMPLETE: // Fall through.
              case EC_USERABORT: // Fall through.
             case EC_ERRORABORT:
             CleanUp();
             PostQuitMessage(0);
             return;
             }
        }
    }
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值