Commit a8a8ce6e authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

WIP: simulcast

parent 1e977213
......@@ -7,6 +7,8 @@ package main
import (
"errors"
"strconv"
"strings"
"github.com/pion/rtp"
"github.com/pion/webrtc/v2"
......@@ -35,6 +37,7 @@ type upTrack interface {
type downConnection interface {
GetMaxBitrate(now uint64) uint64
GetVideoLayer() uint32
}
type downTrack interface {
......@@ -42,3 +45,17 @@ type downTrack interface {
Accumulate(bytes uint32)
setTimeOffset(ntp uint64, rtp uint32)
}
func splitLabel(label string) (string, uint32) {
split := strings.SplitN(label, "-", 2)
if len(split) != 2 {
return label, ^uint32(0)
}
n, err := strconv.ParseUint(split[1], 10, 32)
if err != nil {
return label, ^uint32(0)
}
return split[0], uint32(n)
}
......@@ -179,6 +179,24 @@ type diskTrack struct {
}
func newDiskConn(directory, label string, up upConnection, remoteTracks []upTrack) (*diskConn, error) {
// pick the best video track
var video upTrack
for _, t := range remoteTracks {
if t.Codec().Name != webrtc.VP8 {
continue
}
if video == nil {
video = t
} else {
_, s := splitLabel(video.Label())
_, r := splitLabel(t.Label())
if s < r {
video = t
}
}
}
conn := diskConn{
directory: directory,
label: label,
......@@ -191,8 +209,8 @@ func newDiskConn(directory, label string, up upConnection, remoteTracks []upTrac
case webrtc.Opus:
builder = samplebuilder.New(16, &codecs.OpusPacket{})
case webrtc.VP8:
if conn.hasVideo {
return nil, errors.New("multiple video tracks not supported")
if remote != video {
continue
}
builder = samplebuilder.New(32, &codecs.VP8Packet{})
conn.hasVideo = true
......@@ -386,6 +404,10 @@ func (down *diskConn) GetMaxBitrate(now uint64) uint64 {
return ^uint64(0)
}
func (down *diskConn) GetVideoLayer() uint32 {
return ^uint32(0)
}
func (t *diskTrack) Accumulate(bytes uint32) {
return
}
......@@ -36,10 +36,6 @@ type chatHistoryEntry struct {
me bool
}
const (
minBitrate = 200000
)
type group struct {
name string
dead bool
......
......@@ -10,6 +10,7 @@ import (
"io"
"log"
"math/bits"
"sort"
"sync"
"sync/atomic"
"time"
......@@ -851,15 +852,57 @@ func sendUpRTCP(conn *rtpUpConnection) error {
},
}
rate := ^uint64(0)
layers := make([]uint32, 0)
for _, t := range conn.tracks {
l, r := splitLabel(t.Label())
if l == "video" {
found := false
for _, rr := range layers {
if rr == r {
found = true
break
}
}
if !found {
layers = append(layers, r)
}
}
}
sort.Slice(layers, func(i, j int) bool {
return layers[i] < layers[j]
})
rates := make(map[uint32]uint64)
for i, layer := range layers {
if i < len(layers) - 1 {
rates[layer] = uint64(layers[i + 1]) * 1024
} else {
rates[layer] = uint64(2 * layer) * 1024
}
}
minBitrate := func(l uint32) uint64 {
if len(layers) == 0 {
return 9600
}
for i := len(layers) - 1; i > 1; i-- {
if l >= layers[i] {
return uint64(layers[i - 1]) * 1000
}
}
return 9600
}
for _, l := range conn.local {
layer := l.GetVideoLayer()
r := l.GetMaxBitrate(now)
if r < rate {
rate = r
min := minBitrate(layer)
if r < min {
r = min
}
rate, ok := rates[layer]
if !ok || r < rate {
rates[layer] = r
}
}
if rate < minBitrate {
rate = minBitrate
}
var ssrcs []uint32
......@@ -870,6 +913,10 @@ func sendUpRTCP(conn *rtpUpConnection) error {
ssrcs = append(ssrcs, t.track.SSRC())
}
var rate uint64
for _, r := range rates {
rate += r
}
if len(ssrcs) > 0 {
packets = append(packets,
&rtcp.ReceiverEstimatedMaximumBitrate{
......@@ -1142,3 +1189,14 @@ func updateUpTrack(track *rtpUpTrack) {
}
track.cache.ResizeCond(packets)
}
func (down *rtpDownConnection) GetVideoLayer() uint32 {
for _, t := range down.tracks {
label := t.remote.Label()
l, r := splitLabel(label)
if l == "video" {
return r
}
}
return 0
}
......@@ -84,6 +84,11 @@ h1 {
margin-right: 0.4em;
}
#sendselect {
width: 8em;
text-align-last: center;
}
#requestselect {
width: 8em;
text-align-last: center;
......
......@@ -50,11 +50,20 @@
<button id="sharebutton" class="invisible">Share screen</button>
<button id="unsharebutton" class="invisible">Stop sharing</button>
<label for="sendselect">Send:</label>
<select id="sendselect">
<option value="200">LQ video</option>
<option value="800" selected>normal video</option>
<option value="2000">HQ video</option>
</select>
<label for="requestselect">Receive:</label>
<select id="requestselect">
<option value="audio">audio only</option>
<option value="screenshare">screen share</option>
<option value="everything" selected>everything</option>
<option value="low">LQ video</option>
<option value="normal" selected>normal video</option>
<option value="high">HQ video</option>
</select>
</div>
</div>
......
......@@ -212,6 +212,11 @@ document.getElementById('requestselect').onchange = function(e) {
sendRequest(this.value);
};
document.getElementById('sendselect').onchange = function(e) {
e.preventDefault();
changePresentation();
};
async function updateStats(conn, sender) {
let tid = sender.track && sender.track.id;
if(!tid)
......@@ -337,6 +342,8 @@ async function setMediaChoices() {
mediaChoicesDone = true;
}
const videoRates = [200, 800, 2000];
async function addLocalMedia(id) {
if(!getUserPass())
return;
......@@ -369,17 +376,41 @@ async function addLocalMedia(id) {
id = await newUpStream(id);
let c = up[id];
let maxRate = document.getElementById('sendselect').value;
let rates = [];
if(maxRate > 0)
videoRates.forEach(r => {
if(r <= maxRate)
rates.push(r);
});
else
console.warn("Couldn't parse video rate");
c.kind = 'local';
c.stream = stream;
stream.getTracks().forEach(t => {
c.labels[t.id] = t.kind
if(t.kind == 'audio' && localMute)
let tracks = stream.getTracks();
for(let i = 0; i < tracks.length; i++) {
let t = tracks[i];
if(t.kind === 'audio' && localMute)
t.enabled = false;
let sender = c.pc.addTrack(t, stream);
c.setInterval(() => {
updateStats(c, sender);
}, 2000);
});
let senders = [];
if(t.kind === 'video' && rates.length > 0) {
for(let j = 0; j < rates.length; j++) {
let tt = (j < rates.length - 1) ? t.clone() : t;
senders.push(c.pc.addTrack(tt, stream));
c.labels[tt.id] = t.kind + '-' + rates[j];
}
} else {
senders.push(c.pc.addTrack(t, stream));
c.labels[t.id] = t.kind
}
senders.forEach(sender => {
c.setInterval(() => {
updateStats(c, sender);
}, 2000);
});
}
c.setInterval(() => {
displayStats(id);
}, 2500);
......@@ -700,11 +731,18 @@ function sendRequest(value) {
case 'screenshare':
request = {audio: true, screenshare: true};
break;
case 'everything':
case 'low':
request = {audio: true, screenshare: true, video: 200};
break;
case 'normal':
request = {audio: true, screenshare: true, video: 800};
break;
case 'high':
request = {audio: true, screenshare: true, video: true};
break;
default:
console.error(`Uknown value ${value} in sendRequest`);
console.warn(`Uknown value ${value} in sendRequest`);
request = {audio: true, screenshare: true, video: 500};
break;
}
......@@ -1222,17 +1260,35 @@ async function negotiate(id) {
throw(new Error("Didn't create offer"));
await c.pc.setLocalDescription(offer);
// mids are not known until this point
// mids and encodings are not known until this point
if(typeof(c.pc.getTransceivers) === 'function') {
c.pc.getTransceivers().forEach(t => {
let transceivers = c.pc.getTransceivers();
for(let i = 0; i < transceivers.length; i++) {
let t = transceivers[i];
if(t.sender && t.sender.track) {
let label = c.labels[t.sender.track.id];
if(label)
c.labelsByMid[t.mid] = label;
else
if(!label) {
console.warn("Couldn't find label for track");
continue;
}
c.labelsByMid[t.mid] = label;
let dash = label.indexOf('-');
if(dash >= 0) {
let rate = parseInt(label.slice(dash + 1));
if(rate > 0) {
let p = t.sender.getParameters();
p.encodings.forEach(e => {
e.maxBitrate = rate * 1000;
});
try {
await t.sender.setParameters(p);
} catch(e) {
console.warn(e);
}
}
}
}
});
}
} else {
console.warn('getTransceivers undefined');
displayWarning('getTransceivers undefined, please upgrade your browser');
......
......@@ -534,19 +534,20 @@ func pushConns(c client) {
}
}
func (c *webClient) isRequested(label string) bool {
return c.requested[label] != 0
}
func addDownConnTracks(c *webClient, remote upConnection, tracks []upTrack) (*rtpDownConnection, error) {
requested := false
requested := make(map[string]uint32)
for _, t := range tracks {
if c.isRequested(t.Label()) {
requested = true
break
l, r := splitLabel(t.Label())
rate := c.requested[l]
if rate != 0 {
if requested[l] == 0 ||
(r <= rate && r > requested[l]) ||
(requested[l] > rate && r < requested[l]) {
requested[l] = r
}
}
}
if !requested {
if len(requested) == 0 {
return nil, nil
}
......@@ -556,7 +557,8 @@ func addDownConnTracks(c *webClient, remote upConnection, tracks []upTrack) (*rt
}
for _, t := range tracks {
if !c.isRequested(t.Label()) {
l, r := splitLabel(t.Label())
if r != requested[l] {
continue
}
_, err = addDownTrack(c, down, t, remote)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment