Skip to content

perf(rtc-node): route audio stream events and capture callbacks by handle - #739

Open
cosmin-staicu wants to merge 1 commit into
livekit:mainfrom
UiPath:perf/route-ffi-events-by-handle
Open

cosmin-staicu wants to merge 1 commit into
livekit:mainfrom
UiPath:perf/route-ffi-events-by-handle

Conversation

@cosmin-staicu

Copy link
Copy Markdown

Every FFI event is emitted to every listener in the process. Each AudioStreamSource and each Room checks every other stream's audio frames, and each frame an AudioSource captures adds a waitFor listener that every other waiter tests until its callback arrives. Dispatch cost grows with the square of the number of concurrent streams. In a CPU profile of one process holding 25 calls, this fan-out was about 30% of the JS thread.

This PR routes the two high-volume event types directly:

  • audioStreamEvent goes to the listener registered for its stream handle.
  • captureAudioFrame goes to the waiter registered for its async id. A callback nothing waits for is still emitted, so waitFor callers keep working.

AudioStreamSource keeps its handle id as a plain bigint, because reading the native FfiHandle.handle getter crosses into native code on every event. Room.onFfiEvent now returns before taking its event lock for anything that is not a room event or an RPC invocation. processFfiEvent only acts on those two cases, and events buffered before connect were already discarded there.

FfiClient is not exported, so the new methods stay internal.

ffi_client.test.ts drives the routing through a mocked livekitInitialize. Three of its five cases fail on main.

…ndle

Every FFI event was emitted to every listener in the process. Each audio
stream and each room checked every other stream's frames, and every frame
an AudioSource captured added a waitFor listener that all other waiters
tested. Dispatch cost grew with the square of the concurrent streams; with
a few dozen calls in one process it was about a third of the JS thread.

Audio stream events now go to the listener registered for their stream
handle, and captureAudioFrame callbacks to the waiter for their async id.
A callback nothing waits for is still emitted, so waitFor callers keep
working. AudioStreamSource keeps its handle id as a plain bigint, because
reading the native FfiHandle.handle getter crosses into native code on
every event. Room.onFfiEvent returns before taking its event lock for
anything that is not a room event or an RPC invocation, the only cases
processFfiEvent acts on.

Signed-off-by: Cosmin Staicu <cosmin.staicu@uipath.com>
@changeset-bot

changeset-bot Bot commented Oct 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 02b9039

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@livekit/rtc-node Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant