webrtc代码走读十四(ice rfc5245 实现)

一、架构图

 

webrtc的ICE符合rfc5245标准协议。该协议的链接为:https://tools.ietf.org/html/rfc5245

每个终端都有一系列传输地址(包括传输协议、IP地址和端口)的候选,可以用来和其他端点进行通信。其中可能包括:
1、直接和网络接口联系的传输地址(host address)
2、经过NAT转换的传输地址,即反射地址(server reflective address)
3、TURN服务器分配的中继地址(relay address)

虽然潜在要求任意一个主叫Client的候选地址都能用来和被叫Client的候选地址进行通信。但是实际中发现有许多组合是无法工作的。举例来说,如果主叫Client和被叫Client都在NAT之后而且不处于同一内网,他们的直接地址就无法进行通信。ICE的目的就是为了发现哪一对候选地址的组合可以工作,并且通过系统的方法对所有组合进行测试(用一种精心挑选的顺序)。

在rfc5245定义里面,实现ICE一共有六个步骤:收集候选地址、连通性测试、候选排序、冻结候选、检查的安全性、ICE结束。webrtc结合实际应用,优化这六步为如下五步:收集候选地址、在信令通道中交换候选选项、执行连接检查、选定并启动媒体。

1、实现收集候选地址核心函数:BasicPortAllocatorSession::DoAllocate

2、在信令通道中交换候选选项:发送本端的候选选项核心函数:AllocationSequence::EnableProtocol

                                                 接收远端的候选选项核心函数:PeerConnection::AddIceCandidate

3、执行连接检查核心函数: P2PTransportChannel::SortConnectionsAndUpdateState

4、选定并启动媒体核心函数:P2PTransportChannel::OnReadyToSend

                                             过程中检测有变化时:P2PTransportChannel::SwitchSelectedConnection

二、处理流程

一、收集候选地址

1、获取turn、stun服务器信息处理流程

Conductor::InitializePeerConnection

->Conductor::CreatePeerConnection
->PeerConnectionFactory::CreatePeerConnection
->PeerConnection::Initialize
->PeerConnection::InitializePortAllocator_n//这个函数解析配置信息,确定探测类型及参数。

->PortAllocator::SetConfiguration//这里配置服务器信息。

2、生效turn、stun服务器处理流程

WebRtcSessionDescriptionFactory::OnMessage

->Conductor::OnSuccess
->PeerConnection::SetLocalDescription   (session_->MaybeStartGathering())
->WebRtcSession::MaybeStartGathering
->TransportController::MaybeStartGathering
->TransportController::MaybeStartGathering_n
->P2PTransportChannel::MaybeStartGathering
AddAllocatorSession(allocator_->CreateSession());//注册信号量
allocator_sessions_.back()->StartGettingPorts(); //启动Port资源申请消息

当P2PTransportChannel::MaybeStartGathering函数调用BasicPortAllocatorSession::StartGettingPorts后,就会启动MSG_CONFIG_START消息处理流程。开始收集本端的candidates信息。

3、Port资源申请状态机

enum {
  MSG_CONFIG_START,
  MSG_CONFIG_READY,
  MSG_ALLOCATE,
  MSG_ALLOCATION_PHASE,
  MSG_SEQUENCEOBJECTS_CREATED,
  MSG_CONFIG_STOP,
};

4、Port状态机接口函数

void BasicPortAllocatorSession::OnMessage(rtc::Message *message) {
  switch (message->message_id) {
  case MSG_CONFIG_START:
    GetPortConfigurations();
    break;
  case MSG_CONFIG_READY:
    OnConfigReady(static_cast<PortConfiguration*>(message->pdata));
    break;
  case MSG_ALLOCATE:
    OnAllocate();
    break;
  case MSG_SEQUENCEOBJECTS_CREATED:
    OnAllocationSequenceObjectsCreated();
    break;
  case MSG_CONFIG_STOP:
    OnConfigStop();
    break;
  default:
    RTC_NOTREACHED();
  }
}

1、MSG_CONFIG_START:申请stun配置文件信息,在配置文件中增加turn信息。配置OK后 ,向消息队列发送ready消息。
2、MSG_CONFIG_READY:push在START里面创建的配置信息到configs_,向消息队列发送MSG_ALLOCATE消息。
3、MSG_ALLOCATE:在这里会创建Port资源,收集candidates信息。
AllocationSequence* sequence =new AllocationSequence();//申请AllocationSequence类
sequence->SignalPortAllocationComplete.connect();//注册信号量
sequence->Init();//启动给自己发送MSG_ALLOCATION_PHASE消息队列
sequence->Start();
sequences_.push_back(sequence);
4、MSG_ALLOCATION_PHASE:Port申请处理流程

void AllocationSequence::OnMessage(rtc::Message* msg) {
  RTC_DCHECK(msg->message_id == MSG_ALLOCATION_PHASE);
  // Perform all of the phases in the current step.
  switch (phase_) {
    case PHASE_UDP:
      CreateUDPPorts();//申请udp port资源
      CreateStunPorts();//申请 stun port资源
      EnableProtocol(PROTO_UDP);
      break;
    case PHASE_RELAY:
      CreateRelayPorts();//申请GTURN或TURN port资源
      break;
    case PHASE_TCP:
      CreateTCPPorts();//申请TCP Port资源
      EnableProtocol(PROTO_TCP);
      break;
    case PHASE_SSLTCP:
      state_ = kCompleted;//不需要继续探测,停止给自己发MSG_ALLOCATION_PHASE消息
      EnableProtocol(PROTO_SSLTCP);
      break;
  }
  if (state() == kRunning) {
    ++phase_;//更新探测阶段
    session_->network_thread()->PostDelayed(RTC_FROM_HERE,MSG_ALLOCATION_PHASE);//给自己发消息。
  } else {
    session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE);//清除给自己发消息资源
    SignalPortAllocationComplete(this);//发送信号量,知会需要探测的Port资源都已经申请完毕。
  }
}

5、发送candidates信息给对端

每次调用EnableProtocol函数,就是将本地的Candidates信息,发送给对端的Client
AllocationSequence::EnableProtocol
->BasicPortAllocatorSession::OnProtocolEnabled
->SignalCandidatesReady
->P2PTransportChannel::OnCandidatesReady
->SignalCandidateGathered
->TransportController::OnChannelCandidateGathered_n
->SignalCandidatesGathered
->WebRtcSession::OnTransportControllerCandidatesGathered
->Conductor::OnIceCandidate
->Conductor::SendMessage
->PeerConnectionClient::SendToPeer
Port资源申请结束后,通过信号量传递该信息:
SignalPortAllocationComplete信号量触发调用
BasicPortAllocatorSession::OnPortAllocationComplete

二、接收对端候选选项

当主叫Client获得了它全部的候选地址,把它们按优先级从高到低排序后通过信令通道(SDP offer)发送给被叫Client。被叫Client收到后做同样的事,然后通过响应发给主叫Client。最后,每个agent都拥有了自己和对方的候选地址。它们共同组成CANDIDATE PAIRS(候选地址对)。client通过CANDIDATE PAIRS发送给对端STUN request来检查它们。

1、处理HTTP的POST/message里面的SDP信令

Conductor::OnMessageFromPeer

->PeerConnection::SetRemoteDescription
->WebRtcSession::SetRemoteDescription
->WebRtcSession::UpdateSessionState
->WebRtcSession::PushdownTransportDescription
->WebRtcSession::PushdownRemoteTransportDescription
->TransportController::SetRemoteTransportDescription
->TransportController::SetRemoteTransportDescription_n
->JsepTransport::SetRemoteTransportDescription
->JsepTransport::ApplyRemoteTransportDescription
->P2PTransportChannel::SetRemoteIceParameters

->P2PTransportChannel::RequestSortAndStateUpdate

->P2PTransportChannel::SortConnectionsAndUpdateState

2、处理HTTP的POST/message里面的Candidate信令

Conductor::OnMessageFromPeer

->PeerConnection::AddIceCandidate
->WebRtcSession::ProcessIceMessage
->WebRtcSession::UseCandidate
->TransportController::AddRemoteCandidates
->TransportController::AddRemoteCandidates_n
->P2PTransportChannel::AddRemoteCandidate
->P2PTransportChannel::SortConnectionsAndUpdateState
->P2PTransportChannel::MaybeSwitchSelectedConnection
->P2PTransportChannel::SwitchSelectedConnection

    SignalReadyToSend  SignalSelectedCandidatePairChanged

三、执行连接检查

可以参考该状态机。当连接检查失败,会启动重新收集candidate信息。

// messages for queuing up work for ourselves
enum {
  MSG_SORT_AND_UPDATE_STATE = 1,
  MSG_CHECK_AND_PING,
  MSG_REGATHER_ON_FAILED_NETWORKS,
  MSG_REGATHER_ON_ALL_NETWORKS
};

四、选择选定的对并启动媒体

进行探测,收到对端的报文的时候,就会发送该信号量,选定传输方式。

AsyncUDPSocket::OnReadEvent发送信号量SignalReadPacket

->Thread::Start
->Thread::PreRun
->Thread::Run
->Thread::ProcessMessages
->MessageQueue::Get
->PhysicalSocketServer::Wait
->SocketDispatcher::OnEvent
->AsyncUDPSocket::OnReadEvent
->AllocationSequence::OnReadPacket
->UDPPort::HandleIncomingPacket
->UDPPort::OnReadPacket
->Connection::OnReadPacket
->StunRequestManager::CheckResponse
->ConnectionRequest::OnResponse
->Connection::OnConnectionRequestResponse
::Connection::ReceivedPingResponse[set_write_state(STATE_WRITABLE)]
::Connection::MaybeUpdateLocalCandidate[SignalStateChange]

UpdateNetworkCost->SignalStateChange:

在多网卡时,对wifi、4G、vpn等类型设置不同权重值。当检测出权重高的网络可以通的时候,进行切换。

set_connected->SignalStateChange   TCP链接成功后,会发送这个信号。
UpdateReceiving->SignalStateChange
HandleBindingRequest->SignalStateChange

五、心跳检测

webrtc进行ICE探测时,会在不稳定链接时,持续发送ping心跳报文,当检测链接已经稳定后,就不再发心跳报文,也不会探测切换传输路径。

链接是否稳定是在weak函数里面判断的。要是服务器资源宽裕的话,可以把weak修改问false。整个通话期间持续探测。可以做到主备倒换的目的

三、附加说明

webrtc会每隔两秒,检查一下网络状态是否有变化,若是有变化,就重新探测网络,选择其他的传输方式。所以会概率出现一会P2P一会中转的现象。

函数调用关系如下:

Network::Network
BasicNetworkManager::CreateNetworks
BasicNetworkManager::UpdateNetworksOnce
BasicNetworkManager::UpdateNetworksContinually
BasicNetworkManager::OnMessage
MessageQueue::Dispatch
Thread::ProcessMessages
Thread::Run
Thread::PreRun

来源:https://www.jianshu.com/p/c51fccf853cb

四、ICE配置参数

enum {
  // Disable local UDP ports. This doesn't impact how we connect to relay
  // servers.
  PORTALLOCATOR_DISABLE_UDP = 0x01,
  PORTALLOCATOR_DISABLE_STUN = 0x02,
  PORTALLOCATOR_DISABLE_RELAY = 0x04,
  // Disable local TCP ports. This doesn't impact how we connect to relay
  // servers.
  PORTALLOCATOR_DISABLE_TCP = 0x08,
  PORTALLOCATOR_ENABLE_IPV6 = 0x40,
  // TODO(pthatcher): Remove this once it's no longer used in:
  // remoting/client/plugin/pepper_port_allocator.cc
  // remoting/protocol/chromium_port_allocator.cc
  // remoting/test/fake_port_allocator.cc
  // It's a no-op and is no longer needed.
  PORTALLOCATOR_ENABLE_SHARED_UFRAG = 0x80,
  PORTALLOCATOR_ENABLE_SHARED_SOCKET = 0x100,
  PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE = 0x200,
  // When specified, we'll only allocate the STUN candidate for the public
  // interface as seen by regular http traffic and the HOST candidate associated
  // with the default local interface.
  PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION = 0x400,
  // When specified along with PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION, the
  // default local candidate mentioned above will not be allocated. Only the
  // STUN candidate will be.
  PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE = 0x800,
  // Disallow use of UDP when connecting to a relay server. Since proxy servers
  // usually don't handle UDP, using UDP will leak the IP address.
  PORTALLOCATOR_DISABLE_UDP_RELAY = 0x1000,

  // When multiple networks exist, do not gather candidates on the ones with
  // high cost. So if both Wi-Fi and cellular networks exist, gather only on the
  // Wi-Fi network. If a network type is "unknown", it has a cost lower than
  // cellular but higher than Wi-Fi/Ethernet. So if an unknown network exists,
  // cellular networks will not be used to gather candidates and if a Wi-Fi
  // network is present, "unknown" networks will not be usd to gather
  // candidates. Doing so ensures that even if a cellular network type was not
  // detected initially, it would not be used if a Wi-Fi network is present.
  PORTALLOCATOR_DISABLE_COSTLY_NETWORKS = 0x2000,

  // When specified, do not collect IPv6 ICE candidates on Wi-Fi.
  PORTALLOCATOR_ENABLE_IPV6_ON_WIFI = 0x4000,

  // When this flag is set, ports not bound to any specific network interface
  // will be used, in addition to normal ports bound to the enumerated
  // interfaces. Without this flag, these "any address" ports would only be
  // used when network enumeration fails or is disabled. But under certain
  // conditions, these ports may succeed where others fail, so they may allow
  // the application to work in a wider variety of environments, at the expense
  // of having to allocate additional candidates.
  PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS = 0x8000,
};

五、参考

https://www.jianshu.com/p/c51fccf853cb

WebRTC ICE 状态与提名处理-阿里云开发者社区

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个使用WebRTC实现音频降噪的示例代码: ```javascript // 创建一个音频上下文 const audioContext = new (window.AudioContext || window.webkitAudioContext)(); // 获取用户媒体流(麦克风输入) navigator.mediaDevices.getUserMedia({ audio: true }) .then((stream) => { // 创建媒体流源节点 const source = audioContext.createMediaStreamSource(stream); // 创建音频处理节点 const processor = audioContext.createScriptProcessor(1024, 1, 1); // 加载降噪模块 processor.onaudioprocess = (event) => { const inputBuffer = event.inputBuffer; const outputBuffer = event.outputBuffer; for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) { const inputData = inputBuffer.getChannelData(channel); const outputData = outputBuffer.getChannelData(channel); // 在这里应用降噪算法 // WebRTC没有内置的降噪算法,你可以使用第三方库或自定义算法来处理音频数据 for (let i = 0; i < inputBuffer.length; i++) { outputData[i] = inputData[i]; } } }; // 连接节点 source.connect(processor); processor.connect(audioContext.destination); }) .catch((error) => { console.error('获取用户媒体流失败:', error); }); ``` 这段代码创建了一个音频上下文,并获取用户的媒体流(麦克风输入)。然后,它创建了一个音频处理节点(ScriptProcessor),并在`onaudioprocess`事件处理程序中实现了降噪算法。 请注意,WebRTC本身并没有内置的降噪算法。在上述代码中,你需要替换注释部分的代码来应用实际的降噪算法。你可以使用第三方库(如RNNoise、Speex等)或自己实现降噪算法。 最后,将媒体流源节点、音频处理节点和目标节点(如扬声器)连接起来,以便实时处理音频数据并播放出来。 这只是一个简单的示例代码,实际的实现可能因需求和环境而有所不同。建议参考WebRTC和音频处理相关的文档、示例代码和社区资源,以获取更详细和具体的指导。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值