fix(capture): gate all-intra HEVC on growing-files; normal record uses long-GOP
The hevc_nvenc codec was hardcoded to all-intra (-force_key_frames expr:1), which is ~4x the NVENC load. Applied to every recording it exceeded the L4's realtime budget at 1080p59.94 10-bit -> fc_pipe dropped ~half the frames -> video came out shorter than the (correct) audio -> A/V drift + pitch-up on playback. Now all-intra is used ONLY when growing-files is on (where it's required for the editable head). Normal recordings use efficient long-GOP HEVC (2s GOP, 2 B-frames) which NVENC sustains in realtime with zero drops.
This commit is contained in:
parent
8e5405c3f9
commit
0ea22e1e53
1 changed files with 32 additions and 1 deletions
|
|
@ -134,6 +134,9 @@ const VIDEO_CODECS = {
|
|||
//
|
||||
// -profile:v main10 / p010le: 10-bit 4:2:0 — the closest NVENC HEVC can get
|
||||
// to ProRes 4:2:2 10-bit. If strict 4:2:2 is required, use prores_hq (CPU).
|
||||
// GROWING-file variant: every frame an IDR (all-intra) so a still-growing
|
||||
// file is decodable to its last complete frame. This is HEAVY — only used when
|
||||
// growing-files is on (see hevcNvencArgs()).
|
||||
hevc_nvenc: {
|
||||
args: ['-c:v', 'hevc_nvenc', '-preset', 'p4', '-rc', 'vbr', '-bf', '0', '-forced-idr', '1', '-g', '600', '-force_key_frames', 'expr:1', '-profile:v', 'main10'],
|
||||
bitrateControl: true,
|
||||
|
|
@ -141,6 +144,27 @@ const VIDEO_CODECS = {
|
|||
},
|
||||
};
|
||||
|
||||
// HEVC/NVENC encode args, GOP structure chosen by mode.
|
||||
// growing=false (normal record): efficient long-GOP (2s @ fps) HEVC. NVENC
|
||||
// easily sustains 1080p59.94 10-bit here, so no frame drops → audio/video
|
||||
// lengths stay locked. This is the DEFAULT for recorders.
|
||||
// growing=true (edit-while-record): ALL-INTRA (every frame an IDR) so the
|
||||
// growing file is decodable to its last written frame — the requirement for
|
||||
// Premiere's growing-file refresh. Much heavier, only used when needed.
|
||||
// `force_key_frames expr:1` (all-intra) is the ~4× compute path that was
|
||||
// crippling realtime when applied to every recording; gating it on `growing`
|
||||
// is the fix for the dropped-frame A/V drift.
|
||||
function hevcNvencArgs(framerate, growing) {
|
||||
const base = ['-c:v', 'hevc_nvenc', '-preset', 'p4', '-rc', 'vbr', '-profile:v', 'main10'];
|
||||
if (growing) {
|
||||
return [...base, '-bf', '0', '-forced-idr', '1', '-g', '600', '-force_key_frames', 'expr:1'];
|
||||
}
|
||||
// Normal long-GOP: ~2s keyframe interval, 2 B-frames. Realtime-friendly.
|
||||
const fps = Number.parseFloat(framerate) || 60;
|
||||
const gop = Math.max(2, Math.round(fps * 2));
|
||||
return [...base, '-bf', '2', '-g', String(gop)];
|
||||
}
|
||||
|
||||
// nvenc codecs available in the capture image. Used both to validate the master
|
||||
// codec and (issue #164) as the GPU-availability signal for the HLS preview.
|
||||
const NVENC_CODECS = new Set(['h264_nvenc', 'hevc_nvenc']);
|
||||
|
|
@ -479,7 +503,14 @@ function buildEncodeArgs({
|
|||
const args = [];
|
||||
if (isNetwork) args.push('-map', '0:v:0?', '-map', '0:a:0?');
|
||||
|
||||
args.push(...v.args);
|
||||
// hevc_nvenc GOP structure is mode-dependent: all-intra only for growing
|
||||
// files, efficient long-GOP for normal record (so NVENC stays realtime and
|
||||
// doesn't drop frames). All other codecs use their static arg set.
|
||||
if (codec === 'hevc_nvenc') {
|
||||
args.push(...hevcNvencArgs(framerate, growing));
|
||||
} else {
|
||||
args.push(...v.args);
|
||||
}
|
||||
if (v.pixFmt) args.push('-pix_fmt', v.pixFmt);
|
||||
if (v.bitrateControl && videoBitrate) args.push('-b:v', videoBitrate);
|
||||
if (framerate && framerate !== 'native') args.push('-r', framerate);
|
||||
|
|
|
|||
Loading…
Reference in a new issue