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 и другие сжатые форматы
float32PCMstereoили многоканальный поток- частоты дискретизации, отличные от
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