Embedded AI Series – Step-by-Step Cross Compilation of gSOAP and ONVIF Applications on Rockchip RV1126 Platform – Beginner’s Guide

1. Introduction

Since the AI box needs to have the capability to search for network cameras within the local area network and obtain the RTSP address of the network camera to pull streams for AI-related applications, it is necessary to integrate the ONVIF protocol and implement related functions.

The author also started from scratch, searching for information online and exploring implementations. Although there is a lot of information available online now, one still encounters various issues and pitfalls when trying to do it oneself. After successfully running the application, this article summarizes the previously convoluted operations.

If readers need a beginner’s operational guide, following the process below should allow for a smooth implementation of the commonly used ONVIF device search and RTSP address retrieval functions.

Once the application is running, if you want to delve deeper into ONVIF, you can modify and enhance your understanding.

2. Preparation

2.1 Background Knowledge Related to ONVIF

ONVIF involves concepts such as gSOAP, Web Services, WSDL, SOAP, HTTP, XML, etc. It is recommended to read articles by experts on CSDN to learn and understand these concepts. Link: https://blog.csdn.net/benkaoya/article/details/72424335

2.2 Hardware Environment Preparation

The RV1126 AI box must be on the same local area network as the ONVIF-supported IPC. You can download and run the ONVIF client tool, ONVIF Device Manager, to verify if the IPC can be found. PS: Download link for ONVIF Device Manager: https://sourceforge.net/projects/onvifdm/, click the green “Download” button on the page. As shown in the figure below, if the IPC can be found, the hardware environment is prepared correctly.

Embedded AI Series - Step-by-Step Cross Compilation of gSOAP and ONVIF Applications on Rockchip RV1126 Platform - Beginner's Guide

2.3 Development Environment Preparation

The author uses an x86 Linux server as the development environment. To avoid affecting the host machine’s environment configuration, a Docker container is used as the development environment, where tools are installed and environment variables are set without affecting the host machine. Here, the official Ubuntu 20.04 Docker image is used to run the Docker container, starting from scratch to install and configure various required software tools.

# Enter the x86 Linux host and create a working directory, e.g., /data1/work/
mkdir -p /data1/work/

# Pull the ubuntu:20.04 Docker image
docker pull ubuntu:20.04

# Run the Docker image
# Note: The host-mounted directory /data1/work/ can be modified according to the reader's actual situation, but do not change the mounted directory /work inside the container, as it will be used in later steps
docker run  -dit --name onvif_test --network=host -v /data1/work/:/work ubuntu:20.04 /bin/bash

# Enter the container
docker exec -it onvif_test /bin/bash

# Create the onvif/ directory and enter it
mkdir -p /work/onvif/
cd /work/onvif/

3. Compile the Host Version of gSOAP

Since the subsequent cross-compilation of gSOAP and the generation of ONVIF source files require the host version of the gSOAP tools wsdl2h and soapcpp2, it is necessary to compile the host version of gSOAP first.

3.1 Download Source Code

To make it easier to find solutions online when issues arise, the historical version 2.8.133 is downloaded here.

# gSOAP open-source version download URL (historical version):
https://sourceforge.net/projects/gsoap2/files/gSOAP/

# Create a directory gsoap_x86/ and place the gSOAP compressed package in the newly created directory ./onvif/gsoap_x86/
mkdir gsoap_x86
cd gsoap_x86

# Install unzip
apt update
apt install unzip

# Unzip gsoap_2.8.133.zip
unzip gsoap_2.8.133.zip

# Enter the directory gsoap-2.8
cd gsoap-2.8

3.2 Install Dependencies

apt-get update
apt-get install build-essential libssl-dev openssl bison flex zlib1g-dev cmake gcc g++ build-essential autoconf

3.3 Compile and Install

# Current directory /work/onvif/gsoap_x86/gsoap-2.8

# Configure compilation options
./configure --prefix=/work/onvif/gsoap_x86/gsoap-2.8/host-build --enable-ipv6 -enable-c-locale --with-openssl CFLAGS="-O2 -fPIC" CXXFLAGS="-O2 -fPIC"
    
# Compile
make -j$(nproc)

# Install to the system
make install

# Update dynamic library cache
ldconfig

3.4 Verify Installation

# Check if the tools are available
./host-build/bin/wsdl2h -v
./host-build/bin/soapcpp2 -v

4. Cross-Compile gSOAP

4.1 Install Cross-Compilation Toolchain

  • • Note: Different embedded hardware platforms use different cross-compilation toolchains. Here, the RV1126 board used by the author is taken as an example.
# Return to the working root directory 
cd /work/onvif/

# Create a directory crosscompilation/toolchain and enter it
mkdir -p crosscompilation/toolchain
cd crosscompilation/toolchain

# Copy the entire cross-compilation tool directory gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf to the current directory
# At this point, the absolute path of the cross toolchain is /work/onvif/crosscompilation/toolchain/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf
                          
# Verify
/work/onvif/crosscompilation/toolchain/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ -v

4.2 Prepare Dependencies – Manually Cross-Compile OpenSSL

4.2.1 Download OpenSSL Source Code

The openssl-1.1.1w version is the most compatible with gSOAP 2.8, so download openssl-1.1.1w.

# Create a directory OpenSSL under crosscompilation and enter it
cd /work/onvif/crosscompilation/
mkdir OpenSSL
cd OpenSSL

# Install wget
apt update
apt install wget

# Download openssl-1.1.1w source code
wget https://github.com/openssl/openssl/releases/download/OpenSSL_1_1_1w/openssl-1.1.1w.tar.gz

# Unzip and enter the directory
 tar xvzf openssl-1.1.1w.tar.gz
cd openssl-1.1.1w

4.2.2 Configure Cross-Compilation Toolchain

# Set environment variables
export TOOLCHAIN_PATH="/work/onvif/crosscompilation/toolchain/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf"
export CROSS_COMPILE="${TOOLCHAIN_PATH}/bin/arm-linux-gnueabihf-"
export CC=${CROSS_COMPILE}gcc
export CXX=${CROSS_COMPILE}g++
export AR=${CROSS_COMPILE}ar
export RANLIB=${CROSS_COMPILE}ranlib
export STRIP=${CROSS_COMPILE}strip

# Configure OpenSSL
./Configure linux-armv4 \
    --prefix=/opt/arm-openssl \
    --cross-compile-prefix= \
    -march=armv7-a -mfpu=neon -mfloat-abi=hard \
    no-asm shared \
    -DOPENSSL_NO_SSL3 \
    -DOPENSSL_NO_WEAK_SSL_CIPHERS

4.2.3 Compile and Install

make -j$(nproc)
make install
# Installed to the directory /opt/arm-openssl

4.2.4 Configure Environment Variables

# Use this path to compile gSOAP later
export OPENSSL_ROOT=/opt/arm-openssl
export CFLAGS="-I$OPENSSL_ROOT/include"
export LDFLAGS="-L$OPENSSL_ROOT/lib"
export PKG_CONFIG_PATH="$OPENSSL_ROOT/lib/pkgconfig:$PKG_CONFIG_PATH"

4.3 Prepare Dependencies – Manually Cross-Compile zlib

4.3.1 Download zlib Source Code

# Create a directory zlib under crosscompilation and enter it
cd /work/onvif/crosscompilation/
mkdir zlib
cd zlib

# Download zlib source code
wget https://zlib.net/fossils/zlib-1.2.13.tar.gz

# Unzip and enter the directory
tar xzf zlib-1.2.13.tar.gz
cd zlib-1.2.13

4.3.2 Configure Cross-Compilation Toolchain

# Set environment variables
export TOOLCHAIN_PATH="/work/onvif/crosscompilation/toolchain/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf"
export CC="${TOOLCHAIN_PATH}/bin/arm-linux-gnueabihf-gcc"
export AR="${TOOLCHAIN_PATH}/bin/arm-linux-gnueabihf-ar"
export RANLIB="${TOOLCHAIN_PATH}/bin/arm-linux-gnueabihf-ranlib"
export STRIP="${TOOLCHAIN_PATH}/bin/arm-linux-gnueabihf-strip"
export PREFIX="/opt/zlib-arm"

# Configure
./configure --prefix=$PREFIX

4.3.3 Compile and Install

make -j$(nproc)
make install
# Installed to the directory /opt/zlib-arm

4.3.4 Configure Environment Variables

export ZLIB_ROOT=/opt/zlib-arm
export CFLAGS="-I$OPENSSL_ROOT/include -I$ZLIB_ROOT/include"
export LDFLAGS="-L$OPENSSL_ROOT/lib -L$ZLIB_ROOT/lib"
export PKG_CONFIG_PATH="$OPENSSL_ROOT/lib/pkgconfig:$ZLIB_ROOT/lib/pkgconfig:$PKG_CONFIG_PATH"

4.5 Cross-Compile gSOAP

  • • Note: When cross-compiling gSOAP, it will default to trying to run the soapcpp2 tool compiled for the target platform on the host machine, which will cause failure. The simplest approach is to copy the host version of the gSOAP tool to overwrite the current gSOAP tool, allowing the compilation to complete. The ONVIF-related source code is generated by us using wsdl2h and soapcpp2, and the construction of the gSOAP library itself does not require these codes. Therefore, we focus more on cross-compiling the gSOAP library. The steps are summarized as follows:1. Compile the host version of the gSOAP tools (soapcpp2 and wsdl2h) and install them to the host-build directory.2. Use the host version of wsdl2h and soapcpp2 to generate ONVIF code (run on the host machine).3. Cross-compile the gSOAP library (excluding tools) and install it to the arm-build directory.4. Cross-compile our application, linking the gSOAP library from arm-build.

4.5.1 Download Source Code

Still using the gSOAP source code version 2.8.133 downloaded in section 3.1.

# Use wget to download, but it has issues, so download from the page: https://sourceforge.net/projects/gsoap2/files/gsoap_2.8.133.zip/download
# Return to the crosscompilation directory
cd /work/onvif/crosscompilation/

# Copy gsoap_2.8.133.zip to the current directory and unzip it
unzip gsoap_2.8.133.zip

# Enter the source directory
cd gsoap-2.8

4.5.2 Configure Cross-Compilation Environment

# Set environment variables
export TOOLCHAIN_PATH="/work/onvif/crosscompilation/toolchain/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf"
export PATH="${TOOLCHAIN_PATH}/bin:$PATH"
export CC="arm-linux-gnueabihf-gcc"
export CXX="arm-linux-gnueabihf-g++"
export AR="arm-linux-gnueabihf-ar"
export RANLIB="arm-linux-gnueabihf-ranlib"
export STRIP="arm-linux-gnueabihf-strip"
export OPENSSL_ROOT=/opt/arm-openssl
export CFLAGS="-I$OPENSSL_ROOT/include"
export LDFLAGS="-L$OPENSSL_ROOT/lib"           

# Configure
make distclean || true
./configure --host=arm-linux-gnueabihf --prefix=$(pwd)/arm-build --enable-openssl --enable-ipv6 --enable-c-locale --with-openssl=/opt/arm-openssl --with-zlib=/opt/zlib-arm CFLAGS="-O2 -fPIC" CXXFLAGS="-O2 -fPIC"

4.5.3 Compile and Install

# Compile
make -j$(nproc)

At this point, the compilation will report an error, as shown in the figure below, indicating that it attempts to run the executable compiled with the cross tool on the host environment, which is not allowed.

Embedded AI Series - Step-by-Step Cross Compilation of gSOAP and ONVIF Applications on Rockchip RV1126 Platform - Beginner's Guide

The simplest approach here is to copy the host version of the gSOAP tool to overwrite the current gSOAP tool, recompile, and let the compilation complete.

# Backup the target machine tool
cp gsoap/src/soapcpp2 gsoap/src/soapcpp2.bak

# Copy and replace with the tool that can run on the host machine
cp /work/onvif/gsoap_x86/gsoap-2.8/host-build/bin/soapcpp2 gsoap/src/

# Re-execute the compilation
make -j$(nproc)

# Restore the target machine tool
cp gsoap/src/soapcpp2.bak gsoap/src/soapcpp2

# Install to the directory ./arm-build
make install

5. Generate ONVIF Code and Compile Test Program

Return to the crosscompilation directory, create a directory onviftest, and enter it.

cd /work/onvif/crosscompilation/
mkdir onviftest
cd onviftest

5.1 Update System CA Certificates

  • • Note: If the certificates are not updated, subsequent HTTPS errors may occur.
# Fix SSL issues with wsd2h
# Update system CA certificates
apt-get update
apt-get install ca-certificates

# Set environment variables to use system certificates
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export SSL_CERT_DIR=/etc/ssl/certs

5.2 Use wsdl2h to Generate Header Files

  • • Note: At this point, in the host environment, the host version of wsdl2h must be used to generate header files.
/work/onvif/gsoap_x86/gsoap-2.8/host-build/bin/wsdl2h  -o onvif.h -s -d -x -Nname -t /work/onvif/gsoap_x86/gsoap-2.8/host-build/share/gsoap/WS/typemap.dat https://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl   http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl https://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl

5.3 Modify the Generated onvif.h

# Install vi tool
apt update
apt install vim

# To use the soap_wsse_add_UsernameTokenDigest function for authorization, add the following line at the beginning of the onvif.h header file before generating ONVIF code with soapcpp2:
#import "wsse.h"

The modified version is shown in the figure below:

Embedded AI Series - Step-by-Step Cross Compilation of gSOAP and ONVIF Applications on Rockchip RV1126 Platform - Beginner's Guide

5.4 Modify wsa.h Header File to Prevent Macro Redefinition

# Open wsa.h
vi /work/onvif/gsoap_x86/gsoap-2.8/host-build/share/gsoap/import/wsa.h

# Comment out the definition of the SOAP_ENV__Fault structure at the end of the file

The modified version is shown in the figure below:

Embedded AI Series - Step-by-Step Cross Compilation of gSOAP and ONVIF Applications on Rockchip RV1126 Platform - Beginner's Guide

5.5 Generate C++ Code

  • • Note: Here, C++ source code is generated, and there is no need to distinguish the running environment, so the host machine tool soapcpp2 can be used.
# Generate server and client code
# -C: indicates to generate only client code, not server code
# -x: indicates not to generate XML example files
# -L: indicates to disable default debug logs

/work/onvif/gsoap_x86/gsoap-2.8/host-build/bin/soapcpp2 -2 -C -L -x onvif.h -I/work/onvif/gsoap_x86/gsoap-2.8/host-build/share/gsoap/import/ -I/work/onvif/gsoap_x86/gsoap-2.8/host-build/include/ -I/work/onvif/gsoap_x86/gsoap-2.8/host-build/share/gsoap/

5.6 Copy Plugin Code to Avoid Compilation Issues

 cp ../gsoap-2.8/gsoap/plugin/wsseapi.* .
 cp ../gsoap-2.8/gsoap/plugin/wsaapi.* .
 cp ../gsoap-2.8/gsoap/plugin/threads.* .
 cp ../gsoap-2.8/gsoap/plugin/smdevp.* .
 cp ../gsoap-2.8/gsoap/plugin/mecevp.* .
 cp ../gsoap-2.8/gsoap/dom.cpp .
 cp ../gsoap-2.8/arm-build/share/gsoap/custom/struct_timeval.* .
 cp ../gsoap-2.8/gsoap/stdsoap2.cpp .
 cp ../gsoap-2.8/gsoap/stdsoap2.h .
 
 # Change all .c file extensions to .cpp
 for f in *.c; do mv "$f" "${f%.c}.cpp"; done

At this point, the files in the current directory are:

Embedded AI Series - Step-by-Step Cross Compilation of gSOAP and ONVIF Applications on Rockchip RV1126 Platform - Beginner's Guide

5.7 Create Test Program Source Code

Create a new file main.cpp with the following content:

  • • Note: The following code is sourced from CSDN and has been modified.
// Copyright statement: This article is an original article by CSDN blogger "Xu Zhenping", following the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this statement when reprinting.
// Original link: https://blog.csdn.net/benkaoya/article/details/72476787

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <vector>

#include <openssl/pem.h>
#include "soapH.h"
#include "soapStub.h"
#include "wsseapi.h"
#include "wsaapi.h"


using namespace std;

#define SOAP_ASSERT     assert
#define SOAP_DBGLOG     printf
#define SOAP_DBGERR     printf

#define SOAP_TO         "urn:schemas-xmlsoap-org:ws:2005:04:discovery"
#define SOAP_ACTION     "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"

#define SOAP_MCAST_ADDR "soap.udp://239.255.255.250:3702"                       // ONVIF specified multicast address

#define SOAP_ITEM       ""                                                      // Device range to search
#define SOAP_TYPES      "dn:NetworkVideoTransmitter"                            // Device type to search

/* Socket timeout (in seconds) */
#define SOAP_SOCK_TIMEOUT    (10)

#define SOAP_CHECK_ERROR(result, soap, str) \
    do { \
        if (SOAP_OK != (result) || SOAP_OK != (soap)->error) { \
            soap_perror((soap), (str)); \
            if (SOAP_OK == (result)) { \
                (result) = (soap)->error; \
            } \
            goto EXIT; \
        } \
} while (0)

SOAP_NMAC struct Namespace namespaces[] = {
  { "SOAP-ENV", "http://www.w3.org/2003/05/soap-envelope", "http://schemas.xmlsoap.org/soap/envelope/", NULL },
  { "SOAP-ENC", "http://www.w3.org/2003/05/soap-encoding", "http://schemas.xmlsoap.org/soap/encoding/", NULL },
  { "xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance", NULL },
  { "xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema", NULL },
  { "c14n", "http://www.w3.org/2001/10/xml-exc-c14n#", NULL, NULL },
  { "ds", "http://www.w3.org/2000/09/xmldsig#", NULL, NULL },
  { "saml1", "urn:oasis:names:tc:SAML:1.0:assertion", NULL, NULL },
  { "saml2", "urn:oasis:names:tc:SAML:2.0:assertion", NULL, NULL },
  { "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", NULL, NULL },
  { "xenc", "http://www.w3.org/2001/04/xmlenc#", NULL, NULL },
  { "wsc", "http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512", "http://schemas.xmlsoap.org/ws/2005/02/sc", NULL },
  { "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd", NULL },
  { "wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing", "http://www.w3.org/2005/08/addressing", NULL },
  { "wsdd", "http://schemas.xmlsoap.org/ws/2005/04/discovery", NULL, NULL },
  { "chan", "http://schemas.microsoft.com/ws/2005/02/duplex", NULL, NULL },
  { "wsa5", "http://www.w3.org/2005/08/addressing", "http://schemas.xmlsoap.org/ws/2004/08/addressing", NULL },
  { "xmime", "http://tempuri.org/xmime.xsd", NULL, NULL },
  { "xop", "http://www.w3.org/2004/08/xop/include", NULL, NULL },
  { "tt", "http://www.onvif.org/ver10/schema", NULL, NULL },
  { "wsrfbf", "http://docs.oasis-open.org/wsrf/bf-2", NULL, NULL },
  { "wsnt", "http://docs.oasis-open.org/wsn/b-2", NULL, NULL },
  { "wstop", "http://docs.oasis-open.org/wsn/t-1", NULL, NULL },
  { "tdn", "http://www.onvif.org/ver10/network/wsdl", NULL, NULL },
  { "tds", "http://www.onvif.org/ver10/device/wsdl", NULL, NULL },
  { "timg", "http://www.onvif.org/ver20/imaging/wsdl", NULL, NULL },
  { "tls", "http://www.onvif.org/ver10/display/wsdl", NULL, NULL },
  { "tptz", "http://www.onvif.org/ver20/ptz/wsdl", NULL, NULL },
  { "trt", "http://www.onvif.org/ver10/media/wsdl", NULL, NULL },
  { NULL, NULL, NULL, NULL } /* end of namespaces[] */
};

void soap_perror(struct soap *soap, const char *str)
{
    if (NULL == str) {
        SOAP_DBGERR("[soap] error: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
    } else {
        SOAP_DBGERR("[soap] %s error: %d, %s, %s\n", str, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
    }
    return;
}

void* ONVIF_soap_malloc(struct soap *soap, unsigned int n)
{
    void *p = NULL;

    if (n > 0) {
        p = soap_malloc(soap, n);
        SOAP_ASSERT(NULL != p);
        memset(p, 0x00 ,n);
    }
    return p;
}

struct soap *ONVIF_soap_new(int timeout)
{
    struct soap *soap = NULL;                                                   // soap environment variable

    SOAP_ASSERT(NULL != (soap = soap_new()));

    soap_set_namespaces(soap, namespaces);                                      // Set soap namespaces
    soap->recv_timeout    = timeout;                                            // Set timeout (exit if no data after specified time)
    soap->send_timeout    = timeout;
    soap->connect_timeout = timeout;

#if defined(__linux__) || defined(__linux)                                      // Reference: https://www.genivia.com/dev.html#client-c modifications:
    soap->socket_flags = MSG_NOSIGNAL;                                          // To prevent connection reset errors
#endif

    soap_register_plugin(soap, soap_wsa);
    soap_register_plugin(soap, soap_wsse);
    soap_set_mode(soap, SOAP_C_UTFSTRING);                                      // Set to UTF-8 encoding, otherwise overlaying Chinese OSD will be garbled

    return soap;
}

void ONVIF_soap_delete(struct soap *soap)
{
    soap_destroy(soap);                                                         // remove deserialized class instances (C++ only)
    soap_end(soap);                                                             // Clean up deserialized data (except class instances) and temporary data
    soap_done(soap);                                                            // Reset, close communications, and remove callbacks
    soap_free(soap);                                                            // Reset and deallocate the context created with soap_new or soap_copy
}

/************************************************************************
** Function: ONVIF_init_header
** Functionality: Initialize soap description message header
** Parameters:
        [in] soap - soap environment variable
** Returns: None
** Remarks:
    1). The memory allocated through ONVIF_soap_malloc in this function will be released in ONVIF_soap_delete
************************************************************************/
void ONVIF_init_header(struct soap *soap)
{
    struct SOAP_ENV__Header *header = NULL;

    SOAP_ASSERT(NULL != soap);

    header = (struct SOAP_ENV__Header *)ONVIF_soap_malloc(soap, sizeof(struct SOAP_ENV__Header));
    soap_default_SOAP_ENV__Header(soap, header);
    header->wsa__MessageID = (char*)soap_wsa_rand_uuid(soap);
    header->wsa__To        = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_TO) + 1);
    header->wsa__Action    = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_ACTION) + 1);
    strcpy(header->wsa__To, SOAP_TO);
    strcpy(header->wsa__Action, SOAP_ACTION);
    soap->header = header;

    return;
}

/************************************************************************
** Function: ONVIF_init_ProbeType
** Functionality: Initialize the range and type of devices to probe
** Parameters:
        [in]  soap  - soap environment variable
        [out] probe - Fill in the range and type of devices to probe
** Returns:
        0 indicates detected, non-0 indicates not detected
** Remarks:
    1). The memory allocated through ONVIF_soap_malloc in this function will be released in ONVIF_soap_delete
************************************************************************/
void ONVIF_init_ProbeType(struct soap *soap, struct wsdd__ProbeType *probe)
{
    struct wsdd__ScopesType *scope = NULL;                                      // Used to describe which type of Web service to find

    SOAP_ASSERT(NULL != soap);
    SOAP_ASSERT(NULL != probe);

    scope = (struct wsdd__ScopesType *)ONVIF_soap_malloc(soap, sizeof(struct wsdd__ScopesType));
    soap_default_wsdd__ScopesType(soap, scope);                                 // Set the range of devices to find
    scope->__item = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_ITEM) + 1);
    strcpy(scope->__item, SOAP_ITEM);

    memset(probe, 0x00, sizeof(struct wsdd__ProbeType));
    soap_default_wsdd__ProbeType(soap, probe);
    probe->Scopes = scope;
    probe->Types  = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_TYPES) + 1);     // Set the type of devices to find
    strcpy(probe->Types, SOAP_TYPES);

    return;
}

void ONVIF_DetectDevice(void (*cb)(char *DeviceXAddr))
{
    int i;
    int result = 0;
    unsigned int count = 0;                                                     // Number of devices found
    struct soap *soap = NULL;                                                   // soap environment variable
    struct wsdd__ProbeType      req;                                            // Used to send Probe message
    struct __wsdd__ProbeMatches rep;                                            // Used to receive Probe response
    struct wsdd__ProbeMatchType *probeMatch;

    std::cout << "ONVIF Detect Device Start..." << std::endl;
    SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));

    ONVIF_init_header(soap);                                                    // Set message header description
    ONVIF_init_ProbeType(soap, &req);                                           // Set the range and type of devices to find
    
    std::cout << "Broadcasting Probe Message..." << std::endl;
    result = soap_send___wsdd__Probe(soap, SOAP_MCAST_ADDR, NULL, &req);        // Broadcast Probe message to multicast address
    while (SOAP_OK == result)                                                   // Start receiving messages sent by devices in a loop
    {
        memset(&rep, 0x00, sizeof(rep));
        result = soap_recv___wsdd__ProbeMatches(soap, &rep);
        if (SOAP_OK == result) {
            if (soap->error) {
                soap_perror(soap, "ProbeMatches");
            } else {                                                            // Successfully received device response message
               // dump__wsdd__ProbeMatches(&rep);

                if (NULL != rep.wsdd__ProbeMatches) {
                    count += rep.wsdd__ProbeMatches->__sizeProbeMatch;
                    for(i = 0; i < rep.wsdd__ProbeMatches->__sizeProbeMatch; i++) {
                        probeMatch = rep.wsdd__ProbeMatches->ProbeMatch + i;
                        if (NULL != cb) {
                            cb(probeMatch->XAddrs);                             // Use device service address to execute function callback
                        }
                    }
                }
            }
        } else if (soap->error) {
            break;
        }
    }

    SOAP_DBGLOG("\ndetect end! It has detected %d devices!\n", count);

    if (NULL != soap) {
        ONVIF_soap_delete(soap);
    }

    return ;
}

#define USERNAME    "admin"
#define PASSWORD    "admin" 

/************************************************************************
** Function: ONVIF_SetAuthInfo
** Functionality: Set authentication information
** Parameters:
        [in] soap     - soap environment variable
        [in] username - username
        [in] password - password
** Returns:
        0 indicates success, non-0 indicates failure
** Remarks:
************************************************************************/
static int ONVIF_SetAuthInfo(struct soap *soap, const char *username, const char *password)
{
    int result = 0;

    SOAP_ASSERT(NULL != username);
    SOAP_ASSERT(NULL != password);

    //std::cout << "in ONVIF_SetAuthInfo, username:" << username << ",password:" << password << std::endl;;

    result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);
    SOAP_CHECK_ERROR(result, soap, "add_UsernameTokenDigest");

EXIT:

    return result;
}

/************************************************************************
** Function: ONVIF_GetDeviceInformation
** Functionality: Get basic device information
** Parameters:
        [in] DeviceXAddr - device service address
** Returns:
        0 indicates success, non-0 indicates failure
** Remarks:
************************************************************************/
int ONVIF_GetDeviceInformation(const char *DeviceXAddr)
{
    int result = 0;
    struct soap *soap = NULL;
    struct _tds__GetDeviceInformation           devinfo_req;
    struct _tds__GetDeviceInformationResponse   devinfo_resp;

    std::cout << "Get Device Information from " << DeviceXAddr << std::endl;

    SOAP_ASSERT(NULL != DeviceXAddr);
    SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));

    ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);

    std::cout << "to call GetDeviceInformation" << std::endl;
    try{
      result = soap_call___tds__GetDeviceInformation(soap, DeviceXAddr, NULL, &devinfo_req, devinfo_resp);
      SOAP_CHECK_ERROR(result, soap, "GetDeviceInformation");
    } catch(...){
        SOAP_DBGERR("Exception occurred in GetDeviceInformation\n");
        goto EXIT;
    }

    std::cout << "----------------------------------------" << std::endl;
    std::cout << "Manufacturer: " << ((!devinfo_resp.Manufacturer) ? "null" : devinfo_resp.Manufacturer) << std::endl;
    std::cout << "Model:        " << ((!devinfo_resp.Model) ? "null" : devinfo_resp.Model) << std::endl;
    std::cout << "FirmwareVer:  " << ((!devinfo_resp.FirmwareVersion) ? "null" : devinfo_resp.FirmwareVersion) << std::endl;
    std::cout << "SerialNumber: " << ((!devinfo_resp.SerialNumber) ? "null" : devinfo_resp.SerialNumber) << std::endl;  
    std::cout << "HardwareId:   " << ((!devinfo_resp.HardwareId) ? "null" : devinfo_resp.HardwareId) << std::endl;
    std::cout << "----------------------------------------" << std::endl;

EXIT:

    if (NULL != soap) {
        ONVIF_soap_delete(soap);
    }
    return result;
}

/************************************************************************
** Function: ONVIF_GetCapabilities
** Functionality: Get device capability information
** Parameters:
        [in] DeviceXAddr - device service address
** Returns:
        0 indicates success, non-0 indicates failure
** Remarks:
    1). One of the main parameters is the media service address
************************************************************************/
int ONVIF_GetCapabilities(const char *DeviceXAddr, std::string &outXAddr)
{
    int result = 0;
    struct soap *soap = NULL;
    struct _tds__GetCapabilities            req;
    struct _tds__GetCapabilitiesResponse    rep;

    SOAP_ASSERT(NULL != DeviceXAddr);
    SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));

    ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);

    result = soap_call___tds__GetCapabilities(soap, DeviceXAddr, NULL, &req, rep);
    SOAP_CHECK_ERROR(result, soap, "GetCapabilities");

    if (rep.Capabilities->Media)
    {
        outXAddr = rep.Capabilities->Media->XAddr;
    }
    std::cout << "media xaddr:" << outXAddr << std::endl; 

EXIT:

    if (NULL != soap) {
        ONVIF_soap_delete(soap);
    }
    return result;
}

struct OnvifVideoEncoderConfiguration{
    std::string token;
    std::string name;
    int         encoding;
    int         width;
    int         height;
    int         frame_rate;
};

struct OnvifMediaInfo{
    std::string token;
    std::string name;
    OnvifVideoEncoderConfiguration video_encode_config;
};

std::ostream& operator<<(std::ostream& os, const OnvifVideoEncoderConfiguration& config) {
    os << "VideoEncoderConfiguration{"
       << "token='" << config.token << "', "
       << "name='" << config.name << "', "
       << "encoding=" << config.encoding << ", "
       << "width=" << config.width << ", "
       << "height=" << config.height << ", "
       << "frame_rate=" << config.frame_rate
       << "}";
    return os;
}

std::ostream& operator<<(std::ostream& os, const OnvifMediaInfo& media_info) {
    os << "OnvifMediaInfo{"
       << "token='" << media_info.token << "', "
       << "name='" << media_info.name << "', "
       << "video_encode_config=" << media_info.video_encode_config
       << "}";
    return os;
}

int ONVIF_GetProfiles(const std::string &DeviceXAddr, std::vector<OnvifMediaInfo> &infos)
{
    int result = 0;
    struct soap *soap = NULL;
    struct _trt__GetProfiles         req;
    struct _trt__GetProfilesResponse rep;

    SOAP_ASSERT(!DeviceXAddr.empty());
    SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));

    ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);

    result = soap_call___trt__GetProfiles(soap, DeviceXAddr.c_str(), NULL, &req, rep);
    SOAP_CHECK_ERROR(result, soap, "ONVIF_GetProfiles");

    SOAP_ASSERT(rep.__sizeProfiles > 0);

    for (int i = 0; i < rep.__sizeProfiles; i++) {
        struct tt__Profile* profiles = rep.Profiles[i];
        OnvifMediaInfo media_info;
        if (profiles->Name)
            media_info.name = profiles->Name;
        if (profiles->token)
            media_info.token = profiles->token;

        if (profiles->VideoEncoderConfiguration) {
            media_info.video_encode_config.name = profiles->VideoEncoderConfiguration->Name;
            media_info.video_encode_config.token = profiles->VideoEncoderConfiguration->token;
            media_info.video_encode_config.encoding = profiles->VideoEncoderConfiguration->Encoding;
            media_info.video_encode_config.width = profiles->VideoEncoderConfiguration->Resolution->Width;
            media_info.video_encode_config.height = profiles->VideoEncoderConfiguration->Resolution->Height;
        }

        infos.push_back(media_info);
        std::cout << "media info (" << i << ") :" << media_info << std::endl;
    }

EXIT:

    if (NULL != soap) {
        ONVIF_soap_delete(soap);
    }
    return result;
}

int ONVIF_GetStreamUri(const std::string &MediaXAddr, const std::string &ProfileToken, std::string &outUri)
{
    int result = 0;
    struct soap *soap = NULL;
    struct tt__StreamSetup            ttStreamSetup;
    struct tt__Transport              ttTransport;
    struct _trt__GetStreamUri         req;
    struct _trt__GetStreamUriResponse rep;

    SOAP_ASSERT(!MediaXAddr.empty());
    SOAP_ASSERT(NULL!= (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));

    ttStreamSetup.Stream               = tt__StreamType__RTP_Unicast;
    ttStreamSetup.Transport            = &ttTransport;
    ttStreamSetup.Transport->Protocol  = tt__TransportProtocol__RTSP;
    ttStreamSetup.Transport->Tunnel    = NULL;
    req.StreamSetup                    = &ttStreamSetup;
    req.ProfileToken                   = const_cast<char *>(ProfileToken.c_str());

    ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
    result = soap_call___trt__GetStreamUri(soap, MediaXAddr.c_str(), NULL, &req, rep);
    SOAP_CHECK_ERROR(result, soap, "ONVIF_GetStreamUri");

    result = -1;
    if ((NULL != rep.MediaUri) && (NULL != rep.MediaUri->Uri)) {
        outUri = std::string(rep.MediaUri->Uri);
        result = 0;
        std::cout << "profile:" << ProfileToken << ", rtsp uri:" << outUri << std::endl;
    }

EXIT:

    if (NULL!= soap) {
        ONVIF_soap_delete(soap);
    }

    return result;
}

void cb_discovery(char *DeviceXAddr)
{
    std::string mediaXAddr;

    ONVIF_GetDeviceInformation(DeviceXAddr);
    ONVIF_GetCapabilities(DeviceXAddr, mediaXAddr);

    if (!mediaXAddr.empty())
    {
        std::vector<OnvifMediaInfo> infos;
        ONVIF_GetProfiles(mediaXAddr, infos);

        for (auto &info : infos)
        {
            std::string uri;
            ONVIF_GetStreamUri(mediaXAddr, info.token, uri);
        }
    }
}

int main(int argc, char **argv)
{
    /* Known device */
    cb_discovery("http://192.168.8.64:80/onvif/device_service");

    /* Search */
    ONVIF_DetectDevice(cb_discovery);

    return 0;
}

5.8 Create CMakeLists.txt

  • • Note: Use the toolchain for the target machine, i.e., the embedded environment.
# CMake minimum version requirement
cmake_minimum_required(VERSION 3.10)

# Set cross-compilation toolchain
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# Cross-compilation toolchain path
set(TOOLCHAIN_DIR "/work/onvif/crosscompilation/toolchain/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf")
set(CMAKE_SYSROOT "${TOOLCHAIN_DIR}/arm-linux-gnueabihf/libc")

# Specify the cross compiler
set(CMAKE_C_COMPILER "${TOOLCHAIN_DIR}/bin/arm-linux-gnueabihf-gcc")
set(CMAKE_CXX_COMPILER "${TOOLCHAIN_DIR}/bin/arm-linux-gnueabihf-g++")

# Compiler flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mfpu=neon -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv7-a -mfpu=neon -mfloat-abi=hard")

# Search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# For libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

# Project name and version
project(ONVIF_Discovery VERSION 1.0.0 LANGUAGES CXX)

# Set C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Updated dependency paths for ARM
set(GSOAP_ROOT_DIR "/work/onvif/crosscompilation/gsoap-2.8/arm-build/")
set(GSOAP_INCLUDE_DIR "${GSOAP_ROOT_DIR}/include")
set(GSOAP_LIBRARY_DIR "${GSOAP_ROOT_DIR}/lib")

set(OPENSSL_ROOT_DIR "/opt/arm-openssl")
set(ZLIB_ROOT "/opt/zlib-arm")

set(GSOAP_LIBRARIES ${GSOAP_LIBRARY_DIR}/libgsoap.a ${GSOAP_LIBRARY_DIR}/libgsoap++.a)


# Include directories
include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${GSOAP_INCLUDE_DIR}
    ${GSOAP_ROOT_DIR}/share/gsoap/plugin/
    ${OPENSSL_ROOT_DIR}/include
    ${ZLIB_ROOT}/include
    ${TOOLCHAIN_DIR}/arm-linux-gnueabihf/include/c++/8.3.0
    ${TOOLCHAIN_DIR}/arm-linux-gnueabihf/include
)

# Link directories
link_directories(
    ${GSOAP_LIBRARY_DIR}
    ${OPENSSL_ROOT_DIR}/lib
    ${ZLIB_ROOT}/lib
    ${TOOLCHAIN_DIR}/arm-linux-gnueabihf/lib
    ${TOOLCHAIN_DIR}/arm-linux-gnueabihf/libc/lib
    ${TOOLCHAIN_DIR}/arm-linux-gnueabihf/libc/usr/lib
)

# Source files
file(GLOB SOURCES "*.cpp")

# Optional sources (if needed)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/dom.cpp")
    list(APPEND SOURCES dom.cpp)
endif()

if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mecevp.cpp")
    list(APPEND SOURCES mecevp.cpp)
endif()

if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/smdevp.cpp")
    list(APPEND SOURCES smdevp.cpp)
endif()

if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/threads.cpp")
    list(APPEND SOURCES threads.cpp)
endif()

if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/struct_timeval.cpp")
    list(APPEND SOURCES struct_timeval.cpp)
endif()

# Add executable
add_executable(${PROJECT_NAME} ${SOURCES})

# Find OpenSSL with custom path
set(OPENSSL_ROOT_DIR "/opt/arm-openssl")
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/include")
set(OPENSSL_CRYPTO_LIBRARY "${OPENSSL_ROOT_DIR}/lib/libcrypto.so")
set(OPENSSL_SSL_LIBRARY "${OPENSSL_ROOT_DIR}/lib/libssl.so")

find_package(OpenSSL REQUIRED)

# Find Threads
find_package(Threads REQUIRED)

# Find ZLIB with custom path
set(ZLIB_ROOT "/opt/zlib-arm")
#find_package(ZLIB REQUIRED)

# Set compiler flags
target_compile_definitions(${PROJECT_NAME} PRIVATE
    WITH_OPENSSL      # Enable OpenSSL support
    WITH_DOM          # Enable DOM support   
    WITH_NONAMESPACES 
)

target_compile_options(${PROJECT_NAME} PRIVATE
    -Wall
    -g     # Include debug information
    -O0    # Disable optimization for debugging
    -fPIC
    -march=armv7-a 
    -mfpu=neon 
    -mfloat-abi=hard
)

# If static linking, these link options may be needed
if(GSOAP_STATIC_LIB AND GSOAPSSL_STATIC_LIB)
    target_link_options(${PROJECT_NAME} PRIVATE
        -static-libstdc++
        -static-libgcc
    )
endif()

# Set linker flags
target_link_libraries(${PROJECT_NAME} PRIVATE
    ${CMAKE_THREAD_LIBS_INIT}
    ${GSOAP_LIBRARIES}  # Use found static or dynamic libraries
    ssl
    crypto
    z
    # Additional cross-compilation libraries
    stdc++
    m
    dl
)

# Link gSOAP libraries (ensure paths are correct)
target_link_directories(${PROJECT_NAME} PRIVATE 
    ${GSOAP_LIBRARY_DIR}
    ${OPENSSL_ROOT_DIR}/lib
    ${ZLIB_ROOT}/lib
    ${TOOLCHAIN_DIR}/arm-linux-gnueabihf/lib
    ${TOOLCHAIN_DIR}/arm-linux-gnueabihf/libc/lib
    ${TOOLCHAIN_DIR}/arm-linux-gnueabihf/libc/usr/lib
)

# Remove duplicate target_link_libraries calls
# The above already includes gSOAP library through ${GSOAP_LIBRARIES}

# Add definitions if needed
target_compile_definitions(${PROJECT_NAME} PRIVATE
    # Add any necessary definitions here
)

# Install target (optional)
install(TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION bin
)

# Print configuration summary
message(STATUS "Project: ${PROJECT_NAME}")
message(STATUS "Cross-compiling for: ${CMAKE_SYSTEM_PROCESSOR}")
message(STATUS "Toolchain: ${TOOLCHAIN_DIR}")
message(STATUS "C++ Compiler: ${CMAKE_CXX_COMPILER}")
message(STATUS "Source files: ${SOURCES}")
message(STATUS "GSOAP root dir: ${GSOAP_ROOT_DIR}")
message(STATUS "GSOAP include dir: ${GSOAP_INCLUDE_DIR}")
message(STATUS "GSOAP library dir: ${GSOAP_LIBRARY_DIR}")
message(STATUS "GSOAP libraries: ${GSOAP_LIBRARIES}")
message(STATUS "OpenSSL root dir: ${OPENSSL_ROOT_DIR}")
message(STATUS "ZLIB root dir: ${ZLIB_ROOT}")
message(STATUS "OpenSSL found: ${OPENSSL_FOUND}")

5.9 Compile

mkdir build
cd build
cmake ..
make -j$(nproc)

// Produce executable file ONVIF_Discovery

6. Deployment and Running

Copy the compiled executable file to the target device RV1126 and run it:

chmod +x ONVIF_Discovery
./ONVIF_Discovery
  • • Note: If you encounter authentication failure errors, as shown in the figure below, check if the username and password in the source code are correct:Embedded AI Series - Step-by-Step Cross Compilation of gSOAP and ONVIF Applications on Rockchip RV1126 Platform - Beginner's GuideModify the username or password in main.cpp to the correct value:Embedded AI Series - Step-by-Step Cross Compilation of gSOAP and ONVIF Applications on Rockchip RV1126 Platform - Beginner's GuideAfter recompiling and deploying, it should successfully retrieve the IPC’s RTSP address:Embedded AI Series - Step-by-Step Cross Compilation of gSOAP and ONVIF Applications on Rockchip RV1126 Platform - Beginner's Guide

Leave a Comment