WebRTC学习之四:最简单的语音聊天
2016-12-26 21:18
381 查看
VoiceEngine中与最简单语音聊天相关的头文件有五个,如下表所示:
头文件 | 包含的类 | 说明 |
voe_base.h | VoiceEngineObserver VoiceEngine VoEBase | 1.默认使用G.711通过RTP进行全双工的VoIP会话 2.初始化和终止 3.通过文件和回调函数跟踪信息 4.多通道支持(比如混合,发送到多个目的端) 5.如果想支持G.711外的编码,需要VoECodec |
voe_errors.h | 无 | 一些VoiceEngine相关错误的定义 |
voe_network.h | VoENetwork | 1.扩展协议支持 2.数据包超时提示 3.监测连接是否断开 |
voe_hardware.h | VoEHardware | 1.管理音频设备 2.获取设备信息 3.采样率设置 |
voe_volume_control.h | VoEVolumeControl | 1.扬声器音量控制 2.麦克风音量控制 3.非线性语音电平控制 4.静音 5.音量放大 |
参考上篇:WebRTC学习之三:录音和播放
二.实现
VoiceEngine中并未明确指定网络通信协议,因此仅仅通过调用VoiceEngine的API是不能实现语音聊天的。VoENetwork中提供了方法RegisterExternalTransport(int channel, Transport& transport),通过它可以为通道channnel指定用户自定义的传输协议transport。因此我们只需要子类化Transport,并在类中实现某种通信协议即可。Transport类在transport.h中,transport.h如下所示。
#ifndef WEBRTC_TRANSPORT_H_ #define WEBRTC_TRANSPORT_H_ #include <stddef.h> #include "webrtc/typedefs.h" namespace webrtc { // TODO(holmer): Look into unifying this with the PacketOptions in // asyncpacketsocket.h. struct PacketOptions { // A 16 bits positive id. Negative ids are invalid and should be interpreted // as packet_id not being set. int packet_id = -1; }; class Transport { public: virtual bool SendRtp(const uint8_t* packet, size_t length, const PacketOptions& options) = 0; virtual bool SendRtcp(const uint8_t* packet, size_t length) = 0; protected: virtual ~Transport() {} }; } // namespace webrtc #endif // WEBRTC_TRANSPORT_H_Transport类中只要两个纯虚函数,我们要去实现它们,并在它们的实现中调用通信协议的发送函数将数据发送出去。需要注意的是RTP和RTCP报文是通过不同的端口来传输的。
下面是我的Tranport子类MyTransport。
mytransport.h
#ifndef MYTRANSPORT_H #define MYTRANSPORT_H #include <QUdpSocket> #include "webrtc/transport.h" using namespace webrtc; class MyTransport:public QObject,public Transport { Q_OBJECT public: MyTransport(); ~MyTransport(); void setLocalReceiver(int port); void stopRecieve(); void setSendDestination(QString ip, int port); void stopSend(); // Transport functions override bool SendRtp(const uint8_t* packet,size_t length,const PacketOptions& options) override; bool SendRtcp(const uint8_t* packet, size_t length) override; private: QUdpSocket * udpsocketSendRTP; QUdpSocket * udpsocketSendRTCP; QUdpSocket * udpSocketRecvRTP; QUdpSocket * udpSocketRecvRTCP; QString destIP; int destPort; bool sendFlag; bool recvFlag; signals: void signalRecvRTPData(char *data,int length); void signalRecvRTCPData(char *data,int length); void signalSendRTPData(char *data,int length); void signalSendRTCPData(char *data,int length); private slots: void slotRTPReadPendingDatagrams(); void slotRTCPReadPendingDatagrams(); void slotSendRTPData(char *data,int length); void slotSendRTCPData(char *data,int length); }; #endif // MYTRANSPORT_Hmytransport.cpp
#include "mytransport.h" #include "QDebug" MyTransport::MyTransport() :destIP(""), destPort(0), sendFlag(true), recvFlag(true) { udpsocketSendRTP=new QUdpSocket(); udpSocketRecvRTP = new QUdpSocket(); udpsocketSendRTCP=new QUdpSocket(); udpSocketRecvRTCP = new QUdpSocket(); connect(udpSocketRecvRTP, SIGNAL(readyRead()), this, SLOT(slotRTPReadPendingDatagrams())); connect(udpSocketRecvRTCP, SIGNAL(readyRead()), this, SLOT(slotRTCPReadPendingDatagrams())); connect(this,SIGNAL(signalSendRTPData(char *,int)),this,SLOT(slotSendRTPData(char *,int))); connect(this,SIGNAL(signalSendRTCPData(char *,int)),this,SLOT(slotSendRTCPData(char *,int))); } MyTransport::~MyTransport() { udpsocketSendRTP->deleteLater(); udpSocketRecvRTP->deleteLater(); udpsocketSendRTCP->deleteLater(); udpSocketRecvRTCP->deleteLater(); } void MyTransport::setLocalReceiver(int port) { udpSocketRecvRTP->bind(port, QUdpSocket::ShareAddress); udpSocketRecvRTCP->bind(port+1, QUdpSocket::ShareAddress); recvFlag=true; } void MyTransport::stopRecieve() { udpSocketRecvRTP->abort(); udpSocketRecvRTCP->abort(); recvFlag=false; } void MyTransport::setSendDestination(QString ip, int port) { destIP=ip; destPort=port; sendFlag=true; } void MyTransport::stopSend() { sendFlag=false; } //为何不直接调用udpsocketSendRTP->writeDatagram,而用信号,是因为SendRtp在另一个线程里 bool MyTransport::SendRtp(const uint8_t* packet,size_t length,const PacketOptions& options) { Q_UNUSED(options); if(sendFlag) emit signalSendRTPData((char*)packet,length); return true; } //为何不直接调用udpsocketSendRTCP->writeDatagram,而用信号,是因为SendRtcp在另一个线程里 bool MyTransport::SendRtcp(const uint8_t* packet, size_t length) { if(sendFlag) emit signalSendRTCPData((char*)packet,length); return true; } void MyTransport::slotSendRTPData(char *data,int length) { udpsocketSendRTP->writeDatagram(data, length,QHostAddress(destIP), destPort); } //RTCP端口为RTP端口+1 void MyTransport::slotSendRTCPData(char *data,int length) { udpsocketSendRTCP->writeDatagram(data, length,QHostAddress(destIP), destPort+1); } void MyTransport::slotRTPReadPendingDatagrams() { QByteArray datagram; while (udpSocketRecvRTP->hasPendingDatagrams()&&recvFlag) { datagram.resize(udpSocketRecvRTP->pendingDatagramSize()); QHostAddress sender; quint16 senderPort; int size=udpSocketRecvRTP->readDatagram( datagram.data(), datagram.size(), &sender, &senderPort); if(size>0) { emit signalRecvRTPData(datagram.data(),datagram.size()); } } } void MyTransport::slotRTCPReadPendingDatagrams() { QByteArray datagram; while (udpSocketRecvRTCP->hasPendingDatagrams()&&recvFlag) { datagram.resize(udpSocketRecvRTCP->pendingDatagramSize()); QHostAddress sender; quint16 senderPort; int size=udpSocketRecvRTCP->readDatagram( datagram.data(), datagram.size(), &sender, &senderPort); if(size>0) { emit signalRecvRTCPData(datagram.data(),datagram.size()); } } }
然后实例化MyTranspot类,并传入RegisterExternalTransport(int channel, Transport& transport)。
上面是发送数据的过程,如果要接收数据,可以将QUdpSocket接收到的数据传递给VoENetwork中的ReceivedRTPPacket和ReceivedRTPPacket方法。当使用自定义传输协议时,从网络中接收到的数据,必须传递给这两个方法。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QThread>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
error(0),
audioChannel(0),
ptrVoEngine(NULL),
ptrVoEBase(NULL),
ptrVoEVolumeControl(NULL),
ptrVoENetwork(NULL),
ptrVoEHardware(NULL)
{
ui->setupUi(this);
creatVoiceEngine();
initialVoiceEngine();
setDevice();
setChannel();
setNetwork();
connect(ui->horizontalSliderMicrophoneVolume,SIGNAL(valueChanged(int)),this,SLOT(slotSetMicrophoneVolumeValue(int)));
connect(ui->horizontalSliderSpeakerVolume,SIGNAL(valueChanged(int)),this,SLOT(slotSetSpeakerVolumeValue(int)));
int vol=getMicrophoneVolumeValue();
ui->horizontalSliderMicrophoneVolume->setValue(vol);
ui->lineEditMicrophoneVolumeValue->setText(QString::number(vol));
vol=getSpeakerVolumeValue();
ui->horizontalSliderSpeakerVolume->setValue(vol);
ui->lineEditSpeakerVolumeValue->setText(QString::number(vol));
}
MainWindow::~MainWindow()
{
delete ui;
unInitialVoiceEngine();
}
void MainWindow::creatVoiceEngine()
{
ptrVoEngine = VoiceEngine::Create();
ptrVoEBase = VoEBase::GetInterface(ptrVoEngine);
ptrVoEVolumeControl = VoEVolumeControl::GetInterface(ptrVoEngine);
ptrVoEHardware = VoEHardware::GetInterface(ptrVoEngine);
ptrVoENetwork= VoENetwork::GetInterface(ptrVoEngine);
}
int MainWindow::initialVoiceEngine()
{
error = ptrVoEBase->Init();
if (error != 0)
{
qDebug()<<"ERROR in VoEBase::Init";
return error;
}
error = ptrVoEBase->RegisterVoiceEngineObserver(myObserver);
if (error != 0)
{
qDebug()<<"ERROR in VoEBase:;RegisterVoiceEngineObserver";
return error;
}
char temp[1024];
error = ptrVoEBase->GetVersion(temp);
if (error != 0)
{
qDebug()<<"ERROR in VoEBase::GetVersion";
return error;
}
ui->lineEditVersion->setText(QString(temp));
return 100;
}
int MainWindow::unInitialVoiceEngine()
{
//Stop Playout
error = ptrVoEBase->StopPlayout(audioChannel);
if (error != 0)
{
qDebug()<<"ERROR in VoEBase::StopPlayout";
return error;
}
//DeRegister
error = ptrVoENetwork->DeRegisterExternalTransport(audioChannel);
if (error != 0)
{
qDebug()<<"ERROR in VoENetwork::DeRegisterExternalTransport";
return error;
}
//Delete Channel
error = ptrVoEBase->DeleteChannel(audioChannel);
if (error != 0)
{
qDebug()<<"ERROR in VoEBase::DeleteChannel";
return error;
}
//DeRegister observer
ptrVoEBase->DeRegisterVoiceEngineObserver();
error = ptrVoEBase->Terminate();
if (error != 0)
{
qDebug()<<"ERROR in VoEBase::Terminate";
return error;
}
if(ptrVoEBase)
{
ptrVoEBase->Release();
}
if(ptrVoEVolumeControl)
{
ptrVoEVolumeControl->Release();
}
if(ptrVoENetwork)
{
ptrVoENetwork->Release();
}
if(ptrVoEHardware)
{
ptrVoEHardware->Release();
}
bool flag = VoiceEngine::Delete(ptrVoEngine);
if (!flag)
{
qDebug()<<"ERROR in VoiceEngine::Delete";
return -1;
}
return 100;
}
int MainWindow::setDevice()
{
int rNum(-1), pNum(-1);
error = ptrVoEHardware->GetNumOfRecordingDevices(rNum);
if (error != 0)
{
qDebug()<<"ERROR in VoEHardware::GetNumOfRecordingDevices";
return error;
}
error = ptrVoEHardware->GetNumOfPlayoutDevices(pNum);
if (error != 0)
{
qDebug()<<"ERROR in VoEHardware::GetNumOfPlayoutDevices";
return error;
}
char name[128] = { 0 };
char guid[128] = { 0 };
for (int j = 0; j < rNum; ++j)
{
error = ptrVoEHardware->GetRecordingDeviceName(j, name, guid);
if (error != 0)
{
qDebug()<<"ERROR in VoEHardware::GetRecordingDeviceName";
return error;
}
ui->comboBoxRecordingDevice->addItem(QString(name));
}
for (int j = 0; j < pNum; ++j)
{
error = ptrVoEHardware->GetPlayoutDeviceName(j, name, guid);
if (error != 0)
{
qDebug()<<"ERROR in VoEHardware::GetPlayoutDeviceName";
return error;
}
ui->comboBoxPlayoutDevice->addItem(QString(name));
}
error = ptrVoEHardware->SetRecordingDevice(ui->comboBoxRecordingDevice->currentIndex());
if (error != 0)
{
qDebug()<<"ERROR in VoEHardware::SetRecordingDevice";
return error;
}
error = ptrVoEHardware->SetPlayoutDevice(ui->comboBoxPlayoutDevice->currentIndex());
if (error != 0)
{
qDebug()<<"ERROR in VoEHardware::SetPlayoutDevice";
return error;
}
return 100;
}
void MainWindow::setChannel()
{
audioChannel = ptrVoEBase->CreateChannel();
if (audioChannel < 0)
{
qDebug()<<"ERROR in VoEBase::CreateChannel";
}
//允许接收
error = ptrVoEBase->StartReceive(audioChannel);
if(error != 0)
{
qDebug()<<"ERROR in VoEBase::StartReceive";
}
//允许播放
error = ptrVoEBase->StartPlayout(audioChannel);
if(error != 0)
{
qDebug()<<"ERROR in VoEBase::StartPlayout";
}
//允许发送
error = ptrVoEBase->StartSend(audioChannel);
if(error != 0)
{
qDebug()<<"ERROR in VoEBase::StartSend=";
}
}
void MainWindow::setNetwork()
{
myTransport = new MyTransport();
error = ptrVoENetwork->RegisterExternalTransport(audioChannel,*myTransport);
if (error != 0)
{
qDebug()<<"ERROR in VoEHardware::RegisterExternalTransport";
}
connect(myTransport,SIGNAL(signalRecvRTPData(char*,int)),this,SLOT(slotRecvRTPData(char*,int)));
connect(myTransport,SIGNAL(signalRecvRTCPData(char*,int)),this,SLOT(slotRecvRTCPData(char*,int)));
}
int MainWindow::getMicrophoneVolumeValue()
{
unsigned int vol = 999;
error = ptrVoEVolumeControl->GetMicVolume(vol);
if (error != 0)
{
qDebug()<<"ERROR in VoEVolume::GetMicVolume";
return 0;
}
if ((vol > 255) || (vol < 0))
{
qDebug()<<"ERROR in GetMicVolume";
return 0;
}
return vol;
}
int MainWindow::getSpeakerVolumeValue()
{
unsigned int vol = 999;
error = ptrVoEVolumeControl->GetSpeakerVolume(vol);
if (error != 0)
{
qDebug()<<"ERROR in VoEVolume::GetSpeakerVolume";
return 0;
}
if ((vol > 255) || (vol < 0))
{
qDebug()<<"ERROR in GetSpeakerVolume";
return 0;
}
return vol;
}
void MainWindow::slotSetMicrophoneVolumeValue(int value)
{
error = ptrVoEVolumeControl->SetMicVolume(value);
if (error != 0)
{
qDebug()<<"ERROR in VoEVolume::SetMicVolume";
}
else
{
ui->lineEditMicrophoneVolumeValue->setText(QString::number(value));
}
}
void MainWindow::slotSetSpeakerVolumeValue(int value)
{
error = ptrVoEVolumeControl->SetSpeakerVolume(value);
if (error != 0)
{
qDebug()<<"ERROR in VoEVolume::SetSpeakerVolume";
}
else
{
ui->lineEditSpeakerVolumeValue->setText(QString::number(value));
}
}
void MainWindow::slotRecvRTPData(char *data,int length)
{
ptrVoENetwork->ReceivedRTPPacket(audioChannel, data, length, PacketTime());
}
void MainWindow::slotRecvRTCPData(char *data,int length)
{
ptrVoENetwork->ReceivedRTCPPacket(audioChannel, data,length);
}
void MainWindow::on_pushButtonSend_clicked()
{
static bool flag=true;
if(flag)
{
myTransport->setSendDestination(ui->lineEditDestIP->text(),ui->lineEditDestPort->text().toInt());
ui->pushButtonSend->setText(QStringLiteral("停止"));
}
else
{
myTransport->stopSend();
ui->pushButtonSend->setText(QStringLiteral("开始"));
}
flag=!flag;
}
void MainWindow::on_pushButtonReceive_clicked()
{
static bool flag=true;
if(flag)
{
myTransport->setLocalReceiver(ui->lineEditLocalPort->text().toInt());
ui->pushButtonReceive->setText(QStringLiteral("停止"));
}
else
{
myTransport->stopRecieve();
ui->pushButtonReceive->setText(QStringLiteral("开始"));
}
flag=!flag;
}
三.效果
源码链接:见http://blog.csdn.net/caoshangpa/article/details/53889057的评论
相关文章推荐
- WebRTC学习之十:最简单的视频聊天(使用WebRtcVideoEngine2)
- 多人语音聊天问题简单说说
- DWR构建的简单聊天程序-供初学者学习
- IOS开发之使用Speex格式实现简单的语音聊天功能(二)
- Android开发学习之路--UI之简单聊天界面
- Android开发学习之路--UI之简单聊天界面
- iPhone网络编程初体验-简单的聊天程序(适合新手学习客户端服务器交互)
- 基于Bmob服务的简单聊天(另加语音通知)
- Qt学习心得之网络编程简单的局域网聊天服务端建立
- Android开发学习之路--UI之简单聊天界面
- android学习--语音聊天一(音频的录制)
- 实现一个简单的语音聊天室(多人语音聊天系统)
- IOS开发之使用Speex格式实现简单的语音聊天功能(二)
- IOS开发之使用Speex格式实现简单的语音聊天功能(一)
- android学习--语音聊天二(发送与接收)
- IOS开发之使用Speex格式实现简单的语音聊天功能(一)
- 【C#学习】聊天机器人,网络数据的简单应用
- c#网络编程学习笔记02_Tcp编程(中)_简单的同步tcp聊天程序
- [Nodejs]初探nodejs学习笔记- 如何使用nodejs搭建简单的UDP聊天功能