Merge branch 'fix/issue-2-configurable-map' into m2-webrtc-core-integration

This commit is contained in:
Zac Gaetano 2026-05-03 12:25:15 +00:00
commit 73d4049893
5 changed files with 104 additions and 10 deletions

View file

@ -32,7 +32,16 @@ func BuildArgs(cfg appcfg.ConfigWebRTC, videoPort int) []string {
acopy = []string{"-c:a", "libopus", "-b:a", "96k"}
}
args := []string{"-map", "0:v:0"}
videoMap := cfg.VideoMap
if videoMap == "" {
videoMap = "0:v:0"
}
audioMap := cfg.AudioMap
if audioMap == "" {
audioMap = "0:a:0"
}
args := []string{"-map", videoMap}
args = append(args, vcopy...)
args = append(args,
"-payload_type", fmt.Sprint(cfg.VideoPT),
@ -40,7 +49,7 @@ func BuildArgs(cfg appcfg.ConfigWebRTC, videoPort int) []string {
fmt.Sprintf("udp://127.0.0.1:%d?pkt_size=1316", videoPort),
)
args = append(args, "-map", "0:a:0")
args = append(args, "-map", audioMap)
args = append(args, acopy...)
args = append(args,
"-payload_type", fmt.Sprint(cfg.AudioPT),

View file

@ -87,3 +87,46 @@ func any(haystack []string, prefix string) bool {
}
return false
}
// TestBuildArgs_DefaultMaps confirms 0:v:0 / 0:a:0 are emitted when
// VideoMap / AudioMap are empty (regression on the fix for issue #2 —
// the prior version had these as hardcoded literals; if VideoMap is
// ever empty unexpectedly, BuildArgs must still produce a working
// command line).
func TestBuildArgs_DefaultMaps(t *testing.T) {
cfg := appcfg.ConfigWebRTC{Enabled: true, VideoPT: 102, AudioPT: 111}
got := BuildArgs(cfg, 50000)
if !contains(got, "-map", "0:v:0") {
t.Fatalf("expected default video map 0:v:0, got %v", got)
}
if !contains(got, "-map", "0:a:0") {
t.Fatalf("expected default audio map 0:a:0, got %v", got)
}
}
// TestBuildArgs_CustomMaps drives the issue-#2 fix: when the user
// configures a multi-input pipeline (audio on input #1, etc.), the
// emitted -map values must follow the user's choice rather than the
// "0:v:0"/"0:a:0" assumption.
func TestBuildArgs_CustomMaps(t *testing.T) {
cfg := appcfg.ConfigWebRTC{
Enabled: true,
VideoPT: 102,
AudioPT: 111,
VideoMap: "0:v:1",
AudioMap: "1:a:0",
}
got := BuildArgs(cfg, 50000)
if !contains(got, "-map", "0:v:1") {
t.Fatalf("expected custom video map 0:v:1, got %v", got)
}
if !contains(got, "-map", "1:a:0") {
t.Fatalf("expected custom audio map 1:a:0, got %v", got)
}
// The default literals should NOT appear when overridden.
for _, opt := range got {
if opt == "0:v:0" || opt == "0:a:0" {
t.Errorf("expected no default maps in output, found %q in %v", opt, got)
}
}
}

View file

@ -44,10 +44,12 @@ type ProcessConfigLimits struct {
// ProcessConfig represents the configuration of an ffmpeg process
type ProcessConfigWebRTC struct {
Enabled bool `json:"enabled"`
VideoPT uint8 `json:"video_pt,omitempty"`
AudioPT uint8 `json:"audio_pt,omitempty"`
ForceTranscode bool `json:"force_transcode,omitempty"`
Enabled bool `json:"enabled"`
VideoPT uint8 `json:"video_pt,omitempty"`
AudioPT uint8 `json:"audio_pt,omitempty"`
ForceTranscode bool `json:"force_transcode,omitempty"`
VideoMap string `json:"video_map,omitempty"`
AudioMap string `json:"audio_map,omitempty"`
}
type ProcessConfig struct {
@ -83,6 +85,8 @@ func (cfg *ProcessConfig) Marshal() *app.Config {
VideoPT: cfg.WebRTC.VideoPT,
AudioPT: cfg.WebRTC.AudioPT,
ForceTranscode: cfg.WebRTC.ForceTranscode,
VideoMap: cfg.WebRTC.VideoMap,
AudioMap: cfg.WebRTC.AudioMap,
},
}
@ -168,6 +172,8 @@ func (cfg *ProcessConfig) Unmarshal(c *app.Config) {
cfg.WebRTC.VideoPT = c.WebRTC.VideoPT
cfg.WebRTC.AudioPT = c.WebRTC.AudioPT
cfg.WebRTC.ForceTranscode = c.WebRTC.ForceTranscode
cfg.WebRTC.VideoMap = c.WebRTC.VideoMap
cfg.WebRTC.AudioMap = c.WebRTC.AudioMap
cfg.Options = make([]string, len(c.Options))
copy(cfg.Options, c.Options)

View file

@ -79,3 +79,31 @@ func TestProcessConfigWebRTCDefaults(t *testing.T) {
t.Fatalf("default should be disabled, got %+v", cfg.WebRTC)
}
}
// TestProcessConfigWebRTCMapsRoundtrip extends the WebRTC DTO
// roundtrip with the issue-#2 VideoMap/AudioMap fields so the
// regression doesn't repeat: a multi-input pipeline that sets
// `audio_map: "1:a:0"` must reach the restream config layer
// unchanged.
func TestProcessConfigWebRTCMapsRoundtrip(t *testing.T) {
body := []byte(`{
"id":"p","input":[{"id":"i","address":"x"}],"output":[{"id":"o","address":"-"}],
"webrtc":{"enabled":true,"video_map":"0:v:1","audio_map":"1:a:0"}
}`)
var dto ProcessConfig
if err := json.Unmarshal(body, &dto); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if dto.WebRTC.VideoMap != "0:v:1" || dto.WebRTC.AudioMap != "1:a:0" {
t.Fatalf("DTO maps lost: %+v", dto.WebRTC)
}
cfg := dto.Marshal()
if cfg.WebRTC.VideoMap != "0:v:1" || cfg.WebRTC.AudioMap != "1:a:0" {
t.Fatalf("app.Config maps lost: %+v", cfg.WebRTC)
}
var back ProcessConfig
back.Unmarshal(cfg)
if back.WebRTC.VideoMap != "0:v:1" || back.WebRTC.AudioMap != "1:a:0" {
t.Fatalf("Unmarshal lost maps: %+v", back.WebRTC)
}
}

View file

@ -25,10 +25,18 @@ type ConfigIO struct {
// RTP to a loopback UDP port the subsystem allocates. The subsystem reads
// that RTP and fans it out to WHEP subscribers.
type ConfigWebRTC struct {
Enabled bool `json:"enabled"`
VideoPT uint8 `json:"video_pt"`
AudioPT uint8 `json:"audio_pt"`
ForceTranscode bool `json:"force_transcode"`
Enabled bool `json:"enabled"`
VideoPT uint8 `json:"video_pt"`
AudioPT uint8 `json:"audio_pt"`
ForceTranscode bool `json:"force_transcode"`
// VideoMap / AudioMap select which input stream the WebRTC RTP
// legs draw from. Defaults are "0:v:0" and "0:a:0" — correct for
// any RTMP / SRT publisher (single input, both A and V on input
// 0). For multi-input pipelines (lavfi test sources, SDI capture
// fed alongside file audio, etc.) the operator can override.
VideoMap string `json:"video_map,omitempty"`
AudioMap string `json:"audio_map,omitempty"`
}
// Clone returns a deep copy of the WebRTC config (currently a value copy;