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

147 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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
```