From af819952e8e9771a196d9829c4479b923165fcc6 Mon Sep 17 00:00:00 2001 From: Alex X Date: Sun, 18 Jan 2026 21:50:26 +0300 Subject: [PATCH] Improve ONVIF server support #1299 --- internal/onvif/onvif.go | 14 ++- pkg/onvif/envelope.go | 14 +-- pkg/onvif/server.go | 191 ++++++++++++++++++++++------------------ 3 files changed, 115 insertions(+), 104 deletions(-) diff --git a/internal/onvif/onvif.go b/internal/onvif/onvif.go index 65f8599a..c305b706 100644 --- a/internal/onvif/onvif.go +++ b/internal/onvif/onvif.go @@ -74,8 +74,10 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { log.Trace().Msgf("[onvif] server request %s %s:\n%s", r.Method, r.RequestURI, b) switch operation { - case onvif.DeviceGetNetworkInterfaces, // important for Hass + case onvif.ServiceGetServiceCapabilities, // important for Hass + onvif.DeviceGetNetworkInterfaces, // important for Hass onvif.DeviceGetSystemDateAndTime, // important for Hass + onvif.DeviceSetSystemDateAndTime, // return just OK onvif.DeviceGetDiscoveryMode, onvif.DeviceGetDNS, onvif.DeviceGetHostname, @@ -83,8 +85,10 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { onvif.DeviceGetNetworkProtocols, onvif.DeviceGetNTP, onvif.DeviceGetScopes, + onvif.MediaGetVideoEncoderConfiguration, onvif.MediaGetVideoEncoderConfigurations, onvif.MediaGetAudioEncoderConfigurations, + onvif.MediaGetVideoEncoderConfigurationOptions, onvif.MediaGetAudioSources, onvif.MediaGetAudioSourceConfigurations: b = onvif.StaticResponse(operation) @@ -100,11 +104,6 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { // important for Hass: SerialNumber (unique server ID) b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host) - case onvif.ServiceGetServiceCapabilities: - // important for Hass - // TODO: check path links to media - b = onvif.GetMediaServiceCapabilitiesResponse() - case onvif.DeviceSystemReboot: b = onvif.StaticResponse(operation) @@ -134,8 +133,7 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) { case onvif.MediaGetStreamUri: host, _, err := net.SplitHostPort(r.Host) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + host = r.Host // in case of Host without port } uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken") diff --git a/pkg/onvif/envelope.go b/pkg/onvif/envelope.go index f0e1b29c..76a41260 100644 --- a/pkg/onvif/envelope.go +++ b/pkg/onvif/envelope.go @@ -15,14 +15,9 @@ type Envelope struct { } const ( - prefix1 = ` - -` - prefix2 = ` -` - suffix = ` - -` + prefix1 = `` + prefix2 = `` + suffix = `` ) func NewEnvelope() *Envelope { @@ -54,8 +49,7 @@ func NewEnvelopeWithUser(user *url.Userinfo) *Envelope { %s - -`, +`, user.Username(), base64.StdEncoding.EncodeToString(h.Sum(nil)), base64.StdEncoding.EncodeToString([]byte(nonce)), diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go index 54272798..94e42fc7 100644 --- a/pkg/onvif/server.go +++ b/pkg/onvif/server.go @@ -21,21 +21,24 @@ const ( DeviceGetScopes = "GetScopes" DeviceGetServices = "GetServices" DeviceGetSystemDateAndTime = "GetSystemDateAndTime" + DeviceSetSystemDateAndTime = "SetSystemDateAndTime" DeviceSystemReboot = "SystemReboot" ) const ( - MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations" - MediaGetAudioSources = "GetAudioSources" - MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations" - MediaGetProfile = "GetProfile" - MediaGetProfiles = "GetProfiles" - MediaGetSnapshotUri = "GetSnapshotUri" - MediaGetStreamUri = "GetStreamUri" - MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations" - MediaGetVideoSources = "GetVideoSources" - MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration" - MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations" + MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations" + MediaGetAudioSources = "GetAudioSources" + MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations" + MediaGetProfile = "GetProfile" + MediaGetProfiles = "GetProfiles" + MediaGetSnapshotUri = "GetSnapshotUri" + MediaGetStreamUri = "GetStreamUri" + MediaGetVideoEncoderConfiguration = "GetVideoEncoderConfiguration" + MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations" + MediaGetVideoEncoderConfigurationOptions = "GetVideoEncoderConfigurationOptions" + MediaGetVideoSources = "GetVideoSources" + MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration" + MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations" ) func GetRequestAction(b []byte) string { @@ -54,13 +57,13 @@ func GetRequestAction(b []byte) string { func GetCapabilitiesResponse(host string) []byte { e := NewEnvelope() - e.Append(` + e.Appendf(` - http://`, host, `/onvif/device_service + http://%s/onvif/device_service - http://`, host, `/onvif/media_service + http://%s/onvif/media_service false false @@ -68,24 +71,24 @@ func GetCapabilitiesResponse(host string) []byte { -`) +`, host, host) return e.Bytes() } func GetServicesResponse(host string) []byte { e := NewEnvelope() - e.Append(` + e.Appendf(` http://www.onvif.org/ver10/device/wsdl - http://`, host, `/onvif/device_service + http://%s/onvif/device_service 25 http://www.onvif.org/ver10/media/wsdl - http://`, host, `/onvif/media_service + http://%s/onvif/media_service 25 -`) +`, host, host) return e.Bytes() } @@ -120,30 +123,19 @@ func GetSystemDateAndTimeResponse() []byte { func GetDeviceInformationResponse(manuf, model, firmware, serial string) []byte { e := NewEnvelope() - e.Append(` - `, manuf, ` - `, model, ` - `, firmware, ` - `, serial, ` + e.Appendf(` + %s + %s + %s + %s 1.00 -`) - return e.Bytes() -} - -func GetMediaServiceCapabilitiesResponse() []byte { - e := NewEnvelope() - e.Append(` - - - -`) +`, manuf, model, firmware, serial) return e.Bytes() } func GetProfilesResponse(names []string) []byte { e := NewEnvelope() - e.Append(` -`) + e.Append(``) for _, name := range names { appendProfile(e, "Profiles", name) } @@ -153,38 +145,40 @@ func GetProfilesResponse(names []string) []byte { func GetProfileResponse(name string) []byte { e := NewEnvelope() - e.Append(` -`) + e.Append(``) appendProfile(e, "Profile", name) e.Append(``) return e.Bytes() } func appendProfile(e *Envelope, tag, name string) { - // empty `RateControl` important for UniFi Protect - e.Append(` - `, name, ` - - VSC - `, name, ` - - - - VEC - H264 - 19201080 - - - -`) + // go2rtc name = ONVIF Profile Name = ONVIF Profile token + e.Appendf(``, tag, name) + e.Appendf(`%s`, name) + appendVideoSourceConfiguration(e, "VideoSourceConfiguration", name) + appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration") + e.Appendf(``, tag) +} + +func GetVideoSourcesResponse(names []string) []byte { + // go2rtc name = ONVIF VideoSource token + e := NewEnvelope() + e.Append(``) + for _, name := range names { + e.Appendf(` + 30.000000 + 19201080 +`, name) + } + e.Append(``) + return e.Bytes() } func GetVideoSourceConfigurationsResponse(names []string) []byte { e := NewEnvelope() - e.Append(` -`) + e.Append(``) for _, name := range names { - appendProfile(e, "Configurations", name) + appendVideoSourceConfiguration(e, "Configurations", name) } e.Append(``) return e.Bytes() @@ -192,46 +186,56 @@ func GetVideoSourceConfigurationsResponse(names []string) []byte { func GetVideoSourceConfigurationResponse(name string) []byte { e := NewEnvelope() - e.Append(` -`) + e.Append(``) appendVideoSourceConfiguration(e, "Configuration", name) e.Append(``) return e.Bytes() } func appendVideoSourceConfiguration(e *Envelope, tag, name string) { - e.Append(` + // go2rtc name = ONVIF VideoSourceConfiguration token + e.Appendf(` VSC - `, name, ` + %s - -`) +`, tag, name, name, tag) } -func GetVideoSourcesResponse(names []string) []byte { +func GetVideoEncoderConfigurationsResponse() []byte { e := NewEnvelope() - e.Append(` -`) - for _, name := range names { - e.Append(` - 30.000000 - 19201080 - -`) - } - e.Append(``) + e.Append(``) + appendVideoEncoderConfiguration(e, "VideoEncoderConfigurations") + e.Append(``) return e.Bytes() } +func GetVideoEncoderConfigurationResponse() []byte { + e := NewEnvelope() + e.Append(``) + appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration") + e.Append(``) + return e.Bytes() +} + +func appendVideoEncoderConfiguration(e *Envelope, tag string) { + // empty `RateControl` important for UniFi Protect + e.Appendf(` + VEC + H264 + 19201080 + + `, tag, tag) +} + func GetStreamUriResponse(uri string) []byte { e := NewEnvelope() - e.Append(``, uri, ``) + e.Appendf(`%s`, uri) return e.Bytes() } func GetSnapshotUriResponse(uri string) []byte { e := NewEnvelope() - e.Append(``, uri, ``) + e.Appendf(`%s`, uri) return e.Bytes() } @@ -239,6 +243,10 @@ func StaticResponse(operation string) []byte { switch operation { case DeviceGetSystemDateAndTime: return GetSystemDateAndTimeResponse() + case MediaGetVideoEncoderConfiguration: + return GetVideoEncoderConfigurationResponse() + case MediaGetVideoEncoderConfigurations: + return GetVideoEncoderConfigurationsResponse() } e := NewEnvelope() @@ -247,11 +255,18 @@ func StaticResponse(operation string) []byte { } var responses = map[string]string{ + ServiceGetServiceCapabilities: ` + + + +`, + DeviceGetDiscoveryMode: `Discoverable`, DeviceGetDNS: ``, DeviceGetHostname: ``, DeviceGetNetworkDefaultGateway: ``, DeviceGetNTP: ``, + DeviceSetSystemDateAndTime: ``, DeviceSystemReboot: `OK`, DeviceGetNetworkInterfaces: ``, @@ -263,16 +278,20 @@ var responses = map[string]string{ Fixedonvif://www.onvif.org/type/Network_Video_Transmitter `, - MediaGetVideoEncoderConfigurations: ` - - VEC - H264 - 19201080 - - -`, - MediaGetAudioEncoderConfigurations: ``, MediaGetAudioSources: ``, MediaGetAudioSourceConfigurations: ``, + + MediaGetVideoEncoderConfigurationOptions: ` + + 16 + + 19201080 + 0100 + 130 + 1100 + Main + + +`, }