feat(local-agent): camera 三平台 input device(ADR-020 WP-1/2)

camera 即時推論開不了根因:vendor decoder-only ffmpeg --disable-everything
沒 enable 任何 input device → macOS avfoundation 認不得。

- WP-1 macOS:ffmpeg rebuild 加 --enable-avfoundation + --enable-indev=avfoundation
  (--disable-autodetect 會靜默 disable、兩行要一起帶)。-list_devices 列出相機、
  +22KB、LGPL-safe。sha 已核對。
- WP-2:buildCaptureArgs 補 Linux v4l2 分支(原誤落 avfoundation default 必壞)+
  ListFFmpegDevices Linux glob /dev/video*。四路明確 case + 回歸鎖。

reviewer WP-1(0C/0M/0m) + WP-2(0C/0M/2m) 通過。Windows dshow / Linux v4l2 實機驗待機器。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-08-02 16:28:51 +08:00
parent e4d27594d6
commit 17134e8eae
7 changed files with 389 additions and 35 deletions

View File

@ -178,6 +178,8 @@ vendor-ffmpeg-macos-build: ## macOS從源碼 build LGPL v3 decoder-only ffmpe
--disable-everything \
--enable-small \
--enable-protocol=file,pipe \
--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-parser=h264,hevc,mpeg4video,mpegaudio,aac \

View File

@ -17,7 +17,7 @@ import (
const stderrTailBytes = 8 * 1024
// FFmpegCamera captures webcam frames using ffmpeg subprocess.
// Supports macOS (AVFoundation) and Windows (DirectShow).
// Supports macOS (AVFoundation), Windows (DirectShow) and Linux (Video4Linux2).
// ffmpeg outputs a continuous MJPEG stream to stdout which is parsed
// by scanning for JPEG SOI (0xFFD8) and EOI (0xFFD9) markers.
type FFmpegCamera struct {
@ -167,10 +167,33 @@ func (c *FFmpegCamera) StderrTail() string {
// buildCaptureArgs returns the ffmpeg arguments for the current OS.
func buildCaptureArgs(cameraIndex int, cameraName string, width, height, framerate int) []string {
return buildCaptureArgsForOS(runtime.GOOS, cameraIndex, cameraName, width, height, framerate)
}
// buildCaptureArgsForOS 依指定 goos 組 ffmpeg capture args把平台判斷抽成參數以便
// table test 三平台輸出(不用 mock runtime.GOOS。三平台各用不同 input deviceindev
// - macOS → avfoundation-i "<index>:none"
// - Windows → dshow-i video="<name>"
// - Linux → v4l2-i /dev/video<N>
//
// 三平台後段皆接 -f image2pipe -vcodec mjpeg -q:v 5 -an -MJPEG pipe 架構共用。
// 對照見 ADR-020 §2.2 三平台 capture args 對照表。
func buildCaptureArgsForOS(goos string, cameraIndex int, cameraName string, width, height, framerate int) []string {
videoSize := fmt.Sprintf("%dx%d", width, height)
fps := fmt.Sprintf("%d", framerate)
switch runtime.GOOS {
// captureTail 是三平台共用的輸出段(把 raw frame 轉成 stdout 上的 MJPEG stream
captureTail := func(args []string) []string {
return append(args,
"-f", "image2pipe",
"-vcodec", "mjpeg",
"-q:v", "5",
"-an",
"-",
)
}
switch goos {
case "windows":
// DirectShow on Windows: -f dshow -i video="Camera Name"
inputName := cameraName
@ -183,30 +206,41 @@ func buildCaptureArgs(cameraIndex int, cameraName string, width, height, framera
inputName = "Integrated Camera"
}
}
return []string{
return captureTail([]string{
"-f", "dshow",
"-framerate", fps,
"-video_size", videoSize,
"-i", fmt.Sprintf("video=%s", inputName),
"-f", "image2pipe",
"-vcodec", "mjpeg",
"-q:v", "5",
"-an",
"-",
}
default:
})
case "linux":
// Video4Linux2 on Linux: -f v4l2 -i /dev/video<N>
// v4l2 以裝置節點路徑(非 index指定攝影機cameraIndex 對應 /dev/video<N>
// 這是 Linux 攝影機的慣例(/dev/video0 = 第一支。cameraName 在 Linux 未使用。
return captureTail([]string{
"-f", "v4l2",
"-framerate", fps,
"-video_size", videoSize,
"-i", fmt.Sprintf("/dev/video%d", cameraIndex),
})
case "darwin":
// AVFoundation on macOS: -f avfoundation -i "index:none"
return []string{
return captureTail([]string{
"-f", "avfoundation",
"-framerate", fps,
"-video_size", videoSize,
"-i", fmt.Sprintf("%d:none", cameraIndex),
"-f", "image2pipe",
"-vcodec", "mjpeg",
"-q:v", "5",
"-an",
"-",
}
})
default:
// 未知平台:明確落到 macOS 的 avfoundation 是錯的(原本 default 就是這個 bug
// 保留 avfoundation 作為最後手段,但這只是為了讓非三大平台不至於 build 失敗;
// 實務上未知平台的 camera 抓取本就不支援,會在 ffmpeg 層以「Unknown input format」失敗。
// 三大目標平台darwin/windows/linux都有明確 case、不會落到這裡。
return captureTail([]string{
"-f", "avfoundation",
"-framerate", fps,
"-video_size", videoSize,
"-i", fmt.Sprintf("%d:none", cameraIndex),
})
}
}

View File

@ -0,0 +1,192 @@
package camera
import (
"strings"
"testing"
)
// TestBuildCaptureArgsForOS 驗證三平台各自用正確的 ffmpeg input deviceindev
// macOS→avfoundation、Windows→dshow、Linux→v4l2。這是 ADR-020 的核心修復:
// 原本 Linux 會誤落 default 分支用 avfoundation → 必壞。
func TestBuildCaptureArgsForOS(t *testing.T) {
const (
width = 640
height = 480
fps = 30
index = 0
)
tests := []struct {
name string
goos string
cameraName string
// wantInputFlag 是預期的 "-f <indev>" 值
wantIndev string
// wantInputArg 是 "-i" 後面的值
wantInputArg string
}{
{
name: "macOS uses avfoundation with index:none",
goos: "darwin",
wantIndev: "avfoundation",
wantInputArg: "0:none",
},
{
name: "Windows uses dshow with video=name",
goos: "windows",
cameraName: "Integrated Camera",
wantIndev: "dshow",
wantInputArg: "video=Integrated Camera",
},
{
name: "Linux uses v4l2 with /dev/video path",
goos: "linux",
wantIndev: "v4l2",
wantInputArg: "/dev/video0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
args := buildCaptureArgsForOS(tt.goos, index, tt.cameraName, width, height, fps)
gotIndev := valueAfterFlag(args, "-f") // 第一個 -f 是 input format
if gotIndev != tt.wantIndev {
t.Errorf("indev = %q, want %q\nargs: %v", gotIndev, tt.wantIndev, args)
}
gotInput := valueAfterFlag(args, "-i")
if gotInput != tt.wantInputArg {
t.Errorf("input = %q, want %q\nargs: %v", gotInput, tt.wantInputArg, args)
}
// 三平台後段皆須為 image2pipe / mjpegMJPEG pipe 架構共用。
if !containsSeq(args, "-f", "image2pipe") {
t.Errorf("missing image2pipe output, args: %v", args)
}
if !containsSeq(args, "-vcodec", "mjpeg") {
t.Errorf("missing mjpeg vcodec, args: %v", args)
}
if args[len(args)-1] != "-" {
t.Errorf("last arg should be stdout '-', got %q", args[len(args)-1])
}
})
}
}
// TestBuildCaptureArgsForOS_LinuxIndexToDevicePath 驗證 Linux 的 cameraIndex 正確
// 對應到 /dev/video<N> 節點路徑。
func TestBuildCaptureArgsForOS_LinuxIndexToDevicePath(t *testing.T) {
for _, idx := range []int{0, 1, 2, 10} {
args := buildCaptureArgsForOS("linux", idx, "", 640, 480, 30)
want := "/dev/video" + itoa(idx)
if got := valueAfterFlag(args, "-i"); got != want {
t.Errorf("index %d → input %q, want %q", idx, got, want)
}
}
}
// TestBuildCaptureArgsForOS_LinuxNotAVFoundation 是 ADR-020 的回歸鎖Linux 絕不能
// 用 avfoundationmacOS 專用)。若未來有人把 Linux case 拿掉、讓它落回 default
// 這個測試會抓到。
func TestBuildCaptureArgsForOS_LinuxNotAVFoundation(t *testing.T) {
args := buildCaptureArgsForOS("linux", 0, "", 640, 480, 30)
joined := strings.Join(args, " ")
if strings.Contains(joined, "avfoundation") {
t.Fatalf("Linux must NOT use avfoundation (macOS-only), args: %v", args)
}
if !strings.Contains(joined, "v4l2") {
t.Fatalf("Linux must use v4l2, args: %v", args)
}
}
// TestParseV4L2Devices 驗證 /dev/video* 路徑清單解析成 CameraInfo且依 index 數值
// 排序video10 排在 video2 之後、而非字典序)。
func TestParseV4L2Devices(t *testing.T) {
paths := []string{
"/dev/video10",
"/dev/video2",
"/dev/video0",
"/dev/video-not-a-number", // 應被略過
"/dev/videoX", // 應被略過
}
got := parseV4L2Devices(paths)
wantIndexes := []int{0, 2, 10}
if len(got) != len(wantIndexes) {
t.Fatalf("got %d devices, want %d: %+v", len(got), len(wantIndexes), got)
}
for i, want := range wantIndexes {
if got[i].Index != want {
t.Errorf("device[%d].Index = %d, want %d", i, got[i].Index, want)
}
if got[i].Name != "/dev/video"+itoa(want) {
t.Errorf("device[%d].Name = %q, want /dev/video%d", i, got[i].Name, want)
}
}
}
// TestParseV4L2Devices_Empty 驗證無裝置時回 nil/空。
func TestParseV4L2Devices_Empty(t *testing.T) {
if got := parseV4L2Devices(nil); len(got) != 0 {
t.Errorf("expected no devices, got %+v", got)
}
}
// TestV4L2Index 驗證從路徑取 index。
func TestV4L2Index(t *testing.T) {
cases := map[string]int{
"/dev/video0": 0,
"/dev/video12": 12,
"/dev/video": -1,
"/dev/videoab": -1,
"videoX": -1,
}
for path, want := range cases {
if got := v4l2Index(path); got != want {
t.Errorf("v4l2Index(%q) = %d, want %d", path, got, want)
}
}
}
// --- helpers ---
// valueAfterFlag 回傳 args 中第一個等於 flag 的元素的下一個值。
func valueAfterFlag(args []string, flag string) string {
for i := 0; i < len(args)-1; i++ {
if args[i] == flag {
return args[i+1]
}
}
return ""
}
// containsSeq 判斷 args 是否包含連續的 a b 兩個元素。
func containsSeq(args []string, a, b string) bool {
for i := 0; i < len(args)-1; i++ {
if args[i] == a && args[i+1] == b {
return true
}
}
return false
}
func itoa(n int) string {
if n == 0 {
return "0"
}
neg := n < 0
if neg {
n = -n
}
var buf []byte
for n > 0 {
buf = append([]byte{byte('0' + n%10)}, buf...)
n /= 10
}
if neg {
buf = append([]byte{'-'}, buf...)
}
return string(buf)
}

View File

@ -3,8 +3,10 @@ package camera
import (
"fmt"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
)
@ -19,7 +21,14 @@ func DetectFFmpeg() bool {
// Automatically selects the correct capture framework for the current OS:
// - macOS: AVFoundation
// - Windows: DirectShow (dshow)
// - Linux: Video4Linux2 (v4l2, enumerated from /dev/video*)
func ListFFmpegDevices() []CameraInfo {
// Linux 走 /dev/video* 列舉、不依賴 ffmpeg binary-list_devices 在部分 v4l2 build
// 不穩定),故不要求 DetectFFmpeg 也能列出裝置節點;實際抓 frame 時才需要 ffmpeg。
if runtime.GOOS == "linux" {
return listV4L2Devices()
}
if !DetectFFmpeg() {
return nil
}
@ -132,3 +141,66 @@ func parseDShowOutput(output string) []CameraInfo {
return cameras
}
// --- Linux (Video4Linux2) ---
// v4l2DeviceGlob 是列舉 v4l2 攝影機節點的 glob pattern。抽成變數讓測試可覆寫成
// 假的目錄,不依賴實機 /dev。
var v4l2DeviceGlob = "/dev/video*"
// v4l2DeviceIndexRe 從 /dev/video<N> 取出 index N。
var v4l2DeviceIndexRe = regexp.MustCompile(`video(\d+)$`)
// listV4L2Devices 列舉 Linux 上的 v4l2 攝影機節點(/dev/video*)。
//
// 為何用 glob 而非 ffmpeg -list_devicesv4l2 indev 的 -list_devices 支援度依 ffmpeg
// 版本而異、部分 build 不輸出可解析清單;直接列舉 /dev/video* 節點是最穩定的跨版本做法。
//
// 注意:/dev/video* 也包含非攝影機的 V4L2 節點(如 metadata / output device這裡先
// 全數列出、由使用者選擇;抓 frame 失敗會在 ffmpeg 層以明確錯誤浮現camera 修 bug 後不再吞錯)。
func listV4L2Devices() []CameraInfo {
matches, err := filepath.Glob(v4l2DeviceGlob)
if err != nil {
return nil
}
return parseV4L2Devices(matches)
}
// parseV4L2Devices 把 /dev/video* 路徑清單轉成 CameraInfo依 index 排序)。
// 抽出來讓測試可直接餵路徑清單、不碰檔案系統。
func parseV4L2Devices(paths []string) []CameraInfo {
// 依裝置 index 數值排序Glob 回傳為字典序video10 會排在 video2 前,需正規化)。
sorted := append([]string(nil), paths...)
sort.Slice(sorted, func(i, j int) bool {
return v4l2Index(sorted[i]) < v4l2Index(sorted[j])
})
var cameras []CameraInfo
for _, path := range sorted {
idx := v4l2Index(path)
if idx < 0 {
continue // 不是 /dev/video<N> 形式、略過
}
cameras = append(cameras, CameraInfo{
ID: fmt.Sprintf("cam-%d", idx),
Name: path, // Linux 以裝置節點路徑作為名稱buildCaptureArgs 用 index 組回路徑)
Index: idx,
Width: 640,
Height: 480,
})
}
return cameras
}
// v4l2Index 從 /dev/video<N> 取出 N非該形式回 -1。
func v4l2Index(path string) int {
m := v4l2DeviceIndexRe.FindStringSubmatch(path)
if len(m) != 2 {
return -1
}
n, err := strconv.Atoi(m[1])
if err != nil {
return -1
}
return n
}

View File

@ -18,17 +18,24 @@ 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-04-15 |
| Build date | 2026-08-02ADR-020 rebuild加回 avfoundation indev |
| Build flags | 見下方 Configure flags 區塊(與 `Makefile``vendor-ffmpeg-macos-build` target 一致) |
> **ADR-020 變更2026-08-02**configure 加 `--enable-avfoundation` + `--enable-indev=avfoundation`
> 讓 camera 即時推論可用 avfoundation 抓實體攝影機。詳見
> `docs/autoflow/04-architecture/adr/adr-020-ffmpeg-camera-indev.md`
> 之前的 decoder-only build2026-04-15沒編任何 indev導致 `Unknown input format: 'avfoundation'`、camera 開不了。
## Binary sha256
| 檔案 | sha256 |
|------|--------|
| `ffmpeg` | `c3cb9f1dad66730267c12fca92c6344d2f8939ab227889caac33005f8947992c` |
| `ffprobe` | `bd388fb4372ed5f7e44ee331a51be6383d702fb2c067bf562cabbdfbdd8b0c5e` |
| `ffmpeg` | `1afa56dabb4ba4fb45323e37510602309ffd544d9e7ecb2d8267697a7cc16626` |
| `ffprobe` | `501ec3fbe450c44f4c28cd7534940c6a16a42f8584e852b4f315de565aba414e` |
| `COPYING.LGPLv3` | `da7eabb7bafdf7d3ae5e9f223aa5bdc1eece45ac569dc21b3b037520b4464768` |
> 舊值2026-04-15 decoder-only、無 indevffmpeg `c3cb9f1d…992c` / ffprobe `bd388fb4…0c5e`
計算指令:
```bash
shasum -a 256 vendor/ffmpeg/macos/ffmpeg vendor/ffmpeg/macos/ffprobe
@ -38,17 +45,21 @@ shasum -a 256 vendor/ffmpeg/macos/ffmpeg vendor/ffmpeg/macos/ffprobe
| 檔案 | Bytes | 人類可讀 |
|------|-------|---------|
| `ffmpeg` | 6,007,520 | 5.7 MB |
| `ffprobe` | 5,865,568 | 5.6 MB |
| `ffmpeg` | 6,030,224 | 5.8 MB |
| `ffprobe` | 5,892,384 | 5.6 MB |
實測比 TDD 原估 1015 MB 小一半,因為 `--disable-everything` + 白名單僅啟用必要 decoder/demuxer/filter無 GPL 元件。
> **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 實測耗時
- **2 分 44 秒**`make vendor-ffmpeg-macos-build``time` 量測)
- user: 559.60ssystem: 56.03swall-clock: 164.56s
- CPU 使用率:~374%macOS x86_648 核 Intel
- 比 TDD 原估 1020 分鐘快很多,因為 `--disable-everything` 大幅削減編譯單元數量
- **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
## License
@ -64,6 +75,13 @@ build 不 link 以下 GPL-only 元件:
僅使用 libavcodec 內建的 LGPL native decoderh264 / hevc / mpeg1video / mpeg2video /
mpeg4 / mjpeg / prores / vp8 / vp9 / aac / mp2 / mp3 / pcm_*)。
**avfoundation indevADR-020為 LGPL-safe不引入任何 GPL 元件**avfoundation input device
只是薄封裝、透過 macOS 系統的 AVFoundation / CoreMedia / CoreVideo framework 抓實體攝影機 frame
不含任何第三方 / GPL codec。加 `--enable-avfoundation` + `--enable-indev=avfoundation` 後,
`ffmpeg -version` 的 configuration line 仍**不含** `--enable-gpl` / `libx264` / `libx265`(已實測驗證),
`--enable-version3`LGPL v3合規未破。新增 link 的皆為 Apple 系統 frameworkAVFoundation /
Foundation / CoreGraphics / libobjc非第三方 dylib。
---
## Configure flags完整複製
@ -82,6 +100,8 @@ mpeg4 / mjpeg / prores / vp8 / vp9 / aac / mp2 / mp3 / pcm_*)。
--disable-everything \
--enable-small \
--enable-protocol=file,pipe \
--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-parser=h264,hevc,mpeg4video,mpegaudio,aac \
@ -109,6 +129,8 @@ mpeg4 / mjpeg / prores / vp8 / vp9 / aac / mp2 / mp3 / pcm_*)。
| `--disable-everything` | 先關全部,白名單 enable確保不額外 link 任何 GPL 元件 |
| `--enable-small` | 最佳化體積而非速度 |
| `--enable-protocol=file,pipe` | 只開 file:// 和 pipeffmpeg 內部 stdin/stdout |
| `--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-parser=...` | 必要,否則某些 decoder 會在碼流切分階段 fail |
@ -192,6 +214,13 @@ codesign -v vendor/ffmpeg/macos/ffprobe
vendor/ffmpeg/macos/ffmpeg -hide_banner -i <some-sample>.mp4 -f image2pipe -vcodec mjpeg -frames:v 1 -q:v 5 /tmp/test.jpg
file /tmp/test.jpg
# 預期JPEG image data
# 7.ADR-020確認 avfoundation indev 有編進去、可列出攝影機
vendor/ffmpeg/macos/ffmpeg -hide_banner -devices 2>&1 | grep avfoundation
# 預期D avfoundation
vendor/ffmpeg/macos/ffmpeg -hide_banner -f avfoundation -list_devices true -i "" 2>&1
# 預期:列出 AVFoundation video/audio devices不再 Unknown input format: 'avfoundation'
# 註:-list_devices true 列完裝置後會以非 0 退出Error opening input屬正常非失敗。
```
---
@ -254,22 +283,28 @@ $ vendor/ffmpeg/macos/ffmpeg -hide_banner -formats 2>&1 \
- `mpegts` — MPEG Transport Stream
- `matroska,webm` — ok
### 4. Dynamic dependencies (`otool -L`)
### 4. Dynamic dependencies (`otool -L`)ADR-020 rebuild 後)
```
vendor/ffmpeg/macos/ffmpeg:
/System/Library/Frameworks/Foundation.framework/.../Foundation ← ADR-020 新增avfoundation 依賴)
/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo
/System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia
/System/Library/Frameworks/AVFoundation.framework/.../AVFoundation ← ADR-020 新增
/System/Library/Frameworks/CoreVideo.framework/.../CoreVideo
/System/Library/Frameworks/CoreMedia.framework/.../CoreMedia
/System/Library/Frameworks/CoreGraphics.framework/.../CoreGraphics ← ADR-020 新增avfoundation suggest
/System/Library/Frameworks/CoreFoundation.framework/.../CoreFoundation
/usr/lib/libobjc.A.dylib ← ADR-020 新增Objective-C runtime
vendor/ffmpeg/macos/ffprobe:
(同上四個 macOS system framework
(同上一組 macOS system framework
```
- ✅ 只依賴 macOS 系統內建 framework`libSystem`, `CoreFoundation`, `CoreVideo`, `CoreMedia`
- ✅ 只依賴 macOS 系統內建 framework / dylibAVFoundation / Foundation / CoreVideo / CoreMedia /
CoreGraphics / CoreFoundation / libSystem / libobjc
- ✅ **無任何第三方 dylib**`libx264`, `libx265`, `libvpx`, `libopus`... 都不存在)
- ✅ 等同於 self-contained binary搬到任一台 macOS 10.15+ x86_64 都能跑
- ✅ 新增的皆為 Apple 第一方系統 framework**LGPL 合規未破**,仍是 self-contained binary搬到任一台
macOS 10.15+ x86_64 都能跑
### 5. Code signing
@ -278,7 +313,26 @@ $ codesign -v vendor/ffmpeg/macos/ffmpeg # exit 0, no output
$ codesign -v vendor/ffmpeg/macos/ffprobe # exit 0, no output
```
ad-hoc simbol signing okGatekeeper 可過。
ad-hoc symbol signing okGatekeeper 可過。
### 6. avfoundation indevADR-020本次 rebuild 新增)
```
$ vendor/ffmpeg/macos/ffmpeg -hide_banner -devices 2>&1 | grep avfoundation
D avfoundation
$ vendor/ffmpeg/macos/ffmpeg -hide_banner -f avfoundation -list_devices true -i ""
[AVFoundation indev @ ...] AVFoundation video devices:
[AVFoundation indev @ ...] [0] FaceTime HD相機內建
[AVFoundation indev @ ...] [1] Capture screen 0
[AVFoundation indev @ ...] AVFoundation audio devices:
[AVFoundation indev @ ...] [0] MacBook Pro的麥克風
```
- ✅ `-devices` 列出 `avfoundation`demuxing supported
- ✅ `-list_devices true` 成功列出實體攝影機FaceTime HD 相機)+ 音訊裝置
- ✅ 不再出現 `Unknown input format: 'avfoundation'`camera 開不了的根因已解)
- 註:`-list_devices true` 列完裝置後以非 0 退出(`Error opening input`)屬正常行為,非失敗。
---

Binary file not shown.

Binary file not shown.