<!-- title: Claude Code音视频流媒体全链路实战 -->

## 概述

本教程面向有一定Claude Code使用经验的开发者，教你如何利用Claude Code构建完整的音视频流媒体处理链路——从FFmpeg编码、MediaMTX媒体服务器、到WebRTC低延迟推拉流。学完本教程，你将独立搭建一个端到端的实时直播系统，并掌握Claude Code在多媒体领域的实战技巧。

**适用人群**：有Claude Code基础、对音视频开发感兴趣的后端/全栈开发者

**学习目标**：
- 用Claude Code生成可靠的FFmpeg命令链
- 搭建MediaMTX + WebRTC流媒体服务器
- 实现WHIP推流 + WHEP拉流的完整闭环
- 构建自动化视频处理Pipeline

---

## 前置条件

| 条件 | 说明 |
|------|------|
| Claude Code | 已安装并完成API Key配置 |
| FFmpeg 7.x+ | 支持WHIP muxer（2025年已合并主线） |
| Node.js 18+ | 运行媒体服务器和管理脚本 |
| Docker | 可选，用于容器化部署 |
| 操作系统 | macOS / Linux（推荐Ubuntu 22.04+） |
| 网络 | 本地开发环境，需开放UDP端口 |

### 环境检查

```bash
# 检查FFmpeg版本（需要7.0+以支持WHIP）
ffmpeg -version | head -1

# 检查FFmpeg是否编译了WHIP支持
ffmpeg -protocols 2>&1 | grep whip

# 检查Node.js版本
node --version

# 检查Docker（可选）
docker --version
```

如果FFmpeg版本低于7.0或没有WHIP支持，可通过以下方式升级：

```bash
# Ubuntu - 使用FFmpeg官方PPA
sudo add-apt-repository ppa:savoury1/ffmpeg6
sudo apt update && sudo apt install ffmpeg

# macOS
brew install ffmpeg
```

---

## 第一步：理解流媒体全链路架构

在开始编码前，先用Claude Code生成一张完整的架构图，帮助我们理解数据流向。

### 架构全景

```mermaid
graph LR
    subgraph 采集源
        A[摄像头/RTSP] --> B[本地文件]
        A --> C[桌面采集]
    end

    subgraph FFmpeg处理层
        D[解码] --> E[滤镜/水印]
        E --> F[编码 H.264/VP8]
        F --> G[封装]
    end

    subgraph 信令与分发
        H[WHIP推流端点] --> I[MediaMTX SFU]
        I --> J[WHEP拉流端点]
    end

    subgraph 播放端
        K[浏览器 WebRTC] --> L[移动端 SDK]
        L --> M[第三方平台]
    end

    B --> D
    G --> H
    J --> K
```

### 用Claude Code理解协议栈

在项目根目录创建`CLAUDE.md`，注入流媒体领域知识：

```markdown
# CLAUDE.md - 流媒体项目上下文

## 项目概述
实时流媒体系统，FFmpeg编码 + MediaMTX中转 + WebRTC分发

## 关键协议
- WHIP: WebRTC-HTTP Ingestion Protocol（推流协议，IETF RFC drafts）
- WHEP: WebRTC-HTTP Egress Protocol（拉流协议）
- RTMP: Real-Time Messaging Protocol（传统推流，TCP）
- SRT: Secure Reliable Transport（低延迟传输，UDP）

## 编码规范
- 视频编码优先H.264（兼容性最佳），备选VP8/VP9
- 音频编码使用AAC或Opus（WebRTC场景必须Opus）
- 关键帧间隔建议2秒（`-g 60`@30fps）

## FFmpeg注意事项
- 硬件加速优先：h264_nvenc（NVIDIA）、h264_videotoolbox（macOS）
- 编码preset推荐：veryfast或ultrafast（低延迟场景）
- 输出容器格式：MPEG-TS或直接RTP
```

---

## 第二步：FFmpeg命令生成的Claude Code实战

FFmpeg有数千个参数，手动拼命令容易出错。Claude Code在这一环节能极大提升效率。

### 2.1 基础推流命令生成

直接用自然语言描述需求，让Claude Code生成FFmpeg命令：

```bash
# 启动Claude Code
claude

# 在对话中描述需求
> "我需要用FFmpeg将本地摄像头推流到MediaMTX的RTSP端口8554，
>  使用H.264编码、Opus音频、30fps、720p分辨率，
>  要求延迟尽可能低"
```

Claude Code会生成类似如下的命令：

```bash
# Linux (V4L2摄像头 + PulseAudio)
ffmpeg \
  -f v4l2 -framerate 30 -video_size 1280x720 -i /dev/video0 \
  -f pulse -i default \
  -c:v libx264 -preset ultrafast -tune zerolatency \
  -g 60 -keyint_min 60 \
  -b:v 2500k -maxrate 3000k -bufsize 5000k \
  -c:a libopus -b:a 128k \
  -f rtsp rtsp://localhost:8554/mystream

# macOS (AVFoundation)
ffmpeg \
  -f avfoundation -framerate 30 -video_size 1280x720 -i "0:0" \
  -c:v libx264 -preset ultrafast -tune zerolatency \
  -g 60 -keyint_min 60 \
  -b:v 2500k -maxrate 3000k -bufsize 5000k \
  -c:a libopus -b:a 128k \
  -f rtsp rtsp://localhost:8554/mystream
```

### 2.2 关键参数解析

| 参数 | 作用 | 注意事项 |
|------|------|----------|
| `-preset ultrafast` | 编码速度最快，延迟最低 | 文件体积稍大，直播场景可接受 |
| `-tune zerolatency` | 禁用前瞻帧，减少编码延迟 | 与`-preset`配合使用 |
| `-g 60` | 每60帧一个关键帧（2秒@30fps） | WebRTC解码需要定期关键帧 |
| `-b:v 2500k` | 视频码率2.5Mbps | 720p直播推荐2-4Mbps |
| `-c:a libopus` | Opus音频编码 | WebRTC强制要求Opus |
| `-f rtsp` | 输出RTSP协议 | MediaMTX原生支持RTSP |

### 2.3 FFmpeg Skill加速

安装社区FFmpeg Skill，让Claude Code获得更专业的FFmpeg知识：

```bash
# 克隆FFmpeg Skill到本地
git clone https://github.com/ychoi-kr/claude-ffmpeg-skill.git

# 将skill复制到项目目录
cp -r claude-ffmpeg-skill/.claude/skills/ffmpeg-skill \
      ./your-project/.claude/skills/
```

安装后，Claude Code在生成FFmpeg命令时会参考Skill中的参考手册，减少参数幻觉。这在2026年尤为关键——因为FFmpeg在2025年经历了大量API变更（包括WHIP/WHEP合并、新版滤镜语法等），模型训练数据可能滞后。

### 2.4 常见问题：Claude Code生成FFmpeg命令的陷阱

根据社区反馈，Claude Code生成FFmpeg命令时存在以下已知问题：

| 问题 | 表现 | 解决方案 |
|------|------|----------|
| 编码器名称过时 | 使用`libx264-old`等不存在的编码器 | 在CLAUDE.md中列出当前版本支持的编码器 |
| 硬件加速误用 | 在Linux上使用`h264_videotoolbox` | 明确指定操作系统和GPU型号 |
| 滤镜语法错误 | 滤镜链中引号或分号处理不当 | 要求Claude Code逐行解释滤镜链 |
| 码率单位混淆 | `2500k`写成`2500`或`2.5m` | 明确要求使用`k`单位 |

**实战技巧**：让Claude Code先生成命令，再逐参数解释含义，最后用`ffmpeg -help`验证每个参数的合法性：

```bash
# 让Claude Code验证生成的命令
> "请用ffmpeg -help encoder=libx264验证上面命令中的所有
>  libx264相关参数是否合法，然后运行测试"
```

---

## 第三步：MediaMTX媒体服务器搭建

MediaMTX（前身rtsp-simple-server）是2025-2026年最活跃的开源流媒体服务器，支持RTSP/RTMP/WebRTC/HLS/SRT全协议。

### 3.1 Docker一键部署

在Claude Code中执行：

```bash
> "创建一个MediaMTX的Docker Compose配置，
>  要求：开启WebRTC（端口8889）、RTSP（8554）、
>  RTMP（1935）、HLS（8080），
>  并配置WHIP/WHEP端点"
```

生成的`docker-compose.yml`：

```yaml
version: '3.8'

services:
  mediamtx:
    image: bluenviron/mediamtx:latest
    container_name: mediamtx
    ports:
      - "8554:8554"   # RTSP
      - "1935:1935"   # RTMP
      - "8889:8889"   # WebRTC (HTTP API + WHIP/WHEP)
      - "8080:8080"   # HLS
      - "9997:9997"   # Metrics (Prometheus)
    volumes:
      - ./mediamtx.yml:/mediamtx.yml
    restart: unless-stopped
    network_mode: host  # WebRTC需要UDP端口范围
```

### 3.2 MediaMTX配置文件

创建`mediamtx.yml`：

```yaml
# 基础配置
logLevel: info
logDestinations: [stdout]

# RTSP配置
rtspAddress: :8554
rtspTransports: [tcp, udp]

# RTMP配置
rtmpAddress: :1935

# HLS配置
hlsAddress: :8080
hlsVariant: lowLatency
hlsSegmentCount: 4
hlsSegmentDuration: 1s

# WebRTC配置
webrtcAddress: :8889
webrtcICEHostNAT1To1IPs: [127.0.0.1]  # 本地开发用
webrtcICEUDPMuxAddress: :8189
webrtcICETCPMuxAddress: :8189

# 路径配置
paths:
  # 所有路径通配
  all:
    source: publisher
    sourceOnDemand: no
  
  # 专用直播流路径
  livestream:
    source: publisher
    sourceOnDemand: no
    maxReaders: 1000
    
  # RTSP拉取源（从IP摄像头）
  # camera1:
  #   source: rtsp://admin:pass@192.168.1.100:554/stream
```

### 3.3 启动与验证

```bash
# 启动MediaMTX
docker compose up -d

# 检查服务状态
curl http://localhost:8889/v3/config/global/get 2>/dev/null | head -5

# 查看日志
docker logs mediamtx --tail 20
```

预期输出：

```
INF MediaMTX v1.11.0
INF [RTSP] listener opened on :8554 (tcp), :8555 (udp)
INF [RTMP] listener opened on :1935
INF [WebRTC] listener opened on :8889
INF [HLS] listener opened on :8080
```

---

## 第四步：WebRTC WHIP推流实战

FFmpeg 7.x+已内置WHIP muxer，可以直接将编码后的流推送到WebRTC端点。

### 4.1 FFmpeg WHIP推流命令

```bash
# 从本地文件推流到MediaMTX的WHIP端点
ffmpeg -re -i input.mp4 \
  -c:v libx264 -preset veryfast -tune zerolatency \
  -g 60 -keyint_min 60 \
  -b:v 2500k -maxrate 3000k -bufsize 5000k \
  -c:a libopus -b:a 128k -ar 48000 \
  -f whip "http://localhost:8889/mystream/whip"
```

**关键参数说明**：

- `-re`：按原始帧率读取（模拟实时流，不加此参数FFmpeg会以最快速度推完）
- `-f whip`：使用WHIP封装格式输出
- URL格式：`http://<host>:<port>/<stream-path>/whip`

### 4.2 摄像头实时推流

```bash
# Linux摄像头 + WHIP
ffmpeg \
  -f v4l2 -framerate 30 -video_size 1280x720 -i /dev/video0 \
  -f pulse -i default \
  -c:v libx264 -preset ultrafast -tune zerolatency \
  -g 60 -b:v 2500k \
  -c:a libopus -b:a 128k \
  -f whip "http://localhost:8889/livestream/whip"
```

### 4.3 用Claude Code构建推流脚本

让Claude Code创建一个智能推流脚本，支持自动检测设备和多路推流：

```bash
> "创建一个Node.js脚本pusher.mjs，功能如下：
> 1. 自动检测本地摄像头设备（Linux/macOS兼容）
> 2. 自动检测GPU类型选择硬件加速编码器
> 3. 支持同时推流到RTSP和WHIP两个端点
> 4. 进程退出时清理资源
> 5. 带详细的日志输出"
```

生成的`pusher.mjs`核心逻辑：

```javascript
#!/usr/bin/env node

import { execSync, spawn } from 'child_process';
import { platform } from 'os';

const os = platform();

// 检测GPU类型
function detectEncoder() {
  try {
    // 检查NVIDIA GPU
    execSync('nvidia-smi', { stdio: 'ignore' });
    return { video: 'h264_nvenc', name: 'NVIDIA NVENC' };
  } catch {}

  if (os === 'darwin') {
    try {
      execSync('system_profiler SPDisplaysDataType 2>/dev/null | grep Metal', 
               { stdio: 'pipe' });
      return { video: 'h264_videotoolbox', name: 'Apple VideoToolbox' };
    } catch {}
  }

  console.log('[WARN] 未检测到硬件加速，使用CPU编码');
  return { video: 'libx264', name: 'x264 (CPU)' };
}

// 检测摄像头设备
function detectCamera() {
  if (os === 'darwin') {
    return '0'; // AVFoundation默认设备
  }
  // Linux: 查找第一个video设备
  try {
    const devices = execSync('ls /dev/video* 2>/dev/null').toString().trim();
    return devices.split('\n')[0];
  } catch {
    console.error('[ERROR] 未找到摄像头设备');
    process.exit(1);
  }
}

// 构建FFmpeg命令
function buildFFmpegArgs(config) {
  const encoder = detectEncoder();
  const camera = detectCamera();
  const isHW = encoder.video !== 'libx264';

  const inputArgs = os === 'darwin'
    ? ['-f', 'avfoundation', '-framerate', '30',
       '-video_size', '1280x720', '-i', `${camera}:0`]
    : ['-f', 'v4l2', '-framerate', '30',
       '-video_size', '1280x720', '-i', camera,
       '-f', 'pulse', '-i', 'default'];

  const videoArgs = isHW
    ? ['-c:v', encoder.video, '-b:v', '2500k',
       '-maxrate', '3000k', '-bufsize', '5000k']
    : ['-c:v', 'libx264', '-preset', 'ultrafast',
       '-tune', 'zerolatency', '-g', '60',
       '-b:v', '2500k', '-maxrate', '3000k', '-bufsize', '5000k'];

  const whipUrl = config.whipUrl || 'http://localhost:8889/livestream/whip';

  return [
    ...inputArgs,
    ...videoArgs,
    '-c:a', 'libopus', '-b:a', '128k', '-ar', '48000',
    '-f', 'whip', whipUrl
  ];
}

// 启动推流
const config = {
  whipUrl: process.env.WHIP_URL || 'http://localhost:8889/livestream/whip'
};

console.log(`[INFO] 平台: ${os}`);
console.log(`[INFO] 编码器: ${detectEncoder().name}`);
console.log(`[INFO] 推流地址: ${config.whipUrl}`);

const args = buildFFmpegArgs(config);
const ffmpeg = spawn('ffmpeg', args);

ffmpeg.stderr.on('data', (data) => {
  const msg = data.toString();
  if (msg.includes('frame=') || msg.includes('ERROR')) {
    process.stderr.write(msg);
  }
});

// 优雅退出
process.on('SIGINT', () => {
  console.log('\n[INFO] 正在停止推流...');
  ffmpeg.kill('SIGTERM');
  setTimeout(() => process.exit(0), 1000);
});

ffmpeg.on('exit', (code) => {
  console.log(`[INFO] FFmpeg退出，代码: ${code}`);
});
```

---

## 第五步：WebRTC WHEP拉流与前端播放

### 5.1 WHEP拉流

MediaMTX提供WHEP端点，浏览器可直接通过WebRTC接收流：

```bash
# 使用ffmpeg拉取WHEP流（用于录制或转码）
ffmpeg \
  -f whep "http://localhost:8889/mystream/whep" \
  -c copy -f mp4 output.mp4
```

> 注意：WHEP demuxer在FFmpeg主线中仍在完善，部分版本可能需要通过`https://github.com/ossrs/ffmpeg-webrtc`补丁支持。

### 5.2 浏览器端WebRTC播放器

让Claude Code创建一个完整的WebRTC播放页面：

```bash
> "创建一个HTML文件player.html，使用原生WebRTC API
>  连接MediaMTX的WHEP端点播放直播流。
>  要求：1）自动连接 2）显示连接状态 3）显示延迟统计
>  4）支持全屏 5）断线自动重连"
```

生成的`player.html`：

```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebRTC直播播放器</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { background: #000; font-family: system-ui; }
        
        .container {
            display: flex; flex-direction: column;
            align-items: center; justify-content: center;
            min-height: 100vh; padding: 20px;
        }
        
        video {
            width: 100%; max-width: 960px;
            background: #111; border-radius: 8px;
        }
        
        .status-bar {
            display: flex; gap: 20px; margin-top: 12px;
            color: #aaa; font-size: 14px;
        }
        
        .status-dot {
            width: 8px; height: 8px; border-radius: 50%;
            display: inline-block; margin-right: 6px;
        }
        .status-dot.connected { background: #4caf50; }
        .status-dot.connecting { background: #ff9800; animation: pulse 1s infinite; }
        .status-dot.disconnected { background: #f44336; }
        
        @keyframes pulse { 50% { opacity: 0.5; } }
        
        .controls {
            margin-top: 12px; display: flex; gap: 10px;
        }
        .controls button {
            padding: 8px 20px; border: 1px solid #555;
            background: #222; color: #eee; border-radius: 4px;
            cursor: pointer;
        }
        .controls button:hover { background: #333; }
    </style>
</head>
<body>
    <div class="container">
        <video id="video" autoplay playsinline muted></video>
        
        <div class="status-bar">
            <span>
                <span class="status-dot disconnected" id="statusDot"></span>
                <span id="statusText">未连接</span>
            </span>
            <span>延迟: <span id="latency">--</span>ms</span>
            <span>分辨率: <span id="resolution">--</span></span>
            <span>码率: <span id="bitrate">--</span></span>
        </div>
        
        <div class="controls">
            <input id="url" type="text" 
                   value="http://localhost:8889/mystream/whep" 
                   style="width:300px;padding:8px;background:#222;color:#eee;
                          border:1px solid #555;border-radius:4px;">
            <button id="connectBtn">连接</button>
            <button id="fullscreenBtn">全屏</button>
        </div>
    </div>

    <script>
        const video = document.getElementById('video');
        const statusDot = document.getElementById('statusDot');
        const statusText = document.getElementById('statusText');
        const latencyEl = document.getElementById('latency');
        const resolutionEl = document.getElementById('resolution');
        const bitrateEl = document.getElementById('bitrate');
        const urlInput = document.getElementById('url');
        
        let pc = null;
        let reconnectTimer = null;
        let statsInterval = null;

        function setStatus(state, text) {
            statusDot.className = `status-dot ${state}`;
            statusText.textContent = text;
        }

        async function connect() {
            if (pc) { pc.close(); pc = null; }
            if (reconnectTimer) { clearTimeout(reconnectTimer); }
            
            setStatus('connecting', '连接中...');
            
            pc = new RTCPeerConnection({
                iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
            });

            // 接收远端轨道
            pc.ontrack = (event) => {
                video.srcObject = event.streams[0];
            };

            pc.addTransceiver('video', { direction: 'recvonly' });
            pc.addTransceiver('audio', { direction: 'recvonly' });

            pc.oniceconnectionstatechange = () => {
                switch (pc.iceConnectionState) {
                    case 'connected':
                    case 'completed':
                        setStatus('connected', '已连接');
                        startStats();
                        break;
                    case 'disconnected':
                        setStatus('disconnected', '已断开');
                        scheduleReconnect();
                        break;
                    case 'failed':
                        setStatus('disconnected', '连接失败');
                        scheduleReconnect();
                        break;
                }
            };

            // 创建Offer并通过WHEP发送
            const offer = await pc.createOffer();
            await pc.setLocalDescription(offer);

            // 等待ICE候选收集完成
            await new Promise((resolve) => {
                if (pc.iceGatheringState === 'complete') return resolve();
                pc.onicegatheringstatechange = () => {
                    if (pc.iceGatheringState === 'complete') resolve();
                };
            });

            try {
                const resp = await fetch(urlInput.value, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/sdp' },
                    body: pc.localDescription.sdp
                });

                if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
                
                const answer = await resp.text();
                await pc.setRemoteDescription(
                    new RTCSessionDescription({ type: 'answer', sdp: answer })
                );
            } catch (e) {
                console.error('WHEP连接失败:', e);
                setStatus('disconnected', '连接失败');
                scheduleReconnect();
            }
        }

        function scheduleReconnect() {
            reconnectTimer = setTimeout(() => {
                console.log('尝试重连...');
                connect();
            }, 3000);
        }

        function startStats() {
            if (statsInterval) clearInterval(statsInterval);
            statsInterval = setInterval(async () => {
                if (!pc) return;
                const stats = await pc.getStats();
                stats.forEach((report) => {
                    if (report.type === 'inbound-rtp' && report.kind === 'video') {
                        resolutionEl.textContent = 
                            `${report.frameWidth || '?'}x${report.frameHeight || '?'}`;
                        const br = report.bytesReceived 
                            ? Math.round((report.bytesReceived * 8) / 
                                ((Date.now() - report.timestamp) / 1000) / 1000)
                            : '?';
                        bitrateEl.textContent = `${br}kbps`;
                    }
                });
            }, 1000);
        }

        // 按钮事件
        document.getElementById('connectBtn').addEventListener('click', connect);
        document.getElementById('fullscreenBtn').addEventListener('click', () => {
            video.requestFullscreen?.();
        });

        // 页面加载后自动连接
        connect();
    </script>
</body>
</html>
```

### 5.3 验证完整链路

```bash
# 终端1：启动MediaMTX
docker compose up -d

# 终端2：用FFmpeg推流测试文件
ffmpeg -re -i test_video.mp4 \
  -c:v libx264 -preset veryfast -tune zerolatency \
  -g 60 -b:v 2500k \
  -c:a libopus -b:a 128k \
  -f whip "http://localhost:8889/mystream/whip"

# 终端3：用Python快速启动HTTP服务器播放页面
python3 -m http.server 3000
# 浏览器打开 http://localhost:3000/player.html
```

---

## 第六步：自动化视频处理Pipeline

将上述组件串联成一个可复用的自动化Pipeline。

### 6.1 Pipeline架构

```mermaid
flowchart TD
    A[输入源] --> B{类型判断}
    B -->|本地文件| C[FFmpeg转码]
    B -->|RTSP流| D[FFmpeg拉流+转码]
    B -->|摄像头| E[FFmpeg采集+编码]

    C --> F[输出选择器]
    D --> F
    E --> F

    F -->|低延迟直播| G[WHIP → MediaMTX]
    F -->|CDN分发| H[HLS切片 → CDN]
    F -->|录制存档| I[MPEG-TS → 对象存储]

    G --> J[WHEP → 浏览器播放]
    H --> K[HLS.js → 回放]
    I --> L[点播系统]

    style A fill:#e1f5fe
    style G fill:#c8e6c9
    style J fill:#fff9c4
```

### 6.2 Pipeline管理脚本

让Claude Code创建完整的Pipeline管理工具：

```bash
> "创建一个stream-pipeline.mjs脚本，实现：
> 1. 支持三种输入源（文件、RTSP、摄像头）
> 2. 支持三种输出（WHIP推流、HLS切片、本地录制）
> 3. 可通过配置文件或命令行参数控制
> 4. 支持多路并行处理
> 5. 进程管理和健康检查"
```

核心代码`stream-pipeline.mjs`：

```javascript
#!/usr/bin/env node

import { spawn, fork } from 'child_process';
import { readFileSync, existsSync } from 'fs';
import { argv, exit } from 'process';

// 配置加载
function loadConfig(path = './pipeline.json') {
  if (!existsSync(path)) {
    console.error(`[ERROR] 配置文件 ${path} 不存在`);
    exit(1);
  }
  return JSON.parse(readFileSync(path, 'utf-8'));
}

// FFmpeg进程管理器
class StreamProcess {
  constructor(name, config) {
    this.name = name;
    this.config = config;
    this.process = null;
    this.restartCount = 0;
    this.maxRestarts = 5;
    this.startTime = null;
  }

  buildArgs() {
    const { input, output } = this.config;
    const args = [];

    // 输入源
    if (input.type === 'file') {
      args.push('-re', '-i', input.path);
    } else if (input.type === 'rtsp') {
      args.push(
        '-rtsp_transport', 'tcp',
        '-i', input.url
      );
    } else if (input.type === 'camera') {
      args.push(
        '-f', process.platform === 'darwin' ? 'avfoundation' : 'v4l2',
        '-framerate', String(input.fps || 30),
        '-video_size', input.resolution || '1280x720',
        '-i', input.device || '0'
      );
      if (process.platform !== 'darwin') {
        args.push('-f', 'pulse', '-i', input.audioDevice || 'default');
      }
    }

    // 视频编码
    const encoder = input.hwAccel || 'libx264';
    if (encoder === 'libx264') {
      args.push(
        '-c:v', 'libx264',
        '-preset', output.preset || 'veryfast',
        '-tune', 'zerolatency',
        '-g', String((input.fps || 30) * 2)
      );
    } else {
      args.push('-c:v', encoder);
    }
    args.push(
      '-b:v', output.videoBitrate || '2500k',
      '-maxrate', output.maxBitrate || '3000k'
    );

    // 音频编码
    args.push('-c:a', 'libopus', '-b:a', output.audioBitrate || '128k');

    // 输出
    if (output.type === 'whip') {
      args.push('-f', 'whip', output.url);
    } else if (output.type === 'hls') {
      args.push(
        '-f', 'hls',
        '-hls_time', '2',
        '-hls_list_size', '3',
        '-hls_flags', 'delete_segments',
        output.path
      );
    } else if (output.type === 'record') {
      args.push('-c', 'copy', '-f', 'mp4', output.path);
    }

    return args;
  }

  start() {
    const args = this.buildArgs();
    console.log(`[${this.name}] 启动: ffmpeg ${args.join(' ')}`);

    this.process = spawn('ffmpeg', args);
    this.startTime = Date.now();

    this.process.stderr.on('data', (data) => {
      const msg = data.toString();
      // 只打印关键信息
      if (msg.includes('frame=') && Math.random() < 0.1) {
        process.stdout.write(`[${this.name}] ${msg}`);
      }
      if (msg.toLowerCase().includes('error')) {
        process.stderr.write(`[${this.name}] ERROR: ${msg}`);
      }
    });

    this.process.on('exit', (code) => {
      const uptime = ((Date.now() - this.startTime) / 1000).toFixed(0);
      console.log(`[${this.name}] 退出 (代码=${code}, 运行=${uptime}s)`);
      
      if (code !== 0 && this.restartCount < this.maxRestarts) {
        this.restartCount++;
        const delay = Math.min(5000 * this.restartCount, 30000);
        console.log(`[${this.name}] ${delay/1000}秒后重启 (${this.restartCount}/${this.maxRestarts})`);
        setTimeout(() => this.start(), delay);
      }
    });
  }

  stop() {
    if (this.process) {
      this.process.kill('SIGTERM');
    }
  }

  getUptime() {
    return this.startTime ? Date.now() - this.startTime : 0;
  }
}

// 主流程
const configPath = argv[2] || './pipeline.json';
const config = loadConfig(configPath);

console.log(`[Pipeline] 加载配置: ${configPath}`);
console.log(`[Pipeline] 启动 ${config.streams.length} 路流`);

const processes = config.streams.map(
  (stream) => new StreamProcess(stream.name, stream)
);

// 启动所有流
processes.forEach((p) => p.start());

// 定期健康检查
setInterval(() => {
  processes.forEach((p) => {
    const uptime = (p.getUptime() / 1000).toFixed(0);
    const status = p.process?.exitCode === null ? '运行中' : '已停止';
    console.log(`[Health] ${p.name}: ${status} (运行${uptime}s, 重启${p.restartCount}次)`);
  });
}, 30000);

// 优雅退出
process.on('SIGINT', () => {
  console.log('\n[Pipeline] 正在停止所有流...');
  processes.forEach((p) => p.stop());
  setTimeout(() => process.exit(0), 2000);
});
```

### 6.3 Pipeline配置文件

创建`pipeline.json`：

```json
{
  "streams": [
    {
      "name": "live-camera",
      "input": {
        "type": "camera",
        "device": "/dev/video0",
        "fps": 30,
        "resolution": "1280x720"
      },
      "output": {
        "type": "whip",
        "url": "http://localhost:8889/live/whip",
        "preset": "ultrafast",
        "videoBitrate": "2500k",
        "maxBitrate": "3000k"
      }
    },
    {
      "name": "rtsp-relay",
      "input": {
        "type": "rtsp",
        "url": "rtsp://admin:pass@192.168.1.100:554/stream1"
      },
      "output": {
        "type": "whip",
        "url": "http://localhost:8889/camera1/whip",
        "preset": "veryfast",
        "videoBitrate": "4000k",
        "maxBitrate": "5000k"
      }
    },
    {
      "name": "vod-recording",
      "input": {
        "type": "rtsp",
        "url": "rtsp://admin:pass@192.168.1.100:554/stream1"
      },
      "output": {
        "type": "record",
        "path": "/recordings/camera1_$(date +%Y%m%d_%H%M%S).mp4"
      }
    }
  ]
}
```

### 6.4 启动Pipeline

```bash
# 给脚本执行权限
chmod +x stream-pipeline.mjs

# 启动Pipeline
./stream-pipeline.mjs pipeline.json

# 后台运行（生产环境建议用systemd或Docker）
nohup ./stream-pipeline.mjs pipeline.json > pipeline.log 2>&1 &
```

---

## 实战案例：多路监控直播系统

将以上所有组件组合，构建一个完整的多路监控+直播系统。

### 系统架构

```mermaid
graph TB
    subgraph 采集层
        C1[摄像头1 RTSP]
        C2[摄像头2 RTSP]
        C3[桌面采集]
        C4[测试文件]
    end

    subgraph 处理层 - Claude Code生成的FFmpeg命令
        F1[FFmpeg进程1]
        F2[FFmpeg进程2]
        F3[FFmpeg进程3]
        F4[FFmpeg进程4]
    end

    subgraph 服务层
        MTX[MediaMTX SFU]
        WEB[Web管理面板]
        API[流管理API]
    end

    subgraph 分发层
        P1[浏览器WebRTC]
        P2[HLS回放]
        P3[录制存储]
    end

    C1 --> F1 --> MTX
    C2 --> F2 --> MTX
    C3 --> F3 --> MTX
    C4 --> F4 --> MTX

    MTX <--> API <--> WEB
    MTX --> P1
    MTX --> P2
    MTX --> P3
```

### 完整项目结构

```
streaming-platform/
├── CLAUDE.md                    # 项目上下文
├── docker-compose.yml           # MediaMTX + Web面板
├── mediamtx.yml                 # MediaMTX配置
├── pipeline.json                # 多路流配置
├── stream-pipeline.mjs          # Pipeline管理器
├── pusher.mjs                   # 推流工具
├── player.html                  # WebRTC播放器
├── dashboard/                   # 管理面板
│   ├── index.html
│   ├── styles.css
│   └── app.mjs
├── scripts/
│   ├── start-all.sh             # 一键启动
│   └── health-check.sh          # 健康检查
└── recordings/                  # 录制文件目录
```

### 一键启动脚本

```bash
#!/bin/bash
# scripts/start-all.sh

set -e

echo "=== 流媒体平台启动 ==="

# 1. 检查依赖
command -v ffmpeg >/dev/null 2>&1 || { echo "需要FFmpeg"; exit 1; }
command -v node >/dev/null 2>&1 || { echo "需要Node.js"; exit 1; }

# 2. 创建必要目录
mkdir -p recordings

# 3. 启动MediaMTX
echo "[1/3] 启动MediaMTX..."
docker compose up -d
sleep 3

# 4. 验证MediaMTX就绪
if curl -s http://localhost:8889/v3/config/global/get > /dev/null 2>&1; then
    echo "  MediaMTX就绪 ✓"
else
    echo "  MediaMTX未就绪，请检查日志"
    exit 1
fi

# 5. 启动Pipeline
echo "[2/3] 启动流Pipeline..."
node stream-pipeline.mjs pipeline.json &
PIPELINE_PID=$!
echo "  Pipeline PID: $PIPELINE_PID"

# 6. 启动Web面板
echo "[3/3] 启动Web管理面板..."
cd dashboard && python3 -m http.server 3000 &
DASHBOARD_PID=$!
cd ..

echo ""
echo "=== 启动完成 ==="
echo "  WebRTC播放器: http://localhost:3000/player.html"
echo "  管理面板:     http://localhost:3000"
echo "  MediaMTX API: http://localhost:8889"
echo ""
echo "按Ctrl+C停止所有服务..."

# 捕获退出信号
trap "echo '停止中...'; kill $PIPELINE_PID $DASHBOARD_PID 2>/dev/null; docker compose down; exit 0" SIGINT SIGTERM

wait
```

### 管理面板核心逻辑

用Claude Code生成管理面板：

```bash
> "创建dashboard/index.html管理面板，要求：
> 1. 显示所有活跃流的缩略图
> 2. 每路流的连接数、码率、延迟统计
> 3. 支持开始/停止单路流
> 4. 调用MediaMTX的/v3 API获取状态
> 5. 使用简洁的深色主题"
```

管理面板通过MediaMTX REST API获取流状态：

```javascript
// 获取所有活跃路径
async function getStreams() {
  const resp = await fetch('http://localhost:8889/v3/paths/list');
  const data = await resp.json();
  return data.items || [];
}

// 获取特定流的信息
async function getStreamInfo(path) {
  const resp = await fetch(`http://localhost:8889/v3/paths/get/${path}`);
  return resp.json();
}
```

---

## 第七步：性能调优与生产部署

### 7.1 延迟优化清单

| 优化项 | 配置 | 预期效果 |
|--------|------|----------|
| 编码preset | `ultrafast` / `veryfast` | 减少100-300ms编码延迟 |
| zerolatency tune | `-tune zerolatency` | 消除B帧缓冲 |
| 硬件加速 | NVENC / VideoToolbox | 减少50-200ms编码延迟 |
| GOP大小 | `-g 60`（2秒@30fps） | 限制最大解码等待 |
| WebRTC ICE | 配置STUN/TURN | 减少NAT穿透延迟 |
| UDP缓冲区 | `sysctl net.core.rmem_max=26214400` | 减少丢包 |

### 7.2 Docker生产部署

```yaml
# docker-compose.prod.yml
version: '3.8'

services:
  mediamtx:
    image: bluenviron/mediamtx:latest
    network_mode: host
    volumes:
      - ./mediamtx.yml:/mediamtx.yml
      - ./recordings:/recordings
    restart: always
    environment:
      - TZ=Asia/Shanghai
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 512M

  pipeline:
    build:
      context: .
      dockerfile: Dockerfile.pipeline
    network_mode: host
    volumes:
      - ./pipeline.json:/app/pipeline.json
      - ./recordings:/recordings
    restart: always
    depends_on:
      - mediamtx
    deploy:
      resources:
        limits:
          memory: 4G
          cpus: '4'

  dashboard:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./dashboard:/usr/share/nginx/html
    restart: always
```

### 7.3 Claude Code辅助调优

在性能调优阶段，Claude Code可以快速分析FFmpeg的运行日志并给出优化建议：

```bash
# 将FFmpeg统计日志传给Claude Code分析
ffmpeg -re -i test.mp4 \
  -c:v libx264 -preset veryfast \
  -g 60 -b:v 2500k \
  -c:a libopus -b:a 128k \
  -f whip "http://localhost:8889/test/whip" \
  2>&1 | tee ffmpeg.log

# 用Claude Code分析日志
cat ffmpeg.log | claude -p "分析这个FFmpeg运行日志，
  找出：1）编码速度是否跟得上实时帧率 
  2）有没有丢帧 3）给出优化建议"
```

### 7.4 压力测试

```bash
# 用Claude Code生成多路并发推流脚本
> "创建一个脚本stress-test.sh，同时推4路720p流到MediaMTX，
>  每路使用不同的流名称，持续60秒后停止，
>  最后报告每路的帧率和丢帧统计"
```

---

## 常见问题排查

### 问题1：WHIP连接被拒绝

**现象**：`ffmpeg`报错`Connection refused`或`404 Not Found`

**排查**：
```bash
# 检查MediaMTX是否运行
curl http://localhost:8889/v3/paths/list

# 检查端口是否开放
ss -tlnp | grep 8889

# 确认流路径配置正确
# WHIP URL格式: http://host:port/<path>/whip
```

### 问题2：WebRTC画面黑屏

**现象**：浏览器连接成功但无画面

**排查**：
```bash
# 检查FFmpeg是否正确编码
ffmpeg -re -i test.mp4 -frames:v 10 -f null -

# 检查ICE候选是否正确
# 确认mediamtx.yml中webrtcICEHostNAT1To1IPs配置
# NAT环境需要配置公网IP
```

### 问题3：延迟过高（>3秒）

**排查步骤**：
```bash
# 1. 检查编码延迟
ffmpeg -benchmark -re -i test.mp4 \
  -c:v libx264 -preset ultrafast -tune zerolatency \
  -frames:v 300 -f null -

# 2. 检查网络延迟（应在同一局域网测试）
ping <mediamtx-host>

# 3. 检查缓冲区大小
cat /proc/sys/net/core/rmem_max
# 建议 ≥ 26214400
```

---

## Claude Code在音视频开发中的最佳实践

### DO（推荐做法）

1. **在CLAUDE.md中预置FFmpeg版本信息**——避免Claude Code生成已废弃的参数
2. **让Claude Code先生成、再验证**——用`ffmpeg -help`确认参数合法性
3. **使用FFmpeg Skill**——社区维护的Skill包含大量经过验证的命令模板
4. **分步骤调试**——先让Claude Code生成最简命令，确认能跑再逐步加参数
5. **保留命令日志**——每次成功的FFmpeg命令都记录到项目文档中

### DON'T（避免做法）

1. **不要直接信任生成的FFmpeg命令**——始终验证编码器名称和参数
2. **不要在CLAUDE.md中写入临时调试信息**——会干扰后续命令生成
3. **不要让Claude Code一次性处理超过3路流**——复杂度会导致幻觉
4. **不要忽略FFmpeg的stderr输出**——那是唯一的调试信息来源
5. **不要在WebRTC场景使用AAC音频**——WebRTC只支持Opus和PCMU/PCMA

---

## 延伸阅读

- [FFmpeg WHIP/WHEP官方Wiki](https://trac.ffmpeg.org/wiki/StreamingGuide) — FFmpeg流媒体指南
- [MediaMTX项目文档](https://github.com/bluenviron/mediamtx) — 完整配置和API参考
- [WebRTC WHIP规范 (IETF)](https://datatracker.ietf.org/doc/draft-ietf-wish-whip/) — WHIP协议标准草案
- [Claude Code FFmpeg Skill](https://github.com/ychoi-kr/claude-ffmpeg-skill) — 社区FFmpeg Skill
- [ossrs/ffmpeg-webrtc](https://github.com/ossrs/ffmpeg-webrtc) — FFmpeg WebRTC补丁

---

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| **适合训练营类型** | 音视频开发实战营、流媒体工程师训练营、全栈多媒体训练营 |
| **预计教学时长** | 4-6小时（含实操） |
| **难度等级** | 中高级（需要网络和多媒体基础） |
| **分组建议** | 2-3人一组，每组搭建独立的流媒体Pipeline |
| **考核方式** | 搭建可运行的多路直播系统，端到端延迟<1秒 |

---

## 参考来源

1. [Using Claude Code for FFmpeg: Your AI-Powered Video & Audio Processing Assistant](https://claude-blog.setec.rs/blog/claude-code-ffmpeg-video-audio-processing) — Claude Code与FFmpeg结合的完整工作流指南
2. [Claude Code Automates FFmpeg Video Rendering](https://claudearchitectcertification.com/blog/2026-05-02-claude-code-automates-ffmpeg) — 2026年Claude Code生成FFmpeg命令的准确度评估与最佳实践
3. [ossrs/ffmpeg-webrtc: Support WebRTC WHIP and WHEP for FFmpeg](https://github.com/ossrs/ffmpeg-webrtc) — FFmpeg WebRTC WHIP/WHEP补丁实现
4. [Part 1: Building Your First Video Pipeline: FFmpeg & MediaMTX Basics](https://hackernoon.com/part-1building-your-first-video-pipeline-ffmpeg-and-mediamtx-basics) — FFmpeg+MediaMTX流媒体Pipeline实战教程
5. [Claude-Code-Video-Toolkit](https://github.com/wilwaldon/Claude-Code-Video-Toolkit) — Claude Code视频制作工具包，覆盖Remotion/Manim/FFmpeg工作流

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
