Skip to content

Commit 905cca7

Browse files
DataTracks integration through FFI (#66)
1 parent a48a726 commit 905cca7

48 files changed

Lines changed: 5067 additions & 127 deletions

Some content is hidden

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

CMakeLists.txt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ set(FFI_PROTO_FILES
7676
${FFI_PROTO_DIR}/e2ee.proto
7777
${FFI_PROTO_DIR}/stats.proto
7878
${FFI_PROTO_DIR}/data_stream.proto
79+
${FFI_PROTO_DIR}/data_track.proto
7980
${FFI_PROTO_DIR}/rpc.proto
8081
${FFI_PROTO_DIR}/track_publication.proto
8182
)
@@ -323,15 +324,20 @@ add_library(livekit SHARED
323324
src/audio_processing_module.cpp
324325
src/audio_source.cpp
325326
src/audio_stream.cpp
327+
src/data_track_frame.cpp
326328
src/data_stream.cpp
329+
src/data_track_error.cpp
330+
src/data_track_stream.cpp
327331
src/e2ee.cpp
328332
src/ffi_handle.cpp
329333
src/ffi_client.cpp
330334
src/ffi_client.h
331335
src/livekit.cpp
332336
src/logging.cpp
333337
src/local_audio_track.cpp
338+
src/local_data_track.cpp
334339
src/remote_audio_track.cpp
340+
src/remote_data_track.cpp
335341
src/room.cpp
336342
src/room_proto_converter.cpp
337343
src/room_proto_converter.h
@@ -683,10 +689,6 @@ install(FILES
683689
# Build the LiveKit C++ bridge before examples (human_robot depends on it)
684690
add_subdirectory(bridge)
685691

686-
# ---- Examples ----
687-
# add_subdirectory(examples)
688-
689-
690692
if(LIVEKIT_BUILD_EXAMPLES)
691693
add_subdirectory(examples)
692694
endif()

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,35 @@ 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/livekit_integration_tests
455+
valgrind --leak-check=full ./build-debug/bin/livekit_stress_tests
456+
```
457+
458+
# Running locally
459+
1. Install the livekit-server
460+
https://docs.livekit.io/transport/self-hosting/local/
461+
462+
Start the livekit-server with data tracks enabled:
463+
```bash
464+
LIVEKIT_CONFIG="enable_data_tracks: true" livekit-server --dev
465+
```
466+
467+
```bash
468+
# generate tokens, do for all participants
469+
lk token create \
470+
--api-key devkey \
471+
--api-secret secret \
472+
-i robot \
473+
--join \
474+
--valid-for 99999h \
475+
--room robo_room \
476+
--grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}'
477+
```
478+
450479
<!--BEGIN_REPO_NAV-->
451480
<br/><table>
452481
<thead><tr><th colspan="2">LiveKit Ecosystem</th></tr></thead>

client-sdk-rust

examples/CMakeLists.txt

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ set(EXAMPLES_ALL
4343
SimpleJoystickSender
4444
SimpleJoystickReceiver
4545
SimpleDataStream
46+
PingPongPing
47+
PingPongPong
48+
HelloLivekitSender
49+
HelloLivekitReceiver
4650
LoggingLevelsBasicUsage
4751
LoggingLevelsCustomSinks
4852
BridgeRobot
@@ -242,6 +246,77 @@ add_custom_command(
242246
$<TARGET_FILE_DIR:SimpleDataStream>/data
243247
)
244248

249+
# --- ping_pong (request/response latency measurement over data tracks) ---
250+
251+
add_library(ping_pong_support STATIC
252+
ping_pong/json_converters.cpp
253+
ping_pong/json_converters.h
254+
ping_pong/constants.h
255+
ping_pong/messages.h
256+
ping_pong/utils.h
257+
)
258+
259+
target_include_directories(ping_pong_support PUBLIC
260+
${CMAKE_CURRENT_SOURCE_DIR}/ping_pong
261+
)
262+
263+
target_link_libraries(ping_pong_support
264+
PRIVATE
265+
nlohmann_json::nlohmann_json
266+
)
267+
268+
add_executable(PingPongPing
269+
ping_pong/ping.cpp
270+
)
271+
272+
target_include_directories(PingPongPing PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})
273+
274+
target_link_libraries(PingPongPing
275+
PRIVATE
276+
ping_pong_support
277+
livekit
278+
spdlog::spdlog
279+
)
280+
281+
add_executable(PingPongPong
282+
ping_pong/pong.cpp
283+
)
284+
285+
target_include_directories(PingPongPong PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})
286+
287+
target_link_libraries(PingPongPong
288+
PRIVATE
289+
ping_pong_support
290+
livekit
291+
spdlog::spdlog
292+
)
293+
294+
# --- hello_livekit (minimal synthetic video + data publish / subscribe) ---
295+
296+
add_executable(HelloLivekitSender
297+
hello_livekit/sender.cpp
298+
)
299+
300+
target_include_directories(HelloLivekitSender PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})
301+
302+
target_link_libraries(HelloLivekitSender
303+
PRIVATE
304+
livekit
305+
spdlog::spdlog
306+
)
307+
308+
add_executable(HelloLivekitReceiver
309+
hello_livekit/receiver.cpp
310+
)
311+
312+
target_include_directories(HelloLivekitReceiver PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})
313+
314+
target_link_libraries(HelloLivekitReceiver
315+
PRIVATE
316+
livekit
317+
spdlog::spdlog
318+
)
319+
245320
# --- bridge_human_robot examples (robot + human; use livekit_bridge and SDL3) ---
246321

247322
add_executable(BridgeRobot
@@ -398,4 +473,4 @@ if(UNIX)
398473
foreach(EXAMPLE ${EXAMPLES_BRIDGE})
399474
add_dependencies(${EXAMPLE} copy_bridge_to_bin)
400475
endforeach()
401-
endif()
476+
endif()

examples/bridge_human_robot/human.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ static void renderFrame(const livekit::VideoFrame &frame) {
103103
static std::atomic<uint64_t> g_audio_frames{0};
104104
static std::atomic<uint64_t> g_video_frames{0};
105105

106+
constexpr const char *kRobotMicTrackName = "robot-mic";
107+
constexpr const char *kRobotSimAudioTrackName = "robot-sim-audio";
108+
constexpr const char *kRobotCamTrackName = "robot-cam";
109+
constexpr const char *kRobotSimVideoTrackName = "robot-sim-frame";
110+
106111
int main(int argc, char *argv[]) {
107112
// ----- Parse args / env -----
108113
bool no_audio = false;
@@ -232,7 +237,7 @@ int main(int argc, char *argv[]) {
232237

233238
// ----- Set audio callbacks using Room::setOnAudioFrameCallback -----
234239
room->setOnAudioFrameCallback(
235-
"robot", livekit::TrackSource::SOURCE_MICROPHONE,
240+
"robot", kRobotMicTrackName,
236241
[playAudio, no_audio](const livekit::AudioFrame &frame) {
237242
g_audio_frames.fetch_add(1, std::memory_order_relaxed);
238243
if (!no_audio && g_selected_source.load(std::memory_order_relaxed) ==
@@ -242,7 +247,7 @@ int main(int argc, char *argv[]) {
242247
});
243248

244249
room->setOnAudioFrameCallback(
245-
"robot", livekit::TrackSource::SOURCE_SCREENSHARE_AUDIO,
250+
"robot", kRobotSimAudioTrackName,
246251
[playAudio, no_audio](const livekit::AudioFrame &frame) {
247252
g_audio_frames.fetch_add(1, std::memory_order_relaxed);
248253
if (!no_audio && g_selected_source.load(std::memory_order_relaxed) ==
@@ -253,7 +258,7 @@ int main(int argc, char *argv[]) {
253258

254259
// ----- Set video callbacks using Room::setOnVideoFrameCallback -----
255260
room->setOnVideoFrameCallback(
256-
"robot", livekit::TrackSource::SOURCE_CAMERA,
261+
"robot", kRobotCamTrackName,
257262
[](const livekit::VideoFrame &frame, std::int64_t /*timestamp_us*/) {
258263
g_video_frames.fetch_add(1, std::memory_order_relaxed);
259264
if (g_selected_source.load(std::memory_order_relaxed) ==
@@ -263,7 +268,7 @@ int main(int argc, char *argv[]) {
263268
});
264269

265270
room->setOnVideoFrameCallback(
266-
"robot", livekit::TrackSource::SOURCE_SCREENSHARE,
271+
"robot", kRobotSimVideoTrackName,
267272
[](const livekit::VideoFrame &frame, std::int64_t /*timestamp_us*/) {
268273
g_video_frames.fetch_add(1, std::memory_order_relaxed);
269274
if (g_selected_source.load(std::memory_order_relaxed) ==
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
/// Subscribes to the sender's camera video and data track. Run
18+
/// HelloLivekitSender first; use the identity it prints, or the sender's known
19+
/// participant name.
20+
///
21+
/// Usage:
22+
/// HelloLivekitReceiver <ws-url> <receiver-token> <sender-identity>
23+
///
24+
/// Or via environment variables:
25+
/// LIVEKIT_URL, LIVEKIT_RECEIVER_TOKEN, LIVEKIT_SENDER_IDENTITY
26+
27+
#include "livekit/livekit.h"
28+
29+
#include <atomic>
30+
#include <chrono>
31+
#include <csignal>
32+
#include <cstdlib>
33+
#include <thread>
34+
35+
using namespace livekit;
36+
37+
constexpr const char *kDataTrackName = "app-data";
38+
constexpr const char *kVideoTrackName = "camera0";
39+
40+
std::atomic<bool> g_running{true};
41+
42+
void handleSignal(int) { g_running.store(false); }
43+
44+
std::string getenvOrEmpty(const char *name) {
45+
const char *v = std::getenv(name);
46+
return v ? std::string(v) : std::string{};
47+
}
48+
49+
int main(int argc, char *argv[]) {
50+
std::string url = getenvOrEmpty("LIVEKIT_URL");
51+
std::string receiver_token = getenvOrEmpty("LIVEKIT_RECEIVER_TOKEN");
52+
std::string sender_identity = getenvOrEmpty("LIVEKIT_SENDER_IDENTITY");
53+
54+
if (argc >= 4) {
55+
url = argv[1];
56+
receiver_token = argv[2];
57+
sender_identity = argv[3];
58+
}
59+
60+
if (url.empty() || receiver_token.empty() || sender_identity.empty()) {
61+
LK_LOG_ERROR("Usage: HelloLivekitReceiver <ws-url> <receiver-token> "
62+
"<sender-identity>\n"
63+
" or set LIVEKIT_URL, LIVEKIT_RECEIVER_TOKEN, "
64+
"LIVEKIT_SENDER_IDENTITY");
65+
return 1;
66+
}
67+
68+
std::signal(SIGINT, handleSignal);
69+
#ifdef SIGTERM
70+
std::signal(SIGTERM, handleSignal);
71+
#endif
72+
73+
livekit::initialize(livekit::LogLevel::Info, livekit::LogSink::kConsole);
74+
75+
auto room = std::make_unique<Room>();
76+
RoomOptions options;
77+
options.auto_subscribe = true;
78+
options.dynacast = false;
79+
80+
if (!room->Connect(url, receiver_token, options)) {
81+
LK_LOG_ERROR("[receiver] Failed to connect");
82+
livekit::shutdown();
83+
return 1;
84+
}
85+
86+
LocalParticipant *lp = room->localParticipant();
87+
assert(lp);
88+
89+
LK_LOG_INFO("[receiver] Connected as identity='{}' room='{}'; subscribing "
90+
"to sender identity='{}'",
91+
lp->identity(), room->room_info().name, sender_identity);
92+
93+
int video_frame_count = 0;
94+
room->setOnVideoFrameCallback(
95+
sender_identity, kVideoTrackName,
96+
[&video_frame_count](const VideoFrame &frame, std::int64_t timestamp_us) {
97+
const auto ts_ms =
98+
std::chrono::duration<double, std::milli>(timestamp_us).count();
99+
const int n = video_frame_count++;
100+
if (n % 10 == 0) {
101+
LK_LOG_INFO("[receiver] Video frame #{} {}x{} ts_ms={}", n,
102+
frame.width(), frame.height(), ts_ms);
103+
}
104+
});
105+
106+
int data_frame_count = 0;
107+
room->addOnDataFrameCallback(
108+
sender_identity, kDataTrackName,
109+
[&data_frame_count](const std::vector<std::uint8_t> &payload,
110+
std::optional<std::uint64_t> user_ts) {
111+
const int n = data_frame_count++;
112+
if (n % 10 == 0) {
113+
LK_LOG_INFO("[receiver] Data frame #{}", n);
114+
}
115+
});
116+
117+
LK_LOG_INFO("[receiver] Listening for video track '{}' + data track '{}'; "
118+
"Ctrl-C to exit",
119+
kVideoTrackName, kDataTrackName);
120+
121+
while (g_running.load()) {
122+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
123+
}
124+
125+
LK_LOG_INFO("[receiver] Shutting down");
126+
room.reset();
127+
128+
livekit::shutdown();
129+
return 0;
130+
}

0 commit comments

Comments
 (0)