Skip to content

livekit_bridge: An ergonomic interface into the Livekit C++ SDK#58

Open
stephen-derosa wants to merge 1 commit intolivekit:mainfrom
stephen-derosa:feat/livekit_cpp_bridge.sdd
Open

livekit_bridge: An ergonomic interface into the Livekit C++ SDK#58
stephen-derosa wants to merge 1 commit intolivekit:mainfrom
stephen-derosa:feat/livekit_cpp_bridge.sdd

Conversation

@stephen-derosa
Copy link

Overview

An ergonomic library providing simple usage of the C++ SDK.

Building

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

Testing

The bridge/examples/ directory has simulated human and robot tests. There are 4 files:

  • robot.cpp: publishes video and audio from a webcam and microphone. This requires a webcam and microphone to be available.
  • robot_stub.cpp: publishes stubbed audio and video. This exists to exemplify simplicity.
  • human.cpp: receives and renders video to the screen, receives and plays audio through the speaker.
  • human_stub.cpp: receives video and audio and prints that it was received. This exists to exemplify simplicity.

These have been tested manually.

Unit tests

  1. CallbackKey hashing/equality,
  2. BridgeAudioTrack/BridgeVideoTrack state management, and
  3. LiveKitBridge pre-connection behaviour (callback registration, error handling).

Limitations

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

  • E2EE configuration
  • RPC / data channels / data tracks
  • Simulcast tuning
  • Video format selection (RGBA is the default; no format option yet)
  • Custom RoomOptions or TrackPublishOptions

For advanced use cases, use the full client-sdk-cpp API directly, or expand the bridge to support your use case.

TODOs before merge

  • ❓ what other tests should we require here to ensure we have a good infrastructure moving forward?
  • ❓ Other general LiveKit reqs?

@stephen-derosa stephen-derosa self-assigned this Feb 13, 2026
@stephen-derosa stephen-derosa added the enhancement New feature or request label Feb 13, 2026
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Collaborator

@xianshijing-lk xianshijing-lk left a comment

Choose a reason for hiding this comment

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

Good work, that is a large PR, I will need more time to review it.

some initial questions to get a clearer picture on the design.

@@ -0,0 +1,272 @@
/*
Copy link
Collaborator

Choose a reason for hiding this comment

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

please add livekit copyright, and add it to all the new files.

#include <cstring>
#include <iostream>
#include <mutex>
#include <vector>
Copy link
Collaborator

Choose a reason for hiding this comment

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

run clang format, you can hook it up to git commit:

Clang format
CPP SDK is using clang C++ format

brew install clang-format


// Check for a new video frame
{
std::lock_guard<std::mutex> lock(g_latest_video.mutex);
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we try avoiding locking while doing the heavy duty things ?

I think this can be achieved by having a local_pixel, and just swap the buffer, like
std::vectorstd::uint8_t local_pixels;
int fw = 0, fh = 0;
bool have_frame = false;
{
std::lock_guardstd::mutex lock(g_latest_video.mutex);
if (g_latest_video.dirty && g_latest_video.width > 0 && g_latest_video.height > 0) {
fw = g_latest_video.width;
fh = g_latest_video.height;
local_pixels.swap(g_latest_video.data); // avoid copy
g_latest_video.dirty = false;
have_frame = true;
}
}

and use the local_pixel and other local variables for rendering.

@@ -0,0 +1,133 @@
/*
* Human example -- receives audio and video frames from a robot in a
Copy link
Collaborator

Choose a reason for hiding this comment

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

copy right

options.auto_subscribe = true;
options.dynacast = false;

bool result = room_->Connect(url, token, options);
Copy link
Collaborator

Choose a reason for hiding this comment

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

In general, I think it is not recommended to call Connect() while holding the std::lock_guardstd::mutex lock(mutex_);

maybe it is better to create a local room, like
auto room = std::make_uniquelivekit::Room();
set things up, then reassign it to room_

* @param token Access token for authentication.
* @return true if connection succeeded.
*/
bool connect(const std::string &url, const std::string &token);
Copy link
Collaborator

Choose a reason for hiding this comment

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

curiously, don't you need to expose the room option via the connect() function ?

* @param source Track source (e.g. SOURCE_MICROPHONE).
* @param callback Function to invoke per audio frame.
*/
void registerOnAudioFrame(const std::string &participant_identity,
Copy link
Collaborator

Choose a reason for hiding this comment

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

is it register** the right word ?

I wonder what will happen if users register the same callback or multiple callbacks here ?
do we support those use cases ?

if not, can we make it a bit less ambiguous ?

* If a reader thread is active for this (identity, source), it is
* stopped and joined.
*/
void unregisterOnAudioFrame(const std::string &participant_identity,
Copy link
Collaborator

Choose a reason for hiding this comment

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

it looks like it should "clear" rather than unregister ?

void startAudioReader(const CallbackKey &key,
const std::shared_ptr<livekit::Track> &track,
AudioFrameCallback cb);
void startVideoReader(const CallbackKey &key,
Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm. this is a bit worrying, are we saying that we will have multiple render threads if we want to render multiple tracks ?

* mic->mute();
* mic.reset(); // unpublishes
*/
class BridgeAudioTrack {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are these BridgeAudioTrack, BridgeVideoTrack public interface to our developers ?

Note, from the code, these track implementations are not thread safe on its own, that says, if users want to get access to these bridge track, they are not protected

released_ = true;

// Unpublish the track from the room
if (participant_ && publication_) {
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 read, the BridgeVideoTrack can outlive the LivekitBridge::disconnect(), like that can be destruct after calling livekitBridge::disconnect().
In that case, how does our code guarantee that the it will not call into the participant_ ?

}

// Create room and delegate
room_ = std::make_unique<livekit::Room>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit, should you add an assert to make sure room_ is nullptr here ?

{
std::lock_guard<std::mutex> lock(mutex_);

if (!connected_) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it is wrong to return here.

For instance, if the connect() go thru the steps and fail at bool result = room_->Connect(url, token, options);

the connected_ is false, but you never call livekit::shutdown(); to clear up things

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.

3 participants