声音采集程序(一)

我当初没有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(效果满意)。也有两款电脑效果不好,声音波动太微小,不便观察,程序运行是没问题的。

补充二:其实续录音块,很是烦人,应该是可以简化的,用一个录音块多好,录一次,发出去,覆盖再用,再发,实质上我也是这样做的,简化工作,谁收获,谁去做。

发布了66 篇原创文章 · 获赞 12 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ganggangwawa/article/details/102496683