/* * Copyright (C) 2024 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-gfx-framerate-log.h" #include #include "grd-rdp-frame-stats.h" #define STABLE_ENCODING_RATE_THRESHOLD_PERCENT 0.8 #define MIN_N_ENC_RATES 4 #define MIN_ENC_RATE_THRESHOLD 5 #define MIN_VIDEO_FRAMERATE 24 typedef struct { uint32_t enc_rate; int64_t tracked_time_us; } EncRateInfo; struct _GrdRdpGfxFramerateLog { GObject parent; GMutex framerate_log_mutex; GQueue *enc_rates; uint32_t last_ack_rate; uint32_t missing_dual_frame_acks; }; G_DEFINE_TYPE (GrdRdpGfxFramerateLog, grd_rdp_gfx_framerate_log, G_TYPE_OBJECT) static void clear_old_enc_rates (GrdRdpGfxFramerateLog *framerate_log) { EncRateInfo *enc_rate_info; int64_t current_time_us; current_time_us = g_get_monotonic_time (); while ((enc_rate_info = g_queue_peek_head (framerate_log->enc_rates)) && current_time_us - enc_rate_info->tracked_time_us >= G_USEC_PER_SEC >> 1) g_free (g_queue_pop_head (framerate_log->enc_rates)); } void grd_rdp_gfx_framerate_log_notify_frame_stats (GrdRdpGfxFramerateLog *framerate_log, GrdRdpFrameStats *frame_stats) { EncRateInfo *enc_rate_info; enc_rate_info = g_new0 (EncRateInfo, 1); enc_rate_info->enc_rate = grd_rdp_frame_stats_get_enc_rate (frame_stats); enc_rate_info->tracked_time_us = g_get_monotonic_time (); g_mutex_lock (&framerate_log->framerate_log_mutex); clear_old_enc_rates (framerate_log); g_queue_push_tail (framerate_log->enc_rates, enc_rate_info); framerate_log->last_ack_rate = grd_rdp_frame_stats_get_ack_rate (frame_stats); framerate_log->missing_dual_frame_acks = grd_rdp_frame_stats_get_missing_dual_frame_acks (frame_stats); g_mutex_unlock (&framerate_log->framerate_log_mutex); } static int cmp_enc_rates (gconstpointer a, gconstpointer b, gpointer user_data) { const EncRateInfo *enc_rate_info_a = a; const EncRateInfo *enc_rate_info_b = b; int64_t enc_rate_a = enc_rate_info_a->enc_rate; int64_t enc_rate_b = enc_rate_info_b->enc_rate; return enc_rate_a - enc_rate_b; } static gboolean has_stable_enc_rate (double enc_rate_min, double enc_rate_median) { return enc_rate_min >= floor (enc_rate_median * STABLE_ENCODING_RATE_THRESHOLD_PERCENT); } gboolean grd_rdp_gfx_framerate_log_should_avoid_dual_frame (GrdRdpGfxFramerateLog *framerate_log) { g_autoptr (GMutexLocker) locker = NULL; g_autoptr (GQueue) tmp = NULL; EncRateInfo *enc_rate_info; uint32_t n_enc_rates; uint32_t enc_rate_min; uint32_t enc_rate_quartile3; uint32_t enc_rate_median; uint32_t last_ack_rate; uint32_t missing_dual_frame_acks; uint32_t i; locker = g_mutex_locker_new (&framerate_log->framerate_log_mutex); clear_old_enc_rates (framerate_log); n_enc_rates = g_queue_get_length (framerate_log->enc_rates); if (n_enc_rates < MIN_N_ENC_RATES) return FALSE; tmp = g_queue_copy (framerate_log->enc_rates); g_queue_sort (tmp, cmp_enc_rates, NULL); enc_rate_info = g_queue_peek_head (tmp); enc_rate_min = enc_rate_info->enc_rate; for (i = 0; i < n_enc_rates >> 2; ++i) g_queue_pop_tail (tmp); enc_rate_info = g_queue_peek_tail (tmp); enc_rate_quartile3 = enc_rate_info->enc_rate; for (i = 0; i < n_enc_rates >> 2; ++i) g_queue_pop_tail (tmp); enc_rate_info = g_queue_peek_tail (tmp); enc_rate_median = enc_rate_info->enc_rate; last_ack_rate = framerate_log->last_ack_rate; missing_dual_frame_acks = framerate_log->missing_dual_frame_acks; g_clear_pointer (&locker, g_mutex_locker_free); if (enc_rate_median < MIN_ENC_RATE_THRESHOLD) return FALSE; if (enc_rate_median >= MIN_VIDEO_FRAMERATE || has_stable_enc_rate (enc_rate_min, enc_rate_median)) return enc_rate_quartile3 + 3 * missing_dual_frame_acks >= last_ack_rate; return FALSE; } GrdRdpGfxFramerateLog * grd_rdp_gfx_framerate_log_new (void) { return g_object_new (GRD_TYPE_RDP_GFX_FRAMERATE_LOG, NULL); } static void grd_rdp_gfx_framerate_log_dispose (GObject *object) { GrdRdpGfxFramerateLog *framerate_log = GRD_RDP_GFX_FRAMERATE_LOG (object); if (framerate_log->enc_rates) { g_queue_free_full (framerate_log->enc_rates, g_free); framerate_log->enc_rates = NULL; } G_OBJECT_CLASS (grd_rdp_gfx_framerate_log_parent_class)->dispose (object); } static void grd_rdp_gfx_framerate_log_finalize (GObject *object) { GrdRdpGfxFramerateLog *framerate_log = GRD_RDP_GFX_FRAMERATE_LOG (object); g_mutex_clear (&framerate_log->framerate_log_mutex); G_OBJECT_CLASS (grd_rdp_gfx_framerate_log_parent_class)->finalize (object); } static void grd_rdp_gfx_framerate_log_init (GrdRdpGfxFramerateLog *framerate_log) { framerate_log->enc_rates = g_queue_new (); g_mutex_init (&framerate_log->framerate_log_mutex); } static void grd_rdp_gfx_framerate_log_class_init (GrdRdpGfxFramerateLogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_gfx_framerate_log_dispose; object_class->finalize = grd_rdp_gfx_framerate_log_finalize; }