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
+5 -2
View File
@@ -12,7 +12,7 @@ from ..config import Camera, save_config
from ..runtime import runtime
from ..services import go2rtc
from ..transcode import (encode_args, norm_profile, norm_bitrate, norm_preset,
norm_keyint, norm_codec)
norm_keyint, norm_codec, norm_rc, norm_qp)
router = APIRouter(prefix="/api/cameras", tags=["cameras"])
@@ -86,6 +86,8 @@ async def camera_live_transcoded(
preset: str = Query(None),
keyint: str = Query(None),
codec: str = Query(None),
rc: str = Query(None),
qp: str = Query(None),
stream: str = Query("sub"),
):
"""Live с перекодированием в H.264 (fragmented mp4) под профиль/битрейт клиента.
@@ -103,7 +105,8 @@ async def camera_live_transcoded(
"-fflags", "nobuffer", "-flags", "low_delay",
"-rtsp_transport", "tcp", "-i", src,
*encode_args(norm_codec(codec), norm_profile(profile), norm_bitrate(bitrate),
low_latency=True, preset=norm_preset(preset), keyint_sec=norm_keyint(keyint)),
low_latency=True, preset=norm_preset(preset), keyint_sec=norm_keyint(keyint),
rc=norm_rc(rc), qp=norm_qp(qp)),
"-an",
"-movflags", "frag_keyframe+empty_moov+default_base_moof",
"-f", "mp4", "pipe:1",
+6 -2
View File
@@ -9,7 +9,8 @@ from fastapi.responses import FileResponse, StreamingResponse
from ..runtime import runtime
from ..transcode import (H264_PROFILES, BITRATES, encode_args, norm_profile,
norm_bitrate, norm_preset, norm_keyint, norm_codec)
norm_bitrate, norm_preset, norm_keyint, norm_codec,
norm_rc, norm_qp)
router = APIRouter(prefix="/api/recordings", tags=["recordings"])
@@ -53,6 +54,8 @@ async def recording_transcoded(
preset: str = Query(None),
keyint: str = Query(None),
codec: str = Query(None),
rc: str = Query(None),
qp: str = Query(None),
start: float = Query(0.0),
):
"""Перекодирование H.265→H.264 на лету (fragmented mp4) — для браузеров без HEVC.
@@ -71,7 +74,8 @@ async def recording_transcoded(
*(["-ss", f"{start:.3f}"] if start and start > 0 else []),
"-i", rec.path,
*encode_args(norm_codec(codec), norm_profile(profile), norm_bitrate(bitrate),
preset=norm_preset(preset), keyint_sec=norm_keyint(keyint)),
preset=norm_preset(preset), keyint_sec=norm_keyint(keyint),
rc=norm_rc(rc), qp=norm_qp(qp)),
"-an",
"-movflags", "frag_keyframe+empty_moov+default_base_moof",
"-f", "mp4", "pipe:1",