我当初没有C#版本的声音采集程序,我就使用这个版本把音频传递到C#处理程序那头,这个程序,你也可以当做vc++入门练习,不过有难点,但是可以先吞下来,回头针对难点消化。又要抄程序了,这是必修课,音乐家都是抄乐谱起家的。
我的这个程序的名称叫newnotacm。
首先录音的波形要在界面窗口显示出来,在stdafx.h中写入:
#include<gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib,"gdiplus")
以上初始化写在newnotacm.h和newnotacm.cpp中:public:
GdiplusStartupInput gdiplusInput;
ULONG_PTR gdiplusToken;
///////////////////////newnotacm.cpp
InitInstance(){......GdiplusStartup(&gdiplusToken,&gdiplusInput,NULL);..................}
int CnewNotAcmApp::ExitInstance()
{
// TODO: 在此添加专用代码和/或调用基类
GdiplusShutdown(gdiplusToken);
return CWinApp::ExitInstance();
}
好,进入newnotacmdlg.h和.cpp,先声明函数:AudioInit(),然后在OnInitDialog()函数中调用:
if(AudioInit()!=true)
{
OnCancel();
}
/////////////////////////
bool CnewNotAcmDlg::AudioInit()//这里使用的是微软的东东,你需要去查资料理解
{
MMRESULT mmr;
SrcFormat.wFormatTag=WAVE_FORMAT_PCM;//原音格式
SrcFormat.nChannels=1;
SrcFormat.nSamplesPerSec=8000;//每秒采样8000,为什么不是4096*2?给快速傅里叶快速变换制造麻烦。
SrcFormat.nAvgBytesPerSec=8000;
SrcFormat.nBlockAlign=1;
SrcFormat.wBitsPerSample=8;
SrcFormat.cbSize=0;
mmr=waveInOpen(&m_hWaveIn,WAVE_MAPPER,&SrcFormat,(UINT)m_hWnd,0,CALLBACK_WINDOW);//打开音频input
if(mmr!=MMSYSERR_NOERROR)
{
return false;
}
mmr=waveOutOpen(&m_hWaveOut,WAVE_MAPPER,&SrcFormat,(UINT)m_hWnd,0L,CALLBACK_WINDOW);//打开音频output
if(mmr!=MMSYSERR_NOERROR)
{
return false;
}
if(AudioBufferInit()!=true)//录音存储块准备
{
return false;
}
for(int i=0;i<3;i++)
{
mmr=waveInPrepareHeader(m_hWaveIn,m_pWaveHdr.GetAt(i),sizeof(WAVEHDR));
if(mmr!=MMSYSERR_NOERROR)
{
for(int j=0;j<i;j++)
{
waveInUnprepareHeader(m_hWaveIn,m_pWaveHdr.GetAt(j),sizeof(WAVEHDR));
m_pWaveHdr.GetAt(j)->dwUser=0;//_FREE;
m_WaveHdrIndex=0;
}
AudioStop();
return false;
}
mmr=waveInAddBuffer(m_hWaveIn,m_pWaveHdr.GetAt(i),sizeof(WAVEHDR));
if(mmr!=MMSYSERR_NOERROR)
{
for(int j=0;j<i;j++)
{
waveInUnprepareHeader(m_hWaveIn,m_pWaveHdr.GetAt(j),sizeof(WAVEHDR));
m_pWaveHdr.GetAt(j)->dwUser=0;//_FREE;
m_WaveHdrIndex=0;
}
AudioStop();
return false;
}
m_pWaveHdr.GetAt(i)->dwUser=1;//_INUSE
m_WaveHdrIndex++;
if(m_WaveHdrIndex==m_pWaveHdr.GetSize())
{
m_WaveHdrIndex=0;
}
}
return true;
}
/////////////////////
bool CnewNotAcmDlg::AudioBufferInit()
{
for(int i=0;i<5;i++)//buffernum
{
LPWAVEHDR temp;
temp=new WAVEHDR;//新建一个缓存块
temp->dwBufferLength=1920;//根据自己需求确定
temp->dwLoops=1;
temp->dwBytesRecorded=0;
temp->dwUser=0;
temp->lpData=(LPSTR)(new char[1920]);
temp->lpNext=NULL;
temp->dwFlags=0;
temp->reserved=0;
m_pWaveHdr.Add(temp);
LPWAVEHDR tempout;
tempout=new WAVEHDR;
tempout->dwBufferLength=1920;
tempout->dwLoops=1;
tempout->dwBytesRecorded=0;
tempout->dwUser=0;
tempout->lpData=(LPSTR)(new char[1920]);
tempout->lpNext=NULL;
tempout->dwFlags=0;
tempout->reserved=0;
m_pWaveHdrOut.Add(tempout);
}
if(m_pWaveHdr.GetSize()<3)
{
AudioBufferDel();
return false;
}
return true;//共5个缓存块
}
void CnewNotAcmDlg::AudioStop()
{
if(m_hWaveIn!=NULL)
{
waveInStop(m_hWaveIn);
waveInReset(m_hWaveIn);
waveInClose(m_hWaveIn);
m_hWaveIn=NULL;
}
//AcmStop();
AudioBufferDel();
}//////////////////////////////////
void CnewNotAcmDlg::AudioStop()
{
if(m_hWaveIn!=NULL)
{
waveInStop(m_hWaveIn);//api函数
waveInReset(m_hWaveIn);
waveInClose(m_hWaveIn);
m_hWaveIn=NULL;
}
//AcmStop();
AudioBufferDel();
}///////////////////////////////////////////
void CnewNotAcmDlg::AudioBufferDel()
{
for(int i=0;i<m_pWaveHdr.GetSize();i++)
{
delete m_pWaveHdr.GetAt(i);
}
}
界面窗口放置一个dialog控件和一个按钮控件,以上准备工作就绪,可以录音了,由按钮点击完成:
void CnewNotAcmDlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
//OnOK();
waveInStart(m_hWaveIn);
}
需要注意的是,在这里有一个回调函数需要处理(难点,可以对比bios中断处理函数来理解):
#define WM_USER_ACMINCMPLT WM_USER+1123
ON_MESSAGE(WM_USER_ACMINCMPLT,OnAcmInCmplt)
LRESULT CnewNotAcmDlg::OnAcmInCmplt(WPARAM wpara,LPARAM lparam)
{//这个回调函数红色部分正是丢给音频处理程序的声波。每次1920字节
MMRESULT mmr;
LPWAVEHDR pWaveHdr=(LPWAVEHDR)lparam;
LPWAVEHDR hdr;
bool tag=false;
int No=m_outHdrIndex;
bool circle=false;
do{
hdr=m_pWaveHdrOut.GetAt(m_outHdrIndex);
m_outHdrIndex++;
if(m_outHdrIndex==m_pWaveHdrOut.GetSize())m_outHdrIndex=0;
if(m_outHdrIndex==No)
{
circle=true;
}
if(hdr->dwUser==0)
{
hdr->dwUser=1;
hdr->lpData=pWaveHdr->lpData;
/*HWND hWnd= ::FindWindow(NULL,(LPCSTR)"arrimg");*/
HWND hWnd= ::FindWindow(NULL,(LPCSTR)"arrimgMatch");//用来实时匹配的程序名字
cds.dwData=0;
cds.cbData=1920;
cds.lpData=(PVOID)hdr->lpData;
::SendMessage(hWnd,WM_COPYDATA,NULL,(LPARAM)&cds);
////hWnd= ::FindWindow(NULL,(LPCSTR)"arrimg");//用来制作特征头的程序名字
////::SendMessage(hWnd,WM_COPYDATA,NULL,(LPARAM)&cds);
this->m_ArrImage.Invalidate();
for(int i=0;i<1920;i++)//last is'\0'?
{
if( (byte)(hdr->lpData[i])>m_maxValue)
m_maxValue=(byte)hdr->lpData[i];
if( (byte)(hdr->lpData[i])<m_minValue)
m_minValue=(byte)hdr->lpData[i];
UpdateData(false);//maxvalue=128
}
mmr=waveOutPrepareHeader(m_hWaveOut,hdr,sizeof(WAVEHDR));
if(mmr==MMSYSERR_NOERROR)
{
//mmr=waveOutWrite(m_hWaveOut,hdr,sizeof(WAVEHDR));
waveOutUnprepareHeader(m_hWaveOut,hdr,sizeof(WAVEHDR));
tag=true;
hdr->dwUser=0;
}
else
{
hdr->dwUser=0;
}
}
}while((tag==false)&&(circle==false));
return 0;
}
那么,是谁调用了这个回调函数呢?在这个时候,系统完成对一个缓存块的录制,就会触发WM_WIN_DATA消息来处理,如下:ON_MESSAGE(MM_WIM_DATA,OnMmWimData)
LRESULT CnewNotAcmDlg::OnMmWimData(WPARAM wpara,LPARAM lparam)
{
LPWAVEHDR pWaveHdr=(LPWAVEHDR)lparam;
waveInUnprepareHeader(m_hWaveIn,pWaveHdr,sizeof(WAVEHDR));//取消对缓存块的准备
MMRESULT mmr;
LPWAVEHDR hdr;
bool tag=false;
int No=m_WaveHdrIndex;
bool circle=false;
do{
hdr=m_pWaveHdr.GetAt(m_WaveHdrIndex);
m_WaveHdrIndex++;
if(m_WaveHdrIndex==m_pWaveHdr.GetSize())m_WaveHdrIndex=0;
if(m_WaveHdrIndex==No)
{circle=true;}
if(hdr->dwUser==0)
{
hdr->dwUser=1;
mmr=waveInPrepareHeader(m_hWaveIn,hdr,sizeof(WAVEHDR));
if(mmr==MMSYSERR_NOERROR)
{
mmr=waveInAddBuffer(m_hWaveIn,hdr,sizeof(WAVEHDR));
if(mmr==MMSYSERR_NOERROR)
{
tag=true;
}else
{
waveInUnprepareHeader(m_hWaveIn,hdr,sizeof(WAVEHDR));
hdr->dwUser=0;
}
}else hdr->dwUser=0;
}
}while((tag==false)&&(circle==false));
if((circle==true)&&(tag==false))
{
//若所有buffer都在使用中,则新建buffer
}
AfxBeginThread(WaveInChecker,pWaveHdr);//是否考虑未取即盖?
pWaveHdr->dwFlags=0;
pWaveHdr->dwBytesRecorded=0;
pWaveHdr->dwUser=0;
return 0;
}
UINT WaveInChecker(LPVOID lp);
UINT WaveInChecker(LPVOID lp)
{
SendMessage(AfxGetMainWnd()->m_hWnd,WM_USER_ACMINCMPLT,0,(LPARAM)lp);//m_hWnd
return 0;
}
剩下的就是在dialog控件中把波形显示出来:我们使用drawarr.h和.cpp文件,声明如下:public:
int _RoiW ;
int _RoiH ;
LPSTR tempArrImg;
BYTE * gb_pChildextBuffer;
///////////////////////////////drawarr.cpp
CDrawArr::CDrawArr()
{
tempArrImg=NULL;
gb_pChildextBuffer=NULL;
_RoiW = 1920/ 2;
_RoiH = 512;
gb_pChildextBuffer=new BYTE[_RoiW*_RoiH*4];
}
void CDrawArr::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 在此?添加消息?理程序代?
// 不???消息?用 CWnd::OnPaint()
CnewNotAcmDlg *pm=(CnewNotAcmDlg *)::AfxGetMainWnd();
//
if(pm->cds.lpData!=NULL)
{
tempArrImg=(LPSTR)pm->cds.lpData;
for (int i=0; i<_RoiH; i++)
{
for (int j=0; j<_RoiW; j++)
{
int m = 4* (i * _RoiW + j);
gb_pChildextBuffer[m] = 0;
gb_pChildextBuffer[m + 1] = 0;
gb_pChildextBuffer[m + 2] = 0;
gb_pChildextBuffer[m+3]=128;
}
}
for (int j = 0; j < _RoiW; j++)
{
int m = 4 * ((byte)(tempArrImg[j]) * _RoiW + j);
gb_pChildextBuffer[m] = 0;
gb_pChildextBuffer[m + 1] = 0;
gb_pChildextBuffer[m + 2] = 255;
gb_pChildextBuffer[m+3]=128;
}
for (int j = 0; j < _RoiW; j++)
{
int m = 4 * (((byte)(tempArrImg[j+960])+256) * _RoiW + j);
gb_pChildextBuffer[m] = 0;
gb_pChildextBuffer[m + 1] = 0;
gb_pChildextBuffer[m + 2] = 255;
gb_pChildextBuffer[m+3]=128;
}
Graphics graph(dc.m_hDC);
Bitmap bitmap(960 ,
512,
960 * 4,
PixelFormat32bppRGB,
gb_pChildextBuffer);
graph.DrawImage(&bitmap,0, 0);
}
}
以上就是采集声音,显示声音,丢出去声音的全过程。关于微软的WM_COPYDATA,真是个好东西,前面介绍太多,也有改造在c#中。若要把声音放出来,也是可以的,请参考:黄友生的《编程实现实时对音频压缩管理器(ACM)的调用》(2002年第三期电脑编程技巧与维护)
我后来因为要整合到一块,使用c#,觉得不方便,便改造了网上的一个C#版本的采集声音程序,但还是舍不得这个版本,是先入为主?还是有感情了?还是他稳定性更好?
下一节,我们展示C#版本声音采集,对付那些不用c++的人。
待续(慢慢来!...........)每天一点小改变☺
我的邮箱[email protected];[email protected]
补充一:这是今天第一次在acer switch12,win8.1(x64)系统下初次运行的图片(因为在此写博文,顺便试了一下(说话声显示太微,直接手指录音口敲了几下),960*2=1920字节,效果如下):
平时一直使用富士通t901,xp系统,另外也使用了onda obook20 plus平板,win10(x64)系统,均测试ok(效果满意)。也有两款电脑效果不好,声音波动太微小,不便观察,程序运行是没问题的。
补充二:其实续录音块,很是烦人,应该是可以简化的,用一个录音块多好,录一次,发出去,覆盖再用,再发,实质上我也是这样做的,简化工作,谁收获,谁去做。