Skip to content

Commit f9580fa

Browse files
Remove unsafe late-sink replay
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 17c92f7 commit f9580fa

2 files changed

Lines changed: 2 additions & 237 deletions

File tree

internal/airplay/capture_broadcast.go

Lines changed: 2 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ type BroadcastCapture struct {
4848
// adjacent PTS values. A leaky upstream queue can legitimately create large
4949
// PTS gaps while only one encoded picture is pending.
5050
frameDuration time.Duration
51-
now func() time.Time
5251
mu sync.Mutex
5352
done chan struct{}
5453
err error // set before done is closed
@@ -58,11 +57,6 @@ type BroadcastCapture struct {
5857
// following sequence, which gives attachment an exact cutover even when a
5958
// source read has completed but has not yet been fanned out.
6059
sequence uint64
61-
// Timestamped access-unit fan-out caches the latest complete parameter-set
62-
// plus random-access AU. Legacy byte-stream fan-out retains its exact
63-
// next-read cutover and therefore waits for the encoder's next keyframe.
64-
primer VideoAccessUnit
65-
6660
drainTimeout time.Duration
6761

6862
sinks []*BroadcastSink
@@ -79,7 +73,6 @@ type BroadcastSink struct {
7973

8074
queue [][]byte
8175
frameQueue []VideoAccessUnit
82-
primer VideoAccessUnit
8376
headOffset int
8477
queuedBytes int
8578
// queuedFrameDuration is a nominal sample-duration sum. It deliberately does
@@ -96,7 +89,6 @@ type BroadcastSink struct {
9689
maxFrameQueueDuration time.Duration
9790
backpressure bool
9891
blockedProducers int // number waiting for queue handoff; guarded by mu
99-
awaitingRandomAccess bool
10092

10193
inputClosed bool // the source ended; drain queue, then return EOF
10294
closed bool // explicitly removed; discard queue and return EOF
@@ -145,7 +137,6 @@ func NewBroadcastCaptureWithFrameRate(src *ScreenCapture, fps int) *BroadcastCap
145137
src: src,
146138
frames: src != nil && src.frames != nil,
147139
frameDuration: time.Second / time.Duration(fps),
148-
now: time.Now,
149140
done: make(chan struct{}),
150141
drainTimeout: broadcastSinkDrainTimeout,
151142
}
@@ -164,11 +155,6 @@ func (bc *BroadcastCapture) AddSink() *BroadcastSink {
164155
return s
165156
}
166157
s.startSequence = bc.sequence + 1
167-
s.primer = bc.primer
168-
if len(s.primer.AnnexB) > 0 {
169-
s.primer.PTS = bc.now()
170-
s.awaitingRandomAccess = true
171-
}
172158
bc.sinks = append(bc.sinks, s)
173159
bc.mu.Unlock()
174160
return s
@@ -194,11 +180,6 @@ func (bc *BroadcastCapture) AddBackpressuredSink() (*BroadcastSink, error) {
194180
}
195181
bc.exclusive = true
196182
s.startSequence = bc.sequence + 1
197-
s.primer = bc.primer
198-
if len(s.primer.AnnexB) > 0 {
199-
s.primer.PTS = bc.now()
200-
s.awaitingRandomAccess = true
201-
}
202183
bc.sinks = append(bc.sinks, s)
203184
bc.mu.Unlock()
204185
return s, nil
@@ -286,12 +267,6 @@ func (bc *BroadcastCapture) runFrames() error {
286267
frame, readErr := bc.src.ReadVideoAccessUnit()
287268
if len(frame.AnnexB) > 0 {
288269
bc.mu.Lock()
289-
if len(frame.AnnexB) <= broadcastSinkQueueBytes && isDecoderPrimer(frame.AnnexB) {
290-
bc.primer = VideoAccessUnit{
291-
AnnexB: append([]byte(nil), frame.AnnexB...),
292-
PTS: frame.PTS,
293-
}
294-
}
295270
sinks := make([]*BroadcastSink, 0, len(bc.sinks))
296271
for _, sink := range bc.sinks {
297272
if sink.startSequence <= sequence {
@@ -313,58 +288,6 @@ func (bc *BroadcastCapture) runFrames() error {
313288
}
314289
}
315290

316-
func isDecoderPrimer(annexB []byte) bool {
317-
var h264SPS, h264PPS, h264IDR bool
318-
var hevcVPS, hevcSPS, hevcPPS, hevcIRAP bool
319-
for _, nal := range splitAnnexBAccessUnit(annexB) {
320-
raw := stripStartCode(nal)
321-
if len(raw) == 0 {
322-
continue
323-
}
324-
switch raw[0] & 0x1f {
325-
case 5:
326-
h264IDR = true
327-
case 7:
328-
h264SPS = true
329-
case 8:
330-
h264PPS = true
331-
}
332-
if len(raw) < 2 {
333-
continue
334-
}
335-
switch nalType := hevcNALType(raw); nalType {
336-
case 32:
337-
hevcVPS = true
338-
case 33:
339-
hevcSPS = true
340-
case 34:
341-
hevcPPS = true
342-
default:
343-
hevcIRAP = hevcIRAP || nalType >= 16 && nalType <= 23
344-
}
345-
}
346-
return h264SPS && h264PPS && h264IDR || hevcVPS && hevcSPS && hevcPPS && hevcIRAP
347-
}
348-
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-
368291
// finish stops accepting sinks, lets existing sinks drain, and only then
369292
// publishes BroadcastCapture completion.
370293
func (bc *BroadcastCapture) finish(err error) {
@@ -478,12 +401,6 @@ func (s *BroadcastSink) enqueueFrame(frame VideoAccessUnit) error {
478401
if s.closed || s.inputClosed {
479402
return io.ErrClosedPipe
480403
}
481-
if s.awaitingRandomAccess {
482-
if !isRandomAccessUnit(frame.AnnexB) {
483-
return nil
484-
}
485-
s.awaitingRandomAccess = false
486-
}
487404
if len(s.frameQueue) == 0 && len(frame.AnnexB) > s.maxQueuedBytes {
488405
return errBroadcastSinkBacklog
489406
}
@@ -506,7 +423,7 @@ func (s *BroadcastSink) enqueueFrame(frame VideoAccessUnit) error {
506423
}
507424

508425
func (s *BroadcastSink) queueEmptyLocked() bool {
509-
return len(s.queue) == 0 && len(s.frameQueue) == 0 && len(s.primer.AnnexB) == 0
426+
return len(s.queue) == 0 && len(s.frameQueue) == 0
510427
}
511428

512429
// finish marks source EOF without discarding data already queued.
@@ -536,7 +453,6 @@ func (s *BroadcastSink) abort() {
536453
s.frameQueue[i].AnnexB = nil
537454
}
538455
s.frameQueue = nil
539-
s.primer = VideoAccessUnit{}
540456
s.headOffset = 0
541457
s.queuedBytes = 0
542458
s.queuedFrameDuration = 0
@@ -598,20 +514,12 @@ func (s *BroadcastSink) Read(p []byte) (int, error) {
598514
func (s *BroadcastSink) ReadVideoAccessUnit() (VideoAccessUnit, error) {
599515
s.mu.Lock()
600516
defer s.mu.Unlock()
601-
for len(s.primer.AnnexB) == 0 && len(s.frameQueue) == 0 && !s.inputClosed && !s.closed {
517+
for len(s.frameQueue) == 0 && !s.inputClosed && !s.closed {
602518
s.cond.Wait()
603519
}
604520
if s.closed {
605521
return VideoAccessUnit{}, io.EOF
606522
}
607-
if len(s.primer.AnnexB) > 0 {
608-
primer := s.primer
609-
s.primer = VideoAccessUnit{}
610-
if s.inputClosed && len(s.frameQueue) == 0 && len(s.queue) == 0 {
611-
s.closeDoneLocked()
612-
}
613-
return primer, nil
614-
}
615523
if len(s.frameQueue) == 0 {
616524
s.closeDoneLocked()
617525
return VideoAccessUnit{}, io.EOF

internal/airplay/capture_broadcast_test.go

Lines changed: 0 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@ type channelVideoAccessUnitReader struct {
1818
frames <-chan VideoAccessUnit
1919
}
2020

21-
type signaledVideoAccessUnitReader struct {
22-
frames <-chan VideoAccessUnit
23-
reads chan<- struct{}
24-
}
25-
2621
func waitForBroadcastSinkState(t *testing.T, sink *BroadcastSink, predicate func(*BroadcastSink) bool, description string) {
2722
t.Helper()
2823
deadline := time.Now().Add(time.Second)
@@ -55,15 +50,6 @@ func (r *channelVideoAccessUnitReader) ReadVideoAccessUnit() (VideoAccessUnit, e
5550
return frame, nil
5651
}
5752

58-
func (r *signaledVideoAccessUnitReader) ReadVideoAccessUnit() (VideoAccessUnit, error) {
59-
r.reads <- struct{}{}
60-
frame, ok := <-r.frames
61-
if !ok {
62-
return VideoAccessUnit{}, io.EOF
63-
}
64-
return frame, nil
65-
}
66-
6753
func (r *sliceVideoAccessUnitReader) ReadVideoAccessUnit() (VideoAccessUnit, error) {
6854
if r.index == len(r.frames) {
6955
return VideoAccessUnit{}, io.EOF
@@ -115,135 +101,6 @@ func TestBroadcastCapturePreservesTimestampedAccessUnits(t *testing.T) {
115101
}
116102
}
117103

118-
func TestBroadcastCaptureReplaysDecoderPrimerToLateSink(t *testing.T) {
119-
frames := make(chan VideoAccessUnit)
120-
reads := make(chan struct{}, 4)
121-
capture := &ScreenCapture{
122-
frames: &signaledVideoAccessUnitReader{frames: frames, reads: reads},
123-
waitCh: make(chan struct{}),
124-
}
125-
broadcast := NewBroadcastCaptureWithFrameRate(capture, 30)
126-
replayPTS := time.Unix(110, 0)
127-
broadcast.now = func() time.Time { return replayPTS }
128-
runDone := make(chan error, 1)
129-
go func() { runDone <- broadcast.Run() }()
130-
131-
<-reads
132-
primer := VideoAccessUnit{
133-
AnnexB: []byte{
134-
0, 0, 0, 1, 0x67, 0x42, 0x00, 0x1f,
135-
0, 0, 0, 1, 0x68, 0xce, 0x06, 0xe2,
136-
0, 0, 0, 1, 0x65, 0x80,
137-
},
138-
PTS: time.Unix(100, 0),
139-
}
140-
frames <- primer
141-
<-reads
142-
143-
sink := broadcast.AddSink()
144-
defer sink.Close()
145-
replayDone := make(chan struct {
146-
frame VideoAccessUnit
147-
err error
148-
}, 1)
149-
go func() {
150-
frame, err := sink.ReadVideoAccessUnit()
151-
replayDone <- struct {
152-
frame VideoAccessUnit
153-
err error
154-
}{frame: frame, err: err}
155-
}()
156-
var replayed VideoAccessUnit
157-
select {
158-
case result := <-replayDone:
159-
if result.err != nil {
160-
t.Fatalf("read replayed decoder primer: %v", result.err)
161-
}
162-
replayed = result.frame
163-
case <-time.After(time.Second):
164-
t.Fatal("cached decoder primer was not available immediately")
165-
}
166-
if !bytes.Equal(replayed.AnnexB, primer.AnnexB) {
167-
t.Fatalf("first late-sink frame = %x, want cached decoder primer %x", replayed.AnnexB, primer.AnnexB)
168-
}
169-
if !replayed.PTS.Equal(replayPTS) {
170-
t.Fatalf("replayed primer PTS = %v, want attachment PTS %v", replayed.PTS, replayPTS)
171-
}
172-
173-
boundary := VideoAccessUnit{
174-
AnnexB: []byte{0, 0, 0, 1, 0x61, 0x40},
175-
PTS: primer.PTS.Add(10 * time.Second),
176-
}
177-
frames <- boundary
178-
<-reads
179-
live := VideoAccessUnit{
180-
AnnexB: []byte{0, 0, 0, 1, 0x61, 0x80},
181-
PTS: boundary.PTS.Add(time.Second / 30),
182-
}
183-
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
190-
close(frames)
191-
192-
next, err := sink.ReadVideoAccessUnit()
193-
if err != nil {
194-
t.Fatalf("read live frame after decoder primer: %v", err)
195-
}
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)
198-
}
199-
if err := <-runDone; !errors.Is(err, io.EOF) {
200-
t.Fatalf("broadcast run = %v, want EOF", err)
201-
}
202-
}
203-
204-
func TestBroadcastCapturePrimerSnapshotSurvivesRefresh(t *testing.T) {
205-
frames := make(chan VideoAccessUnit)
206-
reads := make(chan struct{}, 4)
207-
capture := &ScreenCapture{
208-
frames: &signaledVideoAccessUnitReader{frames: frames, reads: reads},
209-
waitCh: make(chan struct{}),
210-
}
211-
broadcast := NewBroadcastCapture(capture)
212-
broadcast.now = func() time.Time { return time.Unix(120, 0) }
213-
runDone := make(chan error, 1)
214-
go func() { runDone <- broadcast.Run() }()
215-
216-
first := VideoAccessUnit{AnnexB: []byte{
217-
0, 0, 0, 1, 0x67, 0x42,
218-
0, 0, 0, 1, 0x68, 0xce,
219-
0, 0, 0, 1, 0x65, 0xaa,
220-
}}
221-
second := VideoAccessUnit{AnnexB: []byte{
222-
0, 0, 0, 1, 0x67, 0x64,
223-
0, 0, 0, 1, 0x68, 0xee,
224-
0, 0, 0, 1, 0x65, 0xbb,
225-
}}
226-
<-reads
227-
frames <- first
228-
<-reads
229-
sink := broadcast.AddSink()
230-
defer sink.Close()
231-
frames <- second
232-
<-reads
233-
234-
got, err := sink.ReadVideoAccessUnit()
235-
if err != nil {
236-
t.Fatalf("read primer snapshot: %v", err)
237-
}
238-
if !bytes.Equal(got.AnnexB, first.AnnexB) {
239-
t.Fatalf("primer snapshot = %x, want %x", got.AnnexB, first.AnnexB)
240-
}
241-
close(frames)
242-
if err := <-runDone; !errors.Is(err, io.EOF) {
243-
t.Fatalf("broadcast run = %v, want EOF", err)
244-
}
245-
}
246-
247104
func TestBroadcastSinkBackpressuresWithOnePendingAccessUnit(t *testing.T) {
248105
sink := newBroadcastSinkWithPolicy(nil, true)
249106
base := time.Now()

0 commit comments

Comments
 (0)