天天看點

WebRTC學習之四:最簡單的語音聊天

WebRTC學習之四:最簡單的語音聊天

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;
}
           

三.效果

WebRTC學習之四:最簡單的語音聊天

源碼連結:見http://blog.csdn.net/caoshangpa/article/details/53889057的評論

WebRTC學習之四:最簡單的語音聊天

繼續閱讀