00 V4L2 mimarisi: /dev/videoN ve subdev zinciri
Video4Linux2 (V4L2), Linux'un standart video yakalama ve çıkış framework'üdür. Basit bir USB kameradan karmaşık MIPI CSI + ISP pipeline'larına kadar tek bir API ile yönetilir.
Temel mimari
V4L2'nin temel nesnesi /dev/videoN cihaz dosyasıdır. Bu dosya üzerinden ioctl çağrıları ile kamera formatı ayarlanır, buffer'lar tahsis edilir ve frame'ler yakalanır. Karmaşık donanım pipeline'larında ise birden fazla bileşen subdev (alt cihaz) olarak modellenir; her subdev /dev/v4l-subdevN dosyasıyla temsil edilir.
MIPI CSI sensör → MIPI CSI-2 alıcı → ISP → Scaler → /dev/video0
↓ ↓ ↓ ↓
/dev/v4l-subdev0 /dev/v4l-subdev1 ... media controller (/dev/mediaX)
V4L2 cihaz türleri
| Cihaz dosyası | Tür | Açıklama |
|---|---|---|
| /dev/videoN | Video cihazı | Frame yakalama/çıkış ana arayüzü |
| /dev/v4l-subdevN | Subdev | Sensör, ISP, CSI alıcı, scaler |
| /dev/mediaN | Media controller | Pipeline bağlantı yönetimi |
MIPI CSI pipeline bileşenleri
Gömülü sistemlerde kamera genellikle MIPI CSI-2 arayüzü üzerinden bağlanır. Bu pipeline birkaç ayrı bileşenden oluşur:
V4L2 kernel mimarisi
# Tüm V4L2 cihazlarını listele
v4l2-ctl --list-devices
# Integrated Camera: Integrated Camera (usb-0000:00:14.0-6):
# /dev/video0
# /dev/video1
# imx219 4-0010: imx219 (platform:fe801000.csi):
# /dev/video2
# Media controller cihazları
ls /dev/media*
# /dev/media0
# Subdev'leri listele
ls /dev/v4l-subdev*
Bu bölümde
- V4L2: /dev/videoN (frame yakalama) + /dev/v4l-subdevN (bileşen ayarı) + /dev/mediaN (pipeline)
- MIPI CSI pipeline: sensör → CSI-2 alıcı → ISP → scaler → DMA → /dev/video0
- v4l2-ctl --list-devices: sistemdeki tüm V4L2 cihazlarını listeler
- Basit USB kameralar genellikle yalnızca /dev/video0; gömülü kameralar ek subdev ve media controller
01 v4l2-ctl ile temel kullanım
v4l2-ctl, V4L2 cihazlarını komut satırından yönetmek, test etmek ve format ayarlamak için temel araçtır. v4l-utils paketinde gelir.
Kurulum ve cihaz bilgisi
# Kurulum
sudo apt-get install -y v4l-utils
# Cihaz kapabilitelerini sorgula
v4l2-ctl -d /dev/video0 --all
# Kısa kapabilite özeti
v4l2-ctl -d /dev/video0 --info
# Driver name : uvcvideo
# Card type : HD USB Camera
# Bus info : usb-0000:00:14.0-6
# Driver version: 6.6.0
# Capabilities : 0x84a00001
# Video Capture
# Streaming
Desteklenen formatları sorgulama
# Desteklenen piksel formatları ve çözünürlükler
v4l2-ctl -d /dev/video0 --list-formats-ext
# ioctl: VIDIOC_ENUM_FMT
# Type: Video Capture
# [0]: 'YUYV' (YUYV 4:2:2)
# Size: Discrete 640x480
# Interval: Discrete 0.033s (30.000 fps)
# Size: Discrete 1280x720
# Interval: Discrete 0.033s (30.000 fps)
# [1]: 'MJPG' (Motion-JPEG)
# Size: Discrete 1920x1080
# Interval: Discrete 0.033s (30.000 fps)
# Mevcut format ayarı
v4l2-ctl -d /dev/video0 --get-fmt-video
# Format Video Capture:
# Width/Height : 640/480
# Pixel Format : 'YUYV' (YUYV 4:2:2)
# Bytes per Line : 1280
# Size Image : 614400
Format ayarlama ve streaming
# Video formatını ayarla
v4l2-ctl -d /dev/video0 \
--set-fmt-video=width=1280,height=720,pixelformat=YUYV
# FPS ayarla
v4l2-ctl -d /dev/video0 \
--set-parm=30
# Tek frame yakala (JPEG)
v4l2-ctl -d /dev/video0 \
--set-fmt-video=width=1920,height=1080,pixelformat=MJPG \
--stream-mmap --stream-count=1 \
--stream-to=frame.jpg
# Sürekli streaming (10 saniye, her frame ayrı dosya)
v4l2-ctl -d /dev/video0 \
--stream-mmap --stream-count=300 \
--stream-to=frames/frame-%04d.raw
V4L2 kontrolleri
# Tüm kontrolleri listele
v4l2-ctl -d /dev/video0 --list-ctrls
# brightness 0x00980900 (int): min=-64 max=64 step=1 default=0 value=0
# contrast 0x00980901 (int): min=0 max=64 step=1 default=32 value=32
# exposure 0x00980911 (int): min=3 max=2047 step=1 default=250 value=250
# Kontrol değeri oku
v4l2-ctl -d /dev/video0 --get-ctrl=brightness
# Kontrol değeri yaz
v4l2-ctl -d /dev/video0 --set-ctrl=brightness=10
v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1
v4l2-ctl -d /dev/video0 --set-ctrl=exposure=500
Bu bölümde
- v4l2-ctl --list-formats-ext: desteklenen piksel formatları, çözünürlükler ve FPS değerleri
- --set-fmt-video=width=X,height=Y,pixelformat=FOURCC: format ayarlama
- --stream-mmap --stream-count=N --stream-to=file: frame yakalama (programsız)
- --list-ctrls / --set-ctrl: brightness, contrast, exposure gibi kamera parametreleri
02 V4L2 ioctl API: temel operasyonlar
V4L2 uygulamaları /dev/videoN dosyası üzerinde ioctl çağrıları yapar. Temel akış: cihazı aç → kapabilitesini sorgula → format ayarla → buffer'lar tahsis et → streaming başlat → frame oku.
VIDIOC_QUERYCAP: kapabilite sorgulama
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
int main(void)
{
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) { perror("open"); return 1; }
struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
perror("VIDIOC_QUERYCAP"); return 1;
}
printf("Driver: %s\n", cap.driver);
printf("Card: %s\n", cap.card);
printf("Bus: %s\n", cap.bus_info);
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
fprintf(stderr, "Video capture desteklenmiyor\n");
return 1;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf(stderr, "Streaming desteklenmiyor\n");
return 1;
}
return 0;
}
VIDIOC_S_FMT: format ayarlama
int set_format(int fd, uint32_t width, uint32_t height, uint32_t pixfmt)
{
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = pixfmt; /* V4L2_PIX_FMT_YUYV vb. */
fmt.fmt.pix.field = V4L2_FIELD_NONE; /* Progressive scan */
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
perror("VIDIOC_S_FMT");
return -1;
}
/* Sürücü istediğimiz formatı değiştirmiş olabilir — gerçek değerleri oku */
printf("Ayarlanan format: %dx%d\n",
fmt.fmt.pix.width, fmt.fmt.pix.height);
printf("Bytes per line: %u\n", fmt.fmt.pix.bytesperline);
printf("Size image: %u\n", fmt.fmt.pix.sizeimage);
return 0;
}
/* VIDIOC_TRY_FMT: format desteklenip desteklenmediğini test et (değişiklik yapmaz) */
int try_format(int fd, uint32_t width, uint32_t height, uint32_t pixfmt)
{
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = pixfmt;
if (ioctl(fd, VIDIOC_TRY_FMT, &fmt) < 0) {
perror("VIDIOC_TRY_FMT");
return -1;
}
printf("Desteklenen en yakın: %dx%d\n",
fmt.fmt.pix.width, fmt.fmt.pix.height);
return 0;
}
VIDIOC_REQBUFS: buffer talep etme
int request_buffers(int fd, uint32_t count, uint32_t memory_type)
{
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = count; /* İstenen buffer sayısı */
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = memory_type; /* V4L2_MEMORY_MMAP, USERPTR veya DMABUF */
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
perror("VIDIOC_REQBUFS");
return -1;
}
if (req.count < 2) {
fprintf(stderr, "Yetersiz buffer: %u\n", req.count);
return -1;
}
printf("Tahsis edilen buffer sayısı: %u\n", req.count);
return req.count;
}
/* VIDIOC_QUERYBUF: buffer bilgisini sorgula (MMAP için offset) */
void query_buffer(int fd, uint32_t index, struct v4l2_buffer *buf)
{
memset(buf, 0, sizeof(*buf));
buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf->memory = V4L2_MEMORY_MMAP;
buf->index = index;
if (ioctl(fd, VIDIOC_QUERYBUF, buf) < 0)
perror("VIDIOC_QUERYBUF");
}
/* VIDIOC_QBUF: buffer'ı kuyruğa ekle (sürücüye ver) */
/* VIDIOC_DQBUF: dolu buffer'ı kuyruktan al (uygulama işler) */
Streaming başlatma ve durdurma
int start_streaming(int fd)
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
perror("VIDIOC_STREAMON");
return -1;
}
return 0;
}
int stop_streaming(int fd)
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
perror("VIDIOC_STREAMOFF");
return -1;
}
return 0;
}
Bu bölümde
- VIDIOC_QUERYCAP: sürücü adı, kart bilgisi, desteklenen kapabiliteler (capture/streaming)
- VIDIOC_S_FMT: çözünürlük ve piksel formatı ayarla; sürücü en yakın desteklenen değeri döner
- VIDIOC_TRY_FMT: format test et (değişiklik yapmadan)
- VIDIOC_REQBUFS → VIDIOC_QUERYBUF → QBUF/DQBUF: buffer yönetim döngüsü
03 Buffer yönetimi: MMAP, USERPTR ve DMA-BUF
V4L2 üç farklı bellek yönetimi yöntemi sunar. MMAP en yaygın kullanılandır; USERPTR uygulamanın kendi belleğini kullanır; DMA-BUF sıfır kopya pipeline'lar için kullanılır.
MMAP buffer yönetimi
Kernel, buffer'ları tahsis eder. Uygulama mmap() ile bu belleğe erişir. En güvenli ve en yaygın yöntemdir.
#include <sys/mman.h>
#define BUFFER_COUNT 4
struct buffer {
void *start;
size_t length;
};
struct buffer buffers[BUFFER_COUNT];
int setup_mmap_buffers(int fd)
{
/* Buffer talep et */
struct v4l2_requestbuffers req = {
.count = BUFFER_COUNT,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP,
};
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
perror("VIDIOC_REQBUFS"); return -1;
}
/* Her buffer'ı mmap et */
for (uint32_t i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP,
.index = i,
};
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
perror("VIDIOC_QUERYBUF"); return -1;
}
buffers[i].length = buf.length;
buffers[i].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, buf.m.offset);
if (buffers[i].start == MAP_FAILED) {
perror("mmap"); return -1;
}
/* Buffer'ı kuyruğa ekle (sürücüye ver) */
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("VIDIOC_QBUF"); return -1;
}
}
return 0;
}
int capture_frame(int fd, void **data, size_t *size,
struct timeval *timestamp)
{
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP,
};
/* Dolu buffer al */
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
perror("VIDIOC_DQBUF"); return -1;
}
*data = buffers[buf.index].start;
*size = buf.bytesused;
*timestamp = buf.timestamp; /* Kernel timestamp */
/* İşle... */
/* Buffer'ı geri ver */
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("VIDIOC_QBUF"); return -1;
}
return 0;
}
void cleanup_mmap_buffers(uint32_t count)
{
for (uint32_t i = 0; i < count; i++)
munmap(buffers[i].start, buffers[i].length);
}
USERPTR buffer yönetimi
Uygulama kendi tahsis ettiği belleği sürücüye verir. DMA scatter-gather uyumlu bellek gerekebilir.
int setup_userptr_buffers(int fd, size_t frame_size)
{
struct v4l2_requestbuffers req = {
.count = BUFFER_COUNT,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_USERPTR,
};
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
perror("VIDIOC_REQBUFS"); return -1;
}
for (uint32_t i = 0; i < BUFFER_COUNT; i++) {
/* Page-aligned bellek tahsis et */
if (posix_memalign(&buffers[i].start, getpagesize(), frame_size)) {
return -1;
}
buffers[i].length = frame_size;
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_USERPTR,
.index = i,
.m.userptr = (unsigned long)buffers[i].start,
.length = frame_size,
};
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("VIDIOC_QBUF"); return -1;
}
}
return 0;
}
buf.timestamp: frame zamanlaması
/* VIDIOC_DQBUF sonrasında buf.timestamp kernel zamanını içerir */
struct v4l2_buffer buf;
ioctl(fd, VIDIOC_DQBUF, &buf);
/* Saniye ve mikrosaniye */
printf("Frame timestamp: %ld.%06ld\n",
buf.timestamp.tv_sec,
buf.timestamp.tv_usec);
/* İki frame arası süre (FPS hesaplama) */
static struct timeval prev_ts = {0};
double dt = (buf.timestamp.tv_sec - prev_ts.tv_sec) +
(buf.timestamp.tv_usec - prev_ts.tv_usec) * 1e-6;
printf("FPS: %.1f\n", 1.0 / dt);
prev_ts = buf.timestamp;
Bu bölümde
- MMAP: kernel tahsis eder, uygulama mmap() ile erişir; en yaygın ve taşınabilir yöntem
- USERPTR: uygulama tahsis eder, page-aligned bellek gerektirir; DMA için dikkat gerekir
- QBUF/DQBUF döngüsü: boş buffer sürücüye ver → dolu buffer geri al → işle → geri ver
- buf.timestamp: kernel'ın frame'i aldığı anı CLOCK_MONOTONIC ile ölçer
04 Pixel formatları ve format negotiation
V4L2, onlarca piksel formatını destekler. Doğru formatı seçmek hem işlem yükünü hem de bellek bant genişliğini doğrudan etkiler.
Yaygın V4L2 piksel formatları
| FourCC | V4L2 sabiti | Bit/piksel | Açıklama |
|---|---|---|---|
| YUYV | V4L2_PIX_FMT_YUYV | 16 | YUV 4:2:2 interleaved; en yaygın USB kamera formatı |
| NV12 | V4L2_PIX_FMT_NV12 | 12 | YUV 4:2:0 semi-planar; video codec ve GPU için |
| NV21 | V4L2_PIX_FMT_NV21 | 12 | NV12 ile U/V sırası değişik; Android kameralar |
| YU12/I420 | V4L2_PIX_FMT_YUV420 | 12 | YUV 4:2:0 fully planar |
| RGB24 | V4L2_PIX_FMT_RGB24 | 24 | 8:8:8 RGB; OpenCV için hazır |
| BGR24 | V4L2_PIX_FMT_BGR24 | 24 | 8:8:8 BGR; OpenCV'nin tercih ettiği |
| MJPG | V4L2_PIX_FMT_MJPEG | değişken | Motion JPEG; yüksek çözünürlük için bant genişliği tasarrufu |
| H264 | V4L2_PIX_FMT_H264 | değişken | H.264 compressed; donanım codec'li kameralar |
| RGGB | V4L2_PIX_FMT_SRGGB10 | 10/16 | Bayer RAW; ISP öncesi ham sensör verisi |
YUYV'dan RGB'ye dönüşüm
USB kameralar genellikle YUYV formatında veri gönderir. OpenCV veya görüntü işleme için RGB'ye çevirmek gerekir.
/* YUYV: Y0 U0 Y1 V0 | Y2 U2 Y3 V2 | ...
Her iki Y için bir (U,V) çifti — 4 bayt = 2 piksel */
void yuyv_to_rgb(const uint8_t *yuyv, uint8_t *rgb,
int width, int height)
{
int npixels = width * height;
for (int i = 0; i < npixels / 2; i++) {
int y0 = yuyv[4*i + 0];
int u = yuyv[4*i + 1];
int y1 = yuyv[4*i + 2];
int v = yuyv[4*i + 3];
u -= 128; v -= 128;
/* Piksel 0 */
int r = y0 + 1.402 * v;
int g = y0 - 0.344 * u - 0.714 * v;
int b = y0 + 1.772 * u;
rgb[6*i+0] = (uint8_t)(r < 0 ? 0 : r > 255 ? 255 : r);
rgb[6*i+1] = (uint8_t)(g < 0 ? 0 : g > 255 ? 255 : g);
rgb[6*i+2] = (uint8_t)(b < 0 ? 0 : b > 255 ? 255 : b);
/* Piksel 1 */
r = y1 + 1.402 * v;
g = y1 - 0.344 * u - 0.714 * v;
b = y1 + 1.772 * u;
rgb[6*i+3] = (uint8_t)(r < 0 ? 0 : r > 255 ? 255 : r);
rgb[6*i+4] = (uint8_t)(g < 0 ? 0 : g > 255 ? 255 : g);
rgb[6*i+5] = (uint8_t)(b < 0 ? 0 : b > 255 ? 255 : b);
}
}
Format enumeration ve negotiation
void list_formats(int fd)
{
struct v4l2_fmtdesc fmtdesc;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for (fmtdesc.index = 0;
ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0;
fmtdesc.index++) {
printf("[%u] %.4s — %s\n",
fmtdesc.index,
(char *)&fmtdesc.pixelformat,
fmtdesc.description);
/* Bu format için desteklenen çözünürlükler */
struct v4l2_frmsizeenum frmsize = {
.pixel_format = fmtdesc.pixelformat,
.index = 0,
};
while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) {
if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
printf(" %ux%u\n",
frmsize.discrete.width,
frmsize.discrete.height);
}
frmsize.index++;
}
}
}
Bu bölümde
- YUYV: en yaygın USB kamera formatı; 4 bayt = 2 piksel (YUV 4:2:2)
- NV12: video codec ve GPU için ideal; YUV 4:2:0 semi-planar
- VIDIOC_ENUM_FMT + VIDIOC_ENUM_FRAMESIZES: desteklenen tüm format ve çözünürlükleri listeler
- VIDIOC_TRY_FMT önce test et, VIDIOC_S_FMT ile ayarla; sürücü en yakın değeri döner
05 Media controller: pipeline yapılandırma
Media controller, karmaşık video pipeline'larını grafik olarak yönetir. Her bileşen bir entity'dir; entity'ler arasındaki veri akışı pad'ler ve link'ler ile tanımlanır.
Media controller kavramları
media-ctl ile pipeline keşfi ve yapılandırma
# Kurulum
sudo apt-get install -y v4l-utils
# Media controller pipeline'ını görüntüle
media-ctl -d /dev/media0 --print-topology
# Örnek çıktı (Raspberry Pi CSI kamera):
# Media controller API version 6.6.31
#
# Media device information
# driver : rp1-cfe
# model : rp1-cfe
#
# Entities:
# - imx219 4-0010 (2 pads, 0 links)
# pad0: Source [fmt:SRGGB10_1X10/3280x2464]
# - fe801000.csi (4 pads, 3 links)
# pad0: Sink [fmt:SRGGB10_1X10/3280x2464]
# pad1: Source
# Link etkinleştir: imx219 → CSI alıcı
media-ctl -d /dev/media0 \
--links '"imx219 4-0010":0->"fe801000.csi":0[1]'
# Format ayarla: sensör çözünürlüğü
media-ctl -d /dev/media0 \
--set-v4l2 '"imx219 4-0010":0[fmt:SRGGB10_1X10/1920x1080]'
# CSI alıcı çıkışı
media-ctl -d /dev/media0 \
--set-v4l2 '"fe801000.csi":1[fmt:SRGGB10_1X10/1920x1080]'
Tam Raspberry Pi kamera pipeline kurulumu
#!/bin/bash
# rpi_camera_setup.sh — IMX219 MIPI CSI pipeline
MEDIA=/dev/media0
WIDTH=1920
HEIGHT=1080
FORMAT=SRGGB10_1X10
# Pipeline link'lerini etkinleştir
media-ctl -d $MEDIA --links '"imx219 4-0010":0->"fe801000.csi":0[1]'
media-ctl -d $MEDIA --links '"fe801000.csi":1->"rp1-cfe-csi2_ch0":0[1]'
# Sensör formatı
media-ctl -d $MEDIA \
--set-v4l2 "\"imx219 4-0010\":0[fmt:${FORMAT}/${WIDTH}x${HEIGHT}]"
# CSI alıcı pad formatları
media-ctl -d $MEDIA \
--set-v4l2 "\"fe801000.csi\":0[fmt:${FORMAT}/${WIDTH}x${HEIGHT}]"
media-ctl -d $MEDIA \
--set-v4l2 "\"fe801000.csi\":1[fmt:${FORMAT}/${WIDTH}x${HEIGHT}]"
# Video node formatı
v4l2-ctl -d /dev/video0 \
--set-fmt-video=width=$WIDTH,height=$HEIGHT,pixelformat=pRAA
echo "Pipeline hazır: v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 --stream-to=frame.raw"
Bu bölümde
- Media controller: entity (bileşen) + pad (giriş/çıkış) + link (bağlantı) kavramları
- media-ctl --print-topology: pipeline'ı ve mevcut link'leri görüntüler
- --links: entity'ler arası link etkinleştirme; format[1]=etkin, format[0]=pasif
- --set-v4l2: her entity/pad için format ve çözünürlük ayarı
06 ISP ve subdev kontrolü
MIPI CSI kameralarda sensör ve ISP parametreleri, V4L2 subdev arayüzü üzerinden ayarlanır. VIDIOC_SUBDEV_S_FMT, crop ve kontroller bu arayüzün temel araçlarıdır.
VIDIOC_SUBDEV_S_FMT: subdev format ayarı
#include <linux/v4l2-subdev.h>
int set_subdev_format(int subdev_fd, uint32_t pad,
uint32_t width, uint32_t height,
uint32_t mbus_code)
{
struct v4l2_subdev_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
fmt.pad = pad;
fmt.format.width = width;
fmt.format.height = height;
fmt.format.code = mbus_code; /* MEDIA_BUS_FMT_SRGGB10_1X10 vb. */
fmt.format.field = V4L2_FIELD_NONE;
fmt.format.colorspace = V4L2_COLORSPACE_RAW;
if (ioctl(subdev_fd, VIDIOC_SUBDEV_S_FMT, &fmt) < 0) {
perror("VIDIOC_SUBDEV_S_FMT");
return -1;
}
return 0;
}
Crop ve compose: seçim bölgeleri
int set_subdev_crop(int subdev_fd, uint32_t pad,
uint32_t left, uint32_t top,
uint32_t width, uint32_t height)
{
struct v4l2_subdev_selection sel;
memset(&sel, 0, sizeof(sel));
sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
sel.pad = pad;
sel.target = V4L2_SEL_TGT_CROP; /* Crop hedefi */
sel.r.left = left;
sel.r.top = top;
sel.r.width = width;
sel.r.height = height;
if (ioctl(subdev_fd, VIDIOC_SUBDEV_S_SELECTION, &sel) < 0) {
perror("VIDIOC_SUBDEV_S_SELECTION");
return -1;
}
return 0;
}
Sensör kontrolleri: exposure ve gain
#include <linux/v4l2-controls.h>
int set_sensor_control(int subdev_fd, uint32_t control_id, int32_t value)
{
struct v4l2_control ctrl = {
.id = control_id,
.value = value,
};
if (ioctl(subdev_fd, VIDIOC_S_CTRL, &ctrl) < 0) {
perror("VIDIOC_S_CTRL");
return -1;
}
return 0;
}
/* Kullanım örnekleri */
void configure_sensor(int subdev_fd)
{
/* Manuel exposure modu */
set_sensor_control(subdev_fd, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL);
/* Exposure süresi (birim: satır/mikrosaniye, sürücüye göre) */
set_sensor_control(subdev_fd, V4L2_CID_EXPOSURE, 1000);
/* Analog gain */
set_sensor_control(subdev_fd, V4L2_CID_ANALOGUE_GAIN, 256);
/* Dijital gain */
set_sensor_control(subdev_fd, V4L2_CID_DIGITAL_GAIN, 256);
/* White balance sıcaklığı */
set_sensor_control(subdev_fd, V4L2_CID_WHITE_BALANCE_TEMPERATURE, 5000);
/* Flip: dikey/yatay */
set_sensor_control(subdev_fd, V4L2_CID_VFLIP, 0);
set_sensor_control(subdev_fd, V4L2_CID_HFLIP, 0);
}
Genişletilmiş kontroller (v4l2_ext_controls)
void set_multiple_controls(int fd)
{
struct v4l2_ext_control ctrls[2] = {
{ .id = V4L2_CID_EXPOSURE, .value = 1000 },
{ .id = V4L2_CID_ANALOGUE_GAIN, .value = 256 },
};
struct v4l2_ext_controls ext = {
.ctrl_class = V4L2_CTRL_CLASS_CAMERA,
.count = 2,
.controls = ctrls,
};
if (ioctl(fd, VIDIOC_S_EXT_CTRLS, &ext) < 0)
perror("VIDIOC_S_EXT_CTRLS");
}
Bu bölümde
- VIDIOC_SUBDEV_S_FMT: subdev pad'inin format ve çözünürlüğünü ayarlar; mbus_code medya veri yolu formatı
- VIDIOC_SUBDEV_S_SELECTION (V4L2_SEL_TGT_CROP): subdev'de crop bölgesi tanımlar
- V4L2_CID_EXPOSURE / V4L2_CID_ANALOGUE_GAIN: sensör exposure ve kazanç ayarı
- VIDIOC_S_EXT_CTRLS: birden fazla kontrol atomik olarak ayarlar; güvenli senkronizasyon
07 DMA-BUF ve sıfır kopya pipeline
DMA-BUF, farklı kernel subsystem'ler arasında bellek tamponu paylaşımının standart mekanizmasıdır. V4L2 → GPU veya V4L2 → Video Decoder gibi sıfır kopya pipeline'lar için kritiktir.
DMA-BUF kavramı
Normalde kamera frame'ini işlemek için veriyi V4L2 buffer'ından GPU belleğine kopyalamak gerekir. Bu kopyalama CPU ve bellek bant genişliğini tüketir. DMA-BUF ile kamera frame'i doğrudan GPU'nun erişebileceği bellekte olur; kopyalamaya gerek kalmaz.
Kamera (DMA) → MMAP buffer → CPU kopyalama → GPU belleği [Kopya yöntemi] Kamera (DMA) → DMA-BUF → GPU import (fd paylaşımı) [Sıfır kopya]
V4L2 DMA-BUF export
#include <linux/videodev2.h>
/* V4L2 buffer'ını DMA-BUF fd'ye dönüştür */
int export_dmabuf(int v4l2_fd, uint32_t buf_index)
{
struct v4l2_exportbuffer expbuf;
memset(&expbuf, 0, sizeof(expbuf));
expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
expbuf.index = buf_index;
expbuf.plane = 0; /* Multi-planar için plane numarası */
expbuf.flags = O_RDONLY;
if (ioctl(v4l2_fd, VIDIOC_EXPBUF, &expbuf) < 0) {
perror("VIDIOC_EXPBUF");
return -1;
}
/* expbuf.fd: DMA-BUF file descriptor
Bu fd başka bir driver'a aktarılabilir (GPU, video decoder vb.) */
return expbuf.fd;
}
DMA-BUF import: GPU'ya aktarma
#include <xf86drm.h>
#include <drm/drm.h>
/* DMA-BUF fd'yi DRM/GEM handle'a dönüştür */
uint32_t import_dmabuf_to_drm(int drm_fd, int dmabuf_fd)
{
struct drm_prime_handle prime = {
.fd = dmabuf_fd,
.flags = 0,
};
if (ioctl(drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime) < 0) {
perror("DRM_IOCTL_PRIME_FD_TO_HANDLE");
return 0;
}
/* prime.handle: GPU'nun bu buffer'a erişmesi için GEM handle */
return prime.handle;
}
/* DMA-BUF sıfır kopya pipeline */
void zero_copy_pipeline(int v4l2_fd, int drm_fd)
{
/* 1. V4L2 MMAP buffer'larını tahsis et */
request_buffers(v4l2_fd, 4, V4L2_MEMORY_MMAP);
/* 2. Her buffer için DMA-BUF fd al */
int dmabuf_fds[4];
for (int i = 0; i < 4; i++) {
dmabuf_fds[i] = export_dmabuf(v4l2_fd, i);
/* Bu fd'yi GPU'ya ver — kopyalama yok */
uint32_t gem = import_dmabuf_to_drm(drm_fd, dmabuf_fds[i]);
printf("Buffer %d: dmabuf_fd=%d, gem=%u\n", i, dmabuf_fds[i], gem);
}
/* 3. Streaming döngüsü — GPU doğrudan kamera buffer'ına erişir */
start_streaming(v4l2_fd);
/* ... DQBUF → GPU işle → QBUF döngüsü ... */
}
DMA-BUF ile V4L2 import
/* Dışarıdan tahsis edilmiş DMA-BUF'u V4L2'ye ver */
int setup_dmabuf_import(int fd, int *dmabuf_fds, uint32_t count)
{
struct v4l2_requestbuffers req = {
.count = count,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_DMABUF,
};
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
perror("VIDIOC_REQBUFS"); return -1;
}
for (uint32_t i = 0; i < count; i++) {
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_DMABUF,
.index = i,
.m.fd = dmabuf_fds[i], /* DMA-BUF fd */
};
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("VIDIOC_QBUF"); return -1;
}
}
return 0;
}
Bu bölümde
- DMA-BUF: kernel subsystem'ler arasında buffer paylaşımı; file descriptor üzerinden
- VIDIOC_EXPBUF: V4L2 MMAP buffer'ını DMA-BUF fd'ye dönüştürür
- DRM_IOCTL_PRIME_FD_TO_HANDLE: DMA-BUF fd'yi GPU GEM handle'a çevirir; sıfır kopya GPU erişimi
- V4L2_MEMORY_DMABUF + VIDIOC_QBUF: dışarıdan gelen DMA-BUF'u V4L2'ye import eder
08 Pratik: C ile frame yakalama ve test araçları
Tam C uygulaması ile MMAP buffer kullanarak YUYV frame yakalama, PPM dosyasına kaydetme, v4l2-compliance ile driver test ve ffmpeg V4L2 backend.
Tam frame yakalama uygulaması
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#define WIDTH 640
#define HEIGHT 480
#define NBUFS 4
struct buffer { void *start; size_t length; };
static struct buffer bufs[NBUFS];
static uint8_t clamp(int v) {
return (uint8_t)(v < 0 ? 0 : v > 255 ? 255 : v);
}
static void save_ppm(const uint8_t *yuyv, const char *filename)
{
FILE *f = fopen(filename, "wb");
fprintf(f, "P6\n%d %d\n255\n", WIDTH, HEIGHT);
for (int i = 0; i < WIDTH * HEIGHT / 2; i++) {
int y0 = yuyv[4*i], u = yuyv[4*i+1] - 128;
int y1 = yuyv[4*i+2], v = yuyv[4*i+3] - 128;
uint8_t p[6] = {
clamp(y0 + 1402*v/1000),
clamp(y0 - 344*u/1000 - 714*v/1000),
clamp(y0 + 1772*u/1000),
clamp(y1 + 1402*v/1000),
clamp(y1 - 344*u/1000 - 714*v/1000),
clamp(y1 + 1772*u/1000),
};
fwrite(p, 1, 6, f);
}
fclose(f);
}
int main(int argc, char *argv[])
{
const char *dev = argc > 1 ? argv[1] : "/dev/video0";
int fd = open(dev, O_RDWR);
if (fd < 0) { perror("open"); return 1; }
/* Kapabilite kontrolü */
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ||
!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf(stderr, "Gerekli kapabilite yok\n"); return 1;
}
/* Format ayarla */
struct v4l2_format fmt = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
};
fmt.fmt.pix.width = WIDTH;
fmt.fmt.pix.height = HEIGHT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
ioctl(fd, VIDIOC_S_FMT, &fmt);
/* Buffer'ları tahsis et */
struct v4l2_requestbuffers req = {
.count = NBUFS, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP,
};
ioctl(fd, VIDIOC_REQBUFS, &req);
for (int i = 0; i < NBUFS; i++) {
struct v4l2_buffer b = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP, .index = i };
ioctl(fd, VIDIOC_QUERYBUF, &b);
bufs[i].length = b.length;
bufs[i].start = mmap(NULL, b.length, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, b.m.offset);
ioctl(fd, VIDIOC_QBUF, &b);
}
/* Streaming başlat */
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);
/* Frame yakala */
struct v4l2_buffer b = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP };
ioctl(fd, VIDIOC_DQBUF, &b);
printf("Frame: %u bytes, ts=%ld.%06ld\n",
b.bytesused, b.timestamp.tv_sec, b.timestamp.tv_usec);
save_ppm(bufs[b.index].start, "frame.ppm");
printf("Kaydedildi: frame.ppm\n");
ioctl(fd, VIDIOC_QBUF, &b);
ioctl(fd, VIDIOC_STREAMOFF, &type);
for (int i = 0; i < NBUFS; i++)
munmap(bufs[i].start, bufs[i].length);
close(fd);
return 0;
}
# Derleme
gcc -o v4l2_capture v4l2_capture.c
# Çalıştırma
./v4l2_capture /dev/video0
# Frame: 614400 bytes, ts=1234567890.123456
# Kaydedildi: frame.ppm
# PPM'yi görüntüle
eog frame.ppm # GNOME
display frame.ppm # ImageMagick
v4l2-compliance ile driver test
# Tüm V4L2 uyumluluk testlerini çalıştır
v4l2-compliance -d /dev/video0
# Streaming testlerini de dahil et
v4l2-compliance -d /dev/video0 -s
# Örnek çıktı:
# Driver Info:
# Driver name : uvcvideo
# Compliance test for device /dev/video0:
# PASS: VIDIOC_QUERYCAP
# PASS: VIDIOC_G/S_PRIORITY
# PASS: VIDIOC_ENUM_FMT
# FAIL: VIDIOC_G_FMT → format mismatch
# Total: 42 tests, 1 failures
ffmpeg ile V4L2 yakalama
# Cihaz bilgisi
ffmpeg -f v4l2 -list_formats all -i /dev/video0
# YUYV yakalama → MP4
ffmpeg \
-f v4l2 \
-framerate 30 \
-video_size 1280x720 \
-input_format yuyv422 \
-i /dev/video0 \
-c:v libx264 \
-preset ultrafast \
output.mp4
# MJPEG yakalama (CPU daha az kullanır)
ffmpeg \
-f v4l2 \
-input_format mjpeg \
-framerate 30 \
-video_size 1920x1080 \
-i /dev/video0 \
-c:v copy \
output.avi
# Gerçek zamanlı preview (SDL)
ffplay -f v4l2 -framerate 30 /dev/video0
Bu bölümde
- Tam capture uygulaması: open → QUERYCAP → S_FMT → REQBUFS+mmap → QBUF → STREAMON → DQBUF → işle
- YUYV → PPM: ITU-R BT.601 dönüşüm formülleriyle RGB çıktı
- v4l2-compliance -s: sürücünün standarda uygunluğunu test eder; driver geliştirme için zorunlu
- ffmpeg -f v4l2 -input_format mjpeg: donanım MJPEG codec'ini kullanır; CPU yükü minimum