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.c provides obs_encoder_add_roi, obs_encoder_clear_roi, and obs_encoder_enum_roi for 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:

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →