3. V4L2 Selection API:V4L2_SEL_TGT_CROP 与 V4L2_SEL_TGT_COMPOSE 目标类型详解

V4L2 Selection API 是驱动与应用程序之间协商图像裁剪(Crop)与合成(Compose)窗口的标准接口。该 API 通过 VIDIOC_G_SELECTION 和 VIDIOC_S_SELECTION ioctl 配合不同的目标类型(Target Type)来操作。本章深入剖析最核心的两个目标类型:V4L2_SEL_TGT_CROP 和 V4L2_SEL_TGT_COMPOSE,并对比它们的语义、数据流位置及配置约束。

3.1 Selection API 核心数据结构

所有 Selection 操作均通过 struct v4l2_selection 结构体传递参数。该结构体定义在 linux/v4l2-common.h 中,关键字段如下:

字段 类型 说明
type __u32 缓冲区类型,如 V4L2_BUF_TYPE_VIDEO_CAPTURE 或 V4L2_BUF_TYPE_VIDEO_OUTPUT
target __u32 目标类型,如 V4L2_SEL_TGT_CROP、V4L2_SEL_TGT_COMPOSE
flags __u32 标志位,如 V4L2_SEL_FLAG_GE / V4L2_SEL_FLAG_LE 用于约束协商
r struct v4l2_rect 矩形区域,包含 left, top, width, height
reserved __u32[11] 保留字段,必须清零
关键点: v4l2_rect 中的 left/top 是相对于源图像或目标缓冲区的坐标原点(0,0)的偏移量,单位为像素。width/height 定义裁剪或合成窗口的尺寸。

3.2 V4L2_SEL_TGT_CROP:裁剪源窗口

V4L2_SEL_TGT_CROP 用于定义从传感器或视频源中选取的感兴趣区域(ROI)。该区域决定了后续处理管线实际使用的像素数据范围。对于 Capture 设备,CROP 窗口位于“源图像”之上;对于 Output 设备,CROP 窗口定义从输入缓冲区中选取的区域。

3.2.1 语义与数据流位置

  • 方向: 从源图像中“切出”一个子矩形。
  • 默认值: 通常等于源图像的全尺寸(即 left=0, top=0, width=源宽, height=源高)。
  • 硬件约束: 驱动可能要求对齐到特定步长(如 2 像素对齐),或限制最小/最大尺寸。
  • 典型应用: 数字变焦、传感器 binning/skipping 后的有效区域提取、多路复用场景下的子画面选择。
实战提示: 在设置 CROP 之前,建议先调用 VIDIOC_G_SELECTION 获取当前驱动支持的默认 CROP 矩形和边界(通过 V4L2_SEL_TGT_CROP_BOUNDS 查询最大范围)。然后使用 V4L2_SEL_FLAG_GE 或 V4L2_SEL_FLAG_LE 标志让驱动自动调整到最接近的合法值。

3.2.2 CROP 配置流程

  1. 查询设备支持的 CROP 边界(V4L2_SEL_TGT_CROP_BOUNDS)。
  2. 查询当前 CROP 默认值(V4L2_SEL_TGT_CROP)。
  3. 应用程序设置期望的 CROP 矩形,可携带 V4L2_SEL_FLAG_GE(驱动只增不减)或 V4L2_SEL_FLAG_LE(驱动只减不增)。
  4. 驱动返回实际生效的 CROP 矩形(可能经过硬件对齐修正)。
  5. 应用程序读取修正后的矩形,用于后续的 COMPOSE 或格式配置。

3.3 V4L2_SEL_TGT_COMPOSE:合成目标窗口

V4L2_SEL_TGT_COMPOSE 用于定义裁剪后的图像如何放置到最终输出缓冲区中。它控制图像在目标帧缓冲中的位置和缩放行为。对于 Capture 设备,COMPOSE 窗口决定裁剪后的数据在用户空间缓冲区中的布局;对于 Output 设备,COMPOSE 窗口定义输入缓冲区中的图像如何映射到显示或编码区域。

3.3.1 语义与数据流位置

  • 方向: 将裁剪后的图像“合成”到目标缓冲区中的指定矩形。
  • 默认值: 通常等于裁剪后的尺寸(即无缩放、无偏移),但驱动可能支持缩放。
  • 硬件约束: 缩放比例可能受限于硬件 scaler 能力;位置可能要求对齐到像素边界。
  • 典型应用: 画中画(PiP)、图像缩放后放置到指定区域、多路视频拼接中的子窗口定位。
注意: 并非所有 V4L2 设备都支持 COMPOSE。对于不支持硬件 scaler 的简单设备,COMPOSE 窗口必须等于 CROP 窗口尺寸,且 left/top 必须为 0。在设置前应通过 VIDIOC_QUERYCAP 检查设备能力标志 V4L2_CAP_VIDEO_CAPTURE 和 V4L2_CAP_VIDEO_OUTPUT,并尝试查询 COMPOSE 边界确认支持。

3.4 CROP 与 COMPOSE 的对比与协作

对比维度 V4L2_SEL_TGT_CROP V4L2_SEL_TGT_COMPOSE
数据流阶段 源图像阶段(输入侧) 目标缓冲区阶段(输出侧)
主要作用 选择感兴趣区域,丢弃外部像素 定位与缩放裁剪后的图像到最终缓冲区
坐标参考系 源图像坐标系(0,0 为源左上角) 目标缓冲区坐标系(0,0 为缓冲区左上角)
缩放能力 无缩放(仅裁剪) 支持缩放(如果硬件 scaler 可用)
典型默认值 全源尺寸 等于 CROP 尺寸,无偏移
硬件对齐要求 常见 2 像素或 16 像素对齐 位置可能要求偶数坐标

3.5 数据流与窗口关系图

下图展示了从传感器原始图像到最终用户缓冲区的完整窗口链,清晰标出 CROP 与 COMPOSE 的协作关系:

CROP 与 COMPOSE 数据流关系图 CROP 与 COMPOSE 窗口协作数据流 传感器源图像 1920x1080 (原始全尺寸) CROP 窗口 left=100, top=50 width=800, height=600 (裁剪 ROI) COMPOSE 窗口 left=0, top=0 width=640, height=480 (缩放+定位) 用户缓冲区 640x480 选取 ROI 缩放/定位 填充 说明 CROP 决定“看什么” COMPOSE 决定“放哪里”

3.6 代码示例:查询与设置 CROP 和 COMPOSE

以下代码片段演示了在 Capture 设备上查询 CROP 边界、设置自定义 CROP 窗口,并读取 COMPOSE 默认值的完整流程:

#include <linux/v4l2-common.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int configure_crop_compose(int fd) {
    struct v4l2_selection sel;
    int ret;

    /* 1. 查询 CROP 边界(最大允许范围) */
    memset(&sel, 0, sizeof(sel));
    sel.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    sel.target = V4L2_SEL_TGT_CROP_BOUNDS;
    ret = ioctl(fd, VIDIOC_G_SELECTION, &sel);
    if (ret < 0) {
        perror("G_SELECTION CROP_BOUNDS failed");
        return -1;
    }
    printf("CROP bounds: left=%d top=%d width=%d height=%d\n",
           sel.r.left, sel.r.top, sel.r.width, sel.r.height);

    /* 2. 设置自定义 CROP 窗口(带 LE 标志,驱动只减不增) */
    memset(&sel, 0, sizeof(sel));
    sel.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    sel.target = V4L2_SEL_TGT_CROP;
    sel.flags = V4L2_SEL_FLAG_LE;
    sel.r.left = 100;
    sel.r.top = 50;
    sel.r.width = 800;
    sel.r.height = 600;
    ret = ioctl(fd, VIDIOC_S_SELECTION, &sel);
    if (ret < 0) {
        perror("S_SELECTION CROP failed");
        return -1;
    }
    printf("CROP set to: left=%d top=%d width=%d height=%d\n",
           sel.r.left, sel.r.top, sel.r.width, sel.r.height);

    /* 3. 查询当前 COMPOSE 默认值 */
    memset(&sel, 0, sizeof(sel));
    sel.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    sel.target = V4L2_SEL_TGT_COMPOSE;
    ret = ioctl(fd, VIDIOC_G_SELECTION, &sel);
    if (ret < 0) {
        /* 设备可能不支持 COMPOSE,此时忽略 */
        printf("COMPOSE not supported, using CROP size directly\n");
    } else {
        printf("COMPOSE default: left=%d top=%d width=%d height=%d\n",
               sel.r.left, sel.r.top, sel.r.width, sel.r.height);
    }

    return 0;
}
代码要点:
  • 先查询 CROP_BOUNDS 了解硬件限制,避免设置越界值。
  • 使用 V4L2_SEL_FLAG_LE 让驱动自动缩小到合法值,适合用户期望“不超过指定尺寸”的场景。
  • COMPOSE 查询失败不代表设备不可用,许多简单摄像头不支持 COMPOSE,此时应默认使用 CROP 尺寸作为输出尺寸。

3.7 常见配置陷阱与调试建议

  • 陷阱1: 未先设置 CROP 就直接设置 COMPOSE。部分驱动要求 CROP 必须先于 COMPOSE 配置,否则 COMPOSE 设置可能被拒绝或产生意外结果。
  • 陷阱2: 忽略 V4L2_SEL_FLAG_GE / V4L2_SEL_FLAG_LE 标志。不带标志时,驱动可能直接返回错误(EINVAL)如果请求的矩形不精确匹配硬件支持的值。
  • 陷阱3: 假设 COMPOSE 支持任意缩放。实际硬件 scaler 通常有最小/最大缩放比限制(如 1/4x 到 4x),超出范围会导致设置失败。
  • 调试建议: 使用 v4l2-ctl --get-crop 和 v4l2-ctl --set-crop 命令行工具快速验证设备行为;在驱动中增加 dev_dbg 打印实际生效的矩形值。
兼容性提醒: 部分老旧驱动或硬件不支持 Selection API,此时 VIDIOC_G_SELECTION 和 VIDIOC_S_SELECTION 会返回 EINVAL 或 ENOTTY。应用程序应优雅降级,回退到使用 VIDIOC_S_FMT 的 bytesperline 和 sizeimage 字段间接推断裁剪信息。