Qt实现基于G.729A(G729A)的语音聊天
一、G.729协议简介
G.729协议是由ITU-T的第15研究小组提出的,并在1996年3月通过的8Kbps的语音编码协议。
G.729系列主要有以下几种:
G.729—最基本的G.729标准协议,原始版
G.729A—精简版的G.729,兼容原始版G.729,对G.729一些算法进行简单处理,相当于降低了算法的复杂度
G.729B—加入了语音端点检测模块,在编码前对语音进行语音和静默音进行检测,然后分别对不同情况进行编码
G.729AB—就是G.729A中加入语音端点检测模块,兼容G.729B,目前G.729AB用得比较多
G.729协议的实现是开源的,源码可以从ITU官网下载。
下载链接:https://www.itu.int/rec/T-REC-G.729/e
本文采用VoiceAge公司封装的G.729A静态库进行语音的编解码。
下载链接:http://download.csdn.net/detail/caoshangpa/9496833
由于低带宽的需求,G.729通常应用于VoIP(Voice over Internet Protocol),比如说视频会议。G.729有两大特点。
1.占用带宽小
使用普通编码的语音通讯需要占用64Kbps的带宽,而G.729仅仅需要8Kbps。
2.占用CPU时间多
使用G.729时CPU的使用时间大约为G.711的4倍,所以使用G.729时需要注意设备是否有足够的处理能力。
二、聊天过程
1.初始化
- Widget::Widget(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::Widget)
- {
- ui->setupUi(this);
- //设置采样格式
- QAudioFormat audioFormat;
- //设置采样率
- audioFormat.setSampleRate(8000);
- //设置通道数
- audioFormat.setChannelCount(1);
- //设置采样大小,一般为8位或16位
- audioFormat.setSampleSize(16);
- //设置编码方式
- audioFormat.setCodec("audio/pcm");
- //设置字节序
- audioFormat.setByteOrder(QAudioFormat::LittleEndian);
- //设置样本数据类型
- audioFormat.setSampleType(QAudioFormat::UnSignedInt);
- //获取设备信息
- QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
- if (!info.isFormatSupported(audioFormat))
- {
- qDebug()<<"default format not supported try to use nearest";
- audioFormat = info.nearestFormat(audioFormat);
- }
- info = QAudioDeviceInfo::defaultOutputDevice();
- if (!info.isFormatSupported(audioFormat)) {
- qDebug()<<"default format not supported try to use nearest";
- audioFormat = info.nearestFormat(audioFormat);
- }
- audioInput = new QAudioInput(audioFormat, this);
- //将麦克风的音频数据传输到输入设备
- streamIn = audioInput->start();
- //当输入设备检测到数据时,调用槽函数slogReadData
- connect(streamIn, SIGNAL(readyRead()), SLOT(slogReadData()));
- audioOutput = new QAudioOutput(audioFormat, this);
- //将音频数据传输到输出设备,再由输出设备写入到扬声器
- streamOut = audioOutput->start();
- //创建UDP线程
- CUdpThread *udpThread=new CUdpThread();
- udpThreadFather=new QThread();
- udpThread->moveToThread(udpThreadFather);
- connect(udpThreadFather,SIGNAL(started()),udpThread,SLOT(run()));
- //启动线程
- udpThreadFather->start();
- connect(this,SIGNAL(signalSendData(const QByteArray &)),udpThread,SLOT(slotSendData(const QByteArray &)));
- connect(udpThread,SIGNAL(signalSendData(const QByteArray &)),this,SLOT(slotSendData(const QByteArray &)));
- }
2.编码发送
- void Widget::slogReadData()
- {
- short srcAudio[L_FRAME]={0};
- unsigned char dstAudio[L_FRAME_COMPRESSED]={'\0'};
- if (!audioInput)
- {
- qDebug() << "AudioInput Error";
- return;
- }
- QByteArray dataBuffer(BUFFER_SIZE,0);
- qint64 len1 = audioInput->bytesReady();
- if (len1 > BUFFER_SIZE)
- {
- qDebug()<<"BUFFER_SIZE too small";
- return;
- }
- qint64 len2 = streamIn->read(dataBuffer.data(), len1);
- tempBuffer.append(dataBuffer.data(),len2);
- for(int i=0;i<tempBuffer.length()/(L_FRAME*2);i++)
- {
- //char转short
- memcpy(srcAudio,tempBuffer.data()+i*L_FRAME*2,L_FRAME*2);
- //编码
- cg729Encoder.encode(srcAudio, dstAudio);
- QByteArray frame;
- //reinterpret_cast用于强制转换,这里将unsigned char *转换为const char *。
- frame.append(reinterpret_cast<const char*>(dstAudio),L_FRAME_COMPRESSED);
- signalSendData(frame);
- }
- tempBuffer.clear();
- }
3.接收解码
- void Widget::slotSendData(const QByteArray &byte_array)
- {
- for(int i=0;i<byte_array.length()/L_FRAME_COMPRESSED;i++)
- {
- unsigned char srcAudio[L_FRAME_COMPRESSED]={'\0'};
- short dstAudio[L_FRAME]={0};
- memcpy(srcAudio,(unsigned char*)byte_array.data()+i * L_FRAME_COMPRESSED,L_FRAME_COMPRESSED);
- //G729解码
- cg729Decoder.decode(srcAudio,dstAudio,0);
- //short转char
- tempframe.append((char *)dstAudio,L_FRAME * 2);
- if(audioOutput&&audioOutput->state()!=QAudio::StoppedState&&
- audioOutput->state()!=QAudio::SuspendedState)
- {
- int chunks = audioOutput->bytesFree()/audioOutput->periodSize();
- while (chunks)
- {
- if (tempframe.length() >= audioOutput->periodSize())
- {
- //写入到扬声器
- streamOut->write(tempframe.data(),audioOutput->periodSize());
- tempframe = tempframe.mid(audioOutput->periodSize());
- }
- else
- {
- //写入到扬声器
- streamOut->write(tempframe);
- tempframe.clear();
- break;
- }
- --chunks;
- }
- }
- }
- }
三、演示效果
程序启动后,麦克风就开始工作了,聊天双方指定目的IP后,点击按钮1就可以进行聊天。如果不想对方听到自己的声音,点击按钮2关闭声音发送。
参考链接:https://en.wikipedia.org/wiki/G.729
参考链接:http://blog.csdn.net/jdh99/article/details/39525451
源码链接:见http://blog.csdn.net/caoshangpa/article/details/51225733的评论
http://blog.csdn.net/caoshangpa/article/details/51225733
Qt实现基于G.729A(G729A)的语音聊天的更多相关文章
- C#基于UDP实现的P2P语音聊天工具(1)
这篇文章主要是一个应用,使用udp传送语音和文本等信息.在这个系统中没有服务端和客户端,相互通讯都是直接相互联系的.能够很好的实现效果. 语音获取 要想发送语音信息,首先得获取语音,这里有几种方法,一 ...
- c#基于udp实现的p2p语音聊天工具
原创性申明 此博文的出处 为 http://blog.csdn.net/zhujunxxxxx/article/details/40124773假设进行转载请注明出处.本文作者原创,邮箱zhujunx ...
- qt中采用宽带speex进行网络语音通话实验程序
qt中采用宽带speex进行网络语音通话实验程序 本文博客链接:http://blog.csdn.NET/jdh99,作者:jdh,转载请注明. 环境: 主机:WIN8 开发环境:Qt5 3.1. ...
- 基于C#局域网语音聊天
基于C#局域网语音聊天室,可实现文本消息的发送.接收及语音聊天,是一个很不错的,适合初学者的软件开发 ...
- 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)
搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ...
- C#实现多人语音聊天
在上一篇文章 实现一个简单的语音聊天室(多人语音聊天系统)中,我用C#实现了一个简单的语音聊天室,并给出了源代码下载.尽管有源代码,可是非常多朋友反映,理解起来还是有些模糊.不够清楚.如今想来,是由于 ...
- 基于Android Classic Bluetooth的蓝牙聊天软件
代码地址如下:http://www.demodashi.com/demo/12133.html BluetoothChat 基于Android Classic Bluetooth的蓝牙聊天软件,目前仅 ...
- Android 高仿微信语音聊天页面高斯模糊效果
目前的应用市场上,使用毛玻璃效果的APP随处可见,比如用过微信语音聊天的人可以发现,语音聊天页面就使用了高斯模糊效果. 先看下效果图: 仔细观察上图,我们可以发现,背景图以用户头像为模板,对其进行了高 ...
- C# 实现语音聊天
一.语音聊天说专业点就是即时语音,是一种基于网络的快速传递语音信息的技术,普遍应用于各类社交软件中,优势主要有以下几点: (1)时效性:视频直播会因为带宽问题有时出现延迟高的问题,而语音直播相对来说会 ...
随机推荐
- Delphi 函数指针(函数可以当参数)
首先学习: 指向非对象(一般的)函数/过程的函数指针 Pascal 中的过程类型与C语言中的函数指针相似,为了统一说法,以下称函数指针.函数指针的声明只需要参数列表:如果是函数,再加个返回值.例如声明 ...
- 针对portmap 的DDOS攻击
iptables -I INPUT -p tcp --dport 111 -j DROP iptables -I INPUT -s 10.171.254.221 -p tcp --dport 111 ...
- linq 跨库查询
可以用多个DBContext,例如有DBContext1和DBContext2,但是不能将两个DBContext用在同一个查询中,可以分开,先用一个查出结果集1,再在第二个查询中使用结果集1就可以了
- Struct2(五)处理表单
简介: 1.表单的提交 表单和对应的Java模型的类 在此次的例子中,我们将会模仿一个用户提交表单的动作,具体提交表单做什么,不关心,我们需要知道 first last Name,Email addr ...
- Fence Repair (POJ 3253)
农夫约翰为了修理栅栏,要将一块很长的木板切割成N块.准备切成的木板长度为L1.L2.L3...LN,未切割前的木板长度恰好为切割后木板长度的总和.每次切断木板时,需要的开销为这块木板的长度.例如长度为 ...
- Lucene 高亮功能
原文转载自: http://qindongliang1922.iteye.com/blog/1953409 高亮功能一直都是全文检索的一项非常优秀的模块,在一个标准的搜索引擎中,高亮的返回命中结果,几 ...
- 第21/22讲 UI_布局 之 线性布局
第21/22讲 UI_布局 之 线性布局 布局管理就是组件在activity中呈现方式,包括组件的大小,间距和对齐方式等. Android提供了两种布局的实现方式: 1.在xml配置文件中声明:这种方 ...
- Mysql--mysqldump命令 备份数据库
mysqldump命令用来备份数据库. mysqldump命令在DOS的[url=file://\\mysql\\bin]\\mysql\\bin[/url]目录下执行. 1) 导出整个数据库(导出文 ...
- std::remove_if
原型: #include <algorithm>forward_iterator remove_if( forward_iterator start, forward_iterator e ...
- openMP的一点使用经验
最近在看多核编程.简单来说,由于现在电脑CPU一般都有两个核,4核与8核的CPU也逐渐走入了寻常百姓家,传统的单线程编程方式难以发挥多核CPU的强大功能,于是多核编程应运而生.按照我的理解,多核编程可 ...