音频转码 via DirectShow

转码(transcoding)其实就是把音频从一种编码转换成另一种编码的过程,如 MP3 → WMA。基本流程如下图:
transcoding

DirectShow 简介

DirectShow(有时缩写为 DS 或 DShow),开发代号 Quartz,是微软在 ActiveMovie 和 Video for Windows 的基础上推出的新一代基于 COM 的流媒体处理的开发包,与 DirectX 开发包一起发布。DShow 使用一种叫 Filter Graph 的模型来管理整个数据流的处理过程,有了 DShow,我们可以很方便地从支持 WDM 驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。这样使在多媒体数据库管理系统(MDBMS)中多媒体数据的存取变得更加方便。它广泛地支持各种媒体格式,包括 asf、mpeg、avi、dv、mp3、wav 等,为多媒体流的捕捉和回放提供了强有力的支持。

DirectShow 转码

Transcoding 流程图

DShow transcoding audio

Transcoding 代码

以下是整个转码过程的概要代码,略去各个函数的具体实现和资源释放:

hr = CoInitialize(NULL);
hr = _initGraph();

hr = m_pGraph->AddSourceFilter(szSrcFile, NULL, &pSource);
hr = _addEncoder(szTargetFile, &pEncoder);
hr = _addFileWriter(szTargetFile, &pFileWriter);
hr = _renderStream(pSource, pEncoder, pFileWriter);

hr = m_pControl->Run();
hr = handleDShowEvent(m_pEvent, NULL);

hr = m_pControl->Stop();
CoUninitialize();

_initGraph 函数

创建 filter graph 并获取相关的接口:

  • IMediaControl:通过 filter graph 控制数据流,包括运行,暂停和停止 filter graph 的方法
  • IMediaEventEx:支持来自 filter graph 和 filters 的事件通知到应用程序,允许注册窗口以接收消息。
HRESULT _initGraph()
{
    HRESULT hr = S_OK;
    _tearDownGraph();

    // Create the Filter Graph Manager.
    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGraph);
    RETURN_IF_FAILED(hr);

    // Query for graph interfaces.
    hr = m_pGraph->QueryInterface(IID_IMediaControl, (void**)&m_pControl);
    RETURN_IF_FAILED(hr);

    hr = m_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&m_pEvent);
    RETURN_IF_FAILED(hr);

    return hr;
}

_addEncoder 函数

这里只处理了 mp3 和 wma 格式,其他的音频编码需要有对应的 encoder filter 注册到系统中。
注意:下面还用到了 DMO wrapper filter,使 DShow 应用程序能够在 filter graph 中使用 DirectX Media Object

HRESULT _addEncoder(LPCTSTR szTargetFile, IBaseFilter** ppEncoder)
{
    HRESULT hr = E_FAIL;
    *ppEncoder = NULL;

    GUID encoder = GUID_NULL;
    LPCTSTR ext = szTargetFile + _tcslen(szTargetFile) - 3;
    if (_tcsicmp(ext, _T("mp3")) == 0) {
        encoder = CLSID_LameMp3Enc;
    }
    else if (_tcsicmp(ext, _T("wma")) == 0) {
        encoder = CLSID_CWMAEncMediaObject;
    }

    if (encoder != GUID_NULL) {
        hr = AddFilterByCLSID(m_pGraph, encoder, ppEncoder, _T("Encoder"));   
        if (FAILED(hr))
            hr = addDMOFilter(m_pGraph, encoder, DMOCATEGORY_AUDIO_ENCODER, _T("Audio Encoder DMO"), ppEncoder);
        RETURN_IF_FAILED(hr);
    }

    return S_OK;
}

_addFileWriter 函数

用来写入转码的目标文件。

HRESULT _addFileWriter(LPCTSTR szTargetFile, IBaseFilter** ppFileWriter)
{
    HRESULT hr = E_FAIL;
    CComPtr<IFileSinkFilter> pSink = NULL;  // File sink object   

    hr = AddFilterByCLSID(m_pGraph, CLSID_FileWriter, ppFileWriter, _T("File Writer"));   
    RETURN_IF_FAILED(hr);

    // Set the file name.   
    hr = (*ppFileWriter)->QueryInterface(IID_IFileSinkFilter, (void**)&pSink);   
    RETURN_IF_FAILED(hr);

    hr = pSink->SetFileName(szTargetFile, NULL);   
    RETURN_IF_FAILED(hr);

    return S_OK;
}

_renderStream 函数

其实就是把各个 filter 手拉手连成一串。

注意:这里用到了一个 WavDest filter,从名字上看是输出 WAV 格式的 filter,但实际上它是个脸盲,只要是音频都接受,且按照输入格式直接输出,它在这里的作用就像一个掮客,原本 encoder 和 file writer 互不认识(不能连接),通过它就认识(连接)上了。

HRESULT_renderStream(IBaseFilter *pSource, IBaseFilter* pEncoder, IBaseFilter *pFileWriter)
{
    HRESULT hr = S_OK;
    
    CComPtr<IBaseFilter> pMux = NULL; // muxer object 
    hr = AddFilterByCLSID(m_pGraph, CLSID_WavDest, &pMux, _T("WavDest"));
    RETURN_IF_FAILED(hr);
    
    if (pEncoder == NULL) {
        hr = ConnectFilters(m_pGraph, pSource, pMux);   
        RETURN_IF_FAILED(hr);
    }
    else {
        hr = ConnectFilters(m_pGraph, pSource, pEncoder);   
        RETURN_IF_FAILED(hr);
        
        hr = ConnectFilters(m_pGraph, pEncoder, pMux);   
        RETURN_IF_FAILED(hr);
    }
    
    hr = ConnectFilters(m_pGraph, pMux, pFileWriter);   
    RETURN_IF_FAILED(hr);
    
    return S_OK;
}

handleDShowEvent 函数

其实 DShow 定义了很多 event,下面只处理了 EC_COMPLETE。常用的还有 EC_ERRORABORT:发生了严重错误,将强制终止。

typedef void (*PF_ON_COMPLETE)();

HRESULT handleDShowEvent(IMediaEventEx* pEvent, PF_ON_COMPLETE pfOnCompleteCallback)
{
    HRESULT hr = S_OK;
    HANDLE hEvent = NULL;
    hr = pEvent->GetEventHandle((OAEVENT*)&hEvent);
    RETURN_IF_FAILED(hr);
    
    bool bDone = false;
    while (!bDone) {
        DWORD ret = WaitForSingleObject(hEvent, INFINITE);
        RETURN_IF_FALSE(WAIT_OBJECT_0 == ret);
        
        long evCode = 0, param1 = 0, param2 = 0;
        while (SUCCEEDED(pEvent->GetEvent(&evCode, &param1, &param2, 0))) {
            hr = pEvent->FreeEventParams(evCode, param1, param2);
            if (EC_COMPLETE == evCode) {
                bDone = true;
                
                if (NULL != pfOnCompleteCallback)
                    pfOnCompleteCallback();
            }
        }
    }
    return S_OK;
}

DShow transcoding graph

以下是 mp3 转码 wma 的 Filter Graph,包含六个模块:Source Filter, Splitter, Decoder,Encoder,WavDest 和 File Writer。
transcoding graph

其他框架的转码

  • 关于 FFmpeg 的音频转码请参考 这里
  • 关于 Media Foundation 的音频转码请参考 这里

Blueware
EOF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值