Привет, друзья!
Представляю вашему вниманию третью (заключительную) часть перевода этой замечательной книги по WebRTC.
Если вам это интересно, прошу под кат.
Содержание этой части
-
Обмен другими данными
- Как это работает?
- DCEP
- DATA_CHANNEL_OPEN
- DATA_CHANNEL_ACK
- SCTP
- Концепция
- Ассоциация
- Потоки
- Датаграммы
- Части
- Последовательный номер передачи
- Идентификатор потока
- Идентификатор протокола полезной нагрузки
- Протокол
- DATA
- INIT
- SACK
- HEARTBEAT
- ABORT
- SHUTDOWN
- ERROR
- TSN FORWARD
- Конвейер SCTP
- Установка соединения
- Завершение сессии
- Механизм поддержания активности
-
Применение
- Случаи использования
- Конференц-связь
- Вещание
- Удаленный доступ
- Передача файлов и обход цензуры
- Интернет вещей
- Обмен медиаданными
- Обмен другими данными
- Телеоперация
- Распределенные CDN
- Топологии WebRTC
- Один-к-одному
- Полная сетка
- Гибридная сетка
- Единица выборочной пересылки
- Многоточечная конференц-система
-
Отладка
Обмен другими данными
WebRTC
предоставляет каналы (data channels) для обмена данными (data communication). Между двумя пирами можно открыть до 65 534
каналов. Канал данных основан на датаграммах (datagram) и имеет определенную продолжительность (durability). По умолчанию канал данных гарантирует сохранения порядка сообщений.
На первый взгляд каналы данных могут казаться лишними. Зачем нам эта подсистема, когда мы можем использовать HTTP
или веб-сокеты для обмена данными?
Одна из особенностей каналов данных состоит в том, что в результате настройки они могут стать неким подобием UDP
с неупорядоченной/ненадежной доставкой сообщений. Это может потребоваться в ситуациях, когда критически важными являются низкая задержка и высокая производительность. Мы можем провести измерения и обеспечить отправку только такого количества сообщений, с которым гарантированно справится наша сеть.
Как это работает?
Для обмена данными WebRTC
использует SCTP
, определенный в RFC 4960. SCTP
— это транспортный протокол, являющийся альтернативой TCP
и UDP
. В WebRTC
он используется как протокол уровня приложения поверх соединения DTLS
.
SCTP
состоит из потоков, каждый из которых может настраиваться независимо от других. Каналы данных — это всего лишь абстракция потоков. Настройки, связанные с продолжительностью существования канала и порядком доставки сообщений, просто передаются агенту SCTP
.
Каналы данных имеют некоторые возможности, отсутствующие в SCTP
, например, метки каналов (channel labels). Для этого используется Протокол установки канала данных (Data Channel Establishment Protocol, DCEP), определенный в RFC 8832. DCEP
определяет сообщения для взаимодействия с меткой канала и протоколом.
DCEP
DCEP
содержит два сообщения: DATA_CHANNEL_OPEN
и DATA_CHANNEL_ACK
. Открытый канал должен получить ACK
от удаленного пира.
DATA_CHANNEL_OPEN
Данное сообщение отправляется агентом, желающим открыть соединение.
Формат пакета
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Type | Channel Type | Priority |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reliability Parameter |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Label Length | Protocol Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ \
/ Label /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ \
/ Protocol /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Message Type
Message Type
(тип сообщения) имеет фиксированное значение 0x03
.
Channel Type
Channel Type
(тип канала) определяет такие атрибуты канала, как продолжительность и порядок. Может иметь следующие значения:
-
DATA_CHANNEL_RELIABLE
(0x00
) — сообщения не были потеряны и будут прибывать в правильном порядке; -
DATA_CHANNEL_RELIABLE_UNORDERED
(0x80
) — сообщения не были потеряны, но могут прибывать в неправильном порядке; -
DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT
(0x01
) — сообщения могут быть потеряны (после выполнения всех попыток), но будут прибывать в правильном порядке; -
DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED
(0x81
) — сообщения могут быть потеряны (после выполнения всех попыток) и могут прибывать в неправильном порядке; -
DATA_CHANNEL_PARTIAL_RELIABLE_TIMED
(0x02
) — сообщения могут быть потеряны (если они не прибывают в запрошенное время), но будут прибывать в правильном порядке; -
DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED
(0x82
) — сообщения могут быть потеряны (если они не прибывают в запрошенное время) и могут прибывать в неправильном порядке.
Priority (приоритет)
Определяет приоритет канала. Каналы с более высоким приоритетом создаются раньше. Большие сообщения с низким приоритетом не задерживают отправку сообщений с высоким приоритетом.
Reliability Parameter (параметр надежности)
Если типом канала является DATA_CHANNEL_PARTIAL_RELIABLE
, суффикс определяет следующее поведение:
-
REXMIT
— определяет количество попыток отправки сообщения; -
TIMED
— определяет, как долго отправитель будет пытаться отправить сообщение (в мс).
Label (метка, подпись)
Строка в формате UTF-8
, содержащая название канала данных. Данная строка может быть пустой.
Protocol (протокол)
Если данная строка является пустой, протокол считается неопределенным. Если строка не является пустой, в ней должно содержаться значение из "Реестра имен подпротокола WebSocket" (WebSocket Subprotocol Name Registry), определенного в RFC 6455.
DATA_CHANNEL_ACK
Данное сообщение является подтверждением открытия канала данных, передаваемого от отправителя получателю.
Формат пакета
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Type |
+-+-+-+-+-+-+-+-+
SCTP
SCTP
предоставляет следующие возможности:
- мультиплексирование;
- надежная доставка сообщений (для этого используется механизм передачи, подобный
TCP
); - настройки частичной надежности (partial-reliability options);
- предотвращение перегрузки;
- контроль потока.
Мы разделим рассмотрение SCTP
на три части.
Концепция
SCTP
предоставляет большое количество возможностей. Мы рассмотрим только те возможности, которые используются в WebRTC
. К возможностям, которые не используются WebRTC
, относятся множественная адресация (multi-homing) и выбор пути.
SCTP
разрабатывается более 20 лет, так что его непросто понять.
Ассоциация
Ассоциация (assosiation) — термин, относящийся к сессии SCTP
. Это состояние, распределяемое между двумя взаимодействующими агентами.
Потоки
Поток (stream) — это двунаправленная последовательность пользовательских данных. Канал данных — это поток. Каждая ассоциация SCTP
содержит список потоков. Каждый поток может обладать разным типом надежности.
WebRTC
позволяет настраивать поток только в момент создания, но SCTP
позволяет менять настройки в любое время.
Датаграммы
Кадры (фреймы, frames) SCTP
— это набор датаграмм, а не байтовый поток. Отправка и получение данных похожа на использование UDP
вместо TCP
. Для передачи нескольких сообщений через один поток не нужен дополнительный код.
Сообщения SCTP
не имеют ограничений по размеру, в отличие от сообщений UDP
. Одно сообщение SCTP
может иметь размер в несколько гигабайт.
Части
Протокол SCTP
состоит из частей (chunks). Существуют различные типы частей. Эти части используются для всей коммуникации. Пользовательские данные, установка соединения, контроль перегрузки и т.д. — все это делается с помощью частей.
Каждый пакет SCTP
состоит из списка частей. Поэтому в одном пакете UDP
может содержаться несколько частей с сообщениями из разных потоков.
Последовательный номер передачи
Последовательный номер передачи (Transmission Sequence Number, TSN) — это глобальный уникальный идентификатор частей DATA
. Часть DATA
содержит все сообщения, которые желает отправить пользователь. TSN
помогает получателю определять потерю или неправильный порядок пакетов.
Если получатель замечает отсутствующий TSN
, он не передает данные пользователю до решения этой проблемы.
Идентификатор потока
Каждый поток имеет уникальный идентификатор. При создании канала данных с явным ID
, этот ID
просто передается SCTP
в качестве идентификатора потока. Если такой ID
отсутствует, он генерируется автоматически.
Идентификатор протокола полезной нагрузки
Каждая часть DATA
также имеет идентификатор протокола полезной нагрузки (Payload Protocol Identifier, PPID). Он используется для идентификации типа передаваемых данных. SCTP
имеет много PPID
, но в WebRTC
используются следующие пять:
-
WebRTC DCEP
(50
) — сообщенияDCEP
; -
WebRTC String
(51
) — строковые сообщенияDataChannel
; -
WebRTC Binary
(53
) — бинарные сообщенияDataChannel
; -
WebRTC String Empty
(56
) — строковое сообщенияDataChannel
с нулевой длиной (пустое строковое сообщение); -
WebRTC Binary Empty
(57
) — бинарное сообщенияDataChannel
с нулевой длиной (пустое бинарное сообщение).
Протокол
Ниже представлены некоторые "чанки", которые используются в WebRTC
.
Каждый чанк начинается с поля type
. Перед списком чанков находится заголовок (header).
DATA
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 0 | Reserved|U|B|E| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| TSN |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Stream Identifier | Stream Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Protocol Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ \
/ User Data /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Чанк DATA
используется для передачи пользовательских данных. Когда мы что-то отправляем через канал данных, используется этот чанк.
Бит U
устанавливается, если данный пакет является неупорядоченным (unordered). Мы можем игнорировать последовательный номер потока.
B
и E
— это начальный и конечный биты. Если мы хотим отправить сообщение, которое является слишком большим для одного чанка DATA
, оно будет разделено (fragmented) на несколько чанков DATA
, передаваемых в нескольких пакетах. Для этого SCTP
использует биты B
и E
, а также последовательные номера.
-
B=1
,E=0
— первая часть фрагментированного сообщения пользователя; -
B=0
,E=0
— средняя часть фрагментированного сообщения пользователя; -
B=0
,E=1
— последняя часть фрагментированного сообщения пользователя; -
B=1
,E=1
— целое (нефрагментированное) сообщение.
TSN
— последовательный номер передачи, уникальный идентификатор данного чанка DATA
. После 4 294 967 295
чанка TSN
обнуляется. TSN
увеличивается на единицу для каждого чанка во фрагментированном сообщении пользователя, что позволяет получателю определять порядок чанков для восстановления исходного сообщения.
Stream Identifier
— уникальный идентификатор потока, которому принадлежат данные.
Stream Sequence Number
— это 16-битный номер, увеличивающийся при каждом сообщении и включаемый в заголовок сообщения чанка DATA
. После 65 535
сообщения SSN
обнуляется. Это число используется для определения порядка доставки сообщений получателю, когда U
имеет значение 0
. SSN
похож на TSN
, но он увеличивается для каждого сообщения, а не для каждого чанка.
Payload Protocol Identifier
— тип данных, передаваемых через поток. В случае с WebRTC
он будет DCEP
, строкой или двоичными данными.
User Data
— это то, что мы отправляем. Все данные, передаваемые через WebRTC
, передаются через чанк DATA
.
INIT
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 1 | Chunk Flags | Chunk Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Initiate Tag |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Advertised Receiver Window Credit (a_rwnd) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Outbound Streams | Number of Inbound Streams |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Initial TSN |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ \
/ Optional/Variable-Length Parameters /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Чанк INIT
запускает процесс создания ассоциации.
Initiate Tag
используется для генерации куки (cookie). Куки используются для предотвращения атак "Человек посередине" и "Отказ в обслуживании".
Advertised Receiver Window Credit
используется для контроля перегрузки SCTP
. Это поле определяет размер буфера, выделенного получателем для данной ассоциации.
Number of Outbound/Inbound Streams
уведомляет удаленный пир о количестве потоков, поддерживаемых данным агентом.
Initial TSN
— произвольное число (uint32
), с которого начинается локальный TSN
.
Поле Optional Parameters
предназначено для добавления в протокол новых возможностей.
SACK
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 3 |Chunk Flags | Chunk Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cumulative TSN Ack |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Advertised Receiver Window Credit (a_rwnd) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Gap Ack Blocks = N | Number of Duplicate TSNs = X |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Gap Ack Block #1 Start | Gap Ack Block #1 End |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ ... \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Gap Ack Block #N Start | Gap Ack Block #N End |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Duplicate TSN 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ ... \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Duplicate TSN X |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Чанк SACK
— это подтверждение получения пакета получателем. До получения отправителем SACK
для TSN
он будет повторно отправлять чанк DATA
по запросу. Но SACK
не только обновляет TSN
.
Cumulative TSN ACK
— максимальный полученный TSN
.
Advertised Receiver Window Credit
— размер буфера получателя. Это значение может меняться в течение сессии, если у получателя появится больше свободной памяти.
Ack Blocks
— это TSN
, полученные после Cumulative TSN ACK
. Это значение используется при наличии бреши в доставленных пакетах. Допустим, были доставлены чанки DATA
с TSN
, имеющими значения 100
, 102
, 103
и 104
. Cumulative TSN ACK
будет иметь значение 100
, но Ack Blocks
может использоваться для уведомления отправителя об отсутствии необходимости в повторной отправке чанков 102
, 103
и 104
.
Duplicate TSN
информирует отправителя, что он получил указанные чанки DATA
более одного раза.
HEARTBEAT
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 4 | Chunk Flags | Heartbeat Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ \
/ Heartbeat Information TLV (Variable-Length) /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Чанк HEARTBEAT
используется для проверки активности удаленного пира. Может использоваться для обеспечения открытости NAT
без отправки чанков DATA
.
ABORT
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 6 |Reserved |T| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Zero or more Error Causes \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Чанк ABORT
резко закрывает ассоциацию. Используется, когда одна из сторон переходит в состояние ошибки (error state). Для плавного закрытия соединения используется чанк SHUTDOWN
.
SHUTDOWN
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 7 | Chunk Flags | Length = 8 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cumulative TSN Ack |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Чанк SHUTDOWN
запускает процесс плавного закрытия ассоциации SCTP
. Каждый агент информирует другую сторону о последнем полученном TSN
. Это позволяет убедиться в том, что никакие пакеты не были потеряны. WebRTC
не закрывает ассоциацию автоматически. Поэтому каждый канал данных должен быть закрыт вручную.
Cumulative TSN ACK
— последний отправленный TSN
. Каждая сторона закрывает соединение только после получения чанка DATA
с этим TSN
.
ERROR
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 9 | Chunk Flags | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ \
/ Одна или более причина ошибки /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Чанк ERROR
используется для уведомления другой стороны о возникновении не критичной для ассоциации ошибки.
TSN FORWARD
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 192 | Flags = 0x00 | Length = Variable |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| New Cumulative TSN |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Stream-1 | Stream Sequence-1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ /
/ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Stream-N | Stream Sequence-N |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Чанк TSN FORWARD
сдвигает глобальный TSN
вперед. Это позволяет пропустить пакеты, которые нам больше не нужны. Предположим, что мы отправили пакеты 10 11 12 13 14 15
, которые являются валидными только в случае доставки всех пакетов и только в случае своевременной доставки.
Если мы потеряли пакеты 12
и 13
, в отправке пакетов 14
и 15
нет смысла. SCTP
использует чанк TSN FORWARD
для решения этой задачи. Он сообщает получателю, что пакеты 14
и 15
не будут доставлены.
New Cumulative TSN
— новый TSN
подключения. Любые пакеты, доставленные перед ним, будут отброшены.
Stream
и Stream Sequence
используются для сдвига вперед Stream Sequence Number
.
Конвейер SCTP
Есть несколько интересных вещей, касающихся машины состояния (state machine) SCTP
. WebRTC
не использует все возможности этой машины — такие возможности мы рассматривать не будем. Мы также упростим некоторые компоненты для их лучшего понимания.
Установка соединения
Чанки INIT
и INIT ACK
используются для обмена возможностями и настройками каждого пира. SCTP
использует куки в процессе рукопожатия для валидации пира, с которым предполагается коммуникация. Это предотвращает перехват рукопожатия и DoS-атаки
.
Чанк INIT ACK
содержит куки. Куки возвращаются создателю с помощью COOKIE ECHO
. Если верификация куки прошла успешно, отправляется COOKIE ACK
и можно обмениваться чанками DATA
.
Завершение сессии
Для этого используется чанк SHUTDOWN
. Когда агент получает чанк SHUTDOWN
, он ждет получения запрошенного Cumulative TSN ACK
. Это позволяет убедиться в доставке всех данных, даже если соединение является ненадежным.
Механизм поддержания активности
Для поддержания соединения в активном состоянии используются чанки HEARTBEAT REQUEST
и HEARTBEAT ACK
. Они отправляются с определенной периодичностью. SCTP
также выполняет экспоненциальную выдержку (exponential backoff), когда пакеты не доставляются.
Чанк HEARTBEAT
также содержит время, что позволяет двум ассоциациям рассчитать время передачи данных между двумя агентами.
Применение
Данный раздел посвящен тому, что можно создать с помощью WebRTC
, а также тому, как это делается. Все возможности WebRTC
имеют свою цену и ценность. Разработка реальных приложений WebRTC
— задача не из простых.
Случаи использования
Многие считают, что WebRTC
— это про конференции в браузере. Однако, это намного больше. WebRTC
используется в широком спектре приложений. Все время появляется что-то новое.
Конференц-связь
Конференц-связь (conferencing) — стандартный случай использования WebRTC
. Протокол содержит несколько возможностей, которых не содержит ни один другой протокол. Систему конференц-связи можно построить с помощью WebSocket
и она будет работать. Но в условиях реальных сетей WebRTC
— лучший выбор.
WebRTC
предоставляет контроль перегрузки и автоматически адаптируемый битрейт для медиа. При изменении сетевых условий пользователи всегда будут иметь наилучший опыт использования приложения. Разработчикам не нужно писать дополнительный код для измерения этих условий.
Участники конференции могут отправлять и получать несколько потоков одновременно. Они также могут добавлять и удалять потоки в любое время в течение сессии. Используемые кодеки также предварительно согласовываются. Весь этот функционал предоставляется браузером.
Такая конференция также выигрывает от наличия каналов данных. Пользователи могут обмениваться дополнительными данными (metadata) или документами. Мы можем создать несколько потоков и сделать некоторые из них более производительными в ущерб надежной доставке сообщений.
Вещание
Все больше новых проектов, в которых используется WebRTC
, связаны с вещанием (broadcasting) в той или иной степени. Протокол предоставляет большое количество возможностей как для издателя (publisher), так и для потребителя (consumer) медиа.
WebRTC
существенно облегчает процесс публикации видео. Пользователям не нужно устанавливать специальное ПО. Любая платформа с браузером может публиковать видео. Издатели могут отправлять несколько треков и модифицировать или удалять их в любое время. Это является существенным преимуществом перед старыми протоколами, которые позволяли передавать только один аудио или видеотрек через одно соединение.
WebRTC
предоставляет разработчикам серьезный контроль над задержкой и качеством. Важно, что задержка никогда не превышает максимальный порог (threshold), и мы можем игнорировать некоторые артефакты декодирования. Мы можем запускать воспроизведение видео сразу после его доставки. С другими протоколами, функционирующими поверх TCP
, это сделать не так просто. В браузере мы просто запрашиваем данные и сразу их получаем.
Удаленный доступ
Удаленный доступ (remote access) — это когда мы получаем доступ к другому компьютеру через WebRTC
. Мы можем контролировать удаленный хост целиком или только отдельное приложение, работающее на нем. Это отлично подходит для выполнения сложных вычислительных задач, когда собственной мощности оказывается недостаточно. WebRTC
– это революция сразу в трех направлениях.
WebRTC
может использоваться для доступа к удаленному хосту, скрытому от остального мира. С помощью обхода NAT
мы можем получить доступ к компьютеру, доступному только через STUN
. Это обеспечивает безопасность и конфиденциальность. Пользователям не нужно передавать видео через посредника или "переходник" (ретранслятор). Отображение NAT
также облегчает деплой приложения. Нам не надо беспокоиться о перенаправлении портов или настройке статического IP
.
Каналы данных также активно применяются в этом сценарии использования. Они могут быть настроены таким образом, что будут принимать только последние данные. В случае с TCP
мы рискуем столкнуться с блокировкой начала строки (Head-of-line blocking). Клик мыши или нажатие клавиши могут задержаться в пути и блокировать получение следующих за ними событий. Каналы данных спроектированы специально для обработки таких случаев и позволяют отключить повторную отправку потерянных пакетов. Мы также можем измерять нагрузку и проверять, что отправляем ровно столько данных, сколько способна выдержать наша сеть.
Наличие WebRTC
в браузере сильно упрощает жизнь. Нам не нужно скачивать проприетарного (платного) клиента для начала сессии. Все большее количество клиентов имеют встроенную поддержку WebRTC
, например, умные телевизоры (Smart TV).
Передача файлов и обход цензуры
Передача файлов (File Sharing) и обход цензуры (Censorship Circumvention) — это две совершенно разные проблемы. Однако, WebRTC
успешно решает обе. Он делает коммуникацию легкодоступной и практически не поддающейся блокировке.
Первая задача, которую решает WebRTC
— это получение клиента. Если мы хотим присоединиться к файлообменной сети, нам нужно загрузить клиента. Даже если сеть является распределенной, все равно нужен клиент. В ограниченной сети загрузка клиента может быть заблокированной. Даже если получится скачать клиента, сможет ли пользователь самостоятельно установить и запустить его? WebRTC
доступен в каждом браузере: это готовый к использованию клиент.
Вторая проблема — блокировка трафика. Если мы используем протокол, предназначенный только для передачи файлов или обхода цензуры, его легко заблокировать. Поскольку WebRTC
— это протокол общего назначения, его блокировка затронет каждого. Блокировка WebRTC
может помешать подключению новых участников к конференции.
Интернет вещей
Интернет вещей (Internet of Things, IoT) включает в себя несколько вещей. Для многих это означает камеры видеонаблюдения. С помощью WebRTC
мы можем передавать видео другому пиру, такому как телефон или браузер. Другим случаем является подключение устройств и обмен чувствительными данными (sensor data). У нас может быть два устройства, функционирующих в локальной сети, которые обмениваются данными о климате, шуме или освещенности.
WebRTC
имеет существенные преимущества перед старыми протоколами для обмена потоками. Поскольку WebRTC
поддерживает P2P-соединения
, мы можем передавать видео с камеры напрямую в браузер. Нет необходимости использовать сторонний сервер. Даже если видео будет зашифрованным, злоумышленник может извлечь некоторую информацию из метаданных звонка.
Совместимость — еще одно преимущество, предоставляемое WebRTC
. WebRTC
доступен во многих языках программирования: C#, C++, C, Go, Java, Python, Rust, JavaScript и TypeScript. Это означает, что мы можем использовать язык, который хотим. Нам не требуются проприетарные протоколы или форматы для подключения клиентов.
Обмен медиаданными
Предположим, что у нас есть "железо" и ПО, генерирующее видео, но мы пока не можем их обновить. Для просмотра видео пользователь должен загрузить проприетарного клиента. Это является фрустрирующим. Ответ — запуск моста (bridge) WebRTC
. Мост транслируется между двумя протоколами, поэтому пользователи могут использовать браузер со старыми настройками.
Во многих форматах, используемые разработчиками, применяются те же протоколы, что и в WebRTC
. Протокол установления сеанса (Session Initiation Protocol, SIP), как правило, транслируется через WebRTC
и позволяет пользователям совершать телефонные звонки с помощью браузера. Потоковый протокол реального времени (Real Time Streaming Protocol, RTSP) используется в старых камерах видеонаблюдения. Они оба используют одинаковые протоколы (RTP
и SDP
). Мост требует добавления или удаления только тех вещей, которые специфичны для WebRTC
.
Обмен другими данными
Для передачи данных браузер может использовать ограниченный набор протоколов: HTTP
, WebSockets
, WebRTC
и QUIC. Если мы хотим к чему-то подключиться, то должны использовать протокольный мост (protocol bridge). Протокольный мост — это сервер, который преобразует входящий трафик в нечто, доступное для браузера. Хорошим примером является SSH, который используется для доступа к серверу. Каналы данных WebRTC
в этом отношении предоставляют несколько преимуществ.
Каналы данных допускают ненадежную и неупорядоченную доставку. Это необходимо в случаях, когда критически важной является низкая задержка доставки пакетов. Мы не хотим, чтобы новые данные блокировались старыми (блокировка начала строки). Представьте, что играете в многопользовательский "шутер" от первого лица. Вам важно, где пользователь был две секунды назад? Если данные не прибывают вовремя, какой смысл продолжать попытки их отправки. Ненадежная и неупорядоченная доставка позволяет использовать данные сразу по их прибытии.
Каналы данных также предоставляют обратную связь о нагрузке (feedback pressure). Это позволяет своевременно определять, что мы отправляем больше данных, чем поддерживает наша сеть. В этом случае у нас есть два варианта: канал данных может помещать сообщения в буфер и доставлять их с задержкой или же мы можем игнорировать "опоздавшие" сообщения.
Телеоперация
Телеоперация (teleoperation) — это управление удаленным устройством через каналы данных WebRTC
и обратная отправка видео через RTP
. Сегодня WebRTC
позволяет разработчикам удаленно управлять автомобилями! Это используется для управления роботами при создании сайтов и доставки посылок.
WebRTC
повсюду. Все, что нужно пользователю, это браузер и устройство. Браузеры поддерживают джойстики и геймпады. WebRTC
избавляет от необходимости устанавливать дополнительных клиентов на устройство пользователя.
Распределенные CDN
Распределенные CDN
(Content Delivery Network — сеть доставки контента) — это разновидность файлообменной сети. Распределяемые файлы настраиваются оператором CDN
. Когда пользователь подключается к CDN
, он может скачивать и делиться разрешенными (allowed) файлами.
CDN
хорошо работают при наличии хорошего соединения по локальной сети и отсутствии необходимости в хорошем внешнем соединении. У нас может быть один пользователь, скачавший видео, и поделившийся им с остальными. Поскольку пользователи не пытаются скачать один и тот же файл из внешней сети, передача происходит быстро.
Топологии WebRTC
WebRTC
— это протокол, предназначенный для прямого соединения двух агентов, но как разработчику подключить тысячу людей? Существует несколько способов, каждый из которых имеет свои преимущества и недостатки. Эти решения условно можно разделить на две категории: равный-к-равному (Peer-to-Peer) или клиент/сервер (Client/Server). Гибкость WebRTC
позволяет легко реализовать любой из этих сценариев.
Один-к-одному
Один-к-одному (One-to-One) — первый тип подключения, используемый в WebRTC
. Мы соединяем двух агентов напрямую и они могут обмениваться медиа и другими данными. В этом случае подключение выглядит так:
Полная сетка
Полная сетка (Full Mesh) — это решение для разработки конференции или многопользовательской игры. В данном случае каждый пользователь устанавливает соединение с другими пользователями напрямую. Это позволяет создать приложение, но имеет некоторые недостатки.
В полной сетке каждый пользователь подключается напрямую. Это означает, что нам нужно шифровать и загружать видео независимо для каждого участника сессии. Условия сети между каждым соединением будут разными, поэтому мы не может повторно использовать одно и тоже видео. Обработка ошибок — в данном случае также задача не из простых. Необходимо правильно определять степень потери соединения: является ли оно полным или касается только одного удаленного пира.
По этим и другим причинам полная сетка хорошо подходит для небольших групп. Для большого количества участников сессии лучше подходит топология клиент/сервер.
Гибридная сетка
Гибридная сетка (Hybrid Mesh) — это альтернатива полной сетки, которая может решить некоторые проблемы с ней. В гибридной сетке вместо установки соединения между каждым пользователем медиа ретранслируется через пиры в сети. Это означает, что создателю медиа не требуется большая пропускная способность для передачи данных.
Этот подход также имеет некоторые недостатки. Создатель медиа не знает о том, кому передаются данные и успешно ли они доставляются. Каждый переход также увеличивает время доставки пакетов.
Единица выборочной пересылки
Единица выборочной пересылки (Selective Forwarding Unit, SFU) также устраняет недостатки полной сетки, но несколько другим способом. SFU
реализует топологию клиент/сервер вместо P2P
. Каждый пир подключается к SFU
и загружает в него свое медиа. Затем SFU
пересылает медиа каждому подключенному клиенту.
В SFU
каждый агент должен зашифровать и загрузить видео только один раз. Ответственность за передачу видео всем участникам ложится на SFU
. Установка SFU-соединения
, к тому же, проще, чем установка P2P-соединения
. SFU
может быть запущен на открытом маршрутизируемом адресе, что облегчает подключение к нему клиентов. Поэтому в данном случае не требуется отображение NAT
. Однако нам все еще необходимо обеспечивать доступность SFU
через TCP
(через ICE-TCP
или TURN
).
Разработка простого SFU
может быть завершена в течение недели. Разработка хорошего SFU
, обрабатывающего все типы клиентов — бесконечный процесс, в который входит постоянное совершенствование контроля перегрузки, обработки ошибок и повышение производительности.
Многоточечная конференц-система
Многоточечная конференц-система (Multi-point Conferencing Unit, MCU) — это топология клиент/сервер, похожая на SFU
, но объединяющая выходные потоки. Вместо того, чтобы передавать медиаданные в неизменном виде, они декодируются в один поток.
Отладка
Отладка WebRTC
— сложная задача, поскольку в нем имеется большое количество подвижных частей, которые могут ломаться независимо друг от друга. Если не проявлять осторожности, можно потратить недели на решение несуществующих проблем. При обнаружении сломавшейся части, необходимо потратить еще немного времени на то, чтобы понять, почему произошла поломка.
Локализация проблемы
При отладке приложения WebRTC
сначала необходимо определить источник проблемы.
Потеря сигнала
Потеря сети
Проверяем сервер STUN
с помощью netcat
:
- Формируем 20-байтный пакет запроса на привязку (binding request packet):
echo -ne "\x00\x01\x00\x00\x21\x12\xA4\x42TESTTESTTEST" | hexdump -C
00000000 00 01 00 00 21 12 a4 42 54 45 53 54 54 45 53 54 |....!..BTESTTEST|
00000010 54 45 53 54 |TEST|
00000014
Здесь:
-
00 01
— тип сообщения; -
00 00
— размер данных (длина соответствующего раздела); -
21 12 a4 42
— магическое куки; -
54 45 53 54 54 45 53 54 54 45 53 54
(декодированное вASCII
:TESTTESTTEST
) — 12-байтовый идентификатор транзакции.
- Отправляем запрос и ждем получения 32-байтного ответа:
stunserver=stun1.l.google.com;stunport=19302;listenport=20000;echo -ne "\x00\x01\x00\x00\x21\x12\xA4\x42TESTTESTTEST" | nc -u -p $listenport $stunserver $stunport -w 1 | hexdump -C
00000000 01 01 00 0c 21 12 a4 42 54 45 53 54 54 45 53 54 |....!..BTESTTEST|
00000010 54 45 53 54 00 20 00 08 00 01 6f 32 7f 36 de 89 |TEST. ....o2.6..|
00000020
Здесь:
-
01 01
— тип сообщения; -
00 0c
— размер данных, которые декодируются до12
в десятичном формате; -
21 12 a4 42
— магическое куки; -
54 45 53 54 54 45 53 54 54 45 53 54
(декодированное вASCII
:TESTTESTTEST
) — 12-байтовый идентификатор транзакции; -
00 20 00 08 00 01 6f 32 7f 36 de 89
— 12-байтовые данные:-
00 20
— это тип:XOR-MAPPED-ADDRESS
; -
00 08
— размер значения, декодируемого до8
в десятичном формате; -
00 01 6f 32 7f 36 de 89
— значение данных: -
00 01
— тип адреса (IPv4
); -
6f 32
— XOR-сопоставленный (XOR-mapped) порт; -
7f 36 de 89
— XOR-сопоставленный IP-адрес.
-
Декодирование XOR-сопоставленного раздела является сложным, но мы можем обмануть сервер STUN
для выполнения фиктивного отображения XOR
с помощью фиктивного магического куки со значением 00 00 00 00
:
stunserver=stun1.l.google.com;stunport=19302;listenport=20000;echo -ne "\x00\x01\x00\x00\x00\x00\x00\x00TESTTESTTEST" | nc -u -p $listenport $stunserver $stunport -w 1 | hexdump -C
00000000 01 01 00 0c 00 00 00 00 54 45 53 54 54 45 53 54 |........TESTTEST|
00000010 54 45 53 54 00 01 00 08 00 01 4e 20 5e 24 7a cb |TEST......N ^$z.|
00000020
XOR-операция против фиктивного магического куки будет идемпотентной, поэтому порт и адрес в ответе будут достоверными. Это будет работать не во всех случаях, поскольку некоторые маршрутизаторы манипулируют проходящими через них пакетами, обманывая (cheating) IP-адрес
. Последние 8 байтов ответа будут выглядеть следующим образом:
-
00 01 4e 20 5e 24 7a cb
— значение данных:-
00 01
— тип адреса (IPv4
); -
4e 20
— сопоставленный порт, которые декодируется до20000
в десятичном формате; -
5e 24 7a cb
—IP-адрес
, который декодируется в94.36.122.203
.
-
Ошибка безопасности
Ошибка медиа
Ошибка данных
Инструменты для отладки
netcat (nc)
netcat — сетевая утилита командной строки для чтения из/записи в сетевые соединения с помощью TCP
или UDP
. Данная утилита, обычно, доступна через команду nc
.
tcpdump
tcpdump — это интерфейс командной строки для анализа сетевых пакетов с данными.
Наиболее распространенные команды:
- перехватываем пакеты
UDP
в/из порта19302
, отображаем шестнадцатеричный дамп (hexdump) содержимого пакета
sudo tcpdump 'udp port 19302' -xx
- тоже самое, но сохраняем пакеты в файле
PCAP
(захват пакета — packet capture) для дальнейшего изучения
sudo tcpdump 'udp port 19302' -w stun.pcap
Файл PCAP
можно открыть с помощью команды wireshark stun.pcap
.
Wireshark
Wireshark — это широко используемый анализатор сетевых протоколов.
webrtc-internals
Chrome
предоставляет встроенную страницу статистики WebRTC
: chrome://webrtc-internals
.
Задержка
Как понять, что имеет место высокая задержка доставки сообщений? Мы можем заметить, что наше видео лагает (lagging), но как определить точную величину задержки? Для уменьшения задержки, необходимо сначала ее измерить.
Предполагается, что истинная задержка должна измеряться от начала до конца (end-to-end). Это означает учет не только задержки сетевого пути между отправителем и получателем, но общую задержку захвата камеры, шифрования кадров, передачи, получения, расшифровки и отображения, а также возможную буферизацию (постановку задач в очередь — queueing) на любом из этапов.
Задержка от начала до конца — это не просто сумма задержек каждого компонента.
Несмотря на то, что теоретически мы можем измерить задержку каждого компонента потоковой передачи видео по отдельности и суммировать результаты, на практике некоторые компоненты будут либо недоступными для измерения, либо результаты измерения этих компонентов вне конвейера (pipeline) будут сильно отличаться от результатов их измерения в связке с другими компонентами. Разная глубина очереди на разных этапах, топология сети и изменения экспозиции камеры — вот лишь несколько примеров влияния компонентов на конечную задержку.
Задержка каждого компонента в системе потоковой передачи данных может меняться и влиять на нижележащие компоненты. Даже содержимое захваченного видео влияет на задержку. Например, требуется намного больше битов для высокочастотных (high frequency) изображений вроде ветвей дерева по сравнению с низкочастотными кадрами вроде чистого голубого неба. Камера с автоматической экспозицией может захватывать кадр гораздо дольше ожидаемых 33 мс
, даже при установке частоты захвата (capture rate) в значение 30 кадров в секунду
(frames per second). Передача по сети, особенно по сотовой, также очень динамична из-за постоянно меняющегося спроса (на ресурсы сети). Чем больше пользователей, тем больше "шума" в эфире. Наша физическая локация (например, в зоне с плохим сигналом) и множество других факторов увеличивают потерю пакетов и задержку. Что происходит при отправке пакета по сети, например, через адаптер WiFi
или LTE-модем
? Если пакет не может быть доставлен незамедлительно, он помещается в очередь, чем больше очередь, тем выше задержка.
Ручное измерение конечной задержки
Когда мы говорим о конечной задержке, мы имеем в виду время между возникновением события и его наблюдением, например, отображением видеокадра на экране.
EndToEndLatency = T(observe) - T(happen)
Наивный подход заключается в фиксировании времени возникновения события и его вычитание из времени наблюдения события. Однако при точности в мс
критически важной становится задача синхронизации времени. Попытки синхронизации времени в распределенных системах являются практически бесполезными, поскольку даже небольшая ошибка приводит к недостоверному измерению задержки.
Простейшим решением проблемы синхронизации часов является использование одних часов. Для этого отправитель и получатель должны находиться в одной системе отсчета.
Представим, что у нас есть таймер, отсчитывающий миллисекунды. Мы хотим измерить задержку в системе, в которой наш таймер транслируется в реальном времени на удаленный экран. Наивный способ измерения времени между возникновением события и появлением на экране видео кадра состоит в следующем:
- фиксируем время на часах;
- отправляем видео получателю, находящемуся в той же физической локации;
- фиксируем время появления видео на экране получателя;
- вычитаем одно время из другого.
Это самый простой способ измерения конечной задержки. Он учитывает все компоненты, влияющие на задержку (камера, кодировщик, сеть, декодер) и не полагается на синхронизацию часов.
На приведенном выше фото конечная задержка составляет 101 мс. Время возникновения события составляет 10:16:02.761
, а время его наблюдения — 10:16:02.862
.
Автоматическое измерение задержки
В настоящее время (май 2021 года) стандарт WebRTC
, посвященный конечной задержке, находится в стадии активного обсуждения. Firefox
реализовал набор API
поверх стандартных API
WebRTC
для автоматического измерения задержки. Однако далее мы рассмотрим более общий способ.
Суть времени пути (round-trip time, rtt): я отправляю тебе мое время tR1
, при получении от тебя tR1
обратно я фиксирую время tR2
, таким образом, время пути составляет tR1 - tR2
.
При наличии канала коммуникации между отправителем и получателем (например, DataChannel
), получатель может смоделировать монотонные часы (monotonic clock) отправителя следующим образом:
- Получатель отправляет сообщение со своим временем
tR1
. - При получении сообщения от получателя, отправитель фиксирует свое время
tS1
и отправляет копиюtR1
иtS1
, а также время видеотрека отправителяtSV1
. - При получении сообщений от отправителя, получатель фиксирует свое время
tR2
и вычисляет время пути:RTT = tR2 - tR1
. - Времени пути
RTT
и времени отправителяtS1
достаточно для вычисления монотонных часов отправителя. Текущее время отправителя дляtR2
будет равнятьсяtS1
+ половина времени пути. - Времени отправителя
tS1
вместе с временем видеотрекаtSV1
достаточно для синхронизации времени видеотрека получателя со временем видеотрека отправителя.
Поскольку нам известно время tSV1
, мы можем приблизительно вычислить задержку посредством вычитания текущего времени отображения видеокадра (actual_video_time
) из ожидаемого времени:
expected_video_time = tSV1 + time_since(tSV1)
latency = expected_video_time - actual_video_time
Недостатком этого метода является то, что не учитывается задержка камеры. В большинстве видеосистем время захвата кадра считается временем доставки кадра из камеры в память, что происходит через некоторое время после возникновения события записи.
Пример вычисления задержки
В следующем примере мы открываем канал данных latency
на стороне получателя и периодически отправляем время монотонного таймера отправителю. Отправитель отвечает сообщением в формате JSON
, и получатель вычисляет задержку на основании этого сообщения.
{
"received_time": 64714, // Время, отправленное получателем, которое фиксируется отправителем
"delay_since_received": 46, // Время, прошедшее с последнего `received_time`, полученного отправителем
"local_clock": 1597366470336, // Текущее монотонное время отправителя
"track_times_msec": {
"myvideo_track1": [
13100, // Временная метка видеокадра RTP (в мс)
1597366470289 // Временная метка монотонных часов видеокадра
]
}
}
Открываем канал данных на стороне получателя:
dataChannel = peerConnection.createDataChannel('latency')
Периодически отправляем время получателя tR1
. В данном случае интервал составляет 2 секунды
:
setInterval(() => {
const tR1 = Math.trunc(performance.now())
dataChannel.send(String(tR1))
}, 2000)
Обрабатываем сообщение получателя на стороне отправителя:
// предположим, что `event.data` - это строка типа '1234567'
const tR1 = event.data
const now = Math.trunc(performance.now())
const tSV1 = 42000 // Временная метка текущего фрейма RTP, преобразованная в мс
const tS1 = 1597366470289 // Временная метка монотонных часов текущего фрейма
const msg = {
"received_time": tR1,
"delay_since_received": 0,
"local_clock": now,
"track_times_msec": {
"myvideo_track1": [tSV1, tS1]
}
}
dataChannel.send(JSON.stringify(msg))
Обрабатываем сообщение отправителя на стороне получателя, вычисляем задержку и выводим ее в консоль:
const tR2 = Math.trunc(performance.now())
const fromSender = JSON.parse(event.data)
const tR1 = fromSender['']
const delay = fromSender[''] // Сколько прошло времени между получением сообщения отправителем и отправкой ответа
const senderTimeFromResponse = fromSender['']
const rtt = tR2 - delay - tR1
const networkLatency = rtt / 2
const senderTime = (senderTimeFromResponse + delay + networkLatency)
const video$ = document.querySelector('video')
video$.requestVideoFrameCallback((now, framemeta) => {
// Вычисляем текущее время отправителя
const delaySinceVideoCallbackRequested = now - tR2
senderTime += delaySinceVideoCallbackRequested
const [tSV1, tS1] = Object.entries(fromSender[''])[0][1]
const timeSinceLastKnownFrame = senderTime - tS1
const expectedVideoTimeMsec = tSV1 + timeSinceLastKnownFrame
const actualVideoTimeMsec = Math.trunc(framemeta.rtpTimestamp / 90) // Преобразуем время RTP (90000) в мс
const latency = expectedVideoTimeMsec - actualVideoTimeMsec
console.log('latency', latency, 'ms')
})
Реальное время видео в браузере
HTMLVideoElement.requestVideoFrameCallback()
позволяет разработчикам получать уведомления о представлении фрейма для композиции.
До недавнего времени (май 2020 года) было практически невозможно получить достоверное время отображения видеокадра в браузере. Данная проблема решалась через HTMLVideoElement.currentTime
, но не давала необходимой точности. В настоящее время стандарт W3C
HTMLVideoElement.requestVideoFrameCallback(), позволяющий получать текущее время видеокадра, поддерживается Chrome
и Firefox
. Несмотря на то, что это дополнение кажется тривиальным, оно сделало возможным использование некоторых продвинутых возможностей в приложениях, требующих синхронизации аудио и видео. Функция обратного вызова включает поле rtpTimestamp
— временную метку RTP
, связанную с текущим видеокадром.
Отладка задержки
Поскольку отладка в данном случае означает измерение задержки, общее правило таково: упрощаем настройку до минимума при условии сохранения (воспроизводимости) проблемы. Чем больше компонентов мы удалим, тем проще будет определить, какой компонент вызывает задержку.
Задержка камеры
В зависимости от настроек камеры производимая ей задержка может быть разной. Проверяем настройки автоэкспозиции, автофокуса и автоматического баланса белого. Всем автоматическим настройкам требуется дополнительное время для анализа захваченного изображения перед его передачей в стек WebRTC
.
Если у вас Linux
, для управления настройками камеры можно воспользоваться командой v4l2-ctl
:
# Отключаем автофокус
v4l2-ctl -d /dev/video0 -c focus_auto=0
# Устанавливаем фокус в значение бесконечности
v4l2-ctl -d /dev/video0 -c focus_absolute=0
Также можно воспользоваться графическим интерфейсом guvcview
для быстрой проверки и установки настроек камеры.
Задержка шифрования
Большинство современных кодировщиков буферизуют некоторые кадры перед их шифрованием. Их главный приоритет — баланс между качеством производимого изображения и битрейтом. Многопроходное кодирование (multipass encoding) — экстремальный пример пренебрежения кодировщиком конечной задержкой. На первом проходе кодировщик "проглатывает" видео целиком перед началом производства кадров.
Однако при правильной настройке можно добиться уменьшения подкадровой (sub-frame) задержки. Убедитесь, что ваш кодировщик не использует чрезмерное количество эталонных кадров (reference frames) и не полагается на B-кадры (B-frames). Настройки для разных кодеков будет разными, но для x264
рекомендуется использовать tune=zerolatency
и profile=baseline
для снижения задержки.
Сетевая задержка
Сетевая задержка — это то, с чем почти ничего нельзя сделать, кроме обновления сети. Сетевая задержка — это как погода: мы не можем остановить дождь, но можем посмотреть прогноз и взять с собой зонт. WebRTC
измеряет сетевые условия с точностью до мс. И при этом самые важные метрики:
- время пути;
- потеря и ретрансляция пакетов.
Время пути
Стек WebRTC
имеет встроенный механизм для измерения сетевого времени пути (RTT). Достаточно хорошее приблизительное вычисление задержки — это половина RTT
. Механизм исходит из предположения, что отправка и получение пакетов занимает одинаковое время, что не всегда соответствует действительности. RTT
устанавливает нижнюю границу конечной задержки. Наши видеокадры не могут достичь получателя быстрее, чем за RTT / 2
, независимо от того, насколько оптимизирована наша камера и конвейер кодировщика.
Встроенный механизм RTT
основан на специальных пакетах RTCP
, которые называются отчетами отправителя/получателя (sender/receiver reports). Отправитель передает свое время получателю, получатель отвечает тем же временем. Это позволяет отправителю определить количество времени, которое занимает путь пакета туда и обратно.
Потеря и ретрансляция пакетов
Протоколы RTP
и RTCP
основаны на UDP
, что означает отсутствие гарантии сохранения порядка, надежной доставки пакетов или отсутствия дубликатов. Все это может происходить и действительно происходит в реальных WebRTC-приложениях
. Простые реализации декодеров ожидают получения всех кадров изображения для его успешного восстановления. В случае потери пакетов P-кадра (P-frame) могут появиться различные артефакты декодирования. Если потеряны пакеты I-кадра (I-frame), все зависимые кадры получат тяжелые артефакты или совсем не будут декодированы. Это может привести к "зависанию" видео на какое-то время.
Для преодоления зависания видео или появления артефактов декодирования WebRTC
использует сообщения с негативными благодарностями (Negative Acknowledgement, NACK). Когда получатель не получает ожидаемый пакет RTP
, он возвращает отправителю сообщение NACK
для повторной отправки недостающего пакета. Получатель ждет повторной отправки пакета. Такое ожидание увеличивает задержку. Количество отправленных и полученных пакетов NACK
записывается в статические поля nackCount исходящего потока и nackCount входящего потока.
Графы входящих и исходящих nackCount
можно увидеть на странице webrtc-internals
. Если мы видим, что nackCount
увеличивается, значит, имеет место высокая потеря пакетов, и стек WebRTC
делает все возможное для плавного воспроизведения видео или аудио.
При высокой потере пакетов, когда декодер не может произвести изображение или последующие зависимые изображения, например, при полной потере I-кадра, последующие P-кадры не декодируются. Получатель пытается смягчить это путем отправки специального сообщения-индикатора потери изображения (Picture Loss Indicator, PLI). Когда отправитель получает PLI
, он повторно отправляет новый I-кадр. I-кадры, как правило, больше P-кадров по размеру. Это увеличивает количество пакетов для передачи. Как и в случае с NACK
, получатель ждет получения нового I-кадра, что приводит к увеличению задержки.
Смотрите на значение поля pliCount
на странице webrtc-internals
. Если значение данного поля увеличивается, установите меньшее количество производимых декодером кадров или включите более лояльный к ошибкам режим.
Задержка на стороне получателя
Задержка также увеличивается при прибытии пакетов в неправильном порядке. Если нижняя часть изображения прибывает раньше верхней, декодирование изображения будет заниматься чуть больше времени, чем когда части изображения прибывают в правильном порядке. Мы говорили об этом в разделе, посвященном джиттеру.
Встроенная метрика jitterBufferDelay показывает, как долго кадр находился в буфере получателя, ожидая прибытия недостающих пакетов, перед его передачей декодеру для расшифровки.
На этом третья часть перевода завершена.
Благодарю за внимание и happy coding!