2026年1月20日 星期二
修復 v4l2loopback 0.15.0 的 V4L2 緩衝區佇列問題
## 問題現象
使用 GStreamer v4l2sink 將影像送到 v4l2loopback 虛擬攝影機時出現錯誤:
```
WARN v4l2bufferpool: buffer X was not queued, this indicates a driver bug
```
經過分析發現是 v4l2loopback 0.15.0 違反 V4L2 規格所致。
## V4L2 緩衝區佇列規格
V4L2 規格定義的緩衝區使用流程:
1. **VIDIOC_REQBUFS** - 配置緩衝區
2. **VIDIOC_QBUF** - 將緩衝區排入驅動程式佇列
3. **VIDIOC_DQBUF** - 從驅動程式佇列取出緩衝區
核心規則:DQBUF 只能回傳先前透過 QBUF 排入的緩衝區。
正確流程:`QBUF → (處理) → DQBUF → QBUF → (處理) → DQBUF ...`
v4l2loopback 0.15.0 的錯誤流程:`(預填充) → DQBUF → (自動循環) → DQBUF → (自動循環) → DQBUF ...`
錯誤點:完全不需要 QBUF,DQBUF 永遠有可用緩衝區。
## v4l2loopback 0.15.0 的問題
### prepare_buffer_queue() 預先填充佇列
```c
/* 原始碼 */
for (pos = 0; pos < count; ++pos) {
bufd = &dev->buffers[pos];
if (list_empty(&bufd->list_head))
list_add_tail(&bufd->list_head, &dev->outbufs_list);
}
```
問題:所有緩衝區在配置後立即被加入輸出佇列,沒有經過 QBUF。
### vidioc_dqbuf() 循環使用緩衝區
```c
/* 原始碼 */
bufd = list_first_entry_or_null(&dev->outbufs_list, ...);
if (bufd)
list_move_tail(&bufd->list_head, &dev->outbufs_list);
```
問題:使用 `list_move_tail()` 將緩衝區移到佇列尾端,形成循環。
### 違反規格的行為
1. 緩衝區未經 QBUF 就出現在輸出佇列
2. DQBUF 回傳從未排入的緩衝區
3. 緩衝區自動循環使用
實際流程:`DQBUF → DQBUF → DQBUF ...` (完全不需要 QBUF)
### GStreamer 的檢查
GStreamer v4l2bufferpool 會追蹤已排入的緩衝區。收到未排入的緩衝區時報錯:
```
WARN v4l2bufferpool: buffer X was not queued, this indicates a driver bug
```
這是正確的行為,因為驅動程式確實違反了規格。
## 修復方法
### 修改 prepare_buffer_queue()
清空輸出佇列,不要預先填充:
```c
/* 清空輸出佇列 */
list_for_each_entry_safe(bufd, n, &dev->outbufs_list, list_head) {
list_del_init(&bufd->list_head);
}
/* 重設緩衝區狀態 */
for (pos = 0; pos < count; ++pos) {
bufd = &dev->buffers[pos];
unset_flags(bufd->buffer.flags);
dev->bufpos2index[pos % count] = bufd->buffer.index;
}
```
### 修改 vidioc_dqbuf()
移除緩衝區,不要循環使用:
```c
bufd = list_first_entry_or_null(&dev->outbufs_list, ...);
if (bufd)
list_del_init(&bufd->list_head); /* 移除,不循環 */
```
### 修復後的行為
- 輸出佇列初始為空
- 緩衝區只在透過 QBUF 排入時才進入輸出佇列
- DQBUF 移除緩衝區,不循環使用
- 符合 V4L2 規格要求
## 測試驗證
修復前:
```bash
gst-launch-1.0 icamerasrc ! v4l2sink device=/dev/video0
# ERROR: buffer X was not queued, this indicates a driver bug
```
修復後:
```bash
gst-launch-1.0 icamerasrc ! v4l2sink device=/dev/video0
# 正常運作,無錯誤訊息
# Firefox/Chrome 可正常使用虛擬攝影機
```
## 技術細節
### 為什麼原設計會預先填充?
v4l2loopback 作為虛擬裝置,可能想簡化緩衝區管理,確保永遠有可用緩衝區。但這違反了 V4L2 規格,與嚴格遵循規格的程式(如 GStreamer)不相容。
### list_del_init vs list_move_tail
- `list_del_init()`: 明確表示「緩衝區不在佇列中」
- `list_move_tail()`: 暗示「移到另一個位置」,語義模糊
使用 `list_del_init()` 讓緩衝區狀態更清楚。
### 多執行緒考量
正確的緩衝區狀態追蹤在多執行緒環境下很重要,可避免競爭條件。
## 參考資料
- V4L2 Buffer: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/buffer.html
- VIDIOC_QBUF/DQBUF: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-qbuf.html
- V4L2 Streaming I/O: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/io.html
- GStreamer v4l2bufferpool: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/main/subprojects/gst-plugins-good/sys/v4l2/gstv4l2bufferpool.c
## 上游貢獻
修補程式已提交至上游:https://github.com/v4l2loopback/v4l2loopback/pull/656
## 結論
V4L2 緩衝區管理規格的核心原則:
1. 明確的狀態轉換:QBUF → DQBUF
2. 應用程式控制:由應用程式決定何時提供緩衝區
3. 可預測行為:嚴格遵循規格
當應用程式報告 "driver bug" 時,通常確實是驅動程式的問題。
訂閱:
張貼留言 (Atom)
沒有留言:
張貼留言