Pelco Developer Network (PDN)

Retrieving Recorded Video from an ONVIF Device

ONVIF Profile G defines the requirements for onboard video storage and retrieval. ONVIF complements the Pelco API in Sarix cameras by providing additional functionality in the form of web service calls. The C++ client application discussed in this article invokes Profile G functions to obtain information about recorded video clips that are stored on the camera, including a URI to initiate playback. Profile G is not a separate API, but rather a set of calls spread across several ONVIF web services, such as Device Management, Recording Search, and so on.

Although the concept of web services and SOAP-based communication is easy to grasp, the details can be difficult to manage. Many developers prefer to use classes that wrap SOAP calls instead of reading and writing raw XML strings. These classes hide the strings and the communication between client and server. There are several ways to create wrappers around the SOAP calls necessary for a client to communicate with a camera or other ONVIF device. The sample provided in this article uses C++ classes generated by the gSOAP toolkit, as discussed in Generating ONVIF Proxy Classes using gSOAP. In addition, you may want to read our previous articles Developing an ONVIF C++ Client Application and Supporting HTTP Digest Authentication in an ONVIF Client.

The complete application will be discussed in sections throughout the article. View the main.cpp file here. Projects for Visual Studio and Xcode are available on the Sample Code page.


Application Overview

Here is the sequence of calls made by the application:

  1. GetServices: find the endpoint address for each supported service. Each vendor may implement services at different locations. This application uses the Device Management, Recording Search, and Replay services.
  2. GetRecordingSummary: get an overview of all recorded video available on the device. This is not required but does provide a sanity check prior to conducting a detailed search.
  3. FindRecordings: initiate an asynchronous search for video on the camera. Return a token that can be used in subsequent calls to reference the search results.
  4. GetRecordingSearchResults: use the search token to list specific recordings.
  5. GetReplayUri: obtain a URI that can be used to initiate playback of a specific recording.

The first step is to generate class files to handle the details of communicating with the web services on the camera.


Using gSOAP to generate header and class files

As discussed in Generating ONVIF Proxy Classes using gSOAP, use the wsdl2h and soapcpp2 tools to create a set of C++ proxy classes for the ONVIF services. The difference in this article is that multiple ONVIF services are specified on the command-line, and fortunately all are automatically combined into one intermediate header file (onvif.h) that is fed to the soapcpp2 tool.

 The following gSOAP listings are truncated. View the full listings here.

 

$ wsdl2h -I gsoap -o onvif.h http://www.onvif.org/ver10/Replay.wsdl \
    http://www.onvif.org/ver10/Search.wsdl \
    http://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl

**  The gSOAP WSDL/Schema processor for C and C++, wsdl2h release 2.8.11
**  Copyright (C) 2000-2012 Robert van Engelen, Genivia Inc.
**  All Rights Reserved. This product is provided "as is", without any warranty.
**  The wsdl2h tool is released under one of the following two licenses:
**  GPL or the commercial license by Genivia Inc. Use option -l for details.

Saving onvif.h

Reading type definitions from type map file 'typemap.dat'

Connecting to 'http://www.onvif.org/ver10/Replay.wsdl' to retrieve WSDL/XSD...
Connected, receiving...

Connecting to 'http://www.onvif.org/ver10/../ver10/schema/onvif.xsd' to retrieve 
        relative '../ver10/schema/onvif.xsd' schema...
Connected, receiving...
Done reading '../ver10/schema/onvif.xsd'

Connecting to 'http://docs.oasis-open.org/wsn/b-2.xsd' to retrieve schema...
Connected, receiving...
Done reading 'http://docs.oasis-open.org/wsn/b-2.xsd'

Connecting to 'http://docs.oasis-open.org/wsrf/bf-2.xsd' to retrieve schema...
Connected, receiving...
Done reading 'http://docs.oasis-open.org/wsrf/bf-2.xsd'

Connecting to 'http://docs.oasis-open.org/wsn/t-1.xsd' to retrieve schema...
Connected, receiving...
Warning: unexpected element 'xsd:unique' at level 2 is skipped (safe to ignore)
Warning: unexpected element 'xsd:unique' at level 6 is skipped (safe to ignore)
Done reading 'http://docs.oasis-open.org/wsn/t-1.xsd'
Done reading 'http://www.onvif.org/ver10/Replay.wsdl'

Connecting to 'http://www.onvif.org/ver10/Search.wsdl' to retrieve WSDL/XSD...
Connected, receiving...

. . .

To complete the process, compile with:
> soapcpp2 onvif.h
or to generate C++ proxy and object classes:
> soapcpp2 -j onvif.h

$

 

The use of soapcpp2 is the same as in the gSOAP article. gSOAP honors the namespaces for each service: tis for Device Management, tee for Replay, and tse for Search.

$ soapcpp2 -Iimport -j onvif.h

**  The gSOAP code generator for C and C++, soapcpp2 release 2.8.11
**  Copyright (C) 2000-2012, Robert van Engelen, Genivia Inc.
**  All Rights Reserved. This product is provided "as is", without any warranty.
**  The soapcpp2 tool is released under one of the following two licenses:
**  GPL or the commercial license by Genivia Inc.

Saving soapStub.h annotated copy of the input declarations
Using tds service name: DeviceBinding
Using tds service style: document
Using tds service encoding: literal
Using tds service location: http://localhost:80
Using tds schema namespace: http://www.onvif.org/ver10/device/wsdl
Saving soapDeviceBindingProxy.h client proxy class
Saving soapDeviceBindingProxy.cpp client proxy class
. . .
Saving DeviceBinding.nsmap namespace mapping table
Using trp service name: ReplayBinding
Using trp service style: document
Using trp service encoding: literal
Using trp service location: http://localhost:80
Using trp schema namespace: http://www.onvif.org/ver10/replay/wsdl
Saving soapReplayBindingProxy.h client proxy class
Saving soapReplayBindingProxy.cpp client proxy class
Saving soapReplayBindingService.h service class
Saving soapReplayBindingService.cpp service class
Saving ReplayBinding.GetServiceCapabilities.req.xml sample SOAP/XML request
Saving ReplayBinding.GetServiceCapabilities.res.xml sample SOAP/XML response
Saving ReplayBinding.GetReplayUri.req.xml sample SOAP/XML request
Saving ReplayBinding.GetReplayUri.res.xml sample SOAP/XML response
Saving ReplayBinding.GetReplayConfiguration.req.xml sample SOAP/XML request
Saving ReplayBinding.GetReplayConfiguration.res.xml sample SOAP/XML response
Saving ReplayBinding.SetReplayConfiguration.req.xml sample SOAP/XML request
Saving ReplayBinding.SetReplayConfiguration.res.xml sample SOAP/XML response
Saving ReplayBinding.nsmap namespace mapping table
Using tse service name: SearchBinding
Using tse service style: document
Using tse service encoding: literal
Using tse service location: http://localhost:80
Using tse schema namespace: http://www.onvif.org/ver10/search/wsdl
Saving soapSearchBindingProxy.h client proxy class
Saving soapSearchBindingProxy.cpp client proxy class
. . .

Compilation successful 

$ 

 

Once the proxy classes are generated, they may be added to the project. See the section Source Code Files for details.

 

main()

As in our previous articles, the main() function handles all interaction with the camera. This application is slightly more complicated due to the use of multiple services. Since each service uses a unique endpoint, the first step is to use the device management service to list the endpoints. Determining Endpoints The application uses proxy classes to call through to the web services. Each different service requires a specific proxy. For the device management service, create a DeviceBindingProxy object and initialize it with a default soap structure.

DeviceBindingProxy proxy( soap );

 

The next step is to find the endpoint for each service. In this case we know where the device management service is located on the camera, and use that to create the endpoint URL.

std::string endpoint = "http://" + ipAddress + "/onvif/device_service";

 

Each call through the proxy uses a specific data type for the request and response. The default namespace scheme prepends an underscore ‘_’ to the ONVIF namespace, which is followed by a double underscore ‘__’, which is followed by the name of the call. Here, the variable names are simply the data type name without the leading underscore.

_tds__GetServices tds__GetServices;
_tds__GetServicesResponse tds__GetServicesResponse;

 

There are a couple of variations available for each method call. This application calls the variant that includes the endpoint, a NULL flag to force usage of the default service definition, and the request and response objects.

result = proxy.GetServices( endpoint.c_str(), NULL, &tds__GetServices, &tds__GetServicesResponse );

 

Note that the NULL flag is checked in the proxy class:

if (!soap_action)
    soap_action = "http://www.onvif.org/ver10/device/wsdl/GetServices";

 

The call to GetServices returns the following service information. A full listing can be viewed here. The service URI can be used dynamically to determine the endpoint.

Service: http://10.220.237.17/onvif/device_service
Namespace: http://www.onvif.org/ver10/device/wsdl
Version: 2.42
Capabilities: 

. . .

Service: http://10.220.237.17/onvif/replay_service
Namespace: http://www.onvif.org/ver10/replay/wsdl
Version: 2.21
Capabilities: 

Service: http://10.220.237.17/onvif/recording_search_service
Namespace: http://www.onvif.org/ver10/search/wsdl
Version: 2.42
Capabilities: 

 

Recording Summary

Similar to device management, create a proxy for the RecordingSearch service using the service endpoint returned by GetServices. Create request and response objects, then call GetRecordingSummary.

SearchBindingProxy searchProxy( &soapSearch );
    
std::string searchEndpoint = "http://" + host + "/onvif/recording_search_service";

_tse__GetRecordingSummary tse__GetRecordingSummary;
_tse__GetRecordingSummaryResponse tse__GetRecordingSummaryResponse;

result = searchProxy.GetRecordingSummary( searchEndpoint.c_str(), NULL, 
    &tse__GetRecordingSummary, &tse__GetRecordingSummaryResponse );

 

The summary shows that this camera contains one very long recording.
Number of recordings: 1
Date range from: Thu Sep 18 03:46:20 2014 to Sat Sep 20 23:31:24 2014

 

Search for Recordings

The Search service method FindRecordings starts an asynchronous search on the camera. FindRecordings returns a token that references the search results. Even though there is only one recording available, a search is the proper way to obtain a reference for that recording.

FindRecordings requires a placeholder for data structures inside the request, and values that drive the search. maxMatches specifies the number of recordings to find. The maxTime value (milliseconds) is used to prevent a timeout of the search results on the camera.

This sequence uses the same RecordingSearch proxy created for GetRecordingSummary.

_tse__FindRecordings tse__FindRecordings;
_tse__FindRecordingsResponse tse__FindRecordingsResponse;

int maxMatches = 2;
long long maxTime = 12000;
    
tt__SearchScope searchScope;
    
tse__FindRecordings.MaxMatches = &maxMatches;
tse__FindRecordings.KeepAliveTime = maxTime;
tse__FindRecordings.Scope = &searchScope;
    
result = searchProxy.FindRecordings( searchEndpoint.c_str(), NULL, 
    &tse__FindRecordings, &tse__FindRecordingsResponse );

The subsequent call to GetRecordingSearchResults uses the token returned by FindRecordings to return a list of recordings and corresponding tokens. Each token allows the application to reference that specific recording.

if ( result == SOAP_OK )
{
    std::string recordingToken;

    tt__FindRecordingResultList *summary = 
        tse__GetRecordingSearchResultsResponse.ResultList;

    std::vector v = summary->RecordingInformation;

    for ( unsigned int i = 0; i < v.size(); i++ )
    {
        tt__RecordingInformation *info = v.at( i );

        std::cout << "Recording token: " << info->RecordingToken << "\n";

        recordingToken = info->RecordingToken;

        std::cout << "\n";
    }

    ...
}

 

The token returned for the recording on this camera:

Recording token: 0

 

Video Playback

The Replay service handles playback. Initialize a ReplayBindingProxy using the standard sequence. Here we used a new soap structure.

    struct soap soapReplay;
            
    soap_init( &soapReplay );
            
    ReplayBindingProxy replayProxy( &soapReplay );
            
    std::string replayEndpoint = "http://" + host + "/onvif/replay_service";

 

The request includes additional parameters that setup the stream as Unicast or Multicast and one of several transport methods. The enumerations are declared in soapStub.h but included here for clarity. Note the use of the recordingToken value from the search results.

    _trp__GetReplayUri trp__GetReplayUri;
    _trp__GetReplayUriResponse trp__GetReplayUriResponse;

    tt__StreamSetup streamSetup;

    /*
        enum tt__StreamType {
            tt__StreamType__RTP_Unicast = 0,
            tt__StreamType__RTP_Multicast = 1};
    */
    streamSetup.Stream = tt__StreamType__RTP_Unicast;

    tt__Transport transport;

    /* 
        enum tt__TransportProtocol {
            tt__TransportProtocol__UDP = 0, tt__TransportProtocol__TCP = 1, 
            tt__TransportProtocol__RTSP = 2, tt__TransportProtocol__HTTP = 3};
    */
    transport.Protocol = tt__TransportProtocol__RTSP;

    streamSetup.Transport = &transport;

    trp__GetReplayUri.StreamSetup = &streamSetup;

    trp__GetReplayUri.RecordingToken = recordingToken;
          
    result = replayProxy.GetReplayUri( replayEndpoint.c_str(), NULL, 
        &trp__GetReplayUri, &trp__GetReplayUriResponse );

 

GetReplayUri returns the path to the recording.

Playback URI: rtsp://10.220.237.17/recording1 

 

VLC can play back the video using the URI returned from GetReplayUri. Figure 1 shows a frame of the retrieved video playing in VLC.

Video playback in VLC
Figure 1. Play the video in VLC.

Source Code Files

Figure 2 lists the source and library files included in this project. Most of the source files are gSOAP-generated files and not all are C++. The gSOAP documentation, including the README.txt and INSTALL.txt files included in the archive, is the best source of information regarding what needs to be included in a project.

Source file list.

Figure 2. Source file list.

The project also includes several libraries:

  • libgsoapssl++.a, the gSOAP C++ library with SSL support
  • libPocoFoundation.dylib, containing the core Poco classes
  • libPocoNet.dylib, the Poco networking classes.

The Poco classes implement HTTP Digest authentication and other security methods, and are used in sequences that require authentication. ONVIF Profile G specifies the use of HTTP Digest authentication.

Using the techniques demonstrated in this article you can retrieve recorded video from a camera using an ONVIF C++ client application. If you are not already familiar with gSOAP, read the article Generating ONVIF Proxy Classes using gSOAP. Proxy classes save a lot of development effort and allow you to focus on application functionality.

For More Information…

The links below provide additional information regarding the tools referenced in this article.