V4L2 (Video4Linux2) 서브시스템
V4L2 서브시스템을 카메라 입력부터 코덱 처리와 출력까지 이어지는 미디어 파이프라인(Pipeline) 관점에서 심층 분석합니다. V4L2 ioctl 계약과 상태 머신, videobuf2 큐 모델과 DMA 버퍼(Buffer) 수명주기, Media Controller 그래프와 서브디바이스 링크, 센서·ISP·M2M 코덱 통합, UVC 및 Device Tree 바인딩 패턴, 포맷 협상과 프레임 동기화, gstreamer/v4l2-ctl/media-ctl 기반 검증·디버깅(Debugging) 절차까지 실전 영상 장치 드라이버(Device Driver) 개발에 필요한 핵심을 다룹니다.
핵심 요약
- V4L2 디바이스 모델 —
v4l2_device,video_device,v4l2_subdev3계층 구조로 비디오 하드웨어를 추상화합니다. - 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) → 출력까지의 다단계 처리 체인을 서브디바이스 그래프로 구성합니다.
단계별 이해
- V4L2 프레임워크 구조 파악 —
v4l2_device(브릿지),video_device(캐릭터 디바이스),v4l2_subdev(하위 장치) 계층 구조와 역할을 이해합니다.v4l2-ctl --list-devices로 시스템의 V4L2 장치 목록을 확인하고,/dev/video*노드와의 매핑을 파악합니다. - 캡처 흐름과 버퍼 관리 학습 —
VIDIOC_REQBUFS→VIDIOC_QBUF→VIDIOC_STREAMON→VIDIOC_DQBUFioctl 시퀀스를 따라가며 프레임 캡처 흐름을 이해합니다.videobuf2가 DMA 버퍼를 어떻게 할당하고,
vb2_ops콜백(Callback)이 드라이버와 어떻게 연동되는지 코드 수준에서 추적합니다. - 포맷 협상과 컨트롤 실습 —
v4l2-ctl --set-fmt-video로 해상도/픽셀 포맷을 설정하고,--set-ctrl로 밝기/노출 파라미터를 조작합니다.드라이버의
.vidioc_s_fmt_vid_cap과v4l2_ctrl_handler가 요청을 처리하는 내부 경로를 분석합니다. - Media Controller와 파이프라인 구성 —
media-ctl -p로 하드웨어 토폴로지를 확인하고, 센서→ISP→출력 경로를 수동으로 링크 설정합니다.SoC 카메라 플랫폼에서 서브디바이스 간 포맷 전파(format propagation)와 패드(pad) 협상 과정을 실습합니다.
- V4L2 드라이버 개발과 디버깅 — 간단한 V4L2 드라이버를 작성하여
video_register_device()로 등록하고,v4l2-compliance로 API 준수를 검증합니다.CONFIG_VIDEO_ADV_DEBUG와 dynamic debug(dyndbg)를 활용하여 드라이버 동작을 추적하고 문제를 진단합니다.
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_device/video_device/v4l2_subdev계층 구조 - 버퍼 관리 — videobuf2(vb2) 프레임워크가 DMA 버퍼 할당/매핑/큐잉을 통합 처리
- 미디어 토폴로지 — Media Controller가 하드웨어 블록 간 연결 그래프를 유저스페이스에 노출
- 컨트롤 프레임워크 — 밝기/대비/노출 등 파라미터를 타입 안전(Type-safe)하게 관리
- 포맷 협상(Format Negotiation) — FourCC 기반 픽셀 포맷과 미디어 버스(Bus) 코드로 데이터 형식 합의
V4L2 진화 이력
| 커널 버전 | 주요 변화 |
|---|---|
| 2.1 | Video4Linux(V4L) 최초 도입 — 단순 캡처 API |
| 2.5 | V4L2 전면 재설계 — ioctl 기반 인터페이스, 멀티플레인(Multi-plane) 미지원 |
| 2.6.38 | videobuf2 도입 — videobuf 대체, DMA-BUF 지원 기반 마련 |
| 3.3 | Media Controller API 안정화 — 파이프라인 토폴로지 표현 |
| 3.14 | 멀티플레인 API 확산 — V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE |
| 4.11 | Request API 초기 작업 — stateless 코덱 지원 시작 |
| 5.3 | Request API 안정화 — MEDIA_IOC_REQUEST_ALLOC |
| 6.1 | Streams API 도입 — 멀티플렉스(Multiplex) 라우팅(Routing) |
| 6.3 | v4l2_subdev active state — 서브디바이스 상태 관리 개선 |
| 6.5 | AV1 Stateless 디코더 uAPI 안정화 — Rockchip RK3588(rkvdec-av1), MediaTek MT8195 지원 |
| 6.8 | vb2 삭제 가능 버퍼 — VIDIOC_REMOVE_BUFS 지원 |
| 6.10 | Intel IPU6 ISYS 메인라인 병합 — MTL/LNL MIPI CSI-2 수신기 지원 (out-of-tree 탈피) |
| 6.11 | Intel IPU6 PSYS 초기 지원, uvcvideo 확장 — UVC 1.5 H.265 페이로드(Payload) |
| 6.13~6.14 | V4L2_CAP_IO_MC 정비, visl 가상 드라이버 테스트 범위 확대, MediaTek/Qualcomm 코덱 드라이버 성능 개선 |
| 6.15 | Rockchip rkvdec2 신규 드라이버 준비, NXP i.MX8MP Dewarper 서브디바이스 병합, HDR10 메타데이터 컨트롤 확장 |
| 6.17 | Intel 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 포맷 확장 |
icamerasrc, ipu3-aiqb 바이너리 IPA 등)은 점진적으로 libcamera SoftISP와 community IPA로 대체되고 있습니다.
디바이스 유형
V4L2는 여러 유형의 디바이스 노드(Device Node)를 생성합니다:
| 디바이스 노드 | 매크로(Macro) | 용도 | 예시 |
|---|---|---|---|
/dev/videoN | VFL_TYPE_VIDEO | 비디오 캡처/출력 디바이스 | 웹캠 캡처, M2M 코덱 |
/dev/vbiN | VFL_TYPE_VBI | Vertical Blanking Interval (텔레텍스트 등) | 자막, 텔레텍스트(Teletext) |
/dev/radioN | VFL_TYPE_RADIO | 라디오 튜너 | FM 라디오 |
/dev/swradioN | VFL_TYPE_SDR | Software Defined Radio | SDR 수신기 |
/dev/v4l-touchN | VFL_TYPE_TOUCH | 터치 디바이스 | 터치 센서(Sensor) 프레임 |
/dev/v4l-subdevN | VFL_TYPE_SUBDEV | 서브디바이스 직접 접근 | 센서/ISP 개별 제어 |
/dev/mediaN | — | Media Controller 디바이스 | 토폴로지 조회/링크 설정 |
/dev/v4l-metaN | VFL_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.h | V4L2 UAPI 헤더 (ioctl 정의) | ioctl 번호, 구조체(Struct), 상수 정의 |
V4L2와 DRM/KMS 비교
V4L2와 DRM(Direct Rendering Manager)/KMS(Kernel Mode Setting)는 모두 미디어 데이터를 다루지만 역할이 다릅니다:
| 항목 | V4L2 | DRM/KMS |
|---|---|---|
| 주요 역할 | 비디오 캡처/인코딩/디코딩 | 비디오 출력/디스플레이 |
| 데이터 방향 | 카메라 → 메모리 (캡처) | 메모리 → 디스플레이 (출력) |
| 디바이스 노드 | /dev/videoN | /dev/dri/cardN |
| 버퍼 공유 | DMA-BUF exporter/importer | DMA-BUF exporter/importer |
| 파이프라인 표현 | Media Controller | CRTC/Encoder/Connector 체인(Chain) |
| 제로카피(Zero-copy) 연동 | DMA-BUF fd로 V4L2 캡처 → DRM 디스플레이 직접 전달 가능 | |
V4L2 핵심 기능 특징
V4L2는 단순한 웹캠 API를 넘어, 임베디드·자동차·산업용 영상 시스템을 위한 완전한 미디어 프레임워크입니다. 다음은 V4L2가 제공하는 핵심 기능과 각 기능이 해결하는 문제를 정리한 것입니다:
| 기능 | 도입 시기 | 설명 | 관련 API / 개념 |
|---|---|---|---|
| 제로카피(Zero-copy) 파이프라인 | 커널 3.x | DMA-BUF fd를 공유하여 카메라 캡처 → GPU 렌더링 → 디스플레이 출력까지 메모리 복사 없이 전달합니다 | V4L2_MEMORY_DMABUF, dma_buf_fd |
| 멀티플레인(Multi-plane) API | 커널 3.14 | Y/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.3 | ISP, 센서, 스케일러 등 하드웨어 블록 간 연결을 유저스페이스에 그래프로 노출합니다. 런타임에 파이프라인 경로를 변경할 수 있습니다 | /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.x | BT.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.18 | V4L2 stateless VP9 디코더 코어 로직을 Rust로 재작성하여 메모리 안전성(Memory Safety)을 강화합니다. V4L2 서브시스템에서 Rust가 실험적으로 도입된 최초 사례입니다 | rust/kernel/v4l2/ (RFC) |
주요 지원 하드웨어 및 드라이버
V4L2 서브시스템은 이미지 센서, SoC ISP, 하드웨어 코덱, USB 카메라, PCIe 캡처 카드, HDMI 브릿지, TV 튜너 등 폭넓은 하드웨어를 지원합니다. 이 섹션에서는 메인라인 커널에 포함된 주요 드라이버를 카테고리별로 정리합니다.
이미지 센서 드라이버
V4L2 I2C 서브디바이스 드라이버로 구현된 주요 이미지 센서입니다. 대부분 drivers/media/i2c/ 경로에 위치합니다:
| 제조사 | 센서 | 해상도 | 인터페이스 | 커널 드라이버 | 주요 특징 |
|---|---|---|---|---|---|
| Sony | IMX219 | 8MP (3280×2464) | CSI-2 2-lane | imx219.c | Raspberry Pi Camera v2, 광각 센서 |
| IMX290 / IMX327 | 2MP (1920×1080) | CSI-2 2/4-lane | imx290.c | 저조도(Starlight) 성능, 보안 카메라 | |
| IMX335 | 5MP (2592×1944) | CSI-2 2/4-lane | imx335.c | HDR 지원, 보안/산업용 | |
| IMX296 | 1.6MP (1456×1088) | CSI-2 1-lane | imx296.c | 글로벌 셔터(Global Shutter), 산업용 | |
| IMX412 | 12.3MP (4056×3040) | CSI-2 2/4-lane | imx412.c | 고해상도 산업/의료용 | |
| OmniVision | OV5640 | 5MP (2592×1944) | CSI-2 / DVP | ov5640.c | 오토포커스(AF), JPEG 출력 지원, 다용도 |
| OV5647 | 5MP (2592×1944) | CSI-2 2-lane | ov5647.c | Raspberry Pi Camera v1 | |
| OV5693 | 5MP (2592×1944) | CSI-2 2-lane | ov5693.c | Intel 노트북 카메라, 소형 모듈 | |
| OV2680 | 2MP (1600×1200) | CSI-2 1-lane | ov2680.c | 저전력 저가 센서 | |
| OV9282 | 1MP (1280×800) | CSI-2 2-lane | ov9282.c | 글로벌 셔터, 스테레오 비전/드론 | |
| Samsung | S5K6A3 | 2MP (1392×1392) | CSI-2 | s5k6a3.c | Exynos SoC 내장 카메라 |
| ON Semi | AR0521 | 5MP (2592×1944) | CSI-2 2/4-lane | ar0521.c | 산업용 머신 비전, 넓은 동적 범위 |
| Hynix | HI846 | 8MP (3264×2448) | CSI-2 2/4-lane | hi846.c | 노트북/태블릿 전면 카메라 |
| GalaxyCore | GC2145 | 2MP (1600×1200) | CSI-2 / DVP | gc2145.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 |
|---|---|---|---|---|---|
| Rockchip | RK3399 / RK3588 | rkisp1 | ISP 전체 (디모자이크, AWB, AE, 감마, NR) | drivers/media/platform/rockchip/rkisp1/ | 지원 |
| Qualcomm | SDM845 / SM8250 / SM8450 | camss | CSI-2 수신, VFE(ISP), CSID 라우팅 | drivers/media/platform/qcom/camss/ | 지원 |
| Samsung | Exynos 4/5 | exynos4-is | FIMC ISP, FIMC-LITE 캡처, CSIS CSI-2 | drivers/media/platform/samsung/exynos4-is/ | — |
| NXP | i.MX8MP | imx8-isi | 스케일러, CSC, 크롭, 디인터레이스 | drivers/media/platform/nxp/imx8-isi/ | — |
| NXP | i.MX7/i.MX6ULL | imx7-media-csi | 기본 CSI 캡처 (ISP 없음) | drivers/media/platform/nxp/ | — |
| Allwinner | A64 / H6 | sun6i-csi | 기본 CSI/패러럴 캡처 | drivers/media/platform/sunxi/sun6i-csi/ | — |
| TI | AM65x | cal | CSI-2 캡처, 픽셀 처리 | drivers/media/platform/ti/cal/ | — |
| TI | J721E / AM62A | j721e-csi2rx | CSI-2 RX + DMA 캡처 | drivers/media/platform/ti/ | — |
| Intel | Skylake~Coffee Lake | ipu3-cio2 | CSI-2 수신 (ISP는 독립 펌웨어(Firmware)) | drivers/media/pci/intel/ipu3/ | 지원 |
| Intel | Tiger Lake+ | ipu6 | CSI-2 수신, ISYS/PSYS 분리 | drivers/media/pci/intel/ipu6/ | 지원 |
| ARM | Mali-C55 ISP | mali-c55 | 풀 ISP (3A, NR, 디모자이크, 톤매핑) | drivers/media/platform/arm/mali-c55/ | 지원 |
| Renesas | R-Car H3/M3/E3 | rcar-vin | CSI-2 캡처, 아날로그 비디오 입력 | drivers/media/platform/renesas/rcar-vin/ | — |
| STMicro | STM32MP1 | stm32-dcmi | 패러럴/CSI-2 캡처 (ISP 없음) | drivers/media/platform/st/stm32/ | — |
하드웨어 비디오 코덱 드라이버
하드웨어 비디오 인코더/디코더는 V4L2 M2M(Memory-to-Memory) 프레임워크를 사용합니다. 상세 동작은 V4L2 M2M 섹션을 참조하세요. 아래는 주요 코덱 드라이버의 코덱 지원 매트릭스입니다:
| 드라이버 | 벤더/SoC | 유형 | H.264 | HEVC | VP8 | VP9 | AV1 | MPEG-2 |
|---|---|---|---|---|---|---|---|---|
hantro | Rockchip / NXP (Verisilicon) | Stateless | D | — | D | D | D | D |
rkvdec | Rockchip RK3399/RK3588 | Stateless | D | D | — | D | — | — |
cedrus | Allwinner H5/H6/A64 | Stateless | D | D | D | — | — | D |
venus | Qualcomm SDM845/SM8250 | Stateful | E/D | E/D | E/D | D | — | — |
mtk-vcodec | MediaTek MT8173/MT8192 | Stateful | E/D | — | E/D | D | — | — |
s5p-mfc | Samsung Exynos | Stateful | E/D | — | — | — | — | D |
wave5 | Chips&Media (다중 SoC) | Stateful | E/D | E/D | — | — | — | — |
visl | 가상 (테스트용) | Stateless | D | D | D | D | D | D |
E = 인코딩, D = 디코딩, E/D = 인코딩+디코딩 모두 지원
USB 비디오 디바이스
UVC(USB Video Class) 호환 디바이스는 커널의 uvcvideo 드라이버 하나로 모두 지원됩니다. 별도 드라이버 설치가 필요 없으며, 장치를 연결하면 자동으로 /dev/videoN이 생성됩니다. 상세 UVC 아키텍처는 UVC 섹션을 참조하세요.
| 카테고리 | 대표 제품 | 드라이버 | 특이사항 |
|---|---|---|---|
| 웹캠 | Logitech C920/C930e/BRIO, Microsoft LifeCam, Razer Kiyo | uvcvideo | MJPEG/YUYV/H.264(BRIO), UVC 1.0~1.5 |
| 산업용 카메라 | e-con Systems, FLIR/Teledyne Blackfly S (UVC 모델) | uvcvideo | 글로벌 셔터, 다양한 해상도/프레임레이트 |
| 문서 카메라 | IPEVO, HoverCam | uvcvideo | 고해상도 문서 촬영, 오토포커스 |
| USB 캡처 장치 | Elgato Cam Link 4K, AVerMedia Live Gamer | uvcvideo | HDMI → USB 변환, UVC로 노출 |
| 회의 카메라 | Jabra PanaCast, Poly Studio | uvcvideo | 파노라마, AI 프레이밍(디바이스 내장) |
lsusb -v | grep "bInterfaceClass.*Video"로 UVC 지원 여부를 확인할 수 있습니다. 비표준 카메라는 uvcvideo의 quirks 테이블에 등록하여 대응합니다.
PCIe 캡처 카드
PCIe 기반 비디오 캡처 카드는 아날로그/디지털 비디오 입력을 처리합니다:
| 칩셋 | 드라이버 | 입력 | 커널 소스 경로 | 비고 |
|---|---|---|---|---|
| Conexant CX23885 | cx23885 | 아날로그 + DVB-T/S | drivers/media/pci/cx23885/ | PCI Express 하이브리드 TV 캡처 |
| Conexant CX88 | cx88 | 아날로그 TV/FM | drivers/media/pci/cx88/ | 레거시 PCI 아날로그 캡처 |
| Techwell TW686x | tw686x | 4/8채널 아날로그 | drivers/media/pci/tw686x/ | 다채널 보안 카메라(DVR) 캡처 |
| Conexant CX231xx | cx231xx | 아날로그 + DVB | drivers/media/usb/cx231xx/ | USB 브릿지 기반 캡처 |
| Philips SAA7134 | saa7134 | 아날로그 TV | drivers/media/pci/saa7134/ | 레거시 PCI 아날로그/DVB |
| Intel DT3155 | dt3155 | 아날로그 (NTSC/PAL) | drivers/media/pci/dt3155/ | 산업용 프레임 그래버(Frame Grabber) |
HDMI/비디오 브릿지 IC 드라이버
HDMI 수신기, 아날로그-디지털 변환기, GMSL 디시리얼라이저(Deserializer) 등 비디오 브릿지 IC는 V4L2 서브디바이스로 구현됩니다:
| 칩 | 기능 | 출력 인터페이스 | 커널 드라이버 | 활용 사례 |
|---|---|---|---|---|
| Toshiba TC358743 | HDMI → CSI-2 변환 | CSI-2 2/4-lane | tc358743.c | HDMI 캡처 to SBC (Raspberry Pi 등) |
| ADV7604 / ADV7611 / ADV7612 | HDMI 수신 + 디지털화 | CSI-2 / 패러럴 | adv7604.c | 방송/전문 HDMI 캡처 |
| ADV7180 / ADV7182 | 아날로그(CVBS/S-Video) → 디지털 | 패러럴 BT.656 | adv7180.c | 레거시 아날로그 카메라 입력 |
| TVP5150 / TVP5151 | 아날로그(CVBS) → 디지털 | 패러럴 BT.656 | tvp5150.c | 임베디드 보드 아날로그 입력 |
| MAX96712 | GMSL2 4채널 디시리얼라이저 | CSI-2 4-lane | max96712.c | 자율주행 멀티카메라 (4카메라 → 1 CSI) |
| MAX9286 | GMSL1 4채널 디시리얼라이저 | CSI-2 4-lane | max9286.c | 자동차 서라운드 뷰 시스템 |
| DS90UB960 | FPD-Link III 4채널 디시리얼라이저 | CSI-2 | ds90ub960.c | TI 자동차 비전 시스템 |
TV 튜너 및 SDR
V4L2는 역사적으로 TV 튜너 지원에서 시작되었습니다. 아날로그/디지털 TV와 SDR(Software Defined Radio)을 포함합니다:
| 카테고리 | 칩셋/드라이버 | 디바이스 노드 | 설명 |
|---|---|---|---|
| 아날로그 TV | saa7134, cx88, bttv | /dev/videoN, /dev/vbiN | NTSC/PAL 아날로그 TV 캡처, 텔레텍스트(VBI) |
| 디지털 TV (DVB) | em28xx, cx231xx, cx23885 | /dev/dvb/adapterN/ | DVB-T/T2/S/S2/C 디지털 TV 수신 |
| FM 라디오 | si4713, tea5767, radio-si476x | /dev/radioN | FM 라디오 수신/송신 |
| SDR 수신기 | rtl2832_sdr | /dev/swradioN | RTL-SDR 기반 소프트웨어 정의 라디오 |
| SDR 송수신기 | airspy | /dev/swradioN | AirSpy 광대역 SDR |
특수 V4L2 디바이스
일반적인 카메라/코덱 외에도 V4L2 프레임워크를 활용하는 특수 디바이스가 있습니다:
| 디바이스 | 드라이버 | 디바이스 노드 | 설명 |
|---|---|---|---|
| V4L2 테스트 드라이버 | vivid | /dev/videoN | 가상 캡처/출력/M2M 디바이스. 테스트 패턴 생성, 다양한 포맷/해상도 시뮬레이션 |
| Virtual Media Controller | vimc | /dev/videoN, /dev/mediaN | 가상 카메라 파이프라인. 센서→디베이어→스케일러→캡처 토폴로지 시뮬레이션 |
| Virtual Stateless 코덱 | visl | /dev/videoN | Stateless 코덱 API 테스트용 가상 디바이스 |
| 터치 센서 | sur40 | /dev/v4l-touchN | Samsung SUR40 터치 테이블, 적외선 터치 프레임 캡처 |
| HDMI CEC | cec-core | /dev/cecN | HDMI CEC 프로토콜 제어 (전원 on/off, 볼륨 등) |
| 메타데이터 캡처 | 플랫폼별 | /dev/v4l-metaN | 센서 통계 데이터(3A 히스토그램), 임베디드 데이터 캡처 |
vivid와 vimc는 V4L2 드라이버 개발 시 실제 하드웨어 없이 API를 테스트할 수 있는 필수 도구입니다. modprobe vivid로 로드하면 즉시 가상 V4L2 디바이스가 생성됩니다. v4l2-compliance 개발에도 이 드라이버들이 기준(reference) 구현으로 사용됩니다.
V4L2 프레임워크 아키텍처
V4L2 프레임워크는 계층적 구조로 설계되어 있습니다. 최상위에 v4l2_device가 있고, 그 아래 하나 이상의 video_device와 v4l2_subdev가 연결됩니다. 이 구조는 하나의 물리 디바이스가 여러 비디오 노드(캡처, 출력, 메타데이터)와 여러 서브디바이스(센서, ISP, CSI 리시버)를 가질 수 있는 현실을 반영합니다.
위 다이어그램에서 핵심 관계를 정리하면:
- v4l2_device — 물리 디바이스당 1개 생성. 모든 하위 요소의 부모 역할
- video_device — 유저스페이스에 노출되는
/dev/videoN. 하나의v4l2_device에 여러 개 등록 가능 - v4l2_subdev — 파이프라인 내 개별 하드웨어 블록 (센서, ISP 등).
v4l2_device에 등록 - vb2_queue — 각
video_device에 연결되어 DMA 버퍼 큐잉 관리 - v4l2_ctrl_handler —
video_device또는v4l2_subdev에 연결되어 컨트롤 관리 - media_device — 선택적으로 연결. 복잡한 파이프라인의 토폴로지를 유저스페이스에 노출
v4l2_device 구조체
v4l2_device는 V4L2 드라이버의 최상위 컨테이너(Container)입니다. 하나의 물리 디바이스에 대해 하나의 인스턴스를 생성하며, 산하의 모든 video_device와 v4l2_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_device
v4l2_device는 V4L2 드라이버의 최상위 컨테이너로,include/media/v4l2-device.h에 정의됩니다. 하나의 물리 디바이스(PCI/platform/USB 등)당 하나의 인스턴스를 생성합니다. - dev
dev필드는 부모struct device포인터로, 디바이스 모델 계층에서의 위치를 나타냅니다. DMA 매핑과 PM(전원 관리(Power Management)) 등에 사용됩니다. - mdevMedia Controller 디바이스 포인터로, 복잡한 파이프라인(센서 → ISP → DMA)을 가진 드라이버에서 엔터티/링크 토폴로지를 관리합니다. 단순 드라이버는
NULL로 둘 수 있습니다. - subdevs이 디바이스에 등록된 모든
v4l2_subdev(센서, ISP 등)의 연결 리스트(Linked List)입니다.v4l2_device_register_subdev()로 추가됩니다. - notify서브디바이스가 브릿지 드라이버에 비동기 이벤트(예: 프레임 동기화)를 알릴 때 사용하는 콜백입니다.
- v4l2_device_register
drivers/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_device
video_device는 유저스페이스에/dev/videoN캐릭터 디바이스 노드를 노출하는 구조체로,include/media/v4l2-dev.h에 정의됩니다. - fops
v4l2_file_operations포인터로,open/release/mmap/poll/unlocked_ioctl등 파일 오퍼레이션을 지정합니다. 대부분 vb2 헬퍼(vb2_fop_*)를 그대로 사용할 수 있습니다. - ioctl_ops
v4l2_ioctl_ops포인터로,VIDIOC_*ioctl마다 대응하는 콜백을 정의합니다.video_ioctl2()가 이 테이블을 조회하여 적절한 핸들러(Handler)를 호출합니다. - queue
vb2_queue포인터로, videobuf2 프레임워크의 버퍼 큐를 연결합니다. 이 필드를 설정하면 vb2 ioctl 헬퍼(vb2_ioctl_reqbufs등)가 자동으로 동작합니다. - device_caps
V4L2_CAP_*플래그 조합으로, 이 디바이스 노드의 능력을 선언합니다.VIDIOC_QUERYCAP응답의device_caps필드에 반영됩니다. - video_register_device
drivers/media/v4l2-core/v4l2-dev.c에 구현되며, 캐릭터 디바이스를 등록하고/dev/videoN노드를 생성합니다.nr인자가-1이면 자동 번호 할당됩니다.
ioctl 디스패치(Dispatch) 메커니즘
V4L2의 ioctl 처리 흐름을 이해하면 드라이버 디버깅에 도움이 됩니다:
유저스페이스 ioctl() 호출 경로:
sys_ioctl()→vfs_ioctl()video_device->fops->unlocked_ioctl = video_ioctl2video_ioctl2()→video_usercopy()→__video_do_ioctl()__video_do_ioctl()→v4l2_ioctl_ops의 해당 콜백
video_ioctl2의 역할:
- 유저스페이스 ↔ 커널 데이터 복사 (
copy_from_user/copy_to_user) video_device.lock기반 직렬화(Serialization)- 입력 검증 (버퍼 타입, 인덱스 범위 등)
v4l2_fh기반 우선순위(Priority) 확인
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_PREPARING | PREPARE_BUF 처리 중 | 준비 완료 → PREPARED |
VB2_BUF_STATE_PREPARED | 준비 완료, 큐 대기 | QBUF → QUEUED |
VB2_BUF_STATE_QUEUED | vb2 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 | vb2 코어 | buf_prepare() 콜백 실행 중. 버퍼 유효성 검증 |
QUEUED | vb2 코어 | vb2 내부 큐에서 대기. 아직 하드웨어에 전달되지 않음 |
ACTIVE | 드라이버/HW | buf_queue() 후 하드웨어가 DMA 진행 중 |
DONE | vb2 코어 | DMA 완료. DQBUF로 유저스페이스에 반환 대기 |
ERROR | vb2 코어 | DMA 오류 발생. DQBUF로 반환 시 에러 플래그 포함 |
버퍼 큐 상태 전이 사이클
실제 스트리밍에서 버퍼는 유저스페이스와 드라이버 사이를 순환합니다. VIDIOC_QBUF로 제출된 버퍼는 하드웨어 DMA가 완료되면 VIDIOC_DQBUF로 반환되고, 다시 큐에 제출되는 사이클을 반복합니다:
vb2_queue 구조체와 초기화
/* include/media/videobuf2-core.h */
struct vb2_queue {
unsigned int type; /* V4L2_BUF_TYPE_* */
enum vb2_io_modes io_modes; /* VB2_MMAP | VB2_USERPTR | VB2_DMABUF */
const struct vb2_ops *ops; /* 드라이버 콜백 */
const struct vb2_mem_ops *mem_ops; /* 메모리 할당자 */
struct mutex *lock; /* 직렬화 lock */
unsigned int min_queued_buffers;
u32 timestamp_flags;
struct device *dev; /* DMA 용 디바이스 */
...
};
/* 초기화 예제 */
static int my_queue_init(struct my_device *mydev)
{
struct vb2_queue *q = &mydev->vb_queue;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_DMABUF;
q->ops = &my_vb2_ops;
q->mem_ops = &vb2_dma_contig_memops;
q->drv_priv = mydev;
q->buf_struct_size = sizeof(struct my_buffer);
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->lock = &mydev->mutex;
q->dev = mydev->dev;
return vb2_queue_init(q);
}
코드 설명
- struct vb2_queue
vb2_queue는 videobuf2 프레임워크의 핵심 구조체로,include/media/videobuf2-core.h에 정의됩니다. 버퍼 풀(Pool)의 생명주기 전체를 관리합니다. - type
V4L2_BUF_TYPE_VIDEO_CAPTURE등 버퍼 타입을 지정합니다. 멀티플레인 캡처라면V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE을 사용합니다. - io_modes지원하는 메모리 모드의 비트마스크입니다.
VB2_MMAP | VB2_DMABUF로 설정하면 mmap과 DMA-BUF import를 모두 지원합니다. - ops / mem_ops
ops는 드라이버 콜백(vb2_ops),mem_ops는 메모리 할당 백엔드입니다.vb2_dma_contig_memops(연속 DMA)와vb2_dma_sg_memops(scatter-gather)가 대표적입니다. - buf_struct_size드라이버 고유 버퍼 구조체의 크기를 지정합니다. vb2 코어가 이 크기만큼 할당하여
container_of로 접근할 수 있게 합니다. - timestamp_flags
V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC을 설정하면ktime_get_ns()기반 모노토닉 타임스탬프가 버퍼에 기록됩니다. - vb2_queue_init
include/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);
}
메모리 타입 비교
| 메모리 타입 | 매크로 | 할당 주체 | 매핑 방식 | 사용 시나리오 |
|---|---|---|---|---|
| MMAP | V4L2_MEMORY_MMAP | 커널 (vb2) | mmap() | 일반적 캡처, 가장 보편적 |
| USERPTR | V4L2_MEMORY_USERPTR | 유저스페이스 | 유저 포인터 전달 | 유저 버퍼 재사용, 레거시 |
| DMABUF | V4L2_MEMORY_DMABUF | 외부 (DMA-BUF) | fd 기반 공유 | GPU/디스플레이 간 제로카피 |
vb2 메모리 할당자(Allocator) 비교
vb2는 세 가지 메모리 할당자를 제공합니다. 드라이버의 DMA 요구사항에 따라 선택합니다:
| 할당자 | mem_ops | 메모리 특성 | IOMMU 필요 | 사용 시나리오 |
|---|---|---|---|---|
| DMA contiguous | vb2_dma_contig_memops | 물리적 연속 메모리 (CMA 기반) | 아니오 | 연속 DMA 주소 필요 디바이스 (대부분 카메라/코덱) |
| DMA scatter-gather | vb2_dma_sg_memops | 물리적 비연속, SG 리스트(Scatter-Gather List) | 예 (권장) | IOMMU 있는 환경, 대용량 버퍼 |
| vmalloc | vb2_vmalloc_memops | 가상 연속 메모리 | 해당 없음 | 소프트웨어 처리 전용, 하드웨어 DMA 불가 |
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: EXPBUF
VIDIOC_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 import
V4L2_MEMORY_DMABUF모드로VIDIOC_QBUF를 호출하면, 외부에서 할당된 DMA-BUF(예: GPU 버퍼)를 V4L2 캡처 대상으로 직접 사용할 수 있습니다.drivers/media/v4l2-core/v4l2-ioctl.c의v4l2_qbuf에서 처리됩니다. - m.fd
v4l2_buffer의m.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_setup
VIDIOC_REQBUFS호출 시 vb2 코어가 호출합니다. 드라이버는 현재 포맷에 따라 필요한 버퍼 수, 플레인 수, 각 플레인의 크기를 반환해야 합니다. - buf_prepare
VIDIOC_QBUF마다 호출되며, 버퍼 크기가 현재 포맷에 충분한지 검증합니다. 타임스탬프와 필드 정보를 초기화하는 것이 일반적입니다. - buf_queuevb2 코어가 버퍼를 드라이버에 전달할 때 호출됩니다. 드라이버는 이 버퍼를 내부 DMA 큐에 추가하고, 하드웨어가 다음 프레임을 이 버퍼에 기록하도록 프로그래밍합니다.
- start_streaming
VIDIOC_STREAMON시 호출되며, 하드웨어 DMA 엔진을 시작합니다. 최소min_queued_buffers개의 버퍼가 큐잉된 후에만 호출됩니다. - stop_streaming
VIDIOC_STREAMOFF시 호출됩니다. DMA를 중지하고, 아직 처리되지 않은 모든 버퍼를vb2_buffer_done(VB2_BUF_STATE_ERROR)로 반환해야 합니다. - wait_prepare / wait_finishvb2 코어가
DQBUF대기 전후에 호출하는 lock 관리 헬퍼입니다.vb2_ops_wait_prepare/vb2_ops_wait_finish는vb2_queue.lock뮤텍스(Mutex)를 자동으로 해제/획득합니다.
각 콜백의 호출 시점:
| 콜백 | 호출 시점 | 주요 역할 |
|---|---|---|
queue_setup | VIDIOC_REQBUFS / CREATE_BUFS | 필요한 버퍼 수, 플레인 수, 크기 반환 |
buf_init | 버퍼 최초 할당 시 | DMA 매핑 등 1회 초기화 |
buf_prepare | VIDIOC_QBUF 시 | 버퍼 크기 검증, 타임스탬프 초기화 |
buf_queue | 버퍼가 vb2에서 드라이버로 전달 | 하드웨어 DMA 큐에 추가 |
start_streaming | VIDIOC_STREAMON | 하드웨어 DMA 엔진 시작 |
stop_streaming | VIDIOC_STREAMOFF | DMA 중지, 모든 버퍼 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가지 구현 외에 커스텀 할당자도 만들 수 있습니다:
| 콜백 | 용도 | MMAP | DMABUF |
|---|---|---|---|
alloc | 버퍼 메모리 할당 | CMA/vmalloc 할당 | — |
put | 메모리 해제 | 할당 해제 | 참조 카운트(Reference Count) 감소 |
get_dmabuf | DMA-BUF fd 생성 | EXPBUF에서 호출 | — |
attach_dmabuf | 외부 DMA-BUF 연결 | — | importer 설정 |
map_dmabuf | DMA-BUF를 디바이스에 매핑 | — | DMA 매핑 |
unmap_dmabuf | DMA 매핑 해제 | — | 언매핑 |
vaddr | 커널 가상 주소(Virtual Address) 반환 | kmap/vmap | — |
cookie | 하드웨어 주소(DMA addr) 반환 | DMA 주소 | SG 테이블 |
mmap | 유저스페이스 mmap | vm_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_CAPTURE | 0x00000001 | 싱글플레인 비디오 캡처 |
V4L2_CAP_VIDEO_CAPTURE_MPLANE | 0x00001000 | 멀티플레인 비디오 캡처 |
V4L2_CAP_VIDEO_OUTPUT | 0x00000002 | 비디오 출력 |
V4L2_CAP_VIDEO_M2M_MPLANE | 0x00004000 | M2M 멀티플레인 |
V4L2_CAP_STREAMING | 0x04000000 | 스트리밍 I/O (REQBUFS) |
V4L2_CAP_META_CAPTURE | 0x00800000 | 메타데이터 캡처 |
V4L2_CAP_IO_MC | 0x20000000 | Media 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.c의v4l2_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_FMT → S_FMT → G_FMT 시퀀스를 통해 드라이버와 포맷을 협상합니다. 드라이버는 요청된 값을 하드웨어 능력에 맞게 조정하여 반환합니다:
주요 픽셀 포맷:
| 매크로 | FourCC | 설명 |
|---|---|---|
V4L2_PIX_FMT_YUYV | YUYV | YUV 4:2:2 packed |
V4L2_PIX_FMT_NV12 | NV12 | YUV 4:2:0 semi-planar (Y + UV interleaved) |
V4L2_PIX_FMT_NV21 | NV21 | YUV 4:2:0 semi-planar (Y + VU, Android 선호) |
V4L2_PIX_FMT_YUV420 | YU12 | YUV 4:2:0 3-planar |
V4L2_PIX_FMT_RGB24 | RGB3 | RGB 24bpp packed |
V4L2_PIX_FMT_SRGGB10 | RG10 | 10-bit Bayer RGGB |
V4L2_PIX_FMT_MJPEG | MJPG | Motion JPEG |
V4L2_PIX_FMT_H264 | H264 | H.264 elementary stream |
버퍼 ioctl
| ioctl | 방향 | 설명 |
|---|---|---|
VIDIOC_REQBUFS | RW | 버퍼 할당 요청, 개수 협상 |
VIDIOC_QUERYBUF | RW | 버퍼 상태/오프셋(Offset) 조회 (mmap 용) |
VIDIOC_QBUF | RW | 버퍼를 드라이버 큐에 제출 |
VIDIOC_DQBUF | RW | 완료된 버퍼 수거 (blocking) |
VIDIOC_EXPBUF | RW | MMAP 버퍼를 DMA-BUF fd로 내보내기 |
VIDIOC_CREATE_BUFS | RW | 추가 버퍼 할당 (다른 포맷 가능) |
스트리밍 제어와 이벤트
/* 스트리밍 시작/중지 */
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:4 | 1:1:1 | 24 | V4L2_PIX_FMT_YUV444 | 색차 손실 없음, 최고 품질 |
| 4:2:2 | 2:1:1 | 16 | V4L2_PIX_FMT_YUYV, UYVY | 수평 2:1 서브샘플링, 방송 표준 |
| 4:2:0 | 4:1:1 | 12 | V4L2_PIX_FMT_NV12, YU12 | 수평+수직 2:1, 코덱 표준 (H.264/HEVC) |
| 4:1:1 | 4:1:1 | 12 | V4L2_PIX_FMT_Y41P | 수평 4:1 서브샘플링, 드물게 사용 |
메모리 레이아웃(Layout) 변형:
| 레이아웃 | 설명 | 예시 |
|---|---|---|
| Packed | Y/U/V 샘플이 인터리브(Interleave)됨 | YUYV: Y0 U0 Y1 V0 Y2 U1 Y3 V1 ... |
| Semi-planar | Y 플레인 + UV 인터리브 플레인 | NV12: [YYYY...] [UVUV...] |
| Planar | Y/U/V 각각 독립 플레인 | YU12: [YYYY...] [UU...] [VV...] |
Bayer(RAW) 포맷
이미지 센서는 Bayer 패턴(Color Filter Array)으로 RAW 데이터를 출력합니다. 각 픽셀은 R/G/B 중 하나의 색만 캡처하며, ISP의 디모자이크(Demosaic) 과정을 거쳐야 완전한 이미지가 됩니다:
| 패턴 | FourCC 예 | 비트 깊이(Depth) | 설명 |
|---|---|---|---|
| RGGB | RGGB, RG10, RG12 | 8/10/12 | 첫 행: R G, 둘째 행: G B |
| BGGR | BA81, BG10, BG12 | 8/10/12 | 첫 행: B G, 둘째 행: G R |
| GRBG | GRBG, BA10 | 8/10 | 첫 행: G R, 둘째 행: B G |
| GBRG | GBRG, GB10 | 8/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, BT2020 | RGB → YCbCr 변환 행렬. 해상도에 따라 다름 |
| 양자화 | FULL_RANGE, LIM_RANGE | Full: 0~255, Limited: 16~235(Y)/16~240(CbCr) |
V4L2_QUANTIZATION_DEFAULT를 설정하면 V4L2가 컬러스페이스에 따라 자동 결정합니다.
싱글플레인 vs 멀티플레인 API
V4L2는 두 가지 버퍼 타입 API를 제공합니다:
| 항목 | 싱글플레인 | 멀티플레인 |
|---|---|---|
| 버퍼 타입 | V4L2_BUF_TYPE_VIDEO_CAPTURE | V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE |
| 포맷 구조체 | v4l2_pix_format | v4l2_pix_format_mplane |
| 플레인 수 | 1개 (연속 메모리) | 최대 8개 (독립 DMA 버퍼) |
| 사용 예 | YUYV packed, RGB | NV12 (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_ops
v4l2_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/DQEVENTioctl이 동작합니다.
완전한 캡처 드라이버 예제
#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 패드 포맷을 자동으로 결정합니다:
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 | 설명 |
|---|---|---|
| User | V4L2_CID_BRIGHTNESS, _CONTRAST, _SATURATION | 기본 이미지 조정 |
| Camera | V4L2_CID_EXPOSURE_AUTO, _EXPOSURE_ABSOLUTE | 노출 제어 |
| Camera | V4L2_CID_AUTO_WHITE_BALANCE, _WHITE_BALANCE_TEMPERATURE | 화이트 밸런스 |
| Camera | V4L2_CID_FOCUS_AUTO, _FOCUS_ABSOLUTE | 오토포커스 |
| Image Source | V4L2_CID_ANALOGUE_GAIN, _DIGITAL_GAIN | 센서 게인 |
| Image Processing | V4L2_CID_TEST_PATTERN | 테스트 패턴 |
| Codec | V4L2_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_BRIDGE | CSI-2 리시버 등 인터페이스 브리지(Bridge) |
MEDIA_ENT_F_PROC_VIDEO_ISP | ISP (Image Signal Processor) |
MEDIA_ENT_F_IO_V4L | V4L2 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)를 관리합니다.
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 코덱 비교
| 항목 | Stateful | Stateless |
|---|---|---|
| 비트스트림 파싱 | 하드웨어/펌웨어 | 호스트 CPU (userspace) |
| 상태 관리 | 코덱 내부 | 호스트가 Request API로 전달 |
| 참조 프레임 관리 | 하드웨어 자동 | 호스트가 CAPTURE 버퍼 인덱스로 지정 |
| 입력 단위 | NAL/패킷(Packet) 스트림 | 프레임/슬라이스 단위 |
| 해상도 변경 | SOURCE_CHANGE 이벤트 | 유저스페이스가 직접 감지 |
| 복잡도 | 단순 (API 단순) | 복잡 (파싱 라이브러리 필요) |
| 지연 | 높음 (내부 버퍼링) | 낮음 (프레임 단위 제어) |
| 대표 드라이버 | venus (Qualcomm), mtk-vcodec | hantro, cedrus, rkvdec |
주요 stateless 코덱 드라이버:
| 드라이버 | SoC | 지원 코덱 | 커널 소스 위치 |
|---|---|---|---|
hantro | Rockchip, NXP | H.264, VP8, MPEG-2, VP9, AV1 | drivers/media/platform/verisilicon/ |
cedrus | Allwinner | H.264, HEVC, MPEG-2, VP8 | drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/ |
rkvdec | Rockchip | H.264, VP9, HEVC | drivers/media/platform/rockchip/rkvdec/ |
visl | 가상 (테스트용) | 모든 stateless 코덱 | drivers/media/test-drivers/visl/ |
주요 stateful 코덱 드라이버:
| 드라이버 | SoC | 지원 코덱 | 커널 소스 위치 |
|---|---|---|---|
venus | Qualcomm SDM845/SM8250 | H.264, HEVC, VP8, VP9 (인코딩/디코딩) | drivers/media/platform/qcom/venus/ |
mtk-vcodec | MediaTek MT8173/MT8192 | H.264, VP8, VP9 (인코딩/디코딩) | drivers/media/platform/mediatek/vcodec/ |
s5p-mfc | Samsung Exynos | H.264, MPEG-4, H.263 (인코딩/디코딩) | drivers/media/platform/samsung/s5p-mfc/ |
wave5 | Chips&Media | HEVC, AVC (인코딩/디코딩) | drivers/media/platform/chips-media/wave5/ |
카메라 서브시스템
임베디드 카메라는 여러 하드웨어 블록이 파이프라인으로 연결됩니다. V4L2 서브디바이스와 Media Controller를 조합하여 이 파이프라인을 모델링합니다.
카메라 파이프라인 구조
CSI-2 / MIPI 인터페이스
MIPI CSI-2(Camera Serial Interface 2)는 임베디드 카메라의 표준 인터페이스입니다. 고속 직렬 링크를 통해 센서 데이터를 SoC에 전달합니다:
| 항목 | D-PHY | C-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) only | Frame Start (FS), Frame End (FE), Line Start/End |
| Long Packet | 헤더 + Payload + CRC | 이미지 데이터, 임베디드 데이터 |
| 임베디드 데이터 | Long Packet (DT=0x12) | 센서 메타데이터 (노출, 게인, 온도 등) |
가상 채널(Virtual Channel)을 사용한 멀티카메라 구성:
| VC | 데이터 타입 | 용도 |
|---|---|---|
| VC0 | RAW10 이미지 | 메인 카메라 이미지 데이터 |
| VC0 | 임베디드 데이터 | 메인 카메라 메타데이터 |
| VC1 | RAW10 이미지 | 보조 카메라 이미지 (멀티카메라 센서) |
| 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 컨트롤 |
|---|---|---|---|
| 1 | Black Level Correction | 센서의 어둠 전류(Dark Current) 보정. 0 입사광에도 발생하는 신호 오프셋 제거 | — |
| 2 | Defect Pixel Correction | 불량(Dead/Hot) 픽셀을 주변 값으로 보간(Interpolation) | — |
| 3 | Lens Shading Correction | 렌즈 특성에 의한 주변부 밝기 저하(비네팅) 보정 | — |
| 4 | AWB (Auto White Balance) | 장면의 색온도를 감지하여 R/G/B 게인(Gain) 자동 조정 | V4L2_CID_AUTO_WHITE_BALANCE |
| 5 | Demosaic (CFA) | Bayer 패턴 → 풀 RGB 변환. 각 픽셀에 누락된 2개 색상을 보간 | — |
| 6 | Color Correction Matrix | 3x3 행렬로 색 공간 변환. 센서 고유 색 응답을 표준 색 공간으로 매핑 | — |
| 7 | Gamma Correction | 선형 → 비선형 감마 커브 적용. 인간 시각에 맞는 밝기 분포로 변환 | V4L2_CID_GAMMA |
| 8 | Noise Reduction | 공간(Spatial)/시간(Temporal) 도메인 노이즈 제거 | — |
| 9 | Sharpening | 엣지(Edge) 강조. Unsharp Mask 또는 커널 기반 필터 | V4L2_CID_SHARPNESS |
| 10 | Color Space Conversion | RGB → YUV 변환. BT.601/709/2020 행렬 선택 | — |
| 11 | Scaler/Resizer | 출력 해상도 조정. 바이리니어(Bilinear)/바이큐빅(Bicubic) 보간 | Selection API |
/dev/v4l-metaN 노드를 통해
3A 통계 데이터(히스토그램, 초점 값 등)를 캡처합니다.
libcamera 아키텍처
복잡한 카메라 파이프라인(ISP 포함)에서는 V4L2만으로는 부족합니다. libcamera는 V4L2/Media Controller 위에서 파이프라인 구성, 3A 알고리즘, 버퍼 관리를 통합하는 유저스페이스 프레임워크입니다:
| 계층 | 역할 | 구성 요소 |
|---|---|---|
| 애플리케이션 | 카메라 사용 | GStreamer, PipeWire, cam 유틸리티 |
| libcamera API | 통합 카메라 API | CameraManager, Camera, Request, FrameBuffer |
| IPA (Image Processing Algorithm) | 3A 알고리즘 | 플랫폼별 IPA 모듈 (rkisp1, ipu3, mali-c55) |
| Pipeline Handler | V4L2/MC 제어 | 플랫폼별 파이프라인 핸들러 |
| V4L2 / Media Controller | 커널 인터페이스 | /dev/videoN, /dev/mediaN, /dev/v4l-subdevN |
주요 ISP/카메라 플랫폼 드라이버:
| 드라이버 | SoC | 위치 |
|---|---|---|
rkisp1 | Rockchip RK3399/RK3588 | drivers/media/platform/rockchip/rkisp1/ |
sun6i-csi | Allwinner A64/H6 | drivers/media/platform/sunxi/sun6i-csi/ |
imx8-isi | NXP i.MX8 | drivers/media/platform/nxp/imx8-isi/ |
camss | Qualcomm (MSM/SDM) | drivers/media/platform/qcom/camss/ |
mali-c55 | ARM Mali-C55 ISP | drivers/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 처리 | ISP | BLC→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 Unit | VC_PROCESSING_UNIT | 밝기, 대비, 화이트 밸런스 등 이미지 처리 |
| Extension Unit | VC_EXTENSION_UNIT | 벤더 고유 기능 |
| Output Terminal (Streaming) | OTT_STREAMING | 비디오 스트림 출력 |
| Encoding Unit | VC_ENCODING_UNIT | H.264 인코딩 (UVC 1.5) |
UVC 토폴로지 예시
일반적인 USB 웹캠의 UVC 내부 토폴로지:
커널 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 ioctls | QUERYCAP, ENUM_FMT, G/S_FMT 등 | ioctl_ops 콜백 미구현 |
| Buffer ioctls | REQBUFS, QUERYBUF, QBUF/DQBUF | vb2 설정 오류, 버퍼 타입 불일치 |
| Streaming | STREAMON/STREAMOFF, 실제 프레임 캡처 | start/stop_streaming 콜백 버그 |
| Controls | G/S_CTRL, G/S_EXT_CTRLS | 범위 초과, 타입 불일치 |
| Input/Output | ENUM_INPUT, G/S_INPUT | 입력 인덱스 범위, 누락 구현 |
| Format | TRY_FMT, CREATE_BUFS 포맷 호환 | TRY_FMT와 S_FMT 결과 불일치 |
| Selection | G/S_SELECTION, 경계 확인 | BOUNDS 미구현, 정렬 미준수 |
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 device | USB 카메라 분리 | 디바이스 재연결, fd 재오픈 |
VIDIOC_STREAMON: No space left | USB 대역폭 부족 | 해상도/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-compliance | ioctl 적합성 테스트 | v4l2-compliance -d /dev/video0 -s |
media-ctl -p | Media Controller 토폴로지 덤프(Dump) | media-ctl -d /dev/media0 -p |
dmesg | 드라이버 등록(Driver Registration)/에러 메시지 | dmesg | grep -i 'v4l2\|video\|camera' |
ftrace | V4L2 ioctl 추적 | echo 1 > events/v4l2/enable |
debugfs | 드라이버별 debugfs 엔트리 | ls /sys/kernel/debug/video0/ |
perf trace | V4L2 tracepoint 추적 | perf trace -e v4l2:* -p PID |
strace | 유저스페이스 ioctl 호출 추적(Call Trace) | strace -e ioctl v4l2-ctl --stream-mmap |
v4l2-dbg | V4L2 레지스터 읽기/쓰기 | 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_ALLOC | media_device에서 Request fd 할당 |
| 설정 | VIDIOC_S_EXT_CTRLS (request_fd 지정) | Request에 컨트롤 값 바인딩 |
| 버퍼 연결 | VIDIOC_QBUF (V4L2_BUF_FLAG_REQUEST_FD) | Request에 버퍼 연결 |
| 큐잉 | MEDIA_REQUEST_IOC_QUEUE | Request를 드라이버에 제출 |
| 실행 | (드라이버 내부) | 하드웨어가 버퍼+컨트롤 원자적 적용 |
| 완료 | poll() / VIDIOC_DQBUF | Request 완료 대기 |
| 재초기화 | MEDIA_REQUEST_IOC_REINIT | Request 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_V4L2 | V4L2 코어 프레임워크 |
CONFIG_VIDEO_V4L2_SUBDEV_API | 서브디바이스 유저스페이스 API (/dev/v4l-subdevN) |
CONFIG_VIDEOBUF2_CORE | videobuf2 코어 |
CONFIG_VIDEOBUF2_DMA_CONTIG | vb2 DMA contiguous 할당자 |
CONFIG_VIDEOBUF2_DMA_SG | vb2 DMA scatter-gather 할당자 |
CONFIG_VIDEOBUF2_VMALLOC | vb2 vmalloc 할당자 |
CONFIG_MEDIA_CONTROLLER | Media Controller API |
CONFIG_MEDIA_CONTROLLER_REQUEST_API | Request API (stateless 코덱) |
CONFIG_USB_VIDEO_CLASS | UVC (USB Video Class) 드라이버 |
CONFIG_V4L2_MEM2MEM_DEV | M2M 프레임워크 |
CONFIG_V4L2_FWNODE | V4L2 fwnode (DT/ACPI) 파싱 |
CONFIG_VIDEO_V4L2_I2C | V4L2 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 분리 시 커널 OOPS | video_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 # 모든 테스트 통과해야 함
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 "=== 테스트 완료 ==="
커널 버전 호환성 참고
| 기능 | 최소 커널 버전 | 비고 |
|---|---|---|
| videobuf2 | 2.6.38 | videobuf(v1) 대체 |
DMA-BUF (V4L2_MEMORY_DMABUF) | 3.8 | 제로카피 버퍼 공유 |
| Media Controller | 3.3 | 파이프라인 토폴로지 |
| 멀티플레인 API | 3.1 | V4L2_BUF_TYPE_*_MPLANE |
| Selection API | 3.2 | 레거시 crop API 대체 |
| Request API | 5.3 | stateless 코덱 |
| Streams API | 6.1 | 멀티플렉스 라우팅 |
| v4l2_subdev active state | 6.3 | 서브디바이스 상태 관리 |
V4L2_CAP_IO_MC | 5.9 | MC 기반 I/O 능력 |
VIDIOC_REMOVE_BUFS | 6.8 | 런타임 버퍼 삭제 |
V4L2_SUBDEV_FL_STREAMS | 6.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 참조 |
| libcamera | libcamera.org | 카메라 파이프라인 추상화 프레임워크 |
| v4l-utils | linuxtv.org/wiki/index.php/V4l-utils | 유틸리티 모음 (v4l2-ctl, v4l2-compliance 등) |
| MIPI Alliance | mipi.org | CSI-2, D-PHY, C-PHY 사양 |
| Linux Media Subsystem Wiki | linuxtv.org/wiki/ | 커뮤니티 위키 |
V4L2 API 빠른 참조
커널 API 요약
| 카테고리 | 함수 | 용도 |
|---|---|---|
| v4l2_device | v4l2_device_register() | 최상위 디바이스 등록 |
v4l2_device_unregister() | 디바이스 해제 | |
| video_device | video_register_device() | /dev/videoN 생성 |
video_unregister_device() | 디바이스 노드 제거 | |
video_set_drvdata() | 드라이버 전용 데이터 설정 | |
| v4l2_subdev | v4l2_subdev_init() | 서브디바이스 초기화 |
v4l2_device_register_subdev() | 서브디바이스 등록 | |
v4l2_async_register_subdev() | 비동기 서브디바이스 등록 | |
| vb2 | vb2_queue_init() | 버퍼 큐 초기화 |
vb2_buffer_done() | 버퍼 완료 알림 (IRQ에서) | |
vb2_set_plane_payload() | 유효 데이터 크기 설정 | |
| v4l2_ctrl | v4l2_ctrl_handler_init() | 컨트롤 핸들러 초기화 |
v4l2_ctrl_new_std() | 표준 컨트롤 추가 | |
v4l2_ctrl_new_custom() | 커스텀 컨트롤 추가 | |
v4l2_ctrl_handler_free() | 핸들러 정리 | |
| Media Controller | media_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_QUERYCAP | v4l2_capability | 디바이스 능력 조회 |
VIDIOC_ENUM_FMT | v4l2_fmtdesc | 지원 포맷 열거 |
VIDIOC_G_FMT / S_FMT | v4l2_format | 포맷 조회/설정 |
VIDIOC_TRY_FMT | v4l2_format | 포맷 테스트 (실제 적용 없음) |
VIDIOC_REQBUFS | v4l2_requestbuffers | 버퍼 할당 |
VIDIOC_QUERYBUF | v4l2_buffer | 버퍼 정보 조회 |
VIDIOC_QBUF | v4l2_buffer | 버퍼 큐잉 |
VIDIOC_DQBUF | v4l2_buffer | 버퍼 수거 |
VIDIOC_STREAMON | v4l2_buf_type | 스트리밍 시작 |
VIDIOC_STREAMOFF | v4l2_buf_type | 스트리밍 중지 |
VIDIOC_EXPBUF | v4l2_exportbuffer | DMA-BUF fd 내보내기 |
VIDIOC_G_CTRL / S_CTRL | v4l2_control | 컨트롤 조회/설정 |
VIDIOC_G_EXT_CTRLS / S_EXT_CTRLS | v4l2_ext_controls | 확장 컨트롤 조회/설정 |
VIDIOC_SUBSCRIBE_EVENT | v4l2_event_subscription | 이벤트 구독 |
VIDIOC_DQEVENT | v4l2_event | 이벤트 수거 |
VIDIOC_G_SELECTION / S_SELECTION | v4l2_selection | 크롭/컴포즈 설정 |
VIDIOC_ENUM_FRAMESIZES | v4l2_frmsizeenum | 지원 해상도 열거 |
VIDIOC_ENUM_FRAMEINTERVALS | v4l2_frmivalenum | 지원 프레임 간격 열거 |
VIDIOC_G_PARM / S_PARM | v4l2_streamparm | 프레임 레이트 설정 |
서브디바이스 ioctl 빠른 참조
| ioctl | 구조체 | 용도 |
|---|---|---|
VIDIOC_SUBDEV_QUERYCAP | v4l2_subdev_capability | 서브디바이스 능력 조회 |
VIDIOC_SUBDEV_G_FMT / S_FMT | v4l2_subdev_format | 패드 포맷 조회/설정 |
VIDIOC_SUBDEV_ENUM_MBUS_CODE | v4l2_subdev_mbus_code_enum | 미디어 버스 코드 열거 |
VIDIOC_SUBDEV_ENUM_FRAME_SIZE | v4l2_subdev_frame_size_enum | 지원 해상도 열거 |
VIDIOC_SUBDEV_G_SELECTION / S_SELECTION | v4l2_subdev_selection | 패드 레벨 크롭/컴포즈 |
VIDIOC_SUBDEV_G_ROUTING / S_ROUTING | v4l2_subdev_routing | Streams API 라우팅 |
VIDIOC_SUBDEV_G_FRAME_INTERVAL | v4l2_subdev_frame_interval | 프레임 간격 조회/설정 |
Media Controller ioctl 빠른 참조
| ioctl | 구조체 | 용도 |
|---|---|---|
MEDIA_IOC_DEVICE_INFO | media_device_info | 미디어 디바이스 정보 |
MEDIA_IOC_G_TOPOLOGY | media_v2_topology | 전체 토폴로지 조회 |
MEDIA_IOC_SETUP_LINK | media_link_desc | 링크 활성화/비활성화 |
MEDIA_IOC_REQUEST_ALLOC | int * | Request fd 할당 |
MEDIA_REQUEST_IOC_QUEUE | — | Request 제출 |
MEDIA_REQUEST_IOC_REINIT | — | Request 재초기화 |
최신 동향 (2025-2026)
커널 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 DKMS | drivers/media/pci/intel/ipu6/ (6.10 병합) | MTL/LNL 공용 |
| ISP 파이프라인 (PSYS) | 펌웨어 + icamerasrc | IPU6 PSYS(6.11+) + libcamera SoftISP | Bayer → 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 투명 지원 |
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.x | 2024 | Intel IPU6 Simple pipeline 지원, 소프트 ISP 초기 구현 |
| 0.4.x | 2025 상반기 | 글로벌 configuration 파일 도입(/etc/libcamera/configuration.yaml), V4L2 Request API 통합 |
| 0.5.x | 2025 하반기 | NXP i.MX8MP Dewarper 지원(리사이즈/회전/크롭/렌즈 왜곡 보정), HDR 메타데이터 파이프라인 |
| 0.6.x | 2025 말~2026 | Intel 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 코드이며, 목표는:
- 메모리 안전성 — 디코더 상태 머신은 악의적 비트스트림에 노출되므로, Rust의 소유권(Ownership) 모델로 use-after-free/OOB를 구조적으로 차단합니다.
- 테스트 용이성 — Rust의 단위 테스트(
#[test])로 비트스트림 파서를 커널 밖에서도 검증합니다. - 점진 도입 — C로 구현된 H/W 인터페이스 레이어는 유지하고, 파서/상태 머신만 Rust 트레이트(Trait)로 추상화하여 기존
hantro,rkvdec드라이버가 공유합니다.
이 작업이 병합되면 향후 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-ctl | Streams API 토폴로지 시각화 개선 (-p --format), active state dump | 멀티카메라 CSI-2 디버깅 단순화 |
| GStreamer 1.28 | v4l2av1dec(stateful AV1), v4l2slh265dec(stateless HEVC) 안정화 | 하드웨어 코덱 활용 앱 개발 용이 |
| FFmpeg 7.1+ | V4L2 Request API 지원 개선, DRM/KMS zero-copy 출력 | 임베디드 플레이어의 CPU 사용률 감소 |
| Chromium/Android | ChromeOS의 V4L2VideoDecoder 기본화, Android 16 HAL이 V4L2 M2M 표준 | 웹/모바일 미디어 재생 경로 단일화 |
V4L2_CAP_IO_MC 표기, (3) active state 기반 포맷 관리, (4) v4l2-compliance --stream-count 통과를 병합 조건으로 요구합니다. 구형 스타일 드라이버는 메인라인 진입이 거절되므로, 신규 포팅 시 drivers/media/i2c/imx335.c(현대적 레퍼런스)를 모델로 삼으세요.
참고 링크
- Kernel Docs — Video for Linux API (V4L2) — V4L2 유저 공간 API 공식 문서
- Kernel Docs — V4L2 Driver API — V4L2 커널 드라이버 개발 API
- V4L2 Controls — 밝기, 대비, 화이트 밸런스 등 컨트롤 인터페이스
- V4L2 Pixel Formats — YUYV, NV12, MJPEG 등 픽셀 포맷 레퍼런스
- V4L2 I/O Methods — mmap, userptr, dmabuf 스트리밍 방식
- V4L2 Sub-device Formats — 미디어 파이프라인 서브디바이스 포맷
- Media Controller API — 미디어 파이프라인 토폴로지 관리
- V4L2 Memory-to-Memory — 하드웨어 코덱/ISP M2M 인터페이스
- V4L2 Sub-device Driver API — ISP, 센서, CSI 브릿지 서브디바이스 드라이버
- MIPI CSI-2 Support — 커널 CSI-2 서브시스템 지원
- LinuxTV — V4L2, DVB, 미디어 커널 서브시스템 커뮤니티 허브
- LinuxTV Wiki — V4L2 드라이버 호환성 목록, 튜토리얼
- v4l-utils (v4l2-ctl, media-ctl) — V4L2 유저 공간 유틸리티 소스
- GStreamer — V4L2 소스/싱크 플러그인 기반 멀티미디어 프레임워크
- FFmpeg — V4L2 입력/출력 디바이스 지원, V4L2 M2M 하드웨어 코덱
- libcamera — 차세대 카메라 프레임워크 (복잡한 ISP 파이프라인 지원)
- MIPI CSI-2 Specification — 카메라 시리얼 인터페이스 표준
- linux-media 메일링 리스트 — V4L2/미디어 커널 개발 토론
- LWN.net — Video4Linux — V4L2 관련 심층 기술 기사 모음
- DMA-BUF API — V4L2 DMABUF 스트리밍에 사용되는 버퍼 공유
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
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.