本过滤器播放PCM和FLOAT音频流。
过滤器信息
过滤器名称:音频渲染
过滤器GUID:{4A910FA8-08DC-4832-85B2-4B7A3FF87F88}
DLL注册函数名:DllRegisterServer
删除注册函数名:DllUnregisterServer
过滤器有1个输入引脚。
输入引脚标识:In
输入引脚媒体类型:
主要类型:MEDIATYPE_Audio
子类型:MEDIASUBTYPE_PCM或MEDIASUBTYPE_IEEE_FLOAT
格式类型:FORMAT_WaveFormatEx
在子类型为PCM时,样本为16位。为FLOAT时,样本为32位。
过滤器开发信息
使用CoCreateInstance函数创建设备枚举器,接口为IMMDeviceEnumerator,使用该接口获取音频渲染默认端点,接口为IMMDevice,使用该接口的Activate方法获取音频端点客户端,接口为IAudioClient,使用该接口的Initialize方法以指定格式初始化音频流,本示例请求了1秒钟的缓冲区大小,由Initialize方法的参数3指定;使用IAudioClient接口的GetService方法获取IAudioRenderClient接口,该接口可以将音频数据写入渲染端点缓冲区。即,使用接口的GetBuffer方法获取指定大小的可用渲染缓冲区的指针,将音频数据复制到缓冲区,然后使用接口的ReleaseBuffer方法释放GetBuffer获取的缓冲区。调用IAudioClient接口的Start方法后,填充的音频数据将被播放。
在调用Start方法前,需预先填充一些音频数据;Start方法调用后,使用IAudioClient的GetCurrentPadding方法获取等待播放的音频帧数量,计算出渲染缓冲区可用的大小,反复的向渲染缓冲区填充音频数据,音频数据将被连续播放。
过滤器DLL的全部代码
DLL.h
cpp
#ifndef DLL_FILE
#define DLL_FILE
#include "strmbase10.h"//过滤器基础类定义文件
#if _DEBUG
#pragma comment(lib, "strmbasd10.lib")//过滤器基础类实现文件调试版本
#else
#pragma comment(lib, "strmbase10.lib")//过滤器基础类实现文件发布版本
#endif
// {4A910FA8-08DC-4832-85B2-4B7A3FF87F88}
DEFINE_GUID(CLSID_MyAudioRender,
0x4a910fa8, 0x8dc, 0x4832, 0x85, 0xb2, 0x4b, 0x7a, 0x3f, 0xf8, 0x7f, 0x88);
template <class T> void SafeRelease(T** ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
#define REFTIMES_PER_SEC 10000000
#define REFTIMES_PER_MILLISEC 10000
class CFilter;
class CPin : public CBaseInputPin
{
friend class CFilter;
CFilter *pCFilter;
public:
CPin(CFilter *pFilter, HRESULT *phr, LPCWSTR pPinName);
~CPin();
HRESULT CheckMediaType(const CMediaType *pmt);
HRESULT SetMediaType(const CMediaType *pmt);
STDMETHODIMP Receive(IMediaSample *pSample);
HRESULT Active();
HRESULT Inactive();
BOOL First = FALSE;
WORD nBlockAlign;//块对齐
REFERENCE_TIME hnsRequestedDuration;
REFERENCE_TIME hnsActualDuration;
void *pEnumerator = NULL;
void *pDevice = NULL;
void *pAudioClient = NULL;
void *pRenderClient = NULL;
WAVEFORMATEX *pwfx = NULL;
UINT32 bufferFrameCount;
UINT32 numFramesAvailable;
UINT32 numFramesPadding;
BYTE *pData;
DWORD flags = 0;
};
class CFilter : public CBaseFilter, public CCritSec
{
friend class CPin;
IUnknown *m_pPosition = NULL;
public:
CFilter(LPWSTR lpName, LPUNKNOWN pUnk, HRESULT *phr);
virtual ~CFilter();
DECLARE_IUNKNOWN
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
static CUnknown * WINAPI CreateInstance(LPUNKNOWN, HRESULT *);
int GetPinCount();
CBasePin *GetPin(int n);
CPin *pCPin; //输入引脚指针
};
#endif //DLL_FILE
DLL.cpp
cpp
#include "DLL.h"
const AMOVIESETUP_MEDIATYPE sudPinTypes[] =
{
{
&MEDIATYPE_Audio, //主要类型
&MEDIASUBTYPE_PCM //子类型
},
{
&MEDIATYPE_Audio, //主要类型
&MEDIASUBTYPE_IEEE_FLOAT //子类型
}
};
const AMOVIESETUP_PIN sudPin = //引脚信息
{
L"In", //引脚名称
TRUE, //渲染引脚
FALSE, //输出引脚
FALSE, //具有该引脚的零个实例
FALSE, //可以创建一个以上引脚的实例
&CLSID_NULL, //该引脚连接的过滤器的类标识
NULL, //该引脚连接的引脚名称
2, //引脚支持的媒体类型数
sudPinTypes //媒体类型信息
};
const AMOVIESETUP_FILTER AudioRender = //过滤器的注册信息
{
&CLSID_MyAudioRender, //过滤器的类标识
L"音频渲染", //过滤器的名称
MERIT_DO_NOT_USE, //过滤器优先值
1, //引脚数量
&sudPin //引脚信息
};
CFactoryTemplate g_Templates[] =
{
{
L"音频渲染" //对象(这里为过滤器)名称
, &CLSID_MyAudioRender //对象CLSID的指针
, CFilter::CreateInstance //创建对象实例的函数的指针
, NULL //指向从DLL入口点调用的函数的指针
, &AudioRender //指向AMOVIESETUP_FILTER结构的指针
}
};
int g_cTemplates = 1;
STDAPI DllRegisterServer()//注册DLL
{
return AMovieDllRegisterServer2(TRUE);
}
STDAPI DllUnregisterServer()//删除DLL注册
{
return AMovieDllRegisterServer2(FALSE);
}
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
}
CFilter.cpp
cpp
#include "DLL.h"
CFilter::CFilter(LPWSTR lpName, LPUNKNOWN pUnk, HRESULT *phr) : CBaseFilter(lpName, pUnk, (CCritSec *) this, CLSID_MyAudioRender)
{
pCPin = new CPin(this, phr, L"In");//创建输入引脚
}
CFilter::~CFilter()
{
if (m_pPosition != NULL)delete m_pPosition;
}
CUnknown * WINAPI CFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
return new CFilter(L"音频渲染", pUnk, phr);//创建过滤器
}
int CFilter::GetPinCount()
{
return 1;
}
CBasePin *CFilter::GetPin(int n)
{
if (n != 0)
{
return NULL;
}
return pCPin;
}
STDMETHODIMP CFilter::NonDelegatingQueryInterface(REFIID iid, void ** ppv)
{
HRESULT hr = NOERROR;
if( iid == IID_IMediaPosition || iid == IID_IMediaSeeking)
{
if (m_pPosition == NULL)
{
hr = CreatePosPassThru(CBaseFilter::GetOwner(), FALSE, (IPin *)pCPin, &m_pPosition);
if (FAILED(hr)) return hr;
}
return m_pPosition->QueryInterface(iid, ppv);
}
else
return CBaseFilter::NonDelegatingQueryInterface(iid, ppv);
}
CPin.cpp
cpp
#include "DLL.h"
#include "mmdeviceapi.h"
#include "audioclient.h"
CPin::CPin(CFilter *pFilter, HRESULT *phr, LPCWSTR pPinName) : CBaseInputPin(NAME("In"), pFilter, pFilter, phr, pPinName)
{
pCFilter = pFilter;
hnsRequestedDuration = REFTIMES_PER_SEC;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
if (hr != S_OK)
{
MessageBox(NULL, L"创建设备枚举器失败", L"音频渲染", MB_OK); return;
}
IMMDevice* pMD = NULL;
hr = ((IMMDeviceEnumerator*)pEnumerator)->GetDefaultAudioEndpoint(eRender, eConsole, &pMD);
if (hr != S_OK)
{
MessageBox(NULL, L"获取默认音频端点失败", L"音频渲染", MB_OK); return;
}
pDevice = (void*)pMD;
hr = ((IMMDevice*)pDevice)->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioClient);
if (hr != S_OK)
{
MessageBox(NULL, L"获取音频端点客户端失败", L"音频渲染", MB_OK); return;
}
hr = ((IAudioClient*)pAudioClient)->GetMixFormat(&pwfx);
if (hr != S_OK)
{
MessageBox(NULL, L"获取音频端点流格式失败", L"音频渲染", MB_OK); return;
}
}
CPin::~CPin()
{
CoTaskMemFree(pwfx);
IMMDeviceEnumerator* pE = (IMMDeviceEnumerator*)pEnumerator;
SafeRelease(&pE);
IMMDevice *pD = (IMMDevice*)pDevice;
SafeRelease(&pD);
IAudioClient *pA = (IAudioClient*)pAudioClient;
SafeRelease(&pA);
IAudioRenderClient *pR = (IAudioRenderClient*)pRenderClient;
SafeRelease(&pR);
}
HRESULT CPin::CheckMediaType(const CMediaType *pmt)
{
if (pmt->majortype == MEDIATYPE_Audio && (pmt->subtype == MEDIASUBTYPE_PCM || pmt->subtype == MEDIASUBTYPE_IEEE_FLOAT) && pmt->formattype == FORMAT_WaveFormatEx)
{
return S_OK;
}
return S_FALSE;
}
HRESULT CPin::SetMediaType(const CMediaType *pmt)
{
WAVEFORMATEX* p = (WAVEFORMATEX*)pmt->pbFormat;
HRESULT hr= ((IAudioClient*)pAudioClient)->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, hnsRequestedDuration, 0, p, NULL);//以共享模式初始化音频流
if (hr == S_OK)
{
hr = ((IAudioClient*)pAudioClient)->GetBufferSize(&bufferFrameCount);//获取分配的缓冲区的实际大小
}
IAudioRenderClient *pRender = NULL;
if (hr == S_OK)
{
hr = ((IAudioClient*)pAudioClient)->GetService(__uuidof(IAudioRenderClient), (void**)&pRender);
}
if (hr == S_OK)
{
pRenderClient = (void*)pRender;
nBlockAlign = p->nBlockAlign;
}
else return hr;
return CBaseInputPin::SetMediaType(pmt);
}
HRESULT CPin::Receive(IMediaSample * pSample)//接收函数
{
HRESULT hr;
BYTE* pBy = NULL;
hr = pSample->GetPointer(&pBy);//获取引脚样本缓冲区指针
long len = pSample->GetActualDataLength();//获取有效数据长度
if (First)
{
First = FALSE;
hr = ((IAudioRenderClient*)pRenderClient)->GetBuffer(len / nBlockAlign, &pData);//参数1,请求的缓冲区的音频帧大小;参数2,获取缓冲区的指针
CopyMemory(pData, pBy, len);
hr = ((IAudioRenderClient*)pRenderClient)->ReleaseBuffer(len / nBlockAlign, flags);
hnsActualDuration = (double)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec;
hr = ((IAudioClient*)pAudioClient)->Start();//启动音频流
return S_OK;
}
Agan:
hr = ((IAudioClient*)pAudioClient)->GetCurrentPadding(&numFramesPadding);//获取未播放的音频帧数
numFramesAvailable = bufferFrameCount - numFramesPadding;//计算缓冲区中可填充的帧数
if ((long)(numFramesAvailable * nBlockAlign) < len)//如果可填充的大小,小于引脚样本有效数据长度
{
Sleep(10); goto Agan;//等待并阻塞
}
hr = ((IAudioRenderClient*)pRenderClient)->GetBuffer(len / nBlockAlign, &pData);//获取渲染缓冲区可用内存的指针,由参数2输出;参数1为输入参数,请求的可用内存的大小,单位音频帧
CopyMemory(pData, pBy, len);
hr = ((IAudioRenderClient*)pRenderClient)->ReleaseBuffer(len / nBlockAlign, flags);//释放GetBuffer获取的缓冲区
return S_OK;
}
HRESULT CPin::Active()
{
First = TRUE;
return CBaseInputPin::Active();
}
HRESULT CPin::Inactive()
{
Sleep((DWORD)(hnsActualDuration / REFTIMES_PER_MILLISEC / 2));
HRESULT hr;
hr = ((IAudioClient*)pAudioClient)->Stop();//停止音频流
hr = ((IAudioClient*)pAudioClient)->Reset();//重置流,刷新所有挂起的数据,将音频时钟流位置重置为0
return CBaseInputPin::Inactive();
}