-
Notifications
You must be signed in to change notification settings - Fork 201
ai/live: Gateway native WHEP server #3691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package media | ||
|
||
const ( | ||
// MPEG‑TS PTS is 33 bits wide; it wraps at 2^33. | ||
mpegtsMaxPts = 1 << 33 | ||
halfPts = mpegtsMaxPts / 2 | ||
) | ||
|
||
// RTPTimestamper keeps track of wrap‑arounds in a 33‑bit PTS stream. | ||
type RTPTimestamper struct { | ||
lastRaw int64 // last raw PTS seen | ||
wrapCount int64 // how many times we've wrapped | ||
initialized bool // false until first Unwrap call | ||
} | ||
|
||
// NewRTPTimestamper creates a fresh unwrapper. | ||
func NewRTPTimestamper() *RTPTimestamper { | ||
return &RTPTimestamper{} | ||
} | ||
|
||
// returns a monotonic 32‑bit timeline by accounting for 33-bit wrap‑arounds. | ||
func (u *RTPTimestamper) ToTS(rawPts int64) uint32 { | ||
if u.initialized { | ||
delta := rawPts - u.lastRaw | ||
if delta < -halfPts { | ||
// wrapped forward | ||
u.wrapCount++ | ||
} else if delta > halfPts { | ||
// wrapped backward (unlikely in normal streams) | ||
u.wrapCount-- | ||
} | ||
} else { | ||
u.initialized = true | ||
} | ||
u.lastRaw = rawPts | ||
return uint32(rawPts + u.wrapCount*mpegtsMaxPts) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't this overflow as soon as https://go.dev/play/p/dVuB5eJTbbQ
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it would overflow a uint32 by wrapping back to zero. The main problem is there would be two discontinuous overflows: first when it overflows 32 bits during the cast to RTP timestamp, then again when the mpegts timestamp overflows 33 bits. So this bit of plumbing is to make those timestamps consistent. The unit tests aren't the best at describing this intended behavior right now, so I'll add some comments here and rework the tests a bit so things are clearer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I assume we want it to overflow and "mod" to a value that fits into a uint32, instead of a panic. How come this doesn't panic like the playground snippet I shared? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure, probably some compile-time constant propagation + bounds checking. With real variables this doesn't happen, maybe because all the intermediate types are int64 until the last minute. The uint32 conversion does mod the value during overflows. Here is the conversion code with a simplified set of test cases that shows the overflow behavior: https://go.dev/play/p/_6NNdC7LbSN BTW had GPT math this out and seems like the explicit wraparound tracking isn't actually necessary since 2^33 - 2^32 = 2^32 ... so maybe we can just do a straight cast from the int64 timestamp to uint32 unless we want to also track the actual timestamps for some reason, idk |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package media | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
const ( | ||
mod32 = 1 << 32 // 4 294 967 296 | ||
) | ||
|
||
func TestRTPTimestamper(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
inputs []int64 | ||
wantRtp []uint32 | ||
}{ | ||
{ | ||
name: "Initialization", | ||
inputs: []int64{0}, | ||
wantRtp: []uint32{0}, | ||
}, | ||
{ | ||
name: "Monotonic increase", | ||
inputs: []int64{0, 1, 2, 1000}, | ||
wantRtp: []uint32{0, 1, 2, 1000}, | ||
}, | ||
{ | ||
name: "Small backwards jump (no wrap)", | ||
inputs: []int64{1000, 990}, | ||
wantRtp: []uint32{1000, 990}, | ||
}, | ||
{ | ||
name: "Forward 33-bit PTS wrap", | ||
inputs: []int64{mpegtsMaxPts - 2, 2}, | ||
wantRtp: []uint32{ | ||
uint32((mpegtsMaxPts - 2) % mod32), | ||
uint32((2 + mpegtsMaxPts) % mod32), | ||
}, | ||
}, | ||
{ | ||
name: "Backward 33-bit PTS wrap", | ||
inputs: []int64{5, mpegtsMaxPts - 3}, | ||
wantRtp: []uint32{ | ||
5, | ||
uint32(mod32 - 3), | ||
}, | ||
}, | ||
{ | ||
name: "Crossing 32-bit boundary (wrap in RTP)", | ||
inputs: []int64{halfPts - 1, halfPts, halfPts + 1}, | ||
wantRtp: []uint32{uint32(halfPts - 1), 0, 1}, | ||
}, | ||
{ | ||
name: "Random jitter near wrap threshold", | ||
inputs: []int64{mpegtsMaxPts - halfPts/2, 2}, | ||
wantRtp: []uint32{ | ||
uint32((mpegtsMaxPts - halfPts/2) % mod32), | ||
2, | ||
}, | ||
}, | ||
{ | ||
name: "Large monotonic advance (< halfPts)", | ||
inputs: []int64{100, halfPts - 1}, | ||
wantRtp: []uint32{100, uint32(halfPts - 1)}, | ||
}, | ||
{ | ||
name: "Multiple successive wraps", | ||
inputs: []int64{mpegtsMaxPts - 1, 2, mpegtsMaxPts - 1, 2}, | ||
wantRtp: []uint32{ | ||
uint32((mpegtsMaxPts - 1) % mod32), | ||
uint32((2 + mpegtsMaxPts) % mod32), | ||
uint32((mpegtsMaxPts - 1) % mod32), | ||
uint32((2 + mpegtsMaxPts) % mod32), | ||
}, | ||
}, | ||
{ | ||
name: "Idempotent Unwrap (same raw twice)", | ||
inputs: []int64{500, 500}, | ||
wantRtp: []uint32{500, 500}, | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
t.Run(tc.name, func(t *testing.T) { | ||
u := NewRTPTimestamper() | ||
gotRtp := make([]uint32, len(tc.inputs)) | ||
for i, raw := range tc.inputs { | ||
ext := u.ToTS(raw) | ||
gotRtp[i] = ext | ||
} | ||
require.Equal(t, tc.wantRtp, gotRtp) | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess this will be already in
main