当前位置:   article > 正文

WebRTC 详解信令服务器媒体协商及实战(及通讯过程)_webrtc 通信

webrtc 通信

uni-app webrtc 实现H5音视频通讯 实战项目详情

1. 前言

目前市场上音视频技术方案大致分为以下几类,WebRTC因其超低延时、集成音视频采集传输等优点,是在线教育、远程会议等领域首选技术。

方案优势劣势应用场景
基于浏览器插件的flash播放RTMP即将淘汰即将淘汰传统直播
跨平台的HLS/DASH 播放方案- 跨端广泛支持:苹果浏览器原生支持 - hls.js- 延时高 - 碎片化传统直播,如赛事直播、大型会议直播
基于HTML5 MSE 能力的flv播放技术- 格式简单 - 无需插件- 移动端MSE支持性差 - 一定延时传统直播,同上
WebRTC实时通讯技术- 毫秒级的低延时 - 音视频采集上行传输- 相对复杂 - 支持度低 - 价格高 - 容量有限在线教育、远程会议

WebRTC是 Google 在 2010 年收购 VoIP 软件开发商 GlobalIPSolutions 的 GIPS 引擎后,基于
GIPS 引擎实现的浏览器音视频和数据通信技术,在 2012 年集成到 chrome 浏览器,到目前为止,大部分主流现代浏览器都已经支持。


在这里插入图片描述

2. WebRTC架构

一个简单的音视频架构大致如下:
在这里插入图片描述

  • 音视频采集模块:调用系统API,从系统麦克风和摄像头读取设备并采集音视频数据。音频是PCM数据,视频是YUV数据
  • 音视频编码模块:根据不同类型数据使用不同编码方式,将原始PCM、YUV数据压缩编码
  • 网络传输模块:将压缩编码后的数据封装成RTP包,通过网络传输至对端,同时对端接收RTP数据
  • 音视频解码模块:将接收到的压缩编码数据还原成原始的PCM、YUV/RGB数据
  • 音视频渲染模块:拿到原始数据后,音频数据输出到扬声器,视频数据输出到显示器

如果我们按照上面的架构实现一个音视频通信系统,相当于至少需要开发7个小模块,想想就费时费力。此时WebRTC就可以闪亮登场了,它内部标准化的实现上述架构,并在此基础上进行拓展,对外只暴露了相关的API,其架构图如下( 官网 的有点旧,重新画的):
在这里插入图片描述
WebRTC大体可以分为四层:接口层、Session层、引擎层、设备层:

  • 接口层:暴露给业务侧,业务侧可以使用原生的 C++ API 接口或者 Web API 开发音视频实时通信。核心是 RTCPeerConnection
  • Session层:用于控制业务逻辑,比如媒体协商、收集 Candidate 等
  • 引擎层:包括音频引擎、视频引擎和网络传输
  • 设备层:主要和硬件交互,负责音频的采集和播放,视频的采集,物理网络等

3. WebRTC音视频通信过程

在这里插入图片描述

一个正常音视频通信架构如上图所示,通信双方分别是 caller(主叫) 与 callee(被叫),两边的内部逻辑相似,下面以caller端为例,了解内部流程:

  1. 调用音视频检测模块,检测终端是否有可用的音视频设备
  2. 调用音视频采集模块,采集用户音视频数据
  3. 根据用户选择,是否开启录制(授权)
  4. 通过信令模块交换SDP
  5. 创建WebRTC的核心对象RTCPeerConnection,之后添加采集到的音视频数据
  6. RTCPeerConnection向STUN(SessionTraversal Utilities forNAT)/TURN(Traversal Using Relays aroundNAT)服务器发送请求,返回caller的外网ip地址和端口号
  7. 通过信令服务器,caller和callee互相传递对方的外网ip地址和端口(媒体协商)
  8. 最终P2P链接建立完成,后面就会源源不断的发送音视频数据到对端

下面就是该过程对应的泳道图:
在这里插入图片描述

4. 信令服务器(重要)

信令是实现音视频通信的重要一环,比如创建房间、离开房间、交换双端offer/answer以及candidate信息等。但WebRTC规范文档中并未定义信令相关的内容,因为不同业务,逻辑不同,信令也会千差万别,所以需要各个业务自己实现一套信令服务。
下面以socket.io为例,实现一套信令服务:

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const USER_LIMITS = 3;

const app = express();
const httpServer = http.createServer(app);
const io = new Server(httpServer);

const getRoomUsers = room => {
  const myRoom = io.sockets.adapter.rooms[room];
  return myRoom || [];
};

const getRoomUsersCount = room => {
  return getRoomUsers(room).length;
};

// 连接事件
io.sockets.on('connection', socket => {
  // 转发消息
  socket.on('message', (room, data) => {
    console.log(`message, room: ${room}, data type: ${data.type}`);
    socket.to(room).emit('message', room, data);
  });

  // 加入房间
  socket.on('join', room => {
    socket.join(room);
    const userCount = getRoomUsersCount(room);
    console.log(`user join, room number ${userCount}`);

    // 房间未满员
    if (userCount < USER_LIMITS) {
      // 广播用户加入房间
      socket.emit('joined', room, socket.id);

      if (userCount > 1) {
        // 广播其他用户加入房间
        socket.to(room).emit('otherJoin', room, socket.id);
      }
    }
    // 房间满员
    else {
      socket.leave(room);
      socket.emit('full', room, socket.id);
    }
  });

  socket.on('leave', room => {
    socket.leave(room);

    const userCount = getRoomUsersCount(room);
    console.log(`user leave, room number ${userCount}`);

    // 广播有用户退出房间
    socket.to(room).emit('exit', room, socket.id);

    socket.emit('leaved', room, socket.id);
  });
});

httpServer.listen('80');

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

常用API

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】

推荐阅读
相关标签