当根据DirectShow程序包中AudioCap例子写了自己的MyAudioMFC程序后,虽然程序本身可以很好的运行,也可以实现“边录边听”,并实现播放;但感觉还是很有必要把思路理清一下;将其MFC改写为普通函数API的SDK方式后,一般理解起来就更容易些。且程序界面也简单的改写为只包含四个按钮的录音程序(封装了Input Device、InputPins、Graph Filters、InputPins和OutPins):
在DialogProc中的WM_INITDIALOAG消息中初始化COM库: CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);为使用一系列的COM接口函数做好准备;
在初始化一系列要用到的变量,默认都为0值,SetDefaults();
初始化准备做好后在写COM接口函数
HRESULT GetInterfaces()
{
HRESULT hr;
// Create the filter graph.
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
IID_IGraphBuilder, (void **)&m_pGB);
if (FAILED(hr) || !m_pGB)
return E_NOINTERFACE;
// Create the capture graph builder.
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC,
IID_ICaptureGraphBuilder2, (void **)&m_pCapture);
if (FAILED(hr) || !m_pCapture)
return E_NOINTERFACE;
// Associate the filter graph with the capture graph builder
hr = m_pCapture->SetFiltergraph(m_pGB);
if (FAILED(hr))
return hr;
// Get useful interfaces from the graph builder
JIF(hr = m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC));
JIF(hr = m_pGB->QueryInterface(IID_IMediaEventEx, (void **)&m_pME));
// Have the graph signal events via window callbacks
hr = m_pME->SetNotifyWindow((OAHWND)m_hWnd, WM_GRAPHNOTIFY, RECORD_EVENT);
CLEANUP:
return hr;
}
先初始化Filter Graph的变量IGraphBuilder *m_pGB;再初始化辅助组件Capture Graph Builder(此组件可以简化Filter Graph的构建)ICaptureGraphBuilder2 *m_pCapture的变量;在通过hr = m_pCapture->SetFiltergraph(m_pGB); 设置Filter Graph Manager对象指针;则在下面的应用中就可以方便的使用ICaptureBuilder2接口方法(如RenderStream、FindPin、FindInterface等)来构建Filter Graph。
枚举音频采集设备
创建系统枚举器组件对象,获得ICreateDevEnum接口:
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC, IID_ICreateDevEnum,
(void **)&pSysDevEnum););
为指定的Filtre注册类型目录创建一个枚举器,获得IEnumMoniker接口:
hr = pSysDevEnum->CreateClassEnumerator(*clsid, &pEnumCat, 0);
枚举指定类型目录下所有设备标识(Device Moniker):
while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK){}
访问设备标识的属性集,相关属性值(如Friendly Name)可以保存在IPropertyBag接口对象中:hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
(void **)&pPropBag);
读出Friendly Name的属性值,可调用IPropertyBag接口的成员函数Read():
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
CString str(varName.bstrVal);
其中varName是一个VARIANT类型的结构体变量
由于实在SDK中实现的音频采集,界面很简单(只有四个按钮),没有AudioCap中的复杂界面;则就要在此while循环中判断设备标识中是否含有输入Pin,有则选定并跳出,没有则继续循环,知道Next()值为S_FALSE:
hr = EnumPinsOnFilter(m_pInputDevice, PINDIR_INPUT);
if (FAILED(hr))
continue;
else
break;
其中枚举输入Pin时:
hr = m_pInputDevice->EnumPins(&pEnum);
while((hr = pEnum->Next(1, &pPin, 0)) == S_OK){}
在循环中, hr = pPin->QueryDirection(&PinDirThis); 可查询Pin的方向,结果返回到PinDirThis中
if (PINDIR_INPUT== PinDirThis){ PIN_INFO pininfo={0};
hr = pPin->QueryPinInfo(&pininfo);
if (SUCCEEDED(hr))
{
CString str(pininfo.achName);
// Listbox.AddString(str);
}
}
调用QueryPinInfo()可将Pin的属性集值返回到PIN_INFO类型的变量中;Pin的名字则保存与PIN_INFO.achName中;
要选定音频采集的输入端子,则需使用IAMAudioInputMixer接口:
hr = pPin->QueryInterface(IID_IAMAudioInputMixer, (void **)&pPinMixer);
然后将选定的输入Pin设置为:
hr = pPinMixer->put_Enable(TRUE);
break;
跳出循环;
当这前两不都准备好后,开始录音,实现边听边录,点击“录音”按钮即可;
一般的Audio Caputre Fitler只有一个Capture输出Pin,要实现录音音频流的话,可以使用ICaptureGraphBuilder2::RenderStream来实现,它可根据需要自动插入一个Smart Tee Filter(类似与Infinite Pin Tee的标准Filter):
HRESULT RenderPreviewStream(){}
其中也可以设置采集音频的参数:
int nChannels = 2; //双声道
int nBytesPerSample = 2;//16位(量化精度)
nFrequency = 44100;//44kHz
long lBytesPerSecond = (long) (nBytesPerSample * nFrequency * nChannels);//一秒传送的字节数
long lBufferSize = (long) ((float) lBytesPerSecond * DEFAULT_BUFFER_TIME);//设置数据的缓存大小
//得到这几个变量值后就可以使用IID_IAMBufferNegotiation、IAMStreamConfig来分别设置采集的缓冲大小和音频的声道数、量化精度、采样频率
for (int i=0; i<2; i++)
{
hr = GetPin(m_pInputDevice, PINDIR_OUTPUT, i, &pPin);
if (SUCCEEDED(hr))
{
// Get buffer negotiation interface
hr = pPin->QueryInterface(IID_IAMBufferNegotiation, (void **)&pNeg);
if (FAILED(hr))
{
pPin->Release();
break;
}
}
hr = pNeg->SuggestAllocatorProperties(&prop);
其中prop是ALLOCATOR_PROPERTIES的结构体变量,保存了音频采集的缓冲大小:
prop.cbBuffer = lBufferSize;
prop.cBuffers = 6;
prop.cbAlign = nBytesPerSample * nChannels;
设置IAMStreamConfig接口对象:
hr = pPin->QueryInterface(IID_IAMStreamConfig, (void **)&pCfg);
获取当前输出Pin的数据媒体类型
AM_MEDIA_TYPE *pmt={0};
hr = pCfg->GetFormat(&pmt);
设置当前输出Pin的数据媒体类型
hr = pCfg->SetFormat(pmt);
AM_MEDIA_TYPE *pmt={0};
hr = pCfg->GetFormat(&pmt); AM_MEDIA_TYPE *pmt={0};
hr = pCfg->GetFormat(&pmt); long lBytesPerSecond = (long) (nBytesPerSample * nFrequency * nChannels);
至此“边听边录”的效果已经实现,若还想实现回路播放则需写
HRESULT RenderCaptureStream(){}函数:
创建Wave Dest Filter和File Write Filter,并加入Filter Grpah中,经过必要的设置后,取得声卡Filter或Smart Tee的输出Pin,在调用Render进行剩余部分的Filter连接:
hr = CoCreateInstance(CLSID_WavDest, NULL, CLSCTX_INPROC,
IID_IBaseFilter, (void **)&m_pWAVDest);
hr = CoCreateInstance(CLSID_FileWriter, NULL, CLSCTX_INPROC,
IID_IFileSinkFilter2, (void **)&pFileSink);
hr = pFileSink->QueryInterface(IID_IBaseFilter, (void **)&m_pFileWriter);
hr = m_pGB->AddFilter(m_pWAVDest, L"WAV Dest");
hr = m_pGB->AddFilter(m_pFileWriter, L"File Writer");
hr = pFileSink->SetMode(AM_FILE_OVERWRITE);//设置生成的文件总是覆盖原有文件
wcscpy(wszFilename, T2W(TEXT("C:\\test.wav\0")));
hr = pFileSink->SetFileName(wszFilename, NULL);//设置数据流与文件磁盘中
获得Smart Tee Fitler上的IPin接口
hr = GetPin(m_pSplitter, PINDIR_OUTPUT, 0, &pPin);
进行“智能连接”:
hr = m_pGB->Render(pPin);
到了这里一条完整的采集Filter Graph链路便连接完成了,在Edit Graph中自己电脑中的链路如下图
按下“录音”按钮:
hr = m_pMC->Run();
按下“停止”按钮:
if (m_pMCPlayback)
hr = m_pMCPlayback->Stop();
if (m_pMCPlayback)
hr = m_pMCPlayback->Stop();
if (m_pMC)
hr = m_pMC->StopWhenReady();
按下“播放”按钮:
if (SUCCEEDED(GetPlaybackInterfaces()))
{
HRESULT hr = m_pGBPlayback->RenderFile(T2W(TEXT("C:\\test.wav")), NULL);
if (SUCCEEDED(hr))
{
hr = m_pMCPlayback->Run();
}
}
按下“暂停”按钮:
if (m_pMCPlayback)
{
hr = m_pMCPlayback->Pause();
}
else
{
if (m_pMC)
{
hr = m_pMC->Pause();
}
}
OK了,这就是一个音频采集的SDK程序了