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的評論