使用源读取器处理媒体数据
本主题介绍如何使用 源读取器 处理媒体数据。
若要使用源读取器,请遵循以下基本步骤:
- 创建源读取器的实例。
- 枚举可能的输出格式。
- 设置每个流的实际输出格式。
- 处理源中的数据。
本主题的其余部分详细介绍了这些步骤。
创建源读取器
若要创建源读取器的实例,请调用以下函数之一:
函数 | 说明 |
---|---|
MFCreateSourceReaderFromURL |
将 URL 作为输入。 此函数使用 源解析程序 从 URL 创建媒体源。 |
MFCreateSourceReaderFromByteStream |
获取指向字节流的指针。 此函数还使用源解析程序创建媒体源。 |
MFCreateSourceReaderFromMediaSource |
获取指向已创建的媒体源的指针。 此函数适用于源解析程序无法创建的媒体源,例如捕获设备或自定义媒体源。 |
通常,对于媒体文件,使用 MFCreateSourceReaderFromURL。 对于网络摄像头等设备,请使用 MFCreateSourceReaderFromMediaSource。 (有关 Microsoft Media Foundation 中捕获设备的详细信息,请参阅 Audio/Video Capture.)
每个函数都采用可选的 IMFAttributes 指针,该指针用于设置源读取器上的各种选项,如这些函数的参考主题中所述。 若要获取默认行为,请将此参数设置为 NULL。 每个函数返回一个 IMFSourceReader 指针作为输出参数。 在调用其中任何函数之前,必须调用 CoInitialize (Ex) 和 MFStartup 函数。
以下代码从 URL 创建源读取器。
int __cdecl wmain(int argc, __in_ecount(argc) PCWSTR* argv)
{
if (argc < 2)
{
return 1;
}
const WCHAR *pszURL = argv[1];
// Initialize the COM runtime.
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.
IMFSourceReader *pReader;
hr = MFCreateSourceReaderFromURL(pszURL, NULL, &pReader);
if (SUCCEEDED(hr))
{
ReadMediaFile(pReader);
pReader->Release();
}
// Shut down Media Foundation.
MFShutdown();
}
CoUninitialize();
}
}
枚举输出格式
每个媒体源至少有一个流。 例如,视频文件可能包含视频流和音频流。 每个流的格式都使用由 IMFMediaType 接口表示的媒体类型进行描述。 有关媒体类型的详细信息,请参阅 媒体类型。 必须检查媒体类型以了解从源读取器获取的数据的格式。
最初,每个流都有一个默认格式,可以通过调用 IMFSourceReader::GetCurrentMediaType 方法找到该格式:
对于每个流,媒体源都会为该流提供可能媒体类型的列表。 类型数取决于源。 如果源表示媒体文件,则每个流通常只有一种类型。 另一方面,网络摄像头可能能够以多种不同的格式流式传输视频。 在这种情况下,应用程序可以从媒体类型列表中选择要使用的格式。
若要获取流的媒体类型之一,请调用 IMFSourceReader::GetNativeMediaType 方法。 此方法采用两个索引参数:流的索引,以及流媒体类型列表中的索引。 若要枚举流的所有类型,请在保持流索引不变的同时递增列表索引。 当列表索引超出边界时, GetNativeMediaType 将返回 MF_E_NO_MORE_TYPES。
HRESULT EnumerateTypesForStream(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
HRESULT hr = S_OK;
DWORD dwMediaTypeIndex = 0;
while (SUCCEEDED(hr))
{
IMFMediaType *pType = NULL;
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;
}
设置输出格式
若要更改输出格式,请调用 IMFSourceReader::SetCurrentMediaType 方法。 此方法采用流索引和媒体类型:
hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);
对于媒体类型,这取决于是否要插入解码器。
- 若要直接从源获取数据而不对其进行解码,请使用 GetNativeMediaType 返回的类型之一。
- 若要解码流,请创建描述所需未压缩格式的新媒体类型。
对于解码器,请按如下所示创建媒体类型:
- 调用 MFCreateMediaType 创建新的媒体类型。
- 设置 MF_MT_MAJOR_TYPE 属性以指定音频或视频。
- 设置 MF_MT_SUBTYPE 属性以指定解码格式的子类型。 (请参阅 音频子类型 GUID 和 视频子类型 GUIDs.)
- 调用 IMFSourceReader::SetCurrentMediaType。
源读取器将自动加载解码器。 若要获取解码格式的完整详细信息,请在调用 SetCurrentMediaType 后调用 IMFSourceReader::GetCurrentMediaType
以下代码为 RGB-32 配置视频流,为 PCM 音频配置音频流。
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.
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;
}
处理媒体数据
若要从源获取媒体数据,请调用 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标志设置为NULL。 在这种情况下, ReadSample 方法返回 S_OK ,因为未发生错误,即使 pSample 参数设置为 NULL 也是如此。 因此,在取消引用 pSample 之前,始终检查它的值。
以下代码演示如何在循环中调用 ReadSample,并检查 方法返回的信息,直到到达媒体文件的末尾。
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 返回 NULL 示例指针。 根据解码器缓冲样本的方式,这种情况可能会立即发生或在多次调用 ReadSample 之后发生。
获取文件持续时间
若要获取媒体文件的持续时间,请调用 IMFSourceReader::GetPresentationAttribute 方法并请求 MF_PD_DURATION 属性,如以下代码所示。
HRESULT GetDuration(IMFSourceReader *pReader, LONGLONG *phnsDuration)
{
PROPVARIANT var;
HRESULT hr = pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE,
MF_PD_DURATION, &var);
if (SUCCEEDED(hr))
{
hr = PropVariantToInt64(var, phnsDuration);
PropVariantClear(&var);
}
return hr;
}
此处显示的函数获取持续时间(以 100 纳秒为单位)。 除以 10,000,000 可得到持续时间(以秒为单位)。
寻求
从本地文件获取数据的媒体源通常可以查找文件中的任意位置。 捕获设备(如网络摄像头)通常无法查找,因为数据是实时的。 通过网络流式传输数据的源可能能够查找,具体取决于网络流式处理协议。
若要了解媒体源是否可以查找,请调用 IMFSourceReader::GetPresentationAttribute 并请求 MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS 属性,如以下代码所示:
HRESULT GetSourceFlags(IMFSourceReader *pReader, ULONG *pulFlags)
{
ULONG flags = 0;
PROPVARIANT var;
PropVariantInit(&var);
HRESULT hr = pReader->GetPresentationAttribute(
MF_SOURCE_READER_MEDIASOURCE,
MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS,
&var);
if (SUCCEEDED(hr))
{
hr = PropVariantToUInt32(var, &flags);
}
if (SUCCEEDED(hr))
{
*pulFlags = flags;
}
PropVariantClear(&var);
return hr;
}
此函数从源获取一组功能标志。 这些标志在 MFMEDIASOURCE_CHARACTERISTICS 枚举中定义。 两个标志与查找相关:
标志 | 描述 |
---|---|
MFMEDIASOURCE_CAN_SEEK |
源可以查找。 |
MFMEDIASOURCE_HAS_SLOW_SEEK |
查找可能需要很长时间才能完成。 例如,源可能需要先下载整个文件,然后才能查找。 (源没有严格的条件可返回此标志。) |
以下代码测试 MFMEDIASOURCE_CAN_SEEK 标志。
BOOL SourceCanSeek(IMFSourceReader *pReader)
{
BOOL bCanSeek = FALSE;
ULONG flags;
if (SUCCEEDED(GetSourceFlags(pReader, &flags)))
{
bCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
}
return bCanSeek;
}
若要查找,请调用 IMFSourceReader::SetCurrentPosition 方法,如以下代码所示。
HRESULT SetPosition(IMFSourceReader *pReader, const LONGLONG& hnsPosition)
{
PROPVARIANT var;
HRESULT hr = InitPropVariantFromInt64(hnsPosition, &var);
if (SUCCEEDED(hr))
{
hr = pReader->SetCurrentPosition(GUID_NULL, var);
PropVariantClear(&var);
}
return hr;
}
第一个参数提供用于指定搜寻位置的时间格式。 Media Foundation 中的所有媒体源都需要支持 100 纳秒单位,由 值GUID_NULL指示。 第二个参数是包含搜寻位置的 PROPVARIANT 。 对于 100 纳秒的时间单位,数据类型为 LONGLONG。
请注意,并非每个媒体源都提供帧准确的查找。 查找的准确性取决于多个因素,例如关键帧间隔、媒体文件是否包含索引,以及数据是否具有常量或可变比特率。 因此,在查找文件中的位置后,无法保证下一个示例中的时间戳与请求的位置完全匹配。 通常,实际位置不会晚于请求的位置,因此可以丢弃样本,直到到达流中的所需点。
播放速率
尽管可以使用源阅读器设置播放速率,但这样做通常不是很有用,原因如下:
- 源阅读器不支持反向播放,即使媒体源支持反向播放。
- 应用程序控制呈现时间,因此应用程序可以实现快速或慢速播放,而无需在源上设置速率。
- 某些媒体源支持 精简 模式,其中源提供的样本较少,通常只是关键帧。 但是,如果要删除非关键帧,则可以检查MFSampleExtension_CleanPoint属性的每个示例。
若要使用源读取器设置播放速率,请调用 IMFSourceReader::GetServiceForStream 方法,从媒体源获取 IMFRateSupport 和 IMFRateControl 接口。
硬件加速
源阅读器与 Microsoft DirectX 视频加速 (DXVA) 2.0 兼容,用于硬件加速视频解码。 若要将 DXVA 与源读取器配合使用,请执行以下步骤。
- 创建 Microsoft Direct3D 设备。
- 调用 DXVA2CreateDirect3DDeviceManager9 函数以创建 Direct3D 设备管理器。 此函数获取指向 IDirect3DDeviceManager9 接口的 指针。
- 使用指向 Direct3D 设备的指针调用 IDirect3DDeviceManager9::ResetDevice 方法。
- 通过调用 MFCreateAttributes 函数创建属性存储。
- 创建源读取器。 在创建函数的 pAttributes 参数中传递属性存储。
提供 Direct3D 设备时,源读取器会分配与 DXVA 视频处理器 API 兼容的视频样本。 可以使用 DXVA 视频处理来执行硬件去交错或视频混合。 有关详细信息,请参阅 DXVA 视频处理。 此外,如果解码器支持 DXVA 2.0,它将使用 Direct3D 设备执行硬件加速解码。
重要
从 Windows 8 开始,可以使用 IMFDXGIDeviceManager 代替 IDirect3DDeviceManager9。 对于 Windows 应用商店应用,必须使用 IMFDXGIDeviceManager。 有关详细信息,请参阅 Direct3D 11 视频 API。
相关主题