v4l2-ctl 拍照與取流
media-ctl -d /dev/media0 -r media-ctl -d /dev/media0 -l "'ov5640 3-003c':0->'csi2':0[1]" media-ctl -d /dev/media0 -V "'ov5640 3-003c':0[fmt:UYVY8_2X8/1280x720]" media-ctl -d /dev/media0 -V "'csi2':0[fmt:UYVY8_2X8/1280x720]"
路徑/格式依板卡 BSP 而異,以 media-ctl -p 輸出為準。
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=YUYV v4l2-ctl -d /dev/video0 --stream-mmap=1 --stream-count=1 --stream-to=frame.yuv
gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! autovideosink
| 症狀 | 方向 |
|---|---|
| v4l2-ctl stream 失敗 | media 管線未設好 / 驅動 probe 失敗 |
| 全黑 | 曝光 0、格式錯、感測器沒輸出 |
| 綠/紫 | 色彩格式(YUYV/Bayer)設定錯 |
# 確認 media 管線 media-ctl -p -d /dev/media0 # 取一幀 v4l2-ctl -d /dev/video0 --stream-mmap=1 --stream-count=1 --stream-to=f.yuv # 檢查格式 v4l2-ctl -d /dev/video0 --list-formats-ext
「取一幀」背後是完整的 V4L2 buffer 迴圈。理解它,之後要處理幀率、頻寬、多相機才不會卡住。
--stream-mmap=3 代表配置 3 個 mmap buffer;buffer 由 kernel 填、使用者取。--stream-count=1 代表只取一幀就停(並自動 STREAMOFF)。bytesperline 對齊(如 16/64 byte 倍數)——原始 buffer 尺寸與寬×高不一定相同。media-ctl -d /dev/media0 -r media-ctl -d /dev/media0 -l "'ov5640 3-003c':0->'csi2':0[1]" media-ctl -d /dev/media0 -V "'ov5640 3-003c':0[fmt:UYVY8_2X8/1280x720]" media-ctl -d /dev/media0 -V "'csi2':0[fmt:UYVY8_2X8/1280x720]"
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=YUYV
v4l2-ctl -d /dev/video0 --stream-mmap=3 --stream-count=1 --stream-to=frame.yuv
ls -l frame.yuv # 1280*720*2 = 1,843,200 bytesgst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! autovideosink
media-ctl -p 輸出為準,別照抄本站範例。| 現象 | 方向 |
|---|---|
v4l2-ctl 回 EINVAL/EBUSY | 格式不支援 → 用 --list-formats-ext 查 |
| 格式 OK 但一直 timeout | 管線 link 沒建 / subdev 沒 streaming → media-ctl -p 檢查 |
| 取到幀但全黑 | 曝光 0 / 沒光 / 感測器沒輸出 → 單元 6/10 |
| 取到幀但綠/紫 | YUYV/Bayer 設定錯 → 統一 fourcc |
| GStreamer 白/黑畫面 | caps 沒對齊 → 加 caps 明確指定格式 |
pixelformat 大小寫/順序錯(YUYV 不是 YUVY);② 忘了先建 link 就取流——media graph 未連通必然沒資料;③ 用 --stream-to=- 寫 stdout 卻忘了重導向;④ GStreamer 的 autovideosink 在無顯示的 SSH 環境會失敗——改用 fakesink 驗證。frame.yuv 大小。--list-formats-ext 列出全部 fourcc × 解析度組合。fakesink 跑 5 秒並讀 fps 統計。場景:Orange Pi 5(RK3588)接 OV5640,GStreamer 預覽全白,需要從 fakesink 反向追蹤問題。
gst-launch-1.0 v4l2src device=/dev/video0 ! 'video/x-raw,format=YUYV,width=1280,height=720' \ ! videoconvert ! fakesink silent=false 2>&1 | grep -i "buffer\|error"
gst-launch-1.0 v4l2src device=/dev/video0 ! 'video/x-raw,format=YUYV,width=1280,height=720' \ ! filesink location=frame.gst
gst-inspect-1.0 v4l2src | grep -A20 "SINK template" gst-launch-1.0 v4l2src device=/dev/video0 ! capsfilter caps='video/x-raw,format=YUYV' ! fakesink -v 2>&1 | head -20
autovideosink 在無 X11/Wayland 的 SSH 環境會失敗(fails to initialize display),但它不是「沒有畫面」的真正原因。用 fakesink 隔離「資料流動」與「顯示輸出」是標準除錯手法。RK3588 有硬體編碼器(MPP, Media Process Platform),但它不在 V4L2 media graph 內:
容易忽略的邊界案例:RK3588 的 MPP encoder 需要 NV12 輸入,但 OV5640 出 YUYV——中間必須有 videoconvert 做 format 轉換。如果直接把 YUYV 送進 MPP,會靜默失敗(encoder 回傳 0 byte)而非報錯。
videoconvert 在 CPU 上做 format 轉換,會成為瓶頸。RK3588 的 RGA 可以做硬體 YUYV→NV12 轉換,但需要額外設定 rga element。| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
v4l2-ctl --stream-to 成功但 gst-launch timeout | GStreamer caps 沒對齊(format/width/height) | 用 capsfilter 明確指定 video/x-raw,format=YUYV,width=1280,height=720 |
| GStreamer fakesink 有 buffer 但 autovideosink 白屏 | 顯示後端未初始化(無 X11/Wayland) | 設定 DISPLAY 或 WAYLAND_DISPLAY;或用 ximagesink 替代 |
media-ctl -V 設定格式後 video device 回 EINVAL | format 四角不對齊(sensor pad 與 video pad 格式不同) | 用 media-ctl -p 確認 sensor output 與 video input 格式一致 |
| 取到幀但 sizeimage 與預期不同 | bytesperline 對齊導致 padding | 用 v4l2-ctl --get-fmt-video 取實際 sizeimage |
| GStreamer 有 frame 但全綠 | format 設成 NV12 但實際是 YUYV | 確認 fourcc:YUYV vs NV12 的 bytes/pixel 不同 |
v4l2-ctl --stream-mmap 從同一個 video device 取流,觀察是否成功。解釋 V4L2 的 multi-queue 行為。把單元 5 的取流知識做成端到端錄製專案:先取單幀驗證,再建立「連續錄影 → 自動分段 → 格式檢查」的完整管線。這是所有影像應用(監控、檢測、錄製)的共通基礎。
media-ctl -d /dev/media0 -r
media-ctl -d /dev/media0 -l "'ov5640 3-003c':0->'csi2':0[1]"
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=YUYV \
--stream-mmap=3 --stream-count=1 --stream-to=first.yuv
ls -l first.yuv # 1280*720*2 = 1,843,200#!/bin/bash
for i in $(seq 1 10); do
v4l2-ctl -d /dev/video0 --stream-mmap=4 --stream-count=30 \
--stream-to=clip_$i.yuv # 30 幀 = 1 秒(30fps)
echo "clip_$i 完成: $(stat -c%s clip_$i.yuv) bytes"
donepython3 - <<'EOF'
import numpy as np
W,H=1280,720
buf = np.fromfile('clip_1.yuv', dtype=np.uint8)
print('size =', buf.size, '預期 =', W*H*2)
y = buf.reshape(H,W,2)[...,0]
print('Y 平均亮度 =', y.mean().round(1), ' 峰值 =', y.max())
EOFgst-launch-1.0 v4l2src device=/dev/video0 ! \ 'video/x-raw,format=YUYV,width=1280,height=720,framerate=30/1' ! \ videoconvert ! video/x-raw,format=NV12 ! \ mpph264enc ! h264parse ! mp4mux ! filesink location=rec.mp4
record_clips.sh 可自動分段錄製,且能驗證每個 clip 的大小與亮度正常。整條「取流 → 驗證 → 編碼 → 存檔」管線可重複執行——這正是所有實作專案的骨架。| 步驟 | 指令 | 通過判據 |
|---|---|---|
| 1. 管線重置 | media-ctl -d /dev/media0 -r | 無錯誤 |
| 2. 建 link | media-ctl -l '...:0->...:0[1]' | link 建立成功 |
| 3. 取 100 幀 | --stream-count=100 | 100 幀全數成功,無 timeout |
| 4. 量幀率 | 計時 100 幀所需時間 | 接近目標(如 30fps → ~3.3s) |
| 5. 長期穩定 | 連續錄 10 分鐘監看掉幀 | 0 掉幀;掉幀 → 查頻寬(單元 11) |
| 6. 中斷恢復 | Ctrl-C 後立即重跑 | 能重新取得節點(無 EBUSY) |
| 面向 | Orange Pi | RPi5 | Orin Nano | Thor |
|---|---|---|---|---|
| 取流指令 | v4l2-ctl | rpicam-still / libcamera | gst-launch nvarguscamerasrc | Holoscan capture |
| 預覽 | GStreamer v4l2src | libcamera-hello | nvarguscamerasrc | Holoscan 範例 |
| 硬體編碼 | MPP(RK3588)/ CPU | H.264/H.265 硬體 | NVENC | NVENC(Blackwell) |
| 錄製格式 | mp4mux + mpph264enc | libcamera-vid | GStreamer + nvv4l2h264enc | Holoscan encoder |
| 最大解析度 | 依 BSP | 1080p 常見 | 4K+ | 4K+ 多路 |
- [ ] 我能跑完 record_clips.sh 並驗證每個 clip 大小與亮度。 - [ ] 我能用 GStreamer 完成 V4L2 → 硬體編碼 → MP4 的完整管線。 - [ ] 我已驗證 100 幀連續取流無 timeout。 - [ ] 我能用 fakesink 隔離「資料流動」與「顯示輸出」問題。 - [ ] 我能解釋 mmap buffer 迴圈(REQBUFS→QBUF→DQBUFF)的流程。 - [ ] 我能指出 YUYV 與 NV12 在 GStreamer 管線中的轉換必要。
「第一個鏡頭跑起來」的關鍵是正確設定 V4L2 管線並驗證資料流動。以下是完整的位元級序列:
| 步驟 | 操作 | 指令 | 預期值 |
|---|---|---|---|
| 1. 確認管線 | media-ctl -p | media-ctl -p -d /dev/media0 | sensor → csi → video |
| 2. 設定 format | media-ctl -V | media-ctl -V "...:0[fmt:SRGGB10_1X10/1920x1080]" | 格式設定成功 |
| 3. 建 link | media-ctl -l | media-ctl -l "...:0->...:0[1]" | link active |
| 4. 取單幀 | v4l2-ctl | v4l2-ctl --stream-mmap=1 --stream-count=1 -d /dev/video0 | 成功產出檔案 |
| 5. 驗證 sizeimage | file size | ls -l stream.raw | sizeimage 正確(依格式) |
| 6. 連續取流 | v4l2-ctl | v4l2-ctl --stream-mmap=4 --stream-count=100 | 100 幀無 timeout |
決策樹 A:stream-on 失敗
v4l2-ctl --stream-mmap=1 --stream-count=1 ├─ "VIDIOC_DQBUF: No such device" → video device 無回應 │ ├─ 檢查 media-ctl -l → link 是否 active │ └─ 檢查 sensor 有無串流(I2C 設定是否完成) ├─ "timeout" → 無幀到達 │ ├─ CSI 接收異常 → dmesg 看 csi 錯誤 │ └─ 嘗試降低解析度(640x480)→ 排除頻寬 └─ "Invalid argument" → format 不支援 └─ 用 media-ctl -V 設定正確格式
決策樹 B:有流但全黑
v4l2-ctl 取到幀但全黑? ├─ sensor 曝光異常 │ ├─ 讀 0x3500~0x3502 → 手動曝光是否有效 │ └─ 用 v4l2-ctl --set-ctrl=exposure=500 ├─ format 錯誤 │ └─ 換 YUYV 格式測試(排除 Bayer 問題) └─ 資料到 video 但後處理問題 └─ 用 fakesink 隔離(GStreamer 管線)
| 步驟 | 指令 | 預期輸出 | 判讀標準 |
|---|---|---|---|
| 1. 管線確認 | media-ctl -p | sensor → csi → video | link 正確 |
| 2. 設定格式 | media-ctl -V | SRGGB10/1920x1080 | match sensor 能力 |
| 3. 建 link | media-ctl -l | link active | 正確 pad |
| 4. 取單幀 | v4l2-ctl --stream-mmap=1 --stream-count=1 | 成功 | sizeimage 正確 |
| 5. 驗證格式 | file stream.raw | head | 依 fourcc | SRGGB10 header |
| 6. 連續取流 | v4l2-ctl --stream-mmap=4 --stream-count=100 | 100 幀無 error | 無 timeout/dropped |
| 7. 頻寬驗證 | v4l2-ctl --stream-mmap=4 --stream-count=1000 | 1000 幀穩定 | 長期穩定無掉幀 |
| 面向 | Orange Pi | RPi5 | Orin Nano | Thor | 推薦 |
|---|---|---|---|---|---|
| 取流工具 | v4l2-ctl | rpicam-vid | nvgstcapture | Holoscan | 底層→v4l2-ctl |
| media-ctl | ✅ 必用 | libcamera 抽象 | 封閉 | 封閉 | 學習→media-ctl |
| GStreamer | v4l2src | libcamera 插件 | nvarguscamerasrc | Holoscan | 通用→GStreamer |
| 硬體編碼 | MPP(RK3588) | H.264 HW | NVENC | NVENC | 效能→NVENC |
| buffer 管理 | REQBUFS/QBUF/DQBUF | libcamera buffers | NV 管理 | Holoscan | 底層→V4L2 |
- [ ] 確認 /dev/video0 存在 - [ ] 用 media-ctl -p 確認完整管線 - [ ] 設定 format(media-ctl -V) - [ ] 建 link(media-ctl -l) - [ ] v4l2-ctl 取一幀 YUV 並驗證 sizeimage - [ ] 換 SRGGB10 格式取 RAW 並驗證 - [ ] 用 GStreamer v4l2src 預覽 - [ ] 連續取 100 幀驗證穩定性 - [ ] 用 fakesink 隔離「資料流動」與「顯示輸出」 - [ ] 用 mmap buffer 迴圈理解 REQBUFS→QBUF→DQBUF - [ ] 產出「取流能力表」(格式/解析度/fps/stability)