fix: normalize desktop capture dimensions

This commit is contained in:
Haitao Pan 2026-06-07 22:30:12 +08:00
parent 0f096a588d
commit 0a0d04f3a7
2 changed files with 49 additions and 15 deletions

View File

@ -20,6 +20,32 @@ func normalizeRTPPort(port int) int {
return port return port
} }
func normalizeVideoDimension(value, fallback int) int {
if value <= 0 {
value = fallback
}
if value%2 != 0 {
value--
}
if value < 2 {
return fallback
}
return value
}
func normalizePipelineConfig(cfg PipelineConfig) PipelineConfig {
cfg.Port = normalizeRTPPort(cfg.Port)
cfg.Width = normalizeVideoDimension(cfg.Width, 1280)
cfg.Height = normalizeVideoDimension(cfg.Height, 720)
if cfg.FPS <= 0 {
cfg.FPS = 30
}
if cfg.Bitrate <= 0 {
cfg.Bitrate = 2000
}
return cfg
}
// PipelineManager manages the screen capture process lifecycle // PipelineManager manages the screen capture process lifecycle
type PipelineManager struct { type PipelineManager struct {
cmd *exec.Cmd cmd *exec.Cmd
@ -58,19 +84,7 @@ func (pm *PipelineManager) Start(cfg PipelineConfig) error {
cfg.Display = ":0.0" cfg.Display = ":0.0"
} }
} }
cfg.Port = normalizeRTPPort(cfg.Port) cfg = normalizePipelineConfig(cfg)
if cfg.Width <= 0 {
cfg.Width = 1280
}
if cfg.Height <= 0 {
cfg.Height = 720
}
if cfg.FPS <= 0 {
cfg.FPS = 30
}
if cfg.Bitrate <= 0 {
cfg.Bitrate = 2000
}
tool, args, err := pm.resolvePipeline(cfg) tool, args, err := pm.resolvePipeline(cfg)
if err != nil { if err != nil {
@ -172,13 +186,18 @@ func (pm *PipelineManager) hasExecutable(name string) bool {
} }
func (pm *PipelineManager) buildGStreamer(cfg PipelineConfig) (string, []string, error) { func (pm *PipelineManager) buildGStreamer(cfg PipelineConfig) (string, []string, error) {
cfg = normalizePipelineConfig(cfg)
var pipelineParts []string var pipelineParts []string
// 1. Capture Source (X11) // 1. Capture Source (X11)
pipelineParts = append(pipelineParts, fmt.Sprintf("ximagesrc display-name=%s", cfg.Display)) pipelineParts = append(pipelineParts, fmt.Sprintf("ximagesrc display-name=%s", cfg.Display))
pipelineParts = append(pipelineParts, "video/x-raw,framerate=30/1") pipelineParts = append(pipelineParts, fmt.Sprintf("video/x-raw,framerate=%d/1", cfg.FPS))
pipelineParts = append(pipelineParts, "videoconvert") pipelineParts = append(pipelineParts, "videoconvert")
pipelineParts = append(pipelineParts, "video/x-raw,format=I420") pipelineParts = append(pipelineParts, "videoscale")
pipelineParts = append(
pipelineParts,
fmt.Sprintf("video/x-raw,format=I420,width=%d,height=%d,framerate=%d/1", cfg.Width, cfg.Height, cfg.FPS),
)
// 2. Encoder // 2. Encoder
encoderStr := "x264enc speed-preset=ultrafast tune=zerolatency bitrate=" + fmt.Sprintf("%d", cfg.Bitrate) + " byte-stream=true key-int-max=30" encoderStr := "x264enc speed-preset=ultrafast tune=zerolatency bitrate=" + fmt.Sprintf("%d", cfg.Bitrate) + " byte-stream=true key-int-max=30"
@ -211,6 +230,7 @@ func (pm *PipelineManager) buildGStreamer(cfg PipelineConfig) (string, []string,
} }
func (pm *PipelineManager) buildFFmpeg(cfg PipelineConfig) (string, []string, error) { func (pm *PipelineManager) buildFFmpeg(cfg PipelineConfig) (string, []string, error) {
cfg = normalizePipelineConfig(cfg)
args := []string{ args := []string{
"-f", "x11grab", "-f", "x11grab",
"-draw_mouse", "1", "-draw_mouse", "1",

View File

@ -23,6 +23,8 @@ func TestBuildGStreamerUsesBrowserFriendlyH264(t *testing.T) {
joined := strings.Join(args, " ") joined := strings.Join(args, " ")
for _, expected := range []string{ for _, expected := range []string{
"video/x-raw,format=I420", "video/x-raw,format=I420",
"width=1280",
"height=720",
"x264enc", "x264enc",
"tune=zerolatency", "tune=zerolatency",
"key-int-max=30", "key-int-max=30",
@ -37,6 +39,18 @@ func TestBuildGStreamerUsesBrowserFriendlyH264(t *testing.T) {
} }
} }
func TestNormalizeVideoDimensionUsesEvenValues(t *testing.T) {
if got := normalizeVideoDimension(1353, 1280); got != 1352 {
t.Fatalf("expected odd dimension to be rounded down to 1352, got %d", got)
}
if got := normalizeVideoDimension(0, 720); got != 720 {
t.Fatalf("expected fallback for zero dimension, got %d", got)
}
if got := normalizeVideoDimension(1, 720); got != 720 {
t.Fatalf("expected fallback for too-small dimension, got %d", got)
}
}
func TestBuildFFmpegUsesBrowserFriendlyH264(t *testing.T) { func TestBuildFFmpegUsesBrowserFriendlyH264(t *testing.T) {
pm := NewPipelineManager() pm := NewPipelineManager()