/*
 * V4L2 三级枚举示例:像素格式 -> 分辨率 -> 帧率 (FPS)
 *
 * 演示三个 ioctl 的嵌套枚举:
 *   VIDIOC_ENUM_FMT         枚举像素格式
 *   VIDIOC_ENUM_FRAMESIZES  枚举每种格式的分辨率
 *   VIDIOC_ENUM_FRAMEINTERVALS 枚举每个分辨率的帧间隔(帧率)
 *
 * 帧率 = denominator / numerator(例如 1/30 就是 30fps)
 */

#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static char *dev = "/dev/video0";

/*
 * enum_frameintervals - 枚举指定格式+分辨率下的帧间隔(帧率)
 * @fd:        设备描述符
 * @fourcc:    像素格式 FourCC
 * @w:         宽度
 * @h:         高度
 *
 * 使用 VIDIOC_ENUM_FRAMEINTERVALS ioctl。
 * v4l2_frmivalenum.type 也有三种类型,用法与帧尺寸枚举类似:
 *
 *   V4L2_FRMIVAL_TYPE_DISCRETE
 *     离散帧率列表,需递增 index 多次调用。每次返回一个分数 (numerator/denominator),
 *     帧率 = denominator / numerator (单位 fps)。
 *     例如 numerator=1, denominator=30 表示 30fps。
 *
 *   V4L2_FRMIVAL_TYPE_STEPWISE
 *     步进范围,一次调用获得 min~max/step 的帧率范围。
 *
 *   V4L2_FRMIVAL_TYPE_CONTINUOUS
 *     连续范围,一次调用获得 min~max 的帧率范围,区间内任意帧率都支持。
 */
static void enum_frameintervals(int fd, unsigned int fourcc, int w, int h)
{
    struct v4l2_frmivalenum frmi;

    for (int index = 0; ; index++) {
        memset(&frmi, 0, sizeof(frmi));
        frmi.index        = index;
        frmi.pixel_format = fourcc;
        frmi.width        = w;
        frmi.height       = h;

        if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmi) < 0)
            break;

        switch (frmi.type) {
        case V4L2_FRMIVAL_TYPE_DISCRETE:
            /* 离散帧率:转换分数为浮点 fps */
            printf("              [%d] %.2f fps\n", index,
                   (float)frmi.discrete.denominator / frmi.discrete.numerator);
            break;
        case V4L2_FRMIVAL_TYPE_STEPWISE:
            /* 步进帧率:min ~ max fps,步进 step fps */
            printf("              [%d] %.2f ~ %.2f fps  step %.3f\n", index,
                   (float)frmi.stepwise.min.denominator / frmi.stepwise.min.numerator,
                   (float)frmi.stepwise.max.denominator / frmi.stepwise.max.numerator,
                   (float)frmi.stepwise.step.denominator / frmi.stepwise.step.numerator);
            return;
        case V4L2_FRMIVAL_TYPE_CONTINUOUS:
            /* 连续帧率:min ~ max fps */
            printf("              [%d] %.2f ~ %.2f fps  (continuous)\n", index,
                   (float)frmi.stepwise.min.denominator / frmi.stepwise.min.numerator,
                   (float)frmi.stepwise.max.denominator / frmi.stepwise.max.numerator);
            return;
        }
    }
}

/*
 * enum_framesizes - 枚举像素格式下的分辨率及对应帧率
 * @fd:        设备描述符
 * @fourcc:    像素格式 FourCC
 */
static void enum_framesizes(int fd, unsigned int fourcc)
{
    struct v4l2_frmsizeenum frmsize;

    /* 先获知帧尺寸类型(DISCRETE/STEPWISE/CONTINUOUS) */
    memset(&frmsize, 0, sizeof(frmsize));
    frmsize.pixel_format = fourcc;
    frmsize.index = 0;

    if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) < 0)
        return;

    switch (frmsize.type) {
    case V4L2_FRMSIZE_TYPE_DISCRETE:
        /* 离散分辨率:递增 index 枚举每一项 */
        for (int index = 0; ; index++) {
            memset(&frmsize, 0, sizeof(frmsize));
            frmsize.pixel_format = fourcc;
            frmsize.index = index;
            if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) < 0)
                break;
            printf("      [%d] %dx%d\n", index,
                   frmsize.discrete.width, frmsize.discrete.height);
            /* 进一步枚举该分辨率的帧率 */
            enum_frameintervals(fd, fourcc,
                                frmsize.discrete.width,
                                frmsize.discrete.height);
        }
        break;

    case V4L2_FRMSIZE_TYPE_STEPWISE:
        printf("      stepwise: %dx%d ~ %dx%d  step %dx%d\n",
               frmsize.stepwise.min_width,  frmsize.stepwise.min_height,
               frmsize.stepwise.max_width,  frmsize.stepwise.max_height,
               frmsize.stepwise.step_width, frmsize.stepwise.step_height);
        break;

    case V4L2_FRMSIZE_TYPE_CONTINUOUS:
        printf("      continuous: %dx%d ~ %dx%d\n",
               frmsize.stepwise.min_width,  frmsize.stepwise.min_height,
               frmsize.stepwise.max_width,  frmsize.stepwise.max_height);
        break;
    }
}

/*
 * enum_all - 三级枚举:格式 -> 分辨率 -> 帧率
 */
static void enum_all(int fd)
{
    struct v4l2_fmtdesc fmt;

    printf("====== 枚举像素格式/分辨率/帧率 ======\n");
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    for (fmt.index = 0; ; fmt.index++) {
        if (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) < 0)
            break;
        printf("  [%d] 0x%08x  %s\n",
               fmt.index, fmt.pixelformat, fmt.description);
        enum_framesizes(fd, fmt.pixelformat);
    }
    printf("\n");
}

int main(void)
{
    int fd = open(dev, O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }

    enum_all(fd);

    close(fd);
    return 0;
}

结果

root@wyl:~/c-std/for-linux/099_other/v4l2# ./002_v4l2_fmt_fps
====== 枚举像素格式/分辨率/帧率 ======
  [0] 0x56595559  YUYV 4:2:2
      [0] 96x200
              [0] 25.00 fps
      [1] 200x96
              [0] 25.00 fps
      [2] 8x8448
              [0] 25.00 fps
      [3] 8448x8
              [0] 25.00 fps
      [4] 4x2321
              [0] 25.00 fps
      [5] 96x100
              [0] 25.00 fps
      [6] 4x2637
              [0] 25.00 fps
      [7] 4x14733
              [0] 25.00 fps
      [8] 4x5188
              [0] 25.00 fps
      [9] 5188x4
              [0] 25.00 fps
      [10] 8x8642
              [0] 25.00 fps
      [11] 8642x8
              [0] 25.00 fps
      [12] 96x96
              [0] 25.00 fps
      [13] 240x240
              [0] 25.00 fps