使用waveIn进行录音

c++语音聊天

  
对语音控制思路为:先在服务端录音然后通用网络传输最后在客户端播放,下面我们分别讨论录音,传输,放音的实现步骤

录音实现:

对计算机录音我们可以使用一系列API,简单过程如下
waveInOpen 
                              打开录音设备
waveInPrepareHeader                    准备录音缓冲区
waveInAddBuffer                    将缓冲区加入队列
waveInStart                                开始录音
waveInUnPrepareHeader            释放录音缓冲区
waveInReset                                停止录音
waveInClose                                关闭录音设备

放音实现:

对计算机放音,简单过程如下

waveOutOpen                    打开回放设备
waveOutPrepareHeader准备放音缓冲区
waveOutWrite                    开始播放
waveOutRest                    停止放音
waveOutClose                    关闭回放设备
放音与录音相差无几,在后面的实例中将详细说明它的的使用

文件传输:

对于未经压缩处理的音频数据,它的体积是相当壮观的,对音频数据有效的压缩可以提高传输效率,为了方便本文没有对
数据进行压缩,而直接使用TCP进行传输


连续录/放音实现方法:

为了实现声音的平滑播放,在录放音时通常准备两个以上的缓冲区,当一个缓冲区用完后,将发出一个结束消息,并自动
转入下个缓冲区。当录音完成时会发出一个 MM_WIM_DATA消息,当放音完成时会发出一个MM_WOM_DONE消息。

两个重要的结构:

1.声音采样格式
原形如下:
typedef struct {
    WORD    wFormatTag;                        //数据格式,一般为WAVE_FORMAT_PCM即脉冲编码
    WORD    nChannels;                        //声道
    DWORDnSamplesPerSec;            //采样频率
    DWORDnAvgBytesPerSec;            //每秒数据量
    WORD    nBlockAlign;
    WORD    wBitsPerSample;            //样本大小
    WORD    cbSize;
} WAVEFORMATEX;   
对于这个结构我们通常使用默认或固定的值

2.音频数据块缓存结构WAVEHDR
其声明如下:   
type struct{
LPSTR lpData;                        //指向锁定的数据缓冲区的指针
DWORD dwBufferLength;            //数据缓冲区的大小
DWORD dwByteRecorded;            //录音时指明缓冲区中的数据量
DWORD dwUser;                        //用户数据
DWORD dwFlag;                        //提供缓冲区信息的标志
DWORD dwLoops;                        //循环播放的次数
struct wavehdr_tag *lpNext; //保留
DWORD reserved;                        //保留
} WAVEHDR;

声音的采集和播放都要使用这个音频数据块结构,实际上主要用到的就是第一个成员变量lpData和第二个成员变量dwBufferLength。           


相关AIP的使用:

waveInOpen的原型如下

MMRESULT waveInOpen(
    LPHWAVEINphwi,                //输入设备句柄一个指向HWAVEIN的指针
    UINTuDeviceID,                //输入设备ID
    LPWAVEFORMATEXpwfx,        //录音格式指针
    DWORDdwCallback,            //处理MM_***消息的回调函数或窗口句柄
    DWORDdwCallbackInstance,   
    DWORD fdwOpen                    //处理消息方式的符号位
);



在打开录音设置后就要指定录音缓冲区
它原形如下:
MMRESULT waveInPrepareHeader(
    HWAVEIN hwi,                           
    LPWAVEHDRpwh,                       
    UINT cbwh                               
);
其中HWAVEIN hwi为我们上面用waveInOpen打开的句柄,pwh为音频数据块缓存结构WAVEHDR。
其它的操作都比较简单就不再一一说明了,可参照MSDN使用。


服务端实现:


在开始前我们需要加载winmm.lib库和 mmsystem.h头文件
#include <mmsystem.h>
#pragma comment(lib,"winmm")

在开始录音按钮上添加如下代码:


            m_RecStart.EnableWindow(false);                                //停用录音按钮
            m_RecStart.SetWindowText("录音中...");            //改变按钮文字
            m_exit.SetFocus();                                                                //设置焦点按钮

            wavehdr=reinterpret_cast<PWAVEHDR>(malloc(sizeof(WAVEHDR)));

            //录音采样格式
            waveform.wFormatTag=WAVE_FORMAT_PCM;
            waveform.nChannels=1;
            waveform.nSamplesPerSec=11025;
            waveform.nAvgBytesPerSec=11025;
            waveform.nBlockAlign=1;
            waveform.wBitsPerSample=8;
            waveform.cbSize=0;

            //设定缓冲结构
            wavehdr->lpData=(LPTSTR)buffer;
            wavehdr->dwBufferLength=BUFFER_SIZE;
            wavehdr->dwBytesRecorded=0;
            wavehdr->dwUser=0;
            wavehdr->dwFlags=0;
            wavehdr->dwLoops=1;
            wavehdr->lpNext=NULL;
            wavehdr->reserved=0;

            //打开录音设备函数
            if(waveInOpen(&hWaveIn,WAVE_MAPPER,&waveform,(DWORD)this->m_hWnd,NULL,CALLBACK_WINDOW))
            {

                    AfxMessageBox("Audio can not be open!");
            }
           

            for(inti=0;i<2;i++)//加入2个缓冲区
            {
            //为录音设备准备缓冲区
            waveInPrepareHeader(hWaveIn,wavehdr,sizeof(WAVEHDR));
            //给输入设备增加一个缓存
            waveInAddBuffer(hWaveIn, wavehdr, sizeof (WAVEHDR)) ;
            }
            waveInStart(hWaveIn) ;//开始录音

当缓存录满后系统将发出MM_WIM_DATA消息,我们添加消息处理函数,当收到MM_WIM_DATA消息时就将数据发给
客户端处理,对于添加消息的方法可以参考一下VC教程。在MM_WIM_DATA消息中发送数据代码如下:

void CCCDlg::OnMM_WIM_DATA(UINT wParam,LONG lParam)//录音完成
{

            //释放录音缓冲区
            waveInUnprepareHeader(hWaveIn,wavehdr,sizeof(WAVEHDR));
            //拷贝录音数据
            CopyMemory(buffer,wavehdr->lpData,wavehdr->dwBufferLength);
            //调用函数发送数据
            SendBuffer(buffer);
            //重新准备缓冲区
            waveInPrepareHeader(hWaveIn,wavehdr,sizeof(WAVEHDR));
            //重新加入缓冲区
            waveInAddBuffer(hWaveIn, wavehdr, sizeof (WAVEHDR)) ;
}
当录音完成后系统会自动转入下个缓冲区,继续录音,我们就释放录音缓冲区,然后拷贝数据,最后重新加入缓冲区,这样就实现了对声音的循环录制。

发送数据函数SendBuffer(buffer)代码如下:
void SendBuffer(char *buffer)
{
            WSADATAwsadata;
            SOCKETclient;
            SOCKADDR_INserveraddr;
            intport=5555;
            WORDver=MAKEWORD(2,2);                                                                            //判断winsock版本
            WSAStartup(ver,&wsadata);                                                                //初始SOCKET
            client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
            serveraddr.sin_family=AF_INET;
            serveraddr.sin_port=htons(port);
            serveraddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
            connect(client,(SOCKADDR*)&serveraddr,sizeof(serveraddr));
            send(client,buffer,BUFFER_SIZE,0);//发送数据
            closesocket(client);
            WSACleanup();
}



客户端实现:

在对话框上添加监听按钮,并加入响应代码:

void CSSDlg::OnStart()
{
                    m_start.SetWindowText("监听中...");                    //改变按钮文字
                    m_start.EnableWindow(false);                                //停用录音按钮
                    hwnd=m_hWnd;
                    ::SendMessage(hwnd,MM_WOM_DONE,0,0);                    //发送MM_WOM_DONE消息
}

MM_WOM_DONE消息函数代码如下:

void CSSDlg::OnMM_WOM_DONE(UINT wParam,LONG lParam)//放音结束
{
            WSADATAwsadata;
            SOCKETserver;
            SOCKETclient;
            SOCKADDR_INserveraddr;
            SOCKADDR_INclientaddr;
            intport=5555;
            WORDver=MAKEWORD(2,2);                                                                            //判断winsock版本
            WSAStartup(ver,&wsadata);                                                                //初始SOCKET

            char*buffer=(char *)malloc(BUFFER_SIZE);                    //分配空间
            if(!buffer)
            {
                    AfxMessageBox("Memory error!");
            }

            server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

            serveraddr.sin_family=AF_INET;
            serveraddr.sin_port=htons(port);
            serveraddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);


            bind(server,(SOCKADDR*)&serveraddr,sizeof(serveraddr));
            listen(server,5);
            intlen=sizeof(clientaddr);
            client=accept(server,(sockaddr*)&clientaddr,&len);

            if(recv(client,buffer,BUFFER_SIZE,0))
            {
                    wavehdr->lpData=(LPTSTR)buffer;
                    wavehdr->dwBufferLength=BUFFER_SIZE;
                    wavehdr->dwBytesRecorded=0;
                    wavehdr->dwUser=0;
                    wavehdr->dwFlags=0;
                    wavehdr->dwLoops=1;
                    wavehdr->lpNext=NULL;
                    wavehdr->reserved=0;

                    waveform.wFormatTag                              WAVE_FORMAT_PCM;
                    waveform.nChannels                              1;
                    waveform.nSamplesPerSec                      11025;
                    waveform.nAvgBytesPerSec=            11025;
                    waveform.nBlockAlign                      1;
                    waveform.wBitsPerSample                      8;
                    waveform.cbSize                                          0;

            waveOutOpen(&hWaveOut,WAVE_MAPPER,&waveform,(DWORD)hwnd,NULL,CALLBACK_WINDOW)
            waveOutPrepareHeader(hWaveOut, wavehdr, sizeof (WAVEHDR))
            waveOutWrite(hWaveOut, wavehdr, sizeof (WAVEHDR))
            }

            closesocket(server);
            closesocket(client);
            WSACleanup();

}
我们用SendMessage(hwnd,MM_WOM_DONE,0,0)手动发送MM_WOM_DONE消息后,程序开始接受网络数据并进行播放,当播放结束时又自动发出一个MM_WOM_DONE消息,从而实现循环接受数据并播放。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值