單元 5 · 第一個鏡頭跑起來

v4l2-ctl 拍照與取流

步驟 1:設定 media 管線

media-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 輸出為準。

步驟 2:拍照

v4l2-ctl 取一幀
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
第一步驗證:能取到一幀 = 感測器→CSI→video 鏈路通。接著才談調校。

步驟 3:GStreamer 預覽

即時預覽
gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! autovideosink

常見失敗

症狀方向
v4l2-ctl stream 失敗media 管線未設好 / 驅動 probe 失敗
全黑曝光 0、格式錯、感測器沒輸出
綠/紫色彩格式(YUYV/Bayer)設定錯

5.7 深入:取流的完整驗證

OV5640 取流驗證
# 確認 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
通過標準:取到一幀 = 感測器→CSI→video 鏈路通。接著才談調校。

5.9 深入原理:streaming 的 buffer 流程

「取一幀」背後是完整的 V4L2 buffer 迴圈。理解它,之後要處理幀率、頻寬、多相機才不會卡住。

VIDIOC_REQBUFSMMAP 對映QBUF(排隊)STREAMONDQBUFF 拿幀QBUF 放回
為什麼用 mmap 而不用 read:mmap 零拷貝、可多 buffer 並行,適合高幀率。正式產品(GStreamer/FFmpeg)都用這個模型。

5.10 完整 Worked Example:穩定的取流 SOP

步驟 1|重置並建 link
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]"
步驟 2|設定 video 格式
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=YUYV
步驟 3|取一幀並檢查大小
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 bytes
步驟 4|即時預覽
gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! autovideosink
紀律:每次換場景/板卡,先從「重設管線」開始——避免上一次殘留的 link/格式污染本次結果。管線路徑與格式字串必須以 media-ctl -p 輸出為準,別照抄本站範例。

5.11 疑難排解決策樹:取不到幀

現象方向
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 驗證。

5.12 練習

  1. 重現 SOP,記錄每步輸出與 frame.yuv 大小。
  2. --list-formats-ext 列出全部 fourcc × 解析度組合。
  3. 故意不建 link 取流,記錄錯誤訊息並寫下判斷。
  4. 用 GStreamer fakesink 跑 5 秒並讀 fps 統計。

5.13 回顧練習

  1. 跑完整取流流程,確認格式。
  2. 記錄支援的 fourcc 清單。

5.14 進階真實情境 Worked Example:GStreamer 管線除錯——從 fakesink 到實際顯示

場景:Orange Pi 5(RK3588)接 OV5640,GStreamer 預覽全白,需要從 fakesink 反向追蹤問題。

步驟 1|用 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"
步驟 2|用 filesink 保存一幀驗證
gst-launch-1.0 v4l2src device=/dev/video0 ! 'video/x-raw,format=YUYV,width=1280,height=720' \
  ! filesink location=frame.gst
步驟 3|檢查 caps 是否被接受
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
設計決策:GStreamer 管線中 autovideosink 在無 X11/Wayland 的 SSH 環境會失敗(fails to initialize display),但它不是「沒有畫面」的真正原因。用 fakesink 隔離「資料流動」與「顯示輸出」是標準除錯手法。

5.15 深入原理擴充:RK3588 MPP 硬體編碼與 V4L2 的串接

RK3588 有硬體編碼器(MPP, Media Process Platform),但它不在 V4L2 media graph 內:

V4L2 video deviceGStreamer v4l2srcvideoconvertmpph264ench264parsemp4mux

容易忽略的邊界案例:RK3588 的 MPP encoder 需要 NV12 輸入,但 OV5640 出 YUYV——中間必須有 videoconvert 做 format 轉換。如果直接把 YUYV 送進 MPP,會靜默失敗(encoder 回傳 0 byte)而非報錯。

效能影響:videoconvert 在 CPU 上做 format 轉換,會成為瓶頸。RK3588 的 RGA 可以做硬體 YUYV→NV12 轉換,但需要額外設定 rga element。

5.16 診斷式疑難排解表

症狀可能原因解決方案
v4l2-ctl --stream-to 成功但 gst-launch timeoutGStreamer caps 沒對齊(format/width/height)capsfilter 明確指定 video/x-raw,format=YUYV,width=1280,height=720
GStreamer fakesink 有 buffer 但 autovideosink 白屏顯示後端未初始化(無 X11/Wayland)設定 DISPLAYWAYLAND_DISPLAY;或用 ximagesink 替代
media-ctl -V 設定格式後 video device 回 EINVALformat 四角不對齊(sensor pad 與 video pad 格式不同)media-ctl -p 確認 sensor output 與 video input 格式一致
取到幀但 sizeimage 與預期不同bytesperline 對齊導致 paddingv4l2-ctl --get-fmt-video 取實際 sizeimage
GStreamer 有 frame 但全綠format 設成 NV12 但實際是 YUYV確認 fourcc:YUYV vs NV12 的 bytes/pixel 不同

5.17 進階挑戰題

  1. 用 GStreamer 串接 OV5640 → MPP 硬體編碼 → MP4 檔案,測量端到端延遲(從觸發到檔案可播放)。比較「用 videoconvert」與「用 RGA」的延遲差異。
  2. 寫一支 shell script:自動取 10 幀 RAW,統計每幀的平均亮度與 σ,判斷 AEC 是否已收斂。
  3. 在 RK3588 上同時開兩個 v4l2-ctl --stream-mmap 從同一個 video device 取流,觀察是否成功。解釋 V4L2 的 multi-queue 行為。

5.18 專案級 Worked Example:取流與錄製管線專案 — 從單幀到自動錄製

把單元 5 的取流知識做成端到端錄製專案:先取單幀驗證,再建立「連續錄影 → 自動分段 → 格式檢查」的完整管線。這是所有影像應用(監控、檢測、錄製)的共通基礎。

階段 1|單幀驗證(單元 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
階段 2|連續錄製並分段
#!/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"
done
階段 3|格式驗證與可視化
python3 - <<'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())
EOF
階段 4|GStreamer 即時錄製(硬體編碼)
gst-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 的大小與亮度正常。整條「取流 → 驗證 → 編碼 → 存檔」管線可重複執行——這正是所有實作專案的骨架。

5.19 量測/驗證 SOP:取流穩定性驗證

步驟指令通過判據
1. 管線重置media-ctl -d /dev/media0 -r無錯誤
2. 建 linkmedia-ctl -l '...:0->...:0[1]'link 建立成功
3. 取 100 幀--stream-count=100100 幀全數成功,無 timeout
4. 量幀率計時 100 幀所需時間接近目標(如 30fps → ~3.3s)
5. 長期穩定連續錄 10 分鐘監看掉幀0 掉幀;掉幀 → 查頻寬(單元 11)
6. 中斷恢復Ctrl-C 後立即重跑能重新取得節點(無 EBUSY)
SOP 紀律:「能取一幀」與「能穩定取流」是兩件事。驗收目標是長時間不掉幀、中斷後可立即重連——這才是產品級取流。

5.20 平台間對照:取流與錄製

面向Orange PiRPi5Orin NanoThor
取流指令v4l2-ctlrpicam-still / libcameragst-launch nvarguscamerasrcHoloscan capture
預覽GStreamer v4l2srclibcamera-hellonvarguscamerasrcHoloscan 範例
硬體編碼MPP(RK3588)/ CPUH.264/H.265 硬體NVENCNVENC(Blackwell)
錄製格式mp4mux + mpph264enclibcamera-vidGStreamer + nvv4l2h264encHoloscan encoder
最大解析度依 BSP1080p 常見4K+4K+ 多路
核心領悟:「取流 → 編碼 → 存檔」的管線邏輯四平台相同,差別只在編碼器(MPP vs H264 HW vs NVENC)。Orange Pi 學到的 V4L2 取流 + GStreamer 管線概念可直接平移。

5.21 互動式檢核清單:本單元進階驗收

checklist
- [ ] 我能跑完 record_clips.sh 並驗證每個 clip 大小與亮度。
- [ ] 我能用 GStreamer 完成 V4L2 → 硬體編碼 → MP4 的完整管線。
- [ ] 我已驗證 100 幀連續取流無 timeout。
- [ ] 我能用 fakesink 隔離「資料流動」與「顯示輸出」問題。
- [ ] 我能解釋 mmap buffer 迴圈(REQBUFS→QBUF→DQBUFF)的流程。
- [ ] 我能指出 YUYV 與 NV12 在 GStreamer 管線中的轉換必要。

5.22 Register 位元級完整工作流

「第一個鏡頭跑起來」的關鍵是正確設定 V4L2 管線並驗證資料流動。以下是完整的位元級序列:

步驟操作指令預期值
1. 確認管線media-ctl -pmedia-ctl -p -d /dev/media0sensor → csi → video
2. 設定 formatmedia-ctl -Vmedia-ctl -V "...:0[fmt:SRGGB10_1X10/1920x1080]"格式設定成功
3. 建 linkmedia-ctl -lmedia-ctl -l "...:0->...:0[1]"link active
4. 取單幀v4l2-ctlv4l2-ctl --stream-mmap=1 --stream-count=1 -d /dev/video0成功產出檔案
5. 驗證 sizeimagefile sizels -l stream.rawsizeimage 正確(依格式)
6. 連續取流v4l2-ctlv4l2-ctl --stream-mmap=4 --stream-count=100100 幀無 timeout
位元級關鍵: sizeimage = width × height × bytes_per_pixel。SRGGB10 = 10bit → 1.25 bytes/pixel。若 sizeimage 不符,代表格式設定錯誤或 buffer 配置異常。

5.23 多層疑難排解決策樹

決策樹 A:stream-on 失敗

決策樹 A
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:有流但全黑

決策樹 B
v4l2-ctl 取到幀但全黑?
├─ sensor 曝光異常
│  ├─ 讀 0x3500~0x3502 → 手動曝光是否有效
│  └─ 用 v4l2-ctl --set-ctrl=exposure=500
├─ format 錯誤
│  └─ 換 YUYV 格式測試(排除 Bayer 問題)
└─ 資料到 video 但後處理問題
   └─ 用 fakesink 隔離(GStreamer 管線)

5.24 量測驗證完整 SOP

步驟指令預期輸出判讀標準
1. 管線確認media-ctl -psensor → csi → videolink 正確
2. 設定格式media-ctl -VSRGGB10/1920x1080match sensor 能力
3. 建 linkmedia-ctl -llink active正確 pad
4. 取單幀v4l2-ctl --stream-mmap=1 --stream-count=1成功sizeimage 正確
5. 驗證格式file stream.raw | head依 fourccSRGGB10 header
6. 連續取流v4l2-ctl --stream-mmap=4 --stream-count=100100 幀無 error無 timeout/dropped
7. 頻寬驗證v4l2-ctl --stream-mmap=4 --stream-count=10001000 幀穩定長期穩定無掉幀

5.25 四平台終極對照

面向Orange PiRPi5Orin NanoThor推薦
取流工具v4l2-ctlrpicam-vidnvgstcaptureHoloscan底層→v4l2-ctl
media-ctl✅ 必用libcamera 抽象封閉封閉學習→media-ctl
GStreamerv4l2srclibcamera 插件nvarguscamerasrcHoloscan通用→GStreamer
硬體編碼MPP(RK3588)H.264 HWNVENCNVENC效能→NVENC
buffer 管理REQBUFS/QBUF/DQBUFlibcamera buffersNV 管理Holoscan底層→V4L2

5.26 完整 Bring-up 專案 Checklist

checklist
- [ ] 確認 /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)
看完這單元你應該能說出:
  • 用 media-ctl 設定管線連結與格式。
  • 用 v4l2-ctl 拍照取流。
  • 用 GStreamer 預覽。
  • 三種常見失敗的方向。

延伸閱讀