Skip to content

Commit

Permalink
Add vp9 svc support by Dependency Descriptor (livekit#1586)
Browse files Browse the repository at this point in the history
* Add VP9 SVC support

* Fix preferred fps does not work

* Fix forwarder test
  • Loading branch information
cnderrauber authored and hautvfami committed Jul 21, 2023
1 parent 4e5cef5 commit 1be3e66
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 18 deletions.
18 changes: 8 additions & 10 deletions pkg/rtc/mediaengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,14 @@ func registerCodecs(me *webrtc.MediaEngine, codecs []*livekit.Codec, rtcpFeedbac
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, RTCPFeedback: rtcpFeedback.Video},
PayloadType: 96,
},
/*
{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=0", RTCPFeedback: rtcpFeedback.Video},
PayloadType: 98,
},
{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=1", RTCPFeedback: rtcpFeedback.Video},
PayloadType: 100,
},
*/
{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=0", RTCPFeedback: rtcpFeedback.Video},
PayloadType: 98,
},
{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=1", RTCPFeedback: rtcpFeedback.Video},
PayloadType: 100,
},
{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", RTCPFeedback: rtcpFeedback.Video},
PayloadType: 125,
Expand Down
2 changes: 1 addition & 1 deletion pkg/rtc/participant_sdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (p *ParticipantImpl) setCodecPreferencesVideoForPublisher(offer webrtc.Sess

mime = strings.ToUpper(mime)
// remove dd extension if av1 not preferred
if !strings.Contains(mime, "AV1") {
if !strings.Contains(mime, "AV1") && !strings.Contains(mime, "VP9") {
for i, attr := range unmatchVideo.Attributes {
if strings.Contains(attr.Value, dd.ExtensionUrl) {
unmatchVideo.Attributes[i] = unmatchVideo.Attributes[len(unmatchVideo.Attributes)-1]
Expand Down
2 changes: 2 additions & 0 deletions pkg/sfu/buffer/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,8 @@ func (b *Buffer) getExtPacket(rtpPacket *rtp.Packet, arrivalTime int64) *ExtPack
ep.KeyFrame = IsH264Keyframe(rtpPacket.Payload)
case "video/av1":
ep.KeyFrame = IsAV1Keyframe(rtpPacket.Payload)
case "video/vp9":
ep.KeyFrame = IsVP9Keyframe(rtpPacket.Payload)
}

if ep.KeyFrame {
Expand Down
26 changes: 26 additions & 0 deletions pkg/sfu/buffer/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"encoding/binary"
"errors"

"github.com/pion/rtp/codecs"

"github.com/livekit/protocol/logger"
)

Expand Down Expand Up @@ -351,4 +353,28 @@ func IsAV1Keyframe(payload []byte) bool {
}
}

// IsVP9Keyframe detects if vp9 payload is a keyframe
// taken from https://github.com/jech/galene/blob/master/codecs/codecs.go
// all credits belongs to Juliusz Chroboczek @jech and the awesome Galene SFU
func IsVP9Keyframe(payload []byte) bool {
var vp9 codecs.VP9Packet
_, err := vp9.Unmarshal(payload)
if err != nil || len(vp9.Payload) < 1 {
return false
}
if !vp9.B {
return false
}

if (vp9.Payload[0] & 0xc0) != 0x80 {
return false
}

profile := (vp9.Payload[0] >> 4) & 0x3
if profile != 3 {
return (vp9.Payload[0] & 0xC) == 0
}
return (vp9.Payload[0] & 0x6) == 0
}

// -------------------------------------
10 changes: 5 additions & 5 deletions pkg/sfu/forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,8 @@ func (f *Forwarder) DetermineCodec(codec webrtc.RTPCodecCapability) {
case "video/vp8":
f.isTemporalSupported = true
f.vp8Munger = NewVP8Munger(f.logger)
case "video/av1":
// TODO : we only enable dd layer selector for av1 now, at future we can
case "video/av1", "video/vp9":
// TODO : we only enable dd layer selector for av1 and vp9 now, at future we can
// enable it for vp8 too
f.ddLayerSelector = NewDDVideoLayerSelector(f.logger)
}
Expand Down Expand Up @@ -515,7 +515,7 @@ func (f *Forwarder) AllocateOptimal(availableLayers []int32, brs Bitrates, allow
}
alloc.TargetLayers = buffer.VideoLayer{
Spatial: int32(math.Min(float64(f.maxPublishedLayer), float64(maxSpatial))),
Temporal: buffer.DefaultMaxLayerTemporal,
Temporal: f.maxLayers.Temporal,
}
}

Expand Down Expand Up @@ -570,14 +570,14 @@ func (f *Forwarder) AllocateOptimal(availableLayers []int32, brs Bitrates, allow
alloc.TargetLayers.Spatial = l
}
}
alloc.TargetLayers.Temporal = buffer.DefaultMaxLayerTemporal
alloc.TargetLayers.Temporal = f.maxLayers.Temporal

alloc.RequestLayerSpatial = alloc.TargetLayers.Spatial
} else {
requestLayerSpatial := int32(math.Min(float64(f.maxLayers.Spatial), float64(f.maxPublishedLayer)))
if f.currentLayers.IsValid() && requestLayerSpatial == f.requestLayerSpatial && f.currentLayers.Spatial == f.requestLayerSpatial {
// current is locked to desired, stay there
alloc.TargetLayers = f.currentLayers
alloc.TargetLayers = buffer.VideoLayer{Spatial: f.requestLayerSpatial, Temporal: f.maxLayers.Temporal}
alloc.RequestLayerSpatial = f.requestLayerSpatial
} else {
// opportunistically latch on to anything
Expand Down
4 changes: 2 additions & 2 deletions pkg/sfu/forwarder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ func TestForwarderAllocateOptimal(t *testing.T) {
f.requestLayerSpatial = 0
expectedTargetLayers = buffer.VideoLayer{
Spatial: 2,
Temporal: 3,
Temporal: 1,
}
expectedResult = VideoAllocation{
PauseReason: VideoPauseReasonFeedDry,
Expand All @@ -397,7 +397,7 @@ func TestForwarderAllocateOptimal(t *testing.T) {
TargetLayers: expectedTargetLayers,
RequestLayerSpatial: 2,
MaxLayers: f.maxLayers,
DistanceToDesired: -1.5,
DistanceToDesired: -1,
}
result = f.AllocateOptimal([]int32{0, 1}, emptyBitrates, true)
require.Equal(t, expectedResult, result)
Expand Down

0 comments on commit 1be3e66

Please sign in to comment.