Skip to content

Commit 6386736

Browse files
authored
Simulcast: Allow layer selection for publisher (#477)
* Simulcast: Allow layer selection for publisher Changed ion-sfu channel messaging to be peer based and added aditional types Addressing PR comments Rename IonSfuMessage to ChannelAPIMessage. Use interface{} instead of string, fixes double marshalling issue * Rebase on new interface changes
1 parent 7add00c commit 6386736

File tree

4 files changed

+195
-36
lines changed

4 files changed

+195
-36
lines changed

pkg/middlewares/datachannel/subscriberapi.go

Lines changed: 127 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,92 @@ package datachannel
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67

78
"github.com/pion/ion-sfu/pkg/sfu"
89
"github.com/pion/webrtc/v3"
910
)
1011

1112
const (
12-
highValue = "high"
13-
mediumValue = "medium"
14-
lowValue = "low"
15-
mutedValue = "none"
13+
highValue = "high"
14+
mediumValue = "medium"
15+
lowValue = "low"
16+
mutedValue = "none"
17+
ActiveLayerMethod = "activeLayer"
1618
)
1719

1820
type setRemoteMedia struct {
19-
StreamID string `json:"streamId"`
20-
Video string `json:"video"`
21-
Framerate string `json:"framerate"`
22-
Audio bool `json:"audio"`
21+
StreamID string `json:"streamId"`
22+
Video string `json:"video"`
23+
Framerate string `json:"framerate"`
24+
Audio bool `json:"audio"`
25+
Layers []string `json:"layers"`
26+
}
27+
28+
type activeLayerMessage struct {
29+
StreamID string `json:"streamId"`
30+
ActiveLayer string `json:"activeLayer"`
31+
AvailableLayers []string `json:"availableLayers"`
32+
}
33+
34+
func layerStrToInt(layer string) (int, error) {
35+
switch layer {
36+
case highValue:
37+
return 2, nil
38+
case mediumValue:
39+
return 1, nil
40+
case lowValue:
41+
return 0, nil
42+
default:
43+
// unknown value
44+
return -1, fmt.Errorf("Unknown value")
45+
}
46+
}
47+
48+
func layerIntToStr(layer int) (string, error) {
49+
switch layer {
50+
case 0:
51+
return lowValue, nil
52+
case 1:
53+
return mediumValue, nil
54+
case 2:
55+
return highValue, nil
56+
default:
57+
return "", fmt.Errorf("Unknown value: %d", layer)
58+
}
59+
}
60+
61+
func transformLayers(layers []string) ([]uint16, error) {
62+
res := make([]uint16, len(layers))
63+
for _, layer := range layers {
64+
if l, err := layerStrToInt(layer); err == nil {
65+
res = append(res, uint16(l))
66+
} else {
67+
return nil, fmt.Errorf("Unknown layer value: %v", layer)
68+
}
69+
}
70+
return res, nil
71+
}
72+
73+
func sendMessage(streamID string, peer sfu.Peer, layers []string, activeLayer int) {
74+
al, _ := layerIntToStr(activeLayer)
75+
payload := activeLayerMessage{
76+
StreamID: streamID,
77+
ActiveLayer: al,
78+
AvailableLayers: layers,
79+
}
80+
msg := sfu.ChannelAPIMessage{
81+
Method: ActiveLayerMethod,
82+
Params: payload,
83+
}
84+
bytes, err := json.Marshal(msg)
85+
if err != nil {
86+
sfu.Logger.Error(err, "unable to marshal active layer message")
87+
}
88+
89+
if err := peer.SendAPIChannelMessage(&bytes); err != nil {
90+
sfu.Logger.Error(err, "unable to send ActiveLayerMessage to peer", "peer_id", peer.ID())
91+
}
2392
}
2493

2594
func SubscriberAPI(next sfu.MessageProcessor) sfu.MessageProcessor {
@@ -28,35 +97,59 @@ func SubscriberAPI(next sfu.MessageProcessor) sfu.MessageProcessor {
2897
if err := json.Unmarshal(args.Message.Data, srm); err != nil {
2998
return
3099
}
31-
downTracks := args.Peer.Subscriber().GetDownTracks(srm.StreamID)
32-
for _, dt := range downTracks {
33-
switch dt.Kind() {
34-
case webrtc.RTPCodecTypeAudio:
35-
dt.Mute(!srm.Audio)
36-
case webrtc.RTPCodecTypeVideo:
37-
switch srm.Video {
38-
case highValue:
39-
dt.Mute(false)
40-
dt.SwitchSpatialLayer(2, true)
41-
case mediumValue:
42-
dt.Mute(false)
43-
dt.SwitchSpatialLayer(1, true)
44-
case lowValue:
45-
dt.Mute(false)
46-
dt.SwitchSpatialLayer(0, true)
47-
case mutedValue:
48-
dt.Mute(true)
49-
}
50-
switch srm.Framerate {
51-
case highValue:
52-
dt.SwitchTemporalLayer(2, true)
53-
case mediumValue:
54-
dt.SwitchTemporalLayer(1, true)
55-
case lowValue:
56-
dt.SwitchTemporalLayer(0, true)
100+
// Publisher changing active layers
101+
if srm.Layers != nil && len(srm.Layers) > 0 {
102+
layers, err := transformLayers(srm.Layers)
103+
if err != nil {
104+
sfu.Logger.Error(err, "error reading layers")
105+
next.Process(ctx, args)
106+
return
107+
}
108+
109+
session := args.Peer.Session()
110+
peers := session.Peers()
111+
for _, peer := range peers {
112+
if peer.ID() != args.Peer.ID() {
113+
downTracks := peer.Subscriber().GetDownTracks(srm.StreamID)
114+
for _, dt := range downTracks {
115+
if dt.Kind() == webrtc.RTPCodecTypeVideo {
116+
newLayer, _ := dt.UptrackLayersChange(layers)
117+
sendMessage(srm.StreamID, peer, srm.Layers, int(newLayer))
118+
}
119+
}
57120
}
58121
}
122+
} else {
123+
downTracks := args.Peer.Subscriber().GetDownTracks(srm.StreamID)
124+
for _, dt := range downTracks {
125+
switch dt.Kind() {
126+
case webrtc.RTPCodecTypeAudio:
127+
dt.Mute(!srm.Audio)
128+
case webrtc.RTPCodecTypeVideo:
129+
switch srm.Video {
130+
case highValue:
131+
dt.Mute(false)
132+
dt.SwitchSpatialLayer(2, true)
133+
case mediumValue:
134+
dt.Mute(false)
135+
dt.SwitchSpatialLayer(1, true)
136+
case lowValue:
137+
dt.Mute(false)
138+
dt.SwitchSpatialLayer(0, true)
139+
case mutedValue:
140+
dt.Mute(true)
141+
}
142+
switch srm.Framerate {
143+
case highValue:
144+
dt.SwitchTemporalLayer(2, true)
145+
case mediumValue:
146+
dt.SwitchTemporalLayer(1, true)
147+
case lowValue:
148+
dt.SwitchTemporalLayer(0, true)
149+
}
150+
}
59151

152+
}
60153
}
61154
next.Process(ctx, args)
62155
})

pkg/sfu/downtrack.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package sfu
22

33
import (
4+
"fmt"
45
"strings"
56
"sync"
67
"sync/atomic"
@@ -211,6 +212,40 @@ func (d *DownTrack) SwitchSpatialLayer(targetLayer int64, setAsMax bool) {
211212
}
212213
}
213214

215+
func (d *DownTrack) UptrackLayersChange(availableLayers []uint16) (int64, error) {
216+
if d.trackType == SimulcastDownTrack {
217+
currentLayer := uint16(atomic.LoadInt32(&d.spatialLayer))
218+
maxLayer := uint16(atomic.LoadInt64(&d.maxSpatialLayer))
219+
220+
var maxFound uint16 = 0
221+
layerFound := false
222+
var minFound uint16 = 0
223+
for _, target := range availableLayers {
224+
if target <= maxLayer {
225+
if target > maxFound {
226+
maxFound = target
227+
layerFound = true
228+
}
229+
} else {
230+
if minFound > target {
231+
minFound = target
232+
}
233+
}
234+
}
235+
var targetLayer uint16
236+
if layerFound {
237+
targetLayer = maxFound
238+
} else {
239+
targetLayer = minFound
240+
}
241+
if currentLayer != targetLayer {
242+
d.SwitchSpatialLayer(int64(targetLayer), false)
243+
}
244+
return int64(targetLayer), nil
245+
}
246+
return -1, fmt.Errorf("Downtrack %s does not support simulcast", d.id)
247+
}
248+
214249
func (d *DownTrack) SwitchTemporalLayer(targetLayer int64, setAsMax bool) {
215250
if d.trackType == SimulcastDownTrack {
216251
layer := atomic.LoadInt32(&d.temporalLayer)

pkg/sfu/peer.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Peer interface {
3030
Publisher() *Publisher
3131
Subscriber() *Subscriber
3232
Close() error
33+
SendAPIChannelMessage(msg *[]byte) error
3334
}
3435

3536
// JoinConfig allow adding more control to the peers joining a SessionLocal.
@@ -48,6 +49,11 @@ type SessionProvider interface {
4849
GetSession(sid string) (Session, WebRTCTransportConfig)
4950
}
5051

52+
type ChannelAPIMessage struct {
53+
Method string `json:"method"`
54+
Params interface{} `json:"params,omitempty"`
55+
}
56+
5157
// PeerLocal represents a pair peer connection
5258
type PeerLocal struct {
5359
sync.Mutex
@@ -241,6 +247,22 @@ func (p *PeerLocal) Trickle(candidate webrtc.ICECandidateInit, target int) error
241247
return nil
242248
}
243249

250+
func (p *PeerLocal) SendAPIChannelMessage(msg *[]byte) error {
251+
if p.subscriber == nil {
252+
return fmt.Errorf("No subscriber for this peer")
253+
}
254+
dc := p.subscriber.DataChannel(APIChannelLabel)
255+
256+
if dc == nil {
257+
return fmt.Errorf("Data channel %s doesn't exist", APIChannelLabel)
258+
}
259+
260+
if err := dc.SendText(string(*msg)); err != nil {
261+
return fmt.Errorf("Failed to send message: %v", err)
262+
}
263+
return nil
264+
}
265+
244266
// Close shuts down the peer connection and sends true to the done channel
245267
func (p *PeerLocal) Close() error {
246268
p.Lock()

pkg/sfu/session.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Session interface {
2222
GetDCMiddlewares() []*Datachannel
2323
GetDataChannelLabels() []string
2424
GetDataChannels(origin, label string) (dcs []*webrtc.DataChannel)
25+
Peers() []Peer
2526
}
2627

2728
type SessionLocal struct {
@@ -35,6 +36,10 @@ type SessionLocal struct {
3536
onCloseHandler func()
3637
}
3738

39+
const (
40+
AudioLevelsMethod = "audioLevels"
41+
)
42+
3843
// NewSession creates a new SessionLocal
3944
func NewSession(id string, dcs []*Datachannel, cfg WebRTCTransportConfig) Session {
4045
s := &SessionLocal{
@@ -45,7 +50,6 @@ func NewSession(id string, dcs []*Datachannel, cfg WebRTCTransportConfig) Sessio
4550
}
4651
go s.audioLevelObserver(cfg.Router.AudioLevelInterval)
4752
return s
48-
4953
}
5054

5155
// ID return SessionLocal id
@@ -251,7 +255,12 @@ func (s *SessionLocal) audioLevelObserver(audioLevelInterval int) {
251255
continue
252256
}
253257

254-
l, err := json.Marshal(&levels)
258+
msg := ChannelAPIMessage{
259+
Method: AudioLevelsMethod,
260+
Params: levels,
261+
}
262+
263+
l, err := json.Marshal(&msg)
255264
if err != nil {
256265
Logger.Error(err, "Marshaling audio levels err")
257266
continue

0 commit comments

Comments
 (0)