Skip to content

Commit

Permalink
Improve ONVIF server
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexxIT committed Jan 3, 2025
1 parent 2c3219f commit f601c47
Show file tree
Hide file tree
Showing 8 changed files with 504 additions and 344 deletions.
72 changes: 72 additions & 0 deletions examples/onvif_client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"log"
"net"
"net/url"
"os"

"github.com/AlexxIT/go2rtc/pkg/onvif"
)

func main() {
var rawURL = os.Args[1]
var operation = os.Args[2]
var token string
if len(os.Args) > 3 {
token = os.Args[3]
}

client, err := onvif.NewClient(rawURL)
if err != nil {
log.Panic(err)
}

var b []byte

switch operation {
case onvif.ServiceGetServiceCapabilities:
b, err = client.MediaRequest(operation)
case onvif.DeviceGetCapabilities,
onvif.DeviceGetDeviceInformation,
onvif.DeviceGetDiscoveryMode,
onvif.DeviceGetDNS,
onvif.DeviceGetHostname,
onvif.DeviceGetNetworkDefaultGateway,
onvif.DeviceGetNetworkInterfaces,
onvif.DeviceGetNetworkProtocols,
onvif.DeviceGetNTP,
onvif.DeviceGetScopes,
onvif.DeviceGetServices,
onvif.DeviceGetSystemDateAndTime,
onvif.DeviceSystemReboot:
b, err = client.DeviceRequest(operation)
case onvif.MediaGetProfiles, onvif.MediaGetVideoSources:
b, err = client.MediaRequest(operation)
case onvif.MediaGetProfile:
b, err = client.GetProfile(token)
case onvif.MediaGetVideoSourceConfiguration:
b, err = client.GetVideoSourceConfiguration(token)
case onvif.MediaGetStreamUri:
b, err = client.GetStreamUri(token)
case onvif.MediaGetSnapshotUri:
b, err = client.GetSnapshotUri(token)
default:
log.Printf("unknown action\n")
}

if err != nil {
log.Printf("%s\n", err)
}

u, err := url.Parse(rawURL)
if err != nil {
log.Fatal(err)
}

host, _, _ := net.SplitHostPort(u.Host)

if err = os.WriteFile(host+"_"+operation+".xml", b, 0644); err != nil {
log.Printf("%s\n", err)
}
}
25 changes: 25 additions & 0 deletions internal/onvif/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# ONVIF

A regular camera has a single video source (`GetVideoSources`) and two profiles (`GetProfiles`).

Go2rtc has one video source and one profile per stream.

## Tested clients

Go2rtc works as ONVIF server:

- Happytime onvif client (windows)
- Home Assistant ONVIF integration (linux)
- Onvier (android)
- ONVIF Device Manager (windows)

PS. Support only TCP transport for RTSP protocol. UDP and HTTP transports - unsupported yet.

## Tested cameras

Go2rtc works as ONVIF client:

- Dahua IPC-K42
- OpenIPC
- Reolink RLC-520A
- TP-Link Tapo TC60
84 changes: 48 additions & 36 deletions internal/onvif/init.go → internal/onvif/onvif.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,76 +55,88 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
return
}

action := onvif.GetRequestAction(b)
if action == "" {
operation := onvif.GetRequestAction(b)
if operation == "" {
http.Error(w, "malformed request body", http.StatusBadRequest)
return
}

log.Trace().Msgf("[onvif] %s", action)

var res string

switch action {
case onvif.ActionGetCapabilities:
log.Trace().Msgf("[onvif] server request %s %s:\n%s", r.Method, r.RequestURI, b)

switch operation {
case onvif.DeviceGetNetworkInterfaces, // important for Hass
onvif.DeviceGetSystemDateAndTime, // important for Hass
onvif.DeviceGetDiscoveryMode,
onvif.DeviceGetDNS,
onvif.DeviceGetHostname,
onvif.DeviceGetNetworkDefaultGateway,
onvif.DeviceGetNetworkProtocols,
onvif.DeviceGetNTP,
onvif.DeviceGetScopes:
b = onvif.StaticResponse(operation)

case onvif.DeviceGetCapabilities:
// important for Hass: Media section
res = onvif.GetCapabilitiesResponse(r.Host)

case onvif.ActionGetServices:
res = onvif.GetServicesResponse(r.Host)

case onvif.ActionGetSystemDateAndTime:
// important for Hass
res = onvif.GetSystemDateAndTimeResponse()
b = onvif.GetCapabilitiesResponse(r.Host)

case onvif.ActionGetNetworkInterfaces:
// important for Hass: none
res = onvif.GetNetworkInterfacesResponse()
case onvif.DeviceGetServices:
b = onvif.GetServicesResponse(r.Host)

case onvif.ActionGetDeviceInformation:
case onvif.DeviceGetDeviceInformation:
// important for Hass: SerialNumber (unique server ID)
res = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host)
b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host)

case onvif.ActionGetServiceCapabilities:
case onvif.ServiceGetServiceCapabilities:
// important for Hass
res = onvif.GetServiceCapabilitiesResponse()
// TODO: check path links to media
b = onvif.GetMediaServiceCapabilitiesResponse()

case onvif.ActionSystemReboot:
res = onvif.SystemRebootResponse()
case onvif.DeviceSystemReboot:
b = onvif.StaticResponse(operation)

time.AfterFunc(time.Second, func() {
os.Exit(0)
})

case onvif.ActionGetProfiles:
case onvif.MediaGetVideoSources:
b = onvif.GetVideoSourcesResponse(streams.GetAll())

case onvif.MediaGetProfiles:
// important for Hass: H264 codec, width, height
res = onvif.GetProfilesResponse(streams.GetAll())
b = onvif.GetProfilesResponse(streams.GetAll())

case onvif.ActionGetVideoSources:
res = onvif.GetVideoSourcesResponse(streams.GetAll())
case onvif.MediaGetProfile:
token := onvif.FindTagValue(b, "ProfileToken")
b = onvif.GetProfileResponse(token)

case onvif.ActionGetStreamUri:
case onvif.MediaGetVideoSourceConfiguration:
token := onvif.FindTagValue(b, "ConfigurationToken")
b = onvif.GetVideoSourceConfigurationResponse(token)

case onvif.MediaGetStreamUri:
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

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

case onvif.ActionGetSnapshotUri:
case onvif.MediaGetSnapshotUri:
uri := "http://" + r.Host + "/api/frame.jpeg?src=" + onvif.FindTagValue(b, "ProfileToken")
res = onvif.GetSnapshotUriResponse(uri)
b = onvif.GetSnapshotUriResponse(uri)

default:
http.Error(w, "unsupported action", http.StatusBadRequest)
http.Error(w, "unsupported operation", http.StatusBadRequest)
log.Debug().Msgf("[onvif] unsupported request:\n%s", b)
return
}

log.Trace().Msgf("[onvif] server response:\n%s", b)

w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8")
if _, err = w.Write([]byte(res)); err != nil {
if _, err = w.Write(b); err != nil {
log.Error().Err(err).Caller().Send()
}
}
Expand Down Expand Up @@ -170,7 +182,7 @@ func apiOnvif(w http.ResponseWriter, r *http.Request) {
}

if l := log.Trace(); l.Enabled() {
b, _ := client.GetProfiles()
b, _ := client.MediaRequest(onvif.MediaGetProfiles)
l.Msgf("[onvif] src=%s profiles:\n%s", src, b)
}

Expand Down
38 changes: 38 additions & 0 deletions pkg/onvif/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## Profiles

- Profile A - For access control configuration
- Profile C - For door control and event management
- Profile S - For basic video streaming
- Video streaming and configuration
- Profile T - For advanced video streaming
- H.264 / H.265 video compression
- Imaging settings
- Motion alarm and tampering events
- Metadata streaming
- Bi-directional audio

## Services

https://www.onvif.org/profiles/specifications/

- https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl
- https://www.onvif.org/ver20/imaging/wsdl/imaging.wsdl
- https://www.onvif.org/ver10/media/wsdl/media.wsdl

## TMP

| | Dahua | Reolink | TP-Link |
|------------------------|---------|---------|---------|
| GetCapabilities | no auth | no auth | no auth |
| GetServices | no auth | no auth | no auth |
| GetServiceCapabilities | no auth | no auth | auth |
| GetSystemDateAndTime | no auth | no auth | no auth |
| GetNetworkInterfaces | auth | auth | auth |
| GetDeviceInformation | auth | auth | auth |
| GetProfiles | auth | auth | auth |
| GetScopes | auth | auth | auth |

- Dahua - onvif://192.168.10.90:80
- Reolink - onvif://192.168.10.92:8000
- TP-Link - onvif://192.168.10.91:2020/onvif/device_service
-
Loading

1 comment on commit f601c47

@latel
Copy link

@latel latel commented on f601c47 Feb 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can I add cameras from go2rtc as onvif now?

Please sign in to comment.