Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ipcamera] Add support to change MJPEG source to a different RTSP sub stream to lower CPU usage #17591

Merged
merged 4 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions bundles/org.openhab.binding.ipcamera/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,10 @@ Thing ipcamera:hikvision:West "West Camera"

### Reolink

- NVR's made by Reolink have ONVIF disabled by default and may require a screen connected to the hardware to enable ONVIF or newer firmwares may be able to do this via their app or web UI.
- NVR's made by Reolink have ONVIF disabled by default and may require a screen connected to the hardware to enable ONVIF, or newer firmwares may be able to do this via their app or web UI.
- This binding will use the Reolink API for polling the alarms if the `nvrChannel` is 1 or higher and does not need ONVIF to be enabled. To use ONVIF event methods for the alarms, you can set `nvrChannel` to 0.
- Cameras have ONVIF, RTSP and HTTP disabled by default, to enable these required features, do the following: DEVICE SETTINGS>NETWORK>ADVANCED>PORT SETTINGS> then turn these on leaving the default port number alone.
- Consider setting the substream of the camera to be 4 FPS and to 640*360 as this will lower CPU load if using the ipcamera.mjpeg stream.

## Discovery

Expand Down Expand Up @@ -194,7 +196,7 @@ If you do not specify any of these, the binding will use the default which shoul
| `alarmInputUrl` | A URL you can use for the FFmpeg created Audio and Motion Alarms as they don't require high res feeds as they are not seen. |
| `customMotionAlarmUrl`| Foscam only, for custom enable motion alarm use. More info found in Foscam's setup steps. |
| `customAudioAlarmUrl`| Foscam only, for custom enable audio alarm use. More info found in Foscam's setup steps. |
| `mjpegUrl`| A HTTP URL for MJPEG format streams. If you enter 'ffmpeg' the stream can be generated from the RTSP URL. |
| `mjpegUrl`| A HTTP or RTSP URL to use for MJPEG streams. If you enter 'ffmpeg' the stream can be generated using the RTSP URL. |
| `ffmpegInput`| Best if this stream is in H.264 format and can be RTSP or HTTP URLs. Leave this blank to use the auto detected RTSP address for ONVIF cameras. |
| `ffmpegInputOptions` | Allows you to specify any options before the -i on the commands for FFmpeg. If you have an ESP32 camera that only has a mjpeg stream then make this equal `-f mjpeg`. |
| `ffmpegLocation`| The full path including the filename for where you have installed FFmpeg. The default should work for most Linux installs but if using windows use this format: `c:\ffmpeg\bin\ffmpeg.exe` |
Expand Down Expand Up @@ -434,14 +436,15 @@ You can cast it which can be handy to show a moving picture that keeps repeating
## MJPEG Streams

Cameras that have built in MJPEG abilities can stream to openHAB with the MJPEG format with next to no CPU load, less than 1 second lag, and FFmpeg does not need to be installed.
Cameras without this ability can still use this binding to convert their RTSP H.264 format to MJPEG (keep reading for more on this below) and this will take a lot of CPU power to handle the conversion.
Cameras without this ability can still use this binding to convert their RTSP H.264 format to MJPEG (keep reading for more on this below) and this may use a lot of CPU power to handle the conversion.
The lower the resolution of the stream, the lower the CPU load, so consider using a substream of the camera in the `mjpegUrl` configuration field.
The alternative is to use HLS format which does not need the conversion and does not use any CPU to speak of.
For video without a delay, you need MJPEG and without a camera that can create it, you will need to use a lot of CPU power.
This can be done in a dedicated video server which will be the only way with lots of cameras, unless you purchase cameras that have the ability built in.

An alternative way to keep the CPU load low is to use the `snapshots.mjpeg` feature of the binding to create a stream from the cameras snapshots instead of the RTSP stream.
An alternative way to keep the CPU load low is to use the `snapshots.mjpeg` or `autofps.mjpeg` feature of the binding to create a stream from the cameras snapshots instead of the RTSP stream.

The main cameras that can do MJPEG with very low CPU load are Amcrest, Dahua, Hikvision, Foscam HD and Instar HD.
The main cameras that can do MJPEG with very low CPU load are Amcrest, Dahua, ESP32 Camera, Hikvision, Foscam HD and Instar HD.
To set this up, see [Special Notes for Different Brands](#special-notes-for-different-brands).
The binding can then distribute this stream to many devices around your home whilst the camera only sees a single open stream.

Expand Down Expand Up @@ -597,7 +600,7 @@ Webview url="http://192.168.6.4:8080/static/html/file.html" height=5
There are two ways to cast a camera.

1. openHAB Cloud Connector and using metadata/tags.
1. Chromecast Bindings `playuri` channel.
2. Chromecast Bindings `playuri` channel.

The first method once setup allows you to ask "OK Google show X camera", or "OK Google show X camera on Y display".
By optionally naming the display that you wish to use, it can be cast directly to your Chromecast (connected to your TV) by speaking to a Google Nest Mini.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public Ffmpeg(IpCameraHandler handle, FFmpegFormat format, String ffmpegLocation
ipCameraHandler = handle;
String altInput = input;
// Input can be snapshots not just rtsp or http
if (!password.isEmpty() && !input.contains("@") && input.contains("rtsp")) {
if (!password.isEmpty() && !input.contains("@") && input.toLowerCase().startsWith("rtsp")) {
String credentials = username + ":" + this.password + "@";
// will not work for https: but currently binding does not use https
altInput = input.substring(0, 7) + credentials + input.substring(7);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ public void startStreamServer() {
}

public void openCamerasStream() {
if (mjpegUri.isEmpty() || "ffmpeg".equals(mjpegUri)) {
if (usingRtspForMjpeg()) {
setupFfmpegFormat(FFmpegFormat.MJPEG);
return;
}
Expand Down Expand Up @@ -811,7 +811,7 @@ public void setupFfmpegFormat(FFmpegFormat format) {
logger.warn("The camera tried to use a FFmpeg feature when the location for FFmpeg is not known.");
return;
}
if (rtspUri.toLowerCase().contains("rtsp")) {
if (mjpegUri.toLowerCase().startsWith("rtsp://") || rtspUri.toLowerCase().startsWith("rtsp://")) {
if (inputOptions.isEmpty()) {
inputOptions = "-rtsp_transport tcp";
}
Expand Down Expand Up @@ -938,10 +938,17 @@ public void setupFfmpegFormat(FFmpegFormat format) {
} else {
inputOptions += " -hide_banner";
}
ffmpegMjpeg = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, rtspUri,
cameraConfig.getMjpegOptions(), "http://127.0.0.1:" + SERVLET_PORT + "/ipcamera/"
+ getThing().getUID().getId() + "/ipcamera.jpg",
cameraConfig.getUser(), cameraConfig.getPassword());
if (mjpegUri.toLowerCase().startsWith("rtsp://")) {
ffmpegMjpeg = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, mjpegUri,
cameraConfig.getMjpegOptions(), "http://127.0.0.1:" + SERVLET_PORT + "/ipcamera/"
+ getThing().getUID().getId() + "/ipcamera.jpg",
cameraConfig.getUser(), cameraConfig.getPassword());
} else {
ffmpegMjpeg = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, rtspUri,
cameraConfig.getMjpegOptions(), "http://127.0.0.1:" + SERVLET_PORT + "/ipcamera/"
+ getThing().getUID().getId() + "/ipcamera.jpg",
cameraConfig.getUser(), cameraConfig.getPassword());
}
}
Ffmpeg localMjpeg = ffmpegMjpeg;
if (localMjpeg != null) {
Expand Down Expand Up @@ -1644,7 +1651,10 @@ public void initialize() {
threadPool = Executors.newScheduledThreadPool(2);
mainEventLoopGroup = new NioEventLoopGroup(3);
snapshotUri = getCorrectUrlFormat(cameraConfig.getSnapshotUrl());
mjpegUri = getCorrectUrlFormat(cameraConfig.getMjpegUrl());
mjpegUri = cameraConfig.getMjpegUrl();
if (!mjpegUri.toLowerCase().startsWith("rtsp://")) {
mjpegUri = getCorrectUrlFormat(mjpegUri);
}
rtspUri = cameraConfig.getFfmpegInput();
if (cameraConfig.getFfmpegOutput().isEmpty()) {
cameraConfig
Expand Down Expand Up @@ -1734,6 +1744,10 @@ public void initialize() {
rtspUri = "rtsp://" + cameraConfig.getIp() + ":554/h264Preview_0"
+ (cameraConfig.getNvrChannel() + 1) + "_main";
}
if (mjpegUri.isEmpty()) {
mjpegUri = "rtsp://" + cameraConfig.getIp() + ":554/h264Preview_0"
+ (cameraConfig.getNvrChannel() + 1) + "_sub";
}
break;
}
// for poll times 9 seconds and above don't display a warning about the Image channel.
Expand Down Expand Up @@ -1773,7 +1787,7 @@ private boolean supportsOnvifEvents() {
private void keepMjpegRunning() {
CameraServlet localServlet = servlet;
if (localServlet != null && !localServlet.openStreams.isEmpty()) {
if (!mjpegUri.isEmpty() && !"ffmpeg".equals(mjpegUri)) {
if (!usingRtspForMjpeg()) {
localServlet.openStreams.queueFrame(("--" + localServlet.openStreams.boundary + "\r\n\r\n").getBytes());
}
localServlet.openStreams.queueFrame(getSnapshot());
Expand Down Expand Up @@ -1871,6 +1885,10 @@ public String getWhiteList() {
return cameraConfig.getIpWhitelist();
}

public boolean usingRtspForMjpeg() {
return (mjpegUri.isEmpty() || "ffmpeg".equals(mjpegUri) || mjpegUri.toLowerCase().startsWith("rtsp://"));
}

@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(IpCameraActions.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ public void processReply(RequestType requestType, String message) {
if (xml != null) {
rtspUri = xml;
logger.debug("GetStreamUri: {}", rtspUri);
if (ipCameraHandler.cameraConfig.getFfmpegInput().isEmpty()) {
if (ipCameraHandler.rtspUri.isEmpty()) {// only use if not hard coded in initialize()
ipCameraHandler.rtspUri = rtspUri;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,12 @@ public void run() {
if (openStreams.isEmpty()) {
logger.debug("First stream requested, opening up stream from camera");
handler.openCamerasStream();
if (handler.mjpegUri.isEmpty() || "ffmpeg".equals(handler.mjpegUri)) {
if (handler.usingRtspForMjpeg()) {
output = new StreamOutput(resp);
} else {
output = new StreamOutput(resp, handler.mjpegContentType);
}
} else if (handler.mjpegUri.isEmpty() || "ffmpeg".equals(handler.mjpegUri)) {
} else if (handler.usingRtspForMjpeg()) {
output = new StreamOutput(resp);
} else {
ChannelTracking tracker = handler.channelTrackingMap.get(handler.getTinyUrl(handler.mjpegUri));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2419,7 +2419,7 @@
<label>MJPEG Options</label>
<description>This gives you direct access to specify your own FFmpeg options to be used for MJPEG streams.
</description>
<default>-q:v 5 -r 2 -vf scale=640:-2 -update 1</default>
<default>-q:v 4 -r 4 -update 1</default>
<advanced>true</advanced>
</parameter>

Expand Down