From ff5e97358c07c53336f3b85b0af07b15e29bff27 Mon Sep 17 00:00:00 2001
From: Cameron Gutman <aicommander@gmail.com>
Date: Sun, 27 Oct 2024 20:32:05 -0500
Subject: [PATCH 1/2] feat(vaapi): add option to enable strict enforcement of
 frame size

---
 docs/configuration.md                         |  27 +++
 src/config.cpp                                |   6 +
 src/config.h                                  |   4 +
 src/platform/common.h                         |   2 +-
 src/platform/linux/vaapi.cpp                  | 164 +++++++++++++++++-
 src/video.cpp                                 |  22 +--
 src_assets/common/assets/web/config.html      |  11 +-
 .../web/configs/tabs/ContainerEncoders.vue    |   8 +
 .../configs/tabs/encoders/VAAPIEncoder.vue    |  28 +++
 .../assets/web/public/assets/locale/en.json   |   2 +
 10 files changed, 250 insertions(+), 24 deletions(-)
 create mode 100644 src_assets/common/assets/web/configs/tabs/encoders/VAAPIEncoder.vue

diff --git a/docs/configuration.md b/docs/configuration.md
index 2b282d1c7a1..d4e3354045b 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -2303,6 +2303,33 @@ editing the `conf` file in a text editor. Use the examples as reference.
     </tr>
 </table>
 
+## VA-API Encoder
+
+### vaapi_strict_rc_buffer
+
+<table>
+    <tr>
+        <td>Description</td>
+        <td colspan="2">
+            Enabling this option can avoid dropped frames over the network during scene changes, but video quality may
+            be reduced during motion.
+            @note{This option only applies for H.264 and HEVC when using VA-API [encoder](#encoder) on AMD GPUs.}
+        </td>
+    </tr>
+    <tr>
+        <td>Default</td>
+        <td colspan="2">@code{}
+            disabled
+            @endcode</td>
+    </tr>
+    <tr>
+        <td>Example</td>
+        <td colspan="2">@code{}
+            vaapi_strict_rc_buffer = enabled
+            @endcode</td>
+    </tr>
+</table>
+
 ## Software Encoder
 
 ### sw_preset
diff --git a/src/config.cpp b/src/config.cpp
index 8475a5e3370..a61b69b96f1 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -377,6 +377,10 @@ namespace config {
       -1,
     },  // vt
 
+    {
+      false,  // strict_rc_buffer
+    },  // vaapi
+
     {},  // capture
     {},  // encoder
     {},  // adapter_name
@@ -1014,6 +1018,8 @@ namespace config {
     int_f(vars, "vt_software", video.vt.vt_require_sw, vt::force_software_from_view);
     int_f(vars, "vt_realtime", video.vt.vt_realtime, vt::rt_from_view);
 
+    bool_f(vars, "vaapi_strict_rc_buffer", video.vaapi.strict_rc_buffer);
+
     string_f(vars, "capture", video.capture);
     string_f(vars, "encoder", video.encoder);
     string_f(vars, "adapter_name", video.adapter_name);
diff --git a/src/config.h b/src/config.h
index e599afae454..891a4079772 100644
--- a/src/config.h
+++ b/src/config.h
@@ -71,6 +71,10 @@ namespace config {
       int vt_coder;
     } vt;
 
+    struct {
+      bool strict_rc_buffer;
+    } vaapi;
+
     std::string capture;
     std::string encoder;
     std::string adapter_name;
diff --git a/src/platform/common.h b/src/platform/common.h
index 368bc733b19..ee48ed3909a 100644
--- a/src/platform/common.h
+++ b/src/platform/common.h
@@ -418,7 +418,7 @@ namespace platf {
      * @note Implementations may set or modify codec options prior to codec initialization.
      */
     virtual void
-    init_codec_options(AVCodecContext *ctx, AVDictionary *options) {};
+    init_codec_options(AVCodecContext *ctx, AVDictionary **options) {};
 
     /**
      * @brief Prepare to derive a context.
diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp
index ada6370034a..49cad211cf9 100644
--- a/src/platform/linux/vaapi.cpp
+++ b/src/platform/linux/vaapi.cpp
@@ -9,6 +9,7 @@
 
 extern "C" {
 #include <libavcodec/avcodec.h>
+#include <libavutil/pixdesc.h>
 #include <va/va.h>
 #include <va/va_drm.h>
 #if !VA_CHECK_VERSION(1, 9, 0)
@@ -129,13 +130,168 @@ namespace va {
       return 0;
     }
 
+    /**
+     * @brief Finds a supported VA entrypoint for the given VA profile.
+     * @param profile The profile to match.
+     * @return A valid encoding entrypoint or 0 on failure.
+     */
+    VAEntrypoint
+    select_va_entrypoint(VAProfile profile) {
+      std::vector<VAEntrypoint> entrypoints(vaMaxNumEntrypoints(va_display));
+      int num_eps;
+      auto status = vaQueryConfigEntrypoints(va_display, profile, entrypoints.data(), &num_eps);
+      if (status != VA_STATUS_SUCCESS) {
+        BOOST_LOG(error) << "Failed to query VA entrypoints: "sv << vaErrorStr(status);
+        return (VAEntrypoint) 0;
+      }
+      entrypoints.resize(num_eps);
+
+      // Sorted in order of descending preference
+      VAEntrypoint ep_preferences[] = {
+        VAEntrypointEncSliceLP,
+        VAEntrypointEncSlice,
+        VAEntrypointEncPicture
+      };
+      for (auto ep_pref : ep_preferences) {
+        if (std::find(entrypoints.begin(), entrypoints.end(), ep_pref) != entrypoints.end()) {
+          return ep_pref;
+        }
+      }
+
+      return (VAEntrypoint) 0;
+    }
+
+    /**
+     * @brief Determines if a given VA profile is supported.
+     * @param profile The profile to match.
+     * @return Boolean value indicating if the profile is supported.
+     */
+    bool
+    is_va_profile_supported(VAProfile profile) {
+      std::vector<VAProfile> profiles(vaMaxNumProfiles(va_display));
+      int num_profs;
+      auto status = vaQueryConfigProfiles(va_display, profiles.data(), &num_profs);
+      if (status != VA_STATUS_SUCCESS) {
+        BOOST_LOG(error) << "Failed to query VA profiles: "sv << vaErrorStr(status);
+        return false;
+      }
+      profiles.resize(num_profs);
+
+      return std::find(profiles.begin(), profiles.end(), profile) != profiles.end();
+    }
+
+    /**
+     * @brief Determines the matching VA profile for the codec configuration.
+     * @param ctx The FFmpeg codec context.
+     * @return The matching VA profile or `VAProfileNone` on failure.
+     */
+    VAProfile
+    get_va_profile(AVCodecContext *ctx) {
+      if (ctx->codec_id == AV_CODEC_ID_H264) {
+        // There's no VAAPI profile for H.264 4:4:4
+        return VAProfileH264High;
+      }
+      else if (ctx->codec_id == AV_CODEC_ID_HEVC) {
+        switch (ctx->profile) {
+          case FF_PROFILE_HEVC_REXT:
+            switch (av_pix_fmt_desc_get(ctx->sw_pix_fmt)->comp[0].depth) {
+              case 10:
+                return VAProfileHEVCMain444_10;
+              case 8:
+                return VAProfileHEVCMain444;
+            }
+            break;
+          case FF_PROFILE_HEVC_MAIN_10:
+            return VAProfileHEVCMain10;
+          case FF_PROFILE_HEVC_MAIN:
+            return VAProfileHEVCMain;
+        }
+      }
+      else if (ctx->codec_id == AV_CODEC_ID_AV1) {
+        switch (ctx->profile) {
+          case FF_PROFILE_AV1_HIGH:
+            return VAProfileAV1Profile1;
+          case FF_PROFILE_AV1_MAIN:
+            return VAProfileAV1Profile0;
+        }
+      }
+
+      BOOST_LOG(error) << "Unknown encoder profile: "sv << ctx->profile;
+      return VAProfileNone;
+    }
+
     void
-    init_codec_options(AVCodecContext *ctx, AVDictionary *options) override {
-      // Don't set the RC buffer size when using H.264 on Intel GPUs. It causes
-      // major encoding quality degradation.
+    init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
+      auto va_profile = get_va_profile(ctx);
+      if (va_profile == VAProfileNone || !is_va_profile_supported(va_profile)) {
+        // Don't bother doing anything if the profile isn't supported
+        return;
+      }
+
+      auto va_entrypoint = select_va_entrypoint(va_profile);
+      if (va_entrypoint == 0) {
+        // It's possible that only decoding is supported for this profile
+        return;
+      }
+
       auto vendor = vaQueryVendorString(va_display);
-      if (ctx->codec_id != AV_CODEC_ID_H264 || (vendor && !strstr(vendor, "Intel"))) {
+
+      if (va_entrypoint == VAEntrypointEncSliceLP) {
+        BOOST_LOG(info) << "Using LP encoding mode"sv;
+        av_dict_set_int(options, "low_power", 1, 0);
+      }
+      else {
+        BOOST_LOG(info) << "Using normal encoding mode"sv;
+      }
+
+      VAConfigAttrib rc_attr = { VAConfigAttribRateControl };
+      auto status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &rc_attr, 1);
+      if (status != VA_STATUS_SUCCESS) {
+        // Stick to the default rate control (CQP)
+        rc_attr.value = 0;
+      }
+
+      VAConfigAttrib slice_attr = { VAConfigAttribEncMaxSlices };
+      status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &slice_attr, 1);
+      if (status != VA_STATUS_SUCCESS) {
+        // Assume only a single slice is supported
+        slice_attr.value = 1;
+      }
+      if (ctx->slices > slice_attr.value) {
+        BOOST_LOG(info) << "Limiting slice count to encoder maximum: "sv << slice_attr.value;
+        ctx->slices = slice_attr.value;
+      }
+
+      // Use VBR with a single frame VBV when the user forces it and for known good cases:
+      // - Intel GPUs
+      // - AV1
+      //
+      // VBR ensures the bitstream isn't full of filler data for bitrate undershoots and
+      // single frame VBV ensures that we don't have large bitrate overshoots (at least
+      // as much as they can be avoided without pre-analysis).
+      //
+      // When we have to resort to the default 1 second VBV for encoding quality reasons,
+      // we stick to CBR in order to avoid encoding huge frames after bitrate undershoots
+      // leave headroom available in the RC window.
+      if (config::video.vaapi.strict_rc_buffer ||
+          (vendor && strstr(vendor, "Intel")) ||
+          ctx->codec_id == AV_CODEC_ID_AV1) {
         ctx->rc_buffer_size = ctx->bit_rate * ctx->framerate.den / ctx->framerate.num;
+
+        if (rc_attr.value & VA_RC_VBR) {
+          BOOST_LOG(info) << "Using VBR with single frame VBV size"sv;
+          av_dict_set(options, "rc_mode", "VBR", 0);
+        }
+        else if (rc_attr.value & VA_RC_CBR) {
+          BOOST_LOG(info) << "Using CBR with single frame VBV size"sv;
+          av_dict_set(options, "rc_mode", "CBR", 0);
+        }
+        else {
+          BOOST_LOG(info) << "Using default rate control with single frame VBV size"sv;
+        }
+      }
+      else {
+        BOOST_LOG(info) << "Using default rate control"sv;
       }
     }
 
diff --git a/src/video.cpp b/src/video.cpp
index ecd92e2504a..603eb334732 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -857,7 +857,6 @@ namespace video {
     {
       // Common options
       {
-        { "low_power"s, 1 },
         { "async_depth"s, 1 },
         { "idr_interval"s, std::numeric_limits<int>::max() },
       },
@@ -865,17 +864,13 @@ namespace video {
       {},  // HDR-specific options
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
-      {
-        // Fallback options
-        { "low_power"s, 0 },  // Not all VAAPI drivers expose LP entrypoints
-      },
+      {},  // Fallback options
       std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
       "av1_vaapi"s,
     },
     {
       // Common options
       {
-        { "low_power"s, 1 },
         { "async_depth"s, 1 },
         { "sei"s, 0 },
         { "idr_interval"s, std::numeric_limits<int>::max() },
@@ -884,17 +879,13 @@ namespace video {
       {},  // HDR-specific options
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
-      {
-        // Fallback options
-        { "low_power"s, 0 },  // Not all VAAPI drivers expose LP entrypoints
-      },
+      {},  // Fallback options
       std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
       "hevc_vaapi"s,
     },
     {
       // Common options
       {
-        { "low_power"s, 1 },
         { "async_depth"s, 1 },
         { "sei"s, 0 },
         { "idr_interval"s, std::numeric_limits<int>::max() },
@@ -903,15 +894,12 @@ namespace video {
       {},  // HDR-specific options
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
-      {
-        // Fallback options
-        { "low_power"s, 0 },  // Not all VAAPI drivers expose LP entrypoints
-      },
+      {},  // Fallback options
       std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
       "h264_vaapi"s,
     },
     // RC buffer size will be set in platform code if supported
-    LIMITED_GOP_SIZE | PARALLEL_ENCODING | SINGLE_SLICE_ONLY | NO_RC_BUF_LIMIT
+    LIMITED_GOP_SIZE | PARALLEL_ENCODING | NO_RC_BUF_LIMIT
   };
 #endif
 
@@ -1696,7 +1684,7 @@ namespace video {
       }
 
       // Allow the encoding device a final opportunity to set/unset or override any options
-      encode_device->init_codec_options(ctx.get(), options);
+      encode_device->init_codec_options(ctx.get(), &options);
 
       if (auto status = avcodec_open2(ctx.get(), codec, &options)) {
         char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html
index c9c0a6ddf71..a7681fdde02 100644
--- a/src_assets/common/assets/web/config.html
+++ b/src_assets/common/assets/web/config.html
@@ -257,6 +257,13 @@ <h1 class="my-4">{{ $t('config.configuration') }}</h1>
               "vt_realtime": "enabled",
             },
           },
+          {
+            id: "vaapi",
+            name: "VA-API Encoder",
+            options: {
+              "vaapi_strict_rc_buffer": "disabled",
+            },
+          },
           {
             id: "sw",
             name: "Software Encoder",
@@ -283,7 +290,7 @@ <h1 class="my-4">{{ $t('config.configuration') }}</h1>
           var app = document.getElementById("app");
           if (this.platform === "windows") {
             this.tabs = this.tabs.filter((el) => {
-              return el.id !== "vt";
+              return el.id !== "vt" && el.id !== "vaapi";
             });
           }
           if (this.platform === "linux") {
@@ -293,7 +300,7 @@ <h1 class="my-4">{{ $t('config.configuration') }}</h1>
           }
           if (this.platform === "macos") {
             this.tabs = this.tabs.filter((el) => {
-              return el.id !== "amd" && el.id !== "nv" && el.id !== "qsv";
+              return el.id !== "amd" && el.id !== "nv" && el.id !== "qsv" && el.id !== "vaapi";
             });
           }
 
diff --git a/src_assets/common/assets/web/configs/tabs/ContainerEncoders.vue b/src_assets/common/assets/web/configs/tabs/ContainerEncoders.vue
index 084c3001f57..1dce1403adf 100644
--- a/src_assets/common/assets/web/configs/tabs/ContainerEncoders.vue
+++ b/src_assets/common/assets/web/configs/tabs/ContainerEncoders.vue
@@ -5,6 +5,7 @@ import IntelQuickSyncEncoder from './encoders/IntelQuickSyncEncoder.vue'
 import AmdAmfEncoder from './encoders/AmdAmfEncoder.vue'
 import VideotoolboxEncoder from './encoders/VideotoolboxEncoder.vue'
 import SoftwareEncoder from './encoders/SoftwareEncoder.vue'
+import VAAPIEncoder from './encoders/VAAPIEncoder.vue'
 
 const props = defineProps([
   'platform',
@@ -45,6 +46,13 @@ const config = ref(props.config)
       :config="config"
   />
 
+  <!-- VAAPI Encoder Tab -->
+  <VAAPIEncoder
+      v-if="currentTab === 'vaapi'"
+      :platform="platform"
+      :config="config"
+  />
+
   <!-- Software Encoder Tab -->
   <SoftwareEncoder
       v-if="currentTab === 'sw'"
diff --git a/src_assets/common/assets/web/configs/tabs/encoders/VAAPIEncoder.vue b/src_assets/common/assets/web/configs/tabs/encoders/VAAPIEncoder.vue
new file mode 100644
index 00000000000..2300fd6e52e
--- /dev/null
+++ b/src_assets/common/assets/web/configs/tabs/encoders/VAAPIEncoder.vue
@@ -0,0 +1,28 @@
+<script setup>
+import { ref } from 'vue'
+
+const props = defineProps([
+  'platform',
+  'config',
+])
+
+const config = ref(props.config)
+</script>
+
+<template>
+  <div id="vaapi-encoder" class="config-page">
+    <!-- Strict RC Buffer -->
+    <div class="mb-3">
+      <label for="vaapi_strict_rc_buffer" class="form-label">{{ $t('config.vaapi_strict_rc_buffer') }}</label>
+      <select id="vaapi_strict_rc_buffer" class="form-select" v-model="config.vaapi_strict_rc_buffer">
+        <option value="enabled">{{ $t('_common.enabled') }}</option>
+        <option value="disabled">{{ $t('_common.disabled_def') }}</option>
+      </select>
+      <div class="form-text">{{ $t('config.vaapi_strict_rc_buffer_desc') }}</div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+
+</style>
diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json
index 12844fcf874..a41617fe682 100644
--- a/src_assets/common/assets/web/public/assets/locale/en.json
+++ b/src_assets/common/assets/web/public/assets/locale/en.json
@@ -310,6 +310,8 @@
     "touchpad_as_ds4_desc": "If disabled, touchpad presence will not be taken into account during gamepad type selection.",
     "upnp": "UPnP",
     "upnp_desc": "Automatically configure port forwarding for streaming over the Internet",
+    "vaapi_strict_rc_buffer": "Strictly enforce frame bitrate limits for H.264/HEVC on AMD GPUs",
+    "vaapi_strict_rc_buffer_desc": "Enabling this option can avoid dropped frames over the network during scene changes, but video quality may be reduced during motion.",
     "virtual_sink": "Virtual Sink",
     "virtual_sink_desc": "Manually specify a virtual audio device to use. If unset, the device is chosen automatically. We strongly recommend leaving this field blank to use automatic device selection!",
     "virtual_sink_placeholder": "Steam Streaming Speakers",

From 61362c190652fd35ccc9b85b42d2443575d21b2e Mon Sep 17 00:00:00 2001
From: Cameron Gutman <aicommander@gmail.com>
Date: Thu, 31 Oct 2024 22:02:48 -0500
Subject: [PATCH 2/2] Eliminate the QP fallback code that was only required for
 VAAPI

---
 src/platform/linux/vaapi.cpp |   7 ++-
 src/video.cpp                | 113 +++++++++--------------------------
 src/video.h                  |   7 ---
 3 files changed, 35 insertions(+), 92 deletions(-)

diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp
index 49cad211cf9..909ade10548 100644
--- a/src/platform/linux/vaapi.cpp
+++ b/src/platform/linux/vaapi.cpp
@@ -287,9 +287,14 @@ namespace va {
           av_dict_set(options, "rc_mode", "CBR", 0);
         }
         else {
-          BOOST_LOG(info) << "Using default rate control with single frame VBV size"sv;
+          BOOST_LOG(warning) << "Using CQP with single frame VBV size"sv;
+          av_dict_set_int(options, "qp", config::video.qp, 0);
         }
       }
+      else if (!(rc_attr.value & (VA_RC_CBR | VA_RC_VBR))) {
+        BOOST_LOG(warning) << "Using CQP rate control"sv;
+        av_dict_set_int(options, "qp", config::video.qp, 0);
+      }
       else {
         BOOST_LOG(info) << "Using default rate control"sv;
       }
diff --git a/src/video.cpp b/src/video.cpp
index 603eb334732..20d22385353 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -462,7 +462,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "av1_nvenc"s,
     },
     {
@@ -472,7 +471,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "hevc_nvenc"s,
     },
     {
@@ -482,7 +480,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "h264_nvenc"s,
     },
     PARALLEL_ENCODING | REF_FRAMES_INVALIDATION | YUV444_SUPPORT  // flags
@@ -525,7 +522,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "av1_nvenc"s,
     },
     {
@@ -552,7 +548,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "hevc_nvenc"s,
     },
     {
@@ -576,7 +571,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "h264_nvenc"s,
     },
     PARALLEL_ENCODING
@@ -618,7 +612,6 @@ namespace video {
         { "profile"s, (int) qsv::profile_av1_e::high },
       },
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "av1_qsv"s,
     },
     {
@@ -652,7 +645,6 @@ namespace video {
         // Fallback options
         { "low_power"s, []() { return config::video.qsv.qsv_slow_hevc ? 0 : 1; } },
       },
-      std::nullopt,  // QP rate control fallback
       "hevc_qsv"s,
     },
     {
@@ -683,7 +675,6 @@ namespace video {
         // Fallback options
         { "low_power"s, 0 },  // Some old/low-end Intel GPUs don't support low power encoding
       },
-      std::nullopt,  // QP rate control fallback
       "h264_qsv"s,
     },
     PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE | NO_RC_BUF_LIMIT | YUV444_SUPPORT
@@ -716,7 +707,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "av1_amf"s,
     },
     {
@@ -741,7 +731,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "hevc_amf"s,
     },
     {
@@ -767,7 +756,6 @@ namespace video {
         // Fallback options
         { "usage"s, 2 /* AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY */ },  // Workaround for https://github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/410
       },
-      std::nullopt,  // QP rate control fallback
       "h264_amf"s,
     },
     PARALLEL_ENCODING
@@ -797,13 +785,10 @@ namespace video {
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
 
-      // QP rate control fallback
-      std::nullopt,
-
 #ifdef ENABLE_BROKEN_AV1_ENCODER
-      // Due to bugs preventing on-demand IDR frames from working and very poor
-      // real-time encoding performance, we do not enable libsvtav1 by default.
-      // It is only suitable for testing AV1 until the IDR frame issue is fixed.
+           // Due to bugs preventing on-demand IDR frames from working and very poor
+           // real-time encoding performance, we do not enable libsvtav1 by default.
+           // It is only suitable for testing AV1 until the IDR frame issue is fixed.
       "libsvtav1"s,
 #else
       {},
@@ -825,7 +810,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "libx265"s,
     },
     {
@@ -839,7 +823,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,  // QP rate control fallback
       "libx264"s,
     },
     H264_ONLY | PARALLEL_ENCODING | ALWAYS_REPROBE | YUV444_SUPPORT
@@ -865,7 +848,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
       "av1_vaapi"s,
     },
     {
@@ -880,7 +862,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
       "hevc_vaapi"s,
     },
     {
@@ -895,7 +876,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
       "h264_vaapi"s,
     },
     // RC buffer size will be set in platform code if supported
@@ -926,7 +906,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,
       "av1_videotoolbox"s,
     },
     {
@@ -943,7 +922,6 @@ namespace video {
       {},  // YUV444 SDR-specific options
       {},  // YUV444 HDR-specific options
       {},  // Fallback options
-      std::nullopt,
       "hevc_videotoolbox"s,
     },
     {
@@ -963,7 +941,6 @@ namespace video {
         // Fallback options
         { "flags"s, "-low_delay" },
       },
-      std::nullopt,
       "h264_videotoolbox"s,
     },
     DEFAULT
@@ -1639,49 +1616,40 @@ namespace video {
         }
       }
 
-      if (video_format[encoder_t::CBR]) {
-        auto bitrate = config.bitrate * 1000;
-        ctx->rc_max_rate = bitrate;
-        ctx->bit_rate = bitrate;
+      auto bitrate = config.bitrate * 1000;
+      ctx->rc_max_rate = bitrate;
+      ctx->bit_rate = bitrate;
 
-        if (encoder.flags & CBR_WITH_VBR) {
-          // Ensure rc_max_bitrate != bit_rate to force VBR mode
-          ctx->bit_rate--;
-        }
-        else {
-          ctx->rc_min_rate = bitrate;
-        }
+      if (encoder.flags & CBR_WITH_VBR) {
+        // Ensure rc_max_bitrate != bit_rate to force VBR mode
+        ctx->bit_rate--;
+      }
+      else {
+        ctx->rc_min_rate = bitrate;
+      }
 
-        if (encoder.flags & RELAXED_COMPLIANCE) {
-          ctx->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL;
-        }
+      if (encoder.flags & RELAXED_COMPLIANCE) {
+        ctx->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL;
+      }
 
-        if (!(encoder.flags & NO_RC_BUF_LIMIT)) {
-          if (!hardware && (ctx->slices > 1 || config.videoFormat == 1)) {
-            // Use a larger rc_buffer_size for software encoding when slices are enabled,
-            // because libx264 can severely degrade quality if the buffer is too small.
-            // libx265 encounters this issue more frequently, so always scale the
-            // buffer by 1.5x for software HEVC encoding.
-            ctx->rc_buffer_size = bitrate / ((config.framerate * 10) / 15);
-          }
-          else {
-            ctx->rc_buffer_size = bitrate / config.framerate;
+      if (!(encoder.flags & NO_RC_BUF_LIMIT)) {
+        if (!hardware && (ctx->slices > 1 || config.videoFormat == 1)) {
+          // Use a larger rc_buffer_size for software encoding when slices are enabled,
+          // because libx264 can severely degrade quality if the buffer is too small.
+          // libx265 encounters this issue more frequently, so always scale the
+          // buffer by 1.5x for software HEVC encoding.
+          ctx->rc_buffer_size = bitrate / ((config.framerate * 10) / 15);
+        }
+        else {
+          ctx->rc_buffer_size = bitrate / config.framerate;
 
 #ifndef __APPLE__
-            if (encoder.name == "nvenc" && config::video.nv_legacy.vbv_percentage_increase > 0) {
-              ctx->rc_buffer_size += ctx->rc_buffer_size * config::video.nv_legacy.vbv_percentage_increase / 100;
-            }
-#endif
+          if (encoder.name == "nvenc" && config::video.nv_legacy.vbv_percentage_increase > 0) {
+            ctx->rc_buffer_size += ctx->rc_buffer_size * config::video.nv_legacy.vbv_percentage_increase / 100;
           }
+#endif
         }
       }
-      else if (video_format.qp) {
-        handle_option(*video_format.qp);
-      }
-      else {
-        BOOST_LOG(error) << "Couldn't set video quality: encoder "sv << encoder.name << " doesn't support qp"sv;
-        return nullptr;
-      }
 
       // Allow the encoding device a final opportunity to set/unset or override any options
       encode_device->init_codec_options(ctx.get(), &options);
@@ -2407,18 +2375,11 @@ namespace video {
       return false;
     }
 
-  retry:
     // If we're expecting failure, use the autoselect ref config first since that will always succeed
     // if the encoder is available.
     auto max_ref_frames_h264 = expect_failure ? -1 : validate_config(disp, encoder, config_max_ref_frames);
     auto autoselect_h264 = max_ref_frames_h264 >= 0 ? max_ref_frames_h264 : validate_config(disp, encoder, config_autoselect);
     if (autoselect_h264 < 0) {
-      if (encoder.h264.qp && encoder.h264[encoder_t::CBR]) {
-        // It's possible the encoder isn't accepting Constant Bit Rate. Turn off CBR and make another attempt
-        encoder.h264.capabilities.set();
-        encoder.h264[encoder_t::CBR] = false;
-        goto retry;
-      }
       return false;
     }
     else if (expect_failure) {
@@ -2442,7 +2403,6 @@ namespace video {
       config_autoselect.videoFormat = 1;
 
       if (disp->is_codec_supported(encoder.hevc.name, config_autoselect)) {
-      retry_hevc:
         auto max_ref_frames_hevc = validate_config(disp, encoder, config_max_ref_frames);
 
         // If H.264 succeeded with max ref frames specified, assume that we can count on
@@ -2451,13 +2411,6 @@ namespace video {
                                  max_ref_frames_hevc :
                                  validate_config(disp, encoder, config_autoselect);
 
-        if (autoselect_hevc < 0 && encoder.hevc.qp && encoder.hevc[encoder_t::CBR]) {
-          // It's possible the encoder isn't accepting Constant Bit Rate. Turn off CBR and make another attempt
-          encoder.hevc.capabilities.set();
-          encoder.hevc[encoder_t::CBR] = false;
-          goto retry_hevc;
-        }
-
         for (auto [validate_flag, encoder_flag] : packet_deficiencies) {
           encoder.hevc[encoder_flag] = (max_ref_frames_hevc & validate_flag && autoselect_hevc & validate_flag);
         }
@@ -2480,7 +2433,6 @@ namespace video {
       config_autoselect.videoFormat = 2;
 
       if (disp->is_codec_supported(encoder.av1.name, config_autoselect)) {
-      retry_av1:
         auto max_ref_frames_av1 = validate_config(disp, encoder, config_max_ref_frames);
 
         // If H.264 succeeded with max ref frames specified, assume that we can count on
@@ -2489,13 +2441,6 @@ namespace video {
                                 max_ref_frames_av1 :
                                 validate_config(disp, encoder, config_autoselect);
 
-        if (autoselect_av1 < 0 && encoder.av1.qp && encoder.av1[encoder_t::CBR]) {
-          // It's possible the encoder isn't accepting Constant Bit Rate. Turn off CBR and make another attempt
-          encoder.av1.capabilities.set();
-          encoder.av1[encoder_t::CBR] = false;
-          goto retry_av1;
-        }
-
         for (auto [validate_flag, encoder_flag] : packet_deficiencies) {
           encoder.av1[encoder_flag] = (max_ref_frames_av1 & validate_flag && autoselect_av1 & validate_flag);
         }
diff --git a/src/video.h b/src/video.h
index 6a50b2e3832..1f39764820d 100644
--- a/src/video.h
+++ b/src/video.h
@@ -120,7 +120,6 @@ namespace video {
     enum flag_e {
       PASSED,  ///< Indicates the encoder is supported.
       REF_FRAMES_RESTRICT,  ///< Set maximum reference frames.
-      CBR,  ///< Some encoders don't support CBR, if not supported attempt constant quantization parameter instead.
       DYNAMIC_RANGE,  ///< HDR support.
       YUV444,  ///< YUV 4:4:4 support.
       VUI_PARAMETERS,  ///< AMD encoder with VAAPI doesn't add VUI parameters to SPS.
@@ -135,7 +134,6 @@ namespace video {
       switch (flag) {
         _CONVERT(PASSED);
         _CONVERT(REF_FRAMES_RESTRICT);
-        _CONVERT(CBR);
         _CONVERT(DYNAMIC_RANGE);
         _CONVERT(YUV444);
         _CONVERT(VUI_PARAMETERS);
@@ -167,11 +165,6 @@ namespace video {
       std::vector<option_t> hdr444_options;
       std::vector<option_t> fallback_options;
 
-      // QP option to set in the case that CBR/VBR is not supported
-      // by the encoder. If CBR/VBR is guaranteed to be supported,
-      // don't specify this option to avoid wasteful encoder probing.
-      std::optional<option_t> qp;
-
       std::string name;
       std::bitset<MAX_FLAGS> capabilities;