## 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) ```bash sudo apt install g++ cmake make libsfml-dev ``` Сборка: ```bash cmake -S . -B build cmake --build build ``` ### Запуск Приемник: ```bash ./build/consumer 5000 ``` Передача файла: ```bash ./build/producer file input.wav 5000 127.0.0.1 ``` Передача микрофона: ```bash ./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` Эквивалентная краткая запись формата: ```text PCM s16le mono 44100 Hz ``` ### Интерпретация payload Каждые 2 байта образуют один сэмпл `int16`: ```text 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` Минимально совместимый контракт можно сформулировать так: ```text UDP packet payload = raw PCM samples sample format = signed 16-bit little-endian channels = 1 sample rate = 44100 Hz max payload = 4096 bytes ```