Skip to content

Commit af81995

Browse files
committed
Improve ONVIF server support #1299
1 parent 159fb46 commit af81995

3 files changed

Lines changed: 115 additions & 104 deletions

File tree

internal/onvif/onvif.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,21 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
7474
log.Trace().Msgf("[onvif] server request %s %s:\n%s", r.Method, r.RequestURI, b)
7575

7676
switch operation {
77-
case onvif.DeviceGetNetworkInterfaces, // important for Hass
77+
case onvif.ServiceGetServiceCapabilities, // important for Hass
78+
onvif.DeviceGetNetworkInterfaces, // important for Hass
7879
onvif.DeviceGetSystemDateAndTime, // important for Hass
80+
onvif.DeviceSetSystemDateAndTime, // return just OK
7981
onvif.DeviceGetDiscoveryMode,
8082
onvif.DeviceGetDNS,
8183
onvif.DeviceGetHostname,
8284
onvif.DeviceGetNetworkDefaultGateway,
8385
onvif.DeviceGetNetworkProtocols,
8486
onvif.DeviceGetNTP,
8587
onvif.DeviceGetScopes,
88+
onvif.MediaGetVideoEncoderConfiguration,
8689
onvif.MediaGetVideoEncoderConfigurations,
8790
onvif.MediaGetAudioEncoderConfigurations,
91+
onvif.MediaGetVideoEncoderConfigurationOptions,
8892
onvif.MediaGetAudioSources,
8993
onvif.MediaGetAudioSourceConfigurations:
9094
b = onvif.StaticResponse(operation)
@@ -100,11 +104,6 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
100104
// important for Hass: SerialNumber (unique server ID)
101105
b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host)
102106

103-
case onvif.ServiceGetServiceCapabilities:
104-
// important for Hass
105-
// TODO: check path links to media
106-
b = onvif.GetMediaServiceCapabilitiesResponse()
107-
108107
case onvif.DeviceSystemReboot:
109108
b = onvif.StaticResponse(operation)
110109

@@ -134,8 +133,7 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
134133
case onvif.MediaGetStreamUri:
135134
host, _, err := net.SplitHostPort(r.Host)
136135
if err != nil {
137-
http.Error(w, err.Error(), http.StatusInternalServerError)
138-
return
136+
host = r.Host // in case of Host without port
139137
}
140138

141139
uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken")

pkg/onvif/envelope.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,9 @@ type Envelope struct {
1515
}
1616

1717
const (
18-
prefix1 = `<?xml version="1.0" encoding="utf-8"?>
19-
<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">
20-
`
21-
prefix2 = `<s:Body>
22-
`
23-
suffix = `
24-
</s:Body>
25-
</s:Envelope>`
18+
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">`
19+
prefix2 = `<s:Body>`
20+
suffix = `</s:Body></s:Envelope>`
2621
)
2722

2823
func NewEnvelope() *Envelope {
@@ -54,8 +49,7 @@ func NewEnvelopeWithUser(user *url.Userinfo) *Envelope {
5449
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">%s</wsu:Created>
5550
</wsse:UsernameToken>
5651
</wsse:Security>
57-
</s:Header>
58-
`,
52+
</s:Header>`,
5953
user.Username(),
6054
base64.StdEncoding.EncodeToString(h.Sum(nil)),
6155
base64.StdEncoding.EncodeToString([]byte(nonce)),

pkg/onvif/server.go

Lines changed: 105 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,24 @@ const (
2121
DeviceGetScopes = "GetScopes"
2222
DeviceGetServices = "GetServices"
2323
DeviceGetSystemDateAndTime = "GetSystemDateAndTime"
24+
DeviceSetSystemDateAndTime = "SetSystemDateAndTime"
2425
DeviceSystemReboot = "SystemReboot"
2526
)
2627

2728
const (
28-
MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations"
29-
MediaGetAudioSources = "GetAudioSources"
30-
MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations"
31-
MediaGetProfile = "GetProfile"
32-
MediaGetProfiles = "GetProfiles"
33-
MediaGetSnapshotUri = "GetSnapshotUri"
34-
MediaGetStreamUri = "GetStreamUri"
35-
MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations"
36-
MediaGetVideoSources = "GetVideoSources"
37-
MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration"
38-
MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations"
29+
MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations"
30+
MediaGetAudioSources = "GetAudioSources"
31+
MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations"
32+
MediaGetProfile = "GetProfile"
33+
MediaGetProfiles = "GetProfiles"
34+
MediaGetSnapshotUri = "GetSnapshotUri"
35+
MediaGetStreamUri = "GetStreamUri"
36+
MediaGetVideoEncoderConfiguration = "GetVideoEncoderConfiguration"
37+
MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations"
38+
MediaGetVideoEncoderConfigurationOptions = "GetVideoEncoderConfigurationOptions"
39+
MediaGetVideoSources = "GetVideoSources"
40+
MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration"
41+
MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations"
3942
)
4043

4144
func GetRequestAction(b []byte) string {
@@ -54,38 +57,38 @@ func GetRequestAction(b []byte) string {
5457

5558
func GetCapabilitiesResponse(host string) []byte {
5659
e := NewEnvelope()
57-
e.Append(`<tds:GetCapabilitiesResponse>
60+
e.Appendf(`<tds:GetCapabilitiesResponse>
5861
<tds:Capabilities>
5962
<tt:Device>
60-
<tt:XAddr>http://`, host, `/onvif/device_service</tt:XAddr>
63+
<tt:XAddr>http://%s/onvif/device_service</tt:XAddr>
6164
</tt:Device>
6265
<tt:Media>
63-
<tt:XAddr>http://`, host, `/onvif/media_service</tt:XAddr>
66+
<tt:XAddr>http://%s/onvif/media_service</tt:XAddr>
6467
<tt:StreamingCapabilities>
6568
<tt:RTPMulticast>false</tt:RTPMulticast>
6669
<tt:RTP_TCP>false</tt:RTP_TCP>
6770
<tt:RTP_RTSP_TCP>true</tt:RTP_RTSP_TCP>
6871
</tt:StreamingCapabilities>
6972
</tt:Media>
7073
</tds:Capabilities>
71-
</tds:GetCapabilitiesResponse>`)
74+
</tds:GetCapabilitiesResponse>`, host, host)
7275
return e.Bytes()
7376
}
7477

7578
func GetServicesResponse(host string) []byte {
7679
e := NewEnvelope()
77-
e.Append(`<tds:GetServicesResponse>
80+
e.Appendf(`<tds:GetServicesResponse>
7881
<tds:Service>
7982
<tds:Namespace>http://www.onvif.org/ver10/device/wsdl</tds:Namespace>
80-
<tds:XAddr>http://`, host, `/onvif/device_service</tds:XAddr>
83+
<tds:XAddr>http://%s/onvif/device_service</tds:XAddr>
8184
<tds:Version><tt:Major>2</tt:Major><tt:Minor>5</tt:Minor></tds:Version>
8285
</tds:Service>
8386
<tds:Service>
8487
<tds:Namespace>http://www.onvif.org/ver10/media/wsdl</tds:Namespace>
85-
<tds:XAddr>http://`, host, `/onvif/media_service</tds:XAddr>
88+
<tds:XAddr>http://%s/onvif/media_service</tds:XAddr>
8689
<tds:Version><tt:Major>2</tt:Major><tt:Minor>5</tt:Minor></tds:Version>
8790
</tds:Service>
88-
</tds:GetServicesResponse>`)
91+
</tds:GetServicesResponse>`, host, host)
8992
return e.Bytes()
9093
}
9194

@@ -120,30 +123,19 @@ func GetSystemDateAndTimeResponse() []byte {
120123

121124
func GetDeviceInformationResponse(manuf, model, firmware, serial string) []byte {
122125
e := NewEnvelope()
123-
e.Append(`<tds:GetDeviceInformationResponse>
124-
<tds:Manufacturer>`, manuf, `</tds:Manufacturer>
125-
<tds:Model>`, model, `</tds:Model>
126-
<tds:FirmwareVersion>`, firmware, `</tds:FirmwareVersion>
127-
<tds:SerialNumber>`, serial, `</tds:SerialNumber>
126+
e.Appendf(`<tds:GetDeviceInformationResponse>
127+
<tds:Manufacturer>%s</tds:Manufacturer>
128+
<tds:Model>%s</tds:Model>
129+
<tds:FirmwareVersion>%s</tds:FirmwareVersion>
130+
<tds:SerialNumber>%s</tds:SerialNumber>
128131
<tds:HardwareId>1.00</tds:HardwareId>
129-
</tds:GetDeviceInformationResponse>`)
130-
return e.Bytes()
131-
}
132-
133-
func GetMediaServiceCapabilitiesResponse() []byte {
134-
e := NewEnvelope()
135-
e.Append(`<trt:GetServiceCapabilitiesResponse>
136-
<trt:Capabilities SnapshotUri="true" Rotation="false" VideoSourceMode="false" OSD="false" TemporaryOSDText="false" EXICompression="false">
137-
<trt:StreamingCapabilities RTPMulticast="false" RTP_TCP="false" RTP_RTSP_TCP="true" NonAggregateControl="false" NoRTSPStreaming="false" />
138-
</trt:Capabilities>
139-
</trt:GetServiceCapabilitiesResponse>`)
132+
</tds:GetDeviceInformationResponse>`, manuf, model, firmware, serial)
140133
return e.Bytes()
141134
}
142135

143136
func GetProfilesResponse(names []string) []byte {
144137
e := NewEnvelope()
145-
e.Append(`<trt:GetProfilesResponse>
146-
`)
138+
e.Append(`<trt:GetProfilesResponse>`)
147139
for _, name := range names {
148140
appendProfile(e, "Profiles", name)
149141
}
@@ -153,92 +145,108 @@ func GetProfilesResponse(names []string) []byte {
153145

154146
func GetProfileResponse(name string) []byte {
155147
e := NewEnvelope()
156-
e.Append(`<trt:GetProfileResponse>
157-
`)
148+
e.Append(`<trt:GetProfileResponse>`)
158149
appendProfile(e, "Profile", name)
159150
e.Append(`</trt:GetProfileResponse>`)
160151
return e.Bytes()
161152
}
162153

163154
func appendProfile(e *Envelope, tag, name string) {
164-
// empty `RateControl` important for UniFi Protect
165-
e.Append(`<trt:`, tag, ` token="`, name, `" fixed="true">
166-
<tt:Name>`, name, `</tt:Name>
167-
<tt:VideoSourceConfiguration token="`, name, `">
168-
<tt:Name>VSC</tt:Name>
169-
<tt:SourceToken>`, name, `</tt:SourceToken>
170-
<tt:Bounds x="0" y="0" width="1920" height="1080"></tt:Bounds>
171-
</tt:VideoSourceConfiguration>
172-
<tt:VideoEncoderConfiguration token="vec">
173-
<tt:Name>VEC</tt:Name>
174-
<tt:Encoding>H264</tt:Encoding>
175-
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
176-
<tt:RateControl />
177-
</tt:VideoEncoderConfiguration>
178-
</trt:`, tag, `>
179-
`)
155+
// go2rtc name = ONVIF Profile Name = ONVIF Profile token
156+
e.Appendf(`<trt:%s token="%s" fixed="true">`, tag, name)
157+
e.Appendf(`<tt:Name>%s</tt:Name>`, name)
158+
appendVideoSourceConfiguration(e, "VideoSourceConfiguration", name)
159+
appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration")
160+
e.Appendf(`</trt:%s>`, tag)
161+
}
162+
163+
func GetVideoSourcesResponse(names []string) []byte {
164+
// go2rtc name = ONVIF VideoSource token
165+
e := NewEnvelope()
166+
e.Append(`<trt:GetVideoSourcesResponse>`)
167+
for _, name := range names {
168+
e.Appendf(`<trt:VideoSources token="%s">
169+
<tt:Framerate>30.000000</tt:Framerate>
170+
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
171+
</trt:VideoSources>`, name)
172+
}
173+
e.Append(`</trt:GetVideoSourcesResponse>`)
174+
return e.Bytes()
180175
}
181176

182177
func GetVideoSourceConfigurationsResponse(names []string) []byte {
183178
e := NewEnvelope()
184-
e.Append(`<trt:GetVideoSourceConfigurationsResponse>
185-
`)
179+
e.Append(`<trt:GetVideoSourceConfigurationsResponse>`)
186180
for _, name := range names {
187-
appendProfile(e, "Configurations", name)
181+
appendVideoSourceConfiguration(e, "Configurations", name)
188182
}
189183
e.Append(`</trt:GetVideoSourceConfigurationsResponse>`)
190184
return e.Bytes()
191185
}
192186

193187
func GetVideoSourceConfigurationResponse(name string) []byte {
194188
e := NewEnvelope()
195-
e.Append(`<trt:GetVideoSourceConfigurationResponse>
196-
`)
189+
e.Append(`<trt:GetVideoSourceConfigurationResponse>`)
197190
appendVideoSourceConfiguration(e, "Configuration", name)
198191
e.Append(`</trt:GetVideoSourceConfigurationResponse>`)
199192
return e.Bytes()
200193
}
201194

202195
func appendVideoSourceConfiguration(e *Envelope, tag, name string) {
203-
e.Append(`<trt:`, tag, ` token="`, name, `" fixed="true">
196+
// go2rtc name = ONVIF VideoSourceConfiguration token
197+
e.Appendf(`<tt:%s token="%s" fixed="true">
204198
<tt:Name>VSC</tt:Name>
205-
<tt:SourceToken>`, name, `</tt:SourceToken>
199+
<tt:SourceToken>%s</tt:SourceToken>
206200
<tt:Bounds x="0" y="0" width="1920" height="1080"></tt:Bounds>
207-
</trt:`, tag, `>
208-
`)
201+
</tt:%s>`, tag, name, name, tag)
209202
}
210203

211-
func GetVideoSourcesResponse(names []string) []byte {
204+
func GetVideoEncoderConfigurationsResponse() []byte {
212205
e := NewEnvelope()
213-
e.Append(`<trt:GetVideoSourcesResponse>
214-
`)
215-
for _, name := range names {
216-
e.Append(`<trt:VideoSources token="`, name, `">
217-
<tt:Framerate>30.000000</tt:Framerate>
218-
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
219-
</trt:VideoSources>
220-
`)
221-
}
222-
e.Append(`</trt:GetVideoSourcesResponse>`)
206+
e.Append(`<trt:GetVideoEncoderConfigurationsResponse>`)
207+
appendVideoEncoderConfiguration(e, "VideoEncoderConfigurations")
208+
e.Append(`</trt:GetVideoEncoderConfigurationsResponse>`)
209+
return e.Bytes()
210+
}
211+
212+
func GetVideoEncoderConfigurationResponse() []byte {
213+
e := NewEnvelope()
214+
e.Append(`<trt:GetVideoEncoderConfigurationResponse>`)
215+
appendVideoEncoderConfiguration(e, "VideoEncoderConfiguration")
216+
e.Append(`</trt:GetVideoEncoderConfigurationResponse>`)
223217
return e.Bytes()
224218
}
225219

220+
func appendVideoEncoderConfiguration(e *Envelope, tag string) {
221+
// empty `RateControl` important for UniFi Protect
222+
e.Appendf(`<tt:%s token="vec">
223+
<tt:Name>VEC</tt:Name>
224+
<tt:Encoding>H264</tt:Encoding>
225+
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
226+
<tt:RateControl />
227+
</tt:%s>`, tag, tag)
228+
}
229+
226230
func GetStreamUriResponse(uri string) []byte {
227231
e := NewEnvelope()
228-
e.Append(`<trt:GetStreamUriResponse><trt:MediaUri><tt:Uri>`, uri, `</tt:Uri></trt:MediaUri></trt:GetStreamUriResponse>`)
232+
e.Appendf(`<trt:GetStreamUriResponse><trt:MediaUri><tt:Uri>%s</tt:Uri></trt:MediaUri></trt:GetStreamUriResponse>`, uri)
229233
return e.Bytes()
230234
}
231235

232236
func GetSnapshotUriResponse(uri string) []byte {
233237
e := NewEnvelope()
234-
e.Append(`<trt:GetSnapshotUriResponse><trt:MediaUri><tt:Uri>`, uri, `</tt:Uri></trt:MediaUri></trt:GetSnapshotUriResponse>`)
238+
e.Appendf(`<trt:GetSnapshotUriResponse><trt:MediaUri><tt:Uri>%s</tt:Uri></trt:MediaUri></trt:GetSnapshotUriResponse>`, uri)
235239
return e.Bytes()
236240
}
237241

238242
func StaticResponse(operation string) []byte {
239243
switch operation {
240244
case DeviceGetSystemDateAndTime:
241245
return GetSystemDateAndTimeResponse()
246+
case MediaGetVideoEncoderConfiguration:
247+
return GetVideoEncoderConfigurationResponse()
248+
case MediaGetVideoEncoderConfigurations:
249+
return GetVideoEncoderConfigurationsResponse()
242250
}
243251

244252
e := NewEnvelope()
@@ -247,11 +255,18 @@ func StaticResponse(operation string) []byte {
247255
}
248256

249257
var responses = map[string]string{
258+
ServiceGetServiceCapabilities: `<trt:GetServiceCapabilitiesResponse>
259+
<trt:Capabilities SnapshotUri="true" Rotation="false" VideoSourceMode="false" OSD="false" TemporaryOSDText="false" EXICompression="false">
260+
<trt:StreamingCapabilities RTPMulticast="false" RTP_TCP="false" RTP_RTSP_TCP="true" NonAggregateControl="false" NoRTSPStreaming="false" />
261+
</trt:Capabilities>
262+
</trt:GetServiceCapabilitiesResponse>`,
263+
250264
DeviceGetDiscoveryMode: `<tds:GetDiscoveryModeResponse><tds:DiscoveryMode>Discoverable</tds:DiscoveryMode></tds:GetDiscoveryModeResponse>`,
251265
DeviceGetDNS: `<tds:GetDNSResponse><tds:DNSInformation /></tds:GetDNSResponse>`,
252266
DeviceGetHostname: `<tds:GetHostnameResponse><tds:HostnameInformation /></tds:GetHostnameResponse>`,
253267
DeviceGetNetworkDefaultGateway: `<tds:GetNetworkDefaultGatewayResponse><tds:NetworkGateway /></tds:GetNetworkDefaultGatewayResponse>`,
254268
DeviceGetNTP: `<tds:GetNTPResponse><tds:NTPInformation /></tds:GetNTPResponse>`,
269+
DeviceSetSystemDateAndTime: `<tds:SetSystemDateAndTimeResponse />`,
255270
DeviceSystemReboot: `<tds:SystemRebootResponse><tds:Message>OK</tds:Message></tds:SystemRebootResponse>`,
256271

257272
DeviceGetNetworkInterfaces: `<tds:GetNetworkInterfacesResponse />`,
@@ -263,16 +278,20 @@ var responses = map[string]string{
263278
<tds:Scopes><tt:ScopeDef>Fixed</tt:ScopeDef><tt:ScopeItem>onvif://www.onvif.org/type/Network_Video_Transmitter</tt:ScopeItem></tds:Scopes>
264279
</tds:GetScopesResponse>`,
265280

266-
MediaGetVideoEncoderConfigurations: `<trt:GetVideoEncoderConfigurationsResponse>
267-
<tt:VideoEncoderConfiguration token="vec">
268-
<tt:Name>VEC</tt:Name>
269-
<tt:Encoding>H264</tt:Encoding>
270-
<tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
271-
<tt:RateControl />
272-
</tt:VideoEncoderConfiguration>
273-
</trt:GetVideoEncoderConfigurationsResponse>`,
274-
275281
MediaGetAudioEncoderConfigurations: `<trt:GetAudioEncoderConfigurationsResponse />`,
276282
MediaGetAudioSources: `<trt:GetAudioSourcesResponse />`,
277283
MediaGetAudioSourceConfigurations: `<trt:GetAudioSourceConfigurationsResponse />`,
284+
285+
MediaGetVideoEncoderConfigurationOptions: `<trt:GetVideoEncoderConfigurationOptionsResponse>
286+
<trt:Options>
287+
<tt:QualityRange><tt:Min>1</tt:Min><tt:Max>6</tt:Max></tt:QualityRange>
288+
<tt:H264>
289+
<tt:ResolutionsAvailable><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:ResolutionsAvailable>
290+
<tt:GovLengthRange><tt:Min>0</tt:Min><tt:Max>100</tt:Max></tt:GovLengthRange>
291+
<tt:FrameRateRange><tt:Min>1</tt:Min><tt:Max>30</tt:Max></tt:FrameRateRange>
292+
<tt:EncodingIntervalRange><tt:Min>1</tt:Min><tt:Max>100</tt:Max></tt:EncodingIntervalRange>
293+
<tt:H264ProfilesSupported>Main</tt:H264ProfilesSupported>
294+
</tt:H264>
295+
</trt:Options>
296+
</trt:GetVideoEncoderConfigurationOptionsResponse>`,
278297
}

0 commit comments

Comments
 (0)