Skip to Content
This documentation is provided with the HEAT environment and describes this HEAT instance.

Interface control document

1. Identification

ItemValue
DocumentICD-CMTOBII-001 rev 1
ProductTobii module (Tobii Module in the module registry)
Product versions coveredaa2ba9b
PeerA screen-based Tobii eye tracker, reached through the Tobii Stream Engine native library tobii_stream_engine.dll
HostHEAT Capture, HEAT.Capture.ModuleSDK 2.0.3
Settings keyTobiiSDK, configuration section HEAT.TobiiModule
Declared capabilityprocess.unrestricted
Runtimenet10.0, Windows only (Media Foundation and the vendored Stream Engine DLL)
Data modelTwo distinct SimulationFrame shapes: a gaze frame and a video frame, both on entity Tobii_Data
Date2026-09-02
OwnerVRAI Simulation

2. Scope

This document defines the Tobii Stream Engine surface the module actually calls, the callback data it consumes, the two frame shapes it publishes to HEAT Capture, and the H.264 camera stream contract.

Audit finding TOBII-02 recorded that the module’s data page described a video and stream contract without specifying it as an interface. Section 7 is that specification. It is constrained to what source in this repository shows, and section 7.6 states explicitly which stream parameters this repository does not define. Where a parameter is decided by the vendor runtime or by the Media Foundation encoder selected at run time, that is said rather than filled in with a plausible value.

The module reads measurement data and writes device configuration. It does not expose any remote or network interface of its own; every peer call is an in-process P/Invoke.

3. Interface inventory

IDNameDirectionTransportPeer
IF-CMTOBII-01Discovery, licensing and connectionModule to engineP/Invoke into tobii_stream_engineTobii Stream Engine
IF-CMTOBII-02Gaze, eye-openness and head-pose subscriptionsEngine to moduleStream Engine callbacks, pumped on a module threadTobii device
IF-CMTOBII-03Primary camera image subscriptionEngine to moduleStream Engine callback, same pumpTobii device
IF-CMTOBII-04Device configurationModule to engineP/Invoke into tobii_stream_engineTobii device
IF-CMTOBII-05Gaze frame publicationModule to hostModule SDK interval publicationHEAT Capture
IF-CMTOBII-06Video frame publicationModule to hostModule SDK Publish, out of bandHEAT Capture

3.1 Native entry points used

Every entry point below is declared with CallingConvention.Cdecl against library name tobii_stream_engine. The DLL is vendored in this repository and copied to the package output root, from where the module loader resolves it.

Entry pointInterfacePurpose in this module
tobii_api_createIF-CMTOBII-01Creates the API context. Passed a null custom-log callback, so Stream Engine internal logging is not routed into the HEAT logger.
tobii_find_all_eyetrackersIF-CMTOBII-01Enumerates trackers into a managed array of tobii_eyetracker_t, each carrying a url string.
tobii_connect_eyetrackerIF-CMTOBII-01Connects with a licence key, yielding the device context.
tobii_disconnectIF-CMTOBII-01Disconnects at capture end.
tobii_api_destroyIF-CMTOBII-01Destroys the API context at capture end.
tobii_error_messageallRenders an error code for the log.
tobii_wait_for_callbacksIF-CMTOBII-02, 03Blocks the pump thread until there is something to process.
tobii_device_process_callbacksIF-CMTOBII-02, 03Invokes the registered callbacks on the pump thread.
tobii_device_reconnectIF-CMTOBII-02, 03Called after a connection failure from the pump.
tobii_gaze_data_subscribe / _unsubscribeIF-CMTOBII-02Per-eye gaze data.
tobii_absolute_eye_openness_subscribe / _unsubscribeIF-CMTOBII-02Eyelid openness.
tobii_head_pose_subscribe / _unsubscribeIF-CMTOBII-02Head position and rotation.
tobii_gaze_point_subscribe / _unsubscribeIF-CMTOBII-02Combined, lightly filtered screen-space gaze point.
tobii_primary_camera_image_subscribe / _unsubscribeIF-CMTOBII-03Raw camera frames.
tobii_get_geometry_mountingIF-CMTOBII-04Reads the tracker’s physical mounting geometry.
tobii_calculate_display_area_basicIF-CMTOBII-04Derives a display area from settings plus that geometry.
tobii_set_display_areaIF-CMTOBII-04Applies the derived display area.
tobii_set_output_frequencyIF-CMTOBII-04Requests 33 Hz.
tobii_calibration_applyIF-CMTOBII-04Applies the bundled calibration blob.

The bindings in this repository also declare presence, user presence, user position guide, gaze origin, normalised eye position, track box, digital syncport, illumination mode, face type, diagnostics image, power save and remote wake entry points. None of them is called by the module in this revision. They are dormant binding surface, not part of this interface.

The module never calls tobii_get_api_version, so it neither reads nor asserts the version of the Stream Engine library it loads.

4. IF-CMTOBII-01 Discovery, licensing and connection

4.1 Addressing and device selection

ItemValue
AddressingThe Stream Engine url string returned by enumeration. It is not a setting and is not filtered.
Selection ruleThe module connects to eyetrackers[0], the first entry of the enumerated array. There is no setting to choose among several trackers, and the array order is whatever the Stream Engine returns.
No device foundNo eye trackers found is logged as an error and InitialiseModule returns false, so the module does not start.

The connected URL is logged twice during startup, as Connecting to eye tracker: {Url} and then Device context created for {Url}.

4.2 Authentication and authorisation

There is no authentication of HEAT Capture to the tracker and none of the tracker to HEAT Capture. Any local Tobii device that enumeration returns is accepted.

What the LicenceFile setting provides is authorisation for privileged Stream Engine feature groups, not identity. The file is read from the module assembly’s own directory joined with the setting value, decoded as UTF-16, and passed as a tobii_license_key_t whose size_in_bytes is the character count times two.

The licence levels the vendor headers require for the calls this module makes:

CallRequired feature group, per the vendor header
tobii_gaze_data_subscribe / _unsubscribeProfessional
tobii_primary_camera_image_subscribe / _unsubscribeInternal
tobii_set_output_frequencyNot stated in the header as a feature group. The module handles TOBII_ERROR_INSUFFICIENT_LICENSE from it specifically and describes it in that log line as requiring a Config-level licence.
tobii_gaze_point_subscribe, tobii_head_pose_subscribe, tobii_absolute_eye_openness_subscribeNo licence level stated in the header.

LicenceFile defaults to an empty string. With the default, the joined path resolves to the module assembly’s own directory, and the file read is attempted against a directory. The module performs no existence check before that read, unlike the calibration path which does check. The resulting I/O exception propagates out of InitialiseModule, where the SDK catches it and logs {Module} InitialiseModule failed., and the module does not start. A missing or unset licence file is therefore a hard startup failure reported as an exception, not a diagnosable configuration error.

4.3 Connection lifecycle

InitialiseModule runs, in order: create the API context, enumerate trackers, read the licence file and connect to the first tracker, configure the display area (IF-CMTOBII-04), then apply the calibration blob. All of it is bounded by the SDK StartupTimeout. The module does not override that property, so the bound is the SDK default of 5 seconds, covering enumeration, connection, licence file I/O, three device configuration round trips and a calibration apply.

StartCapture assigns the five callback delegates to fields so they are not collected while native code holds them, issues the four data subscriptions, optionally starts the video path (IF-CMTOBII-03), and then starts the pump thread TobiiCallbackThread.

Subscription failures do not fail the start. Each subscribe is wrapped in a helper that logs at critical level and returns, so StartCapture returns true even when every subscription was rejected. The observable result is a module that runs and publishes frames containing only default values. Distinguish this case by the presence of Failed subscribing to ... critical log lines.

EndCapture cancels the pump token and joins the pump thread for up to 2 seconds, cancels the encode token, completes the camera queue and joins the encode thread for up to 2 seconds, logging Encode thread did not stop in time if that join fails. It then unsubscribes the four data streams, unsubscribes the camera, disconnects, destroys the API context, and zeroes both handles. Failures at this stage are logged as errors and do not stop teardown.

The SDK clears its initialised flag on stop, so a subsequent start re-runs InitialiseModule and rebuilds both contexts. Enumeration, connection, display-area configuration and calibration are therefore repeated per capture session, not once per process.

The camera unsubscribe is guarded by the current value of CaptureVideoFeed rather than by the value that was in effect when the subscription was made. Toggling the setting during a session makes teardown either skip a live camera subscription or attempt to unsubscribe one that was never made.

4.4 Reconnect

Reconnect is handled inside the pump loop, not by a connection supervisor.

Pump resultModule behaviour
TOBII_ERROR_NO_ERROR from the waitCalls tobii_device_process_callbacks.
TOBII_ERROR_TIMED_OUT from the waitContinues immediately. The vendor documents this as a housekeeping opportunity after a timeout of some hundred milliseconds, not a failure.
Any other error from the waitLogs tobii_wait_for_callbacks returned: {Error} as a warning and continues, with no backoff.
TOBII_ERROR_CONNECTION_FAILED from processingLogs a warning containing Tobii device connection lost and attempting reconnect, then calls tobii_device_reconnect. The return value of that call is discarded, so a failed reconnect is not reported and the loop retries on the next iteration.
Any other error from processingLogs tobii_device_process_callbacks returned: {Error} as a warning and continues.
Managed exception in the loop bodyLogged as Exception in Tobii callback processing loop and the loop continues.

The loop also exits if the device context has been zeroed, which happens only during teardown.

5. IF-CMTOBII-02 Gaze, eye-openness and head-pose subscriptions

5.1 Delivery model

Four callbacks are registered and all are invoked from the module’s own pump thread during tobii_device_process_callbacks. Each callback receives its vendor struct by reference, validates per-field validity flags, and writes into three long-lived component objects held by the module. No queue, ring buffer or timestamp history is kept: each callback overwrites the retained value.

The callback bodies are individually wrapped in try/catch and log Exception in OnGazeDataCallback, Exception in OnGazePointCallback, Exception in OnHeadPoseCallback or Exception in OnEyePpennessCallback. The last of those log strings contains a typo in this revision and is reproduced here as it appears, because log-based diagnostics have to match the emitted text.

Each callback returns immediately without writing anything if the device context has been zeroed.

5.2 Wire format consumed

StructFields the module reads
tobii_gaze_data_tPer eye: gaze_origin_validity, gaze_origin_from_eye_tracker_mm_xyz, eye_position_validity, eye_position_in_track_box_normalized_xyz, pupil_validity, pupil_diameter_mm
tobii_gaze_point_tvalidity, position
tobii_head_pose_tposition_validity, position_xyz, rotation_x_validity, rotation_y_validity, rotation_z_validity, rotation_xyz
tobii_absolute_eye_openness_tleft_validity, left_eye_openness, right_validity, right_eye_openness

The module reads no timestamp from any of these structs. timestamp_tracker_us and timestamp_system_us on gaze data and eye openness, and timestamp_us on gaze point and head pose, are all ignored. It also declares but never reads gaze_point_validity, gaze_point_from_eye_tracker_mm_xyz, gaze_point_on_display_normalized_xy, eyeball_center_validity and eyeball_center_from_eye_tracker_mm_xyz: two local validity variables are computed from the first and fourth of those and then never used.

6. IF-CMTOBII-05 Gaze frame publication and data model reference

6.1 Frame shape

Every capture cycle builds and returns one SimulationFrame with exactly one entity.

ItemValue
Entity nameTobii_Data
Entity id(ulong)Guid.NewGuid().GetHashCode() & 0x7FFFFFFF, evaluated once when the module object is constructed
ComponentsGazeComponent, PupillometryDataComponent, SpatialComponent
Capture timeUTC Unix milliseconds read at frame build

The entity id is derived from a fresh GUID, so it is a fresh unpredictable value on every host process start. It is stable for the lifetime of a module instance, including across stop and restart of capture within one process, but it is not reproducible and carries no relationship to the device or to the entity name. Correlating entities across sessions requires the entity name, not the id.

HandleCapture never returns null, so a frame is published on every cycle whether or not any callback has fired. Before the first callback the frame carries default component values, and the components are never reset at start or end of capture, so the first frames of a second session in the same process carry the previous session’s last values. There is no timestamp, sequence number or validity flag on the published data to distinguish a fresh reading from a retained one; only left_eye_occluded and right_eye_occluded carry any liveness signal, and those default to false.

HandleCapture returns the retained component objects themselves rather than copies, so serialisation by the SDK reads objects that the pump thread is concurrently mutating, with no lock or exchange between the two threads. A frame can therefore mix fields from two different device samples.

6.2 Published fields

Proto fieldComponentSourceWritten when
left_gaze_originGazeComponentgaze_data.left.gaze_origin_from_eye_tracker_mm_xyzgaze_origin_validity is valid
right_gaze_originGazeComponentgaze_data.right.gaze_origin_from_eye_tracker_mm_xyzgaze_origin_validity is valid
left_eye_positionGazeComponentgaze_data.left.eye_position_in_track_box_normalized_xyzeye_position_validity is valid
right_eye_positionGazeComponentgaze_data.right.eye_position_in_track_box_normalized_xyzeye_position_validity is valid
left_eye_occludedGazeComponentNegation of gaze_data.left.eye_position_validity being validEvery gaze-data callback
right_eye_occludedGazeComponentNegation of gaze_data.right.eye_position_validity being validEvery gaze-data callback
gaze_screen_pointGazeComponentgaze_point.position x and y, copied unchangedgaze_point.validity is valid
left_eye_pupil_diameterPupillometryDataComponentgaze_data.left.pupil_diameter_mmpupil_validity is valid
right_eye_pupil_diameterPupillometryDataComponentgaze_data.right.pupil_diameter_mmpupil_validity is valid
left_eyelid_opennessPupillometryDataComponenteye_openness.left_eye_opennessEvery eye-openness callback, regardless of validity
left_eyelid_openness_qualityPupillometryDataComponent1.0 if left_validity is valid, otherwise 0.0Every eye-openness callback
right_eyelid_opennessPupillometryDataComponenteye_openness.right_eye_opennessEvery eye-openness callback, regardless of validity
right_eyelid_openness_qualityPupillometryDataComponent1.0 if right_validity is valid, otherwise 0.0Every eye-openness callback
pose.positionSpatialComponenthead_pose.position_xyzposition_validity is valid
pose.orientationSpatialComponentQuaternion converted from head_pose.rotation_xyzAll three of rotation_x_validity, rotation_y_validity, rotation_z_validity are valid

left_eyelid_openness and right_eyelid_openness are the only readings written without checking their validity flag. A consumer must gate them on the paired quality field, which is the binary 1.0 or 0.0 carrying that flag, and not treat a 0.0 quality reading as a measured value of an eyelid.

The pose is rebuilt from an empty builder on each head-pose callback and assigned only if position or rotation was valid that callback. When only one of the two is valid, the other half of the pose is the builder default rather than its previous value, so a position update during a rotation dropout publishes a default orientation rather than the last known one.

Not populated by this module: gaze_direction, left_gaze_direction, right_gaze_direction, combined_eye_position, gaze_point, blink, has_glasses, and every gaze quality field. SpatialComponent carries only pose; the module sets no image, velocity or other spatial field. PupillometryDataComponent combined fields and other_pupil_data are not populated.

6.3 Cadence

Gaze publication is driven entirely by the SDK capture loop, so it runs at Interval, default 1000 ms, re-read every cycle. The device is asked to run at 33 Hz (IF-CMTOBII-04), so at default settings the callbacks update the retained values roughly 33 times for every frame published, and 32 of those 33 samples are discarded. The module offers no native-rate publication path and no per-sample buffering. To approach the device rate, Interval must be lowered.

7. IF-CMTOBII-03 and IF-CMTOBII-06 Camera stream

Active only when CaptureVideoFeed is true, which is the default.

7.1 Ingest

tobii_primary_camera_image_subscribe is called with a null user-data pointer. The callback receives tobii_primary_camera_image_t with timestamp_us, bits_per_pixel, width, height, stride, data_size and data.

The vendor header documents timestamp_us as having an undefined epoch, useful only for measuring elapsed time between a pair of values. The module does not use it. It stamps each frame with DateTimeOffset.UtcNow at callback receipt instead, and that host-clock value is what reaches the consumer as capture_time. Video timestamps are therefore not comparable with the tracker clock, and they include the callback delivery latency of the pump thread.

Accepted frames must have non-zero data_size, width and height, bits_per_pixel equal to 8, and a stride that is not smaller than width; a stride of zero or less is treated as equal to width. Any other pixel format logs Unsupported camera pixel format: {Bpp}bpp, video disabled once and disables video for the session. The pixel data is copied out of the native buffer before the callback returns.

7.2 Queue and back pressure

ItemValue
QueueBounded channel, single reader, single writer
Capacity66 frames, documented in source as about 2 seconds at 33 Hz
Full modeDrop oldest
Overflow actionThe drop callback requests a forced keyframe, so the next access unit re-anchors the stream after the lost history

7.3 Encode

A dedicated background thread named TobiiEncodeThread drains the queue. The encoder is created lazily on the first frame, because the frame dimensions are not known until then, and it is created, used and disposed only on that thread.

ItemValue
BackendMedia Foundation transform, CPU sample path. Hardware encoders enumerated first, then the Microsoft software H.264 transform. Reported as MFT-HW or MFT-SW.
Input media typeMFVideoFormat_NV12 in system memory, progressive, frame size and frame rate set to the encode size and 33
Greyscale conversionY plane receives the camera bytes row by row, zero-padded to the encode size; the UV plane is filled with the constant 0x80
Encode sizeBoth source dimensions rounded up to a multiple of 16
Output media typeMFVideoFormat_H264, average bitrate attribute 2 000 000, progressive, pixel aspect ratio 1:1
Codec API requestsConstant bitrate mode, mean bitrate 2 000 000, GOP size 33, zero B-pictures, low-latency mode. All five are best-effort; a rejection is logged at debug level and encoding continues with the transform’s own default.
Sample timingEach input sample is given a time of the frame’s host timestamp in 100 ns units and a duration of one frame at 33 fps
Timestamp carrySubmitted timestamps are queued and dequeued FIFO as access units emerge, so each published access unit carries the host timestamp of the camera frame it came from

The initialisation log line is H264 encoder: backend={Backend} {W}x{H}@{Fps} and reports the 16-aligned encode size, not the camera’s native size.

7.4 Published frame shape

One SimulationFrame per completed access unit, published immediately through the SDK Publish path from the encode thread. It is not produced by HandleCapture and is not gated by Interval.

ItemValue
EntitySame id and name as the gaze frame, Tobii_Data
ComponentsStreamComponent only, with one StreamSource holding one VideoSource. No gaze, pupillometry or spatial component.
VideoSource.namecamera.ir
VideoSource.codecThe literal string H264
VideoSource.width / heightThe encoder’s 16-aligned encode dimensions, so not the camera’s native dimensions unless those are already multiples of 16
VideoSource.frame_rateThe integer constant 33, always, independent of the rate the device actually delivers
VideoSource.video_dataThe bytes of exactly one access unit
capture_timeThe host-clock timestamp of the camera frame, not the encode completion time

There is no keyframe flag on the message. Identifying an IDR requires inspecting NAL unit types in video_data. A consumer must decode in arrival order per stream name and cannot begin decoding before an IDR arrives.

7.5 Cadence

Video cadence is the rate at which the device delivers camera frames, reduced by any frames dropped at ingest, at the queue or by the encoder. The module never resamples or pads, so the emitted rate is not held to the declared frame_rate of 33. The declared value is a constant in source and is not measured, so it should be treated as a nominal hint rather than as the stream’s true rate.

7.6 Parameters this repository does not define

The following are outside what source in this repository fixes, and are recorded here as undetermined rather than assumed:

  • Camera native resolution and frame rate. The module accepts whatever the first callback reports and derives its encode size from that. No resolution or rate is asserted anywhere in the module. The authority is the connected Tobii device and its runtime.
  • Bitstream framing and parameter-set placement. The module sets no bitstream-format attribute on either media type. Annex-B framing with SPS and PPS present on IDR frames is asserted by the encoder’s own documentation comment and by the module’s design specification as the behaviour of the Media Foundation H.264 transform. It is neither configured nor verified by code in this repository, and the transform chosen varies by machine because hardware encoders are preferred over the Microsoft software fallback. Verify framing against the actual capture before relying on it.
  • Profile and level. Not set. Whatever the selected transform defaults to.
  • Achieved bitrate. 2 000 000 is a requested average and a best-effort constant-bitrate request. A hardware transform may reject the rate-control mode, in which case its own default applies.
  • Keyframe interval. GOP size 33 is requested best-effort, and forced keyframes are additionally requested on queue overflow, which itself may be rejected and is then logged as ForceKeyframe request rejected.
  • Colour metadata. None is set on either media type. The source is 8-bit greyscale and the chroma plane is synthetic neutral, so colour primaries, transfer characteristics and matrix coefficients are whatever the transform writes by default.
  • Access-unit-to-frame ratio. Timestamp carry assumes one output access unit per submitted frame. If a transform emits an additional standalone output, the FIFO shifts and later access units carry the timestamp of an earlier frame. When the timestamp queue is empty, the access unit is published with a capture_time of 0.

8. IF-CMTOBII-04 Device configuration

Applied once per capture session during InitialiseModule, before any subscription.

  1. tobii_get_geometry_mounting reads the tracker’s own mounting geometry. Its log contains Tracker geometry, {Guides}, {Width}mm and {Angle}deg. Failure logs Failed retrieving geometry mounting and aborts initialisation.
  2. tobii_calculate_display_area_basic combines ScreenWidth, ScreenHeight and TrackerOffsetX with that geometry. Failure logs Failed calculating display area and aborts initialisation.
  3. tobii_set_display_area applies the result, and the three computed corners are logged. Failure logs Failed setting display area and aborts initialisation.
  4. tobii_set_output_frequency requests 33 Hz. TOBII_ERROR_INSUFFICIENT_LICENSE logs a warning stating the device will run at its default frequency; any other error logs Failed setting output frequency. Neither aborts initialisation, so a session can run at a device frequency other than 33 Hz while the module still declares frame_rate 33 on video and still assumes 33 fps for encoder timing.
  5. tobii_calibration_apply applies the blob at CalibrationBlobPath, resolved relative to the module assembly directory and defaulting to data/calib_default.blob, a 345-byte file bundled in the package. A missing file logs a message containing No calibration blob found at '{Path}' and proceeding without applying calibration, then continues uncalibrated. An apply failure logs Failed applying calibration blob and also continues.

The same bundled default blob is applied on every session. The module provides no per-user or per-session calibration step, so gaze accuracy reflects that default calibration and not the current operator.

9. Units and conventions

ConceptConvention
Gaze originMillimetres from the eye tracker, as reported by the device. Passed through unscaled.
Eye positionNormalised track-box coordinates in the range 0 to 1, per the vendor header. Passed through.
Pupil diameterMillimetres, as reported by the device.
Eyelid opennessMillimetres. The vendor header defines it as the diameter of the largest sphere that fits between the upper and lower eyelids, so it is an absolute distance rather than a normalised ratio.
Openness qualityBinary 1.0 or 0.0, a re-encoding of the vendor validity flag. It is not a graduated confidence, even though the proto documents the field as a [0..1] quality.
Head positionMillimetres from the centre of the display, per the vendor header.
Head rotationThe vendor header describes right-handed Euler rotations about each axis but does not state the angle unit. The module’s conversion applies trigonometric functions directly to the values, which treats them as radians. Confirm against the vendor documentation for the specific device before deriving anything from the published orientation.
Euler-to-quaternion mappingThe module maps rotation_xyz.x to pitch, .y to yaw and .z to roll, then composes a quaternion. The vendor header states only that the z rotation is about the vector pointing towards the user.
Gaze screen pointNormalised, and may fall outside 0 to 1 when the user looks off screen. The vendor header defines the vertical axis with 0.0 at the top edge and 1.0 at the bottom edge. The HEAT gaze_screen_point field is documented as normalised with (0,0) at bottom-left and (1,1) at top-right. The module copies x and y unchanged, applying no vertical flip, so the published vertical component follows the Tobii top-down convention and not the convention the proto field documents. Treat the published y as top-down until this is reconciled.
Gaze and video time baseHost wall clock, UTC Unix milliseconds. No device timestamp is carried on any published field. Gaze frames are stamped at frame build; video frames are stamped when the camera callback is received.
Video pixel data8-bit greyscale from the device, encoded through NV12 with a synthetic neutral chroma plane. The decoded image is greyscale content in a nominally colour format.

10. Configuration affecting the interfaces

SettingDefaultInterface effect
LicenceFileemptyDetermines which Stream Engine feature groups are authorised. Empty or missing is a hard startup failure, see 4.2. Read only during InitialiseModule.
ScreenWidth527.0 mmInput to the display-area calculation. A wrong value produces a valid but physically incorrect display area and therefore a wrong gaze-to-screen mapping, with no error.
ScreenHeight296.0 mmAs above.
TrackerOffsetX0.0 mmAs above. There is no equivalent Y or Z offset.
CaptureVideoFeedtrueWhether IF-CMTOBII-03 and IF-CMTOBII-06 exist at all. Read at StartCapture for the subscription and again at EndCapture for the unsubscribe, see 4.3.
CalibrationBlobPathdata/calib_default.blobWhich calibration blob is applied. Hidden from the settings UI.
Interval1000 msGaze frame cadence only. Does not affect the device output frequency and does not affect video.
EnabledtrueWhether the module runs.
OverrideNodeId, NodeIdfalse, 0Node id stamped on published packets by the SDK.
Timeout30 sInherited from the SDK and not read by this module.

The output frequency request, the video frame rate, the encoder bitrate, the GOP size, the queue capacity, the thread join timeouts and the reconnect behaviour are all compile-time constants with no setting. Settings changed mid-session have no effect on any subscription, because subscriptions are made only at StartCapture.

See Manage capture modules for the generic settings and the SDK ICD for the lifecycle and publication contract this module implements.

11. Error behaviour

ConditionDetectionModule behaviourObservable effect
API context creation failstobii_api_create errorLogs Failed Creating Tobii API Context at critical level and continuesInitialisation proceeds with a zero API context and fails later, at the display-area calculation
Enumeration failstobii_find_all_eyetrackers errorLogs Failed enumerating eyetrackers at critical level; the array is emptyFalls through to the no-device path
No tracker presentEmpty enumeration arrayLogs No eye trackers found as an error, returns falseModule does not start; no retry
Licence file missing or unsetException from the file readUncaught in the module; the SDK logs {Module} InitialiseModule failed.Module does not start
Connection failstobii_connect_eyetracker errorLogs Failed creating device context at critical level; the zero context is then caught by an explicit check that logs Failed to create device context and returns falseModule does not start
Geometry, display-area calculation or display-area apply failsVendor error codeLogs the specific failure and returns falseModule does not start
Output frequency rejectedTOBII_ERROR_INSUFFICIENT_LICENSE or other errorWarning onlySession runs at the device’s default frequency while still declaring 33
Calibration blob missing or apply failsFile check or vendor errorWarning or error, continuesSession runs uncalibrated
Any subscription rejectedVendor error codeLogs Failed subscribing to ... at critical level and continuesModule runs and publishes frames whose corresponding fields never update
Connection lost while capturingTOBII_ERROR_CONNECTION_FAILED from the pumpWarns and calls tobii_device_reconnect, discarding the resultRetries every pump iteration indefinitely; gaze frames keep publishing the last retained values with no staleness indicator
Exception inside a data callbacktry/catch per callbackLogged, retained values left as they wereThat sample is lost
Unsupported camera pixel formatbits_per_pixel not 8, or stride below widthLogs Unsupported camera pixel format: {Bpp}bpp, video disabled once and disables video for the sessionVideo stops, gaze continues
No usable H.264 transformEnumeration and configuration all failLogs No usable H.264 encoder MFT found; video disabled from the encoder and H264 encoder unavailable; video disabled for this session from the module, completes the queue and exits the encode threadVideo never starts, gaze continues
Encoder busy or input rejectedAsync token exhausted or ProcessInput failureFrame dropped; H264 encoder submit failed (hr=...); frames will be dropped logged once per sessionGap in the video stream, not re-anchored
Output stalledProcessOutput failureWarning logged once per sessionVideo output stops for the session
Camera frames arriving faster than encodeQueue fullOldest frame dropped and a keyframe forcedGap in the video stream, re-anchored at the next access unit
Transient exception around encode or publishtry/catch in the encode loopTransient error encoding/publishing camera frame; continuing logged once per session, loop continuesIsolated frames lost
Fatal exception in the encode looptry/catch around the loopEncode loop failed; video disabled for this session, queue completed, encoder disposedVideo stops, gaze continues
Exception thrown by HandleCaptureSDKLogged as Error in module, loop continuesCycle produces no frame

No error condition in this module raises a HEAT-visible fault, and none stops the capture session. Every failure short of a startup failure degrades silently into missing or frozen data, so log inspection is the only reliable way to establish that a session’s data is trustworthy.

12. Compatibility and versioning

There is no version negotiation on either side. The module does not query the Stream Engine version and does not read the device model or firmware. Compatibility rests on the vendored tobii_stream_engine.dll in this repository matching the layout of the binding structs alongside it; a vendor DLL swap that changes any consumed struct layout produces misread fields rather than an error, because the bindings are sequential-layout structs marshalled directly.

Device requirements that follow from source rather than from documentation: the tracker must be screen-based, since the module uses the screen-based API and not the wearable API whose bindings are also present but unused; it must supply an 8-bit greyscale primary camera image for video to work; and the licence must reach at least Professional for gaze data and Internal for the camera image.

The host contract is HEAT.Capture.ModuleSDK 2.0.3, targeting net10.0. The module is Windows-only in practice: Media Foundation for encoding and the vendored Windows Stream Engine DLL.

Audit finding TOBII-01 applies to pinning an artifact to this revision. In this revision, non-main builds select a full SemVer package version but the generated release location and tag use only majorMinorPatch, so a prerelease package is published under a tag that looks like a stable release and collides with the eventual stable release of the same version. Pin by commit, and verify the package version recorded inside the artifact rather than trusting the release tag, when confirming that a deployed module is this revision.

13. Security considerations

Gaze, pupillometry, head pose and the infrared camera image are all personal data. The camera feed in particular is imagery of a person’s eyes and face region, and gaze data is behavioural biometric data from which attention and identity inferences can be drawn. Both are published into HEAT Capture and persisted with the session.

ItemProperty
TransportIn-process P/Invoke to a local native library and local device I/O. No network interface is exposed by this module.
AuthenticationNone in either direction, see 4.2.
EncryptionNone; not applicable to in-process calls, and the module applies none to the data it publishes.
Licence filePlain UTF-16 text read from the module directory. It grants access to privileged Stream Engine feature groups, including the camera image, so treat it as a credential and restrict read access to the module directory accordingly.
Declared capabilityprocess.unrestricted, so the module runs without the host’s process restrictions.
Video contentInfrared imagery of the operator’s eyes. Retained in captured sessions as H.264. There is no setting to blur, crop or redact it; the only control is turning CaptureVideoFeed off.
Data minimisationTurning CaptureVideoFeed off removes the camera subscription entirely, so no imagery reaches the host. Use that when the scenario does not need imagery.

Restrict access to the workstation, the module directory and stored captures according to the deployment’s own data-handling policy, and treat any exported session containing camera.ir as containing identifiable imagery.

14. Conformance

  1. Place a valid Tobii licence file in the module directory and set LicenceFile to it. Confirm the log records License file loaded from '{Path}' and Device context created for {Url}. Confirm no Failed subscribing to ... critical line appears for any of gaze data, eye openness, head pose or gaze point.
  2. Confirm the startup log records the tracker geometry line, the three display-area corners, and either Output frequency set to 33Hz or the insufficient-licence warning. If the warning appears, record the device’s actual frequency, because the declared video frame_rate of 33 will be wrong for that session.
  3. Confirm one entity named Tobii_Data appears in the captured data, and that both frame shapes arrive on the same entity id: gaze frames carrying gaze, pupillometry and spatial components, and video frames carrying only a stream component.
  4. With the operator looking at known screen corners, confirm the sign convention of gaze_screen_point. Looking at the top of the screen should be checked against the y value: a value near 0 confirms the top-down Tobii convention described in section 9, and near 1 would contradict it. Fix downstream interpretation to match what you observe, not to what the proto field documents.
  5. Confirm eyelid openness is only trusted when its quality field is 1.0. Cover one eye and check that the corresponding quality drops to 0.0 while the openness field still carries a number.
  6. With Interval at the default, confirm gaze frames arrive at about 1 Hz and not at the device rate. Lower Interval and confirm the rate follows it.
  7. With CaptureVideoFeed on, dump the video_data of consecutive camera.ir frames to a raw .h264 file in arrival order and probe it. Record the actual resolution, the observed keyframe cadence and the achieved bitrate, and confirm whether framing is Annex-B with parameter sets on IDR frames. This is the step that resolves the parameters section 7.6 leaves undetermined, and it has to be repeated per machine class, because the transform selected differs between rigs with and without a usable hardware encoder. Note the backend from the H264 encoder: backend={Backend} {W}x{H}@{Fps} line alongside the result.
  8. Confirm the encode size in that log line against the camera’s native size, and expect the declared width and height to be rounded up to a multiple of 16.
  9. Disconnect the tracker while capturing. Confirm a warning containing Tobii device connection lost and attempting reconnect appears, and confirm that gaze frames keep publishing the last values with no staleness indicator. Reconnect and confirm values resume changing without restarting the capture session.
  10. Stop and restart capture within the same host process. Confirm initialisation runs again, and confirm that the first frames of the second session carry the previous session’s retained values before the first callback of the new session arrives.
  11. Start with CaptureVideoFeed off and confirm no camera.ir stream is published and no encode thread starts, while gaze, pupillometry and head pose publish normally.

15. Revision history

RevDateProduct versionChange
12026-09-02aa2ba9bInitial issue. Supersedes the placeholder that deferred the stream contract to the data page, closing audit finding TOBII-02.
Opens your browser's print dialog. Choose "Save as PDF" as the destination.