天天看点

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学习之四:最简单的语音聊天

继续阅读