Qt实现基于G.729A(G729A)的语音聊天
2016-07-26 05:15
621 查看
一、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.初始化
[cpp] view plain copy
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.编码发送
[cpp] view plain copy
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.接收解码
[cpp] view plain copy
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
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.初始化
[cpp] view plain copy
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.编码发送
[cpp] view plain copy
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.接收解码
[cpp] view plain copy
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
相关文章推荐
- Deepin系统安装QT5
- pyqt5按钮计数
- QGrapicsItem类
- QT第六天学习
- 【QT小贴士】删除QListWidget中的某项
- QT之文件对话框
- 本类的静态指针
- Qt共享内存实现进程间通信(QSharedMemory)
- QT 控件示例收集
- Qt中的动态布局(Dynamic Layout)
- Qt之属性系统
- Qt之属性系统
- Qt之属性系统
- Qt浅谈之十八:GraphicsView框架事件处理
- pyqt5+eric6 openfile
- Qt中调用OpenCV函数库时Crashed问题的解决。
- QT如何将QlineEdit里的QString字符串存储到unsigned char 数组中
- QFile Class(Qt官方文档译)
- Qt应用中检测内存泄露——VLD
- 商品管理系统(QT+数据库)