对语音控制思路为:先在服务端录音然后通用网络传输最后在客户端播放,下面我们分别讨论录音,传输,放音的实现步骤
录音实现:对计算机录音我们可以使用一系列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; //声道 DWORD nSamplesPerSec; //采样频率 DWORD nAvgBytesPerSec; //每秒数据量 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( LPHWAVEIN phwi, //输入设备句柄一个指向HWAVEIN的指针 UINT uDeviceID, //输入设备ID LPWAVEFORMATEX pwfx, //录音格式指针 DWORD dwCallback, //处理MM_***消息的回调函数或窗口句柄 DWORD dwCallbackInstance, DWORD fdwOpen //处理消息方式的符号位);在打开录音设置后就要指定录音缓冲区它原形如下:MMRESULT waveInPrepareHeader( HWAVEIN hwi, LPWAVEHDR pwh, 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(int i=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){ WSADATA wsadata; SOCKET client; SOCKADDR_IN serveraddr; int port=5555; WORD ver=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)//放音结束{ WSADATA wsadata; SOCKET server; SOCKET client; SOCKADDR_IN serveraddr; SOCKADDR_IN clientaddr; int port=5555; WORD ver=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); int len=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消息,从而实现循环接受数据并播放。