音频转码 via Media Foundation
转码(transcoding)其实就是把音频从一种编码转换成另一种编码的过程,如 MP3 → WMA。基本流程如下图:
Media Foundation 简介
Media Foundation (简称 MF)是微软在 Windows Vista上 推出的新一代多媒体应用库,目的是提供 Windows 平台一个统一的多媒体影音解决方案,开发者可以通过 MF 播放视频或声音文件、进行多媒体文件格式转码,或者将一连串图片编码为视频等等。
MF 是 DirectShow 为主的旧式多媒体应用程序接口的替代者与继承者,在微软的计划下将逐步汰换 DirectShow 技术。MF 要求 Windows Vista 或更高版本,不支持较早期的 Windows 版本,特别是 Windows XP。
MF 长于高质量的音频和视频播放,高清内容(如 HDTV,高清电视)和数字版权管理(DRM)访问控制。MF 在不同的 Windows 版本上能力不同,如 Windows 7 上就添加了 h.264 编码支持。Windows 8 上则提供数种更高质量的设置。
MF 提供了两种编程模型,第一种是以 Media Session 为主的 Media pipeline 模型,但是该模型太过复杂,且曝露过多底层细节,故微软于 Windows 7 上推出第二种编程模型,内含 SourceReader、Transcode API 、SinkWriter 及 MFPlay 等高度封装模块,大大简化了 MF 的使用难度。
# 本文使用了第二种(简单的)编程模型。
Media Foundation 转码音频
Transcoding 流程图
Transcoding 代码
以下是整个 MF 转码过程的概要代码,略去 TopoBuilder 类的实现:
HRESULT CTranscodeApi::transcodeFile(PCWSTR pszInput, PCWSTR pszOutput)
{
HRESULT hr = S_OK;
CComPtr<IMFTopology> pTopology;
hr = MFCreateMediaSession(NULL, &m_pSession);
RETURN_IF_FAILED(hr);
hr = m_pSession->BeginGetEvent((IMFAsyncCallback*)this, NULL);
RETURN_IF_FAILED(hr);
hr = _setTranscodeProfile(pszOutput);
RETURN_IF_FAILED(hr);
hr = m_topoBuilder.createTranscodeTopology(pszInput, pszOutput);
RETURN_IF_FAILED(hr);
pTopology = m_topoBuilder.getTopology();
hr = m_pSession->SetTopology(0, pTopology);
if (SUCCEEDED(hr)) {
PROPVARIANT varStart;
PropVariantClear(&varStart);
hr = m_pSession->Start(&GUID_NULL, &varStart);
}
return hr;
}
setTranscodeProfile 函数
Windows7以上系统默认支持 AAC 和 WMA encoder。
HRESULT CTranscodeApi::_setTranscodeProfile(LPCTSTR pszOutputFile)
{
HRESULT hr = E_NOTIMPL;
LPCTSTR ext = pszOutputFile + _tcslen(pszOutputFile) - 3;
if (_tcsicmp(ext, _T("aac")) == 0) {
hr = m_topoBuilder.SetTranscodeProfile(
MFAudioFormat_AAC,
GUID_NULL,
MFTranscodeContainerType_MPEG4);
}
else if (_tcsicmp(ext, _T("wma")) == 0) {
hr = m_topoBuilder.SetTranscodeProfile(
MFAudioFormat_WMAudioV9,
GUID_NULL,
MFTranscodeContainerType_ASF);
}
return hr;
}
HRESULT CTranscodeTopoBuilder::setTranscodeProfile(
const GUID& audioFormat, // target audio format
const GUID& videoFormat, // target video format
const GUID& containerType) // target file container
{
HRESULT hr = S_OK;
hr = MFCreateTranscodeProfile(&m_pTranscodeProfile);
RETURN_IF_FAILED(hr);
hr = _setAudioAttributes(audioFormat);
RETURN_IF_FAILED(hr);
hr = _setVideoAttributes(videoFormat);
RETURN_IF_FAILED(hr);
hr = _setContainerAttributes(containerType);
RETURN_IF_FAILED(hr);
return hr;
}
CTranscodeTopoBuilder::_setAudioAttributes 函数
枚举所有支持的输出媒体类型并选择第一个。
HRESULT CTranscodeTopoBuilder::_setAudioAttributes(const GUID& audioFormat)
{
HRESULT hr = S_OK;
CComPtr<IMFCollection> pAudioTypeCollection;
CComPtr<IMFAttributes> pAudioAttrs;
// enumerate all of the audio encoders that match the specified
// parameters and find all audio types that can be generated.
DWORD dwFlags = (MFT_ENUM_FLAG_ALL & (~MFT_ENUM_FLAG_FIELDOFUSE))
| MFT_ENUM_FLAG_SORTANDFILTER;
hr = MFTranscodeGetAudioOutputAvailableTypes(
audioFormat, // specify the requested audio format
dwFlags, // get all MFTs except for the FOU, and sort
NULL, // no custom attributes
&pAudioTypeCollection); // store result in specified collection
RETURN_IF_FAILED(hr);
// get the first element from the collection of media types,
// copy all the information of the first type into a new attribute
// collection, and return the attribute collection
hr = getTypeAttributesFromTypeCollection(pAudioTypeCollection, 0, pAudioAttrs);
RETURN_IF_FAILED(hr);
hr = m_pTranscodeProfile->SetAudioAttributes(pAudioAttrs);
RETURN_IF_FAILED(hr);
return hr;
}
CTranscodeTopoBuilder::_setContainerAttributes 函数
设置媒体容器的类型,如 MP3,AVI 等。
HRESULT CTranscodeTopoBuilder::_setContainerAttributes(const GUID& containerType)
{
HRESULT hr = S_OK;
CComPtr<IMFAttributes> pContainerAttributes;
RETURN_IF_NULL(m_pTranscodeProfile);
hr = MFCreateAttributes(&pContainerAttributes, 1);
RETURN_IF_FAILED(hr);
// store an attribute that indicates that we want to write to an ASF file
hr = pContainerAttributes->SetGUID(
MF_TRANSCODE_CONTAINERTYPE, // attribute ID GUID - container type
containerType); // generate the specified container
RETURN_IF_FAILED(hr);
// store the container attributes in the transcode profile
hr = m_pTranscodeProfile->SetContainerAttributes(pContainerAttributes);
RETURN_IF_FAILED(hr);
return hr;
}
CTranscodeTopoBuilder::createTranscodeTopology 函数
MF 提供了 transcoding 专用的 topology。
HRESULT CTranscodeTopoBuilder::createTranscodeTopology(PCWSTR pszInput, PCWSTR pszOutput)
{
HRESULT hr = S_OK;
// standard media source creation
hr = createMediaSource(pszInput, m_pSource);
RETURN_IF_FAILED(hr);
// create the actual transcode topology based on the transcode profile
hr = MFCreateTranscodeTopology(
m_pSource, // the source of the content to transcode
pszOutput, // output filename
m_pTranscodeProfile, // transcode profile to use
&m_pTopology); // resulting topology
RETURN_IF_FAILED(hr);
return hr;
}
IMFAsyncCallback::Invoke 函数
Media Session 的 所有事件 都会回调这里。当收到 MESessionEnded 事件时就说明转码结束了。
注意:转码未完时要重新 BeginGetEvent,不然就傻傻等不到下一个事件了。
HRESULT CTranscodeApi::Invoke(IMFAsyncResult* pAsyncResult)
{
CComPtr<IMFMediaEvent> pEvent;
HRESULT hr = S_OK;
MediaEventType eventType;
// Get the event from the event queue.
hr = m_pSession->EndGetEvent(pAsyncResult, &pEvent);
RETURN_IF_FAILED(hr);
// Get the event type.
hr = pEvent->GetType(&eventType);
RETURN_IF_FAILED(hr);
if (eventType == MESessionEnded) {
hr = m_pSession->Close();
}
else if (eventType == MESessionClosed) {
SetEvent(m_closeCompleteEvent);
}
else {
hr = m_pSession->BeginGetEvent(this, NULL);
}
RETURN_IF_FAILED(hr);
return S_OK;
}
其他框架的转码
– EOF –