V4L2 (Video4Linux2) 서브시스템

V4L2 서브시스템을 카메라 입력부터 코덱 처리와 출력까지 이어지는 미디어 파이프라인(Pipeline) 관점에서 심층 분석합니다. V4L2 ioctl 계약과 상태 머신, videobuf2 큐 모델과 DMA 버퍼(Buffer) 수명주기, Media Controller 그래프와 서브디바이스 링크, 센서·ISP·M2M 코덱 통합, UVC 및 Device Tree 바인딩 패턴, 포맷 협상과 프레임 동기화, gstreamer/v4l2-ctl/media-ctl 기반 검증·디버깅(Debugging) 절차까지 실전 영상 장치 드라이버(Device Driver) 개발에 필요한 핵심을 다룹니다.

전제 조건: 디바이스 드라이버DMA 문서를 먼저 읽으세요. 멀티미디어/가속기 경로는 대용량 버퍼 이동과 동기화가 성능의 핵심이므로, 메모리 경로와 큐 모델을 먼저 파악해야 합니다.
일상 비유: 이 주제는 영상 제작 파이프라인과 비슷합니다. 촬영·편집·인코딩 단계가 끊기지 않아야 결과가 나오듯이, 버퍼 큐와 하드웨어 스케줄링의 연속성이 중요합니다.

핵심 요약

  • V4L2 디바이스 모델v4l2_device, video_device, v4l2_subdev 3계층 구조로 비디오 하드웨어를 추상화합니다.
  • Streaming I/O — MMAP, USERPTR, DMABUF 세 가지 버퍼 교환 방식으로 유저스페이스와 제로카피(Zero-copy) 프레임 전달을 지원합니다.
  • videobuf2(vb2) — DMA 버퍼 할당, 매핑, 큐잉을 통합 관리하는 프레임워크로, 드라이버가 버퍼 관리를 직접 구현할 필요를 없앱니다.
  • Media Controller — ISP, 센서, 스케일러 등 하드웨어 블록 간 연결 토폴로지를 유저스페이스에 그래프로 노출합니다.
  • 픽셀 포맷과 컬러스페이스(Colorspace)V4L2_PIX_FMT_* FourCC 코드로 수백 가지 포맷을 정의하고, 색 공간 변환을 관리합니다.
  • 컨트롤 프레임워크 — 밝기, 대비, 노출, 화이트밸런스 등 파라미터를 타입 안전(Type-safe)하게 관리하고 이벤트로 변경을 통지합니다.
  • 카메라 파이프라인 — 센서(Sensor) → ISP(Image Signal Processor) → 출력까지의 다단계 처리 체인을 서브디바이스 그래프로 구성합니다.

단계별 이해

  1. V4L2 프레임워크 구조 파악v4l2_device(브릿지), video_device(캐릭터 디바이스), v4l2_subdev(하위 장치) 계층 구조와 역할을 이해합니다.

    v4l2-ctl --list-devices로 시스템의 V4L2 장치 목록을 확인하고, /dev/video* 노드와의 매핑을 파악합니다.

  2. 캡처 흐름과 버퍼 관리 학습VIDIOC_REQBUFSVIDIOC_QBUFVIDIOC_STREAMONVIDIOC_DQBUF ioctl 시퀀스를 따라가며 프레임 캡처 흐름을 이해합니다.

    videobuf2가 DMA 버퍼를 어떻게 할당하고, vb2_ops 콜백이 드라이버와 어떻게 연동되는지 코드 수준에서 추적합니다.

  3. 포맷 협상과 컨트롤 실습v4l2-ctl --set-fmt-video로 해상도/픽셀 포맷을 설정하고, --set-ctrl로 밝기/노출 파라미터를 조작합니다.

    드라이버의 .vidioc_s_fmt_vid_capv4l2_ctrl_handler가 요청을 처리하는 내부 경로를 분석합니다.

  4. Media Controller와 파이프라인 구성media-ctl -p로 하드웨어 토폴로지를 확인하고, 센서→ISP→출력 경로를 수동으로 링크 설정합니다.

    SoC 카메라 플랫폼에서 서브디바이스 간 포맷 전파(format propagation)와 패드(pad) 협상 과정을 실습합니다.

  5. V4L2 드라이버 개발과 디버깅 — 간단한 V4L2 드라이버를 작성하여 video_register_device()로 등록하고, v4l2-compliance로 API 준수를 검증합니다.

    CONFIG_VIDEO_ADV_DEBUG와 dynamic debug(dyndbg)를 활용하여 드라이버 동작을 추적하고 문제를 진단합니다.

관련 표준: V4L2 API Specification (linuxtv.org) — Video4Linux2 비디오 캡처 인터페이스 표준입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
관련 커널 소스: drivers/media/ — V4L2 코어, videobuf2, 서브디바이스 프레임워크, Media Controller, 개별 드라이버가 위치하는 Linux 미디어 서브시스템 최상위 디렉토리입니다.

V4L2 개요

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

V4L2의 설계 철학은 다양한 하드웨어를 단일 사용자 공간 API로 추상화하는 것입니다. 단순한 USB 웹캠부터 복잡한 SoC 카메라 파이프라인, 하드웨어 비디오 코덱까지 같은 ioctl 인터페이스로 제어할 수 있습니다.

V4L2 서브시스템은 Linux 미디어 생태계에서 중심적 역할을 합니다. 웹 회의, 보안 카메라, 자율 주행(Autonomous Driving), 의료 영상(Medical Imaging), 드론(Drone), 산업 검사 등 다양한 영역에서 활용됩니다. 최근에는 AI/ML 추론(Inference) 가속기와의 통합도 진행되고 있어, 카메라 입력부터 추론 출력까지 제로카피 파이프라인을 구성하는 사례가 늘고 있습니다.

이를 위해 V4L2는 다음과 같은 핵심 추상화 계층을 제공합니다:

V4L2 진화 이력

커널 버전주요 변화
2.1Video4Linux(V4L) 최초 도입 — 단순 캡처 API
2.5V4L2 전면 재설계 — ioctl 기반 인터페이스, 멀티플레인(Multi-plane) 미지원
2.6.38videobuf2 도입 — videobuf 대체, DMA-BUF 지원 기반 마련
3.3Media Controller API 안정화 — 파이프라인 토폴로지 표현
3.14멀티플레인 API 확산 — V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
4.11Request API 초기 작업 — stateless 코덱 지원 시작
5.3Request API 안정화 — MEDIA_IOC_REQUEST_ALLOC
6.1Streams API 도입 — 멀티플렉스(Multiplex) 라우팅
6.3v4l2_subdev active state — 서브디바이스 상태 관리 개선
6.8vb2 삭제 가능 버퍼 — VIDIOC_REMOVE_BUFS 지원

디바이스 유형

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

디바이스 노드매크로(Macro)용도예시
/dev/videoNVFL_TYPE_VIDEO비디오 캡처/출력 디바이스웹캠 캡처, M2M 코덱
/dev/vbiNVFL_TYPE_VBIVertical Blanking Interval (텔레텍스트 등)자막, 텔레텍스트(Teletext)
/dev/radioNVFL_TYPE_RADIO라디오 튜너FM 라디오
/dev/swradioNVFL_TYPE_SDRSoftware Defined RadioSDR 수신기
/dev/v4l-touchNVFL_TYPE_TOUCH터치 디바이스터치 센서(Sensor) 프레임
/dev/v4l-subdevNVFL_TYPE_SUBDEV서브디바이스 직접 접근센서/ISP 개별 제어
/dev/mediaNMedia Controller 디바이스토폴로지 조회/링크 설정
/dev/v4l-metaNVFL_TYPE_VIDEO메타데이터(Metadata) 캡처센서 통계 데이터

커널 소스 구조

경로설명주요 파일
drivers/media/v4l2-core/V4L2 코어 프레임워크v4l2-dev.c, v4l2-ioctl.c, v4l2-subdev.c, v4l2-ctrls-core.c
drivers/media/common/videobuf2/videobuf2 버퍼 관리 프레임워크videobuf2-core.c, videobuf2-v4l2.c, videobuf2-dma-contig.c
drivers/media/mc/Media Controller 코어mc-entity.c, mc-devnode.c, mc-request.c
drivers/media/i2c/I2C 센서/디코더 서브디바이스 드라이버imx219.c, ov5640.c, imx335.c
drivers/media/platform/SoC/플랫폼 특화 드라이버 (ISP, 코덱 등)rockchip/, qcom/, samsung/
drivers/media/usb/USB 비디오 드라이버 (uvcvideo 등)uvc/uvc_driver.c, uvc/uvc_video.c
drivers/media/pci/PCI 캡처 카드 드라이버cx23885/, tw686x/
drivers/media/cec/HDMI CEC 프레임워크core/cec-core.c
include/media/미디어 서브시스템 헤더 파일v4l2-device.h, v4l2-subdev.h, videobuf2-core.h
include/uapi/linux/videodev2.hV4L2 UAPI 헤더 (ioctl 정의)ioctl 번호, 구조체, 상수 정의

V4L2와 DRM/KMS 비교

V4L2와 DRM(Direct Rendering Manager)/KMS(Kernel Mode Setting)는 모두 미디어 데이터를 다루지만 역할이 다릅니다:

항목V4L2DRM/KMS
주요 역할비디오 캡처/인코딩/디코딩비디오 출력/디스플레이
데이터 방향카메라 → 메모리 (캡처)메모리 → 디스플레이 (출력)
디바이스 노드/dev/videoN/dev/dri/cardN
버퍼 공유DMA-BUF exporter/importerDMA-BUF exporter/importer
파이프라인 표현Media ControllerCRTC/Encoder/Connector 체인(Chain)
제로카피(Zero-copy) 연동DMA-BUF fd로 V4L2 캡처 → DRM 디스플레이 직접 전달 가능

V4L2 프레임워크 아키텍처

V4L2 프레임워크는 계층적 구조로 설계되어 있습니다. 최상위에 v4l2_device가 있고, 그 아래 하나 이상의 video_devicev4l2_subdev가 연결됩니다. 이 구조는 하나의 물리 디바이스가 여러 비디오 노드(캡처, 출력, 메타데이터)와 여러 서브디바이스(센서, ISP, CSI 리시버)를 가질 수 있는 현실을 반영합니다.

유저스페이스 애플리케이션 open() / ioctl() / mmap() / poll() / read() 커널 경계 (시스템 콜) v4l2-dev.c / v4l2-ioctl.c (디스패처) video_device (캡처) video_device (출력) video_device (메타) v4l2_device (최상위 컨테이너 — 물리 디바이스당 1개) v4l2_subdev (센서/ISP/CSI) vb2_queue (버퍼 큐 관리) v4l2_ctrl_handler (컨트롤 관리) v4l2_async_notifier (비동기 바인딩) media_device (선택적 Media Controller — 파이프라인 토폴로지) /dev/videoN 커널 내부 구조체 서브 컴포넌트 /dev/mediaN

위 다이어그램에서 핵심 관계를 정리하면:

v4l2_device 구조체(Struct)

v4l2_device는 V4L2 드라이버의 최상위 컨테이너(Container)입니다. 하나의 물리 디바이스에 대해 하나의 인스턴스를 생성하며, 산하의 모든 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);
코드 설명
  • struct v4l2_devicev4l2_device는 V4L2 드라이버의 최상위 컨테이너로, include/media/v4l2-device.h에 정의됩니다. 하나의 물리 디바이스(PCI/platform/USB 등)당 하나의 인스턴스를 생성합니다.
  • devdev 필드는 부모 struct device 포인터로, 디바이스 모델 계층에서의 위치를 나타냅니다. DMA 매핑과 PM(전원 관리) 등에 사용됩니다.
  • mdevMedia Controller 디바이스 포인터로, 복잡한 파이프라인(센서 → ISP → DMA)을 가진 드라이버에서 엔터티/링크 토폴로지를 관리합니다. 단순 드라이버는 NULL로 둘 수 있습니다.
  • subdevs이 디바이스에 등록된 모든 v4l2_subdev(센서, ISP 등)의 연결 리스트입니다. v4l2_device_register_subdev()로 추가됩니다.
  • notify서브디바이스가 브릿지 드라이버에 비동기 이벤트(예: 프레임 동기화)를 알릴 때 사용하는 콜백입니다.
  • v4l2_device_registerdrivers/media/v4l2-core/v4l2-device.c에 구현되며, sysfs 연결과 내부 리스트를 초기화합니다. probe() 함수 초반에 호출하는 것이 일반적입니다.

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);
코드 설명
  • struct video_devicevideo_device는 유저스페이스에 /dev/videoN 캐릭터 디바이스 노드를 노출하는 구조체로, include/media/v4l2-dev.h에 정의됩니다.
  • fopsv4l2_file_operations 포인터로, open/release/mmap/poll/unlocked_ioctl 등 파일 오퍼레이션을 지정합니다. 대부분 vb2 헬퍼(vb2_fop_*)를 그대로 사용할 수 있습니다.
  • ioctl_opsv4l2_ioctl_ops 포인터로, VIDIOC_* ioctl마다 대응하는 콜백을 정의합니다. video_ioctl2()가 이 테이블을 조회하여 적절한 핸들러를 호출합니다.
  • queuevb2_queue 포인터로, videobuf2 프레임워크의 버퍼 큐를 연결합니다. 이 필드를 설정하면 vb2 ioctl 헬퍼(vb2_ioctl_reqbufs 등)가 자동으로 동작합니다.
  • device_capsV4L2_CAP_* 플래그 조합으로, 이 디바이스 노드의 능력을 선언합니다. VIDIOC_QUERYCAP 응답의 device_caps 필드에 반영됩니다.
  • video_register_devicedrivers/media/v4l2-core/v4l2-dev.c에 구현되며, 캐릭터 디바이스를 등록하고 /dev/videoN 노드를 생성합니다. nr 인자가 -1이면 자동 번호 할당됩니다.

ioctl 디스패치 메커니즘

V4L2의 ioctl 처리 흐름을 이해하면 드라이버 디버깅에 도움이 됩니다:

/* 유저스페이스 ioctl() 호출 경로 */
/* 1. sys_ioctl() → vfs_ioctl() */
/* 2. video_device->fops->unlocked_ioctl = video_ioctl2 */
/* 3. video_ioctl2() → video_usercopy() → __video_do_ioctl() */
/* 4. __video_do_ioctl() → v4l2_ioctl_ops의 해당 콜백 */

/* video_ioctl2의 역할: */
/* - 유저스페이스 ↔ 커널 데이터 복사 (copy_from_user/copy_to_user) */
/* - video_device.lock 기반 직렬화 */
/* - 입력 검증 (버퍼 타입, 인덱스 범위 등) */
/* - v4l2_fh 기반 우선순위 확인 */

v4l2_fh (파일 핸들)

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

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 버퍼 할당, 유저스페이스 매핑(Mapping), 스트리밍 큐 관리를 담당합니다. 드라이버는 직접 버퍼를 관리할 필요 없이 vb2 콜백(Callback)만 구현하면 됩니다.

vb2 버퍼 수명주기(Lifecycle)

vb2 버퍼는 엄격한 상태 머신(State Machine)을 따릅니다. 각 버퍼는 다음 상태 중 하나에 있습니다:

DEQUEUED 유저스페이스 소유 PREPARING buf_prepare() QUEUED vb2 큐 대기 ACTIVE 하드웨어 DMA 중 DONE DMA 완료 ERROR DMA 오류 QBUF 검증 통과 buf_queue() vb2_buffer_done() 오류 발생 DQBUF DQBUF REQBUFS 후 초기 상태
상태소유권설명
DEQUEUED유저스페이스애플리케이션이 버퍼를 보유. 데이터 읽기/쓰기 가능
PREPARINGvb2 코어buf_prepare() 콜백 실행 중. 버퍼 유효성 검증
QUEUEDvb2 코어vb2 내부 큐에서 대기. 아직 하드웨어에 전달되지 않음
ACTIVE드라이버/HWbuf_queue() 후 하드웨어가 DMA 진행 중
DONEvb2 코어DMA 완료. DQBUF로 유저스페이스에 반환 대기
ERRORvb2 코어DMA 오류 발생. DQBUF로 반환 시 에러 플래그 포함

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);
}
코드 설명
  • struct vb2_queuevb2_queue는 videobuf2 프레임워크의 핵심 구조체로, include/media/videobuf2-core.h에 정의됩니다. 버퍼 풀(Pool)의 생명주기 전체를 관리합니다.
  • typeV4L2_BUF_TYPE_VIDEO_CAPTURE 등 버퍼 타입을 지정합니다. 멀티플레인 캡처라면 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE을 사용합니다.
  • io_modes지원하는 메모리 모드의 비트마스크입니다. VB2_MMAP | VB2_DMABUF로 설정하면 mmap과 DMA-BUF import를 모두 지원합니다.
  • ops / mem_opsops는 드라이버 콜백(vb2_ops), mem_ops는 메모리 할당 백엔드입니다. vb2_dma_contig_memops(연속 DMA)와 vb2_dma_sg_memops(scatter-gather)가 대표적입니다.
  • buf_struct_size드라이버 고유 버퍼 구조체의 크기를 지정합니다. vb2 코어가 이 크기만큼 할당하여 container_of로 접근할 수 있게 합니다.
  • timestamp_flagsV4L2_BUF_FLAG_TIMESTAMP_MONOTONIC을 설정하면 ktime_get_ns() 기반 모노토닉 타임스탬프가 버퍼에 기록됩니다.
  • vb2_queue_initinclude/media/videobuf2-v4l2.h에 선언되며, 큐 필드 검증 후 내부 리스트와 동기화 객체를 초기화합니다. 실패 시 음수 에러 코드를 반환합니다.

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 메모리 할당자(Allocator) 비교

vb2는 세 가지 메모리 할당자를 제공합니다. 드라이버의 DMA 요구사항에 따라 선택합니다:

할당자mem_ops메모리 특성IOMMU 필요사용 시나리오
DMA contiguousvb2_dma_contig_memops물리적 연속 메모리 (CMA 기반)아니오연속 DMA 주소 필요 디바이스 (대부분 카메라/코덱)
DMA scatter-gathervb2_dma_sg_memops물리적 비연속, SG 리스트(Scatter-Gather List)예 (권장)IOMMU 있는 환경, 대용량 버퍼
vmallocvb2_vmalloc_memops가상 연속 메모리해당 없음소프트웨어 처리 전용, 하드웨어 DMA 불가
CMA 크기 주의: vb2_dma_contig_memops는 CMA(Contiguous Memory Allocator)에서 버퍼를 할당합니다. 4K 해상도(3840x2160) YUV 4:2:0 버퍼 1장은 약 12MB이므로, 4~8개 버퍼를 큐잉하면 48~96MB의 연속 메모리가 필요합니다. 커널 부트 파라미터로 CMA 크기를 충분히 설정해야 합니다: cma=256M
/* DMA scatter-gather 할당자 사용 예 — IOMMU 환경 */
static int my_queue_init_sg(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_sg_memops;  /* SG 할당자 */
    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;
    q->gfp_flags        = GFP_DMA32;  /* 32-bit DMA 주소 제한 시 */

    return vb2_queue_init(q);
}

/* SG 버퍼에서 DMA 주소 가져오기 */
static void my_buf_queue_sg(struct vb2_buffer *vb)
{
    struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0);
    /* sgt->sgl, sgt->nents 로 하드웨어 SG DMA 프로그래밍 */
}

DMA-BUF 버퍼 공유 패턴

V4L2 캡처 버퍼를 GPU/디스플레이와 제로카피로 공유하는 대표적 패턴:

/* 패턴 1: V4L2 MMAP → EXPBUF → DRM import */
/* V4L2 측: MMAP 버퍼를 DMA-BUF fd로 내보내기 */
struct v4l2_exportbuffer expbuf = {
    .type  = V4L2_BUF_TYPE_VIDEO_CAPTURE,
    .index = buf_index,
    .flags = O_RDONLY,
};
ioctl(v4l2_fd, VIDIOC_EXPBUF, &expbuf);
int dmabuf_fd = expbuf.fd;  /* 다른 디바이스에 전달 가능 */

/* DRM 측: DMA-BUF fd로 GEM 핸들 생성 (제로카피) */
struct drm_prime_handle prime = { .fd = dmabuf_fd };
ioctl(drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime);

/* 패턴 2: 외부 DMA-BUF → V4L2 DMABUF import */
/* V4L2 측: 외부 DMA-BUF fd를 캡처 버퍼로 사용 */
struct v4l2_buffer buf = {
    .type   = V4L2_BUF_TYPE_VIDEO_CAPTURE,
    .memory = V4L2_MEMORY_DMABUF,
    .m.fd   = external_dmabuf_fd,  /* GPU가 할당한 버퍼 */
};
ioctl(v4l2_fd, VIDIOC_QBUF, &buf);
코드 설명
  • 패턴 1: EXPBUFVIDIOC_EXPBUF는 V4L2 MMAP 버퍼를 DMA-BUF 파일 디스크립터(fd)로 내보냅니다. 이 fd를 다른 디바이스(GPU, 디스플레이)에 전달하면 물리 메모리 복사 없이 제로카피(Zero-Copy) 공유가 가능합니다.
  • DRM_IOCTL_PRIME_FD_TO_HANDLEDRM 서브시스템에서 DMA-BUF fd를 GEM 핸들로 변환합니다. 카메라 캡처 → GPU 텍스처 → 디스플레이 출력의 전체 경로에서 버퍼 복사가 발생하지 않습니다.
  • 패턴 2: DMABUF importV4L2_MEMORY_DMABUF 모드로 VIDIOC_QBUF를 호출하면, 외부에서 할당된 DMA-BUF(예: GPU 버퍼)를 V4L2 캡처 대상으로 직접 사용할 수 있습니다. drivers/media/v4l2-core/v4l2-ioctl.cv4l2_qbuf에서 처리됩니다.
  • m.fdv4l2_bufferm.fd 필드에 외부 DMA-BUF fd를 지정합니다. vb2 코어가 dma_buf_attach()dma_buf_map_attachment()를 호출하여 해당 디바이스의 DMA 주소로 매핑합니다.

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 호출 시 vb2 코어가 호출합니다. 드라이버는 현재 포맷에 따라 필요한 버퍼 수, 플레인 수, 각 플레인의 크기를 반환해야 합니다.
  • buf_prepareVIDIOC_QBUF마다 호출되며, 버퍼 크기가 현재 포맷에 충분한지 검증합니다. 타임스탬프와 필드 정보를 초기화하는 것이 일반적입니다.
  • buf_queuevb2 코어가 버퍼를 드라이버에 전달할 때 호출됩니다. 드라이버는 이 버퍼를 내부 DMA 큐에 추가하고, 하드웨어가 다음 프레임을 이 버퍼에 기록하도록 프로그래밍합니다.
  • start_streamingVIDIOC_STREAMON 시 호출되며, 하드웨어 DMA 엔진을 시작합니다. 최소 min_queued_buffers개의 버퍼가 큐잉된 후에만 호출됩니다.
  • stop_streamingVIDIOC_STREAMOFF 시 호출됩니다. DMA를 중지하고, 아직 처리되지 않은 모든 버퍼를 vb2_buffer_done(VB2_BUF_STATE_ERROR)로 반환해야 합니다.
  • wait_prepare / wait_finishvb2 코어가 DQBUF 대기 전후에 호출하는 lock 관리 헬퍼입니다. vb2_ops_wait_prepare/vb2_ops_wait_finishvb2_queue.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 언매핑(Unmapping) 등 정리

스트리밍 흐름

유저스페이스의 전형적인 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() */
버퍼 수 선택 가이드:
  • 최소 3개 — 1개 HW 활성, 1개 큐 대기, 1개 유저스페이스 처리. 프레임 드롭 위험 있음
  • 4~6개 (권장) — 안정적 스트리밍. 유저스페이스 처리 지연에 대한 여유
  • 8개 이상 — 긴 처리 시간이 필요한 경우 (GPU 처리, 네트워크 전송 등)
  • 지연 최소화 — 3개 + non-blocking DQBUF + 가장 최신 프레임만 사용

vb2_mem_ops 상세

vb2_mem_ops는 메모리 할당자의 저수준 인터페이스입니다. 커널이 제공하는 3가지 구현 외에 커스텀 할당자도 만들 수 있습니다:

콜백용도MMAPDMABUF
alloc버퍼 메모리 할당CMA/vmalloc 할당
put메모리 해제할당 해제참조 카운트 감소
get_dmabufDMA-BUF fd 생성EXPBUF에서 호출
attach_dmabuf외부 DMA-BUF 연결importer 설정
map_dmabufDMA-BUF를 디바이스에 매핑DMA 매핑
unmap_dmabufDMA 매핑 해제언매핑
vaddr커널 가상 주소 반환kmap/vmap
cookie하드웨어 주소(DMA addr) 반환DMA 주소SG 테이블
mmap유저스페이스 mmapvm_area 매핑

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);
/* 드라이버가 요청값을 가장 가까운 지원값으로 조정할 수 있음 */
코드 설명
  • VIDIOC_ENUM_FMT디바이스가 지원하는 픽셀 포맷을 열거합니다. fmtdesc.index를 0부터 증가시키며 반복 호출하고, -EINVAL 반환 시 종료합니다. drivers/media/v4l2-core/v4l2-ioctl.cv4l2_enum_fmt()에서 드라이버의 vidioc_enum_fmt_vid_cap 콜백을 호출합니다.
  • VIDIOC_G_FMT현재 설정된 포맷(해상도, 픽셀 포맷, stride 등)을 조회합니다. v4l2_format.type을 지정하면 해당 버퍼 타입의 포맷 정보가 반환됩니다.
  • VIDIOC_S_FMT원하는 포맷을 설정합니다. 드라이버는 요청된 값을 하드웨어가 지원하는 가장 가까운 값으로 조정(negotiation)할 수 있으므로, 호출 후 반환된 fmt 값을 반드시 확인해야 합니다.
  • pixelformatFourCC 코드로 픽셀 포맷을 지정합니다. V4L2_PIX_FMT_YUYV(YUV 4:2:2)는 USB 웹캠에서 널리 사용되며, V4L2_PIX_FMT_NV12(YUV 4:2:0)는 하드웨어 코덱과 GPU에서 선호됩니다.
  • V4L2_FIELD_NONE프로그레시브(비인터레이스) 스캔을 나타냅니다. 인터레이스 소스라면 V4L2_FIELD_INTERLACED 등을 사용합니다.

주요 픽셀 포맷:

매크로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버퍼 상태/오프셋(Offset) 조회 (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— 컴포즈 가능 최대 범위 */

포맷과 컬러스페이스(Colorspace)

V4L2의 포맷 시스템은 FourCC 코드(4바이트 식별자)로 픽셀 포맷을 식별합니다. 올바른 색 재현을 위해 컬러스페이스, 전송 함수(Transfer Function), 양자화(Quantization) 범위를 함께 지정해야 합니다.

FourCC 시스템

FourCC(Four Character Code)는 4바이트 정수로 픽셀 포맷을 고유하게 식별합니다. V4L2는 v4l2_fourcc() 매크로로 FourCC를 생성합니다:

/* include/uapi/linux/videodev2.h */
#define v4l2_fourcc(a, b, c, d) \
    ((__u32)(a) | ((__u32)(b) << 8) | \
     ((__u32)(c) << 16) | ((__u32)(d) << 24))

/* 예: V4L2_PIX_FMT_YUYV = v4l2_fourcc('Y','U','Y','V') = 0x56595559 */

YUV 포맷 상세

YUV 포맷은 밝기(Y, Luma)와 색차(U/V, Chroma) 성분으로 분리됩니다. 크로마 서브샘플링(Chroma Subsampling)에 따라 다양한 변형이 있습니다:

서브샘플링Y:U:V 비율BPP주요 포맷설명
4:4:41:1:124V4L2_PIX_FMT_YUV444색차 손실 없음, 최고 품질
4:2:22:1:116V4L2_PIX_FMT_YUYV, UYVY수평 2:1 서브샘플링, 방송 표준
4:2:04:1:112V4L2_PIX_FMT_NV12, YU12수평+수직 2:1, 코덱 표준 (H.264/HEVC)
4:1:14:1:112V4L2_PIX_FMT_Y41P수평 4:1 서브샘플링, 드물게 사용

메모리 레이아웃(Layout) 변형:

레이아웃설명예시
PackedY/U/V 샘플이 인터리브(Interleave)됨YUYV: Y0 U0 Y1 V0 Y2 U1 Y3 V1 ...
Semi-planarY 플레인 + UV 인터리브 플레인NV12: [YYYY...] [UVUV...]
PlanarY/U/V 각각 독립 플레인YU12: [YYYY...] [UU...] [VV...]

Bayer(RAW) 포맷

이미지 센서는 Bayer 패턴(Color Filter Array)으로 RAW 데이터를 출력합니다. 각 픽셀은 R/G/B 중 하나의 색만 캡처하며, ISP의 디모자이크(Demosaic) 과정을 거쳐야 완전한 이미지가 됩니다:

패턴FourCC 예비트 깊이(Depth)설명
RGGBRGGB, RG10, RG128/10/12첫 행: R G, 둘째 행: G B
BGGRBA81, BG10, BG128/10/12첫 행: B G, 둘째 행: G R
GRBGGRBG, BA108/10첫 행: G R, 둘째 행: B G
GBRGGBRG, GB108/10첫 행: G B, 둘째 행: R G

컬러스페이스와 관련 파라미터

v4l2_pix_format은 픽셀 포맷 외에 올바른 색 재현을 위한 추가 파라미터를 포함합니다:

struct v4l2_pix_format {
    ...
    __u32 colorspace;    /* V4L2_COLORSPACE_* */
    __u32 xfer_func;     /* V4L2_XFER_FUNC_*  — 전송 함수 */
    __u32 ycbcr_enc;     /* V4L2_YCBCR_ENC_*  — YCbCr 인코딩 */
    __u32 quantization;  /* V4L2_QUANTIZATION_* — 양자화 범위 */
};
파라미터주요 값설명
컬러스페이스SRGB, REC709, BT2020, RAW색 좌표계. sRGB는 웹/모니터, BT.709는 HD 비디오, BT.2020은 4K/HDR
전송 함수SRGB, 709, SMPTE2084선형 → 비선형(감마) 변환. SMPTE 2084는 HDR(PQ 커브)
YCbCr 인코딩601, 709, BT2020RGB → YCbCr 변환 행렬. 해상도에 따라 다름
양자화FULL_RANGE, LIM_RANGEFull: 0~255, Limited: 16~235(Y)/16~240(CbCr)
흔한 실수: YUV 데이터의 양자화 범위를 혼동하면 이미지가 전체적으로 어둡거나(Full→Limited) 밝아집니다(Limited→Full). 센서의 기본 출력은 대부분 Full Range이고, 방송/HDMI 표준은 Limited Range입니다. 드라이버에서 V4L2_QUANTIZATION_DEFAULT를 설정하면 V4L2가 컬러스페이스에 따라 자동 결정합니다.

싱글플레인 vs 멀티플레인 API

V4L2는 두 가지 버퍼 타입 API를 제공합니다:

항목싱글플레인멀티플레인
버퍼 타입V4L2_BUF_TYPE_VIDEO_CAPTUREV4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
포맷 구조체v4l2_pix_formatv4l2_pix_format_mplane
플레인 수1개 (연속 메모리)최대 8개 (독립 DMA 버퍼)
사용 예YUYV packed, RGBNV12 (Y + UV 분리 할당), YU12 (Y + U + V)
권장 여부단순 디바이스신규 드라이버 권장
/* 멀티플레인 포맷 설정 예: NV12 (2 플레인) */
struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE };
fmt.fmt.pix_mp.width        = 1920;
fmt.fmt.pix_mp.height       = 1080;
fmt.fmt.pix_mp.pixelformat  = V4L2_PIX_FMT_NV12;
fmt.fmt.pix_mp.num_planes   = 2;
fmt.fmt.pix_mp.colorspace   = V4L2_COLORSPACE_REC709;

ioctl(fd, VIDIOC_S_FMT, &fmt);

/* 플레인별 크기 확인 */
printf("Plane 0 (Y): %u bytes, stride %u\n",
       fmt.fmt.pix_mp.plane_fmt[0].sizeimage,
       fmt.fmt.pix_mp.plane_fmt[0].bytesperline);
printf("Plane 1 (UV): %u bytes, stride %u\n",
       fmt.fmt.pix_mp.plane_fmt[1].sizeimage,
       fmt.fmt.pix_mp.plane_fmt[1].bytesperline);

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,
};
코드 설명
  • struct v4l2_ioctl_opsv4l2_ioctl_ops는 V4L2 ioctl 디스패치 테이블로, include/media/v4l2-ioctl.h에 정의됩니다. __video_do_ioctl()(drivers/media/v4l2-core/v4l2-ioctl.c)이 ioctl 번호에 따라 이 테이블의 해당 콜백을 호출합니다.
  • vidioc_querycap드라이버가 반드시 구현해야 하는 필수 콜백입니다. 디바이스 이름, 버스 정보, capability 플래그를 채워 반환합니다.
  • 포맷 콜백enum_fmt/g_fmt/s_fmt/try_fmt 네 가지가 한 세트입니다. try_fmt는 실제 변경 없이 포맷 가능 여부만 확인하므로, s_fmt 구현 시 try_fmt를 먼저 호출하고 그 결과를 적용하는 패턴이 권장됩니다.
  • vb2 ioctl 헬퍼버퍼 관련 ioctl(reqbufs, qbuf, dqbuf 등)은 videobuf2가 제공하는 vb2_ioctl_* 헬퍼를 그대로 지정하면 됩니다. video_device.queue가 설정되어 있어야 동작합니다.
  • 입력 콜백enum_input/g_input/s_input은 비디오 입력 소스(예: 컴포지트, S-Video)를 관리합니다. 단일 입력 드라이버도 이 콜백을 구현해야 일부 애플리케이션과의 호환성이 보장됩니다.
  • 이벤트 콜백v4l2_ctrl_subscribe_event는 컨트롤 변경 이벤트를 구독하는 기본 핸들러입니다. v4l2_event_unsubscribe와 함께 사용하면 VIDIOC_SUBSCRIBE_EVENT/DQEVENT ioctl이 동작합니다.

완전한 캡처 드라이버 예제

#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);
}

인터럽트(Interrupt) 핸들러와 버퍼 완료 패턴

실제 캡처 드라이버에서 프레임 완료 인터럽트를 처리하는 패턴:

static irqreturn_t my_cam_irq(int irq, void *data)
{
    struct my_cam *cam = data;
    struct my_buffer *buf;
    unsigned long flags;
    u32 status;

    /* 인터럽트 상태 읽기 */
    status = readl(cam->regs + REG_INT_STATUS);
    if (!(status & INT_FRAME_DONE))
        return IRQ_NONE;

    /* 인터럽트 클리어 */
    writel(status, cam->regs + REG_INT_CLEAR);

    spin_lock_irqsave(&cam->slock, flags);

    /* 현재 활성 버퍼 가져오기 */
    buf = list_first_entry_or_null(&cam->buf_list,
                                    struct my_buffer, list);
    if (buf) {
        list_del(&buf->list);

        /* 타임스탬프와 시퀀스 번호 기록 */
        buf->vb.vb2_buf.timestamp = ktime_get_ns();
        buf->vb.sequence = cam->sequence++;
        buf->vb.field = V4L2_FIELD_NONE;

        /* 실제 사용된 바이트 수 설정 */
        vb2_set_plane_payload(&buf->vb.vb2_buf, 0,
                              cam->fmt.fmt.pix.sizeimage);

        /* 버퍼 완료 → 유저스페이스 poll/DQBUF 깨움 */
        vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
    }

    /* 다음 버퍼가 있으면 DMA 주소 프로그래밍 */
    buf = list_first_entry_or_null(&cam->buf_list,
                                    struct my_buffer, list);
    if (buf) {
        dma_addr_t addr = vb2_dma_contig_plane_dma_addr(
            &buf->vb.vb2_buf, 0);
        writel(addr, cam->regs + REG_DMA_ADDR);
    }

    spin_unlock_irqrestore(&cam->slock, flags);
    return IRQ_HANDLED;
}

에러 처리와 복구

V4L2 드라이버에서 자주 발생하는 에러 상황과 올바른 처리 패턴:

/* start_streaming 실패 시: 큐잉된 모든 버퍼를 ERROR 상태로 반환 */
static int my_start_streaming_safe(struct vb2_queue *q, unsigned int count)
{
    struct my_cam *cam = vb2_get_drv_priv(q);
    int ret;

    cam->sequence = 0;

    /* 클록/전원 활성화 */
    ret = clk_prepare_enable(cam->clk);
    if (ret)
        goto err_return_bufs;

    /* 하드웨어 초기화 */
    ret = my_hw_init(cam);
    if (ret)
        goto err_clk;

    /* DMA 시작 */
    ret = my_dma_start(cam);
    if (ret)
        goto err_hw;

    return 0;

err_hw:
    my_hw_deinit(cam);
err_clk:
    clk_disable_unprepare(cam->clk);
err_return_bufs:
    /* 필수: 실패 시 모든 버퍼를 ERROR로 반환 */
    {
        struct my_buffer *buf, *tmp;
        unsigned long flags;

        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_QUEUED);
        }
        spin_unlock_irqrestore(&cam->slock, flags);
    }
    return ret;
}

/* probe 에러 처리 — 역순 해제 패턴 */
static int my_cam_probe_safe(struct platform_device *pdev)
{
    struct my_cam *cam;
    int ret;

    cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL);
    if (!cam)
        return -ENOMEM;

    ret = v4l2_device_register(&pdev->dev, &cam->v4l2_dev);
    if (ret)
        return ret;

    ret = my_ctrl_init(cam);
    if (ret)
        goto err_v4l2;

    ret = my_queue_init(cam);
    if (ret)
        goto err_ctrl;

    ret = video_register_device(&cam->vdev, VFL_TYPE_VIDEO, -1);
    if (ret)
        goto err_ctrl;

    dev_info(&pdev->dev, "registered as /dev/video%d\n", cam->vdev.num);
    return 0;

err_ctrl:
    v4l2_ctrl_handler_free(&cam->ctrl_handler);
err_v4l2:
    v4l2_device_unregister(&cam->v4l2_dev);
    return ret;
}

런타임 PM(Power Management) 통합

비디오 디바이스는 유휴(Idle) 시 전력 소모가 크므로 런타임 PM 통합이 중요합니다:

/* open 시 PM resume */
static int my_cam_open(struct file *file)
{
    struct my_cam *cam = video_drvdata(file);
    int ret;

    ret = v4l2_fh_open(file);
    if (ret)
        return ret;

    /* 첫 번째 open이면 하드웨어 전원 켜기 */
    ret = pm_runtime_resume_and_get(cam->dev);
    if (ret < 0) {
        v4l2_fh_release(file);
        return ret;
    }
    return 0;
}

/* release 시 PM suspend */
static int my_cam_release(struct file *file)
{
    struct my_cam *cam = video_drvdata(file);
    int ret;

    ret = vb2_fop_release(file);
    pm_runtime_put(cam->dev);
    return ret;
}

/* 런타임 PM 콜백 */
static int my_cam_runtime_suspend(struct device *dev)
{
    struct my_cam *cam = dev_get_drvdata(dev);
    clk_disable_unprepare(cam->clk);
    return 0;
}

static int my_cam_runtime_resume(struct device *dev)
{
    struct my_cam *cam = dev_get_drvdata(dev);
    return clk_prepare_enable(cam->clk);
}

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 */
    ...
};

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

서브디바이스의 패드(입출력(I/O) 포트)는 미디어 버스 포맷(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 */

서브디바이스 active state (커널 6.3+)

최신 커널에서는 v4l2_subdev_state가 서브디바이스의 포맷/Selection 상태를 중앙 관리합니다. TRY와 ACTIVE 상태를 구분하여 포맷 협상의 안전성을 보장합니다:

/* 패드 포맷 조회 — state에서 가져오기 */
static int my_get_fmt(struct v4l2_subdev *sd,
                      struct v4l2_subdev_state *state,
                      struct v4l2_subdev_format *fmt)
{
    /* state가 TRY/ACTIVE를 자동으로 구분 */
    fmt->format = *v4l2_subdev_state_get_format(state, fmt->pad);
    return 0;
}

/* 패드 포맷 설정 — state에 저장 */
static int my_set_fmt(struct v4l2_subdev *sd,
                      struct v4l2_subdev_state *state,
                      struct v4l2_subdev_format *fmt)
{
    struct v4l2_mbus_framefmt *mf;

    /* 지원하는 포맷으로 조정 */
    fmt->format.code = my_find_best_code(fmt->format.code);
    fmt->format.width = clamp(fmt->format.width, 64, 4096);
    fmt->format.height = clamp(fmt->format.height, 64, 4096);

    /* state에 저장 */
    mf = v4l2_subdev_state_get_format(state, fmt->pad);
    *mf = fmt->format;

    /* source 패드도 연쇄(Cascade) 업데이트 */
    mf = v4l2_subdev_state_get_format(state, MY_SOURCE_PAD);
    *mf = fmt->format;
    /* ISP가 포맷 변환하면 source 패드 코드만 변경 */

    return 0;
}

/* init_state — 서브디바이스 기본 포맷 설정 */
static int my_init_state(struct v4l2_subdev *sd,
                         struct v4l2_subdev_state *state)
{
    struct v4l2_mbus_framefmt *fmt;

    fmt = v4l2_subdev_state_get_format(state, 0);
    fmt->code   = MEDIA_BUS_FMT_SRGGB10_1X10;
    fmt->width  = 1920;
    fmt->height = 1080;
    fmt->field  = V4L2_FIELD_NONE;
    fmt->colorspace = V4L2_COLORSPACE_RAW;

    return 0;
}

static const struct v4l2_subdev_internal_ops my_internal_ops = {
    .init_state = my_init_state,
};

포맷 협상(Format Negotiation) 흐름

파이프라인의 포맷 협상은 소스에서 싱크 방향(센서 → ISP → DMA)으로 진행됩니다. 각 서브디바이스의 싱크 패드에 포맷을 설정하면, 서브디바이스가 source 패드 포맷을 자동으로 결정합니다:

Sensor pad[0]: SOURCE SRGGB10 3840x2160 set_fmt → output 결정 CSI-2 CSI-2 Receiver pad[0]: SINK SRGGB10 3840x2160 pad[1]: SOURCE SRGGB10 3840x2160 (패스스루 — 포맷 변경 없음) ISP pad[0]: SINK SRGGB10 3840x2160 pad[1]: SOURCE YUYV8 1920x1080 (디모자이크+스케일링+CSC) DMA Engine video_device YUYV 1920x1080 → /dev/videoN 1. 센서 출력 포맷 결정 2. 수신기 포맷 전달 3. ISP 변환 후 출력 4. DMA → 메모리 포맷 협상 순서: 센서 set_fmt(pad 0) → CSI set_fmt(pad 0, 1) → ISP set_fmt(pad 0, 1) → video S_FMT TRY 모드로 먼저 검증 후, ACTIVE 모드로 실제 적용 media-ctl -V 로 각 서브디바이스 패드 포맷 설정 → v4l2-ctl --set-fmt-video 로 최종 캡처 포맷 설정

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

최신 커널(6.3+)의 Streams API는 하나의 패드에서 여러 데이터 스트림을 다중화(Multiplexing)할 수 있습니다. 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_ctrl_handler_init컨트롤 핸들러를 초기화합니다. 두 번째 인자는 예상 컨트롤 수의 힌트로, 내부 해시 테이블 크기 결정에 사용됩니다. include/media/v4l2-ctrls.h에 선언되어 있습니다.
  • v4l2_ctrl_new_std표준 정수형 컨트롤을 생성합니다. 인자 순서는 (handler, ops, id, min, max, step, default)입니다. 생성된 v4l2_ctrl 포인터가 반환되며, 실패 시 hdl->error에 에러가 누적됩니다.
  • v4l2_ctrl_new_std_menu메뉴형 컨트롤(열거형 선택)을 생성합니다. V4L2_CID_EXPOSURE_AUTO처럼 미리 정의된 선택지가 있는 컨트롤에 사용합니다. 세 번째 인자는 최대값, 네 번째는 사용 불가 항목 비트마스크입니다.
  • hdl->error컨트롤 프레임워크는 에러 누적 패턴을 사용합니다. 개별 v4l2_ctrl_new_* 호출의 반환값을 매번 확인할 필요 없이, 모든 컨트롤 등록 후 hdl->error 한 번만 검사하면 됩니다.
  • ctrl_handler 연결video_device.ctrl_handler에 연결하면 유저스페이스에서 VIDIOC_G_CTRL/S_CTRL/QUERYCTRL로 접근할 수 있습니다. 서브디바이스의 경우 v4l2_subdev.ctrl_handler에 연결합니다.

표준 컨트롤 (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);

컨트롤 상속(Inheritance)과 위임

V4L2 컨트롤은 서브디바이스에서 video_device로 자동 상속됩니다. 유저스페이스는 /dev/videoN을 통해 서브디바이스의 컨트롤에 접근할 수 있습니다:

/* 서브디바이스의 컨트롤을 video_device에 상속 */
/* video_device에 ctrl_handler를 설정하지 않으면 */
/* v4l2_device의 ctrl_handler를 자동으로 사용 */

/* 방법 1: 직접 설정 */
cam->vdev.ctrl_handler = &cam->ctrl_handler;

/* 방법 2: 서브디바이스 핸들러를 video_device에 추가 */
v4l2_ctrl_add_handler(&cam->ctrl_handler,
                       sensor_sd->ctrl_handler, NULL, true);

/* 방법 3: v4l2_device 레벨에서 설정 (모든 video_device에 적용) */
cam->v4l2_dev.ctrl_handler = &cam->ctrl_handler;

컨트롤 이벤트

컨트롤 값 변경을 유저스페이스에 알리는 이벤트 메커니즘:

/* 유저스페이스: 컨트롤 변경 이벤트 구독 */
struct v4l2_event_subscription sub = {
    .type = V4L2_EVENT_CTRL,
    .id   = V4L2_CID_EXPOSURE_ABSOLUTE,
};
ioctl(fd, VIDIOC_SUBSCRIBE_EVENT, &sub);

/* 이벤트 대기 (poll + DQEVENT) */
struct v4l2_event ev;
ioctl(fd, VIDIOC_DQEVENT, &ev);
if (ev.type == V4L2_EVENT_CTRL) {
    struct v4l2_event_ctrl *ctrl_ev = &ev.u.ctrl;
    printf("Control %08x changed: value=%d, flags=%x\n",
           ev.id, ctrl_ev->value, ctrl_ev->flags);
}

/* 드라이버 측: 커스텀 이벤트 발생 */
v4l2_ctrl_s_ctrl(ctrl, new_value);
/* → 프레임워크가 자동으로 이벤트 생성 */

유저스페이스에서 컨트롤 사용

# 모든 컨트롤 목록 조회
$ v4l2-ctl -d /dev/video0 --list-ctrls
#                      brightness 0x00980900 (int)    : min=0 max=255 step=1 default=128 value=128
#                        contrast 0x00980901 (int)    : min=0 max=255 step=1 default=128 value=128
#                      saturation 0x00980902 (int)    : min=0 max=255 step=1 default=128 value=128
#               exposure_auto (1) 0x009a0901 (menu)   : min=0 max=3 default=3 value=3

# 컨트롤 상세 조회 (확장 컨트롤 포함)
$ v4l2-ctl -d /dev/video0 --list-ctrls-menus

# 컨트롤 값 설정
$ v4l2-ctl -d /dev/video0 --set-ctrl brightness=200
$ v4l2-ctl -d /dev/video0 --set-ctrl exposure_auto=1,exposure_absolute=500

# 서브디바이스 직접 컨트롤 (Media Controller 환경)
$ v4l2-ctl -d /dev/v4l-subdev0 --list-ctrls
$ v4l2-ctl -d /dev/v4l-subdev0 --set-ctrl analogue_gain=200

Media Controller

Media Controller는 복잡한 미디어 파이프라인의 토폴로지(Topology)를 유저스페이스에 노출합니다. 센서, 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 리시버 등 인터페이스 브리지(Bridge)
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 처리 */
}

미디어 그래프 구축 패턴

실제 카메라 파이프라인에서 Media Controller 그래프를 구성하는 완전한 예제:

static int my_platform_probe(struct platform_device *pdev)
{
    struct my_platform *plat;
    int ret;

    plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL);

    /* 1. media_device 초기화 */
    media_device_init(&plat->mdev);
    plat->mdev.dev = &pdev->dev;
    strscpy(plat->mdev.model, "My Camera Platform",
            sizeof(plat->mdev.model));

    /* 2. v4l2_device에 media_device 연결 */
    plat->v4l2_dev.mdev = &plat->mdev;
    ret = v4l2_device_register(&pdev->dev, &plat->v4l2_dev);

    /* 3. CSI 리시버 서브디바이스 등록 */
    plat->csi.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
    plat->csi_pads[0].flags = MEDIA_PAD_FL_SINK;
    plat->csi_pads[1].flags = MEDIA_PAD_FL_SOURCE;
    media_entity_pads_init(&plat->csi.entity, 2, plat->csi_pads);

    /* 4. DMA 엔진 video_device의 엔터티 설정 */
    plat->vdev.entity.function = MEDIA_ENT_F_IO_V4L;
    plat->vdev_pad.flags = MEDIA_PAD_FL_SINK;
    media_entity_pads_init(&plat->vdev.entity, 1, &plat->vdev_pad);

    /* 5. 내부 링크 생성: CSI source → DMA sink */
    media_create_pad_link(&plat->csi.entity, 1,
                          &plat->vdev.entity, 0,
                          MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);

    /* 6. async notifier로 센서 바인딩 대기 */
    v4l2_async_nf_init(&plat->notifier, &plat->v4l2_dev);
    /* ... fwnode에서 센서 추가 ... */

    /* 7. media_device 등록 */
    ret = media_device_register(&plat->mdev);

    return 0;
}

/* async notifier .bound 콜백: 센서 바인딩 시 링크 생성 */
static int my_notifier_bound(struct v4l2_async_notifier *notifier,
                             struct v4l2_subdev *sd,
                             struct v4l2_async_connection *asc)
{
    struct my_platform *plat = container_of(notifier,
        struct my_platform, notifier);

    /* 센서 source pad → CSI sink pad 링크 */
    return media_create_pad_link(&sd->entity, 0,
                                  &plat->csi.entity, 0,
                                  MEDIA_LNK_FL_ENABLED |
                                  MEDIA_LNK_FL_IMMUTABLE);
}

유저스페이스 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]'

완전한 카메라 파이프라인 설정 스크립트 예제:

#!/bin/bash
# Rockchip RK3588 + IMX219 카메라 파이프라인 설정 예

MEDIA_DEV=/dev/media0

# 1. 링크 활성화
media-ctl -d $MEDIA_DEV -l '"imx219 4-0010":0 -> "rkisp1_csi":0 [1]'
media-ctl -d $MEDIA_DEV -l '"rkisp1_csi":1 -> "rkisp1_isp":0 [1]'
media-ctl -d $MEDIA_DEV -l '"rkisp1_isp":2 -> "rkisp1_resizer_mainpath":0 [1]'

# 2. 센서 포맷 설정 (3280x2464 @ 10-bit Bayer)
media-ctl -d $MEDIA_DEV -V '"imx219 4-0010":0 [fmt:SRGGB10_1X10/3280x2464]'

# 3. CSI 리시버 포맷 (패스스루)
media-ctl -d $MEDIA_DEV -V '"rkisp1_csi":1 [fmt:SRGGB10_1X10/3280x2464]'

# 4. ISP 입력 포맷
media-ctl -d $MEDIA_DEV -V '"rkisp1_isp":0 [fmt:SRGGB10_1X10/3280x2464 crop:(0,0)/3280x2464]'

# 5. ISP 출력 포맷 (디모자이크 + 스케일링)
media-ctl -d $MEDIA_DEV -V '"rkisp1_isp":2 [fmt:YUYV8_2X8/3280x2464 crop:(0,0)/3280x2464]'

# 6. 리사이저 출력 (1920x1080으로 다운스케일)
media-ctl -d $MEDIA_DEV -V '"rkisp1_resizer_mainpath":1 [fmt:YUYV8_2X8/1920x1080]'

# 7. 최종 캡처 포맷 설정
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=NV12

# 8. 캡처 시작
v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=100 --stream-to=capture.raw

V4L2 M2M (Memory-to-Memory)

M2M(Memory-to-Memory) 프레임워크는 입력 버퍼(OUTPUT 큐)에서 데이터를 읽어 처리 후 출력 버퍼(CAPTURE 큐)에 쓰는 하드웨어 코덱/스케일러를 모델링합니다. 일반 캡처 드라이버와 달리 M2M 디바이스는 두 개의 vb2_queue(OUTPUT + CAPTURE)를 관리합니다.

유저스페이스 압축 비트스트림 (H.264/HEVC/VP9) OUTPUT 큐 V4L2_BUF_TYPE _VIDEO_OUTPUT_MPLANE HW 코덱 디코더: 압축 → RAW 인코더: RAW → 압축 device_run() 콜백 CAPTURE 큐 V4L2_BUF_TYPE _VIDEO_CAPTURE_MPLANE QBUF DMA 읽기 DMA 쓰기 DQBUF (소비 완료) DQBUF (디코딩된 프레임 수거) v4l2_m2m 스케줄러: OUTPUT+CAPTURE 버퍼 쌍 확보 → device_run() M2M은 단일 /dev/videoN을 통해 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 루프
플러시(Flush)빈 OUTPUT 버퍼 제출 → EOS 이벤트V4L2_ENC_CMD_STOP → EOS

Stateless 코덱 (Request API)

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

/* 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 디코딩 상세 흐름

Stateless 디코더는 호스트(CPU)가 비트스트림 파싱을 수행하고, 프레임별로 디코딩 파라미터(SPS, PPS, 슬라이스 헤더 등)를 하드웨어에 전달합니다. 이 방식은 Request API와 함께 사용됩니다:

/* Stateless H.264 디코딩 전체 흐름 */

/* 1. 코덱 포맷 설정 */
struct v4l2_format out_fmt = { .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE };
out_fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264_SLICE;  /* 슬라이스 단위 입력 */
out_fmt.fmt.pix_mp.width = 1920;
out_fmt.fmt.pix_mp.height = 1080;
ioctl(fd, VIDIOC_S_FMT, &out_fmt);

struct v4l2_format cap_fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE };
cap_fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;  /* 디코딩 출력 */
ioctl(fd, VIDIOC_S_FMT, &cap_fmt);

/* 2. 버퍼 할당 + STREAMON */
ioctl(fd, VIDIOC_REQBUFS, &out_reqbufs);
ioctl(fd, VIDIOC_REQBUFS, &cap_reqbufs);
ioctl(fd, VIDIOC_STREAMON, &out_type);
ioctl(fd, VIDIOC_STREAMON, &cap_type);

/* 3. 프레임별 디코딩 루프 */
for (frame = 0; frame < total_frames; frame++) {
    /* Request 할당 */
    int req_fd;
    ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd);

    /* SPS 컨트롤 설정 */
    struct v4l2_ctrl_h264_sps sps = { ... };
    struct v4l2_ext_control ctrls[] = {
        { .id = V4L2_CID_STATELESS_H264_SPS, .ptr = &sps, .size = sizeof(sps) },
        { .id = V4L2_CID_STATELESS_H264_PPS, .ptr = &pps, .size = sizeof(pps) },
        { .id = V4L2_CID_STATELESS_H264_SLICE_PARAMS, .ptr = &slice, .size = sizeof(slice) },
        { .id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, .ptr = &decode, .size = sizeof(decode) },
    };
    struct v4l2_ext_controls ext = {
        .count = 4, .controls = ctrls,
        .which = V4L2_CTRL_WHICH_REQUEST_VAL,
        .request_fd = req_fd,
    };
    ioctl(fd, VIDIOC_S_EXT_CTRLS, &ext);

    /* 비트스트림 데이터를 OUTPUT 버퍼에 복사 */
    memcpy(out_buf_ptr, slice_data, slice_size);

    /* OUTPUT 버퍼 제출 (Request에 연결) */
    struct v4l2_buffer out_buf = {
        .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
        .request_fd = req_fd,
        .flags = V4L2_BUF_FLAG_REQUEST_FD,
    };
    ioctl(fd, VIDIOC_QBUF, &out_buf);

    /* CAPTURE 버퍼 제출 */
    ioctl(fd, VIDIOC_QBUF, &cap_buf);

    /* Request 큐잉 → 하드웨어 디코딩 시작 */
    ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE, NULL);

    /* 결과 대기 + 수거 */
    ioctl(fd, VIDIOC_DQBUF, &out_buf);  /* OUTPUT 반환 */
    ioctl(fd, VIDIOC_DQBUF, &cap_buf);  /* 디코딩된 프레임 */

    /* Request 재사용 */
    ioctl(req_fd, MEDIA_REQUEST_IOC_REINIT, NULL);
}

Stateful vs Stateless 코덱 비교

항목StatefulStateless
비트스트림 파싱하드웨어/펌웨어호스트 CPU (userspace)
상태 관리코덱 내부호스트가 Request API로 전달
참조 프레임 관리하드웨어 자동호스트가 CAPTURE 버퍼 인덱스로 지정
입력 단위NAL/패킷 스트림프레임/슬라이스 단위
해상도 변경SOURCE_CHANGE 이벤트유저스페이스가 직접 감지
복잡도단순 (API 단순)복잡 (파싱 라이브러리 필요)
지연(Latency)높음 (내부 버퍼링)낮음 (프레임 단위 제어)
대표 드라이버venus (Qualcomm), mtk-vcodechantro, cedrus, rkvdec

주요 stateless 코덱 드라이버:

드라이버SoC지원 코덱커널 소스 위치
hantroRockchip, NXPH.264, VP8, MPEG-2, VP9, AV1drivers/media/platform/verisilicon/
cedrusAllwinnerH.264, HEVC, MPEG-2, VP8drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/
rkvdecRockchipH.264, VP9, HEVCdrivers/media/platform/rockchip/rkvdec/
visl가상 (테스트용)모든 stateless 코덱drivers/media/test-drivers/visl/

주요 stateful 코덱 드라이버:

드라이버SoC지원 코덱커널 소스 위치
venusQualcomm SDM845/SM8250H.264, HEVC, VP8, VP9 (인코딩/디코딩)drivers/media/platform/qcom/venus/
mtk-vcodecMediaTek MT8173/MT8192H.264, VP8, VP9 (인코딩/디코딩)drivers/media/platform/mediatek/vcodec/
s5p-mfcSamsung ExynosH.264, MPEG-4, H.263 (인코딩/디코딩)drivers/media/platform/samsung/s5p-mfc/
wave5Chips&MediaHEVC, AVC (인코딩/디코딩)drivers/media/platform/chips-media/wave5/

카메라 서브시스템

임베디드 카메라는 여러 하드웨어 블록이 파이프라인으로 연결됩니다. 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(Camera Serial Interface 2)는 임베디드 카메라의 표준 인터페이스입니다. 고속 직렬 링크를 통해 센서 데이터를 SoC에 전달합니다:

항목D-PHYC-PHY
물리 구조차동(Differential) 페어3선 트리오(Trio)
최대 속도4.5 Gbps/lane (v3.0)6 Gsps/trio (v2.0)
레인 수1~4 데이터 + 1 클럭1~3 트리오 (클럭 내장)
PIN 수 (4-lane)10 (4x2 데이터 + 2 클럭)9 (3x3 트리오)
대역폭 (4-lane)최대 18 Gbps최대 18 Gsps

CSI-2 패킷 구조:

패킷 유형구조용도
Short Packet헤더(32bit) onlyFrame Start (FS), Frame End (FE), Line Start/End
Long Packet헤더 + Payload + CRC이미지 데이터, 임베디드 데이터
임베디드 데이터Long Packet (DT=0x12)센서 메타데이터 (노출, 게인, 온도 등)

가상 채널(Virtual Channel)을 사용한 멀티카메라 구성:

VC데이터 타입용도
VC0RAW10 이미지메인 카메라 이미지 데이터
VC0임베디드 데이터메인 카메라 메타데이터
VC1RAW10 이미지보조 카메라 이미지 (멀티카메라 센서)
VC2통계 데이터ISP 3A 통계
/* 드라이버에서 CSI-2 파라미터 확인 */
struct v4l2_fwnode_endpoint ep = {
    .bus_type = V4L2_MBUS_CSI2_DPHY,
};
v4l2_fwnode_endpoint_alloc_parse(fwnode, &ep);

dev_info(dev, "CSI-2: %d data lanes, %llu Hz link freq\n",
         ep.bus.mipi_csi2.num_data_lanes,
         ep.link_frequencies[0]);

/* 필요한 대역폭 계산 예 */
/* 3840x2160 @ 30fps, 10-bit RAW, 4 lanes */
/* = 3840 * 2160 * 30 * 10 = 2,488,320,000 bps ≈ 2.49 Gbps */
/* 4 lanes: 2.49 / 4 = 622 Mbps/lane → link_freq = 311 MHz */
/* (DDR: link_freq = bit_rate / 2) */

ISP 파이프라인 블록

ISP(Image Signal Processor)는 RAW Bayer 데이터를 처리하여 최종 이미지를 생성합니다. 각 블록은 순서대로 실행됩니다:

순서블록기능V4L2 컨트롤
1Black Level Correction센서의 어둠 전류(Dark Current) 보정. 0 입사광에도 발생하는 신호 오프셋 제거
2Defect Pixel Correction불량(Dead/Hot) 픽셀을 주변 값으로 보간(Interpolation)
3Lens Shading Correction렌즈 특성에 의한 주변부 밝기 저하(비네팅) 보정
4AWB (Auto White Balance)장면의 색온도를 감지하여 R/G/B 게인(Gain) 자동 조정V4L2_CID_AUTO_WHITE_BALANCE
5Demosaic (CFA)Bayer 패턴 → 풀 RGB 변환. 각 픽셀에 누락된 2개 색상을 보간
6Color Correction Matrix3x3 행렬로 색 공간 변환. 센서 고유 색 응답을 표준 색 공간으로 매핑
7Gamma Correction선형 → 비선형 감마 커브 적용. 인간 시각에 맞는 밝기 분포로 변환V4L2_CID_GAMMA
8Noise Reduction공간(Spatial)/시간(Temporal) 도메인 노이즈 제거
9Sharpening엣지(Edge) 강조. Unsharp Mask 또는 커널 기반 필터V4L2_CID_SHARPNESS
10Color Space ConversionRGB → YUV 변환. BT.601/709/2020 행렬 선택
11Scaler/Resizer출력 해상도 조정. 바이리니어(Bilinear)/바이큐빅(Bicubic) 보간Selection API
3A 알고리즘: AE(Auto Exposure), AF(Auto Focus), AWB(Auto White Balance)는 ISP의 통계(Statistics) 출력을 사용하여 유저스페이스 데몬(libcamera 등)이 피드백 루프(Feedback Loop)로 수행합니다. V4L2에서는 /dev/v4l-metaN 노드를 통해 3A 통계 데이터(히스토그램, 초점 값 등)를 캡처합니다.

libcamera 아키텍처

복잡한 카메라 파이프라인(ISP 포함)에서는 V4L2만으로는 부족합니다. libcamera는 V4L2/Media Controller 위에서 파이프라인 구성, 3A 알고리즘, 버퍼 관리를 통합하는 유저스페이스 프레임워크입니다:

계층역할구성 요소
애플리케이션카메라 사용GStreamer, PipeWire, cam 유틸리티
libcamera API통합 카메라 APICameraManager, Camera, Request, FrameBuffer
IPA (Image Processing Algorithm)3A 알고리즘플랫폼별 IPA 모듈 (rkisp1, ipu3, mali-c55)
Pipeline HandlerV4L2/MC 제어플랫폼별 파이프라인 핸들러
V4L2 / Media Controller커널 인터페이스/dev/videoN, /dev/mediaN, /dev/v4l-subdevN

주요 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/

전체 카메라 파이프라인 상세 흐름

임베디드 카메라 시스템에서 한 프레임이 센서에서 유저스페이스까지 전달되는 과정을 단계별로 살펴봅니다:

단계위치동작관련 V4L2 API
1. 노출센서빛 → 전하 변환 (노출 시간 동안 광자 축적)V4L2_CID_EXPOSURE_ABSOLUTE
2. 읽기센서 ADC아날로그 → 디지털 변환 (10/12/14비트)V4L2_CID_ANALOGUE_GAIN
3. 전송MIPI CSI-2직렬 고속 전송 (D-PHY/C-PHY)DT: data-lanes, link-frequencies
4. 수신CSI-2 리시버역직렬화(Deserialization), 패킷 파싱서브디바이스 s_stream()
5. ISP 처리ISPBLC→DPC→LSC→AWB→디모자이크→CCM→감마→NR→CSC센서/ISP 컨트롤
6. 스케일링ISP/리사이저해상도 변경, Selection API로 크롭VIDIOC_S_SELECTION
7. DMA 전송DMA 엔진처리 결과를 시스템 메모리에 기록vb2_buffer_done()
8. 유저 수거유저스페이스poll() → DQBUF로 프레임 수거VIDIOC_DQBUF

카메라 전원 시퀀스(Power Sequence)

카메라 센서는 엄격한 전원 투입 순서를 요구합니다. 잘못된 순서는 센서 손상이나 I2C 통신 실패를 유발합니다:

/* 전형적인 카메라 센서 전원 시퀀스 (예: IMX219) */
static int my_sensor_power_on(struct device *dev)
{
    struct my_sensor *sensor = dev_get_drvdata(dev);
    int ret;

    /* 1. 아날로그 전원 (AVDD) — 일반적으로 2.8V */
    ret = regulator_enable(sensor->avdd);
    if (ret)
        return ret;

    /* 2. 디지털 전원 (DVDD) — 일반적으로 1.2V */
    ret = regulator_enable(sensor->dvdd);
    if (ret)
        goto err_avdd;

    /* 3. I/O 전원 (DOVDD) — 일반적으로 1.8V */
    ret = regulator_enable(sensor->dovdd);
    if (ret)
        goto err_dvdd;

    /* 4. 클럭 활성화 (XCLK/MCLK) */
    ret = clk_prepare_enable(sensor->xclk);
    if (ret)
        goto err_dovdd;

    /* 5. 리셋 해제 (XCLR pin active high) */
    gpiod_set_value_cansleep(sensor->reset_gpio, 0);

    /* 6. 안정화 대기 — 데이터시트 참조 (보통 1~10ms) */
    usleep_range(6000, 10000);

    return 0;

err_dovdd:
    regulator_disable(sensor->dovdd);
err_dvdd:
    regulator_disable(sensor->dvdd);
err_avdd:
    regulator_disable(sensor->avdd);
    return ret;
}

/* 전원 off — 역순 */
static int my_sensor_power_off(struct device *dev)
{
    struct my_sensor *sensor = dev_get_drvdata(dev);

    gpiod_set_value_cansleep(sensor->reset_gpio, 1);
    clk_disable_unprepare(sensor->xclk);
    regulator_disable(sensor->dovdd);
    regulator_disable(sensor->dvdd);
    regulator_disable(sensor->avdd);

    return 0;
}

static const struct dev_pm_ops my_sensor_pm_ops = {
    SET_RUNTIME_PM_OPS(my_sensor_power_off,
                        my_sensor_power_on, NULL)
};

센서 모드(Mode) 테이블 패턴

카메라 센서는 여러 해상도/프레임레이트 모드를 지원합니다. 각 모드는 레지스터 테이블로 정의됩니다:

/* 센서 레지스터 설정 */
struct sensor_reg {
    u16 addr;
    u8  val;
};

/* 모드 정의 */
struct sensor_mode {
    u32 width;
    u32 height;
    u32 code;           /* MEDIA_BUS_FMT_* */
    struct v4l2_fract interval;  /* 프레임 간격(Interval) */
    const struct sensor_reg *reg_list;
    u32 reg_list_size;
    /* 노출/게인 범위 (모드별로 다를 수 있음) */
    u32 exposure_max;
    u32 vblank_def;     /* 수직 블랭킹 기본값 */
    u64 link_freq;      /* MIPI 링크 주파수 */
    u32 pixel_rate;     /* 픽셀 클럭 */
};

/* 모드 테이블 */
static const struct sensor_mode supported_modes[] = {
    {   /* Mode 0: 3840x2160 @ 30fps */
        .width      = 3840,
        .height     = 2160,
        .code       = MEDIA_BUS_FMT_SRGGB10_1X10,
        .interval   = { .numerator = 1, .denominator = 30 },
        .reg_list   = mode_3840x2160_regs,
        .reg_list_size = ARRAY_SIZE(mode_3840x2160_regs),
        .link_freq  = 600000000ULL,
        .pixel_rate = 240000000,
    },
    {   /* Mode 1: 1920x1080 @ 60fps */
        .width      = 1920,
        .height     = 1080,
        .code       = MEDIA_BUS_FMT_SRGGB10_1X10,
        .interval   = { .numerator = 1, .denominator = 60 },
        .reg_list   = mode_1920x1080_regs,
        .reg_list_size = ARRAY_SIZE(mode_1920x1080_regs),
        .link_freq  = 300000000ULL,
        .pixel_rate = 120000000,
    },
    {   /* Mode 2: 640x480 @ 120fps (슬로우 모션) */
        .width      = 640,
        .height     = 480,
        .code       = MEDIA_BUS_FMT_SRGGB10_1X10,
        .interval   = { .numerator = 1, .denominator = 120 },
        .reg_list   = mode_640x480_regs,
        .reg_list_size = ARRAY_SIZE(mode_640x480_regs),
        .link_freq  = 150000000ULL,
        .pixel_rate = 60000000,
    },
};

센서 드라이버 패턴과 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)

UVC 토폴로지 예시

일반적인 USB 웹캠의 UVC 내부 토폴로지:

Input Terminal (ITT_CAMERA) 노출, 포커스, 줌 팬/틸트, 개인정보 Processing Unit (VC_PROCESSING_UNIT) 밝기, 대비, 색조 화이트밸런스, 감마 Extension Unit (선택적) 벤더 고유 기능 얼굴 추적, HDR 등 Output Terminal (OTT_STREAMING) 비디오 스트림 출력 → USB 벌크/등시 전송 UVC 디스크립터가 이 토폴로지를 USB 디스크립터로 기술 uvcvideo 드라이버가 파싱하여 V4L2 컨트롤/video_device로 매핑

커널 uvcvideo 드라이버

# 커널 설정
CONFIG_USB_VIDEO_CLASS=m

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

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

# UVC 디바이스 스트리밍 인터페이스 정보
$ v4l2-ctl -d /dev/video0 --all
$ v4l2-ctl -d /dev/video0 --list-formats-ext

# UVC 카메라 일반적 포맷/해상도
$ v4l2-ctl -d /dev/video0 --list-formats-ext
# [0]: 'YUYV' (YUYV 4:2:2)
#   Size: 640x480, 1280x720, 1920x1080
#   Interval: 1/30, 1/15
# [1]: 'MJPG' (Motion-JPEG)
#   Size: 640x480, 1280x720, 1920x1080, 3840x2160
#   Interval: 1/30, 1/60

Extension Unit 접근

/* UVC Extension Unit 직접 접근 */
#include <linux/uvcvideo.h>

/* Extension Unit 컨트롤 조회 */
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);

/* Extension Unit 컨트롤 설정 */
xquery.query = UVC_SET_CUR;
uint8_t value[] = { 0x01, 0x00, 0x00, 0x00 };
xquery.data = value;
ioctl(fd, UVCIOC_CTRL_QUERY, &xquery);

/* UVC quirks — 비표준 카메라 대응 */
/* uvcvideo 드라이버의 quirks 모듈 파라미터 */
/* 또는 커널 소스의 uvc_ids[] 테이블에 추가 */
$ modprobe uvcvideo quirks=0x80   /* UVC_QUIRK_FORCE_Y8 등 */

UVC Gadget (디바이스 측)

Linux의 USB Gadget 프레임워크를 사용하면 SBC(Single Board Computer)를 USB 카메라로 동작시킬 수 있습니다:

# UVC Gadget 설정 (ConfigFS 기반)
$ mkdir -p /sys/kernel/config/usb_gadget/uvc
$ cd /sys/kernel/config/usb_gadget/uvc

# USB 디스크립터 설정
$ echo 0x1d6b > idVendor     # Linux Foundation
$ echo 0x0104 > idProduct    # Multifunction Composite Gadget

# UVC 기능 추가
$ mkdir -p functions/uvc.usb0
$ mkdir -p functions/uvc.usb0/streaming/uncompressed/u/1080p
$ echo 1920 > functions/uvc.usb0/streaming/uncompressed/u/1080p/wWidth
$ echo 1080 > functions/uvc.usb0/streaming/uncompressed/u/1080p/wHeight
$ echo 333333 > functions/uvc.usb0/streaming/uncompressed/u/1080p/dwDefaultFrameInterval

# uvc-gadget 도구로 V4L2 소스 → USB 출력
$ uvc-gadget -d /dev/video0 -u /dev/video1

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>;
            };
        };
    };
};

멀티 카메라 DT 바인딩

두 개 이상의 카메라 센서를 연결하는 경우 각 CSI-2 포트를 별도로 기술합니다:

/* 듀얼 카메라 설정: IMX219 + OV5640 */
&i2c1 {
    imx219: sensor@10 {
        compatible = "sony,imx219";
        reg = <0x10>;
        port {
            imx219_out: endpoint {
                remote-endpoint = <&csi0_in>;
                data-lanes = <1 2>;
            };
        };
    };
};

&i2c2 {
    ov5640: sensor@3c {
        compatible = "ovti,ov5640";
        reg = <0x3c>;
        port {
            ov5640_out: endpoint {
                remote-endpoint = <&csi1_in>;
                data-lanes = <1 2>;
            };
        };
    };
};

/* CSI-2 리시버 0 */
&csi0 {
    ports {
        port@0 {
            csi0_in: endpoint {
                remote-endpoint = <&imx219_out>;
                data-lanes = <1 2>;
            };
        };
    };
};

/* CSI-2 리시버 1 */
&csi1 {
    ports {
        port@0 {
            csi1_in: endpoint {
                remote-endpoint = <&ov5640_out>;
                data-lanes = <1 2>;
            };
        };
    };
};

플래시 LED DT 바인딩

/* 카메라 플래시 LED 바인딩 */
flash_led: led-controller@63 {
    compatible = "ti,lm3646";
    reg = <0x63>;
    #address-cells = <1>;
    #size-cells = <0>;

    led@0 {
        reg = <0>;
        function = LED_FUNCTION_FLASH;
        color = <LED_COLOR_ID_WHITE>;
        led-max-microamp = <250000>;
        flash-max-microamp = <1000000>;
        flash-max-timeout-us = <1000000>;
    };
};

/* 센서에서 플래시 참조 */
&imx219 {
    flash-leds = <&flash_led 0>;
};

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

# 서브디바이스 테스트
$ v4l2-compliance -d /dev/v4l-subdev0 -e

# 상세 출력 모드
$ v4l2-compliance -d /dev/video0 -v -s

# 결과 해석
# Total for device /dev/video0: 95, Succeeded: 93, Failed: 2, Warnings: 0

주요 v4l2-compliance 테스트 카테고리:

카테고리테스트 내용흔한 실패 원인
Required ioctlsQUERYCAP, ENUM_FMT, G/S_FMT 등ioctl_ops 콜백 미구현
Buffer ioctlsREQBUFS, QUERYBUF, QBUF/DQBUFvb2 설정 오류, 버퍼 타입 불일치
StreamingSTREAMON/STREAMOFF, 실제 프레임 캡처start/stop_streaming 콜백 버그
ControlsG/S_CTRL, G/S_EXT_CTRLS범위 초과, 타입 불일치
Input/OutputENUM_INPUT, G/S_INPUT입력 인덱스 범위, 누락 구현
FormatTRY_FMT, CREATE_BUFS 포맷 호환TRY_FMT와 S_FMT 결과 불일치
SelectionG/S_SELECTION, 경계 확인BOUNDS 미구현, 정렬 미준수
드라이버 업스트림 필수: 커널에 V4L2 드라이버를 제출할 때 v4l2-compliance를 통과해야 합니다. 실패 항목이 있으면 리뷰에서 거부됩니다. v4l2-compliance의 버전은 최신 v4l-utils를 사용해야 합니다.

v4l2-ctl 고급 사용법

# 지원 해상도와 프레임 레이트 열거
$ v4l2-ctl -d /dev/video0 --list-framesizes=MJPG
# Size: Discrete 640x480
# Size: Discrete 1280x720
# Size: Discrete 1920x1080

$ v4l2-ctl -d /dev/video0 --list-frameintervals=width=1920,height=1080,pixelformat=YUYV
# Interval: Discrete 0.033s (30.000 fps)

# 프레임 레이트 설정
$ v4l2-ctl -d /dev/video0 --set-parm=60

# DMA-BUF 모드 캡처
$ v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 \
    --export-device=/dev/video1 --stream-dmabuf

# Selection (크롭) 설정
$ v4l2-ctl -d /dev/video0 --set-selection=target=crop,left=100,top=100,width=640,height=480

# 연속 캡처 + 프레임 레이트 측정
$ v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=300 --verbose 2>&1 | tail -5
# 300 frames captured, average fps: 29.97

# 로그 상태 덤프 (dmesg에 출력)
$ v4l2-ctl -d /dev/video0 --log-status

# EDID 관련 (HDMI 캡처 카드)
$ v4l2-ctl -d /dev/video0 --get-edid
$ v4l2-ctl -d /dev/video0 --set-edid=file=edid.bin

libcamera / GStreamer / FFmpeg 연동

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

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

# GStreamer — V4L2 M2M 하드웨어 디코딩
$ gst-launch-1.0 filesrc location=video.mp4 ! qtdemux ! h264parse ! \
    v4l2slh264dec ! videoconvert ! autovideosink

# GStreamer — V4L2 하드웨어 인코딩 + 스트리밍
$ gst-launch-1.0 v4l2src device=/dev/video0 ! \
    video/x-raw,width=1920,height=1080,framerate=30/1 ! \
    v4l2h264enc extra-controls="controls,video_bitrate=4000000" ! \
    h264parse ! mpegtsmux ! udpsink host=192.168.1.100 port=5000

# GStreamer — libcamerasrc 사용 (ISP 파이프라인 자동 구성)
$ gst-launch-1.0 libcamerasrc ! \
    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 하드웨어 인코더 (stateful 코덱)
$ ffmpeg -f v4l2 -i /dev/video0 -c:v h264_v4l2m2m -b:v 4M output.mp4

# FFmpeg — V4L2 M2M 하드웨어 디코딩
$ ffmpeg -hwaccel v4l2m2m -i input.mp4 -f rawvideo output.yuv

GStreamer V4L2 요소(Element) 확인

# V4L2 관련 GStreamer 요소 목록
$ gst-inspect-1.0 | grep v4l2
v4l2:  v4l2src: Video (video4linux2) Source
v4l2:  v4l2sink: Video (video4linux2) Sink
v4l2:  v4l2h264dec: V4L2 H.264 Decoder
v4l2:  v4l2h264enc: V4L2 H.264 Encoder
v4l2:  v4l2slh264dec: V4L2 Stateless H.264 Decoder
v4l2:  v4l2h265dec: V4L2 H.265 Decoder
...

# V4L2 소스 속성 확인
$ gst-inspect-1.0 v4l2src
# Properties:
#   device: /dev/video0 (기본값)
#   io-mode: mmap/userptr/dmabuf/dmabuf-import
#   extra-controls: V4L2 컨트롤 설정

Python에서 V4L2 사용

# v4l2-python3 또는 PyV4L2Camera 사용 예
import subprocess
import struct
import fcntl

# ioctl 래퍼를 통한 직접 접근
VIDIOC_QUERYCAP = 0x80685600

with open('/dev/video0', 'rb') as f:
    # QUERYCAP 호출
    buf = bytearray(104)
    fcntl.ioctl(f, VIDIOC_QUERYCAP, buf)
    driver = buf[0:16].decode('utf-8').strip('\x00')
    card = buf[16:48].decode('utf-8').strip('\x00')
    print(f"Driver: {driver}, Card: {card}")

# OpenCV로 V4L2 캡처
import cv2
cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M','J','P','G'))
ret, frame = cap.read()
cap.release()

디버깅과 프로파일링(Profiling)

커널 디버그 메시지

/* 드라이버 내 디버그 출력 */
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");
v4l2_warn(&mydev->v4l2_dev, "format adjusted\\n");

/* 모듈 파라미터로 디버그 레벨 제어 */
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "debug level (0=off, 1=info, 2=verbose)");

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

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

ftrace를 이용한 V4L2 ioctl 추적

V4L2는 ftrace tracepoint를 제공하여 ioctl 호출을 상세히 추적할 수 있습니다:

# V4L2 tracepoint 확인
$ ls /sys/kernel/debug/tracing/events/v4l2/
v4l2_dqbuf  v4l2_qbuf  vb2_v4l2_buf_done  vb2_v4l2_buf_queue  ...

# 모든 V4L2 tracepoint 활성화
$ echo 1 > /sys/kernel/debug/tracing/events/v4l2/enable

# 특정 이벤트만 추적 (버퍼 QBUF/DQBUF)
$ echo 1 > /sys/kernel/debug/tracing/events/v4l2/v4l2_qbuf/enable
$ echo 1 > /sys/kernel/debug/tracing/events/v4l2/v4l2_dqbuf/enable

# 추적 시작 + 캡처 실행
$ echo 1 > /sys/kernel/debug/tracing/tracing_on
$ v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10

# 추적 결과 확인
$ cat /sys/kernel/debug/tracing/trace
# v4l2-ctl-1234: v4l2_qbuf: minor=0 index=0 type=1 bytesused=0 flags=0x2000
# IRQ-56:       vb2_v4l2_buf_done: minor=0 index=0 type=1 bytesused=460800
# v4l2-ctl-1234: v4l2_dqbuf: minor=0 index=0 type=1 bytesused=460800 flags=0x2001

# 추적 중지
$ echo 0 > /sys/kernel/debug/tracing/tracing_on

perf를 이용한 성능 분석

# V4L2 tracepoint 기반 성능 분석
$ perf trace -e v4l2:* v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=100

# 프레임 지연 시간 측정 (QBUF → DQBUF 간격)
$ perf trace -e v4l2:v4l2_qbuf,v4l2:v4l2_dqbuf --duration 5 -p $(pidof my_app)

# DMA 관련 시스템 콜 성능 분석
$ perf stat -e 'syscalls:sys_enter_ioctl' -p $(pidof my_app) sleep 5

# IRQ 처리 시간 분석
$ perf record -g -e irq:irq_handler_entry,irq:irq_handler_exit sleep 5
$ perf report

debugfs 엔트리

많은 V4L2 드라이버가 debugfs를 통해 내부 상태를 노출합니다:

# vb2 큐 상태 확인 (커널 6.x)
$ cat /sys/kernel/debug/video0/vb2_queue
# type: 1 (VIDEO_CAPTURE)
# io_modes: 5 (MMAP|DMABUF)
# min_queued_buffers: 1
# queued_count: 3
# owned_by_drv_count: 2
# streaming: 1

# UVC 디버그 상세 활성화
$ echo 0xffff > /sys/module/uvcvideo/parameters/trace
$ dmesg | grep uvcvideo

# Media Controller 토폴로지 덤프
$ media-ctl -d /dev/media0 -p

# rkisp1 ISP 통계 확인
$ cat /sys/kernel/debug/rkisp1/input_status

자주 발생하는 문제와 해결

증상원인해결
VIDIOC_DQBUF: No such deviceUSB 카메라 분리디바이스 재연결, fd 재오픈
VIDIOC_STREAMON: No space leftUSB 대역폭(Bandwidth) 부족해상도/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-ctl -d /dev/video0 --log-status
v4l2-complianceioctl 적합성 테스트v4l2-compliance -d /dev/video0 -s
media-ctl -pMedia Controller 토폴로지 덤프(Dump)media-ctl -d /dev/media0 -p
dmesg드라이버 등록(Driver Registration)/에러 메시지dmesg | grep -i 'v4l2\|video\|camera'
ftraceV4L2 ioctl 추적echo 1 > events/v4l2/enable
debugfs드라이버별 debugfs 엔트리ls /sys/kernel/debug/video0/
perf traceV4L2 tracepoint 추적perf trace -e v4l2:* -p PID
strace유저스페이스 ioctl 호출 추적strace -e ioctl v4l2-ctl --stream-mmap
v4l2-dbgV4L2 레지스터 읽기/쓰기v4l2-dbg -d /dev/video0 --chip=subdev0 0x3000

성능 문제 진단 체크리스트

증상점검 항목해결 방법
프레임 레이트(Frame Rate) 미달센서 설정, ISP 처리 시간, DMA 대역폭센서 해상도/FPS 확인, v4l2-ctl --get-parm, perf로 병목 분석
프레임 드롭(Drop)버퍼 부족, DQBUF 지연, CPU 부하버퍼 수 증가 (4→8), 애플리케이션 처리 시간 단축
프레임 지연(Latency) 높음버퍼 과다 큐잉, ISP 파이프라인 깊이최소 버퍼 수(3~4) 사용, V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC 확인
DMA 오류CMA 메모리 부족, IOMMU 매핑 실패dmesg | grep -i cma, CMA 크기 증가, cat /proc/meminfo | grep Cma
캡처 이미지 찢어짐(Tearing)V-Sync 미동기화, DMA 주소 업데이트 타이밍더블 버퍼링 확인, IRQ 핸들러에서 DMA 주소 전환
USB 대역폭 부족동시 사용 USB 디바이스, 허브 공유cat /sys/kernel/debug/usb/devices, USB 3.0 포트 직결

Request API 상세

Request API는 V4L2의 원자적(Atomic) 파라미터 적용 메커니즘입니다. 하나의 Request에 버퍼와 컨트롤을 묶어서 하드웨어에 동시 적용할 수 있습니다. 주로 stateless 코덱과 복잡한 카메라 파이프라인에서 사용됩니다.

Request 수명주기

단계API설명
할당MEDIA_IOC_REQUEST_ALLOCmedia_device에서 Request fd 할당
설정VIDIOC_S_EXT_CTRLS (request_fd 지정)Request에 컨트롤 값 바인딩
버퍼 연결VIDIOC_QBUF (V4L2_BUF_FLAG_REQUEST_FD)Request에 버퍼 연결
큐잉MEDIA_REQUEST_IOC_QUEUERequest를 드라이버에 제출
실행(드라이버 내부)하드웨어가 버퍼+컨트롤 원자적 적용
완료poll() / VIDIOC_DQBUFRequest 완료 대기
재초기화MEDIA_REQUEST_IOC_REINITRequest fd 재사용 (새 설정 가능)
해제close(req_fd)Request 리소스 해제

드라이버 측 Request 구현

/* Request API 지원 드라이버 설정 */
static const struct media_device_ops my_media_ops = {
    .req_alloc    = vb2_request_alloc,      /* vb2 제공 헬퍼 */
    .req_validate = my_request_validate,   /* 커스텀 검증 */
    .req_queue    = vb2_m2m_request_queue, /* M2M 헬퍼 */
};

/* Request 검증: 필수 컨트롤이 모두 설정되었는지 확인 */
static int my_request_validate(struct media_request *req)
{
    struct media_request_object *obj;
    int has_sps = 0, has_pps = 0;

    list_for_each_entry(obj, &req->objects, list) {
        /* 컨트롤 객체 검사 */
        if (obj->ops == &v4l2_ctrl_request_ops) {
            /* 필수 H.264 파라미터 존재 확인 */
        }
    }

    return vb2_request_validate(req);
}

/* device_run에서 Request의 컨트롤 적용 */
static void my_device_run(void *priv)
{
    struct my_ctx *ctx = priv;
    struct vb2_v4l2_buffer *src, *dst;

    src = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
    dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);

    /* Request의 컨트롤 값을 하드웨어에 적용 */
    v4l2_ctrl_request_setup(src->vb2_buf.req_obj.req,
                            &ctx->ctrl_handler);

    /* 하드웨어에 작업 전달 */
    my_hw_start(ctx, src, dst);
}

/* IRQ 완료 시 Request 정리 */
static void my_irq_done(struct my_ctx *ctx)
{
    struct vb2_v4l2_buffer *src, *dst;

    src = v4l2_m2m_src_buf_remove(ctx->m2m_ctx);
    dst = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx);

    /* Request 컨트롤 정리 */
    v4l2_ctrl_request_complete(src->vb2_buf.req_obj.req,
                               &ctx->ctrl_handler);

    v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE);
    v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE);
    v4l2_m2m_job_finish(ctx->m2m_dev, ctx->m2m_ctx);
}

주요 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)를 활성화한 뒤, 필요한 카테고리(카메라/코덱/플랫폼)를 선택합니다. 개별 드라이버는 해당 카테고리 하위에 위치합니다.

V4L2 드라이버 개발 베스트 프랙티스(Best Practices)

업스트림 제출 체크리스트

Linux 커널 미디어 서브시스템에 드라이버를 제출할 때 필수적으로 확인해야 할 항목:

항목확인 사항도구/방법
v4l2-compliance모든 테스트 통과 (특히 -s 스트리밍 테스트)v4l2-compliance -d /dev/video0 -s
코딩 스타일커널 코딩 스타일 준수scripts/checkpatch.pl
DT 바인딩YAML 형식 DT 바인딩 문서 포함make dt_binding_check
커밋 메시지"media: vendor: description" 형식기존 로그 참고
메일링 리스트linux-media@vger.kernel.org에 패치 제출git send-email
MAINTAINERS드라이버 유지보수자 항목 추가scripts/get_maintainer.pl
sparse/smatch정적 분석 경고 없음make C=2 drivers/media/...

락킹(Locking) 패턴

V4L2 드라이버의 올바른 락킹 전략:

/* 1. video_device.lock — ioctl 직렬화 */
/*    video_device에 mutex를 설정하면 V4L2 코어가 모든 ioctl을 직렬화 */
cam->vdev.lock = &cam->mutex;

/* 2. vb2_queue.lock — 버퍼 ioctl 직렬화 */
/*    video_device.lock과 같은 mutex를 사용하는 것이 일반적 */
cam->queue.lock = &cam->mutex;

/* 3. spinlock — IRQ 핸들러와 공유 데이터 보호 */
/*    buf_list 등 IRQ 컨텍스트에서 접근하는 데이터 */
spin_lock_irqsave(&cam->slock, flags);
list_add_tail(&buf->list, &cam->buf_list);
spin_unlock_irqrestore(&cam->slock, flags);

/* 4. v4l2_ctrl_handler — 컨트롤 프레임워크 내부 락 */
/*    v4l2_ctrl_handler_setup()은 lock 내부에서 호출 금지 */
/*    s_ctrl 콜백은 handler->lock 하에 호출됨 */

/* 5. 데드락 방지: wait_prepare/wait_finish */
/*    vb2가 DQBUF에서 대기할 때 mutex를 해제하는 헬퍼 */
static const struct vb2_ops my_vb2_ops = {
    .wait_prepare = vb2_ops_wait_prepare,  /* queue->lock 해제 */
    .wait_finish  = vb2_ops_wait_finish,   /* queue->lock 획득 */
    ...
};

흔한 실수와 방지책

실수결과올바른 방법
stop_streaming에서 버퍼 미반환vb2 WARNING, 메모리 누수모든 버퍼를 vb2_buffer_done(ERROR)로 반환
start_streaming 실패 시 버퍼 미반환vb2 hang실패 시 vb2_buffer_done(QUEUED)로 복원
IRQ 핸들러에서 mutex 사용커널 BUG (sleep in atomic)spinlock_irqsave 사용
v4l2_fh 미초기화이벤트/우선순위 미동작open에서 v4l2_fh_open() 또는 v4l2_fh_init()+add()
device_caps 미설정v4l2-compliance 실패vdev.device_caps에 정확한 CAP 플래그 설정
timestamp 미기록프레임 타이밍 정보 없음IRQ에서 vb2_buf.timestamp = ktime_get_ns()
sequence 미증가프레임 드롭 감지 불가IRQ에서 vb.sequence = cam->sequence++
remove/disconnect 미처리USB 분리 시 커널 OOPSvideo_unregister_device() + vb2_queue_release()

테스트 전략

# 1. 기본 적합성 테스트
$ v4l2-compliance -d /dev/video0 -v
$ v4l2-compliance -d /dev/video0 -s    # 스트리밍 포함

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

# 3. 스트레스 테스트 (장시간 캡처)
$ v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10000

# 4. 다양한 해상도/포맷 테스트
$ for fmt in YUYV NV12 MJPG; do
    v4l2-ctl -d /dev/video0 --set-fmt-video=pixelformat=$fmt
    v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10
  done

# 5. KASAN/UBSAN 활성화 빌드로 메모리 오류 검출
# CONFIG_KASAN=y, CONFIG_UBSAN=y

# 6. lockdep 활성화로 데드락 검출
# CONFIG_PROVE_LOCKING=y

# 7. vivid 가상 드라이버로 참조 구현 비교
$ modprobe vivid
$ v4l2-compliance -d /dev/video0   # 모든 테스트 통과해야 함
vivid 가상 드라이버: drivers/media/test-drivers/vivid/에 위치한 vivid는 V4L2 API의 참조 구현입니다. 실제 하드웨어 없이 모든 V4L2 기능(캡처, 출력, M2M, 컨트롤, 멀티플레인 등)을 테스트할 수 있어 드라이버 개발 학습에 매우 유용합니다. modprobe vivid로 로드하면 여러 개의 /dev/videoN 노드가 생성됩니다.

CI/자동화 테스트

V4L2 드라이버 개발에 활용할 수 있는 자동화 테스트 환경:

#!/bin/bash
# V4L2 드라이버 자동 테스트 스크립트

set -e
DEVICE=${1:-/dev/video0}
MEDIA=${2:-/dev/media0}

echo "=== V4L2 드라이버 자동 테스트 ==="

# 1. 적합성 테스트
echo "[1/5] v4l2-compliance 실행..."
v4l2-compliance -d $DEVICE -s 2>&1 | tee compliance.log
FAILED=$(grep "Failed:" compliance.log | awk '{print $2}')
if [ "$FAILED" != "0" ]; then
    echo "경고: $FAILED 개 테스트 실패"
fi

# 2. 지원 포맷 확인
echo "[2/5] 포맷 열거..."
v4l2-ctl -d $DEVICE --list-formats-ext

# 3. 각 포맷으로 10프레임 캡처 테스트
echo "[3/5] 포맷별 캡처 테스트..."
for FMT in $(v4l2-ctl -d $DEVICE --list-formats | grep "'" | awk -F"'" '{print $2}'); do
    echo "  테스트: $FMT"
    v4l2-ctl -d $DEVICE --set-fmt-video=pixelformat=$FMT \
        --stream-mmap --stream-count=10 2>/dev/null || echo "  $FMT 실패"
done

# 4. 컨트롤 테스트
echo "[4/5] 컨트롤 테스트..."
v4l2-ctl -d $DEVICE --list-ctrls

# 5. Media Controller 테스트 (있는 경우)
if [ -e "$MEDIA" ]; then
    echo "[5/5] Media Controller 테스트..."
    v4l2-compliance -m $MEDIA 2>&1 | tee media-compliance.log
    media-ctl -d $MEDIA -p
fi

echo "=== 테스트 완료 ==="

커널 버전 호환성 참고

기능최소 커널 버전비고
videobuf22.6.38videobuf(v1) 대체
DMA-BUF (V4L2_MEMORY_DMABUF)3.8제로카피 버퍼 공유
Media Controller3.3파이프라인 토폴로지
멀티플레인 API3.1V4L2_BUF_TYPE_*_MPLANE
Selection API3.2레거시 crop API 대체
Request API5.3stateless 코덱
Streams API6.1멀티플렉스 라우팅
v4l2_subdev active state6.3서브디바이스 상태 관리
V4L2_CAP_IO_MC5.9MC 기반 I/O 능력
VIDIOC_REMOVE_BUFS6.8런타임 버퍼 삭제
V4L2_SUBDEV_FL_STREAMS6.3서브디바이스 스트림 지원

외부 참고 자료

자료URL설명
V4L2 공식 문서kernel.org/doc/html/latest/userspace-api/media/v4l/V4L2 UAPI 사양
Media Controller 문서kernel.org/doc/html/latest/driver-api/media/드라이버 API 참조
libcameralibcamera.org카메라 파이프라인 추상화 프레임워크
v4l-utilslinuxtv.org/wiki/index.php/V4l-utils유틸리티 모음 (v4l2-ctl, v4l2-compliance 등)
MIPI Alliancemipi.orgCSI-2, D-PHY, C-PHY 사양
Linux Media Subsystem Wikilinuxtv.org/wiki/커뮤니티 위키

V4L2 API 빠른 참조

커널 API 요약

카테고리함수용도
v4l2_devicev4l2_device_register()최상위 디바이스 등록
v4l2_device_unregister()디바이스 해제
video_devicevideo_register_device()/dev/videoN 생성
video_unregister_device()디바이스 노드 제거
video_set_drvdata()드라이버 전용 데이터 설정
v4l2_subdevv4l2_subdev_init()서브디바이스 초기화
v4l2_device_register_subdev()서브디바이스 등록
v4l2_async_register_subdev()비동기 서브디바이스 등록
vb2vb2_queue_init()버퍼 큐 초기화
vb2_buffer_done()버퍼 완료 알림 (IRQ에서)
vb2_set_plane_payload()유효 데이터 크기 설정
v4l2_ctrlv4l2_ctrl_handler_init()컨트롤 핸들러 초기화
v4l2_ctrl_new_std()표준 컨트롤 추가
v4l2_ctrl_new_custom()커스텀 컨트롤 추가
v4l2_ctrl_handler_free()핸들러 정리
Media Controllermedia_device_register()/dev/mediaN 생성
media_entity_pads_init()엔터티 패드 초기화
media_create_pad_link()패드 간 링크 생성
DMA 주소vb2_dma_contig_plane_dma_addr()연속 DMA 주소 획득
vb2_dma_sg_plane_desc()SG 테이블 획득
vb2_plane_vaddr()커널 가상 주소 획득

유저스페이스 ioctl 빠른 참조

ioctl구조체용도
VIDIOC_QUERYCAPv4l2_capability디바이스 능력 조회
VIDIOC_ENUM_FMTv4l2_fmtdesc지원 포맷 열거
VIDIOC_G_FMT / S_FMTv4l2_format포맷 조회/설정
VIDIOC_TRY_FMTv4l2_format포맷 테스트 (실제 적용 없음)
VIDIOC_REQBUFSv4l2_requestbuffers버퍼 할당
VIDIOC_QUERYBUFv4l2_buffer버퍼 정보 조회
VIDIOC_QBUFv4l2_buffer버퍼 큐잉
VIDIOC_DQBUFv4l2_buffer버퍼 수거
VIDIOC_STREAMONv4l2_buf_type스트리밍 시작
VIDIOC_STREAMOFFv4l2_buf_type스트리밍 중지
VIDIOC_EXPBUFv4l2_exportbufferDMA-BUF fd 내보내기
VIDIOC_G_CTRL / S_CTRLv4l2_control컨트롤 조회/설정
VIDIOC_G_EXT_CTRLS / S_EXT_CTRLSv4l2_ext_controls확장 컨트롤 조회/설정
VIDIOC_SUBSCRIBE_EVENTv4l2_event_subscription이벤트 구독
VIDIOC_DQEVENTv4l2_event이벤트 수거
VIDIOC_G_SELECTION / S_SELECTIONv4l2_selection크롭/컴포즈 설정
VIDIOC_ENUM_FRAMESIZESv4l2_frmsizeenum지원 해상도 열거
VIDIOC_ENUM_FRAMEINTERVALSv4l2_frmivalenum지원 프레임 간격 열거
VIDIOC_G_PARM / S_PARMv4l2_streamparm프레임 레이트 설정

서브디바이스 ioctl 빠른 참조

ioctl구조체용도
VIDIOC_SUBDEV_QUERYCAPv4l2_subdev_capability서브디바이스 능력 조회
VIDIOC_SUBDEV_G_FMT / S_FMTv4l2_subdev_format패드 포맷 조회/설정
VIDIOC_SUBDEV_ENUM_MBUS_CODEv4l2_subdev_mbus_code_enum미디어 버스 코드 열거
VIDIOC_SUBDEV_ENUM_FRAME_SIZEv4l2_subdev_frame_size_enum지원 해상도 열거
VIDIOC_SUBDEV_G_SELECTION / S_SELECTIONv4l2_subdev_selection패드 레벨 크롭/컴포즈
VIDIOC_SUBDEV_G_ROUTING / S_ROUTINGv4l2_subdev_routingStreams API 라우팅
VIDIOC_SUBDEV_G_FRAME_INTERVALv4l2_subdev_frame_interval프레임 간격 조회/설정

Media Controller ioctl 빠른 참조

ioctl구조체용도
MEDIA_IOC_DEVICE_INFOmedia_device_info미디어 디바이스 정보
MEDIA_IOC_G_TOPOLOGYmedia_v2_topology전체 토폴로지 조회
MEDIA_IOC_SETUP_LINKmedia_link_desc링크 활성화/비활성화
MEDIA_IOC_REQUEST_ALLOCint *Request fd 할당
MEDIA_REQUEST_IOC_QUEUERequest 제출
MEDIA_REQUEST_IOC_REINITRequest 재초기화

참고 링크

커널 공식 문서:
외부 참고 자료:
커널 소스 참고 경로:
  • drivers/media/v4l2-core/ — V4L2 코어 프레임워크
  • drivers/media/platform/ — SoC별 미디어 드라이버 (ISP, 코덱)
  • drivers/media/usb/ — USB 웹캠 드라이버 (uvcvideo 등)
  • drivers/media/i2c/ — I2C 카메라 센서/코덱 드라이버
  • drivers/media/mc/ — Media Controller 코어
  • include/media/ — V4L2 커널 API 헤더
  • include/uapi/linux/videodev2.h — V4L2 유저 공간 ioctl/구조체 정의
  • include/uapi/linux/media.h — Media Controller uAPI

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.