第一篇: DirectShow视频采集

官方文档:DirectShow 简介 - Win32 apps | Microsoft Learn

一、前言

1)、DriectShow是windows基础的采集模块,支持xp、win7、vista 以及更新的wIndows版本。

2)、在win8之后 建议使用MediaPlayer、 IMFMediaEngine 和 Media Foundation ,但由于部分虚拟摄像头 并不支持MediaFoundation,所以DirectShow在采集领域仍然占领一席之地

3)、DriectShow也有一定的局限性,能识别的采集格式相对较少,在部分摄像头(HD Web Camera)测试出在部分分辨率的情况下会出现卡顿

二、筛选器和筛选图形

1)、DirectShow的构建基块是一个名为筛选器的软件组件

筛选器是一个软件组件,用于对多媒体流执行某些操作。 例如,DirectShow筛选器可以

  • 读取文件

  • 从视频捕获设备获取视频

  • 解码各种流格式,例如 MPEG-1 视频

  • 将数据传递到图形或声卡

筛选器接收输入并生成输出。 例如,如果筛选器解码 MPEG-1 视频,则输入为 MPEG 编码流,输出是一系列未压缩的视频帧。

在DirectShow中,应用程序通过将筛选器链连接在一起来执行任何任务,以便一个筛选器的输出成为另一个筛选器的输入。 一组连接的筛选器称为 筛选器图。 例如,下图显示了用于播放 AVI 文件的筛选器图。

3)、筛选器可分为多个大类:

4)、筛选器Graph管理器支持以下图形生成方法

  • 将文件中的原始数据作为字节流读取, (文件源筛选器) 。

  • 检查 AVI 标头,并将字节流分析为单独的视频帧和音频示例, (AVI 拆分器筛选器) 。

  • 根据压缩格式) , (各种解码器筛选器解码视频帧。

  • (视频呈现器筛选器) 绘制视频帧。

  • 将音频示例发送到声卡, (默认 DirectSound 设备筛选器)

    文件源筛选器从硬盘读取 AVI 文件。 AVI 拆分器筛选器将文件分析为两个流,即压缩的视频流和音频流。 AVI 解压缩器筛选器解码视频帧。 视频呈现器筛选器使用 DirectDraw 或 GDI 将帧绘制到显示器。 默认 DirectSound 设备筛选器使用 DirectSound 播放音频流。

    2)、筛选器Graph管理器 是一个 COM 对象,用于控制筛选器图中的筛选器。 它执行许多函数,包括:

  • 协调筛选器之间的状态更改。

  • 建立引用时钟。

  • 将事件传达回应用程序。

  • 为应用程序提供生成筛选器图的方法。

  • 筛选器将数据引入图形。 数据可能来自文件、网络、相机或其他地方。 每个源筛选器处理不同类型的数据源。

  • 转换筛选器采用输入流、处理数据并创建输出流。 编码器和解码器是转换筛选器的示例。

  • 呈现器 筛选器位于链的末尾。 他们接收数据并将其呈现给用户。 例如,视频呈现器在显示器上绘制视频帧;音频呈现器将音频数据发送到声卡;和文件编写器筛选器将数据写入文件。

  • 拆分器筛选器将输入流拆分为两个或多个输出,通常沿路分析输入流。 例如,AVI 拆分器将字节流分析为单独的视频和音频流。

  • 复用筛选器采用多个输入,并将其合并到单个流中。 例如,AVI Mux 执行 AVI 拆分器反运算。 它采用音频和视频流并生成 AVI 格式字节流。

  • IFilterGraph::ConnectDirect 尝试在两个引脚之间建立直接连接。 如果引脚无法连接,该方法将失败。

  • IGraphBuilder::连接连接两个引脚。 如果可能,它将建立直接连接。 否则,它使用中间筛选器来完成连接。

  • IGraphBuilder::Render 从输出图钉开始,并生成图形的其余部分。 此方法根据需要添加筛选器,在下游工作,直到到达呈现器筛选器。

  • IGraphBuilder::RenderFile 生成完整的文件播放图形。

  • IFilterGraph::AddFilter 向图形添加筛选器。 它不连接筛选器。 必须先创建筛选器,然后调用 CoCreateInstance 或使用筛选器映射器或系统设备枚举器来调用此方法。

三、编写DirectShow应用程序

有三个任务DirectShow应用程序必须执行。 下图说明了这些内容。

  1. 应用程序创建筛选器Graph管理器的实例。

  2. 应用程序使用筛选器Graph管理器生成筛选器图。 图形中的确切筛选器集将取决于应用程序。

  3. 应用程序使用筛选器Graph管理器来控制筛选器图并通过筛选器流式传输数据。 在此过程中,应用程序还将响应来自筛选器Graph管理器的事件。

     处理完成后,应用程序将释放筛选器Graph管理器和所有

四、多媒体存在的挑战

  • 多媒体流包含大量数据,必须非常快速地处理这些数据。

  • 音频和视频必须同步,使其同时启动和停止,并以相同的速率播放。

  • 数据可能来自许多来源,包括本地文件、计算机网络、电视广播和摄像机。

  • 数据采用各种格式,例如Audio-Video交错 (AVI) 、高级流式处理格式 (ASF) 、电影专家组 (MPEG) 和数字视频 (DV) 。

  • 程序员事先不知道最终用户系统上将存在哪些硬件设备。

DirectShow解决方案:

1)、DirectShow旨在解决上述每个挑战。 其主要设计目标是通过将应用程序与数据传输、硬件差异和同步的复杂性隔离,从而简化在 Windows 平台上创建数字媒体应用程序的任务。

2)、若要实现流式传输视频和音频所需的吞吐量,D

irectShow尽可能使用 Direct3D 和 DirectSound。 这些技术有效地将数据呈现给用户的声音和图形卡。 DirectShow通过在时间戳样本中封装媒体数据来同步播放。 为了处理可能的各种源、格式和硬件设备,DirectShow使用模块化体系结构,其中应用程序混合和匹配称为筛选器的不同软件组件。

3)、DirectShow提供筛选器,这些筛选器支持基于 Windows 驱动程序模型 (WDM) ,以及支持为音频压缩管理器 (ACM) 和视频压缩管理器 (VCM) ) 接口编写的旧 Windows (视频视频的筛选器。

下图显示了应用程序、DirectShow组件以及DirectShow支持的一些硬件和软件组件之间的关系。

DirectShow筛选器与各种设备通信和控制,包括本地文件系统、电视调音器和视频捕获卡、VfW 编解码器、通过 DirectDraw 或 GDI) (视频显示、通过 DirectSound) (声卡。 因此,DirectShow使应用程序免受这些设备的许多复杂性的隔离。 DirectShow还为某些文件格式提供本机压缩和解压缩筛选器。

五、媒体类型

媒体类型是描述数字媒体格式的通用且可扩展的方法。 当两个筛选器连接时,它们同意媒体类型。 媒体类型标识上游筛选器将传递到下游筛选器的数据类型,以及数据的物理布局。 如果两个筛选器无法在媒体类型上达成一致,则它们将不会连接。

对于某些应用程序,你永远不会担心媒体类型。 例如,在文件播放中,DirectShow处理所有详细信息。 其他类型的应用程序可能需要直接处理媒体类型。

媒体类型是使用 AM_MEDIA_TYPE 结构定义的。 此结构包含以下信息:

  • 主要类型:主要类型是一个 GUID,用于定义数据的总体类别。 主要类型包括视频、音频、未分析字节流、MIDI 数据等。

  • 子类型:子类型是另一个 GUID,进一步定义格式。 例如,在视频主要类型中,RGB-24、RGB-32、UYVY 等有子类型。 在音频中,有 PCM 音频、MPEG-1 有效负载等。 子类型提供了比主要类型更多的信息,但它不定义有关格式的所有内容。 例如,视频子类型不定义图像大小或帧速率。 这些由格式块定义,如下所述。

  • 格式块:格式块是详细描述格式的数据块。 格式块与 AM_MEDIA_TYPE 结构分开分配。 AM_MEDIA_TYPE结构的 pbFormat 成员指向格式块。

    pbFormat 成员的类型为 void* ,因为格式块的布局会根据媒体类型而更改。 例如,PCM 音频使用 波形图X 结构。 视频使用各种结构,包括 VIDEOINFOHEADERVIDEOINFOHEADER2AM_MEDIA_TYPE结构的 formattype 成员是一个 GUID,指定格式块中包含的结构。 为每个格式结构分配 GUID。 cbFormat 成员指定格式块的大小。 在取消引用 pbFormat 指针之前,请始终检查这些值。

如果填充了格式块,则主要类型和子类型包含冗余信息。 但是,主要类型和子类型提供了一种方便的方法,用于标识格式时没有完整的格式块。 例如,可以指定通用 24 位 RGB 格式 (MEDIASUBTYPE_RGB24) ,而无需了解 VIDEOINFOHEADER 结构所需的所有信息,例如图像大小和帧速率。

例如,筛选器可能使用以下代码检查媒体类型:

HRESULT CheckMediaType(AM_MEDIA_TYPE *pmt)
{
    if (pmt == NULL) return E_POINTER;

    // Check the major type. We're looking for video.
    if (pmt->majortype != MEDIATYPE_Video)
    {
        return VFW_E_INVALIDMEDIATYPE;
    }

    // Check the subtype. We're looking for 24-bit RGB.
    if (pmt->subtype != MEDIASUBTYPE_RGB24)
    {
        return VFW_E_INVALIDMEDIATYPE;
    }

    // Check the format type and the size of the format block.
    if ((pmt->formattype == FORMAT_VideoInfo) &&
         (pmt->cbFormat >= sizeof(VIDEOINFOHEADER) &&
         (pmt->pbFormat != NULL))
    {
        // Now it's safe to coerce the format block pointer to the
        // correct structure, as defined by the formattype GUID.
        VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat;
    
        // Examine pVIH (not shown). If it looks OK, return S_OK.
        return S_OK;
    }

    return VFW_E_INVALIDMEDIATYPE;
}

AM_MEDIA_TYPE结构还包含一些可选字段。 这些筛选器可用于提供其他信息,但不需要筛选器才能使用这些信息:

  • lSampleSize。 如果此字段为非零,则定义每个样本的大小。 如果为零,则表示样本大小可能从样本更改为样本。

  • bFixedSizeSamples。 如果此布尔标志为 TRUE,则表示 lSampleSize 中的值有效。 否则,应忽略 lSampleSize

  • bTemporalCompression。 如果此布尔标志为 FALSE,则表示所有帧都是关键帧。

六、数据流

1)、数据保存在缓冲区中,这些缓冲区只是字节数组。 每个缓冲区由称为 媒体示例的 COM 对象包装,该对象实现 IMediaSample 接口。 示例由另一种类型的对象创建,称为分配器,该分配 器实现 IMemAllocator 接口。 为每个引脚连接分配分配器分配一个分配器,尽管两个或更多个引脚连接可能会共享相同的分配器。 下图演示了此过程。

每个分配器创建一个媒体示例池,并为每个样本分配缓冲区。 每当筛选器需要用数据填充缓冲区时,它都会通过调用 IMemAllocator::GetBuffer 从分配器请求示例。 如果分配器具有当前未由另一个筛选器使用的任何样本, 则 GetBuffer 方法会立即返回指向示例的指针。 如果所有分配器的示例都正在使用中,该方法将阻止,直到样本可用为止。 当该方法返回示例时,筛选器会将数据放入缓冲区,在示例上设置相应的标志, (通常包括时间戳) ,并传递下游示例。

2)、当呈现器筛选器收到样本时,它会检查时间戳并保留到样本上,直到筛选器图的引用时钟指示应呈现数据。 筛选器呈现数据后,会释放示例。 在样本的引用计数为零之前,该示例不会返回到分配器的样本池中,这意味着每个筛选器都释放了样本。 下图演示了此过程。

上游筛选器可能在呈现器之前运行,也就是说,它可能会比呈现器使用它们快填充缓冲区。 即便如此,样本也不会提前呈现,因为呈现器将每个呈现器保留到呈现时间为止。 此外,上游筛选器不会意外覆盖缓冲区,因为 GetSample 仅返回未使用的其他样本。 上游筛选器可以提前运行的量取决于分配器池中的样本数。

3)、上图仅显示一个分配器,但通常每个流有多个分配器。 因此,当呈现器释放示例时,它可以具有级联效果。 下图显示了解码器在等待呈现器释放示例时保存压缩的视频帧的情况。 分析器筛选器还等待解码器释放示例。

当呈现器释放其示例时,解码器的对 GetBuffer 的挂起调用将返回。 然后,解码器可以解码压缩的视频帧并释放它持有的示例,从而取消阻止分析程序挂起的 GetBuffer 调用。

七、捕获Graph生成器

1)、执行视频或音频捕获的筛选器图称为 捕获图。 捕获图形通常比文件播放图复杂得多。 为了使应用程序更轻松地生成捕获图,DirectShow提供了一个名为 Capture Graph Builder 的帮助程序对象。 Capture Graph Builder 公开 ICaptureGraphBuilder2 接口,该接口包含用于生成和控制捕获图的方法。 下图演示了 Capture Graph Builder 和 ICaptureGraphBuilder2 接口。

2)、通过调用 CoCreateInstance 创建捕获Graph生成器和筛选器Graph管理器的新实例来"开始"菜单。 然后,通过调用 ICaptureGraphBuilder2::SetFiltergraph,使用指向筛选器Graph管理器的 IGraphBuilder 接口的指针初始化捕获Graph生成器。 下图演示了此过程。

HRESULT InitCaptureGraphBuilder(
  IGraphBuilder **ppGraph,  // Receives the pointer.
  ICaptureGraphBuilder2 **ppBuild  // Receives the pointer.
)
{
    if (!ppGraph || !ppBuild)
    {
        return E_POINTER;
    }
    IGraphBuilder *pGraph = NULL;
    ICaptureGraphBuilder2 *pBuild = NULL;

    // Create the Capture Graph Builder.
    HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, 
        CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pBuild );
    if (SUCCEEDED(hr))
    {
        // Create the Filter Graph Manager.
        hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
            IID_IGraphBuilder, (void**)&pGraph);
        if (SUCCEEDED(hr))
        {
            // Initialize the Capture Graph Builder.
            pBuild->SetFiltergraph(pGraph);

            // Return both interface pointers to the caller.
            *ppBuild = pBuild;
            *ppGraph = pGraph; // The caller must release both interfaces.
            return S_OK;
        }
        else
        {
            pBuild->Release();
        }
    }
    return hr; // Failed
}

八、选取采集设备

要选择音频或视频捕获设备,请使用 系统设备枚举器,如“ 使用系统设备枚举器”主题中所述。 系统设备枚举器返回设备名字对象集合,由设备类别选择。 名字对象是包含有关另一个对象的信息的 COM 对象。 名字对象使应用程序能够获取有关对象的信息,而无需实际创建对象。 稍后,应用程序可以使用名字对象创建对象。 有关名字对象的详细信息,请参阅 IMoniker 的文档。

若要使用系统设备枚举器,请执行以下步骤。

  1. 调用 CoCreateInstance 以创建系统设备枚举器的实例。

  2. 调用 ICreateDevEnum::CreateClassEnumerator ,并将设备类别指定为 GUID。 对于捕获设备,以下类别是相关的。

    类别 GUID说明
    CLSID_AudioInputDeviceCategory音频捕获设备
    CLSID_VideoInputDeviceCategory视频捕获设备

    如果摄像机具有集成麦克风,则会在这两个类别中显示。 但是,出于枚举、设备创建和数据流的目的,相机和麦克风被系统视为单独的设备。

  3. CreateClassEnumerator 方法返回指向 IEnumMoniker 接口的指针。 若要枚举名字对象,请调用 IEnumMoniker::Next

#include <windows.h>
#include <dshow.h>

#pragma comment(lib, "strmiids")

HRESULT EnumerateDevices(REFGUID category, IEnumMoniker **ppEnum)
{
    // Create the System Device Enumerator.
    ICreateDevEnum *pDevEnum;
    HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,  
        CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));

    if (SUCCEEDED(hr))
    {
        // Create an enumerator for the category.
        hr = pDevEnum->CreateClassEnumerator(category, ppEnum, 0);
        if (hr == S_FALSE)
        {
            hr = VFW_E_NOT_FOUND;  // The category is empty. Treat as an error.
        }
        pDevEnum->Release();
    }
    return hr;
}

IEnumMoniker 接口枚举 IMoniker 接口的列表,其中每个接口都表示设备名字对象。 应用程序可以从名字对象读取属性,或使用名字对象为设备创建DirectShow捕获筛选器。 名字对象属性作为 VARIANT 值返回。 设备名字对象支持以下属性。

properties说明VARIANT 类型
“FriendlyName”设备的名称。VT_BSTR
“说明”设备的说明。VT_BSTR
“DevicePath”标识设备的唯一字符串。 仅 (视频捕获设备。)VT_BSTR
“WaveInID”音频捕获设备的标识符。 仅 (音频捕获设备。)VT_I4

“FriendlyName”和“Description”属性适合在 UI 中显示。

  • “FriendlyName”属性可用于每个设备。 它包含设备的可读名称。

  • “Description”属性仅适用于 DV 和 D-VHS/MPEG 摄像机设备。 有关详细信息,请参阅 MSDV 驱动程序MSTape 驱动程序。 如果可用,它包含比“FriendlyName”属性更具体的设备的说明。 通常,它包括供应商名称。

  • “DevicePath”属性不是人类可读的字符串,但保证对系统上的每个视频捕获设备都是唯一的。 可以使用此属性来区分同一设备模型的两个或多个实例。

  • 如果存在“WaveInID”属性,则表示DirectShow捕获筛选器在内部使用波形音频 API 与设备通信。 “WaveInID”属性的值对应于 waveIn* 函数使用的标识符,如 waveInOpen

若要从名字对象读取属性,请执行以下步骤。

  1. 调用 IMoniker::BindToStorage 以获取指向 IPropertyBag 接口的指针。

  2. 调用 IPropertyBag::Read 以读取属性。

void DisplayDeviceInformation(IEnumMoniker *pEnum)
{
    IMoniker *pMoniker = NULL;

    while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
    {
        IPropertyBag *pPropBag;
        HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
        if (FAILED(hr))
        {
            pMoniker->Release();
            continue;  
        } 

        VARIANT var;
        VariantInit(&var);

        // Get description or friendly name.
        hr = pPropBag->Read(L"Description", &var, 0);
        if (FAILED(hr))
        {
            hr = pPropBag->Read(L"FriendlyName", &var, 0);
        }
        if (SUCCEEDED(hr))
        {
            printf("%S\n", var.bstrVal);
            VariantClear(&var); 
        }

        hr = pPropBag->Write(L"FriendlyName", &var);

        // WaveInID applies only to audio capture devices.
        hr = pPropBag->Read(L"WaveInID", &var, 0);
        if (SUCCEEDED(hr))
        {
            printf("WaveIn ID: %d\n", var.lVal);
            VariantClear(&var); 
        }

        hr = pPropBag->Read(L"DevicePath", &var, 0);
        if (SUCCEEDED(hr))
        {
            // The device path is not intended for display.
            printf("Device path: %S\n", var.bstrVal);
            VariantClear(&var); 
        }

        pPropBag->Release();
        pMoniker->Release();
    }
}

void main()
{
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (SUCCEEDED(hr))
    {
        IEnumMoniker *pEnum;

        hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &pEnum);
        if (SUCCEEDED(hr))
        {
            DisplayDeviceInformation(pEnum);
            pEnum->Release();
        }
        hr = EnumerateDevices(CLSID_AudioInputDeviceCategory, &pEnum);
        if (SUCCEEDED(hr))
        {
            DisplayDeviceInformation(pEnum);
            pEnum->Release();
        }
        CoUninitialize();
    }
}

若要为设备创建DirectShow捕获筛选器,请调用 IMoniker::BindToObject 方法以获取 IBaseFilter 指针。 然后调用 IFilterGraph::AddFilter 将筛选器添加到筛选器图:

IBaseFilter *pCap = NULL;
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap);
if (SUCCEEDED(hr))
{
    hr = m_pGraph->AddFilter(pCap, L"Capture Filter");
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值