Files
onvif-go/server/imaging.go
T
2025-10-31 01:25:44 +00:00

420 lines
14 KiB
Go

package server
import (
"encoding/xml"
"fmt"
"sync"
)
// Imaging service SOAP message types
// GetImagingSettingsRequest represents GetImagingSettings request
type GetImagingSettingsRequest struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver20/imaging/wsdl GetImagingSettings"`
VideoSourceToken string `xml:"VideoSourceToken"`
}
// GetImagingSettingsResponse represents GetImagingSettings response
type GetImagingSettingsResponse struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver20/imaging/wsdl GetImagingSettingsResponse"`
ImagingSettings *ImagingSettings `xml:"ImagingSettings"`
}
// ImagingSettings represents imaging settings
type ImagingSettings struct {
BacklightCompensation *BacklightCompensationSettings `xml:"BacklightCompensation,omitempty"`
Brightness *float64 `xml:"Brightness,omitempty"`
ColorSaturation *float64 `xml:"ColorSaturation,omitempty"`
Contrast *float64 `xml:"Contrast,omitempty"`
Exposure *ExposureSettings20 `xml:"Exposure,omitempty"`
Focus *FocusConfiguration20 `xml:"Focus,omitempty"`
IrCutFilter *string `xml:"IrCutFilter,omitempty"`
Sharpness *float64 `xml:"Sharpness,omitempty"`
WideDynamicRange *WideDynamicRangeSettings `xml:"WideDynamicRange,omitempty"`
WhiteBalance *WhiteBalanceSettings20 `xml:"WhiteBalance,omitempty"`
}
// BacklightCompensationSettings represents backlight compensation settings
type BacklightCompensationSettings struct {
Mode string `xml:"Mode"`
Level *float64 `xml:"Level,omitempty"`
}
// ExposureSettings20 represents exposure settings for ONVIF 2.0
type ExposureSettings20 struct {
Mode string `xml:"Mode"`
Priority *string `xml:"Priority,omitempty"`
Window *Rectangle `xml:"Window,omitempty"`
MinExposureTime *float64 `xml:"MinExposureTime,omitempty"`
MaxExposureTime *float64 `xml:"MaxExposureTime,omitempty"`
MinGain *float64 `xml:"MinGain,omitempty"`
MaxGain *float64 `xml:"MaxGain,omitempty"`
MinIris *float64 `xml:"MinIris,omitempty"`
MaxIris *float64 `xml:"MaxIris,omitempty"`
ExposureTime *float64 `xml:"ExposureTime,omitempty"`
Gain *float64 `xml:"Gain,omitempty"`
Iris *float64 `xml:"Iris,omitempty"`
}
// FocusConfiguration20 represents focus configuration for ONVIF 2.0
type FocusConfiguration20 struct {
AutoFocusMode string `xml:"AutoFocusMode"`
DefaultSpeed *float64 `xml:"DefaultSpeed,omitempty"`
NearLimit *float64 `xml:"NearLimit,omitempty"`
FarLimit *float64 `xml:"FarLimit,omitempty"`
}
// WideDynamicRangeSettings represents WDR settings
type WideDynamicRangeSettings struct {
Mode string `xml:"Mode"`
Level *float64 `xml:"Level,omitempty"`
}
// WhiteBalanceSettings20 represents white balance settings for ONVIF 2.0
type WhiteBalanceSettings20 struct {
Mode string `xml:"Mode"`
CrGain *float64 `xml:"CrGain,omitempty"`
CbGain *float64 `xml:"CbGain,omitempty"`
}
// Rectangle represents a rectangle
type Rectangle struct {
Bottom float64 `xml:"bottom,attr"`
Top float64 `xml:"top,attr"`
Right float64 `xml:"right,attr"`
Left float64 `xml:"left,attr"`
}
// SetImagingSettingsRequest represents SetImagingSettings request
type SetImagingSettingsRequest struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver20/imaging/wsdl SetImagingSettings"`
VideoSourceToken string `xml:"VideoSourceToken"`
ImagingSettings *ImagingSettings `xml:"ImagingSettings"`
ForcePersistence bool `xml:"ForcePersistence,omitempty"`
}
// SetImagingSettingsResponse represents SetImagingSettings response
type SetImagingSettingsResponse struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver20/imaging/wsdl SetImagingSettingsResponse"`
}
// GetOptionsRequest represents GetOptions request
type GetOptionsRequest struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver20/imaging/wsdl GetOptions"`
VideoSourceToken string `xml:"VideoSourceToken"`
}
// GetOptionsResponse represents GetOptions response
type GetOptionsResponse struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver20/imaging/wsdl GetOptionsResponse"`
ImagingOptions *ImagingOptions `xml:"ImagingOptions"`
}
// ImagingOptions represents imaging options/capabilities
type ImagingOptions struct {
BacklightCompensation *BacklightCompensationOptions `xml:"BacklightCompensation,omitempty"`
Brightness *FloatRange `xml:"Brightness,omitempty"`
ColorSaturation *FloatRange `xml:"ColorSaturation,omitempty"`
Contrast *FloatRange `xml:"Contrast,omitempty"`
Exposure *ExposureOptions `xml:"Exposure,omitempty"`
Focus *FocusOptions `xml:"Focus,omitempty"`
IrCutFilterModes []string `xml:"IrCutFilterModes,omitempty"`
Sharpness *FloatRange `xml:"Sharpness,omitempty"`
WideDynamicRange *WideDynamicRangeOptions `xml:"WideDynamicRange,omitempty"`
WhiteBalance *WhiteBalanceOptions `xml:"WhiteBalance,omitempty"`
}
// BacklightCompensationOptions represents backlight compensation options
type BacklightCompensationOptions struct {
Mode []string `xml:"Mode"`
Level *FloatRange `xml:"Level,omitempty"`
}
// ExposureOptions represents exposure options
type ExposureOptions struct {
Mode []string `xml:"Mode"`
Priority []string `xml:"Priority,omitempty"`
MinExposureTime *FloatRange `xml:"MinExposureTime,omitempty"`
MaxExposureTime *FloatRange `xml:"MaxExposureTime,omitempty"`
MinGain *FloatRange `xml:"MinGain,omitempty"`
MaxGain *FloatRange `xml:"MaxGain,omitempty"`
MinIris *FloatRange `xml:"MinIris,omitempty"`
MaxIris *FloatRange `xml:"MaxIris,omitempty"`
ExposureTime *FloatRange `xml:"ExposureTime,omitempty"`
Gain *FloatRange `xml:"Gain,omitempty"`
Iris *FloatRange `xml:"Iris,omitempty"`
}
// FocusOptions represents focus options
type FocusOptions struct {
AutoFocusModes []string `xml:"AutoFocusModes"`
DefaultSpeed *FloatRange `xml:"DefaultSpeed,omitempty"`
NearLimit *FloatRange `xml:"NearLimit,omitempty"`
FarLimit *FloatRange `xml:"FarLimit,omitempty"`
}
// WideDynamicRangeOptions represents WDR options
type WideDynamicRangeOptions struct {
Mode []string `xml:"Mode"`
Level *FloatRange `xml:"Level,omitempty"`
}
// WhiteBalanceOptions represents white balance options
type WhiteBalanceOptions struct {
Mode []string `xml:"Mode"`
YrGain *FloatRange `xml:"YrGain,omitempty"`
YbGain *FloatRange `xml:"YbGain,omitempty"`
}
// MoveRequest represents Move (focus) request
type MoveRequest struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver20/imaging/wsdl Move"`
VideoSourceToken string `xml:"VideoSourceToken"`
Focus *FocusMove `xml:"Focus"`
}
// FocusMove represents focus move parameters
type FocusMove struct {
Absolute *AbsoluteFocus `xml:"Absolute,omitempty"`
Relative *RelativeFocus `xml:"Relative,omitempty"`
Continuous *ContinuousFocus `xml:"Continuous,omitempty"`
}
// AbsoluteFocus represents absolute focus
type AbsoluteFocus struct {
Position float64 `xml:"Position"`
Speed *float64 `xml:"Speed,omitempty"`
}
// RelativeFocus represents relative focus
type RelativeFocus struct {
Distance float64 `xml:"Distance"`
Speed *float64 `xml:"Speed,omitempty"`
}
// ContinuousFocus represents continuous focus
type ContinuousFocus struct {
Speed float64 `xml:"Speed"`
}
// MoveResponse represents Move response
type MoveResponse struct {
XMLName xml.Name `xml:"http://www.onvif.org/ver20/imaging/wsdl MoveResponse"`
}
// Imaging service handlers
var imagingMutex sync.RWMutex
// HandleGetImagingSettings handles GetImagingSettings request
func (s *Server) HandleGetImagingSettings(body interface{}) (interface{}, error) {
var req GetImagingSettingsRequest
if err := unmarshalBody(body, &req); err != nil {
return nil, fmt.Errorf("invalid request: %w", err)
}
// Get imaging state
imagingMutex.RLock()
defer imagingMutex.RUnlock()
state, ok := s.imagingState[req.VideoSourceToken]
if !ok {
return nil, fmt.Errorf("video source not found: %s", req.VideoSourceToken)
}
// Build imaging settings response
settings := &ImagingSettings{
Brightness: &state.Brightness,
ColorSaturation: &state.Saturation,
Contrast: &state.Contrast,
Sharpness: &state.Sharpness,
IrCutFilter: &state.IrCutFilter,
BacklightCompensation: &BacklightCompensationSettings{
Mode: state.BacklightComp.Mode,
Level: &state.BacklightComp.Level,
},
Exposure: &ExposureSettings20{
Mode: state.Exposure.Mode,
Priority: &state.Exposure.Priority,
MinExposureTime: &state.Exposure.MinExposure,
MaxExposureTime: &state.Exposure.MaxExposure,
MinGain: &state.Exposure.MinGain,
MaxGain: &state.Exposure.MaxGain,
ExposureTime: &state.Exposure.ExposureTime,
Gain: &state.Exposure.Gain,
},
Focus: &FocusConfiguration20{
AutoFocusMode: state.Focus.AutoFocusMode,
DefaultSpeed: &state.Focus.DefaultSpeed,
NearLimit: &state.Focus.NearLimit,
FarLimit: &state.Focus.FarLimit,
},
WideDynamicRange: &WideDynamicRangeSettings{
Mode: state.WideDynamicRange.Mode,
Level: &state.WideDynamicRange.Level,
},
WhiteBalance: &WhiteBalanceSettings20{
Mode: state.WhiteBalance.Mode,
CrGain: &state.WhiteBalance.CrGain,
CbGain: &state.WhiteBalance.CbGain,
},
}
return &GetImagingSettingsResponse{
ImagingSettings: settings,
}, nil
}
// HandleSetImagingSettings handles SetImagingSettings request
func (s *Server) HandleSetImagingSettings(body interface{}) (interface{}, error) {
var req SetImagingSettingsRequest
if err := unmarshalBody(body, &req); err != nil {
return nil, fmt.Errorf("invalid request: %w", err)
}
// Get imaging state
imagingMutex.Lock()
defer imagingMutex.Unlock()
state, ok := s.imagingState[req.VideoSourceToken]
if !ok {
return nil, fmt.Errorf("video source not found: %s", req.VideoSourceToken)
}
// Update settings
settings := req.ImagingSettings
if settings.Brightness != nil {
state.Brightness = *settings.Brightness
}
if settings.ColorSaturation != nil {
state.Saturation = *settings.ColorSaturation
}
if settings.Contrast != nil {
state.Contrast = *settings.Contrast
}
if settings.Sharpness != nil {
state.Sharpness = *settings.Sharpness
}
if settings.IrCutFilter != nil {
state.IrCutFilter = *settings.IrCutFilter
}
if settings.BacklightCompensation != nil {
state.BacklightComp.Mode = settings.BacklightCompensation.Mode
if settings.BacklightCompensation.Level != nil {
state.BacklightComp.Level = *settings.BacklightCompensation.Level
}
}
if settings.Exposure != nil {
state.Exposure.Mode = settings.Exposure.Mode
if settings.Exposure.Priority != nil {
state.Exposure.Priority = *settings.Exposure.Priority
}
if settings.Exposure.ExposureTime != nil {
state.Exposure.ExposureTime = *settings.Exposure.ExposureTime
}
if settings.Exposure.Gain != nil {
state.Exposure.Gain = *settings.Exposure.Gain
}
}
if settings.Focus != nil {
state.Focus.AutoFocusMode = settings.Focus.AutoFocusMode
}
if settings.WideDynamicRange != nil {
state.WideDynamicRange.Mode = settings.WideDynamicRange.Mode
if settings.WideDynamicRange.Level != nil {
state.WideDynamicRange.Level = *settings.WideDynamicRange.Level
}
}
if settings.WhiteBalance != nil {
state.WhiteBalance.Mode = settings.WhiteBalance.Mode
if settings.WhiteBalance.CrGain != nil {
state.WhiteBalance.CrGain = *settings.WhiteBalance.CrGain
}
if settings.WhiteBalance.CbGain != nil {
state.WhiteBalance.CbGain = *settings.WhiteBalance.CbGain
}
}
return &SetImagingSettingsResponse{}, nil
}
// HandleGetOptions handles GetOptions request
func (s *Server) HandleGetOptions(body interface{}) (interface{}, error) {
// Return available imaging options/capabilities
options := &ImagingOptions{
Brightness: &FloatRange{Min: 0, Max: 100},
ColorSaturation: &FloatRange{Min: 0, Max: 100},
Contrast: &FloatRange{Min: 0, Max: 100},
Sharpness: &FloatRange{Min: 0, Max: 100},
IrCutFilterModes: []string{"ON", "OFF", "AUTO"},
BacklightCompensation: &BacklightCompensationOptions{
Mode: []string{"OFF", "ON"},
Level: &FloatRange{Min: 0, Max: 100},
},
Exposure: &ExposureOptions{
Mode: []string{"AUTO", "MANUAL"},
Priority: []string{"LowNoise", "FrameRate"},
MinExposureTime: &FloatRange{Min: 1, Max: 10000},
MaxExposureTime: &FloatRange{Min: 1, Max: 10000},
MinGain: &FloatRange{Min: 0, Max: 100},
MaxGain: &FloatRange{Min: 0, Max: 100},
ExposureTime: &FloatRange{Min: 1, Max: 10000},
Gain: &FloatRange{Min: 0, Max: 100},
},
Focus: &FocusOptions{
AutoFocusModes: []string{"AUTO", "MANUAL"},
DefaultSpeed: &FloatRange{Min: 0, Max: 1},
NearLimit: &FloatRange{Min: 0, Max: 1},
FarLimit: &FloatRange{Min: 0, Max: 1},
},
WideDynamicRange: &WideDynamicRangeOptions{
Mode: []string{"OFF", "ON"},
Level: &FloatRange{Min: 0, Max: 100},
},
WhiteBalance: &WhiteBalanceOptions{
Mode: []string{"AUTO", "MANUAL"},
YrGain: &FloatRange{Min: 0, Max: 255},
YbGain: &FloatRange{Min: 0, Max: 255},
},
}
return &GetOptionsResponse{
ImagingOptions: options,
}, nil
}
// HandleMove handles Move (focus) request
func (s *Server) HandleMove(body interface{}) (interface{}, error) {
var req MoveRequest
if err := unmarshalBody(body, &req); err != nil {
return nil, fmt.Errorf("invalid request: %w", err)
}
// Get imaging state
imagingMutex.Lock()
defer imagingMutex.Unlock()
state, ok := s.imagingState[req.VideoSourceToken]
if !ok {
return nil, fmt.Errorf("video source not found: %s", req.VideoSourceToken)
}
// Process focus move
if req.Focus != nil {
if req.Focus.Absolute != nil {
state.Focus.CurrentPos = req.Focus.Absolute.Position
} else if req.Focus.Relative != nil {
state.Focus.CurrentPos += req.Focus.Relative.Distance
// Clamp to valid range
if state.Focus.CurrentPos < 0 {
state.Focus.CurrentPos = 0
} else if state.Focus.CurrentPos > 1 {
state.Focus.CurrentPos = 1
}
}
// Continuous focus would start a background task
}
return &MoveResponse{}, nil
}