博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++ 语音聊天
阅读量:7099 次
发布时间:2019-06-28

本文共 5462 字,大约阅读时间需要 18 分钟。

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

录音实现:
对计算机录音我们可以使用一系列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消息,从而实现循环接受数据并播放。

转载于:https://www.cnblogs.com/xiaopengblog/p/3167539.html

你可能感兴趣的文章
springCloud分布式事务实战(六)编写第二个微服务
查看>>
spark的HA集群搭建
查看>>
Essential Studio for WPF 2018 v3最新版发布(上)
查看>>
angularjs-currency 过滤器
查看>>
H3C-1000S 内部服务器映射
查看>>
Linux负载均衡软件LVS+keepalived
查看>>
世界500强某知名日企面试题库
查看>>
MySQL MyISAM 库转换为InnoDB的方法
查看>>
使用Python读Excel数据Insert到MySQL
查看>>
linux chkconfig and umask
查看>>
公司那些事-薪酬
查看>>
我的友情链接
查看>>
引用类型和值类型的比较
查看>>
Oracle查询被锁的表及进程的方法
查看>>
linux学习笔记四(shell编程一)
查看>>
树莓派2+无线网卡==伪热点+钓鱼网站
查看>>
js中的隐式转换
查看>>
如何用 k8s 管理超过 2500 个节点的集群
查看>>
HDU1087 Super Jumping! Jumping! Jumping!
查看>>
RHEL6基础五十之VMware下Linux系统安装VMware Tools
查看>>