WebRtc 信令服务器协议正式版 - housekeeper-software/tech GitHub Wiki

信令外层格式

{
   "from":"1",
   "to":"2",
   "type":"webrtc",
   "timestamp":"2023-05-17T05:52:01.123Z",
   "userAgent":"platform/version;outdoor/unit/version",
   "sessionId":"xxxxx",
   "payload":"ssss",
   "statusResponse":{
      "statusCode":404,
      "description":"user not found"
   }
}

from:消息源的用户id
to:消息目标用户id
type:分为 webrtc,message两种,不同的type对应不同的payload,解析的时候请注意
timestamp: 消息发送时间,ISO8601 format
userAgent: 见Useragent定义。
sessionId:会话标识,有时候这个标识很有用,可以标识一组信息的归属
payload:经过base64编码的负载内容
以下内容客户端不要设置,这个是服务器专有回复,而且只在发生转发错误得时候才会设置
statusResponse:服务器返回的状态,不是对端返回的。而且只返回给发送端。
statusCode:404:对方离线,400:json格式有误,比如to与from相同

user agent 定义

useragent分为两段:
platform/version:表明平台和版本,比如android/11.0, linux/1.0,web/1.0
room/pad/version,room/mobile/version,表示室内机/室内机子类型/室内机app版本,
如果是室外机:outdoor/unit/version, outdoor/center/version,outdoor/wall/version,outdoor/villa/version,分别表示单元门口机,中心管理机,围墙机,别墅门口机
其他类型可扩展。

webrtc 的负载

{
   "type":"SDP_OFFER",
   "server":[
      {
         "username":"",
         "credential":"",
         "ttl":86400, #以秒为单位的这个账号的有效期。从服务器返回的时候开始计时间。注意,ttl只影响到turn服务器,对stun没有影响。
                      #应为stun服务器负载极低,一般使用google免费的即可。而turn服务器用来中转,必须注意账号的安全性。
                      #在使用的时候,如果账号存活时间超过 ttl-3600秒就算过期,需要重新获取。服务器一般默认配置24小时有效期
         "urls":[
            "stun:stun.l.google.com:19302"  #这个是google免费的stun服务器,这里没有配置turn服务器,因为后期再部署。
         ]
      }
   ],
   "jsep":"base64"
}
type:表示消息类型:SDP_OFFER:表示向对方提供offer,SDP_ANSWER:表示向对方提供answer,ICE_CANDIDATE:表示ice协商消息
server:当type=SDP_OFFER 时需要提供给对方本次所用的ICE服务器信息。其他类型不需要,可以不提供。
    参考: https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer/urls
jsep:经过base64编码的webrtc sdp或者ice详细信息。这个由webrtc生成,可以直接给webrtc。这里经由base64编码,是因为上层一般不会关心里面具体的内容,直接传给
webrtc即可。

消息负载,为实现门禁的状态交互,也可以用于文字聊天

{
   "type":"消息类型",
   "encode":true,
   "data":"",
   "ack":true,
   "result":0
}
type: 包含如下定义:
    ---invite:表示想要邀请对方对讲,接受方在第一个通道收到之后需要回复同意或者busy,其他通道收到相同的会话则忽略
    ---pickup:告诉对方,我方已经摘机,不用回复
    ---hangup:挂断,无需回复
    ---unlock:开锁,需要回复
    ---elevator:梯控,召梯:"data":"1", 授权:"data":"2", 需要回复
    ---text:文本消息,可以传递任意内容
    ---ping:用来探测对方是否活着,对方收到回复ping,ack=true
encode:表示data字段是否base64编码,也用于发送方指示data字段是否需要编码,有些短文本,可以不编码,如有复杂格式需要编码
data:正文
ack:是否是确认包,有些包需要对方确认
result:命令执行结果,如下:
enum MessageResult {
 kResultOK, //for everthing
 kResultBusy,//for invite,当我们无法接受对方的邀请,可以回复busy,当然也可以不理,不过快速的回复加快对端的邀请过程,避免无意义的等待。
 kResultTimeout, //for invite,当接受对方的邀请之后,收到对端的挂断,错误代码为这个,则表示回复已经晚了,对方已经结束了邀请进入了会话状态。立即结束会话即可
 kResultAbort,//for hangup,这是一个特殊的错误码,发起端收到这个错误代码,如果当前回复无人接听,则立即中止所有呼叫。场景就是,如果室内不想接听,直接挂断,那么
室外机将中止呼叫,不再呼叫其他设备。这必须是有人干预的场景,如果室内机无人取消,最终超时应当发送kResultOK,室外机将继续等待其他设备接听

 kResultAnsweredOnOtherDevice, //for hangup,表明会话已经在其他设备上接听了,data中包含接听设备的信息
 kResultWebRtcError, //start webrtc failed  , 启动webrc失败,会话将中止
 kResultBadCredentials,//for session not match,or other ,目前没有用到,可能用在开锁召梯等功能,表示无权操作

 kResultNetworkError, //for everything: local error,内部错误,不会再网络上给对方

};

门禁交互逻辑

室外机呼叫逻辑:
室外机一般由三种组合呼叫逻辑,首先是pad室内机呼叫,其次是移动端呼叫,最后是电话呼叫。三种逻辑由策略来配置,其中,pad室内机呼叫是立即执行,无需任何等待,
移动端app呼叫需要指定一个延时,比如30秒。正常情况下,会在会话开始的30秒后开始向移动端推送呼叫请求,然后等待移动端回复。
当移动端和室内机都超时之后,才开始呼叫电话(因为技术原因,此电话呼叫和其他两种不能共存,必须在前二者结束之后才能开始电话呼叫)。但呼叫室内机和移动端可以同时,
本质上都是webrtc呼叫。电话呼叫虽然也是webrtc底层技术,但二者不是一回事,共存时导致录音设备冲突。
首先,室外机会通过点对点的http向指定的用户id发送http请求(接口在/api/webrtc),当室外机获取到iceserverlist的时候,将通过websocket服务器向指定设备发送请求。
二者之间有微小的时间差,通过websocket发送请求之前,会检查p2p有没有接收,如果接受了,则不会从websocket发送请求。同时走两个路由发送请求的可能性是存在的。
室内机两个路由都可以接受之,室外机只会选择最先收到回复的路由。被呼叫端回复invite,ack=true,如果result=ResultOK,表示接受,其他的表示拒绝,室外机将忽略。
室内机必须回复invite,可以result=ResultBusy,表明自己此刻忙碌,比如正在其他会话。不回复的话将拖延会话进度。

当室外机收到invite ack,且ResultOK之后,立即向接受方发送offer,注意,接受方无需向服务器请求ice 服务器信息,offer中已经包含。如果是点对点呼叫,无需ice server信息。 
室内机/移动端收到offer之后。建立webrtc peerchannel,并将webrtc生成的answer和ice candidation信息按照invite路由转发给室外机,呼叫建立完成。此刻可以看到室外机视频。
作为webrtc来说,没有早期媒体的概念,一般是在建立连接之前进行呼叫确认,我们这里是模拟sip的早期媒体,接收端需要发送pickup信令,以使得室外机开启声音。
如果室内机发送接听指令,则室外机会挂断除了接听设备之外的其他所有会话。当然,如果接听指令到达时,会话已经被其他设备接听,则回复挂断,result= ResultkAnsweredOnOtherDevice,data中包含接听的userid。
如果室内机没有接听直接挂断,此刻发送hangup,但result=ResultAbort,表示会话立即中止。如果result=ResultOK,则其他会话将保持。

查看监控的室内机按照上述室外机的逻辑处理即可。
通讯双方,一旦发现错误,需要给对方发送hangup,并给出合理的MessageResult
注意,invite中的sessionid在整个会话逻辑中保持不变,这个由发起方来定义。如果给出错误的sessionid,视为无效。

协议传输

可以有两种方式:
1.通过 websocket,这个需要服务器支持,服务器可以部署在社区里,或者云端,根据场景而定。  
2.通过 http 点对点发送。双方均需要侦听在 18080 端口,发送消息直接往对方这个端口发送,从这个端口接收其他端的消息。
  如果是别墅方案,这个端口通过ssdp协议约定,如果是普通方案,需要强制约定。 接口定义为: /api/webrtc
一般来说,室内机和室外机两种方式同时支持(除非是不支持点对点连接的场景),其他端只需要支持websocket即可。

websocket 协议头定制

X-Genius-ClientId: userid
X-Genius-Key:xxxx
X-Genius-Nonce=16位随机数
X-Genius-Timestamp=2018-01-29T04:43:02.999Z
X-Genius-Signature=, 将所有key(除了Signature,ClientId)按照字典序排序,最后在拼接成 key=value&key=value,算法: hexencode(HMAC-SHA256(message))

key,secret需要通过其他途径获得

发起会话流程

室外机发起会话流程:
首先执行邀请操作,这时候可以设置多个通道邀请(上层可以配置一个或者多个通道的名称),总体邀请有效期为10秒(可配置),超时无人应答则视为呼叫失败。
其中,如果配置P2P通道的话,则应该优先发送邀请,其他通道会延迟500毫秒开始发送邀请。如果没有配置P2P通道,则立即向所有通道发送邀请。
室内机可能在一个或者多个通道接收到相同sessionid的邀请,如果有接听条件的话(此刻用户无感知,只要有接听的物理条件即可),立即回复ack包。对于从其他通道的相同sessionid的
邀请应立即拒绝(回复ack,result=busy)。当然也可以置之不理。
室内机一旦接受了邀请,应该创建一个对象来保存这个状态,并且等待室外机发送webrtc的 offer。当然,接受邀请之后应该启动定时器,确保当室外机或者网络异常的情况下,会话过期之后清理现场。
对于每一个室内机标识(可以是房号:单元门口机等,或者唯一的id:别墅门口机),只要收到一个接受邀请的回复,这个室内机的邀请过程立即结束,并立即进入呼叫过程,开始向接受的
终端发送offer。在室内机回复 answer之后,室内机将看到画面。但此刻没有声音,因为室外机默认禁止了声音,需要用户在室内机上点击【接听】按钮,室内机发送pickup指令之后,室外机
使能声音,双方都可以如此处理。这不是webrtc默认的逻辑,是经过修改的以实现可视对讲的场景需求。
在对讲过程中,任意一方均可向对方发送hangup指令,则会话结束。注意,hangup指令无需回复。发送方发送之后立即结束会话,不管对端有没有收到。
在邀请过程中还经常存在另外一种场景,当某个设备接受邀请之后,就收到了挂断指令,原因是消息到达太迟了,会话已经在其他设备上接听了。
室内机发送会话流程(比如查看室外机监控,或者呼叫其他终端):
这个过程和室外机发送邀请过程一致。

补充一点,对于P2P通道,邀请会立即发送,对于其他通道,发送端首先场景获取ice server列表(从本地缓存或者服务器),只有成功获取到ice sever之后才会发送邀请。
因为没有ice server通话无法建立。

ICE 服务器列表的处理

ICE 服务器信息对通话至关重要,没有的话无法建立会话(除了P2P通道,无需ice服务器信息)。一般来所,turn服务器分配的账号都有一定的期限(ttl),如果在设备端需要维护这个
有效期。
具体可以参考下面的源码

特别注意

我们不使用webrtc数据通道传输指令,因为有些指令并不依赖webrtc通话

webrtc的呼叫逻辑

webrtc有着固定的呼叫逻辑。对webrtc来说,当我们需要呼叫对方的时候,按照如下步骤进行:
1)创建webrtc peerconnection,一般首先需要设置 ice server list,如上所示,包含stun和turn服务器信息。其中stun服务器一般不需要账号,但turn服务器一般需要认证。
2)peerconnection 生成 offer,就是jsep中描述的信息,通过websocket或者其他通道传递给被叫方。
3)被叫方收到offer之后,解析ice serverlist,然后初始化peerconnection,然后将jsep传递给peerconnection
4)被叫方根据offer生成对应的answer,通过网络传给主叫方
5)主被双方开始交换ice信息,寻找合适的媒体传输通道。如果点对点不成功,最终会经过turn服务器进行中转。如果配置turn服务器,呼叫总是能够成功。
6)一旦传输通道协商好之后,通话正式开始。

针对可视对讲领域的特别定制流程

webrtc本身没有早期媒体(就是在接听之前传输视频)的能力,所以,要对webrtc自己的逻辑适当的定制,具体如下:
1)在接听之前,将webrtc静音,具体是设置 peerconnection.SetAudioPlayout(false),SetAudioRecording(false)
2) 被叫方接听之后,在将上述两个设置为true。 
需要注意的是,webrtc不能先协商一个通道(比如视频),然后中途再协商另外一个通道,所以,一开始协商的时候,音视频均需要同时协商。
通过静音的方式模拟接听的效果。
另外,就是视频的单向传输,室外机和室内机基本都是单向传输视频。
主叫方可以指定视频:webrtc::RtpTransceiverDirection::kSendOnly(java或者js中应该有类似设置)
作为被叫方不能指定。但是,可以在给peerconnection设置主叫方的offer,修改视频的媒体方向。然后再生成answer。
记住,单向视频可以提高设备多路会话的承载能力。

signal服务器的一些约束

信令服务器只关心外层协议,如文章开始所示。只要保持外层协议不变,可以自行扩展协议的具体内容。 
具体来说,signal服务器通过设备端登录的时候提供的clientid,标识连接的身份。并通过协议中的to字段,转发给指定的连接。
如果to指定的连接不存在,则通过statusResponse返回错误给发送方。如果转发成功,不会给发送方发送回复.  
客户端一旦发现收到的协议包中存在statusResponse节点,一定是sigal服务器回复的,是对最近所发包的状态回复。通讯双方交换的包不能包含statusResponse节点。

signal服务器没有数据库,也不依赖其他的服务(除了认证),所以,服务器本身的状态极其简单,客户端掉线之后,直接删除。因而,signal服务器不判断to指定的用户
是否合法。
signal服务器对客户端登录时候的认证。
客户端登录signal服务器需要拥有一个key和一个secret,这个可以通过其他业务系统获取。secret用来对登录信息进行加密,加密算法参考别墅门口机。
signal服务器预先配置一个认证服务器的接口地址。将使用 key 和clientid请求对方,以获得secret。
然后用secret对登录信息进行解密。如果解密成功,则允许登录,否则将中断连接。
第三方认证服务器需要提供认证接口(POST),
请求方发送json格式:
{
"X-Genius-ClientId":"客户端的id",
"X-Genius-Key":"客户端指定的key"
}
认证方返回:
{
"code":200,表示成功,其他表示失败
"secret":"xxx",密钥
}

为减少对认证服务器的压力,提高登录效率,signal服务器有cache(是内存cache,不是redis),可以指定secret的有效时间。

多个信令服务器

  我们支持一个P2P通道和多个Server通道,每个通道以不同的名称区别
  在呼叫的时候,我们可以配置一个或者多个通道,具体的场景如下:
  对于室外机,可以呼叫P2P通道,也可以同时呼叫社区内网的server通道,也可以呼叫位于云端的通道(包括室内机和手机端)
  对于室内机,可以通过云端上线,也可以通过社区内网服务器上线,也可二者均可以上线,这样布线方式非常灵活。
  对于移动端,一般通过云端上线即可。
  通过invite机制,我们可以寻找到最佳的呼叫通道,哪个通道反馈最快就选择哪个

P2P 信令通道

室内机和室外机均启动http server,侦听到双方共识的端口(别墅门口机可以通过ssdp协议协商)。httpserver本身可以用于其他用途,但我们保留 /api/webrtc(POST方法,且
无任何认证)接口专门
用于webrtc信令交互。一般而言,为了处理方便,我们不适用request/response模式,向对方发送信令,对方只需要返回200即可,不要在请求中发送回复信令。
如果要发送新的信令或者ack包,请向对端发送request操作。
/api/ping专门用来探测对方是否存活。不适用于对讲,对讲使用invite来探测,

客户端信令解析和处理的参考代码

#ifndef WEBRTC_CORE_WEBRTC_MESSAGE_H_
#define WEBRTC_CORE_WEBRTC_MESSAGE_H_

#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/time/time.h"
#include "base/values.h"
#include "base/optional.h"

namespace webrtc {
//https://github.com/housekeeper-software/tech/wiki/WebRtc-%E4%BF%A1%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8D%8F%E8%AE%AE%E6%AD%A3%E5%BC%8F%E7%89%88
extern const char kTypeWebRtc[];
extern const char kTypeMessage[];

//subtype with kTypeWebRtc
extern const char kSDPOffer[];
extern const char kSDPAnswer[];
extern const char kICECandidate[];

//subtype with kTypeMessage
extern const char kTypeInvite[];
extern const char kTypePickup[];
extern const char kTypeHangup[];
extern const char kTypeUnlock[];
extern const char kTypeElevator[];
extern const char kTypeText[];
extern const char kTypePing[];

extern const char kTelephoneUserAgent[];

class IceServer {
public:
 IceServer();
 ~IceServer();
 IceServer(const IceServer &);
 IceServer &operator=(const IceServer &);
 bool IsExpired() const;
 bool IsTurn() const;
 std::string username;
 std::string credential;
 int ttl;
 std::vector<std::string> urls;
 base::TimeTicks expired_time;
};

class IceServerList {
public:
 IceServerList();
 ~IceServerList();
 IceServerList(const IceServerList &);
 IceServerList &operator=(const IceServerList &);

 static bool FromString(const std::string &json, IceServerList *ice_server_list);
 static bool FromJson(const base::DictionaryValue *root, IceServerList *ice_server_list);

 std::string ToJson() const;
 void ToJson(base::DictionaryValue *root) const;
 void AddServer(const IceServer &server);
 bool IsExpired() const;
 bool empty() const { return server_list.empty(); }

 std::vector<IceServer> server_list;
};

enum class DeviceType : int {
 DeviceTypeUnknown,
 DeviceTypePad,
 DeviceTypeMobile,
 DeviceTypeUnit,
 DeviceTypeCenter,
 DeviceTypeWall,
 DeviceTypeVilla,
 DeviceTypeTelephone,
};

class UserAgent {
public:
 UserAgent();
 UserAgent(const std::string &str);
 ~UserAgent();
 UserAgent(const UserAgent &);
 UserAgent &operator=(const UserAgent &);
 static bool FromString(const std::string &str, UserAgent *user_agent);

 std::string ToString() const;
 DeviceType type() const;

 std::string platform;
 std::string platform_version;
 std::string device_type;
 std::string device_subtype;
 std::string device_version;
private:
 bool Parse(const std::string &str);
};

/*
{
   "type":"SDP_OFFER",
   "server":[
      {
         "userName":"",
         "password":"",
         "ttl":86400,
         "urls":[
            "stun:stun.l.google.com:19302"
         ]
      }
   ],
   "jsep":"base64"
}
 */
class WebRtcMessage {
public:
 WebRtcMessage();
 ~WebRtcMessage();
 WebRtcMessage(const WebRtcMessage &other);
 WebRtcMessage &operator=(const WebRtcMessage &other);
 std::string ToJson() const;
 std::string ToString() const;
 static bool FromString(const std::string &json, WebRtcMessage *message);

 std::string type;
 std::string jsep;
 IceServerList ice_server_list;
};

enum MessageResult {
 kResultOK, //for everthing
 kResultBusy,//for invite
 kResultTimeout, //for invite
 kResultAbort,//for hangup
 kResultAnsweredOnOtherDevice, //for hangup
 kResultWebRtcError, //start webrtc failed
 kResultBadCredentials,//for session not match,or other

 kResultNetworkError, //for everything: local error
};

/*
{
   "type":"消息类型",
   "encode":true,
   "data":"",
   "ack":true,
   "result":0
}
 */

class CommonMessage {
public:
 CommonMessage();
 ~CommonMessage();
 explicit CommonMessage(const std::string &type, bool ack, int result,
                        const std::string &data = {}, bool encode = false);
 CommonMessage(const CommonMessage &);
 CommonMessage &operator=(const CommonMessage &);

 std::string ToString() const;
 std::string ToJson() const;
 bool IsIntercomMessage() const;
 static bool FromString(const std::string &json, CommonMessage *message);

 std::string type;
 bool ack;
 int result;
 std::string data;
private:
 bool encode;
};

enum StatusCode {
 StatusBadRequest = 400,
 StatusNotFound = 404,
 StatusServiceUnavailable = 503,
};

/*
{
   "from":"1",
   "to":"2",
   "type":"webrtc",
   "timestamp":"2023-05-17T05:52:01.123Z",
   "userAgent":"platform/version;app/version",
   "sessionId":"xxxxx",
   "payload":"ssss",
   "statusResponse":{
      "statusCode":404,
      "description":"user not found"
   }
}
 */

class SignalingMessage {
public:
 SignalingMessage();
 ~SignalingMessage();
 SignalingMessage(const SignalingMessage &);
 SignalingMessage &operator=(const SignalingMessage &);

 static bool FromString(const std::string &json, SignalingMessage *message);
 static bool FromByteArray(const uint8_t *data, size_t len, SignalingMessage *message);
 std::string ToString() const;
 std::string ToJson() const;
 std::string PeerId() const;
 bool OK() const;
 bool IsWebRtcMessage() const;
 bool IsCommonMessage() const;
 bool IsValidCommonMessage() const;
 bool IsValidWebRtcMessage() const;
 bool IsValidInviteMessage() const;
 bool IsValidInviteAckMessage() const;
 bool IsSessionMatch(const std::string &sessionid) const;
 SignalingMessage ServiceUnavailable() const;
 void ClearError();

 std::string from; //sender userid
 std::string to; //target userid
 std::string type;
 base::Time timestamp;
 UserAgent user_agent;
 std::string sessiond_id;

 base::Optional<WebRtcMessage> webrtc_message;
 base::Optional<CommonMessage> common_message;

 base::Optional<int> status_code; //see StatusCode
 base::Optional<std::string> status_description;
};

}

#endif //WEBRTC_CORE_WEBRTC_MESSAGE_H_
///////////////////////////////////////////////
//这里是具体实现
#include "webrtc/core/webrtc_message.h"
#include "base/values.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/base64.h"
#include "base/time/time_to_iso8601.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/string_util.h"

namespace webrtc {

const char kTypeWebRtc[] = "webrtc";
const char kTypeMessage[] = "message";

const char kSDPOffer[] = "SDP_OFFER";
const char kSDPAnswer[] = "SDP_ANSWER";
const char kICECandidate[] = "ICE_CANDIDATE";

const char kTypeInvite[] = "invite";
const char kTypePickup[] = "pickup";
const char kTypeHangup[] = "hangup";
const char kTypeUnlock[] = "unlock";
const char kTypeElevator[] = "elevator";
const char kTypeText[] = "text";
const char kTypePing[] = "ping";

const char kTelephoneUserAgent[] = "Unknown/1.0;room/telephone/1.0";

IceServer::IceServer()
    : ttl(0) {}
IceServer::~IceServer() = default;
IceServer::IceServer(const IceServer &) = default;
IceServer &IceServer::operator=(const IceServer &) = default;
bool IceServer::IsExpired() const {
  return expired_time > base::TimeTicks::Now() - base::TimeDelta::FromHours(1);
}
bool IceServer::IsTurn() const {
  if (urls.empty()) return false;
  return base::StartsWith(urls[0], "turn:", base::CompareCase::INSENSITIVE_ASCII);
}

IceServerList::IceServerList() = default;
IceServerList::~IceServerList() = default;
IceServerList::IceServerList(const IceServerList &) = default;
IceServerList &IceServerList::operator=(const IceServerList &) = default;

std::string IceServerList::ToJson() const {
  base::ListValue servers;
  for (const auto &i : server_list) {
    std::unique_ptr<base::DictionaryValue> ice(new base::DictionaryValue());
    ice->SetString("username", i.username);
    ice->SetString("credential", i.credential);
    ice->SetInteger("ttl", i.ttl);
    std::unique_ptr<base::ListValue> urls(new base::ListValue());
    for (const auto &j : i.urls) {
      urls->AppendString(j);
    }
    ice->Set("urls", std::move(urls));
    servers.Append(std::move(ice));
  }
  std::string json;
  base::JSONWriter::Write(servers, &json);
  return json;
}

void IceServerList::AddServer(const IceServer &server) {
  server_list.push_back(server);
}

bool IceServerList::IsExpired() const {
  for (const auto &i : server_list) {
    if (i.IsTurn() && i.IsExpired())
      return true;
  }
  return false;
}

bool IceServerList::FromString(const std::string &json, IceServerList *ice_server_list) {
  auto value = base::JSONReader::ReadDeprecated(json);
  if (!value) return false;
  const base::DictionaryValue *root = nullptr;
  if (!value->GetAsDictionary(&root) || !root)
    return false;
  return FromJson(root, ice_server_list);
}

//static
bool IceServerList::FromJson(const base::DictionaryValue *value, IceServerList *ice_server_list) {
  const base::ListValue *root = nullptr;
  if (!value->GetList("server", &root))
    return false;

  for (size_t i = 0; i < root->GetSize(); ++i) {
    const base::DictionaryValue *item = nullptr;
    if (!root->GetDictionary(i, &item) || !item)
      continue;
    IceServer server;
    const base::ListValue *urls = nullptr;
    if (item->GetList("urls", &urls) && urls) {
      for (size_t j = 0; j < urls->GetSize(); ++j) {
        std::string str;
        urls->GetString(j, &str);
        if (!str.empty()) {
          server.urls.emplace_back(std::move(str));
        }
      }
    }
    item->GetString("username", &server.username);
    item->GetString("credential", &server.credential);
    item->GetInteger("ttl", &server.ttl);
    if (server.ttl > 0) {
      server.expired_time = base::TimeTicks::Now() + base::TimeDelta::FromSeconds(server.ttl);
    }
    ice_server_list->AddServer(server);
  }
  return true;
}

void IceServerList::ToJson(base::DictionaryValue *root) const {
  std::unique_ptr<base::ListValue> servers(new base::ListValue());
  for (const auto &i : server_list) {
    std::unique_ptr<base::DictionaryValue> ice(new base::DictionaryValue());
    ice->SetString("username", i.username);
    ice->SetString("credential", i.credential);
    ice->SetInteger("ttl", i.ttl);
    std::unique_ptr<base::ListValue> urls(new base::ListValue());
    for (const auto &j : i.urls) {
      urls->AppendString(j);
    }
    ice->Set("urls", std::move(urls));
    servers->Append(std::move(ice));
  }
  root->Set("server", std::move(servers));
}

UserAgent::UserAgent() = default;
UserAgent::~UserAgent() = default;
UserAgent::UserAgent(const UserAgent &) = default;
UserAgent &UserAgent::operator=(const UserAgent &) = default;

UserAgent::UserAgent(const std::string &str) {
  Parse(str);
}

bool UserAgent::Parse(const std::string &str) {
  std::vector<std::string> v = base::SplitString(str, ";", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
  if (v.size() < 2)
    return false;

  auto platform_vector = base::SplitString(v[0], "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
  if (platform_vector.size() < 2)
    return false;
  platform = platform_vector[0];
  platform_version = platform_vector[1];

  auto device = base::SplitString(v[1], "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
  if (device.size() < 3)
    return false;
  device_type = device[0];
  device_subtype = device[1];
  device_version = device[2];
  return true;
}
//static
bool UserAgent::FromString(const std::string &str, UserAgent *user_agent) {
  UserAgent ua;
  if (!ua.Parse(str))
    return false;
  *user_agent = ua;
  return true;
}

std::string UserAgent::ToString() const {
  if (platform.empty() && platform_version.empty() && device_type.empty() && device_subtype.empty()
      && device_version.empty())
    return {};
  return base::StringPrintf("%s/%s;%s/%s/%s", platform.c_str(),
                            platform_version.c_str(),
                            device_type.c_str(),
                            device_subtype.c_str(),
                            device_version.c_str());
}

DeviceType UserAgent::type() const {
  if (base::CompareCaseInsensitiveASCII(device_type, "room") == 0) {
    if (base::CompareCaseInsensitiveASCII(device_subtype, "pad") == 0) {
      return DeviceType::DeviceTypePad;
    }
    if (base::CompareCaseInsensitiveASCII(device_subtype, "mobile") == 0) {
      return DeviceType::DeviceTypeMobile;
    }
    if (base::CompareCaseInsensitiveASCII(device_subtype, "telephone") == 0) {
      return DeviceType::DeviceTypeTelephone;
    }
  } else if (base::CompareCaseInsensitiveASCII(device_type, "outdoor") == 0) {
    if (base::CompareCaseInsensitiveASCII(device_subtype, "unit") == 0) {
      return DeviceType::DeviceTypeUnit;
    }
    if (base::CompareCaseInsensitiveASCII(device_subtype, "center") == 0) {
      return DeviceType::DeviceTypeCenter;
    }
    if (base::CompareCaseInsensitiveASCII(device_subtype, "wall") == 0) {
      return DeviceType::DeviceTypeWall;
    }
    if (base::CompareCaseInsensitiveASCII(device_subtype, "villa") == 0) {
      return DeviceType::DeviceTypeVilla;
    }
  }
  return DeviceType::DeviceTypeUnknown;
}

WebRtcMessage::WebRtcMessage() = default;
WebRtcMessage::~WebRtcMessage() = default;
WebRtcMessage::WebRtcMessage(const WebRtcMessage &other) = default;
WebRtcMessage &WebRtcMessage::operator=(const WebRtcMessage &other) = default;

bool WebRtcMessage::FromString(const std::string &json, WebRtcMessage *message) {
  auto value = base::JSONReader::ReadDeprecated(json);
  if (!value) return false;
  const base::DictionaryValue *root = nullptr;
  if (!value->GetAsDictionary(&root))
    return false;
  root->GetString("type", &message->type);
  IceServerList::FromJson(root, &message->ice_server_list);
  if (root->GetString("jsep", &message->jsep)) {
    base::Base64Decode(message->jsep, &message->jsep);
  }
  return true;
}

std::string WebRtcMessage::ToJson() const {
  base::DictionaryValue root;
  root.SetString("type", type);
  if (!jsep.empty()) {
    std::string str;
    base::Base64Encode(jsep, &str);
    root.SetString("jsep", str);
  }
  if (!ice_server_list.empty()) {
    ice_server_list.ToJson(&root);
  }
  std::string json;
  base::JSONWriter::Write(root, &json);
  return json;
}

std::string WebRtcMessage::ToString() const {
  return base::StringPrintf("TYPE:%s\nJSEP:%s\nICE:%s\n",
                            type.c_str(),
                            jsep.c_str(),
                            ice_server_list.ToJson().c_str());
}

CommonMessage::CommonMessage()
    : ack(false),
      result(kResultOK),
      encode(false) {}
CommonMessage::~CommonMessage() = default;
CommonMessage::CommonMessage(const CommonMessage &) = default;
CommonMessage &CommonMessage::operator=(const CommonMessage &) = default;

CommonMessage::CommonMessage(const std::string &type, bool ack, int result,
                             const std::string &data, bool encode)
    : type(type),
      ack(ack),
      result(result),
      data(data),
      encode(encode) {}

//static
bool CommonMessage::FromString(const std::string &json, CommonMessage *message) {
  auto value = base::JSONReader::ReadDeprecated(json);
  if (!value) return false;
  const base::DictionaryValue *root = nullptr;
  if (!value->GetAsDictionary(&root))
    return false;
  root->GetString("type", &message->type);
  root->GetBoolean("ack", &message->ack);
  root->GetInteger("result", &message->result);
  root->GetBoolean("encode", &message->encode);
  root->GetString("data", &message->data);
  if (message->encode && !message->data.empty()) {
    base::Base64Decode(message->data, &message->data);
  }
  return true;
}

std::string CommonMessage::ToJson() const {
  base::DictionaryValue root;
  root.SetString("type", type);
  root.SetBoolean("ack", ack);
  root.SetInteger("result", result);
  root.SetBoolean("encode", encode);
  if (encode && !data.empty()) {
    std::string str;
    base::Base64Encode(data, &str);
    root.SetString("data", str);
  } else {
    root.SetString("data", data);
  }
  std::string json;
  base::JSONWriter::Write(root, &json);
  return json;
}

std::string CommonMessage::ToString() const {
  return base::StringPrintf("TYPE:%s\nACK=%s\nRESULT=%d\nDATA:%s\n",
                            type.c_str(),
                            ack ? "true" : "false",
                            result,
                            data.c_str());
}

bool CommonMessage::IsIntercomMessage() const {
  return base::CompareCaseInsensitiveASCII(type, kTypeInvite) == 0 ||
      base::CompareCaseInsensitiveASCII(type, kTypeHangup) == 0 ||
      base::CompareCaseInsensitiveASCII(type, kTypePickup) == 0;
}

SignalingMessage::SignalingMessage()
    : timestamp(base::Time::Now()) {}
SignalingMessage::~SignalingMessage() = default;
SignalingMessage::SignalingMessage(const SignalingMessage &) = default;
SignalingMessage &SignalingMessage::operator=(const SignalingMessage &) = default;

bool SignalingMessage::FromByteArray(const uint8_t *data, size_t len, SignalingMessage *message) {
  std::string json(data, data + len);
  return FromString(json, message);
}

bool SignalingMessage::FromString(const std::string &json, SignalingMessage *message) {
  auto value = base::JSONReader::ReadDeprecated(json);
  if (!value) return false;
  const base::DictionaryValue *root = nullptr;
  if (!value->GetAsDictionary(&root))
    return false;
  root->GetString("from", &message->from);
  root->GetString("to", &message->to);
  root->GetString("type", &message->type);
  std::string str;
  if (root->GetString("userAgent", &str)) {
    UserAgent::FromString(str, &message->user_agent);
  }
  root->GetString("sessionId", &message->sessiond_id);

  if (root->GetString("payload", &str)) {
    base::Base64Decode(str, &str);
    if (base::CompareCaseInsensitiveASCII(message->type, kTypeWebRtc) == 0) {
      WebRtcMessage webrtc_message;
      if (WebRtcMessage::FromString(str, &webrtc_message)) {
        message->webrtc_message = base::make_optional<WebRtcMessage>(std::move(webrtc_message));
      } else {
        LOG(ERROR) << "Failed to parse webrtc message(" << str << ")";
      }
    } else if (base::CompareCaseInsensitiveASCII(message->type, kTypeMessage) == 0) {
      CommonMessage common_message;
      if (CommonMessage::FromString(str, &common_message)) {
        message->common_message = base::make_optional<CommonMessage>(std::move(common_message));
      } else {
        LOG(ERROR) << "Failed to parse common message(" << str << ")";
      }
    }
  }
  if (root->GetString("timestamp", &str)) {
    if (!base::Time::FromUTCString(str.c_str(), &message->timestamp)) {
      LOG(ERROR) << "Failed to parse timestamp(" << str << ")";
    }
  }
  const base::DictionaryValue *response = nullptr;
  if (root->GetDictionary("statusResponse", &response)) {
    int v = 0;
    if (response->GetInteger("statusCode", &v)) {
      message->status_code = base::make_optional(v);
    }
    std::string description;
    if (response->GetString("description", &description)) {
      message->status_description = base::make_optional(description);
    }
    LOG(ERROR) << "Failed to send message to(" << message->to
               << "), code(" << v << "),description(" << description << ")";
  }
  return true;
}

std::string SignalingMessage::ToJson() const {
  base::DictionaryValue root;
  root.SetString("from", from);
  root.SetString("to", to);
  root.SetString("type", type);
  root.SetString("userAgent", user_agent.ToString());
  root.SetString("sessionId", sessiond_id);
  std::string payload;
  if (base::CompareCaseInsensitiveASCII(type, kTypeWebRtc) == 0) {
    if (webrtc_message) {
      payload = webrtc_message->ToJson();
    }
  } else if (base::CompareCaseInsensitiveASCII(type, kTypeMessage) == 0) {
    if (common_message) {
      payload = common_message->ToJson();
    }
  }
  if (!payload.empty()) {
    base::Base64Encode(payload, &payload);
    root.SetString("payload", payload);
  }
  root.SetString("timestamp", base::TimeToISO8601(timestamp));

  if (status_code) {
    std::unique_ptr<base::DictionaryValue> status(new base::DictionaryValue());
    status->SetInteger("statusCode", *status_code);
    if (status_description) {
      status->SetString("description", *status_description);
    }
    root.Set("statusResponse", std::move(status));
  }
  std::string json;
  base::JSONWriter::Write(root, &json);
  return json;
}

std::string SignalingMessage::ToString() const {
  std::string str = base::StringPrintf("\nFROM:%s\nTO:%s\nTYPE:%s\nUSERAGENT:%s\nSESSION:%s\nTIMESTAMP:%s",
                                       from.c_str(), to.c_str(), type.c_str(), user_agent.ToString().c_str(),
                                       sessiond_id.c_str(), base::TimeToISO8601(timestamp).c_str());
  if (!OK()) {
    base::StringAppendF(&str, "\nCODE:%d\nDESCRIPTION:%s",
                        *status_code,
                        status_description.has_value() ? (*status_description).c_str() : "");
  }
  if (webrtc_message) {
    base::StringAppendF(&str, "\nMESSAGE:%s", webrtc_message->ToString().c_str());
  } else if (common_message) {
    base::StringAppendF(&str, "\nMESSAGE:%s", common_message->ToString().c_str());
  }
  return str;
}

std::string SignalingMessage::PeerId() const {
  if (status_code) return to;
  return from;
}

bool SignalingMessage::OK() const {
  return !status_code;
}

bool SignalingMessage::IsWebRtcMessage() const {
  return base::CompareCaseInsensitiveASCII(type, kTypeWebRtc) == 0;
}

bool SignalingMessage::IsCommonMessage() const {
  return base::CompareCaseInsensitiveASCII(type, kTypeMessage) == 0;
}

bool SignalingMessage::IsValidCommonMessage() const {
  return IsCommonMessage() && common_message;
}

bool SignalingMessage::IsValidWebRtcMessage() const {
  return IsWebRtcMessage() && webrtc_message;
}

bool SignalingMessage::IsValidInviteMessage() const {
  return OK() && IsValidCommonMessage() &&
      base::CompareCaseInsensitiveASCII(common_message->type, kTypeInvite) == 0 &&
      !common_message->ack;
}

bool SignalingMessage::IsValidInviteAckMessage() const {
  return OK() && IsValidCommonMessage() &&
      base::CompareCaseInsensitiveASCII(common_message->type, kTypeInvite) == 0 &&
      common_message->ack;
}

bool SignalingMessage::IsSessionMatch(const std::string &sessionid) const {
  return sessiond_id == sessionid;
}

SignalingMessage SignalingMessage::ServiceUnavailable() const {
  SignalingMessage response = *this;
  response.status_code = base::make_optional<int>(StatusServiceUnavailable);
  response.status_description = base::make_optional<std::string>("server offline");
  return response;
}

void SignalingMessage::ClearError() {
  status_code.reset();
  status_description.reset();
}

}

消息发送的操作

头文件
#ifndef WEBRTC_CORE_WEBRTC_MESSAGE_SENDER_H_
#define WEBRTC_CORE_WEBRTC_MESSAGE_SENDER_H_

#include <memory>
#include <string>
#include <set>
#include "base/macros.h"
#include "base/threading/thread_checker.h"
#include "base/memory/ref_counted.h"
#include "webrtc/core/webrtc_message.h"
#include "webrtc/core/webrtc_interface.h"
#include "webrtc/core/webrtc_call_state.h"

namespace webrtc {
class WebRtcMessageSender;

class SignalingMessageBuilder
    : public base::RefCountedThreadSafe<SignalingMessageBuilder> {
public:
 explicit SignalingMessageBuilder(const std::string &local_userid,
                                  const UserAgent &user_agent);
 explicit SignalingMessageBuilder(const std::string &local_userid,
                                  const std::string &user_agent);
 SignalingMessage BuildCommonMessage(const std::string &to,
                                     const std::string &session_id,
                                     const CommonMessage &message) const;
 SignalingMessage BuildCommonMessage(const std::string &to,
                                     const std::string &session_id,
                                     const char *type,
                                     int result = kResultOK,
                                     bool ack = false,
                                     const std::string &data = {},
                                     bool encode = false) const;
 SignalingMessage BuildWebRtcMessage(const std::string &to,
                                     const std::string &session_id,
                                     const WebRtcMessage &message) const;

 const std::string local_userid_;
 const UserAgent user_agent_;
private:
 friend class base::RefCountedThreadSafe<SignalingMessageBuilder>;
 virtual ~SignalingMessageBuilder();
 DISALLOW_COPY_AND_ASSIGN(SignalingMessageBuilder);
};

class WebRtcMessageSender {
public:
 class Builder {
 public:
  ~Builder() = default;
  void SendCommonMessage(const char *type,
                         int result = kResultOK,
                         bool ack = false,
                         const std::string &data = {},
                         bool encode = false);
  void SendHangUpMessage(const CallEndReason &reason);
  void SendInviteAckMessage(int result);
  void SendWebRtcMessage(const WebRtcMessage &message) const;
 private:
  friend class WebRtcMessageSender;
  Builder(WebRtcMessageSender *sender,
          const std::string &channel_name,
          const std::string &to,
          const std::string &session_id);
  WebRtcMessageSender *sender_;
  const std::string channel_name_;
  const std::string to_;
  const std::string session_id_;
  DISALLOW_COPY_AND_ASSIGN(Builder);
 };
 explicit WebRtcMessageSender(const scoped_refptr<SignalingMessageBuilder> &message_builder,
                              WebRtcInterface *webrtc_interface);
 virtual ~WebRtcMessageSender() = default;
 bool is_hangup_sent() const;
 bool is_command_sent(const char *type) const;
 scoped_refptr<SignalingMessageBuilder> message_builder() const;
 WebRtcInterface *webrtc_interface() const;
 Builder Get(const std::string &channel_name,
             const std::string &to,
             const std::string &session_id);
 Builder Get(const WebRtcCallInfo &info);
private:
 void SendCommonMessage(const std::string &channel_name,
                        const std::string &to,
                        const std::string &session_id,
                        const char *type,
                        int result = kResultOK,
                        bool ack = false,
                        const std::string &data = {},
                        bool encode = false);
 void SendWebRtcMessage(const std::string &channel_name,
                        const std::string &to,
                        const std::string &session_id,
                        const WebRtcMessage &message) const;
 scoped_refptr<SignalingMessageBuilder> message_builder_;
 WebRtcInterface *webrtc_interface_;
 std::set<std::string> type_history_;
 base::ThreadChecker thread_checker_;
 DISALLOW_COPY_AND_ASSIGN(WebRtcMessageSender);
};
}

#endif //WEBRTC_CORE_WEBRTC_MESSAGE_SENDER_H_
///////////////////////////////////////////////
//具体实现
#include "webrtc/core/webrtc_message_sender.h"
#include "base/stl_util.h"

namespace webrtc {

SignalingMessageBuilder::SignalingMessageBuilder(const std::string &local_userid,
                                                 const UserAgent &user_agent)
    : local_userid_(local_userid),
      user_agent_(user_agent) {}
SignalingMessageBuilder::SignalingMessageBuilder(const std::string &local_userid,
                                                 const std::string &user_agent)
    : SignalingMessageBuilder(local_userid, UserAgent(user_agent)) {}
SignalingMessageBuilder::~SignalingMessageBuilder() = default;

SignalingMessage
SignalingMessageBuilder::BuildCommonMessage(const std::string &to,
                                            const std::string &session_id,
                                            const CommonMessage &message) const {
  SignalingMessage signaling_message;
  signaling_message.type = kTypeMessage;
  signaling_message.user_agent = user_agent_;
  signaling_message.sessiond_id = session_id;
  signaling_message.from = local_userid_;
  signaling_message.to = to;
  signaling_message.common_message = base::make_optional<CommonMessage>(message);
  return signaling_message;
}

SignalingMessage
SignalingMessageBuilder::BuildCommonMessage(const std::string &to,
                                            const std::string &session_id,
                                            const char *type,
                                            int result,
                                            bool ack,
                                            const std::string &data,
                                            bool encode) const {
  CommonMessage common_message(type, ack, result, data, encode);
  return BuildCommonMessage(to, session_id, common_message);
}

SignalingMessage
SignalingMessageBuilder::BuildWebRtcMessage(const std::string &to,
                                            const std::string &session_id,
                                            const WebRtcMessage &message) const {
  SignalingMessage signaling_message;
  signaling_message.type = kTypeWebRtc;
  signaling_message.user_agent = user_agent_;
  signaling_message.sessiond_id = session_id;
  signaling_message.from = local_userid_;
  signaling_message.to = to;
  signaling_message.webrtc_message = base::make_optional<WebRtcMessage>(message);
  return signaling_message;
}

WebRtcMessageSender::Builder::Builder(WebRtcMessageSender *sender,
                                      const std::string &channel_name,
                                      const std::string &to,
                                      const std::string &session_id)
    : sender_(sender),
      channel_name_(channel_name),
      to_(to),
      session_id_(session_id) {}

void WebRtcMessageSender::Builder::SendCommonMessage(const char *type,
                                                     int result,
                                                     bool ack,
                                                     const std::string &data,
                                                     bool encode) {
  if (channel_name_.empty()) return;
  sender_->SendCommonMessage(channel_name_, to_, session_id_,
                             type, result, ack, data, encode);
}

void WebRtcMessageSender::Builder::SendHangUpMessage(const CallEndReason &reason) {
  if (channel_name_.empty()) return;
  sender_->SendCommonMessage(channel_name_, to_, session_id_,
                             kTypeHangup, reason.result, false, reason.source.ToJson(), true);
}

void WebRtcMessageSender::Builder::SendInviteAckMessage(int result) {
  if (channel_name_.empty()) return;
  sender_->SendCommonMessage(channel_name_, to_, session_id_,
                             kTypeInvite, result, true);
}

void WebRtcMessageSender::Builder::SendWebRtcMessage(const WebRtcMessage &message) const {
  if (channel_name_.empty()) return;
  sender_->SendWebRtcMessage(channel_name_, to_, session_id_, message);
}

WebRtcMessageSender::WebRtcMessageSender(const scoped_refptr<SignalingMessageBuilder> &message_builder,
                                         WebRtcInterface *webrtc_interface)
    : message_builder_(message_builder),
      webrtc_interface_(webrtc_interface) {
  thread_checker_.DetachFromThread();
}

bool WebRtcMessageSender::is_hangup_sent() const {
  return is_command_sent(kTypeHangup);
}

bool WebRtcMessageSender::is_command_sent(const char *type) const {
  DCHECK(thread_checker_.CalledOnValidThread());
  return base::ContainsKey(type_history_, type);
}

scoped_refptr<SignalingMessageBuilder>
WebRtcMessageSender::message_builder() const {
  return message_builder_;
}

WebRtcInterface *WebRtcMessageSender::webrtc_interface() const {
  return webrtc_interface_;
}

WebRtcMessageSender::Builder WebRtcMessageSender::Get(const std::string &channel_name,
                                                      const std::string &to,
                                                      const std::string &session_id) {
  return {this, channel_name, to, session_id};
}

WebRtcMessageSender::Builder WebRtcMessageSender::Get(const WebRtcCallInfo &info) {
  return {this, info.channel_name, info.peer_userid, info.session_id};
}

void WebRtcMessageSender::SendCommonMessage(const std::string &channel_name,
                                            const std::string &to,
                                            const std::string &session_id,
                                            const char *type,
                                            int result,
                                            bool ack,
                                            const std::string &data,
                                            bool encode) {
  DCHECK(thread_checker_.CalledOnValidThread());
  type_history_.emplace(type);
  auto message = message_builder_->BuildCommonMessage(to, session_id, type, result, ack, data, encode);
  webrtc_interface_->OnWebRtcCallSignalingMessageToPeer(channel_name, message);
}

void WebRtcMessageSender::SendWebRtcMessage(const std::string &channel_name,
                                            const std::string &to,
                                            const std::string &session_id,
                                            const WebRtcMessage &message) const {
  auto signaling_message
      = message_builder_->BuildWebRtcMessage(to, session_id, message);
  webrtc_interface_->OnWebRtcCallSignalingMessageToPeer(channel_name, signaling_message);
}
}

⚠️ **GitHub.com Fallback** ⚠️