Add QP threshold experiment for H265.

The old experiment WebRTC-Video-QualityScaling only applies for VP8,
VP9, H264 and Generic, which does not cover H265.

In order to fine tune QP thresholds for H265, a new field trial is
added, this allows us to experiment with those without affecting the
old field trial which has a bunch of existing dependencies.

Some drive-by comments are added to the existing code.

Bug: webrtc:402154973, chromium:391907171
Change-Id: I13c8b496a1c708528d7538c8d1d2ff66de4c2ae5
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/380420
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#44088}
diff --git a/experiments/field_trials.py b/experiments/field_trials.py
index 1834752..d9e46f3 100755
--- a/experiments/field_trials.py
+++ b/experiments/field_trials.py
@@ -188,6 +188,9 @@
     FieldTrial('WebRTC-IceHandshakeDtls',
                367395350,
                date(2026, 1, 1)),
+    FieldTrial('WebRTC-H265-QualityScaling',
+               402154973,
+               date(2026, 1, 1)),
     # keep-sorted end
 ])  # yapf: disable
 
diff --git a/rtc_base/experiments/BUILD.gn b/rtc_base/experiments/BUILD.gn
index c5fce04..9f5573c 100644
--- a/rtc_base/experiments/BUILD.gn
+++ b/rtc_base/experiments/BUILD.gn
@@ -66,6 +66,7 @@
     "../../api:field_trials_view",
     "../../api/transport:field_trial_based_config",
     "../../api/video_codecs:video_codecs_api",
+    "../../rtc_base/experiments:field_trial_parser",
     "//third_party/abseil-cpp/absl/strings",
   ]
 }
diff --git a/rtc_base/experiments/quality_scaling_experiment.cc b/rtc_base/experiments/quality_scaling_experiment.cc
index 7bad026..be56363 100644
--- a/rtc_base/experiments/quality_scaling_experiment.cc
+++ b/rtc_base/experiments/quality_scaling_experiment.cc
@@ -16,10 +16,14 @@
 #include "absl/strings/match.h"
 #include "api/field_trials_view.h"
 #include "api/transport/field_trial_based_config.h"
+#include "rtc_base/experiments/field_trial_parser.h"
 #include "rtc_base/logging.h"
 
 namespace webrtc {
 namespace {
+
+// This experiment controls QP thresholds for VP8, VP9, H264 and Generic codecs.
+// Generic includes H265X but not standard H265.
 constexpr char kFieldTrial[] = "WebRTC-Video-QualityScaling";
 constexpr int kMinQp = 1;
 constexpr int kMaxVp8Qp = 127;
@@ -28,6 +32,13 @@
 constexpr int kMaxGenericQp = 255;
 
 #if !defined(WEBRTC_IOS)
+// On non-iOS, this default string is used unless explicitly overriden.
+// TODO(https://crbug.com/400338987): For use cases that does not explicitly
+// turn the QP experiment on (e.g. Chrome), it does not make sense for this QP
+// threshold to override the QP thresholds provided by the encoder
+// implementation - we should trust that an encoder implementation that reports
+// its own QP thresholds would know best, and only use these as a fallback for
+// when the encoder does not specify any.
 constexpr char kDefaultQualityScalingSetttings[] =
     "Enabled-29,95,149,205,24,37,26,36,0.9995,0.9999,1";
 #endif
@@ -42,14 +53,42 @@
   return std::optional<VideoEncoder::QpThresholds>(
       VideoEncoder::QpThresholds(low, high));
 }
+
+// This experiment controls QP thresholds for standard H265 (not H265X).
+// - Only for debugging/experimentation. Once QP thresholds have been determined
+//   it is up to the encoder implementation to provide
+//   VideoEncoder::EncoderInfo::scaling_settings.
+//
+// Example usage:
+// --force-fieldtrials=WebRTC-H265-QualityScaling/low_qp:27,high_qp:35/
+struct WebRTCH265QualityScaling {
+  static constexpr char kFieldTrialName[] = "WebRTC-H265-QualityScaling";
+
+  WebRTCH265QualityScaling(const FieldTrialsView& field_trials)
+      : low_qp("low_qp"), high_qp("high_qp") {
+    ParseFieldTrial({&low_qp, &high_qp}, field_trials.Lookup(kFieldTrialName));
+  }
+
+  bool IsEnabled() const { return low_qp && high_qp; }
+  VideoEncoder::QpThresholds ToQpThresholds() const {
+    RTC_DCHECK(IsEnabled());
+    return VideoEncoder::QpThresholds(*low_qp, *high_qp);
+  }
+
+  FieldTrialOptional<int> low_qp;
+  FieldTrialOptional<int> high_qp;
+};
 }  // namespace
 
 bool QualityScalingExperiment::Enabled(const FieldTrialsView& field_trials) {
+  WebRTCH265QualityScaling h265_quality_scaling(field_trials);
+  return
 #if defined(WEBRTC_IOS)
-  return absl::StartsWith(field_trials.Lookup(kFieldTrial), "Enabled");
+      absl::StartsWith(field_trials.Lookup(kFieldTrial), "Enabled") ||
 #else
-  return !absl::StartsWith(field_trials.Lookup(kFieldTrial), "Disabled");
+      !absl::StartsWith(field_trials.Lookup(kFieldTrial), "Disabled") ||
 #endif
+      h265_quality_scaling.IsEnabled();
 }
 
 std::optional<QualityScalingExperiment::Settings>
@@ -75,6 +114,12 @@
 std::optional<VideoEncoder::QpThresholds>
 QualityScalingExperiment::GetQpThresholds(VideoCodecType codec_type,
                                           const FieldTrialsView& field_trials) {
+  if (codec_type == kVideoCodecH265) {
+    WebRTCH265QualityScaling h265_quality_scaling(field_trials);
+    if (h265_quality_scaling.IsEnabled()) {
+      return h265_quality_scaling.ToQpThresholds();
+    }
+  }
   const auto settings = ParseSettings(field_trials);
   if (!settings)
     return std::nullopt;