From f8935ec1b2d061097d0582e1f8e44f9ec265be47 Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Tue, 6 Oct 2026 08:18:06 -0500
Subject: [PATCH] keep using ffmpeg module
---
docs/docs/configuration/camera_specific.md | 9 ++--
docs/docs/troubleshooting/go2rtc.md | 4 +-
.../settings/camera-wizard-reolink.spec.ts | 18 +++++++-
web/public/locales/en/views/settings.json | 3 +-
.../settings/wizard/Step2ProbeOrSnapshot.tsx | 3 ++
.../settings/wizard/Step4Validation.tsx | 1 +
web/src/utils/streamHealth.ts | 3 +-
web/src/utils/streamIssues.ts | 43 ++++++++++++-------
8 files changed, 60 insertions(+), 24 deletions(-)
diff --git a/docs/docs/configuration/camera_specific.md b/docs/docs/configuration/camera_specific.md
index cd5d4ff7f8..7bc9b0b9c3 100644
--- a/docs/docs/configuration/camera_specific.md
+++ b/docs/docs/configuration/camera_specific.md
@@ -170,6 +170,7 @@ The [Add Camera Wizard](cameras.md#adding-a-camera-with-the-add-camera-wizard) i
1. Click **Add Camera** in .
2. Choose **Manual selection** as the stream detection method and select **Reolink** as the camera brand.
3. The wizard queries the camera and automatically uses an http-flv stream for cameras 5MP and lower. For higher resolution cameras, it tries the http-flv stream first and falls back to RTSP when the camera does not support it.
+4. The wizard turns on **Use stream compatibility mode** for the http-flv stream it selects. Enable it for any other http-flv stream you add, such as the sub stream.
If you use the **Probe camera** method instead, the discovered stream URLs will be RTSP. For Reolink cameras 5MP and lower, where http-flv is recommended, the wizard will show a warning in the validation step.
@@ -202,20 +203,20 @@ go2rtc:
your_reolink_camera:
- "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=username&password=password#video=copy#audio=copy#audio=opus"
your_reolink_camera_sub:
- - "http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=username&password=password"
+ - "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=username&password=password"
# example for connecting to a Reolink camera that supports two way talk
your_reolink_camera_twt:
- "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=username&password=password#video=copy#audio=copy#audio=opus"
- "rtsp://username:password@reolink_ip/Preview_01_sub"
your_reolink_camera_twt_sub:
- - "http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=username&password=password"
+ - "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=username&password=password"
- "rtsp://username:password@reolink_ip/Preview_01_sub"
# example for connecting to a Reolink NVR
your_reolink_camera_via_nvr:
- - "http://reolink_nvr_ip/flv?port=1935&app=bcs&stream=channel3_main.bcs&user=username&password=password" # channel numbers are 0-15
+ - "ffmpeg:http://reolink_nvr_ip/flv?port=1935&app=bcs&stream=channel3_main.bcs&user=username&password=password" # channel numbers are 0-15
- "ffmpeg:your_reolink_camera_via_nvr#audio=aac"
your_reolink_camera_via_nvr_sub:
- - "http://reolink_nvr_ip/flv?port=1935&app=bcs&stream=channel3_ext.bcs&user=username&password=password"
+ - "ffmpeg:http://reolink_nvr_ip/flv?port=1935&app=bcs&stream=channel3_ext.bcs&user=username&password=password"
cameras:
your_reolink_camera:
diff --git a/docs/docs/troubleshooting/go2rtc.md b/docs/docs/troubleshooting/go2rtc.md
index 12ddf72d65..2ee8bd756e 100644
--- a/docs/docs/troubleshooting/go2rtc.md
+++ b/docs/docs/troubleshooting/go2rtc.md
@@ -190,14 +190,14 @@ If you rename or remove a go2rtc stream while experimenting and the live stream
Several camera brands have well-known quirks with go2rtc. Rather than repeat them here, see the [camera-specific configuration](/configuration/camera_specific) page, which covers them in detail. The highlights:
-- **Reolink**: RTSP is unreliable on many models; the **http-flv** stream is recommended, and you must enable HTTP/RTMP in the camera and **reboot** it. 6MP+ models stream H.265 over http-flv-enhanced, which requires FFmpeg 8.0. See [Reolink Cameras](/configuration/camera_specific#reolink-cameras).
+- **Reolink**: RTSP is unreliable on many models; the **http-flv** stream through the FFmpeg module is recommended, and you must enable HTTP/RTMP in the camera and **reboot** it. 6MP+ models stream H.265 over http-flv-enhanced, which requires FFmpeg 8.0. See [Reolink Cameras](/configuration/camera_specific#reolink-cameras).
- **TP-Link Tapo**: use go2rtc's native `tapo://` source for stability and two-way audio; a stale RTSP credential can often be revived by clicking play once in the go2rtc web UI.
- **Ubiquiti/UniFi Protect**: use the `rtspx://` scheme (not `rtsps://…?enableSrtp`).
- **Amcrest/Dahua**: use the `/cam/realmonitor?channel=1&subtype=N` scheme, where `subtype=0` is the main stream. See [Amcrest & Dahua](/configuration/camera_specific#amcrest--dahua).
## Non-RTSP sources and the FFmpeg module
-go2rtc's native zero-copy handling only supports well-formed RTSP H.264/H.265. Anything else (MJPEG, RTMP, or unusual codecs) must be handed to the FFmpeg module by prefixing the source with `ffmpeg:`. This is also necessary for some camera streams to be parsed at all, at the cost of slightly slower startup. MJPEG and other non-H.264 sources additionally need `#video=h264` (with `#hardware`) before they can be used for the `record`, `detect`, or restream roles. See [MJPEG Cameras](/configuration/camera_specific#mjpeg-cameras) for a complete example.
+go2rtc's native zero-copy handling only supports well-formed RTSP H.264/H.265. Anything else (MJPEG, HTTP/HTTP-FLV, RTMP, or unusual codecs) must be handed to the FFmpeg module by prefixing the source with `ffmpeg:`. This is also necessary for some camera streams to be parsed at all, at the cost of slightly slower startup. MJPEG and other non-H.264 sources additionally need `#video=h264` (with `#hardware`) before they can be used for the `record`, `detect`, or restream roles. See [MJPEG Cameras](/configuration/camera_specific#mjpeg-cameras) for a complete example.
## Hardware-accelerated transcoding with FFmpeg 8
diff --git a/web/e2e/specs/settings/camera-wizard-reolink.spec.ts b/web/e2e/specs/settings/camera-wizard-reolink.spec.ts
index d03bd78081..cacc0bfcc0 100644
--- a/web/e2e/specs/settings/camera-wizard-reolink.spec.ts
+++ b/web/e2e/specs/settings/camera-wizard-reolink.spec.ts
@@ -3,7 +3,8 @@
*
* The wizard asks the camera for its resolution, then probes http-flv first
* above 5MP and falls back to RTSP. The Step 4 RTSP warning is only for
- * cameras that should be on http-flv.
+ * cameras that should be on http-flv. An http-flv stream the wizard selects
+ * is registered with go2rtc through the ffmpeg module.
*/
import { test, expect } from "../../fixtures/frigate-test";
@@ -12,6 +13,7 @@ import type { Page } from "@playwright/test";
const FLV_PATH = "channel0_main.bcs";
const RTSP_PATH = "Preview_01_main";
const RTSP_WARNING = "Reolink RTSP is not recommended";
+const HTTP_WARNING = "Reolink HTTP streams should use FFmpeg";
const FFPROBE_OK = [
{
@@ -105,6 +107,20 @@ test.describe("Camera wizard Reolink stream selection @medium @mobile", () => {
expect(probed).toEqual([FLV_PATH]);
await expect(dialog.locator(`input[value*="${FLV_PATH}"]`)).toBeVisible();
+
+ const registered: string[] = [];
+ await frigateApp.page.route("**/api/go2rtc/streams/**", (route) => {
+ const src = new URL(route.request().url()).searchParams.get("src");
+ if (src) registered.push(src);
+ return route.fulfill({ json: {} });
+ });
+
+ await dialog.getByRole("button", { name: /^Next$/i }).click();
+ await expect(
+ dialog.getByRole("button", { name: /Save New Camera/i }),
+ ).toBeVisible();
+ await expect.poll(() => registered[0]).toMatch(/^ffmpeg:http:\/\//);
+ await expect(dialog.getByText(HTTP_WARNING)).toHaveCount(0);
});
test("above 5MP falls back to RTSP without a warning", async ({
diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json
index 6246fe0629..5bbe7b80d1 100644
--- a/web/public/locales/en/views/settings.json
+++ b/web/public/locales/en/views/settings.json
@@ -505,7 +505,8 @@
"audioCodecRequired": "An audio stream is required to support audio detection.",
"restreamingWarning": "Reducing connections to the camera for the record stream may increase CPU usage slightly.",
"brands": {
- "reolink-rtsp": "Reolink RTSP is not recommended. Enable HTTP in the camera's firmware settings and restart the wizard."
+ "reolink-rtsp": "Reolink RTSP is not recommended. Enable HTTP in the camera's firmware settings and restart the wizard.",
+ "reolink-http": "Reolink HTTP streams should use FFmpeg for better compatibility. Enable 'Use stream compatibility mode' for this stream."
},
"dahua": {
"substreamWarning": "Substream 1 is locked to a low resolution. Many Dahua / Amcrest / EmpireTech cameras support additional substreams that need to be enabled in the camera's settings. It is recommended to check and utilize those streams if available."
diff --git a/web/src/components/settings/wizard/Step2ProbeOrSnapshot.tsx b/web/src/components/settings/wizard/Step2ProbeOrSnapshot.tsx
index 84818d9d40..f1cfdac4ea 100644
--- a/web/src/components/settings/wizard/Step2ProbeOrSnapshot.tsx
+++ b/web/src/components/settings/wizard/Step2ProbeOrSnapshot.tsx
@@ -393,6 +393,9 @@ export default function Step2ProbeOrSnapshot({
url: streamUrl,
roles: ["detect"] as StreamRole[],
testResult: result,
+ useFfmpeg:
+ wizardData.brandTemplate === "reolink" &&
+ streamUrl.startsWith("http://"),
},
],
});
diff --git a/web/src/components/settings/wizard/Step4Validation.tsx b/web/src/components/settings/wizard/Step4Validation.tsx
index 4516379d68..a6d21aba16 100644
--- a/web/src/components/settings/wizard/Step4Validation.tsx
+++ b/web/src/components/settings/wizard/Step4Validation.tsx
@@ -500,6 +500,7 @@ function StreamIssues({
roles: stream.roles,
brand: wizardData.brandTemplate,
reolinkProtocol: wizardData.reolinkProtocol,
+ useFfmpeg: stream.useFfmpeg,
restream: stream.restream,
testResult: stream.testResult,
},
diff --git a/web/src/utils/streamHealth.ts b/web/src/utils/streamHealth.ts
index 4dd1fc1d7b..6fe72d0a2b 100644
--- a/web/src/utils/streamHealth.ts
+++ b/web/src/utils/streamHealth.ts
@@ -12,7 +12,7 @@ import {
} from "@/utils/streamIssues";
// rules the add camera wizard shows that do not belong on the Health tab
-const WIZARD_ONLY_RULES = new Set(["restream", "reolink-rtsp"]);
+const WIZARD_ONLY_RULES = new Set(["restream", "reolink-rtsp", "reolink-http"]);
/**
* Whether the record output keeps the camera's audio codec. The default
@@ -115,6 +115,7 @@ export function streamHealth(
url,
roles: input.roles as StreamRole[],
brand: inferCameraBrand(url),
+ useFfmpeg: restream?.useFfmpeg,
restream: !!restream,
testResult: result,
},
diff --git a/web/src/utils/streamIssues.ts b/web/src/utils/streamIssues.ts
index d5b848b008..f763cc9c6d 100644
--- a/web/src/utils/streamIssues.ts
+++ b/web/src/utils/streamIssues.ts
@@ -14,6 +14,7 @@ export type StreamIssueInput = {
roles: StreamRole[];
brand?: CameraBrand;
reolinkProtocol?: "http-flv" | "rtsp" | null;
+ useFfmpeg?: boolean;
restream?: boolean;
testResult?: TestResult;
};
@@ -94,18 +95,27 @@ export function getStreamIssues(
const result: StreamIssue[] = [];
const { roles, testResult } = input;
- if (
- input.brand === "reolink" &&
- input.reolinkProtocol !== "rtsp" &&
- input.url.toLowerCase().startsWith("rtsp://")
- ) {
- result.push({
- type: "warning",
- rule: "reolink-rtsp",
- message: t("cameraWizard.step4.issues.brands.reolink-rtsp", {
- ns: "views/settings",
- }),
- });
+ if (input.brand === "reolink") {
+ const streamUrl = input.url.toLowerCase();
+ if (streamUrl.startsWith("rtsp://") && input.reolinkProtocol !== "rtsp") {
+ result.push({
+ type: "warning",
+ rule: "reolink-rtsp",
+ message: t("cameraWizard.step4.issues.brands.reolink-rtsp", {
+ ns: "views/settings",
+ }),
+ });
+ }
+
+ if (streamUrl.startsWith("http://") && !input.useFfmpeg) {
+ result.push({
+ type: "warning",
+ rule: "reolink-http",
+ message: t("cameraWizard.step4.issues.brands.reolink-http", {
+ ns: "views/settings",
+ }),
+ });
+ }
}
if (testResult?.videoCodec) {
@@ -259,7 +269,7 @@ export function getStreamIssues(
export function resolveRestreamSource(
path: string,
streams: Record | undefined,
-): { url: string } | undefined {
+): { url: string; useFfmpeg: boolean } | undefined {
const name = parseRestreamStreamName(path);
if (!name || !streams) {
@@ -279,8 +289,11 @@ export function resolveRestreamSource(
}
if (source.startsWith("ffmpeg:")) {
- return { url: source.slice("ffmpeg:".length).split("#")[0] };
+ return {
+ url: source.slice("ffmpeg:".length).split("#")[0],
+ useFfmpeg: true,
+ };
}
- return { url: source };
+ return { url: source, useFfmpeg: false };
}