diff --git a/node/src/DirectTransport.ts b/node/src/DirectTransport.ts index 34ac3789ca..4a6fc7bea7 100644 --- a/node/src/DirectTransport.ts +++ b/node/src/DirectTransport.ts @@ -166,6 +166,16 @@ export class DirectTransport extends 'setMaxOutgoingBitrate() not implemented in DirectTransport'); } + /** + * @override + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async setMinOutgoingBitrate(bitrate: number): Promise + { + throw new UnsupportedError( + 'setMinOutgoingBitrate() not implemented in DirectTransport'); + } + /** * Send RTCP packet. */ diff --git a/node/src/Transport.ts b/node/src/Transport.ts index fb65f2e4d2..13f76c9627 100644 --- a/node/src/Transport.ts +++ b/node/src/Transport.ts @@ -86,8 +86,8 @@ export type TransportTraceEventData = export type SctpState = 'new' | 'connecting' | 'connected' | 'failed' | 'closed'; -export type TransportEvents = -{ +export type TransportEvents = +{ routerclose: []; listenserverclose: []; trace: [TransportTraceEventData]; @@ -523,6 +523,19 @@ export class Transport + { + logger.debug('setMinOutgoingBitrate() [bitrate:%s]', bitrate); + + const reqData = { bitrate }; + + await this.channel.request( + 'transport.setMinOutgoingBitrate', this.internal.transportId, reqData); + } + /** * Create a Producer. */ diff --git a/node/src/tests/test-WebRtcTransport.ts b/node/src/tests/test-WebRtcTransport.ts index 2ad1b9f1ad..ae2b333ec7 100644 --- a/node/src/tests/test-WebRtcTransport.ts +++ b/node/src/tests/test-WebRtcTransport.ts @@ -347,7 +347,14 @@ test('webRtcTransport.setMaxIncomingBitrate() succeeds', async () => test('webRtcTransport.setMaxOutgoingBitrate() succeeds', async () => { - await expect(transport.setMaxIncomingBitrate(100000)) + await expect(transport.setMaxOutgoingBitrate(100000)) + .resolves + .toBeUndefined(); +}, 2000); + +test('webRtcTransport.setMinOutgoingBitrate() succeeds', async () => +{ + await expect(transport.setMinOutgoingBitrate(100000)) .resolves .toBeUndefined(); }, 2000); @@ -500,6 +507,10 @@ test('WebRtcTransport methods reject if closed', async () => .rejects .toThrow(Error); + await expect(transport.setMinOutgoingBitrate(200000)) + .rejects + .toThrow(Error); + await expect(transport.restartIce()) .rejects .toThrow(Error); diff --git a/rust/src/messages.rs b/rust/src/messages.rs index f933c18290..865954b592 100644 --- a/rust/src/messages.rs +++ b/rust/src/messages.rs @@ -595,6 +595,12 @@ request_response!( TransportSetMaxOutgoingBitrateRequest { bitrate: u32 }, ); +request_response!( + TransportId, + "transport.setMinOutgoingBitrate", + TransportSetMinOutgoingBitrateRequest { bitrate: u32 }, +); + request_response!( TransportId, "transport.restartIce", diff --git a/rust/src/router/transport.rs b/rust/src/router/transport.rs index 5ab9362558..e008955a1c 100644 --- a/rust/src/router/transport.rs +++ b/rust/src/router/transport.rs @@ -7,6 +7,7 @@ use crate::messages::{ TransportEnableTraceEventRequest, TransportGetStatsRequest, TransportProduceDataRequest, TransportProduceRequest, TransportSetMaxIncomingBitrateRequest, TransportSetMaxOutgoingBitrateRequest, + TransportSetMinOutgoingBitrateRequest, }; pub use crate::ortc::{ ConsumerRtpParametersError, RtpCapabilitiesError, RtpParametersError, RtpParametersMappingError, @@ -387,6 +388,12 @@ pub(super) trait TransportImpl: TransportGeneric { .await } + async fn set_min_outgoing_bitrate_impl(&self, bitrate: u32) -> Result<(), RequestError> { + self.channel() + .request(self.id(), TransportSetMinOutgoingBitrateRequest { bitrate }) + .await + } + async fn enable_trace_event_impl( &self, types: Vec, diff --git a/rust/src/router/webrtc_transport.rs b/rust/src/router/webrtc_transport.rs index 0ea5f47cdd..53a1ac08ca 100644 --- a/rust/src/router/webrtc_transport.rs +++ b/rust/src/router/webrtc_transport.rs @@ -794,6 +794,14 @@ impl WebRtcTransport { self.set_max_outgoing_bitrate_impl(bitrate).await } + /// Set minimum outgoing bitrate for media streams sent by the remote endpoint over this + /// transport. + pub async fn set_min_outgoing_bitrate(&self, bitrate: u32) -> Result<(), RequestError> { + debug!("set_min_outgoing_bitrate() [bitrate:{}]", bitrate); + + self.set_min_outgoing_bitrate_impl(bitrate).await + } + /// Local ICE role. Due to the mediasoup ICE Lite design, this is always `Controlled`. #[must_use] pub fn ice_role(&self) -> IceRole { diff --git a/rust/tests/integration/webrtc_transport.rs b/rust/tests/integration/webrtc_transport.rs index bf12e3ae49..b12c140ada 100644 --- a/rust/tests/integration/webrtc_transport.rs +++ b/rust/tests/integration/webrtc_transport.rs @@ -464,6 +464,28 @@ fn set_max_outgoing_bitrate_succeeds() { }); } +#[test] +fn set_min_outgoing_bitrate_succeeds() { + future::block_on(async move { + let (_worker, router) = init().await; + + let transport = router + .create_webrtc_transport(WebRtcTransportOptions::new(TransportListenIps::new( + ListenIp { + ip: IpAddr::V4(Ipv4Addr::LOCALHOST), + announced_ip: Some("9.9.9.1".parse().unwrap()), + }, + ))) + .await + .expect("Failed to create WebRTC transport"); + + transport + .set_min_outgoing_bitrate(100000) + .await + .expect("Failed to set min outgoing bitrate on WebRTC transport"); + }); +} + #[test] fn restart_ice_succeeds() { future::block_on(async move { diff --git a/worker/include/Channel/ChannelRequest.hpp b/worker/include/Channel/ChannelRequest.hpp index be10df42a6..7381c94d7e 100644 --- a/worker/include/Channel/ChannelRequest.hpp +++ b/worker/include/Channel/ChannelRequest.hpp @@ -43,6 +43,7 @@ namespace Channel TRANSPORT_CONNECT, TRANSPORT_SET_MAX_INCOMING_BITRATE, TRANSPORT_SET_MAX_OUTGOING_BITRATE, + TRANSPORT_SET_MIN_OUTGOING_BITRATE, TRANSPORT_RESTART_ICE, TRANSPORT_PRODUCE, TRANSPORT_CONSUME, diff --git a/worker/include/RTC/Transport.hpp b/worker/include/RTC/Transport.hpp index c52a197616..87e5e81909 100644 --- a/worker/include/RTC/Transport.hpp +++ b/worker/include/RTC/Transport.hpp @@ -329,6 +329,7 @@ namespace RTC uint32_t initialAvailableOutgoingBitrate{ 600000u }; uint32_t maxIncomingBitrate{ 0u }; uint32_t maxOutgoingBitrate{ 0u }; + uint32_t minOutgoingBitrate{ 0u }; struct TraceEventTypes traceEventTypes; }; } // namespace RTC diff --git a/worker/include/RTC/TransportCongestionControlClient.hpp b/worker/include/RTC/TransportCongestionControlClient.hpp index 17f7684f04..dd58ed943c 100644 --- a/worker/include/RTC/TransportCongestionControlClient.hpp +++ b/worker/include/RTC/TransportCongestionControlClient.hpp @@ -56,7 +56,8 @@ namespace RTC RTC::TransportCongestionControlClient::Listener* listener, RTC::BweType bweType, uint32_t initialAvailableBitrate, - uint32_t maxOutgoingBitrate); + uint32_t maxOutgoingBitrate, + uint32_t minOutgoingBitrate); virtual ~TransportCongestionControlClient(); public: @@ -74,6 +75,7 @@ namespace RTC void ReceiveRtcpTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket* feedback); void SetDesiredBitrate(uint32_t desiredBitrate, bool force); void SetMaxOutgoingBitrate(uint32_t maxBitrate); + void SetMinOutgoingBitrate(uint32_t minBitrate); const Bitrates& GetBitrates() const { return this->bitrates; @@ -118,6 +120,7 @@ namespace RTC RTC::BweType bweType; uint32_t initialAvailableBitrate{ 0u }; uint32_t maxOutgoingBitrate{ 0u }; + uint32_t minOutgoingBitrate{ 0u }; Bitrates bitrates; bool availableBitrateEventCalled{ false }; uint64_t lastAvailableBitrateEventAtMs{ 0u }; diff --git a/worker/src/Channel/ChannelRequest.cpp b/worker/src/Channel/ChannelRequest.cpp index 8ed466a22d..d107b39329 100644 --- a/worker/src/Channel/ChannelRequest.cpp +++ b/worker/src/Channel/ChannelRequest.cpp @@ -37,6 +37,7 @@ namespace Channel { "transport.connect", ChannelRequest::MethodId::TRANSPORT_CONNECT }, { "transport.setMaxIncomingBitrate", ChannelRequest::MethodId::TRANSPORT_SET_MAX_INCOMING_BITRATE }, { "transport.setMaxOutgoingBitrate", ChannelRequest::MethodId::TRANSPORT_SET_MAX_OUTGOING_BITRATE }, + { "transport.setMinOutgoingBitrate", ChannelRequest::MethodId::TRANSPORT_SET_MIN_OUTGOING_BITRATE }, { "transport.restartIce", ChannelRequest::MethodId::TRANSPORT_RESTART_ICE }, { "transport.produce", ChannelRequest::MethodId::TRANSPORT_PRODUCE }, { "transport.consume", ChannelRequest::MethodId::TRANSPORT_CONSUME }, diff --git a/worker/src/RTC/Transport.cpp b/worker/src/RTC/Transport.cpp index 9aef0f78cc..f3868a7eb3 100644 --- a/worker/src/RTC/Transport.cpp +++ b/worker/src/RTC/Transport.cpp @@ -630,6 +630,12 @@ namespace RTC "bitrate must be >= %" PRIu32 " bps", RTC::TransportCongestionControlMinOutgoingBitrate); } + if (this->minOutgoingBitrate != 0u && bitrate < this->minOutgoingBitrate) + { + MS_THROW_TYPE_ERROR( + "bitrate must be >= minOutgoingBitrate (%" PRIu32 " bps)", this->minOutgoingBitrate); + } + if (this->tccClient) { // NOTE: This may throw so don't update things before calling this @@ -651,6 +657,55 @@ namespace RTC break; } + case Channel::ChannelRequest::MethodId::TRANSPORT_SET_MIN_OUTGOING_BITRATE: + { + auto jsonBitrateIt = request->data.find("bitrate"); + + // clang-format off + if ( + jsonBitrateIt == request->data.end() || + !Utils::Json::IsPositiveInteger(*jsonBitrateIt) + ) + // clang-format on + { + MS_THROW_TYPE_ERROR("missing bitrate"); + } + + const uint32_t bitrate = jsonBitrateIt->get(); + + if (bitrate < RTC::TransportCongestionControlMinOutgoingBitrate) + { + MS_THROW_TYPE_ERROR( + "bitrate must be >= %" PRIu32 " bps", RTC::TransportCongestionControlMinOutgoingBitrate); + } + + if (this->maxOutgoingBitrate != 0u && bitrate > this->maxOutgoingBitrate) + { + MS_THROW_TYPE_ERROR( + "bitrate must be <= maxOutgoingBitrate (%" PRIu32 " bps)", this->maxOutgoingBitrate); + } + + if (this->tccClient) + { + // NOTE: This may throw so don't update things before calling this + // method. + this->tccClient->SetMinOutgoingBitrate(bitrate); + this->minOutgoingBitrate = bitrate; + + MS_DEBUG_TAG(bwe, "minimum outgoing bitrate set to %" PRIu32, this->minOutgoingBitrate); + + ComputeOutgoingDesiredBitrate(); + } + else + { + this->minOutgoingBitrate = bitrate; + } + + request->Accept(); + + break; + } + case Channel::ChannelRequest::MethodId::TRANSPORT_PRODUCE: { std::string producerId; @@ -1002,7 +1057,11 @@ namespace RTC }; this->tccClient = std::make_shared( - this, bweType, this->initialAvailableOutgoingBitrate, this->maxOutgoingBitrate); + this, + bweType, + this->initialAvailableOutgoingBitrate, + this->maxOutgoingBitrate, + this->minOutgoingBitrate); if (IsConnected()) this->tccClient->TransportConnected(); diff --git a/worker/src/RTC/TransportCongestionControlClient.cpp b/worker/src/RTC/TransportCongestionControlClient.cpp index f555bdf7a5..098018e8b5 100644 --- a/worker/src/RTC/TransportCongestionControlClient.cpp +++ b/worker/src/RTC/TransportCongestionControlClient.cpp @@ -27,11 +27,12 @@ namespace RTC RTC::TransportCongestionControlClient::Listener* listener, RTC::BweType bweType, uint32_t initialAvailableBitrate, - uint32_t maxOutgoingBitrate) + uint32_t maxOutgoingBitrate, + uint32_t minOutgoingBitrate) : listener(listener), bweType(bweType), initialAvailableBitrate(std::max( initialAvailableBitrate, RTC::TransportCongestionControlMinOutgoingBitrate)), - maxOutgoingBitrate(maxOutgoingBitrate) + maxOutgoingBitrate(maxOutgoingBitrate), minOutgoingBitrate(minOutgoingBitrate) { MS_TRACE(); @@ -281,6 +282,16 @@ namespace RTC } } + void TransportCongestionControlClient::SetMinOutgoingBitrate(uint32_t minBitrate) + { + this->minOutgoingBitrate = minBitrate; + + ApplyBitrateUpdates(); + + this->bitrates.minBitrate = std::max( + this->minOutgoingBitrate, RTC::TransportCongestionControlMinOutgoingBitrate); + } + void TransportCongestionControlClient::SetDesiredBitrate(uint32_t desiredBitrate, bool force) { MS_TRACE(); @@ -305,7 +316,9 @@ namespace RTC this->bitrates.effectiveDesiredBitrate = desiredBitrate; #endif - this->bitrates.minBitrate = RTC::TransportCongestionControlMinOutgoingBitrate; + this->bitrates.minBitrate = std::max( + this->minOutgoingBitrate, RTC::TransportCongestionControlMinOutgoingBitrate); + // NOTE: Setting 'startBitrate' to 'availableBitrate' has proven to generate // more stable values. this->bitrates.startBitrate = std::max( @@ -358,6 +371,9 @@ namespace RTC this->bitrates.maxBitrate = newMaxBitrate; } + this->bitrates.minBitrate = std::max( + this->minOutgoingBitrate, RTC::TransportCongestionControlMinOutgoingBitrate); + MS_DEBUG_DEV( "[desiredBitrate:%" PRIu32 ", desiredBitrateTrend:%" PRIu32 ", startBitrate:%" PRIu32 ", minBitrate:%" PRIu32 ", maxBitrate:%" PRIu32 ", maxPaddingBitrate:%" PRIu32 "]",