【MediaFoundation】相关的概念

MF 概览

在这里插入图片描述
Media Foundation 提供了两种不同的编程模型,左边展示的是端到端的媒体数据模型,主要用在:播放URL或者文件,以及控制流。

在图表右侧展示的第二种模型中,应用程序可以从源头拉取数据,也可以将数据推送到目的地(或两者兼而有之)。如果需要处理数据,这个模型特别有用,因为应用程序可以直接访问数据流。

一些用到的概念

  • Attributes 属性是在对象内部存储信息的通用方式,以键/值对的列表形式存在。
  • Media Types 媒体类型描述了媒体数据流的格式。
  • Media Buffers 媒体缓冲区保存媒体数据的块,例如视频帧和音频样本,并用于在对象之间传输数据。
  • Media Samples 媒体样本是媒体缓冲区的容器。它们还包含有关缓冲区的元数据,例如时间戳。

这些概念对应了后面编程中的一些对象,所以也是比较重要的。
MF平台API提供了一些核心功能,这些功能被MF流水线使用,例如异步回调和工作队列。某些应用程序可能需要直接调用这些API;此外,如果要为MF实现自定义源、转换或接收器,您将需要使用这些API。

Media Pipeline

媒体流水线包含三种生成或处理媒体数据的对象类型:

  1. 媒体源(Media Sources):将数据引入流水线。媒体源可以从本地文件(如视频文件)、网络流或硬件捕获设备获取数据。

  2. Media Foundation Transforms,MFTs:处理来自流的数据。编码器和解码器通常作为MFTs实现。

  3. 媒体接收器(Media Sinks):消耗数据,例如通过在显示器上显示视频、播放音频或将数据写入媒体文件。

第三方可以实现自己的自定义源、接收器和MFTs,例如,以支持新的媒体文件格式。

媒体会话(Media Session)控制数据在流水线中的流动,并处理诸如质量控制、音视频同步和对格式变更的响应等任务。

Source Reader and Sink Writer

Source ReaderSink Writer 提供了使用基本MF组件(媒体源、变换和媒体接收器)的替代方法。Source Reader 一个媒体源和零个或多个解码器,而 Sink Writer 托管一个媒体接收器和零个或多个编码器。您可以使用Source Reader从媒体源获取经过压缩或未经压缩的数据,并使用 Sink Writer 对数据进行编码并将数据发送到媒体接收器。

Attributes

属性是一个键/值对,其中键是一个GUID,而值是一个PROPVARIANT。在整个Microsoft Media Foundation中,属性被用于配置对象、描述媒体格式、查询对象属性以及其他目的。

关于属性

Attributes 是一个键值对,key 是 GUID,而值是 PROPVARIANT, 所以什么是 GUID 什么又是 PROPVARIANT

  • GUID(Globally Unique Identifier)是全局唯一标识符的缩写,它是一个128位的数字,通常以16进制表示,用于唯一标识对象或实体。在Microsoft Media Foundation中,GUID经常用作属性的键,以确保唯一性。
  • PROPVARIANT 是一种在Windows编程中常见的数据结构,用于表示通用的属性值。它是一个联合体,可以存储多种不同类型的数据,包括整数、浮点数、字符串等。PROPVARIANT 的设计旨在提供一种通用的方式来处理属性值,使得在处理不同类型的属性时更加灵活方便。在Microsoft Media Foundation中,PROPVARIANT通常用于存储属性的值。

但是 值也被限制到如下的数据类型中:

  • Unsigned 32-bit integer (UINT32).
  • Unsigned 64-bit integer (UINT64).
  • 64-bit floating-point number.
  • GUID.
  • Null-terminated wide-character string.
  • Byte array.
  • IUnknown pointer.

这些值定义在 MF_ATTRIBUTE_TYPE 枚举中。 为了获得这些属性值,可以使用 IMFAttributes 接口。 此接口包含按数据类型获取和设置值的类型安全方法。例如,要设置32位整数,调用 IMFAttributes::SetUINT32。属性键在对象内是唯一的。如果使用相同的键设置两个不同的值,第二个值将覆盖第一个。

多个MF接口继承了 IMFAttributes 接口。公开这个接口的对象具有应用程序应该在对象上设置的可选或强制属性,或者具有应用程序可以检索的属性。此外,一些方法和函数以 IMFAttributes 指针作为参数,这使应用程序能够设置配置信息。应用程序必须创建一个属性存储来保存配置属性。要创建一个空的属性存储,调用 MFCreateAttributes。

Demo:

extern const GUID MY_ATTRIBUTE;

HRESULT ShowCreateAttributeStore(IMFAttributes **ppAttributes)
{
    IMFAttributes *pAttributes = NULL;
    const UINT32 cElements = 10;  // Starting size.

    // Create the empty attribute store.
    HRESULT hr = MFCreateAttributes(&pAttributes, cElements);

    // Set the MY_ATTRIBUTE attribute with a string value.
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetString(
            MY_ATTRIBUTE,
            L"This is a string value"
            );
    }

    // Return the IMFAttributes pointer to the caller.
    if (SUCCEEDED(hr))
    {
        *ppAttributes = pAttributes;
        (*ppAttributes)->AddRef();
    }

    SAFE_RELEASE(pAttributes);

    return hr;
}

HRESULT ShowGetAttributes()
{
    IMFAttributes *pAttributes = NULL;
    WCHAR *pwszValue = NULL;
    UINT32 cchLength = 0;

    // Create the attribute store.
    HRESULT hr = ShowCreateAttributeStore(&pAttributes);

    // Get the attribute.
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->GetAllocatedString(
            MY_ATTRIBUTE,
            &pwszValue,
            &cchLength
            );
    }

    CoTaskMemFree(pwszValue);
    SAFE_RELEASE(pAttributes);

    return hr;
}

更多关于请查看 官方文档

Media Types (Media Foundation)

媒体类型是描述媒体流格式的一种方式。在MF中,媒体类型由 IMFMediaType 接口表示。应用程序使用媒体类型来了解媒体文件或媒体流的格式。

媒体类型描述了媒体流的格式。在Microsoft Media Foundation中,媒体类型由IMFMediaType接口表示。该接口继承了IMFAttributes接口。媒体类型的详细信息被指定为属性。

要创建一个新的媒体类型,调用MFCreateMediaType函数。该函数返回指向IMFMediaType接口的指针。媒体类型最初没有属性。要设置格式的详细信息,设置相关的属性。

Major Types and Subtypes

对于任何媒体类型,两个重要的信息是主类型(major type)和子类型(subtype)。

主类型是一个GUID,定义了媒体流中数据的整体类别。主类型包括视频和音频。要指定主类型,设置MF_MT_MAJOR_TYPE属性。IMFMediaType::GetMajorType方法返回此属性的值。

子类型进一步定义了格式。例如,在视频主类型中,有RGB-24、RGB-32、YUY2等子类型。在音频中,有PCM音频、IEEE浮点音频等。子类型提供比主类型更多的信息,但它并不完全定义格式的所有内容。例如,视频子类型不定义图像大小或帧率。要指定子类型,设置MF_MT_SUBTYPE属性。

所有媒体类型都应具有主类型GUID和子类型GUID。有关主类型和子类型GUID的列表,请参阅媒体类型GUIDs。

为什么是Attributes

属性相对于以前的技术中使用的格式结构具有几个优势,例如DirectShow和Windows Media Format SDK。

更容易表示“不知道”或“不关心”的值。例如,如果你正在编写一个视频转换器,你可能事先知道转换器支持哪些RGB和YUV格式,但直到从视频源获取它们之前,你可能不知道视频帧的尺寸。同样,你可能不关心某些细节,比如视频的原色。使用格式结构,每个成员都必须填充某个值。结果,使用零来表示未知或默认值已经变得很常见。如果另一个组件将零视为合法值,这种做法可能会导致错误。使用属性,你可以简单地省略未知或与组件无关的属性。

随着需求随时间的变化,格式结构通过在结构末尾添加附加数据而被扩展。例如,WAVEFORMATEXTENSIBLE扩展了WAVEFORMATEX结构。这种做法容易出错,因为组件必须将结构指针转换为其他结构类型。属性可以更安全地进行扩展。

相互不兼容的格式结构已经被定义。例如,DirectShow定义了VIDEOINFOHEADER和VIDEOINFOHEADER2结构。属性是独立设置的,因此不会出现这个问题。

Video Media Types

在媒体类型中,主类型描述数据的总体类别,例如音频或视频。如果有的话,子类型进一步细化了主类型。例如,如果主类型是视频,子类型可能是32位RGB视频。子类型还区分了编码格式,例如H.264视频,和未压缩格式。

一些比较常见的属性,可以看看文档,这里介绍几个不常见的:

  • MF_MT_DEFAULT_STRIDE Surface stride(表面步幅)。步幅是从一行像素到下一行所需的字节数。如果字节中的步幅与视频的宽度不同,请设置此属性。否则,可以省略此属性。
  • 高32位包含像素宽高比的分子,低32位包含分母。分子是宽高比的水平分量,分母是垂直分量。要设置此属性,请使用MFSetAttributeRatio函数。要获取此属性,请使用MFGetAttributeRatio函数。MF_MT_PIXEL_ASPECT_RATIO 像素宽高比描述了显示的视频图像中像素的形状。如果图像具有非正方形像素,请设置此属性。为了在具有正方形像素的显示设备上正确显示,图像必须按照图像像素宽高比的倒数进行缩放。此属性的GUID常量是从mfuuid.lib导出的。

Media Buffers

媒体缓冲区是一个COM对象,用于管理一块内存,通常用于存储媒体数据。媒体缓冲区用于将数据从一个流水线组件传递到下一个。大多数应用程序不直接使用媒体缓冲区,因为媒体会话处理流水线对象之间的所有数据流。如果您正在编写自己的流水线组件,或者直接在没有媒体会话的情况下使用流水线组件,那么您必须使用媒体缓冲区。

媒体缓冲区公开了 IMFMediaBuffer 接口。此接口设计用于读取或写入任何类型的数据。未压缩的视频帧需要特殊处理,因为它们可能存储在位于视频内存中的Direct3D表面中。

媒体缓冲区与两个相关的大小:

  1. 最大长度是为缓冲区分配的内存的物理大小。这个值在缓冲区创建时设置,而在缓冲区的生命周期内不会改变。最大长度指示缓冲区可以存储多少数据。要找到最大大小,调用 IMFMediaBuffer::GetMaxLength

  2. 当前长度是当前在缓冲区中的有效数据量。当首次分配缓冲区时,当前长度为零,因为缓冲区中没有有效数据。如果将任何数据写入缓冲区,必须通过调用 IMFMediaBuffer::SetCurrentLength 来更新当前长度。例如,如果向缓冲区写入了100字节的数据,请使用值100调用 SetCurrentLength。如果从媒体缓冲区读取数据,请调用 IMFMediaBuffer::GetCurrentLength 来查找当前缓冲区中有多少数据。不要读取超出当前长度。当前长度永远不会超过缓冲区的最大长度。

Accessing the Buffer Memory

要访问缓冲区中的内存,请调用 IMFMediaBuffer::Lock。该方法返回指向内存块起始位置的指针。它还返回最大长度和当前长度。当使用完指针后,请调用 IMFMediaBuffer::Unlock

写入数据到媒体缓冲区的步骤:

  1. 调用 IMFMediaBuffer::Lock 以获取指向内存的指针。该方法还返回缓冲区的最大长度。
  2. 将数据写入内存,写入的数据量不得超过缓冲区的最大长度。
  3. 调用 IMFMediaBuffer::SetCurrentLength 来更新当前长度。将当前长度设置为第2步中写入的数据量。
  4. 调用 IMFMediaBuffer::Unlock 来解锁缓冲区。

从媒体缓冲区读取数据的步骤:

  1. 调用 IMFMediaBuffer::Lock 以获取指向内存的指针。该方法还返回缓冲区的当前长度(缓冲区中有效数据的量)。
  2. 读取内存的内容,读取的数据量不得超过当前长度。
  3. 调用 IMFMediaBuffer::Unlock 来解锁缓冲区。

Creating System Memory Buffers

系统内存缓冲区是一个管理系统内存块的媒体缓冲区。要创建此对象的实例,请调用 MFCreateMemoryBufferMFCreateAlignedMemoryBuffer 并指定缓冲区大小。这两个函数都分配一个内存块并返回一个 IMFMediaBuffer 指针。当媒体缓冲区的引用计数达到零并销毁对象时,内存会自动释放。

Uncompressed Video Buffers

如何处理包含未压缩视频帧的媒体缓冲区。按照首选顺序,有以下选项。并非每个媒体缓冲区都支持每个选项。

  • 使用底层的 Direct3D 表面。(仅适用于存储在 Direct3D 表面中的视频帧。)
  • 使用 IMF2DBuffer 接口。
  • 使用 IMFMediaBuffer 接口。

Use the Underlying Direct3D Surface

视频帧可能存储在Direct3D表面中。如果是这样,您可以通过在媒体缓冲区对象上调用 IMFGetService::GetServiceMFGetService 来获取指向表面的指针。使用服务标识符 MR_BUFFER_SERVICE。当访问视频帧的组件设计为使用Direct3D时,建议使用此方法。例如,支持DirectX视频加速的视频解码器应使用此方法。

Use the IMF2DBuffer Interface

如果视频帧未存储在Direct3D表面中,或者组件未设计为使用Direct3D,则下一个推荐的访问视频帧的方式是查询支持 IMF2DBuffer 接口的缓冲区。该接口专门设计用于图像数据。要获取指向该接口的指针,请在媒体缓冲区上调用 QueryInterface。并非所有的媒体缓冲区对象都公开此接口。但是,如果媒体缓冲区确实公开了 IMF2DBuffer 接口,建议在可能的情况下使用该接口访问数据,而不是使用 IMFMediaBuffer。仍然可以使用 IMFMediaBuffer 接口,但效率可能较低。

以下是访问数据的步骤:

  1. 在媒体缓冲区上调用 QueryInterface 以获取 IMF2DBuffer 接口。
  2. 调用 IMF2DBuffer::Lock2D 来访问缓冲区的内存。此方法返回指向顶部像素行的第一个字节的指针。它还返回图像步幅,即从一行像素的开始到下一行的开始的字节数。缓冲区可能在每行像素后包含填充字节,因此步幅可能比以字节为单位的图像宽度更宽。如果图像在内存中是朝下的,步幅也可能是负值。有关更多信息,请参阅图像步幅。
  3. 仅在需要访问内存时保持缓冲区锁定。通过调用 IMF2DBuffer::Unlock2D 解锁缓冲区。

并非每个媒体缓冲区都实现了 IMF2DBuffer 接口。如果媒体缓冲区没有实现此接口(即在步骤1中缓冲区对象返回 E_NOINTERFACE),则必须使用下面描述的 IMFMediaBuffer 接口。

Use the IMFMediaBuffer Interface

如果媒体缓冲区没有公开 IMF2DBuffer 接口,则使用 IMFMediaBuffer 接口。此接口的一般语义在主题“使用媒体缓冲区”中有描述。

以下是访问数据的步骤:

  1. 在媒体缓冲区上调用 QueryInterface 以获取 IMFMediaBuffer 接口。
  2. 调用 IMFMediaBuffer::Lock 来访问缓冲区内存。此方法返回指向缓冲区内存的指针。当使用 Lock 方法时,步幅始终是与问题中的视频格式相关的最小步幅,没有额外的填充字节。
  3. 仅在需要访问内存时保持缓冲区锁定。通过调用 IMFMediaBuffer::Unlock 解锁缓冲区。

如果缓冲区公开了 IMF2DBuffer,则应始终避免调用 IMFMediaBuffer::Lock,因为 Lock 方法可能会强制将缓冲区对象中的视频帧放入一个连续的内存块,然后再次返回。另一方面,如果缓冲区不支持 IMF2DBuffer,那么 IMFMediaBuffer::Lock 可能不会导致复制。

根据媒体类型计算最小步幅的步骤如下:

  1. 最小步幅可能存储在 MF_MT_DEFAULT_STRIDE 属性中。
  2. 如果未设置 MF_MT_DEFAULT_STRIDE 属性,请调用 MFGetStrideForBitmapInfoHeader 函数计算大多数常见视频格式的步幅。
  3. 如果 MFGetStrideForBitmapInfoHeader 函数无法识别格式,则必须根据格式的定义计算步幅。在这种情况下,没有通用规则;您需要了解格式定义的详细信息。

Media Samples

媒体样本是一个包含有序列表的零个或多个缓冲区的对象。媒体样本公开了 IMFSample 接口。一个样本中包含的数据量取决于创建样本的组件以及缓冲区中数据的类型。对于未压缩视频,一个样本通常包含一个视频帧。对于未压缩音频,数据量可能会有所变化,但通常一个音频帧不会跨越两个样本。对于压缩的数据,这些建议可能不适用。

出于效率原因,单个样本可能包含多个缓冲区。例如,在ASF文件中,视频帧通常分散在多个ASF数据包中。媒体源可能将这些数据包读入多个缓冲区。源不是将每个片段复制到一个缓冲区中,而是将所有缓冲区放入一个样本中。下游组件随后可以决定是否将较小的缓冲区复制到一个连续的缓冲区中。通常,如果您正在编写一个流水线组件,应假设任何样本可能包含多个缓冲区。

下面的代码展示了创建一个 Sample 以及添加一个 buffer

HRESULT CreateMediaSample(DWORD cbData, IMFSample **ppSample)
{
    HRESULT hr = S_OK;

    IMFSample *pSample = NULL;
    IMFMediaBuffer *pBuffer = NULL;

    hr = MFCreateSample(&pSample);

    if (SUCCEEDED(hr))
    {
        hr = MFCreateMemoryBuffer(cbData, &pBuffer);
    }

    if (SUCCEEDED(hr))
    {
        hr = pSample->AddBuffer(pBuffer);
    }

    if (SUCCEEDED(hr))
    {
        *ppSample = pSample;
        (*ppSample)->AddRef();
    }

    SafeRelease(&pSample);
    SafeRelease(&pBuffer);
    return hr;
}

推荐的获取样本中缓冲区的方式是调用 IMFSample::ConvertToContiguousBuffer。该方法返回一个单一的连续缓冲区。

要遍历列表中的缓冲区,首先调用 IMFSample::GetBufferCount。该方法返回缓冲区的数量。然后调用 IMFSample::GetBufferByIndex 并指定要检索的缓冲区的索引。缓冲区的索引从零开始。

以下代码显示了如何遍历样本中的缓冲区:

HRESULT IterateSampleBuffers(IMFSample *pSample)
{
    HRESULT hr = S_OK;

    // Check for a valid sample pointer
    if (!pSample)
        return E_POINTER;

    // Get the number of buffers in the sample
    DWORD bufferCount = 0;
    hr = pSample->GetBufferCount(&bufferCount);
    if (FAILED(hr))
        return hr;

    // Iterate through the buffers
    for (DWORD i = 0; i < bufferCount; i++)
    {
        // Get the buffer by index
        IMFMediaBuffer *pBuffer = nullptr;
        hr = pSample->GetBufferByIndex(i, &pBuffer);
        if (FAILED(hr))
            return hr;

        // Process the buffer as needed...

        // Release the buffer when done
        pBuffer->Release();
        pBuffer = nullptr;
    }

    return hr;
}

此代码演示了如何遍历样本中的缓冲区。在循环中,可以按需处理每个缓冲区。在处理完每个缓冲区后,必须释放对该缓冲区的引用。

Media Foundation and COM

Microsoft Media Foundation(MF)使用一些 COM 构造,但并非完全基于 COM 的 API。本主题描述了 COM 与 Media Foundation 之间的交互,并定义了一些开发 Media Foundation 插件组件的最佳实践。遵循这些实践可以帮助您避免一些常见但微妙的编程错误。

Best Practices for Applications

在 Media Foundation 中,异步处理和回调由工作队列处理。工作队列始终具有多线程单元(MTA)线程,因此如果应用程序也在 MTA 线程上运行,它将具有更简单的实现。因此,建议调用 CoInitializeEx 并使用 COINIT_MULTITHREADED 标志。

Media Foundation 不会将单线程单元(STA)对象封送到工作队列线程,也不会确保保持 STA 不变。因此,STA 应用程序必须小心,不要将 STA 对象或代理传递给 Media Foundation API。Media Foundation 不支持仅 STA 的对象。

如果有一个 STA 代理指向 MTA 或自由线程对象,该对象可以通过使用工作队列回调进行封送到 MTA 代理。CoCreateInstance 函数可以返回原始指针或 STA 代理,具体取决于注册表中为该 CLSID 定义的对象模型。如果返回 STA 代理,则不得将指针传递给 Media Foundation API。

例如,假设您要将 IPropertyStore 指针传递给 IMFSourceResolver::BeginCreateObjectFromURL 方法。您可以调用 PSCreateMemoryPropertyStore 来创建 IPropertyStore 指针。如果您是从 STA 调用的,必须在将其传递给 BeginCreateObjectFromURL 之前封送指针。

如果要获取有关全局接口表的更多信息,请参阅 IGlobalInterfaceTable。

如果您在进程内使用 Media Foundation,从 Media Foundation 方法和函数返回的对象是指向对象的直接指针。对于跨进程的 Media Foundation,这些对象可能是 MTA 代理,如果需要在 STA 线程中使用,应将它们封送。类似地,在回调内获取的对象(例如从 MESessionTopologyStatus 事件中获取的拓扑)在进程内使用 Media Foundation 时是直接指针,但在跨进程使用 Media Foundation 时是 MTA 代理。

看不懂这个是啥~~~~~ 我就只想读一读摄像头,处理一下异常!!!!

Source Reader

Media Foundation提供了一个专为播放优化的管道。该管道是端到端的,意味着它处理从源(例如视频文件)到目标(例如图形显示)的数据流。然而,如果您希望在数据通过管道时读取或修改它,您必须编写一个自定义插件。这需要对Media Foundation管道有相当深入的了解。对于某些任务,创建一个新插件的开销太大。Source Reader专为这种情况设计,当您希望从源获取原始数据而无需整个管道的开销时。

在内部,源阅读器持有指向媒体源的指针。媒体源是生成来自外部源(如媒体文件或视频捕获设备)的媒体数据的Media Foundation对象。源阅读器管理对媒体源的所有方法调用。 (有关媒体源的详细信息,请参见媒体源。)

如果媒体源提供压缩数据,您可以使用源阅读器解码数据。在这种情况下,源阅读器将加载正确的解码器并管理媒体源和解码器之间的数据流。源阅读器还可以执行一些有限的视频处理:从YUV到RGB-32的颜色转换和软件去隔行,尽管不建议在实时视频渲染中使用这些操作。 下图说明了这个过程。
在这里插入图片描述

我也看不懂~~~, 就知道这种模式可以改数据

源阅读器不会将数据发送到目标;由应用程序来消耗数据。例如,源阅读器可以读取视频文件,但不会将视频渲染到屏幕上。此外,源阅读器不管理演示时钟,不处理定时问题,也不同步视频和音频。

考虑在以下情况使用源阅读器:

  • 您想从媒体文件中获取数据,而不用担心底层文件结构。
  • 您想从音频或视频捕获设备中获取数据。
  • 您的数据处理任务不受时间限制,或者您不需要演示时钟。
  • 您已经有一个不基于Media Foundation的媒体管道,并希望将Media Foundation媒体源合并到您自己的管道中。

在以下情况下不建议使用源阅读器:

  • 对于受保护的内容。源阅读器不支持数字版权管理(DRM)。
  • 如果您关心底层文件结构的详细信息。源阅读器隐藏了这种类型的细节。

还是看不懂,这个 Source Reader 到底是干嘛的!!! 我是不是不适合做程序员?看这个文档我真的怀疑人生了。

Using the Source Reader to Process Media Data

Creating the Source Reader, 终于可以看到怎样读视频、流媒体、摄像头了!!!

函数描述
MFCreateSourceReaderFromURL此函数以 URL 作为输入。该函数使用源解析器(Source Resolver)从 URL 创建一个媒体源。这个URL包括文件嘛?
MFCreateSourceReaderFromByteStream此函数以指向字节流的指针作为输入。该函数还使用源解析器(Source Resolver)来创建媒体源。
MFCreateSourceReaderFromMediaSource此函数以指向已创建的媒体源的指针作为输入。对于源解析器无法创建的媒体源,例如捕获设备或自定义媒体源,此函数非常有用。

这三个又是干嘛的?? 但是好像读摄像头使用MFCreateSourceReaderFromMediaSource, 使用IMActivate** 创建!

通常,对于媒体文件,使用 MFCreateSourceReaderFromURL。对于设备,例如网络摄像头,使用 MFCreateSourceReaderFromMediaSource。(有关 Microsoft Media Foundation 中的捕获设备的更多信息,请参见音频/视频捕获。)

这些函数中的每一个都接受一个可选的 IMFAttributes 指针,用于在 Source Reader 上设置各种选项,详细信息请参阅这些函数的参考主题。要获取默认行为,请将此参数设置为 NULL。每个函数都将 IMFSourceReader 指针作为输出参数返回。在调用这些函数之前,您必须调用 CoInitialize(Ex)MFStartup 函数。

哦, 原来读文件用xxxURL, 读设备用 xxxxSource, 还可以通过属性指针传一些特定的值, 也可以不传。然后这三个函数都返回一个同意的指针。这样是不是就对文件和摄像头统一操作了呢? 嗯, 还是有点厉害了感觉。

又给了个例子

int __cdecl wmain(int argc, __in_ecount(argc) PCWSTR* argv)
{
    if (argc < 2)
    {
        return 1;
    }

    const WCHAR *pszURL = argv[1];

    // Initialize the COM runtime. 先初始化一下 COM,感觉这个COM好高级,先抄下来,搞球不懂。
    HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
    if (SUCCEEDED(hr))
    {
        // Initialize the Media Foundation platform. 初始化运行时
        hr = MFStartup(MF_VERSION);
        if (SUCCEEDED(hr))
        {
            // Create the source reader. 这里开始创建 pReader 了
            IMFSourceReader *pReader;
            hr = MFCreateSourceReaderFromURL(pszURL, NULL, &pReader);
            if (SUCCEEDED(hr))
            {
                ReadMediaFile(pReader);
                pReader->Release();
            }
            // Shut down Media Foundation. 开了还要关,就能RAII?
            MFShutdown();
        }
        CoUninitialize(); // 这里又要关
    }
}

我也不知道这个能干嘛, 但是学到了这套又臭又长的框架

Enumerating Output Formats

每个媒体源至少有一个流。例如,一个视频文件可能包含一个视频流和一个音频流。每个流的格式使用媒体类型描述,由 IMFMediaType 接口表示。有关媒体类型的更多信息,请参见媒体类型。您必须检查媒体类型以了解从 Source Reader 获取的数据的格式。

最初,每个流都有一个默认格式,您可以通过调用 IMFSourceReader::GetCurrentMediaType 方法找到:

对于每个流,媒体源提供了该流的可能媒体类型列表。类型的数量取决于源。如果源表示一个媒体文件,则通常每个流只有一种类型。另一方面,摄像头可能能够以几种不同的格式传输视频。在这种情况下,应用程序可以从媒体类型列表中选择要使用的格式。

要获取流的媒体类型之一,请调用 IMFSourceReader::GetNativeMediaType 方法。此方法使用两个索引参数:流的索引和流的媒体类型列表中的索引。要枚举流的所有类型,请保持流索引不变,递增列表索引。当列表索引超出边界时,GetNativeMediaType 返回 MF_E_NO_MORE_TYPES

每个媒体都至少有一个流,视频流,音频流,字幕流。。。 每个流的格式用这个IMFMediaType 接口表示, 这个我大概晓得,我前面看了哈, 摄像头可能有几种不同的格式传数据,这个我知道,YUV把我头的搞大了。。。最后一段又在说啥? 又来一个代码

// 看样子是从流里面枚举类型
HRESULT EnumerateTypesForStream(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    HRESULT hr = S_OK;
    DWORD dwMediaTypeIndex = 0;

    while (SUCCEEDED(hr))
    {
        IMFMediaType *pType = NULL;
        // 拿到第idx个流,的 类型
        hr = pReader->GetNativeMediaType(dwStreamIndex, dwMediaTypeIndex, &pType);
        if (hr == MF_E_NO_MORE_TYPES)
        {
            hr = S_OK;
            break;
        }
        else if (SUCCEEDED(hr))
        {
            // Examine the media type. (Not shown.)
            // 检查媒体类型。(未显示代码。)检查啥?我好懵逼
			// 又要释放,能不能封装一个智能指针?虽然我也不会封装,智能指针也不懂,还得找个时间学学智能指针
            pType->Release();
        }
        ++dwMediaTypeIndex;
    }
    return hr;
}

上面这个例子展示了,读取流的多个类型。比如摄像头拉流的时候,支持多种格式。

要枚举每个流的媒体类型,请增加流索引。当流索引超出范围时,GetNativeMediaType 将返回 MF_E_INVALIDSTREAMNUMBER。

我暂时枚举视频流就行了,谢谢

HRESULT EnumerateMediaTypes(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    DWORD dwStreamIndex = 0;

    while (SUCCEEDED(hr))
    {
        hr = EnumerateTypesForStream(pReader, dwStreamIndex);
        if (hr == MF_E_INVALIDSTREAMNUMBER)
        {
            hr = S_OK;
            break;
        }
        ++dwStreamIndex;
    }
    return hr;
}

Setting Output Formats

要更改输出格式,请调用 IMFSourceReader::SetCurrentMediaType 方法。此方法需要流索引和媒体类型参数:

hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);

改流输出格式,能把YUV转成RGB吗?球球了!

关于媒体类型,这取决于您是否希望插入解码器。

  • 如果要直接从源获取数据而不解码它,请使用 GetNativeMediaType 返回的类型之一。
  • 要解码流,请创建描述所需未压缩格式的新媒体类型。
    对于解码器的情况,请按以下步骤创建媒体类型:
  1. 调用 MFCreateMediaType 创建一个新的媒体类型。
  2. 设置 MF_MT_MAJOR_TYPE 特性以指定音频或视频。
  3. 设置 MF_MT_SUBTYPE 特性以指定解码格式的子类型。(请参阅 音频子类型 GUID 和 视频子类型 GUID。)
  4. 调用 IMFSourceReader::SetCurrentMediaType。

Source Reader 将自动加载解码器。要获取解码格式的完整详细信息,请在调用 SetCurrentMediaType 后调用 IMFSourceReader::GetCurrentMediaType。

我不需要,我拿的都是本地摄像头

// 解码?解码是我能看得懂的?
HRESULT ConfigureDecoder(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    IMFMediaType *pNativeType = NULL;
    IMFMediaType *pType = NULL;

    // Find the native format of the stream. 先来找一下流的格式
    HRESULT hr = pReader->GetNativeMediaType(dwStreamIndex, 0, &pNativeType);
    if (FAILED(hr))
    {
        return hr;
    }

    GUID majorType, subtype;

    // Find the major type. 又来找具体的格式
    hr = pNativeType->GetGUID(MF_MT_MAJOR_TYPE, &majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Define the output type. 又创建了一个空的Type
    hr = MFCreateMediaType(&pType);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pType->SetGUID(MF_MT_MAJOR_TYPE, majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Select a subtype.
    if (majorType == MFMediaType_Video)
    {
        subtype= MFVideoFormat_RGB32;
    }
    else if (majorType == MFMediaType_Audio)
    {
        subtype = MFAudioFormat_PCM;
    }
    else
    {
        // Unrecognized type. Skip.
        goto done;
    }

    hr = pType->SetGUID(MF_MT_SUBTYPE, subtype);
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the uncompressed format.
    hr = pReader->SetCurrentMediaType(dwStreamIndex, NULL, pType);
    if (FAILED(hr))
    {
        goto done;
    }

done:
    SafeRelease(&pNativeType);
    SafeRelease(&pType);
    return hr;
}

这样就可以了吗?我就可以拿到 RGB的数据了吗? 太酷啦

Processing Media Data

要从源获取媒体数据,请调用 IMFSourceReader::ReadSample 方法,如下所示的代码所示。

DWORD streamIndex, flags;  	// 这里好像是要指定获取的哪个流,音频流,视频流
LONGLONG llTimeStamp; 		// 时间戳,好像也好有用

// 这样就可以读了吗?
hr = pReader->ReadSample(
   MF_SOURCE_READER_ANY_STREAM,    // Stream index.
   0,                              // Flags.
   &streamIndex,                   // Receives the actual stream index. 
   &flags,                         // Receives status flags.
   &llTimeStamp,                   // Receives the time stamp.
   &pSample                        // Receives the sample or NULL.
   );
  • 第一个参数是要获取数据的流的索引。您还可以指定 MF_SOURCE_READER_ANY_STREAM 来从任何流获取下一个可用的数据。
  • 第二个参数包含可选标志;请参阅 MF_SOURCE_READER_CONTROL_FLAG 以获取这些标志的列表。
  • 第三个参数接收实际产生数据的流的索引。如果将第一个参数设置为 MF_SOURCE_READER_ANY_STREAM,则需要此信息。
  • 第四个参数接收状态标志,指示读取数据时可能发生的各种事件,例如流中的格式更改。有关状态标志的列表,请参阅 MF_SOURCE_READER_FLAG

如果媒体源能够为请求的流生成数据,则 ReadSample 的最后一个参数将接收指向媒体样本对象的 IMFSample 接口的指针。使用媒体样本可以:

  • 获取指向媒体数据的指针。
  • 获取呈现时间和样本持续时间。
  • 获取描述样本的交错、场首和其他方面的属性。

媒体数据的内容取决于流的格式。对于未压缩的视频流,每个媒体样本包含一个视频帧。对于未压缩的音频流,每个媒体样本包含一系列音频帧。

ReadSample 方法可以返回 S_OK,但在 pSample 参数中不返回媒体样本。例如,当达到文件末尾时,ReadSampledwFlags 中设置 MF_SOURCE_READERF_ENDOFSTREAM 标志,并将 pSample 设置为 NULL。在这种情况下,ReadSample 方法返回 S_OK,因为没有发生错误,即使 pSample 参数设置为 NULL。因此,在引用 pSample 之前始终检查其值。

拿到pSample就可以搞事情了吗?反正解码我也不会,能转成RGB就行了

HRESULT ProcessSamples(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    IMFSample *pSample = NULL;
    size_t  cSamples = 0;

    bool quit = false;
    while (!quit)
    {
        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;

        hr = pReader->ReadSample(
            MF_SOURCE_READER_ANY_STREAM,    // Stream index.
            0,                              // Flags.
            &streamIndex,                   // Receives the actual stream index. 
            &flags,                         // Receives status flags.
            &llTimeStamp,                   // Receives the time stamp.
            &pSample                        // Receives the sample or NULL.
            );

        if (FAILED(hr))
        {
            break;
        }

        wprintf(L"Stream %d (%I64d)\n", streamIndex, llTimeStamp);
        if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
        {
            wprintf(L"\tEnd of stream\n");
            quit = true;
        }
        if (flags & MF_SOURCE_READERF_NEWSTREAM)
        {
            wprintf(L"\tNew stream\n");
        }
        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            wprintf(L"\tNative type changed\n");
        }
        if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
        {
            wprintf(L"\tCurrent type changed\n");
        }
        if (flags & MF_SOURCE_READERF_STREAMTICK)
        {
            wprintf(L"\tStream tick\n");
        }

        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            // The format changed. Reconfigure the decoder.
            hr = ConfigureDecoder(pReader, streamIndex);
            if (FAILED(hr))
            {
                break;
            }
        }

        if (pSample)
        {
            ++cSamples;
        }

        SafeRelease(&pSample);
    }

    if (FAILED(hr))
    {
        wprintf(L"ProcessSamples FAILED, hr = 0x%x\n", hr);
    }
    else
    {
        wprintf(L"Processed %d samples\n", cSamples);
    }
    SafeRelease(&pSample);
    return hr;
}

这个 & 好神奇,看不明白,后面回来看吧。

在数据处理过程中,解码器或其他转换可能会缓冲输入样本。在以下示意图中,应用程序调用 ReadSample 并收到一个具有呈现时间等于 t1 的样本。解码器正在保存呈现时间为 t2 和 t3 的样本。

在这里插入图片描述

在下一次调用 ReadSample 时,源阅读器可能会将 t4 提供给解码器,并将 t2 返回给应用程序。

如果要解码解码器当前缓冲的所有样本,而不传递任何新样本给解码器,请在 ReadSampledwControlFlags 参数中设置 MF_SOURCE_READER_CONTROLF_DRAIN 标志。继续在循环中执行此操作,直到 ReadSample 返回一个空样本指针。根据解码器缓冲样本的方式,这可能会立即发生,也可能在多次调用 ReadSample 之后发生。

搞球不懂!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/146714.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文了解VR全景拍摄设备如何选择,全景图片如何处理

引言&#xff1a; 在如今的数字化时代&#xff0c;虚拟现实&#xff08;VR&#xff09;技术不仅为我们的生活增添了许多乐趣&#xff0c;也为摄影领域带来了新的摄影方式&#xff0c;那么VR全景拍摄如何选择设备&#xff0c;全景图片又怎样处理呢&#xff1f; 一. VR全景拍摄设…

uniapp项目笔记

1.生成二维码 import uqrCode from /static/erweima.js uqrCode.make({canvasId: qrcode,componentInstance: this,text: JSON.stringify(item.id),size: 150,margin: 0,backgroundColor: #ffffff,foregroundColor: #000000,fileType: jpg,errorCorrectLevel: uqrCode.errorCor…

高质量实时渲染笔记

文章目录 Real-time shadows1 自遮挡问题2 解决阴影detach问题&#xff1f;3 Aliasing4 近似积分5 percentage closer soft shadows(PCSS)percenta closer filtering(PCF)PCSS的思想 6 Variance Soft Shadow Mapping (VSSM)步骤Moment Shadow Mapping 7 Distance field shadow …

LeetCode - 232.用栈实现队列 225.用队列模拟实现栈 (C语言,配图)

目录 232.用栈实现队列 225.用队列模拟实现栈 注&#xff1a;本文是基于C语言实现的代码&#xff0c;所以栈和队列是在力扣上制造实现的&#xff0c;如果你使用C等语言&#xff0c;可以忽略前面相当大部分的代码。 在栈模拟实现栈和队列之前&#xff0c;我们先来复习一下栈和…

Skywalking流程分析_4(插件的加载和不同版本的识别)

插件的结构 之前我们介绍了插件的加载&#xff0c;接下来就是真正开始进行插件的执行了&#xff0c;首先要看下插件的结构是怎么样的&#xff0c;以阿里的druid数据源为例 skywalking-plugin.def: druid-1.xorg.apache.skywalking.apm.plugin.druid.v1.define.DruidPooledCo…

【PG】PostgreSQL高可用方案repmgr部署(非常详细)

目录 简介 1 概述 1.1 术语 1.2 组件 1.2.1 repmgr 1.2.2 repmgrd 1.3 Repmgr用户与元数据 2 安装部署 2.0 部署环境 2.1 安装要求 2.1.1 操作系统 2.1.2 PostgreSQL 版本 2.1.3 操作系统用户 2.1.4 安装位置 2.1.5 版本要求 2.2 安装 2.2.1 软件包安装 2.2…

使用Filebeat+Kafka+Logstash+Elasticsearch构建日志分析系统

随着时间的积累&#xff0c;日志数据会越来越多&#xff0c;当您需要查看并分析庞杂的日志数据时&#xff0c;可通过FilebeatKafkaLogstashElasticsearch采集日志数据到Elasticsearch中&#xff0c;并通过Kibana进行可视化展示与分析。本文介绍具体的实现方法。 一、背景信息 …

科学上网导致Adobe软件运行弹出This non-genuine Adobe app will be disabled soon,尝试解决办法

之前介绍用防火墙拦截Adobe软件的出站规则可以解决软件的非正版弹窗&#xff0c;但是有的用户却不行是为什么&#xff0c;原因是使用了代理网络。因为Adobe此时跑的不是本地的流量而是代理的流量。所以防火墙拦截就不起作用了。 首先是之前介绍过的拦截方法&#xff0c;如果你没…

百度飞浆环境安装

前言&#xff1a; 在安装飞浆环境之前得先把pytorch环境安装好&#xff0c;不过关于pytorch网上教程最多的都是通过Anaconda来安装&#xff0c;但是Anaconda环境安装容易遇到安装超时导致安装失败的问题&#xff0c;本文将叫你如何通过pip安装的方式快速安装&#xff0c;其实这…

14——1

这句话的意思是&#xff0c;如图中月份12天数23时&#xff0c;就是1223&#xff1b;当月份9天数2时&#xff0c;就是0902. 可以看到在上面给出的数组元素中&#xff0c;并没有连续挨在一起的2023数字元素——就有人可能输出答案0。 所以这里要看一下—— ——子序列的含义&…

The 8th China Open Source Conference Successfully Concludes

由开源社主办的第八届中国开源年会&#xff08;COSCon23&#xff09;于 2023年10月29日在成都圆满收官。本次大会&#xff0c;为期两天&#xff0c;线下参会报名逾千人次&#xff0c;在线直播观看人数总计 168610 人&#xff0c;直播观看次数达 248725 次&#xff0c;官网累计浏…

网络编程 —— TCP 和 UDP 编程详解

目录 网络编程主要函数介绍 1. socket 函数 2. bind 函数 3. listen 函数 4. accept 函数 5. connect 函数 6. send 函数 7. recv 函数 8. recvfrom 函数 9. sendto 函数 TCP 和 UDP 原理上的区别 TCP 编程 服务端代码&#xff1a; 客户端代码&#xff1a; UDP 编…

nodejs+vue公益帮学网站的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计

在当今高度发达的信息中&#xff0c;信息管理改革已成为一种更加广泛和全面的趋势。为确保中国经济的持续发展&#xff0c; 如何用方便快捷的方式使管理者在广阔的数据海洋里面查询、存储、管理和共享有效的数据信息&#xff0c;对我们的学习&#xff0c;工作和生活具有重要的现…

创造者设计模式

Bike package com.jmj.pattern.builder.demo01;public class Bike {private String frame;//车架private String seat;//车座public String getFrame() {return frame;}public void setFrame(String frame) {this.frame frame;}public String getSeat() {return seat;}public…

Webpack Bundle Analyzer包分析器

当我们需要分析打包文件dist里哪些资源可以进一步优化时&#xff0c;就可以使用包分析器插件webpack-bundle-analyzer。NPM上的介绍是使用交互式可缩放树图可视化 webpack 输出文件的大小。 我的是vue2项目。 1、webpack-bundle-analyzer插件的安装 $ npm install --save-dev…

接口测试vs功能测试

接口测试和功能测试的区别&#xff1a; 本文主要分为两个部分&#xff1a; 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什…

Kyligence 入选 Gartner® 2023 客户之声报告,高分获评“卓越表现者”

近日&#xff0c;Gartner 发布了最新的《2023 分析和商业智能平台“客户之声”报告》(Voice of the Customer for Analytics and Business Intelligence Platforms, 2023, October 2023)。跬智信息&#xff08;Kyligence&#xff09;成功入选该报告&#xff0c;并凭借 4.7 分&a…

嵌入式养成计划-54----ARM--异常处理流程

一百三十五、异常处理流程 135.1 arm处理器工作模式 135.2 异常源和异常模式关系 135.2.1 异常源 异常源就是引发处理器进入相应异常模式 135.2.2 对应关系 异常模式异常源FIQ模式FIQ类型异常源引发处理器进入FIQ模式IRQ模式IRQ类型异常源引发处理器进入IRQ模式SVC模式上电…

opencv车牌识别<一>

目录 一、概述 二、ANPR简介 一、概述 本文将介绍创建自动车牌识别(Automatic Number Plate Recognition&#xff0c;ANPR)所需的步骤。对于不同的情形&#xff0c;实现自动车牌识别会用不同的方法和技术&#xff0c;例如&#xff0c;IR 摄像机、固定汽车位置、光照条件等…

spring cloud之配置中心

Config 统一配置中心(*) 1.简介 # 统一配置中心 - 官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.3.RELEASE/reference/html/#_spring_cloud_config_server- config 分为 config server 和 config client。用来统一管理所有微服务的配置统一配置…