"Hearing the voice before the storm"
Voice Activity Detection на Go с микрофона через PipeWire (pw-cat). Чистый Go, без CGO.
Захватывает аудио с микрофона, прогоняет через WebRTC VAD, печатает [VOICE START] / [VOICE END] и записывает аудио в WAV-файлы или отправляет на сервер.
- Linux с PipeWire (
pw-cat) - Go 1.25+
make # собрать все архитектуры
make all # то же самое
make clean # удалить бинарникиСобранные бинарники в build/:
| Бинарник | Архитектура |
|---|---|
heimdallr-sense-linux-amd64 |
x86_64 |
heimdallr-sense-linux-arm64 |
ARM64 (Raspberry Pi 4+, Rock5) |
heimdallr-sense-linux-armv7 |
ARMv7 (Tinker Board, RPi 2/3) |
Локальная сборка:
go build -o heimdallr-sense ./cmd/vad./heimdallr-senseПример вывода (log_enabled: true):
{"time":"2026-07-23T15:30:45Z","level":"INFO","msg":"config loaded from ./config.yaml"}
{"time":"2026-07-23T15:30:45Z","level":"INFO","msg":"listening","rate":8000,"frame_ms":30,"frames":5,"voice_threshold":4,"silence_threshold":3,"record_mode":"file"}
{"time":"2026-07-23T15:30:46Z","level":"INFO","msg":"voice start"}
{"time":"2026-07-23T15:30:46Z","level":"INFO","msg":"recording started","pre_buffer_chunks":3}
{"time":"2026-07-23T15:30:48Z","level":"INFO","msg":"voice end"}
{"time":"2026-07-23T15:30:48Z","level":"INFO","msg":"recording saved","chunks":8,"duration_s":"1.2"}
{"time":"2026-07-23T15:30:48Z","level":"INFO","msg":"file saved","path":"./recordings/2026-07-23_15-30-46.000.wav"}Ctrl+C для остановки.
Все параметры задаются в config.yaml. Конфиг ищется в порядке:
/etc/heimdallr-sense/config.yaml./config.yaml(текущая директория)
Если файл не найден — используются дефолты.
sample_rate: 8000
vad_frame_ms: 30
frames_per_chunk: 5
voice_threshold: 4
silence_threshold: 3
vad_mode: 3
# audio_source: pw-cat, arecord, custom
audio_source: pw-cat
audio_command: ""
# recording: none, file, http, https, both
record_mode: file
record_dir: ./recordings
pre_buffer_chunks: 3
https_url: ""
http_timeout: 10
min_chunks: 3
tls_skip_verify: false
log_enabled: true| Параметр | Описание | Дефолт |
|---|---|---|
sample_rate |
Частота дискретизации микрофона (Hz) | 16000 |
vad_frame_ms |
Длительность фрейма VAD (мс) | 30 |
frames_per_chunk |
Сколько фреймов анализировать за раз | 5 |
voice_threshold |
Мин. фреймов с голосом для START | 4 |
silence_threshold |
Мин. чанков без голоса для END | 3 |
vad_mode |
Агрессивность VAD (0-3) | 3 |
| Параметр | Описание | Дефолт |
|---|---|---|
audio_source |
Источник аудио: pw-cat, arecord, custom |
pw-cat |
audio_command |
Кастомная команда (при audio_source: custom) |
"" |
Примеры:
# PipeWire (по умолчанию)
audio_source: pw-cat
# ALSA
audio_source: arecord
# Кастомная команда (например, через SSH)
audio_source: custom
audio_command: "ssh remote-host arecord -f S16_LE -r 8000 -c 1 -t raw -"| Параметр | Описание | Дефолт |
|---|---|---|
record_mode |
Режим записи: none, file, http, https, both |
none |
record_dir |
Каталог для WAV-файлов | ./recordings |
pre_buffer_chunks |
Чанков в кольцевом буфере (запись до голоса) | 3 |
https_url |
URL для POST-запроса (multipart/form-data), поддерживаются http:// и https:// |
"" |
http_timeout |
Таймаут HTTP-запроса (сек) | 10 |
min_chunks |
Мин. чанков для сохранения записи (отсекает короткие шумы) | 3 |
tls_skip_verify |
Игнорировать проверку TLS-сертификата | false |
log_enabled |
Включить JSON-логи (slog) | true |
- Длительность чанка =
vad_frame_ms*frames_per_chunk(по умолчанию 150мс) - Задержка VOICE START = 1 чанк (150мс)
- Задержка VOICE END =
silence_thresholdчанков (450мс) - Pre-buffer =
pre_buffer_chunksчанков (450мс) — аудио до начала голоса
| Значение | Режим | Описание |
|---|---|---|
| 0 | Quality | Максимальная чувствительность |
| 1 | LowBitrate | Средняя |
| 2 | Aggressive | Снижает ложные срабатывания |
| 3 | VeryAggressive | Минимальная чувствительность, только явная речь |
| Значение | Описание |
|---|---|
none |
Запись отключена |
file |
Сохранение WAV в record_dir |
http |
POST на https_url по HTTP |
https |
POST на https_url по HTTPS |
both |
И файл, и POST |
- 16-bit PCM, mono
- Имя файла:
YYYY-MM-DD_HH-MM-SS.mmm.wav(время начала записи) - Запись включает pre-buffer (аудио до детекции голоса)
Режимы http, https и both отправляют WAV-файл на https_url методом POST
в формате multipart/form-data. Адрес может начинаться с http:// или https:// —
протокол определяется схемой URL, а не значением record_mode.
Запрос содержит:
- поля формы:
filename,sample_rate,duration - файл
file(WAV, 16-bit PCM mono) - query-параметр
filenameв URL
Примеры конфигурации:
# HTTP-отправка на локальный сервис
record_mode: http
https_url: http://127.0.0.1:8080/api/transcribe
# HTTPS с игнорированием проверки сертификата
record_mode: https
https_url: https://example.com/upload
tls_skip_verify: truetls_skip_verify применяется только к https:// URL, на http:// он не влияет.
При ошибке загрузки (недоступный сервер, таймаут, HTTP-статус >= 400) ошибка
выводится в stderr и в JSON-лог. Если включён режим both, запись при этом не
теряется — файл сохраняется в record_dir.
Пример серверного обработчика:
@app.post("/upload")
async def upload(file: UploadFile):
data = await file.read()
# data — WAV-файлФайл сервиса: systemd/heimdallr-sense.service
Установка:
# бинарник
sudo cp build/heimdallr-sense-linux-amd64 /usr/local/bin/heimdallr-sense
sudo chmod +x /usr/local/bin/heimdallr-sense
# конфиг
sudo mkdir -p /etc/heimdallr-sense
sudo cp config.yaml /etc/heimdallr-sense/config.yaml
# сервис
sudo cp systemd/heimdallr-sense.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable heimdallr-sense
sudo systemctl start heimdallr-senseУправление:
sudo systemctl start heimdallr-sense # запустить
sudo systemctl stop heimdallr-sense # остановить
sudo systemctl restart heimdallr-sense # перезапустить
sudo systemctl status heimdallr-sense # статус
sudo systemctl disable heimdallr-sense # отключить автозапускЛоги:
journalctl -u heimdallr-sense -f # следить в реальном времени
journalctl -u heimdallr-sense --since today # за сегодня
journalctl -u heimdallr-sense -n 50 # последние 50 строкДоступ к микрофону:
Если сервис запускается от root, доступ к PipeWire/ALSA может потребовать дополнительных настроек:
# добавить в [Service] если нужен доступ к PulseAudio/PipeWire
Environment="PULSE_SERVER=unix:/run/user/1000/pulse/native"
# или запускать от пользователя, у которого есть доступ к микрофону
User=sb
Group=sbФайл скрипта: systemd/heimdallr-sense.init
Установка:
# бинарник
sudo cp build/heimdallr-sense-linux-amd64 /usr/local/bin/heimdallr-sense
sudo chmod +x /usr/local/bin/heimdallr-sense
# конфиг
sudo mkdir -p /etc/heimdallr-sense
sudo cp config.yaml /etc/heimdallr-sense/config.yaml
# init-скрипт
sudo cp systemd/heimdallr-sense.init /etc/init.d/heimdallr-sense
sudo chmod +x /etc/init.d/heimdallr-sense
# регистрация в автозагрузке
sudo update-rc.d heimdallr-sense defaultsУправление:
sudo service heimdallr-sense start # запустить
sudo service heimdallr-sense stop # остановить
sudo service heimdallr-sense restart # перезапуститьЛоги:
tail -f /var/log/heimdallr-sense.logДеинсталляция:
sudo update-rc.d -f heimdallr-sense remove
sudo rm /etc/init.d/heimdallr-sense
sudo rm /etc/heimdallr-sense/config.yaml
sudo rm /usr/local/bin/heimdallr-sensepw-cat (микрофон)
→ raw S16LE PCM
→ ring buffer (pre-buffer)
→ WebRTC VAD → [VOICE START/END]
→ recording buffer → WAV file / HTTPS POST
Программа запускает pw-cat -r --format s16 --rate <rate> --channels 1 -, читает сырые PCM-данные, разбивает на фреймы и прогоняет через WebRTC VAD (реализация rolandhe/go-vad — чистый Go порт libfvad).
При обнаружении голоса буфер pre-buffer сбрасывается в начало записи, аудио копится пока голос есть, и после silence_threshold чанков тишины запись финализируется.