[ipcamera] Improve FFmpeg motion detection to support wider FPS range (#11067)

* Fix urls are null until binding restarts.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

* Change to using port config.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

* automate the ffmpeg output folder to follow the UID.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

* spotless fixes.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

* Update readme.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

* change to using the userdata folder.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

* change to better field description.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

* Add advanced.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

* Add link to docs.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

* improve readme.

Signed-off-by: Matthew Skinner <matt@pcmus.com>

* improve example path in readme.


Signed-off-by: Matthew Skinner <matt@pcmus.com>

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

Cut and paste bandit strikes again. thanks.

Signed-off-by: Matthew Skinner <matt@pcmus.com>

Co-authored-by: Fabian Wolter <github@fabian-wolter.de>

* Change to using ipcamera as folder loc.

Signed-off-by: Matthew Skinner <matt@pcmus.com>

* Improve Ffmpeg motion and refactor to remove compiler warnings.

Signed-off-by: Matthew Skinner <matt@pcmus.com>

Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
Matthew Skinner 2021-08-02 04:06:28 +10:00 committed by GitHub
parent e465155d84
commit 91879336e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 93 additions and 76 deletions

View File

@ -65,105 +65,105 @@ public class DahuaHandler extends ChannelDuplexHandler {
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 {

View File

@ -127,20 +127,39 @@ public class Ffmpeg {
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")) {
@ -148,8 +167,6 @@ public class Ffmpeg {
} else if (line.contains("silence_end")) {
ipCameraHandler.audioDetected();
}
} else {
logger.debug("{}", line);
}
}
}

View File

@ -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;
@ -42,6 +43,8 @@ public class IpCameraBindingConstants {
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";

View File

@ -50,10 +50,7 @@ public class IpCameraHandlerFactory extends BaseThingHandlerFactory {
@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

View File

@ -104,7 +104,7 @@ public class MyNettyAuthHandler extends ChannelDuplexHandler {
}
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.");
}

View File

@ -70,7 +70,7 @@ public class StreamServerGroupHandler extends ChannelInboundHandlerAdapter {
}
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";
@ -87,7 +87,7 @@ public class StreamServerGroupHandler extends ChannelInboundHandlerAdapter {
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())) {

View File

@ -81,7 +81,7 @@ public class StreamServerHandler extends ChannelInboundHandlerAdapter {
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)) {

View File

@ -19,6 +19,7 @@ import java.io.FileNotFoundException;
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;
@ -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;
@ -1035,15 +1036,15 @@ public class IpCameraHandler extends BaseThingHandler {
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());
@ -1262,10 +1263,9 @@ public class IpCameraHandler extends BaseThingHandler {
} 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;

View File

@ -576,87 +576,87 @@ public class OnvifConnection {
}
switch (topic) {
case "RuleEngine/CellMotionDetector/Motion":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_CELL_MOTION_ALARM);
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_CELL_MOTION_ALARM);
}
break;
case "VideoSource/MotionAlarm":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
}
break;
case "AudioAnalytics/Audio/DetectedSound":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.audioDetected();
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.noAudioDetected();
}
break;
case "RuleEngine/FieldDetector/ObjectsInside":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_FIELD_DETECTION_ALARM);
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_FIELD_DETECTION_ALARM);
}
break;
case "RuleEngine/LineDetector/Crossed":
if (dataName.equals("ObjectId")) {
if ("ObjectId".equals(dataName)) {
ipCameraHandler.motionDetected(CHANNEL_LINE_CROSSING_ALARM);
} else {
ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
}
break;
case "RuleEngine/TamperDetector/Tamper":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TAMPER_ALARM, OnOffType.ON);
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TAMPER_ALARM, OnOffType.OFF);
}
break;
case "Device/HardwareFailure/StorageFailure":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_STORAGE_ALARM, OnOffType.ON);
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_STORAGE_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/ImageTooDark/AnalyticsService":
case "VideoSource/ImageTooDark/ImagingService":
case "VideoSource/ImageTooDark/RecordingService":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_DARK_ALARM, OnOffType.ON);
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_DARK_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/GlobalSceneChange/AnalyticsService":
case "VideoSource/GlobalSceneChange/ImagingService":
case "VideoSource/GlobalSceneChange/RecordingService":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.ON);
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/ImageTooBright/AnalyticsService":
case "VideoSource/ImageTooBright/ImagingService":
case "VideoSource/ImageTooBright/RecordingService":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BRIGHT_ALARM, OnOffType.ON);
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BRIGHT_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/ImageTooBlurry/AnalyticsService":
case "VideoSource/ImageTooBlurry/ImagingService":
case "VideoSource/ImageTooBlurry/RecordingService":
if (dataValue.equals("true")) {
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.ON);
} else if (dataValue.equals("false")) {
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.OFF);
}
break;

View File

@ -112,7 +112,7 @@ public class OnvifDiscovery {
ipAddress = temp.substring(beginIndex, endIndex);
}
String brand = checkForBrand(xml);
if (brand.equals("onvif")) {
if ("onvif".equals(brand)) {
try {
brand = getBrandFromLoginPage(ipAddress);
} catch (IOException e) {
@ -127,7 +127,7 @@ public class OnvifDiscovery {
String xml = packet.content().toString(CharsetUtil.UTF_8);
logger.trace("Device replied to discovery with:{}", xml);
String xAddr = Helper.fetchXML(xml, "", "d:XAddrs>");// Foscam <wsdd:XAddrs> and all other brands <d:XAddrs>
if (!xAddr.equals("")) {
if (!xAddr.isEmpty()) {
searchReply(xAddr, xml);
} else if (xml.contains("onvif")) {
logger.info("Possible ONVIF camera found at:{}", packet.sender().getHostString());