Сидел я значится между двумя ноутбуками, на одном из которых играла музыка. Динамики старые и стерео выдают в неприкрытом моно. Между звуками барабанов и синтезаторов в моей голове прозвучала мысль — «А давай‑ка одновременно включим музыку на двух ноутбуках, получу ли я музыкальное наслаждение?» — Получил это музыкальное наслаждение я аж целых три года назад, тогда же появилась идея оформить сеё чудо в виде приложения, которое бы захватывало системный звук устройства и раздавало бы его на динамики любых других устройств (Компьютеры, планшеты, телефоны...), включая устройство захвата.

Первая попытка

Начал я с трехступенчатой технологии Забрал - Отдал - Помолился

1. Забирал AudioChunk-и из аудиокарты Забрал

2. В сухом виде отдавал по WebSocket-у на фронт Отдал

3. AudioChunk-и отдавались Audio-элементу Помолился

Задержка была самая минимальная из всех используемых мною технологии, но вкладка в браузере, в которую беспощадно заливали мегатонны незакомпресированных, незасинхронизированных байт не щадила слабые устройства, из-за чего на телефоне высвечивалось предложение закрыть приложение браузера и попробовать другой способ передачи аудио

Вторая попытка

В рамках профессиональный деятельности познакомился с WebRTC, технология которая напомнила мне о моей заброшенной к этому моменту идее - Передача медиа-траффика между подписчиком и зрителем с наименьшей задержкой. Тогда я решил выйти за рамки и интегрировать WebRTC в свой личный проект. Использовал WebRTC Native библиотеку на C++, чтобы создавать RTCPeerConnection на том же уровне, на котором я забираю звук. По концепции получилась SFU сессия, где один участник, в виде аудиокарты устройства на котором запущено приложение, раздает звук всем участникам, подключающихся через фронт. Основная задача стояла в том, чтобы использовать WebRTC не только как передачу с низкой задержкой, но и засинхронизировать звук между участниками. Архитектура выглядела следующим образом

  • Создается loopback, который устанавливается как дефолтный аудиовыход, за счет чего получает все звуки устройства

  • MASTER RTCPeerConnection, который создается при запуске приложения как мастер-пир, забирает звук с loopback и с которым все создают соединения и получают от него audio, timestamp , syncTime

  • LOCAL RTCPeerConnection, который также создается при запуске приложения, устанавливает соединение с мастер-пиром и перенаправляет аудио-данные на физическую аудиокарту, это шаг позволяет устройству поставляющего звук, играть его синхронно с другими устройствами

  • BROWSER RTCPeerConnection, который создается

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

Финальная попытка

Нашел готовый велосипед, модифицировал под свои хотелки и натянул на дизайн собственной разработки.

        Snapcast - Synchronous audio player

Snapcast - сервер, который раздает звук в идеальной синхронизации, остается только его кормить

Архитектура вышла следующей:

  • LOOPBACK - Создается виртуальный аудиовыход, который устанавливается как дефолтный в системе

  • ROUTER - Перенаправляет звук с LOOPBACK на SNAPCAST

  • SNAPCAST - Сервер, который раздает

  • LOCAL SNAPCLIENT - Создается на устройство, где запущено приложение, подключается к SNAPCAST и перенаправляет звук на физические динамики

  • BROWSER SNAPCLIENT - Любое устройство, подключившееся к приложению через фронт

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