Говоря о видеозвонках WebRTC

внешний интерфейс WebRTC Разработка аудио и видео

WebRTC предоставляет набор стандартных API-интерфейсов, которые позволяют веб-приложениям напрямую предоставлять функции аудио- и видеосвязи в реальном времени. Большинство браузеров и операционных систем поддерживают WebRTC, и вы можете напрямую инициировать аудио- и видеозвонки в реальном времени на стороне браузера.В этой статье используется точка зрения новичков в WebRTC для создания веб-версии аудио- и видеозвонков 1V1 в реальном времени.

Для совершения аудио- и видеовызова вам необходимо понимать четыре модуля: сбор аудио- и видеоданных, сервер STUN/TURN, сервер сигнализации и P2P-соединение между терминалами. Используйте API WebRTC для сбора аудио и видео, взаимодействуйте с сигнальным сервером и WebRTCRTCPeerConnectionМетод может реализовать вызов 1V1, простой процесс выглядит следующим образом:

image.png

Далее мы по очереди объясним их функции и основные API.

Аудио и видео коллекция

Использование WebRTCgetUserMediaПолучите объект медиапотока, соответствующий камере и микрофону.MediaStream, медиапотоки могут передаваться по WebRTC и совместно использоваться несколькими одноранговыми узлами. Назначьте объект потока srcObject элемента видео для локального воспроизведения аудио и видео.

Атрибуты значение
width ширина видео
height высота видео
aspectRatio Доля
frameRate частота кадров
facingMode зеркальный режим
resizeMode режим размера
API:navigator.mediaDevices.getUserMedia
参数:constraints
返回:promise,方法调用成功得到MediaStream对象。

const localVideo = document.querySelector("video");

function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream; 
}

navigator.mediaDevices
  .getUserMedia({ 
      video: {
        width: 640,    
        height: 480,  
        frameRate:15, 
        facingMode: 'enviroment', // 设置为后置摄像头 
        deviceId : deviceId ? {exact:deviceId} : undefined 
      },
      audio: false
   })
  .then(gotLocalMediaStream)
  .catch((error) => console.log("navigator.getUserMedia error: ", error));

управление соединением

Знайте, как записывать локальные аудио и видео, а затем научитесь устанавливать соединение с другим концом для передачи аудио и видео данных.

RTCPeerConnectionЭто унифицированный интерфейс для WebRTC для реализации сетевого подключения, управления мультимедиа и управления данными. Чтобы установить P2P-соединение, вам нужно использоватьRTCPeerConnectionНесколько важных классов в:SDP,ICE,STUN/TURN.

  1. Информация описания сеанса RTCSessionDescription (SDP)

SDP — это возможности каждого конца, включая тип аудиокодека, протокол передачи и т. д. Эта информация должна быть передана при установлении соединения.Обе стороны знают, поддерживает ли видео звук и какой метод кодирования, и их можно получить через SDP.

Например, для передачи видео я использую кодировку H264. Другая сторона может декодировать только H265, поэтому они не могут общаться.

Описание SDP разделено на две части: описание уровня сеанса (уровень сеанса) и описание уровня мультимедиа (уровень мультимедиа), на конкретный состав которых можно ссылаться.RFC4566, те, что отмечены звездочкой (*), являются необязательными. Общие из них следующие:

Session description(会话级别描述) 
    v= (protocol version) 
    o= (originator and session identifier)
    s= (session name) 
    c=* (connection information -- not required if included in all media) One or more Time descriptions ("t=" and "r=" lines; see below) 
    a=* (zero or more session attribute lines) Zero or more Media descriptions 
    
Time description 
    t= (time the session is active) 

Media description(媒体级别描述), if present 
    m= (media name and transport address) 
    c=* (connection information -- optional if included at session level) 
    a=* (zero or more media attribute lines)

При анализе SDP каждая строка SDP начинается сkey=...форма, после анализа того, что ключ является, может быть два способа, пожалуйста, обратитесь кRFC4566:

a=<attribute> 
a=<attribute>:<value>

Иногда это не должно быть двоеточие (:)<attribute>:<value>, на самом деле в значении будут и двоеточия, например:

a=fingerprint:sha-256 7C:93:85:40:01:07:91:BE 
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset 
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time 
a=ssrc:2527104241 msid:gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS

Взгляните на конкретные примеры:

alert(pc.remoteDescription.sdp);

 v=0
 o=alice 2890844526 2890844526 IN IP4 host.anywhere.com
 s=
 c=IN IP4 host.anywhere.com
 t=0 0
 //下面的媒体描述,在媒体描述部分包括音频和视频两路媒体
 m=audio 49170 RTP/AVP 0
 a=fmtp:111 minptime=10;useinbandfec=1 //对格式参数的描述
 a=rtpmap:0 PCMU/8000 //对RTP数据的描述
 
... 
 //上面是音频媒体描述,下面是视频媒体描述
 m=video 51372 RTP/AVP 31
 a=rtpmap:31 H261/90000
 ... 
 m=video 53000 RTP/AVP 32
 a=rtpmap:32 MPV/90000
  1. Кандидат ICE RTCIceCandidate

Наиболее удобным методом двухточечного соединения WebRTC является прямое IP-соединение между двумя сторонами, но в практических приложениях две стороны будут разделеныNATУстройство вызывает проблемы с получением адреса.

WebRTC проходитICEПлатформа определяет наилучший путь для установления сетевых соединений на обоих концах, ограждая разработчиков от сложных технических деталей.

(НАТ иICEФреймворк — это черный ящик для разработчиков, использующих WebRTC, для оптимизации чтения эта часть размещена в конце как дополнительные знания)

Разработчики должны знать:

  1. принцип

Два узла обмениваются кандидатами ICE, чтобы договориться о том, как они сами хотят подключиться.После того, как обе стороны согласуют взаимно совместимый кандидат, SDP-кандидат используется для создания и открытия соединения, через которое начинается потоковая передача мультимедиа.

  1. два API

onicecandidate: Запускается после того, как локальный агент создает предложение SDP и вызывает setLocalDescription(offer) и передает информацию о кандидате на удаленный конец через сервер сигнализации в обработчике событий.

addIceCandidate: Вызывается после получения информации о кандидате, отправленной сервером сигнализации, для добавления агента ICE к машине.

API:pc.onicecandidate = eventHandler
pc.onicecandidate = function(event) {
  if (event.candidate) {
    // Send the candidate to the remote peer
  } else {
    // All ICE candidates have been sent
  }
}


API:pc.addIceCandidate
pc.addIceCandidate(candidate).then(_=>{
  // Do stuff when the candidate is successfully passed to the ICE agent
}).catch(e=>{
  console.log("Error: Failure during addIceCandidate()");
});

сигнальный сервер

На информацию WebRTC SDP и ICE нужно полагаться信令服务器Осуществлять передачу и обмен сообщениями, устанавливать P2P-соединение, а затем проводить аудио- и видеозвонки и передавать текстовую информацию. WebRTC не может общаться без сигнального сервера.

обычно используетсяsocket.ioВозможности связи в реальном времени для создания серверов сигнализации.socket.ioКросс-платформенный, кросс-терминальный и кросс-языковой, нам удобно реализовывать каждый конец сигнализации на каждом конце и подключаться к нашему серверу.

Эта картинка выражает роль сервера сигнализации во всем процессе вызова.

image.png

Посмотрите, как создать сигнальный сервер socket.io с помощью кода.

var express = require("express");
var app = express();
var http = require("http");
const { Server } = require("socket.io");
const httpServer = http.createServer(app);
const io = new Server(httpServer);

io.on("connection", (socket) => {
    console.log("a user connected");
    socket.on("message", (room, data) => {
      logger.debug("message, room: " + room + ", data, type:" + data.type);
      socket.to(room).emit("message", room, data);
    })
    socket.on("join", (room) => {
      socket.join(room);
    })
});

Сквозные P2P-соединения

  1. процесс подключения

Процесс установления сетевого соединения между A и B выглядит следующим образом:

image.png

  • A инициирует вызов WebRTC к B
  • Создайте объект peerConnection и укажите адрес Turn/Stun в параметре
var pcConfig = {
  iceServers: [
    {
      urls: "turn:stun.al.learningrtc.cn:3478",
      credential: "mypasswd",
      username: "garrylea",
    },
    {
      urls:[
        "stun:stun.example.com",
        "stun:stun-1.example.com"
      ]
    }
  ],
};

pc = new RTCPeerConnection(pcConfig);
  • ВызовcreateOfferМетод создает локальное описание сеанса (предложение SDP), предложение SDP содержит всю информацию о кодеках и параметрах, которые были прикреплены к сеансу WebRTC, поддерживается браузером.MediaStreamTrackинформация, иICEПрокси-сервер, предназначенный для отправки по сигнальному каналу потенциальной удаленной конечной точке для запроса соединения или обновления конфигурации существующего соединения.
  • ВызовsetLocalDescriptionметод устанавливает предложение в описание локального сеанса и передает его на уровень ICE. Затем отправьте описание сеанса на B через сигнальный сервер.
API:pc.createOffer
参数:无
返回:SDP Offer

API:pc. setLocalDescription
参数:offer
返回:Promise<null>

function sendMessage(roomid, data) {
  if (!socket) {
    console.log("socket is null");
  }
  socket.emit("message", roomid, data);
}

const offer = await pc.createOffer()
await pc.setLocalDescription(offer).catch(handleOfferError);
message.log(`传输发起方本地SDP`);
sendMessage(roomid, offer);
  • После создания pc.setLocalDescription(offer) на стороне A событие icecandidate отправляется наRTCPeerConnection,onicecandidateСобытие будет запущено. Сторона B получает новую информацию об адресе кандидата ICE, отправленную сигнализацией с удаленной страницы, локальная машина может вызватьRTCPeerConnection.addIceCandidate()чтобы добавить агента ICE.
//A端
pc.onicecandidate = (event) => {
  if (!event.candidate) return;
  sendMessage(roomid, {
    type: "candidate",
    label: event.candidate.sdpMLineIndex,
    id: event.candidate.sdpMid,
    candidate: event.candidate.candidate,
  });
};


//B端
socket.onmessage = e => {
 if (e.data.hasOwnProperty("type") && e.data.type === "candidate") {
  var candidate = new RTCIceCandidate({
    sdpMLineIndex: data.label,
    candidate: data.candidate,
  });
  pc.addIceCandidate(candidate)
    .then(() => {
      console.log("Successed to add ice candidate");
    })
    .catch((err) => {
      console.error(err);
    });
 }
}
  • Как вызывающий абонент A получает локальный медиапоток и вызываетaddtrackСпособ присоединиться к аудио и видео потокуRTCPeerConnectionОбъект передается на другой конец, и другой конец срабатывает, когда он присоединяетсяontrackсобытие.
媒体流加入媒体轨道
API:stream.getTracks
参数:无
返回:媒体轨道对象数组

const pc = new RTCPeerConnection();
stream.getTracks().forEach((track) => {
  pc.addTrack(track, stream); 
});

const remoteVideo = document.querySelector("#remote-video");
pc.ontrack = (e) => {
  if (e && e.streams) {
    message.log("收到对方音频/视频流数据...");
    remoteVideo.srcObject = e.streams[0];
  }
};
  • Как вызывающая сторона B получает информацию о сеансе, отправленную A с сервера сигнализации, и вызываетsetRemoteDescriptionМетод передает предложение на уровень ICE и вызывает метод addTrack для присоединения к RTCPeerConnction.
  • B вызывает метод createAnswer для создания ответа, вызываяsetLocalDeacriptionОтвет метода устанавливается для локального сеанса и передается на уровень ICE.
socket.onmessage = e => {
    message.log("接收到发送方SDP");
    await pc.setRemoteDescription(new RTCSessionDescription(e.data));
    message.log("创建接收方(应答)SDP");
    const answer = await pc.createAnswer();
    message.log(`传输接收方(应答)SDP`);
    sendMessage(roomid, answer);
    await pc.setLocalDescription(answer);
}
  • AB имеет свой собственный SDP и SDP другой стороны и достиг соглашения об обмене медиа.Собранный ICE завершает обнаружение подключения и устанавливает наиболее метод подключения, а также устанавливается P2P-соединение для получения аудио- и видеопотока мультимедиа другой стороны. .
pc.ontrack = (e) => {
  if (e && e.streams) {
    message.log("收到对方音频/视频流数据...");
    remoteVideo.srcObject = e.streams[0];
  }
};
  1. Двунаправленное подключение к каналу данных

RTCDataChannelton может устанавливать двухточечное соединение P2P через API RTCPeerConnection без необходимости использования промежуточного сервера и с меньшей задержкой.

Один конец устанавливает канал данных, а другой конец получает объект канала данных через ondatachannel.

API:pc.createDataChannel
参数: label  通道名
      options?  通道参数
返回:RTCDataChannel


function receivemsg(e) {
  var msg = e.data;
  if (msg) {
    message.log("-> " + msg + "\r\n");
  } else {
    console.error("received msg is null");
  }
}

const dc = pc.createDataChannel("chat");
dc.onmessage = receivemsg;
dc.onopen = function () {
  console.log("datachannel open");
};

dc.onclose = function () {
  console.log("datachannel close");
};

pc.ondatachannel = e => {
  if(!dc){
    dc = e.channel;
    dc.onmessage = receivemsg;
    dc.onopen = dataChannelStateChange;
    dc.opclose = dataChannelStateChange;
  }
}; //当对接创建数据通道时会回调该方法。

Фреймворк NAT и ICE

упомянутый вышеICEОн объединяет различные технологии обхода NAT, такие как STUN и TURN, которые могут реализоватьNATПроникновение, обнаружение механизма пути передачи P2P между хостами. Далее кратко расскажем, что такое NAT, STUN и TURN.

  1. Преобразование сетевых адресов (NAT)

NATЧасто развертывается на выходе из сети организации. Сеть разделена на две части: частная сеть и публичная сеть.Шлюз NAT устанавливается на выходе маршрута из частной сети в публичную сеть.Двунаправленные данные между частной сетью и публичной сетью должны проходить через NAT-шлюз . Большое количество устройств внутри организации может совместно использовать общедоступный IP-адрес через NAT, что решает проблему нехватки IPv4-адресов.

Как показано на рисунке ниже, есть две организации, и NAT каждой организации назначен общедоступный IP-адрес 1.2.3.4 и 1.2.3.5. Частное сетевое устройство каждой организации преобразует внутренний сетевой адрес в общедоступный сетевой адрес через NAT, а затем подключается к Интернету.

image.png

Существует четыре реализации NAT для UDP, а именно: полный конус, конус с ограниченным адресом, конус с ограниченным портом и симметричный.

  1. Утилиты обхода сеанса для NAT (STUN)

STUNПозволяет клиентам за NAT (или несколькими NAT) узнать свой общедоступный сетевой адрес, тип NAT, за которым они находятся, и общедоступный сетевой порт, который NAT связывает с локальным портом.

image.png

STUNЭто протокол в режиме C/S.Клиент отправляет запрос STUN, а ответ службы STUN информируетNATIP-адрес и номер порта, назначенные хосту, также являются протоколом запроса/ответа Номер порта по умолчанию — 3478.

Чтобы хост внутренней сети знал свой внешний сетевой IP-адрес, вам необходимо настроить STUN-сервер в общедоступной сети, отправить запрос на этот сервер, и сервер вернет свой общедоступный сетевой IP-адрес.

Ниже приведена захваченная пара запроса и ответа привязки STUN. Сначала клиент отправляет запрос на привязку (запрос на привязку STUN) на сервер STUN с адресом 216.93.246.18.

image.png

Сервер вернул Binding Response и вернул общедоступный IP-адрес:

  1. обход с использованием реле NAT (TURN)

TURNявляется протоколом передачи данных. Разрешает проникновение NAT или брандмауэра через TCP или UDP. TURN — это клиент-серверный протокол. Метод обхода NAT в TURN похож на STUN тем, что он обеспечивает обход NAT путем получения общедоступного сетевого адреса на прикладном уровне.

  1. Коллекция ЛЕД

ICEДва конца не знают местоположение и тип NAT сети, в которой они находятся.ICEОптимальный путь передачи может быть найден динамически. Сторона ICE собирает локальные адреса, передаетSTUNСервис собирает внешние сетевые адреса NAT, передаетTURNСоберите ретрансляционные адреса, чтобы было три адреса-кандидата:

тип хоста, то есть IP и порт локальной сети;

тип srflx, то есть IP и порт внешней сети после локального сопоставления NAT;

тип ретрансляции, который является IP-адресом и портом сервера ретрансляции.

{ 
    IP: xxx.xxx.xxx.xxx, 
    port: number, 
    type: host/srflx/relay, 
    priority: number, 
    protocol: UDP/TCP, 
    usernameFragment: string 
    ...
 }

На рисунке ниже Алиса и Боб собирают три типа кандидатов через серверы STUN и TURN.

После того, как ICE соберет кандидатов, он выполняет обнаружение подключения, чтобы определить наилучший путь передачи P2P между хостами.

Эффект