# 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 f...

- Repository: [OBS Project/obs-studio](https://github.com/obsproject/obs-studio)
- Tags: 
- Published: 2026-03-03

---

**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`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-encoder.h) specifies pixel coordinates and quality priority:

```c
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`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/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:

```c
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`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/plugins/obs-qsv11/QSV_Encoder_Internal.cpp) supports up to 256 simultaneous ROI regions. The `AddROI` method constructs Media SDK extension buffers:

```cpp
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`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/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:

```c
/* 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`](https://github.com/obsproject/obs-studio/blob/main/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`:

- **x264** ([`plugins/obs-x264/obs-x264.c`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/plugins/obs-nvenc/nvenc.c)) – Hardware encoding with per-macroblock QP delta buffers
- **AMD AMF** ([`plugins/obs-ffmpeg/texture-amf.cpp`](https://github.com/obsproject/obs-studio/blob/main/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.