feat: implement M1 teleport protocol sender
Stream a synthetic test pattern and silent PCM audio over the OBS Teleport protocol: - protocol: wire format (Header/ImageHeader/WaveHeader), BT.709 full range colour matrix, JPEG encode via turbojpeg cgo, WAVE packet builder - output: TCP sender with per-connection buffered channels and drop-on-overflow - discovery: multicast announce via peerdiscovery - cmd: teleportfling CLI with flags, test-pattern frame generator Verified end-to-end: OBS discovers and renders the stream with correct colours and motion.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestWriteReadVideoPacket verifies the full video packet round-trip through
|
||||
// WritePacket/ReadPacket, including the little-endian float32s in the image
|
||||
// header and the exact byte layout (Header + ImageHeader + payload).
|
||||
func TestWriteReadVideoPacket(t *testing.T) {
|
||||
img := DefaultBT709Full()
|
||||
img.ColorMatrix[0] = 0.12345
|
||||
img.ColorRangeMax[2] = 0.9999
|
||||
|
||||
h := Header{Type: VideoType, Timestamp: 1_700_000_000, Size: 5}
|
||||
wire, err := WritePacket(h, &img, nil, []byte("hello"))
|
||||
if err != nil {
|
||||
t.Fatalf("WritePacket: %v", err)
|
||||
}
|
||||
|
||||
gotH, gotImg, gotWave, payload, err := ReadPacket(bytes.NewReader(wire))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadPacket: %v", err)
|
||||
}
|
||||
if gotH != h {
|
||||
t.Errorf("header mismatch: got %+v want %+v", gotH, h)
|
||||
}
|
||||
if gotImg == nil || gotWave != nil {
|
||||
t.Fatalf("expected image header and no wave header")
|
||||
}
|
||||
if *gotImg != img {
|
||||
t.Errorf("image header mismatch:\n got %+v\nwant %+v", *gotImg, img)
|
||||
}
|
||||
if string(payload) != "hello" {
|
||||
t.Errorf("payload mismatch: got %q", payload)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVideoPacketWireSize locks the on-wire size of a video packet to the
|
||||
// reference layout: 16-byte Header + 80-byte ImageHeader + payload.
|
||||
func TestVideoPacketWireSize(t *testing.T) {
|
||||
const payload = 10
|
||||
ih := DefaultBT709Full()
|
||||
h := Header{Type: VideoType, Size: payload}
|
||||
wire, err := WritePacket(h, &ih, nil, make([]byte, payload))
|
||||
if err != nil {
|
||||
t.Fatalf("WritePacket: %v", err)
|
||||
}
|
||||
if len(wire) != headerSize+imageHeaderSize+payload {
|
||||
t.Errorf("wire size = %d, want %d", len(wire), headerSize+imageHeaderSize+payload)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWriteReadAudioPacket round-trips a WAVE packet and checks that the
|
||||
// WaveHeader fields arrive intact.
|
||||
func TestWriteReadAudioPacket(t *testing.T) {
|
||||
pcm := make([]byte, 480*2*4) // 480 frames, 2 ch, 4 bytes float
|
||||
h := Header{Type: AudioType, Timestamp: 42, Size: int32(len(pcm))}
|
||||
w := WaveHeader{Format: AudioFormatF32, SampleRate: 48000, Speakers: 2, Frames: 480}
|
||||
|
||||
wire, err := WritePacket(h, nil, &w, pcm)
|
||||
if err != nil {
|
||||
t.Fatalf("WritePacket: %v", err)
|
||||
}
|
||||
if len(wire) != headerSize+waveHeaderSize+len(pcm) {
|
||||
t.Errorf("wire size = %d, want %d", len(wire), headerSize+waveHeaderSize+len(pcm))
|
||||
}
|
||||
|
||||
_, gotImg, gotWave, payload, err := ReadPacket(bytes.NewReader(wire))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadPacket: %v", err)
|
||||
}
|
||||
if gotWave == nil || gotImg != nil {
|
||||
t.Fatalf("expected wave header and no image header")
|
||||
}
|
||||
if *gotWave != w {
|
||||
t.Errorf("wave header mismatch: got %+v want %+v", *gotWave, w)
|
||||
}
|
||||
if !bytes.Equal(payload, pcm) {
|
||||
t.Errorf("pcm payload mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildWavePacket checks the convenience builder enforces PCM length
|
||||
// vs. the declared format/speakers/frames.
|
||||
func TestBuildWavePacket(t *testing.T) {
|
||||
good := make([]byte, 480*2*4)
|
||||
if _, err := BuildWavePacket(1, AudioFormatF32, 48000, 2, 480, good); err != nil {
|
||||
t.Errorf("valid packet rejected: %v", err)
|
||||
}
|
||||
|
||||
if _, err := BuildWavePacket(1, AudioFormatF32, 48000, 2, 480, good[:len(good)-1]); err == nil {
|
||||
t.Error("truncated pcm accepted")
|
||||
}
|
||||
|
||||
u8 := make([]byte, 480) // 1 ch, 480 frames, 1 byte
|
||||
if _, err := BuildWavePacket(1, AudioFormatU8, 48000, 1, 480, u8); err != nil {
|
||||
t.Errorf("u8 packet rejected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnsupportedPacketType ensures ReadPacket rejects unknown markers by
|
||||
// feeding it a raw 16-byte header with a non-"JPEG"/"WAVE" type.
|
||||
func TestUnsupportedPacketType(t *testing.T) {
|
||||
var wire [headerSize]byte
|
||||
wire[0] = 'Z'
|
||||
wire[1] = 'Z'
|
||||
wire[2] = 'Z'
|
||||
wire[3] = 'Z'
|
||||
|
||||
if _, _, _, _, err := ReadPacket(bytes.NewReader(wire[:])); err == nil {
|
||||
t.Error("unknown packet type accepted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user