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 디바이스 모델v4l2_device, video_device, v4l2_subdev 3계층 구조로 비디오 하드웨어를 추상화합니다.
  • Streaming I/O — MMAP, USERPTR, DMABUF 세 가지 버퍼 교환 방식으로 유저스페이스와 제로카피(Zero-copy) 프레임 전달을 지원합니다.
  • videobuf2(vb2) — DMA 버퍼 할당, 매핑(Mapping), 큐잉을 통합 관리하는 프레임워크로, 드라이버가 버퍼 관리를 직접 구현할 필요를 없앱니다.
  • Media Controller — ISP, 센서, 스케일러 등 하드웨어 블록 간 연결 토폴로지(Topology)를 유저스페이스에 그래프로 노출합니다.
  • 픽셀 포맷과 컬러스페이스(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 콜백(Callback)이 드라이버와 어떻게 연동되는지 코드 수준에서 추적합니다.

  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의 설계 철학은 다양한 하드웨어를 단일 사용자 공간(User Space) 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) 라우팅(Routing)
6.3v4l2_subdev active state — 서브디바이스 상태 관리 개선
6.5AV1 Stateless 디코더 uAPI 안정화 — Rockchip RK3588(rkvdec-av1), MediaTek MT8195 지원
6.8vb2 삭제 가능 버퍼 — VIDIOC_REMOVE_BUFS 지원
6.10Intel IPU6 ISYS 메인라인 병합 — MTL/LNL MIPI CSI-2 수신기 지원 (out-of-tree 탈피)
6.11Intel IPU6 PSYS 초기 지원, uvcvideo 확장 — UVC 1.5 H.265 페이로드(Payload)
6.13~6.14V4L2_CAP_IO_MC 정비, visl 가상 드라이버 테스트 범위 확대, MediaTek/Qualcomm 코덱 드라이버 성능 개선
6.15Rockchip rkvdec2 신규 드라이버 준비, NXP i.MX8MP Dewarper 서브디바이스 병합, HDR10 메타데이터 컨트롤 확장
6.17Intel IPU7 staging 드라이버 — Lunar Lake/Arrow Lake용 3세대 CSI-2 RX + PSYS (libcamera 0.6 연동)
6.18 (LTS)V4L2 VP9 stateless 디코더 핵심 코드의 Rust 재작성(RFC), AV1 필름 그레인(Film Grain) 컨트롤 확장, metadata capture 포맷 확장
메인라인 병합 흐름(2025-2026): Intel IPU6/IPU7 mainline 병합과 Rockchip AV1 stateless 디코더 안정화로 "libcamera + mainline kernel" 조합이 산업용 카메라/노트북 웹캠 구성의 사실상 기준이 되었습니다. 벤더 독자 스택(icamerasrc, ipu3-aiqb 바이너리 IPA 등)은 점진적으로 libcamera SoftISP와 community IPA로 대체되고 있습니다.

디바이스 유형

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 번호, 구조체(Struct), 상수 정의

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는 단순한 웹캠 API를 넘어, 임베디드·자동차·산업용 영상 시스템을 위한 완전한 미디어 프레임워크입니다. 다음은 V4L2가 제공하는 핵심 기능과 각 기능이 해결하는 문제를 정리한 것입니다:

기능도입 시기설명관련 API / 개념
제로카피(Zero-copy) 파이프라인커널 3.xDMA-BUF fd를 공유하여 카메라 캡처 → GPU 렌더링 → 디스플레이 출력까지 메모리 복사 없이 전달합니다V4L2_MEMORY_DMABUF, dma_buf_fd
멀티플레인(Multi-plane) API커널 3.14Y/CbCr 등 분리된 메모리 평면(Plane)을 가진 포맷을 효율적으로 처리합니다. GPU/ISP 하드웨어가 요구하는 평면별 정렬(Alignment)을 지원합니다V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
Request API커널 5.3프레임 단위로 버퍼와 컨트롤을 원자적(Atomic)으로 묶어 제출합니다. Stateless 코덱에서 프레임별 디코딩 파라미터를 정확히 동기화합니다MEDIA_IOC_REQUEST_ALLOC, MEDIA_REQUEST_IOC_QUEUE
Streams API커널 6.1단일 패드(Pad)에 여러 스트림을 멀티플렉싱합니다. 멀티카메라 센서나 임베디드 데이터/이미지 분리 전송에 사용됩니다VIDIOC_SUBDEV_S_ROUTING, struct v4l2_subdev_route
Media Controller 토폴로지커널 3.3ISP, 센서, 스케일러 등 하드웨어 블록 간 연결을 유저스페이스에 그래프로 노출합니다. 런타임에 파이프라인 경로를 변경할 수 있습니다/dev/mediaN, MEDIA_IOC_ENUM_LINKS
컨트롤 클러스터링(Clustering)커널 3.x밝기+대비, 노출+게인 등 관련 파라미터를 원자적으로 업데이트합니다. 일부만 변경되어 불일치가 발생하는 것을 방지합니다v4l2_ctrl_cluster(), VIDIOC_S_EXT_CTRLS
Async 서브디바이스 등록커널 3.x서로 다른 버스(I2C, platform)에 위치한 센서와 CSI-2 리시버를 비동기적으로 바인딩합니다. 프로브(Probe) 순서에 의존하지 않습니다v4l2_async_nf_register(), v4l2_async_register_subdev()
런타임 PM 통합커널 4.x카메라 미사용 시 센서 전원을 자동으로 차단합니다. open()/close()에 연동하여 전력 소모를 최소화합니다pm_runtime_get_sync(), SET_RUNTIME_PM_OPS()
v4l2-compliance 자동 테스트v4l-utils드라이버의 V4L2 API 준수 여부를 자동으로 검증합니다. 업스트림 제출의 필수 요건입니다v4l2-compliance -d /dev/video0 -s
Active State (서브디바이스 상태)커널 6.3서브디바이스의 per-pad 포맷/크롭 상태를 프레임워크가 관리합니다. 드라이버가 직접 상태를 추적할 필요를 없앱니다v4l2_subdev_state, v4l2_subdev_lock_and_get_active_state()
HDR / Wide Color Gamut커널 6.xBT.2020 색 공간, HLG/PQ 전달 함수를 지원합니다. 10/12비트 HDR 영상 파이프라인을 구성할 수 있습니다V4L2_COLORSPACE_BT2020, V4L2_XFER_FUNC_SMPTE2084
삭제 가능 버퍼(Removable Buffers)커널 6.8스트리밍 중 개별 버퍼를 동적으로 제거할 수 있습니다. 장시간 스트리밍에서 메모리 관리(Memory Management)를 유연하게 합니다VIDIOC_REMOVE_BUFS
Intel IPU6 메인라인커널 6.10+Meteor Lake/Lunar Lake 내장 CSI-2 수신기를 out-of-tree 바이너리 스택 없이 mainline만으로 구동합니다. libcamera SoftISP와 결합해 노트북 웹캠을 상용 데스크톱 환경에서 사용할 수 있습니다drivers/media/pci/intel/ipu6/, ipu-bridge
AV1 Stateless 디코더커널 6.5+AV1 Frame Header/Tile Group 파라미터를 V4L2 Control로 정의하고 Request API로 프레임 단위 동기화합니다. Rockchip RK3588(rkvdec-av1)과 MediaTek MT8195가 첫 하드웨어 지원 SoC입니다V4L2_CTRL_TYPE_AV1_*, V4L2_PIX_FMT_AV1_FRAME
Intel IPU7 (staging)커널 6.17+Lunar Lake/Arrow Lake의 3세대 IPU로, CSI-2 DPHY/CPHY C-PHY 통합과 향상된 PSYS 파이프라인을 제공합니다. 초기에는 staging 트리로 병합되어 libcamera 0.6과 함께 검증되고 있습니다drivers/staging/media/ipu7/
Rust VP9 디코더 (RFC)커널 6.18V4L2 stateless VP9 디코더 코어 로직을 Rust로 재작성하여 메모리 안전성(Memory Safety)을 강화합니다. V4L2 서브시스템에서 Rust가 실험적으로 도입된 최초 사례입니다rust/kernel/v4l2/ (RFC)
프레임워크 성숙도: V4L2는 20년 이상 진화하며 수백 개의 드라이버를 지원합니다. 단순 USB 웹캠부터 다중 카메라 자율주행 시스템, 하드웨어 코덱, 산업용 머신 비전까지 동일한 API로 제어할 수 있는 것이 V4L2의 핵심 강점입니다.

주요 지원 하드웨어 및 드라이버

V4L2 서브시스템은 이미지 센서, SoC ISP, 하드웨어 코덱, USB 카메라, PCIe 캡처 카드, HDMI 브릿지, TV 튜너 등 폭넓은 하드웨어를 지원합니다. 이 섹션에서는 메인라인 커널에 포함된 주요 드라이버를 카테고리별로 정리합니다.

이미지 센서 드라이버

V4L2 I2C 서브디바이스 드라이버로 구현된 주요 이미지 센서입니다. 대부분 drivers/media/i2c/ 경로에 위치합니다:

제조사센서해상도인터페이스커널 드라이버주요 특징
SonyIMX2198MP (3280×2464)CSI-2 2-laneimx219.cRaspberry Pi Camera v2, 광각 센서
IMX290 / IMX3272MP (1920×1080)CSI-2 2/4-laneimx290.c저조도(Starlight) 성능, 보안 카메라
IMX3355MP (2592×1944)CSI-2 2/4-laneimx335.cHDR 지원, 보안/산업용
IMX2961.6MP (1456×1088)CSI-2 1-laneimx296.c글로벌 셔터(Global Shutter), 산업용
IMX41212.3MP (4056×3040)CSI-2 2/4-laneimx412.c고해상도 산업/의료용
OmniVisionOV56405MP (2592×1944)CSI-2 / DVPov5640.c오토포커스(AF), JPEG 출력 지원, 다용도
OV56475MP (2592×1944)CSI-2 2-laneov5647.cRaspberry Pi Camera v1
OV56935MP (2592×1944)CSI-2 2-laneov5693.cIntel 노트북 카메라, 소형 모듈
OV26802MP (1600×1200)CSI-2 1-laneov2680.c저전력 저가 센서
OV92821MP (1280×800)CSI-2 2-laneov9282.c글로벌 셔터, 스테레오 비전/드론
SamsungS5K6A32MP (1392×1392)CSI-2s5k6a3.cExynos SoC 내장 카메라
ON SemiAR05215MP (2592×1944)CSI-2 2/4-lanear0521.c산업용 머신 비전, 넓은 동적 범위
HynixHI8468MP (3264×2448)CSI-2 2/4-lanehi846.c노트북/태블릿 전면 카메라
GalaxyCoreGC21452MP (1600×1200)CSI-2 / DVPgc2145.c저가 임베디드, RGB/YUV 출력
센서 드라이버 목록: 전체 센서 드라이버는 drivers/media/i2c/ 디렉터리에서 확인할 수 있습니다. 최근 메인라인에는 100개를 넘는 센서 드라이버가 포함되어 있습니다. ls drivers/media/i2c/imx*.c drivers/media/i2c/ov*.c로 주요 센서를 확인하세요.

SoC 카메라/ISP 플랫폼 드라이버

SoC에 내장된 카메라 인터페이스 및 ISP(Image Signal Processor) 드라이버입니다. 대부분 drivers/media/platform/ 하위에 위치합니다:

벤더SoC드라이버ISP 기능커널 소스 경로libcamera
RockchipRK3399 / RK3588rkisp1ISP 전체 (디모자이크, AWB, AE, 감마, NR)drivers/media/platform/rockchip/rkisp1/지원
QualcommSDM845 / SM8250 / SM8450camssCSI-2 수신, VFE(ISP), CSID 라우팅drivers/media/platform/qcom/camss/지원
SamsungExynos 4/5exynos4-isFIMC ISP, FIMC-LITE 캡처, CSIS CSI-2drivers/media/platform/samsung/exynos4-is/
NXPi.MX8MPimx8-isi스케일러, CSC, 크롭, 디인터레이스drivers/media/platform/nxp/imx8-isi/
NXPi.MX7/i.MX6ULLimx7-media-csi기본 CSI 캡처 (ISP 없음)drivers/media/platform/nxp/
AllwinnerA64 / H6sun6i-csi기본 CSI/패러럴 캡처drivers/media/platform/sunxi/sun6i-csi/
TIAM65xcalCSI-2 캡처, 픽셀 처리drivers/media/platform/ti/cal/
TIJ721E / AM62Aj721e-csi2rxCSI-2 RX + DMA 캡처drivers/media/platform/ti/
IntelSkylake~Coffee Lakeipu3-cio2CSI-2 수신 (ISP는 독립 펌웨어(Firmware))drivers/media/pci/intel/ipu3/지원
IntelTiger Lake+ipu6CSI-2 수신, ISYS/PSYS 분리drivers/media/pci/intel/ipu6/지원
ARMMali-C55 ISPmali-c55풀 ISP (3A, NR, 디모자이크, 톤매핑)drivers/media/platform/arm/mali-c55/지원
RenesasR-Car H3/M3/E3rcar-vinCSI-2 캡처, 아날로그 비디오 입력drivers/media/platform/renesas/rcar-vin/
STMicroSTM32MP1stm32-dcmi패러럴/CSI-2 캡처 (ISP 없음)drivers/media/platform/st/stm32/

하드웨어 비디오 코덱 드라이버

하드웨어 비디오 인코더/디코더는 V4L2 M2M(Memory-to-Memory) 프레임워크를 사용합니다. 상세 동작은 V4L2 M2M 섹션을 참조하세요. 아래는 주요 코덱 드라이버의 코덱 지원 매트릭스입니다:

드라이버벤더/SoC유형H.264HEVCVP8VP9AV1MPEG-2
hantroRockchip / NXP (Verisilicon)StatelessDDDDD
rkvdecRockchip RK3399/RK3588StatelessDDD
cedrusAllwinner H5/H6/A64StatelessDDDD
venusQualcomm SDM845/SM8250StatefulE/DE/DE/DD
mtk-vcodecMediaTek MT8173/MT8192StatefulE/DE/DD
s5p-mfcSamsung ExynosStatefulE/DD
wave5Chips&Media (다중 SoC)StatefulE/DE/D
visl가상 (테스트용)StatelessDDDDDD

E = 인코딩, D = 디코딩, E/D = 인코딩+디코딩 모두 지원

USB 비디오 디바이스

UVC(USB Video Class) 호환 디바이스는 커널의 uvcvideo 드라이버 하나로 모두 지원됩니다. 별도 드라이버 설치가 필요 없으며, 장치를 연결하면 자동으로 /dev/videoN이 생성됩니다. 상세 UVC 아키텍처는 UVC 섹션을 참조하세요.

카테고리대표 제품드라이버특이사항
웹캠Logitech C920/C930e/BRIO, Microsoft LifeCam, Razer KiyouvcvideoMJPEG/YUYV/H.264(BRIO), UVC 1.0~1.5
산업용 카메라e-con Systems, FLIR/Teledyne Blackfly S (UVC 모델)uvcvideo글로벌 셔터, 다양한 해상도/프레임레이트
문서 카메라IPEVO, HoverCamuvcvideo고해상도 문서 촬영, 오토포커스
USB 캡처 장치Elgato Cam Link 4K, AVerMedia Live GameruvcvideoHDMI → USB 변환, UVC로 노출
회의 카메라Jabra PanaCast, Poly Studiouvcvideo파노라마, AI 프레이밍(디바이스 내장)
UVC 호환성: 대부분의 USB 웹캠은 UVC 표준을 따르므로 별도 드라이버가 필요하지 않습니다. lsusb -v | grep "bInterfaceClass.*Video"로 UVC 지원 여부를 확인할 수 있습니다. 비표준 카메라는 uvcvideo의 quirks 테이블에 등록하여 대응합니다.

PCIe 캡처 카드

PCIe 기반 비디오 캡처 카드는 아날로그/디지털 비디오 입력을 처리합니다:

칩셋드라이버입력커널 소스 경로비고
Conexant CX23885cx23885아날로그 + DVB-T/Sdrivers/media/pci/cx23885/PCI Express 하이브리드 TV 캡처
Conexant CX88cx88아날로그 TV/FMdrivers/media/pci/cx88/레거시 PCI 아날로그 캡처
Techwell TW686xtw686x4/8채널 아날로그drivers/media/pci/tw686x/다채널 보안 카메라(DVR) 캡처
Conexant CX231xxcx231xx아날로그 + DVBdrivers/media/usb/cx231xx/USB 브릿지 기반 캡처
Philips SAA7134saa7134아날로그 TVdrivers/media/pci/saa7134/레거시 PCI 아날로그/DVB
Intel DT3155dt3155아날로그 (NTSC/PAL)drivers/media/pci/dt3155/산업용 프레임 그래버(Frame Grabber)
메인라인 미포함: Blackmagic DeckLink, Magewell Pro Capture 등 일부 전문 캡처 카드는 벤더가 독립적으로 드라이버를 배포하며 메인라인 커널에 포함되지 않습니다. 이러한 카드는 벤더 SDK를 통해 V4L2 호환 인터페이스를 제공하는 경우가 있습니다.

HDMI/비디오 브릿지 IC 드라이버

HDMI 수신기, 아날로그-디지털 변환기, GMSL 디시리얼라이저(Deserializer) 등 비디오 브릿지 IC는 V4L2 서브디바이스로 구현됩니다:

기능출력 인터페이스커널 드라이버활용 사례
Toshiba TC358743HDMI → CSI-2 변환CSI-2 2/4-lanetc358743.cHDMI 캡처 to SBC (Raspberry Pi 등)
ADV7604 / ADV7611 / ADV7612HDMI 수신 + 디지털화CSI-2 / 패러럴adv7604.c방송/전문 HDMI 캡처
ADV7180 / ADV7182아날로그(CVBS/S-Video) → 디지털패러럴 BT.656adv7180.c레거시 아날로그 카메라 입력
TVP5150 / TVP5151아날로그(CVBS) → 디지털패러럴 BT.656tvp5150.c임베디드 보드 아날로그 입력
MAX96712GMSL2 4채널 디시리얼라이저CSI-2 4-lanemax96712.c자율주행 멀티카메라 (4카메라 → 1 CSI)
MAX9286GMSL1 4채널 디시리얼라이저CSI-2 4-lanemax9286.c자동차 서라운드 뷰 시스템
DS90UB960FPD-Link III 4채널 디시리얼라이저CSI-2ds90ub960.cTI 자동차 비전 시스템

TV 튜너 및 SDR

V4L2는 역사적으로 TV 튜너 지원에서 시작되었습니다. 아날로그/디지털 TV와 SDR(Software Defined Radio)을 포함합니다:

카테고리칩셋/드라이버디바이스 노드설명
아날로그 TVsaa7134, cx88, bttv/dev/videoN, /dev/vbiNNTSC/PAL 아날로그 TV 캡처, 텔레텍스트(VBI)
디지털 TV (DVB)em28xx, cx231xx, cx23885/dev/dvb/adapterN/DVB-T/T2/S/S2/C 디지털 TV 수신
FM 라디오si4713, tea5767, radio-si476x/dev/radioNFM 라디오 수신/송신
SDR 수신기rtl2832_sdr/dev/swradioNRTL-SDR 기반 소프트웨어 정의 라디오
SDR 송수신기airspy/dev/swradioNAirSpy 광대역 SDR

특수 V4L2 디바이스

일반적인 카메라/코덱 외에도 V4L2 프레임워크를 활용하는 특수 디바이스가 있습니다:

디바이스드라이버디바이스 노드설명
V4L2 테스트 드라이버vivid/dev/videoN가상 캡처/출력/M2M 디바이스. 테스트 패턴 생성, 다양한 포맷/해상도 시뮬레이션
Virtual Media Controllervimc/dev/videoN, /dev/mediaN가상 카메라 파이프라인. 센서→디베이어→스케일러→캡처 토폴로지 시뮬레이션
Virtual Stateless 코덱visl/dev/videoNStateless 코덱 API 테스트용 가상 디바이스
터치 센서sur40/dev/v4l-touchNSamsung SUR40 터치 테이블, 적외선 터치 프레임 캡처
HDMI CECcec-core/dev/cecNHDMI CEC 프로토콜 제어 (전원 on/off, 볼륨 등)
메타데이터 캡처플랫폼별/dev/v4l-metaN센서 통계 데이터(3A 히스토그램), 임베디드 데이터 캡처
개발 필수 도구: vividvimc는 V4L2 드라이버 개발 시 실제 하드웨어 없이 API를 테스트할 수 있는 필수 도구입니다. modprobe vivid로 로드하면 즉시 가상 V4L2 디바이스가 생성됩니다. v4l2-compliance 개발에도 이 드라이버들이 기준(reference) 구현으로 사용됩니다.

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 구조체

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(전원 관리(Power Management)) 등에 사용됩니다.
  • mdevMedia Controller 디바이스 포인터로, 복잡한 파이프라인(센서 → ISP → DMA)을 가진 드라이버에서 엔터티/링크 토폴로지를 관리합니다. 단순 드라이버는 NULL로 둘 수 있습니다.
  • subdevs이 디바이스에 등록된 모든 v4l2_subdev(센서, ISP 등)의 연결 리스트(Linked List)입니다. 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()가 이 테이블을 조회하여 적절한 핸들러(Handler)를 호출합니다.
  • 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 디스패치(Dispatch) 메커니즘

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의 역할:

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

ioctl 디스패치 내부 동작

V4L2 코어의 ioctl 처리는 v4l2-ioctl.c에 정의된 v4l2_ioctls[] 디스패치 테이블(Dispatch Table)을 기반으로 동작합니다. 각 ioctl 번호(_IOC_NR)를 배열 인덱스로 사용하여 O(1)으로 핸들러를 찾습니다:

/* drivers/media/v4l2-core/v4l2-ioctl.c — 디스패치 경로 */

/* 1. 유저스페이스 ioctl() 호출 → 커널 진입 */
/*    video_device->fops->unlocked_ioctl = video_ioctl2 */

long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg)
{
    /* 2. 유저 메모리 ↔ 커널 버퍼 복사 처리 */
    return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

static long __video_do_ioctl(struct file *file, unsigned int cmd, void *arg)
{
    /* 3. v4l2_ioctls[] 테이블에서 핸들러 검색 */
    const struct v4l2_ioctl_info *info = &v4l2_ioctls[_IOC_NR(cmd)];

    /* 4. valid_ioctls 비트맵 확인 — 드라이버가 구현하지 않은 ioctl 거부 */
    if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls))
        return -ENOTTY;

    /* 5. 핸들러 호출 — info->func가 실제 v4l2_ioctl_ops 콜백으로 전달 */
    return info->func(ops, file, fh, arg);
}

valid_ioctls 비트맵(Bitmap)은 video_register_device() 시점에 드라이버가 제공한 v4l2_ioctl_ops의 콜백 존재 여부를 검사하여 자동으로 설정됩니다. 유저스페이스의 VIDIOC_QUERYCAP이 반환하는 capabilities 플래그도 이 비트맵에 기반합니다.

vb2 상태 머신(State Machine) 구현 상세

videobuf2는 버퍼와 큐 각각에 독립적인 상태 머신을 유지합니다. 내부 구현의 핵심은 queued_list에서 드라이버로 버퍼를 전달하는 __enqueue_in_driver() 호출입니다:

버퍼 상태의미전이 조건
VB2_BUF_STATE_DEQUEUED유저스페이스 소유QBUF → PREPARING
VB2_BUF_STATE_PREPARINGPREPARE_BUF 처리 중준비 완료 → PREPARED
VB2_BUF_STATE_PREPARED준비 완료, 큐 대기QBUF → QUEUED
VB2_BUF_STATE_QUEUEDvb2 queued_list에 등록__enqueue_in_driver() → ACTIVE
VB2_BUF_STATE_ACTIVE드라이버/하드웨어 처리 중vb2_buffer_done() → DONE 또는 ERROR
VB2_BUF_STATE_DONE처리 완료, DQBUF 대기DQBUF → DEQUEUED
VB2_BUF_STATE_ERROR에러 발생DQBUF → DEQUEUED (V4L2_BUF_FLAG_ERROR 설정)

큐 수준의 상태 전이는 다음과 같습니다:

큐 상태전이관련 함수
미초기화VIDIOC_REQBUFS → 초기화됨vb2_reqbufs()
초기화됨VIDIOC_STREAMON → 스트리밍vb2_streamon()vb2_start_streaming()
스트리밍VIDIOC_STREAMOFF → 중지vb2_streamoff()stop_streaming() 콜백
중지VIDIOC_REQBUFS(0) → 미초기화모든 버퍼 해제
구현 핵심: vb2_start_streaming()queued_list에 있는 모든 버퍼를 buf_queue 콜백을 통해 드라이버에 전달합니다. 드라이버의 start_streaming() 콜백이 실패하면, 이미 전달된 버퍼를 모두 VB2_BUF_STATE_QUEUED로 복원해야 합니다.

Media Pipeline 유효성 검증

media_pipeline_start()는 스트리밍 시작 전에 파이프라인 전체의 유효성을 검증합니다. 비디오 노드에서 시작하여 그래프를 역방향으로 탐색하며 모든 엔터티(Entity)의 링크 상태를 확인합니다:

/* 스트리밍 시작 시 파이프라인 검증 패턴 */
static int my_start_streaming(struct vb2_queue *q, unsigned int count)
{
    struct my_device *dev = vb2_get_drv_priv(q);
    int ret;

    /* 파이프라인 시작 — 그래프 탐색 + 유효성 검증 */
    ret = media_pipeline_start(&dev->vdev.entity, &dev->pipe);
    if (ret) {
        /* 링크 비활성화 또는 포맷 불일치 시 -EPIPE 반환 */
        dev_err(dev->dev, "pipeline validation failed: %d\n", ret);
        return ret;
    }

    /* 파이프라인의 모든 서브디바이스에 s_stream(1) 전파 */
    ret = v4l2_subdev_call(dev->sensor, video, s_stream, 1);
    if (ret) {
        media_pipeline_stop(&dev->vdev.entity);
        return ret;
    }
    return 0;
}

static void my_stop_streaming(struct vb2_queue *q)
{
    struct my_device *dev = vb2_get_drv_priv(q);

    v4l2_subdev_call(dev->sensor, video, s_stream, 0);
    media_pipeline_stop(&dev->vdev.entity);
    /* 모든 버퍼 반환 — 필수! */
    return_all_buffers(dev, VB2_BUF_STATE_ERROR);
}

media_pipeline_start() 내부에서는 streaming_count를 증가시켜 파이프라인 활성 중 토폴로지 변경(MEDIA_IOC_SETUP_LINK)을 방지합니다. 이미 스트리밍 중인 엔터티가 있으면 해당 파이프라인에 합류(join)합니다.

서브디바이스 상태 관리와 포맷 전파

커널 6.3부터 도입된 v4l2_subdev_state는 서브디바이스의 per-pad 포맷과 셀렉션(Selection) 정보를 체계적으로 관리합니다. active state와 try state를 분리하여 하드웨어에 영향을 주지 않고 포맷을 검증할 수 있습니다:

상태 종류용도접근 방법
Active state현재 하드웨어에 적용된 실제 설정V4L2_SUBDEV_FORMAT_ACTIVE
Try state포맷 검증용 임시 상태 (하드웨어 미반영)V4L2_SUBDEV_FORMAT_TRY
/* 서브디바이스 set_fmt 구현 패턴 (active state 사용) */
static int my_subdev_set_fmt(struct v4l2_subdev *sd,
                              struct v4l2_subdev_state *state,
                              struct v4l2_subdev_format *fmt)
{
    struct v4l2_mbus_framefmt *framefmt;

    /* state에서 해당 패드의 포맷 슬롯 참조 */
    framefmt = v4l2_subdev_state_get_format(state, fmt->pad);

    /* 하드웨어 제약 조건에 맞게 조정 */
    fmt->format.width = clamp(fmt->format.width, 64U, 3840U);
    fmt->format.height = clamp(fmt->format.height, 64U, 2160U);

    /* state에 저장 (active면 하드웨어 반영, try면 검증만) */
    *framefmt = fmt->format;

    /* source 패드라면 연결된 sink 패드로 포맷 전파 */
    if (fmt->pad == MY_PAD_SOURCE) {
        struct v4l2_mbus_framefmt *sink_fmt;
        sink_fmt = v4l2_subdev_state_get_format(state, MY_PAD_SINK);
        /* sink 패드 포맷도 함께 업데이트 */
        sink_fmt->width = fmt->format.width;
        sink_fmt->height = fmt->format.height;
    }

    return 0;
}

/* 동시성 제어 — state 락 사용 */
struct v4l2_subdev_state *state;
state = v4l2_subdev_lock_and_get_active_state(sd);
/* ... state 접근 ... */
v4l2_subdev_unlock_state(state);

포맷 전파(Format Propagation)는 파이프라인에서 source 패드의 포맷 변경이 연결된 다음 엔터티의 sink 패드로 자동 전파되는 메커니즘입니다. media-ctl로 포맷을 설정할 때 센서 출력 → CSI-2 입력 → CSI-2 출력 → ISP 입력 순서로 포맷을 전파해야 합니다.

videobuf2 (vb2) 버퍼 관리

videobuf2(vb2)는 V4L2의 핵심 버퍼 관리 프레임워크입니다. DMA 버퍼 할당, 유저스페이스 매핑, 스트리밍 큐 관리를 담당합니다. 드라이버는 직접 버퍼를 관리할 필요 없이 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로 반환 시 에러 플래그 포함

버퍼 큐 상태 전이 사이클

실제 스트리밍에서 버퍼는 유저스페이스와 드라이버 사이를 순환합니다. VIDIOC_QBUF로 제출된 버퍼는 하드웨어 DMA가 완료되면 VIDIOC_DQBUF로 반환되고, 다시 큐에 제출되는 사이클을 반복합니다:

DEQUEUED 유저스페이스 소유 QUEUED vb2 큐 대기 ACTIVE HW DMA 진행 중 DONE DMA 완료, 반환 대기 VIDIOC_QBUF buf_queue() vb2_buffer_done() VIDIOC_DQBUF 스트리밍 사이클 (반복) buf_prepare()는 QBUF 내부에서 자동 호출 애플리케이션이 프레임 처리 후 다시 QBUF 제출

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 파일 디스크립터(File Descriptor)(fd)로 내보냅니다. 이 fd를 다른 디바이스(GPU, 디스플레이)에 전달하면 물리 메모리(Physical Memory) 복사 없이 제로카피(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 뮤텍스(Mutex)를 자동으로 해제/획득합니다.

각 콜백의 호출 시점:

콜백호출 시점주요 역할
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개 (권장) — 안정적 스트리밍. 유저스페이스 처리 지연(Latency)에 대한 여유
  • 8개 이상 — 긴 처리 시간이 필요한 경우 (GPU 처리, 네트워크 전송 등)
  • 지연 최소화 — 3개 + non-blocking DQBUF + 가장 최신 프레임만 사용

vb2_mem_ops 상세

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

콜백용도MMAPDMABUF
alloc버퍼 메모리 할당CMA/vmalloc 할당
put메모리 해제할당 해제참조 카운트(Reference Count) 감소
get_dmabufDMA-BUF fd 생성EXPBUF에서 호출
attach_dmabuf외부 DMA-BUF 연결importer 설정
map_dmabufDMA-BUF를 디바이스에 매핑DMA 매핑
unmap_dmabufDMA 매핑 해제언매핑
vaddr커널 가상 주소(Virtual Address) 반환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 등을 사용합니다.

포맷 협상 흐름

유저스페이스는 TRY_FMTS_FMTG_FMT 시퀀스를 통해 드라이버와 포맷을 협상합니다. 드라이버는 요청된 값을 하드웨어 능력에 맞게 조정하여 반환합니다:

유저스페이스 드라이버 VIDIOC_TRY_FMT 1920x1080 YUYV try_fmt_vid_cap() 하드웨어 능력 검증 조정된 값 반환 VIDIOC_S_FMT 조정된 포맷 적용 요청 s_fmt_vid_cap() HW 레지스터 설정 설정 완료 VIDIOC_G_FMT 실제 설정값 확인 g_fmt_vid_cap() 현재 포맷 반환 최종 포맷 최종 확정 REQBUFS 진행 TRY_FMT는 실제 적용 없이 드라이버 조정 결과만 확인 (안전한 사전 검증) S_FMT 반환값이 요청값과 다를 수 있으므로 반드시 G_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버퍼 상태/오프셋(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 */
    ...
};

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

서브디바이스의 패드(입출력(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 (멀티플렉스 라우팅)

최신 커널(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컨트롤 핸들러를 초기화합니다. 두 번째 인자는 예상 컨트롤 수의 힌트로, 내부 해시 테이블(Hash Table) 크기 결정에 사용됩니다. 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메뉴형 컨트롤(열거형(Enum) 선택)을 생성합니다. 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는 복잡한 미디어 파이프라인의 토폴로지를 유저스페이스에 노출합니다. 센서, 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를 사용하여 버퍼와 컨트롤을 원자적으로 묶습니다:

/* 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/패킷(Packet) 스트림프레임/슬라이스 단위
해상도 변경SOURCE_CHANGE 이벤트유저스페이스가 직접 감지
복잡도단순 (API 단순)복잡 (파싱 라이브러리 필요)
지연높음 (내부 버퍼링)낮음 (프레임 단위 제어)
대표 드라이버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 트리오)
대역폭(Bandwidth) (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) 테이블 패턴

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

/* 센서 레지스터 설정 */
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 대역폭 부족해상도/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 호출 추적(Call Trace)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로 병목(Bottleneck) 분석
프레임 드롭(Drop)버퍼 부족, DQBUF 지연, CPU 부하버퍼 수 증가 (4→8), 애플리케이션 처리 시간 단축
프레임 지연 높음버퍼 과다 큐잉, 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의 원자적 파라미터 적용 메커니즘입니다. 하나의 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
코딩 스타일(Coding Style)커널 코딩 스타일 준수scripts/checkpatch.pl
DT 바인딩YAML 형식 DT 바인딩 문서 포함make dt_binding_check
커밋 메시지"media: vendor: description" 형식기존 로그 참고
메일링 리스트linux-media@vger.kernel.org에 패치(Patch) 제출git send-email
MAINTAINERS드라이버 유지보수자 항목 추가scripts/get_maintainer.pl
sparse/smatch정적 분석 경고 없음make C=2 drivers/media/...

잠금(Lock)킹(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 재초기화

커널 6.10~6.18 사이의 V4L2 서브시스템은 (1) Intel IPU 메인라인화, (2) AV1 stateless 디코더 성숙, (3) libcamera 기반 SoftISP 확산, (4) Rust 기반 코덱 재작성의 네 축으로 요약됩니다. 각 항목은 out-of-tree 스택이 mainline으로 수렴하고, 유저스페이스 표준(libcamera, GStreamer)이 V4L2 위에 단일화되는 추세를 반영합니다.

Intel IPU6/IPU7 mainline — 노트북 웹캠의 전환점

과거 Intel Tiger Lake 이후 노트북(MIPI CSI-2 내장 웹캠)은 out-of-tree ipu6-drivers, 독점 icamerasrc(GStreamer 플러그인), 바이너리 IPA 튜닝 파일에 의존해야 했습니다. 이는 보안 업데이트/커널 업그레이드마다 트리를 재빌드해야 하는 운영 부담을 야기했습니다. 2025년 커널 6.10 이후 상황이 변했습니다:

구성 요소과거 (out-of-tree)현재 (mainline 6.10+)비고
CSI-2 수신 (ISYS)ipu6-drivers DKMSdrivers/media/pci/intel/ipu6/ (6.10 병합)MTL/LNL 공용
ISP 파이프라인 (PSYS)펌웨어 + icamerasrcIPU6 PSYS(6.11+) + libcamera SoftISPBayer → sRGB 소프트 처리
3A 튜닝 (AWB/AE/AF)독점 .aiqb 파일libcamera 오픈 IPA (소프트 3A)센서별 tuning.yaml
IPU7 (Lunar Lake 신세대)Intel GitHub 브랜치6.17 staging 병합C-PHY 통합
레거시 앱 호환별도 v4l2loopback 브릿지PipeWire camera + v4l2 노드 자동 노출Zoom/Teams/Discord 투명 지원
운영 팁: Fedora 41+은 ov2740, ov01a10, hi556 센서를 기본 지원하며 PipeWire가 libcamera 파이프라인을 /dev/video*로 노출합니다. 기존 애플리케이션에서 카메라가 보이지 않을 경우 pw-record --target=camera0으로 노드를 확인하고, pipewire-v4l2 shim 활성화를 점검하세요.

AV1 Stateless 디코더 안정화

AV1은 H.264/HEVC보다 훨씬 복잡한 신택스(OBU, Tile Group, Film Grain)를 가지므로, 커뮤니티는 V4L2 Control로 파라미터를 정의하는 Stateless 모델을 채택했습니다. Collabora가 주도한 Rockchip RK3588 rkvdec-av1 드라이버는 1년에 걸친 v10까지의 리뷰 끝에 커널 6.5에 병합되었고, 이후 지속적으로 안정화되고 있습니다:

/* include/uapi/linux/v4l2-controls.h (커널 6.5+) */
#define V4L2_CID_STATELESS_AV1_SEQUENCE         (V4L2_CID_CODEC_STATELESS_BASE + 500)
#define V4L2_CID_STATELESS_AV1_FRAME            (V4L2_CID_CODEC_STATELESS_BASE + 501)
#define V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY (V4L2_CID_CODEC_STATELESS_BASE + 502)
#define V4L2_CID_STATELESS_AV1_FILM_GRAIN       (V4L2_CID_CODEC_STATELESS_BASE + 505)

/* 대응 하드웨어 */
 *  - Rockchip RK3588 (rkvdec-av1, 커널 6.5+, 최대 4K@60 10-bit)
 *  - MediaTek MT8195 (mtk-vcodec AV1, 커널 6.5+)
 *  - Rockchip RK3566/3568 rkvdec2 (커널 6.15~ 준비 중)
 *  - Qualcomm Venus AV1 (커널 6.14+, 일부 SM8550/8650)

유저스페이스에서는 GStreamer 1.28(v4l2av1dec, v4l2slav1dec), FFmpeg 7.1+의 --enable-v4l2-request 빌드, Chromium의 V4L2StatelessVideoDecoderBackend가 이 하드웨어를 활용합니다.

libcamera — V4L2 위의 사실상 표준 카메라 API

libcamera는 V4L2 Raw + Media Controller 그래프를 내부에서 조율하여 애플리케이션에 단일 카메라 API를 제공합니다. 2025년 릴리스 현황:

버전시기주요 변경
0.3.x2024Intel IPU6 Simple pipeline 지원, 소프트 ISP 초기 구현
0.4.x2025 상반기글로벌 configuration 파일 도입(/etc/libcamera/configuration.yaml), V4L2 Request API 통합
0.5.x2025 하반기NXP i.MX8MP Dewarper 지원(리사이즈/회전/크롭/렌즈 왜곡 보정), HDR 메타데이터 파이프라인
0.6.x2025 말~2026Intel IPU7 pipeline handler, SoftISP 성능 개선, Rust 바인딩 확장
플랫폼 조합 가이드:
  • Raspberry Pi 4/5 — libcamera + Unicam(Pi 전용 pipeline handler) + 공식 rpicam-apps
  • Rockchip RK3588 — libcamera + rkisp1 + rkvdec-av1(재생 측) 조합
  • Intel Meteor/Lunar Lake 노트북 — libcamera + IPU6/IPU7 + SoftISP(오픈 IPA)
  • Qualcomm SM8x50 — libcamera + camss(CSI-2/VFE/CSID) — 상용 드라이버 camx 대체

Rust V4L2 — 메모리 안전성 도입

커널 6.18 사이클에서 VP9 stateless 디코더 코어 로직을 Rust로 재작성하는 RFC가 제출되었습니다. V4L2 서브시스템 최초의 Rust 코드이며, 목표는:

이 작업이 병합되면 향후 AV1 Film Grain 합성과 HEVC RExt(Range Extension) 구현도 Rust로 작성될 가능성이 높습니다. Android의 Cuttlefish 에뮬레이터가 초기 검증 타겟입니다.

도구 체인 정비 요약

도구2025-2026 변경실전 영향
v4l2-ctl / v4l2-compliance (v4l-utils 1.28+)AV1 컨트롤 덤프, HDR10 메타데이터 포맷, --stream-count 장시간 안정성 테스트드라이버 업스트림 제출 전 필수 검증
media-ctlStreams API 토폴로지 시각화 개선 (-p --format), active state dump멀티카메라 CSI-2 디버깅 단순화
GStreamer 1.28v4l2av1dec(stateful AV1), v4l2slh265dec(stateless HEVC) 안정화하드웨어 코덱 활용 앱 개발 용이
FFmpeg 7.1+V4L2 Request API 지원 개선, DRM/KMS zero-copy 출력임베디드 플레이어의 CPU 사용률 감소
Chromium/AndroidChromeOS의 V4L2VideoDecoder 기본화, Android 16 HAL이 V4L2 M2M 표준웹/모바일 미디어 재생 경로 단일화
주의 — CSI-2 센서 드라이버 흐름: 2025년부터 linux-media 메인테이너는 신규 센서 드라이버에 대해 (1) Device Tree YAML 바인딩, (2) V4L2_CAP_IO_MC 표기, (3) active state 기반 포맷 관리, (4) v4l2-compliance --stream-count 통과를 병합 조건으로 요구합니다. 구형 스타일 드라이버는 메인라인 진입이 거절되므로, 신규 포팅 시 drivers/media/i2c/imx335.c(현대적 레퍼런스)를 모델로 삼으세요.

참고자료

커널 공식 문서:
외부 참고 자료:
커널 소스 참고 경로:
  • 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

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