Implementing ROI (Region of Interest) Encoding with OBS_ENCODER_CAP_ROI
OBS Studio enables per-region quality control through the OBS_ENCODER_CAP_ROI capability, allowing encoders like x264, NVENC, QSV, and AMF to apply priority-based QP adjustments to specific rectangular areas of each video frame.
The obsproject/obs-studio repository provides a robust framework for Region of Interest (ROI) encoding via the OBS_ENCODER_CAP_ROI encoder capability. This feature allows developers and plugin authors to designate specific screen areas for higher or lower encoding quality, optimizing bandwidth allocation during streaming or recording.
Understanding the ROI Architecture in OBS Studio
The ROI implementation in libobs follows a modular three-part architecture that separates policy from mechanism.
The Three-Part Workflow
First, the Public API layer in libobs/obs-encoder.c exposes thread-safe functions for adding, clearing, and enumerating ROI regions. Second, Encoder plugins translate these abstract regions into codec-specific data structures. Third, a Dynamic update mechanism uses a revision counter to ensure ROI maps are rebuilt only when the region list changes, eliminating per-frame overhead.
Public ROI API Functions and Data Structures
The core data structure defined in libobs/obs-encoder.h specifies pixel coordinates and quality priority:
struct obs_encoder_roi {
uint32_t top, bottom, left, right; // Pixel coordinates in input space
float priority; // Range -1.0 to +1.0
};
Key API functions implemented in libobs/obs-encoder.c include:
obs_encoder_add_roi– Validates parameters and stores a new ROI rectangle (lines 88-106)obs_encoder_clear_roi– Removes all ROIs and increments the revision counter (lines 108-117)obs_encoder_enum_roi– Iterates over stored ROIs, scaling coordinates to output size when necessary (lines 119-146)obs_encoder_get_roi_increment– Returns a monotonic revision number that encoders use to detect changes (lines 148-152)
Encoder-Specific ROI Implementations
Each encoder plugin advertises OBS_ENCODER_CAP_ROI and implements codec-specific translation of ROI priorities into quantization parameter adjustments.
x264 Software Encoding
The x264 plugin in plugins/obs-x264/obs-x264.c declares capabilities including OBS_ENCODER_CAP_ROI (lines 64-66). The implementation converts ROI priorities to QP (Quantization Parameter) offsets using macroblock-aligned coordinates:
static void roi_cb(void *param, struct obs_encoder_roi *roi)
{
const struct roi_params *rp = param;
const uint32_t roi_left = roi->left / MB_SIZE;
const uint32_t roi_top = roi->top / MB_SIZE;
const uint32_t roi_right = (roi->right - 1) / MB_SIZE;
const uint32_t roi_bottom = (roi->bottom - 1) / MB_SIZE;
const float qp_offset = -51.0f * roi->priority; // QP range 0-51
for (uint32_t mb_y = roi_top; mb_y <= roi_bottom; ++mb_y) {
for (uint32_t mb_x = roi_left; mb_x <= roi_right; ++mb_x) {
rp->map[mb_y * rp->mb_width + mb_x] = qp_offset;
}
}
}
The encoder applies these offsets via pic->prop.quant_offsets (lines 51-74 in obs-x264.c), allowing x264 to adjust per-macroblock quantization.
Intel QSV Hardware Encoding
The QSV plugin in plugins/obs-qsv11/QSV_Encoder_Internal.cpp supports up to 256 simultaneous ROI regions. The AddROI method constructs Media SDK extension buffers:
void QSV_Encoder_Internal::AddROI(mfxU32 left, mfxU32 top,
mfxU32 right, mfxU32 bottom,
mfxI16 delta)
{
if (m_roi.NumROI == 256) {
warn("Maximum number of ROIs hit, ignoring additional ROI!");
return;
}
m_roi.Header.BufferId = MFX_EXTBUFF_ENCODER_ROI;
m_roi.Header.BufferSz = sizeof(mfxExtEncoderROI);
m_roi.ROIMode = MFX_ROI_MODE_QP_DELTA;
m_roi.ROI[m_roi.NumROI] = { left, top, right, bottom, delta };
++m_roi.NumROI;
}
QSV applies these regions using MFX_ROI_MODE_QP_DELTA for hardware-accelerated quality adjustment.
NVIDIA NVENC Hardware Encoding
The NVENC plugin in plugins/obs-nvenc/nvenc.c advertises OBS_ENCODER_CAP_ROI (lines 1395-1412). The implementation uses a callback mechanism to build NVENC-specific NV_ENC_INPUT_RECT structures and per-macroblock QP delta buffers. The encoder passes these to the NVIDIA Video Codec SDK via NV_ENC_INPUT_BUF structures during the encoding session.
AMD AMF Hardware Encoding
The AMF plugin in plugins/obs-ffmpeg/texture-amf.cpp queries AMF_VIDEO_ENCODER_CAP_ROI to determine hardware support. When available, the plugin passes ROI data through the AMF_VIDEO_ENCODER_ROI_DATA property to the AMD Media Framework, enabling per-region quality control on compatible AMD GPUs.
Practical Implementation Guide
To implement ROI encoding in a custom OBS plugin, use the public API after obtaining an encoder instance:
/* Assuming `encoder` is a valid obs_encoder_t* obtained from obs_output_get_video_encoder() */
struct obs_encoder_roi roi = {
.top = 200,
.bottom = 560,
.left = 320,
.right = 960,
.priority = 0.6f // Positive values boost quality; negative reduces it
};
if (!obs_encoder_add_roi(encoder, &roi)) {
blog(LOG_WARNING, "Failed to add ROI: encoder may not support OBS_ENCODER_CAP_ROI");
}
/* To remove all regions and reset encoding parameters */
obs_encoder_clear_roi(encoder);
This example adds a rectangular region covering coordinates (320,200) to (960,560) with enhanced quality priority. The obs_encoder_add_roi function returns false if the encoder lacks OBS_ENCODER_CAP_ROI or if parameters are invalid.
Summary
- OBS_ENCODER_CAP_ROI enables per-region quality control across software and hardware encoders in OBS Studio.
- The public API in
libobs/obs-encoder.cprovidesobs_encoder_add_roi,obs_encoder_clear_roi, andobs_encoder_enum_roifor managing regions. - Encoder plugins translate abstract ROI priorities into codec-specific QP offsets using macroblock-level maps (x264), Media SDK extension buffers (QSV), or proprietary SDK structures (NVENC, AMF).
- The increment counter mechanism ensures ROI maps are rebuilt only when the region list changes, minimizing per-frame overhead.
Frequently Asked Questions
What is the valid range for ROI priority values?
The priority field in struct obs_encoder_roi accepts floating-point values from -1.0 to +1.0. Positive values instruct the encoder to allocate more bits to the region, improving visual quality, while negative values reduce quality. A priority of zero applies no quality adjustment to the specified rectangular area.
How does OBS Studio optimize ROI map generation performance?
OBS Studio uses a revision counter (roi_increment) to avoid rebuilding ROI maps on every frame. When obs_encoder_add_roi or obs_encoder_clear_roi modifies the ROI list, the increment value changes. Encoder plugins cache the current increment value and only regenerate the macroblock-level QP map when obs_encoder_get_roi_increment returns a different value, significantly reducing CPU overhead during encoding.
Which encoders support OBS_ENCODER_CAP_ROI?
As of the current obsproject/obs-studio source code, the following encoder plugins advertise OBS_ENCODER_CAP_ROI:
- x264 (
plugins/obs-x264/obs-x264.c) – Software H.264 encoding with macroblock-level QP control - Intel QSV (
plugins/obs-qsv11/) – Hardware encoding via Intel Media SDK supporting up to 256 ROIs - NVIDIA NVENC (
plugins/obs-nvenc/nvenc.c) – Hardware encoding with per-macroblock QP delta buffers - AMD AMF (
plugins/obs-ffmpeg/texture-amf.cpp) – Hardware encoding through AMD Media Framework
Each encoder translates ROI data into codec-specific formats, such as x264's quant_offsets or QSV's mfxExtEncoderROI structures.
Can ROIs be updated dynamically during streaming?
Yes, ROIs can be added, modified, or cleared at any time during an active encoding session. The obs_encoder_add_roi and obs_encoder_clear_roi functions are thread-safe and update the encoder's internal ROI list immediately. However, because encoder plugins cache the ROI map using the increment counter, changes may take effect on the next frame that triggers a map rebuild rather than instantaneously. This design ensures thread safety while maintaining encoding performance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →