ONVIF server with multi-lens camera support
This commit is contained in:
@@ -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")
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user