Skip to content

Enable publishing FlexFEC - #1424

Open
chenosaurus wants to merge 10 commits into
mainfrom
dc/feature/fec
Open

chenosaurus wants to merge 10 commits into
mainfrom
dc/feature/fec

Conversation

@chenosaurus

Copy link
Copy Markdown
Contributor
  • add options to enable FlexFEC at room level
  • add options to TrackPublishOptions to configure FEC level

chenosaurus and others added 8 commits June 12, 2026 12:27
…ions

- webrtc-sys: FixedRateFecController replaces FecControllerDefault for video
  send streams, requesting constant protection (rate/frames runtime-tunable
  via atomics) instead of ramping only after observed loss. Field trials are
  injected into the factory Environment (set_field_trials +
  LK_WEBRTC_FIELD_TRIALS env var).
- libwebrtc crate: native::fec_controller wrapper (config, field trials,
  send-side FEC rate metrics from the RTP layer).
- livekit crate: RoomOptions::flexfec (FlexFecOptions{protection_percent,
  max_fec_frames, bursty_mask}), Room::set_flexfec_options,
  Room::fec_sender_stats. Publish codec preferences now retain rtx and
  flexfec-03 when FlexFEC is configured (they were dropped, so FEC could
  never negotiate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fec_publisher publishes a synthetic animated VP8 track (optionally FlexFEC
protected) and logs 1 Hz send stats incl. the FEC send rate; fec_subscriber
decodes all video tracks and logs per-track loss/FEC/freeze stats. Both
write CSV consumed by the livekit flexfec-harness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Publisher attaches PacketTrailerFeatures{user_timestamp,frame_id} and stamps
each frame's frame_metadata with wall-clock capture time + frame id.
Subscriber wires the receiver trailer handler via subscribe_timing_events(),
reads frame_metadata off each decoded frame to compute capture-to-decode
latency, and adds jitter_buffer_delay / total_processing_delay / e2e latency
columns to its CSV. These expose the latency FEC saves over NACK (NACK waits
a retransmit RTT; FEC repairs in place).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	Cargo.lock
#	livekit/src/room/mod.rs
#	livekit/src/rtc_engine/lk_runtime.rs
#	webrtc-sys/build.rs
#	webrtc-sys/src/peer_connection_factory.cpp
@chenosaurus
chenosaurus requested review from ladvoc and xianshijing-lk and removed request for cloudwebrtc and ladvoc September 11, 2026 00:37
@github-actions

Copy link
Copy Markdown
Contributor

Changeset incomplete

This PR's changeset is missing version bumps for packages that are affected by the change. The following packages still require a bump:

  • livekit-capture
  • livekit-ffi

Already covered:

  • libwebrtc (patch)
  • livekit (minor)
  • webrtc-sys (patch)

A package must be bumped when its own files change, and whenever a package it depends on is bumped (so downstream consumers get a matching release).

Click here to create a changeset for the missing packages

The link pre-populates a changeset file with patch bumps for the missing packages. You can also add them to your existing changeset. Edit the bump types as needed before committing.

If this change doesn't require a version bump, add the internal label 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 found 1 potential issue.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment on lines +55 to +61
bool FecGlobalState::SetFieldTrials(const std::string& field_trials) {
if (factory_created_.load()) {
return false;
}
std::lock_guard<std::mutex> lock(mutex_);
field_trials_ = field_trials;
return true;

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.

🟡 Accepted field trials never apply

When set_field_trials races with factory creation, it can return true after the factory consumed its configuration. MarkFactoryCreated runs outside the locked snapshot, so those trials never take effect.

Learn more

Factory creation reads the configured trial string in CreateEnvironment, then marks the factory created later in its constructor. SetFieldTrials checks factory_created_ before taking mutex_, so a concurrent setter can pass the check after the snapshot or wait until after it, write a value, and return success. No later factory reads that value because the peer connection factory is process-wide.

Example: Thread A snapshots an empty trial string. Thread B then stores WebRTC-Example/Enabled/ and receives true. Thread A marks the factory created, but its environment lacks that trial.

Recommended fix: Synchronize the availability check, trial snapshot, and transition to the created state with the same mutex. Mark configuration closed atomically while taking the snapshot used by CreateEnvironment, before releasing the lock.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copilot AI 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.

🟡 Changes recommended

Unresolved doctest, FFI propagation, wasm behavior, and example clock-handling issues remain.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds native FlexFEC publishing with room-level enablement, per-track protection levels, metrics, documentation, and test examples.

Changes:

  • Adds FEC controller and WebRTC encoder/RTP sender integration.
  • Adds RoomOptions::fec_enabled and per-track FEC configuration.
  • Adds FEC examples, documentation, build wiring, and changeset metadata.
File summaries
File Summary / review status
webrtc-sys/src/video_encoder_factory.cpp Applies FEC encoder configuration.
webrtc-sys/src/rtp_sender.rs Exposes sender FEC options.
webrtc-sys/src/rtp_sender.cpp Selects encoders with FEC settings.
webrtc-sys/src/peer_connection_factory.cpp Enables FlexFEC trials and controller support.
webrtc-sys/src/lib.rs Registers the FEC bridge module.
webrtc-sys/src/fec_controller.rs Defines FEC bridge types and metrics.
webrtc-sys/src/fec_controller.cpp Implements FEC control and metrics.
webrtc-sys/include/livekit/rtp_sender.h Declares sender option APIs.
webrtc-sys/include/livekit/fec_controller.h Declares FEC controller interfaces.
webrtc-sys/build.rs Builds FEC bridge sources.
README.md Documents Rust FlexFEC publishing.
livekit/src/rtc_engine/rtc_session.rs Applies FEC options during publishing. Moderate: wasm32 can negotiate FEC while ignoring requested settings.
livekit/src/rtc_engine/mod.rs Carries room FEC configuration.
livekit/src/room/options.rs Adds protection levels and track options.
livekit/src/room/mod.rs Adds room configuration and metrics APIs. Moderate: protobuf room options do not map fec_enabled.
livekit/README.md Documents the SDK FlexFEC API. Critical: the included doctest is incomplete and will fail compilation.
livekit-ffi/src/conversion/room.rs Converts FFI room and track options. Moderate: FEC settings are discarded without corresponding protocol fields and mappings.
libwebrtc/src/rtp_sender.rs Exposes sender configuration.
libwebrtc/src/native/rtp_sender.rs Bridges native sender configuration.
libwebrtc/src/native/mod.rs Exports native FEC support.
libwebrtc/src/native/fec_controller.rs Provides native FEC metrics and configuration.
libwebrtc/src/lib.rs Re-exports native FEC support.
examples/fec_test/src/subscriber.rs Implements FEC receive statistics.
examples/fec_test/src/publisher.rs Publishes configurable FEC video.
examples/fec_test/src/common.rs Provides shared example utilities. Moderate: handle fallible clock conversion instead of unwrapping.
examples/fec_test/Cargo.toml Defines the FEC test package.
Cargo.toml Adds the example workspace member.
Cargo.lock Locks the example package.
.changeset/per_track_fec.md Documents release impacts.
Review details

Suppressed comments (2)

examples/fec_test/src/common.rs:44

  • duration_since(UNIX_EPOCH) can fail when the system clock is before the epoch, so this utility can panic during the example run. Return a contextual expect (or handle the error) instead of unwrapping the fallible clock conversion.
    SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros() as u64

livekit/src/room/mod.rs:596

  • The room-level flag is forwarded only from the native RoomOptions; impl From<proto::RoomOptions> for RoomOptions never reads a corresponding protobuf field, so FFI clients always connect with fec_enabled == false and cannot enable publishing FlexFEC. Add the FFI schema/conversion mapping for this new option.
                fec_enabled: options.fec_enabled,
  • Files reviewed: 28/29 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread livekit/README.md
Comment on lines +14 to +18
use livekit::options::{FecProtection, TrackPublishOptions};

let mut options = RoomOptions::default();
options.fec_enabled = true;
let (room, events) = Room::connect(&url, &token, options).await?;

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.

Addressed in 16d3a19 by marking the README FlexFEC example as an ignored Rust doctest.

}

pub fn unix_time_secs() -> f64 {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64()
Comment thread livekit-ffi/src/conversion/room.rs Outdated
),
video_encoder: video_encoder_from_proto(opts.video_encoder)
.unwrap_or(default_publish_options.video_encoder),
fec: default_publish_options.fec,

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.

Addressed in edf9beb.

Comment on lines +1985 to +1989
if self.options.fec_enabled {
for codec in unmatched {
let mime_type = codec.mime_type.to_lowercase();
if mime_type == "video/flexfec-03" || mime_type == "video/rtx" {
matched.push(codec);
Co-authored-by: chenosaurus <509698+chenosaurus@users.noreply.github.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ chenosaurus
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

Co-authored-by: chenosaurus <509698+chenosaurus@users.noreply.github.com>
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.

4 participants