How we decided to launch a system of video streaming
As in any other company, in ours there is some outdated equipment. It is quite workable, but cannot be used by employees. So, there are monitors, system units, and many other little computer parts like old-fashioned PS/2 mice and keyboards lying useless.
In this article we are going to talk about monitors we decided to use as an announcement board.
As the source of videos it was decided to use a Raspberry Pi.
Raspberry Pi is quite a weak hardware, but it suits our purpose well.
The Broadcom chip installed in the Raspberry Pi has hardware video acceleration support that allows you to play H.264 video with a bit rate of 40 MBits/s.
It's rather good for such a small board.
The first experience
Let's start with the story of the first experience of video streaming.
Well, not exactly video streaming, first it was just one monitor and one Raspberry Pi board.
There was nothing complicated: we installed a cross-platform media player, saved the video files on a flash drive and started their cyclic playback, perfect!
The screens displayed employees' current month's birthdays, and such information is quite useful at a company of our size.
There were also announcements of the upcoming events and photo and video records of them. In general, everyone liked the idea.
But there was a screen only on one floor of the office, the matter was we wanted to cover the entire audience. That's why a second monitor was bought and a second Raspberry board, and then we went through all the same steps again.
Everything worked fine, but we wanted more, besides, updating files on each screen separately was rather tiresome.
The improvement of the system
The idea was the following:
We set up the streaming from the server to all the screens, as a result, we get a centralized streaming, and on all screens the same information is always displayed.
In the previous version, there was mal-synchronization, because there was no simultaneous start, it was not very important while the screens were on different floors.
But at some point, there were multiple screens, and mal-synchronization started to be too obvious, which a perfectionist couldn't bear.
We had had experience setting streaming using VideoLAN, and we wanted to use it.
Thus, we set up the server on Debian Linux, installed VLC, placed video files on the server and started streaming.
Challenges
However, not everything started going smoothly right away, and in the process of introducing the video streaming system, we faced various challenges.
1. The cross-platform media player was not very convenient, we wanted a real Linux Way
The media player we use is a multimedia processor, it does a lot, but at the same time it consumes more resources, and we only need one function — to reproduce the video stream.
Since the media player turned out to be too bulky for the task, it was decided to use another media player.
For this purpose, the basic version of Debian Linux was installed on the Raspberry Pi.
2. Finding a suitable media player
To take full advantage of the video hardware acceleration, we took the OMXPlayer, because it is a console media player with OpenMAX hardware acceleration.
On the Raspberry Pi, the installation does not cause any problem, because there is a package in the default repository.
apt-get install omxplayer
After the installation, you can run "omxplayer –live udp://@:1234" and the player is ready to accept the stream.
3. Optimization of parameters
Since most of the RAM was freed up, there appeared a possibility to reallocate the physical memory to increase graphics memory.
For this you need to edit the file /boot/config.txt
Having added gpu_mem=256 to it
4. OMXPlayer is very sensitive to the parameters of the video stream
For example, it used to fall displaying a cheerful phrase "have a nice day", whenever the bit rate of the video stream was changed.
And those days were really "nice", because now we know the cause, but then it was a mystery.
We started preparing to stream video-files with the same settings, and it helped, streamings stopped interrupting.
But sometimes the human factor intervened, and the playlist had at least one file with parameters different from the others, and then all went wrong.
The final version of the system
Once again I wanted to change things for the better and prevent unpleasant surprises and the human factor.
The solution was found quickly, but getting from the idea to realization was not at all easy.
The solution itself was to make the video files with the same parameters converted completely automatically, without any human participation.
No, a human is still required, but only to put the playlist together and place the video files in the network folder.
Starting from this point on everything is done automatically.
At midnight when everyone is asleep, video-files start being converted and after the completion of the process the streaming restarts.
The streaming started, and the image appeared on the screen. It's probably the same feeling that the first television watchers experienced.
To implement the plan, the Bash script was written:
#!/bin/bash ### Path to FFmpeg FFMPEG=/usr/local/bin/ffmpeg ### Path to logo LOGO=/Converter_new/logo.png ### Work dirs without trailing slash INFILES=/Converter_new/videos OUTDIR=/Converter_new/videos_out TEMPDIR=/Converter_new/temp_out MD5DIR=/Converter_new/fingerprints ### Cleaning filenames rename "s/ /_/g" $INFILES/* rename "s/'//g" $INFILES/* rename "s/'//g" $INFILES/* rename "s/,//g" $INFILES/* rename "s/\!//g" $INFILES/* rename "s/\№//g" $INFILES/* rename "s/\050/_/g" $INFILES/* rename "s/\051//g" $INFILES/* rename "s/\.\././g" $INFILES/* rename "s/\._/_/g" $INFILES/* rename "s/__/_/g" $INFILES/* function converting { file_to_convert=$1 converted_file=$2 ### We want HD Ready (1280x720) frame_width=1280 frame_high=720 $FFMPEG -i $file_to_convert -i $LOGO -r 25 -codec:v libx264 -profile:v high -level 3.2 -preset veryslow -g 15 -crf 18 -pix_fmt yuv420p -maxrate 4000k -bufsize 2000k \ -filter_complex "[0:v]scale=iw*min($frame_width/iw\,$frame_high/ih):ih*min($frame_width/iw\,$frame_high/ih),pad=$frame_width:$frame_high:($frame_width-iw)/2:($frame_high-ih)/2[v1];[v1][1:v]overlay=main_w-overlay_w-10:10" \ -threads 0 -af dynaudnorm -c:a aac -ac 2 -ar 44100 -b:a 128k -y $TEMPDIR/$converted_file ### Save some disk space dd if=/dev/urandom bs=1 count=128 2>/dev/null | base64 -w 0 | rev | cut -b 2- | rev > $file ### Create MD5 hash for file md5sum $file_to_convert | cut -d " " -f1 > $TEMPDIR/$converted_file.md5 STREAMING_RESTART=1 } function find_converted { file_md5=$1 converted_file=$2 echo " Trying to find converted video..." for md5_file in $MD5DIR/* do FINDED=0 md5_search=`cat $md5_file` if [ "$file_md5" = "$md5_search" ] then echo " Lucky day! We finded it in $(basename "$converted_file")." tempfile=`echo $md5_file | awk -F/ '{print$(NF)}' | awk -F.md5 '{print($1)}'` cp $OUTDIR/$tempfile $TEMPDIR/$converted_file mv $md5_file $TEMPDIR/$converted_file.md5 FINDED=1 STREAMING_RESTART=1 break fi done } ### Take action on each file in INFILES path. ### Initializing variables i=1 CONVERTED=0 ### $file_to_convert store current file name wit absolute path. for file_to_convert in $INFILES/* do echo "Processing $(basename "$file_to_convert") file..." ### Does the file exist? if [ ! -f $file_to_convert ] then echo "ERROR: $(basename "$file_to_convert") does not exist - aborting" exit 1 fi ### Does the file non zero lenght? if [ -z $file_to_convert ] then echo "ERROR: $(basename "$file_to_convert") is empty - aborting" exit 1 fi ### Setting output filename format ### 001.mp4 002.mp4 003.mp4 etc. converted_file=$(printf "%0.3d.mp4" $i) i=$((i + 1)) ### Path to file's saved md5sum fingerprintfile=$MD5DIR/$converted_file.md5 ### Create the md5sum from the file to check in future file_md5=`md5sum $file_to_convert | cut -d " " -f1` ### Do we have allready an saved fingerprint of this file? if [ -f $fingerprintfile ] then ### Get the saved md5 saved_md5=`cat $fingerprintfile` ### Check if it's empty if [ -z $saved_md5 ] then echo "WARNING: The file is empty" saved_md5=0 fi ### Compare the saved md5 with the one we have now if [ "$saved_md5" = "$file_md5" ] then echo " File has not been changed" mv $fingerprintfile $TEMPDIR/$converted_file.md5 mv $OUTDIR/$converted_file $TEMPDIR/$converted_file else echo " File has been changed or renamed" find_converted $file_md5 $converted_file if [ $FINDED -ne 1 ]; then converting "$file_to_convert" "$converted_file" fi fi else find_converted $file_md5 $converted_file if [ $FINDED -ne 1 ]; then converting $file_to_convert $converted_file fi fi done echo "Moving converted files and md5 files." if [ "$(ls -A $MD5DIR)" ]; then echo "Take action $MD5DIR is not Empty" rm $MD5DIR/*.md5 fi if [ "$(ls -A $OUTDIR)" ]; then echo "Take action $OUTDIR is not Empty" rm $OUTDIR/*.mp4 fi mv $TEMPDIR/*.md5 $MD5DIR/ mv $TEMPDIR/*.mp4 $OUTDIR/ if [ $STREAMING_RESTART -eq 1 ]; then echo "Concatating converted files." for f in $OUTDIR/*; do echo "file '$file_to_convert'" >> $TEMPDIR/mylist.txt; done $FFMPEG -f concat -i $TEMPDIR/mylist.txt -c copy -y /Converter_new/All_in_one.mp4 rm $TEMPDIR/mylist.txt ### Stop streaming echo "Stopping streaming..." VLC_pid=`cat /var/run/vlc.pid` kill -9 $VLC_pid >> /dev/null 2>&1 rm -f /var/run/vlc.pid sleep 5 ### Start streaming echo "Starting streaming..." /usr/bin/vlc /Converter_new/All_in_one.mp4 --intf rc --rc-host 127.0.0.1:4567 --rc-fake-tty --loop --file-caching 0 --sout-keep --sout-mux-caching 0 \ --sout-udp-caching 0 --sout-udp-group 50000 --sout-ts-shaping 1000 --sout-ts-use-key-frames --no-drop-late-frames --no-skip-frames \ --sout "#gather:duplicate{\ dst=std{access=udp,mux=ts,dst=172.17.8.201:1234},\ dst=std{access=udp,mux=ts,dst=172.17.8.202:1234},\ dst=std{access=udp,mux=ts,dst=172.17.9.88:1234},\ dst=std{access=udp,mux=ts,dst=172.17.8.204:1234},\ dst=std{access=udp,mux=ts,dst=172.17.8.205:1234}}" >> /var/log/vlc.log 2>&1 & sleep 5 ps ax | grep "/usr/bin/vlc" | head -1 | awk -F " " '{ print $1 }' > /var/run/vlc.pid fi
We hope you will find this post about our experience useful.
Have you had experience of launching a video streaming system? Feel free to tell us in the comments!