转:WindowsMobile 调用DirectShow进行视频操作

 

既然您已经了解了基础知识,现在该看看 Windows Mobile 5.0 软件中多媒体支持的更高级的用法了。一个最有趣的开发是在 Windows Mobile 5.0 软件中包含 DirectShow API。查看该 API 的主要原因是您需要对应用程序中使用的多媒体进行更多的控制。如果前面描述的高级支持不足以满足应用程序的需求,DirectShow 提供更多的控制和更大的灵活性。DirectShow 是一个本机 API,因此可以通过本机工具集 (Visual C++) 使用,如本文后面的代码示例所示。

如果想在托管代码中访问 DirectShow,一个解决方案是将 DirectShow 的使用打包到一个本机 DLL 中,托管代码可以使用平台调用来调用该 DLL。这对于媒体捕获这样的功能而言可能是个很好的解决方案,这些功能并不依赖于用户界面组件(如媒体播放)。另一个解决方案是使用 .NET Compact Framework 2.0 中包括的 COM 互操作性来包装 DirectShow COM 组件。有一个称为 DirectShowNET 库的有趣项目,它为桌面计算机上的 DirectShow 提供托管包装,但目前没有可用于 .NET Compact Framework 的版本。

一个不错的常用方法是利用托管代码来实现用户界面、业务逻辑、数据库访问和基本的照相机交互。然后,由于需要更多的控制和灵活性,您可以使用媒体播放器控件进行媒体播放,使用 DirectShow 进行媒体捕获(功能包装在一个本机 DLL 中,通过平台调用从托管代码调用)。

开发过程中,DirectShow 在内部称为 Quartz,Quartz 还是主 DLL 的名称。1996 年 7 月,它首次作为 Microsoft ActiveMovie 版本 1.0 发布,当时它针对媒体播放提供了一个 ActiveX 控件,该控件除了支持音频文件外,还支持 Motion Picture Experts Group (MPEG) 1、影音交叉存取技术(Audio-Video Interleaved,AVI)和 QuickTime 视频。DirectShow SDK 作为 Microsoft DirectX SDK 的一部分由来已久,但 DirectShow SDK 现在包括在平台 SDK 中,它为开发 DirectShow 筛选器和应用程序提供工具和信息。

Windows Mobile 5.0 软件的 DirectShow 是用于流媒体的一种体系结构,它提供多媒体流的高质量播放和捕获。它支持很多格式,如波形 (WAV)、MP3(MPEG Audio Layer-3)、AVI、高级数据流格式(Advanced Streaming Format,ASF)和 MPEG。

DirectShow 与 Windows Mobile 5.0 支持的其他两种 DirectX 技术(DirectDraw 和 Direct3D)相集成。DirectShow 使用任何可用的视频和音频加速硬件,也支持不使用加速硬件的系统。

DirectShow 简化了媒体播放和格式转换,但对于需要自定义解决方案的应用程序而言,它还提供对基础流控制体系结构的访问。例如,您可以创建自己的组件来支持新媒体格式或自定义效果。您可以使用 DirectShow 编写的应用程序示例包括:AVI 和 MP3 播放器、AVI 到 ASF 的转换器,以及音频/视频捕获和编辑应用程序。DirectShow 基于 COM 并提供大量 COM 组件。要扩展 DirectShow,您需要实现自己的 COM 组件。

筛选器和筛选器图形

DirectShow 的主构造块是一个称为筛选器的组件。筛选器是一个在多媒体流上执行操作的软件(实际上是一个 COM)组件。例如,筛选器可以读取文件,从视频捕获设备获取视频,解码各种流格式,以及将数据传递到图形卡或声卡。

筛选器接收输入和产生输出,信息通过筛选器针在筛选器之间传递。一个针是一个筛选器端口,它可以是输入端口也可以是输出端口。如果筛选器解码 WMV 视频,则输入是 WMV 编码的流,输出是一系列未压缩的视频帧。在 DirectShow 中,一个应用程序通过将筛选器链连接在一起来执行任何任务,这样一个筛选器的输出就成为另一个筛选器的输入。一组连接的筛选器称为一个筛选器图形,图 18 显示一个用于播放带声音的视频文件的筛选器图形。

clip_image001

18. 典型视频文件的筛选器图形

筛选器图形必须遵循某些原则,第一个原则是需要一个源筛选器。这是数据的最初来源,无论它是文件、流媒体的 URL,还是诸如内置照相机这样的设备。然后,源筛选器的输出运行通过任意数量的转换筛选器。转换筛选器是这样的中间筛选器:它们接收某种类型的输入数据,修改传入的数据,然后将修改的数据传递到其输出。图的最后一部分是输出程序筛选器。输出程序筛选器是筛选器图形中处理的任何数据的最终目的地。输出程序可以代表以下内容:用于在屏幕上显示视频的窗口、用来发出声音的声卡,或者用来将数据存储到磁盘的筛选器编写器。

在图 18 中,这些筛选器如下所示:

文件源筛选器从文件系统读取视频文件。

拆分器筛选器将文件内容解析为两个流:一个压缩的视频流和一个音频流。

视频解码器筛选器对视频帧进行解码。

视频输出程序筛选器使用 DirectDraw 或图形设备接口 (GDI) 将这些帧绘制到显示器。

声音设备筛选器使用 DirectSound 播放音频流。

请注意,图 18 中筛选器边缘的小正方形表示每个筛选器的针。

DirectShow SDK 附带一个名为 GraphEdit 的工具,用于处理筛选器图形。图 19 显示 GraphEdit 工具已经呈现的 WMV 视频文件。

clip_image002

19. GraphEdit 工具中 .wmv 文件的筛选器图形。

可以使用 GraphEdit 工具呈现文件、生成自定义图形、测试自定义筛选器、逐步(一帧接一帧)显示一个图形,以及处理类似的任务。如果要在应用程序中使用的图形已经确认在 GraphEdit 中运行,使用该工具将节省大量开发时间。(需要牢记的重要一点是,如果筛选器图形无法在 GraphEdit 中工作,它也无法在您的应用程序中工作。)GraphEdit 甚至可以将一个完整的筛选器图形保存为一个文件(.grf 扩展名),应用程序稍后可以加载该文件。

DirectShow 应用程序结构

DirectShow 应用程序进行的第一个操作是使用最重要的 COM 组件,即 Filter Graph Manager。为了使应用程序开发人员免于管理筛选器及其交互的复杂任务,该组件作为一个高级构造可以简化对筛选器图形及其筛选器的控制。您可以通过将筛选器连接在一起来使用 Filter Graph Manager 生成筛选器图形,然后应用程序可以进行诸如 RunPauseStop 这样的简单调用,以便通过筛选器图形控制数据流。在筛选器图形的处理过程中,Filter Graph Manager 还将事件通知传递给应用程序。如需对流过程进行更多控制,也可以通过这些筛选器的 COM 接口直接访问它们。在任何情况下,很好地了解 COM 在使用 DirectShow 时都很有帮助。

简言之,DirectShow 应用程序的典型步骤是:

创建 Filter Graph Manager 实例。

使用 Filter Graph Manager 实例生成一个筛选器图形,方法是直接使用呈现功能或筛选器。

通过对 Filter Graph Manager 实例进行高级调用来控制媒体流,并响应 Filter Graph Manager 实例引发的事件。

处理完成后,释放 Filter Graph Manager 实例以及使用的所有筛选器。

当使用 DirectShow 播放媒体时,需要注意的重要一点是,它使用单独的线程运行筛选器图形。在 DirectShow 筛选器图形的执行过程中,您会看到创建并运行了若干个线程,因为 DirectShow 为 Filter Graph Manager 创建一个线程,然后为筛选器图形中的每个筛选器都创建一个单独的线程。因此,应用程序将在 DirectShow 播放该媒体文件时继续运行,在大多数应用程序中,这对用户界面响应是件好事。然而,您需要为 DirectShow 线程留出足够的时间运行。例如,如果应用程序的主线程在播放媒体文件的同时进行大量处理,由于这些线程的优先级较低,媒体播放时将时断时续。

视频播放

简单任务(如显示视频文件)以及一些基本的控制台应用程序代码如下所示:

#include 
 
 
void __cdecl main(void)
{
  IGraphBuilder *pGraphBuilder;
  IMediaControl *pMediaControl;
  CoInitialize(NULL);
    
  CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, 
    IID_IGraphBuilder, (void **)&pGraphBuilder);
  pGraphBuilder->QueryInterface(IID_IMediaControl,
    (void **)&pMediaControl);
 
  pGraphBuilder->RenderFile(L"test.wmv", NULL);
 
  pMediaControl->Run();
 
  MessageBox(NULL, "Click OK to end playback.", "DirectShow", MB_OK);
 
  pMediaControl->Release();
  pGraphBuilder->Release();
  CoUninitialize();
}

上述代码示例首先声明和初始化 COM 库,然后该 COM 库用于创建 Filter Graph Manager 实例,该实例引用了图形生成器 (IGraphBuilder) 和媒体控件 (IMediaControl) 接口。图形生成器通过呈现(使用 RenderFile 方法)视频文件 (test.wmv) 来创建筛选器图形,然后媒体控件接口 (pMediaControl) 启动(使用 Run 方法)筛选器图形处理。显示一个消息框以防止应用程序关闭,但这不会影响视频的呈现,因为筛选器图形运行在单独的线程上。当该用户在消息框中点击 OK 时,接口引用随 COM 库一起释放。

更复杂的解决方案是使用以下代码侦听来自 Filter Graph Manager 实例的事件。

#include 
 
 
void __cdecl main(void)
{
  IGraphBuilder *pGraphBuilder;
  IMediaControl *pMediaControl;
  IMediaEvent   *pMediaEvent;
  CoInitialize(NULL);
    
  CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, 
    IID_IGraphBuilder, (void **)&pGraphBuilder);
  pGraphBuilder->QueryInterface(IID_IMediaControl,
    (void **)&pMediaControl);
  pGraphBuilder->QueryInterface(IID_IMediaEvent,
    (void **)&pMediaEvent);
 
  pGraphBuilder->RenderFile(L"test.wmv", NULL);
 
  pMediaControl->Run();
 
  long eventCode;
  pMediaEvent->WaitForCompletion(INFINITE, &eventCode);
 
  pMediaControl->Release();
  pGraphBuilder->Release();
  CoUninitialize();
}

请注意,上述代码除了以粗体显示的新加内容外,几乎与之前的基本代码示例相同。在此处,您创建一个对事件 (IMediaEvent) 接口的引用,它用于等待筛选器图形处理完成。然而,在实际应用程序中,您应该避免使用 INFINITE,因为它可能会导致应用程序无限期阻塞。

如果不指定其他内容,该播放在单独的弹出窗口中进行。但在许多情况中,您可能想使播放窗口成为应用程序的子窗口。要指定播放窗口的所有者、类型和位置,可以使用以下代码(从上面代码示例修改而来)。

#include 
 
 
void __cdecl main(void)
{
  IGraphBuilder *pGraphBuilder;
  IMediaControl *pMediaControl;
  IMediaEvent   *pMediaEvent;
  IVideoWindow  *pVideoWindow;
  CoInitialize(NULL);
    
  CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, 
    IID_IGraphBuilder, (void **)&pGraphBuilder);
  pGraphBuilder->QueryInterface(IID_IMediaControl,
    (void **)&pMediaControl);
  pGraphBuilder->QueryInterface(IID_IMediaEvent,
    (void **)&pMediaEvent);
  pGraphBuilder->QueryInterface(IID_IVideoWindow,
    (void **)&pVideoWindow);
 
  pGraphBuilder->RenderFile(L"test.wmv", NULL);
 
  pVideoWindow->put_Owner((OAHWND)g_hwnd);
  pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
 
  RECT rect;
  GetClientRect(g_hWnd, &rect);
  pVideoWindow->SetWindowPosition(0, 0, rect.right, rect.bottom);
 
  pMediaControl->Run();
 
  long eventCode;
  pMediaEvent->WaitForCompletion(INFINITE, &eventCode);
 
  pVideoWindow->Release();
  pMediaControl->Release();
  pGraphBuilder->Release();
  CoUninitialize();
}

改动的内容还是用粗体表示。首先,设置典型子窗口的所有者和样式。然后,将视频播放窗口的大小和位置设置为与主应用程序窗口 (g_hWnd) 的客户端区域(在 rect 中加载)的大小和位置相同。

请注意,在前面的代码示例中,排除了错误处理以便容易阅读。为完整起见,包括错误处理的相同代码如下所示。

#include 
 
 
void __cdecl main(void)
{
  IGraphBuilder *pGraphBuilder;
  IMediaControl *pMediaControl;
  IMediaEvent   *pMediaEvent;
  IVideoWindow  *pVideoWindow;
  HRESULT hr = CoInitialize(NULL);
  if(FAILED(hr))
  {
    printf("ERROR: Couldn't initialize COM library!");
    return;
  }
 
  hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, 
    IID_IGraphBuilder, (void **)&pGraphBuilder);
  if(FAILED(hr))
  {
    printf("ERROR: Couldn't create Filter Graph Manager instance!");
    return;
  }
 
  pGraphBuilder->QueryInterface(IID_IMediaControl,
    (void **)&pMediaControl);
  pGraphBuilder->QueryInterface(IID_IMediaEvent,
    (void **)&pMediaEvent);
  pGraphBuilder->QueryInterface(IID_IVideoWindow,
    (void **)&pVideoWindow);
 
  hr = pGraphBuilder->RenderFile(L"test.wmv", NULL);
  if(SUCCEEDED(hr))
  {
    pVideoWindow->put_Owner((OAHWND)g_hwnd);
    pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
 
    RECT rect;
    GetClientRect(g_hWnd, &rect);
    pVideoWindow->SetWindowPosition(0, 0, rect.right, rect.bottom);
 
    hr = pMediaControl->Run();
    if(SUCCEEDED(hr))
    {
      long eventCode;
      pMediaEvent->WaitForCompletion(INFINITE, &eventCode);
    }
    else
      printf("ERROR: Couldn't run filter graph!");
  }
  else
    printf("ERROR: Couldn't render video file!");
 
  pVideoWindow->Release();
  pMediaControl->Release();
  pGraphBuilder->Release();
  CoUninitialize();
}

要接收 Filter Graph Manager 引发的事件,需要以下两个全局声明。

#define WM_FILTERGRAPHNOTIFY WM_APP + 1
IMediaEventEx *g_pMediaEventEx = NULL;

请注意,常量 WM_FILTERGRAPHNOTIFY 可设置为任何值,WM_APP + 1 就是一个示例。在播放该流(运行筛选器图形)之前,请使用以下代码。

g_pGraphBuilder->QueryInterface(IID_IMediaEventEx,
  (void **)&g_pMediaEventEx);
g_pMediaEventEx->SetNotifyWindow((OAHWND)g_hWnd,
  WM_FILTERGRAPHNOTIFY, 0);

上述代码指示 Filter Graph Manager 使用第二个参数 (WM_FILTERGRAPHNOTIFY) 的消息标识将事件发送到主应用程序窗口 (g_hWnd)。请注意,SetNotifyWindow 调用的第三个参数将作为窗口消息 (WM_FILTERGRAPHNOTIFY) 的 lParam 参数返回到应用程序。在前面的示例代码中不使用该参数,因此它设为零。然而,该参数可用于传递实例数据和事件。

现在,可以将以下代码添加到应用程序的消息循环(通常在 WndProc 函数中)。

case WM_FILTERGRAPHNOTIFY:
  HandleFilterGraphEvent();
  break;

然后,可以使用以下代码处理事件。

void HandleFilterGraphEvent()
{
  if (g_pMediaEventEx == NULL)
    return;
 
  long eventCode;
  LONG_PTR param1, param2;
  HRESULT hr;
  while(SUCCEEDED(g_pMediaEventEx->GetEvent(&eventCode,
    ¶m1, ¶m2, 0)))
  {
    g_pMediaEventEx->FreeEventParams(eventCode, param1, param2);
    switch(eventCode)
    {
      case EC_COMPLETE:
      case EC_USERABORT:
      case EC_ERRORABORT:
        g_pMediaControl->Stop();
        long eventCode;
        g_pMediaEvent->WaitForCompletion(INFINITE, &eventCode);
        g_pMediaEventEx->SetNotifyWindow(NULL, 0, 0);
        g_pMediaEventEx->Release();
        g_pMediaEventEx = NULL;
        // Do other clean-up (releases, etc.)
        PostQuitMessage(0);
        return;
    }
  } 
}

如果没有设置事件指针,将退出该处理,然后检索该队列上的所有事件。GetEvent 方法的第四个参数是等待事件的时间(以毫秒为单位)。因为来自 Filter Graph Manager 的事件已经在队列中,所以该参数可以设为零,这意味着不等待。请注意,EC_COMPLETE 事件不会自动停止筛选器图形的处理,因此在接收到该事件时停止该筛选器图形是一个好做法。

该介绍将使您能够在自己的应用程序中开始实现媒体播放,因此,现在本文将解决更复杂的捕获视频和声音的任务。

视频捕获

创建针对视频和音频捕获的筛选器图形比创建针对播放的筛选器更复杂。图 20 显示带有声音的视频捕获的典型筛选器图形。

clip_image003

20. 带有声音的视频捕获的筛选器图形

为了帮助创建和控制筛选器图形,DirectShow 提供了一个名为 Capture Graph Builder 的组件。就像对播放筛选器图形一样,首先创建一个 Filter Graph Manager 实例。然后,创建 Capture Graph Builder 实例并将两者相连。该本机代码如下所示。

IGraphBuilder *pGraphBuilder;
ICaptureGraphBuilder2 *pCaptureGraphBuilder;
 
CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
  IID_IGraphBuilder, (void**)&pGraphBuilder);
 
CoCreateInstance(CLSID_CaptureGraphBuilder, NULL,
  CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2,
  (void**)&pCaptureGraphBuilder);
 
pCaptureGraphBuilder->SetFiltergraph(pGraphBuilder);

因为它将节省一些编码工作(特别是关于 COM 的代码),您可以使用以下活动模板库 (ATL) 代码。

CComPtr
 
 
  
           pGraphBuilder;
 
 
CComPtr
 
 
  
   pCaptureGraphBuilder;
 
 
 
pCaptureGraphBuilder.CoCreateInstance(CLSID_CaptureGraphBuilder);
pGraphBuilder.CoCreateInstance(CLSID_FilterGraph);
pCaptureGraphBuilder->SetFiltergraph(pGraphBuilder);

继续 ATL,下一步是使用以下代码初始化视频捕获筛选器(如前面的图 20 所示)。

CComPtr
 
 
  
           pVideoCapture;
 
 
CComPtr
 
 
  
   pPropertyBag;
 
 
DEVMGR_DEVICE_INFORMATION    di;
CPropertyBag                 PropBag;
CComVariant                  varCamName;
GUID guidCamera = { 0xCB998A05, 0x122C, 0x4166, 0x84, 0x6A,
                    0x93, 0x3E, 0x4D, 0x7E, 0x3C, 0x86 };
 
di.dwSize = sizeof(di);
HANDLE handle = FindFirstDevice(DeviceSearchByGuid, &guidCamera, &di);
FindClose(handle);
pVideoCapture.CoCreateInstance(CLSID_VideoCapture)); 
pVideoCapture.QueryInterface(&pPropertyBag));
varCamName = di.szLegacyName;
PropBag.Write(L"VCapName", &varCamName);   
pPropertyBag->Load(&PropBag, NULL);
pPropertyBag.Release();
pGraphBuilder->AddFilter(pVideoCapture, L"Video capture source");

第一个照相机捕获设备通过 FindFirstDevice 函数检索,该函数的第二个参数设置为 DEVCLASS_CAMERA_GUID(对应于在前面的代码中硬编码的 GUID [CB998A05-122C-4166-846A-933E4D7E3C86]),该检索方式是查找捕获设备的最可靠方式。属性包实例(PropBag 是实现 IPropertyBag 接口的自定义类 [CPropertyBag] 的实例)用于将捕获设备名称信息传递到捕获筛选器,然后该视频捕获筛选器添加到筛选器图形。

下一步是初始化音频捕获筛选器,为此您可以使用以下代码。

CComPtr
 
 
  
   pAudioCaptureFilter;
 
 
 
pAudioCaptureFilter.CoCreateInstance(CLSID_AudioCapture);
pAudioCaptureFilter.QueryInterface(&pPropertyBag);
pPropertyBag->Load(NULL, NULL);
pGraphBuilder->AddFilter(pAudioCaptureFilter, L"Audio Capture Filter");

创建音频捕获筛选器并将其添加到筛选器图形。现在应该初始化视频编码器并将其添加到筛选器图形。您可以借助于DMO Wrapper 筛选器在筛选器图形中使用 DirectX 媒体对象 (DMO) 实例。要使用 DMO Wrapper 筛选器用 WMV 9 DMO 对视频进行编码,可以使用以下代码。

CComPtr
 
 
  
         pVideoEncoder;
 
 
CComPtr
 
 
  
   pVideoWrapperFilter;
 
 
 
pVideoEncoder.CoCreateInstance(CLSID_DMOWrapperFilter);
pVideoEncoder.QueryInterface(&pVideoWrapperFilter);
pVideoWrapperFilter->Init(CLSID_CWMV9EncMediaObject,
  DMOCATEGORY_VIDEO_ENCODER);
pGraphBuilder->AddFilter(pVideoEncoder, L"WMV9 DMO Encoder");

WMV 9 编码器加载到 DMO Wrapper 筛选器中之后,就该加载 ASF 多路复用器和设置多路复用器的名称了(使用对多路复用器的文件接收接口的引用)。进行此操作的代码如下所示。

CComPtr
 
 
  
       pAsfWriter;
 
 
CComPtr
 
 
  
   pFileSink;
 
 
 
pAsfWriter.CoCreateInstance(CLSID_ASFWriter);
pAsfWriter->QueryInterface(IID_IFileSinkFilter, (void**) &pFileSink);
pFileSink->SetFileName(L"My Documentstest.asf", NULL);

现在已经创建了该图形中需要的所有筛选器。下一步是使用以下代码将这些筛选器的针连接在一起。

pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,
  &MEDIATYPE_Video, pVideoCapture, pVideoEncoder, pAsfWriter );
 
pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,
  &MEDIATYPE_Audio, pAudioCaptureFilter, NULL, pAsfWriter );
 
pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
  &MEDIATYPE_Video, pVideoCapture, NULL, NULL );

视频捕获通过视频编码器连接到多路复用器,然后音频捕获也连接到多路复用器。最后,视频捕获筛选器的预览针连接到视频输出程序。不需要指定视频输出程序(作为最后一个参数),因为它是默认指定的。

既然已经对这些筛选器进行了初始化,将这些筛选器添加到筛选器图形并且连接了所有针,现在就已经准备好使用下面的代码来捕获数据了。

CComPtr
 
 
  
   pMediaControl;
 
 
CComPtr
 
 
  
     pMediaEvent;
 
 
CComPtr
 
 
  
   pMediaSeeking;
 
 
 
pCaptureGraphBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,
  &MEDIATYPE_Video, pVideoCapture, 0, 0 , 0, 0);
pCaptureGraphBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,
  &MEDIATYPE_Audio, pAudioCaptureFilter, 0, 0, 0, 0);
 
pGraphBuilder.QueryInterface(&pMediaControl);
pMediaControl->Run();
Sleep(1000);
 
LONGLONG dwStart = 0;
LONGLONG dwEnd = MAXLONGLONG;
OutputDebugString(L"Starting to capture the first file" );
pCaptureGraphBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,
  &MEDIATYPE_Video, pVideoCapture, &dwStart, &dwEnd, 0, 0);
pCaptureGraphBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,
  &MEDIATYPE_Audio, pAudioCaptureFilter, &dwStart, &dwEnd, 0, 0);
Sleep(5000);
 
OutputDebugString(L"Stopping the capture");
pGraphBuilder.QueryInterface(&pMediaSeeking);
pMediaSeeking->GetCurrentPosition(&dwEnd);
pCaptureGraphBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,
  &MEDIATYPE_Video, pVideoCapture, &dwStart, &dwEnd, 1, 2);
pCaptureGraphBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,
  &MEDIATYPE_Audio, pAudioCaptureFilter, &dwStart, &dwEnd, 1, 2);
 
OutputDebugString(L"Wating for the control stream events");
pGraphBuilder.QueryInterface(&pMediaEvent);
long lEventCode;
LONG_PTR lParam1, lParam2;
do
{
  pMediaEvent->GetEvent(&lEventCode, &lParam1, &lParam2, INFINITE);
  pMediaEvent->FreeEventParams(lEventCode, lParam1, lParam2);
 
  if(lEventCode == EC_STREAM_CONTROL_STOPPED)
  {
    OutputDebugString(L"Received a control stream stop event");
    count++;
  }
} while(count < 2);
 
OutputDebugString(L"The file has been captured");

该捕获图形由视频和音频控制流阻塞,然后在实际捕获数据之前允许它运行一秒。该延迟为捕获图形提供了时间,以确保分配它所有的缓冲区以及同步所有进程。一个捕获进行 5 秒,然后停止。视频和音频的控制流用于停止该流,最后,一个循环等待一个标志该流停止的事件。

下面的代码片段显示一种更明确的方式来捕获图形正在运行的时刻(与等待 1 秒相比)。

OAFilterState state = State_Stopped;
pMediaControl->Run();  
while(state != State_Running)  
  pMediaControl->GetState(100, &state);

GetState 方法的第一个参数是超时(以毫秒为单位),因此该代码每 1/10 秒将尝试一次,以查看该捕获图形是否已经启动且正在运行。

有关视频捕获的更多详细信息,请参阅 Windows Mobile 5.0 Pocket PC SDK 附带的 CameraCapture 示例。

自定义筛选器

正如前面提到的,基本上有三种筛选器类型:源筛选器、转换筛选器和输出程序筛选器。源筛选器提供来自源的原始多媒体数据,如文件、URL 或类似照相机的实时源。源筛选器可以将原始数据传递到分析器或拆分器筛选器,也可以自己进行分析或拆分。输出程序筛选器接受完全处理的数据并在显示器或扬声器上进行呈现,它们包括编写文件的筛选器。源筛选器和输出程序筛选器之间的所有筛选器都是转换筛选器。转换筛选器使用原始数据或部分处理的数据,并在将其传递到下一个筛选器之前进行处理。有许多不同类型的转换筛选器;一些筛选器将字节流解析为示例或帧,而其他筛选器进行压缩或解压缩,甚至进行格式转换。

虽然 DirectShow 包括大量用于播放、转换和捕获许多不同媒体格式的现成筛选器,但是开发人员可以生成自己的自定义筛选器以便处理自定义或标准数据格式。实现自定义筛选器时,它可能是一个转换筛选器。它可能是一个将效果(如淡入或淡出)添加到视频流的筛选器。

DirectShow SDK 包括大量自定义筛选器,SDK 文档提供针对编写自定义筛选器的优秀介绍。以下来自 SDK 文档的摘要提供创建转换筛选器的基本步骤:

确定筛选器是必须复制媒体示例还是适当地处理它们。媒体流中进行的复制越少越好。然而,某些筛选器需要一个复制操作;该要求影响基类的选择。

确定使用哪些基类并从基类派生筛选器类(如果需要,也可以派生针类)。在该步骤中,为筛选器创建一个或多个标头。在许多情况中,可以使用转换基类,从正确的转换筛选器类派生类,以及重写几个成员函数。在其他情况中,可以使用更通用的基类。这些类实现了大部分的连接和协商机制;但这些类也提供在重写更多成员函数的开销方面的灵活性。

添加实例化筛选器所需的代码。该步骤需要将静态 CreateInstance 成员函数添加到派生的类中,该类还是一个全局数组,包含筛选器名称、CLSID 和指向该成员函数的指针。

调用 NonDelegatingQueryInterface 函数在您的筛选器中分布任何唯一的接口。该步骤强调实现接口的 COM 方面,而不是基类中的其他方面。

重写基类成员函数。该步骤包括编写对于筛选器而言唯一的转换函数,以及重写连接过程所需的几个成员函数,如设置分配器大小或提供媒体类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值