├── .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
--------------------------------------------------------------------------------