mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-23 13:06:45 +08:00
[feat] allocate DeskHub video bandwidth automatically
This commit is contained in:
@@ -23,22 +23,31 @@ struct StreamSettings {
|
||||
{}, {320, 180}, {640, 360}, {1280, 720}, {1920, 1080},
|
||||
{2560, 1440}, {3840, 2160}, {}}};
|
||||
static constexpr std::array<int, 4> kFrameRates{0, 15, 30, 60};
|
||||
static constexpr std::array<int, 6> kBandwidthMbps{2, 5, 10, 20, 50, 100};
|
||||
int resolution = 0;
|
||||
int frame_rate = 0; // 0 follows the session's existing 30/60 FPS preference.
|
||||
int bandwidth_mbps = 20;
|
||||
|
||||
static std::optional<StreamSettings> FromIndices(int resolution, int fps) {
|
||||
static std::optional<StreamSettings> FromIndices(int resolution, int fps,
|
||||
int bandwidth = 3) {
|
||||
if (resolution < 0 || resolution >= int(kResolutions.size()) ||
|
||||
fps < 0 || fps >= int(kFrameRates.size()))
|
||||
fps < 0 || fps >= int(kFrameRates.size()) ||
|
||||
bandwidth < 0 || bandwidth >= int(kBandwidthMbps.size()))
|
||||
return {};
|
||||
return StreamSettings{resolution, kFrameRates[fps]};
|
||||
return StreamSettings{resolution, kFrameRates[fps], kBandwidthMbps[bandwidth]};
|
||||
}
|
||||
int FrameRateIndex() const {
|
||||
return int(std::find(kFrameRates.begin(), kFrameRates.end(), frame_rate) -
|
||||
kFrameRates.begin());
|
||||
}
|
||||
int BandwidthIndex() const {
|
||||
return int(std::find(kBandwidthMbps.begin(), kBandwidthMbps.end(), bandwidth_mbps) -
|
||||
kBandwidthMbps.begin());
|
||||
}
|
||||
bool Valid() const {
|
||||
return resolution >= 0 && resolution < int(kResolutions.size()) &&
|
||||
FrameRateIndex() < int(kFrameRates.size());
|
||||
FrameRateIndex() < int(kFrameRates.size()) &&
|
||||
BandwidthIndex() < int(kBandwidthMbps.size());
|
||||
}
|
||||
static StreamSettings FromJson(const nlohmann::json& json) {
|
||||
try {
|
||||
@@ -50,13 +59,19 @@ struct StreamSettings {
|
||||
fps < 0 || fps > 60) return {};
|
||||
// Retired sparse presets migrate to the lowest current setting.
|
||||
StreamSettings result{int(resolution), fps == 1 || fps == 8 ? 15 : int(fps)};
|
||||
if (json.contains("bandwidth_mbps") && json.at("bandwidth_mbps").is_number_integer()) {
|
||||
const auto bandwidth = json.at("bandwidth_mbps").get<int64_t>();
|
||||
if (std::find(kBandwidthMbps.begin(), kBandwidthMbps.end(), bandwidth) !=
|
||||
kBandwidthMbps.end()) result.bandwidth_mbps = int(bandwidth);
|
||||
}
|
||||
return result.Valid() ? result : StreamSettings{};
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
nlohmann::json ToJson() const {
|
||||
return {{"resolution", resolution}, {"frame_rate", frame_rate}};
|
||||
return {{"resolution", resolution}, {"frame_rate", frame_rate},
|
||||
{"bandwidth_mbps", bandwidth_mbps}};
|
||||
}
|
||||
PreviewResolution Resolution(bool wall_managed, PreviewResolution wall_view,
|
||||
PreviewResolution stream_view) const {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-10-13
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _PREVIEW_BANDWIDTH_H_
|
||||
#define _PREVIEW_BANDWIDTH_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace crossdesk::gallery {
|
||||
struct PreviewBandwidthDemand {
|
||||
int desired_bps = 0;
|
||||
bool foreground = false;
|
||||
|
||||
bool operator==(const PreviewBandwidthDemand& other) const {
|
||||
return desired_bps == other.desired_bps && foreground == other.foreground;
|
||||
}
|
||||
|
||||
static int Estimate(int width, int height, int fps) {
|
||||
// A demand ceiling, not a throughput measurement. Include room for video
|
||||
// repair traffic; the sender's congestion control may allocate less.
|
||||
const int64_t pixels = width > 0 && height > 0
|
||||
? int64_t(width) * height : 3840LL * 2160;
|
||||
const int64_t estimate = pixels * std::clamp(fps, 1, 60) * 15 / 100;
|
||||
return int(std::clamp<int64_t>((estimate + 49'999) / 50'000 * 50'000,
|
||||
150'000, 20'000'000));
|
||||
}
|
||||
};
|
||||
|
||||
inline std::vector<int> AllocatePreviewBandwidth(
|
||||
const std::vector<PreviewBandwidthDemand>& demands, int budget_bps) {
|
||||
std::vector<int> result(demands.size(), 0);
|
||||
int remaining = std::max(0, budget_bps);
|
||||
int active = 0;
|
||||
int64_t desired_total = 0;
|
||||
for (const auto& demand : demands) {
|
||||
active += demand.desired_bps > 0;
|
||||
desired_total += std::max(0, demand.desired_bps);
|
||||
}
|
||||
if (!active || !remaining) return result;
|
||||
if (desired_total <= remaining) {
|
||||
for (std::size_t i = 0; i < demands.size(); ++i)
|
||||
result[i] = std::max(0, demands[i].desired_bps);
|
||||
return result;
|
||||
}
|
||||
// First preserve a small fair share for every visible device, then perform
|
||||
// weighted max-min allocation. Satisfied devices return unused capacity.
|
||||
const int baseline = std::min(100'000, remaining / active);
|
||||
for (std::size_t i = 0; i < demands.size(); ++i) {
|
||||
result[i] = std::min(std::max(0, demands[i].desired_bps), baseline);
|
||||
remaining -= result[i];
|
||||
}
|
||||
while (remaining > 0) {
|
||||
int weights = 0;
|
||||
for (std::size_t i = 0; i < demands.size(); ++i)
|
||||
if (result[i] < demands[i].desired_bps)
|
||||
weights += demands[i].foreground ? 4 : 1;
|
||||
if (!weights) break;
|
||||
const int available = remaining;
|
||||
for (std::size_t i = 0; i < demands.size() && remaining > 0; ++i) {
|
||||
if (result[i] >= demands[i].desired_bps) continue;
|
||||
const int share = int(int64_t(available) *
|
||||
(demands[i].foreground ? 4 : 1) / weights);
|
||||
const int extra = std::min({remaining, demands[i].desired_bps - result[i],
|
||||
std::max(1, share)});
|
||||
result[i] += extra;
|
||||
remaining -= extra;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// UI refreshes much more often than video demand changes. Keep the previous
|
||||
// result while membership, dimensions, cadence, priority and budget are stable.
|
||||
class PreviewBandwidthAllocator {
|
||||
public:
|
||||
const std::vector<int>& Allocate(
|
||||
const std::vector<PreviewBandwidthDemand>& demands, int budget_bps) {
|
||||
if (budget_bps_ != budget_bps || demands_ != demands) {
|
||||
demands_ = demands;
|
||||
budget_bps_ = budget_bps;
|
||||
allocations_ = AllocatePreviewBandwidth(demands, budget_bps);
|
||||
}
|
||||
return allocations_;
|
||||
}
|
||||
|
||||
private:
|
||||
int budget_bps_ = -1;
|
||||
std::vector<PreviewBandwidthDemand> demands_;
|
||||
std::vector<int> allocations_;
|
||||
};
|
||||
} // namespace crossdesk::gallery
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user