
For several years I used Vstarcam software (IP Camera Super Client) on Windows to record video stream from my cameras. This software (I preferred specific version 1.1.4.557) is quite reliable, though it produces video files that can’t be played by other video players. You can watch them only using this application.
It was also painful to restore archived video after reinstallation of the system or after changing the directory to which files are recorded.
After some time problems, which were caused by this software, became much significant than conveniences and I decided to move all recording processes to dedicated Ubuntu 16.04 server.
Unfortunately, it turned out that streams from my cameras, after they were recorded by direct copying using ffmpeg, can’t be played by third-party players such as MPC-HC or VLC ( players actually can’t seek in these files ), so I couldn’t just write stream but needed to recompress it.
At first I thought it is impossible to recompress three 720p streams on the fly, but I was wrong
For the purpose of video recording I have very interesting machine – Asus EB1505. It’s a nettop with low-end Celeron 847 CPU, but considering the price by which I have gotten it (50$ including 500GB HDD and 2GB RAM) and the possibility to install up to 16GB RAM into 2 slots (I tested 12GB) and the possibility to install two hard drives (not mentioning external eSATA) this is a very interesting device, though its CPU frequency 1.1GHz is somewhat low.
Anyway, I think this CPU is much better than any old or new Atom and the level of extensibility of this device is unusually high for nettop.
I have been cursing Asus for its crappy EeePC 701 and Fonepad 7″, which I have unluckily stumbled upon, but EB1505 is very interesting device at least for 50$
Celeron 847 has integrated Intel HD Graphics with hardware h264 decoder. It is important. I spent quite a bit time digging through ffmpeg docs and the Internet before I understood that this CPU has decoder only.
Hardware acceleration for video in Intel CPUs is called QuickSync and it seemed that HD Graphics 3000 should have hardware h264 encoding but it turned out that QuickSync in Celerons is somewhat abridged.
Anyway, I grateful even for hardware decoding because it reduces CPU usage for about 30%.
Later I tried to move my recording facility on a laptop with Radeon Xpress 1250 integrated video, but I failed. I couldn’t find any support for hardware acceleration in ffmpeg for this graphics card at all.
Even user interface on desktop linux was glitchy on that laptop, so old cheap AMD video cards are supported very poorly.
Moving back to the topic.
Here is my steps:
1) Install ffmpeg (I think that standard installation from official repo will be sufficient, though I have installed and checked a number of PPAs and even compiled ffmpeg myself in violent attempts to use hardware encoding where it is absent. So I am not quite sure which ffmpeg version I use now…).
Upon installation check your hardware video acceleration capabilities:
# vainfo error: XDG_RUNTIME_DIR not set in the environment. error: can't connect to X server! libva info: VA-API version 0.39.4 libva info: va_getDriverName() returns 0 libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/i965_drv_video.so libva info: Found init function __vaDriverInit_0_39 libva info: va_openDriver() returns 0 vainfo: VA-API version: 0.39 (libva 1.7.3) vainfo: Driver version: Intel i965 driver for Intel(R) Sandybridge Mobile - 1.7.0 vainfo: Supported profile and entrypoints VAProfileMPEG2Simple : VAEntrypointVLD VAProfileMPEG2Main : VAEntrypointVLD VAProfileH264ConstrainedBaseline: VAEntrypointVLD VAProfileH264Main : VAEntrypointVLD VAProfileH264High : VAEntrypointVLD VAProfileH264StereoHigh : VAEntrypointVLD VAProfileVC1Simple : VAEntrypointVLD VAProfileVC1Main : VAEntrypointVLD VAProfileVC1Advanced : VAEntrypointVLD VAProfileNone : VAEntrypointVideoProc |
This command tests VA-API capabilities and string like “VAProfileH264Main : VAEntrypointVLD” means that hardware is able to decode video with this profile.
If I had a GPU with encoding capabilities I would see lines similar to “VAProfileH264Main : VAEntrypointEncSlice”.
As you can see, my hardware have no any encoding capabilities so only software encoding is available.
2) next step is to ensure that the user, who will run ffmpeg, has access to hardware encoding device /dev/dri/renderD128
To do so your user has to belong to group video.
Personally I needed to do system restart after this manipulation or maybe reboot is all that were needed after installation of ffmpeg
3) next you can try some variations of ffmpeg command from terminal to ensure that everything works as it should. That hardware acceleration works, that ffmpeg is able to catch the stream from you camera and so on. You also have to choose the balance between quality+compression level and CPU resources according to this page
4) Personally I use ffmpeg segmenter to create 30min video files from live h264 stream. Unfortunately there is a slight loss of frames during the switching to the new segment, but it seems to be less than one second.
I use command like this:
– hardware decoding
– RTSP source
– h264 encoding with superfast preset and rate factor of 36 which gives me about 1.5 – 2 GB video data per day from one camera.
– use segmenter
– use internal strftime function
– segment size 30min
– reset timestamps from stream
– save to mp4 file with name that includes camera’s id and date.
# ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -i rtsp://admin:888888@192.168.100.102:10554/udp/av0_0 -an -codec:v libx264 -preset superfast -crf 36 -f ssegment -strftime 1 -segment_time 1800 -reset_timestamps 1 /HDD/CamVideo/CAM02/cam2_%Y-%m-%d_%H-%M-%S.mp4 # |
5) I arranged my ffmpeg recording commands as systemd services (one per stream) so they can be automatically started after system startup and restarted after ffmpeg crash (it crashes about 10 times per day in average mostly because it is unable to catch the stream.). It is also convenient, that after correct stopping or restarting of such a service, ffmpeg closes current video file properly so it can be played.
Example of the daemon for one stream:
Perl script
#! /usr/bin/perl use strict; use utf8; use IO::File; $SIG{__WARN__} = \&alert_sysadmin; $SIG{__DIE__} = \&alert_sysadmin; $SIG{HUP} = \&do_reload; my $PIDFILE = '/tmp/camrec02.pid'; open(STDIN, '</dev/null'); open(STDOUT, '>/dev/null'); open(STDERR, '>&STDOUT'); chdir '/path/to/daemon'; my $pidfh = IO::File->new($PIDFILE, O_WRONLY|O_CREAT|O_EXCL, 0644); if($pidfh){ print $pidfh $$; close $pidfh; }else{ alert_sysadmin("first open of the pidfile failed $!"); open my $pidfile, '<', $PIDFILE or die "unable to open existing pid file for reading"; my $pid = <$pidfile>; if( $pid =~ /^\d+$/){ if(kill(0, $pid)){# process exists alert_sysadmin("daemon process already exists"), die; }else{# process doesn't exists create_pid_file(); } }else{# file corrupted create_pid_file(); } } alert_sysadmin('daemon started'); my $r = `/usr/bin/ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -i rtsp://admin:888888@192.168.100.102:10554/udp/av0_0 -an -codec:v libx264 -preset superfast -crf 36 -f ssegment -strftime 1 -segment_time 1800 -reset_timestamps 1 /HDD/CamVideo/CAM02/cam2_%Y-%m-%d_%H-%M-%S.mp4`; sub do_reload{ alert_sysadmin("reload requested"); unlink $PIDFILE; exec './camrec02.pl'; } sub alert_sysadmin{ my $message = shift; system('/bin/jabbersend.sh', "camrec stream 02 ALERT: $message"); } sub create_pid_file{ unlink $PIDFILE or die "unable to unlink old pidfile"; my $pidfh = IO::File->new($PIDFILE, O_WRONLY|O_CREAT|O_EXCL, 0644); die "unable to create pidfile exlusively" unless $pidfh; print $pidfh $$; close $pidfh; alert_sysadmin("pidfile recreated"); } |
To make this systemd service you should create a unit file in /etc/systemd/system/camrec02.service
[Unit] Description=CAM2 stream recorder After=syslog.target After=network.target [Service] Type=simple PIDFile=/tmp/camrec02.pid WorkingDirectory=/path/to/daemon User=myusername Group=myusername OOMScoreAdjust=-100 ExecStart=/path/to/daemon/camrec02.pl ExecStop=/bin/kill -TERM $MAINPID ExecReload=/bin/kill -HUP $MAINPID TimeoutSec=15 RestartSec=15 Restart=always [Install] WantedBy=multi-user.target
and then
systemctl enable camrec02 systemctl start camrec02 systemctl status camrec02
6) I’ve made a few auxiliary cron-scripts that control the process of recording.
– script that moves records from the previous day to the specific folder (daemon writes video chunks to the root of the /HDD/CamVideo/$CAMID folder and then this script moves these chunks to /HDD/CamVideo/$CAMID/PreviousDate)
this script also deletes folders older than 35 days to remove old records
It runs once per day at night.
Bash script
#! /bin/bash PREV_DATE=$(date -d "yesterday 13:00" '+%Y-%m-%d') for CAMID in CAM01 CAM02 CAM03 do cd "/HDD/CamVideo/$CAMID" mkdir $PREV_DATE if [ $? -eq 0 ]; then find ./ -maxdepth 1 -type f -daystart -mtime 1 -exec mv {} ./$PREV_DATE \; find ./ -maxdepth 1 -type d -ctime +35 -exec rm -rf {} \; else echo "unable to create directory /HDD/CamVideo/$CAMID for video files" exit 1 fi done |
– script that checks that recording process is alive (two types of checks)
it runs each 5 minutes
Bash script
#! /bin/bash for CAMID in CAM01 CAM02 CAM03 do ERROR=0 cd "/HDD/CamVideo/$CAMID" # there must be a file that has been modifyed during the last minute FINDOUT=`find ./ -maxdepth 1 -type f -mmin -1 -print` if [ -z "$FINDOUT" ]; then /bin/jabbersend.sh "$CAMID => no live file. daemon must be restarted" ERROR=1 fi # files older that 10 minutes must be at least 2MB, or there is no actual record is going FINDOUT=`find ./ -maxdepth 1 -type f -size -2000k -mmin +10 -print` if ! [ -z "$FINDOUT" ]; then /bin/jabbersend.sh "$CAMID => there is an old files with size less than 2MB" ERROR=2 fi if [ $ERROR -gt 0 ]; then CAMNUM=`echo $CAMID | grep -Po '\d+'` /bin/systemctl stop camrec$CAMNUM if [ $ERROR -eq 2 ]; then find ./ -maxdepth 1 -type f -size -2000k -delete fi /bin/systemctl start camrec$CAMNUM fi done |
7) All records are now accessible on the LAN via SMB protocol and can be viewed using standard player from each computer on local network which is very convenient to me, though rarely some records are corrupted and my auxiliary scripts can’t prevent all possible errors.
With my parameters for ffmpeg I have a load average about 1.3 but it depends…
If there is a lot of chaotic movement on the camera (a noise in a low-light conditions for instance) – one camera can raise CPU usage up to 2.0 which is dangerous condition for my 2-core CPU.
Update 2018-09-14
8) After a few months of recording RTSP streams from my cameras using ffmpeg I noticed one more glitch that should be taken care of.
About once a day one of the recording processes begins to work improperly. It doesn’t actually record anything and consumes high amount of CPU time.
Usually CPU load by such a process is above 90% (my system allows CPU load up to 200%).
Because this process doesn’t record any file it will be killed as soon as one of the mentioned above watchdogs notice that video files are too small, or maybe it will be restarted together with the beginning of the new ffmpeg segment. Anyway, it is too much time to keep my server overloaded.
This is why I was forced to write one more watchdog service…
This watchdog script checks CPU load for particular processes. I decided to make a check once in 10 seconds but it could be adjusted.
Script works as a daemon so it could remember previous measurements for each process in which we are interested.
If some quantity (in my case – five) measurements in a row for certain process are higher than selected threshold (90% in my case) – the process is restarted.
The structure of the daemon is the same as in paragraph (5), so I show here only the main cycle and necessary variables.
Perl script
#! /usr/bin/perl our $THRESHOLD = 90; # threshold system load in percent our $QUEUE_SIZE = 5; # how many consecutive records we consider as failure our $QUEUE_POS = 0; # current position in cyclic queue our $CHECK_TIMEOUT = 10; # timeout in seconds between sequential checks our $watch_queues = { "camrec01" => { # arbitrary name for observed process command => "top -c -b -n1 -w1024| grep 192.168.100.101 | grep ffmpeg", # command to select needed process from the 'top' command queue => [(0) x $QUEUE_SIZE] # initialize queue for this process }, "camrec02" => { command => "top -c -b -n1 -w1024| grep 192.168.100.102 | grep ffmpeg", queue => [(0) x $QUEUE_SIZE] }, "camrec03" => { command => "top -c -b -n1 -w1024| grep 192.168.100.103 | grep ffmpeg", queue => [(0) x $QUEUE_SIZE] }, }; while(1){ foreach my $rec_proc( keys %{$watch_queues}){ # call appropriate command, remove leading spaces, split it by spaces and so get 'top'-command columns my($pr_pid, $pr_user, $pr_prio, $pr_nice, $pr_virt, $pr_res, $pr_shr, $pr_s, $pr_cpu, $pr_mem, $pr_time) = split(/\s+/, `$$watch_queues{$rec_proc}{command}` =~ s/^\s+(.*)/$1/r); $$watch_queues{$rec_proc}{queue}[$QUEUE_POS] = $pr_cpu; # record gathered cpu load in queue my $threshold_reached = 1; foreach (@{$$watch_queues{$rec_proc}{queue}}){ # compare each queue's value to threshold $threshold_reached = 0 if $_ < $THRESHOLD; } if($threshold_reached){ # all sequential measuremens yeild high cpu usage alert_sysadmin("restarting daemon $rec_proc load records are " . join(' ', @{$$watch_queues{$rec_proc}{queue}})); `/bin/systemctl restart $rec_proc`; # issuing command to restart service which gone nuts sleep(1); $_ = 0 foreach(@{$$watch_queues{$rec_proc}{queue}}); # clear measurement queue to start new series of measurements my $res = `/bin/systemctl status $rec_proc | grep Active`; # checking service uptime to be sure that it has been restarted alert_sysadmin("result: $res"); } } $QUEUE_POS = 0 if ++$QUEUE_POS >= $QUEUE_SIZE; sleep($CHECK_TIMEOUT); } |
1 Comment
Thanks for useful article