├── .gitignore ├── LICENSE ├── README.md ├── audioDetection.pl └── example_openhab.png /.gitignore: -------------------------------------------------------------------------------- 1 | /blib/ 2 | /.build/ 3 | _build/ 4 | cover_db/ 5 | inc/ 6 | Build 7 | !Build/ 8 | Build.bat 9 | .last_cover_stats 10 | /Makefile 11 | /Makefile.old 12 | /MANIFEST.bak 13 | /META.yml 14 | /META.json 15 | /MYMETA.* 16 | nytprof.out 17 | /pm_to_blib 18 | *.o 19 | *.bs 20 | /_eumm/ 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, pvizeli 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of openHab-AudioDetection nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openHab-AudioDetection 2 | Audio voice/noice detection from IP camera feed. If a audio/voice detect it can also stream to a icecast server for playing with SONOS. 3 | 4 | It take very small CPU performance. On Raspberry Pi2 B run the scan with 1.6% CPU usage. If it detect a noise/voice in the audio stream they can encode it as mp3 and send that to icecast. This is optional but very usefully for some situation.The encoding process use 5-6% of cpu usage. 5 | 6 | I wrote this program for use openHab with a IP-Cam (D-Link 2332L) as baby monitor. 7 | 8 | ## Audio filtering 9 | Default it is optimize for human voice sound. It use a highpass filter with 300Hz and a lowpass filter from 2500Hz. This filter all frequenze with none human voice. Use this filter for reduce background noice. 10 | 11 | ## Audio silence detection 12 | It detects that the input audio volume is less or equal to a noise tolerance value for a duration greater or equal to the minimum detected noise duration. 13 | 14 | # Commandline 15 | 16 | Start process: 17 | ``` 18 | audioDetection.pl --start [--pidFile=/tmp/audioDetection.pid] [--pipeFile=/tmp/audioDetection.pid.pipe] [--logFile=/tmp/myDetection.log] [--ffmpegBin=ffmpeg] [--curlBin=curl] --openHabUrl=http://192.168.1.10:8080 --openHabItem=Baby_Alarm --ipCamUrl=rtsp://192.168.1.11:554/live1.sdp [[--intPort=9554] --iceCastUrl=icecast://user:pw@192.168.1.12:8000/cam_audio.mp3 [--iceCastLegacy] [--iceCastVol=2]] [--highpass=300] [--lowpass=2500] [--silenceDb=-30] [--silenceSec=20] 19 | ``` 20 | 21 | Stop process: 22 | ``` 23 | audioDetection.pl --stop [--pidFile=/tmp/audioDetection.pid] 24 | ``` 25 | 26 | ## Instructions 27 | - ```--openHabUrl``` URL to OpenHab web interface for rest API. 28 | - ```--openHabItem``` Name of switch item for trigger the alarm. 29 | - ```--ipCamUrl``` URL for cam live feed. It will ignore the video so you can use the live feed with low bandwidth. 30 | - ```--intPort``` Port for internal RTP streaming. Default port is 9554. 31 | - ```--iceCastUrl``` FFMPEG icecast URL. Is no URL defined, it'will not send the stream to icecast server. 32 | - ```--iceCastLegacy``` Is the icecast server older than 2.4, set this flag. 33 | - ```--iceCastVol``` Multi input volume with this value for output. 34 | - ```--highpass``` Cut all frequence lower than this value. Default is 300Hz. 35 | - ```--lowpass``` Cut all frequence higher than this value. Default is 2500Hz. 36 | - ```--silenceDb``` Noise tolerance value in Db. Default is -30Db. 37 | - ```--silenceSec``` Duration of the minimum detected noise time. Default is 20 Sec. 38 | - ```--sampleRate``` Scale up audio sample rate. For SONOS use minimal 16000 that is also the default value. 39 | - ```--pidFile``` Set the pid file for deamon. For multible instance use multible pid file. 40 | - ```--pipeFile``` Set the pipe file for IPC communication. Default is pidFile.pipe. 41 | - ```--logFile``` Use a logfile for debug ouput. If don't set a file (default) it dosn't log. 42 | - ```--start``` Start the program as daemon. 43 | - ```--stop``` Stop a running daemon. 44 | - ```--ffmpegBin``` Ffmpeg binary to use. Default is ffmpeg without a path. 45 | - ```--curlBin``` Curl binary to use. Default is curl without a path. 46 | - ```--version``` Print the version of the script. 47 | - ```--help``` Print this URL out. 48 | 49 | ## Troubleshoting 50 | If you have trouble please us the --logFile option to write a debug log. That will help in some cases. 51 | 52 | # Install 53 | This script use ffmpeg (not libav!) for audio analysing and streaming and curl for calling openHab rest API. Theoretical it work an all system they have perl, ffmpeg and curl. But for process handling I've only implement POSIX systems to use my script. Windows have a other process/service handling and I use this script on my raspberry. I've no time to spend for windows compatibility but you are free to delete all POSIX stuff in my script for use it on windows. 54 | 55 | **Software:** 56 | - Perl with Proc::Daemon 57 | - ffmpeg with libmp3lame support 58 | - curl 59 | - icecast2 *(Optional)* for stream audio to i.e. SONOS 60 | - openhab-addon-binding-exec 61 | 62 | Copy the *audioDetection.pl* script to path they have access from openhab. That is all. Start an stop is possible from openhab with a switch. 63 | 64 | ## Debian 65 | Install ffmpeg from multimedia backports http://www.deb-multimedia.org/. 66 | 67 | ``` 68 | sudo apt-get install curl libproc-daemon-perl icecast2 69 | ``` 70 | 71 | If you have a older icecast2 Server than 2.4, in my case Raspbian with Whessy, you need the flag --iceCastLegacy to work with older version. 72 | 73 | ### Raspberry 74 | If you have Raspbian with jessie you can use multimedia backports if you don't need HW h264 support. For all other you need compile self. You need time for that... 75 | 76 | **Compile:** 77 | ``` 78 | sudo apt-get remove --purge libtool libaacplus-* libx264 libvpx librtmp ffmpeg 79 | sudo apt-get install curl libproc-daemon-perl icecast2 libmp3lame-dev 80 | ``` 81 | 82 | **x264** 83 | ``` 84 | git clone git://git.videolan.org/x264 85 | cd x264 86 | ./configure --enable-static --disable-opengl 87 | make 88 | sudo make install 89 | ``` 90 | 91 | **ffmpeg** 92 | ``` 93 | git clone --depth 1 git://git.videolan.org/ffmpeg 94 | cd ffmpeg 95 | ./configure --enable-gpl --enable-libx264 --enable-nonfree --enable-libmp3lame 96 | make 97 | sudo make install 98 | ``` 99 | 100 | # Configs 101 | 102 | ## OpenHab 103 | 104 | **Note:** on Raspberry/Debien it is importet to use option --ffmpegBin/--curlBin with full path. The user openhab dosn't have the same PATH as a normal user. 105 | 106 | ``` 107 | Switch Babyphone_Alarm "Babyphone Alarm" (Child) 108 | Switch Baby_Monitor "Babyphone" (Child) { exec="ON:perl@@audioDetection.pl@@--start@@--pidFile=/tmp/baby_monitor.pid@@--openHabUrl=http://192.168.1.10:8080@@--openHabItem=Babyphone_Alarm@@--ipCamUrl=rtsp://admin:pw@192.168.1.20/live3.sdp@@--iceCastLegacy@@--iceCastUrl=icecast://camUser:camPW@127.0.0.1:8000/baby_phone.mp3, OFF:perl@@audioDetection.pl@@--stop@@--pidFile=/tmp/baby_monitor.pid" } 109 | 110 | ``` 111 | 112 | ## IceCast 113 | 114 | ``` 115 | 116 | /audio_stream.mp3 117 | 118 | camUser 119 | camPW 120 | 121 | ``` 122 | 123 | # Changelog 124 | - *0.7*: Set default silent time to 60s and stop alarm event if close ffmpeg 125 | - *0.6*: Reconnect with pipe handling 126 | - *0.5*: Fix iceCast2 bug in streaming and add option --iceCastVol 127 | - *0.4*: Fix high/low pass filtering bug. Use pipe for IPC 128 | - *0.3*: If the connection break to camera, it reconnect after 10 sec 129 | - *0.2*: Add commands --fmpegBin, --curlBin and --logFile 130 | 131 | # Example 132 | That's my babephone :) 133 | 134 | -------------------------------------------------------------------------------- /audioDetection.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use v5.10; 3 | 4 | use POSIX; 5 | use IO::File; 6 | use Getopt::Long; 7 | use Proc::Daemon; 8 | 9 | ### 10 | # Static object 11 | ### 12 | my $VERSION = "0.7"; 13 | my $daemon = Proc::Daemon->new(); 14 | 15 | ### 16 | # audioDetection Options 17 | ### 18 | my $openHabUrl; 19 | my $openHabItem; 20 | my $ipCamUrl; 21 | my $intPort = "9554"; 22 | my $iceCastUrl; 23 | my $iceCastLegacy; 24 | my $iceCastVol; 25 | my $highpass = "300"; 26 | my $lowpass = "2500"; 27 | my $silenceDb = "-30"; 28 | my $silenceSec = "60"; 29 | my $sampleRate = "16000"; 30 | my $pidFile = "/tmp/audioDetection.pid"; 31 | my $pipeFile; 32 | my $daemonStart = ""; 33 | my $daemonStop = ""; 34 | my $ffmpegBin = "ffmpeg"; 35 | my $curlBin = "curl"; 36 | my $logFile; 37 | 38 | GetOptions( 39 | "openHabUrl:s" => \$openHabUrl, 40 | "openHabItem:s" => \$openHabItem, 41 | "ipCamUrl:s" => \$ipCamUrl, 42 | "intPort=i" => \$intPort, 43 | "iceCastUrl:s" => \$iceCastUrl, 44 | "iceCastLegacy" => \$iceCastLegacy, 45 | "iceCastVol:1" => \$iceCastVol, 46 | "highpass=i" => \$highpass, 47 | "lowpass=i" => \$lowpass, 48 | "silenceDb=i" => \$silenceDb, 49 | "silenceSec=i" => \$silenceSec, 50 | "sampleRate=i" => \$sampleRate, 51 | "ffmpegBin:s" => \$ffmpegBin, 52 | "curlBin:s" => \$curlBin, 53 | "pidFile=s" => \$pidFile, 54 | "pipeFile:s" => \$pipeFile, 55 | "start" => \$daemonStart, 56 | "stop" => \$daemonStop, 57 | "logFile:s" => \$logFile, 58 | "version" => sub { 59 | say("openHab-AudioDetection $VERSION"); 60 | exit(0); 61 | }, 62 | "help" => sub { 63 | say("http://github.com/pvizeli/openHab-AudioDetection"); 64 | exit(0); 65 | } 66 | ) or die("Command line error!"); 67 | 68 | ### 69 | # Deamon controll things 70 | ### 71 | 72 | # stop daemon 73 | if ($daemonStop) { 74 | 75 | # state of daemon 76 | my $pid = $daemon->Status($pidFile); 77 | 78 | # daemon is running 79 | if ($pid) { 80 | $daemon->Kill_Daemon($pid, 'INT'); 81 | } 82 | 83 | # end program 84 | exit(0); 85 | } 86 | # start daemon 87 | elsif ($daemonStart) { 88 | # params okay? 89 | if (!$openHabUrl or !$openHabItem or !$ipCamUrl) { 90 | die("Command line error for start a daemon!"); 91 | } 92 | 93 | # start daemon 94 | my $pid = $daemon->Init({ 95 | pid_file => $pidFile 96 | }); 97 | 98 | # end program 99 | if ($pid) { 100 | exit(0); 101 | } 102 | } 103 | # Cmd error 104 | else { 105 | die("Use --start or --stop in command!"); 106 | } 107 | 108 | ### 109 | # Sign handling 110 | ### 111 | my $FFMPEG; 112 | my $ICECAST; 113 | my $PIPE; 114 | my $LOGFILE; 115 | 116 | # close daemon on INT 117 | $SIG{INT} = \&initSign; 118 | 119 | ### 120 | # Logfile things 121 | ### 122 | 123 | if ($logFile) { 124 | $LOGFILE = IO::File->new(">> $logFile"); 125 | } 126 | 127 | ### 128 | # Advents options 129 | ### 130 | my $noiceF = "highpass=f=$highpass, lowpass=f=$lowpass"; 131 | my $silenceF = "silencedetect=n=" . $silenceDb . "dB:d=$silenceSec"; 132 | 133 | ## 134 | # IceCast 135 | my $iceCastOpt = ""; 136 | my $iceCastFilter = ""; 137 | 138 | # old icecast vers 139 | if ($iceCastLegacy) { 140 | $iceCastOpt = "-legacy_icecast 1"; 141 | } 142 | 143 | # vol 144 | if ($iceCastVol) { 145 | $iceCastFilter = "-af 'volume=volume=$iceCastVol'"; 146 | } 147 | 148 | ### 149 | # Pipe 150 | ### 151 | 152 | # Generate default 153 | if (!$pipeFile) { 154 | $pipeFile = $pidFile . ".pipe"; 155 | } 156 | 157 | # don't exist pipe / create one 158 | if (!-e $pipeFile) { 159 | POSIX::mkfifo($pipeFile, 0744); 160 | } 161 | # not a pipe 162 | elsif (!-p $pipeFile) { 163 | unlink($pipeFile); 164 | POSIX::mkfifo($pipeFile, 0744); 165 | } 166 | 167 | ### 168 | # Main Loop 169 | ### 170 | my $iceCastPid = 0; 171 | my $ffmpegPid = 0; 172 | my $okStream = 0; 173 | 174 | do { 175 | # open pipe 176 | $PIPE = IO::File->new("+< $pipeFile"); 177 | 178 | # Start read data from webcam 179 | $ffmpegPid = open($FFMPEG, "$ffmpegBin -i $ipCamUrl -vn -af '$noiceF, $silenceF' -f rtp rtp://127.0.0.1:$intPort 2> $pipeFile |"); 180 | 181 | # log 182 | $LOGFILE->say("FFMPEG start") if $logFile; 183 | 184 | # read data 185 | while(my $line = $PIPE->getline()) { 186 | # log 187 | $LOGFILE->print($line) if $logFile; 188 | 189 | # Start voice 190 | if ($line =~ /silence_end/) { 191 | $okStream = 1; 192 | 193 | # start Icecast stream 194 | if ($iceCastUrl) { 195 | $iceCastPid = open($ICECAST, "$ffmpegBin -i rtp://127.0.0.1:$intPort -acodec libmp3lame -ar $sampleRate $iceCastFilter $iceCastOpt -f mp3 $iceCastUrl 2> /dev/null |"); 196 | 197 | # log 198 | $LOGFILE->say("IceCast start") if $logFile; 199 | sleep(1); 200 | } 201 | 202 | # send 203 | sendOpenHab("ON"); 204 | } 205 | # End voice 206 | elsif ($line =~ /silence_start/) { 207 | $okStream = 1; 208 | 209 | # send 210 | sendOpenHab("OFF"); 211 | 212 | # close Icecast stream 213 | if ($iceCastPid) { 214 | $daemon->Kill_Daemon($iceCastPid); 215 | close($ICECAST); 216 | 217 | $iceCastPid = 0; 218 | 219 | # log 220 | $LOGFILE->say("IceCast streaming end") if $logFile; 221 | } 222 | } 223 | } 224 | 225 | # close ffmpeg 226 | close($FFMPEG); 227 | 228 | # close pipe 229 | $PIPE->close(); 230 | 231 | # log 232 | $LOGFILE->say("FFMPEG abrupt end!") if $logFile; 233 | 234 | # wait befor reconnect 235 | sleep(30); 236 | } while($okStream); 237 | 238 | # log 239 | $LOGFILE->say("FFMPEG streaming end") if $logFile; 240 | $LOGFILE->close() if $logFile; 241 | 242 | ### 243 | # End 244 | ### 245 | 246 | sub initSign() 247 | { 248 | $LOGFILE->say("Receive SIGINT") if $logFile; 249 | 250 | # Send stop to openhab 251 | sendOpenHab("OFF") if $iceCastPid != 0; 252 | 253 | $daemon->Kill_Daemon($iceCastPid); 254 | $daemon->Kill_Daemon($ffmpegPid); 255 | 256 | # process handle 257 | close($FFMPEG); 258 | close($ICECAST); 259 | 260 | # file handle 261 | $PIPE->close(); 262 | $LOGFILE->close() if $logFile; 263 | 264 | exit(0); 265 | } 266 | 267 | sub sendOpenHab() 268 | { 269 | my $cmd = shift; 270 | 271 | system("$curlBin --header \"Content-Type: text/plain\" --request POST --data \"$cmd\" $openHabUrl/rest/items/$openHabItem"); 272 | 273 | # log 274 | $LOGFILE->say("Send $cmd to openHab") if $logFile; 275 | } 276 | -------------------------------------------------------------------------------- /example_openhab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pvizeli/openHab-AudioDetection/27c2575c6476124715dd708bfac2bd8d6d10e915/example_openhab.png --------------------------------------------------------------------------------