Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3518966
  • 博文数量: 864
  • 博客积分: 14125
  • 博客等级: 上将
  • 技术积分: 10634
  • 用 户 组: 普通用户
  • 注册时间: 2007-07-27 16:53
个人简介

https://github.com/zytc2009/BigTeam_learning

文章分类

全部博文(864)

文章存档

2023年(1)

2021年(1)

2019年(3)

2018年(1)

2017年(10)

2015年(3)

2014年(8)

2013年(3)

2012年(69)

2011年(103)

2010年(357)

2009年(283)

2008年(22)

分类: WINDOWS

2009-07-05 07:49:45

本节描述DirectShow的整体结构。本节包含的内容比较丰富,我们可能不需要知道所有的这些知识。因此,我们首先应该选择浏览全部的内容,然后根据实际应用程序的需要查看Using DirectShow的内容。如果有关于DirectShow结构的特殊问题,可以再回过来参考本节的内容。

【续前一篇文章】

    4.3 Filter States
    Filter有三种状态,停止,暂停,运行。暂停状态是为了在GraphCue Data, 使得运行命令可以立即响应。Filter Graph Manager控制着所有状态的转换。当应用程序调用IMediaControlRun, Pause, Stop方法时, Filter Graph Manager就调用所有Filter的相应IMediaFilter方法。停止,运行状态的切换总是要经过暂停,因此,当应用程序对停止的Graph 调用RUN命令时,Filter图表管理器在Run之前首先要暂停。对于大多数的Filter来说,RunningPaused状态是一样的。考虑如下的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保持暂停,在第一帧SampleSample就堆积在Graph, 直到每个Filter都阻塞在ReceiveGetBuffer. 但是不会有数据丢失。一旦Source线程非阻塞,它简单从阻塞的点恢复。
    Source FilterTransform 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 FilterIMediaFilter方法GetState返回VFW_S_CANT_CUE。此返回值表示Filter已经转换到暂停状态,即使Renderer没有接收到任何数据。
    当一个Filter停止时,它拒绝发送给它的任何SamplesSource Filter关闭他们的Stream线程,其他Filter也关闭他们创建的工作线程,PinDecommit他们的内存分配器。
 
    4.3.1   State Transitions
    Filter Graph Manager按照逆流的方向来切换Filter的状态,从Renderer FilterSource Filter,这种方式可以防止死锁和Sample的丢失。最关键的状态切换是暂停和停止之间。
    ·从停止到暂停,当Filter暂停时就准备好了从连接Filter接收SampleSource Filter是最后一个暂停的。它创建Streaming线程发送Sample,因为下游的Filter的状态都已经切换到暂停了,所以,所有的Filter都可以接收Sample。只有当Graph所有的Renderer都接收到SampleFilter Graph Manager才算完成了状态的切换(除了前面讲的实时源例外)
    ·从暂停到停止。当Filter停止时它要释放它拥有的所有的Sample以使得上一级Filter中的IMemAllocator::GetBuffer调用脱离阻塞。如果FilterReceive函数中等待资源,它也会停止等待,并从Receive返回,以使调用Filter解锁。因此,当Filter Graph Manager停止连接的上一级Filter时,Filter就不会在GetBufferReceive函数中阻塞,也就能响应停止命令。上级Filter在得到停止命令前可以会递送一些额外Sample。但是下级Filter可能拒绝他们,因为他们已经停止。
    4.4 Pull Model
IMemInputPin接口中,上级Filter决定发送什么样数据,然后将数据推给下Filter。但对某些Filter,拉模式更适合。现在,下Filter向上Filter请求数据。数据依然是从上级到下级,从输出Pin到输入Pin,但是由下Filter发动数据流动。这种类型连接采用的是IAsyncReader接口。
   拉模式的典型应用是文件回放。例如在AVI文件的回放Graph中,Async File Source Filter就执行通用文件读写操作,然后将数据以字节流的方式(不带格式信息)发送给下FilterAVI Splitter读取AVI头并把数据流分析为视频、音频流。AVI Splitter能比Async File Source Filter更好的决定它需要什么类型的数据,因此它用IMemInputPin代替IAsyncReader接口。
    为了从输出PIN请求数据,输入PIN调用如下一种方法:
    ·IAsyncReader::Request
    ·IAsyncReader::SyncRead
    ·IAsyncReader::SyncReadAligned
    第一个方法是异步的,支持多个重叠读写。其他是同步的。
    理论上,任何Filter可以支持IAsyncReader,但是实际上它被设计来连接Source FilterParser FilterParser的功能与推模式中的Source Filter非常相同。当它暂停时,创建一个数据流线程从IAsyncReader连接拉数据,并推向下一级。输出PIN使用IMemInputPinGraph的余下部分使用标准的推模式。
 
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, ¶m1, ¶m2, 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对事件的缺省处理,可以用某个事件码做参数调用IMediaEventCancelDefaultHandling方法。这样就可以屏蔽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_APP0XBFFF之间的消息值作为私有消息。只要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, ¶m1, ¶m2, 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;
             }
        }
    }
}
阅读(1046) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~