Monitoring a video live stream

For some unknown reason I have a rare problem with one of my video sources presented in the dashboard that sometimes simply stops or freezes. So I'm looking for best way or method how I can check that the video stream "is alive". What I have come up with so far is a solution checking incoming frames but if there is another better way, I would gladly change to that

In the current monitoring solution as seen below, I use ffmpeg to simply stream the video. As long as there are incomming frames (at a rate of 1/second) everything is considered ok and working. If the video stream would stop, the video application needs to be restarted (first stopped after 10 seconds and then started again after another 5). After another five seconds, the ffmpeg monitoring is restarted. In the flow I also have some other supporting nodes for various purposes

The concept works, I have tested it successfully, but it would be interesting if there are any other ways to monitor, i.e. eliminating the need to use ffmpeg just for this purpose

I am not clear on this part. You want to eliminate ffmpeg or the extra nodes for monitoring and restarting it?

If you plan on keeping ffmpeg, then you may be able to use -stimeout on the input to kill ffmpeg if it does not receive input video in a specified amount of time. You can then catch the exit status and restart the node. I do this with my rtsp video inputs and it works good to keep everything running. If you really don’t need it to output any video, then you can probably write it to dev/null.

1 Like

Hi Kevin, thanks!
Need to try that, could be the solution! What I want is just to be sure the video source is producing frames and if stops, I need to action. In one way or another. To get a yes/no so that I can take action if the frames stop coming in. It's ok to keep ffmpeg and catching the exit status wwould be fine as well as writing to dev/null. With your suggestions I do not need to stream video frames the whole way thrue the remaining of the flow. Thanks for your suggestions, I'll try more tomorrow

1 Like

I ran a little experiment with ffmpeg to test the performance of dumping the video and to only use it for monitoring purposes. If I set the output muxer to null (-f null) without specifying -c copy, then it was a high cpu load because it was unnecessarily causing my rtsp stream to be decoded. The best option was to stream copy the input, tell ffmpeg to keep it as null format, then write the output to null. Also, have to use -y because ffmpeg thinks that it is overwriting an existing file @ /dev/null.
-c copy -f null /dev/null -y

I cant tell what type of input you have based on the screenshot, so my example may not work for you.

My flow sets -stimeout to 10 seconds. After ffmpeg is running and connected to the stream, I log into my cam and reboot it, simulating a loss of video(could have just pulled the ethernet). After about 10 to 20 seconds, ffmpeg exits and it triggers restart/delay cycle.

Here is an example flow to monitor an rtsp stream:

[{"id":"4558af8e.5f86f","type":"ffmpeg-spawn","z":"54a2b510.4696dc","name":"","outputs":3,"migrate":2e-9,"cmdPath":"","cmdArgs":"[\"-y\",\"-loglevel\",\"error\",\"-stimeout\",\"10000000\",\"-rtsp_transport\",\"tcp\",\"-i\",\"rtsp://admin:pass@\",\"-c\",\"copy\",\"-f\",\"null\",\"/dev/null\",\"-progress\",\"pipe:1\"]","cmdOutputs":2,"killSignal":"SIGTERM","msgOutput":"split","x":340,"y":1820,"wires":[["d85bcb3d.d51f68","aac1947b.a452e8"],["d7814fe.72c2ab"],["c2ff0e3d.43b21"]]},{"id":"d7814fe.72c2ab","type":"function","z":"54a2b510.4696dc","name":"progress","func":"const props = msg.payload.toString().split('\\n');\n\nconst progress = {};\n\nprops.forEach(item => {\n    \n    const [name, value] = item.split('=');\n    \n    if (name && value) {\n    \n        progress[name] = value;\n\n    }\n    \n});\n\nconst color = progress['progress'] === 'continue' ? 'green' : 'red';\n\nconst message = `fps: ${progress['fps']}, bitrate: ${progress['bitrate']}`;\n\nnode.status({ fill: color, shape: 'dot', text: message });\n\nreturn { payload: progress };","outputs":1,"noerr":0,"initialize":"","finalize":"","x":560,"y":1820,"wires":[["8b7f4604.450278"]]},{"id":"c2ff0e3d.43b21","type":"function","z":"54a2b510.4696dc","name":"errors","func":"const { payload } = msg;\n\nreturn { payload: payload.toString() };","outputs":1,"noerr":0,"initialize":"","finalize":"","x":550,"y":1900,"wires":[["7ea4919c.6059b"]]},{"id":"2af851ab.af730e","type":"inject","z":"54a2b510.4696dc","name":"start","props":[{"p":"action","v":"{\"command\":\"start\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":"5","topic":"","payloadType":"str","x":110,"y":1800,"wires":[["4558af8e.5f86f"]]},{"id":"150169af.e8c486","type":"inject","z":"54a2b510.4696dc","name":"stop","props":[{"p":"action","v":"{\"command\":\"stop\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"str","x":110,"y":1840,"wires":[["4558af8e.5f86f"]]},{"id":"8b7f4604.450278","type":"debug","z":"54a2b510.4696dc","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":1820,"wires":[]},{"id":"7ea4919c.6059b","type":"debug","z":"54a2b510.4696dc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":1900,"wires":[]},{"id":"d85bcb3d.d51f68","type":"function","z":"54a2b510.4696dc","name":"restart","func":"const { payload = {} } = msg;\n\nconst { status, killed } = payload;\n\nif (status === 'close' && killed === false) {\n    return { action: { command: 'start' }, delay: 5000 };\n}\n\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":270,"y":1740,"wires":[["77ede303.5482ac"]]},{"id":"77ede303.5482ac","type":"delay","z":"54a2b510.4696dc","name":"delay","pauseType":"delayv","timeout":"1","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":397,"y":1740,"wires":[["4558af8e.5f86f"]]},{"id":"94085554.6cabf8","type":"exec","z":"54a2b510.4696dc","command":"top -b -d 5 -p","addpay":true,"append":"","useSpawn":"true","timer":"","oldrc":false,"name":"","x":630,"y":1736,"wires":[["36e00db.c8505f2"],[],[]],"l":false},{"id":"36e00db.c8505f2","type":"function","z":"54a2b510.4696dc","name":"%CPU, %MEM","func":"const { payload } = msg;\n\nif (typeof payload === 'string') {\n    \n    const lines = payload.toString().trim().split(/[\\n]+/);\n    \n    if (lines.length >= 2) {\n        \n        const labels = lines[lines.length - 2].trim().split(/[\\s]+/);\n\n        const values = lines[lines.length - 1].trim().split(/[\\s]+/);\n        \n        const payload = {};\n        \n        for (let i = 0; i < labels.length; ++i) {\n            payload[labels[i]] = values[i] || '0.0';\n        }\n\n        const message = `%CPU ${payload['%CPU']}, %MEM ${payload['%MEM']}`;\n\n        node.status({ fill: 'green', shape: 'dot', text: message });\n\n        return { payload };\n    }\n\n}\n\nnode.status({});\n\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":756,"y":1723,"wires":[[]]},{"id":"aac1947b.a452e8","type":"function","z":"54a2b510.4696dc","name":"","func":"const { payload = {} } = msg;\n\nconst { status, pid } = payload;\n\nif (status === 'spawn') {\n    \n    return { payload: pid };\n    \n} else if (status === 'close') {\n    \n    return { payload: 'kill', kill: 'SIGTERM'};\n    \n} else {\n    \n    return null;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":570,"y":1736,"wires":[["94085554.6cabf8"]],"l":false}]


Excellent!!!! Again :wink:
Working just perfect! Also with my video source. I just modified the params a bit since the source is a web server (Motion) sending jpegs

So in my case, this works fine

The cpu load caused by ffmpeg is now down to 0.3%. Thats very good! Indeed a nice recipe how to monitor video live streams!

ffmpeg -y -re -i -c copy -f null /dev/null -progress pipe:1

Thats great news. Also, you don't need to output the progress to pipe:1. That was just to illustrate additional monitoring options.

Hi Kevin, yes, understood, but I use that as a "kind of heartbeat" to feed my flow, to keep a trigger node happy so to say

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.