教程:单步 Windows 媒体编码
编码是指将数字媒体从一种格式转换为另一种格式的过程。 例如,将 MP3 音频转换为由高级系统格式 (ASF) 规范定义的 Windows Media Audio 格式。
注意 ASF 规范定义了输出文件(.wma 或 .wmv)的容器类型,该文件可以包含任何格式的媒体数据,无论是压缩的还是未压缩的。 例如,ASF 容器(如 .wma 文件)可以包含 MP3 格式的媒体数据。 编码过程转换文件中包含的数据的实际格式。
本教程介绍将清晰内容(非受 DRM 保护)输入源编码为 Windows Media 内容,并使用管道层 ASF 组件将数据写入新的 ASF文件 (.wm*)。 这些组件将用于生成部分编码拓扑,该拓扑将由媒体会话的实例控制。
在本教程中,你将创建一个控制台应用程序,该应用程序将输入和输出文件名以及编码模式作为参数。 输入文件可以是压缩的或未压缩的媒体格式。 有效的编码模式是“CBR”(恒定比特率)或“VBR”(可变比特率)。 应用程序创建一个媒体源来表示输入文件名指定的源,并创建 ASF 文件接收器来将源文件的编码内容归档到 ASF 文件中。 为了使方案易于实现,输出文件将只有一个音频流和一个视频流。 应用程序为音频流格式转换插入 Windows Media Audio 9.1 Professional 编解码器,并为视频流插入 Windows Media Video 9 编解码器。
对于恒定比特率编码,在编码会话开始之前,编码器必须知道它必须达到的目标比特率。 在本教程中,对于“CBR”模式,应用程序使用在媒体类型协商期间从编码器检索到的第一个输出媒体类型的可用比特率作为目标比特率。 对于可变比特率编码,本教程通过设置质量级别来演示使用可变比特率进行编码。 音频流以 98 的质量级别编码,视频流以 95 的质量级别进行编码。
以下过程总结了使用单步编码模式在 ASF 容器中对 Windows Media 内容进行编码的步骤。
- 使用源解析程序为指定内容的创建媒体源。
- 枚举媒体源中的流。
- 根据媒体源中需要编码的流,创建 ASF 媒体接收器并添加流接收器。
- 使用所需的编码属性配置媒体接收器。
- 为输出文件中的流创建 Windows Media 编码器。
- 使用在媒体接收器上设置的属性配置编码器。
- 生成部分编码拓扑。
- 实例化媒体会话,并在媒体会话上设置拓扑。
- 控制媒体会话并从媒体会话获取所有相关事件,启动编码会话。
- 对于 VBR 编码,请从编码器获取编码属性值,并在媒体接收器上进行设置。
- 关闭并关上编码会话。
本教程包含以下各节:
先决条件
本教程的假设条件如下:
熟悉 Media Foundation 提供的用于处理 ASF 对象的 ASF 文件结构、管道层ASF 组件。 这些组件包括以下对象:
-
注意 如果正在执行转换(在不更改格式的情况下将较高比特率文件转换为较低比特率文件),将使用 ASF 媒体源。
-
熟悉 Windows Media 编码器以及各种编码类型,尤其是恒定比特率编码和基于质量的可变比特率编码。
熟悉编码器 MFT 操作。 具体来说,就是创建编码器的实例,并在编码器上设置输入和输出类型。
熟悉拓扑对象以及如何生成编码拓扑。 有关拓扑和拓扑节点的详细信息,请参阅创建拓扑。
设置项目
在源文件中包含以下标头:
#include <new> #include <mfidl.h> // Media Foundation interfaces #include <mfapi.h> // Media Foundation platform APIs #include <mferror.h> // Media Foundation error codes #include <wmcontainer.h> // ASF-specific components #include <wmcodecdsp.h> // Windows Media DSP interfaces #include <Dmo.h> // DMO objects #include <uuids.h> // Definition for FORMAT_VideoInfo #include <propvarutil.h>
链接到以下库文件:
// The required link libraries #pragma comment(lib, "mfplat") #pragma comment(lib, "mf") #pragma comment(lib, "mfuuid") #pragma comment(lib, "msdmo") #pragma comment(lib, "strmiids") #pragma comment(lib, "propsys")
声明 SafeRelease 函数。
template <class T> void SafeRelease(T **ppT) { if (*ppT) { (*ppT)->Release(); *ppT = NULL; } }
声明 ENCODING_MODE 枚举,以定义 CBR 和 VBR 编码类型。
// Encoding mode typedef enum ENCODING_MODE { NONE = 0x00000000, CBR = 0x00000001, VBR = 0x00000002, } ;
为视频流的缓冲区窗口定义一个常量。
// Video buffer window const INT32 VIDEO_WINDOW_MSEC = 3000;
创建媒体源
为输入源创建媒体源。 Media Foundation 为各种媒体格式提供内置媒体源:MP3、MP4/3GP、AVI/WAVE。 有关 Media Foundation 支持的格式的信息,请参阅 Media Foundation 中支持的媒体格式。
若要创建媒体源,请使用源解析程序。 此对象分析为源文件指定的 URL,并创建适当的媒体源。
进行以下调用:
IMFSourceResolver::CreateObjectFromURL
有关这些调用的详细信息,请参阅使用源解析程序。
如果输入文件为ASF格式,并且希望在不更改格式的情况下将其转换为不同的比特率文件,则源解析程序会创建 ASF 媒体源的实例。
以下代码示例显示一个函数 CreateMediaSource,该函数为指定的文件创建媒体源。
// Create a media source from a URL.
HRESULT CreateMediaSource(PCWSTR sURL, IMFMediaSource **ppSource)
{
MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;
IMFSourceResolver* pSourceResolver = NULL;
IUnknown* pSource = NULL;
// Create the source resolver.
HRESULT hr = MFCreateSourceResolver(&pSourceResolver);
if (FAILED(hr))
{
goto done;
}
// Use the source resolver to create the media source.
// Note: For simplicity this sample uses the synchronous method to create
// the media source. However, creating a media source can take a noticeable
// amount of time, especially for a network source. For a more responsive
// UI, use the asynchronous BeginCreateObjectFromURL method.
hr = pSourceResolver->CreateObjectFromURL(
sURL, // URL of the source.
MF_RESOLUTION_MEDIASOURCE, // Create a source object.
NULL, // Optional property store.
&ObjectType, // Receives the created object type.
&pSource // Receives a pointer to the media source.
);
if (FAILED(hr))
{
goto done;
}
// Get the IMFMediaSource interface from the media source.
hr = pSource->QueryInterface(IID_PPV_ARGS(ppSource));
done:
SafeRelease(&pSourceResolver);
SafeRelease(&pSource);
return hr;
}
创建 ASF 文件接收器
创建 ASF 文件接收器的实例,该实例将在编码会话结束时将编码的媒体数据存档到 ASF 文件。
在本教程中,需要为 ASF 文件接收器创建一个激活对象。 文件接收器需要以下信息才能创建所需的流接收器。
- 要编码并写入最终文件的流。
- 用于对媒体内容进行编码的编码属性,例如编码类型、编码次数和相关属性。
- 全局文件属性,指示媒体接收器是否应自动调整泄漏桶参数(比特率和缓冲区大小)。
流信息在 ASF 配置文件对象中配置,编码和全局属性在 ASF ContentInfo 对象管理的属性存储区中设置。
有关 ASF 文件接收器的概述,请参阅 ASF 媒体接收器。
创建 ASF 配置文件对象
对于 ASF 文件接收器,要将编码的媒体数据写入 ASF 文件,接收器需要知道要为其创建流接收器的流的数量和流的类型。 在本教程中,需要从媒体源提取该信息,并创建相应的输出流。 将输出流限制为一个音频流和一个视频流。 对于源中的每个选定流,创建一个目标流,并将其添加到配置文件中。
若要实现这一步,需要以下对象。
- ASF 配置文件
- 媒体源的演示描述符
- 媒体源中所选流的流描述符。
- 所选流的媒体类型。
以下步骤介绍创建 ASF 配置文件和目标流的过程。
创建 ASF 配置文件
调用 MFCreateASFProfile,以创建空配置文件对象。
调用 IMFMediaSource::CreatePresentationDescriptor,为在本教程“创建媒体源”部分所述步骤中创建的媒体源创建演示描述符。
调用 IMFPresentationDescriptor::GetStreamDescriptorCount,以获取媒体源中的流数。
为媒体源中的每个流调用 IMFPresentationDescriptor::GetStreamDescriptorByIndex,获取流的流描述符。
调用 IMFStreamDescriptor::GetMediaTypeHandler,然后调用 IMFMediaTypeHandler::GetMediaTypeByIndex,获取流的第一个媒体类型。
注意 为了避免复杂的调用,假设每个流只存在一种媒体类型,并选择流的第一种媒体类型。 对于复杂的流,需要从媒体类型处理程序中枚举每个媒体类型,并选择要编码的媒体类型。
调用 IIMFMediaType::GetMajorType,以获取流的主要类型,从而确定流是包含音频还是视频。
根据流的主要类型,创建目标媒体类型。 这些媒体类型将保存编码器将在编码会话期间使用的格式信息。 本教程的以下部分介绍创建目标媒体类型的过程。
- 创建压缩音频媒体类型
- 创建压缩视频媒体类型
基于目标媒体类型创建流,并根据应用程序的要求配置流,并将流添加到配置文件中。 有关详细信息,请参阅将流信息添加到 ASF 文件接收器。
- 调用 IMFASFProfile::CreateStream,传递目标媒体类型以创建输出流。 该方法检索流对象的 IMFASFStreamConfig 接口。
- 配置流。
- 调用 IMFASFStreamConfig::SetStreamNumber,为流分配一个编号。 此设置是强制的。
- (可选)通过调用 IMFASFStreamConfig 方法和相关的流配置属性,在每个流上配置泄漏桶参数、有效负载扩展、互相排斥。
- 使用 ASF ContentInfo 对象添加流级别编码属性。 有关此步骤的更多信息,请参阅本教程中的“创建 ASF ContentInfo 对象”一节。
- 调用 IMFASFProfile::SetStream,将流添加到配置文件。
以下代码示例创建输出音频流。
//------------------------------------------------------------------- // CreateAudioStream // Create an audio stream and add it to the profile. // // pProfile: A pointer to the ASF profile. // wStreamNumber: Stream number to assign for the new stream. //------------------------------------------------------------------- HRESULT CreateAudioStream(IMFASFProfile* pProfile, WORD wStreamNumber) { if (!pProfile) { return E_INVALIDARG; } if (wStreamNumber < 1 || wStreamNumber > 127 ) { return MF_E_INVALIDSTREAMNUMBER; } IMFMediaType* pAudioType = NULL; IMFASFStreamConfig* pAudioStream = NULL; //Create an output type from the encoder HRESULT hr = GetOutputTypeFromWMAEncoder(&pAudioType); if (FAILED(hr)) { goto done; } //Create a new stream with the audio type hr = pProfile->CreateStream(pAudioType, &pAudioStream); if (FAILED(hr)) { goto done; } //Set stream number hr = pAudioStream->SetStreamNumber(wStreamNumber); if (FAILED(hr)) { goto done; } //Add the stream to the profile hr = pProfile->SetStream(pAudioStream); if (FAILED(hr)) { goto done; } wprintf_s(L"Audio Stream created. Stream Number: %d.\n", wStreamNumber); done: SafeRelease(&pAudioStream); SafeRelease(&pAudioType); return hr; }
以下代码示例创建输出视频流。
//------------------------------------------------------------------- // CreateVideoStream // Create an video stream and add it to the profile. // // pProfile: A pointer to the ASF profile. // wStreamNumber: Stream number to assign for the new stream. // pType: A pointer to the source's video media type. //------------------------------------------------------------------- HRESULT CreateVideoStream(IMFASFProfile* pProfile, WORD wStreamNumber, IMFMediaType* pType) { if (!pProfile) { return E_INVALIDARG; } if (wStreamNumber < 1 || wStreamNumber > 127 ) { return MF_E_INVALIDSTREAMNUMBER; } HRESULT hr = S_OK; IMFMediaType* pVideoType = NULL; IMFASFStreamConfig* pVideoStream = NULL; UINT32 dwBitRate = 0; //Create a new video type from the source type hr = CreateCompressedVideoType(pType, &pVideoType); if (FAILED(hr)) { goto done; } //Create a new stream with the video type hr = pProfile->CreateStream(pVideoType, &pVideoStream); if (FAILED(hr)) { goto done; } //Set a valid stream number hr = pVideoStream->SetStreamNumber(wStreamNumber); if (FAILED(hr)) { goto done; } //Add the stream to the profile hr = pProfile->SetStream(pVideoStream); if (FAILED(hr)) { goto done; } wprintf_s(L"Video Stream created. Stream Number: %d .\n", wStreamNumber); done: SafeRelease(&pVideoStream); SafeRelease(&pVideoType); return hr; }
以下代码示例根据源中的流创建输出流。
//For each stream in the source, add target stream information in the profile
for (DWORD iStream = 0; iStream < dwSrcStream; iStream++)
{
hr = pPD->GetStreamDescriptorByIndex(
iStream, &fSelected, &pStreamDesc);
if (FAILED(hr))
{
goto done;
}
if (!fSelected)
{
continue;
}
//Get the media type handler
hr = pStreamDesc->GetMediaTypeHandler(&pHandler);
if (FAILED(hr))
{
goto done;
}
//Get the first media type
hr = pHandler->GetMediaTypeByIndex(0, &pMediaType);
if (FAILED(hr))
{
goto done;
}
//Get the major type
hr = pMediaType->GetMajorType(&guidMajor);
if (FAILED(hr))
{
goto done;
}
// If this is a video stream, create the target video stream
if (guidMajor == MFMediaType_Video)
{
//The stream level encoding properties will be set in this call
hr = CreateVideoStream(pProfile, wStreamID, pMediaType);
if (FAILED(hr))
{
goto done;
}
}
// If this is an audio stream, create the target audio stream
if (guidMajor == MFMediaType_Audio)
{
//The stream level encoding properties will be set in this call
hr = CreateAudioStream(pProfile, wStreamID);
if (FAILED(hr))
{
goto done;
}
}
//Get stream's encoding property
hr = pContentInfo->GetEncodingConfigurationPropertyStore(wStreamID, &pContentInfoProps);
if (FAILED(hr))
{
goto done;
}
//Set the stream-level encoding properties
hr = SetEncodingProperties(guidMajor, pContentInfoProps);
if (FAILED(hr))
{
goto done;
}
SafeRelease(&pMediaType);
SafeRelease(&pStreamDesc);
SafeRelease(&pContentInfoProps);
wStreamID++;
}
创建压缩音频媒体类型
如果要在输出文件中包括音频流,请通过设置所需的属性指定编码类型的特征来创建音频类型。 若要确保音频类型与 Windows Media 音频编码器兼容,请实例化编码器 MFT,设置编码属性,并通过调用 IMFTransform::GetOutputAvailableType 获取媒体类型。 通过循环浏览所有可用类型,获取每个媒体类型的属性,并选择最符合要求的类型,从而获得所需的输出类型。 在本教程中,从编码器支持的输出媒体类型列表中获取第一个可用类型。
注意 对于 Windows 7,Media Foundation 提供了一个新函数 MFTranscodeGetAudioOutputAvailableTypes,用于检索兼容音频类型的列表。 此函数仅获取用于 CBR 编码的媒体类型。
完整的音频类型必须设置以下属性:
- MF_MT_MAJOR_TYPE
- MF_MT_SUBTYPE
- MF_MT_AUDIO_NUM_CHANNELS
- MF_MT_AUDIO_SAMPLES_PER_SECOND
- MF_MT_AUDIO_BLOCK_ALIGNMENT
- MF_MT_AUDIO_AVG_BYTES_PER_SECOND
- MF_MT_AUDIO_BITS_PER_SAMPLE
以下代码示例通过从 Windows Media 音频编码器获取兼容类型来创建压缩音频类型。 SetEncodingProperties 的实现如本教程的“创建 ASF ContentInfo 对象”部分所示。
//-------------------------------------------------------------------
// GetOutputTypeFromWMAEncoder
// Gets a compatible output type from the Windows Media audio encoder.
//
// ppAudioType: Receives a pointer to the media type.
//-------------------------------------------------------------------
HRESULT GetOutputTypeFromWMAEncoder(IMFMediaType** ppAudioType)
{
if (!ppAudioType)
{
return E_POINTER;
}
IMFTransform* pMFT = NULL;
IMFMediaType* pType = NULL;
IPropertyStore* pProps = NULL;
//We need to find a suitable output media type
//We need to create the encoder to get the available output types
//and discard the instance.
CLSID *pMFTCLSIDs = NULL; // Pointer to an array of CLISDs.
UINT32 cCLSID = 0; // Size of the array.
MFT_REGISTER_TYPE_INFO tinfo;
tinfo.guidMajorType = MFMediaType_Audio;
tinfo.guidSubtype = MFAudioFormat_WMAudioV9;
// Look for an encoder.
HRESULT hr = MFTEnum(
MFT_CATEGORY_AUDIO_ENCODER,
0, // Reserved
NULL, // Input type to match. None.
&tinfo, // WMV encoded type.
NULL, // Attributes to match. (None.)
&pMFTCLSIDs, // Receives a pointer to an array of CLSIDs.
&cCLSID // Receives the size of the array.
);
if (FAILED(hr))
{
goto done;
}
// MFTEnum can return zero matches.
if (cCLSID == 0)
{
hr = MF_E_TOPO_CODEC_NOT_FOUND;
goto done;
}
else
{
// Create the MFT decoder
hr = CoCreateInstance(pMFTCLSIDs[0], NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pMFT));
if (FAILED(hr))
{
goto done;
}
}
// Get the encoder's property store
hr = pMFT->QueryInterface(IID_PPV_ARGS(&pProps));
if (FAILED(hr))
{
goto done;
}
//Set encoding properties
hr = SetEncodingProperties(MFMediaType_Audio, pProps);
if (FAILED(hr))
{
goto done;
}
//Get the first output type
//You can loop through the available output types to choose
//the one that meets your target requirements
hr = pMFT->GetOutputAvailableType(0, 0, &pType);
if (FAILED(hr))
{
goto done;
}
//Return to the caller
*ppAudioType = pType;
(*ppAudioType)->AddRef();
done:
SafeRelease(&pProps);
SafeRelease(&pType);
SafeRelease(&pMFT);
CoTaskMemFree(pMFTCLSIDs);
return hr;
}
创建压缩视频媒体类型
如果要在输出文件中包含视频流,请创建一个完全编码的视频类型。 完整的媒体类型必须包括所需的比特率和编解码器专用数据。
有两种方法可以创建完整的视频媒体类型。
创建一个空媒体类型,并从源视频类型复制媒体类型属性,然后使用 GUID 常量 MFVideoFormat_WMV3 覆盖 MF_MT_SUBTYPE 属性。
完整的视频类型必须设置以下属性:
- MF_MT_MAJOR_TYPE
- MF_MT_SUBTYPE
- MF_MT_FRAME_RATE
- MF_MT_FRAME_SIZE
- MF_MT_INTERLACE_MODE
- MF_MT_PIXEL_ASPECT_RATIO
- MF_MT_AVG_BITRATE
- MF_MT_USER_DATA
以下代码示例从源的视频类型创建压缩视频类型。
//------------------------------------------------------------------- // CreateCompressedVideoType // Creates an output type from source's video media type. // // pType: A pointer to the source's video media type. // ppVideoType: Receives a pointer to the media type. //------------------------------------------------------------------- HRESULT CreateCompressedVideoType( IMFMediaType* pType, IMFMediaType** ppVideoType) { if (!pType) { return E_INVALIDARG; } if (!ppVideoType) { return E_POINTER; } HRESULT hr = S_OK; MFRatio fps = { 0 }; MFRatio par = { 0 }; UINT32 width = 0, height = 0; UINT32 interlace = MFVideoInterlace_Unknown; UINT32 fSamplesIndependent = 0; UINT32 cBitrate = 0; IMFMediaType* pMediaType = NULL; GUID guidMajor = GUID_NULL; hr = pType->GetMajorType(&guidMajor); if (FAILED(hr)) { goto done; } if (guidMajor != MFMediaType_Video ) { hr = MF_E_INVALID_FORMAT; goto done; } hr = MFCreateMediaType(&pMediaType); if (FAILED(hr)) { goto done; } hr = pType->CopyAllItems(pMediaType); if (FAILED(hr)) { goto done; } //Fill the missing attributes //1. Frame rate hr = MFGetAttributeRatio(pMediaType, MF_MT_FRAME_RATE, (UINT32*)&fps.Numerator, (UINT32*)&fps.Denominator); if (hr == MF_E_ATTRIBUTENOTFOUND) { fps.Numerator = 30000; fps.Denominator = 1001; hr = MFSetAttributeRatio(pMediaType, MF_MT_FRAME_RATE, fps.Numerator, fps.Denominator); if (FAILED(hr)) { goto done; } } ////2. Frame size hr = MFGetAttributeSize(pMediaType, MF_MT_FRAME_SIZE, &width, &height); if (hr == MF_E_ATTRIBUTENOTFOUND) { width = 1280; height = 720; hr = MFSetAttributeSize(pMediaType, MF_MT_FRAME_SIZE, width, height); if (FAILED(hr)) { goto done; } } ////3. Interlace mode if (FAILED(pMediaType->GetUINT32(MF_MT_INTERLACE_MODE, &interlace))) { hr = pMediaType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); if (FAILED(hr)) { goto done; } } ////4. Pixel aspect Ratio hr = MFGetAttributeRatio(pMediaType, MF_MT_PIXEL_ASPECT_RATIO, (UINT32*)&par.Numerator, (UINT32*)&par.Denominator); if (hr == MF_E_ATTRIBUTENOTFOUND) { par.Numerator = par.Denominator = 1; hr = MFSetAttributeRatio(pMediaType, MF_MT_PIXEL_ASPECT_RATIO, (UINT32)par.Numerator, (UINT32)par.Denominator); if (FAILED(hr)) { goto done; } } //6. bit rate if (FAILED(pMediaType->GetUINT32(MF_MT_AVG_BITRATE, &cBitrate))) { hr = pMediaType->SetUINT32(MF_MT_AVG_BITRATE, 1000000); if (FAILED(hr)) { goto done; } } hr = pMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_WMV3); if (FAILED(hr)) { goto done; } // Return the pointer to the caller. *ppVideoType = pMediaType; (*ppVideoType)->AddRef(); done: SafeRelease(&pMediaType); return hr; }
通过设置编码属性,然后调用 IMFTransform::GetOutputAvailableType,从 Windows Media 视频编码器获取兼容的媒体类型。 此方法返回部分类型。 通过添加以下信息,确保将部分类型转换为完整类型:
- 在 MF_MT_AVG_BITRATE 属性中设置视频比特率。
- 通过设置 MF_MT_USER_DATA 属性添加编解码器专用数据。 有关详细说明,请参阅配置 WMV 编码器中的“专用编解码器数据”。
由于 IWMCodecPrivateData::GetPrivateData 在返回编解码器专用数据之前检查比特率,因此请确保在获取编解码器专用数据之前设置比特率。
以下代码示例通过从 Windows Media 视频编码器获取兼容类型来创建压缩视频类型。 同时还会创建未压缩的视频类型,并将其设置为编码器的输入。 这在 Helper 函数 CreateUncompressedVideoType 中实现。 GetOutputTypeFromWMVEncoder 通过添加编解码器专用数据将返回的部分类型转换为完整类型。 SetEncodingProperties 的实现如本教程的“创建 ASF ContentInfo 对象”部分所示。
//------------------------------------------------------------------- // GetOutputTypeFromWMVEncoder // Gets a compatible output type from the Windows Media video encoder. // // pType: A pointer to the source video stream's media type. // ppVideoType: Receives a pointer to the media type. //------------------------------------------------------------------- HRESULT GetOutputTypeFromWMVEncoder(IMFMediaType* pType, IMFMediaType** ppVideoType) { if (!ppVideoType) { return E_POINTER; } IMFTransform* pMFT = NULL; IPropertyStore* pProps = NULL; IMFMediaType* pInputType = NULL; IMFMediaType* pOutputType = NULL; UINT32 cBitrate = 0; //We need to find a suitable output media type //We need to create the encoder to get the available output types //and discard the instance. CLSID *pMFTCLSIDs = NULL; // Pointer to an array of CLISDs. UINT32 cCLSID = 0; // Size of the array. MFT_REGISTER_TYPE_INFO tinfo; tinfo.guidMajorType = MFMediaType_Video; tinfo.guidSubtype = MFVideoFormat_WMV3; // Look for an encoder. HRESULT hr = MFTEnum( MFT_CATEGORY_VIDEO_ENCODER, 0, // Reserved NULL, // Input type to match. None. &tinfo, // WMV encoded type. NULL, // Attributes to match. (None.) &pMFTCLSIDs, // Receives a pointer to an array of CLSIDs. &cCLSID // Receives the size of the array. ); if (FAILED(hr)) { goto done; } // MFTEnum can return zero matches. if (cCLSID == 0) { hr = MF_E_TOPO_CODEC_NOT_FOUND; goto done; } else { //Create the MFT decoder hr = CoCreateInstance(pMFTCLSIDs[0], NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pMFT)); if (FAILED(hr)) { goto done; } } //Get the video encoder property store hr = pMFT->QueryInterface(IID_PPV_ARGS(&pProps)); if (FAILED(hr)) { goto done; } //Set encoding properties hr = SetEncodingProperties(MFMediaType_Video, pProps); if (FAILED(hr)) { goto done; } hr = CreateUncompressedVideoType(pType, &pInputType); if (FAILED(hr)) { goto done; } hr = pMFT->SetInputType(0, pInputType, 0); //Get the first output type //You can loop through the available output types to choose //the one that meets your target requirements hr = pMFT->GetOutputAvailableType(0, 0, &pOutputType); if (FAILED(hr)) { goto done; } hr = pType->GetUINT32(MF_MT_AVG_BITRATE, &cBitrate); if (FAILED(hr)) { goto done; } //Now set the bit rate hr = pOutputType->SetUINT32(MF_MT_AVG_BITRATE, cBitrate); if (FAILED(hr)) { goto done; } hr = AddPrivateData(pMFT, pOutputType); if (FAILED(hr)) { goto done; } //Return to the caller *ppVideoType = pOutputType; (*ppVideoType)->AddRef(); done: SafeRelease(&pProps); SafeRelease(&pOutputType); SafeRelease(&pInputType); SafeRelease(&pMFT); CoTaskMemFree(pMFTCLSIDs); return hr; }
以下代码示例创建未压缩的视频类型。
//------------------------------------------------------------------- // CreateUncompressedVideoType // Creates an uncompressed video type. // // pType: A pointer to the source's video media type. // ppVideoType: Receives a pointer to the media type. //------------------------------------------------------------------- HRESULT CreateUncompressedVideoType(IMFMediaType* pType, IMFMediaType** ppMediaType) { if (!pType) { return E_INVALIDARG; } if (!ppMediaType) { return E_POINTER; } MFRatio fps = { 0 }; MFRatio par = { 0 }; UINT32 width = 0, height = 0; UINT32 interlace = MFVideoInterlace_Unknown; UINT32 cBitrate = 0; IMFMediaType* pMediaType = NULL; GUID guidMajor = GUID_NULL; HRESULT hr = pType->GetMajorType(&guidMajor); if (FAILED(hr)) { goto done; } if (guidMajor != MFMediaType_Video ) { hr = MF_E_INVALID_FORMAT; goto done; } hr = MFGetAttributeRatio(pType, MF_MT_FRAME_RATE, (UINT32*)&fps.Numerator, (UINT32*)&fps.Denominator); if (hr == MF_E_ATTRIBUTENOTFOUND) { fps.Numerator = 30000; fps.Denominator = 1001; } hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height); if (hr == MF_E_ATTRIBUTENOTFOUND) { width = 1280; height = 720; } interlace = MFGetAttributeUINT32(pType, MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); hr = MFGetAttributeRatio(pType, MF_MT_PIXEL_ASPECT_RATIO, (UINT32*)&par.Numerator, (UINT32*)&par.Denominator); if (FAILED(hr)) { par.Numerator = par.Denominator = 1; } cBitrate = MFGetAttributeUINT32(pType, MF_MT_AVG_BITRATE, 1000000); hr = MFCreateMediaType(&pMediaType); if (FAILED(hr)) { goto done; } hr = pMediaType->SetGUID(MF_MT_MAJOR_TYPE, guidMajor); if (FAILED(hr)) { goto done; } hr = pMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); if (FAILED(hr)) { goto done; } hr = MFSetAttributeRatio(pMediaType, MF_MT_FRAME_RATE, fps.Numerator, fps.Denominator); if (FAILED(hr)) { goto done; } hr = MFSetAttributeSize(pMediaType, MF_MT_FRAME_SIZE, width, height); if (FAILED(hr)) { goto done; } hr = pMediaType->SetUINT32(MF_MT_INTERLACE_MODE, 2); if (FAILED(hr)) { goto done; } hr = pMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); if (FAILED(hr)) { goto done; } hr = pMediaType->SetUINT32(MF_MT_AVG_BITRATE, cBitrate); if (FAILED(hr)) { goto done; } // Return the pointer to the caller. *ppMediaType = pMediaType; (*ppMediaType)->AddRef(); done: SafeRelease(&pMediaType); return hr; }
以下代码示例将编解码器专用数据添加到指定的视频媒体类型。
// // AddPrivateData // Appends the private codec data to a media type. // // pMFT: The video encoder // pTypeOut: A video type from the encoder's type list. // // The function modifies pTypeOut by adding the codec data. // HRESULT AddPrivateData(IMFTransform *pMFT, IMFMediaType *pTypeOut) { HRESULT hr = S_OK; ULONG cbData = 0; BYTE *pData = NULL; IWMCodecPrivateData *pPrivData = NULL; DMO_MEDIA_TYPE mtOut = { 0 }; // Convert the type to a DMO type. hr = MFInitAMMediaTypeFromMFMediaType( pTypeOut, FORMAT_VideoInfo, (AM_MEDIA_TYPE*)&mtOut ); if (SUCCEEDED(hr)) { hr = pMFT->QueryInterface(IID_PPV_ARGS(&pPrivData)); } if (SUCCEEDED(hr)) { hr = pPrivData->SetPartialOutputType(&mtOut); } // // Get the private codec data // // First get the buffer size. if (SUCCEEDED(hr)) { hr = pPrivData->GetPrivateData(NULL, &cbData); } if (SUCCEEDED(hr)) { pData = new BYTE[cbData]; if (pData == NULL) { hr = E_OUTOFMEMORY; } } // Now get the data. if (SUCCEEDED(hr)) { hr = pPrivData->GetPrivateData(pData, &cbData); } // Add the data to the media type. if (SUCCEEDED(hr)) { hr = pTypeOut->SetBlob(MF_MT_USER_DATA, pData, cbData); } delete [] pData; MoFreeMediaType(&mtOut); SafeRelease(&pPrivData); return hr; }
创建 ASF ContentInfo 对象
ASF ContentInfo 对象是一个 WMContainer 级别组件,主要用于存储 ASF 标头对象信息。 ASF 文件接收器实现 ContentInfo 对象,以便存储将用于写入编码文件的 ASF 标头对象的信息(在属性存储中)。 为此,在启动编码会话之前,必须在 ContentInfo 对象上创建并配置以下一组信息。
调用 MFCreateASFContentInfo,以创建空的 ContentInfo 对象。
以下代码示例创建一个空的 ContentInfo 对象。
// Create an empty ContentInfo object hr = MFCreateASFContentInfo(&pContentInfo); if (FAILED(hr)) { goto done; }
调用 IMFASFContentInfo::GetEncodingConfigurationPropertyStore,以获取文件接收器的流级属性存储。 在此调用中,需要传递在创建 ASF 配置文件时为流分配的流编号。
在文件接收器的流属性存储中设置流级别编码属性。 有关详细信息,请参阅在文件接收器中设置属性中的“流编码属性”。
以下代码示例设置文件接收器的属性存储中的流级别属性。
//Get stream's encoding property hr = pContentInfo->GetEncodingConfigurationPropertyStore(wStreamID, &pContentInfoProps); if (FAILED(hr)) { goto done; } //Set the stream-level encoding properties hr = SetEncodingProperties(guidMajor, pContentInfoProps); if (FAILED(hr)) { goto done; }
以下代码示例显示 SetEncodingProperties 的实现。 此函数用于设置 CBR 和 VBR 的流级别编码属性。
//------------------------------------------------------------------- // SetEncodingProperties // Create a media source from a URL. // // guidMT: Major type of the stream, audio or video // pProps: A pointer to the property store in which // to set the required encoding properties. //------------------------------------------------------------------- HRESULT SetEncodingProperties (const GUID guidMT, IPropertyStore* pProps) { if (!pProps) { return E_INVALIDARG; } if (EncodingMode == NONE) { return MF_E_NOT_INITIALIZED; } HRESULT hr = S_OK; PROPVARIANT var; switch (EncodingMode) { case CBR: // Set VBR to false. hr = InitPropVariantFromBoolean(FALSE, &var); if (FAILED(hr)) { goto done; } hr = pProps->SetValue(MFPKEY_VBRENABLED, var); if (FAILED(hr)) { goto done; } // Set the video buffer window. if (guidMT == MFMediaType_Video) { hr = InitPropVariantFromInt32(VIDEO_WINDOW_MSEC, &var); if (FAILED(hr)) { goto done; } hr = pProps->SetValue(MFPKEY_VIDEOWINDOW, var); if (FAILED(hr)) { goto done; } } break; case VBR: //Set VBR to true. hr = InitPropVariantFromBoolean(TRUE, &var); if (FAILED(hr)) { goto done; } hr = pProps->SetValue(MFPKEY_VBRENABLED, var); if (FAILED(hr)) { goto done; } // Number of encoding passes is 1. hr = InitPropVariantFromInt32(1, &var); if (FAILED(hr)) { goto done; } hr = pProps->SetValue(MFPKEY_PASSESUSED, var); if (FAILED(hr)) { goto done; } // Set the quality level. if (guidMT == MFMediaType_Audio) { hr = InitPropVariantFromUInt32(98, &var); if (FAILED(hr)) { goto done; } hr = pProps->SetValue(MFPKEY_DESIRED_VBRQUALITY, var); if (FAILED(hr)) { goto done; } } else if (guidMT == MFMediaType_Video) { hr = InitPropVariantFromUInt32(95, &var); if (FAILED(hr)) { goto done; } hr = pProps->SetValue(MFPKEY_VBRQUALITY, var); if (FAILED(hr)) { goto done; } } break; default: hr = E_UNEXPECTED; break; } done: PropVariantClear(&var); return hr; }
调用 IMFASFContentInfo::GetEncodingConfigurationPropertyStore,以获取文件接收器的全局属性存储。 在此调用中,需要在第一个参数中传递 0。 有关详细信息,请参阅在文件接收器中设置属性中的“全局文件接收器属性”。
将 MFPKEY_ASFMEDIASINK_AUTOADJUST_BITRATE 设置为VARIANT_TRUE,以确保正确调整 ASF 多路复用器中的泄漏桶值。 有关此属性的信息,请参阅创建多路复用器对象中的“多路复用器初始化和泄漏桶设置”。
以下代码示例设置文件接收器的属性存储中的流级别属性。
//Now we need to set global properties that will be set on the media sink hr = pContentInfo->GetEncodingConfigurationPropertyStore(0, &pContentInfoProps); if (FAILED(hr)) { goto done; } //Auto adjust Bitrate var.vt = VT_BOOL; var.boolVal = VARIANT_TRUE; hr = pContentInfoProps->SetValue(MFPKEY_ASFMEDIASINK_AUTOADJUST_BITRATE, var); //Initialize with the profile hr = pContentInfo->SetProfile(pProfile); if (FAILED(hr)) { goto done; }
注意
可以在全局级别为文件接收器设置其他属性。 有关详细信息,请参阅在 ContentInfo 对象中设置属性中的“使用编码器设置配置 ContentInfo 对象”。
将使用配置的 ASF ContentInfo 为 ASF 文件接收器创建激活对象(下一部分中介绍)。
如果要创建进程外文件接收器 (MFCreateASFMediaSinkActivate),即使用激活对象,则可以使用配置的 ContentInfo 对象实例化 ASF 媒体接收器(下一部分中介绍)。 如果要创建进程内文件接收器 (MFCreateASFMediaSink),而不是按照步骤 1 中所述创建空 ContentInfo 对象,请通过在文件接收器上调用 IMFMediaSink::QueryInterface 来获得对 IMFASFContentInfo 接口的引用。 然后,必须按照本节所述配置 ContentInfo 对象。
创建 ASF 文件接收器
在本教程的此步骤中,将使用在上一步中创建的已配置的 ASF ContentInfo,通过调用 MFCreateASFMediaSinkActivate 函数,为 ASF 文件接收器创建激活对象。 有关详细信息,请参阅创建 ASF 文件接收器。
以下代码示例为文件接收器创建激活对象。
//Create the activation object for the file sink
hr = MFCreateASFMediaSinkActivate(sURL, pContentInfo, &pActivate);
if (FAILED(hr))
{
goto done;
}
生成部分编码拓扑
接下来,将通过为媒体源、所需的 Windows Media 编码器和 ASF 文件接收器创建拓扑节点来生成部分编码拓扑。 添加拓扑节点后,将连接源节点、转换节点和接收器节点。 在添加拓扑节点之前,必须通过调用 MFCreateTopology 创建空拓扑对象。
为媒体源创建源拓扑节点
在此步骤中,将为媒体源创建源拓扑节点。
若要创建此节点,需要以下引用:
- 指向在本教程“创建媒体源”部分所述步骤中创建的媒体源的指针。
- 指向媒体源的演示描述符的指针。 可以通过调用 IMFMediaSource::CreatePresentationDescriptor 来获取对媒体源的 IMFPresentationDescriptor 接口的引用。
- 指向媒体源中每个流的流描述符的指针,你已在本教程的“创建 ASF 配置文件对象”部分中所述的步骤中为其创建了目标流。
有关创建源节点和代码示例的详细信息,请参阅创建源节点。
以下代码示例通过添加源节点和所需的转换节点来创建部分拓扑。 此代码调用 Helper 函数 AddSourceNode 和 AddTransformOutputNodes。 这些函数将在本教程后面介绍。
//-------------------------------------------------------------------
// BuildPartialTopology
// Create a partial encoding topology by adding the source and the sink.
//
// pSource: A pointer to the media source to enumerate the source streams.
// pSinkActivate: A pointer to the activation object for ASF file sink.
// ppTopology: Receives a pointer to the topology.
//-------------------------------------------------------------------
HRESULT BuildPartialTopology(
IMFMediaSource *pSource,
IMFActivate* pSinkActivate,
IMFTopology** ppTopology)
{
if (!pSource || !pSinkActivate)
{
return E_INVALIDARG;
}
if (!ppTopology)
{
return E_POINTER;
}
HRESULT hr = S_OK;
IMFPresentationDescriptor* pPD = NULL;
IMFStreamDescriptor *pStreamDesc = NULL;
IMFMediaTypeHandler* pMediaTypeHandler = NULL;
IMFMediaType* pSrcType = NULL;
IMFTopology* pTopology = NULL;
IMFTopologyNode* pSrcNode = NULL;
IMFTopologyNode* pEncoderNode = NULL;
IMFTopologyNode* pOutputNode = NULL;
DWORD cElems = 0;
DWORD dwSrcStream = 0;
DWORD StreamID = 0;
GUID guidMajor = GUID_NULL;
BOOL fSelected = FALSE;
//Create the topology that represents the encoding pipeline
hr = MFCreateTopology (&pTopology);
if (FAILED(hr))
{
goto done;
}
hr = pSource->CreatePresentationDescriptor(&pPD);
if (FAILED(hr))
{
goto done;
}
hr = pPD->GetStreamDescriptorCount(&dwSrcStream);
if (FAILED(hr))
{
goto done;
}
for (DWORD iStream = 0; iStream < dwSrcStream; iStream++)
{
hr = pPD->GetStreamDescriptorByIndex(
iStream, &fSelected, &pStreamDesc);
if (FAILED(hr))
{
goto done;
}
if (!fSelected)
{
continue;
}
hr = AddSourceNode(pTopology, pSource, pPD, pStreamDesc, &pSrcNode);
if (FAILED(hr))
{
goto done;
}
hr = pStreamDesc->GetMediaTypeHandler (&pMediaTypeHandler);
if (FAILED(hr))
{
goto done;
}
hr = pStreamDesc->GetStreamIdentifier(&StreamID);
if (FAILED(hr))
{
goto done;
}
hr = pMediaTypeHandler->GetMediaTypeByIndex(0, &pSrcType);
if (FAILED(hr))
{
goto done;
}
hr = pSrcType->GetMajorType(&guidMajor);
if (FAILED(hr))
{
goto done;
}
hr = AddTransformOutputNodes(pTopology, pSinkActivate, pSrcType, &pEncoderNode);
if (FAILED(hr))
{
goto done;
}
//now we have the transform node, connect it to the source node
hr = pSrcNode->ConnectOutput(0, pEncoderNode, 0);
if (FAILED(hr))
{
goto done;
}
SafeRelease(&pStreamDesc);
SafeRelease(&pMediaTypeHandler);
SafeRelease(&pSrcType);
SafeRelease(&pEncoderNode);
SafeRelease(&pOutputNode);
guidMajor = GUID_NULL;
}
*ppTopology = pTopology;
(*ppTopology)->AddRef();
wprintf_s(L"Partial Topology Built.\n");
done:
SafeRelease(&pStreamDesc);
SafeRelease(&pMediaTypeHandler);
SafeRelease(&pSrcType);
SafeRelease(&pEncoderNode);
SafeRelease(&pOutputNode);
SafeRelease(&pTopology);
return hr;
}
以下代码示例创建源拓扑节点,并将其添加到编码拓扑中。 它使用指向先前拓扑对象的指针、枚举源流的媒体源、媒体源的表示描述符和媒体源的流描述符。 调用方接收指向源拓扑节点的指针。
// Add a source node to a topology.
HRESULT AddSourceNode(
IMFTopology *pTopology, // Topology.
IMFMediaSource *pSource, // Media source.
IMFPresentationDescriptor *pPD, // Presentation descriptor.
IMFStreamDescriptor *pSD, // Stream descriptor.
IMFTopologyNode **ppNode) // Receives the node pointer.
{
IMFTopologyNode *pNode = NULL;
// Create the node.
HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &pNode);
if (FAILED(hr))
{
goto done;
}
// Set the attributes.
hr = pNode->SetUnknown(MF_TOPONODE_SOURCE, pSource);
if (FAILED(hr))
{
goto done;
}
hr = pNode->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, pPD);
if (FAILED(hr))
{
goto done;
}
hr = pNode->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, pSD);
if (FAILED(hr))
{
goto done;
}
// Add the node to the topology.
hr = pTopology->AddNode(pNode);
if (FAILED(hr))
{
goto done;
}
// Return the pointer to the caller.
*ppNode = pNode;
(*ppNode)->AddRef();
done:
SafeRelease(&pNode);
return hr;
}
实例化所需的编码器并创建转换节点
Media Foundation 管道不会为其必须编码的流自动插入所需的 Windows Media 编码器。 应用程序必须手动添加编码器。 为此,请枚举在本教程的“创建 ASF 配置文件对象”部分中所述的步骤中创建的 ASF 配置文件中的流。 对于源中的每个流和配置文件中的相应流,实例化所需的编码器。 对于此步骤,需要一个指向文件接收器的激活对象的指针,该文件接收器是你在本教程的“创建 ASF 文件接收器”部分中所述的步骤中创建的。
有关通过激活对象创建编码器的概述,请参阅使用编码器的激活对象。
以下过程描述了实例化所需编码器所需的步骤。
通过在文件接收器上调用 IMFActivate::ActivateObject,然后通过调用 QueryInterface 从文件接收器查询 IMFASFContentInfo,获取对接收器的 ContentInfo 对象的引用。
通过调用 IMFASFContentInfo::GetProfile,获取与 ContentInfo 对象关联的 ASF 配置文件。
枚举配置文件中的流。 为此,需要流计数和对每个流的 IMFASFStreamConfig 接口的引用。
调用以下方法:
对于每个流,从 ContentInfo 对象中获取主要类型和流的编码属性。
调用以下方法:
根据流、音频或视频的类型,通过调用 MFCreateWMAEncoderActivate or MFCreateWMVEncoderActivate 来实例化编码器的激活对象。
若要调用这些函数,需要以下引用:
- 指向上一步中由 IMFASFStreamConfig::GetMediaType 检索到的流的媒体类型的指针。
- 指向由 IMFASFContentInfo::GetEncodingConfigurationPropertyStore 检索到的流的编码属性存储的指针。 通过将指针传递到属性存储,将文件接收器中设置的流属性复制到编码器 MFT 上。
更新音频流的泄漏桶参数。
MFCreateWMAEncoderActivate 设置 Windows 媒体音频编解码器的基础编码器 MFT 上的输出类型。 设置输出媒体类型后,编码器从输出媒体类型中获得平均比特率,计算缓冲区窗口范围比特率,并设置将在编码会话期间使用的泄漏桶值。 可以通过查询编码器或设置自定义值来更新文件接收器中的这些值。 若要更新值,需要以下信息集:
- 平均比特率:从在媒体类型协商期间选择的输出媒体类型上设置的 MF_MT_AUDIO_AVG_BYTES_PER_SECOND 属性中获取平均比特率。
- 缓冲区窗口:可以通过调用 IWMCodecLeakyBucket::GetBufferSizeBits 来检索。
- 初始缓冲区大小:设置为 0。
创建一个 DWORD 数组,并在音频流接收器的 MFPKEY_ASFSTREAMSINK_CORRECTED_LEAKYBUCKET 属性中设置值。 如果未提供更新后的值,“媒体会话”会对其进行适当设置。
有关详细信息,请参阅泄漏桶缓冲区模型。
在步骤 5 中创建的激活对象必须作为转换拓扑节点添加到拓扑中。 有关详细信息和代码示例,请参阅创建转换节点中的“从激活对象创建转换节点”。
以下代码示例创建并添加所需的编码器激活。 它获取指向先前创建的拓扑对象、文件接收器的激活对象和源流的媒体类型的指针。 它还调用 AddOutputNode(请参阅下一个代码示例),创建接收节点并将其添加到编码拓扑中。 调用方接收指向源拓扑节点的指针。
//-------------------------------------------------------------------
// AddTransformOutputNodes
// Creates and adds the sink node to the encoding topology.
// Creates and adds the required encoder activates.
// pTopology: A pointer to the topology.
// pSinkActivate: A pointer to the file sink's activation object.
// pSourceType: A pointer to the source stream's media type.
// ppNode: Receives a pointer to the topology node.
//-------------------------------------------------------------------
HRESULT AddTransformOutputNodes(
IMFTopology* pTopology,
IMFActivate* pSinkActivate,
IMFMediaType* pSourceType,
IMFTopologyNode **ppNode // Receives the node pointer.
)
{
if (!pTopology || !pSinkActivate || !pSourceType)
{
return E_INVALIDARG;
}
IMFTopologyNode* pEncNode = NULL;
IMFTopologyNode* pOutputNode = NULL;
IMFASFContentInfo* pContentInfo = NULL;
IMFASFProfile* pProfile = NULL;
IMFASFStreamConfig* pStream = NULL;
IMFMediaType* pMediaType = NULL;
IPropertyStore* pProps = NULL;
IMFActivate *pEncoderActivate = NULL;
IMFMediaSink *pSink = NULL;
GUID guidMT = GUID_NULL;
GUID guidMajor = GUID_NULL;
DWORD cStreams = 0;
WORD wStreamNumber = 0;
HRESULT hr = S_OK;
hr = pSourceType->GetMajorType(&guidMajor);
if (FAILED(hr))
{
goto done;
}
// Create the node.
hr = MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &pEncNode);
if (FAILED(hr))
{
goto done;
}
//Activate the sink
hr = pSinkActivate->ActivateObject(__uuidof(IMFMediaSink), (void**)&pSink);
if (FAILED(hr))
{
goto done;
}
//find the media type in the sink
//Get content info from the sink
hr = pSink->QueryInterface(__uuidof(IMFASFContentInfo), (void**)&pContentInfo);
if (FAILED(hr))
{
goto done;
}
hr = pContentInfo->GetProfile(&pProfile);
if (FAILED(hr))
{
goto done;
}
hr = pProfile->GetStreamCount(&cStreams);
if (FAILED(hr))
{
goto done;
}
for(DWORD index = 0; index < cStreams ; index++)
{
hr = pProfile->GetStream(index, &wStreamNumber, &pStream);
if (FAILED(hr))
{
goto done;
}
hr = pStream->GetMediaType(&pMediaType);
if (FAILED(hr))
{
goto done;
}
hr = pMediaType->GetMajorType(&guidMT);
if (FAILED(hr))
{
goto done;
}
if (guidMT!=guidMajor)
{
SafeRelease(&pStream);
SafeRelease(&pMediaType);
guidMT = GUID_NULL;
continue;
}
//We need to activate the encoder
hr = pContentInfo->GetEncodingConfigurationPropertyStore(wStreamNumber, &pProps);
if (FAILED(hr))
{
goto done;
}
if (guidMT == MFMediaType_Audio)
{
hr = MFCreateWMAEncoderActivate(pMediaType, pProps, &pEncoderActivate);
if (FAILED(hr))
{
goto done;
}
wprintf_s(L"Audio Encoder created. Stream Number: %d .\n", wStreamNumber);
break;
}
if (guidMT == MFMediaType_Video)
{
hr = MFCreateWMVEncoderActivate(pMediaType, pProps, &pEncoderActivate);
if (FAILED(hr))
{
goto done;
}
wprintf_s(L"Video Encoder created. Stream Number: %d .\n", wStreamNumber);
break;
}
}
// Set the object pointer.
hr = pEncNode->SetObject(pEncoderActivate);
if (FAILED(hr))
{
goto done;
}
// Add the node to the topology.
hr = pTopology->AddNode(pEncNode);
if (FAILED(hr))
{
goto done;
}
//Add the output node to this node.
hr = AddOutputNode(pTopology, pSinkActivate, wStreamNumber, &pOutputNode);
if (FAILED(hr))
{
goto done;
}
//now we have the output node, connect it to the transform node
hr = pEncNode->ConnectOutput(0, pOutputNode, 0);
if (FAILED(hr))
{
goto done;
}
// Return the pointer to the caller.
*ppNode = pEncNode;
(*ppNode)->AddRef();
done:
SafeRelease(&pEncNode);
SafeRelease(&pOutputNode);
SafeRelease(&pEncoderActivate);
SafeRelease(&pMediaType);
SafeRelease(&pProps);
SafeRelease(&pStream);
SafeRelease(&pProfile);
SafeRelease(&pContentInfo);
SafeRelease(&pSink);
return hr;
}
为文件接收器创建输出拓扑节点
在这一步中,将为 ASF 文件接收器创建输出拓扑节点。
若要创建此节点,需要以下引用:
- 指向你在本教程的“创建 ASF 文件接收器”部分中所述的步骤中创建的激活对象的指针。
- 用于标识添加到文件接收器的流接收器的流编号。 流编号与流创建过程中设置的流标识相匹配。
有关创建输出节点和代码示例的详细信息,请参阅创建输出节点中的“从激活对象创建输出节点”。
如果未将激活对象用于文件接收器,则必须枚举 ASF 文件接收器中的流接收器,并将每个流接收器设置为拓扑中的输出节点。 有关流接收器枚举的信息,请参阅将流信息添加到 ASF 文件接收器中的“枚举流接收器”。
以下代码示例创建接收器节点并将其添加到编码拓扑中。 它获取指向先前创建的拓扑对象、文件接收器的激活对象和流的标识号的指针。 调用方接收指向源拓扑节点的指针。
// Add an output node to a topology.
HRESULT AddOutputNode(
IMFTopology *pTopology, // Topology.
IMFActivate *pActivate, // Media sink activation object.
DWORD dwId, // Identifier of the stream sink.
IMFTopologyNode **ppNode) // Receives the node pointer.
{
IMFTopologyNode *pNode = NULL;
// Create the node.
HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &pNode);
if (FAILED(hr))
{
goto done;
}
// Set the object pointer.
hr = pNode->SetObject(pActivate);
if (FAILED(hr))
{
goto done;
}
// Set the stream sink ID attribute.
hr = pNode->SetUINT32(MF_TOPONODE_STREAMID, dwId);
if (FAILED(hr))
{
goto done;
}
hr = pNode->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE);
if (FAILED(hr))
{
goto done;
}
// Add the node to the topology.
hr = pTopology->AddNode(pNode);
if (FAILED(hr))
{
goto done;
}
// Return the pointer to the caller.
*ppNode = pNode;
(*ppNode)->AddRef();
done:
SafeRelease(&pNode);
return hr;
}
以下代码示例枚举给定媒体接收器的流接收器。
//-------------------------------------------------------------------
// EnumerateStreamSinks
// Enumerates the stream sinks within the specified media sink.
//
// pSink: A pointer to the media sink.
//-------------------------------------------------------------------
HRESULT EnumerateStreamSinks (IMFMediaSink* pSink)
{
if (!pSink)
{
return E_INVALIDARG;
}
IMFStreamSink* pStreamSink = NULL;
DWORD cStreamSinks = 0;
HRESULT hr = pSink->GetStreamSinkCount(&cStreamSinks);
if (FAILED(hr))
{
goto done;
}
for(DWORD index = 0; index < cStreamSinks; index++)
{
hr = pSink->GetStreamSinkByIndex (index, &pStreamSink);
if (FAILED(hr))
{
goto done;
}
//Use the stream sink
//Not shown.
}
done:
SafeRelease(&pStreamSink);
return hr;
}
连接源、转换和接收器节点
在本步骤中,将把源节点连接到引用你在本教程的“实例化所需编码器和创建转换节点”部分中所述的步骤中创建的编码激活的转换节点。 转换节点将连接到包含文件接收器的激活对象的输出节点。
处理编码会话
在此步骤中,你将执行以下步骤:
调用 MFCreateMediaSession,以创建编码会话。
调用 IMFMediaSession::SetTopology,在会话上设置编码拓扑。 如果调用完成,媒体会话将评估拓扑节点,并插入其他转换对象,例如将指定压缩源转换为未压缩样本的解码器,以作为编码器的输入。
调用 IMFMediaSession::GetEvent,以请求媒体会话引发的事件。
在事件循环中,将根据媒体会话引发的事件启动和关闭编码会话。 IMFMediaSession::SetTopology 调用导致在媒体会话中引发 MESessionTopologyStatus 事件,并设置 MF_TOPOSTATUS_READY 标志。 所有事件都是异步生成的,应用程序可以同步或异步检索这些事件。 因为本教程中的应用程序是一个控制台应用程序,并且阻止用户界面线程不是一个问题,所以我们将同步地从媒体会话获取事件。
若要异步获取事件,应用程序必须实现 IMFAsyncCallback 接口。 有关此界面的更多信息和示例实现,请参阅如何使用媒体基础播放媒体文件中的“处理会话事件”。
在获取媒体会话事件的事件循环中,等待 IMFMediaSession::SetTopology 完成并解析拓扑时引发的 MESessionTopologyStatus 事件。 获取 MESessionTopologyStatus 事件后,通过调用 IMFMediaSession::Start 启动编码会话。 当所有编码操作完成时,媒体会话会生成 MEEndOfPresentation 事件。 必须针对 VBR 编码处理此事件,本教程的下一节“更新 VBR 编码的文件接收器上的编码属性”中对此事件进行了讨论。
媒体会话生成 ASF 标头对象,并在编码会话完成后结束该文件,然后引发 MESessionClosed 事件。 必须通过对媒体会话执行正确的关闭操作来处理此事件。 若要开始关闭操作,请调用 IMFMediaSession::Shutdown。 关闭编码会话后,无法在同一媒体会话实例上为编码设置其他拓扑。 若要对另一个文件进行编码,必须关闭并释放当前媒体会话,并且必须在新创建的媒体会话上设置新拓扑。 以下代码示例创建媒体会话、设置编码拓扑并处理媒体会话事件。
以下代码示例创建媒体会话,设置编码拓扑,并通过处理媒体会话中的事件来控制编码会话。
//-------------------------------------------------------------------
// Encode
// Controls the encoding session and handles events from the media session.
//
// pTopology: A pointer to the encoding topology.
//-------------------------------------------------------------------
HRESULT Encode(IMFTopology *pTopology)
{
if (!pTopology)
{
return E_INVALIDARG;
}
IMFMediaSession *pSession = NULL;
IMFMediaEvent* pEvent = NULL;
IMFTopology* pFullTopology = NULL;
IUnknown* pTopoUnk = NULL;
MediaEventType meType = MEUnknown; // Event type
HRESULT hr = S_OK;
HRESULT hrStatus = S_OK; // Event status
MF_TOPOSTATUS TopoStatus = MF_TOPOSTATUS_INVALID; // Used with MESessionTopologyStatus event.
hr = MFCreateMediaSession(NULL, &pSession);
if (FAILED(hr))
{
goto done;
}
hr = pSession->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, pTopology);
if (FAILED(hr))
{
goto done;
}
//Get media session events synchronously
while (1)
{
hr = pSession->GetEvent(0, &pEvent);
if (FAILED(hr))
{
goto done;
}
hr = pEvent->GetType(&meType);
if (FAILED(hr))
{
goto done;
}
hr = pEvent->GetStatus(&hrStatus);
if (FAILED(hr))
{
goto done;
}
if (FAILED(hrStatus))
{
hr = hrStatus;
goto done;
}
switch(meType)
{
case MESessionTopologyStatus:
{
// Get the status code.
MF_TOPOSTATUS status = (MF_TOPOSTATUS)MFGetAttributeUINT32(
pEvent, MF_EVENT_TOPOLOGY_STATUS, MF_TOPOSTATUS_INVALID);
if (status == MF_TOPOSTATUS_READY)
{
PROPVARIANT var;
PropVariantInit(&var);
wprintf_s(L"Topology resolved and set on the media session.\n");
hr = pSession->Start(NULL, &var);
if (FAILED(hr))
{
goto done;
}
}
if (status == MF_TOPOSTATUS_STARTED_SOURCE)
{
wprintf_s(L"Encoding started.\n");
break;
}
if (status == MF_TOPOSTATUS_ENDED)
{
wprintf_s(L"Encoding complete.\n");
hr = pSession->Close();
if (FAILED(hr))
{
goto done;
}
break;
}
}
break;
case MESessionEnded:
wprintf_s(L"Encoding complete.\n");
hr = pSession->Close();
if (FAILED(hr))
{
goto done;
}
break;
case MEEndOfPresentation:
{
if (EncodingMode == VBR)
{
hr = pSession->GetFullTopology(MFSESSION_GETFULLTOPOLOGY_CURRENT, 0, &pFullTopology);
if (FAILED(hr))
{
goto done;
}
hr = PostEncodingUpdate(pFullTopology);
if (FAILED(hr))
{
goto done;
}
wprintf_s(L"Updated sinks for VBR. \n");
}
}
break;
case MESessionClosed:
wprintf_s(L"Encoding session closed.\n");
hr = pSession->Shutdown();
goto done;
}
if (FAILED(hr))
{
goto done;
}
SafeRelease(&pEvent);
}
done:
SafeRelease(&pEvent);
SafeRelease(&pSession);
SafeRelease(&pFullTopology);
SafeRelease(&pTopoUnk);
return hr;
}
更新文件接收器中的编码属性
某些编码属性(如编码比特率和准确的泄漏桶值)在编码完成之前是未知的,尤其是对于 VBR 编码。 若要获取正确的值,应用程序必须等待 MEEndOfPresentation 事件,该事件指示编码会话已完成。 泄漏桶值必须在接收器中更新,以便 ASF 标头对象能够反映准确的值。
以下过程描述了遍历编码拓扑中的节点以获取文件接收器节点并设置所需的泄漏桶属性所需的步骤。
更新 ASF 文件接收器上的编码后属性值
- 调用 IMFTopology::GetOutputNodeCollection,从编码拓扑获取输出节点集合。
- 对于每个节点,通过调用 IMFTopologyNode::GetObject,获取指向节点中的流接收器的指针。 查询 IMFTopologyNode::GetObject 返回的 IUnknown 指针上的 IMFStreamSink 接口。
- 对于每个流接收器,通过调用 IMFTopologyNode::GetInput 获取下游节点(编码器)。
- 查询节点,以从编码器节点获取 IMFTransform 指针。
- 向编码器查询 IPropertyStore 指针,以便从编码器获取编码属性存储。
- 在流接收器中查询 IPropertyStore 指针,以获取流接收器的属性存储。
- 调用 IPropertyStore::GetValue 从编码器的属性存储中获取所需的属性值,并通过调用 IPropertyStore::SetValue 将它们复制到流接收器的属性存储。
下表显示了必须在视频流的流接收器上设置的编码后属性值。
编码类型 | 属性名称 (GetValue) | 属性名称 (SetValue) |
---|---|---|
恒定比特率编码 | MFPKEY_BAVG MFPKEY_RAVG |
MFPKEY_STAT_BAVG MFPKEY_STAT_RAVG |
基于质量的可变比特率编码 | MFPKEY_BAVG MFPKEY_RAVG MFPKEY_BMAX MFPKEY_RMAX |
MFPKEY_STAT_BAVG MFPKEY_STAT_RAVG MFPKEY_STAT_BMAX MFPKEY_STAT_RMAX |
以下代码示例设置编码后属性值。
//-------------------------------------------------------------------
// PostEncodingUpdate
// Updates the file sink with encoding properties set on the encoder
// during the encoding session.
//1. Get the output nodes
//2. For each node, get the downstream node
//3. For the downstream node, get the MFT
//4. Get the property store
//5. Get the required values
//6. Set them on the stream sink
//
// pTopology: A pointer to the full topology retrieved from the media session.
//-------------------------------------------------------------------
HRESULT PostEncodingUpdate(IMFTopology *pTopology)
{
if (!pTopology)
{
return E_INVALIDARG;
}
HRESULT hr = S_OK;
IMFCollection* pOutputColl = NULL;
IUnknown* pNodeUnk = NULL;
IMFMediaType* pType = NULL;
IMFTopologyNode* pNode = NULL;
IUnknown* pSinkUnk = NULL;
IMFStreamSink* pStreamSink = NULL;
IMFTopologyNode* pEncoderNode = NULL;
IUnknown* pEncoderUnk = NULL;
IMFTransform* pEncoder = NULL;
IPropertyStore* pStreamSinkProps = NULL;
IPropertyStore* pEncoderProps = NULL;
GUID guidMajorType = GUID_NULL;
PROPVARIANT var;
PropVariantInit( &var );
DWORD cElements = 0;
hr = pTopology->GetOutputNodeCollection( &pOutputColl);
if (FAILED(hr))
{
goto done;
}
hr = pOutputColl->GetElementCount(&cElements);
if (FAILED(hr))
{
goto done;
}
for(DWORD index = 0; index < cElements; index++)
{
hr = pOutputColl->GetElement(index, &pNodeUnk);
if (FAILED(hr))
{
goto done;
}
hr = pNodeUnk->QueryInterface(IID_IMFTopologyNode, (void**)&pNode);
if (FAILED(hr))
{
goto done;
}
hr = pNode->GetInputPrefType(0, &pType);
if (FAILED(hr))
{
goto done;
}
hr = pType->GetMajorType( &guidMajorType );
if (FAILED(hr))
{
goto done;
}
hr = pNode->GetObject(&pSinkUnk);
if (FAILED(hr))
{
goto done;
}
hr = pSinkUnk->QueryInterface(IID_IMFStreamSink, (void**)&pStreamSink);
if (FAILED(hr))
{
goto done;
}
hr = pNode->GetInput( 0, &pEncoderNode, NULL );
if (FAILED(hr))
{
goto done;
}
hr = pEncoderNode->GetObject(&pEncoderUnk);
if (FAILED(hr))
{
goto done;
}
hr = pEncoderUnk->QueryInterface(IID_IMFTransform, (void**)&pEncoder);
if (FAILED(hr))
{
goto done;
}
hr = pStreamSink->QueryInterface(IID_IPropertyStore, (void**)&pStreamSinkProps);
if (FAILED(hr))
{
goto done;
}
hr = pEncoder->QueryInterface(IID_IPropertyStore, (void**)&pEncoderProps);
if (FAILED(hr))
{
goto done;
}
if( guidMajorType == MFMediaType_Video )
{
hr = pEncoderProps->GetValue( MFPKEY_BAVG, &var );
if (FAILED(hr))
{
goto done;
}
hr = pStreamSinkProps->SetValue( MFPKEY_STAT_BAVG, var );
if (FAILED(hr))
{
goto done;
}
PropVariantClear( &var );
hr = pEncoderProps->GetValue( MFPKEY_RAVG, &var );
if (FAILED(hr))
{
goto done;
}
hr = pStreamSinkProps->SetValue( MFPKEY_STAT_RAVG, var);
if (FAILED(hr))
{
goto done;
}
PropVariantClear( &var );
hr = pEncoderProps->GetValue( MFPKEY_BMAX, &var );
if (FAILED(hr))
{
goto done;
}
hr = pStreamSinkProps->SetValue( MFPKEY_STAT_BMAX, var);
if (FAILED(hr))
{
goto done;
}
PropVariantClear( &var );
hr = pEncoderProps->GetValue( MFPKEY_RMAX, &var );
if (FAILED(hr))
{
goto done;
}
hr = pStreamSinkProps->SetValue( MFPKEY_STAT_RMAX, var);
if (FAILED(hr))
{
goto done;
}
}
else if( guidMajorType == MFMediaType_Audio )
{
hr = pEncoderProps->GetValue( MFPKEY_STAT_BAVG, &var );
if (FAILED(hr))
{
goto done;
}
hr = pStreamSinkProps->SetValue( MFPKEY_STAT_BAVG, var );
if (FAILED(hr))
{
goto done;
}
PropVariantClear( &var );
hr = pEncoderProps->GetValue( MFPKEY_STAT_RAVG, &var );
if (FAILED(hr))
{
goto done;
}
hr = pStreamSinkProps->SetValue( MFPKEY_STAT_RAVG, var );
if (FAILED(hr))
{
goto done;
}
PropVariantClear( &var );
hr = pEncoderProps->GetValue( MFPKEY_STAT_BMAX, &var);
if (FAILED(hr))
{
goto done;
}
hr = pStreamSinkProps->SetValue( MFPKEY_STAT_BMAX, var);
if (FAILED(hr))
{
goto done;
}
PropVariantClear( &var );
hr = pEncoderProps->GetValue( MFPKEY_STAT_RMAX, &var );
if (FAILED(hr))
{
goto done;
}
hr = pStreamSinkProps->SetValue( MFPKEY_STAT_RMAX, var );
if (FAILED(hr))
{
goto done;
}
PropVariantClear( &var );
hr = pEncoderProps->GetValue( MFPKEY_WMAENC_AVGBYTESPERSEC, &var );
if (FAILED(hr))
{
goto done;
}
hr = pStreamSinkProps->SetValue( MFPKEY_WMAENC_AVGBYTESPERSEC, var );
if (FAILED(hr))
{
goto done;
}
}
PropVariantClear( &var );
}
done:
SafeRelease (&pOutputColl);
SafeRelease (&pNodeUnk);
SafeRelease (&pType);
SafeRelease (&pNode);
SafeRelease (&pSinkUnk);
SafeRelease (&pStreamSink);
SafeRelease (&pEncoderNode);
SafeRelease (&pEncoderUnk);
SafeRelease (&pEncoder);
SafeRelease (&pStreamSinkProps);
SafeRelease (&pEncoderProps);
return hr;
}
实现 Main
下面的代码示例显示了控制台应用程序的 main 函数。
int wmain(int argc, wchar_t* argv[])
{
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
if (argc != 4)
{
wprintf_s(L"Usage: %s inputaudio.mp3, %s output.wm*, %Encoding Type: CBR, VBR\n");
return 0;
}
HRESULT hr = S_OK;
IMFMediaSource* pSource = NULL;
IMFTopology* pTopology = NULL;
IMFActivate* pFileSinkActivate = NULL;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr))
{
goto done;
}
//Set the requested encoding mode
if (wcscmp(argv[3], L"CBR")==0)
{
EncodingMode = CBR;
}
else if (wcscmp(argv[3], L"VBR")==0)
{
EncodingMode = VBR;
}
else
{
EncodingMode = CBR;
}
// Start up Media Foundation platform.
hr = MFStartup(MF_VERSION);
if (FAILED(hr))
{
goto done;
}
//Create the media source
hr = CreateMediaSource(argv[1], &pSource);
if (FAILED(hr))
{
goto done;
}
//Create the file sink activate
hr = CreateMediaSink(argv[2], pSource, &pFileSinkActivate);
if (FAILED(hr))
{
goto done;
}
//Build the encoding topology.
hr = BuildPartialTopology(pSource, pFileSinkActivate, &pTopology);
if (FAILED(hr))
{
goto done;
}
//Instantiate the media session and start encoding
hr = Encode(pTopology);
if (FAILED(hr))
{
goto done;
}
done:
// Clean up.
SafeRelease(&pSource);
SafeRelease(&pTopology);
SafeRelease(&pFileSinkActivate);
MFShutdown();
CoUninitialize();
if (FAILED(hr))
{
wprintf_s(L"Could not create the output file due to 0x%X\n", hr);
}
return 0;
}
测试输出文件
以下列表描述用于测试编码文件的检查列表。 这些值可以在文件属性对话框中选中,可以右键单击编码的文件并从上下文菜单中选择属性来显示该对话框。
- 编码文件的路径准确。
- 文件的大小大于 0 KB,并且播放持续时间与源文件的持续时间匹配。
- 对于视频流,检查帧宽度和高度、帧速率。 这些值应与你在“创建 ASF 配置文件对象”部分中所述的步骤中创建的 ASF 配置中指定的值相匹配。
- 对于音频流,比特率必须接近你在目标媒体类型上指定的值。
- 在 Windows Media Player 中打开文件,并检查编码质量。
- 在 ASFViewer 中打开 ASF 文件,以查看 ASF 文件的结构。 此工具可从此 Microsoft 网站下载。
常见错误代码和调试提示
以下列表介绍了可能收到的常见错误代码以及调试提示。
对 IMFSourceResolver::CreateObjectFromURL 的调用会停止应用程序。
通过调用 MFStartup,确保已初始化媒体基础平台。 此函数在内部设置异步平台,该平台由所有启动异步操作的方法使用,如 IMFSourceResolver::CreateObjectFromURL。
IMFSourceResolver::CreateObjectFromURL 返回 HRESULT 0x80070002“系统找不到指定的文件。
请确保用户在第一个参数中指定的输入文件名存在。
HRESULT 0x80070020“进程无法访问该文件,因为它正在被另一个进程使用。 "
请确保输入和输出文件当前未被系统中的其他资源使用。
对 IMFTransform 方法的调用返回 MF_E_INVALIDMEDIATYPE。
确保下列条件为 true:
- 指定的输入类型或输出类型与编码器支持的媒体类型兼容。
- 指定的媒体类型是完整的。 若要使媒体类型完整,请参阅本教程的“创建压缩音频媒体类型”和“创建压缩视频媒体类型”部分中所需的属性。
- 确保已为要添加编解码器专用数据的部分媒体类型设置了目标比特率。
媒体会话在事件状态下返回 MF_E_UNSUPPORTED_D3D_TYPE。
当源的媒体类型指示 Windows Media 视频编码器不支持的混合交错模式时,将返回此错误。 如果压缩的视频媒体类型设置为使用渐进模式,则管道必须使用反交错转换。 由于管道无法找到匹配项(由此错误代码指示),因此必须在解码器和编码器节点之间手动插入反交错器(转码视频处理器)。
媒体会话在事件状态下返回 E_INVALIDARG。
当源的媒体类型属性与 Windows Media 编码器上设置的输出媒体类型的属性不兼容时,将返回此错误。
IWMCodecPrivateData::GetPrivateData 返回 HRESULT 0x80040203 “尝试评估查询字符串时出现语法错误”
请确保在编码器 MFT 上设置了输入类型。
相关主题