From 3ed49289005d7846fa07f19ad14cb7b7071624e9 Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 14:42:45 +0100
Subject: [PATCH 01/12] onvif: Add Support for Camera Names and Main/Sub stream
---
internal/onvif/onvif.go | 69 +++++++++++++++++++++++++++++--
pkg/onvif/server.go | 92 ++++++++++++++++++++++++++++++++++-------
2 files changed, 141 insertions(+), 20 deletions(-)
diff --git a/internal/onvif/onvif.go b/internal/onvif/onvif.go
index 6dfa633a6..adb5e6163 100644
--- a/internal/onvif/onvif.go
+++ b/internal/onvif/onvif.go
@@ -8,6 +8,7 @@ import (
"os"
"strconv"
"time"
+ "sort"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/app"
@@ -18,7 +19,29 @@ import (
"github.com/rs/zerolog"
)
+var OnvifCameras []onvif.OnvifCamera
+
func Init() {
+ var cfg struct {
+ OnvifCameras []onvif.OnvifCamera `yaml:"onvif"`
+ }
+
+ app.LoadConfig(&cfg)
+ OnvifCameras = cfg.OnvifCameras
+
+ // Debug print
+ data, err := json.MarshalIndent(OnvifCameras, "", " ")
+ if err != nil {
+ fmt.Println("Error marshalling JSON:", err)
+ } else {
+ fmt.Println("Loaded ONVIF cameras configuration:")
+ fmt.Println(string(data))
+ }
+
+ sort.Slice(OnvifCameras, func(i, j int) bool {
+ return OnvifCameras[i].ID < OnvifCameras[j].ID
+ })
+
log = app.GetLogger("onvif")
streams.HandleFunc("onvif", streamOnvif)
@@ -32,6 +55,31 @@ func Init() {
var log zerolog.Logger
+func GetConfiguredStreams() []string {
+ if len(OnvifCameras) == 0 {
+ return streams.GetAllNames()
+ }
+
+ var streamsList []string
+ for _, cam := range OnvifCameras {
+ streamsList = append(streamsList, cam.MainStream)
+ if cam.SubStream != "" {
+ streamsList = append(streamsList, cam.SubStream)
+ }
+ }
+
+ return streamsList
+}
+
+func GetCameraNameByMainStream(mainStream string) string {
+ for _, cam := range OnvifCameras {
+ if cam.MainStream == mainStream || cam.SubStream == mainStream {
+ return cam.Name
+ }
+ }
+ return "Unknown Camera"
+}
+
func streamOnvif(rawURL string) (core.Producer, error) {
client, err := onvif.NewClient(rawURL)
if err != nil {
@@ -86,6 +134,13 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
case onvif.DeviceGetServices:
b = onvif.GetServicesResponse(r.Host)
+ case onvif.DeviceGetOSDs:
+ token := onvif.FindTagValue(b, "ConfigurationToken")
+ b = onvif.GetOSDsResponse(token, GetCameraNameByMainStream(token))
+
+ case onvif.DeviceGetOSDOptions:
+ b = onvif.GetOSDOptionsResponse()
+
case onvif.DeviceGetDeviceInformation:
// important for Hass: SerialNumber (unique server ID)
b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host)
@@ -103,19 +158,25 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
})
case onvif.MediaGetVideoSources:
- b = onvif.GetVideoSourcesResponse(streams.GetAllNames())
+ b = onvif.GetVideoSourcesResponse(GetConfiguredStreams())
case onvif.MediaGetProfiles:
// important for Hass: H264 codec, width, height
- b = onvif.GetProfilesResponse(streams.GetAllNames())
+ b = onvif.GetProfilesResponse(OnvifCameras)
case onvif.MediaGetProfile:
token := onvif.FindTagValue(b, "ProfileToken")
- b = onvif.GetProfileResponse(token)
+ fmt.Println("MediaGetProfile:")
+ fmt.Println(string(token))
+ for _, cam := range OnvifCameras {
+ if(cam.MainStream == token || cam.SubStream == token){
+ b = onvif.GetProfileResponse(cam)
+ }
+ }
case onvif.MediaGetVideoSourceConfigurations:
// important for Happytime Onvif Client
- b = onvif.GetVideoSourceConfigurationsResponse(streams.GetAllNames())
+ b = onvif.GetVideoSourceConfigurationsResponse(OnvifCameras)
case onvif.MediaGetVideoSourceConfiguration:
token := onvif.FindTagValue(b, "ConfigurationToken")
diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go
index 54272798a..b13cded56 100644
--- a/pkg/onvif/server.go
+++ b/pkg/onvif/server.go
@@ -6,6 +6,13 @@ import (
"time"
)
+type OnvifCamera struct {
+ ID int `yaml:"id"`
+ Name string `yaml:"name"`
+ MainStream string `yaml:"main_stream"`
+ SubStream string `yaml:"sub_stream,omitempty"`
+}
+
const ServiceGetServiceCapabilities = "GetServiceCapabilities"
const (
@@ -18,6 +25,8 @@ const (
DeviceGetNetworkInterfaces = "GetNetworkInterfaces"
DeviceGetNetworkProtocols = "GetNetworkProtocols"
DeviceGetNTP = "GetNTP"
+ DeviceGetOSDs = "GetOSDs"
+ DeviceGetOSDOptions = "GetOSDOptions"
DeviceGetScopes = "GetScopes"
DeviceGetServices = "GetServices"
DeviceGetSystemDateAndTime = "GetSystemDateAndTime"
@@ -140,51 +149,68 @@ func GetMediaServiceCapabilitiesResponse() []byte {
return e.Bytes()
}
-func GetProfilesResponse(names []string) []byte {
+func GetProfilesResponse(OnvifCameras []OnvifCamera) []byte {
e := NewEnvelope()
e.Append(`
`)
- for _, name := range names {
- appendProfile(e, "Profiles", name)
+ for _, cam := range OnvifCameras {
+ appendProfile(e, "Profiles", cam)
}
e.Append(``)
return e.Bytes()
}
-func GetProfileResponse(name string) []byte {
+func GetProfileResponse(cam OnvifCamera) []byte {
e := NewEnvelope()
e.Append(`
`)
- appendProfile(e, "Profile", name)
+ appendProfile(e, "Profile", cam)
e.Append(``)
return e.Bytes()
}
-func appendProfile(e *Envelope, tag, name string) {
- // empty `RateControl` important for UniFi Protect
- e.Append(`
- `, name, `
-
+func appendProfile(e *Envelope, tag string, cam OnvifCamera) {
+ e.Append(`
+ `, cam.MainStream, `
+
VSC
- `, name, `
+ `, cam.MainStream, `
-
- VEC
+
+ MainStream
H264
19201080
`)
+
+ if cam.SubStream != "" {
+ e.Append(`
+ `, cam.SubStream, `
+
+ VSC
+ `, cam.MainStream, `
+
+
+
+ SubStream
+ H264
+ 640360
+
+
+
+`)
+ }
}
-func GetVideoSourceConfigurationsResponse(names []string) []byte {
+func GetVideoSourceConfigurationsResponse(OnvifCameras []OnvifCamera) []byte {
e := NewEnvelope()
e.Append(`
`)
- for _, name := range names {
- appendProfile(e, "Configurations", name)
+ for _, cam := range OnvifCameras {
+ appendProfile(e, "Configurations", cam)
}
e.Append(``)
return e.Bytes()
@@ -235,6 +261,40 @@ func GetSnapshotUriResponse(uri string) []byte {
return e.Bytes()
}
+func GetOSDOptionsResponse() []byte {
+ e := NewEnvelope()
+ e.Append(`
+
+
+ Text
+ Custom
+
+ Plain
+
+
+`)
+ return e.Bytes()
+}
+
+func GetOSDsResponse(configurationToken string, cameraName string) []byte {
+ e := NewEnvelope()
+ e.Append(`
+
+ `, configurationToken, `
+ Text
+
+ Custom
+
+
+
+ Plain
+ `, cameraName, `
+
+
+`)
+ return e.Bytes()
+}
+
func StaticResponse(operation string) []byte {
switch operation {
case DeviceGetSystemDateAndTime:
From 92749fe7e0f9c8566869f81c2de76ce612e0dc78 Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 14:45:43 +0100
Subject: [PATCH 02/12] remove my debug
---
internal/onvif/onvif.go | 2 --
1 file changed, 2 deletions(-)
diff --git a/internal/onvif/onvif.go b/internal/onvif/onvif.go
index adb5e6163..2aadd6850 100644
--- a/internal/onvif/onvif.go
+++ b/internal/onvif/onvif.go
@@ -166,8 +166,6 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
case onvif.MediaGetProfile:
token := onvif.FindTagValue(b, "ProfileToken")
- fmt.Println("MediaGetProfile:")
- fmt.Println(string(token))
for _, cam := range OnvifCameras {
if(cam.MainStream == token || cam.SubStream == token){
b = onvif.GetProfileResponse(cam)
From 44a5624a4dd6f74a44def40c689f876b15b950e2 Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 14:50:27 +0100
Subject: [PATCH 03/12] remove debug code
---
internal/onvif/onvif.go | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/internal/onvif/onvif.go b/internal/onvif/onvif.go
index 2aadd6850..5d7e2ea65 100644
--- a/internal/onvif/onvif.go
+++ b/internal/onvif/onvif.go
@@ -29,15 +29,6 @@ func Init() {
app.LoadConfig(&cfg)
OnvifCameras = cfg.OnvifCameras
- // Debug print
- data, err := json.MarshalIndent(OnvifCameras, "", " ")
- if err != nil {
- fmt.Println("Error marshalling JSON:", err)
- } else {
- fmt.Println("Loaded ONVIF cameras configuration:")
- fmt.Println(string(data))
- }
-
sort.Slice(OnvifCameras, func(i, j int) bool {
return OnvifCameras[i].ID < OnvifCameras[j].ID
})
From 65a51381afef74ce98aa30818e6d07f07c8f0d98 Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 14:53:59 +0100
Subject: [PATCH 04/12] Change `id` to `index`
---
internal/onvif/onvif.go | 2 +-
pkg/onvif/server.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/internal/onvif/onvif.go b/internal/onvif/onvif.go
index 5d7e2ea65..9464f0c2e 100644
--- a/internal/onvif/onvif.go
+++ b/internal/onvif/onvif.go
@@ -30,7 +30,7 @@ func Init() {
OnvifCameras = cfg.OnvifCameras
sort.Slice(OnvifCameras, func(i, j int) bool {
- return OnvifCameras[i].ID < OnvifCameras[j].ID
+ return OnvifCameras[i].Index < OnvifCameras[j].Index
})
log = app.GetLogger("onvif")
diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go
index b13cded56..d807b419e 100644
--- a/pkg/onvif/server.go
+++ b/pkg/onvif/server.go
@@ -7,7 +7,7 @@ import (
)
type OnvifCamera struct {
- ID int `yaml:"id"`
+ Index int `yaml:"index"`
Name string `yaml:"name"`
MainStream string `yaml:"main_stream"`
SubStream string `yaml:"sub_stream,omitempty"`
From f953273e83b766b683bcf7019521c511ace5b3f0 Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 15:44:05 +0100
Subject: [PATCH 05/12] remove index from OnvifCamera
---
internal/onvif/onvif.go | 5 -----
pkg/onvif/server.go | 1 -
2 files changed, 6 deletions(-)
diff --git a/internal/onvif/onvif.go b/internal/onvif/onvif.go
index 9464f0c2e..e61ec528b 100644
--- a/internal/onvif/onvif.go
+++ b/internal/onvif/onvif.go
@@ -8,7 +8,6 @@ import (
"os"
"strconv"
"time"
- "sort"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/app"
@@ -29,10 +28,6 @@ func Init() {
app.LoadConfig(&cfg)
OnvifCameras = cfg.OnvifCameras
- sort.Slice(OnvifCameras, func(i, j int) bool {
- return OnvifCameras[i].Index < OnvifCameras[j].Index
- })
-
log = app.GetLogger("onvif")
streams.HandleFunc("onvif", streamOnvif)
diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go
index d807b419e..43c9901cf 100644
--- a/pkg/onvif/server.go
+++ b/pkg/onvif/server.go
@@ -7,7 +7,6 @@ import (
)
type OnvifCamera struct {
- Index int `yaml:"index"`
Name string `yaml:"name"`
MainStream string `yaml:"main_stream"`
SubStream string `yaml:"sub_stream,omitempty"`
From 05a13bc933eb0a12fdd1c35293ac9e9e05c34d63 Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 16:00:20 +0100
Subject: [PATCH 06/12] Add ONVIF module to README.md
---
README.md | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/README.md b/README.md
index 90a2537fc..3899d5507 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,7 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg
* [Module: MP4](#module-mp4)
* [Module: HLS](#module-hls)
* [Module: MJPEG](#module-mjpeg)
+ * [Module: ONVIF](#module-onvif)
* [Module: Log](#module-log)
* [Security](#security)
* [Codecs filters](#codecs-filters)
@@ -1208,6 +1209,46 @@ API examples:
[](https://www.youtube.com/watch?v=sHj_3h_sX7M)
+### Module: ONVIF
+
+This module provides an **ONVIF server** that allows go2rtc to act as an ONVIF-compatible device, making it easier to integrate cameras with ONVIF-supported software like Dahua NVRs or Home Assistant.
+
+With ONVIF support, go2rtc can:
+- Expose configured streams as ONVIF profiles.
+- Provide additional ONVIF functionalities like `GetOSDs` to show camera name in Dahua NVR.
+- Maintain a **consistent camera order** to prevent issues with NVRs that rely on `GetProfilesResponse` for identification.
+
+**Example Configuration**
+
+```yaml
+onvif:
+ - name: Camera 1
+ main_stream: camera1
+ sub_stream: camera1_lq
+ - name: Camera 2
+ main_stream: camera2
+ sub_stream: camera2_lq
+
+streams:
+ camera1:
+ - rtsp://admin:admin@192.168.1.1/cam/realmonitor?channel=1&subtype=0&unicast=true
+ camera1_lq:
+ - ffmpeg:camera1#video=h264#height=360
+ camera2:
+ - rtsp://admin:admin@192.168.1.2/cam/realmonitor?channel=1&subtype=0&unicast=true
+ camera2_lq:
+ - ffmpeg:camera2#video=h264#height=360
+```
+
+**Example Dahua NVR configuration:**
+- **Channel**:
+- **Manufacturer**: ONVIF
+- **IP Address**:
+- **RTSP Port**: Self-adaptive
+- **HTTP Port**:
+- **Username / Password**: Currently auth is not supported by go2rtc
+- **Remote CH No.**:
+
### Module: Log
You can set different log levels for different modules.
From 030611ab888a65f120dc4cc3cabe373ff3c221c2 Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 16:46:01 +0100
Subject: [PATCH 07/12] add information about onvif module in hass section
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3899d5507..a421c78c3 100644
--- a/README.md
+++ b/README.md
@@ -176,6 +176,7 @@ Available modules:
- [mp4](#module-mp4) - MSE, MP4 stream and MP4 snapshot Server
- [hls](#module-hls) - HLS TS or fMP4 stream Server
- [mjpeg](#module-mjpeg) - MJPEG Server
+- [onvif](#module-onvif) - ONVIF server
- [ffmpeg](#source-ffmpeg) - FFmpeg integration
- [ngrok](#module-ngrok) - ngrok integration (external access for private network)
- [hass](#module-hass) - Home Assistant integration
@@ -587,7 +588,7 @@ Support import camera links from [Home Assistant](https://www.home-assistant.io/
- [Generic Camera](https://www.home-assistant.io/integrations/generic/), setup via GUI
- [HomeKit Camera](https://www.home-assistant.io/integrations/homekit_controller/)
-- [ONVIF](https://www.home-assistant.io/integrations/onvif/)
+- [ONVIF](https://www.home-assistant.io/integrations/onvif/) via [Module: ONVIF](#module-onvif)
- [Roborock](https://github.com/humbertogontijo/homeassistant-roborock) vacuums with camera
```yaml
From 253f8ecb232b34626d7ea528343bb88d6a6322ba Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 17:15:39 +0100
Subject: [PATCH 08/12] fix README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index a421c78c3..359fffeb7 100644
--- a/README.md
+++ b/README.md
@@ -588,7 +588,7 @@ Support import camera links from [Home Assistant](https://www.home-assistant.io/
- [Generic Camera](https://www.home-assistant.io/integrations/generic/), setup via GUI
- [HomeKit Camera](https://www.home-assistant.io/integrations/homekit_controller/)
-- [ONVIF](https://www.home-assistant.io/integrations/onvif/) via [Module: ONVIF](#module-onvif)
+- [ONVIF](https://www.home-assistant.io/integrations/onvif/)
- [Roborock](https://github.com/humbertogontijo/homeassistant-roborock) vacuums with camera
```yaml
@@ -1114,7 +1114,7 @@ You have several options on how to add a camera to Home Assistant:
2. Camera [any source](#module-streams) => [go2rtc config](#configuration) => [Generic Camera](https://www.home-assistant.io/integrations/generic/)
- Install any [go2rtc](#fast-start)
- Add your stream to [go2rtc config](#configuration)
- - Hass > Settings > Integrations > Add Integration > [ONVIF](https://my.home-assistant.io/redirect/config_flow_start/?domain=onvif) > Host: `127.0.0.1`, Port: `1984`
+ - Hass > Settings > Integrations > Add Integration > [ONVIF](https://my.home-assistant.io/redirect/config_flow_start/?domain=onvif) > Host: `127.0.0.1`, Port: `1984` (using [Module: ONVIF](#module-onvif))
- Hass > Settings > Integrations > Add Integration > [Generic Camera](https://my.home-assistant.io/redirect/config_flow_start/?domain=generic) > Stream Source URL: `rtsp://127.0.0.1:8554/camera1` (change to your stream name, leave everything else as is)
You have several options on how to watch the stream from the cameras in Home Assistant:
From bdb9da81f990cfb632cd79e55064e9df700884a5 Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 17:55:20 +0100
Subject: [PATCH 09/12] Change config style, OnvifCameras -> OnvifProfiles, add
stream arguments
---
internal/onvif/onvif.go | 45 +++++++++++-------
pkg/onvif/server.go | 102 +++++++++++++++++++++++-----------------
2 files changed, 87 insertions(+), 60 deletions(-)
diff --git a/internal/onvif/onvif.go b/internal/onvif/onvif.go
index e61ec528b..9a72fdd34 100644
--- a/internal/onvif/onvif.go
+++ b/internal/onvif/onvif.go
@@ -18,15 +18,17 @@ import (
"github.com/rs/zerolog"
)
-var OnvifCameras []onvif.OnvifCamera
+var OnvifProfiles []onvif.OnvifProfile
func Init() {
var cfg struct {
- OnvifCameras []onvif.OnvifCamera `yaml:"onvif"`
+ Onvif struct {
+ OnvifProfiles []onvif.OnvifProfile `yaml:"profiles"`
+ } `yaml:"onvif"`
}
app.LoadConfig(&cfg)
- OnvifCameras = cfg.OnvifCameras
+ OnvifProfiles = cfg.Onvif.OnvifProfiles
log = app.GetLogger("onvif")
@@ -42,25 +44,28 @@ func Init() {
var log zerolog.Logger
func GetConfiguredStreams() []string {
- if len(OnvifCameras) == 0 {
+ if len(OnvifProfiles) == 0 {
return streams.GetAllNames()
}
var streamsList []string
- for _, cam := range OnvifCameras {
- streamsList = append(streamsList, cam.MainStream)
- if cam.SubStream != "" {
- streamsList = append(streamsList, cam.SubStream)
+ for _, profile := range OnvifProfiles {
+ for _, stream := range profile.Streams {
+ name, _, _, _ := onvif.ParseStream(stream)
+ streamsList = append(streamsList, name)
}
}
return streamsList
}
-func GetCameraNameByMainStream(mainStream string) string {
- for _, cam := range OnvifCameras {
- if cam.MainStream == mainStream || cam.SubStream == mainStream {
- return cam.Name
+func GetCameraNameByStream(streamName string) string {
+ for _, profile := range OnvifProfiles {
+ for _, stream := range profile.Streams {
+ name, _, _, _ := onvif.ParseStream(stream)
+ if name == streamName {
+ return profile.Name
+ }
}
}
return "Unknown Camera"
@@ -122,7 +127,7 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
case onvif.DeviceGetOSDs:
token := onvif.FindTagValue(b, "ConfigurationToken")
- b = onvif.GetOSDsResponse(token, GetCameraNameByMainStream(token))
+ b = onvif.GetOSDsResponse(token, GetCameraNameByStream(token))
case onvif.DeviceGetOSDOptions:
b = onvif.GetOSDOptionsResponse()
@@ -148,19 +153,23 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
case onvif.MediaGetProfiles:
// important for Hass: H264 codec, width, height
- b = onvif.GetProfilesResponse(OnvifCameras)
+ b = onvif.GetProfilesResponse(OnvifProfiles)
case onvif.MediaGetProfile:
token := onvif.FindTagValue(b, "ProfileToken")
- for _, cam := range OnvifCameras {
- if(cam.MainStream == token || cam.SubStream == token){
- b = onvif.GetProfileResponse(cam)
+ for _, profile := range OnvifProfiles {
+ for _, stream := range profile.Streams {
+ name, _, _, _ := onvif.ParseStream(stream)
+ if name == token {
+ b = onvif.GetProfileResponse(profile)
+ break
+ }
}
}
case onvif.MediaGetVideoSourceConfigurations:
// important for Happytime Onvif Client
- b = onvif.GetVideoSourceConfigurationsResponse(OnvifCameras)
+ b = onvif.GetVideoSourceConfigurationsResponse(OnvifProfiles)
case onvif.MediaGetVideoSourceConfiguration:
token := onvif.FindTagValue(b, "ConfigurationToken")
diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go
index 43c9901cf..58fccdee9 100644
--- a/pkg/onvif/server.go
+++ b/pkg/onvif/server.go
@@ -4,12 +4,13 @@ import (
"bytes"
"regexp"
"time"
+ "strconv"
+ "strings"
)
-type OnvifCamera struct {
- Name string `yaml:"name"`
- MainStream string `yaml:"main_stream"`
- SubStream string `yaml:"sub_stream,omitempty"`
+type OnvifProfile struct {
+ Name string `yaml:"name"`
+ Streams []string `yaml:"streams"`
}
const ServiceGetServiceCapabilities = "GetServiceCapabilities"
@@ -148,18 +149,18 @@ func GetMediaServiceCapabilitiesResponse() []byte {
return e.Bytes()
}
-func GetProfilesResponse(OnvifCameras []OnvifCamera) []byte {
+func GetProfilesResponse(OnvifProfiles []OnvifProfile) []byte {
e := NewEnvelope()
e.Append(`
`)
- for _, cam := range OnvifCameras {
+ for _, cam := range OnvifProfiles {
appendProfile(e, "Profiles", cam)
}
e.Append(``)
return e.Bytes()
}
-func GetProfileResponse(cam OnvifCamera) []byte {
+func GetProfileResponse(cam OnvifProfile) []byte {
e := NewEnvelope()
e.Append(`
`)
@@ -168,47 +169,64 @@ func GetProfileResponse(cam OnvifCamera) []byte {
return e.Bytes()
}
-func appendProfile(e *Envelope, tag string, cam OnvifCamera) {
- e.Append(`
- `, cam.MainStream, `
-
- VSC
- `, cam.MainStream, `
-
-
-
- MainStream
- H264
- 19201080
-
-
-
-`)
+// Parsing stream name to: name, width, height, codec
+func ParseStream(stream string) (string, int, int, string) {
+ parts := strings.Split(stream, "#")
+ name := parts[0]
+ width, height := 1920, 1080 // default resolution
+ codec := "H264" // default codec
- if cam.SubStream != "" {
- e.Append(`
- `, cam.SubStream, `
-
- VSC
- `, cam.MainStream, `
-
-
-
- SubStream
- H264
- 640360
-
-
-
-`)
- }
+ resRegex := regexp.MustCompile(`res=(\d+)x(\d+)`)
+ codecRegex := regexp.MustCompile(`codec=([a-zA-Z0-9]+)`)
+
+ for _, part := range parts[1:] {
+ if matches := resRegex.FindStringSubmatch(part); len(matches) == 3 {
+ width, _ = strconv.Atoi(matches[1])
+ height, _ = strconv.Atoi(matches[2])
+ }
+ if matches := codecRegex.FindStringSubmatch(part); len(matches) == 2 {
+ codec = matches[1]
+ }
+ }
+
+ return name, width, height, codec
+}
+
+func appendProfile(e *Envelope, tag string, profile OnvifProfile) {
+ if len(profile.Streams) == 0 {
+ return
+ }
+
+ // Pierwszy stream jako główny
+ firstStream := profile.Streams[0]
+ firstName, firstWidth, firstHeight, _ := ParseStream(firstStream)
+
+ for _, stream := range profile.Streams {
+ streamName, width, height, codec := ParseStream(stream)
+
+ e.Append(`
+ `, streamName, `
+
+ VSC
+ `, firstName, `
+
+
+
+ SubStream
+ `, codec, `
+ `, strconv.Itoa(width), ``, strconv.Itoa(height), `
+
+
+
+ `)
+ }
}
-func GetVideoSourceConfigurationsResponse(OnvifCameras []OnvifCamera) []byte {
+func GetVideoSourceConfigurationsResponse(OnvifProfiles []OnvifProfile) []byte {
e := NewEnvelope()
e.Append(`
`)
- for _, cam := range OnvifCameras {
+ for _, cam := range OnvifProfiles {
appendProfile(e, "Configurations", cam)
}
e.Append(``)
From 50884f8ba5a7918de48b2633031730fa9e07fbfd Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 17:59:06 +0100
Subject: [PATCH 10/12] update README.md with new config style
---
README.md | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 359fffeb7..c659f00bc 100644
--- a/README.md
+++ b/README.md
@@ -1223,24 +1223,31 @@ With ONVIF support, go2rtc can:
```yaml
onvif:
- - name: Camera 1
- main_stream: camera1
- sub_stream: camera1_lq
- - name: Camera 2
- main_stream: camera2
- sub_stream: camera2_lq
+ profiles:
+ - name: Camera 1
+ streams:
+ - camera1#res=1920x1080
+ - camera1_lq#res=1270x720#codec=H265
+ - name: Camera 2
+ streams:
+ - camera2
+ - camera2_lq#res=640x360
streams:
camera1:
- rtsp://admin:admin@192.168.1.1/cam/realmonitor?channel=1&subtype=0&unicast=true
camera1_lq:
- - ffmpeg:camera1#video=h264#height=360
+ - ffmpeg:camera1#video=h265#height=360
camera2:
- rtsp://admin:admin@192.168.1.2/cam/realmonitor?channel=1&subtype=0&unicast=true
camera2_lq:
- ffmpeg:camera2#video=h264#height=360
```
+Default params for `streams`:
+- `res=1920x1080`
+- `codec=H264`
+
**Example Dahua NVR configuration:**
- **Channel**:
- **Manufacturer**: ONVIF
From 35f58180ae1eecd84c7757a5a47814910fc07398 Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 18:17:40 +0100
Subject: [PATCH 11/12] Update README.md with fixed example config
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index c659f00bc..bac14e8ed 100644
--- a/README.md
+++ b/README.md
@@ -1224,9 +1224,9 @@ With ONVIF support, go2rtc can:
```yaml
onvif:
profiles:
- - name: Camera 1
+ - name: Camera 1
streams:
- - camera1#res=1920x1080
+ - camera1#codec=H265
- camera1_lq#res=1270x720#codec=H265
- name: Camera 2
streams:
@@ -1237,7 +1237,7 @@ streams:
camera1:
- rtsp://admin:admin@192.168.1.1/cam/realmonitor?channel=1&subtype=0&unicast=true
camera1_lq:
- - ffmpeg:camera1#video=h265#height=360
+ - ffmpeg:camera1#video=h265#height=720
camera2:
- rtsp://admin:admin@192.168.1.2/cam/realmonitor?channel=1&subtype=0&unicast=true
camera2_lq:
From 72b04e6e29ba4f7eca25815eb2228ce58971b76c Mon Sep 17 00:00:00 2001
From: streamthing <90110072+streamthing@users.noreply.github.com>
Date: Fri, 14 Mar 2025 18:48:52 +0100
Subject: [PATCH 12/12] fix: translate polish comment
---
pkg/onvif/server.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go
index 58fccdee9..9dc3b8f88 100644
--- a/pkg/onvif/server.go
+++ b/pkg/onvif/server.go
@@ -197,7 +197,7 @@ func appendProfile(e *Envelope, tag string, profile OnvifProfile) {
return
}
- // Pierwszy stream jako główny
+ // get first stream as main stream
firstStream := profile.Streams[0]
firstName, firstWidth, firstHeight, _ := ParseStream(firstStream)