diff --git a/plugins/hal-nvr/resource/resource/hal-nvr-reference.db b/plugins/hal-nvr/resource/resource/hal-nvr-reference.db
new file mode 100644
index 00000000..43d1a410
Binary files /dev/null and b/plugins/hal-nvr/resource/resource/hal-nvr-reference.db differ
diff --git a/plugins/hal-nvr/resource/resource/web/camera_config.tmpl b/plugins/hal-nvr/resource/resource/web/camera_config.tmpl
index 307c83f6..a143d61d 100644
--- a/plugins/hal-nvr/resource/resource/web/camera_config.tmpl
+++ b/plugins/hal-nvr/resource/resource/web/camera_config.tmpl
@@ -82,9 +82,9 @@
@@ -102,7 +102,7 @@
- {{#cameraConf}}
+ {{#availableCameraObjectConfig}}
{{#.params}}
@@ -118,6 +118,6 @@
{{/.params}}
- {{/cameraConf}}
+ {{/availableCameraObjectConfig}}
diff --git a/plugins/hal-nvr/resource/resource/web/camera_detail.tmpl b/plugins/hal-nvr/resource/resource/web/camera_detail.tmpl
index 3e3f7007..40405117 100644
--- a/plugins/hal-nvr/resource/resource/web/camera_detail.tmpl
+++ b/plugins/hal-nvr/resource/resource/web/camera_detail.tmpl
@@ -1,9 +1,19 @@
+
+
-
@@ -24,7 +34,7 @@
| Type: |
- {{camera.getDeviceData().getClass().getSimpleName()}} |
+ {{camera.getDeviceConfig().getClass().getSimpleName()}} |
| Owner: |
@@ -40,3 +50,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/plugins/hal-nvr/resource/resource/web/camera_monitor.tmpl b/plugins/hal-nvr/resource/resource/web/camera_monitor.tmpl
index b6d3680f..a1a6e2e4 100644
--- a/plugins/hal-nvr/resource/resource/web/camera_monitor.tmpl
+++ b/plugins/hal-nvr/resource/resource/web/camera_monitor.tmpl
@@ -1,9 +1,7 @@
-
-
@@ -11,7 +9,7 @@
|
-
+
To view this video please enable JavaScript, and consider upgrading to a web browser that
supports HTML5 video.
@@ -21,7 +19,7 @@
-
+
|
|
@@ -29,13 +27,13 @@
|
-
+
|
-
+
|
diff --git a/plugins/hal-nvr/resource/resource/web/camera_overview.tmpl b/plugins/hal-nvr/resource/resource/web/camera_overview.tmpl
index ce467306..7e2d1434 100644
--- a/plugins/hal-nvr/resource/resource/web/camera_overview.tmpl
+++ b/plugins/hal-nvr/resource/resource/web/camera_overview.tmpl
@@ -9,15 +9,11 @@
Name |
Type |
- Data |
- Last Update |
{{#cameras}}
| {{.getName()}} |
{{.getDeviceConfig().getClass().getSimpleName()}} |
- {{.getDeviceData()}} |
- {{.getDeviceData().getTimestamp()}} |
{{/cameras}}
diff --git a/plugins/hal-nvr/src/se/hal/plugin/nvr/CameraControllerManager.java b/plugins/hal-nvr/src/se/hal/plugin/nvr/CameraControllerManager.java
index e067f608..eedfb867 100644
--- a/plugins/hal-nvr/src/se/hal/plugin/nvr/CameraControllerManager.java
+++ b/plugins/hal-nvr/src/se/hal/plugin/nvr/CameraControllerManager.java
@@ -1,16 +1,22 @@
package se.hal.plugin.nvr;
+import se.hal.HalContext;
import se.hal.intf.HalAbstractControllerManager;
+import se.hal.intf.HalEventController;
import se.hal.plugin.nvr.intf.HalCameraConfig;
import se.hal.plugin.nvr.intf.HalCameraController;
import se.hal.plugin.nvr.struct.Camera;
+import se.hal.struct.Sensor;
+import se.hal.util.HalDeviceUtil;
+import zutil.db.DBConnection;
import zutil.log.LogUtil;
import zutil.plugin.PluginManager;
+import java.sql.SQLException;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.logging.Level;
import java.util.logging.Logger;
@@ -21,14 +27,66 @@ public class CameraControllerManager extends HalAbstractControllerManager registeredCameras = Collections.synchronizedList(new ArrayList<>());
- @Override
- public void register(Camera device) {
+ public void initialize(PluginManager pluginManager){
+ super.initialize(pluginManager);
+ instance = this;
+
+ // Read in existing devices
+
+ try {
+ DBConnection db = HalContext.getDB();
+
+ logger.info("Reading in existing cameras.");
+
+ for (Camera camera : Camera.getCameras(db)) {
+ register(camera);
+ }
+ } catch (SQLException e) {
+ logger.log(Level.SEVERE, "Unable to read in existing cameras.", e);
+ }
+ }
+
+
+ @Override
+ public void register(Camera camera) {
+ if (camera.getDeviceConfig() == null) {
+ logger.warning("Camera config is null: " + camera);
+ return;
+ }
+ if (!getAvailableDeviceConfigs().contains(camera.getDeviceConfig().getClass())) {
+ logger.warning("Camera data plugin not available: " + camera.getDeviceConfig().getClass());
+ return;
+ }
+
+ logger.info("Registering new camera(id: " + camera.getId() + "): " + camera.getDeviceConfig().getClass());
+ Class extends HalCameraController> controllerClass = (Class extends HalCameraController>) camera.getControllerClass();
+ HalCameraController controller = getControllerInstance(controllerClass);
+
+ if (controller != null)
+ controller.register(camera.getDeviceConfig());
+
+ registeredCameras.add(camera);
+ //detectedCameras.remove(HalDeviceUtil.findDevice(camera.getDeviceConfig(), detectedCameras)); // Remove if this device was detected
}
@Override
- public void deregister(Camera device) {
+ public void deregister(Camera camera) {
+ if (camera.getDeviceConfig() == null) {
+ logger.warning("Camera config is null: " + camera);
+ return;
+ }
+ Class extends HalCameraController> controllerClass = (Class extends HalCameraController>) camera.getControllerClass();
+ HalCameraController controller = (HalCameraController) controllerMap.get(controllerClass);
+ if (controller != null) {
+ logger.info("Deregistering camera(id: " + camera.getId() + "): " + camera.getDeviceConfig().getClass());
+ controller.deregister(camera.getDeviceConfig());
+ registeredCameras.remove(camera);
+ removeControllerIfEmpty(controller);
+ } else {
+ logger.warning("Controller not instantiated: " + camera.getControllerClass());
+ }
}
@Override
@@ -47,13 +105,6 @@ public class CameraControllerManager extends HalAbstractControllerManager events = Event.getLocalEvents(db);
- Collections.sort(events, DeviceNameComparator.getInstance());
+ List cameras = Camera.getCameras(db);
+ Collections.sort(cameras, DeviceNameComparator.getInstance());
Templator tmpl = new Templator(FileUtil.find(OVERVIEW_TEMPLATE));
- tmpl.set("events", events);
+ tmpl.set("cameras", cameras);
return tmpl;
}
}
diff --git a/plugins/hal-nvr/src/se/hal/plugin/nvr/page/MonitorWebPage.java b/plugins/hal-nvr/src/se/hal/plugin/nvr/page/MonitorWebPage.java
index 858f322f..b2a5606f 100644
--- a/plugins/hal-nvr/src/se/hal/plugin/nvr/page/MonitorWebPage.java
+++ b/plugins/hal-nvr/src/se/hal/plugin/nvr/page/MonitorWebPage.java
@@ -26,10 +26,12 @@ package se.hal.plugin.nvr.page;
import se.hal.HalContext;
import se.hal.intf.HalWebPage;
+import se.hal.plugin.nvr.struct.Camera;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.parser.Templator;
+import java.util.List;
import java.util.Map;
public class MonitorWebPage extends HalWebPage {
@@ -45,10 +47,11 @@ public class MonitorWebPage extends HalWebPage {
DBConnection db = HalContext.getDB();
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
- tmpl.set("stream1", "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8");
- tmpl.set("stream2", "http://vjs.zencdn.net/v/oceans.mp4");
- tmpl.set("stream3", "http://vjs.zencdn.net/v/oceans.mp4");
- tmpl.set("stream4", "http://vjs.zencdn.net/v/oceans.mp4");
+
+ List cameras = Camera.getCameras(db);
+ for (int i = 0; i < cameras.size(); i++) {
+ tmpl.set("camera" + (i+1), cameras.get(i));
+ }
return tmpl;
}
diff --git a/plugins/hal-nvr/src/se/hal/plugin/nvr/plugin.json b/plugins/hal-nvr/src/se/hal/plugin/nvr/plugin.json
index a5a17163..68315066 100644
--- a/plugins/hal-nvr/src/se/hal/plugin/nvr/plugin.json
+++ b/plugins/hal-nvr/src/se/hal/plugin/nvr/plugin.json
@@ -3,8 +3,11 @@
"name": "Hal-NVR",
"description": "A Network Video Recorder plugin for recording network streams.",
"interfaces": [
+ {"se.hal.intf.HalDatabaseUpgrader": "se.hal.plugin.nvr.NVRDatabaseUpgrader"},
{"se.hal.intf.HalAbstractControllerManager": "se.hal.plugin.nvr.CameraControllerManager"},
+ {"se.hal.plugin.nvr.intf.HalCameraConfig": "se.hal.plugin.nvr.rtsp.RTSPCameraConfig"},
+
{"se.hal.intf.HalWebPage": "se.hal.plugin.nvr.page.CameraConfigWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.plugin.nvr.page.CameraOverviewWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.plugin.nvr.page.MonitorWebPage"}
diff --git a/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPCameraConfig.java b/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPCameraConfig.java
index 7c638d9e..966efe77 100644
--- a/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPCameraConfig.java
+++ b/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPCameraConfig.java
@@ -27,9 +27,11 @@ package se.hal.plugin.nvr.rtsp;
import se.hal.intf.HalDeviceData;
import se.hal.plugin.nvr.intf.HalCameraConfig;
import se.hal.plugin.nvr.intf.HalCameraController;
+import zutil.ui.conf.Configurator;
public class RTSPCameraConfig implements HalCameraConfig {
+ @Configurator.Configurable(value = "RTSP URL", description = "Url to the RTSP stream of the camera. (Should start with rtsp://)")
private String rtspUrl;
@@ -54,10 +56,24 @@ public class RTSPCameraConfig implements HalCameraConfig {
return null; // TODO:
}
+
@Override
- public boolean equals(Object obj) {
- if (obj instanceof RTSPCameraConfig)
- return rtspUrl.equals(((RTSPCameraConfig) obj).rtspUrl);
- return false;
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RTSPCameraConfig)) return false;
+
+ RTSPCameraConfig that = (RTSPCameraConfig) o;
+
+ return rtspUrl != null ? rtspUrl.equals(that.rtspUrl) : that.rtspUrl == null;
+ }
+
+ @Override
+ public int hashCode() {
+ return rtspUrl != null ? rtspUrl.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return "URL: " + rtspUrl;
}
}
diff --git a/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPCameraRecorder.java b/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPCameraRecorder.java
index e27d6a9f..594aeac7 100644
--- a/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPCameraRecorder.java
+++ b/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPCameraRecorder.java
@@ -1,7 +1,17 @@
package se.hal.plugin.nvr.rtsp;
+import se.hal.HalContext;
+import zutil.io.file.FileUtil;
import zutil.log.LogUtil;
+import zutil.osal.OSALBinaryManager;
+import zutil.osal.app.ffmpeg.FFmpeg;
+import zutil.osal.app.ffmpeg.FFmpegConstants;
+import zutil.osal.app.ffmpeg.FFmpegInput;
+import zutil.osal.app.ffmpeg.FFmpegOutput;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -11,11 +21,21 @@ import java.util.logging.Logger;
public class RTSPCameraRecorder implements Runnable {
private static final Logger logger = LogUtil.getLogger();
+ private static final File FFMPEG_BINARY_PATH = FileUtil.find(HalContext.RESOURCE_ROOT + "/resource/bin/");
+
private RTSPCameraConfig camera;
+ private String storagePath;
+ private Process process;
- public RTSPCameraRecorder(RTSPCameraConfig camera) {
+ public RTSPCameraRecorder(RTSPCameraConfig camera, String storagePath) {
this.camera = camera;
+ this.storagePath = storagePath;
+ }
+
+
+ public RTSPCameraConfig getCamera() {
+ return camera;
}
@@ -24,15 +44,81 @@ public class RTSPCameraRecorder implements Runnable {
logger.info("Starting up RTSP Stream recording thread for: " + camera.getRtspUrl());
try {
+ new File(storagePath).mkdirs();
+ // ----------------------------------
+ // Setup commandline
+ // ----------------------------------
+
+ FFmpegInput ffmpegInput = new FFmpegInput(camera.getRtspUrl());
+
+ //FFmpegOutput ffmpegOutput = new FFmpegOutput(storagePath + File.separator + "stream.mp4");
+ FFmpegOutput ffmpegOutput = new FFmpegOutput(new File(storagePath, "stream_%v/stream.m3u8").getPath());
+ /*ffmpegOutput.addAdditionalArg("-filter_complex \"[0:v]split=3[v1][v2][v3]; [v1]copy[v1out]; [v2]scale=w=1280:h=720[v2out]; [v3]scale=w=640:h=360[v3out]\"",
+ "-map [v1out] -c:v:0 libx264 -x264-params \"nal-hrd=cbr:force-cfr=1\" -b:v:0 5M -maxrate:v:0 5M -minrate:v:0 5M -bufsize:v:0 10M -preset veryfast -g 25 -sc_threshold 0",
+ "-map [v2out] -c:v:1 libx264 -x264-params \"nal-hrd=cbr:force-cfr=1\" -b:v:1 3M -maxrate:v:1 3M -minrate:v:1 3M -bufsize:v:1 3M -preset veryfast -g 25 -sc_threshold 0",
+ "-map [v3out] -c:v:2 libx264 -x264-params \"nal-hrd=cbr:force-cfr=1\" -b:v:2 1M -maxrate:v:2 1M -minrate:v:2 1M -bufsize:v:2 1M -preset veryfast -g 25 -sc_threshold 0",
+ "-map a:0 -c:a:0 aac -b:a:0 96k -ac 2",
+ "-map a:0 -c:a:1 aac -b:a:1 96k -ac 2",
+ "-map a:0 -c:a:2 aac -b:a:2 48k -ac 2",
+ "-var_stream_map \"v:0,a:0,name:Source v:1,a:1,name:720p v:2,a:2,name:360p\""
+ );*/
+ ffmpegOutput.addAdditionalArg(
+ "-c:v:0 libx264 -x264-params \"nal-hrd=cbr:force-cfr=1\" -b:v:0 5M -maxrate:v:0 5M -minrate:v:0 5M -bufsize:v:0 10M -preset veryfast -g 25 -sc_threshold 0"
+ );
+ ffmpegOutput.addAdditionalArg("-f hls",
+ "-hls_time 2", // segment length in seconds
+ //"-hls_playlist_type event", // Do not delete old segments
+ "-hls_flags independent_segments+delete_segments",
+ "-hls_segment_type mpegts",
+ "-hls_segment_filename \"" + new File(storagePath, "stream_%v/data%02d.ts").getPath() + "\"",
+ "-master_pl_name \"playlist.m3u8\""
+ );
+
+ FFmpeg ffmpeg = new FFmpeg();
+ ffmpeg.setLogLevel(FFmpegConstants.FFmpegLogLevel.ERROR);
+ ffmpeg.addInput(ffmpegInput);
+ ffmpeg.addOutput(ffmpegOutput);
+ String cmdParams = ffmpeg.buildCommand();
+
+ // ----------------------------------
+ // Execute command
+ // ----------------------------------
+
+ File cmdPath = OSALBinaryManager.getPath(FFMPEG_BINARY_PATH, "ffmpeg");
+
+ String cmd = cmdPath.getParent() + File.separator + cmdParams;
+ logger.finest("Executing ffmpeg: " + cmd);
+
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() {
+ if (process != null) process.destroyForcibly();
+ }
+ });
+
+ process = Runtime.getRuntime().exec(cmd);
+ BufferedReader output = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+
+ while (process.isAlive()) {
+ String line;
+ while ((line = output.readLine()) != null) {
+ logger.finest("[Cam: " + camera.getRtspUrl() + "] " + line);
+ }
+
+ Thread.sleep(1000);
+ }
+ output.close();
} catch (Exception e) {
- logger.log(Level.SEVERE, "RTSP Stream recording thread has crashed for: " + camera.getRtspUrl(), e);
+ logger.log(Level.SEVERE, "RTSP Stream recording thread has crashed for: " + camera.getRtspUrl(), e);
} finally {
logger.info("Shutting down RTSP Stream recording thread for: " + camera.getRtspUrl());
}
}
public void close() {
- camera = null;
+ if (process != null) {
+ logger.info("Killing ffmpeg instance.");
+ process.destroy();
+ }
}
}
diff --git a/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPController.java b/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPController.java
index 61e6ac39..85897bc4 100644
--- a/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPController.java
+++ b/plugins/hal-nvr/src/se/hal/plugin/nvr/rtsp/RTSPController.java
@@ -24,6 +24,7 @@
package se.hal.plugin.nvr.rtsp;
+import se.hal.HalContext;
import se.hal.intf.HalDeviceConfig;
import se.hal.intf.HalDeviceReportListener;
import se.hal.plugin.nvr.intf.HalCameraController;
@@ -31,7 +32,7 @@ import zutil.log.LogUtil;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.*;
import java.util.logging.Logger;
@@ -40,7 +41,8 @@ public class RTSPController implements HalCameraController {
private static final String CONFIG_RECORDING_PATH = "nvr.recording_path";
- private List cameras = new ArrayList<>();
+ private static ExecutorService threadPool = Executors.newCachedThreadPool();
+ private List recorders = new ArrayList<>();
private List deviceListeners = new CopyOnWriteArrayList<>();
@@ -67,18 +69,28 @@ public class RTSPController implements HalCameraController {
@Override
public void register(HalDeviceConfig deviceConfig) {
- if (deviceConfig instanceof RTSPCameraConfig)
- cameras.add((RTSPCameraConfig) deviceConfig);
+ if (deviceConfig instanceof RTSPCameraConfig) {
+ RTSPCameraConfig rtspCam = (RTSPCameraConfig) deviceConfig;
+ RTSPCameraRecorder recorder = new RTSPCameraRecorder(rtspCam,
+ HalContext.getStringProperty(CONFIG_RECORDING_PATH, HalContext.RESOURCE_WEB_ROOT + "/recordings/" + deviceConfig.hashCode()));
+
+ recorders.add(recorder);
+ threadPool.execute(recorder);
+ }
}
@Override
public void deregister(HalDeviceConfig deviceConfig) {
- cameras.remove(deviceConfig);
+ RTSPCameraRecorder recorder = getRecorder((RTSPCameraConfig) deviceConfig);
+ if (recorder != null) {
+ recorder.close();
+ recorders.remove(recorder);
+ }
}
@Override
public int size() {
- return cameras.size();
+ return recorders.size();
}
@Override
@@ -87,4 +99,12 @@ public class RTSPController implements HalCameraController {
deviceListeners.add(listener);
}
+ private RTSPCameraRecorder getRecorder(RTSPCameraConfig cameraConfig) {
+ for (RTSPCameraRecorder recorder : recorders) {
+ if (recorder.getCamera().equals(cameraConfig))
+ return recorder;
+ }
+ return null;
+ }
+
}
diff --git a/plugins/hal-nvr/src/se/hal/plugin/nvr/struct/Camera.java b/plugins/hal-nvr/src/se/hal/plugin/nvr/struct/Camera.java
index d2a36a8e..a929939e 100644
--- a/plugins/hal-nvr/src/se/hal/plugin/nvr/struct/Camera.java
+++ b/plugins/hal-nvr/src/se/hal/plugin/nvr/struct/Camera.java
@@ -24,7 +24,6 @@
package se.hal.plugin.nvr.struct;
-import se.hal.intf.HalAbstractController;
import se.hal.intf.HalAbstractDevice;
import se.hal.plugin.nvr.intf.HalCameraConfig;
import se.hal.plugin.nvr.intf.HalCameraData;
@@ -34,7 +33,7 @@ import zutil.db.bean.DBBean;
import java.sql.SQLException;
import java.util.List;
-
+@DBBean.DBTable(value="camera", superBean=true)
public class Camera extends HalAbstractDevice {
public static List getCameras(DBConnection db) throws SQLException{
@@ -46,6 +45,10 @@ public class Camera extends HalAbstractDevice