[plasma/krdp] src: VideoStream: base the in-flight window on round-trip time
Nate Graham <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 909f866e375234601870b5d1ac899566fa89085c by Nate Graham, on behalf of Shouvik Kar.
Committed on 06/08/2026 at 13:37.
Pushed by ngraham into branch 'master'.
VideoStream: base the in-flight window on round-trip time
hasInFlightCapacity() used a fixed limit of two outstanding frames, which
limits throughput on high-latency links because two unacknowledged frames may
not keep the pipe full. Size maxInFlight from the average round-trip time and
requested frame rate instead, clamped to a floor of 2 and a one-second latency
budget.
NetworkDetection now publishes its RTT snapshots via atomics so VideoStream can
read the values safely across threads when deriving the cached in-flight window.
M +15 -10 src/NetworkDetection.cpp
M +27 -3 src/VideoStream.cpp
M +2 -0 src/VideoStream.h
https://invent.kde.org/plasma/krdp/-/commit/909f866e375234601870b5d1ac899566fa89085c
diff --git a/src/NetworkDetection.cpp b/src/NetworkDetection.cpp
index 1613762..6fd45f2 100644
--- a/src/NetworkDetection.cpp
+++ b/src/NetworkDetection.cpp
@@ -11,6 +11,8 @@
#include <ranges>
+#include <atomic>
+
#include <QQueue>
#include <QTimer>
@@ -73,8 +75,9 @@ public:
QHash<uint32_t, clk::system_clock::time_point> rttRequests;
std::vector<RTTMeasurement> rttMeasurements;
- clk::system_clock::duration minimumRtt;
- clk::system_clock::duration averageRtt;
+ // Published as atomic tick counts so the getters are safe to read from any thread.
+ std::atomic<clk::system_clock::rep> minimumRttTicks{0};
+ std::atomic<clk::system_clock::rep> averageRttTicks{0};
clk::system_clock::time_point lastNetworkResult;
@@ -93,12 +96,12 @@ NetworkDetection::~NetworkDetection() = default;
std::chrono::system_clock::duration NetworkDetection::minimumRTT() const
{
- return d->minimumRtt;
+ return clk::system_clock::duration(d->minimumRttTicks.load());
}
std::chrono::system_clock::duration NetworkDetection::averageRTT() const
{
- return d->averageRtt;
+ return clk::system_clock::duration(d->averageRttTicks.load());
}
void NetworkDetection::initialize()
@@ -208,13 +211,15 @@ void NetworkDetection::updateAverageRtt()
return;
}
- d->minimumRtt = std::numeric_limits<clk::system_clock::duration>::max();
+ auto minimum = std::numeric_limits<clk::system_clock::duration>::max();
auto sum = clk::system_clock::duration(0);
- std::for_each(d->rttMeasurements.begin(), d->rttMeasurements.end(), [this, &sum](const auto &measurement) {
- d->minimumRtt = std::min(d->minimumRtt, measurement.roundTripTime);
+ std::for_each(d->rttMeasurements.begin(), d->rttMeasurements.end(), [&minimum, &sum](const auto &measurement) {
+ minimum = std::min(minimum, measurement.roundTripTime);
sum = sum + measurement.roundTripTime;
});
- d->averageRtt = sum / d->rttMeasurements.size();
+ const auto average = sum / d->rttMeasurements.size();
+ d->minimumRttTicks.store(minimum.count());
+ d->averageRttTicks.store(average.count());
Q_EMIT rttChanged();
@@ -230,8 +235,8 @@ void NetworkDetection::updateAverageRtt()
rdpNetworkCharacteristicsResult result;
result.type = RDP_NETCHAR_RESULT_TYPE_BASE_RTT_BW_AVG_RTT;
- result.baseRTT = clk::duration_cast<clk::milliseconds>(d->minimumRtt).count();
- result.averageRTT = clk::duration_cast<clk::milliseconds>(d->averageRtt).count();
+ result.baseRTT = clk::duration_cast<clk::milliseconds>(minimum).count();
+ result.averageRTT = clk::duration_cast<clk::milliseconds>(average).count();
result.bandwidth = d->lastBandwithMeasurement;
d->rdpAutodetect->NetworkCharacteristicsResult(d->rdpAutodetect, RDP_TRANSPORT_TCP, d->nextSequenceNumber(), &result);
}
diff --git a/src/VideoStream.cpp b/src/VideoStream.cpp
index 0bdc35c..b22fc3f 100644
--- a/src/VideoStream.cpp
+++ b/src/VideoStream.cpp
@@ -10,6 +10,8 @@
#include "VideoStream.h"
#include <algorithm>
+#include <atomic>
+#include <cmath>
#include <condition_variable>
#include <QDateTime>
@@ -35,8 +37,9 @@ namespace KRdp
namespace clk = std::chrono;
-// Maximum number of frames to contain in the queue.
-constexpr qsizetype MaximumInFlightFrames = 2;
+constexpr qsizetype MaximumInFlightFrames = 2; // in-flight window floor
+constexpr double InFlightGain = 1.0; // window spans this many round trips of frames
+constexpr double LatencyBudgetSec = 1.0; // never buffer more than this many seconds of video
constexpr uint32_t ProgressiveCodecContextId = 1;
struct RdpCapsInformation {
uint32_t version;
@@ -146,6 +149,7 @@ public:
std::mutex pendingFramesMutex;
std::atomic_int requestedFrameRate = 60;
+ std::atomic<qsizetype> maxInFlight{MaximumInFlightFrames}; // recomputed from RTT on rttChanged
bool initialized = false;
quint8 quality = 100;
@@ -352,6 +356,8 @@ bool VideoStream::initialize()
d->initialized = true;
+ connect(d->session->networkDetection(), &NetworkDetection::rttChanged, this, &VideoStream::updateInFlightWindow);
+
d->frameSubmissionThread = std::jthread([this](std::stop_token token) {
while (!token.stop_requested()) {
if (!hasInFlightCapacity() || !d->gfxContext || !d->capsConfirmed) {
@@ -822,10 +828,28 @@ void VideoStream::performReset(QSize size)
}
}
+void VideoStream::updateInFlightWindow()
+{
+ // Size the in-flight window from the bandwidth-delay product: at the current source
+ // rate, how many frames fit within one base round trip. NetworkDetection publishes the
+ // RTT via atomics, so averageRTT() is safe to read here; hasInFlightCapacity() then
+ // just reads the cached maxInFlight. Falls back to the fixed floor until an RTT is known.
+ const auto rttMs = clk::duration_cast<clk::milliseconds>(d->session->networkDetection()->averageRTT()).count();
+ qsizetype window = MaximumInFlightFrames;
+ if (rttMs > 0 && rttMs < 60000) {
+ const double rttSec = rttMs / 1000.0;
+ const int fps = d->requestedFrameRate.load();
+ const qsizetype bdp = qsizetype(std::ceil(fps * rttSec * InFlightGain));
+ const qsizetype cap = std::max<qsizetype>(MaximumInFlightFrames, qsizetype(std::ceil(fps * LatencyBudgetSec)));
+ window = std::clamp(bdp, qsizetype(MaximumInFlightFrames), cap);
+ }
+ d->maxInFlight.store(window);
+}
+
bool VideoStream::hasInFlightCapacity() const
{
std::lock_guard lock(d->pendingFramesMutex);
- return d->pendingFrames.size() < MaximumInFlightFrames;
+ return d->pendingFrames.size() < d->maxInFlight.load();
}
void VideoStream::sendFrame(const VideoFrame &frame)
diff --git a/src/VideoStream.h b/src/VideoStream.h
index a84738f..285029b 100644
--- a/src/VideoStream.h
+++ b/src/VideoStream.h
@@ -108,6 +108,8 @@ private:
void sendFrameH264(const VideoFrame &frame);
void sendFrameProgressive(const VideoFrame &frame);
+ void updateInFlightWindow();
+
class Private;
const std::unique_ptr<Private> d;
};