Click here to Skip to main content
15,878,809 members
Articles / Programming Languages / C#
Tip/Trick

ONVIF PTZ Control in C#

Rate me:
Please Sign up or sign in to vote.
4.75/5 (7 votes)
20 Jun 2017CPOL3 min read 79.1K   19   21
A C# class to implement a Pan and Tilt controller using ONVIF

Introduction

Some time ago, I developed an application to interface to an IP camera but now I need to drop in pan and tilt control. The camera came with its own SDK but it's only C++ and seems fiendishly complicated; in any case I didn't want my code to be tied to a specific camera or manufacturer. (The camera also supports zoom but it's only digital so I didn't bother to implement that. It would be easy to add.)

Background

The camera supports ONVIF so that seemed the obvious choice for a generic control. I could find no useful source code; almost all searches ended up at the web site for a particular SDK that is apparently quite expensive. I needed to be able to develop and maintain my own code so I collected bits of information from many different sources and started experimenting. The code below represents a working implementation. I'm not entirely happy with it but I ran out of time to improve it before it had to be installed on a test rig.

Using the Code

To use the code, you need to add two service references:

  • Right-click the "References" folder in Visual Studio
  • Choose "Add Service Reference..."
  • Type or paste that address into the Address control
  • Type or paste the namespace into the Namespace control

The service references are:

AddressNamespace
http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdlOnvifMedia10
http://onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdlOnvifPTZService

The code only provides the communication interface to the camera. It exposes public functions and properties to be used by the UI. That means you can drop in the same class for WPF, Windows Forms or whatever else you are using.

Here's the Drop-in Class

C#
namespace PTZController
    {
    using System;
    using System.Net;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Timers;
    using PTZController.OnvifMedia10;
    using PTZController.OnvifPTZService;

    public class Controller
        {
        private enum Direction { None, Up, Down, Left, Right };

        MediaClient mediaClient;
        PTZClient ptzClient;
        Profile profile;
        OnvifPTZService.PTZSpeed velocity;
        PTZVector vector;
        PTZConfigurationOptions options;
        bool relative = false;
        bool initialised = false;
        Timer timer;
        Direction direction;
        float panDistance;
        float tiltDistance;

        public string ErrorMessage { get; private set; }

        public bool Initialised { get { return initialised; } }

        public int PanIncrements { get; set; } = 20;

        public int TiltIncrements { get; set; } = 20;

        public double TimerInterval { get; set; } = 1500;

        public Controller(bool relative = false)
            {
            this.relative = relative;
            }

        public bool Initialise(string cameraAddress, string userName, string password)
            {
            bool result = false;

            try
                {
                var messageElement = new TextMessageEncodingBindingElement()
                    {
                    MessageVersion = MessageVersion.CreateVersion(
                      EnvelopeVersion.Soap12, AddressingVersion.None)
                    };
                HttpTransportBindingElement httpBinding = new HttpTransportBindingElement()
                    {
                    AuthenticationScheme = AuthenticationSchemes.Digest
                    };
                CustomBinding bind = new CustomBinding(messageElement, httpBinding);
                mediaClient = new MediaClient(bind,
                  new EndpointAddress($"http://{cameraAddress}/onvif/Media"));
                mediaClient.ClientCredentials.HttpDigest.AllowedImpersonationLevel =
                  System.Security.Principal.TokenImpersonationLevel.Impersonation;
                mediaClient.ClientCredentials.HttpDigest.ClientCredential.UserName = userName;
                mediaClient.ClientCredentials.HttpDigest.ClientCredential.Password = password;
                ptzClient = new PTZClient(bind,
                  new EndpointAddress($"http://{cameraAddress}/onvif/PTZ"));
                ptzClient.ClientCredentials.HttpDigest.AllowedImpersonationLevel =
                  System.Security.Principal.TokenImpersonationLevel.Impersonation;
                ptzClient.ClientCredentials.HttpDigest.ClientCredential.UserName = userName;
                ptzClient.ClientCredentials.HttpDigest.ClientCredential.Password = password;

                var profs = mediaClient.GetProfiles();
                profile = mediaClient.GetProfile(profs[0].token);

                var configs = ptzClient.GetConfigurations();

                options = ptzClient.GetConfigurationOptions(configs[0].token);

                velocity = new OnvifPTZService.PTZSpeed()
                    {
                    PanTilt = new OnvifPTZService.Vector2D()
                        {
                        x = 0,
                        y = 0,
                        space = options.Spaces.ContinuousPanTiltVelocitySpace[0].URI,
                        },
                    Zoom = new OnvifPTZService.Vector1D()
                        {
                        x = 0,
                        space = options.Spaces.ContinuousZoomVelocitySpace[0].URI,
                        }
                    };
                if (relative)
                    {
                    timer = new Timer(TimerInterval);
                    timer.Elapsed += Timer_Elapsed;
                    velocity.PanTilt.space = options.Spaces.RelativePanTiltTranslationSpace[0].URI;
                    panDistance = (options.Spaces.RelativePanTiltTranslationSpace[0].XRange.Max -
                      options.Spaces.RelativePanTiltTranslationSpace[0].XRange.Min) / PanIncrements;
                    tiltDistance = (options.Spaces.RelativePanTiltTranslationSpace[0].YRange.Max -
                      options.Spaces.RelativePanTiltTranslationSpace[0].YRange.Min) / TiltIncrements;
                    }

                vector = new PTZVector()
                {
                PanTilt = new OnvifPTZService.Vector2D()
                    {
                    x = 0, y = 0, space = options.Spaces.RelativePanTiltTranslationSpace[0].URI
                    }
                };

                ErrorMessage = "";
                result = initialised = true;
                }
            catch (Exception ex)
                {
                ErrorMessage = ex.Message;
                }
            return result;
            }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
            {
            Move();
            }

        public void PanLeft()
            {
            if (initialised)
                {
                if (relative)
                    {
                    direction = Direction.Left;
                    Move();
                    }
                else
                    {
                    velocity.PanTilt.x = options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Min;
                    velocity.PanTilt.y = 0;
                    ptzClient.ContinuousMoveAsync(profile.token, velocity, "PT10S");
                    }
                }
            }

        public void PanRight()
            {
            if (initialised)
                {
                if (relative)
                    {
                    direction = Direction.Right;
                    Move();
                    }
                else
                    {
                    velocity.PanTilt.x = options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Max;
                    velocity.PanTilt.y = 0;
                    ptzClient.ContinuousMoveAsync(profile.token, velocity, "PT10S");
                    }
                }
            }

        public void TiltUp()
            {
            if (initialised)
                {
                if (relative)
                    {
                    direction = Direction.Up;
                    Move();
                    }
                else
                    {
                    velocity.PanTilt.x = 0;
                    velocity.PanTilt.y = options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max;
                    ptzClient.ContinuousMoveAsync(profile.token, velocity, "PT10S");
                    }
                }
            }

        public void TiltDown()
            {
            if (initialised)
                {
                if (relative)
                    {
                    direction = Direction.Down;
                    Move();
                    }
                else
                    {
                    velocity.PanTilt.x = 0;
                    velocity.PanTilt.y = options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Min;
                    ptzClient.ContinuousMoveAsync(profile.token, velocity, "PT10S");
                    }
                }
            }

        public void Stop()
            {
            if (initialised)
                {
                if (relative)
                    timer.Enabled = false;
                direction = Direction.None;
                ptzClient.Stop(profile.token, true, true);
                }
            }

        private void Move()
            {
            bool move = true;

            switch (direction)
                {
                case Direction.Up:
                    velocity.PanTilt.x = 0;
                    velocity.PanTilt.y = options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max;
                    vector.PanTilt.x = 0;
                    vector.PanTilt.y = tiltDistance;
                    break;

                case Direction.Down:
                    velocity.PanTilt.x = 0;
                    velocity.PanTilt.y = options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max;
                    vector.PanTilt.x = 0;
                    vector.PanTilt.y = -tiltDistance;
                    break;

                case Direction.Left:
                    velocity.PanTilt.x = options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Max;
                    velocity.PanTilt.y = 0;
                    vector.PanTilt.x = -panDistance;
                    vector.PanTilt.y = 0;
                    break;

                case Direction.Right:
                    velocity.PanTilt.x = options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Max;
                    velocity.PanTilt.y = 0;
                    vector.PanTilt.x = panDistance;
                    vector.PanTilt.y = 0;
                    break;

                case Direction.None:
                default:
                    move = false;
                    break;
                }
            if (move)
                {
                ptzClient.RelativeMove(profile.token, vector, velocity);
                }
            timer.Enabled = true;
            }
        }
    }

To use it from the UI create and instance, set any properties where you want something different from the default, and then call its Initialise function. Proceed to call the other member functions as necessary, in response to user input. For example:

C#
using PTZController;

// ...

  Controller controller;

// ...
  controller = new Controller(Properties.Settings.Default.PTZRelative);
  controller.Initialise(Properties.Settings.Default.CameraAddress, "UserName", "password");
// ...

// in a button down handler
  controller.PanLeft();

// in a button up handler
  controller.Stop();

Points of Interest

  1. Since I could find no clear documentation anywhere on the specific use of ONVIF, the code was developed by experiment. One conclusion I reached (rightly or wrongly) was that, even though I'm using the ver20 version of the ptz interface, I still needed to use ver10 for the media interface. There's probably a way to use ver20 but it seemed much more straightforward to get the information I needed from ver10 and it worked so I stayed with it. I may revisit this if I get enough time, in case it helps with point 2.
  2. I first tried using Continuous moves but the camera response was very laggy, making the control effectively unusable. This may be something to do with the way that ONVIF is implemented in the camera I'm using; the camera response to the PTZ controls in the web app supplied with it is very good, with almost zero lag. I only spent a short time trying to identify and solve the problem before deciding that it would be better to use relative moves. This is still not very responsive but it's good enough for present needs. For that reason, the code for Continuous moves has been left not fully developed but I think it still works.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
England England
Started my career as an electronics engineer.

Started software development in 4004 assembler.

Progressed to 8080, Z80 and 6802 assembler in the days when you built your own computer from discrete components.

Dabbled in Macro-11 and Coral66 by way of a small digression.

Moved on to C, first on Z80s and then on PCs when they were invented.

Continued to C++ and then C#

Now working mostly in C# and XAML while maintaining about half a million lines of C++

Comments and Discussions

 
QuestionPTZ Command latency too high Pin
Member 1447550830-Jun-23 1:45
Member 1447550830-Jun-23 1:45 
AnswerRe: PTZ Command latency too high Pin
Phil J Pearson5-Jul-23 3:23
Phil J Pearson5-Jul-23 3:23 
QuestionWhat is the layout of the windows form application? Pin
Shine 211-Apr-22 15:17
Shine 211-Apr-22 15:17 
Questionhow to write csharp code to stream live Pin
Member 1459588615-May-21 20:49
Member 1459588615-May-21 20:49 
QuestionHttpDigest class does not contain property AllowedImpersonationLevel Pin
Member 149014988-Dec-20 20:09
Member 149014988-Dec-20 20:09 
PraiseMessage Removed Pin
30-Jul-20 21:38
Aarti Ladikkar30-Jul-20 21:38 
Question'Settings' does not contain a definition for 'PTZRelative' and no accessible extension method 'PTZRelative' Pin
Member 1415852921-Feb-19 9:28
Member 1415852921-Feb-19 9:28 
AnswerRe: 'Settings' does not contain a definition for 'PTZRelative' and no accessible extension method 'PTZRelative' Pin
bradut7-Dec-20 22:02
professionalbradut7-Dec-20 22:02 
QuestionHow am I supposed to make it work with the camera? Pin
Joanne Robertz19-Nov-18 1:57
Joanne Robertz19-Nov-18 1:57 
QuestionHow to create preset and Home position Pin
indra16028-Oct-18 16:03
indra16028-Oct-18 16:03 
Questionhow to zoom in and zoom out? Pin
indra160225-Sep-18 23:10
indra160225-Sep-18 23:10 
AnswerRe: how to zoom in and zoom out? Pin
teisnet26-Sep-18 4:08
teisnet26-Sep-18 4:08 
GeneralRe: how to zoom in and zoom out? Pin
indra16021-Oct-18 14:57
indra16021-Oct-18 14:57 
QuestionCompilation error... Pin
Richard Markham29-Jan-18 6:12
Richard Markham29-Jan-18 6:12 
QuestionCompilation error... Pin
Richard Markham29-Jan-18 2:11
Richard Markham29-Jan-18 2:11 
AnswerRe: Compilation error... Pin
Mokhtar Mostafa4-Aug-18 2:45
Mokhtar Mostafa4-Aug-18 2:45 
QuestionHow do you do zoom Pin
Tim12728-Jun-17 9:09
Tim12728-Jun-17 9:09 
QuestionAlignment Pin
BinaryReason22-Jun-17 3:06
BinaryReason22-Jun-17 3:06 
AnswerRe: Alignment Pin
Phil J Pearson23-Jun-17 7:13
Phil J Pearson23-Jun-17 7:13 
You think it's crazy. You are entitled to your opinion.
I have been using this kind of style (varied slightly through C and C++) for nearly 40 years. I can still read it and understand it. I am not a fashionista.

(Bear in mind that it has been mangled a bit to fit the CP line width.)
Phil



The opinions expressed in this post are not necessarily those of the author, especially if you find them impolite, inaccurate or inflammatory.

QuestionThank you for posting this code Pin
Tim12721-Jun-17 7:37
Tim12721-Jun-17 7:37 
AnswerRe: Thank you for posting this code Pin
Phil J Pearson23-Jun-17 7:10
Phil J Pearson23-Jun-17 7:10 
GeneralRe: Thank you for posting this code Pin
bradut15-Dec-20 22:42
professionalbradut15-Dec-20 22:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.