V4L2 (Video4Linux2) 서브시스템 심화

V4L2 프레임워크 아키텍처, videobuf2 버퍼 관리, ioctl 인터페이스, 서브디바이스/컨트롤/Media Controller, M2M 코덱, 카메라 파이프라인, UVC, Device Tree 바인딩, 유저스페이스 도구, 디버깅까지 종합 가이드.

관련 커널 소스: drivers/media/ — V4L2 코어, videobuf2, 서브디바이스 프레임워크, Media Controller, 개별 드라이버가 위치하는 Linux 미디어 서브시스템 최상위 디렉토리입니다.

V4L2 개요

Video4Linux2(V4L2)는 Linux 커널의 비디오 캡처/출력 프레임워크입니다. 원래 Video4Linux(V4L) API가 커널 2.1에서 도입되었으나, 설계상 한계로 커널 2.5에서 V4L2로 전면 재설계되었습니다. V4L2는 웹캠, TV 튜너, 하드웨어 코덱, ISP(Image Signal Processor) 등 모든 비디오 관련 디바이스를 통합 관리합니다.

디바이스 유형

V4L2는 여러 유형의 디바이스 노드를 생성합니다:

디바이스 노드매크로용도
/dev/videoNVFL_TYPE_VIDEO비디오 캡처/출력 디바이스
/dev/vbiNVFL_TYPE_VBIVertical Blanking Interval (텔레텍스트 등)
/dev/radioNVFL_TYPE_RADIO라디오 튜너
/dev/swradioNVFL_TYPE_SDRSoftware Defined Radio
/dev/v4l-touchNVFL_TYPE_TOUCH터치 디바이스
/dev/v4l-subdevNVFL_TYPE_SUBDEV서브디바이스 직접 접근
/dev/mediaNMedia Controller 디바이스

커널 소스 구조

경로설명
drivers/media/v4l2-core/V4L2 코어 프레임워크 (v4l2-dev, v4l2-ioctl, v4l2-subdev 등)
drivers/media/common/videobuf2/videobuf2 버퍼 관리 프레임워크
drivers/media/mc/Media Controller 코어
drivers/media/i2c/I2C 센서/디코더 서브디바이스 드라이버
drivers/media/platform/SoC/플랫폼 특화 드라이버 (ISP, 코덱 등)
drivers/media/usb/USB 비디오 드라이버 (uvcvideo 등)
drivers/media/pci/PCI 캡처 카드 드라이버
drivers/media/cec/HDMI CEC 프레임워크
include/media/미디어 서브시스템 헤더 파일
include/uapi/linux/videodev2.hV4L2 UAPI 헤더 (ioctl 정의)

V4L2 프레임워크 아키텍처

V4L2 프레임워크는 계층적 구조로 설계되어 있습니다. 최상위에 v4l2_device가 있고, 그 아래 하나 이상의 video_devicev4l2_subdev가 연결됩니다.

유저스페이스: open() / ioctl() / mmap() / read() 커널 경계 video_device (/dev/videoN) v4l2_device (최상위 컨테이너) v4l2_subdev (센서/ISP) vb2_queue (버퍼 관리) v4l2_ctrl_handler media_device (선택적 Media Controller)

v4l2_device 구조체

v4l2_device는 V4L2 드라이버의 최상위 컨테이너입니다. 하나의 물리 디바이스에 대해 하나의 인스턴스를 생성하며, 산하의 모든 video_devicev4l2_subdev를 관리합니다.

/* include/media/v4l2-device.h */
struct v4l2_device {
    struct device          *dev;          /* 부모 struct device */
    struct media_device    *mdev;         /* Media Controller (선택) */
    struct list_head        subdevs;      /* v4l2_subdev 리스트 */
    struct v4l2_ctrl_handler *ctrl_handler; /* 디바이스 레벨 컨트롤 */
    char                    name[36];    /* 드라이버 이름 */
    void                    (*notify)(struct v4l2_subdev *, unsigned, void *);
    struct mutex            lock;         /* 직렬화 */
    ...
};

/* 등록/해제 */
int  v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
void v4l2_device_unregister(struct v4l2_device *v4l2_dev);

video_device 구조체

video_device는 유저스페이스에 노출되는 /dev/videoN 디바이스 노드를 나타냅니다. video_register_device()로 등록하면 캐릭터 디바이스가 생성됩니다.

/* include/media/v4l2-dev.h */
struct video_device {
    const struct v4l2_file_operations *fops;
    const struct v4l2_ioctl_ops      *ioctl_ops;
    struct v4l2_device              *v4l2_dev;
    struct vb2_queue               *queue;       /* vb2 큐 연결 */
    struct v4l2_ctrl_handler       *ctrl_handler;
    struct mutex                   *lock;
    u32                              device_caps; /* V4L2_CAP_* */
    enum vfl_devnode_type            vfl_type;
    int                              minor;
    char                             name[32];
    ...
};

/* 등록: type은 VFL_TYPE_VIDEO 등 */
int video_register_device(struct video_device *vdev,
                         enum vfl_devnode_type type, int nr);
void video_unregister_device(struct video_device *vdev);

v4l2_fh (파일 핸들)

v4l2_fh는 유저스페이스의 open() 호출마다 생성되는 per-open 핸들입니다. 이벤트 구독, 우선순위 관리 등에 사용됩니다.

struct v4l2_fh {
    struct list_head         list;       /* video_device의 fh_list에 연결 */
    struct video_device     *vdev;
    struct v4l2_ctrl_handler *ctrl_handler;
    enum v4l2_priority       prio;       /* 우선순위 */
    struct v4l2_events      *events;     /* 이벤트 큐 */
    ...
};

/* open/close 콜백에서 사용 */
void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev);
void v4l2_fh_add(struct v4l2_fh *fh);
void v4l2_fh_del(struct v4l2_fh *fh);
void v4l2_fh_exit(struct v4l2_fh *fh);

videobuf2 (vb2) 버퍼 관리

videobuf2(vb2)는 V4L2의 핵심 버퍼 관리 프레임워크입니다. DMA 버퍼 할당, 유저스페이스 매핑, 스트리밍 큐 관리를 담당합니다. 드라이버는 직접 버퍼를 관리할 필요 없이 vb2 콜백만 구현하면 됩니다.

vb2_queue 구조체와 초기화

/* include/media/videobuf2-core.h */
struct vb2_queue {
    unsigned int              type;           /* V4L2_BUF_TYPE_* */
    enum vb2_io_modes         io_modes;       /* VB2_MMAP | VB2_USERPTR | VB2_DMABUF */
    const struct vb2_ops     *ops;            /* 드라이버 콜백 */
    const struct vb2_mem_ops *mem_ops;        /* 메모리 할당자 */
    struct mutex             *lock;           /* 직렬화 lock */
    unsigned int              min_queued_buffers;
    u32                       timestamp_flags;
    struct device            *dev;            /* DMA 용 디바이스 */
    ...
};

/* 초기화 예제 */
static int my_queue_init(struct my_device *mydev)
{
    struct vb2_queue *q = &mydev->vb_queue;

    q->type             = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    q->io_modes         = VB2_MMAP | VB2_DMABUF;
    q->ops              = &my_vb2_ops;
    q->mem_ops          = &vb2_dma_contig_memops;
    q->drv_priv         = mydev;
    q->buf_struct_size  = sizeof(struct my_buffer);
    q->timestamp_flags  = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
    q->lock             = &mydev->mutex;
    q->dev              = mydev->dev;

    return vb2_queue_init(q);
}

vb2_buffer / vb2_v4l2_buffer

개별 버퍼는 vb2_buffer로 관리됩니다. V4L2 드라이버는 이를 감싸는 vb2_v4l2_buffer를 사용하며, 드라이버 고유 구조체에 임베딩하는 패턴이 일반적입니다.

/* 드라이버 고유 버퍼 구조체 — 임베딩 패턴 */
struct my_buffer {
    struct vb2_v4l2_buffer  vb;      /* 반드시 첫 번째 멤버 */
    struct list_head        list;    /* 드라이버 내부 큐 */
    dma_addr_t              paddr;   /* DMA 물리 주소 */
};

/* container_of로 변환 */
static inline struct my_buffer *to_my_buffer(struct vb2_v4l2_buffer *vbuf)
{
    return container_of(vbuf, struct my_buffer, vb);
}

메모리 타입 비교

메모리 타입매크로할당 주체매핑 방식사용 시나리오
MMAPV4L2_MEMORY_MMAP커널 (vb2)mmap()일반적 캡처, 가장 보편적
USERPTRV4L2_MEMORY_USERPTR유저스페이스유저 포인터 전달유저 버퍼 재사용, 레거시
DMABUFV4L2_MEMORY_DMABUF외부 (DMA-BUF)fd 기반 공유GPU/디스플레이 간 제로카피
권장: 새 드라이버에서는 MMAP + DMABUF 조합을 권장합니다. USERPTR은 레거시 호환성 용도로만 사용하세요. DMABUF는 GPU/디스플레이 파이프라인과 제로카피 버퍼 공유 시 필수입니다.

vb2_ops 콜백

드라이버는 vb2_ops 콜백을 구현하여 vb2 프레임워크와 상호작용합니다:

static const struct vb2_ops my_vb2_ops = {
    .queue_setup     = my_queue_setup,     /* 버퍼 수/크기 결정 */
    .buf_prepare     = my_buf_prepare,     /* 큐잉 전 버퍼 검증 */
    .buf_queue       = my_buf_queue,       /* 버퍼를 하드웨어에 전달 */
    .start_streaming = my_start_streaming, /* DMA 시작 */
    .stop_streaming  = my_stop_streaming,  /* DMA 중지, 버퍼 반환 */
    .buf_init        = my_buf_init,        /* (선택) 버퍼 초기 설정 */
    .buf_cleanup     = my_buf_cleanup,     /* (선택) 버퍼 정리 */
    .wait_prepare    = vb2_ops_wait_prepare,  /* lock 해제 헬퍼 */
    .wait_finish     = vb2_ops_wait_finish,   /* lock 획득 헬퍼 */
};

각 콜백의 호출 시점:

콜백호출 시점주요 역할
queue_setupVIDIOC_REQBUFS / CREATE_BUFS필요한 버퍼 수, 플레인 수, 크기 반환
buf_init버퍼 최초 할당 시DMA 매핑 등 1회 초기화
buf_prepareVIDIOC_QBUF버퍼 크기 검증, 타임스탬프 초기화
buf_queue버퍼가 vb2에서 드라이버로 전달하드웨어 DMA 큐에 추가
start_streamingVIDIOC_STREAMON하드웨어 DMA 엔진 시작
stop_streamingVIDIOC_STREAMOFFDMA 중지, 모든 버퍼 vb2_buffer_done(VB2_BUF_STATE_ERROR)
buf_cleanup버퍼 해제 시DMA 언매핑 등 정리

스트리밍 흐름

유저스페이스의 전형적인 V4L2 캡처 루프:

/* 1. 버퍼 요청 */
ioctl(fd, VIDIOC_REQBUFS, &reqbufs);    /* → queue_setup() */

/* 2. 버퍼 정보 조회 + mmap */
for (i = 0; i < n; i++) {
    ioctl(fd, VIDIOC_QUERYBUF, &buf[i]);
    buffers[i] = mmap(NULL, buf[i].length,
                      PROT_READ | PROT_WRITE, MAP_SHARED,
                      fd, buf[i].m.offset);
}

/* 3. 모든 버퍼 큐잉 */
for (i = 0; i < n; i++)
    ioctl(fd, VIDIOC_QBUF, &buf[i]);      /* → buf_prepare() → buf_queue() */

/* 4. 스트리밍 시작 */
ioctl(fd, VIDIOC_STREAMON, &type);       /* → start_streaming() */

/* 5. 캡처 루프 */
while (running) {
    poll(fds, 1, -1);                       /* 프레임 대기 */
    ioctl(fd, VIDIOC_DQBUF, &buf);          /* 완료된 버퍼 수거 */
    process_frame(buffers[buf.index], buf.bytesused);
    ioctl(fd, VIDIOC_QBUF, &buf);           /* 버퍼 재큐잉 */
}

/* 6. 스트리밍 중지 */
ioctl(fd, VIDIOC_STREAMOFF, &type);      /* → stop_streaming() */

V4L2 ioctl 인터페이스

V4L2는 include/uapi/linux/videodev2.h에 정의된 풍부한 ioctl 세트를 제공합니다. 핵심 ioctl을 범주별로 살펴봅니다.

VIDIOC_QUERYCAP — 디바이스 능력 조회

struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
/* cap.capabilities: 전체 디바이스, cap.device_caps: 이 노드의 능력 */

주요 capability 플래그:

플래그의미
V4L2_CAP_VIDEO_CAPTURE0x00000001싱글플레인 비디오 캡처
V4L2_CAP_VIDEO_CAPTURE_MPLANE0x00001000멀티플레인 비디오 캡처
V4L2_CAP_VIDEO_OUTPUT0x00000002비디오 출력
V4L2_CAP_VIDEO_M2M_MPLANE0x00004000M2M 멀티플레인
V4L2_CAP_STREAMING0x04000000스트리밍 I/O (REQBUFS)
V4L2_CAP_META_CAPTURE0x00800000메타데이터 캡처
V4L2_CAP_IO_MC0x20000000Media Controller 기반 I/O

포맷 ioctl (ENUM_FMT / G_FMT / S_FMT / TRY_FMT)

포맷 negotiation은 V4L2의 핵심 메커니즘입니다:

/* 지원 포맷 열거 */
struct v4l2_fmtdesc fmtdesc = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
    printf("[%d] %s (%.4s)\n", fmtdesc.index,
           fmtdesc.description, (char *)&fmtdesc.pixelformat);
    fmtdesc.index++;
}

/* 현재 포맷 조회 */
struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
ioctl(fd, VIDIOC_G_FMT, &fmt);

/* 포맷 설정 */
fmt.fmt.pix.width       = 1920;
fmt.fmt.pix.height      = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field       = V4L2_FIELD_NONE;
ioctl(fd, VIDIOC_S_FMT, &fmt);
/* 드라이버가 요청값을 가장 가까운 지원값으로 조정할 수 있음 */

주요 픽셀 포맷:

매크로FourCC설명
V4L2_PIX_FMT_YUYVYUYVYUV 4:2:2 packed
V4L2_PIX_FMT_NV12NV12YUV 4:2:0 semi-planar (Y + UV interleaved)
V4L2_PIX_FMT_NV21NV21YUV 4:2:0 semi-planar (Y + VU, Android 선호)
V4L2_PIX_FMT_YUV420YU12YUV 4:2:0 3-planar
V4L2_PIX_FMT_RGB24RGB3RGB 24bpp packed
V4L2_PIX_FMT_SRGGB10RG1010-bit Bayer RGGB
V4L2_PIX_FMT_MJPEGMJPGMotion JPEG
V4L2_PIX_FMT_H264H264H.264 elementary stream

버퍼 ioctl

ioctl방향설명
VIDIOC_REQBUFSRW버퍼 할당 요청, 개수 협상
VIDIOC_QUERYBUFRW버퍼 상태/오프셋 조회 (mmap 용)
VIDIOC_QBUFRW버퍼를 드라이버 큐에 제출
VIDIOC_DQBUFRW완료된 버퍼 수거 (blocking)
VIDIOC_EXPBUFRWMMAP 버퍼를 DMA-BUF fd로 내보내기
VIDIOC_CREATE_BUFSRW추가 버퍼 할당 (다른 포맷 가능)

스트리밍 제어와 이벤트

/* 스트리밍 시작/중지 */
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON,  &type);
ioctl(fd, VIDIOC_STREAMOFF, &type);

/* 이벤트 구독 (예: EOS, 소스 변경) */
struct v4l2_event_subscription sub = {
    .type = V4L2_EVENT_EOS,   /* 또는 V4L2_EVENT_SOURCE_CHANGE */
};
ioctl(fd, VIDIOC_SUBSCRIBE_EVENT, &sub);

/* 이벤트 수신 (DQEVENT) */
struct v4l2_event ev;
ioctl(fd, VIDIOC_DQEVENT, &ev);

Selection API (크롭/컴포즈)

Selection API는 레거시 crop API를 대체합니다. 캡처 시 소스 영역(crop)과 출력 시 대상 영역(compose)을 설정합니다:

struct v4l2_selection sel = {
    .type   = V4L2_BUF_TYPE_VIDEO_CAPTURE,
    .target = V4L2_SEL_TGT_CROP,       /* 캡처할 소스 영역 */
    .r      = { .left = 100, .top = 100, .width = 640, .height = 480 },
};
ioctl(fd, VIDIOC_S_SELECTION, &sel);

/* 주요 target 값 */
/* V4L2_SEL_TGT_CROP          — 캡처: 소스에서 읽을 영역 */
/* V4L2_SEL_TGT_CROP_BOUNDS   — 크롭 가능 최대 범위 */
/* V4L2_SEL_TGT_COMPOSE       — 출력: 버퍼 내 배치 영역 */
/* V4L2_SEL_TGT_COMPOSE_BOUNDS— 컴포즈 가능 최대 범위 */

V4L2 드라이버 개발

V4L2 캡처 드라이버의 핵심 구성 요소를 살펴봅니다.

v4l2_file_operations 설정

static const struct v4l2_file_operations my_fops = {
    .owner          = THIS_MODULE,
    .open           = v4l2_fh_open,          /* 기본 핸들러 사용 가능 */
    .release        = vb2_fop_release,       /* vb2 정리 포함 */
    .read           = vb2_fop_read,          /* read() I/O (선택) */
    .poll           = vb2_fop_poll,          /* poll/select 지원 */
    .mmap           = vb2_fop_mmap,          /* mmap 매핑 */
    .unlocked_ioctl = video_ioctl2,          /* V4L2 ioctl 디스패처 */
};

v4l2_ioctl_ops 스켈레톤

static const struct v4l2_ioctl_ops my_ioctl_ops = {
    /* 능력 조회 */
    .vidioc_querycap         = my_querycap,

    /* 포맷 */
    .vidioc_enum_fmt_vid_cap = my_enum_fmt,
    .vidioc_g_fmt_vid_cap    = my_g_fmt,
    .vidioc_s_fmt_vid_cap    = my_s_fmt,
    .vidioc_try_fmt_vid_cap  = my_try_fmt,

    /* 버퍼 — vb2 헬퍼 사용 */
    .vidioc_reqbufs          = vb2_ioctl_reqbufs,
    .vidioc_querybuf         = vb2_ioctl_querybuf,
    .vidioc_qbuf             = vb2_ioctl_qbuf,
    .vidioc_dqbuf            = vb2_ioctl_dqbuf,
    .vidioc_expbuf           = vb2_ioctl_expbuf,
    .vidioc_create_bufs      = vb2_ioctl_create_bufs,
    .vidioc_streamon         = vb2_ioctl_streamon,
    .vidioc_streamoff        = vb2_ioctl_streamoff,

    /* 입력 */
    .vidioc_enum_input       = my_enum_input,
    .vidioc_g_input          = my_g_input,
    .vidioc_s_input          = my_s_input,

    /* 이벤트 */
    .vidioc_subscribe_event   = v4l2_ctrl_subscribe_event,
    .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};

완전한 캡처 드라이버 예제

#include <linux/module.h>
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/videobuf2-dma-contig.h>

struct my_cam {
    struct v4l2_device       v4l2_dev;
    struct video_device      vdev;
    struct vb2_queue         queue;
    struct v4l2_ctrl_handler ctrl_handler;
    struct mutex            lock;
    struct v4l2_format      fmt;
    struct list_head        buf_list;
    spinlock_t              slock;
};

struct my_buffer {
    struct vb2_v4l2_buffer vb;
    struct list_head       list;
};

/* vb2 콜백 */
static int my_queue_setup(struct vb2_queue *q,
    unsigned int *nbuffers, unsigned int *nplanes,
    unsigned int sizes[], struct device *alloc_devs[])
{
    struct my_cam *cam = vb2_get_drv_priv(q);
    sizes[0] = cam->fmt.fmt.pix.sizeimage;
    *nplanes = 1;
    return 0;
}

static void my_buf_queue(struct vb2_buffer *vb)
{
    struct my_cam *cam = vb2_get_drv_priv(vb->vb2_queue);
    struct my_buffer *buf = container_of(
        to_vb2_v4l2_buffer(vb), struct my_buffer, vb);
    unsigned long flags;

    spin_lock_irqsave(&cam->slock, flags);
    list_add_tail(&buf->list, &cam->buf_list);
    spin_unlock_irqrestore(&cam->slock, flags);
}

static int my_start_streaming(struct vb2_queue *q, unsigned int count)
{
    /* 하드웨어 DMA 시작 */
    return 0;
}

static void my_stop_streaming(struct vb2_queue *q)
{
    struct my_cam *cam = vb2_get_drv_priv(q);
    struct my_buffer *buf, *tmp;
    unsigned long flags;

    /* 하드웨어 DMA 중지 */
    spin_lock_irqsave(&cam->slock, flags);
    list_for_each_entry_safe(buf, tmp, &cam->buf_list, list) {
        list_del(&buf->list);
        vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
    }
    spin_unlock_irqrestore(&cam->slock, flags);
}

/* probe 함수 (간략화) */
static int my_cam_probe(struct platform_device *pdev)
{
    struct my_cam *cam;
    int ret;

    cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL);
    mutex_init(&cam->lock);
    spin_lock_init(&cam->slock);
    INIT_LIST_HEAD(&cam->buf_list);

    /* v4l2_device 등록 */
    ret = v4l2_device_register(&pdev->dev, &cam->v4l2_dev);

    /* vb2_queue 초기화 */
    cam->queue.type      = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    cam->queue.io_modes  = VB2_MMAP | VB2_DMABUF;
    cam->queue.ops       = &my_vb2_ops;
    cam->queue.mem_ops   = &vb2_dma_contig_memops;
    cam->queue.drv_priv  = cam;
    cam->queue.buf_struct_size = sizeof(struct my_buffer);
    cam->queue.lock      = &cam->lock;
    cam->queue.dev       = &pdev->dev;
    vb2_queue_init(&cam->queue);

    /* video_device 설정 */
    cam->vdev.v4l2_dev    = &cam->v4l2_dev;
    cam->vdev.queue       = &cam->queue;
    cam->vdev.fops        = &my_fops;
    cam->vdev.ioctl_ops   = &my_ioctl_ops;
    cam->vdev.lock        = &cam->lock;
    cam->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    strscpy(cam->vdev.name, "my-cam", sizeof(cam->vdev.name));
    video_set_drvdata(&cam->vdev, cam);

    return video_register_device(&cam->vdev, VFL_TYPE_VIDEO, -1);
}

V4L2 서브디바이스 프레임워크

복잡한 미디어 파이프라인(센서 → CSI-2 리시버 → ISP → DMA 엔진)에서 각 하드웨어 블록을 v4l2_subdev로 모델링합니다. 서브디바이스는 v4l2_device에 등록되며, Media Controller를 통해 연결됩니다.

v4l2_subdev 구조체

/* include/media/v4l2-subdev.h */
struct v4l2_subdev {
    struct list_head             list;        /* v4l2_device의 subdevs 리스트 */
    struct v4l2_device          *v4l2_dev;
    const struct v4l2_subdev_ops *ops;
    const struct v4l2_subdev_internal_ops *internal_ops;
    struct v4l2_ctrl_handler   *ctrl_handler;
    struct media_entity         entity;      /* Media Controller 엔터티 */
    u32                          flags;       /* V4L2_SUBDEV_FL_* */
    char                         name[52];
    ...
};

/* 등록/해제 */
int  v4l2_device_register_subdev(struct v4l2_device *, struct v4l2_subdev *);
void v4l2_device_unregister_subdev(struct v4l2_subdev *);

v4l2_subdev_ops (core/video/pad)

struct v4l2_subdev_ops {
    const struct v4l2_subdev_core_ops  *core;   /* log_status, ioctl 등 */
    const struct v4l2_subdev_video_ops *video;  /* s_stream 등 */
    const struct v4l2_subdev_pad_ops   *pad;    /* 포맷/Selection 협상 */
    const struct v4l2_subdev_sensor_ops *sensor; /* 센서 전용 */
};

/* video ops — 스트리밍 제어 */
struct v4l2_subdev_video_ops {
    int (*s_stream)(struct v4l2_subdev *sd, int enable);
    int (*g_frame_interval)(...);
    int (*s_frame_interval)(...);
    ...
};

/* pad ops — 포맷 협상 */
struct v4l2_subdev_pad_ops {
    int (*enum_mbus_code)(...);     /* 미디어 버스 코드 열거 */
    int (*get_fmt)(...);             /* 패드 포맷 조회 */
    int (*set_fmt)(...);             /* 패드 포맷 설정 */
    int (*get_selection)(...);       /* 크롭/컴포즈 조회 */
    int (*set_selection)(...);       /* 크롭/컴포즈 설정 */
    int (*enable_streams)(...);     /* Streams API */
    int (*disable_streams)(...);    /* Streams API */
    ...
};

패드 오퍼레이션과 미디어 버스 포맷

서브디바이스의 패드(입출력 포트)는 미디어 버스 포맷(v4l2_mbus_framefmt)으로 데이터 형식을 협상합니다:

struct v4l2_mbus_framefmt {
    __u32 width;          /* 이미지 너비 */
    __u32 height;         /* 이미지 높이 */
    __u32 code;           /* MEDIA_BUS_FMT_* */
    __u32 field;          /* V4L2_FIELD_NONE 등 */
    __u32 colorspace;     /* V4L2_COLORSPACE_* */
    ...
};

/* 주요 미디어 버스 코드 예시 */
/* MEDIA_BUS_FMT_SRGGB10_1X10  — 10-bit Bayer RGGB */
/* MEDIA_BUS_FMT_YUYV8_2X8     — YUV 4:2:2 8-bit */
/* MEDIA_BUS_FMT_UYVY8_1X16    — YUV 4:2:2 16-bit bus */
/* MEDIA_BUS_FMT_RGB888_1X24   — RGB 24-bit */

Streams API (멀티플렉스 라우팅)

최신 커널(6.3+)의 Streams API는 하나의 패드에서 여러 데이터 스트림을 다중화할 수 있습니다. CSI-2 가상 채널이나 멀티카메라 시스템에서 활용됩니다:

/* 라우팅 설정 */
struct v4l2_subdev_route routes[] = {
    { .sink_pad = 0, .sink_stream = 0,
      .source_pad = 1, .source_stream = 0,
      .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE },
    { .sink_pad = 0, .sink_stream = 1,
      .source_pad = 2, .source_stream = 0,
      .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE },
};
/* CSI-2 VC0 → 패드1, CSI-2 VC1 → 패드2 라우팅 */

I2C 센서 서브디바이스 드라이버 예제

struct my_sensor {
    struct v4l2_subdev        sd;
    struct media_pad          pad;   /* source 패드 1개 */
    struct v4l2_ctrl_handler  ctrl_handler;
    struct v4l2_mbus_framefmt fmt;
};

static int my_sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
    struct my_sensor *sensor = container_of(sd, struct my_sensor, sd);
    if (enable)
        return my_sensor_start(sensor);   /* I2C 레지스터 설정 */
    return my_sensor_stop(sensor);
}

static int my_sensor_set_fmt(struct v4l2_subdev *sd,
    struct v4l2_subdev_state *state,
    struct v4l2_subdev_format *fmt)
{
    /* 포맷 협상: 드라이버가 지원하는 가장 가까운 값으로 조정 */
    fmt->format.code   = MEDIA_BUS_FMT_SRGGB10_1X10;
    fmt->format.width  = clamp(fmt->format.width, 640, 3840);
    fmt->format.height = clamp(fmt->format.height, 480, 2160);
    *v4l2_subdev_state_get_format(state, fmt->pad) = fmt->format;
    return 0;
}

static int my_sensor_probe(struct i2c_client *client)
{
    struct my_sensor *sensor;

    sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
    v4l2_i2c_subdev_init(&sensor->sd, client, &my_sensor_ops);

    sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
    sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
    media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);

    sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
    return v4l2_async_register_subdev(&sensor->sd);
}

V4L2 컨트롤 프레임워크

V4L2 컨트롤 프레임워크는 밝기, 대비, 노출, 화이트 밸런스 등의 파라미터를 통합 관리합니다. 드라이버는 v4l2_ctrl_handler를 초기화하고 컨트롤을 등록하기만 하면 됩니다.

v4l2_ctrl_handler 초기화

struct v4l2_ctrl_handler *hdl = &mydev->ctrl_handler;

v4l2_ctrl_handler_init(hdl, 8);  /* hint: 예상 컨트롤 수 */

/* 표준 컨트롤 추가 */
v4l2_ctrl_new_std(hdl, &my_ctrl_ops,
    V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);   /* min, max, step, default */
v4l2_ctrl_new_std(hdl, &my_ctrl_ops,
    V4L2_CID_CONTRAST,   0, 255, 1, 128);
v4l2_ctrl_new_std_menu(hdl, &my_ctrl_ops,
    V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_APERTURE_PRIORITY, 0,
    V4L2_EXPOSURE_AUTO);

if (hdl->error)
    return hdl->error;

/* video_device 또는 v4l2_subdev에 연결 */
mydev->vdev.ctrl_handler = hdl;
/* 또는: mydev->sd.ctrl_handler = hdl; */

표준 컨트롤 (V4L2_CID_*)

카테고리주요 컨트롤 ID설명
UserV4L2_CID_BRIGHTNESS, _CONTRAST, _SATURATION기본 이미지 조정
CameraV4L2_CID_EXPOSURE_AUTO, _EXPOSURE_ABSOLUTE노출 제어
CameraV4L2_CID_AUTO_WHITE_BALANCE, _WHITE_BALANCE_TEMPERATURE화이트 밸런스
CameraV4L2_CID_FOCUS_AUTO, _FOCUS_ABSOLUTE오토포커스
Image SourceV4L2_CID_ANALOGUE_GAIN, _DIGITAL_GAIN센서 게인
Image ProcessingV4L2_CID_TEST_PATTERN테스트 패턴
CodecV4L2_CID_MPEG_VIDEO_BITRATE, _GOP_SIZE비디오 인코딩

커스텀/컴파운드 컨트롤

/* 커스텀 컨트롤 정의 */
static const struct v4l2_ctrl_config my_custom_ctrl = {
    .ops  = &my_ctrl_ops,
    .id   = V4L2_CID_USER_BASE + 0x1000,
    .name = "My Custom Control",
    .type = V4L2_CTRL_TYPE_INTEGER,
    .min  = 0, .max = 100, .step = 1, .def = 50,
};
v4l2_ctrl_new_custom(hdl, &my_custom_ctrl, NULL);

/* 컴파운드 컨트롤 (H.264 SPS 등) */
v4l2_ctrl_new_std_compound(hdl, &my_ctrl_ops,
    V4L2_CID_STATELESS_H264_SPS,
    v4l2_ctrl_ptr_create(NULL));

v4l2_ctrl_ops 콜백과 클러스터

static int my_s_ctrl(struct v4l2_ctrl *ctrl)
{
    struct my_device *dev = container_of(
        ctrl->handler, struct my_device, ctrl_handler);

    switch (ctrl->id) {
    case V4L2_CID_BRIGHTNESS:
        return my_hw_set_brightness(dev, ctrl->val);
    case V4L2_CID_EXPOSURE_AUTO:
        /* 클러스터: auto가 켜지면 manual exposure는 비활성 */
        return my_hw_set_exposure(dev, ctrl);
    }
    return -EINVAL;
}

static const struct v4l2_ctrl_ops my_ctrl_ops = {
    .s_ctrl = my_s_ctrl,
};

/* 클러스터: auto/manual 컨트롤 연결 */
v4l2_ctrl_auto_cluster(2, &dev->auto_exposure, V4L2_EXPOSURE_MANUAL, false);

Media Controller

Media Controller는 복잡한 미디어 파이프라인의 토폴로지를 유저스페이스에 노출합니다. 센서, ISP, DMA 엔진 등의 연결 관계를 엔터티/패드/링크 그래프로 표현합니다.

media_device 개요

struct media_device {
    struct device    *dev;
    char             model[32];
    struct list_head  entities;    /* media_entity 리스트 */
    struct list_head  pads;
    struct list_head  links;
    const struct media_device_ops *ops;
    ...
};

/* 초기화 + 등록 */
media_device_init(mdev);
strscpy(mdev->model, "My Camera", sizeof(mdev->model));
media_device_register(mdev);

/* v4l2_device에 연결 */
v4l2_dev->mdev = mdev;
/* 엔터티: 하드웨어 블록 (센서, ISP 등) */
struct media_entity {
    char              name[32];
    u32               function;    /* MEDIA_ENT_F_* */
    struct media_pad *pads;
    u16               num_pads;
    ...
};

/* 패드: 엔터티의 입출력 포트 */
struct media_pad {
    struct media_entity *entity;
    u16                  index;
    u32                  flags;     /* MEDIA_PAD_FL_SINK/SOURCE */
};

/* 링크 생성: 센서 pad[0] → CSI pad[0] */
media_create_pad_link(&sensor->entity, 0,
                      &csi->entity, 0,
                      MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);

주요 엔터티 function 값:

매크로설명
MEDIA_ENT_F_CAM_SENSOR카메라 센서
MEDIA_ENT_F_VID_IF_BRIDGECSI-2 리시버 등 인터페이스 브리지
MEDIA_ENT_F_PROC_VIDEO_ISPISP (Image Signal Processor)
MEDIA_ENT_F_IO_V4LV4L2 DMA 엔진 (video_device)
MEDIA_ENT_F_PROC_VIDEO_ENCODER비디오 인코더
MEDIA_ENT_F_PROC_VIDEO_DECODER비디오 디코더

파이프라인 관리

/* 파이프라인 유효성 검사 + 시작 */
struct media_pipeline pipe;
media_pipeline_start(&entity->pads[0], &pipe);

/* 파이프라인 중지 */
media_pipeline_stop(&entity->pads[0]);

/* 파이프라인 내 모든 엔터티 순회 */
media_pipeline_for_each_entity(&pipe, iter, entity) {
    /* entity 처리 */
}

유저스페이스 API와 media-ctl 예제

# 토폴로지 확인
$ media-ctl -d /dev/media0 -p

# 링크 설정: 센서 → CSI 활성화
$ media-ctl -d /dev/media0 -l '"imx219 0-0010":0 -> "csi2-rx":0 [1]'

# 서브디바이스 포맷 설정
$ media-ctl -d /dev/media0 -V '"imx219 0-0010":0 [fmt:SRGGB10_1X10/1920x1080]'
$ media-ctl -d /dev/media0 -V '"csi2-rx":1 [fmt:SRGGB10_1X10/1920x1080]'

V4L2 M2M (Memory-to-Memory)

M2M 프레임워크는 입력 버퍼(OUTPUT 큐)에서 데이터를 읽어 처리 후 출력 버퍼(CAPTURE 큐)에 쓰는 하드웨어 코덱/스케일러를 모델링합니다.

M2M 프레임워크 구조

struct v4l2_m2m_dev;   /* M2M 디바이스 — v4l2_m2m_init()으로 생성 */
struct v4l2_m2m_ctx;   /* per-open context — OUTPUT/CAPTURE 큐 쌍 */

struct v4l2_m2m_ops {
    void (*device_run)(void *priv);        /* 작업 실행 */
    int  (*job_ready)(void *priv);         /* 실행 가능 여부 */
    void (*job_abort)(void *priv);         /* 작업 취소 */
};

/* 초기화 */
m2m_dev = v4l2_m2m_init(&my_m2m_ops);

/* open 시 context 생성 */
ctx->m2m_ctx = v4l2_m2m_ctx_init(m2m_dev, ctx, my_queue_init);

/* device_run에서: 입력/출력 버퍼 가져오기 */
src = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
/* 하드웨어에 작업 전달 */

/* IRQ 핸들러에서: 완료 알림 */
v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE);
v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE);
v4l2_m2m_job_finish(m2m_dev, ctx->m2m_ctx);

Stateful 코덱

Stateful 코덱(H.264/HEVC/VP9 하드웨어 인코더/디코더)은 내부에 상태 머신을 가집니다:

단계디코더 동작인코더 동작
초기화OUTPUT 포맷 설정 (압축 스트림)CAPTURE 포맷 설정 (압축 출력)
헤더 파싱비트스트림 헤더 제출 → SOURCE_CHANGE 이벤트
해상도 협상CAPTURE 포맷 재협상 (G_FMT로 확인)
스트리밍OUTPUT ↔ CAPTURE QBUF/DQBUF 루프OUTPUT ↔ CAPTURE QBUF/DQBUF 루프
플러시빈 OUTPUT 버퍼 제출 → EOS 이벤트V4L2_ENC_CMD_STOP → EOS

Stateless 코덱 (Request API)

Stateless 코덱은 프레임 단위로 디코딩 파라미터를 전달합니다. Request API를 사용하여 버퍼와 컨트롤을 원자적으로 묶습니다:

/* Request 생성 */
int req_fd;
ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd);

/* 컨트롤 설정 (H.264 SPS/PPS/Slice 등) */
struct v4l2_ext_controls ctrls = { .request_fd = req_fd, ... };
ioctl(video_fd, VIDIOC_S_EXT_CTRLS, &ctrls);

/* 버퍼 제출 */
buf.request_fd = req_fd;
buf.flags |= V4L2_BUF_FLAG_REQUEST_FD;
ioctl(video_fd, VIDIOC_QBUF, &buf);

/* Request 큐잉 */
ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE, NULL);

주요 stateless 코덱 드라이버:

드라이버SoC지원 코덱
hantroRockchip, NXPH.264, VP8, MPEG-2, VP9, AV1
cedrusAllwinnerH.264, HEVC, MPEG-2, VP8
rkvdecRockchipH.264, VP9, HEVC
visl가상 (테스트용)모든 stateless 코덱

카메라 서브시스템

임베디드 카메라는 여러 하드웨어 블록이 파이프라인으로 연결됩니다. V4L2 서브디바이스와 Media Controller를 조합하여 이 파이프라인을 모델링합니다.

카메라 파이프라인 구조

Image Sensor (v4l2_subdev) CSI-2 CSI-2 Receiver (v4l2_subdev) ISP (v4l2_subdev) DMA Engine (video_device) Memory

CSI-2 / MIPI 인터페이스

MIPI CSI-2는 임베디드 카메라의 표준 인터페이스입니다:

항목설명
물리 계층D-PHY (최대 4.5 Gbps/lane), C-PHY (최대 6 Gsps/trio)
레인 수1~4 데이터 레인 + 1 클럭 레인 (D-PHY)
가상 채널0~3 (VC), 하나의 물리 링크에 최대 4 스트림 다중화
데이터 타입RAW8/10/12/14, YUV422, RGB888, 임베디드 데이터 등
패킷 구조Short Packet (FS/FE/LS/LE) + Long Packet (이미지 데이터)

ISP 파이프라인 블록

ISP는 RAW Bayer 데이터를 처리하여 최종 이미지를 생성합니다:

블록기능
Black Level Correction센서 블랙 레벨 보정
Defect Pixel Correction불량 픽셀 보정
Lens Shading Correction렌즈 비네팅 보정
Demosaic (CFA)Bayer → RGB 변환
White Balance색온도 보정
Color Correction Matrix색 공간 변환
Gamma Correction감마 커브 적용
Noise Reduction공간/시간 노이즈 제거
Sharpening엣지 강조
Color Space ConversionRGB → YUV 변환

주요 ISP/카메라 플랫폼 드라이버:

드라이버SoC위치
rkisp1Rockchip RK3399/RK3588drivers/media/platform/rockchip/rkisp1/
sun6i-csiAllwinner A64/H6drivers/media/platform/sunxi/sun6i-csi/
imx8-isiNXP i.MX8drivers/media/platform/nxp/imx8-isi/
camssQualcomm (MSM/SDM)drivers/media/platform/qcom/camss/
mali-c55ARM Mali-C55 ISPdrivers/media/platform/arm/mali-c55/

센서 드라이버 패턴과 async 등록

카메라 파이프라인에서 센서(I2C)와 CSI-2 리시버(platform)는 서로 다른 버스에 위치합니다. async 프레임워크가 서브디바이스 간 바인딩을 처리합니다:

/* CSI-2 리시버 (platform driver) — notifier 등록 */
struct v4l2_async_notifier notifier;

v4l2_async_nf_init(¬ifier, &v4l2_dev);
asd = v4l2_async_nf_add_fwnode_remote(¬ifier,
    of_fwnode_handle(ep_node), sizeof(*asd));
notifier.ops = &my_notifier_ops;  /* .bound, .complete */
v4l2_async_nf_register(¬ifier);

/* 센서 (I2C driver) — 비동기 등록 */
v4l2_async_register_subdev(&sensor->sd);
/* → notifier의 .bound 콜백 → 링크 생성 */

UVC (USB Video Class) 드라이버

UVC는 USB 비디오 디바이스(웹캠 등)의 표준 프로토콜입니다. Linux의 uvcvideo 드라이버는 UVC 1.0~1.5를 지원하며, 별도 드라이버 설치 없이 대부분의 USB 카메라를 사용할 수 있습니다.

UVC 아키텍처 (Terminal/Unit 토폴로지)

UVC 디바이스는 Terminal과 Unit의 연결 그래프로 구성됩니다:

요소타입 ID설명
Input Terminal (Camera)ITT_CAMERA카메라 센서 — 노출, 포커스, 줌 컨트롤
Processing UnitVC_PROCESSING_UNIT밝기, 대비, 화이트 밸런스 등 이미지 처리
Extension UnitVC_EXTENSION_UNIT벤더 고유 기능
Output Terminal (Streaming)OTT_STREAMING비디오 스트림 출력
Encoding UnitVC_ENCODING_UNITH.264 인코딩 (UVC 1.5)

커널 uvcvideo 드라이버

/* 커널 설정 */
CONFIG_USB_VIDEO_CLASS=m

/* 디버그 레벨 설정 */
$ echo 0xffff > /sys/module/uvcvideo/parameters/trace

/* UVC 디바이스 정보 확인 */
$ lsusb -d 046d:0825 -v | grep -A5 VideoControl

/* Extension Unit 접근 (유저스페이스) */
/* libuvc 또는 직접 UVCIOC_CTRL_QUERY ioctl 사용 */
struct uvc_xu_control_query xquery = {
    .unit     = 3,           /* Extension Unit ID */
    .selector = 1,           /* 컨트롤 셀렉터 */
    .query    = UVC_GET_CUR,  /* GET/SET */
    .size     = 4,
    .data     = buf,
};
ioctl(fd, UVCIOC_CTRL_QUERY, &xquery);

Device Tree 바인딩

임베디드 카메라 시스템에서 센서, CSI-2 리시버, ISP의 연결은 Device Tree로 기술합니다.

센서 DT 바인딩 예제

/* IMX219 센서 DT 바인딩 */
&i2c1 {
    imx219: sensor@10 {
        compatible = "sony,imx219";
        reg = <0x10>;
        clocks = <&clk_24mhz>;
        clock-names = "xclk";

        /* 전원 */
        VANA-supply  = <®_2v8>;
        VDIG-supply  = <®_1v8>;
        VDDL-supply  = <®_1v2>;

        /* GPIO 리셋 */
        reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;

        /* CSI-2 포트 */
        port {
            imx219_out: endpoint {
                remote-endpoint = <&csi_in>;
                data-lanes = <1 2>;    /* 2 데이터 레인 */
                clock-lanes = <0>;
                link-frequencies = /bits/ 64 <456000000>;
            };
        };
    };
};

CSI-2 리시버 바인딩 예제

/* CSI-2 리시버 DT 바인딩 */
csi2: csi@fe800000 {
    compatible = "rockchip,rk3588-mipi-csi2";
    reg = <0x0 0xfe800000 0x0 0x10000>;
    clocks = <&cru SCLK_CSI2>;
    clock-names = "pclk";

    ports {
        #address-cells = <1>;
        #size-cells = <0>;

        /* sink: 센서 연결 */
        port@0 {
            reg = <0>;
            csi_in: endpoint {
                remote-endpoint = <&imx219_out>;
                data-lanes = <1 2>;
            };
        };

        /* source: ISP 연결 */
        port@1 {
            reg = <1>;
            csi_out: endpoint {
                remote-endpoint = <&isp_in>;
            };
        };
    };
};

V4L2 fwnode 파싱 API

/* 드라이버에서 DT endpoint 파싱 */
struct v4l2_fwnode_endpoint ep = {
    .bus_type = V4L2_MBUS_CSI2_DPHY,
};
struct fwnode_handle *fwnode;

fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
v4l2_fwnode_endpoint_alloc_parse(fwnode, &ep);

/* 파싱 결과 */
ep.bus.mipi_csi2.num_data_lanes;    /* 레인 수 */
ep.bus.mipi_csi2.data_lanes[0];     /* 레인 매핑 */
ep.link_frequencies[0];             /* 링크 주파수 */
fwnode_handle_put(fwnode);

유저스페이스 도구

v4l2-ctl

v4l2-ctl은 V4L2 디바이스의 범용 CLI 도구입니다 (v4l-utils 패키지):

# 디바이스 정보 확인
$ v4l2-ctl -d /dev/video0 --all

# 지원 포맷 열거
$ v4l2-ctl -d /dev/video0 --list-formats-ext

# 포맷/해상도 설정
$ v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=YUYV

# 프레임 캡처 (10프레임을 파일로)
$ v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10 --stream-to=capture.raw

# 컨트롤 조회/설정
$ v4l2-ctl -d /dev/video0 --list-ctrls
$ v4l2-ctl -d /dev/video0 --set-ctrl=brightness=180,contrast=200

# DMABUF 내보내기 캡처
$ v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 --stream-to=frame.raw --stream-dmabuf

media-ctl

# 토폴로지 텍스트 출력
$ media-ctl -d /dev/media0 -p

# 토폴로지 DOT 그래프 생성
$ media-ctl -d /dev/media0 --print-dot | dot -Tpng -o topology.png

# 링크 활성화
$ media-ctl -l '"sensor":0 -> "csi-rx":0 [1]'

# 서브디바이스 포맷 설정
$ media-ctl -V '"sensor":0 [fmt:SGRBG10_1X10/1920x1080 field:none]'

v4l2-compliance (적합성 테스트)

# 전체 적합성 테스트 실행
$ v4l2-compliance -d /dev/video0

# 스트리밍 테스트 포함
$ v4l2-compliance -d /dev/video0 -s

# Media Controller 테스트
$ v4l2-compliance -m /dev/media0

# 결과 해석 */
# Total for device /dev/video0: 95, Succeeded: 93, Failed: 2, Warnings: 0
드라이버 업스트림 필수: 커널에 V4L2 드라이버를 제출할 때 v4l2-compliance를 통과해야 합니다. 실패 항목이 있으면 리뷰에서 거부됩니다.

libcamera / GStreamer / FFmpeg 연동

# libcamera — 복잡한 카메라 파이프라인 추상화
$ cam --list              # 카메라 열거
$ cam -c1 --capture=10    # 10프레임 캡처

# GStreamer — V4L2 소스 파이프라인
$ gst-launch-1.0 v4l2src device=/dev/video0 ! \
    video/x-raw,width=1920,height=1080 ! videoconvert ! autovideosink

# FFmpeg — V4L2 캡처 + 인코딩
$ ffmpeg -f v4l2 -video_size 1920x1080 -i /dev/video0 -c:v libx264 output.mp4

# FFmpeg — V4L2 M2M 하드웨어 인코더
$ ffmpeg -f v4l2 -i /dev/video0 -c:v h264_v4l2m2m -b:v 4M output.mp4

디버깅

커널 디버그 메시지

/* 드라이버 내 디버그 출력 */
v4l2_dbg(1, debug, &mydev->v4l2_dev, "format: %dx%d\n", w, h);
v4l2_info(&mydev->v4l2_dev, "device registered\n");
v4l2_err(&mydev->v4l2_dev, "failed to start streaming\n");

/* 모듈 파라미터로 디버그 레벨 제어 */
static int debug;
module_param(debug, int, 0644);

/* 서브디바이스 디버그 */
v4l2_subdev_dbg(1, debug, sd, "setting format\n");

/* 런타임에 디버그 활성화 */
$ echo 2 > /sys/module/<driver>/parameters/debug

자주 발생하는 문제와 해결

증상원인해결
VIDIOC_DQBUF: No such deviceUSB 카메라 분리디바이스 재연결, fd 재오픈
VIDIOC_STREAMON: No space leftUSB 대역폭 부족해상도/FPS 낮추기, USB 3.0 포트 사용
VIDIOC_REQBUFS: Invalid argument잘못된 메모리 타입 / 포맷 미설정S_FMT를 먼저 호출
캡처 이미지가 녹색/깨짐포맷 불일치 (Bayer vs YUV)포맷 확인, 디모자이크 적용
프레임 드롭DMA 지연, 버퍼 부족버퍼 수 증가, CPU 부하 확인
서브디바이스 바인딩 실패DT endpoint 미스매치remote-endpoint 양방향 확인
media_pipeline_start: -EPIPE링크 미설정 또는 비활성media-ctl -l로 링크 활성화

디버깅 도구 요약

도구용도
v4l2-ctl --log-status드라이버 log_status 콜백 호출 (dmesg에 상세 상태 출력)
v4l2-complianceioctl 적합성 테스트
media-ctl -pMedia Controller 토폴로지 덤프
dmesg | grep v4l2드라이버 등록/에러 메시지
ftraceV4L2 ioctl 추적 (v4l2_ioctl 필터)
/sys/kernel/debug/드라이버별 debugfs 엔트리
perf trace -e v4l2:*V4L2 tracepoint 추적

주요 Kconfig 옵션

CONFIG 옵션설명
CONFIG_MEDIA_SUPPORT미디어 서브시스템 최상위 스위치
CONFIG_MEDIA_CAMERA_SUPPORT카메라 디바이스 지원
CONFIG_MEDIA_PLATFORM_SUPPORT플랫폼(SoC) 미디어 드라이버
CONFIG_VIDEO_V4L2V4L2 코어 프레임워크
CONFIG_VIDEO_V4L2_SUBDEV_API서브디바이스 유저스페이스 API (/dev/v4l-subdevN)
CONFIG_VIDEOBUF2_COREvideobuf2 코어
CONFIG_VIDEOBUF2_DMA_CONTIGvb2 DMA contiguous 할당자
CONFIG_VIDEOBUF2_DMA_SGvb2 DMA scatter-gather 할당자
CONFIG_VIDEOBUF2_VMALLOCvb2 vmalloc 할당자
CONFIG_MEDIA_CONTROLLERMedia Controller API
CONFIG_MEDIA_CONTROLLER_REQUEST_APIRequest API (stateless 코덱)
CONFIG_USB_VIDEO_CLASSUVC (USB Video Class) 드라이버
CONFIG_V4L2_MEM2MEM_DEVM2M 프레임워크
CONFIG_V4L2_FWNODEV4L2 fwnode (DT/ACPI) 파싱
CONFIG_VIDEO_V4L2_I2CV4L2 I2C 서브디바이스 헬퍼
빌드 팁: make menuconfig에서 "Multimedia support"(CONFIG_MEDIA_SUPPORT)를 활성화한 뒤, 필요한 카테고리(카메라/코덱/플랫폼)를 선택합니다. 개별 드라이버는 해당 카테고리 하위에 위치합니다.