[Enhancement]Extend SimulcastEncoderAdapter to support adaptive streaming#90
Conversation
|
Caution Review failedFailed to post review comments 📝 WalkthroughWalkthroughThis PR introduces simulcast layer adaptation for quality scaling, optimizes frame crypto buffer construction, forwards single-layer encoder characteristics through the simulcast adapter, adds desktop capture exports for macOS frameworks, updates iOS/macOS SDK wiring with privacy info and cryptor headers, and refactors test infrastructure with API signature and mock interface updates. ChangesSimulcast Layer Adaptation and Quality Scaler Control
Single-Layer Encoder Info Forwarding
Frame Crypto Buffer Optimization and API Updates
Desktop Capture Platform Abstraction and macOS Wiring
iOS and macOS SDK Framework Wiring
Test Infrastructure and Audio State Management
🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
This fixes quality scaler interference during simulcast transitions and ensures
the SimulcastEncoderAdapter (SEA) correctly reports the active encoder's runtime
adaptation hints when only one spatial layer is active.
Without this fix, the quality scaler remains active during simulcast and
continuously degrades the input resolution, causing all simulcast layer
dimensions to be derived from scaled-down frames (e.g. f=268x480 instead of
f=720x1280 for 720p capture). Additionally, the SEA was reporting aggregated
encoder info with
scaling_settings = kOffeven when only one layer was active,preventing the quality scaler from starting in single-active-layer mode (1:1 calls).
Problem
Three interrelated issues combined to produce broken quality adaptation behavior
during simulcast transitions:
Quality scaler active during simulcast. When transitioning from a 1:1
call to a group call (simulcast), the quality scaler was not disabled. It
continued to scale down the input resolution, and since simulcast layer
dimensions are computed from the input frame size, all layers (q, h, f) were
recomputed from the degraded input. This produced cascading downgrades
(quality:1 → quality:2 → quality:3 → quality:4) and eventually triggered
simulcast layer count reduction from 3 to 2.
SEA reporting aggregated info in single-active-layer mode. The
SimulcastEncoderAdapter only forwarded the active encoder's info when
stream_contexts_.size() == 1. At runtime, SEA can have multiple streamcontexts while only one layer is unpaused. In that state, SEA reported
scaling_settings = kOff, so the quality scaler could never start — even in1:1 mode where it's needed.
Conservative min_pixels_per_frame floor for iOS encoders. The ObjC
encoder bridge used the 2-argument
ScalingSettingsconstructor, inheritingkDefaultMinPixelsPerFrame = 320*180 = 57600. This prevented the qualityscaler from reducing resolution below ~180x320 (the quarter-resolution
simulcast layer for 720p), limiting adaptation range on constrained networks.
Root Cause
Quality scaler during simulcast:
ConfigureQualityScaler()inVideoStreamEncoderResourceManagerhad no awareness of whether simulcast wasactive. It would start/keep the quality scaler running whenever the encoder
reported QP thresholds or
is_quality_scaling_allowedwas set, regardless ofhow many simulcast layers were active.
SEA encoder info: The bug is in
SimulcastEncoderAdapter::GetEncoderInfo().The old logic treated "single stream context" as equivalent to "single active
layer", but those are not the same thing at runtime. Once SEA enters
multi-encoder mode, the number of stream contexts stays greater than one even if
only one layer is currently unpaused.
FindRequiredActiveLayers:
FindRequiredActiveLayers()inencoder_stream_factory.ccreturned the position of the first active layerinstead of the highest, which could discard upper active layers when lower ones
were inactive.
min_pixels_per_frame: The ObjC bridge in
objc_video_encoder_factory.mmused
ScalingSettings(low, high)which defaultsmin_pixels_per_frameto57600 — too conservative for iOS VideoToolbox encoders that can encode well
below that threshold.
Fix
1. Quality scaler suppression during simulcast
simulcast_active_flag toVideoStreamEncoderResourceManager.SetSimulcastActive(bool)is called fromReconfigureEncoder()based on thenumber of active simulcast layers (not configured streams — the config may
always specify 3 streams while the SFU controls activation).
ConfigureQualityScaler()now checks!simulcast_active_as an additionalcondition for
quality_scaling_allowed. When simulcast is active, the qualityscaler is stopped and removed.
2. Simulcast transition handling in VideoStreamEncoder
ReconfigureEncoder()now detects transitions between single-stream andsimulcast based on
num_active_layers > 1.scaler and calls
ResetAdaptationsForSimulcastChange()to clear accumulatedresolution restrictions. The video source then delivers full-resolution frames
for correct layer dimension computation.
quality scaler from a clean state (zero downgrades).
3. SEA single-active-layer encoder info forwarding
runtime-sensitive fields:
scaling_settings,supports_native_handle,has_trusted_rate_controller,is_hardware_accelerated,is_qp_trusted,resolution_bitrate_limits,min_qp,preferred_pixel_formats.implementation_name,fps_allocation,requested_resolution_alignment,apply_alignment_to_all_simulcast_layers.4. FindRequiredActiveLayers fix
FindRequiredActiveLayers()to return the position after the highestactive layer (not the first), ensuring all active layers are preserved in the
stream count.
5. Lower min_pixels_per_frame for iOS encoders
ScalingSettings(low, high, 90*160)instead of
ScalingSettings(low, high). This lowers the quality scaler floorfrom 57600 to 14400 pixels, allowing adaptation below the quarter-resolution
simulcast layer on constrained networks (e.g. 3G).
Why This Is Safe
state. Single-active-layer behavior (1:1 calls) is unchanged.
first frame after clearing is still degraded, the source delivers
full-resolution frames on the next frame, triggering a reconfigure with
correct dimensions.
pre-init
GetEncoderInfo(), bitrate allocation, or true multi-layeraggregation.
min_pixels_per_framechange only affects ObjC-bridged encoders (iOSVideoToolbox). The global
kDefaultMinPixelsPerFrameremains unchanged forsoftware and Android encoders.
Tests
Added regression coverage in:
video_stream_encoder_unittest.cc: quality scaler restrictions are clearedwhen active layers increase from 1 to multiple, and from 2 to 3.
simulcast_encoder_adapter_unittest.cc: forwarding active encoder info whenonly one layer is unpaused; restoring aggregated SEA behavior when a second
layer becomes active.
encoder_stream_factory_unittest.cc: stream count preservation for sparseactive patterns, highest-only active, lowest-only active.
Validation
Validated with on-device iOS testing:
dimensions match configured ratios from full capture resolution.
Summary by CodeRabbit
New Features
Bug Fixes
Refactor