fix(local-agent): camera pixel format + rawvideo decoder(實測出畫面)

avfoundation 修好後實測發現兩層問題(camera 三層根因的後兩層):
- 改動1(args):darwin buildCaptureArgs 加 -pixel_format uyvy422(放 -i 前)。
  攝影機只支援 uyvy422/yuyv422/nv12(非 yuv420p),不指定→協商失敗 I/O error。
- 改動2(ffmpeg):Makefile decoder 白名單加 rawvideo。攝影機吐 raw uyvy422
  codec=rawvideo,轉 mjpeg 前要解,精簡 build 漏了→ no decoder found→EOF。

本機真攝影機端到端實測:/tmp/cam_ok.jpg = JPEG 640x480(rawvideo native→
mjpeg native 全鏈通、真的出畫面)。reviewer 通過(0C/0M)。只影響 camera
(video/image/batch 解既有壓縮檔、不碰 rawvideo/avfoundation)。只改 macOS
(Windows/Linux full build 內建 rawvideo;pixel_format 待實機 follow-up)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-08-03 00:53:35 +08:00
parent a6c94e21c5
commit e27d8e3bd2
6 changed files with 102 additions and 19 deletions

View File

@ -181,7 +181,7 @@ vendor-ffmpeg-macos-build: ## macOS從源碼 build LGPL v3 decoder-only ffmpe
--enable-avfoundation \
--enable-indev=avfoundation \
--enable-demuxer=mov,avi,mpegps,mpegts,matroska,image2 \
--enable-decoder=h264,hevc,mpeg1video,mpeg2video,mpeg4,mjpeg,prores,vp8,vp9,aac,mp2,mp3,pcm_s16le,pcm_s16be \
--enable-decoder=h264,hevc,mpeg1video,mpeg2video,mpeg4,mjpeg,prores,vp8,vp9,aac,mp2,mp3,pcm_s16le,pcm_s16be,rawvideo \
--enable-parser=h264,hevc,mpeg4video,mpegaudio,aac \
--enable-filter=scale,format,fps,null,anull \
--enable-muxer=image2pipe,image2,null \

View File

@ -223,9 +223,18 @@ func buildCaptureArgsForOS(goos string, cameraIndex int, cameraName string, widt
"-i", fmt.Sprintf("/dev/video%d", cameraIndex),
})
case "darwin":
// AVFoundation on macOS: -f avfoundation -i "index:none"
// AVFoundation on macOS: -f avfoundation -pixel_format uyvy422 -i "index:none"
//
// 必須明確指定攝影機支援的 input pixel format。攝影機只支援
// uyvy422/yuyv422/nv12/0rgb/bgr0不指定時 avfoundation 會嘗試 yuv420p
// → 協商失敗Input/output error。指定 uyvy422攝影機原生格式→ 攝影機成功打開。
//
// 位置關鍵:-pixel_format 是 input 選項,必須放在 -i 之前(跟 -framerate/
// -video_size 同段)。放到 -i 之後會被當成 output 轉碼目標、不解決 input 協商。
// 對照見根因文件 .autoflow/05-implementation/camera-pixel-format-rootcause.md。
return captureTail([]string{
"-f", "avfoundation",
"-pixel_format", "uyvy422",
"-framerate", fps,
"-video_size", videoSize,
"-i", fmt.Sprintf("%d:none", cameraIndex),

View File

@ -24,12 +24,15 @@ func TestBuildCaptureArgsForOS(t *testing.T) {
wantIndev string
// wantInputArg 是 "-i" 後面的值
wantInputArg string
// wantPixelFormat 若非空,斷言 "-pixel_format <值>" 存在且位於 -i 之前input 選項)。
wantPixelFormat string
}{
{
name: "macOS uses avfoundation with index:none",
name: "macOS uses avfoundation with index:none and uyvy422 input pixel format",
goos: "darwin",
wantIndev: "avfoundation",
wantInputArg: "0:none",
wantPixelFormat: "uyvy422",
},
{
name: "Windows uses dshow with video=name",
@ -60,6 +63,20 @@ func TestBuildCaptureArgsForOS(t *testing.T) {
t.Errorf("input = %q, want %q\nargs: %v", gotInput, tt.wantInputArg, args)
}
// macOS avfoundation 必須帶 -pixel_format uyvy422且它是 input 選項、
// 必須位於 -i 之前(放到 -i 之後會被當 output 轉碼目標、不解決 input 協商)。
if tt.wantPixelFormat != "" {
gotPixFmt := valueAfterFlag(args, "-pixel_format")
if gotPixFmt != tt.wantPixelFormat {
t.Errorf("pixel_format = %q, want %q\nargs: %v", gotPixFmt, tt.wantPixelFormat, args)
}
pixIdx := indexOfFlag(args, "-pixel_format")
iIdx := indexOfFlag(args, "-i")
if pixIdx < 0 || iIdx < 0 || pixIdx > iIdx {
t.Errorf("-pixel_format (idx %d) must appear before -i (idx %d)\nargs: %v", pixIdx, iIdx, args)
}
}
// 三平台後段皆須為 image2pipe / mjpegMJPEG pipe 架構共用。
if !containsSeq(args, "-f", "image2pipe") {
t.Errorf("missing image2pipe output, args: %v", args)
@ -162,6 +179,16 @@ func valueAfterFlag(args []string, flag string) string {
return ""
}
// indexOfFlag 回傳 args 中第一個等於 flag 的元素的索引;不存在回 -1。
func indexOfFlag(args []string, flag string) int {
for i, a := range args {
if a == flag {
return i
}
}
return -1
}
// containsSeq 判斷 args 是否包含連續的 a b 兩個元素。
func containsSeq(args []string, a, b string) bool {
for i := 0; i < len(args)-1; i++ {

View File

@ -18,9 +18,20 @@ LGPL static build 來源,採「自 build decoder-only」策略binary 直接
| Toolchain | Apple clang 16.0.0 (clang-1600.0.26.6), Command Line Tools |
| Assembler | nasm 3.01Homebrew bottlecompiled 2025-10-11 |
| Homebrew | 5.1.6 |
| Build date | 2026-08-02ADR-020 rebuild加回 avfoundation indev |
| Build date | 2026-08-03camera raw capture fixdecoder 白名單加 `rawvideo` |
| Build flags | 見下方 Configure flags 區塊(與 `Makefile``vendor-ffmpeg-macos-build` target 一致) |
> **camera raw capture fix2026-08-03**decoder 白名單加 `rawvideo`
> avfoundation 攝影機吐 raw `uyvy422`codec = `rawvideo`),要轉成 MJPEG pipe 必須先「解碼 rawvideo」
> 但先前 decoder-only 白名單沒有 `rawvideo``Decoding requested, but no decoder found for: rawvideo` → EOF。
> 這是 ADR-020avfoundation indev之後 camera 白名單的**第二個盲點**ADR-020 讓攝影機「能被開啟」,
> 但沒處理「攝影機 raw 輸出的解碼」。
> **未來 rebuild 別漏**camera raw capture 需 `rawvideo` decoder + `avfoundation` indev **兩者**齊全。
> `rawvideo` 是 libavcodec 內建 native decoder、LGPL-safe僅 byte 重排 / format 標記、無演算法),不引入任何 GPL 元件;
> swscale 已 enable`rawvideo(uyvy422) decode → swscale 轉 → mjpeg encode` 轉碼鏈齊)。
> 只加 macOSWindows/Linux 用 BtbN full build 內建 rawvideo不需改
> 詳見 `.autoflow/05-implementation/camera-pixel-format-rootcause.md`
> **ADR-020 變更2026-08-02**configure 加 `--enable-avfoundation` + `--enable-indev=avfoundation`
> 讓 camera 即時推論可用 avfoundation 抓實體攝影機。詳見
> `docs/autoflow/04-architecture/adr/adr-020-ffmpeg-camera-indev.md`
@ -30,11 +41,12 @@ LGPL static build 來源,採「自 build decoder-only」策略binary 直接
| 檔案 | sha256 |
|------|--------|
| `ffmpeg` | `1afa56dabb4ba4fb45323e37510602309ffd544d9e7ecb2d8267697a7cc16626` |
| `ffprobe` | `501ec3fbe450c44f4c28cd7534940c6a16a42f8584e852b4f315de565aba414e` |
| `ffmpeg` | `cb42312fa89d2dc8443ae0dab810d42e18d3efefd9b62cf0a2e18762f7a8cdb7` |
| `ffprobe` | `e7eb9e61a6bd77549cd6e2da9fec25beb67e71c3c3c4b1356ee395c591c0b3e9` |
| `COPYING.LGPLv3` | `da7eabb7bafdf7d3ae5e9f223aa5bdc1eece45ac569dc21b3b037520b4464768` |
> 舊值2026-04-15 decoder-only、無 indevffmpeg `c3cb9f1d…992c` / ffprobe `bd388fb4…0c5e`
> 舊值2026-08-02 ADR-020、加 avfoundation indev、無 rawvideoffmpeg `1afa56da…6626` / ffprobe `501ec3fb…414e`
> 更舊2026-04-15 decoder-only、無 indevffmpeg `c3cb9f1d…992c` / ffprobe `bd388fb4…0c5e`
計算指令:
```bash
@ -45,21 +57,24 @@ shasum -a 256 vendor/ffmpeg/macos/ffmpeg vendor/ffmpeg/macos/ffprobe
| 檔案 | Bytes | 人類可讀 |
|------|-------|---------|
| `ffmpeg` | 6,030,224 | 5.8 MB |
| `ffprobe` | 5,892,384 | 5.6 MB |
| `ffmpeg` | 6,034,432 | 5.8 MB |
| `ffprobe` | 5,896,624 | 5.6 MB |
實測比 TDD 原估 1015 MB 小一半,因為 `--disable-everything` + 白名單僅啟用必要 decoder/demuxer/filter無 GPL 元件。
> **rawvideo decoder 體積增量2026-08-03**ffmpeg 6,030,224 → 6,034,432 bytes
> **+4,208 bytes< 0.005 MB**。rawvideo decoder 只做 byte 重排 / format 標記無演算法體積增量極小
> **avfoundation indev 體積增量ADR-020**ffmpeg 6,007,520 → 6,030,224 bytes
> **+22,704 bytes+0.02 MB**,遠低於 ADR-020 估的 < 0.5 MB avfoundation indev 是薄封裝
> 呼叫系統 AVFoundation / CoreMedia / CoreVideo framework不自帶任何 codec。
### Build 實測耗時
- **3 分 57 秒**2026-08-02 ADR-020 rebuild`make vendor-ffmpeg-macos-build``time` 量測)
- user: 870.32ssystem: 92.27swall-clock: 236.62s
- CPU 使用率:~406%macOS x86_648 核 Intel
- 2026-04-15 首次 decoder-only build 為 2 分 44 秒;本次略增因多編 avfoundation indev
- **2 分 17 秒**2026-08-03 加 rawvideo decoder rebuild`make vendor-ffmpeg-macos-build``time` 量測)
- user: 548.43ssystem: 52.22swall-clock: 137.43s
- CPU 使用率:~437%macOS x86_648 核 Intel
- 2026-08-02 ADR-020 rebuild 為 3 分 57 秒2026-04-15 首次 decoder-only build 為 2 分 44 秒。本次較快為 build cache / 系統負載差異rawvideo decoder 本身編譯成本極低
## License
@ -73,7 +88,11 @@ build 不 link 以下 GPL-only 元件:
- 無 `libfaac`non-free
僅使用 libavcodec 內建的 LGPL native decoderh264 / hevc / mpeg1video / mpeg2video /
mpeg4 / mjpeg / prores / vp8 / vp9 / aac / mp2 / mp3 / pcm_*)。
mpeg4 / mjpeg / prores / vp8 / vp9 / aac / mp2 / mp3 / pcm_* / rawvideo
**`rawvideo` decoder2026-08-03為 LGPL-safe**libavcodec 內建 native decoder僅做 byte 重排 /
pixel format 標記、無壓縮演算法,不引入任何 GPL / 第三方元件。加 `rawvideo``ffmpeg -version`
configuration line 仍**不含** `--enable-gpl` / `libx264` / `libx265`已實測驗證LGPL v3 合規未破。
**avfoundation indevADR-020為 LGPL-safe不引入任何 GPL 元件**avfoundation input device
只是薄封裝、透過 macOS 系統的 AVFoundation / CoreMedia / CoreVideo framework 抓實體攝影機 frame
@ -103,7 +122,7 @@ Foundation / CoreGraphics / libobjc非第三方 dylib。
--enable-avfoundation \
--enable-indev=avfoundation \
--enable-demuxer=mov,avi,mpegps,mpegts,matroska,image2 \
--enable-decoder=h264,hevc,mpeg1video,mpeg2video,mpeg4,mjpeg,prores,vp8,vp9,aac,mp2,mp3,pcm_s16le,pcm_s16be \
--enable-decoder=h264,hevc,mpeg1video,mpeg2video,mpeg4,mjpeg,prores,vp8,vp9,aac,mp2,mp3,pcm_s16le,pcm_s16be,rawvideo \
--enable-parser=h264,hevc,mpeg4video,mpegaudio,aac \
--enable-filter=scale,format,fps,null,anull \
--enable-muxer=image2pipe,image2,null \
@ -132,7 +151,7 @@ Foundation / CoreGraphics / libobjc非第三方 dylib。
| `--enable-avfoundation` | **ADR-020** camera 抓實體攝影機需 AVFoundation framework。因本 build 用 `--disable-autodetect`(連 AVFoundation 框架都不自動偵測),必須顯式 `--enable-avfoundation` 才能讓下面的 `avfoundation` indev 的依賴(`avfoundation corevideo coremedia pthreads`)被滿足。**少了這行、`--enable-indev=avfoundation` 會被 configure 靜默 disable`WARNING: Disabled avfoundation_indev because not all dependencies are satisfied`** |
| `--enable-indev=avfoundation` | **ADR-020** camera 即時推論的 macOS input device。ffmpeg `-f avfoundation -i "<index>:none"` 從實體攝影機抓 raw frame → MJPEG pipe。少了它會 `Unknown input format: 'avfoundation'`。LGPL-safe 薄封裝、體積增量 < 0.03MB |
| `--enable-demuxer=mov,avi,mpegps,mpegts,matroska,image2` | 對齊 PRD v2 支援的上傳格式 `.mp4 / .avi / .mov / .mpeg / .mpg` |
| `--enable-decoder=h264,hevc,...` | 涵蓋常見 codecH.264 / H.265 / MPEG1/2/4 / mjpeg / prores / vp8/9 / AAC / MP2/3 / PCM |
| `--enable-decoder=h264,hevc,...,rawvideo` | 涵蓋常見 codecH.264 / H.265 / MPEG1/2/4 / mjpeg / prores / vp8/9 / AAC / MP2/3 / PCM。**`rawvideo`2026-08-03**camera 經 avfoundation 抓出的 frame 是 raw `uyvy422`codec = `rawvideo`),轉 MJPEG pipe 前必須先解碼;少了它 → `no decoder found for: rawvideo` → EOF。LGPL-safe native decoder |
| `--enable-parser=...` | 必要,否則某些 decoder 會在碼流切分階段 fail |
| `--enable-muxer=image2pipe,image2,null` | 輸出單張 JPEG 或 NULL測試用 |
| `--enable-encoder=mjpeg` | `-f image2pipe -vcodec mjpeg` 需要 mjpeg encoderLGPL-safe |
@ -334,6 +353,34 @@ $ vendor/ffmpeg/macos/ffmpeg -hide_banner -f avfoundation -list_devices true -i
- ✅ 不再出現 `Unknown input format: 'avfoundation'`camera 開不了的根因已解)
- 註:`-list_devices true` 列完裝置後以非 0 退出(`Error opening input`)屬正常行為,非失敗。
### 7. rawvideo decoder2026-08-03本次 rebuild 新增)
```
$ vendor/ffmpeg/macos/ffmpeg -hide_banner -decoders 2>&1 | grep -i rawvideo
V..... rawvideo
```
- ✅ `rawvideo` decoder 已編入白名單。
### 8. Camera raw capture 端到端2026-08-03決定性驗證
```
$ vendor/ffmpeg/macos/ffmpeg -f avfoundation -pixel_format uyvy422 \
-framerate 30 -video_size 640x480 -i "0:none" \
-f image2pipe -vcodec mjpeg -q:v 5 -an -frames:v 1 -y /tmp/cam_ok.jpg
Input #0, avfoundation, from '0:none':
Stream #0:0: Video: rawvideo (UYVY / 0x59565955), uyvy422, 640x480, 30 tbr
Stream mapping:
Stream #0:0 -> #0:0 (rawvideo (native) -> mjpeg (native))
frame= 1 ... Lsize= 15KiB
$ file /tmp/cam_ok.jpg
/tmp/cam_ok.jpg: JPEG image data, baseline, precision 8, 640x480, components 3
```
- ✅ 攝影機成功打開(`-pixel_format uyvy422`+ `rawvideo (native) -> mjpeg (native)` 轉碼鏈全通
- ✅ 真的從實體攝影機出一張 640x480 JPEG15 KiB兩層根因args pixel_format + rawvideo decoder都修好。
---
## Commit 清單(只允許這四個檔進 git

Binary file not shown.

Binary file not shown.