Skip to content

Commit 74e4b99

Browse files
DataTracks integration through FFI
realsense-livekit: Examples of using DataTracks, realsense and mcap bridge/tests/: integration, stress, unit bridge_human_robot: DataTracks
1 parent 42b046c commit 74e4b99

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+6122
-103
lines changed

CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ set(FFI_PROTO_FILES
7777
${FFI_PROTO_DIR}/e2ee.proto
7878
${FFI_PROTO_DIR}/stats.proto
7979
${FFI_PROTO_DIR}/data_stream.proto
80+
${FFI_PROTO_DIR}/data_track.proto
8081
${FFI_PROTO_DIR}/rpc.proto
8182
${FFI_PROTO_DIR}/track_publication.proto
8283
)
@@ -325,14 +326,17 @@ add_library(livekit SHARED
325326
src/audio_source.cpp
326327
src/audio_stream.cpp
327328
src/data_stream.cpp
329+
src/data_track_subscription.cpp
328330
src/e2ee.cpp
329331
src/ffi_handle.cpp
330332
src/ffi_client.cpp
331333
src/ffi_client.h
332334
src/livekit.cpp
333335
src/logging.cpp
334336
src/local_audio_track.cpp
337+
src/local_data_track.cpp
335338
src/remote_audio_track.cpp
339+
src/remote_data_track.cpp
336340
src/room.cpp
337341
src/room_proto_converter.cpp
338342
src/room_proto_converter.h
@@ -682,10 +686,6 @@ install(FILES
682686
# Build the LiveKit C++ bridge before examples (human_robot depends on it)
683687
add_subdirectory(bridge)
684688

685-
# ---- Examples ----
686-
# add_subdirectory(examples)
687-
688-
689689
if(LIVEKIT_BUILD_EXAMPLES)
690690
add_subdirectory(examples)
691691
endif()

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,37 @@ CPP SDK is using clang C++ format
447447
brew install clang-format
448448
```
449449

450+
451+
#### Memory Checks
452+
Run valgrind on various examples or tests to check for memory leaks and other issues.
453+
```bash
454+
valgrind --leak-check=full ./build-debug/bin/BridgeRobot
455+
valgrind --leak-check=full ./build-debug/bin/BridgeHuman
456+
valgrind --leak-check=full ./build-debug/bin/livekit_integration_tests
457+
valgrind --leak-check=full ./build-debug/bin/livekit_stress_tests
458+
```
459+
460+
# Running locally
461+
1. Install the livekit-server
462+
https://docs.livekit.io/transport/self-hosting/local/
463+
464+
Start the livekit-server with data tracks enabled:
465+
```bash
466+
LIVEKIT_CONFIG="enable_data_tracks: true" livekit-server --dev
467+
```
468+
469+
```bash
470+
# generate tokens, do for all participants
471+
lk token create \
472+
--api-key devkey \
473+
--api-secret secret \
474+
-i robot \
475+
--join \
476+
--valid-for 99999h \
477+
--room robo_room \
478+
--grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}'
479+
```
480+
450481
<!--BEGIN_REPO_NAV-->
451482
<br/><table>
452483
<thead><tr><th colspan="2">LiveKit Ecosystem</th></tr></thead>

bridge/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
88
add_library(livekit_bridge SHARED
99
src/livekit_bridge.cpp
1010
src/bridge_audio_track.cpp
11+
src/bridge_data_track.cpp
1112
src/bridge_video_track.cpp
1213
src/bridge_room_delegate.cpp
1314
src/bridge_room_delegate.h

bridge/README.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,7 @@ The human will print periodic summaries like:
206206

207207
## Testing
208208

209-
The bridge includes a unit test suite built with [Google Test](https://github.com/google/googletest). Tests cover
210-
1. `CallbackKey` hashing/equality,
211-
2. `BridgeAudioTrack`/`BridgeVideoTrack` state management, and
212-
3. `LiveKitBridge` pre-connection behaviour (callback registration, error handling).
209+
The bridge includes a unit test suite built with [Google Test](https://github.com/google/googletest).
213210

214211
### Building and running tests
215212

@@ -222,18 +219,46 @@ Bridge tests are automatically included when you build with the `debug-tests` or
222219
Then run them directly:
223220

224221
```bash
225-
./build-debug/bin/livekit_bridge_tests
222+
./build-debug/bin/livekit_bridge_*_tests
226223
```
227224

228-
### Standalone bridge tests only
225+
### Bridge Tests
229226

230-
If you want to build bridge tests independently (without the parent SDK tests), set `LIVEKIT_BRIDGE_BUILD_TESTS=ON`:
227+
The bridge layer (`bridge/tests/`) has its own integration and stress tests
228+
that exercise the full `LiveKitBridge` API over a real LiveKit server.
229+
They use the **same environment variables** as the SDK tests above.
231230

232231
```bash
233-
cmake --preset macos-debug -DLIVEKIT_BRIDGE_BUILD_TESTS=ON
234-
cmake --build build-debug --target livekit_bridge_tests
232+
# Run bridge tests via CTest
233+
cd build-debug && ctest -L "bridge_integration" --output-on-failure
234+
cd build-debug && ctest -L "bridge_stress" --output-on-failure
235+
236+
# Or run executables directly
237+
./build-debug/bin/livekit_bridge_integration_tests
238+
./build-debug/bin/livekit_bridge_stress_tests
239+
240+
# Run specific test suites
241+
./build-debug/bin/livekit_bridge_integration_tests --gtest_filter="*AudioFrameRoundTrip*"
242+
./build-debug/bin/livekit_bridge_stress_tests --gtest_filter="*HighThroughput*"
235243
```
236244

245+
| Executable | Description |
246+
|------------|-------------|
247+
| `livekit_bridge_unit_tests` | Unit tests (no server required) |
248+
| `livekit_bridge_integration_tests` | Audio & data round-trip, latency, connect/disconnect cycles |
249+
| `livekit_bridge_stress_tests` | Sustained push, lifecycle, callback churn, multi-track concurrency |
250+
251+
#### Stress test suites
252+
253+
| Test file | Tests | What it exercises |
254+
|-----------|-------|-------------------|
255+
| `test_bridge_audio_stress` | SustainedAudioPush, ReleaseUnderActivePush, RapidConnectDisconnectWithCallback | Audio push at real-time pace, release/push race, abrupt teardown |
256+
| `test_bridge_data_stress` | HighThroughput, LargePayloadStress, CallbackChurn | Data throughput, 64 KB frames, data callback register/clear churn |
257+
| `test_bridge_lifecycle_stress` | DisconnectUnderLoad, TrackReleaseWhileReceiving, FullLifecycleSoak | Disconnect with active pushers+receivers, mid-stream track release, repeated full create→push→release→destroy |
258+
| `test_bridge_callback_stress` | AudioCallbackChurn, MixedCallbackChurn, CallbackReplacement | Audio set/clear churn, mixed audio+data churn, rapid callback overwrite |
259+
| `test_bridge_multi_track_stress` | ConcurrentMultiTrackPush, ConcurrentCreateRelease, FullDuplexMultiTrack | 4 tracks pushed concurrently, create/release cycles under contention, bidirectional audio+data |
260+
261+
237262
## Limitations
238263

239264
The bridge is designed for simplicity and currently only supports limited audio and video features. It does not expose:

bridge/include/livekit_bridge/bridge_audio_track.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2025 LiveKit
2+
* Copyright 2026 LiveKit
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,6 +55,10 @@ class BridgeAudioTrackTest;
5555
* one thread while another calls mute()/unmute()/release(), or to call
5656
* pushFrame() concurrently from multiple threads.
5757
*
58+
* All public methods are thread-safe: it is safe to call pushFrame() from
59+
* one thread while another calls mute()/unmute()/release(), or to call
60+
* pushFrame() concurrently from multiple threads.
61+
*
5862
* Usage:
5963
* auto mic = bridge.createAudioTrack("mic", 48000, 2,
6064
* livekit::TrackSource::SOURCE_MICROPHONE);
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2026 LiveKit
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <cstdint>
20+
#include <memory>
21+
#include <mutex>
22+
#include <optional>
23+
#include <string>
24+
#include <vector>
25+
26+
namespace livekit {
27+
class LocalDataTrack;
28+
class LocalParticipant;
29+
} // namespace livekit
30+
31+
namespace livekit_bridge {
32+
33+
namespace test {
34+
class BridgeDataTrackTest;
35+
} // namespace test
36+
37+
/**
38+
* Handle to a published local data track.
39+
*
40+
* Created via LiveKitBridge::createDataTrack(). The bridge retains a
41+
* reference to every track it creates and will automatically release all
42+
* tracks when disconnect() is called. To unpublish a track mid-session,
43+
* call release() explicitly.
44+
*
45+
* Unlike BridgeAudioTrack / BridgeVideoTrack, data tracks have no
46+
* Source, Publication, mute(), or unmute(). They carry arbitrary binary
47+
* frames via pushFrame().
48+
*
49+
* All public methods are thread-safe.
50+
*
51+
* Usage:
52+
* auto dt = bridge.createDataTrack("sensor-data");
53+
* dt->pushFrame({0x01, 0x02, 0x03});
54+
* dt->release(); // unpublishes mid-session
55+
*/
56+
class BridgeDataTrack {
57+
public:
58+
~BridgeDataTrack();
59+
60+
BridgeDataTrack(const BridgeDataTrack &) = delete;
61+
BridgeDataTrack &operator=(const BridgeDataTrack &) = delete;
62+
63+
/**
64+
* Push a binary frame to all subscribers of this data track.
65+
*
66+
* @param payload Raw bytes to send.
67+
* @param user_timestamp Optional application-defined timestamp.
68+
* @return true if the frame was pushed, false if the track has been
69+
* released or the push failed (e.g. back-pressure).
70+
*/
71+
bool pushFrame(const std::vector<std::uint8_t> &payload,
72+
std::optional<std::uint64_t> user_timestamp = std::nullopt);
73+
74+
/**
75+
* Push a binary frame from a raw pointer.
76+
*
77+
* @param data Pointer to raw bytes.
78+
* @param size Number of bytes.
79+
* @param user_timestamp Optional application-defined timestamp.
80+
* @return true on success, false if released or push failed.
81+
*/
82+
bool pushFrame(const std::uint8_t *data, std::size_t size,
83+
std::optional<std::uint64_t> user_timestamp = std::nullopt);
84+
85+
/// Track name as provided at creation.
86+
const std::string &name() const noexcept { return name_; }
87+
88+
/// Whether the track is still published in the room.
89+
bool isPublished() const;
90+
91+
/// Whether this track has been released / unpublished.
92+
bool isReleased() const noexcept;
93+
94+
/**
95+
* Explicitly unpublish the track and release underlying SDK resources.
96+
*
97+
* After this call, pushFrame() returns false. Called automatically by the
98+
* destructor and by LiveKitBridge::disconnect(). Safe to call multiple
99+
* times (idempotent).
100+
*/
101+
void release();
102+
103+
private:
104+
friend class LiveKitBridge;
105+
friend class test::BridgeDataTrackTest;
106+
107+
BridgeDataTrack(std::string name,
108+
std::shared_ptr<livekit::LocalDataTrack> track,
109+
livekit::LocalParticipant *participant);
110+
111+
/** Protects released_ and track_ for thread-safe access. */
112+
mutable std::mutex mutex_;
113+
114+
/** Publisher-assigned track name (immutable after construction). */
115+
std::string name_;
116+
117+
/** True after release() or disconnect(); prevents further pushFrame(). */
118+
bool released_ = false;
119+
120+
/** Underlying SDK data track handle. Nulled on release(). */
121+
std::shared_ptr<livekit::LocalDataTrack> track_;
122+
123+
/** Participant that published this track; used for unpublish. Not owned. */
124+
livekit::LocalParticipant *participant_ = nullptr;
125+
};
126+
127+
} // namespace livekit_bridge

bridge/include/livekit_bridge/bridge_video_track.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2025 LiveKit
2+
* Copyright 2026 LiveKit
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,6 +55,10 @@ class BridgeVideoTrackTest;
5555
* one thread while another calls mute()/unmute()/release(), or to call
5656
* pushFrame() concurrently from multiple threads.
5757
*
58+
* All public methods are thread-safe: it is safe to call pushFrame() from
59+
* one thread while another calls mute()/unmute()/release(), or to call
60+
* pushFrame() concurrently from multiple threads.
61+
*
5862
* Usage:
5963
* auto cam = bridge.createVideoTrack("cam", 1280, 720,
6064
* livekit::TrackSource::SOURCE_CAMERA);

0 commit comments

Comments
 (0)