fix(playout): use CFR rate + frame GOP for uniform HLS segments
CasparCG's FFMPEG consumer ignores -force_key_frames ("Unused option")
because it routes args to the muxer, not the encoder. Revert to the
frame-based GOP (-g 60 -keyint_min 60) but keep the forced CFR rate
(-r 30000/1001): at 29.97fps a 60-frame GOP is exactly 2.0s, so keyframes
and HLS splits land on clean 2s boundaries. CFR is what was missing
originally — with the channel's irregular feed rate, "60 frames" drifted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f28799317d
commit
87d988810f
1 changed files with 11 additions and 8 deletions
|
|
@ -169,11 +169,16 @@ export class PlayoutManager {
|
|||
// reloads forever ("sliding 0.00 / prev-sn na / MISSED") and never appends
|
||||
// a fragment, so the preview stays black.
|
||||
//
|
||||
// Fix: force a constant output frame rate (-r, regenerates uniform PTS) and
|
||||
// TIME-based keyframes every 2s (-force_key_frames) so every segment is a
|
||||
// clean, keyframe-aligned 2.0s chunk. This yields a spec-compliant playlist
|
||||
// (TARGETDURATION 2, stable 8-segment / 16s window) identical in shape to
|
||||
// the capture/VOD HLS the rest of the app already plays.
|
||||
// Fix: force a constant output frame rate (-r 30000/1001 = 29.97 CFR). This
|
||||
// regenerates uniform PTS and — critically — makes the frame-based GOP land
|
||||
// on regular time boundaries: at 29.97 fps a 60-frame GOP (-g 60) is exactly
|
||||
// 2.0s, so every keyframe (and therefore every HLS split at -hls_time 2) is
|
||||
// a clean 2.0s boundary. The original used -g 60 WITHOUT -r, so "60 frames"
|
||||
// varied with the channel's irregular feed rate and segments drifted.
|
||||
//
|
||||
// NOTE: -force_key_frames does NOT work here — CasparCG's FFMPEG consumer
|
||||
// routes args to the muxer, not the encoder, and logs it as "Unused option".
|
||||
// The CFR rate + frame GOP is the combination that actually takes effect.
|
||||
const out = `${HLS_DIR}/index.m3u8`;
|
||||
const args = [
|
||||
`FILE "${out}"`,
|
||||
|
|
@ -182,9 +187,7 @@ export class PlayoutManager {
|
|||
'-hls_list_size 8',
|
||||
'-hls_flags delete_segments+append_list+independent_segments',
|
||||
'-codec:v libx264 -preset:v veryfast -tune:v zerolatency -b:v 800k -maxrate 1M -bufsize 2M',
|
||||
// 29.97 CFR confidence feed: -r forces constant frame rate (fixes the
|
||||
// duration-0 PTS), -force_key_frames pins keyframes to 2s media boundaries.
|
||||
'-r 30000/1001 -force_key_frames expr:gte(t,n_forced*2) -sc_threshold 0',
|
||||
'-r 30000/1001 -g 60 -keyint_min 60 -sc_threshold 0',
|
||||
'-codec:a aac -b:a 96k -ar 48000',
|
||||
'-filter:v format=yuv420p',
|
||||
].join(' ');
|
||||
|
|
|
|||
Loading…
Reference in a new issue