首页 › tutorials › 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端口

环境检查

# 检查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支持,可通过以下方式升级:

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

# macOS
brew install ffmpeg

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

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

架构全景

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,注入流媒体领域知识:

# 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命令:

# 启动Claude Code
claude

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

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

# 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知识:

# 克隆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验证每个参数的合法性:

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

第三步:MediaMTX媒体服务器搭建

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

3.1 Docker一键部署

在Claude Code中执行:

> "创建一个MediaMTX的Docker Compose配置,
>  要求:开启WebRTC(端口8889)、RTSP(8554)、
>  RTMP(1935)、HLS(8080),
>  并配置WHIP/WHEP端点"

生成的docker-compose.yml:

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:

# 基础配置
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:[email protected]:554/stream

3.3 启动与验证

# 启动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推流命令

# 从本地文件推流到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"

关键参数说明:

4.2 摄像头实时推流

# 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创建一个智能推流脚本,支持自动检测设备和多路推流:

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

生成的pusher.mjs核心逻辑:

#!/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接收流:

# 使用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播放页面:

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

生成的player.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 验证完整链路

# 终端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架构

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管理工具:

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

核心代码stream-pipeline.mjs:

#!/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:

{
  "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:[email protected]: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:[email protected]:554/stream1"
      },
      "output": {
        "type": "record",
        "path": "/recordings/camera1_$(date +%Y%m%d_%H%M%S).mp4"
      }
    }
  ]
}

6.4 启动Pipeline

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

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

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

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

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

系统架构

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/                  # 录制文件目录

一键启动脚本

#!/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生成管理面板:

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

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

// 获取所有活跃路径
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生产部署

# 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的运行日志并给出优化建议:

# 将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 压力测试

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

常见问题排查

问题1:WHIP连接被拒绝

现象:ffmpeg报错Connection refused或404 Not Found

排查:

# 检查MediaMTX是否运行
curl http://localhost:8889/v3/paths/list

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

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

问题2:WebRTC画面黑屏

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

排查:

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

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

问题3:延迟过高(>3秒)

排查步骤:

# 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

延伸阅读


训练营适用性评估

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

参考来源

  1. Using Claude Code for FFmpeg: Your AI-Powered Video & Audio Processing Assistant — Claude Code与FFmpeg结合的完整工作流指南
  2. Claude Code Automates FFmpeg Video Rendering — 2026年Claude Code生成FFmpeg命令的准确度评估与最佳实践
  3. ossrs/ffmpeg-webrtc: Support WebRTC WHIP and WHEP for FFmpeg — FFmpeg WebRTC WHIP/WHEP补丁实现
  4. Part 1: Building Your First Video Pipeline: FFmpeg & MediaMTX Basics — FFmpeg+MediaMTX流媒体Pipeline实战教程
  5. 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学通信知识星球海报-扫码加入