Skip to content

DataTracks integration through FFI#66

Open
stephen-derosa wants to merge 7 commits intolivekit:mainfrom
stephen-derosa:sderosa/data_tracks
Open

DataTracks integration through FFI#66
stephen-derosa wants to merge 7 commits intolivekit:mainfrom
stephen-derosa:sderosa/data_tracks

Conversation

@stephen-derosa
Copy link
Contributor

@stephen-derosa stephen-derosa commented Feb 26, 2026

Overview

Integrate DataTracks into the CPP SDK through the FFI

Building

This library is attached to the build system of the core C++ SDK library. Use build.sh as is.

Testing

bridge/tests/ implements new tests for stress, integration, and unit. These closely follow the testing format of the base SDK.

Examples

  • examples/bridge_human_robot/ has been updated to include a DataTrack. The Robot sends a string with a time and count, the human prints it.
  • examples/realsense-livekit/ has examples for getting data from a realsense camera, serializing, compressing and sending it. It also shows writing receiving participant data and writing it to an mcap which can be visualized in foxglove.

Unit tests

  • bridge/tests/unit/test_bridge_data_track.cpp

Limitations

  • No E2EE configuration
  • RPC (incoming PR)
  • Simulcast tuning
  • Video format selection (RGBA is the default; no format option yet)

Blocked By

Rust SDKs PR

@stephen-derosa stephen-derosa self-assigned this Feb 26, 2026
@stephen-derosa stephen-derosa added the enhancement New feature or request label Feb 26, 2026
- src/**
- include/**
- examples/**
- bridge/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the bridge/ folder code only be internal ?

if so, I think we should put the code under src folder
the structure should be clean like
src/ : contains all the internal source code
tests/ contains all the test code
include/livekit : contain all the public interface

as we are adding bridge, if it is only internal to src, lets put it under src/internal/bridge

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good -- i will make this a single git mv commit at the end so comments are easier to follow!

};

virtual ~DataTrackSubscription();

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from what I understand, this is more like a data_track_stream ?

that will align with our audio_stream or video_stream.

One of the main purpose of data track is to build similar techs like audio / video tracks, to align with the same mental modle

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree -- poor naming. what are your thoughts on deprecating (removing) the existing data_stream work and fully replacing it with data tracks?

Copy link
Contributor

@ladvoc ladvoc Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be called subscription. Unlike audio and video stream, this object actually represents a subscription and follows RAII semantics (e.g., when the subscription is dropped, the SDK communicates with the SFU to unsubscribe in order to stop being forwarded frames).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might not be immediately clear, but RAII is implemented on the Rust side. C++ only needs to worry about dropping the FFI handle (which it is doing) and cleanup will be taken care of on the Rust side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least one confusing thing about "subscription" is that this object can also call pushFrame(), so its not purely a subscription

Copy link
Contributor

@ladvoc ladvoc Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least one confusing thing about "subscription" is that this object can also call pushFrame(), so its not purely a subscription

From sync: data flow is unidirectional, so there should be no push method on the subscription.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushFrame in this context pushes a received DataFrame to the local queue, not to the data track

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

audio_stream and video_stream are very similar that they offer the blocking read() functions, and they are subscribed to ffi events on the audio / video frames and move frames over. Similar to TextStreamReader for the legacy data stream.

@ladvoc, can you point me to the Rust example code how to hook up remove video / audio tracks with the screen and speaker?
I think there are some gaps between rust and c++ / python SDKs that make things different

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shishirng @ladvoc i think we're all in agreement of the path here?

Copy link
Contributor

@ladvoc ladvoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial comments, but I will make another pass. Looking clean so far!


proto::FfiResponse resp = FfiClient::instance().sendRequest(req);
const auto &r = resp.local_data_track_try_push();
return !r.has_error();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Should we be exposing the error reason to the caller? There are several reasons why pushing a frame could fail (e.g., the track is unpublished, buffer full, etc.). For the ROS bridge, a boolean might be fine if there is logging, but for the core SDK, I lean towards we should report the error reason.

Related question: the FFI interface currently flattens all errors to strings for simplicty, however, on the Rust side there are proper error enums that describe the reason for the error. Should I expose these over FFI rather than using strings? Can an enum describing the error reason map nicely into a C++ exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great question -- I need to sync with @xianshijing-lk on what the client facing applications should do. I think in general Livekit should never cause an application to crash (other than assert calls), so the sdk should catch any exceptions. Of course we still want to flow up the root cause (which we are currently doing). I could be way off here.

Copy link
Contributor

@ladvoc ladvoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some additional comments.

@stephen-derosa stephen-derosa force-pushed the sderosa/data_tracks branch 3 times, most recently from 8647582 to 74e4b99 Compare March 6, 2026 00:49
realsense-livekit: Examples of using DataTracks, realsense and mcap

bridge/tests/: integration, stress, unit

bridge_human_robot: DataTracks

// rust side handles buffering, so we should only really ever have one item
if (queue_.size() >= 2) {
LK_LOG_ERROR("[DataTrackSubscription] Queue size is greater than 1, this "
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update log

}

// rust side handles buffering, so we should only really ever have one item
if (frame_.has_value()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xianshijing-lk (cc @1egoman @ladvoc ) FYI because buffering is controlled on the rust side of things, we dont do any queueing/buffering on the cpp receiving side. Instead, we just store the latest frame and warn if there alreadya. frame when rust sends us a new one

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I think if you were to also integrate with @ladvoc's new ffi backpressure change (here) then that should ensure a new frame isn't pushed until the last frame was acknowledged, I think the LK_LOG_ERROR line could maybe become an assertion?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants