feat(host): in-place encoder rate reconfigure — ABR steps no longer cost an IDR
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 1m8s
ci / bench (push) Successful in 5m25s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
arch / build-publish (push) Successful in 11m11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 41s
android / android (push) Successful in 14m53s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6m24s
deb / build-publish (push) Successful in 11m50s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m5s
ci / rust (push) Successful in 23m4s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m54s
docker / deploy-docs (push) Successful in 22s
apple / swift (push) Successful in 4m31s
windows-host / package (push) Successful in 14m46s
apple / screenshots (push) Successful in 18m26s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 1m8s
ci / bench (push) Successful in 5m25s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
arch / build-publish (push) Successful in 11m11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 41s
android / android (push) Successful in 14m53s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6m24s
deb / build-publish (push) Successful in 11m50s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m5s
ci / rust (push) Successful in 23m4s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m54s
docker / deploy-docs (push) Successful in 22s
apple / swift (push) Successful in 4m31s
windows-host / package (push) Successful in 14m46s
apple / screenshots (push) Successful in 18m26s
Every adaptive-bitrate step used to tear the encoder down and rebuild it, opening on a full IDR (a 20-40x frame-size spike, in-flight AU forfeit and an IDR-cooldown anchor) — exactly when the Automatic controller is climbing. Encoder::reconfigure_bitrate(bps) retargets the LIVE encoder instead (default false, so libavcodec/software paths keep the rebuild fallback, which also still owns the bitrate clamping): - Linux + Windows direct NVENC: nvEncReconfigureEncoder (added to the hand-rolled runtime EncodeApi tables) with resetEncoder=0 / forceIDR=0; the same init/config is re-authored via the new shared build_config/ build_init_params with only avg/max bitrate + VBV (PUNKTFUNK_VBV_FRAMES) moved. On-hardware test: 20→60→10 Mbps in place, zero IDRs (RTX 5070 Ti). - Native AMF: TargetBitrate/PeakBitrate/VBVBufferSize are dynamic properties — SetProperty on the live component, no Terminate/re-Init. - Vulkan Video (HEVC + AV1): stage the rate and emit an ENCODE_RATE_CONTROL control command on the next recorded frame (begin keeps declaring the session's current state, as the spec requires). The session glue tries the in-place retarget first and skips the rebuild/ inflight-clear/IDR-cooldown bookkeeping when it succeeds — the reference chain and the wire-index prediction survive, so RFI keeps working across rate steps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4274,47 +4274,67 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
}
|
||||
}
|
||||
// Adaptive bitrate: drain to the NEWEST requested rate (the client's controller may step
|
||||
// several times while we stream) and rebuild the ENCODER ONLY in place — the mode didn't
|
||||
// change, so capture and the virtual output are untouched and the switch costs exactly the
|
||||
// IDR the fresh encoder opens with (the same resync discipline as a mode switch, minus the
|
||||
// pipeline churn). Rates arrive pre-clamped by the control task (`resolve_bitrate_kbps`).
|
||||
// several times while we stream) and retarget the ENCODER ONLY — the mode didn't change,
|
||||
// so capture and the virtual output are untouched. Preferred lever: an IN-PLACE
|
||||
// `reconfigure_bitrate` (Phase 3.2 — NVENC nvEncReconfigureEncoder / AMF dynamic props /
|
||||
// Vulkan RC control), which keeps the encoder, its reference chain and the in-flight AUs,
|
||||
// so the step costs NOTHING on the wire (no IDR, no forfeit — exactly what the Automatic
|
||||
// controller's doubling climb wants). A backend that can't (libavcodec paths) or a driver
|
||||
// rejection falls back to the full rebuild, which costs the IDR the fresh encoder opens
|
||||
// with (the same resync discipline as a mode switch, minus the pipeline churn) and owns
|
||||
// the bitrate clamping. Rates arrive pre-clamped by the control task
|
||||
// (`resolve_bitrate_kbps`).
|
||||
let mut want_kbps = None;
|
||||
while let Ok(k) = bitrate_rx.try_recv() {
|
||||
want_kbps = Some(k);
|
||||
}
|
||||
if let Some(new_kbps) = want_kbps.filter(|&k| k != bitrate_kbps) {
|
||||
// `interval` was built as 1/effective_hz, so the round-trip recovers the integer rate.
|
||||
let hz = interval_hz(interval);
|
||||
match crate::encode::open_video(
|
||||
plan.codec,
|
||||
frame.format,
|
||||
frame.width,
|
||||
frame.height,
|
||||
hz,
|
||||
new_kbps as u64 * 1000,
|
||||
frame.is_cuda(),
|
||||
bit_depth,
|
||||
plan.chroma,
|
||||
) {
|
||||
Ok(new_enc) => {
|
||||
tracing::info!(
|
||||
from_kbps = bitrate_kbps,
|
||||
to_kbps = new_kbps,
|
||||
"encoder rebuilt at new bitrate (adaptive bitrate)"
|
||||
);
|
||||
enc = new_enc;
|
||||
bitrate_kbps = new_kbps;
|
||||
live_bitrate.store(new_kbps, Ordering::Relaxed);
|
||||
// The owed AUs died with the old encoder — same bookkeeping as a mode-switch
|
||||
// rebuild; the fresh encoder opens on an IDR, so anchor the IDR cooldown too.
|
||||
inflight.clear();
|
||||
last_au_at = std::time::Instant::now();
|
||||
encoder_resets = 0;
|
||||
last_forced_idr = Some(std::time::Instant::now());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(error = %format!("{e:#}"), to_kbps = new_kbps,
|
||||
"bitrate-change encoder rebuild failed — keeping the current rate");
|
||||
if enc.reconfigure_bitrate(new_kbps as u64 * 1000) {
|
||||
tracing::info!(
|
||||
from_kbps = bitrate_kbps,
|
||||
to_kbps = new_kbps,
|
||||
"encoder bitrate reconfigured in place (adaptive bitrate — no IDR)"
|
||||
);
|
||||
bitrate_kbps = new_kbps;
|
||||
live_bitrate.store(new_kbps, Ordering::Relaxed);
|
||||
// Same encoder, same stream: the in-flight AUs and the wire-index prediction
|
||||
// stay valid — no inflight forfeit, no IDR-cooldown anchor.
|
||||
} else {
|
||||
// `interval` was built as 1/effective_hz, so the round-trip recovers the integer
|
||||
// rate.
|
||||
let hz = interval_hz(interval);
|
||||
match crate::encode::open_video(
|
||||
plan.codec,
|
||||
frame.format,
|
||||
frame.width,
|
||||
frame.height,
|
||||
hz,
|
||||
new_kbps as u64 * 1000,
|
||||
frame.is_cuda(),
|
||||
bit_depth,
|
||||
plan.chroma,
|
||||
) {
|
||||
Ok(new_enc) => {
|
||||
tracing::info!(
|
||||
from_kbps = bitrate_kbps,
|
||||
to_kbps = new_kbps,
|
||||
"encoder rebuilt at new bitrate (adaptive bitrate)"
|
||||
);
|
||||
enc = new_enc;
|
||||
bitrate_kbps = new_kbps;
|
||||
live_bitrate.store(new_kbps, Ordering::Relaxed);
|
||||
// The owed AUs died with the old encoder — same bookkeeping as a
|
||||
// mode-switch rebuild; the fresh encoder opens on an IDR, so anchor the
|
||||
// IDR cooldown too.
|
||||
inflight.clear();
|
||||
last_au_at = std::time::Instant::now();
|
||||
encoder_resets = 0;
|
||||
last_forced_idr = Some(std::time::Instant::now());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(error = %format!("{e:#}"), to_kbps = new_kbps,
|
||||
"bitrate-change encoder rebuild failed — keeping the current rate");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user