Enable publishing FlexFEC - #1424
chenosaurus wants to merge 10 commits into
Conversation
chenosaurus
commented
Sep 11, 2026
- add options to enable FlexFEC at room level
- add options to TrackPublishOptions to configure FEC level
…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
Changeset incompleteThis PR's changeset is missing version bumps for packages that are affected by the change. The following packages still require a bump:
Already covered:
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 If this change doesn't require a version bump, add the |
There was a problem hiding this comment.
Devin Review found 1 potential issue.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| 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; |
There was a problem hiding this comment.
🟡 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
🟡 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_enabledand 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 contextualexpect(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 RoomOptionsnever reads a corresponding protobuf field, so FFI clients always connect withfec_enabled == falseand 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.
| use livekit::options::{FecProtection, TrackPublishOptions}; | ||
|
|
||
| let mut options = RoomOptions::default(); | ||
| options.fec_enabled = true; | ||
| let (room, events) = Room::connect(&url, &token, options).await?; |
There was a problem hiding this comment.
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() |
| ), | ||
| video_encoder: video_encoder_from_proto(opts.video_encoder) | ||
| .unwrap_or(default_publish_options.video_encoder), | ||
| fec: default_publish_options.fec, |
| 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>
|
|
Co-authored-by: chenosaurus <509698+chenosaurus@users.noreply.github.com>