專案架構
- main.c:SDL 初始化、視窗建立、主迴圈、執行緒同步
- gather_picture.h/.c:V4L2 設備操作(開啟、設定格式、mmap、佇列/取幀)
- SDL 執行緒:負責 YUV 轉 RGB 與材質更新
核心流程
- 開啟設備:
open("/dev/video0", O_RDWR)
- 查詢能力:
VIDIOC_QUERYCAP 確認支援視訊捕獲與串流
- 設定格式:
VIDIOC_S_FMT 設定 640x480 YUYV (YUV 4:2:2)
- 請求緩衝區:
VIDIOC_REQBUFS 申請 4 個 mmap 緩衝區
- 記憶體映射:
mmap() 將核心緩衝區映射至用戶空間
- 佇列緩衝區:
VIDIOC_QBUF 將空緩衡區入佇列
- 啟動串流:
VIDIOC_STREAMON
- 取幀迴圈:
VIDIOC_DQBUF 取得資料 → 處理 → VIDIOC_QBUF 歸還
- SDL 渲染:YUV → RGB (SWS) → SDL_UpdateTexture → SDL_RenderCopy
關鍵代碼片段
V4L2 初始化 (gather_picture.c)
int video_init(void) {
g_videofd = open(VIDEO_DEV, O_RDWR);
// ... 查詢能力、列舉格式、設定格式 ...
struct v4l2_requestbuffers reqbuf = { .count = 4, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP };
ioctl(g_videofd, VIDIOC_REQBUFS, &reqbuf);
// mmap 每個緩衝區
for (i = 0; i < reqbuf.count; i++) {
struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP, .index = i };
ioctl(g_videofd, VIDIOC_QUERYBUF, &buf);
frame_buf.start[i] = mmap(NULL, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, g_videofd, buf.m.offset);
frame_buf.length[i] = buf.length;
}
// 佇列所有緩衝區
for (i = 0; i < reqbuf.count; i++) {
struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP, .index = i };
ioctl(g_videofd, VIDIOC_QBUF, &buf);
}
// 啟動串流
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(g_videofd, VIDIOC_STREAMON, &type);
return 0;
}
SDL 主迴圈 (main.c)
SDL_Init(SDL_INIT_VIDEO);
screen = SDL_SetVideoMode(WIDTH, HEIGHT, 32, SDL_HWSURFACE|SDL_DOUBLEBUF);
// 建立 YUV Overlay 或 Texture (SDL 2.0 建議用 Texture)
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
while (running) {
// 取得幀資料
struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP };
ioctl(g_videofd, VIDIOC_DQBUF, &buf);
void *yuv_data = frame_buf.start[buf.index];
// YUV 轉 RGB (使用 FFmpeg swscale)
sws_scale(sws_ctx, (const uint8_t*[]){(uint8_t*)yuv_data}, linesize, 0, HEIGHT, dst_data, dst_linesize);
// 更新 SDL Texture
SDL_UpdateYUVTexture(texture, NULL, y_plane, y_pitch, u_plane, u_pitch, v_plane, v_pitch);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
// 歸還緩衝區
ioctl(g_videofd, VIDIOC_QBUF, &buf);
SDL_Delay(10);
}
常見問題與解決
| 問題 | 解決方案 |
| 畫面顏色異常(偏綠/偏藍) | 確認 V4L2 格式 (YUYV/UYVY) 與 swscale 輸入格式一致 |
| 幀率低、延遲高 | 減少 mmap 緩衝區數量、使用零拷貝、啟用 DMA |
| SDL 視窗無畫面 | 檢查 SDL_PixelFormat 與 Texture 格式匹配,確認 Renderer 硬體加速 |
| Segmentation fault | 檢查 mmap 回傳值、緩衝區索引邊界、swscale context 初始化 |
編譯鏈結
gcc -o camera main.c gather_picture.c `pkg-config --cflags --libs sdl2 libavcodec libavformat libswscale` -lv4l2 -lpthread
學習資源