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:
| Platform | Toolchain | Package |
|---|---|---|
| Linux x86_64 | GCC 13 | affectiva-emotion-sdk-linux-x86_64-gcc13-<version>.tar.gz |
| Windows x64 | Visual Studio 2022 | affectiva-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:
SyncFrameDetector | FrameDetector | |
|---|---|---|
| Callback thread | the thread calling process() | a background thread |
| Frame dropping | never β every frame is processed | drops frames above processFrameRate |
process() blocks? | yes, until callbacks complete | no |
| Typical use | offline video analysis, tests | live 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.