为什么80%的码农都做不了架构师?>>>   

WebRTC是谷歌的开源的实时视频音频聊天技术,支持跨平台,Nat穿透技术(Stun,Turn,Ice),在部分支持Html5的浏览器里集成了这个功能。

至目前为止支持的PC浏览器有:Chrome 31+,opera 19+,FireFox 26+

至目前为止支持的Android浏览器有:Chrome,opera,FireFox

IE所有版本均不支持!!

IPhone手机暂不支持!!

整个WebRtc里面已经封装好了视频音频采集和传输,你需要做的就是使用任何可以实现WebSocket的语言来开发一套信令服务器

信令服务器负责用户拨号控制,可以集成用户验证等功能来验证用户身份等等,需要为WebRTC做的只有传递协议数据,将一边的传递给另一边,让两边互相了解对方的浏览器视频音频解码类型,版本情况,内外网情况等等,

需要使用的有:vs

chrome

一个公网IP

CentOS

turnserver(https://code.google.com/p/rfc5766-turn-server/)

(这个版本集成了stun和turn,不需要分别再安装了)

需要使用的库:Fleck:一个.net的WebSocket库,百度可以搜得到。

LitJson:一个小巧的Json解析库。

IWebSocketConnection类默认没有Args属性,是我后来修改源码添加的。

下面是我自己写的一个简单的WebRTC服务端,也就是信令服务器

using Fleck;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Reflection;
using LitJson;
namespace WebRtc
{public class Work{public Dictionary<string, IWebSocketConnection> ClientList =new Dictionary<string, IWebSocketConnection>();public string Id = null;public IWebSocketConnection Master = null;public string WorkName = null;public void start(){foreach (WebSocketConnection suser in ClientList.Values){foreach (WebSocketConnection duser in ClientList.Values){if (suser == duser) continue;JsonData jd = JsonHelper.GetJson("conn", "main");jd["wname"] = this.Id;jd["duser"] = duser.Args["username"].ToString();jd["suser"] = suser.Args["username"].ToString();jd["type"] = "start";suser.Send(jd.ToJson());}}}}public class Str{public const string Falid = "falid";public const string Success = "success";public const string Exist = "exist";}public class Command{public const string CreateWork = "createWork";public const string Login = "login";public const string Join = "join";public const string Sec = "sec";public const string Conn = "conn";public const string Start = "start";}class WebRTCServer : IDisposable{public Dictionary<string, Work> WorkList = new Dictionary<string, Work>(); //声明会议室列表public Dictionary<string, IWebSocketConnection> UserList =new Dictionary<string, IWebSocketConnection>(); //声明已登录的用户列表private WebSocketServer server; //声明WebSocket服务类public WebRTCServer(int port) : this("ws://0.0.0.0:" + port) { }public WebRTCServer(string URL){server = new WebSocketServer(URL);server.Start(socket =>{socket.OnMessage = message =>{OnReceive(socket, message);};socket.OnClose = () =>{OnDisconnect(socket);};});}private void OnConnected(IWebSocketConnection context){}private void OnDisconnect(IWebSocketConnection context){if (UserList.Count == 0) return;string key = null;foreach (string i in UserList.Keys)if (UserList[i] == context) key = i;if (key != null) UserList.Remove(key);key = null;foreach (string i in WorkList.Keys){foreach(string u in WorkList[i].ClientList.Keys)if (WorkList[i].ClientList[u] == context) key = u;if (key != null) WorkList[i].ClientList.Remove(key);}key = null;foreach (string i in WorkList.Keys){if (WorkList[i].Master == context)key = i;}if (key != null) WorkList.Remove(key);context = null;}private void OnReceive(IWebSocketConnection context,string msg){if (!msg.Contains("command")) return; //如果没有命令字符跳出JsonData jd = JsonMapper.ToObject(msg);string command = jd["command"].ToString();if (!UserList.ContainsValue(context)) //判断是否登录{switch (command) //未登录情况下的处理{case Command.Login : //登录处理try{string username = jd["username"].ToString();context.Args.Add("username", username);UserList.Add(username, context);context.Send(JsonHelper.GetJsonStr(Command.Login, null, Str.Success));}catch { context.Send(JsonHelper.GetJsonStr(Command.Login, null, Str.Falid)); }break;default: //未登录情况下的默认处理context.Send(JsonHelper.GetJsonStr(Command.Sec, null, Str.Falid));break;}}else{switch (command) //登录之后的处理{case Command.CreateWork: //创建聊天室,这里是工作try{string wname = jd["wname"].ToString();if (!WorkList.ContainsKey(wname)){WorkList.Add(wname, new Work() { Master = context, Id = wname, WorkName = wname });context.Send(JsonHelper.GetJsonStr(Command.CreateWork, wname, Str.Success));}else context.Send(JsonHelper.GetJsonStr(Command.CreateWork, wname, Str.Exist));}catch { context.Send(JsonHelper.GetJsonStr(Command.CreateWork, null, Str.Falid)); }break;case Command.Join: //用户加入try{string wname = jd["wname"].ToString(); string username = jd["username"].ToString();if (!WorkList[wname].ClientList.ContainsKey(username)){WorkList[wname].ClientList.Add(username, context);context.Send(JsonHelper.GetJsonStr(Command.Join, wname, Str.Success));}else context.Send(JsonHelper.GetJsonStr(Command.Join, wname, Str.Exist));}catch { context.Send(JsonHelper.GetJsonStr(Command.Join, null, Str.Falid)); }break;case Command.Start: //正式开始,发起连接try{string wname = jd["wname"].ToString();if (WorkList[wname].Master == context){WorkList[wname].start();}else { context.Send(JsonHelper.GetJsonStr(Command.Sec, null, Str.Falid));}}catch { context.Send(JsonHelper.GetJsonStr(Command.Start, null, Str.Falid)); }break;case Command.Conn: //WebRtc命令转发try{string dname = jd["duser"].ToString();UserList[dname].Send(msg);}catch { }break;}}}public void Dispose(){try{foreach (IWebSocketConnection i in UserList.Values){i.Close();}server.Dispose();UserList.Clear();WorkList.Clear();}catch { }}}public class JsonHelper{public static JsonData GetJson(string command, string ret){JsonData jd = new JsonData();jd["command"] = command;jd["ret"] = ret;return jd;}public static string GetJsonStr(string command, string data, string ret){JsonData jd = new JsonData();jd["command"] = command;jd["data"] = data;jd["ret"] = ret;return jd.ToJson();}}
}

下面是网页端的Js代码,算是客户端,rtc_main.js

var socket;
var PeerConnection = (window.PeerConnection || window.webkitPeerConnection00 || window.webkitRTCPeerConnection || window.mozRTCPeerConnection);
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
var localstream = null;
var rpc = new Array();
var dpc = new Array();
var vrpc = new Array();
var camer_stream = {audio:true, video:{mandatory: {maxWidth: 640,maxHeight: 360}}}
var rconn_count = 1;
var servers = {"iceServers": [{"url":"stun:1.1.1.1"}, //这里1.1.1.1对应你的公网IP{"url":"turn:1.1.1.1?transport=tcp", "credential":"user", "username":"passwd"},]
};
window.onload = function() {console.log("获取本地视频源...");navigator.getUserMedia(camer_stream, getUMsuccess, function() {});
}
function getUMsuccess(stream){console.log("获取本地视频源成功!");vid1.src = webkitURL.createObjectURL(stream); //本地视频显示localstream = stream; //本地流
}
function connect () {socket = new WebSocket("ws://" + server.value + ":8889");setSocketEvents(socket); //设置WebSocket监听事件}
function setSocketEvents(Socket) {Socket.onopen = function() { //连接成功处理方法console.log("Socket已连接!");send(JSON.stringify({"command":"login", "username":username.value}))};Socket.onmessage = function(Message) { //接收信息处理方法var obj = JSON.parse(Message.data);var command = obj.command;switch(command){case "createWork" : {if (obj.ret == "success") console.log("创建会议室成功!");else if(obj.ret == "exist") console.log("会议室已存在!");else console.log("创建会议室失败!");break;}case "login" : {obj.ret == "success" ? console.log("登录成功!") : console.log("登录失败!");break;}case "join" : {obj.ret == "success" ? console.log("加入会议室成功!") : console.log("加入会议室失败!");break;}case "sec" : {console.log("没有权限!");break;}case "conn" : {Conn(obj);break;}default : {console.log(Message.data);}}};Socket.onclose = function() {console.log("Socket连接已断开!");}
}
function createWork() {console.log("创建会议室:" + work.value);var obj = JSON.stringify({"command":"createWork", "wname":work.value});send(obj);
}
function join() {console.log("加入会议室:" + work.value);var obj = JSON.stringify({"command":"join", "wname":work.value, "username":username.value});send(obj);
}
function startwork(){console.log("会议开始:" + work.value);var obj = JSON.stringify({"command":"start", "wname":work.value});send(obj);
}
function Conn(jd){///      发起端代码     ///if (jd.ret == "main"){if (jd.type=="start"){console.log("发起连接:wname:" + jd.wname + ",sname:" + jd.suser +",dname:" + jd.duser);rpc[jd.duser] = new webkitRTCPeerConnection(servers);var trpc = rpc[jd.duser]; vrpc[jd.duser] = ++rconn_count;trpc.addStream(localstream);trpc.onaddstream = function(e){try{document.getElementById('vid' + vrpc[jd.duser]).src = webkitURL.createObjectURL(e.stream);console.log("连接远程媒体成功!");}catch(ex){console.log("连接远程媒体失败!",ex);}};trpc.onicecandidate = function(event){if (event.candidate) {var obj = JSON.stringify({"command":"conn", "type":"ice_data", "suser":jd.suser, "duser":jd.duser, "wname":jd.wname, "ret":"msg", "data":JSON.stringify(event.candidate)});send(obj);}};trpc.createOffer(function(desc){trpc.setLocalDescription(desc);var obj = JSON.stringify({"command":"conn", "type":"offer", "suser":jd.suser, "duser":jd.duser, "wname":jd.wname, "ret":"msg", "data":JSON.stringify(desc)});send(obj);});}else if(jd.type=="answer"){rpc[jd.suser].setRemoteDescription(new RTCSessionDescription(JSON.parse(jd.data)));}else if(jd.type=="ice_data"){console.log("main_candidate",jd.data);rpc[jd.suser].addIceCandidate(new RTCIceCandidate(JSON.parse(jd.data)));}///      接收端代码     ///}else if(jd.ret == "msg"){if (jd.type=="offer"){console.log("接受连接:wname:" + jd.wname + ",sname:" + jd.suser + ",dname:" + jd.duser);dpc[jd.suser] = new webkitRTCPeerConnection(servers);var trpc = dpc[jd.suser];trpc.setRemoteDescription(new RTCSessionDescription(JSON.parse(jd.data)));trpc.addStream(localstream);trpc.onicecandidate = function(event){if (event.candidate) {var obj = JSON.stringify({"command":"conn", "type":"ice_data", "suser":jd.duser, "duser":jd.suser, "wname":jd.wname, "ret":"main", "data":JSON.stringify(event.candidate)});send(obj);}};trpc.createAnswer(function(desc){trpc.setLocalDescription(desc);var obj = JSON.stringify({"command":"conn", "type":"answer", "suser":jd.duser, "duser":jd.suser, "wname":jd.wname, "ret":"main", "data":JSON.stringify(desc)});send(obj);});}else if(jd.type=="ice_data"){console.log("client_candidate",jd.data);dpc[jd.suser].addIceCandidate(new RTCIceCandidate(JSON.parse(jd.data)));}}
}
function send(data){try{socket.send(data);}catch(ex){console.log("消息发送失败!");}
}

网页前台代码。。。很简陋,vid可无限扩展

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>视频会议</title>
<link rel="stylesheet" href="css/main.css" />
<style>
div#container {max-width: 90%;
}
video {margin: 0 0.5em 1.5em 0;
}
@media screen and (min-width: 800px) {video {width: 45%;}
}
</style>
<script src="js/rtc_main.js"></script>
</head>
<body>
<div id="container"><video id="vid1" width="640" height="480" autoplay></video><video id="vid2" width="640" height="480" autoplay></video><div>
<input type="text" id="server" size="30" value='1.1.1.1'/><input type="text" id="work" size="30" value='work1'/><input type="text" id="username" size="30" value='user1'/><button id="btn1" onclick="connect()">连接服务器</button><button id="btn2" onclick="createWork()">创建工作区</button><button id="btn3" onclick="join()">连接到工作区</button><button id="btn4" onclick="startwork()">开始会议</button></div>
</div>
</body>
</html>

main.css

a {
color: #77aaff;
text-decoration: none;
}a:hover {
color: #88bbff;
text-decoration: underline;
}a#viewSource {
display: block;
margin: 1.3em 0 0 0;
border-top: 1px solid #999;
padding: 1em 0 0 0;
}#server{margin: 0 0.5em 0 0;width: 7.5em;color: #aaa;
}div#links a {
display: block;
line-height: 1.3em;
margin: 0 0 1.5em 0;
}@media screen and (min-width: 1000px) {
/* hack! to detect non-touch devices */div#links a {line-height: 0.8em;}
}audio {
max-width: 100%;
}body {
background: #9999;
font-family: Arial, sans-serif;
padding: 20px;
word-break: break-word;
}button {
margin: 0 0.5em 0 0;
width: 9em;
height: 5em;
}button[disabled] {
color: #aaa;
}code {
font-family: 'Courier New', monospace;
letter-spacing: -0.1em;
}div#container {
background: #000;
margin: 0 auto 0 auto;
max-width: 40em;
padding: 1em 1.5em 1.3em 1.5em;
}div#links {padding: 0.5em 0 0 0;
}h1 {
border-bottom: 1px solid #aaa;
color: white;
font-family: Arial, sans-serif;
margin: 0 0 0.8em 0;
padding: 0 0 0.4em 0;
}h2 {
color: #ccc;
font-family: Arial, sans-serif;
margin: 1.8em 0 0.6em 0;
}html {
/* avoid annoying page width change
when moving from the home page */
overflow-y: scroll;
}img {
border: none;
max-width: 100%;
}p {
color: #eee;
line-height: 1.6em;
}p#data {
border-top: 1px dotted #666;
font-family: Courier New, monospace;
line-height: 1.3em;
max-height: 800px;
overflow-y: auto;
padding: 1em 0 0 0;
}p.borderBelow {
border-bottom: 1px solid #aaa;
padding: 0 0 20px 0;
}video {
background: #222;
width: 100%;
}@media screen and (min-width: 800px) {video {}
}@media screen and (max-width: 800px) {video {}
}

下面是Linux配置Stun和Turn服务端

先下载依赖包libevent编译安装

wget https://cloud.github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
tar -xvf libevent-2.0.21-stable.tar.gz
cd libevent*
./configure
make && make install

再下载服务端turnserver编译安装

wget http://turnserver.open-sys.org/downloads/v3.2.3.96/turnserver-3.2.3.96.tar.gz
tar -xvf turnserver-3.2.3.96.tar.gz
cd turnserver*
./configure
make && make install

修改服务端配置文件

cd /usr/local/etc/
cp -p turnserver.conf.default turnserver.conf
cp -p turnuserdb.conf.default turnuserdb.conf
vi turnserver.conf

查找修改以下内容,保存退出。

listening-device=eth1               服务器监听哪块网卡
listening-ip=1.1.1.1        服务器监听哪一个IP 这里1.1.1.1对应你的公网IP

其他选项根据情况设置,有详细的解释

下一步生成用户Key,用来验证用户,(不包含中括号)

turnadmin -k -u [用户名] -r [登录域(例:baidu.com)] -p [密码]

这个命令会产生一个0x开头的字符串,这便是用户的Key。

然后把用户名和Key保存在turnuserdb.conf里

vi turnuserdb.conf

下面是写入内容,保存退出。

[用户名]:[Key]

现在服务器配置完成,可启动服务了。直接运行turnserver即可。

客户端访问测试。

转载于:https://my.oschina.net/u/858881/blog/293751

C#+WebSocket+WebRTC多人语音视频系统相关推荐

  1. 基于webrtc多人音视频的研究(一)

    所周知,WebRTC非常适合点对点(即一对一)的音视频会话.然而,当我们的客户要求超越一对一,即一对多.多对一设置多对多的解决方案或者服务,那么问题就来了:"我们应该采用什么样的架构?&qu ...

  2. 【转】WebRTC多人音视频解决方案

    文章目录 1. 引言 2. 解决方案 2.1 Mesh解决方案 2.2 Mixer解决方案 2.3 Router解决方案 2.4 三个解决方案的流量对比 3. 应该使用哪种架构? 4. 参考资料 1. ...

  3. iOS WebRTC多人音视频建立的流程

    前言 本文主要以"代码是最好的注释"为基点,介绍在处理iOS端多人音视频的建立流程. 在看本篇前建议先了解一下多人音视频通讯现在的常用架构,参考<WebRTC多人音视频聊天架 ...

  4. VUE实现Web端多人语音视频聊天

    1 多人语音聊天功能介绍 本文展示了如何使用 ZEGO Express SDK 构造多人音视频通话场景,即实现多对多实时音视频聊天互动.用户可在房间内与其余用户进行实时音视频通话,互相推拉流.该场景可 ...

  5. 百万并发电信级统一即时通讯(im+voip+多人语音)系统源码

    产品开发地点:广州  团队人数:7人,产品开发时间:3年7个月 产品模块: 完全自主研发的im客户端(没有使用任何第三方控件,完全自主开发) 服务端(openfire xmpp协议 mysql数据库) ...

  6. 类似YY 9158网页版多人语音视频聊天室远程教学系统源码

    仿六间房网页视频聊天室 网页视频直播系统源码 开发采用FMS加flash只需在网页上就可发布视频直播,无需安装插件儿 主要应用远程教育,培训,远程视频直播(类似6间房聊天室) 视频采用H264压缩一路 ...

  7. WebRTC多人音视频聊天架构及实战

    三种模式 简单介绍一下基于 WebRTC 的多人通信的几种架构模式. 1.Mesh 架构 我们之前写过几个 1 v 1 的栗子,它们的连接模式如下: 这是典型的端到端对等连接,所以当我们要实现多人视频 ...

  8. html5 视频语音对讲,一种基于WebRTC的多人语音视频通话方法及系统与流程

    本发明涉及视频通话领域,特别涉及一种基于WebRTC的多人语音视频通话方法及系统. 背景技术: 随着互联网技术和通信技术的快速发展,人们的交流方式与交流内容得到了极大的丰富和发展.在节奏越来越快的信息 ...

  9. 实现一个简单的语音聊天室(多人语音聊天系统)

    多人语音聊天,或语音聊天室,是即时通信应用中常见的功能之一,比如,QQ的语音讨论组就是我们用得比较多的. 本文将基于最新版本的OMCS(V3.5)实现一个简单的语音聊天室,让多个人可以进入同一个房间进 ...

最新文章

  1. 什么是壳 - 脱壳篇01
  2. html 后台参数attribute_平台管理后台与商家菜单资源管理:商家权限及其菜单资源管理设计...
  3. mxnet优化器 SGD_GC
  4. 内存:你跑慢点行不行?CPU:跑慢点你养我吗?内存:我不管!
  5. [PHP] PHP调用IMAP协议读取邮件类库
  6. linux 循环每个月,SHELL脚本每月最后一天判断
  7. 所谓的inference场景与深度学习终端加速器以及边缘计算和雾计算
  8. javascript 分号_让我们谈谈JavaScript中的分号
  9. Java8————Optional
  10. 基于JAVA+SpringMVC+Mybatis+MYSQL的餐厅收银管理系统
  11. dubbo接口统一异常处理的两种方式
  12. Android入门(9)AudioRecord和AudioTrack类的使用【转】http://blog.sina.com.cn/s/blog_6309e1ed0100j1rw.html...
  13. 迅为IMX6ULL开发板Linux RS232/485驱动实验(上)
  14. 计算机网络中netbuie,材料内部空隙体积占其总体积的百分率叫做()。A、孔隙率B、填充率C、空隙率D、密实度...
  15. 百度地图API加载点位
  16. 北京今日限行 API数据接口
  17. 大数据之路:阿里巴巴大数据实践(数据模型篇)
  18. 移动硬盘上装双系统Linux
  19. 个税计算器 / 微信小程序开发
  20. gensim w2v 使用记录

热门文章

  1. 调试兼容性该注意的的点
  2. Restful设计相关
  3. chart 模板 - 每天5分钟玩转 Docker 容器技术(165)
  4. 如何快捷输入函数上方的注释代码(Summary)
  5. Android布局之相对布局——RelativeLayout
  6. Windows Azure虚拟机概览
  7. Jetty 9.0.0 首个里程碑出现
  8. C#--多线程--2
  9. 最保险的函数间数组作为参数值传递与返回方法,用memcpy函数
  10. 计算机应用基础851,清华大学851西方经济学考研参考书目及考研真题