您的位置:首页 > 其它

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_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 <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的评论



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息