MF 概览
Media Foundation 提供了两种不同的编程模型,左边展示的是端到端的媒体数据模型,主要用在:播放URL或者文件,以及控制流。
在图表右侧展示的第二种模型中,应用程序可以从源头拉取数据,也可以将数据推送到目的地(或两者兼而有之)。如果需要处理数据,这个模型特别有用,因为应用程序可以直接访问数据流。
一些用到的概念
Attributes
属性是在对象内部存储信息的通用方式,以键/值对的列表形式存在。Media Types
媒体类型描述了媒体数据流的格式。Media Buffers
媒体缓冲区保存媒体数据的块,例如视频帧和音频样本,并用于在对象之间传输数据。Media Samples
媒体样本是媒体缓冲区的容器。它们还包含有关缓冲区的元数据,例如时间戳。
这些概念对应了后面编程中的一些对象,所以也是比较重要的。
MF平台API提供了一些核心功能,这些功能被MF流水线使用,例如异步回调和工作队列。某些应用程序可能需要直接调用这些API;此外,如果要为MF实现自定义源、转换或接收器,您将需要使用这些API。
Media Pipeline
媒体流水线包含三种生成或处理媒体数据的对象类型:
-
媒体源(Media Sources):将数据引入流水线。媒体源可以从本地文件(如视频文件)、网络流或硬件捕获设备获取数据。
-
Media Foundation Transforms,MFTs:处理来自流的数据。编码器和解码器通常作为MFTs实现。
-
媒体接收器(Media Sinks):消耗数据,例如通过在显示器上显示视频、播放音频或将数据写入媒体文件。
第三方可以实现自己的自定义源、接收器和MFTs,例如,以支持新的媒体文件格式。
媒体会话(Media Session)控制数据在流水线中的流动,并处理诸如质量控制、音视频同步和对格式变更的响应等任务。
Source Reader and Sink Writer
Source Reader
和 Sink 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表面中。
媒体缓冲区与两个相关的大小:
-
最大长度是为缓冲区分配的内存的物理大小。这个值在缓冲区创建时设置,而在缓冲区的生命周期内不会改变。最大长度指示缓冲区可以存储多少数据。要找到最大大小,调用
IMFMediaBuffer::GetMaxLength
。 -
当前长度是当前在缓冲区中的有效数据量。当首次分配缓冲区时,当前长度为零,因为缓冲区中没有有效数据。如果将任何数据写入缓冲区,必须通过调用
IMFMediaBuffer::SetCurrentLength
来更新当前长度。例如,如果向缓冲区写入了100字节的数据,请使用值100调用SetCurrentLength
。如果从媒体缓冲区读取数据,请调用IMFMediaBuffer::GetCurrentLength
来查找当前缓冲区中有多少数据。不要读取超出当前长度。当前长度永远不会超过缓冲区的最大长度。
Accessing the Buffer Memory
要访问缓冲区中的内存,请调用 IMFMediaBuffer::Lock
。该方法返回指向内存块起始位置的指针。它还返回最大长度和当前长度。当使用完指针后,请调用 IMFMediaBuffer::Unlock
。
写入数据到媒体缓冲区的步骤:
- 调用
IMFMediaBuffer::Lock
以获取指向内存的指针。该方法还返回缓冲区的最大长度。 - 将数据写入内存,写入的数据量不得超过缓冲区的最大长度。
- 调用
IMFMediaBuffer::SetCurrentLength
来更新当前长度。将当前长度设置为第2步中写入的数据量。 - 调用
IMFMediaBuffer::Unlock
来解锁缓冲区。
从媒体缓冲区读取数据的步骤:
- 调用
IMFMediaBuffer::Lock
以获取指向内存的指针。该方法还返回缓冲区的当前长度(缓冲区中有效数据的量)。 - 读取内存的内容,读取的数据量不得超过当前长度。
- 调用
IMFMediaBuffer::Unlock
来解锁缓冲区。
Creating System Memory Buffers
系统内存缓冲区是一个管理系统内存块的媒体缓冲区。要创建此对象的实例,请调用 MFCreateMemoryBuffer
或 MFCreateAlignedMemoryBuffer
并指定缓冲区大小。这两个函数都分配一个内存块并返回一个 IMFMediaBuffer
指针。当媒体缓冲区的引用计数达到零并销毁对象时,内存会自动释放。
Uncompressed Video Buffers
如何处理包含未压缩视频帧的媒体缓冲区。按照首选顺序,有以下选项。并非每个媒体缓冲区都支持每个选项。
- 使用底层的 Direct3D 表面。(仅适用于存储在 Direct3D 表面中的视频帧。)
- 使用 IMF2DBuffer 接口。
- 使用 IMFMediaBuffer 接口。
Use the Underlying Direct3D Surface
视频帧可能存储在Direct3D表面中。如果是这样,您可以通过在媒体缓冲区对象上调用 IMFGetService::GetService
或 MFGetService
来获取指向表面的指针。使用服务标识符 MR_BUFFER_SERVICE
。当访问视频帧的组件设计为使用Direct3D时,建议使用此方法。例如,支持DirectX视频加速的视频解码器应使用此方法。
Use the IMF2DBuffer Interface
如果视频帧未存储在Direct3D表面中,或者组件未设计为使用Direct3D,则下一个推荐的访问视频帧的方式是查询支持 IMF2DBuffer
接口的缓冲区。该接口专门设计用于图像数据。要获取指向该接口的指针,请在媒体缓冲区上调用 QueryInterface
。并非所有的媒体缓冲区对象都公开此接口。但是,如果媒体缓冲区确实公开了 IMF2DBuffer
接口,建议在可能的情况下使用该接口访问数据,而不是使用 IMFMediaBuffer
。仍然可以使用 IMFMediaBuffer
接口,但效率可能较低。
以下是访问数据的步骤:
- 在媒体缓冲区上调用
QueryInterface
以获取IMF2DBuffer
接口。 - 调用
IMF2DBuffer::Lock2D
来访问缓冲区的内存。此方法返回指向顶部像素行的第一个字节的指针。它还返回图像步幅,即从一行像素的开始到下一行的开始的字节数。缓冲区可能在每行像素后包含填充字节,因此步幅可能比以字节为单位的图像宽度更宽。如果图像在内存中是朝下的,步幅也可能是负值。有关更多信息,请参阅图像步幅。 - 仅在需要访问内存时保持缓冲区锁定。通过调用
IMF2DBuffer::Unlock2D
解锁缓冲区。
并非每个媒体缓冲区都实现了 IMF2DBuffer
接口。如果媒体缓冲区没有实现此接口(即在步骤1中缓冲区对象返回 E_NOINTERFACE
),则必须使用下面描述的 IMFMediaBuffer
接口。
Use the IMFMediaBuffer Interface
如果媒体缓冲区没有公开 IMF2DBuffer
接口,则使用 IMFMediaBuffer
接口。此接口的一般语义在主题“使用媒体缓冲区”中有描述。
以下是访问数据的步骤:
- 在媒体缓冲区上调用
QueryInterface
以获取IMFMediaBuffer
接口。 - 调用
IMFMediaBuffer::Lock
来访问缓冲区内存。此方法返回指向缓冲区内存的指针。当使用Lock
方法时,步幅始终是与问题中的视频格式相关的最小步幅,没有额外的填充字节。 - 仅在需要访问内存时保持缓冲区锁定。通过调用
IMFMediaBuffer::Unlock
解锁缓冲区。
如果缓冲区公开了 IMF2DBuffer
,则应始终避免调用 IMFMediaBuffer::Lock
,因为 Lock
方法可能会强制将缓冲区对象中的视频帧放入一个连续的内存块,然后再次返回。另一方面,如果缓冲区不支持 IMF2DBuffer
,那么 IMFMediaBuffer::Lock
可能不会导致复制。
根据媒体类型计算最小步幅的步骤如下:
- 最小步幅可能存储在
MF_MT_DEFAULT_STRIDE
属性中。 - 如果未设置
MF_MT_DEFAULT_STRIDE
属性,请调用MFGetStrideForBitmapInfoHeader
函数计算大多数常见视频格式的步幅。 - 如果
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 返回的类型之一。
- 要解码流,请创建描述所需未压缩格式的新媒体类型。
对于解码器的情况,请按以下步骤创建媒体类型:
- 调用 MFCreateMediaType 创建一个新的媒体类型。
- 设置 MF_MT_MAJOR_TYPE 特性以指定音频或视频。
- 设置 MF_MT_SUBTYPE 特性以指定解码格式的子类型。(请参阅 音频子类型 GUID 和 视频子类型 GUID。)
- 调用 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
参数中不返回媒体样本。例如,当达到文件末尾时,ReadSample
在 dwFlags
中设置 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
返回给应用程序。
如果要解码解码器当前缓冲的所有样本,而不传递任何新样本给解码器,请在 ReadSample
的 dwControlFlags
参数中设置 MF_SOURCE_READER_CONTROLF_DRAIN
标志。继续在循环中执行此操作,直到 ReadSample
返回一个空样本指针。根据解码器缓冲样本的方式,这可能会立即发生,也可能在多次调用 ReadSample
之后发生。
搞球不懂!!!!