V4L2 (Video4Linux2) 서브시스템 심화
V4L2 프레임워크 아키텍처, videobuf2 버퍼 관리, ioctl 인터페이스, 서브디바이스/컨트롤/Media Controller, M2M 코덱, 카메라 파이프라인, UVC, Device Tree 바인딩, 유저스페이스 도구, 디버깅까지 종합 가이드.
drivers/media/ — V4L2 코어, videobuf2, 서브디바이스 프레임워크, Media Controller, 개별 드라이버가 위치하는 Linux 미디어 서브시스템 최상위 디렉토리입니다.
V4L2 개요
Video4Linux2(V4L2)는 Linux 커널의 비디오 캡처/출력 프레임워크입니다. 원래 Video4Linux(V4L) API가 커널 2.1에서 도입되었으나, 설계상 한계로 커널 2.5에서 V4L2로 전면 재설계되었습니다. V4L2는 웹캠, TV 튜너, 하드웨어 코덱, ISP(Image Signal Processor) 등 모든 비디오 관련 디바이스를 통합 관리합니다.
디바이스 유형
V4L2는 여러 유형의 디바이스 노드를 생성합니다:
| 디바이스 노드 | 매크로 | 용도 |
|---|---|---|
/dev/videoN | VFL_TYPE_VIDEO | 비디오 캡처/출력 디바이스 |
/dev/vbiN | VFL_TYPE_VBI | Vertical Blanking Interval (텔레텍스트 등) |
/dev/radioN | VFL_TYPE_RADIO | 라디오 튜너 |
/dev/swradioN | VFL_TYPE_SDR | Software Defined Radio |
/dev/v4l-touchN | VFL_TYPE_TOUCH | 터치 디바이스 |
/dev/v4l-subdevN | VFL_TYPE_SUBDEV | 서브디바이스 직접 접근 |
/dev/mediaN | — | Media Controller 디바이스 |
커널 소스 구조
| 경로 | 설명 |
|---|---|
drivers/media/v4l2-core/ | V4L2 코어 프레임워크 (v4l2-dev, v4l2-ioctl, v4l2-subdev 등) |
drivers/media/common/videobuf2/ | videobuf2 버퍼 관리 프레임워크 |
drivers/media/mc/ | Media Controller 코어 |
drivers/media/i2c/ | I2C 센서/디코더 서브디바이스 드라이버 |
drivers/media/platform/ | SoC/플랫폼 특화 드라이버 (ISP, 코덱 등) |
drivers/media/usb/ | USB 비디오 드라이버 (uvcvideo 등) |
drivers/media/pci/ | PCI 캡처 카드 드라이버 |
drivers/media/cec/ | HDMI CEC 프레임워크 |
include/media/ | 미디어 서브시스템 헤더 파일 |
include/uapi/linux/videodev2.h | V4L2 UAPI 헤더 (ioctl 정의) |
V4L2 프레임워크 아키텍처
V4L2 프레임워크는 계층적 구조로 설계되어 있습니다. 최상위에 v4l2_device가 있고, 그 아래 하나 이상의 video_device와 v4l2_subdev가 연결됩니다.
v4l2_device 구조체
v4l2_device는 V4L2 드라이버의 최상위 컨테이너입니다. 하나의 물리 디바이스에 대해 하나의 인스턴스를 생성하며, 산하의 모든 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);
video_device 구조체
video_device는 유저스페이스에 노출되는 /dev/videoN 디바이스 노드를 나타냅니다. video_register_device()로 등록하면 캐릭터 디바이스가 생성됩니다.
/* include/media/v4l2-dev.h */
struct video_device {
const struct v4l2_file_operations *fops;
const struct v4l2_ioctl_ops *ioctl_ops;
struct v4l2_device *v4l2_dev;
struct vb2_queue *queue; /* vb2 큐 연결 */
struct v4l2_ctrl_handler *ctrl_handler;
struct mutex *lock;
u32 device_caps; /* V4L2_CAP_* */
enum vfl_devnode_type vfl_type;
int minor;
char name[32];
...
};
/* 등록: type은 VFL_TYPE_VIDEO 등 */
int video_register_device(struct video_device *vdev,
enum vfl_devnode_type type, int nr);
void video_unregister_device(struct video_device *vdev);
v4l2_fh (파일 핸들)
v4l2_fh는 유저스페이스의 open() 호출마다 생성되는 per-open 핸들입니다. 이벤트 구독, 우선순위 관리 등에 사용됩니다.
struct v4l2_fh {
struct list_head list; /* video_device의 fh_list에 연결 */
struct video_device *vdev;
struct v4l2_ctrl_handler *ctrl_handler;
enum v4l2_priority prio; /* 우선순위 */
struct v4l2_events *events; /* 이벤트 큐 */
...
};
/* open/close 콜백에서 사용 */
void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev);
void v4l2_fh_add(struct v4l2_fh *fh);
void v4l2_fh_del(struct v4l2_fh *fh);
void v4l2_fh_exit(struct v4l2_fh *fh);
videobuf2 (vb2) 버퍼 관리
videobuf2(vb2)는 V4L2의 핵심 버퍼 관리 프레임워크입니다. DMA 버퍼 할당, 유저스페이스 매핑, 스트리밍 큐 관리를 담당합니다. 드라이버는 직접 버퍼를 관리할 필요 없이 vb2 콜백만 구현하면 됩니다.
vb2_queue 구조체와 초기화
/* include/media/videobuf2-core.h */
struct vb2_queue {
unsigned int type; /* V4L2_BUF_TYPE_* */
enum vb2_io_modes io_modes; /* VB2_MMAP | VB2_USERPTR | VB2_DMABUF */
const struct vb2_ops *ops; /* 드라이버 콜백 */
const struct vb2_mem_ops *mem_ops; /* 메모리 할당자 */
struct mutex *lock; /* 직렬화 lock */
unsigned int min_queued_buffers;
u32 timestamp_flags;
struct device *dev; /* DMA 용 디바이스 */
...
};
/* 초기화 예제 */
static int my_queue_init(struct my_device *mydev)
{
struct vb2_queue *q = &mydev->vb_queue;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_DMABUF;
q->ops = &my_vb2_ops;
q->mem_ops = &vb2_dma_contig_memops;
q->drv_priv = mydev;
q->buf_struct_size = sizeof(struct my_buffer);
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->lock = &mydev->mutex;
q->dev = mydev->dev;
return vb2_queue_init(q);
}
vb2_buffer / vb2_v4l2_buffer
개별 버퍼는 vb2_buffer로 관리됩니다. V4L2 드라이버는 이를 감싸는 vb2_v4l2_buffer를 사용하며, 드라이버 고유 구조체에 임베딩하는 패턴이 일반적입니다.
/* 드라이버 고유 버퍼 구조체 — 임베딩 패턴 */
struct my_buffer {
struct vb2_v4l2_buffer vb; /* 반드시 첫 번째 멤버 */
struct list_head list; /* 드라이버 내부 큐 */
dma_addr_t paddr; /* DMA 물리 주소 */
};
/* container_of로 변환 */
static inline struct my_buffer *to_my_buffer(struct vb2_v4l2_buffer *vbuf)
{
return container_of(vbuf, struct my_buffer, vb);
}
메모리 타입 비교
| 메모리 타입 | 매크로 | 할당 주체 | 매핑 방식 | 사용 시나리오 |
|---|---|---|---|---|
| MMAP | V4L2_MEMORY_MMAP | 커널 (vb2) | mmap() | 일반적 캡처, 가장 보편적 |
| USERPTR | V4L2_MEMORY_USERPTR | 유저스페이스 | 유저 포인터 전달 | 유저 버퍼 재사용, 레거시 |
| DMABUF | V4L2_MEMORY_DMABUF | 외부 (DMA-BUF) | fd 기반 공유 | GPU/디스플레이 간 제로카피 |
vb2_ops 콜백
드라이버는 vb2_ops 콜백을 구현하여 vb2 프레임워크와 상호작용합니다:
static const struct vb2_ops my_vb2_ops = {
.queue_setup = my_queue_setup, /* 버퍼 수/크기 결정 */
.buf_prepare = my_buf_prepare, /* 큐잉 전 버퍼 검증 */
.buf_queue = my_buf_queue, /* 버퍼를 하드웨어에 전달 */
.start_streaming = my_start_streaming, /* DMA 시작 */
.stop_streaming = my_stop_streaming, /* DMA 중지, 버퍼 반환 */
.buf_init = my_buf_init, /* (선택) 버퍼 초기 설정 */
.buf_cleanup = my_buf_cleanup, /* (선택) 버퍼 정리 */
.wait_prepare = vb2_ops_wait_prepare, /* lock 해제 헬퍼 */
.wait_finish = vb2_ops_wait_finish, /* lock 획득 헬퍼 */
};
각 콜백의 호출 시점:
| 콜백 | 호출 시점 | 주요 역할 |
|---|---|---|
queue_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 언매핑 등 정리 |
스트리밍 흐름
유저스페이스의 전형적인 V4L2 캡처 루프:
/* 1. 버퍼 요청 */
ioctl(fd, VIDIOC_REQBUFS, &reqbufs); /* → queue_setup() */
/* 2. 버퍼 정보 조회 + mmap */
for (i = 0; i < n; i++) {
ioctl(fd, VIDIOC_QUERYBUF, &buf[i]);
buffers[i] = mmap(NULL, buf[i].length,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd, buf[i].m.offset);
}
/* 3. 모든 버퍼 큐잉 */
for (i = 0; i < n; i++)
ioctl(fd, VIDIOC_QBUF, &buf[i]); /* → buf_prepare() → buf_queue() */
/* 4. 스트리밍 시작 */
ioctl(fd, VIDIOC_STREAMON, &type); /* → start_streaming() */
/* 5. 캡처 루프 */
while (running) {
poll(fds, 1, -1); /* 프레임 대기 */
ioctl(fd, VIDIOC_DQBUF, &buf); /* 완료된 버퍼 수거 */
process_frame(buffers[buf.index], buf.bytesused);
ioctl(fd, VIDIOC_QBUF, &buf); /* 버퍼 재큐잉 */
}
/* 6. 스트리밍 중지 */
ioctl(fd, VIDIOC_STREAMOFF, &type); /* → stop_streaming() */
V4L2 ioctl 인터페이스
V4L2는 include/uapi/linux/videodev2.h에 정의된 풍부한 ioctl 세트를 제공합니다. 핵심 ioctl을 범주별로 살펴봅니다.
VIDIOC_QUERYCAP — 디바이스 능력 조회
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
/* cap.capabilities: 전체 디바이스, cap.device_caps: 이 노드의 능력 */
주요 capability 플래그:
| 플래그 | 값 | 의미 |
|---|---|---|
V4L2_CAP_VIDEO_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);
/* 드라이버가 요청값을 가장 가까운 지원값으로 조정할 수 있음 */
주요 픽셀 포맷:
| 매크로 | 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 | 버퍼 상태/오프셋 조회 (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— 컴포즈 가능 최대 범위 */
V4L2 드라이버 개발
V4L2 캡처 드라이버의 핵심 구성 요소를 살펴봅니다.
v4l2_file_operations 설정
static const struct v4l2_file_operations my_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open, /* 기본 핸들러 사용 가능 */
.release = vb2_fop_release, /* vb2 정리 포함 */
.read = vb2_fop_read, /* read() I/O (선택) */
.poll = vb2_fop_poll, /* poll/select 지원 */
.mmap = vb2_fop_mmap, /* mmap 매핑 */
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl 디스패처 */
};
v4l2_ioctl_ops 스켈레톤
static const struct v4l2_ioctl_ops my_ioctl_ops = {
/* 능력 조회 */
.vidioc_querycap = my_querycap,
/* 포맷 */
.vidioc_enum_fmt_vid_cap = my_enum_fmt,
.vidioc_g_fmt_vid_cap = my_g_fmt,
.vidioc_s_fmt_vid_cap = my_s_fmt,
.vidioc_try_fmt_vid_cap = my_try_fmt,
/* 버퍼 — vb2 헬퍼 사용 */
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_expbuf = vb2_ioctl_expbuf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
/* 입력 */
.vidioc_enum_input = my_enum_input,
.vidioc_g_input = my_g_input,
.vidioc_s_input = my_s_input,
/* 이벤트 */
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
완전한 캡처 드라이버 예제
#include <linux/module.h>
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/videobuf2-dma-contig.h>
struct my_cam {
struct v4l2_device v4l2_dev;
struct video_device vdev;
struct vb2_queue queue;
struct v4l2_ctrl_handler ctrl_handler;
struct mutex lock;
struct v4l2_format fmt;
struct list_head buf_list;
spinlock_t slock;
};
struct my_buffer {
struct vb2_v4l2_buffer vb;
struct list_head list;
};
/* vb2 콜백 */
static int my_queue_setup(struct vb2_queue *q,
unsigned int *nbuffers, unsigned int *nplanes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct my_cam *cam = vb2_get_drv_priv(q);
sizes[0] = cam->fmt.fmt.pix.sizeimage;
*nplanes = 1;
return 0;
}
static void my_buf_queue(struct vb2_buffer *vb)
{
struct my_cam *cam = vb2_get_drv_priv(vb->vb2_queue);
struct my_buffer *buf = container_of(
to_vb2_v4l2_buffer(vb), struct my_buffer, vb);
unsigned long flags;
spin_lock_irqsave(&cam->slock, flags);
list_add_tail(&buf->list, &cam->buf_list);
spin_unlock_irqrestore(&cam->slock, flags);
}
static int my_start_streaming(struct vb2_queue *q, unsigned int count)
{
/* 하드웨어 DMA 시작 */
return 0;
}
static void my_stop_streaming(struct vb2_queue *q)
{
struct my_cam *cam = vb2_get_drv_priv(q);
struct my_buffer *buf, *tmp;
unsigned long flags;
/* 하드웨어 DMA 중지 */
spin_lock_irqsave(&cam->slock, flags);
list_for_each_entry_safe(buf, tmp, &cam->buf_list, list) {
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
}
spin_unlock_irqrestore(&cam->slock, flags);
}
/* probe 함수 (간략화) */
static int my_cam_probe(struct platform_device *pdev)
{
struct my_cam *cam;
int ret;
cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL);
mutex_init(&cam->lock);
spin_lock_init(&cam->slock);
INIT_LIST_HEAD(&cam->buf_list);
/* v4l2_device 등록 */
ret = v4l2_device_register(&pdev->dev, &cam->v4l2_dev);
/* vb2_queue 초기화 */
cam->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
cam->queue.io_modes = VB2_MMAP | VB2_DMABUF;
cam->queue.ops = &my_vb2_ops;
cam->queue.mem_ops = &vb2_dma_contig_memops;
cam->queue.drv_priv = cam;
cam->queue.buf_struct_size = sizeof(struct my_buffer);
cam->queue.lock = &cam->lock;
cam->queue.dev = &pdev->dev;
vb2_queue_init(&cam->queue);
/* video_device 설정 */
cam->vdev.v4l2_dev = &cam->v4l2_dev;
cam->vdev.queue = &cam->queue;
cam->vdev.fops = &my_fops;
cam->vdev.ioctl_ops = &my_ioctl_ops;
cam->vdev.lock = &cam->lock;
cam->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
strscpy(cam->vdev.name, "my-cam", sizeof(cam->vdev.name));
video_set_drvdata(&cam->vdev, cam);
return video_register_device(&cam->vdev, VFL_TYPE_VIDEO, -1);
}
V4L2 서브디바이스 프레임워크
복잡한 미디어 파이프라인(센서 → CSI-2 리시버 → ISP → DMA 엔진)에서 각 하드웨어 블록을 v4l2_subdev로 모델링합니다. 서브디바이스는 v4l2_device에 등록되며, Media Controller를 통해 연결됩니다.
v4l2_subdev 구조체
/* include/media/v4l2-subdev.h */
struct v4l2_subdev {
struct list_head list; /* v4l2_device의 subdevs 리스트 */
struct v4l2_device *v4l2_dev;
const struct v4l2_subdev_ops *ops;
const struct v4l2_subdev_internal_ops *internal_ops;
struct v4l2_ctrl_handler *ctrl_handler;
struct media_entity entity; /* Media Controller 엔터티 */
u32 flags; /* V4L2_SUBDEV_FL_* */
char name[52];
...
};
/* 등록/해제 */
int v4l2_device_register_subdev(struct v4l2_device *, struct v4l2_subdev *);
void v4l2_device_unregister_subdev(struct v4l2_subdev *);
v4l2_subdev_ops (core/video/pad)
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core; /* log_status, ioctl 등 */
const struct v4l2_subdev_video_ops *video; /* s_stream 등 */
const struct v4l2_subdev_pad_ops *pad; /* 포맷/Selection 협상 */
const struct v4l2_subdev_sensor_ops *sensor; /* 센서 전용 */
};
/* video ops — 스트리밍 제어 */
struct v4l2_subdev_video_ops {
int (*s_stream)(struct v4l2_subdev *sd, int enable);
int (*g_frame_interval)(...);
int (*s_frame_interval)(...);
...
};
/* pad ops — 포맷 협상 */
struct v4l2_subdev_pad_ops {
int (*enum_mbus_code)(...); /* 미디어 버스 코드 열거 */
int (*get_fmt)(...); /* 패드 포맷 조회 */
int (*set_fmt)(...); /* 패드 포맷 설정 */
int (*get_selection)(...); /* 크롭/컴포즈 조회 */
int (*set_selection)(...); /* 크롭/컴포즈 설정 */
int (*enable_streams)(...); /* Streams API */
int (*disable_streams)(...); /* Streams API */
...
};
패드 오퍼레이션과 미디어 버스 포맷
서브디바이스의 패드(입출력 포트)는 미디어 버스 포맷(v4l2_mbus_framefmt)으로 데이터 형식을 협상합니다:
struct v4l2_mbus_framefmt {
__u32 width; /* 이미지 너비 */
__u32 height; /* 이미지 높이 */
__u32 code; /* MEDIA_BUS_FMT_* */
__u32 field; /* V4L2_FIELD_NONE 등 */
__u32 colorspace; /* V4L2_COLORSPACE_* */
...
};
/* 주요 미디어 버스 코드 예시 */
/* MEDIA_BUS_FMT_SRGGB10_1X10 — 10-bit Bayer RGGB */
/* MEDIA_BUS_FMT_YUYV8_2X8 — YUV 4:2:2 8-bit */
/* MEDIA_BUS_FMT_UYVY8_1X16 — YUV 4:2:2 16-bit bus */
/* MEDIA_BUS_FMT_RGB888_1X24 — RGB 24-bit */
Streams API (멀티플렉스 라우팅)
최신 커널(6.3+)의 Streams API는 하나의 패드에서 여러 데이터 스트림을 다중화할 수 있습니다. CSI-2 가상 채널이나 멀티카메라 시스템에서 활용됩니다:
/* 라우팅 설정 */
struct v4l2_subdev_route routes[] = {
{ .sink_pad = 0, .sink_stream = 0,
.source_pad = 1, .source_stream = 0,
.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE },
{ .sink_pad = 0, .sink_stream = 1,
.source_pad = 2, .source_stream = 0,
.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE },
};
/* CSI-2 VC0 → 패드1, CSI-2 VC1 → 패드2 라우팅 */
I2C 센서 서브디바이스 드라이버 예제
struct my_sensor {
struct v4l2_subdev sd;
struct media_pad pad; /* source 패드 1개 */
struct v4l2_ctrl_handler ctrl_handler;
struct v4l2_mbus_framefmt fmt;
};
static int my_sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
struct my_sensor *sensor = container_of(sd, struct my_sensor, sd);
if (enable)
return my_sensor_start(sensor); /* I2C 레지스터 설정 */
return my_sensor_stop(sensor);
}
static int my_sensor_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *fmt)
{
/* 포맷 협상: 드라이버가 지원하는 가장 가까운 값으로 조정 */
fmt->format.code = MEDIA_BUS_FMT_SRGGB10_1X10;
fmt->format.width = clamp(fmt->format.width, 640, 3840);
fmt->format.height = clamp(fmt->format.height, 480, 2160);
*v4l2_subdev_state_get_format(state, fmt->pad) = fmt->format;
return 0;
}
static int my_sensor_probe(struct i2c_client *client)
{
struct my_sensor *sensor;
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
v4l2_i2c_subdev_init(&sensor->sd, client, &my_sensor_ops);
sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
return v4l2_async_register_subdev(&sensor->sd);
}
V4L2 컨트롤 프레임워크
V4L2 컨트롤 프레임워크는 밝기, 대비, 노출, 화이트 밸런스 등의 파라미터를 통합 관리합니다. 드라이버는 v4l2_ctrl_handler를 초기화하고 컨트롤을 등록하기만 하면 됩니다.
v4l2_ctrl_handler 초기화
struct v4l2_ctrl_handler *hdl = &mydev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 8); /* hint: 예상 컨트롤 수 */
/* 표준 컨트롤 추가 */
v4l2_ctrl_new_std(hdl, &my_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); /* min, max, step, default */
v4l2_ctrl_new_std(hdl, &my_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 128);
v4l2_ctrl_new_std_menu(hdl, &my_ctrl_ops,
V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_APERTURE_PRIORITY, 0,
V4L2_EXPOSURE_AUTO);
if (hdl->error)
return hdl->error;
/* video_device 또는 v4l2_subdev에 연결 */
mydev->vdev.ctrl_handler = hdl;
/* 또는: mydev->sd.ctrl_handler = hdl; */
표준 컨트롤 (V4L2_CID_*)
| 카테고리 | 주요 컨트롤 ID | 설명 |
|---|---|---|
| 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);
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 리시버 등 인터페이스 브리지 |
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 처리 */
}
유저스페이스 API와 media-ctl 예제
# 토폴로지 확인
$ media-ctl -d /dev/media0 -p
# 링크 설정: 센서 → CSI 활성화
$ media-ctl -d /dev/media0 -l '"imx219 0-0010":0 -> "csi2-rx":0 [1]'
# 서브디바이스 포맷 설정
$ media-ctl -d /dev/media0 -V '"imx219 0-0010":0 [fmt:SRGGB10_1X10/1920x1080]'
$ media-ctl -d /dev/media0 -V '"csi2-rx":1 [fmt:SRGGB10_1X10/1920x1080]'
V4L2 M2M (Memory-to-Memory)
M2M 프레임워크는 입력 버퍼(OUTPUT 큐)에서 데이터를 읽어 처리 후 출력 버퍼(CAPTURE 큐)에 쓰는 하드웨어 코덱/스케일러를 모델링합니다.
M2M 프레임워크 구조
struct v4l2_m2m_dev; /* M2M 디바이스 — v4l2_m2m_init()으로 생성 */
struct v4l2_m2m_ctx; /* per-open context — OUTPUT/CAPTURE 큐 쌍 */
struct v4l2_m2m_ops {
void (*device_run)(void *priv); /* 작업 실행 */
int (*job_ready)(void *priv); /* 실행 가능 여부 */
void (*job_abort)(void *priv); /* 작업 취소 */
};
/* 초기화 */
m2m_dev = v4l2_m2m_init(&my_m2m_ops);
/* open 시 context 생성 */
ctx->m2m_ctx = v4l2_m2m_ctx_init(m2m_dev, ctx, my_queue_init);
/* device_run에서: 입력/출력 버퍼 가져오기 */
src = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
/* 하드웨어에 작업 전달 */
/* IRQ 핸들러에서: 완료 알림 */
v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE);
v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE);
v4l2_m2m_job_finish(m2m_dev, ctx->m2m_ctx);
Stateful 코덱
Stateful 코덱(H.264/HEVC/VP9 하드웨어 인코더/디코더)은 내부에 상태 머신을 가집니다:
| 단계 | 디코더 동작 | 인코더 동작 |
|---|---|---|
| 초기화 | OUTPUT 포맷 설정 (압축 스트림) | CAPTURE 포맷 설정 (압축 출력) |
| 헤더 파싱 | 비트스트림 헤더 제출 → SOURCE_CHANGE 이벤트 | — |
| 해상도 협상 | CAPTURE 포맷 재협상 (G_FMT로 확인) | — |
| 스트리밍 | OUTPUT ↔ CAPTURE QBUF/DQBUF 루프 | OUTPUT ↔ CAPTURE QBUF/DQBUF 루프 |
| 플러시 | 빈 OUTPUT 버퍼 제출 → EOS 이벤트 | V4L2_ENC_CMD_STOP → EOS |
Stateless 코덱 (Request API)
Stateless 코덱은 프레임 단위로 디코딩 파라미터를 전달합니다. Request API를 사용하여 버퍼와 컨트롤을 원자적으로 묶습니다:
/* Request 생성 */
int req_fd;
ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd);
/* 컨트롤 설정 (H.264 SPS/PPS/Slice 등) */
struct v4l2_ext_controls ctrls = { .request_fd = req_fd, ... };
ioctl(video_fd, VIDIOC_S_EXT_CTRLS, &ctrls);
/* 버퍼 제출 */
buf.request_fd = req_fd;
buf.flags |= V4L2_BUF_FLAG_REQUEST_FD;
ioctl(video_fd, VIDIOC_QBUF, &buf);
/* Request 큐잉 */
ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE, NULL);
주요 stateless 코덱 드라이버:
| 드라이버 | SoC | 지원 코덱 |
|---|---|---|
hantro | Rockchip, NXP | H.264, VP8, MPEG-2, VP9, AV1 |
cedrus | Allwinner | H.264, HEVC, MPEG-2, VP8 |
rkvdec | Rockchip | H.264, VP9, HEVC |
visl | 가상 (테스트용) | 모든 stateless 코덱 |
카메라 서브시스템
임베디드 카메라는 여러 하드웨어 블록이 파이프라인으로 연결됩니다. V4L2 서브디바이스와 Media Controller를 조합하여 이 파이프라인을 모델링합니다.
카메라 파이프라인 구조
CSI-2 / MIPI 인터페이스
MIPI CSI-2는 임베디드 카메라의 표준 인터페이스입니다:
| 항목 | 설명 |
|---|---|
| 물리 계층 | D-PHY (최대 4.5 Gbps/lane), C-PHY (최대 6 Gsps/trio) |
| 레인 수 | 1~4 데이터 레인 + 1 클럭 레인 (D-PHY) |
| 가상 채널 | 0~3 (VC), 하나의 물리 링크에 최대 4 스트림 다중화 |
| 데이터 타입 | RAW8/10/12/14, YUV422, RGB888, 임베디드 데이터 등 |
| 패킷 구조 | Short Packet (FS/FE/LS/LE) + Long Packet (이미지 데이터) |
ISP 파이프라인 블록
ISP는 RAW Bayer 데이터를 처리하여 최종 이미지를 생성합니다:
| 블록 | 기능 |
|---|---|
| Black Level Correction | 센서 블랙 레벨 보정 |
| Defect Pixel Correction | 불량 픽셀 보정 |
| Lens Shading Correction | 렌즈 비네팅 보정 |
| Demosaic (CFA) | Bayer → RGB 변환 |
| White Balance | 색온도 보정 |
| Color Correction Matrix | 색 공간 변환 |
| Gamma Correction | 감마 커브 적용 |
| Noise Reduction | 공간/시간 노이즈 제거 |
| Sharpening | 엣지 강조 |
| Color Space Conversion | RGB → YUV 변환 |
주요 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/ |
센서 드라이버 패턴과 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) |
커널 uvcvideo 드라이버
/* 커널 설정 */
CONFIG_USB_VIDEO_CLASS=m
/* 디버그 레벨 설정 */
$ echo 0xffff > /sys/module/uvcvideo/parameters/trace
/* UVC 디바이스 정보 확인 */
$ lsusb -d 046d:0825 -v | grep -A5 VideoControl
/* Extension Unit 접근 (유저스페이스) */
/* libuvc 또는 직접 UVCIOC_CTRL_QUERY ioctl 사용 */
struct uvc_xu_control_query xquery = {
.unit = 3, /* Extension Unit ID */
.selector = 1, /* 컨트롤 셀렉터 */
.query = UVC_GET_CUR, /* GET/SET */
.size = 4,
.data = buf,
};
ioctl(fd, UVCIOC_CTRL_QUERY, &xquery);
Device Tree 바인딩
임베디드 카메라 시스템에서 센서, CSI-2 리시버, ISP의 연결은 Device Tree로 기술합니다.
센서 DT 바인딩 예제
/* IMX219 센서 DT 바인딩 */
&i2c1 {
imx219: sensor@10 {
compatible = "sony,imx219";
reg = <0x10>;
clocks = <&clk_24mhz>;
clock-names = "xclk";
/* 전원 */
VANA-supply = <®_2v8>;
VDIG-supply = <®_1v8>;
VDDL-supply = <®_1v2>;
/* GPIO 리셋 */
reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
/* CSI-2 포트 */
port {
imx219_out: endpoint {
remote-endpoint = <&csi_in>;
data-lanes = <1 2>; /* 2 데이터 레인 */
clock-lanes = <0>;
link-frequencies = /bits/ 64 <456000000>;
};
};
};
};
CSI-2 리시버 바인딩 예제
/* CSI-2 리시버 DT 바인딩 */
csi2: csi@fe800000 {
compatible = "rockchip,rk3588-mipi-csi2";
reg = <0x0 0xfe800000 0x0 0x10000>;
clocks = <&cru SCLK_CSI2>;
clock-names = "pclk";
ports {
#address-cells = <1>;
#size-cells = <0>;
/* sink: 센서 연결 */
port@0 {
reg = <0>;
csi_in: endpoint {
remote-endpoint = <&imx219_out>;
data-lanes = <1 2>;
};
};
/* source: ISP 연결 */
port@1 {
reg = <1>;
csi_out: endpoint {
remote-endpoint = <&isp_in>;
};
};
};
};
V4L2 fwnode 파싱 API
/* 드라이버에서 DT endpoint 파싱 */
struct v4l2_fwnode_endpoint ep = {
.bus_type = V4L2_MBUS_CSI2_DPHY,
};
struct fwnode_handle *fwnode;
fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
v4l2_fwnode_endpoint_alloc_parse(fwnode, &ep);
/* 파싱 결과 */
ep.bus.mipi_csi2.num_data_lanes; /* 레인 수 */
ep.bus.mipi_csi2.data_lanes[0]; /* 레인 매핑 */
ep.link_frequencies[0]; /* 링크 주파수 */
fwnode_handle_put(fwnode);
유저스페이스 도구
v4l2-ctl
v4l2-ctl은 V4L2 디바이스의 범용 CLI 도구입니다 (v4l-utils 패키지):
# 디바이스 정보 확인
$ v4l2-ctl -d /dev/video0 --all
# 지원 포맷 열거
$ v4l2-ctl -d /dev/video0 --list-formats-ext
# 포맷/해상도 설정
$ v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=YUYV
# 프레임 캡처 (10프레임을 파일로)
$ v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10 --stream-to=capture.raw
# 컨트롤 조회/설정
$ v4l2-ctl -d /dev/video0 --list-ctrls
$ v4l2-ctl -d /dev/video0 --set-ctrl=brightness=180,contrast=200
# DMABUF 내보내기 캡처
$ v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 --stream-to=frame.raw --stream-dmabuf
media-ctl
# 토폴로지 텍스트 출력
$ media-ctl -d /dev/media0 -p
# 토폴로지 DOT 그래프 생성
$ media-ctl -d /dev/media0 --print-dot | dot -Tpng -o topology.png
# 링크 활성화
$ media-ctl -l '"sensor":0 -> "csi-rx":0 [1]'
# 서브디바이스 포맷 설정
$ media-ctl -V '"sensor":0 [fmt:SGRBG10_1X10/1920x1080 field:none]'
v4l2-compliance (적합성 테스트)
# 전체 적합성 테스트 실행
$ v4l2-compliance -d /dev/video0
# 스트리밍 테스트 포함
$ v4l2-compliance -d /dev/video0 -s
# Media Controller 테스트
$ v4l2-compliance -m /dev/media0
# 결과 해석 */
# Total for device /dev/video0: 95, Succeeded: 93, Failed: 2, Warnings: 0
v4l2-compliance를 통과해야 합니다. 실패 항목이 있으면 리뷰에서 거부됩니다.
libcamera / GStreamer / FFmpeg 연동
# libcamera — 복잡한 카메라 파이프라인 추상화
$ cam --list # 카메라 열거
$ cam -c1 --capture=10 # 10프레임 캡처
# GStreamer — V4L2 소스 파이프라인
$ gst-launch-1.0 v4l2src device=/dev/video0 ! \
video/x-raw,width=1920,height=1080 ! videoconvert ! autovideosink
# FFmpeg — V4L2 캡처 + 인코딩
$ ffmpeg -f v4l2 -video_size 1920x1080 -i /dev/video0 -c:v libx264 output.mp4
# FFmpeg — V4L2 M2M 하드웨어 인코더
$ ffmpeg -f v4l2 -i /dev/video0 -c:v h264_v4l2m2m -b:v 4M output.mp4
디버깅
커널 디버그 메시지
/* 드라이버 내 디버그 출력 */
v4l2_dbg(1, debug, &mydev->v4l2_dev, "format: %dx%d\n", w, h);
v4l2_info(&mydev->v4l2_dev, "device registered\n");
v4l2_err(&mydev->v4l2_dev, "failed to start streaming\n");
/* 모듈 파라미터로 디버그 레벨 제어 */
static int debug;
module_param(debug, int, 0644);
/* 서브디바이스 디버그 */
v4l2_subdev_dbg(1, debug, sd, "setting format\n");
/* 런타임에 디버그 활성화 */
$ echo 2 > /sys/module/<driver>/parameters/debug
자주 발생하는 문제와 해결
| 증상 | 원인 | 해결 |
|---|---|---|
VIDIOC_DQBUF: No such 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-compliance | ioctl 적합성 테스트 |
media-ctl -p | Media Controller 토폴로지 덤프 |
dmesg | grep v4l2 | 드라이버 등록/에러 메시지 |
ftrace | V4L2 ioctl 추적 (v4l2_ioctl 필터) |
/sys/kernel/debug/ | 드라이버별 debugfs 엔트리 |
perf trace -e v4l2:* | V4L2 tracepoint 추적 |
주요 Kconfig 옵션
| CONFIG 옵션 | 설명 |
|---|---|
CONFIG_MEDIA_SUPPORT | 미디어 서브시스템 최상위 스위치 |
CONFIG_MEDIA_CAMERA_SUPPORT | 카메라 디바이스 지원 |
CONFIG_MEDIA_PLATFORM_SUPPORT | 플랫폼(SoC) 미디어 드라이버 |
CONFIG_VIDEO_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)를 활성화한 뒤, 필요한 카테고리(카메라/코덱/플랫폼)를 선택합니다. 개별 드라이버는 해당 카테고리 하위에 위치합니다.