Fix video codec, aspect and placement

Three problems visible at once on a running instance: the picture
stuttered and drifted behind the sound, it was letterboxed oddly, and it
appeared as a separate tile instead of coming from the bot.

- YouTube handed us AV1 (format 398). Software-decoding AV1 at 720p does
  not sustain real time on a small server, which explains both the
  stutter and the drift; H.264 is now requested first, VP9 second.
- The frame was padded into a fixed box, so a clip whose proportions
  differed got black bars baked in and then more from the client. Size is
  now a bounding box and the frame keeps the clip's own proportions.
- Video was published as a screen share, which every client renders as
  its own tile. VIDEO_SOURCE=camera (the new default) puts it inside the
  bot's tile; "screen" keeps the old behaviour.

VIDEO_SYNC_OFFSET_MS is there for the residual drift, since audio and
video travel as two separately published tracks. Measured loudnorm first
to rule it out as the cause of the desync: it adds 0 ms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-09-09 02:29:29 +03:00
co-authored by Claude Opus 5
parent 9285fd7fbd
commit 0b17b880d6
7 changed files with 113 additions and 25 deletions
+15 -2
View File
@@ -240,6 +240,8 @@ VIDEO_ENABLED=true
VIDEO_WIDTH=640
VIDEO_HEIGHT=360
VIDEO_FPS=24
VIDEO_SOURCE=camera
VIDEO_SYNC_OFFSET_MS=0
```
Что нужно на стороне Stoat: у роли бота — право **Video** в канале, а на инстансе включённое
@@ -259,9 +261,20 @@ VIDEO_FPS=24
- **Один процесс ffmpeg, два выхода:** PCM для голосовой дорожки и сырые I420-кадры для видео.
Темп задаёт `-re`, иначе кадры улетали бы вперёд звука и съедали память — распакованный кадр
720p весит 1.4 МБ.
- **Кодек важнее разрешения.** YouTube по умолчанию отдаёт AV1, а его программное
декодирование не вытягивает реальное время на слабом сервере: картинка дёргается и уползает
от звука. Поэтому запрашивается сначала H.264, затем VP9, и только потом что придётся.
- **Размер — это рамка, а не жёсткий кадр.** `VIDEO_WIDTH`/`VIDEO_HEIGHT` задают ограничение, в
которое кадр вписывается с сохранением пропорций; чёрные поля не добавляются, их при
необходимости рисует сам клиент.
- **Где показывается.** `VIDEO_SOURCE=camera` (по умолчанию) — картинка внутри плитки бота;
`screen` — отдельной плиткой, как демонстрация экрана.
- **Синхронизация.** Звук и картинка публикуются двумя дорожками, поэтому совпадение зависит от
того, успевает ли сервер декодировать в реальном времени. Если картинка стабильно
опережает или отстаёт, подстройте `VIDEO_SYNC_OFFSET_MS` (плюс задерживает видео).
- **Цена.** Кодирование видео ложится на CPU сервера и держится всё время трека, в отличие от
почти бесплатного звука. 360p по умолчанию выбран из осторожности; поднимайте до 720p, если
машина тянет, и снижайте `VIDEO_FPS`, если нагрузка мешает.
почти бесплатного звука. 360p по умолчанию выбран из осторожности; поднимайте, если машина
тянет, и снижайте `VIDEO_FPS`, если нагрузка мешает.
- Живые трансляции и не-YouTube источники играют звуком, как раньше.
## Прокси, когда YouTube недоступен