Thursday, August 12, 2021

Converting individual frames from an HTTP source into MJPEG

Using the same cgi-bin setup as in a couple of posts earlier, this script will repeatedly grab a still frame from a camera, and turn it into a MJPEG stream.

I got myself another camera that claimed to support MJPEG, but it turned out that is what the manufacturer was calling repeatedly fetching individual JPEG frames. Not a neverending stream of them in a single request. *sigh*.

Anyway, we can fix that...

#!/bin/bash

echo "Content-Type: multipart/x-mixed-replace;boundary=boundary12345"
echo "Cache-Control: no-cache"
echo ""
while :
do
    echo "--boundary12345"
    curl -is "http://admin:SECRET@192.168.60.15/mjpgstreamreq/1/image.jpg"
    sleep 0.2
done

Adjust the user:password to suit your device, along with the URL for getting a frame.

This gives a 5 FPS video output. Note that sub-second sleep may not be available on your platform, in which case make it "sleep 1" or find another way to have a shorter day.

It is also intentional that the content-type boundary definition doesn't have the double dash "--", while in the stream it does.

The CPU load of this is very low, as its just copying data without any processing in between.

Wednesday, July 21, 2021

Solidworks macro script to save current document as STL

Tools > Macro > New, then copy in this script:

Dim swApp As Object
Dim Part As Object
Dim boolstatus As Boolean
Dim longstatus As Long, longwarnings As Long
Dim path As String
Dim i As Integer

Sub main()
Set swApp = Application.SldWorks
Set Part = swApp.ActiveDoc
path = Part.GetPathName
i = InStrRev(path, ".")
If (i = Null Or i = 0) Then i = Len(path)
path = Left(path, i) + "stl"
longstatus = Part.SaveAs3(path, 0, 2)
MsgBox ("Export to STL Done : " + Str(longstatus))
End Sub

Then you can add it to your toolbar via  Right-click on toolbar > Commands > New Macro Button (note the "button", don't confuse with "New Macro"), then choose an icon and the macro file you saved from above.

This will save your current file in the same folder with an "stl" extension, overwriting anything already there. If you find it only exports part of your model, you may need to click to deselect anything in the document or tree to get it to save anything. (I think its defaulting to "selected bodies" rather than "all".

Works for exporting, at least, parts and assemblies.

Sunday, July 18, 2021

Converting RTSP to an MJPEG stream on demand using ffmpeg

Background

I'm using a cheap Chinese IP Camera that only has an RTSP stream to monitor my 3D printer via Octoprint/Octoapp, which requires a MJPEG stream over HTTP.

This approach uses lighttpd and cgi-bin as the server. Similar could be done using nginx + fastcgi, or in python -- perhaps even an Octoprint plugin. You might be able to get systemd start ffmpeg directly, but I had trouble getting the output stream to be sent to the socket, and wasn't sure how to stop the process when the socket was closed.

As it converts between video formats it requires a bit of CPU power on the host, though that can be tuned by configuring the resolution and framerate of the original RTSP stream, as well as resizing the output stream if required.

A 1080p stream at 10FPS on an Intel Core i5-8259U uses about 5-10% CPU and consumes 256MB of RAM. This approach is not efficient, so if you have two devices viewing the stream then the CPU load is doubled. Once the web browser closes its connection, or the camera goes offline, the ffmpeg process does seem to reliably stop.

A tool like ONVIF Device Manager may help figuring out the RTSP stream URL if you can't find it in the documentation for your camera (look at the bottom of the "live video" screen).

Environment

Apart from getting the paths right to suit your setup, nothing here should be too specific to Linux or a particular distribution. This was done using:

  • Debian GNU/Linux 10 (buster)
  • lighttpd/1.4.53 (ssl)
  • ffmpeg version 4.1.6-1~deb10u1
  • CPU: Intel Core i5-8259U

lighttpd

Install lighttpd:

apt-get install lighttpd

Enable cgi-bin:

cd /etc/lighttpd/conf-enabled/
ln -s ../conf-available/10-cgi.conf

Configure the cgi-bin folder, and set lighttpd to not buffer the entire file before starting to send:

Alter /etc/lighttpd/conf-available/10-cgi.conf:

server.modules += ( "mod_cgi" )

$HTTP["url"] =~ "^/cgi-bin/" {
        server.stream-response-body = 2
        cgi.assign = ( "" => "" )
        alias.url += ( "/cgi-bin/" => "/var/www/cgi-bin/" )
}

Restart lighttpd to pick up the configuration change:

systemctl restart lighttpd

Create the cgi-bin folder:

mkdir /var/www/cgi-bin

Scripts

Create the scripts that'll generate a single frame and stream, adjust the IP address and RTSP stream URL to suit your particular camera:

Create /var/www/cgi-bin/webcamframe:

#!/bin/bash

echo "Content-Type: image/jpeg"
echo "Cache-Control: no-cache"
echo ""
ffmpeg -i "rtsp://192.168.60.13:554/user=admin&password=SECRET&channel=1&stream=0.sdp" -vframes 1 -f image2pipe -an -

Create /var/www/cgi-bin/webcamstream:

#!/bin/bash

echo "Content-Type: multipart/x-mixed-replace;boundary=ffmpeg"
echo "Cache-Control: no-cache"
echo ""
ffmpeg -i "rtsp://192.168.60.13:554/user=admin&password=SECRET&channel=1&stream=0.sdp" -c:v mjpeg -q:v 1 -f mpjpeg -an -

Make the two scripts executable (otherwise you'll get a 500 Internal Server Error)

chmod +x /var/www/cgi-bin/webcamframe
chmod +x /var/www/cgi-bin/webcamstream

Complete

Now you should be able to access these two URLs on your server:

http://192.168.60.10/cgi-bin/webcamstream
http://192.168.60.10/cgi-bin/webcamframe

Both of these take 1-2 seconds to start, which I think is a combination of getting the RTSP stream going, and an inherent delay in generating the MJPEG output. Once it is running, there is about a one second delay on the video stream, which I gather is normal for ffmpeg generating MJPEG.

Troubleshooting

If you get distorted/smeared/artifacts on the output stream, try adding at the start of the ffmpeg arguments -rtsp_transport tcp  to force RTSP over TCP. Apparently there may be a UDP buffer issue in ffmpeg and/or Linux that can cause frames to get truncated. Other options to try out are here.

You can troubleshoot the scripts on the command line like this, which will let you see the output of ffmpeg and the start of what is sent to the web client:

cd /var/www/cgi-bin
./webcamframe | hd | head

Sample output of it working correctly is shown below. The conversion and pipe errors are OK, they are just due to head stopping the output early.

 

Extra Reading

Also have a look a this post on how to take individual JPG frames an turn them into MJPEG.