本教程面向有一定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.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在这一环节能极大提升效率。
直接用自然语言描述需求,让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
| 参数 | 作用 | 注意事项 |
|---|---|---|
-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 |
安装社区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合并、新版滤镜语法等),模型训练数据可能滞后。
根据社区反馈,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(前身rtsp-simple-server)是2025-2026年最活跃的开源流媒体服务器,支持RTSP/RTMP/WebRTC/HLS/SRT全协议。
在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端口范围
创建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
# 启动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
FFmpeg 7.x+已内置WHIP muxer,可以直接将编码后的流推送到WebRTC端点。
# 从本地文件推流到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封装格式输出http://<host>:<port>/<stream-path>/whip# 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"
让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}`);
});
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补丁支持。
让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>
# 终端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。
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
让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);
});
创建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"
}
}
]
}
# 给脚本执行权限
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();
}
| 优化项 | 配置 | 预期效果 |
|---|---|---|
| 编码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 |
减少丢包 |
# 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
在性能调优阶段,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)给出优化建议"
# 用Claude Code生成多路并发推流脚本
> "创建一个脚本stress-test.sh,同时推4路720p流到MediaMTX,
> 每路使用不同的流名称,持续60秒后停止,
> 最后报告每路的帧率和丢帧统计"
现象: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
现象:浏览器连接成功但无画面
排查:
# 检查FFmpeg是否正确编码
ffmpeg -re -i test.mp4 -frames:v 10 -f null -
# 检查ICE候选是否正确
# 确认mediamtx.yml中webrtcICEHostNAT1To1IPs配置
# NAT环境需要配置公网IP
排查步骤:
# 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
ffmpeg -help确认参数合法性| 维度 | 评估 |
|---|---|
| 适合训练营类型 | 音视频开发实战营、流媒体工程师训练营、全栈多媒体训练营 |
| 预计教学时长 | 4-6小时(含实操) |
| 难度等级 | 中高级(需要网络和多媒体基础) |
| 分组建议 | 2-3人一组,每组搭建独立的流媒体Pipeline |
| 考核方式 | 搭建可运行的多路直播系统,端到端延迟<1秒 |
[!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 等场景全覆盖。
