transcode: add CBR/VBR/QP rate control + more x264 presets

Player/transcoding settings gain encoder controls:
- Rate-control mode VBR / CBR / QP (constant quantizer). x264: VBR uses
  -b:v/-maxrate/-bufsize, CBR adds -x264-params nal-hrd=cbr (min=max=b:v),
  QP uses -qp N (no bitrate). VP9 maps QP -> -crf N -b:v 0, CBR -> min=max=b:v.
- Preset list expanded to ultrafast/superfast/veryfast/faster/fast/medium
  (was only ultrafast/superfast); for VP9 the preset maps to cpu-used.
- New QP field in the UI, shown when rate control = QP, otherwise the
  bitrate field is shown.

Both /play.mp4 (archive) and /live.mp4 accept rc + qp query params.
All three modes verified producing valid output on the live restream.
VERSION -> 0.0.119.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 16:38:04 +05:00
parent 5828873910
commit c876a46e61
5 changed files with 90 additions and 33 deletions
+63 -22
View File
@@ -1,17 +1,25 @@
"""Общие параметры перекодирования H.264 (профиль, битрейт, пресет, GOP)."""
"""Общие параметры перекодирования H.264/VP9: профиль, битрейт, пресет, GOP, режим RC."""
from __future__ import annotations
H264_PROFILES = ("baseline", "main", "high")
BITRATES = ("500k", "1000k", "2000k", "4000k", "8000k", "10000k", "16000k", "20000k", "24000k")
PRESETS = ("ultrafast", "superfast")
PRESETS = ("ultrafast", "superfast", "veryfast", "faster", "fast", "medium")
KEYINTS = (1, 2, 5) # интервал ключевых кадров, секунды
CODECS = ("h264", "vp9")
RC_MODES = ("vbr", "cbr", "qp") # контроль битрейта: переменный / постоянный / постоянный квантизатор
DEFAULT_PROFILE = "main"
DEFAULT_BITRATE = "2000k"
DEFAULT_PRESET = "superfast"
DEFAULT_KEYINT = 2
DEFAULT_CODEC = "h264"
DEFAULT_RC = "vbr"
DEFAULT_QP = 23
QP_MIN, QP_MAX = 0, 51
# пресет x264 → cpu-used VP9 (больше число = быстрее кодирование, ниже качество)
_VP9_CPU = {"ultrafast": "8", "superfast": "8", "veryfast": "7",
"faster": "6", "fast": "5", "medium": "4"}
def norm_profile(p: str | None) -> str:
@@ -30,6 +38,10 @@ def norm_preset(p: str | None) -> str:
return p if p in PRESETS else DEFAULT_PRESET
def norm_rc(m: str | None) -> str:
return m if m in RC_MODES else DEFAULT_RC
def norm_keyint(k) -> int:
try:
k = int(k)
@@ -38,51 +50,80 @@ def norm_keyint(k) -> int:
return k if k in KEYINTS else DEFAULT_KEYINT
def x264_args(profile: str, bitrate: str, low_latency: bool = False,
preset: str | None = None, keyint_sec=None) -> list[str]:
"""Аргументы libx264: профиль, битрейт, пресет и интервал ключевых кадров.
def norm_qp(q) -> int:
try:
q = int(q)
except (TypeError, ValueError):
return DEFAULT_QP
return max(QP_MIN, min(QP_MAX, q))
Ключевые кадры расставляются по времени (через -force_key_frames), поэтому
интервал в секундах соблюдается независимо от fps камеры."""
bufsize = f"{int(bitrate[:-1]) * 2}k"
def _kbps(bitrate: str) -> int:
return int(bitrate[:-1])
def x264_args(profile: str, bitrate: str, low_latency: bool = False,
preset: str | None = None, keyint_sec=None,
rc: str = DEFAULT_RC, qp=DEFAULT_QP) -> list[str]:
"""Аргументы libx264: профиль, пресет, GOP и режим контроля битрейта.
rc: vbr — целевой битрейт с потолком (-b:v/-maxrate); cbr — постоянный
битрейт (nal-hrd=cbr, min=max=b:v); qp — постоянный квантизатор (-qp, без
битрейта). Ключевые кадры расставляются по времени (-force_key_frames)."""
preset = norm_preset(preset)
sec = norm_keyint(keyint_sec)
rc = norm_rc(rc)
args = [
"-c:v", "libx264",
"-preset", preset,
"-profile:v", profile,
"-b:v", bitrate,
"-maxrate", bitrate,
"-bufsize", bufsize,
"-force_key_frames", f"expr:gte(t,n_forced*{sec})",
"-pix_fmt", "yuv420p",
]
if rc == "qp":
args += ["-qp", str(norm_qp(qp))]
elif rc == "cbr":
kb = _kbps(bitrate)
args += ["-b:v", bitrate, "-minrate", bitrate, "-maxrate", bitrate,
"-bufsize", f"{kb}k", "-x264-params", "nal-hrd=cbr"]
else: # vbr
kb = _kbps(bitrate)
args += ["-b:v", bitrate, "-maxrate", bitrate, "-bufsize", f"{kb * 2}k"]
if low_latency:
args += ["-tune", "zerolatency"]
return args
def vp9_args(bitrate: str, low_latency: bool = False,
preset: str | None = None, keyint_sec=None) -> list[str]:
"""Аргументы кодировщика libvpx-VP9 (профиль H.264 неприменим)."""
preset: str | None = None, keyint_sec=None,
rc: str = DEFAULT_RC, qp=DEFAULT_QP) -> list[str]:
"""Аргументы libvpx-VP9 (профиль H.264 неприменим).
rc: vbr/cbr — по битрейту; qp — постоянное качество (-crf N -b:v 0, у VP9
нет прямого -qp). cpu-used маппится из пресета. realtime для live."""
sec = norm_keyint(keyint_sec)
# быстрый пресет → выше cpu-used (быстрее, ниже качество); realtime для live
cpu = "8" if norm_preset(preset) == "ultrafast" else "6"
return [
rc = norm_rc(rc)
args = [
"-c:v", "libvpx-vp9",
"-b:v", bitrate,
"-maxrate", bitrate,
"-deadline", "realtime",
"-cpu-used", cpu,
"-cpu-used", _VP9_CPU.get(norm_preset(preset), "6"),
"-row-mt", "1",
"-force_key_frames", f"expr:gte(t,n_forced*{sec})",
"-pix_fmt", "yuv420p",
]
if rc == "qp":
args += ["-crf", str(norm_qp(qp)), "-b:v", "0"]
elif rc == "cbr":
args += ["-b:v", bitrate, "-minrate", bitrate, "-maxrate", bitrate]
else: # vbr
args += ["-b:v", bitrate, "-maxrate", bitrate]
return args
def encode_args(codec: str, profile: str, bitrate: str, low_latency: bool = False,
preset: str | None = None, keyint_sec=None) -> list[str]:
preset: str | None = None, keyint_sec=None,
rc: str = DEFAULT_RC, qp=DEFAULT_QP) -> list[str]:
"""Аргументы видеокодировщика под выбранный кодек (h264 | vp9)."""
if norm_codec(codec) == "vp9":
return vp9_args(bitrate, low_latency, preset, keyint_sec)
return x264_args(profile, bitrate, low_latency, preset, keyint_sec)
return vp9_args(bitrate, low_latency, preset, keyint_sec, rc, qp)
return x264_args(profile, bitrate, low_latency, preset, keyint_sec, rc, qp)