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://img.youtube.com/vi/sHj_3h_sX7M/mqdefault.jpg)](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)