Introduces the subsystem layer that sits alongside api.API and wires
the M1 core/webrtc primitives into the per-process restream lifecycle.
app/webrtc/subsystem.go:
- Subsystem struct holding the global WebRTC config, core PeerFactory,
per-process stream map, and logger
- New(config.DataWebRTC, logger) constructor
- Enabled(), Hooks(), Close(), lookup() methods
app/webrtc/lifecycle.go:
- onProcessStart: allocates an adjacent UDP port pair, binds two
Pion Sources (video on V, audio on V+1), registers them under the
process id, and returns the two RTP output legs to append to the
FFmpeg command.
- onProcessStop: tears down the pair.
- allocAdjacentPair: retries up to 10 times to find a free (V, V+1)
pair since the kernel's ephemeral picker can hand us an odd port.
- splitRTPLegs: converts BuildArgs' flat []string into two ConfigIO
entries by splitting on the second -map token.
core/webrtc/peer.go + forward.go:
- Adds PeerFactory.CreatePeerFromSources for the M2 two-source
forwarding mode (video and audio on separate UDP ports, no
payload-type sniffing). Leaves CreatePeer intact for the M1 PoC.
- Adds forwardRTPSplit companion goroutine.
config/data.go:
- Promote anonymous WebRTC struct to named type DataWebRTC so
app/webrtc can accept it by value.
60 lines
2 KiB
Go
60 lines
2 KiB
Go
package webrtc
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
appcfg "github.com/datarhei/core/v16/restream/app"
|
|
)
|
|
|
|
// TestSplitRTPLegs_TwoLegs feeds the real BuildArgs output through
|
|
// the splitter and checks both legs come out with the correct shape.
|
|
func TestSplitRTPLegs_TwoLegs(t *testing.T) {
|
|
args := BuildArgs(appcfg.ConfigWebRTC{Enabled: true, VideoPT: 102, AudioPT: 111}, 49200)
|
|
|
|
legs := splitRTPLegs(args)
|
|
if len(legs) != 2 {
|
|
t.Fatalf("expected 2 legs, got %d: %+v", len(legs), legs)
|
|
}
|
|
|
|
video := legs[0]
|
|
audio := legs[1]
|
|
|
|
// Leg 0 is video: address ends with :49200
|
|
if !strings.HasSuffix(video.Address, ":49200?pkt_size=1316") {
|
|
t.Fatalf("video Address unexpected: %q", video.Address)
|
|
}
|
|
// Leg 1 is audio: address ends with :49201
|
|
if !strings.HasSuffix(audio.Address, ":49201?pkt_size=1316") {
|
|
t.Fatalf("audio Address unexpected: %q", audio.Address)
|
|
}
|
|
|
|
// Each leg's options start with -map, end with -f rtp.
|
|
if len(video.Options) < 2 || video.Options[0] != "-map" {
|
|
t.Fatalf("video leg should start with -map, got %v", video.Options)
|
|
}
|
|
if video.Options[len(video.Options)-2] != "-f" || video.Options[len(video.Options)-1] != "rtp" {
|
|
t.Fatalf("video leg should end with -f rtp, got %v", video.Options)
|
|
}
|
|
if len(audio.Options) < 2 || audio.Options[0] != "-map" {
|
|
t.Fatalf("audio leg should start with -map, got %v", audio.Options)
|
|
}
|
|
|
|
// Neither leg's Options should contain the address itself.
|
|
for _, opt := range video.Options {
|
|
if strings.HasPrefix(opt, "udp://") {
|
|
t.Fatalf("video Options must not contain udp:// address: %v", video.Options)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSplitRTPLegs_FallbackOnUnexpectedShape ensures we don't panic
|
|
// or drop data if BuildArgs ever changes shape — the splitter returns
|
|
// a single leg wrapping everything.
|
|
func TestSplitRTPLegs_FallbackOnUnexpectedShape(t *testing.T) {
|
|
// Single -map: shouldn't happen, but don't panic.
|
|
legs := splitRTPLegs([]string{"-map", "0:v:0", "udp://1.2.3.4:5000"})
|
|
if len(legs) != 1 {
|
|
t.Fatalf("expected single fallback leg, got %d", len(legs))
|
|
}
|
|
}
|