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
#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报文是通过不同的端口来传输的。mytransport.h
#ifndef MYTRANSPORT_H
#define MYTRANSPORT_H
#include
#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_H
mytransport.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
#include
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的评论