C++ Quick Start

The C++ API is the SDK’s native interface and the most complete one β€” every feature described in this documentation is available from C++. This guide takes you from the delivered package to a running program that prints emotion and expression metrics.

1 Β· Package contents

The SDK is delivered as one archive per platform. Each name carries the SDK type, the platform, the toolchain it was built with, and the version:

PlatformToolchainPackage
Linux x86_64GCC 13affectiva-emotion-sdk-linux-x86_64-gcc13-<version>.tar.gz
Windows x64Visual Studio 2022affectiva-emotion-sdk-win64-vc2022-<version>.zip

Link against the package built with the toolchain you compile with β€” the C++ ABI is not compatible across compilers. Both archives carry the same 17 headers, but their library layouts differ.

Linux:

affectiva-emotion-sdk-linux-x86_64-gcc13-<version>/
β”œβ”€β”€ include/                            # public headers (flat β€” one include directory)
β”‚   β”œβ”€β”€ Detector.h                      #   base detector: features, listeners, lifecycle
β”‚   β”œβ”€β”€ FrameDetector.h                 #   asynchronous detector
β”‚   β”œβ”€β”€ SyncFrameDetector.h             #   synchronous detector
β”‚   β”œβ”€β”€ Frame.h                         #   image + timestamp wrapper
β”‚   β”œβ”€β”€ Face.h                          #   results object + metric enums
β”‚   β”œβ”€β”€ FaceListener.h                  #   per-frame results callback interface
β”‚   β”œβ”€β”€ ImageListener.h                 #   deprecated results callback
β”‚   β”œβ”€β”€ ProcessStatusListener.h         #   error callback
β”‚   β”œβ”€β”€ Feature.h, Sensors.h, Operations.h, MetricsConnector.h, FaceConnector.h
β”‚   └── Core.h, Exception.h, BoundingBox.h, Events.h
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ libaffectiva-vision.so          # the SDK
β”‚   β”œβ”€β”€ libonnxruntime.so               # symlink β†’ libonnxruntime.so.1.23.0
β”‚   └── libonnxruntime_providers_shared.so
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ GETTING_STARTED.txt
β”‚   β”œβ”€β”€ LICENSE.txt
β”‚   └── vision/                         # generated API reference (HTML)
└── VERSION.txt                         # version, SDK type, and source commit

Windows β€” separate release and debug trees, with the DLLs under bin/ and the import libraries under lib/:

affectiva-emotion-sdk-win64-vc2022-<version>/
β”œβ”€β”€ include/                            # the same 17 headers
β”œβ”€β”€ bin/
β”‚   β”œβ”€β”€ release/
β”‚   β”‚   β”œβ”€β”€ affectiva-vision.dll        # the SDK
β”‚   β”‚   β”œβ”€β”€ onnxruntime.dll
β”‚   β”‚   └── p7-shared.dll
β”‚   └── debug/                          # the same three, debug build
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ release/
β”‚   β”‚   β”œβ”€β”€ affectiva-vision.lib        # import library β€” link against this
β”‚   β”‚   └── python3.13/                 # bundled affvisionpy module and wheel
β”‚   └── debug/
β”‚       └── affectiva-vision.lib
β”œβ”€β”€ docs/
└── VERSION.txt

Ship the runtime libraries too. libaffectiva-vision depends on ONNX Runtime, so deploying the SDK library alone is not enough β€” copy every file from lib/ (Linux) or bin/release/ (Windows), including the libonnxruntime/onnxruntime and p7-shared libraries. On Linux, keep the libonnxruntime.so symlink intact rather than copying only the versioned file.

Models are embedded. Since version 5.3.1, all neural-network models are compiled into libaffectiva-vision. There is no data folder in the package, and detector constructors take no data path. Migrating from 5.3.0 or earlier: delete the data directory from your deployment and drop the path argument from your detector construction.

Requirements

  • A C++17 compiler matching your package: GCC 13 (Ubuntu 24.04) or Visual Studio 2022 (Windows x64). Other toolchains and architectures are built for specific programs rather than shipped with the Emotion SDK β€” ask your Affectiva contact if you need one.
  • CMake 3.20 or newer (recommended; plain compiler invocations work too).
  • A license key (see Licensing).

2 Β· Project setup

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(my-affectiva-app CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Point this at the unpacked SDK
set(AFFECTIVA_SDK_DIR "/opt/affectiva-sdk")

add_executable(my-affectiva-app main.cpp)

target_include_directories(my-affectiva-app PRIVATE "${AFFECTIVA_SDK_DIR}/include")

# Linux: libraries sit directly in lib/. Windows: pick the release or debug import library.
if(WIN32)
  target_link_directories(my-affectiva-app PRIVATE "${AFFECTIVA_SDK_DIR}/lib/$<IF:$<CONFIG:Debug>,debug,release>")
else()
  target_link_directories(my-affectiva-app PRIVATE "${AFFECTIVA_SDK_DIR}/lib")
endif()

target_link_libraries(my-affectiva-app PRIVATE affectiva-vision)

On Linux, make sure the shared libraries are found at run time β€” either install them to a system library directory, set an rpath (set(CMAKE_BUILD_RPATH "${AFFECTIVA_SDK_DIR}/lib")), or export LD_LIBRARY_PATH. On Windows, copy the contents of bin/release/ β€” all three DLLs, not just affectiva-vision.dll β€” next to your executable or onto the PATH.

3 Β· First program

The smallest useful program: a SyncFrameDetector, a FaceListener, and one frame. The synchronous detector processes every frame and invokes your callbacks on the calling thread, which makes it the easiest way to start (and the right choice for offline video processing).

main.cpp

#include <SyncFrameDetector.h>
#include <FaceListener.h>
#include <Frame.h>

#include <iostream>
#include <vector>

using namespace affdex::vision;

// Receives results for every processed frame.
class MyListener : public FaceListener
{
public:
  void onFaceFound(affdex::Timestamp timestamp, FaceId faceId) override
  {
    std::cout << "face " << faceId << " found at " << timestamp << " msn";
  }

  void onFaceLost(affdex::Timestamp timestamp, FaceId faceId) override
  {
    std::cout << "face " << faceId << " lost at " << timestamp << " msn";
  }

  void onFaceResults(std::map<FaceId, Face> faces, const Frame& frame) override
  {
    for (const auto& entry : faces)
    {
      const Face& face = entry.second;
      auto expressions = face.getExpressions();  // std::map<Expression, float>
      auto emotions = face.getEmotions();        // std::map<Emotion, float>

      std::cout << frame.getTimestamp() << " ms  face " << face.getId()
                << "  smile=" << expressions[Expression::SMILE]
                << "  joy=" << emotions[Emotion::JOY]
                << "  pain=" << expressions[Expression::PAIN]
                << "n";
    }
  }
};

int main()
{
  // 1. Construct: track up to 1 face, auto-size the thread pool.
  SyncFrameDetector detector;

  // 2. Enable the metric families you need.
  detector.enable({Feature::EXPRESSIONS, Feature::EMOTIONS});

  // 3. Register your listener.
  MyListener listener;
  detector.setFaceListener(&listener);

  // 4. Start. Pass your key, or leave empty to use AFFECTIVA_LICENSE_KEY.
  detector.start("YOUR-LICENSE-KEY");

  // 5. Process frames. Here: one synthetic 640x480 BGR image.
  std::vector<unsigned char> pixels(640 * 480 * 3, 128);
  Frame frame(/*timestamp_ms=*/0, 640, 480, pixels.data(), Frame::ColorFormat::BGR);
  detector.process(frame);

  detector.stop();
  return 0;
}

Build and run:

cmake -B build && cmake --build build
AFFECTIVA_LICENSE_KEY="YOUR-LICENSE-KEY" ./build/my-affectiva-app

The example, explained

Includes and namespace

#include <SyncFrameDetector.h>
#include <FaceListener.h>
#include <Frame.h>

using namespace affdex::vision;

The SDK installs all public headers flat into one include/ directory. SyncFrameDetector.h brings in the detector (and, transitively, Detector.h with the Feature enum), FaceListener.h the callback interface plus the Face class and metric enums, and Frame.h the image wrapper. Everything lives in affdex::vision.

The listener β€” where output arrives

class MyListener : public FaceListener
{

The SDK never returns metrics from process(); all output is pushed to a listener you provide. FaceListener is an abstract interface with three pure-virtual methods, so your subclass must override all three β€” even the ones you don’t care about.

Face found / lost events

void onFaceFound(affdex::Timestamp timestamp, FaceId faceId) override
void onFaceLost(affdex::Timestamp timestamp, FaceId faceId) override

Called once when the tracker starts tracking a new face, and once when it loses it. Input: the timestamp (ms) of the frame where the event was observed and the face’s tracking id. Use these to bracket per-face sessions β€” for example to open and close a per-person log. Between a found/lost pair, that faceId keys the results in onFaceResults.

Per-frame results

void onFaceResults(std::map<FaceId, Face> faces, const Frame& frame) override
{
  for (const auto& entry : faces)
  {
    const Face& face = entry.second;
    auto expressions = face.getExpressions();  // std::map<Expression, float>
    auto emotions = face.getEmotions();        // std::map<Emotion, float>

Called once for every processed frame. Input: a map of all currently tracked faces (empty if none β€” you still get the callback) and the frame the results belong to, which carries the matching timestamp. Output you read: each Face exposes one getter per metric family; the maps contain one entry per metric of every family you enabled (values 0–100, see the Metrics guide). Indexing with expressions[Expression::SMILE] is safe here because the maps are copies returned by value.

Constructing the detector

SyncFrameDetector detector;

We choose the synchronous detector for a first program because its behavior is the easiest to reason about: every frame is processed, and callbacks run inside process() on your thread β€” no concurrency to manage. The default constructor arguments are maxNumFacesΒ =Β 1 (track one face; raise it to track more at extra CPU cost) and maxThreadsΒ =Β 0 (auto: use CPU cores βˆ’ 1 for analysis).

Enabling features

detector.enable({Feature::EXPRESSIONS, Feature::EMOTIONS});

Input: a std::set<Feature> of the metric families you want computed. Only enabled families cost CPU and appear in the Face maps β€” if you enabled neither, getExpressions()/getEmotions() would return empty maps, though tracking metrics (bounding box, measurements, confidence) are always delivered. Enable before start().

Registering the listener and starting

MyListener listener;
detector.setFaceListener(&listener);

detector.start("YOUR-LICENSE-KEY");

setFaceListener takes a raw pointer and does not take ownership β€” the listener object must stay alive as long as the detector may call it (here: stack lifetime outlasts stop(), which is fine). start() validates the license (input: your key, or "" to fall back to the AFFECTIVA_LICENSE_KEY environment variable), initializes the embedded models, and allocates the analysis thread pool. It throws affdex::Exception if the license is missing or invalid β€” the one call most worth wrapping in try/catch.

Building a Frame

std::vector<unsigned char> pixels(640 * 480 * 3, 128);
Frame frame(/*timestamp_ms=*/0, 640, 480, pixels.data(), Frame::ColorFormat::BGR);

Input: a raw pixel buffer plus its geometry. For BGR, the buffer must hold exactly width Γ— height Γ— 3 bytes in row-major order (this demo uses a flat gray image; real code passes camera/decoder output β€” an OpenCV Mat::data is already BGR). The timestamp (ms) identifies the frame; 0 is fine for the first one, and each subsequent frame must carry a strictly larger value. The constructor copies and converts the pixels, so the buffer only needs to remain valid for the duration of the constructor call. The omitted last parameter, rotation, defaults to UPRIGHT.

Processing and shutting down

detector.process(frame);

detector.stop();

process() runs detection and analysis for this frame and β€” because this is the synchronous detector β€” invokes your listener callbacks on this same thread before returning. Its only output is those callbacks. stop() releases the models and thread pool; the detector can be start()ed again later, and between videos you can call reset() instead to keep it running while allowing timestamps to restart at 0.

4 Β· Processing video

For real input, wrap each decoded frame in a Frame with the frame’s presentation timestamp in milliseconds. With OpenCV as the capture layer:

cv::VideoCapture cap(0);  // or a video file path
cv::Mat img;

while (cap.read(img))
{
  affdex::Timestamp ts =
      static_cast<affdex::Timestamp>(cap.get(cv::CAP_PROP_POS_MSEC));

  Frame frame(ts, img.cols, img.rows, img.data, Frame::ColorFormat::BGR);
  detector.process(frame);   // callbacks fire before process() returns (sync detector)
}

Timestamps must strictly increase. The detector uses timestamps to order frames and compute temporal metrics; submitting a timestamp less than or equal to the previous one throws an exception. When switching to a new video or camera session, call detector.reset() β€” timestamps may then restart at 0 without recreating the detector.

If your camera is mounted rotated, declare it instead of rotating pixels yourself: Frame(ts, w, h, data, Frame::ColorFormat::BGR, Frame::Rotation::CW_90).

5 Β· Asynchronous mode (FrameDetector)

For live camera feeds where you must not block the capture thread, use FrameDetector. It queues frames for a background thread and β€” when frames arrive faster than its target processing rate β€” drops the excess to stay real-time:

// Process at most 30 fps, track up to 2 faces, auto thread pool.
FrameDetector detector(/*processFrameRate=*/30, /*maxNumFaces=*/2);
detector.enable({Feature::EXPRESSIONS, Feature::EMOTIONS});
detector.setFaceListener(&listener);
detector.start("YOUR-LICENSE-KEY");

// process() returns immediately; results arrive on a background thread.
detector.process(frame);

Differences from the synchronous detector, in practice:

SyncFrameDetectorFrameDetector
Callback threadthe thread calling process()a background thread
Frame droppingnever β€” every frame is processeddrops frames above processFrameRate
process() blocks?yes, until callbacks completeno
Typical useoffline video analysis, testslive cameras, interactive apps

Thread safety: with FrameDetector, your listener methods are invoked from the SDK’s background thread. Synchronize any state they share with your UI or main thread, and keep callbacks fast β€” long work in a callback delays subsequent results.

6 Β· Error handling

All SDK errors surface as affdex::Exception (derived from std::runtime_error). Errors raised on a background processing thread cannot be thrown into your code, so the SDK delivers them through a ProcessStatusListener:

#include <ProcessStatusListener.h>

class MyStatusListener : public ProcessStatusListener
{
public:
  void onProcessingException(affdex::Exception& ex) override
  {
    std::cerr << "processing error: " << ex.what() << "n";
  }
};

// before start():
MyStatusListener statusListener;
detector.setProcessStatusListener(&statusListener);

// and wrap direct calls:
try
{
  detector.start("YOUR-LICENSE-KEY");
}
catch (const affdex::Exception& ex)
{
  std::cerr << "failed to start: " << ex.what() << "n";  // e.g. missing/invalid license
}

Next steps

  • Explore every class, method, and metric in the C++ API reference.
  • For a composable, per-metric event stream (for example, “give me only the smile value”), see Sensors & connectors β€” a C++-only pipeline API.
  • Baseline mode (rolling-average metric calculation) is described under Detector.