强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 主动推送到 EdgeCDN 接入

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=xxxhttps://pull.aliyuncdn.com/app/stream.m3u8
腾讯云rtmp://push.example.com/live/stream?txSecret=xxxhttps://play.example.com/live/stream.flv
七牛云rtmp://publish.example.com/live/streamhttp://play.example.com/live/stream.m3u8
AWS IVSrtmp://global-contribute.live-video.net/app/streamhttps://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 4Mbps720p 2Mbps, 480p 1Mbps不同网络带宽的观众
编码转换H.265H.264兼容性
分辨率适配1080p720p移动端节省流量
水印添加无水印带 Logo 水印品牌保护
音频转码AAC 128kAAC 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通用格式,兼容性最好长期存储、点播
TSHLS 分片格式直接用于 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 容量规划

组件单机能力数量总容量
Origin500 推流2 (主备)500 路
Edge5000 拉流3-1015000-50000
CDN100万+多节点无限扩展

注意事项

  1. 回源带宽:Edge 节点需要足够的上行带宽回源
  2. 时间戳一致性:中转时保持原始时间戳,避免音视频不同步
  3. 故障转移:Origin 主备切换时,Edge 应自动切换到备用源
  4. 录制存储:FLV/MP4 文件占用大量磁盘,需定期清理或归档
  5. 转码延迟:FFmpeg 转码会引入 1-3 秒额外延迟
  6. CDN 回源策略:避免所有 Edge 同时回源,使用缓存和预取

扩展阅读


上一章09 - 流媒体服务器 下一章11 - Docker 部署 — 了解容器化部署流媒体服务