vvidic / mjpeg-proxy

Republish a MJPEG HTTP image stream using a server in Go
GNU General Public License v3.0
22 stars 15 forks source link

Reduce tcp buffer size to keep viewers with slow connection real time (sending less FPS) #4

Closed BigNerd95 closed 3 years ago

BigNerd95 commented 3 years ago

Hi! Congrats for this small but very useful project.

It was exactly what I was looking for: pub/sub mjpeg proxy. I made a similar solution some years ago in python for raspberry picamera https://github.com/BigNerd95/picamera/commit/f723b0832f154404c186017a13bf7a14dd48d36b#diff-9dc379f8ac02d2f947ab8b7e67be6877ae0ca646f31a475b0169867e105f91edR112

Anyway my question is:

If a client has a very slow connection, this method sends less frame than the original FPS (which is what I want), but due to tcp buffer (I think) the stream is not real time, because even if you use unbuffered channel, the thread sending frame to a client is not blocking during the tcp send, so it will keep reading frame from the publisher channel until the tcp buffer is full. Only then it will start to drop frame.

I think a possible solution is to reduce the tcp buffer size to a size similar of a single frame, so it will start dropping frame as soon as possible if the viewer connection is too slow to handle the original FPS. Do you have other ideas?

kescherCode commented 3 years ago

One possibility at the top of my head would be to recognize whether a client has downloaded the last frame it was sent yet, and if not, put the next fully available frame into the stream. This could be a pointer to a chunked frame.

vvidic commented 3 years ago

On Fri, Nov 13, 2020 at 03:04:13AM -0800, Jeremy Kescher wrote:

One possibility at the top of my head would be to recognize whether a client has downloaded the last frame yet, and if not, put the next fully available frame into the stream.

I need to test this, but skipping frames should happen if client send is blocking:

func (pubSub *PubSub) doPublish(data []byte) {
    subs := pubSub.subscribers

    for s := range subs {
        select {
        case s.ChunkChannel <- data: // try to send
        default: // or skip this frame
        }
    }
}

-- Valentin

BigNerd95 commented 3 years ago

You can reproduce this by changing "Online" to "Slow 3G" in Chrome Developer Tools > Network. Then add a print in these places: https://github.com/vvidic/mjpeg-proxy/blob/master/mjpeg-proxy.go#L412 https://github.com/vvidic/mjpeg-proxy/blob/master/mjpeg-proxy.go#L419

The server block during part.Write (which is correct)

But how you can see by this log, it block after a lot of frame were sent.

chunker[/source3]: started
Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send 

# here stop a few seconds (on part.Write)

Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send 

# here stop a few seconds

Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send 

# here stop a few seconds

Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send 

# here stop a few seconds

Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send 

# here stop a few seconds

Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send 

# here stop a few seconds

Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send 

# here stop a few seconds

Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send Send 

# ...

Each Send is about 36452 bytes

Note: the first group of Send is much longer than the following ones

So it seems to be filling up an underlying buffer of 8 MB and then block until a 1 MB is free

BigNerd95 commented 3 years ago

I just tried with another computer instead of localhost. Now it sends only 6 frame and then block. So maybe there is a buffer only when using localhost?

Anyway, I set 1kb/s on the receiving host, but it would be better if it only sends 1 or max 2 frame.

vvidic commented 3 years ago

On Fri, Nov 13, 2020 at 06:36:37AM -0800, Lorenzo Santina wrote:

I just tried with another computer instead of localhost. Now it sends only 6 frame and then block. So maybe there is a buffer only when using localhost?

Anyway, I set 1kb/s on the receiving host, but it would be better if it only sends 1 or max 2 frame.

I did some testing and it seems to behave relatively well already. TCP buffer on a real (not simulated) slow connection should be small and not too many frames should be sent at once.

-- Valentin