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 버퍼 할당, 매핑, 큐잉을 통합 관리하는 프레임워크로, 드라이버가 버퍼 관리를 직접 구현할 필요를 없앱니다.
- Media Controller — ISP, 센서, 스케일러 등 하드웨어 블록 간 연결 토폴로지를 유저스페이스에 그래프로 노출합니다.
- 픽셀 포맷과 컬러스페이스(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콜백이 드라이버와 어떻게 연동되는지 코드 수준에서 추적합니다. - 포맷 협상과 컨트롤 실습 —
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의 설계 철학은 다양한 하드웨어를 단일 사용자 공간 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 기반 픽셀 포맷과 미디어 버스 코드로 데이터 형식 합의
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) 라우팅 |
| 6.3 | v4l2_subdev active state — 서브디바이스 상태 관리 개선 |
| 6.8 | vb2 삭제 가능 버퍼 — VIDIOC_REMOVE_BUFS 지원 |
디바이스 유형
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 번호, 구조체, 상수 정의 |
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 프레임워크는 계층적 구조로 설계되어 있습니다. 최상위에 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 구조체(Struct)
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(전원 관리) 등에 사용됩니다. - mdevMedia Controller 디바이스 포인터로, 복잡한 파이프라인(센서 → ISP → DMA)을 가진 드라이버에서 엔터티/링크 토폴로지를 관리합니다. 단순 드라이버는
NULL로 둘 수 있습니다. - subdevs이 디바이스에 등록된 모든
v4l2_subdev(센서, ISP 등)의 연결 리스트입니다.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()가 이 테이블을 조회하여 적절한 핸들러를 호출합니다. - 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 디스패치 메커니즘
V4L2의 ioctl 처리 흐름을 이해하면 드라이버 디버깅에 도움이 됩니다:
/* 유저스페이스 ioctl() 호출 경로 */
/* 1. sys_ioctl() → vfs_ioctl() */
/* 2. video_device->fops->unlocked_ioctl = video_ioctl2 */
/* 3. video_ioctl2() → video_usercopy() → __video_do_ioctl() */
/* 4. __video_do_ioctl() → v4l2_ioctl_ops의 해당 콜백 */
/* video_ioctl2의 역할: */
/* - 유저스페이스 ↔ 커널 데이터 복사 (copy_from_user/copy_to_user) */
/* - video_device.lock 기반 직렬화 */
/* - 입력 검증 (버퍼 타입, 인덱스 범위 등) */
/* - v4l2_fh 기반 우선순위 확인 */
v4l2_fh (파일 핸들)
v4l2_fh는 유저스페이스의 open() 호출마다 생성되는 per-open 핸들입니다. 이벤트 구독, 우선순위(Priority) 관리 등에 사용됩니다.
struct v4l2_fh {
struct list_head list; /* video_device의 fh_list에 연결 */
struct video_device *vdev;
struct v4l2_ctrl_handler *ctrl_handler;
enum v4l2_priority prio; /* 우선순위 */
struct v4l2_events *events; /* 이벤트 큐 */
...
};
/* open/close 콜백에서 사용 */
void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev);
void v4l2_fh_add(struct v4l2_fh *fh);
void v4l2_fh_del(struct v4l2_fh *fh);
void v4l2_fh_exit(struct v4l2_fh *fh);
videobuf2 (vb2) 버퍼 관리
videobuf2(vb2)는 V4L2의 핵심 버퍼 관리 프레임워크입니다. DMA 버퍼 할당, 유저스페이스 매핑(Mapping), 스트리밍 큐 관리를 담당합니다. 드라이버는 직접 버퍼를 관리할 필요 없이 vb2 콜백(Callback)만 구현하면 됩니다.
vb2 버퍼 수명주기(Lifecycle)
vb2 버퍼는 엄격한 상태 머신(State Machine)을 따릅니다. 각 버퍼는 다음 상태 중 하나에 있습니다:
| 상태 | 소유권 | 설명 |
|---|---|---|
DEQUEUED | 유저스페이스 | 애플리케이션이 버퍼를 보유. 데이터 읽기/쓰기 가능 |
PREPARING | vb2 코어 | buf_prepare() 콜백 실행 중. 버퍼 유효성 검증 |
QUEUED | vb2 코어 | vb2 내부 큐에서 대기. 아직 하드웨어에 전달되지 않음 |
ACTIVE | 드라이버/HW | buf_queue() 후 하드웨어가 DMA 진행 중 |
DONE | vb2 코어 | DMA 완료. DQBUF로 유저스페이스에 반환 대기 |
ERROR | vb2 코어 | DMA 오류 발생. DQBUF로 반환 시 에러 플래그 포함 |
vb2_queue 구조체와 초기화
/* include/media/videobuf2-core.h */
struct vb2_queue {
unsigned int type; /* V4L2_BUF_TYPE_* */
enum vb2_io_modes io_modes; /* VB2_MMAP | VB2_USERPTR | VB2_DMABUF */
const struct vb2_ops *ops; /* 드라이버 콜백 */
const struct vb2_mem_ops *mem_ops; /* 메모리 할당자 */
struct mutex *lock; /* 직렬화 lock */
unsigned int min_queued_buffers;
u32 timestamp_flags;
struct device *dev; /* DMA 용 디바이스 */
...
};
/* 초기화 예제 */
static int my_queue_init(struct my_device *mydev)
{
struct vb2_queue *q = &mydev->vb_queue;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_DMABUF;
q->ops = &my_vb2_ops;
q->mem_ops = &vb2_dma_contig_memops;
q->drv_priv = mydev;
q->buf_struct_size = sizeof(struct my_buffer);
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->lock = &mydev->mutex;
q->dev = mydev->dev;
return vb2_queue_init(q);
}
코드 설명
- struct vb2_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 파일 디스크립터(fd)로 내보냅니다. 이 fd를 다른 디바이스(GPU, 디스플레이)에 전달하면 물리 메모리 복사 없이 제로카피(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뮤텍스를 자동으로 해제/획득합니다.
각 콜백의 호출 시점:
| 콜백 | 호출 시점 | 주요 역할 |
|---|---|---|
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개 (권장) — 안정적 스트리밍. 유저스페이스 처리 지연에 대한 여유
- 8개 이상 — 긴 처리 시간이 필요한 경우 (GPU 처리, 네트워크 전송 등)
- 지연 최소화 — 3개 + non-blocking DQBUF + 가장 최신 프레임만 사용
vb2_mem_ops 상세
vb2_mem_ops는 메모리 할당자의 저수준 인터페이스입니다. 커널이 제공하는 3가지 구현 외에 커스텀 할당자도 만들 수 있습니다:
| 콜백 | 용도 | MMAP | DMABUF |
|---|---|---|---|
alloc | 버퍼 메모리 할당 | CMA/vmalloc 할당 | — |
put | 메모리 해제 | 할당 해제 | 참조 카운트 감소 |
get_dmabuf | DMA-BUF fd 생성 | EXPBUF에서 호출 | — |
attach_dmabuf | 외부 DMA-BUF 연결 | — | importer 설정 |
map_dmabuf | DMA-BUF를 디바이스에 매핑 | — | DMA 매핑 |
unmap_dmabuf | DMA 매핑 해제 | — | 언매핑 |
vaddr | 커널 가상 주소 반환 | 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등을 사용합니다.
주요 픽셀 포맷:
| 매크로 | 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 */
...
};
패드 오퍼레이션과 미디어 버스(Bus) 포맷
서브디바이스의 패드(입출력(I/O) 포트)는 미디어 버스 포맷(v4l2_mbus_framefmt)으로 데이터 형식을 협상합니다:
struct v4l2_mbus_framefmt {
__u32 width; /* 이미지 너비 */
__u32 height; /* 이미지 높이 */
__u32 code; /* MEDIA_BUS_FMT_* */
__u32 field; /* V4L2_FIELD_NONE 등 */
__u32 colorspace; /* V4L2_COLORSPACE_* */
...
};
/* 주요 미디어 버스 코드 예시 */
/* MEDIA_BUS_FMT_SRGGB10_1X10 — 10-bit Bayer RGGB */
/* MEDIA_BUS_FMT_YUYV8_2X8 — YUV 4:2:2 8-bit */
/* MEDIA_BUS_FMT_UYVY8_1X16 — YUV 4:2:2 16-bit bus */
/* MEDIA_BUS_FMT_RGB888_1X24 — RGB 24-bit */
서브디바이스 active state (커널 6.3+)
최신 커널에서는 v4l2_subdev_state가 서브디바이스의 포맷/Selection 상태를 중앙 관리합니다. TRY와 ACTIVE 상태를 구분하여 포맷 협상의 안전성을 보장합니다:
/* 패드 포맷 조회 — state에서 가져오기 */
static int my_get_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *fmt)
{
/* state가 TRY/ACTIVE를 자동으로 구분 */
fmt->format = *v4l2_subdev_state_get_format(state, fmt->pad);
return 0;
}
/* 패드 포맷 설정 — state에 저장 */
static int my_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *fmt)
{
struct v4l2_mbus_framefmt *mf;
/* 지원하는 포맷으로 조정 */
fmt->format.code = my_find_best_code(fmt->format.code);
fmt->format.width = clamp(fmt->format.width, 64, 4096);
fmt->format.height = clamp(fmt->format.height, 64, 4096);
/* state에 저장 */
mf = v4l2_subdev_state_get_format(state, fmt->pad);
*mf = fmt->format;
/* source 패드도 연쇄(Cascade) 업데이트 */
mf = v4l2_subdev_state_get_format(state, MY_SOURCE_PAD);
*mf = fmt->format;
/* ISP가 포맷 변환하면 source 패드 코드만 변경 */
return 0;
}
/* init_state — 서브디바이스 기본 포맷 설정 */
static int my_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
struct v4l2_mbus_framefmt *fmt;
fmt = v4l2_subdev_state_get_format(state, 0);
fmt->code = MEDIA_BUS_FMT_SRGGB10_1X10;
fmt->width = 1920;
fmt->height = 1080;
fmt->field = V4L2_FIELD_NONE;
fmt->colorspace = V4L2_COLORSPACE_RAW;
return 0;
}
static const struct v4l2_subdev_internal_ops my_internal_ops = {
.init_state = my_init_state,
};
포맷 협상(Format Negotiation) 흐름
파이프라인의 포맷 협상은 소스에서 싱크 방향(센서 → ISP → DMA)으로 진행됩니다. 각 서브디바이스의 싱크 패드에 포맷을 설정하면, 서브디바이스가 source 패드 포맷을 자동으로 결정합니다:
Streams API (멀티플렉스 라우팅(Routing))
최신 커널(6.3+)의 Streams API는 하나의 패드에서 여러 데이터 스트림을 다중화(Multiplexing)할 수 있습니다. CSI-2 가상 채널이나 멀티카메라 시스템에서 활용됩니다:
/* 라우팅 설정 */
struct v4l2_subdev_route routes[] = {
{ .sink_pad = 0, .sink_stream = 0,
.source_pad = 1, .source_stream = 0,
.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE },
{ .sink_pad = 0, .sink_stream = 1,
.source_pad = 2, .source_stream = 0,
.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE },
};
/* CSI-2 VC0 → 패드1, CSI-2 VC1 → 패드2 라우팅 */
I2C 센서 서브디바이스 드라이버 예제
struct my_sensor {
struct v4l2_subdev sd;
struct media_pad pad; /* source 패드 1개 */
struct v4l2_ctrl_handler ctrl_handler;
struct v4l2_mbus_framefmt fmt;
};
static int my_sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
struct my_sensor *sensor = container_of(sd, struct my_sensor, sd);
if (enable)
return my_sensor_start(sensor); /* I2C 레지스터 설정 */
return my_sensor_stop(sensor);
}
static int my_sensor_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *fmt)
{
/* 포맷 협상: 드라이버가 지원하는 가장 가까운 값으로 조정 */
fmt->format.code = MEDIA_BUS_FMT_SRGGB10_1X10;
fmt->format.width = clamp(fmt->format.width, 640, 3840);
fmt->format.height = clamp(fmt->format.height, 480, 2160);
*v4l2_subdev_state_get_format(state, fmt->pad) = fmt->format;
return 0;
}
static int my_sensor_probe(struct i2c_client *client)
{
struct my_sensor *sensor;
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
v4l2_i2c_subdev_init(&sensor->sd, client, &my_sensor_ops);
sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
return v4l2_async_register_subdev(&sensor->sd);
}
V4L2 컨트롤 프레임워크
V4L2 컨트롤 프레임워크는 밝기, 대비, 노출, 화이트 밸런스 등의 파라미터를 통합 관리합니다. 드라이버는 v4l2_ctrl_handler를 초기화하고 컨트롤을 등록하기만 하면 됩니다.
v4l2_ctrl_handler 초기화
struct v4l2_ctrl_handler *hdl = &mydev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 8); /* hint: 예상 컨트롤 수 */
/* 표준 컨트롤 추가 */
v4l2_ctrl_new_std(hdl, &my_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); /* min, max, step, default */
v4l2_ctrl_new_std(hdl, &my_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 128);
v4l2_ctrl_new_std_menu(hdl, &my_ctrl_ops,
V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_APERTURE_PRIORITY, 0,
V4L2_EXPOSURE_AUTO);
if (hdl->error)
return hdl->error;
/* video_device 또는 v4l2_subdev에 연결 */
mydev->vdev.ctrl_handler = hdl;
/* 또는: mydev->sd.ctrl_handler = hdl; */
코드 설명
- v4l2_ctrl_handler_init컨트롤 핸들러를 초기화합니다. 두 번째 인자는 예상 컨트롤 수의 힌트로, 내부 해시 테이블 크기 결정에 사용됩니다.
include/media/v4l2-ctrls.h에 선언되어 있습니다. - v4l2_ctrl_new_std표준 정수형 컨트롤을 생성합니다. 인자 순서는
(handler, ops, id, min, max, step, default)입니다. 생성된v4l2_ctrl포인터가 반환되며, 실패 시hdl->error에 에러가 누적됩니다. - v4l2_ctrl_new_std_menu메뉴형 컨트롤(열거형 선택)을 생성합니다.
V4L2_CID_EXPOSURE_AUTO처럼 미리 정의된 선택지가 있는 컨트롤에 사용합니다. 세 번째 인자는 최대값, 네 번째는 사용 불가 항목 비트마스크입니다. - hdl->error컨트롤 프레임워크는 에러 누적 패턴을 사용합니다. 개별
v4l2_ctrl_new_*호출의 반환값을 매번 확인할 필요 없이, 모든 컨트롤 등록 후hdl->error한 번만 검사하면 됩니다. - ctrl_handler 연결
video_device.ctrl_handler에 연결하면 유저스페이스에서VIDIOC_G_CTRL/S_CTRL/QUERYCTRL로 접근할 수 있습니다. 서브디바이스의 경우v4l2_subdev.ctrl_handler에 연결합니다.
표준 컨트롤 (V4L2_CID_*)
| 카테고리 | 주요 컨트롤 ID | 설명 |
|---|---|---|
| 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는 복잡한 미디어 파이프라인의 토폴로지(Topology)를 유저스페이스에 노출합니다. 센서, ISP, DMA 엔진 등의 연결 관계를 엔터티/패드/링크 그래프로 표현합니다.
media_device 개요
struct media_device {
struct device *dev;
char model[32];
struct list_head entities; /* media_entity 리스트 */
struct list_head pads;
struct list_head links;
const struct media_device_ops *ops;
...
};
/* 초기화 + 등록 */
media_device_init(mdev);
strscpy(mdev->model, "My Camera", sizeof(mdev->model));
media_device_register(mdev);
/* v4l2_device에 연결 */
v4l2_dev->mdev = mdev;
엔터티/패드/링크
/* 엔터티: 하드웨어 블록 (센서, ISP 등) */
struct media_entity {
char name[32];
u32 function; /* MEDIA_ENT_F_* */
struct media_pad *pads;
u16 num_pads;
...
};
/* 패드: 엔터티의 입출력 포트 */
struct media_pad {
struct media_entity *entity;
u16 index;
u32 flags; /* MEDIA_PAD_FL_SINK/SOURCE */
};
/* 링크 생성: 센서 pad[0] → CSI pad[0] */
media_create_pad_link(&sensor->entity, 0,
&csi->entity, 0,
MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
주요 엔터티 function 값:
| 매크로 | 설명 |
|---|---|
MEDIA_ENT_F_CAM_SENSOR | 카메라 센서 |
MEDIA_ENT_F_VID_IF_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를 사용하여 버퍼와 컨트롤을 원자적(Atomic)으로 묶습니다:
/* Request 생성 */
int req_fd;
ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd);
/* 컨트롤 설정 (H.264 SPS/PPS/Slice 등) */
struct v4l2_ext_controls ctrls = { .request_fd = req_fd, ... };
ioctl(video_fd, VIDIOC_S_EXT_CTRLS, &ctrls);
/* 버퍼 제출 */
buf.request_fd = req_fd;
buf.flags |= V4L2_BUF_FLAG_REQUEST_FD;
ioctl(video_fd, VIDIOC_QBUF, &buf);
/* Request 큐잉 */
ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE, NULL);
Stateless 디코딩 상세 흐름
Stateless 디코더는 호스트(CPU)가 비트스트림 파싱을 수행하고, 프레임별로 디코딩 파라미터(SPS, PPS, 슬라이스 헤더 등)를 하드웨어에 전달합니다. 이 방식은 Request API와 함께 사용됩니다:
/* Stateless H.264 디코딩 전체 흐름 */
/* 1. 코덱 포맷 설정 */
struct v4l2_format out_fmt = { .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE };
out_fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264_SLICE; /* 슬라이스 단위 입력 */
out_fmt.fmt.pix_mp.width = 1920;
out_fmt.fmt.pix_mp.height = 1080;
ioctl(fd, VIDIOC_S_FMT, &out_fmt);
struct v4l2_format cap_fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE };
cap_fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12; /* 디코딩 출력 */
ioctl(fd, VIDIOC_S_FMT, &cap_fmt);
/* 2. 버퍼 할당 + STREAMON */
ioctl(fd, VIDIOC_REQBUFS, &out_reqbufs);
ioctl(fd, VIDIOC_REQBUFS, &cap_reqbufs);
ioctl(fd, VIDIOC_STREAMON, &out_type);
ioctl(fd, VIDIOC_STREAMON, &cap_type);
/* 3. 프레임별 디코딩 루프 */
for (frame = 0; frame < total_frames; frame++) {
/* Request 할당 */
int req_fd;
ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd);
/* SPS 컨트롤 설정 */
struct v4l2_ctrl_h264_sps sps = { ... };
struct v4l2_ext_control ctrls[] = {
{ .id = V4L2_CID_STATELESS_H264_SPS, .ptr = &sps, .size = sizeof(sps) },
{ .id = V4L2_CID_STATELESS_H264_PPS, .ptr = &pps, .size = sizeof(pps) },
{ .id = V4L2_CID_STATELESS_H264_SLICE_PARAMS, .ptr = &slice, .size = sizeof(slice) },
{ .id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, .ptr = &decode, .size = sizeof(decode) },
};
struct v4l2_ext_controls ext = {
.count = 4, .controls = ctrls,
.which = V4L2_CTRL_WHICH_REQUEST_VAL,
.request_fd = req_fd,
};
ioctl(fd, VIDIOC_S_EXT_CTRLS, &ext);
/* 비트스트림 데이터를 OUTPUT 버퍼에 복사 */
memcpy(out_buf_ptr, slice_data, slice_size);
/* OUTPUT 버퍼 제출 (Request에 연결) */
struct v4l2_buffer out_buf = {
.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
.request_fd = req_fd,
.flags = V4L2_BUF_FLAG_REQUEST_FD,
};
ioctl(fd, VIDIOC_QBUF, &out_buf);
/* CAPTURE 버퍼 제출 */
ioctl(fd, VIDIOC_QBUF, &cap_buf);
/* Request 큐잉 → 하드웨어 디코딩 시작 */
ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE, NULL);
/* 결과 대기 + 수거 */
ioctl(fd, VIDIOC_DQBUF, &out_buf); /* OUTPUT 반환 */
ioctl(fd, VIDIOC_DQBUF, &cap_buf); /* 디코딩된 프레임 */
/* Request 재사용 */
ioctl(req_fd, MEDIA_REQUEST_IOC_REINIT, NULL);
}
Stateful vs Stateless 코덱 비교
| 항목 | Stateful | Stateless |
|---|---|---|
| 비트스트림 파싱 | 하드웨어/펌웨어 | 호스트 CPU (userspace) |
| 상태 관리 | 코덱 내부 | 호스트가 Request API로 전달 |
| 참조 프레임 관리 | 하드웨어 자동 | 호스트가 CAPTURE 버퍼 인덱스로 지정 |
| 입력 단위 | NAL/패킷 스트림 | 프레임/슬라이스 단위 |
| 해상도 변경 | SOURCE_CHANGE 이벤트 | 유저스페이스가 직접 감지 |
| 복잡도 | 단순 (API 단순) | 복잡 (파싱 라이브러리 필요) |
| 지연(Latency) | 높음 (내부 버퍼링) | 낮음 (프레임 단위 제어) |
| 대표 드라이버 | 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 트리오) |
| 대역폭 (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) 테이블 패턴
카메라 센서는 여러 해상도/프레임레이트 모드를 지원합니다. 각 모드는 레지스터 테이블로 정의됩니다:
/* 센서 레지스터 설정 */
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 대역폭(Bandwidth) 부족 | 해상도/FPS 낮추기, USB 3.0 포트 사용 |
VIDIOC_REQBUFS: Invalid argument | 잘못된 메모리 타입 / 포맷 미설정 | S_FMT를 먼저 호출 |
| 캡처 이미지가 녹색/깨짐 | 포맷 불일치 (Bayer vs YUV) | 포맷 확인, 디모자이크 적용 |
| 프레임 드롭 | DMA 지연, 버퍼 부족 | 버퍼 수 증가, CPU 부하 확인 |
| 서브디바이스 바인딩 실패 | DT endpoint 미스매치 | remote-endpoint 양방향 확인 |
media_pipeline_start: -EPIPE | 링크 미설정 또는 비활성 | media-ctl -l로 링크 활성화 |
디버깅 도구 요약
| 도구 | 용도 | 예시 명령 |
|---|---|---|
v4l2-ctl --log-status | 드라이버 log_status 콜백 호출 (dmesg에 상세 상태 출력) | v4l2-ctl -d /dev/video0 --log-status |
v4l2-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 호출 추적 | 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로 병목 분석 |
| 프레임 드롭(Drop) | 버퍼 부족, DQBUF 지연, CPU 부하 | 버퍼 수 증가 (4→8), 애플리케이션 처리 시간 단축 |
| 프레임 지연(Latency) 높음 | 버퍼 과다 큐잉, ISP 파이프라인 깊이 | 최소 버퍼 수(3~4) 사용, V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC 확인 |
| DMA 오류 | CMA 메모리 부족, IOMMU 매핑 실패 | dmesg | grep -i cma, CMA 크기 증가, cat /proc/meminfo | grep Cma |
| 캡처 이미지 찢어짐(Tearing) | V-Sync 미동기화, DMA 주소 업데이트 타이밍 | 더블 버퍼링 확인, IRQ 핸들러에서 DMA 주소 전환 |
| USB 대역폭 부족 | 동시 사용 USB 디바이스, 허브 공유 | cat /sys/kernel/debug/usb/devices, USB 3.0 포트 직결 |
Request API 상세
Request API는 V4L2의 원자적(Atomic) 파라미터 적용 메커니즘입니다. 하나의 Request에 버퍼와 컨트롤을 묶어서 하드웨어에 동시 적용할 수 있습니다. 주로 stateless 코덱과 복잡한 카메라 파이프라인에서 사용됩니다.
Request 수명주기
| 단계 | API | 설명 |
|---|---|---|
| 할당 | MEDIA_IOC_REQUEST_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 |
| 코딩 스타일 | 커널 코딩 스타일 준수 | scripts/checkpatch.pl |
| DT 바인딩 | YAML 형식 DT 바인딩 문서 포함 | make dt_binding_check |
| 커밋 메시지 | "media: vendor: description" 형식 | 기존 로그 참고 |
| 메일링 리스트 | linux-media@vger.kernel.org에 패치 제출 | git send-email |
| MAINTAINERS | 드라이버 유지보수자 항목 추가 | scripts/get_maintainer.pl |
| sparse/smatch | 정적 분석 경고 없음 | make C=2 drivers/media/... |
락킹(Locking) 패턴
V4L2 드라이버의 올바른 락킹 전략:
/* 1. video_device.lock — ioctl 직렬화 */
/* video_device에 mutex를 설정하면 V4L2 코어가 모든 ioctl을 직렬화 */
cam->vdev.lock = &cam->mutex;
/* 2. vb2_queue.lock — 버퍼 ioctl 직렬화 */
/* video_device.lock과 같은 mutex를 사용하는 것이 일반적 */
cam->queue.lock = &cam->mutex;
/* 3. spinlock — IRQ 핸들러와 공유 데이터 보호 */
/* buf_list 등 IRQ 컨텍스트에서 접근하는 데이터 */
spin_lock_irqsave(&cam->slock, flags);
list_add_tail(&buf->list, &cam->buf_list);
spin_unlock_irqrestore(&cam->slock, flags);
/* 4. v4l2_ctrl_handler — 컨트롤 프레임워크 내부 락 */
/* v4l2_ctrl_handler_setup()은 lock 내부에서 호출 금지 */
/* s_ctrl 콜백은 handler->lock 하에 호출됨 */
/* 5. 데드락 방지: wait_prepare/wait_finish */
/* vb2가 DQBUF에서 대기할 때 mutex를 해제하는 헬퍼 */
static const struct vb2_ops my_vb2_ops = {
.wait_prepare = vb2_ops_wait_prepare, /* queue->lock 해제 */
.wait_finish = vb2_ops_wait_finish, /* queue->lock 획득 */
...
};
흔한 실수와 방지책
| 실수 | 결과 | 올바른 방법 |
|---|---|---|
stop_streaming에서 버퍼 미반환 | vb2 WARNING, 메모리 누수 | 모든 버퍼를 vb2_buffer_done(ERROR)로 반환 |
start_streaming 실패 시 버퍼 미반환 | vb2 hang | 실패 시 vb2_buffer_done(QUEUED)로 복원 |
| IRQ 핸들러에서 mutex 사용 | 커널 BUG (sleep in atomic) | spinlock_irqsave 사용 |
v4l2_fh 미초기화 | 이벤트/우선순위 미동작 | open에서 v4l2_fh_open() 또는 v4l2_fh_init()+add() |
| device_caps 미설정 | v4l2-compliance 실패 | vdev.device_caps에 정확한 CAP 플래그 설정 |
| timestamp 미기록 | 프레임 타이밍 정보 없음 | IRQ에서 vb2_buf.timestamp = ktime_get_ns() |
| sequence 미증가 | 프레임 드롭 감지 불가 | IRQ에서 vb.sequence = cam->sequence++ |
| remove/disconnect 미처리 | USB 분리 시 커널 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 재초기화 |
참고 링크
- 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
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.