Skip to content

Commit 17c92f7

Browse files
Gate late sinks on live keyframes
Ultraworked with [omo](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
1 parent 48fcdba commit 17c92f7

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

internal/airplay/capture_broadcast.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ type BroadcastSink struct {
9696
maxFrameQueueDuration time.Duration
9797
backpressure bool
9898
blockedProducers int // number waiting for queue handoff; guarded by mu
99+
awaitingRandomAccess bool
99100

100101
inputClosed bool // the source ended; drain queue, then return EOF
101102
closed bool // explicitly removed; discard queue and return EOF
@@ -166,6 +167,7 @@ func (bc *BroadcastCapture) AddSink() *BroadcastSink {
166167
s.primer = bc.primer
167168
if len(s.primer.AnnexB) > 0 {
168169
s.primer.PTS = bc.now()
170+
s.awaitingRandomAccess = true
169171
}
170172
bc.sinks = append(bc.sinks, s)
171173
bc.mu.Unlock()
@@ -195,6 +197,7 @@ func (bc *BroadcastCapture) AddBackpressuredSink() (*BroadcastSink, error) {
195197
s.primer = bc.primer
196198
if len(s.primer.AnnexB) > 0 {
197199
s.primer.PTS = bc.now()
200+
s.awaitingRandomAccess = true
198201
}
199202
bc.sinks = append(bc.sinks, s)
200203
bc.mu.Unlock()
@@ -343,6 +346,25 @@ func isDecoderPrimer(annexB []byte) bool {
343346
return h264SPS && h264PPS && h264IDR || hevcVPS && hevcSPS && hevcPPS && hevcIRAP
344347
}
345348

349+
func isRandomAccessUnit(annexB []byte) bool {
350+
for _, nal := range splitAnnexBAccessUnit(annexB) {
351+
raw := stripStartCode(nal)
352+
if len(raw) == 0 {
353+
continue
354+
}
355+
if raw[0]&0x1f == 5 {
356+
return true
357+
}
358+
if len(raw) >= 2 {
359+
nalType := hevcNALType(raw)
360+
if nalType >= 16 && nalType <= 23 {
361+
return true
362+
}
363+
}
364+
}
365+
return false
366+
}
367+
346368
// finish stops accepting sinks, lets existing sinks drain, and only then
347369
// publishes BroadcastCapture completion.
348370
func (bc *BroadcastCapture) finish(err error) {
@@ -456,6 +478,12 @@ func (s *BroadcastSink) enqueueFrame(frame VideoAccessUnit) error {
456478
if s.closed || s.inputClosed {
457479
return io.ErrClosedPipe
458480
}
481+
if s.awaitingRandomAccess {
482+
if !isRandomAccessUnit(frame.AnnexB) {
483+
return nil
484+
}
485+
s.awaitingRandomAccess = false
486+
}
459487
if len(s.frameQueue) == 0 && len(frame.AnnexB) > s.maxQueuedBytes {
460488
return errBroadcastSinkBacklog
461489
}

internal/airplay/capture_broadcast_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,20 @@ func TestBroadcastCaptureReplaysDecoderPrimerToLateSink(t *testing.T) {
181181
PTS: boundary.PTS.Add(time.Second / 30),
182182
}
183183
frames <- live
184+
<-reads
185+
randomAccess := VideoAccessUnit{
186+
AnnexB: []byte{0, 0, 0, 1, 0x65, 0x80},
187+
PTS: live.PTS.Add(time.Second / 30),
188+
}
189+
frames <- randomAccess
184190
close(frames)
185191

186192
next, err := sink.ReadVideoAccessUnit()
187193
if err != nil {
188194
t.Fatalf("read live frame after decoder primer: %v", err)
189195
}
190-
if !bytes.Equal(next.AnnexB, live.AnnexB) || !next.PTS.Equal(live.PTS) {
191-
t.Fatalf("live frame after primer = {%x %v}, want {%x %v}", next.AnnexB, next.PTS, live.AnnexB, live.PTS)
196+
if !bytes.Equal(next.AnnexB, randomAccess.AnnexB) || !next.PTS.Equal(randomAccess.PTS) {
197+
t.Fatalf("live frame after primer = {%x %v}, want random access {%x %v}", next.AnnexB, next.PTS, randomAccess.AnnexB, randomAccess.PTS)
192198
}
193199
if err := <-runDone; !errors.Is(err, io.EOF) {
194200
t.Fatalf("broadcast run = %v, want EOF", err)

0 commit comments

Comments
 (0)