ONVIF server with multi-lens camera support

This commit is contained in:
ProtoTess
2025-10-31 01:25:44 +00:00
parent 23ed9f3c41
commit 3340094f4f
21 changed files with 4415 additions and 1 deletions
+222
View File
@@ -0,0 +1,222 @@
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/0x524A/go-onvif/server"
)
func main() {
// Create a custom multi-lens camera configuration
config := &server.Config{
Host: "0.0.0.0",
Port: 8080,
BasePath: "/onvif",
Timeout: 30 * time.Second,
DeviceInfo: server.DeviceInfo{
Manufacturer: "MultiCam Systems",
Model: "MC-3000 Pro",
FirmwareVersion: "2.5.1",
SerialNumber: "MC3000-001234",
HardwareID: "HW-MC3000",
},
Username: "admin",
Password: "SecurePass123",
SupportPTZ: true,
SupportImaging: true,
SupportEvents: false,
Profiles: []server.ProfileConfig{
// Profile 1: Main camera with 4K resolution
{
Token: "profile_main_4k",
Name: "Main Camera 4K",
VideoSource: server.VideoSourceConfig{
Token: "video_source_main",
Name: "Main Camera",
Resolution: server.Resolution{Width: 3840, Height: 2160},
Framerate: 30,
Bounds: server.Bounds{X: 0, Y: 0, Width: 3840, Height: 2160},
},
VideoEncoder: server.VideoEncoderConfig{
Encoding: "H264",
Resolution: server.Resolution{Width: 3840, Height: 2160},
Quality: 90,
Framerate: 30,
Bitrate: 20480, // 20 Mbps
GovLength: 30,
},
PTZ: &server.PTZConfig{
NodeToken: "ptz_main",
PanRange: server.Range{Min: -180, Max: 180},
TiltRange: server.Range{Min: -90, Max: 90},
ZoomRange: server.Range{Min: 0, Max: 10}, // 10x optical zoom
DefaultSpeed: server.PTZSpeed{Pan: 0.5, Tilt: 0.5, Zoom: 0.5},
SupportsContinuous: true,
SupportsAbsolute: true,
SupportsRelative: true,
Presets: []server.Preset{
{Token: "preset_home", Name: "Home Position", Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 0}},
{Token: "preset_entrance", Name: "Main Entrance", Position: server.PTZPosition{Pan: -45, Tilt: -20, Zoom: 3}},
{Token: "preset_parking", Name: "Parking Lot", Position: server.PTZPosition{Pan: 90, Tilt: -30, Zoom: 5}},
{Token: "preset_perimeter", Name: "Perimeter View", Position: server.PTZPosition{Pan: 180, Tilt: 0, Zoom: 2}},
},
},
Snapshot: server.SnapshotConfig{
Enabled: true,
Resolution: server.Resolution{Width: 3840, Height: 2160},
Quality: 95,
},
},
// Profile 2: Wide-angle camera for overview
{
Token: "profile_wide",
Name: "Wide Angle Overview",
VideoSource: server.VideoSourceConfig{
Token: "video_source_wide",
Name: "Wide Angle Camera",
Resolution: server.Resolution{Width: 2560, Height: 1440},
Framerate: 30,
Bounds: server.Bounds{X: 0, Y: 0, Width: 2560, Height: 1440},
},
VideoEncoder: server.VideoEncoderConfig{
Encoding: "H264",
Resolution: server.Resolution{Width: 2560, Height: 1440},
Quality: 85,
Framerate: 30,
Bitrate: 8192, // 8 Mbps
GovLength: 30,
},
Snapshot: server.SnapshotConfig{
Enabled: true,
Resolution: server.Resolution{Width: 2560, Height: 1440},
Quality: 90,
},
},
// Profile 3: Telephoto camera for distant subjects
{
Token: "profile_telephoto",
Name: "Telephoto Camera",
VideoSource: server.VideoSourceConfig{
Token: "video_source_telephoto",
Name: "Telephoto Camera",
Resolution: server.Resolution{Width: 1920, Height: 1080},
Framerate: 60, // High framerate for smooth tracking
Bounds: server.Bounds{X: 0, Y: 0, Width: 1920, Height: 1080},
},
VideoEncoder: server.VideoEncoderConfig{
Encoding: "H264",
Resolution: server.Resolution{Width: 1920, Height: 1080},
Quality: 88,
Framerate: 60,
Bitrate: 10240, // 10 Mbps
GovLength: 60,
},
PTZ: &server.PTZConfig{
NodeToken: "ptz_telephoto",
PanRange: server.Range{Min: -180, Max: 180},
TiltRange: server.Range{Min: -45, Max: 45},
ZoomRange: server.Range{Min: 0, Max: 30}, // 30x optical zoom
DefaultSpeed: server.PTZSpeed{Pan: 0.3, Tilt: 0.3, Zoom: 0.3},
SupportsContinuous: true,
SupportsAbsolute: true,
SupportsRelative: true,
Presets: []server.Preset{
{Token: "preset_tel_home", Name: "Home", Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 0}},
{Token: "preset_tel_far", Name: "Far View", Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 20}},
{Token: "preset_tel_left", Name: "Left Side", Position: server.PTZPosition{Pan: -90, Tilt: 0, Zoom: 10}},
{Token: "preset_tel_right", Name: "Right Side", Position: server.PTZPosition{Pan: 90, Tilt: 0, Zoom: 10}},
},
},
Snapshot: server.SnapshotConfig{
Enabled: true,
Resolution: server.Resolution{Width: 1920, Height: 1080},
Quality: 92,
},
},
// Profile 4: Low-light camera for night vision
{
Token: "profile_lowlight",
Name: "Low Light Night Camera",
VideoSource: server.VideoSourceConfig{
Token: "video_source_lowlight",
Name: "Low Light Camera",
Resolution: server.Resolution{Width: 1920, Height: 1080},
Framerate: 30,
Bounds: server.Bounds{X: 0, Y: 0, Width: 1920, Height: 1080},
},
VideoEncoder: server.VideoEncoderConfig{
Encoding: "H264",
Resolution: server.Resolution{Width: 1920, Height: 1080},
Quality: 85,
Framerate: 30,
Bitrate: 6144, // 6 Mbps
GovLength: 30,
},
Snapshot: server.SnapshotConfig{
Enabled: true,
Resolution: server.Resolution{Width: 1920, Height: 1080},
Quality: 88,
},
},
},
}
// Create and start server
srv, err := server.New(config)
if err != nil {
log.Fatalf("Failed to create server: %v", err)
}
// Print configuration
fmt.Println("╔════════════════════════════════════════════════════════════════╗")
fmt.Println("║ ║")
fmt.Println("║ 🎥 ONVIF Multi-Lens Camera Server Example 🎥 ║")
fmt.Println("║ ║")
fmt.Println("╚════════════════════════════════════════════════════════════════╝")
fmt.Println()
fmt.Println(srv.ServerInfo())
fmt.Println()
fmt.Println("📝 Configuration Details:")
fmt.Println(" • 4 camera lenses with different capabilities")
fmt.Println(" • Main camera: 4K resolution with 10x zoom PTZ")
fmt.Println(" • Wide angle: 1440p for area overview")
fmt.Println(" • Telephoto: 1080p@60fps with 30x zoom for distant subjects")
fmt.Println(" • Low light: 1080p optimized for night vision")
fmt.Println()
fmt.Println("🔐 Credentials:")
fmt.Println(" Username: admin")
fmt.Println(" Password: SecurePass123")
fmt.Println()
fmt.Println("Press Ctrl+C to stop the server...")
fmt.Println()
// Create context with cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Setup signal handler
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// Start server in goroutine
go func() {
if err := srv.Start(ctx); err != nil {
log.Printf("Server error: %v", err)
cancel()
}
}()
// Wait for interrupt signal
<-sigChan
fmt.Println("\n🛑 Shutting down server...")
cancel()
time.Sleep(1 * time.Second)
fmt.Println("✅ Server stopped successfully")
}
BIN
View File
Binary file not shown.
+28
View File
@@ -0,0 +1,28 @@
package main
import (
"context"
"fmt"
"log"
"github.com/0x524A/go-onvif/server"
)
func main() {
fmt.Println("Starting ONVIF Server on port 8081...")
fmt.Println("Press Ctrl+C to stop")
fmt.Println()
config := server.DefaultConfig()
config.Port = 8081
srv, err := server.New(config)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
if err := srv.Start(ctx); err != nil {
log.Fatal(err)
}
}
+190
View File
@@ -0,0 +1,190 @@
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/0x524A/go-onvif"
"github.com/0x524A/go-onvif/server"
)
func main() {
fmt.Println("🧪 Testing ONVIF Server Implementation")
fmt.Println("======================================")
fmt.Println()
// Create and start server in background
config := server.DefaultConfig()
config.Port = 8081 // Use different port to avoid conflicts
srv, err := server.New(config)
if err != nil {
log.Fatalf("Failed to create server: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Start server in background
serverReady := make(chan bool)
go func() {
// Give server a moment to start
time.Sleep(500 * time.Millisecond)
serverReady <- true
if err := srv.Start(ctx); err != nil {
log.Printf("Server error: %v", err)
}
}()
// Wait for server to be ready
<-serverReady
fmt.Println("✅ Server started on port 8081")
fmt.Println()
// Create ONVIF client
client, err := onvif.NewClient(
"http://localhost:8081/onvif/device_service",
onvif.WithCredentials("admin", "admin"),
onvif.WithTimeout(10*time.Second),
)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
testCtx := context.Background()
// Test 1: Get Device Information
fmt.Println("Test 1: GetDeviceInformation")
info, err := client.GetDeviceInformation(testCtx)
if err != nil {
log.Fatalf("❌ GetDeviceInformation failed: %v", err)
}
fmt.Printf("✅ Device: %s %s (Firmware: %s)\n", info.Manufacturer, info.Model, info.FirmwareVersion)
fmt.Printf(" Serial: %s\n", info.SerialNumber)
fmt.Println()
// Test 2: Get Capabilities
fmt.Println("Test 2: GetCapabilities")
if err := client.Initialize(testCtx); err != nil {
log.Fatalf("❌ Initialize (GetCapabilities) failed: %v", err)
}
fmt.Println("✅ Capabilities retrieved successfully")
fmt.Println()
// Test 3: Get Profiles
fmt.Println("Test 3: GetProfiles")
profiles, err := client.GetProfiles(testCtx)
if err != nil {
log.Fatalf("❌ GetProfiles failed: %v", err)
}
fmt.Printf("✅ Found %d profiles:\n", len(profiles))
for i, profile := range profiles {
fmt.Printf(" [%d] %s (Token: %s)\n", i+1, profile.Name, profile.Token)
if profile.VideoEncoderConfiguration != nil {
fmt.Printf(" Video: %dx%d @ %s\n",
profile.VideoEncoderConfiguration.Resolution.Width,
profile.VideoEncoderConfiguration.Resolution.Height,
profile.VideoEncoderConfiguration.Encoding)
}
}
fmt.Println()
// Test 4: Get Stream URI
if len(profiles) > 0 {
fmt.Println("Test 4: GetStreamURI")
streamURI, err := client.GetStreamURI(testCtx, profiles[0].Token)
if err != nil {
log.Fatalf("❌ GetStreamURI failed: %v", err)
}
fmt.Printf("✅ Stream URI: %s\n", streamURI.URI)
fmt.Println()
}
// Test 5: Get Snapshot URI
if len(profiles) > 0 {
fmt.Println("Test 5: GetSnapshotURI")
snapshotURI, err := client.GetSnapshotURI(testCtx, profiles[0].Token)
if err != nil {
log.Fatalf("❌ GetSnapshotURI failed: %v", err)
}
fmt.Printf("✅ Snapshot URI: %s\n", snapshotURI.URI)
fmt.Println()
}
// Test 6: PTZ Status (if PTZ is available)
if len(profiles) > 0 && profiles[0].PTZConfiguration != nil {
fmt.Println("Test 6: PTZ GetStatus")
status, err := client.GetStatus(testCtx, profiles[0].Token)
if err != nil {
log.Fatalf("❌ GetStatus failed: %v", err)
}
fmt.Printf("✅ PTZ Position: Pan=%.2f, Tilt=%.2f, Zoom=%.2f\n",
status.Position.PanTilt.X,
status.Position.PanTilt.Y,
status.Position.Zoom.X)
fmt.Println()
// Test 7: PTZ Absolute Move
fmt.Println("Test 7: PTZ AbsoluteMove")
position := &onvif.PTZVector{
PanTilt: &onvif.Vector2D{X: 10.0, Y: -5.0},
Zoom: &onvif.Vector1D{X: 0.5},
}
if err := client.AbsoluteMove(testCtx, profiles[0].Token, position, nil); err != nil {
log.Fatalf("❌ AbsoluteMove failed: %v", err)
}
fmt.Println("✅ PTZ moved to absolute position")
fmt.Println()
// Wait a bit for movement to complete
time.Sleep(600 * time.Millisecond)
// Verify new position
fmt.Println("Test 8: Verify PTZ Position")
status, err = client.GetStatus(testCtx, profiles[0].Token)
if err != nil {
log.Fatalf("❌ GetStatus failed: %v", err)
}
fmt.Printf("✅ New PTZ Position: Pan=%.2f, Tilt=%.2f, Zoom=%.2f\n",
status.Position.PanTilt.X,
status.Position.PanTilt.Y,
status.Position.Zoom.X)
fmt.Println()
// Test 9: PTZ Presets
fmt.Println("Test 9: Get PTZ Presets")
presets, err := client.GetPresets(testCtx, profiles[0].Token)
if err != nil {
log.Fatalf("❌ GetPresets failed: %v", err)
}
fmt.Printf("✅ Found %d presets:\n", len(presets))
for i, preset := range presets {
fmt.Printf(" [%d] %s (Token: %s)\n", i+1, preset.Name, preset.Token)
}
fmt.Println()
}
// Test 10: Get System Date and Time
fmt.Println("Test 10: GetSystemDateAndTime")
_, err = client.GetSystemDateAndTime(testCtx)
if err != nil {
log.Fatalf("❌ GetSystemDateAndTime failed: %v", err)
}
fmt.Println("✅ System date and time retrieved successfully")
fmt.Println()
// All tests passed!
fmt.Println("╔══════════════════════════════════════════════════════════╗")
fmt.Println("║ ║")
fmt.Println("║ ✅ All Tests Passed! ONVIF Server is working! ✅ ║")
fmt.Println("║ ║")
fmt.Println("╚══════════════════════════════════════════════════════════╝")
fmt.Println()
// Stop the server
cancel()
time.Sleep(500 * time.Millisecond)
}