Потоковая обработка данных в реальном времени: стриминг, P2P, телефония

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

Потоковая обработка данных в реальном времени: стриминг, P2P, телефония
INGELLO / BUSINESS2937
Связаться с нами
Звонок в реальном времени: звук, вероятность речи (VAD), джиттер, буфер и события на одной шкале времени. Иллюстрация на типичном сценарии разговора
Звонок в реальном времени: звук, вероятность речи (VAD), джиттер, буфер и события на одной шкале времени. Иллюстрация на типичном сценарии разговора

Есть программирование, о котором пишут книги, снимают курсы и спорят в комментариях. А есть программирование, о котором молчат, потому что его почти никто не делал. Потоковая обработка данных в реальном времени из второй категории: видео из браузера в браузер, живой голос по телефону, поток с датчиков, который нельзя поставить на паузу. Здесь не бывает «обновите страницу и попробуйте ещё раз»: человек уже сказал фразу, кадр уже улетел, пульс уже ударил.

В этом кейсе мы расскажем, что такое потоковая обработка простыми словами, почему это одна из самых сложных и непопулярных задач в разработке, как устроен пиринговый интернет с его NAT, STUN и TURN, и как мы всё это прошли на реальных проектах. Спойлер: местами было больно, но проекты сделаны и работают =)

Бесплатная консультация с архитектором и расчёт цены после неё. Если у вас задача со стримингом, видеосвязью, голосом или датчиками, разберём её на созвоне и пришлём расчёт сроков и стоимости по этапам. Записаться на консультацию

Что такое потоковая обработка: река, а не озеро

Большинство бизнес-программ работает с данными как с озером. Заказы, платежи и отчёты сначала накапливаются в базе, а потом их обрабатывают: раз в час, раз в сутки, по кнопке. Это называется пакетной (батчевой) обработкой. Если ночной отчёт посчитался на пять минут дольше, никто даже не заметит.

Потоковая обработка работает с данными как с рекой. Данные текут непрерывно, мелкими порциями: кадр видео, пакет звука длиной 20 миллисекунд, отсчёт датчика. Каждую порцию нужно принять, проверить, обработать и отдать дальше, пока не пришла следующая. Опоздала обработка, и порция уже никому не нужна: кадр, показанный через секунду после своего времени, это не видео, а слайд-шоу, а ответ голосового агента через пять секунд воспринимается как зависание.

Конвейер потоковой обработки: источник, захват и кодек, транспорт, джиттер-буфер, обработка, выход

По дороге поток подстерегают пять врагов, и с каждым мы знакомы лично:

  • Задержка. На весь путь от микрофона до уха собеседника есть бюджет в несколько сотен миллисекунд. Его делят между захватом, сетью, обработкой и воспроизведением, и каждый участок норовит откусить побольше.
  • Джиттер. Пакеты приходят неравномерно: то пачкой, то с паузой. Если проигрывать их как пришли, звук булькает и заикается. Нужен джиттер-буфер, который чуть придерживает поток и выдаёт его ровно.
  • Потери. В живом видео и голосе используется UDP, а он потерянные пакеты не переспрашивает. И правильно: потерянный кадр через секунду уже не нужен. Значит, система должна уметь жить с дырками.
  • Порядок. Пакеты обгоняют друг друга в сети, и их нужно сложить обратно по номерам и меткам времени. Иначе собеседник скажет «нет, да» вместо «да, нет».
  • Backpressure (обратное давление). Если обработка не успевает за потоком, данные копятся в очередях, задержка растёт, и через минуту разговор идёт с отставанием в десять секунд. Правильный поток умеет вовремя выбросить лишнее, а не копить его.

Почему это сложнейшая и непопулярная задача

Обычный веб живёт по принципу «запрос и ответ». Браузер спросил, сервер ответил, всё. Ошибку можно воспроизвести, поставить точку останова, посмотреть переменные. В потоке так не получится: пока вы стоите на точке останова, поток утёк, соединение отвалилось по таймауту, а собеседник положил трубку. Отлаживать приходится на живом потоке, по логам и метрикам, иногда покадрово.

  • Сеть ведёт себя непредсказуемо. У одного пользователя домашний Wi-Fi, у другого мобильный интернет в лифте, у третьего офисный файрвол, который пропускает только веб-страницы. И всё это должно работать одинаково.
  • Ошибки плавающие. «У меня всё работает» здесь звучит особенно жалко: баг проявляется только у конкретного оператора, только на конкретном телефоне, только при плохом сигнале.
  • Стек огромный. Кодеки, частоты дискретизации, протоколы RTP и SIP, WebRTC, WebSocket, NAT, сертификаты, браузерные аудио-API. Каждая тема тянет на отдельную книгу, и книги эти в основном на английском и в основном для телеком-инженеров.
  • Специалистов почти нет. Людей, которые реально запускали стриминг в продакшене, единицы, и они обычно сидят в крупных компаниях. На рынке услуг их практически не найти даже за хорошие деньги.

Сегодня часть ответов можно спросить у языковой модели, и она даже что-то подскажет. Но большая часть нашего опыта в реальном времени набиралась тогда, когда спросить было не у кого. Документация WebRTC описывала, как должно быть, форумы описывали, как у кого-то не работает, а между этим была пропасть, которую приходилось перепрыгивать самим. Мы искали консультанта по WebRTC за деньги и честно не нашли: те, кто умел, были заняты, а те, кто был свободен, знали тему по той же документации, что и мы. Поэтому всё, о чём дальше пойдёт речь, мы прошли руками: экспериментами, логами и бессонными ночами с консолью браузера.

Пиринговый интернет: почему два компьютера не могут просто поговорить

Кажется, что интернет это сеть, где любой компьютер может связаться с любым другим. В теории так и задумывалось. На практике адресов IPv4 на всех не хватило, и почти каждое устройство сегодня сидит за роутером, который прячет его за одним общим адресом. Это называется NAT (трансляция сетевых адресов).

Два роутера-крепости, маяк STUN и мост TURN между ними
Метафора пирингового соединения: два роутера-крепости, маяк STUN, который подсказывает каждому его внешний адрес, и мост TURN на случай, если напрямую не пробиться

NAT: охранник на входе

Роутер работает как охранник в бизнес-центре. Изнутри наружу вы выходите свободно, он запоминает, кто куда пошёл, и пропускает ответ. А вот снаружи к вам без приглашения не зайти: незнакомый входящий пакет охранник просто выбрасывает. Для сайтов это отлично, вы всегда «звоните первым». Для видеозвонка из браузера в браузер это катастрофа: оба собеседника сидят за своими охранниками, и каждый ждёт, что первым позвонит другой.

UDP против TCP

TCP это заказное письмо: доставка гарантирована, порядок гарантирован, потерянное пересылается. Для страниц и файлов идеально. Для живого звука плохо: пока TCP переспрашивает потерянный пакет, вся очередь за ним стоит, и звук замирает. UDP это открытки: отправил и забыл. Что-то потеряется, зато ничего не ждёт. Поэтому видео и голос в реальном времени живут на UDP, а с потерями разбираются кодеки и джиттер-буфер.

STUN, hole punching и ICE

Чтобы два браузера соединились напрямую, им нужно три вещи. Первая: сигнальный сервер, обычно на WebSocket, через который они обмениваются «визитками» (offer, answer и кандидатами адресов). Сам видеопоток через него не идёт. Вторая: STUN-сервер, который отвечает на простой вопрос «как меня видно снаружи», то есть сообщает внешний адрес и порт. Третья: hole punching, «пробивание дыры». Оба браузера одновременно отправляют друг другу UDP-пакеты, каждый охранник думает, что его жилец «позвонил первым», и пропускает ответ.

Всем этим дирижирует протокол ICE. Он собирает кандидатов (локальный адрес, внешний адрес от STUN, адрес ретранслятора) и перебирает их, пока какой-нибудь не заработает.

Как два браузера соединяются через NAT: сигнальный сервер, STUN, TURN и ICE

TURN, симметричный NAT и мобильные сети

Теперь плохие новости. Есть симметричный NAT, который для каждого нового адресата выдаёт новый внешний порт, и подсказка STUN становится бесполезной. Есть CGNAT у мобильных операторов: NAT за NAT, когда тысячи абонентов сидят за одним адресом. Есть корпоративные сети, где UDP запрещён совсем. Во всех этих случаях напрямую не пробиться, и на сцену выходит TURN: ретранслятор, который принимает поток от одного собеседника и пересылает другому. Работает почти всегда, в том числе по TLS на 443 порту, который выглядит для файрвола как обычный сайт. Но весь трафик идёт через ваш сервер, и вы за него платите.

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

Опыт 1: стриминг в браузере для премиальной социальной платформы

Нашим первым большим проектом в реальном времени стала премиальная социальная платформа с видеочатом и стримингом прямо в браузере. Пользователь включает камеру и ведёт трансляцию, зрители смотрят её без установки чего-либо, а время в чате и на стриме оплачивается внутренними кредитами. То есть каждая секунда видео здесь ещё и деньги: биллинг должен точно знать, когда поток начался и когда оборвался.

Схема «каждый с каждым» (mesh) для стрима не годится: если у стримера сто зрителей, его браузеру пришлось бы отправлять сто копий видео, и ноутбук взлетел бы. Поэтому мы написали собственный SFU (Selective Forwarding Unit, сервер выборочной пересылки) на Node.js. Стример отправляет один поток на сервер, сервер раздаёт его неограниченному числу зрителей, а браузер стримера держит ровно одно соединение.

  • Сигнализация на WebSocket. Обмен offer, answer и ICE-кандидатами между браузером и сервером, роли стримера и зрителя, автоматическое переподключение при обрыве.
  • Свой STUN/TURN на coturn. С TLS-сертификатом, отдельными портами и логами. Без него часть зрителей с мобильного интернета просто видела чёрный экран.
  • Жизненный цикл соединений. Стример ушёл, и все его зрители корректно отключаются; зритель пропал из сети, и сервер освобождает его ресурсы, не дожидаясь вежливого прощания.
  • Чат на WebSocket рядом с видео, под присмотром супервизора, который поднимает демона после падения.
  • Мобильные оболочки для Android и iOS и запись сториз прямо с камеры устройства.
  • Покадровая диагностика. Мы логировали каждый кадр (номер, размер, время) и состояние ICE-соединения раз в секунду. Звучит как паранойя, но именно так ловятся баги, которые случаются только у одного зрителя из двадцати.

Отдельная история: смена камеры на лету. Пользователь подключил внешнюю камеру или выдернул её посреди эфира, браузер шлёт событие, и поток нужно пересобрать, не обрывая трансляцию. В документации это одна строчка, в жизни это неделя экспериментов. Позже в других проектах мы пробовали и готовый медиасервер Janus, но собственный SFU дал главное: мы понимаем каждую строчку пути пакета и можем починить его сами.

Опыт 2: голос и телефония в реальном времени

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

Телефонная трубка и конвейер обработки звука
Путь голоса: телефонная линия, захват, буфер, обработка на сервере и браузерное воспроизведение
  • Телефония. Звонок приходит по SIP в АТС Asterisk, звук идёт по RTP кадрами по 20 миллисекунд в телефонном кодеке 8 кГц. Сервер на Node.js забирает поток, декодирует, пересэмплирует под нужды распознавания и отдаёт обратно синтезированную речь с тем же ритмом, кадр за кадром.
  • Задержка ответа. Первые версии отвечали через несколько секунд, а в телефоне это вечность: человек успевает сказать «алло, вы тут?». Мы разобрали бюджет задержки по участкам, перевели распознавание, генерацию и озвучку в потоковый режим (озвучиваем первую фразу, пока пишется вторая), уменьшили буферы и довели паузу на живом звонке до уровня меньше секунды.
  • Эхо. На мобильной линии секретарь слышал сам себя и вежливо отвечал сам себе. Пришлось учиться отличать собственное эхо от настоящей речи собеседника.
  • Перебивание (barge-in). Живой человек перебивает, и агент должен замолчать за доли секунды, а потом понять, это было «угу» или новая мысль.
  • Конец реплики. Как понять, что человек договорил, а не просто задумался? Мы сравнили несколько подходов на полутора часах реальных записей и выбрали лучший баланс между «перебивает» и «тормозит».
  • Капризы RTP. Например, адрес для отправки звука обратно в звонок становится известен только из первого входящего пакета. Если не начать сразу проигрывать тишину, абонент секретаря просто не услышит. Такие вещи не пишут в учебниках, их находят в логах.
  • Порядок и зависания. Один зависший кусок синтеза речи мог остановить весь разговор. Теперь куски идут строго по порядку, медленные запросы дублируются запасными, а обрыв WebSocket к телефонии восстанавливается автоматически.
  • Переключение без разрыва. Звонок можно перенести между версиями голосового контура прямо во время разговора, и звонящий этого не заметит.

Есть и браузерные сборки: микрофон через getUserMedia с шумоподавлением и эхоподавлением, воспроизведение через AudioWorklet с собственным джиттер-буфером, полнодуплексный режим на WebRTC. А про синтез и распознавание речи как отдельный сервис можно почитать в кейсе NaturalTTS.

Опыт 3: датчики, биометрия и мониторинг

Поток это не только видео и голос. Первый график температуры и влажности в реальном времени мы собрали ещё в 2019 году: датчик на Arduino, последовательный порт, Python и поток событий прямо в браузер. Позже это выросло в IoT-контур на дешёвом железе с автономной записью в память датчика, когда компьютер выключен, и досылкой без дублей при подключении.

Самый плотный поток у нас в проекте ЩИТ: носимое устройство на ESP32 непрерывно снимает биометрию и отправляет её на сервер пачками раз в секунду. Связь пропала, и прибор копит данные на карту памяти, а потом досылает с того места, где остановился. Формат записи мы сделали примерно в десять раз компактнее JSON, а сервер проверили нагрузочным тестом на сотне виртуальных устройств. Панель в браузере рисует графики вживую и перерисовывает только то, что изменилось.

Модули реального времени в наших проектах: телефония, сервер на Node.js, браузер
Инфографика: почему потоковая обработка данных сложна
Шпаргалка по потоковой обработке. Инфографика собрана в Gemini Notebook по материалам этого кейса

Что это даёт вам

Мы не продаём «стриминг из коробки». Мы приносим опыт, который трудно купить: знаем, где поток ломается, и закладываем это в архитектуру с первого дня, а не находим на запуске.

  • Видеосвязь и трансляции в браузере без установки приложений: консультации, обучение, продажи по видео, платные эфиры с биллингом по времени.
  • Голосовые агенты на обычной телефонии: приём звонков, обзвоны, запись и расшифровка разговоров, интеграция с CRM.
  • Мониторинг датчиков и оборудования в реальном времени: от климата на складе до носимых устройств, с буферизацией при обрывах связи.
  • Своя инфраструктура: STUN/TURN, медиасерверы, WebSocket-шлюзы на ваших серверах, без зависимости от чужого сервиса и его тарифа за минуту.
  • Честная оценка: где хватит готового сервиса, а где нужна своя разработка. Иногда правильный ответ «не пишите свой SFU», и мы так и скажем.

Обсудим ваш поток

Если у вас есть данные, которые текут, а не лежат, будь то видео, голос, звонки или датчики, давайте поговорим. Разберём задачу, покажем риски и пришлём расчёт цены и сроков по этапам после бесплатной консультации. Оставить заявку на systems.ingello.com

Технологический стек и подходы

SaaS архитектура Микросервисы Облачные вычисления Масштабируемость ПО API интеграции DevOps процессы High-load системы Кибербезопасность решений

Частые вопросы

01Чем потоковая обработка отличается от обычной?

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

02Зачем нужны STUN и TURN серверы?

Почти все устройства сидят за NAT и не принимают входящие соединения. STUN подсказывает браузеру его внешний адрес, чтобы соединиться напрямую. TURN пересылает поток через себя, когда прямой путь закрыт: при симметричном NAT, в мобильных сетях с CGNAT и в строгих корпоративных сетях.

03Можно ли сделать видеосвязь и трансляции без установки приложения?

Да. WebRTC работает прямо в браузере. Для трансляции на многих зрителей мы используем SFU-сервер, а для пользователей за строгими NAT держим собственный TURN.

04Сколько стоит разработка стриминга или голосового агента?

Зависит от числа пользователей, требований к задержке и инфраструктуры. Цену мы считаем после бесплатной консультации и присылаем расчёт по этапам.