1
Fork 0
mirror of https://github.com/jech/galene.git synced 2024-11-22 16:45:58 +01:00

Move keyframe handling to the sender side.

This is simpler and gets rid of ErrKeyframeNeeded.
This commit is contained in:
Juliusz Chroboczek 2021-05-11 22:36:56 +02:00
parent b2ea8e8533
commit c52e1f4ce0
5 changed files with 59 additions and 108 deletions

View file

@ -9,7 +9,6 @@ import (
) )
var ErrConnectionClosed = errors.New("connection is closed") var ErrConnectionClosed = errors.New("connection is closed")
var ErrKeyframeNeeded = errors.New("keyframe needed")
// Type Up represents a connection in the client to server direction. // Type Up represents a connection in the client to server direction.
type Up interface { type Up interface {
@ -30,6 +29,7 @@ type UpTrack interface {
// get a recent packet. Returns 0 if the packet is not in cache. // get a recent packet. Returns 0 if the packet is not in cache.
GetRTP(seqno uint16, result []byte) uint16 GetRTP(seqno uint16, result []byte) uint16
Nack(conn Up, seqnos []uint16) error Nack(conn Up, seqnos []uint16) error
RequestKeyframe() error
} }
// Type Down represents a connection in the server to client direction. // Type Down represents a connection in the server to client direction.

View file

@ -458,7 +458,7 @@ func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
sample, ts := t.builder.PopWithTimestamp() sample, ts := t.builder.PopWithTimestamp()
if sample == nil { if sample == nil {
if kfNeeded { if kfNeeded {
return conn.ErrKeyframeNeeded t.remote.RequestKeyframe()
} }
return nil return nil
} }
@ -506,7 +506,7 @@ func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
if t.writer == nil { if t.writer == nil {
if !keyframe { if !keyframe {
return conn.ErrKeyframeNeeded t.remote.RequestKeyframe()
} }
return nil return nil
} }

View file

@ -235,7 +235,7 @@ type rtpUpTrack struct {
atomics *upTrackAtomics atomics *upTrackAtomics
cname atomic.Value cname atomic.Value
localCh chan localTrackAction localCh chan trackAction
readerDone chan struct{} readerDone chan struct{}
mu sync.Mutex mu sync.Mutex
@ -246,14 +246,20 @@ type rtpUpTrack struct {
bufferedNACKs []uint16 bufferedNACKs []uint16
} }
type localTrackAction struct { const (
add bool trackActionAdd = iota
trackActionDel
trackActionKeyframe
)
type trackAction struct {
action int
track conn.DownTrack track conn.DownTrack
} }
func (up *rtpUpTrack) notifyLocal(add bool, track conn.DownTrack) { func (up *rtpUpTrack) action(action int, track conn.DownTrack) {
select { select {
case up.localCh <- localTrackAction{add, track}: case up.localCh <- trackAction{action, track}:
case <-up.readerDone: case <-up.readerDone:
} }
} }
@ -271,7 +277,12 @@ func (up *rtpUpTrack) AddLocal(local conn.DownTrack) error {
// do this asynchronously, to avoid deadlocks when multiple // do this asynchronously, to avoid deadlocks when multiple
// clients call this simultaneously. // clients call this simultaneously.
go up.notifyLocal(true, local) go up.action(trackActionAdd, local)
return nil
}
func (up *rtpUpTrack) RequestKeyframe() error {
go up.action(trackActionKeyframe, nil)
return nil return nil
} }
@ -283,7 +294,7 @@ func (up *rtpUpTrack) DelLocal(local conn.DownTrack) bool {
up.local = append(up.local[:i], up.local[i+1:]...) up.local = append(up.local[:i], up.local[i+1:]...)
// do this asynchronously, to avoid deadlocking when // do this asynchronously, to avoid deadlocking when
// multiple clients call this simultaneously. // multiple clients call this simultaneously.
go up.notifyLocal(false, l) go up.action(trackActionDel, l)
return true return true
} }
} }
@ -489,7 +500,7 @@ func newUpConn(c group.Client, id string, label string, offer string) (*rtpUpCon
rate: estimator.New(time.Second), rate: estimator.New(time.Second),
jitter: jitter.New(remote.Codec().ClockRate), jitter: jitter.New(remote.Codec().ClockRate),
atomics: &upTrackAtomics{}, atomics: &upTrackAtomics{},
localCh: make(chan localTrackAction, 2), localCh: make(chan trackAction, 2),
readerDone: make(chan struct{}), readerDone: make(chan struct{}),
} }
@ -977,7 +988,6 @@ func (track *rtpDownTrack) updateRate(loss uint8, now uint64) {
} }
func rtcpDownListener(conn *rtpDownConnection, track *rtpDownTrack, s *webrtc.RTPSender) { func rtcpDownListener(conn *rtpDownConnection, track *rtpDownTrack, s *webrtc.RTPSender) {
var gotFir bool
lastFirSeqno := uint8(0) lastFirSeqno := uint8(0)
buf := make([]byte, 1500) buf := make([]byte, 1500)
@ -1001,18 +1011,7 @@ func rtcpDownListener(conn *rtpDownConnection, track *rtpDownTrack, s *webrtc.RT
for _, p := range ps { for _, p := range ps {
switch p := p.(type) { switch p := p.(type) {
case *rtcp.PictureLossIndication: case *rtcp.PictureLossIndication:
remote, ok := conn.remote.(*rtpUpConnection) track.remote.RequestKeyframe()
if !ok {
continue
}
rt, ok := track.remote.(*rtpUpTrack)
if !ok {
continue
}
err := remote.sendPLI(rt)
if err != nil && err != ErrRateLimited {
log.Printf("sendPLI: %v", err)
}
case *rtcp.FullIntraRequest: case *rtcp.FullIntraRequest:
found := false found := false
var seqno uint8 var seqno uint8
@ -1028,29 +1027,8 @@ func rtcpDownListener(conn *rtpDownConnection, track *rtpDownTrack, s *webrtc.RT
continue continue
} }
increment := true if seqno != lastFirSeqno {
if gotFir { track.remote.RequestKeyframe()
increment = seqno != lastFirSeqno
}
gotFir = true
lastFirSeqno = seqno
remote, ok := conn.remote.(*rtpUpConnection)
if !ok {
continue
}
rt, ok := track.remote.(*rtpUpTrack)
if !ok {
continue
}
err := remote.sendFIR(rt, increment)
if err == ErrUnsupportedFeedback {
err := remote.sendPLI(rt)
if err != nil && err != ErrRateLimited {
log.Printf("sendPLI: %v", err)
}
} else if err != nil && err != ErrRateLimited {
log.Printf("sendFIR: %v", err)
} }
case *rtcp.ReceiverEstimatedMaximumBitrate: case *rtcp.ReceiverEstimatedMaximumBitrate:
track.maxREMBBitrate.Set(p.Bitrate, jiffies) track.maxREMBBitrate.Set(p.Bitrate, jiffies)

View file

@ -21,6 +21,7 @@ func readLoop(conn *rtpUpConnection, track *rtpUpTrack) {
isvideo := track.track.Kind() == webrtc.RTPCodecTypeVideo isvideo := track.track.Kind() == webrtc.RTPCodecTypeVideo
codec := track.track.Codec() codec := track.track.Codec()
sendNACK := track.hasRtcpFb("nack", "") sendNACK := track.hasRtcpFb("nack", "")
var kfNeeded bool
buf := make([]byte, packetcache.BufSize) buf := make([]byte, packetcache.BufSize)
var packet rtp.Packet var packet rtp.Packet
for { for {
@ -41,8 +42,10 @@ func readLoop(conn *rtpUpConnection, track *rtpUpTrack) {
track.jitter.Accumulate(packet.Timestamp) track.jitter.Accumulate(packet.Timestamp)
kf, _ := isKeyframe(codec.MimeType, &packet) kf, kfKnown := isKeyframe(codec.MimeType, &packet)
if kf || !kfKnown {
kfNeeded = false
}
if packet.Extension { if packet.Extension {
packet.Extension = false packet.Extension = false
packet.Extensions = nil packet.Extensions = nil
@ -102,11 +105,29 @@ func readLoop(conn *rtpUpConnection, track *rtpUpTrack) {
select { select {
case action := <-track.localCh: case action := <-track.localCh:
err := writers.add(action.track, action.add) switch action.action {
case trackActionAdd, trackActionDel:
err := writers.add(
action.track,
action.action == trackActionAdd,
)
if err != nil { if err != nil {
log.Printf("add/remove track: %v", err) log.Printf("add/remove track: %v", err)
} }
case trackActionKeyframe:
kfNeeded = true
default:
log.Printf("Unknown action %v", action.action)
}
default: default:
} }
if kfNeeded {
err := conn.sendPLI(track)
if err != nil && err != ErrRateLimited {
log.Printf("sendPLI: %v", err)
kfNeeded = false
}
}
} }
} }

View file

@ -4,7 +4,6 @@ import (
"errors" "errors"
"log" "log"
"sort" "sort"
"strings"
"time" "time"
"github.com/pion/rtp" "github.com/pion/rtp"
@ -223,33 +222,22 @@ func sendKeyframe(kf []uint16, track conn.DownTrack, cache *packetcache.Cache) {
return return
} }
err = track.WriteRTP(&packet) err = track.WriteRTP(&packet)
if err != nil && err != conn.ErrKeyframeNeeded { if err != nil {
return return
} }
track.Accumulate(uint32(bytes)) track.Accumulate(uint32(bytes))
} }
} }
const (
kfUnneeded = iota
kfNeededPLI
kfNeededFIR
kfNeededNewFIR
)
// rtpWriterLoop is the main loop of an rtpWriter. // rtpWriterLoop is the main loop of an rtpWriter.
func rtpWriterLoop(writer *rtpWriter, up *rtpUpConnection, track *rtpUpTrack) { func rtpWriterLoop(writer *rtpWriter, up *rtpUpConnection, track *rtpUpTrack) {
defer close(writer.done) defer close(writer.done)
codec := track.track.Codec()
buf := make([]byte, packetcache.BufSize) buf := make([]byte, packetcache.BufSize)
var packet rtp.Packet var packet rtp.Packet
local := make([]conn.DownTrack, 0) local := make([]conn.DownTrack, 0)
kfNeeded := kfUnneeded
for { for {
select { select {
case action := <-writer.action: case action := <-writer.action:
@ -277,8 +265,7 @@ func rtpWriterLoop(writer *rtpWriter, up *rtpUpConnection, track *rtpUpTrack) {
found, _, lts := track.cache.Last() found, _, lts := track.cache.Last()
kts, _, kf := track.cache.Keyframe() kts, _, kf := track.cache.Keyframe()
if strings.ToLower(codec.MimeType) == "video/vp8" && if found && len(kf) > 0 {
found && len(kf) > 0 {
if ((lts-kts)&0x80000000) != 0 || if ((lts-kts)&0x80000000) != 0 ||
lts-kts < 2*90000 { lts-kts < 2*90000 {
// we got a recent keyframe // we got a recent keyframe
@ -288,8 +275,7 @@ func rtpWriterLoop(writer *rtpWriter, up *rtpUpConnection, track *rtpUpTrack) {
track.cache, track.cache,
) )
} else { } else {
// Request a new keyframe track.RequestKeyframe()
kfNeeded = kfNeededNewFIR
} }
} else { } else {
// no keyframe yet, one should // no keyframe yet, one should
@ -333,44 +319,10 @@ func rtpWriterLoop(writer *rtpWriter, up *rtpUpConnection, track *rtpUpTrack) {
for _, l := range local { for _, l := range local {
err := l.WriteRTP(&packet) err := l.WriteRTP(&packet)
if err != nil { if err != nil {
if err == conn.ErrKeyframeNeeded {
kfNeeded = kfNeededPLI
} else {
continue continue
} }
}
l.Accumulate(uint32(bytes)) l.Accumulate(uint32(bytes))
} }
if kfNeeded > kfUnneeded {
kf, kfKnown :=
isKeyframe(codec.MimeType, &packet)
if kf {
kfNeeded = kfUnneeded
}
if kfNeeded >= kfNeededFIR {
err := up.sendFIR(
track,
kfNeeded >= kfNeededNewFIR,
)
if err == ErrUnsupportedFeedback {
kfNeeded = kfNeededPLI
} else {
kfNeeded = kfNeededFIR
}
}
if kfNeeded == kfNeededPLI {
up.sendPLI(track)
}
if !kfKnown {
// we cannot detect keyframes for
// this codec, reset our state
kfNeeded = kfUnneeded
}
}
} }
} }
} }