Improve ONVIF server support #1299

This commit is contained in:
Alex X
2026-01-18 21:50:26 +03:00
parent 159fb4675c
commit af819952e8
3 changed files with 115 additions and 104 deletions
+6 -8
View File
@@ -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")
+4 -10
View File
@@ -15,14 +15,9 @@ type Envelope struct {
}
const (
prefix1 = `<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:tt="http://www.onvif.org/ver10/schema" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:trt="http://www.onvif.org/ver10/media/wsdl">
`
prefix2 = `<s:Body>
`
suffix = `
</s:Body>
</s:Envelope>`
prefix1 = `<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:tt="http://www.onvif.org/ver10/schema" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:trt="http://www.onvif.org/ver10/media/wsdl">`
prefix2 = `<s:Body>`
suffix = `</s:Body></s:Envelope>`
)
func NewEnvelope() *Envelope {
@@ -54,8 +49,7 @@ func NewEnvelopeWithUser(user *url.Userinfo) *Envelope {
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">%s</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</s:Header>
`,
</s:Header>`,
user.Username(),
base64.StdEncoding.EncodeToString(h.Sum(nil)),
base64.StdEncoding.EncodeToString([]byte(nonce)),
+105 -86
View File
@@ -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(`<tds:GetCapabilitiesResponse>
e.Appendf(`<tds:GetCapabilitiesResponse>
<tds:Capabilities>
<tt:Device>
<tt:XAddr>http://`, host, `/onvif/device_service</tt:XAddr>
<tt:XAddr>http://%s/onvif/device_service</tt:XAddr>
</tt:Device>
<tt:Media>
<tt:XAddr>http://`, host, `/onvif/media_service</tt:XAddr>
<tt:XAddr>http://%s/onvif/media_service</tt:XAddr>
<tt:StreamingCapabilities>
<tt:RTPMulticast>false</tt:RTPMulticast>
<tt:RTP_TCP>false</tt:RTP_TCP>
@@ -68,24 +71,24 @@ func GetCapabilitiesResponse(host string) []byte {
</tt:StreamingCapabilities>
</tt:Media>
</tds:Capabilities>
</tds:GetCapabilitiesResponse>`)
</tds:GetCapabilitiesResponse>`, host, host)
return e.Bytes()
}
func GetServicesResponse(host string) []byte {
e := NewEnvelope()
e.Append(`<tds:GetServicesResponse>
e.Appendf(`<tds:GetServicesResponse>
<tds:Service>
<tds:Namespace>http://www.onvif.org/ver10/device/wsdl</tds:Namespace>
<tds:XAddr>http://`, host, `/onvif/device_service</tds:XAddr>
<tds:XAddr>http://%s/onvif/device_service</tds:XAddr>
<tds:Version><tt:Major>2</tt:Major><tt:Minor>5</tt:Minor></tds:Version>
</tds:Service>
<tds:Service>
<tds:Namespace>http://www.onvif.org/ver10/media/wsdl</tds:Namespace>
<tds:XAddr>http://`, host, `/onvif/media_service</tds:XAddr>
<tds:XAddr>http://%s/onvif/media_service</tds:XAddr>
<tds:Version><tt:Major>2</tt:Major><tt:Minor>5</tt:Minor></tds:Version>
</tds:Service>
</tds:GetServicesResponse>`)
</tds:GetServicesResponse>`, host, host)
return e.Bytes()
}
@@ -120,30 +123,19 @@ func GetSystemDateAndTimeResponse() []byte {
func GetDeviceInformationResponse(manuf, model, firmware, serial string) []byte {
e := NewEnvelope()
e.Append(`<tds:GetDeviceInformationResponse>
<tds:Manufacturer>`, manuf, `</tds:Manufacturer>
<tds:Model>`, model, `</tds:Model>
<tds:FirmwareVersion>`, firmware, `</tds:FirmwareVersion>
<tds:SerialNumber>`, serial, `</tds:SerialNumber>
e.Appendf(`<tds:GetDeviceInformationResponse>
<tds:Manufacturer>%s</tds:Manufacturer>
<tds:Model>%s</tds:Model>
<tds:FirmwareVersion>%s</tds:FirmwareVersion>
<tds:SerialNumber>%s</tds:SerialNumber>
<tds:HardwareId>1.00</tds:HardwareId>
</tds:GetDeviceInformationResponse>`)
return e.Bytes()
}
func GetMediaServiceCapabilitiesResponse() []byte {
e := NewEnvelope()
e.Append(`<trt:GetServiceCapabilitiesResponse>
<trt:Capabilities SnapshotUri="true" Rotation="false" VideoSourceMode="false" OSD="false" TemporaryOSDText="false" EXICompression="false">
<trt:StreamingCapabilities RTPMulticast="false" RTP_TCP="false" RTP_RTSP_TCP="true" NonAggregateControl="false" NoRTSPStreaming="false" />
</trt:Capabilities>
</trt:GetServiceCapabilitiesResponse>`)
</tds:GetDeviceInformationResponse>`, manuf, model, firmware, serial)
return e.Bytes()
}
func GetProfilesResponse(names []string) []byte {
e := NewEnvelope()
e.Append(`<trt:GetProfilesResponse>
`)
e.Append(`<trt:GetProfilesResponse>`)
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(`<trt:GetProfileResponse>
`)
e.Append(`<trt:GetProfileResponse>`)
appendProfile(e, "Profile", name)
e.Append(`</trt:GetProfileResponse>`)
return e.Bytes()
}
func appendProfile(e *Envelope, tag, name string) {
// empty `RateControl` important for UniFi Protect
e.Append(`<trt:`, tag, ` token="`, name, `" fixed="true">
<tt:Name>`, name, `</tt:Name>
<tt:VideoSourceConfiguration token="`, name, `">
<tt:Name>VSC</tt:Name>
<tt:SourceToken>`, name, `</tt:SourceToken>
<tt:Bounds x="0" y="0" width="1920" height="1080"></tt:Bounds>
</tt:VideoSourceConfiguration>
<tt:VideoEncoderConfiguration token="vec">
<tt:Name>VEC</tt:Name>
<tt:Encoding>H264</tt:Encoding>
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
<tt:RateControl />
</tt:VideoEncoderConfiguration>
</trt:`, tag, `>
`)
// go2rtc name = ONVIF Profile Name = ONVIF Profile token
e.Appendf(`<trt:%s token="%s" fixed="true">`, tag, name)
e.Appendf(`<tt:Name>%s</tt:Name>`, name)
appendVideoSourceConfiguration(e, "VideoSourceConfiguration", name)
appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration")
e.Appendf(`</trt:%s>`, tag)
}
func GetVideoSourcesResponse(names []string) []byte {
// go2rtc name = ONVIF VideoSource token
e := NewEnvelope()
e.Append(`<trt:GetVideoSourcesResponse>`)
for _, name := range names {
e.Appendf(`<trt:VideoSources token="%s">
<tt:Framerate>30.000000</tt:Framerate>
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
</trt:VideoSources>`, name)
}
e.Append(`</trt:GetVideoSourcesResponse>`)
return e.Bytes()
}
func GetVideoSourceConfigurationsResponse(names []string) []byte {
e := NewEnvelope()
e.Append(`<trt:GetVideoSourceConfigurationsResponse>
`)
e.Append(`<trt:GetVideoSourceConfigurationsResponse>`)
for _, name := range names {
appendProfile(e, "Configurations", name)
appendVideoSourceConfiguration(e, "Configurations", name)
}
e.Append(`</trt:GetVideoSourceConfigurationsResponse>`)
return e.Bytes()
@@ -192,46 +186,56 @@ func GetVideoSourceConfigurationsResponse(names []string) []byte {
func GetVideoSourceConfigurationResponse(name string) []byte {
e := NewEnvelope()
e.Append(`<trt:GetVideoSourceConfigurationResponse>
`)
e.Append(`<trt:GetVideoSourceConfigurationResponse>`)
appendVideoSourceConfiguration(e, "Configuration", name)
e.Append(`</trt:GetVideoSourceConfigurationResponse>`)
return e.Bytes()
}
func appendVideoSourceConfiguration(e *Envelope, tag, name string) {
e.Append(`<trt:`, tag, ` token="`, name, `" fixed="true">
// go2rtc name = ONVIF VideoSourceConfiguration token
e.Appendf(`<tt:%s token="%s" fixed="true">
<tt:Name>VSC</tt:Name>
<tt:SourceToken>`, name, `</tt:SourceToken>
<tt:SourceToken>%s</tt:SourceToken>
<tt:Bounds x="0" y="0" width="1920" height="1080"></tt:Bounds>
</trt:`, tag, `>
`)
</tt:%s>`, tag, name, name, tag)
}
func GetVideoSourcesResponse(names []string) []byte {
func GetVideoEncoderConfigurationsResponse() []byte {
e := NewEnvelope()
e.Append(`<trt:GetVideoSourcesResponse>
`)
for _, name := range names {
e.Append(`<trt:VideoSources token="`, name, `">
<tt:Framerate>30.000000</tt:Framerate>
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
</trt:VideoSources>
`)
}
e.Append(`</trt:GetVideoSourcesResponse>`)
e.Append(`<trt:GetVideoEncoderConfigurationsResponse>`)
appendVideoEncoderConfiguration(e, "VideoEncoderConfigurations")
e.Append(`</trt:GetVideoEncoderConfigurationsResponse>`)
return e.Bytes()
}
func GetVideoEncoderConfigurationResponse() []byte {
e := NewEnvelope()
e.Append(`<trt:GetVideoEncoderConfigurationResponse>`)
appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration")
e.Append(`</trt:GetVideoEncoderConfigurationResponse>`)
return e.Bytes()
}
func appendVideoEncoderConfiguration(e *Envelope, tag string) {
// empty `RateControl` important for UniFi Protect
e.Appendf(`<tt:%s token="vec">
<tt:Name>VEC</tt:Name>
<tt:Encoding>H264</tt:Encoding>
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
<tt:RateControl />
</tt:%s>`, tag, tag)
}
func GetStreamUriResponse(uri string) []byte {
e := NewEnvelope()
e.Append(`<trt:GetStreamUriResponse><trt:MediaUri><tt:Uri>`, uri, `</tt:Uri></trt:MediaUri></trt:GetStreamUriResponse>`)
e.Appendf(`<trt:GetStreamUriResponse><trt:MediaUri><tt:Uri>%s</tt:Uri></trt:MediaUri></trt:GetStreamUriResponse>`, uri)
return e.Bytes()
}
func GetSnapshotUriResponse(uri string) []byte {
e := NewEnvelope()
e.Append(`<trt:GetSnapshotUriResponse><trt:MediaUri><tt:Uri>`, uri, `</tt:Uri></trt:MediaUri></trt:GetSnapshotUriResponse>`)
e.Appendf(`<trt:GetSnapshotUriResponse><trt:MediaUri><tt:Uri>%s</tt:Uri></trt:MediaUri></trt:GetSnapshotUriResponse>`, 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: `<trt:GetServiceCapabilitiesResponse>
<trt:Capabilities SnapshotUri="true" Rotation="false" VideoSourceMode="false" OSD="false" TemporaryOSDText="false" EXICompression="false">
<trt:StreamingCapabilities RTPMulticast="false" RTP_TCP="false" RTP_RTSP_TCP="true" NonAggregateControl="false" NoRTSPStreaming="false" />
</trt:Capabilities>
</trt:GetServiceCapabilitiesResponse>`,
DeviceGetDiscoveryMode: `<tds:GetDiscoveryModeResponse><tds:DiscoveryMode>Discoverable</tds:DiscoveryMode></tds:GetDiscoveryModeResponse>`,
DeviceGetDNS: `<tds:GetDNSResponse><tds:DNSInformation /></tds:GetDNSResponse>`,
DeviceGetHostname: `<tds:GetHostnameResponse><tds:HostnameInformation /></tds:GetHostnameResponse>`,
DeviceGetNetworkDefaultGateway: `<tds:GetNetworkDefaultGatewayResponse><tds:NetworkGateway /></tds:GetNetworkDefaultGatewayResponse>`,
DeviceGetNTP: `<tds:GetNTPResponse><tds:NTPInformation /></tds:GetNTPResponse>`,
DeviceSetSystemDateAndTime: `<tds:SetSystemDateAndTimeResponse />`,
DeviceSystemReboot: `<tds:SystemRebootResponse><tds:Message>OK</tds:Message></tds:SystemRebootResponse>`,
DeviceGetNetworkInterfaces: `<tds:GetNetworkInterfacesResponse />`,
@@ -263,16 +278,20 @@ var responses = map[string]string{
<tds:Scopes><tt:ScopeDef>Fixed</tt:ScopeDef><tt:ScopeItem>onvif://www.onvif.org/type/Network_Video_Transmitter</tt:ScopeItem></tds:Scopes>
</tds:GetScopesResponse>`,
MediaGetVideoEncoderConfigurations: `<trt:GetVideoEncoderConfigurationsResponse>
<tt:VideoEncoderConfiguration token="vec">
<tt:Name>VEC</tt:Name>
<tt:Encoding>H264</tt:Encoding>
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
<tt:RateControl />
</tt:VideoEncoderConfiguration>
</trt:GetVideoEncoderConfigurationsResponse>`,
MediaGetAudioEncoderConfigurations: `<trt:GetAudioEncoderConfigurationsResponse />`,
MediaGetAudioSources: `<trt:GetAudioSourcesResponse />`,
MediaGetAudioSourceConfigurations: `<trt:GetAudioSourceConfigurationsResponse />`,
MediaGetVideoEncoderConfigurationOptions: `<trt:GetVideoEncoderConfigurationOptionsResponse>
<trt:Options>
<tt:QualityRange><tt:Min>1</tt:Min><tt:Max>6</tt:Max></tt:QualityRange>
<tt:H264>
<tt:ResolutionsAvailable><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:ResolutionsAvailable>
<tt:GovLengthRange><tt:Min>0</tt:Min><tt:Max>100</tt:Max></tt:GovLengthRange>
<tt:FrameRateRange><tt:Min>1</tt:Min><tt:Max>30</tt:Max></tt:FrameRateRange>
<tt:EncodingIntervalRange><tt:Min>1</tt:Min><tt:Max>100</tt:Max></tt:EncodingIntervalRange>
<tt:H264ProfilesSupported>Main</tt:H264ProfilesSupported>
</tt:H264>
</trt:Options>
</trt:GetVideoEncoderConfigurationOptionsResponse>`,
}