Security Camera ONVIF Capabilities

The ONVIF specification standardises the network interface (the network layer) of network video products. It defines a communication framework based on relevant IETF and Web Services standards including security and IP configuration requirements.

After discovering a device the next step was to query it to determine its capabilities. I had some issues with .Net Core 5 application configuring the Windows Communication Foundation(WCF) to use Digest authentication (RFC2617) credentials on all bar the device management service client.

This .Net Core 5 console application queries the device management service (ONVID application programmers guide) to get the capabilities of the device then calls the media, imaging and pan tilt zoom services and displays the results.

I generated the client services using the Microsoft WCF Web Service Reference Provider.

Connected Services management dialog

The Uniform Resource Locators(URL) and namespace prefixes for each generated service are configured in the ConnectedService.json file.

First step configuring a WCF Service

Initially I used a devMobile.IoT.SecurityCameraClient prefix but after some experimentation changed to OnvifServices.

Second step configuring a WCF Service

For testing I selected “Generated Synchronous Operations” as they are easier to use in a console application while exploring the available functionality.

Third step configuring a WCF Service

The WSDL generated a number of warnings so I inspected the WSDL to see if the were easy to fix. I did consider copying the WSDL to my development box but it didn’t appear to be worth the effort.

SVCUtil warning messages about invalid Onvif WSDL

For this application I’m using the CommandLineParser NuGet package to parse and validate the client, username and password configured in the debugger tab.

Required Nuget packages
private static async Task ApplicationCore(CommandLineOptions options)
{
   Device deviceClient;
   ImagingPortClient imagingPortClient;
   MediaClient mediaClient;
   PTZClient panTiltZoomClient;

   var messageElement = new TextMessageEncodingBindingElement()
   {
      MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None),
      WriteEncoding = Encoding.UTF8
    };

    HttpTransportBindingElement httpTransportNoPassword = new HttpTransportBindingElement();
    CustomBinding bindingHttpNoPassword = new CustomBinding(messageElement, httpTransportNoPassword);
         
    HttpTransportBindingElement httpTransport = new HttpTransportBindingElement()
    {
       AuthenticationScheme = AuthenticationSchemes.Digest
    };
    CustomBinding bindingHttpPassword = new CustomBinding(messageElement, httpTransport);

    try
    {
       // Setup the imaging porting binding, use TLS, and ignore certificate errors
       deviceClient = new DeviceClient(bindingHttpNoPassword, new EndpointAddress($"http://{options.CameraUrl}/onvif/devicemgmt"));

       GetCapabilitiesResponse capabilitiesResponse = await deviceClient.GetCapabilitiesAsync(new GetCapabilitiesRequest(new CapabilityCategory[] { CapabilityCategory.All }));

       Console.WriteLine("Device capabilities");
       Console.WriteLine($"  Device: {capabilitiesResponse.Capabilities.Device.XAddr}");
       Console.WriteLine($"  Events: {capabilitiesResponse.Capabilities.Events.XAddr}"); // Not interested in events for V1
       Console.WriteLine($"  Imaging: {capabilitiesResponse.Capabilities.Imaging.XAddr}");
       Console.WriteLine($"  Media: {capabilitiesResponse.Capabilities.Media.XAddr}");
       Console.WriteLine($"  Pan Tilt Zoom: {capabilitiesResponse.Capabilities.PTZ.XAddr}");
       Console.WriteLine();
       ...
       Console.WriteLine($"Video Source Configuration");
       foreach (OnvifServices.Media.VideoSourceConfiguration videoSourceConfiguration in videoSourceConfigurations.Configurations)
      {
         Console.WriteLine($" Name: {videoSourceConfiguration.Name}");
         Console.WriteLine($" Token: {videoSourceConfiguration.token}");
         Console.WriteLine($" UseCount: {videoSourceConfiguration.UseCount}");
         Console.WriteLine($" Bounds: {videoSourceConfiguration.Bounds.x}:{videoSourceConfiguration.Bounds.y} {videoSourceConfiguration.Bounds.width}:{videoSourceConfiguration.Bounds.height}");
         Console.WriteLine($" View mode: {videoSourceConfiguration.ViewMode}");
      }
   }
   catch (Exception ex)
   {
      Console.WriteLine(ex.Message);
   }

   Console.WriteLine();
   Console.WriteLine("Press <enter> to exit");
   Console.ReadLine();
}

I had to do a bit of “null checking” as often if a feature wasn’t supported the root node was null. I need to get a selection of cameras (especially one with pan/tilt/zoom) to check that I’m processing the responses from the device correctly.

Console application output showing capabilities of Uniview device

After confirming the program was working on my development box I used the excellent RaspberryDebugger to download the application and run it on a Raspberry PI 3 running the Raspberry PI OS.