Files
sfml-streamer/README.md
2026-06-26 22:53:15 +03:00

6.3 KiB
Raw Permalink Blame History

SFML Streaming

Небольшой проект на C++ и SFML для локального воспроизведения и UDP-трансляции аудио.

В проекте есть две исполняемые программы:

  • producer - источник аудио. Может читать аудиофайл или захватывать микрофон, приводить поток к внутреннему формату и отправлять его по UDP одному или нескольким получателям.
  • consumer - приемник аудио. Слушает UDP-порт, принимает аудиочанки и сразу воспроизводит их локально.

Как это работает:

  • producer получает звук из файла или с микрофона.
  • входной поток приводится к формату PCM 16-bit mono 44100 Hz.
  • дальше поток режется на чанки и каждый чанк отправляется отдельной UDP-датаграммой.
  • consumer принимает датаграммы, интерпретирует их как массив int16-сэмплов и подает в sf::SoundStream.

Сборка

Требования:

  • CMake
  • компилятор с поддержкой C++17
  • SFML с модулями system, audio, network

Установка зависимостей (debian/ubuntu)

sudo apt install g++ cmake make libsfml-dev

Сборка:

cmake -S . -B build
cmake --build build

Запуск

Приемник:

./build/consumer 5000

Передача файла:

./build/producer file input.wav 5000 127.0.0.1

Передача микрофона:

./build/producer mic 5000 127.0.0.1

Флаг --no-local отключает локальное воспроизведение на стороне producer.

Контракт формата трансляции

Этот раздел описывает точный формат UDP-потока, который ожидает consumer и отправляет producer.

Транспорт

  • Протокол: UDP
  • Одна UDP-датаграмма = один аудиочанк
  • Соединение не устанавливается, подтверждения доставки нет
  • Пакеты могут теряться, дублироваться или приходить не по порядку: приложение это не исправляет

Полезная нагрузка UDP-пакета

  • Только сырые PCM-данные
  • Заголовка пакета нет
  • Метаданных нет
  • Идентификатора потока нет
  • Порядкового номера пакета нет
  • Временной метки нет
  • Контрольной суммы на уровне приложения нет

Иными словами, payload каждого UDP-пакета - это просто последовательность байт, которую приемник читает как массив 16-битных signed little-endian сэмплов.

Аудиоформат

  • Кодирование: PCM
  • Тип сэмпла: signed 16-bit integer
  • Порядок байт: little-endian
  • Каналы: 1 (mono)
  • Частота дискретизации: 44100 Hz

Эквивалентная краткая запись формата:

PCM s16le mono 44100 Hz

Интерпретация payload

Каждые 2 байта образуют один сэмпл int16:

byte0 byte1 -> sample0
byte2 byte3 -> sample1
...

Пример одного сэмпла:

  • байты 0x34 0x12 будут интерпретированы как значение 0x1234

Размер пакета

По текущей реализации приемник читает не более 4096 байт за одну UDP-датаграмму.

Практические требования:

  • размер payload должен быть не больше 4096 байт
  • размер payload должен быть кратен 2, потому что один PCM-сэмпл занимает 2 байта
  • безопаснее отправлять заметно меньше MTU сети, чтобы снизить риск фрагментации UDP-пакетов

producer в текущей реализации обычно отправляет до 2048 сэмплов за раз, то есть до 4096 байт payload.

Что приемник не поддерживает

consumer не умеет автоматически разбирать или декодировать:

  • WAV, RIFF или любой другой контейнер
  • MP3, AAC, Opus, Vorbis, FLAC и другие сжатые форматы
  • float32 PCM
  • stereo или многоканальный поток
  • частоты дискретизации, отличные от 44100 Hz
  • пользовательские заголовки перед PCM-данными

Если передатчик отправляет любой из этих форматов, звук на приемнике будет искажен или не будет воспроизводиться корректно.

Контракт для внешнего передатчика

Если поток отправляется не из producer, а из другого приложения, например из Dart, внешний передатчик должен:

  • открыть UDP-сокет
  • отправлять датаграммы на порт, который слушает consumer
  • помещать в payload только raw PCM s16le mono 44100 Hz
  • не добавлять заголовок перед PCM-данными
  • следить, чтобы размер каждой датаграммы был не больше 4096 байт и кратен 2

Минимально совместимый контракт можно сформулировать так:

UDP packet payload = raw PCM samples
sample format      = signed 16-bit little-endian
channels           = 1
sample rate        = 44100 Hz
max payload        = 4096 bytes