Files
TeleportFling/internal/protocol/wave.go
T
petere 10fa72b228 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.
2026-09-18 18:36:25 +01:00

81 lines
2.0 KiB
Go

// Package protocol implements the obs-teleport wire format.
//
// This file builds the audio ("WAVE") side of the protocol: Header +
// WaveHeader + raw interleaved PCM, byte-for-byte compatible with the
// reference implementation (GPL-2.0).
package protocol
// BuildWavePacket assembles a complete ""WAVE"" wire packet:
// Header + WaveHeader + interleaved PCM payload.
//
// pcm is already interleaved (L,R,L,R,… for stereo). format must be one of
// the AudioFormat* constants. frames is the PCM frame count (samples per
// channel). The returned slice is a freshly allocated buffer safe to hand to
// the network.
func BuildWavePacket(timestamp uint64, format int32, sampleRate, speakers, frames int32, pcm []byte) ([]byte, error) {
expected := int64(speakers) * int64(frames) * int64(bytesPerSample(format))
if int64(len(pcm)) != expected {
return nil, errBadPCMLength{got: len(pcm), want: int(expected)}
}
h := Header{
Type: AudioType,
Timestamp: timestamp,
Size: int32(len(pcm)),
}
wave := WaveHeader{
Format: format,
SampleRate: sampleRate,
Speakers: speakers,
Frames: frames,
}
return WritePacket(h, nil, &wave, pcm)
}
// bytesPerSample maps an AudioFormat* value to its size in bytes.
func bytesPerSample(format int32) int {
switch format {
case AudioFormatU8:
return 1
case AudioFormatS16:
return 2
case AudioFormatS32, AudioFormatF32:
return 4
default:
return 0
}
}
// errBadPCMLength is returned when pcm length does not match the declared
// format/speakers/frames.
type errBadPCMLength struct{ got, want int }
func (e errBadPCMLength) Error() string {
return "protocol: pcm length mismatch" +
": got " + itoa(e.got) + " bytes, want " + itoa(e.want)
}
// itoa is a tiny local int→string helper to avoid importing strconv in the
// hot path callers.
func itoa(v int) string {
if v == 0 {
return "0"
}
neg := v < 0
if neg {
v = -v
}
var buf [20]byte
i := len(buf)
for v > 0 {
i--
buf[i] = byte('0' + v%10)
v /= 10
}
if neg {
i--
buf[i] = '-'
}
return string(buf[i:])
}