[ipcamera] FFmpeg based alarms will now auto restart if stopped (#13446)

* FFmpeg alarms now auto restart

Signed-off-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
Matthew Skinner 2022-10-01 22:19:25 +10:00 committed by GitHub
parent a646fe34e0
commit cc50497f31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 54 deletions

View File

@ -54,6 +54,7 @@ public class Ffmpeg {
private IpCameraFfmpegThread ipCameraFfmpegThread = new IpCameraFfmpegThread(); private IpCameraFfmpegThread ipCameraFfmpegThread = new IpCameraFfmpegThread();
private int keepAlive = 8; private int keepAlive = 8;
private String password; private String password;
private Boolean notFrozen = true;
public Ffmpeg(IpCameraHandler handle, FFmpegFormat format, String ffmpegLocation, String inputArguments, public Ffmpeg(IpCameraHandler handle, FFmpegFormat format, String ffmpegLocation, String inputArguments,
String input, String outArguments, String output, String username, String password) { String input, String outArguments, String output, String username, String password) {
@ -131,45 +132,49 @@ public class Ffmpeg {
String line = null; String line = null;
while ((line = bufferedReader.readLine()) != null) { while ((line = bufferedReader.readLine()) != null) {
logger.debug("{}", line); logger.debug("{}", line);
if (format.equals(FFmpegFormat.RTSP_ALARMS)) { switch (format) {
if (line.contains("lavfi.")) { case RTSP_ALARMS:
// When the number of pixels that change are below the noise floor we need to look if (line.contains("lavfi.")) {
// across frames to confirm it is motion and not noise. // When the number of pixels that change are below the noise floor we need to look
if (countOfMotions < 10) {// Stop increasing otherwise it will take too long to go OFF. // across frames to confirm it is motion and not noise.
countOfMotions++; if (countOfMotions < 10) {// Stop increasing otherwise it takes too long to go OFF
} countOfMotions++;
if (countOfMotions > 9) {
ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else if (countOfMotions > 4 && ipCameraHandler.motionThreshold.intValue() > 10) {
ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else if (countOfMotions > 3 && ipCameraHandler.motionThreshold.intValue() > 15) {
ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else if (countOfMotions > 2 && ipCameraHandler.motionThreshold.intValue() > 30) {
ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else if (countOfMotions > 0 && ipCameraHandler.motionThreshold.intValue() > 89) {
ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
countOfMotions = 4;// Used to debounce the Alarm.
}
} else if (line.contains("speed=")) {
if (countOfMotions > 0) {
if (ipCameraHandler.motionThreshold.intValue() > 89) {
countOfMotions--;
} }
if (ipCameraHandler.motionThreshold.intValue() > 10) { if (countOfMotions > 9) {
countOfMotions -= 2; ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else { } else if (countOfMotions > 4 && ipCameraHandler.motionThreshold.intValue() > 10) {
countOfMotions -= 4; ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else if (countOfMotions > 3 && ipCameraHandler.motionThreshold.intValue() > 15) {
ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else if (countOfMotions > 2 && ipCameraHandler.motionThreshold.intValue() > 30) {
ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else if (countOfMotions > 0 && ipCameraHandler.motionThreshold.intValue() > 89) {
ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
countOfMotions = 4;// Used to debounce the Alarm.
} }
if (countOfMotions <= 0) { } else if (line.contains("speed=")) {
ipCameraHandler.noMotionDetected(CHANNEL_FFMPEG_MOTION_ALARM); if (countOfMotions > 0) {
countOfMotions = 0; if (ipCameraHandler.motionThreshold.intValue() > 89) {
countOfMotions--;
}
if (ipCameraHandler.motionThreshold.intValue() > 10) {
countOfMotions -= 2;
} else {
countOfMotions -= 4;
}
if (countOfMotions <= 0) {
ipCameraHandler.noMotionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
countOfMotions = 0;
}
} }
} else if (line.contains("silence_start")) {
ipCameraHandler.noAudioDetected();
} else if (line.contains("silence_end")) {
ipCameraHandler.audioDetected();
} }
} else if (line.contains("silence_start")) { case SNAPSHOT:
ipCameraHandler.noAudioDetected(); notFrozen = true;// RTSP_ALARMS and SNAPSHOT both set this to true as there is no break.
} else if (line.contains("silence_end")) { break;
ipCameraHandler.audioDetected();
}
} }
} }
} }
@ -212,7 +217,10 @@ public class Ffmpeg {
public boolean getIsAlive() { public boolean getIsAlive() {
Process localProcess = process; Process localProcess = process;
if (localProcess != null) { if (localProcess != null) {
return localProcess.isAlive(); if (localProcess.isAlive() && notFrozen) {
notFrozen = false; // Any process output will set this back to true before next check.
return true;
}
} }
return false; return false;
} }

View File

@ -62,11 +62,11 @@ public class HttpOnlyHandler extends ChannelDuplexHandler {
switch (channelUID.getId()) { switch (channelUID.getId()) {
case CHANNEL_THRESHOLD_AUDIO_ALARM: case CHANNEL_THRESHOLD_AUDIO_ALARM:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
ipCameraHandler.audioAlarmEnabled = true; ipCameraHandler.ffmpegAudioAlarmEnabled = true;
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) { } else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
ipCameraHandler.audioAlarmEnabled = false; ipCameraHandler.ffmpegAudioAlarmEnabled = false;
} else { } else {
ipCameraHandler.audioAlarmEnabled = true; ipCameraHandler.ffmpegAudioAlarmEnabled = true;
try { try {
ipCameraHandler.audioThreshold = Integer.valueOf(command.toString()); ipCameraHandler.audioThreshold = Integer.valueOf(command.toString());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {

View File

@ -183,8 +183,8 @@ public class IpCameraHandler extends BaseThingHandler {
public BigDecimal motionThreshold = BigDecimal.ZERO; public BigDecimal motionThreshold = BigDecimal.ZERO;
public int audioThreshold = 35; public int audioThreshold = 35;
public boolean streamingSnapshotMjpeg = false; public boolean streamingSnapshotMjpeg = false;
public boolean motionAlarmEnabled = false; public boolean ffmpegMotionAlarmEnabled = false;
public boolean audioAlarmEnabled = false; public boolean ffmpegAudioAlarmEnabled = false;
public boolean ffmpegSnapshotGeneration = false; public boolean ffmpegSnapshotGeneration = false;
public boolean snapshotPolling = false; public boolean snapshotPolling = false;
public OnvifConnection onvifCamera = new OnvifConnection(this, "", "", ""); public OnvifConnection onvifCamera = new OnvifConnection(this, "", "", "");
@ -868,20 +868,20 @@ public class IpCameraHandler extends BaseThingHandler {
Ffmpeg localAlarms = ffmpegRtspHelper; Ffmpeg localAlarms = ffmpegRtspHelper;
if (localAlarms != null) { if (localAlarms != null) {
localAlarms.stopConverting(); localAlarms.stopConverting();
if (!audioAlarmEnabled && !motionAlarmEnabled) { if (!ffmpegAudioAlarmEnabled && !ffmpegMotionAlarmEnabled) {
return; return;
} }
} }
String input = (cameraConfig.getAlarmInputUrl().isEmpty()) ? rtspUri : cameraConfig.getAlarmInputUrl(); String input = (cameraConfig.getAlarmInputUrl().isEmpty()) ? rtspUri : cameraConfig.getAlarmInputUrl();
String filterOptions = ""; String filterOptions = "";
if (!audioAlarmEnabled) { if (!ffmpegAudioAlarmEnabled) {
filterOptions = "-an"; filterOptions = "-an";
} else { } else {
filterOptions = "-af silencedetect=n=-" + audioThreshold + "dB:d=2"; filterOptions = "-af silencedetect=n=-" + audioThreshold + "dB:d=2";
} }
if (!motionAlarmEnabled && !ffmpegSnapshotGeneration) { if (!ffmpegMotionAlarmEnabled && !ffmpegSnapshotGeneration) {
filterOptions = filterOptions.concat(" -vn"); filterOptions = filterOptions.concat(" -vn");
} else if (motionAlarmEnabled && !cameraConfig.getMotionOptions().isEmpty()) { } else if (ffmpegMotionAlarmEnabled && !cameraConfig.getMotionOptions().isEmpty()) {
String usersMotionOptions = cameraConfig.getMotionOptions(); String usersMotionOptions = cameraConfig.getMotionOptions();
if (usersMotionOptions.startsWith("-")) { if (usersMotionOptions.startsWith("-")) {
// Need to put the users custom options first in the chain before the motion is detected // Need to put the users custom options first in the chain before the motion is detected
@ -891,7 +891,7 @@ public class IpCameraHandler extends BaseThingHandler {
filterOptions = filterOptions + " " + usersMotionOptions + " -vf select='gte(scene," filterOptions = filterOptions + " " + usersMotionOptions + " -vf select='gte(scene,"
+ motionThreshold.divide(BIG_DECIMAL_SCALE_MOTION) + ")',metadata=print"; + motionThreshold.divide(BIG_DECIMAL_SCALE_MOTION) + ")',metadata=print";
} }
} else if (motionAlarmEnabled) { } else if (ffmpegMotionAlarmEnabled) {
filterOptions = filterOptions.concat(" -vf select='gte(scene," filterOptions = filterOptions.concat(" -vf select='gte(scene,"
+ motionThreshold.divide(BIG_DECIMAL_SCALE_MOTION) + ")',metadata=print"); + motionThreshold.divide(BIG_DECIMAL_SCALE_MOTION) + ")',metadata=print");
} }
@ -924,9 +924,9 @@ public class IpCameraHandler extends BaseThingHandler {
if (ffmpegSnapshot == null) { if (ffmpegSnapshot == null) {
if (inputOptions.isEmpty()) { if (inputOptions.isEmpty()) {
// iFrames only // iFrames only
inputOptions = "-threads 1 -skip_frame nokey -hide_banner -loglevel warning"; inputOptions = "-threads 1 -skip_frame nokey -hide_banner";
} else { } else {
inputOptions += " -threads 1 -skip_frame nokey -hide_banner -loglevel warning"; inputOptions += " -threads 1 -skip_frame nokey -hide_banner";
} }
ffmpegSnapshot = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, rtspUri, ffmpegSnapshot = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, rtspUri,
cameraConfig.getSnapshotOptions(), "http://127.0.0.1:" + SERVLET_PORT + "/ipcamera/" cameraConfig.getSnapshotOptions(), "http://127.0.0.1:" + SERVLET_PORT + "/ipcamera/"
@ -1106,12 +1106,12 @@ public class IpCameraHandler extends BaseThingHandler {
return; return;
case CHANNEL_FFMPEG_MOTION_CONTROL: case CHANNEL_FFMPEG_MOTION_CONTROL:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
motionAlarmEnabled = true; ffmpegMotionAlarmEnabled = true;
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) { } else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
motionAlarmEnabled = false; ffmpegMotionAlarmEnabled = false;
noMotionDetected(CHANNEL_FFMPEG_MOTION_ALARM); noMotionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else if (command instanceof PercentType) { } else if (command instanceof PercentType) {
motionAlarmEnabled = true; ffmpegMotionAlarmEnabled = true;
motionThreshold = ((PercentType) command).toBigDecimal(); motionThreshold = ((PercentType) command).toBigDecimal();
} }
setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS); setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
@ -1564,9 +1564,15 @@ public class IpCameraHandler extends BaseThingHandler {
+ cameraConfig.getPassword()); + cameraConfig.getPassword());
break; break;
} }
Ffmpeg localHLS = ffmpegHLS; Ffmpeg localFfmpeg = ffmpegHLS;
if (localHLS != null) { if (localFfmpeg != null) {
localHLS.checkKeepAlive(); localFfmpeg.checkKeepAlive();
}
if (ffmpegMotionAlarmEnabled || ffmpegAudioAlarmEnabled) {
localFfmpeg = ffmpegRtspHelper;
if (localFfmpeg == null || !localFfmpeg.getIsAlive()) {
setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
}
} }
if (openChannels.size() > 10) { if (openChannels.size() > 10) {
logger.debug("There are {} open Channels being tracked.", openChannels.size()); logger.debug("There are {} open Channels being tracked.", openChannels.size());

View File

@ -1177,6 +1177,8 @@
<channel id="ffmpegMotionAlarm" typeId="ffmpegMotionAlarm"/> <channel id="ffmpegMotionAlarm" typeId="ffmpegMotionAlarm"/>
<channel id="externalMotion" typeId="externalMotion"/> <channel id="externalMotion" typeId="externalMotion"/>
<channel id="motionAlarm" typeId="motionAlarm"/> <channel id="motionAlarm" typeId="motionAlarm"/>
<channel id="thresholdAudioAlarm" typeId="thresholdAudioAlarm"/>
<channel id="audioAlarm" typeId="audioAlarm"/>
<channel id="activateAlarmOutput" typeId="activateAlarmOutput"/> <channel id="activateAlarmOutput" typeId="activateAlarmOutput"/>
<channel id="activateAlarmOutput2" typeId="activateAlarmOutput2"/> <channel id="activateAlarmOutput2" typeId="activateAlarmOutput2"/>
<channel id="doorBell" typeId="doorBell"/> <channel id="doorBell" typeId="doorBell"/>
@ -1727,6 +1729,7 @@
<channel id="enableFieldDetectionAlarm" typeId="enableFieldDetectionAlarm"/> <channel id="enableFieldDetectionAlarm" typeId="enableFieldDetectionAlarm"/>
<channel id="fieldDetectionAlarm" typeId="fieldDetectionAlarm"/> <channel id="fieldDetectionAlarm" typeId="fieldDetectionAlarm"/>
<channel id="enableAudioAlarm" typeId="enableAudioAlarm"/> <channel id="enableAudioAlarm" typeId="enableAudioAlarm"/>
<channel id="thresholdAudioAlarm" typeId="thresholdAudioAlarm"/>
<channel id="audioAlarm" typeId="audioAlarm"/> <channel id="audioAlarm" typeId="audioAlarm"/>
<channel id="activateAlarmOutput" typeId="activateAlarmOutput"/> <channel id="activateAlarmOutput" typeId="activateAlarmOutput"/>
<channel id="enableExternalAlarmInput" typeId="enableExternalAlarmInput"/> <channel id="enableExternalAlarmInput" typeId="enableExternalAlarmInput"/>