Skip to content

Commit

Permalink
[ipcamera] Improve FFmpeg motion detection to support wider FPS range (
Browse files Browse the repository at this point in the history
…openhab#11067)

* Fix urls are null until binding restarts.

Signed-off-by: Matthew Skinner <[email protected]>

* Change to using port config.

Signed-off-by: Matthew Skinner <[email protected]>

* automate the ffmpeg output folder to follow the UID.

Signed-off-by: Matthew Skinner <[email protected]>

* spotless fixes.

Signed-off-by: Matthew Skinner <[email protected]>

* Update readme.

Signed-off-by: Matthew Skinner <[email protected]>

* change to using the userdata folder.

Signed-off-by: Matthew Skinner <[email protected]>

* change to better field description.

Signed-off-by: Matthew Skinner <[email protected]>

* Add advanced.

Signed-off-by: Matthew Skinner <[email protected]>

* Add link to docs.

Signed-off-by: Matthew Skinner <[email protected]>

* improve readme.

Signed-off-by: Matthew Skinner <[email protected]>

* improve example path in readme.

Signed-off-by: Matthew Skinner <[email protected]>

* Update bundles/org.openhab.binding.ipcamera/README.md

Cut and paste bandit strikes again. thanks.

Signed-off-by: Matthew Skinner <[email protected]>

Co-authored-by: Fabian Wolter <[email protected]>

* Change to using ipcamera as folder loc.

Signed-off-by: Matthew Skinner <[email protected]>

* Improve Ffmpeg motion and refactor to remove compiler warnings.

Signed-off-by: Matthew Skinner <[email protected]>

Co-authored-by: Fabian Wolter <[email protected]>
Signed-off-by: Dave J Schoepel <[email protected]>
  • Loading branch information
2 people authored and dschoepel committed Nov 9, 2021
1 parent 97144ac commit 4b6e815
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,105 +65,105 @@ private void processEvent(String content) {
String action = content.substring(startIndex, endIndex);
switch (code) {
case "VideoMotion":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
}
break;
case "TakenAwayDetection":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.motionDetected(CHANNEL_ITEM_TAKEN);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.noMotionDetected(CHANNEL_ITEM_TAKEN);
}
break;
case "LeftDetection":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.motionDetected(CHANNEL_ITEM_LEFT);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.noMotionDetected(CHANNEL_ITEM_LEFT);
}
break;
case "SmartMotionVehicle":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.motionDetected(CHANNEL_CAR_ALARM);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.noMotionDetected(CHANNEL_CAR_ALARM);
}
break;
case "SmartMotionHuman":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.motionDetected(CHANNEL_HUMAN_ALARM);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.noMotionDetected(CHANNEL_HUMAN_ALARM);
}
break;
case "CrossLineDetection":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.motionDetected(CHANNEL_LINE_CROSSING_ALARM);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
}
break;
case "AudioMutation":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.audioDetected();
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.noAudioDetected();
}
break;
case "FaceDetection":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.motionDetected(CHANNEL_FACE_DETECTED);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.noMotionDetected(CHANNEL_FACE_DETECTED);
}
break;
case "ParkingDetection":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.setChannelState(CHANNEL_PARKING_ALARM, OnOffType.ON);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.setChannelState(CHANNEL_PARKING_ALARM, OnOffType.OFF);
}
break;
case "CrossRegionDetection":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.motionDetected(CHANNEL_FIELD_DETECTION_ALARM);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.noMotionDetected(CHANNEL_FIELD_DETECTION_ALARM);
}
break;
case "VideoLoss":
case "VideoBlind":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.setChannelState(CHANNEL_TOO_DARK_ALARM, OnOffType.ON);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.setChannelState(CHANNEL_TOO_DARK_ALARM, OnOffType.OFF);
}
break;
case "VideoAbnormalDetection":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.setChannelState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.ON);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.setChannelState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.OFF);
}
break;
case "VideoUnFocus":
if (action.equals("Start")) {
if ("Start".equals(action)) {
ipCameraHandler.setChannelState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.ON);
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
ipCameraHandler.setChannelState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.OFF);
}
break;
case "AlarmLocal":
if (action.equals("Start")) {
if ("Start".equals(action)) {
if (content.contains("index=0")) {
ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT2, OnOffType.ON);
}
} else if (action.equals("Stop")) {
} else if ("Stop".equals(action)) {
if (content.contains("index=0")) {
ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT, OnOffType.OFF);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,29 +127,46 @@ public void run() {
BufferedReader bufferedReader = new BufferedReader(errorStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
logger.debug("{}", line);
if (format.equals(FFmpegFormat.RTSP_ALARMS)) {
logger.debug("{}", line);
if (line.contains("lavfi.")) {
if (countOfMotions == 4) {
ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else {
// When the number of pixels that change are below the noise floor we need to look
// across frames to confirm it is motion and not noise.
if (countOfMotions < 10) {// Stop increasing otherwise it will take 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) {
countOfMotions--;
countOfMotions--;
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 {
logger.debug("{}", line);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.binding.ipcamera.internal;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
Expand Down Expand Up @@ -42,6 +43,8 @@ public static enum FFmpegFormat {
SNAPSHOT
}

public static final BigDecimal BIG_DECIMAL_SCALE_MOTION = new BigDecimal(5000);

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_GROUP = new ThingTypeUID(BINDING_ID, "group");
public static final String GENERIC_THING = "generic";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ public IpCameraHandlerFactory(final @Reference NetworkAddressService networkAddr

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
if (SUPPORTED_THING_TYPES.contains(thingTypeUID) || GROUP_SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
return true;
}
return false;
return (SUPPORTED_THING_TYPES.contains(thingTypeUID) || GROUP_SUPPORTED_THING_TYPES.contains(thingTypeUID));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public void processAuth(String authenticate, String httpMethod, String requestUR
}

String stale = Helper.searchString(authenticate, "stale=\"");
if (stale.equalsIgnoreCase("true")) {
if ("true".equalsIgnoreCase(stale)) {
logger.debug("Camera reported stale=true which normally means the NONCE has expired.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void handlerAdded(@Nullable ChannelHandlerContext ctx) {
}

private String resolveIndexToPath(String uri) {
if (!uri.substring(1, 2).equals("i")) {
if (!"i".equals(uri.substring(1, 2))) {
return ipCameraGroupHandler.getOutputFolder(Integer.parseInt(uri.substring(1, 2)));
}
return "notFound";
Expand All @@ -87,7 +87,7 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms
HttpRequest httpRequest = (HttpRequest) msg;
String requestIP = "("
+ ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress() + ")";
if (!whiteList.contains(requestIP) && !whiteList.equals("DISABLE")) {
if (!whiteList.contains(requestIP) && !"DISABLE".equals(whiteList)) {
logger.warn("The request made from {} was not in the whitelist and will be ignored.", requestIP);
return;
} else if (HttpMethod.GET.equals(httpRequest.method())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms
try {
if (msg instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) msg;
if (!whiteList.equals("DISABLE")) {
if (!"DISABLE".equals(whiteList)) {
String requestIP = "("
+ ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress() + ")";
if (!whiteList.contains(requestIP)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
Expand Down Expand Up @@ -189,7 +190,7 @@ public class IpCameraHandler extends BaseThingHandler {
private boolean isOnline = false; // Used so only 1 error is logged when a network issue occurs.
private boolean firstAudioAlarm = false;
private boolean firstMotionAlarm = false;
public Double motionThreshold = 0.0016;
public BigDecimal motionThreshold = BigDecimal.ZERO;
public int audioThreshold = 35;
@SuppressWarnings("unused")
private @Nullable StreamServerHandler streamServerHandler;
Expand Down Expand Up @@ -1035,15 +1036,15 @@ public void setupFfmpegFormat(FFmpegFormat format) {
String usersMotionOptions = cameraConfig.getMotionOptions();
if (usersMotionOptions.startsWith("-")) {
// Need to put the users custom options first in the chain before the motion is detected
filterOptions += " " + usersMotionOptions + ",select='gte(scene," + motionThreshold
+ ")',metadata=print";
filterOptions += " " + usersMotionOptions + ",select='gte(scene,"
+ motionThreshold.divide(BIG_DECIMAL_SCALE_MOTION) + ")',metadata=print";
} else {
filterOptions = filterOptions + " " + usersMotionOptions + " -vf select='gte(scene,"
+ motionThreshold + ")',metadata=print";
+ motionThreshold.divide(BIG_DECIMAL_SCALE_MOTION) + ")',metadata=print";
}
} else if (motionAlarmEnabled) {
filterOptions = filterOptions
.concat(" -vf select='gte(scene," + motionThreshold + ")',metadata=print");
filterOptions = filterOptions.concat(" -vf select='gte(scene,"
+ motionThreshold.divide(BIG_DECIMAL_SCALE_MOTION) + ")',metadata=print");
}
ffmpegRtspHelper = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, input,
filterOptions, "-f null -", cameraConfig.getUser(), cameraConfig.getPassword());
Expand Down Expand Up @@ -1262,10 +1263,9 @@ public void handleCommand(ChannelUID channelUID, Command command) {
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
motionAlarmEnabled = false;
noMotionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
} else {
} else if (command instanceof PercentType) {
motionAlarmEnabled = true;
motionThreshold = Double.valueOf(command.toString());
motionThreshold = motionThreshold / 10000;
motionThreshold = ((PercentType) command).toBigDecimal();
}
setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
return;
Expand Down
Loading

0 comments on commit 4b6e815

Please sign in to comment.