-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: bitrate control with Twcc and Remb #265
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -15,10 +15,11 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
transport::{LocalTrackEvent, LocalTrackId, RemoteTrackEvent, RemoteTrackId, TransportEvent, TransportState, TransportStats}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use self::{local_track::EndpointLocalTrack, remote_track::EndpointRemoteTrack}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use self::{bitrate_allocator::BitrateAllocator, local_track::EndpointLocalTrack, remote_track::EndpointRemoteTrack}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use super::{middleware::EndpointMiddleware, EndpointEvent, EndpointReq, EndpointReqId, EndpointRes}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use super::{middleware::EndpointMiddleware, EndpointCfg, EndpointEvent, EndpointReq, EndpointReqId, EndpointRes}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
mod bitrate_allocator; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
mod local_track; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
mod remote_track; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -37,6 +38,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pub struct EndpointInternal { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cfg: EndpointCfg, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
state: TransportState, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
wait_join: Option<(RoomId, PeerId, PeerMeta, RoomInfoPublish, RoomInfoSubscribe)>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
joined: Option<(ClusterRoomHash, RoomId, PeerId)>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -47,11 +49,13 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_middlewares: Vec<Box<dyn EndpointMiddleware>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
queue: VecDeque<InternalOutput>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
switcher: TaskSwitcher, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bitrate_allocator: BitrateAllocator, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
impl EndpointInternal { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pub fn new() -> Self { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pub fn new(cfg: EndpointCfg) -> Self { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Self { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cfg, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
state: TransportState::Connecting, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
wait_join: None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
joined: None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -62,10 +66,25 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_middlewares: Default::default(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
queue: Default::default(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
switcher: TaskSwitcher::new(2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bitrate_allocator: BitrateAllocator::default(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pub fn on_tick<'a>(&mut self, now: Instant) -> Option<InternalOutput> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.bitrate_allocator.on_tick(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(out) = self.bitrate_allocator.pop_output() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
match out { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bitrate_allocator::Output::SetTrackBitrate(track, bitrate) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(index) = self.local_tracks_id.get1(&track) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let out = self.local_tracks.on_event(now, *index, local_track::Input::LimitBitrate(bitrate))?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(out) = self.convert_local_track_output(now, track, out) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Some(out); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+74
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method Consider adding tests to cover the scenarios where bitrate adjustments are made based on the allocator's output. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
loop { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
match self.switcher.looper_current(now)?.try_into().ok()? { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TaskType::LocalTracks => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -124,6 +143,12 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TransportEvent::RemoteTrack(track, event) => self.on_transport_remote_track(now, track, event), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TransportEvent::LocalTrack(track, event) => self.on_transport_local_track(now, track, event), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TransportEvent::Stats(stats) => self.on_transport_stats(now, stats), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TransportEvent::EgressBitrateEstimate(bitrate) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let bitrate2 = bitrate.min(self.cfg.max_egress_bitrate as u64); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
log::debug!("[EndpointInternal] limit egress bitrate {bitrate2}, rewrite from {bitrate}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.bitrate_allocator.set_egress_bitrate(bitrate2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+146
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The handling of Would you like assistance in creating test cases for this event handling? |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -220,10 +245,10 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fn on_transport_local_track<'a>(&mut self, now: Instant, track: LocalTrackId, event: LocalTrackEvent) -> Option<InternalOutput> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if event.need_create() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(kind) = event.need_create() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
log::info!("[EndpointInternal] create local track {:?}", track); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let room = self.joined.as_ref().map(|j| j.0.clone()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let index = self.local_tracks.add_task(EndpointLocalTrack::new(room)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let index = self.local_tracks.add_task(EndpointLocalTrack::new(kind, room)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+248
to
+251
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method It's important to ensure that track creation is handled correctly. Shall I help by adding some tests for this functionality? |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.local_tracks_id.insert(track, index); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let index = self.local_tracks_id.get1(&track)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -333,6 +358,22 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
local_track::Output::Event(event) => Some(InternalOutput::Event(EndpointEvent::LocalMediaTrack(id, event))), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
local_track::Output::Cluster(room, control) => Some(InternalOutput::Cluster(room, ClusterEndpointControl::LocalTrack(id, control))), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
local_track::Output::RpcRes(req_id, res) => Some(InternalOutput::RpcRes(req_id, EndpointRes::LocalTrack(id, res))), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
local_track::Output::DesiredBitrate(bitrate) => Some(InternalOutput::Event(EndpointEvent::BweConfig { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
current: bitrate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
desired: bitrate + 100_000.max(bitrate * 1 / 5), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
local_track::Output::Started(kind, priority) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if kind.is_video() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.bitrate_allocator.set_video_track(id, priority); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
local_track::Output::Stopped(kind) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if kind.is_video() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.bitrate_allocator.del_video_track(id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+361
to
+376
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method - desired: bitrate + 100_000.max(bitrate * 1 / 5),
+ desired: bitrate + 100_000.max(bitrate / 5), This change ensures that the desired bitrate calculation is meaningful by correcting the division operation. Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
use derivative::Derivative; | ||
use std::collections::VecDeque; | ||
|
||
use media_server_protocol::endpoint::TrackPriority; | ||
|
||
use crate::transport::LocalTrackId; | ||
|
||
const DEFAULT_BITRATE_BPS: u64 = 800_000; | ||
|
||
#[derive(Debug, PartialEq, Eq)] | ||
pub enum Output { | ||
SetTrackBitrate(LocalTrackId, u64), | ||
} | ||
|
||
#[derive(Derivative)] | ||
#[derivative(Default)] | ||
pub struct BitrateAllocator { | ||
changed: bool, | ||
#[derivative(Default(value = "DEFAULT_BITRATE_BPS"))] | ||
egress_bitrate: u64, | ||
tracks: smallmap::Map<LocalTrackId, TrackPriority>, | ||
queue: VecDeque<Output>, | ||
} | ||
|
||
impl BitrateAllocator { | ||
pub fn on_tick(&mut self) { | ||
self.process(); | ||
} | ||
|
||
pub fn set_egress_bitrate(&mut self, bitrate: u64) { | ||
self.egress_bitrate = bitrate; | ||
self.changed = true; | ||
} | ||
|
||
pub fn set_video_track(&mut self, track: LocalTrackId, priority: TrackPriority) { | ||
self.tracks.insert(track, priority); | ||
self.changed = true; | ||
} | ||
|
||
pub fn del_video_track(&mut self, track: LocalTrackId) { | ||
self.tracks.remove(&track); | ||
self.changed = true; | ||
} | ||
|
||
pub fn pop_output(&mut self) -> Option<Output> { | ||
self.queue.pop_front() | ||
} | ||
|
||
fn process(&mut self) { | ||
if !self.changed { | ||
return; | ||
} | ||
self.changed = false; | ||
let mut sum = TrackPriority(0); | ||
for (_track, priority) in self.tracks.iter() { | ||
sum = sum + *priority; | ||
Check warning Code scanning / clippy manual implementation of an assign operation Warning
manual implementation of an assign operation
|
||
} | ||
|
||
if *(sum.as_ref()) != 0 { | ||
for (track, priority) in self.tracks.iter() { | ||
self.queue.push_back(Output::SetTrackBitrate(*track, (self.egress_bitrate * priority.0 as u64) / sum.0 as u64)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::{BitrateAllocator, Output, DEFAULT_BITRATE_BPS}; | ||
|
||
#[test] | ||
fn single_source() { | ||
let mut allocator = BitrateAllocator::default(); | ||
allocator.set_video_track(0.into(), 1.into()); | ||
|
||
allocator.on_tick(); | ||
assert_eq!(allocator.pop_output(), Some(Output::SetTrackBitrate(0.into(), DEFAULT_BITRATE_BPS))); | ||
} | ||
|
||
#[test] | ||
fn multi_source() { | ||
let mut allocator = BitrateAllocator::default(); | ||
allocator.set_video_track(0.into(), 1.into()); | ||
allocator.set_video_track(1.into(), 3.into()); | ||
|
||
allocator.on_tick(); | ||
assert_eq!(allocator.pop_output(), Some(Output::SetTrackBitrate(0.into(), DEFAULT_BITRATE_BPS * 1 / 4))); | ||
assert_eq!(allocator.pop_output(), Some(Output::SetTrackBitrate(1.into(), DEFAULT_BITRATE_BPS * 3 / 4))); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor for
Endpoint
is not covered by unit tests. Consider adding tests to cover this critical initialization logic.Would you like me to help by writing some unit tests for this method?