webrtc介绍

WebRTC实现了基于网页的视频会议,标准是WHATWG 协议,目的是通过浏览器提供简单的javascript就可以达到实时通讯(Real-Time Communications (RTC))能力。





实时语音demo

环境搭建

需要信令服务器与中继服务器作为中转

1.express,socket下载,搭node后端(express)(信令服务器)

点击查看代码
 npm install express
npm install socket.io
npm i加载包
node index.js启动

2.后端index.js编写

点击查看代码
var express = require('express');
var app = express();
var http = require('http').createServer(app);
//var io = require('socket.io')(http);
const fs = require('fs');
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
})

现在能看到基础页面index.html

3.监听端口与websoket连接,见图3

点击查看代码
http.listen(443, () => {
  console.log('https listen on');
}); io.on("connection", (socket) => {
  //连接加入子房间
  socket.join( socket.id );
  console.log("a user connected " + socket.id);
 
  socket.on("disconnect", () => {
      console.log("user disconnected: " + socket.id);
      //某个用户断开连接的时候,我们需要告诉所有还在线的用户这个信息
      socket.broadcast.emit('user disconnected', socket.id);
//交流,前后端通过emit发送信息
//除自己外,对其他人广播:socket.broadcast.emit
//自己和其他人广播:io.emit
  });
socket.on('new user greet', (data) => {
      console.log(data);
      console.log(socket.id + ' greet ' + data.msg);
      socket.broadcast.emit('need connect', {sender: socket.id, msg : data.msg});
  });
  //在线用户回应新用户消息的转发
  socket.on('ok we connect', (data) => {
      io.to(data.receiver).emit('ok we connect', {sender : data.sender});
  });
  //sdp 消息的转发
  socket.on( 'sdp', ( data ) => {
      console.log('sdp');
      console.log(data.description);
      //console.log('sdp:  ' + data.sender + '   to:' + data.to);
      socket.to( data.to ).emit( 'sdp', {
          description: data.description,
          sender: data.sender
      } );
  } );
  //candidates 消息的转发
  socket.on( 'ice candidates', ( data ) => {
      console.log('ice candidates:  ');
      console.log(data);
      socket.to( data.to ).emit( 'ice candidates', {
          candidate: data.candidate,
          sender: data.sender
      } );
  } );
});

4.index.html代码

点击查看代码
<h1 id="user-id">用户名称</h1>
<ul id="user-list">
<li>用户列表</li>
</ul>
<audio id="video-local" preload="auto" controls>
<!-- <source src="https://192.168.3.56/a8485020-39a2-4c09-904b-2e2b87d71363" type="audio/mpeg" /> -->
</audio>
<audio id="audio">
<!-- <source src="/public/imgs/wss.wav" type="audio/mpeg" /> -->
</audio>
<ul id="videos"></ul>
<script src="//cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.js
"></script>
<script src="//cdn.bootcdn.net/ajax/libs/socket.io/3.0.4/socket.io.js
"></script>
<script>
function getUserMedia(constrains, success, error) {
let promise;
if (navigator.mediaDevices.getUserMedia) {
//最新标准API
promise = navigator.mediaDevices
.getUserMedia(constrains)
.then(success)
.catch(error);
} else if (navigator.webkitGetUserMedia) {
//webkit内核浏览器
promise = navigator
.webkitGetUserMedia(constrains)
.then(success)
.catch(error);
} else if (navigator.mozGetUserMedia) {
//Firefox浏览器
promise = navagator
.mozGetUserMedia(constrains)
.then(success)
.catch(error);
} else if (navigator.getUserMedia) {
//旧版API
promise = navigator
.getUserMedia(constrains)
.then(success)
.catch(error);
}
return promise;
}
function canGetUserMediaUse() {
return !!(
navigator.mediaDevices.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia
);
}
//中继服务器:最好自己搭,TURN中继服务器
const iceServer = {
iceServers: [
{ urls: ["stun:ss-turn1.xirsys.com"] },
{
username:
"CEqIDkX5f51sbm7-pXxJVXePoMk_WB7w2J5eu0Bd00YpiONHlLHrwSb7hRMDDrqGAAAAAF_OT9V0dWR1d2Vi",
credential: "446118be-38a4-11eb-9ece-0242ac140004",
urls: [
"turn:ss-turn1.xirsys.com:80?transport=udp",
"turn:ss-turn1.xirsys.com:3478?transport=udp",
],
},
],
};
let audio = document.getElementById("audio");
var pc = [];
function StartCall(parterName, createOffer) {
pc[parterName] = new RTCPeerConnection(iceServer); //如果已经有本地流,那么直接获取Tracks并调用addTrack添加到RTC对象中。
if (localStream) {
localStream.getTracks().forEach((track) => {
pc[parterName].addTrack(track, localStream); //should trigger negotiationneeded event
});
} else {
//否则需要重新启动摄像头并获取
if (canGetUserMediaUse()) {
getUserMedia(
{
video: true,
audio: false,
},
function (stream) {
localStream = stream;
localVideoElm.srcObject = stream;
$(localVideoElm).width(800);
},
function (error) {
console.log(
"访问用户媒体设备失败:",
error.name,
error.message
);
}
);
} else {
alert("您的浏览器不兼容");
}
} //如果是呼叫方,那么需要createOffer请求
if (createOffer) {
//每当WebRTC基础结构需要你重新启动会话协商过程时,都会调用此函数。它的工作是创建和发送一个请求,给被叫方,要求它与我们联系。
pc[parterName].onnegotiationneeded = () => {
//https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection/createOffer pc[parterName]
.createOffer()
.then((offer) => {
return pc[parterName].setLocalDescription(offer);
})
.then(() => {
//把发起者的描述信息通过Signal Server发送到接收者
socket.emit("sdp", {
type: "video-offer",
description: pc[parterName].localDescription,
to: parterName,
sender: socket.id,
});
});
};
} //当需要你通过信令服务器将一个ICE候选发送给另一个对等端时,本地ICE层将会调用你的 icecandidate 事件处理程序。有关更多信息,请参阅Sending ICE candidates 以查看此示例的代码。
pc[parterName].onicecandidate = ({ candidate }) => {
socket.emit("ice candidates", {
candidate: candidate,
to: parterName,
sender: socket.id,
});
}; //当向连接中添加磁道时,track 事件的此处理程序由本地WebRTC层调用。例如,可以将传入媒体连接到元素以显示它。详见 Receiving new streams 。
pc[parterName].ontrack = (ev) => {
let str = ev.streams[0]; if (document.getElementById(`${parterName}-video`)) {
document.getElementById(`${parterName}-video`).srcObject = str;
} else {
let newVideo = document.createElement("audio");
newVideo.id = `${parterName}-video`;
newVideo.autoplay = true;
newVideo.controls = true;
//newVideo.className = 'remote-video';
newVideo.srcObject = str;
let li = document.createElement("li");
li.id = `audio-${parterName}`;
li.innerHTML = `<p>与${parterName}通话<p>`;
li.appendChild(newVideo);
let button = $('<button class="uncall" >结束通话</button>');
button.appendTo(li);
document.getElementById("videos").appendChild(li);
$('#user-list li[user-id="' + parterName + '"] .call').css(
"display",
"none"
);
}
};
}
var socket = io();
let b = 0;
socket.on("connect", () => {
InitCamera(socket.id);
//输出内容 其中 socket.id 是当前socket连接的唯一ID
console.log("connect " + socket.id);
$("form").submit(function (e) {
//禁止页面重新加载
e.preventDefault(); //prevents page reloading
//发送事件,其值为文本框中输入的值 socket.emit("chat message", $("#m").val()); $("#messages").append(
$("<li>")
.text($("#m").val() + ":" + socket.id)
.attr("class", "my")
);
//清空文本框的值
$("#m").val("");
//返回false 禁止原始的提交
return false;
}); $("#user-id").text(socket.id); pc.push(socket.id); socket.emit("new user greet", {
sender: socket.id,
msg: "hello world",
});
socket.on("need connect", (data) => {
console.log("need!!!!!!!!!!!!!"); console.log(data);
//创建新的li并添加到用户列表中
let li = $("<li></li>")
.text(data.sender)
.attr("user-id", data.sender);
$("#user-list").append(li);
//同时创建一个按钮
let button = $('<button class="call" >通话</button>');
button.appendTo(li);
//监听按钮的点击事件, 这是个demo 需要添加很多东西,比如不能重复拨打已经连接的用户
$(button).click(function () {
//$(this).parent().attr('user-id')
console.log($(this).parent().attr("user-id"));
//点击时,开启对该用户的通话
StartCall($(this).parent().attr("user-id"), true);
}); socket.emit("ok we connect", {
receiver: data.sender,
sender: socket.id,
});
});
//某个用户失去连接时,我们需要获取到这个信息
socket.on("user disconnected", (socket_id) => {
console.log("disconnect : " + socket_id);
$('#user-list li[user-id="' + socket_id + '"]').remove();
$("#audio-" + socket_id).remove();
$('#user-list li[user-id="' + socket_id + '"] .call').css(
"display",
"block"
);
});
//链接吧..
socket.on("ok we connect", (data) => {
console.log(data);
let li = $("<li></li>")
.text(data.sender)
.attr("user-id", data.sender);
$("#user-list").append(li);
let button = $('<button class="call">通话</button>');
button.appendTo(li);
//监听按钮的点击事件, 这是个demo 需要添加很多东西,比如不能重复拨打已经连接的用户
$(button).click(function () {
//$(this).parent().attr('user-id')
console.log($(this).parent().attr("user-id"));
//点击时,开启对该用户的通话
StartCall($(this).parent().attr("user-id"), true);
});
//这里少了程序,比如之前的按钮啊,按钮的点击监听都没有。
}); //监听发送的sdp事件
socket.on("sdp", (data) => {
//如果时offer类型的sdp
if (data.description.type === "offer") {
//那么被呼叫者需要开启RTC的一套流程,同时不需要createOffer,所以第二个参数为false
StartCall(data.sender, false);
//把发送者(offer)的描述,存储在接收者的remoteDesc中。
let desc = new RTCSessionDescription(data.description);
//按1-13流程走的
pc[data.sender].setRemoteDescription(desc).then(() => {
pc[data.sender]
.createAnswer()
.then((answer) => {
return pc[data.sender].setLocalDescription(answer);
})
.then(() => {
socket.emit("sdp", {
type: "video-answer",
description: pc[data.sender].localDescription,
to: data.sender,
sender: socket.id,
});
})
.catch(); //catch error function empty
});
} else if (data.description.type === "answer") {
//如果使应答类消息(那么接收到这个事件的是呼叫者)
let desc = new RTCSessionDescription(data.description);
pc[data.sender].setRemoteDescription(desc);
}
}); //如果是ice candidates的协商信息
socket.on("ice candidates", (data) => {
console.log("ice candidate: " + data.candidate);
//{ candidate: candidate, to: partnerName, sender: socketID }
//如果ice candidate非空(当candidate为空时,那么本次协商流程到此结束了)
if (data.candidate) {
var candidate = new RTCIceCandidate(data.candidate);
//讲对方发来的协商信息保存
pc[data.sender].addIceCandidate(candidate).catch(); //catch err function empty
}
});
});

以上domo只能本地运行,如果需要网址运行且移动端,需要https。可以选择自签证书尝试,自签证书见下一篇博客

webrtc实时视频语音实现的更多相关文章

  1. webrtc笔记(2): 1对1实时视频/语音通讯原理概述

    开始正文之前,先思考1个问题:2个处于不同网络环境的(具备摄像头/麦克风多媒体设备的)浏览器,要实现点对点的实时视频/语音通讯,难点在哪? 至少得先搞定下面2个问题: 1.彼此要了解对方支持的媒体格式 ...

  2. webRTC中语音降噪模块ANS细节详解(二)

    上篇(webRTC中语音降噪模块ANS细节详解(一))讲了维纳滤波的基本原理.本篇先给出webRTC中ANS的基本处理过程,然后讲其中两步(即时域转频域和频域转时域)中的一些处理细节. ANS的基本处 ...

  3. webRTC中语音降噪模块ANS细节详解(三)

    上篇(webRTC中语音降噪模块ANS细节详解(二))讲了ANS的处理流程和语音在时域和频域的相互转换.本篇开始讲语音降噪的核心部分,首先讲噪声的初始估计以及基于估计出来的噪声算先验信噪比和后验信噪比 ...

  4. webRTC中语音降噪模块ANS细节详解(四)

    上篇(webRTC中语音降噪模块ANS细节详解(三))讲了噪声的初始估计方法以及怎么算先验SNR和后验SNR. 本篇开始讲基于带噪语音和特征的语音和噪声的概率计算方法和噪声估计更新以及基于维纳滤波的降 ...

  5. webRTC中语音降噪模块ANS细节详解(一)

    ANS(adaptive noise suppression) 是webRTC中音频相关的核心模块之一,为众多公司所使用.从2015年开始,我在几个产品中使用了webRTC的3A(AEC/ANS/AG ...

  6. webrtc 实时视频 .net websocket信令服务器

    这篇文章主要参考了 Webrtc WebSocket实现音视频通讯,非常感谢提供代码 前端部分完全是从这篇文章复制过来的,只是修改了webscket的url,还有加入了webrtc-adapterjs ...

  7. 实时视频直播客户端技术盘点:Native、HTML5、WebRTC、微信小程序

    1.前言 2017 年 12 月,微信小程序向开发者开放了实时音视频能力,给业内带来广阔的想象空间.连麦互动视频直播技术在 2016 年直播风口中成为视频直播的标配,然而只有在原生的 APP 上才能保 ...

  8. WebRTC 音视频开发之路

    早在2014年就通过WebRTC实现了PC客户端的实时视频语音,那时P2P连接的建立使用的WebRTC自带的libjingle库,使用peerconnection的API实现的.后来在做远程桌面,文件 ...

  9. WebRTC中的NetEQ

    NetEQ使得WebRTC语音引擎能够快速且高解析度地适应不断变化的网络环境,确保了音质优美且缓冲延迟最小,其集成了自适应抖动控制以及丢包隐藏算法. WebRTC和NetEQ概述 WebRTC Web ...

  10. 基于MCRA-OMLSA的语音降噪(一):原理

    前面的几篇文章讲了webRTC中的语音降噪.最近又用到了基于MCRA-OMLSA的语音降噪,就学习了原理并且软件实现了它.MCRA主要用于噪声估计,OMLSA是基于估计出来的噪声去做降噪.类比于web ...

随机推荐

  1. 解密Prompt系列17. LLM对齐方案再升级 WizardLM & BackTranslation & SELF-ALIGN

    话接上文的指令微调的样本优化方案,上一章是通过多样性筛选和质量过滤,对样本量进行缩减,主打经济实惠.这一章是通过扩写,改写,以及回译等半监督样本挖掘方案对种子样本进行扩充,提高种子指令样本的多样性和复 ...

  2. 开源项目 | 一款基于NodeJs+Vue3的强大的在线设计图片工具

    一.项目概述 一款漂亮且功能强大的在线海报图片设计器,仿稿定设计.适用于海报图片生成.电商分享图.文章长图.视频/公众号封面等多种场景. 二. 技术特性 丝滑的操作体验,丰富的交互细节,基础功能完善 ...

  3. King's Tour 题解

    King's Tour 题面大意 在 \(n\times m\) 的网格中构造一种从 \((1,1)\) 走到 \((a,b)\) 的方案,要求经过所有格子恰好一次,格子之间八联通. 思路分析 模拟赛 ...

  4. 它让你1小时精通RabbitMQ消息队列、且能扛高并发

    支持.Net Core(2.0及以上)与.Net Framework(4.5及以上) 本文所述方案近期被江苏省某亿级数据量+高并发的政府"物联网"项目采用,获得圆满成功!! [目录 ...

  5. 拒绝恶意IP登录服务器

    拒绝恶意IP登录服务器,并加入防火墙黑名单 #!/bin/bash #2020-03-20 16:39 #auto refuse ip dlu #By Precious ############### ...

  6. [Python急救站课程]叠加等边三角形的绘制

    叠加等边三角形的绘制 from turtle import * penup() fd(-100) pendown() pensize(10) seth(60) fd(200) seth(-60) fd ...

  7. 【scipy 基础】--积分和微分方程

    对于手工计算来说,积分计算是非常困难的,对于一些简单的函数,我们可以直接通过已知的积分公式来求解,但在更多的情况下,原函数并没有简单的表达式,因此确定积分的反函数变得非常困难. 另外,相对于微分运算来 ...

  8. 在EXCEL表格中快速自动求和

    在Microsoft Excel中,可以通过多种方式快速自动求和.以下是一种简单但常用的方法: 使用SUM函数 选定求和区域: 在Excel表格中,首先需要选定要进行求和的区域.这可以是一个列.行或者 ...

  9. 第一次使用 GoLand 启动 运行 Go 项目

    一.开始之前确保已经安装 go语言环境 二.新建项目 三.选择项目路径 四.在该目录下手动创建 bin,src两个文件夹 bin 用来存放编译后的 .exe 文件 src 我们的工程的开发文件存放的点 ...

  10. Salesforce LWC学习(四十六) 自定义Datatable实现cell onclick功能

    本篇参考:https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable 背景:我们有时会有这种类 ...