fix(stream): derive H.264 level from frame macroblocks, not height
Anamorphic 2.39:1 scaled to 1080 height = ~2586x1080 = 11016 MBs, busting level 4.1's 8192-MB MaxFS -> nvenc "InitializeEncoder failed: Invalid Level" (libx264: "frame MB size > level limit") -> 0 segments, session stalls. Most 4K rips are 2.39:1, so HLS playback was silently broken for them. H264LevelForFrame(w,h) derives the level from the real macroblock count (max of MB-tier and height-tier). hls.go computes output width and uses it. 16:9 unchanged; anamorphic bumps to 5.0 when needed. Discovered + verified during the trickplay smoke.
This commit is contained in:
parent
9c995fc4dd
commit
8207d1d2a9
4 changed files with 123 additions and 14 deletions
|
|
@ -22,6 +22,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
|
@ -1283,11 +1284,14 @@ func buildHLSFFmpegArgsAt(cfg HLSSessionConfig, probe *StreamProbe, tmpDir strin
|
|||
// per session start, polluting logs even though encode succeeds.
|
||||
args = append(args, "-vaapi_device", "/dev/dri/renderD128")
|
||||
}
|
||||
// Derive H.264 level from the actual output height. A fixed "4.0" caps the
|
||||
// encoder at 1080p — anything taller (1440p, 4K source on quality=original)
|
||||
// fails libx264 with "frame MB size > level limit" and emits unplayable
|
||||
// segments. The output height matches qcap.MaxHeight when the source is
|
||||
// downscaled, otherwise probe.Height (already populated by ffprobe).
|
||||
// Derive H.264 level from the actual output FRAME (width × height), not just
|
||||
// height. A fixed "4.0" caps the encoder at 1080p; deriving by height alone
|
||||
// still under-levels anamorphic content — a 2.39:1 source scaled to 1080
|
||||
// height is ~2586×1080 = 11016 MBs, busting level 4.1's 8192-MB cap, which
|
||||
// fails the encode ("Invalid Level" on nvenc, "frame MB size > level limit"
|
||||
// on libx264) and stalls the session. The output height matches qcap.MaxHeight
|
||||
// when the source is downscaled, otherwise probe.Height; the output width is
|
||||
// the source width scaled by the same factor (the filter chain preserves AR).
|
||||
qcap := resolveQualityCap(cfg.Quality)
|
||||
outputHeight := qcap.MaxHeight
|
||||
if outputHeight == 0 {
|
||||
|
|
@ -1296,7 +1300,11 @@ func buildHLSFFmpegArgsAt(cfg HLSSessionConfig, probe *StreamProbe, tmpDir strin
|
|||
if outputHeight == 0 || (probe.Height > 0 && probe.Height < outputHeight) {
|
||||
outputHeight = probe.Height
|
||||
}
|
||||
args = append(args, "-profile:v", "main", "-level:v", H264LevelForHeight(outputHeight))
|
||||
outputWidth := probe.Width
|
||||
if probe.Height > 0 && outputHeight != probe.Height {
|
||||
outputWidth = int(math.Round(float64(probe.Width) * float64(outputHeight) / float64(probe.Height)))
|
||||
}
|
||||
args = append(args, "-profile:v", "main", "-level:v", H264LevelForFrame(outputWidth, outputHeight))
|
||||
|
||||
// Bitrate must match the level libx264 actually picks for outputHeight,
|
||||
// not the qcap target for the user's requested label. If a user asks for
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue