WebRTSP / ReStreamer

Media URLs ReStreamer and Cloud DVR
GNU General Public License v3.0
32 stars 6 forks source link

Streaming Capacity #7

Closed rbr-goodrich closed 2 years ago

rbr-goodrich commented 3 years ago

Hello,

I was wondering what the Maximum Simultaneous Stream capacity is for this project ?

I've modified the browser client to produce a feed wall instead of showing just a single feed, but the performance is severely degraded after 4+ feeds being shown at the same time. (Meaning there is a long initial connection time, and choppy feeds once they finally show up)

I know its not a bandwidth issue as i have 400MB/s upload. I also have a custom stun/turn server, but using the default google one shows similar results.

Do you experience similar limitations ?

rbr-goodrich commented 3 years ago

This is what i've done to the browser client code.

I either am messing up by calling the "connect" function to many times, or this backend server simply cant handle 12 Simultaneous Feeds.

`


<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <title>WebRTSP</title>
    <link rel="stylesheet" href="css/WebRTSP.css">
    <script type="text/javascript" src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
    <script type="text/javascript" src="Config.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/spin.js/4.1.0/spin.css">

<style>
@keyframes spinner-line-fade-more {
  0%, 100% {
    opacity: 0; /* minimum opacity */
  }
  1% {
    opacity: 1;
  }
}

@keyframes spinner-line-fade-quick {
  0%, 39%, 100% {
    opacity: 0.25; /* minimum opacity */
  }
  40% {
    opacity: 1;
  }
}

@keyframes spinner-line-fade-default {
  0%, 100% {
    opacity: 0.22; /* minimum opacity */
  }
  1% {
    opacity: 1;
  }
}

@keyframes spinner-line-shrink {
  0%, 25%, 100% {
    /* minimum scale and opacity */
    transform: scale(0.5);
    opacity: 0.25;
  }
  26% {
    transform: scale(1);
    opacity: 1;
  }
}

video {
    z-index: -1;
}

body{
      overflow: hidden;        
}

.fullScreenSwal{
    height:720px;
}

.camDivs{
  display:inline-block; 
  margin:0; 
  padding: 0 0;
}

.howdy > video{
        float:left; 
}

.banner {
    display: none;
}

video:hover + .banner, .banner:hover {
    display: inline-block;
}

video::-webkit-media-controls-timeline {display: none;}

</style>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div id="mainCont" class="howdy"></div>

<script>

entireScreenWidth = screen.width
d4Width = $("#mainCont").width()
var fswiwidth = d4Width;
var fswiheight = d4Width;

function float2int (value) {
    return Math.abs(value | 0);
}

var cams = ["Bars","Price Center Plaza","Konský Grúň","Štrbské pleso", "Curacao", "Bedford Hills", "Norwich", "Montana", "Western Cape", "Nordland"] ;
var numCams = cams.length - 1;
var cam_expand = numCams;
let server = null;
if(window.location.protocol === 'http:'){
    server = `ws://${window.location.hostname}:${WebRTSPPort}/`;
}else{
    server = `wss://${window.location.hostname}:${WebRTSPPort}/`;
}

const stunServer =
    (typeof STUNServer === "string") ? STUNServer : "stun:stun.l.google.com:19302"

let streamersList = undefined;

function isOdd(num) { return num % 2;}

function camPop(amt, gen_info){
        var totals;
        if (amt == 0){
            totals = 2
        }
        else
        {
            totals = Math.sqrt(next_sq) *2;
        }
        if(amt > 0 && Math.sqrt(amt) % 1 === 0){
            var next_sq = amt;
        }
        else{
            var next_sq = Math.pow(Math.floor(Math.sqrt(amt))+1, 2);
        }
        totals = Math.sqrt(next_sq) *2;
    for(var i =0; i< next_sq; i++)
        {
        container = document.getElementById('mainCont');
        element = document.createElement('div');
        element.className = 'camDivs';
                var w = float2int(fswiwidth/Math.sqrt(next_sq));
                var h = float2int(fswiheight/totals);
                if(i < cams.length)
                {
                    element.id = "videoContainer"+i;
                    element.innerHTML = 
                    `<video controls controlsList="nodownload" id="video`+i+`" autoplay="true" muted="muted" style="width:`+w+`px; height:`+h+`px; object-fit: fill;" ></video>`;
             container.appendChild(element);
             camDivs = document.getElementsByClassName("camDivs");
             camDivs[i].style = "width:calc(100%/"+Math.sqrt(next_sq)+"); height:calc(100%/"+Math.sqrt(next_sq)+");";
                     onLoad(i, gen_info);
             let vid = document.getElementById('video'+i);
             let vidc = document.getElementById('videoContainer'+i);
                    vid.setAttribute("height",vidc.getBoundingClientRect().height);
                }
                else
                {
                    element.id = "videoContainer"+i;
                    element.innerHTML = 
                    `<video controls controlsList="nodownload" id="video`+i+`" autoplay="true" muted="muted" style="width:`+w+`px; height:`+h+`px; object-fit: fill;" ></video>`;
                    container.appendChild(element);
                    camDivs = document.getElementsByClassName("camDivs");
                    camDivs[i].style = "width:calc(100%/"+Math.sqrt(next_sq)+"); height:calc(100%/"+Math.sqrt(next_sq)+");";
                    let vid = document.getElementById('video'+i);
                    let vidc = document.getElementById('videoContainer'+i);
                    vid.height = vidc.getBoundingClientRect().height;
                }
        }
}

function connect(streamerName, webrtsp) {
  str = JSON.stringify(streamerName);
  if(streamerName)
  {
      document.title = streamerName;
  }
  webrtsp.connect(server, streamerName); 
}

async function onLoad(i, gen_info) {
        let Spin = await import("https://cdnjs.cloudflare.com/ajax/libs/spin.js/4.1.0/spin.min.js");
        let WebRTSP = await import("./WebRTSP.mjs");
        var webrtsp ;
        var videoContainer =
          document.querySelector("#videoContainer"+i);
        var videoElement =
          document.querySelector("#video"+i);
        contHeight = $(videoContainer).height();
        var opts = {
            lines: 6, // The number of lines to draw
            length: 0, // The length of each line
            width: 5, // The line thickness
            radius: 15, // The radius of the inner circle
            scale: 1, // Scales overall size of the spinner
            corners: 1, // Corner roundness (0..1)
            speed: 1.2, // Rounds per second
            rotate: 0, // The rotation offset
            animation: 'spinner-line-fade-quick', // The CSS animation name for the lines
            direction: 1, // 1: clockwise, -1: counterclockwise
            color: '#ffffff', // CSS color or array of colors
            fadeColor: 'transparent', // CSS color or array of colors
            top: String(contHeight/2) + "px", // Top position relative to parent
            left: '50%', // Left position relative to parent
            shadow: '0 0 1px transparent', // Box-shadow for the lines
            zIndex: 2000000000, // The z-index (defaults to 2e9)
            className: 'spinner', // The CSS class to assign to the spinner
            position: 'relative', // Element positioning
        };  

        var spinner = new Spin.Spinner(opts);

        webrtsp = new WebRTSP.WebRTSP(
          videoElement,
          [{
            urls: [stunServer]
          }]);

        webrtsp.events.addEventListener("list", (event) => {
          console.log(Array.from(event.detail.list.keys()))
          console.log(Array.from(event.detail.list.keys())[i], i)
          webrtsp.streamerName = Array.from(event.detail.list.keys())[i];
        });

        webrtsp.events.addEventListener("disconnected", (event) => {
          spinner.spin(videoContainer);
        });

        videoElement.addEventListener('playing', (event) => {
          spinner.stop()
        });

        spinner.spin(videoContainer);
        connect(null, webrtsp);
      }

camPop(numCams, cam_expand);

</script>
</html>

`

RSATom commented 3 years ago

I think the most possible reason is limited power of source you are trying to restream (i.e. source ip cam). The main problem of current implementation it creates separate connection to source for every restream session. I.e. if 10 users watch stream from the same ip cam - there will be 10 simulations connections to that ip cam. Right now I'm trying to solve this issue.

rbr-goodrich commented 3 years ago

So, If a config file has 10 different RTSP feeds in it. Viewing all 10 simultaneously like the client code posted above, will result in 10 connections to each RTSP feed, instead of one to each ?

RSATom commented 3 years ago

no, in this case there will be only one connection to each.

rbr-goodrich commented 3 years ago

Okay, that's what i thought.

I believe there may be an issue with streaming each RTSP Feed simultaneously. As viewing them one at a time is fine, and smooth. However trying to view each one simultaneously like the client code posted above results in a long load time (some do not load), and stale feeds.

rbr-goodrich commented 3 years ago

Unless you see a mistake in the way i request the feed on the client end, there must be some kind of limitation in the websocket server.

RSATom commented 3 years ago

Can you please check CPU load on host with browser and on host with restreamer?

rbr-goodrich commented 3 years ago

I've updated my example above, it is now plug and play replace index.html with it. You'll see what i mean.

I have 8 cpu cores ranging from 35-50% when serving and viewing on the same machine.

Your sources are fairing a bit better than my ip cams, but the effect still occurs.

Some do not load, and some are choppy, while others go in and out of service, but if you view them one by one like in your original index.html they all appear to be fine.

rbr-goodrich commented 3 years ago

I'd like to add one more thing.

This error, also occurs when viewing the feeds with the above client code.

[2021-09-28 18:50:41.410] [libwebsockets] [error] accept: errno 24

Followed by the inevitable crash with the message:

(ReStreamer:3694): GLib-ERROR **: 18:50:41.447: Creating pipes for GWakeup: Too many open files

You're getting these errors too right ? Or is it just me ?

The program crashes in a similar manner to this

I didn't see you using gst_bus_remove_signal_watch() to remove the bus signal anywhere.

Not sure, if this will improve the simultaneous live feeds. It should prevent the program from crashing though.

RSATom commented 3 years ago

Thank you for testing! I'll investigate it soon.

RSATom commented 3 years ago

@rbr-goodrich you was right. There was Unix socket leak from missing gst_bus_remove_watch. Thank you for hint!

RSATom commented 3 years ago

Also I've found mentions about possible limitations for amount of simultaneously playing videos on the same page: https://stackoverflow.com/a/26216249/3847832 Maybe it's your case.

rbr-goodrich commented 2 years ago

Excellent ! Glad to have assisted.

As for the multiple live stream issue, after many sanity checks i've boiled it down to a corporate firewall issue. The ReStreamer Server i'm testing is behind a symmetrical NAT, and for whatever reason is blocking more than 4 STUN requests to a remote client. The weird thing is, your sources are now playing just fine, i can stream all of them at once.

Its only when i connect to my IP cameras that i cannot remotely stream more than 4 at once. When i'm on the local network everything is fine, and all the sources play simultaneously.

I don't know why there is a difference, but it must be down the origin point, of the source that ReStreamer is serving to the client, or maybe i misconfigured my turnserver.

Unless you have any tips, i'm going to close the issue.

Thank you for fixing the socket leak, and feel free to include that feed wall as an example along with index.html !

RSATom commented 2 years ago

@rbr-goodrich could you please create PR to https://github.com/WebRTSP/www with your wall example? I just want save your copyright and confirm licence (WebRTSP/www defined as BSD 2-Clause). Thanks in advance!