10 - 流中转与分发
流中转与分发(Relay & Distribution)
10.1 流分发架构概述
在大规模直播场景中,单台服务器无法承载所有观众。需要通过 中转(Relay) 和 CDN 分发 构建多级架构:
生产端 服务端 消费端
┌──────────────┐ ┌───────────────────┐ ┌──────────────┐
│ 编码器/OBS │ │ Origin 源站 │ │ HLS/DASH │
│ │ RTMP │ │ RTMP │ CDN │
│ 主播端 │──────────→│ 接收推流 │────────→│ │
└──────────────┘ │ 转码/录制 │ │ 观众 │
└─────────┬─────────┘ └──────────────┘
│ ▲
│ RTMP Relay │ HTTP
▼ │
┌───────────────────┐ ┌──────────────┐
│ Edge 边缘节点 │───→│ HTTP-FLV │
│ 分发给观众 │ │ WebSocket │
└───────────────────┘ └──────────────┘
10.2 RTMP 流中转(Relay)
10.2.1 SRS 中转模式
SRS 支持三种中转模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 本地中转 | 同一服务器内多进程中转 | 单机部署 |
| 远程中转 | Edge 从 Origin 拉流 | 分布式集群 |
| 推模式 | Origin 主动推送到 Edge | CDN 接入 |
10.2.2 远程中转(Edge-Pull)
# Edge 配置:从 Origin 拉流
listen 1935;
vhost __defaultVhost__ {
cluster {
mode remote;
origin 192.168.1.10:1935;
# 备源
origin 192.168.1.11:1935;
}
# 回源超时(毫秒)
source_retry 3000;
# 缓存
gop_cache on;
}
10.2.3 推模式(Edge-Push)
# Origin 配置:推送到 Edge
listen 1935;
vhost __defaultVhost__ {
cluster {
mode local;
}
# 推送到下游 Edge
edge_push {
enabled on;
# 推送到多个 Edge 节点
destination 192.168.2.10:1935 192.168.2.11:1935;
}
}
10.2.4 FFmpeg 中转脚本
使用 FFmpeg 实现简单的流中转:
#!/bin/bash
# relay.sh - 流中转脚本
SOURCE_URL="rtmp://origin.example.com:1935/live/stream1"
DEST_URLS=(
"rtmp://edge1.example.com:1935/live/stream1"
"rtmp://edge2.example.com:1935/live/stream1"
"rtmp://edge3.example.com:1935/live/stream1"
)
for dest in "${DEST_URLS[@]}"; do
echo "中转到: $dest"
ffmpeg -re -i "$SOURCE_URL" \
-c copy \
-f flv "$dest" \
-loglevel error &
done
wait
10.3 CDN 接入
10.3.1 CDN 直播架构
┌───────────┐ RTMP ┌────────────┐ RTMP/内部 ┌────────────┐
│ 推流端 │───────→│ CDN 源站 │───────────→│ CDN 边缘 │
│ (编码器) │ │ (Origin) │ │ (Edge) │
└───────────┘ └────────────┘ └─────┬──────┘
│
HTTP/HLS/DASH
▼
┌──────────────┐
│ 观众 │
└──────────────┘
10.3.2 主流 CDN 推流地址
| CDN 服务商 | 推流地址格式 | 播放地址格式 |
|---|---|---|
| 阿里云 | rtmp://push.aliyuncdn.com/app/stream?auth_key=xxx | https://pull.aliyuncdn.com/app/stream.m3u8 |
| 腾讯云 | rtmp://push.example.com/live/stream?txSecret=xxx | https://play.example.com/live/stream.flv |
| 七牛云 | rtmp://publish.example.com/live/stream | http://play.example.com/live/stream.m3u8 |
| AWS IVS | rtmp://global-contribute.live-video.net/app/stream | https://xxx.channel.media.amazonaws.com |
10.3.3 CDN 推流最佳实践
# 推送到多个 CDN(实现高可用)
#!/bin/bash
STREAM_KEY="stream1"
CDN_URLS=(
"rtmp://push.cdn1.com/live/${STREAM_KEY}?auth_key=xxx"
"rtmp://push.cdn2.com/live/${STREAM_KEY}?auth_key=yyy"
)
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -b:v 3000k \
-c:a aac -b:a 128k \
-f flv "${CDN_URLS[0]}" &
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -b:v 3000k \
-c:a aac -b:a 128k \
-f flv "${CDN_URLS[1]}" &
10.4 转码(Transcoding)
10.4.1 为什么需要转码
| 场景 | 原始流 | 转码输出 | 原因 |
|---|---|---|---|
| 多码率分发 | 1080p 4Mbps | 720p 2Mbps, 480p 1Mbps | 不同网络带宽的观众 |
| 编码转换 | H.265 | H.264 | 兼容性 |
| 分辨率适配 | 1080p | 720p | 移动端节省流量 |
| 水印添加 | 无水印 | 带 Logo 水印 | 品牌保护 |
| 音频转码 | AAC 128k | AAC 64k | 降低音频码率 |
10.4.2 SRS FFmpeg 转码
# SRS 转码配置
vhost __defaultVhost__ {
transcode {
enabled on;
ffmpeg /usr/bin/ffmpeg;
# 低码率转码
engine low {
enabled on;
vfilter null;
vcodec libx264;
vbitrate 1000;
vfps 30;
vwidth 1280;
vheight 720;
vthreads 2;
vprofile main;
vpreset veryfast;
acodec aac;
abitrate 64;
asample_rate 44100;
achannels 2;
# 输出流名后缀
output rtmp://127.0.0.1:1935/[app]/[stream]_low;
}
# 超低码率
engine mobile {
enabled on;
vcodec libx264;
vbitrate 500;
vfps 15;
vwidth 640;
vheight 360;
vprofile baseline;
vpreset ultrafast;
acodec aac;
abitrate 32;
asample_rate 44100;
achannels 1;
output rtmp://127.0.0.1:1935/[app]/[stream]_mobile;
}
}
}
10.4.3 FFmpeg 直接转码
#!/bin/bash
# 多码率转码脚本
INPUT="rtmp://localhost:1935/live/stream1"
OUTPUT_BASE="rtmp://localhost:1935/live"
# 输入流 → 多输出
ffmpeg -re -i "$INPUT" \
-map 0:v -map 0:a -map 0:v -map 0:a -map 0:v -map 0:a \
-c:v:0 libx264 -b:v:0 4000k -s:v:0 1920x1080 -preset:v:0 veryfast \
-c:v:1 libx264 -b:v:1 2000k -s:v:1 1280x720 -preset:v:1 veryfast \
-c:v:2 libx264 -b:v:2 800k -s:v:2 640x360 -preset:v:2 veryfast \
-c:a aac -b:a 128k -ar 44100 \
-g 60 -keyint_min 60 \
-f flv "${OUTPUT_BASE}/stream1_1080p" \
-f flv "${OUTPUT_BASE}/stream1_720p" \
-f flv "${OUTPUT_BASE}/stream1_360p"
10.5 录制(Recording)
10.5.1 录制格式
| 格式 | 说明 | 适用场景 |
|---|---|---|
| FLV | 最简单的录制格式,RTMP 原生 | 临时录制、快速回放 |
| MP4 | 通用格式,兼容性最好 | 长期存储、点播 |
| TS | HLS 分片格式 | 直接用于 HLS 回放 |
| MPEG-TS | 传输流格式 | 广播电视 |
10.5.2 SRS DVR 录制
# SRS DVR 配置
vhost __defaultVhost__ {
dvr {
enabled on;
dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].flv;
dvr_plan session; # session=会话级, segment=分段
dvr_duration 3600; # 分段时长(秒)
dvr_wait_keyframe on; # 等待关键帧再开始
}
}
10.5.3 HTTP API 控制录制
# 开始录制
curl -X POST http://localhost:1985/api/v1/dvr \
-d '{
"app": "live",
"stream": "stream1",
"action": "start"
}'
# 停止录制
curl -X POST http://localhost:1985/api/v1/dvr \
-d '{
"app": "live",
"stream": "stream1",
"action": "stop"
}'
# 查询录制文件
curl http://localhost:1985/api/v1/dvr?app=live&stream=stream1
10.5.4 FFmpeg 录制脚本
#!/bin/bash
# 录制脚本:接收 RTMP 流,录制为 MP4
RECORD_DIR="/data/recordings"
STREAM_NAME=$1
DURATION=${2:-3600} # 默认 1 小时
if [ -z "$STREAM_NAME" ]; then
echo "用法: $0 <stream_name> [duration_seconds]"
exit 1
fi
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
OUTPUT_FILE="${RECORD_DIR}/${STREAM_NAME}_${TIMESTAMP}.mp4"
echo "录制: rtmp://localhost:1935/live/${STREAM_NAME}"
echo "输出: ${OUTPUT_FILE}"
echo "时长: ${DURATION} 秒"
ffmpeg -i "rtmp://localhost:1935/live/${STREAM_NAME}" \
-c copy \
-t "$DURATION" \
-movflags +faststart \
"$OUTPUT_FILE"
echo "录制完成: ${OUTPUT_FILE}"
10.6 截图与水印
10.6.1 关键帧截图
# 每秒截取一帧
ffmpeg -i rtmp://localhost:1935/live/stream1 \
-vf "fps=1" -q:v 2 \
/data/thumbnails/thumb_%04d.jpg
# 截取当前帧
ffmpeg -i rtmp://localhost:1935/live/stream1 \
-frames:v 1 \
/data/thumbnails/current.jpg
10.6.2 添加水印
# 添加 Logo 水印
ffmpeg -i rtmp://localhost:1935/live/stream1 \
-i /data/watermark/logo.png \
-filter_complex "overlay=W-w-10:H-h-10" \
-c:v libx264 -preset veryfast \
-c:a copy \
-f flv rtmp://localhost:1935/live/stream1_wm
# 添加文字水印
ffmpeg -i rtmp://localhost:1935/live/stream1 \
-vf "drawtext=text='LIVE':fontsize=48:fontcolor=white:x=10:y=10:shadowcolor=black:shadowx=2:shadowy=2" \
-c:v libx264 -preset veryfast \
-c:a copy \
-f flv rtmp://localhost:1935/live/stream1_wm
10.7 直播转点播(Live-to-VOD)
直播结束后自动生成点播回放:
#!/usr/bin/env python3
"""
直播转点播处理脚本
直播结束后处理录制文件,生成点播内容
"""
import os
import subprocess
import glob
from datetime import datetime
def process_recording(input_flv: str, output_dir: str) -> dict:
"""
处理录制的 FLV 文件,生成 MP4 和缩略图
"""
base_name = os.path.splitext(os.path.basename(input_flv))[0]
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# 生成 MP4
mp4_path = os.path.join(output_dir, f"{base_name}.mp4")
subprocess.run([
'ffmpeg', '-i', input_flv,
'-c', 'copy',
'-movflags', '+faststart',
mp4_path
], check=True)
# 生成缩略图
thumb_dir = os.path.join(output_dir, 'thumbnails')
os.makedirs(thumb_dir, exist_ok=True)
thumb_path = os.path.join(thumb_dir, f"{base_name}_thumb.jpg")
subprocess.run([
'ffmpeg', '-i', input_flv,
'-vf', 'fps=1/10', # 每 10 秒一帧
'-q:v', '2',
os.path.join(thumb_dir, f"{base_name}_%04d.jpg")
], check=True)
# 获取视频信息
probe = subprocess.run([
'ffprobe', '-v', 'quiet',
'-print_format', 'json',
'-show_format', '-show_streams',
mp4_path
], capture_output=True, text=True)
return {
'mp4_path': mp4_path,
'thumb_path': thumb_path,
'probe': probe.stdout,
}
if __name__ == '__main__':
import sys
if len(sys.argv) < 3:
print("用法: python3 live2vod.py <input.flv> <output_dir>")
sys.exit(1)
result = process_recording(sys.argv[1], sys.argv[2])
print(f"✅ MP4: {result['mp4_path']}")
print(f"✅ 缩略图: {result['thumb_path']}")
10.8 大规模分发架构设计
10.8.1 万级观众架构
┌─────────────┐
推流端 ───RTMP───→│ Origin │
│ (SRS x2) │
│ 主备热备 │
└──────┬──────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
┌─────────┐┌─────────┐┌─────────┐
│ Edge-1 ││ Edge-2 ││ Edge-3 │
│ (SRS) ││ (SRS) ││ (SRS) │
└────┬────┘└────┬────┘└────┬────┘
│ │ │
┌────┴──────────┴──────────┴────┐
│ CDN (HLS/HTTP-FLV) │
│ 阿里云 / 腾讯云 / AWS │
└────────────┬──────────────────┘
│
┌─────────┼─────────┐
▼ ▼ ▼
观众群 1 观众群 2 观众群 3
(100万+) (100万+) (100万+)
10.8.2 容量规划
| 组件 | 单机能力 | 数量 | 总容量 |
|---|---|---|---|
| Origin | 500 推流 | 2 (主备) | 500 路 |
| Edge | 5000 拉流 | 3-10 | 15000-50000 |
| CDN | 100万+ | 多节点 | 无限扩展 |
注意事项
- 回源带宽:Edge 节点需要足够的上行带宽回源
- 时间戳一致性:中转时保持原始时间戳,避免音视频不同步
- 故障转移:Origin 主备切换时,Edge 应自动切换到备用源
- 录制存储:FLV/MP4 文件占用大量磁盘,需定期清理或归档
- 转码延迟:FFmpeg 转码会引入 1-3 秒额外延迟
- CDN 回源策略:避免所有 Edge 同时回源,使用缓存和预取
扩展阅读
上一章:09 - 流媒体服务器 下一章:11 - Docker 部署 — 了解容器化部署流媒体服务