一、协议基础认知:RTMP与HLS到底是什么

搞直播开发这件事,绕不开两个名字——RTMP和HLS。你可能在项目里听过它们,也可能在调试播放器时踩过坑。但如果不把这两个协议的本质搞明白,后面做架构选型、调优延迟、排查问题都会很吃力。

RTMP,全称Real-Time Messaging Protocol,是Adobe在1999年推出的一套实时传输协议,最早是为Flash视频流播放设计的。虽然现在Flash已经凉了,但RTMP因为协议轻量、延迟低,反而在直播推流环节成了"老黄牛"——大量推流工具、OBS、手机推流SDK底层都走RTMP。

HLS,全称HTTP Live Streaming,是Apple在2009年推出的基于HTTP的分片传输协议。它的核心思路很直白:把视频切成一个个小片段(通常是2到10秒),每个片段是一个独立的TS文件,然后通过HTTP逐个请求。这种设计的好处是天然兼容CDN、天然穿透防火墙,但代价就是延迟比较高。

说人话就是:RTMP适合"推流",HLS适合"分发"。但为什么这么说?它们各自的工作机制到底有什么区别?下面我们来一层一层拆解。

1.1 RTMP的通信模型

RTMP是一种基于TCP的长连接协议,工作在1935端口(默认)。它的数据传输过程是这样的:

推流端先和服务器建立TCP连接,然后进行RTMP握手(Handshake),握手完成后进入数据传输阶段。传输的数据分为三类:

  • Chunk:最小的传输单元,每个Chunk包含一个Header和Payload
  • Message:逻辑消息,一条Message可以包含一个或多个Chunk
  • Stream:数据流,包含视频帧、音频帧、元数据等
// 技术栈:FFmpeg + Nginx-RTMP
// RTMP消息类型简要说明
/*
 * StreamType = 0 (live)   -> 实时流,适用于直播
 * StreamType = 1 (record) -> 录制流,适用于点播
 *
 * Message Type (消息类型):
 *   0x01 = Set Chunk Stream ID
 *   0x08 = Set Window Acknowledgement Size
 *   0x0B = Set Peer Bandwidth
 *   0x0F = Data Message (含音视频数据)
 *   0x14 = Command Message (含命令)
 *   0x17 = Shared Object Message
 */

// 推流端在RTMP连接中的典型操作序列
const rtmpFlow = [
    "1. TCP三次握手建立连接",
    "2. RTMP Client-Server握手 (C0/C1/C2, S0/S1/S2)",
    "3. 发送connect命令,携带app、tcUrl、pageUrl等参数",
    "4. 服务器返回_result,建立流路径",
    "5. 发送createStream命令,创建流",
    "6. 发送publish命令,声明推流意图",
    "7. 开始发送音视频帧数据 (AVPacket)",
    "8. 接收服务器ACK确认,保证可靠传输"
];

// RTMP连接参数示例
const rtmpConnectParams = {
    app: "live",              // RTMP应用名称
    tcUrl: "rtmp://server:1935/live", // 传输流URL
    pageUrl: "https://example.com",   // 页面来源
    flashVersion: "FMLE/3.0",         // 客户端标识
    swfUrl: "https://example.com/player.swf" // SWF文件URL
};

1.2 HLS的通信模型

HLS的设计思路跟RTMP完全不同。它把视频流切割成定长的小片段(TS文件),每个片段对应一个独立的HTTP请求。播放器不需要维持长连接,而是不断请求新的TS片段来播放。

HLS的核心文件有三个:

  • .m3u8主播放列表:描述播放流的整体信息,指向多个segment
  • .ts分片文件:实际的音视频数据片段
  • .m3u8变体播放列表(可选):用于自适应码率,按不同分辨率列出多个流
// 技术栈:FFmpeg + Nginx-RTMP
// HLS播放列表结构说明
/*
 * master.m3u8 (主播放列表)
 * ├── EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=720x480
 * ├── low/stream.m3u8     <- 低码率变体
 * ├── EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=1280x720
 * ├── mid/stream.m3u8     <- 中码率变体
 * └── EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
 * └── high/stream.m3u8    <- 高码率变体
 *
 * stream.m3u8 (片段播放列表)
 * ├── EXT-X-TARGETDURATION:4        <- 最大片段时长(秒)
 * ├── EXTINF:4.000                  <- 片段1时长
 * ├── seg-00001.ts                   <- 片段1文件
 * ├── EXTINF:4.000                  <- 片段2时长
 * ├── seg-00002.ts                   <- 片段2文件
 * ├── EXTINF:3.500                  <- 片段3时长
 * └── seg-00003.ts                   <- 片段3文件
 */

// HLS自适应码率(Abr)的客户端决策逻辑
const hlsAbrLogic = {
    bufferHealth: "当前缓冲区剩余时长",
    bandwidthEstimate: "当前可用带宽估计值",
    qualityLevels: [
        { bandwidth: 500000,  resolution: "480p",  label: "低画质" },
        { bandwidth: 1280000, resolution: "720p",  label: "中画质" },
        { bandwidth: 2560000, resolution: "1080p", label: "高画质" },
        { bandwidth: 5000000, resolution: "4K",    label: "超清画质" }
    ],
    decision: "根据bufferHealth和bandwidthEstimate选择最佳画质档位"
};

二、推流端与分发端的角色定位

搞清了协议本身,我们来看直播系统里"推流端"和"分发端"各自的职责是什么。

2.1 推流端的工作方式

推流端是视频内容的源头,负责采集音视频数据、编码、打包,然后通过某种协议发送到直播服务器。常见的推流端有:

  • OBS(Open Broadcaster Software):桌面推流软件,支持RTMP和SRT协议
  • FFmpeg:命令行推流工具,支持几乎所有主流协议
  • 手机App:通过推流SDK推流,底层通常是RTMP或SRT
  • 硬件编码器:如硬件转码卡,输出RTMP流

推流端选择RTMP的原因很简单:RTMP握手后建立一条稳定的TCP长连接,数据传输连续且高效,适合实时场景。

2.2 分发端的工作方式

分发端负责把推流到服务器的视频内容,分发给海量观众观看。分发端需要解决的核心问题是:

  • 如何高效复制同一份流给成千上万个观众?
  • 如何穿透各种防火墙和NAT?
  • 如何让不同网络环境的观众都能流畅播放?

HLS在这方面的优势非常明显。HLS基于HTTP协议,天然兼容CDN缓存,每一个TS片段都是独立的HTTP请求,CDN可以直接缓存这些片段,不需要理解RTMP协议。

// 技术栈:FFmpeg + Nginx-RTMP
// 推流端到分发端的完整数据流向
/*
 * 推流端(OBS/FFmpeg/手机SDK)
 *     |
 *     | RTMP协议 (TCP 1935)
 *     v
 * 直播服务器(Nginx-RTMP)
 *     |
 *     |---------------- 转码/转协议 ----------------|
 *     |                                              |
 *     v                                              v
 *  HLS服务器                             WebRTC服务器
 *     |                                              |
 *     | HTTP协议                                    | WebRTC协议
 *     | (TCP 80/443)                               | (UDP)
 *     v                                              v
 *  CDN缓存集群                               低延迟播放端
 *     |
 *     | HTTP请求
 *     v
 *  观众端播放器(HLS播放器)
 *
 * 核心逻辑: 推流端走RTMP -> 服务器转协议 -> 分发端走HLS(或WebRTC)
 */

三、RTMP与HLS协议对比

有了前面的铺垫,我们现在来做一个详细的对比,把两个协议的关键差异掰开了讲清楚。

3.1 架构模型对比

RTMP是推送模型(Push Model),推流端主动建立连接,服务器被动接收。一旦连接建立,数据就沿着这条TCP连接持续流动。

HLS是拉取模型(Pull Model),播放端主动发起HTTP请求,服务器被动响应。每个TS片段都是一个独立的请求-响应周期。

这个根本差异带来了一个很实际的影响:RTMP对推流端的网络要求比较高(需要维持长连接稳定),而HLS对播放端的网络适应性更好(断网重连只需要重新请求,不需要重建整个TCP会话)。

3.2 传输机制对比

RTMP的传输机制:

  • 基于TCP长连接,单次握手后持续传输
  • 数据分片成Chunk,每个Chunk有独立的Header
  • 支持消息类型区分(音视频数据、命令、元数据)
  • 有窗口ACK机制,确保传输可靠性
  • 默认端口1935

HLS的传输机制:

  • 基于HTTP短连接(或HTTP/2多路复用)
  • 视频切割成TS片段,每个片段独立HTTP请求
  • 播放列表(m3u8)动态更新,告知播放器新片段地址
  • 支持分片加密(AES-128加密TS片段)
  • 使用标准80/443端口
// 技术栈:FFmpeg + Nginx-RTMP
// RTMP vs HLS 关键参数对比
const protocolComparison = {
    rtmp: {
        port: 1935,
        transport: "TCP长连接",
        handshake: "RTMP专有握手协议",
        latency: "2~5秒",
        firewallFriendly: false,   // 1935端口常被防火墙拦截
        cdnSupport: false,         // CDN不支持RTMP缓存
        adaptiveBitrate: false,    // 不支持自适应码率
        encryption: false,         // 原生不支持加密(需TLS扩展)
        bandwidthEfficiency: "高",  // 单次握手后无额外开销
        scalability: "一般",        // 单连接模型,扩展性受限
    },
    hls: {
        port: 80 / 443,
        transport: "HTTP短连接",
        handshake: "标准HTTP请求-响应",
        latency: "10~30秒",
        firewallFriendly: true,    // 标准HTTP端口,防火墙默认放行
        cdnSupport: true,          // CDN天然支持HTTP缓存
        adaptiveBitrate: true,     // 支持多码率自适应切换
        encryption: true,          // 支持AES-128片段加密
        bandwidthEfficiency: "中", // 每个请求有HTTP头部开销
        scalability: "优秀",       // 无状态模型,极易横向扩展
    }
};

3.3 延迟特性对比

延迟是直播系统里最敏感的指标之一。RTMP和HLS在延迟上差距很大,这不是谁好谁差的问题,而是各自设计目标的差异。

RTMP的延迟主要来自:

  • 网络传输延迟(TCP RTT)
  • 编码缓冲(编码器需要攒够一定帧数才能开始编码)
  • 网络抖动缓冲(接收端做平滑处理)

典型延迟范围:2~5秒

HLS的延迟主要来自:

  • 网络传输延迟(TCP RTT,通常很低)
  • 片段时长(关键因素!每段2~10秒)
  • 片段下载时间(取决于网速和片段大小)
  • 播放缓冲区(通常缓冲2~3个片段)

典型延迟范围:10~30秒(如果片段时长设为2秒、缓冲2个片段,最低可以压到6~8秒)

四、协议适配逻辑详解

现在讲重点——推流端和分发端之间的协议适配,到底是怎么工作的。

4.1 推流端协议适配

推流端的工作相对简单:采集 -> 编码 -> 打包 -> 发送。适配的关键在于选择正确的协议和参数。

推流端使用RTMP推流时,需要关注几个核心参数:

  • 编码格式:H.264(视频)+ AAC(音频)是RTMP的标准组合,RTMP不支持H.265
  • 码率控制:固定码率(CBR)比可变码率(VBR)更适合RTMP推流,因为CBR对网络波动更稳定
  • GOP大小:建议设置为2秒左右(比如30fps对应60帧),太小会导致关键帧频繁,太大导致切换延迟
  • 连接参数:app名称、流名称、鉴权token等

推流端如果需要使用HLS推流,其实HLS本身不支持推流——HLS是纯分发协议。但可以通过FFmpeg把H.264/AAC编码数据同时输出到RTMP(推给服务器)和HLS(供本地测试播放)。

# 技术栈:FFmpeg + Nginx-RTMP
# RTMP推流命令示例(基础版)

# 从摄像头/屏幕采集,编码为H.264+AAC,通过RTMP推流
ffmpeg \
    -f x11grab \                          # 采集格式:X11屏幕捕获(Linux)
    -i :0.0 \                              # 输入源:屏幕
    -f dshow \                             # 采集格式:DirectShow(Windows)
    -i video=your_camera:audio=your_mic \  # 输入源:摄像头+麦克风
    -c:v libx264 \                         # 视频编码器:H.264
    -preset veryfast \                     # 编码预设:追求速度
    -b:v 2500k \                           # 视频码率:2.5Mbps
    -maxrate 2500k \                       # 最大码率:限制峰值
    -bufsize 5000k \                       # 缓冲区大小:5Mbps
    -c:a aac \                             # 音频编码器:AAC
    -b:a 128k \                            # 音频码率:128kbps
    -g 60 \                                # GOP大小:60帧(30fps下=2秒)
    -rtmp_live live \                      # RTMP模式:live(直播)
    -f flv \                               # 封装格式:FLV(RTMP载体)
    rtmp://your-server:1935/live/streamKey # 推流地址
# 技术栈:FFmpeg + Nginx-RTMP
# RTMP推流命令示例(进阶版,含鉴权)

# 通过RTMP推流,URL中携带鉴权token
ffmpeg \
    -f v4l2 \                              # 采集格式:V4L2(Linux摄像头)
    -i /dev/video0 \                       # 输入源:摄像头设备
    -c:v libx264 \                         # 视频编码器
    -preset ultrafast \                    # 编码预设:极速
    -tune zerolatency \                    # 调优目标:零延迟
    -b:v 3000k \                           # 视频码率:3Mbps
    -maxrate 3000k \                       # 最大码率
    -bufsize 6000k \                       # 缓冲区大小
    -c:a aac \                             # 音频编码器
    -b:a 128k \                            # 音频码率
    -ar 48000 \                            # 音频采样率:48kHz
    -ac 2 \                                # 音频声道:立体声
    -g 60 \                                # GOP大小
    -maxdelay 500000 \                     # 最大延迟:500ms(微秒)
    -rtmp_live live \                      # RTMP模式
    -f flv \                               # 封装格式
    "rtmp://your-server:1935/live/streamKey?token=auth_token_123456"
    #          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    #          推流地址                          鉴权token参数

4.2 分发端协议适配

分发端的适配逻辑比推流端复杂得多。核心问题是:推流端推的是RTMP,但观众端(尤其是移动端和Web端)很多情况下更适合用HLS播放。

解决方案就是在服务器端做协议转换:接收RTMP流,转封装为HLS分片,通过HTTP分发。

这个过程在Nginx-RTMP模块中可以通过一个配置指令搞定,但理解底层的转换逻辑很重要:

  1. 接收RTMP流:Nginx-RTMP模块监听1935端口,接收推流端的RTMP数据
  2. 解封装:从RTMP/FLV中解出原始的H.264视频帧和AAC音频帧
  3. 重新封装:将音视频帧封装为TS(MPEG-TS)格式
  4. 切分:按设定的间隔(比如3秒)切割TS片段
  5. 生成播放列表:更新m3u8文件,加入新的TS片段信息
  6. HTTP分发:通过80/443端口,将TS片段和m3u8文件通过HTTP响应给播放端
// 技术栈:FFmpeg + Nginx-RTMP
// 协议转换流程的伪代码表示
/*
 * 步骤1: RTMP接收
 *         RTMP数据包 -> Chunk解析 -> Message还原 -> FLV解封装
 *         输出: 裸H.264 NAL单元 + AAC帧
 *
 * 步骤2: TS封装
 *         H.264 NAL -> PES包 -> TS包(188字节对齐)
 *         AAC帧 -> ADTS包 -> PES包 -> TS包(188字节对齐)
 *         输出: MPEG-TS格式数据流
 *
 * 步骤3: 片段切割
 *         按3秒间隔切割TS数据流
 *         输出: seg-00001.ts, seg-00002.ts, ...
 *
 * 步骤4: 播放列表更新
 *         每次切割完成后,更新stream.m3u8
 *         输出: 最新的m3u8内容(包含前N个片段)
 *
 * 步骤5: HTTP响应
 *         播放端请求 /live/stream/stream.m3u8 -> 返回m3u8内容
 *         播放端请求 /live/stream/seg-00001.ts -> 返回TS片段
 */

// 协议转换时的关键转换参数
const protocolConversionParams = {
    inputProtocol: "RTMP/FLV",       // 输入协议
    outputProtocol: "HLS/MPEG-TS",  // 输出协议
    videoCodec: "H.264 (不需要重新编码)",  // 视频编码不变
    audioCodec: "AAC (不需要重新编码)",    // 音频编码不变
    segmentDuration: 3,             // 片段时长:3秒
    playlistType: "event",          // 播放列表类型:event(直播)
    targetDuration: 3,              // 目标片段时长
    keyframesOnly: true,            // 是否只在关键帧处切割
    encryption: "none",             // 加密方式:无
};

五、完整技术栈示例演示

下面用FFmpeg和Nginx-RTMP搭建一个完整的RTMP推流 + HLS分发系统,从服务器配置到推流拉流全流程演示。

技术栈说明:本节所有示例统一使用 FFmpeg 6.x + Nginx 1.24 + Nginx-RTMP 模块(基于GitHub上triton/nginx-rtmp开源项目)。

5.1 Nginx-RTMP服务器配置

先搭建服务器端,这是整个系统的核心枢纽。

# 技术栈:FFmpeg + Nginx-RTMP
# Nginx-RTMP 服务器配置文件 (nginx.conf)
# 此配置实现了: RTMP推流接收 -> 自动转封装HLS -> HTTP分发

worker_processes  auto;              # 自动设置worker进程数

events {
    worker_connections  1024;        # 每个worker最大连接数
}

# HTTP服务器块 - 用于HLS分发
http {
    include       mime.types;
    default_type  application/octet-stream;
    
    # HLS片段缓存配置
    location /hls/ {
        types {
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
        }
        
        # 关闭缓存,确保获取最新片段
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        
        # 允许跨域访问
        add_header Access-Control-Allow-Origin "*";
        
        # 日志配置
        access_log logs/hls_access.log;
    }
}

# RTMP服务器块 - 用于推流接收
rtmp {
    # 监听RTMP端口
    server {
        listen 1935;
        server_name rtmp.example.com;
        
        # 应用名称
        application live {
            # 允许推流
            allow publish all;
            
            # 允许拉流
            allow play all;
            
            # 最大同时连接数
            max_connections 1000;
            
            # 流超时设置
            idle_timeout 10s;
            gop_cache on;            # 开启GOP缓存,新观众加入时可快速获取关键帧
            
            # 关键配置:开启HLS转封装
            # 服务器收到RTMP流后,自动转封装为HLS并存储到指定目录
            hls on;                  # 开启HLS功能
            hls_path /tmp/hls;       # HLS文件存储路径
            hls_fragment 3s;         # 每个TS片段时长:3秒
            hls_playlist_length 30s; # 播放列表保留时长:30秒(约10个片段)
            hls_fragment_naming system;  # 片段命名方式:系统时间戳
            hls_fragment_slicing slotted; # 切片方式:按GOP边界切割
            hls_sync_delay 2;        # 同步延迟:2个片段
            hls_nested on;           # 开启嵌套目录结构(按流名称分目录)
            hls_cleanup on;          # 自动清理过期片段
            hls_variant BANDWIDTH=${bandwidth},NAME=${name};  # 自适应码率变体
            hls_base_url https://hls.example.com/hls;          # HLS基础URL
        }
    }
    
    # 日志配置
    access_log logs/rtmp_access.log;
}

5.2 FFmpeg推流示例

服务器配置好后,用FFmpeg作为推流端发送测试流。

# 技术栈:FFmpeg + Nginx-RTMP
# 推流命令:使用测试源推流到Nginx-RTMP服务器

# 示例1:使用测试视频推流(适合本地开发测试)
ffmpeg \
    -re \                                # 按实时速率读取输入(模拟直播)
    -stream_loop -1 \                    # 输入循环:无限循环
    -i test_video.mp4 \                  # 输入源:本地测试视频
    -c:v libx264 \                       # 视频编码器
    -preset veryfast \                   # 编码预设:快速
    -tune zerolatency \                  # 调优:零延迟模式
    -b:v 2500k \                         # 视频码率:2.5Mbps
    -maxrate 2500k \                     # 最大码率限制
    -bufsize 5000k \                     # 缓冲区大小
    -c:a aac \                           # 音频编码器
    -b:a 128k \                          # 音频码率
    -g 60 \                              # GOP大小:60帧(30fps=2秒)
    -f flv \                             # 封装格式:FLV
    rtmp://127.0.0.1:1935/live/myStream  # 推流地址:本地测试流

# 示例2:使用摄像头推流(适合实际直播测试)
ffmpeg \
    -f v4l2 \                            # Linux摄像头采集
    -i /dev/video0 \                     # 摄像头设备
    -f alsa \                            # Linux音频采集
    -i default \                         # 默认音频设备
    -c:v libx264 \                       # 视频编码器
    -preset ultrafast \                  # 编码预设:极速
    -tune zerolatency \                  # 零延迟调优
    -b:v 3000k \                         # 视频码率:3Mbps
    -maxrate 3000k \                     # 最大码率
    -bufsize 6000k \                     # 缓冲区
    -c:a aac \                           # 音频编码器
    -b:a 128k \                          # 音频码率
    -ar 48000 \                          # 采样率:48kHz
    -ac 2 \                              # 声道数:立体声
    -g 60 \                              # GOP大小
    -rtmp_live live \                    # RTMP直播模式
    -f flv \                             # 封装格式
    rtmp://127.0.0.1:1935/live/myStream  # 推流地址

5.3 FFmpeg拉流与HLS播放示例

推流端和服务器配置完成后,观众端就可以通过HLS地址拉流播放了。

# 技术栈:FFmpeg + Nginx-RTMP
# HLS拉流命令:通过HTTP访问Nginx生成的HLS播放列表

# 拉流并转码输出(适合测试HLS流是否正常)
ffmpeg \
    -i http://127.0.0.1:80/hls/live/myStream/index.m3u8 \
    -c copy \                            # 不重新编码,直接复制流
    -f mp4 \                             # 输出格式:MP4
    -t 60 \                              # 录制60秒后停止
    output_test.mp4                      # 输出文件

# 拉流并输出为RTMP(适合HLS到RTMP的反向转换)
ffmpeg \
    -i http://127.0.0.1:80/hls/live/myStream/index.m3u8 \
    -c copy \                            # 直接复制
    -f flv \                             # 输出格式:FLV
    rtmp://another-server:1935/live/relayStream  # 转发到另一个RTMP服务器

# 拉流并做二次转码(适合HLS转低码率分发)
ffmpeg \
    -i http://127.0.0.1:80/hls/live/myStream/index.m3u8 \
    -c:v libx264 \                       # 视频重新编码
    -preset veryfast \                   # 编码预设
    -b:v 1000k \                         # 降低码率到1Mbps
    -c:a aac \                           # 音频重新编码
    -b:a 96k \                           # 降低音频码率
    -f flv \                             # 封装格式
    rtmp://low-bandwidth-server:1935/live/lowStream

5.4 低延迟HLS配置示例

标准HLS延迟太高,可以通过调整参数来降低延迟。

# 技术栈:FFmpeg + Nginx-RTMP
# 低延迟HLS(Low-Latency HLS)配置

# 服务器端:调整Nginx配置中的HLS参数
# 将以下参数应用到nginx.conf的rtmp块中:
#   hls_fragment 1s;          # 片段时长缩短到1秒
#   hls_playlist_length 6s;   # 播放列表只保留6秒
#   hls_fragment_naming timestamp;  # 使用时间戳命名避免浏览器缓存
#   hls_fragment_slicing pass;      # 不按GOP切割,直接按时间切

# 拉流端:FFmpeg拉流时使用live协议,减少缓冲
ffmpeg \
    -live 1 \                            # 启用低延迟模式
    -live_finish_notify_len 1 \          # 通知长度:1
    -probesize 32 \                      # 探测大小:减少探测时间
    -analyzeduration 0 \                # 分析时长:跳过分析
    -i http://127.0.0.1:80/hls/live/myStream/index.m3u8 \
    -c copy \                            # 直接复制
    -f null -                            # 输出到null(仅测试延迟)

# 使用HLS live模式拉流(FFmpeg 5.x以上支持)
ffmpeg \
    -protocol_whitelist file,http,https,tcp,tls,crypto,data \
    -i http://127.0.0.1:80/hls/live/myStream/index.m3u8 \
    -c copy \
    -f mp4 output_low_latency.mp4

5.5 完整测试脚本

把推流和拉流串起来,用一个脚本完成全流程测试。

#!/bin/bash
# 技术栈:FFmpeg + Nginx-RTMP
# 完整直播流测试脚本:启动服务器 -> 推流 -> 拉流验证

set -e  # 遇到错误立即退出

echo "=== 步骤1: 启动Nginx RTMP服务器 ==="
nginx -c /etc/nginx/nginx.conf  # 启动Nginx
sleep 2                         # 等待服务器启动

echo "=== 步骤2: 开始推流 ==="
# 使用测试视频推流,后台运行
ffmpeg \
    -re \
    -stream_loop -1 \
    -i test_video.mp4 \
    -c:v libx264 \
    -preset veryfast \
    -b:v 2500k \
    -maxrate 2500k \
    -c:a aac \
    -b:a 128k \
    -g 60 \
    -rtmp_live live \
    -f flv \
    rtmp://127.0.0.1:1935/live/testStream \
    &                             # 后台运行
PUSH_PID=$!
echo "推流进程PID: $PUSH_PID"

sleep 10                         # 等待推流建立,确保HLS文件生成

echo "=== 步骤3: 验证HLS文件生成 ==="
# 检查HLS文件目录
ls -la /tmp/hls/live/testStream/
echo "HLS播放列表内容:"
cat /tmp/hls/live/testStream/index.m3u8

echo "=== 步骤4: HLS拉流验证 ==="
# 拉取HLS流并检查是否正常
ffmpeg \
    -i http://127.0.0.1:80/hls/live/testStream/index.m3u8 \
    -c copy \
    -f mp4 \
    -t 10 \                       # 只录制10秒验证
    output_verify.mp4 \
    -y                             # 覆盖输出文件

echo "=== 步骤5: 清理 ==="
kill $PUSH_PID 2>/dev/null       # 停止推流
nginx -s stop 2>/dev/null        # 停止Nginx
echo "测试完成!输出文件: output_verify.mp4"

六、应用场景分析

不同的直播场景,对协议的要求差异很大。选错协议可能导致延迟过高、成本失控或者根本无法工作。

6.1 低延迟互动场景

场景描述:电竞直播、体育赛事直播、在线教育互动、电商直播带货。这些场景要求观众看到的内容和主播的实际操作之间的延迟尽量低,通常要求5秒以内,最好是2秒以内。

协议选择:推流端使用RTMP(延迟低),分发端优先选择RTMP拉流或WebRTC。如果必须用HLS,则需要开启低延迟HLS(LL-HLS),把片段时长压到1秒。

适用RTMP拉流的播放器较少(主要是PC端Flash已死,Web端需要flv.js库),所以纯RTMP分发方案正在被WebRTC或LL-HLS替代。

// 技术栈:FFmpeg + Nginx-RTMP
// 低延迟场景的协议选型决策树
/*
 * 延迟要求 < 2秒:
 *   -> 推流端: RTMP或SRT
 *   -> 分发端: WebRTC (推荐) 或 RTMP拉流
 *   -> 播放器: 需要WebRTC支持或flv.js
 *
 * 延迟要求 2~5秒:
 *   -> 推流端: RTMP
 *   -> 分发端: LL-HLS(片段1秒) 或 SRT
 *   -> 播放器: LL-HLS兼容播放器
 *
 * 延迟要求 5~10秒:
 *   -> 推流端: RTMP
 *   -> 分发端: HLS(片段2~3秒)
 *   -> 播放器: 标准HLS播放器(广泛兼容)
 *
 * 延迟要求 > 10秒:
 *   -> 推流端: RTMP
 *   -> 分发端: HLS(标准模式)
 *   -> 播放器: 所有HLS播放器(最大兼容性)
 */

6.2 大规模分发场景

场景描述:综艺节目直播、体育赛事面向全球分发、活动直播。这类场景观众量巨大(百万级甚至千万级),核心诉求是成本和稳定性。

协议选择:推流端RTMP,分发端HLS。HLS基于HTTP,天然适配CDN缓存,每个TS片段可以被CDN节点缓存,不需要回源。百万观众只需要回源1次原始流,CDN节点负责扇出。

RTMP不适合大规模分发,因为它不支持CDN缓存,每个观众都需要单独连接到源站,带宽成本极高。

6.3 移动端播放场景

场景描述:手机App内的直播观看、小程序直播、H5页面直播。移动端网络环境复杂(WiFi、4G、5G、弱网),需要播放器有较好的自适应能力。

协议选择:推流端RTMP,分发端HLS。HLS的优势在移动端非常明显:

  • iOS系统原生支持HLS(AVPlayer直接播m3u8)
  • Android通过ExoPlayer/HlsPlayer支持
  • HLS的自适应码率(ABR)可以根据网络状况自动切换画质
  • 移动端网络不稳定时,HLS的HTTP重试机制比RTMP的TCP重连更可靠

6.4 安全分发场景

场景描述:付费内容直播、版权保护、防盗链。这类场景需要对流内容进行加密,防止被直接下载或盗链。

协议选择:推流端RTMP,分发端HLS + AES-128加密。HLS支持在服务器端对每个TS片段进行AES-128加密,播放器需要先获取密钥才能解密播放。配合防盗链策略(Referer检查、IP白名单、Token鉴权),可以有效保护内容安全。

七、技术优缺点总结

7.1 RTMP的优缺点

RTMP的优势:

  • 延迟低:典型延迟2~5秒,在流协议中属于较低水平
  • 实现简单:推流端和接收端都只需要处理一条TCP连接
  • 带宽效率高:单次握手后无额外HTTP头部开销
  • 成熟稳定:经过20多年生产环境验证,各种边界情况处理完善
  • 推流SDK支持广泛:OBS、FFmpeg、RTMP推流库都成熟可用

RTMP的劣势:

  • 不兼容CDN:CDN不理解RTMP协议,无法缓存RTMP流
  • 防火墙不友好:1935端口经常被企业防火墙或ISP封锁
  • 移动端支持差:iOS已不支持RTMP播放,Android也需要额外库
  • 不支持自适应码率:无法根据网络状况动态调整画质
  • 单点连接风险:TCP连接断开后需要完全重建,恢复时间长
  • 协议封闭性:非Adobe标准,文档和社区资料不如HTTP丰富

7.2 HLS的优缺点

HLS的优势:

  • CDN天然兼容:基于HTTP,CDN可以直接缓存TS片段
  • 防火墙友好:使用标准80/443端口,几乎不会被封锁
  • 移动端支持好:iOS原生支持,Android有成熟播放器库
  • 自适应码率:支持多码率自适应切换,弱网也能流畅播放
  • 水平扩展容易:无状态HTTP请求,加机器就能扩展
  • 加密支持好:原生支持AES-128片段加密
  • 回放友好:TS片段可以作为录制文件保留

HLS的劣势:

  • 延迟高:标准模式延迟10~30秒,低延迟模式也需6秒以上
  • 带宽开销较大:每个HTTP请求都有头部开销
  • 推流不支持:HLS是纯分发协议,不能用来推流
  • 文件碎片化:大量TS片段和m3u8文件管理有成本
  • 播放器兼容性差异:不同播放器对LL-HLS的支持程度不同

八、注意事项

8.1 防火墙与端口问题

RTMP默认使用1935端口,很多企业和学校的防火墙会直接封掉这个端口。如果遇到推流失败,第一步就是检查端口是否被防火墙拦截。解决方案有两种:

  • 在防火墙上开放1935端口(需要网络管理权限)
  • 将RTMP端口改为443或80端口(通过Nginx的listen指令修改),但需要配合TLS加密

HLS使用HTTP端口,通常不会被防火墙拦截。但需要注意HTTPS场景下证书配置正确,否则浏览器会阻止播放。

# 技术栈:FFmpeg + Nginx-RTMP
# RTMP端口伪装为443端口的配置示例

rtmp {
    server {
        listen 443 ssl;           # 监听443端口并启用TLS
        ssl_certificate     /path/to/cert.pem;       # TLS证书
        ssl_certificate_key /path/to/key.pem;        # TLS私钥
        
        application live {
            allow publish all;
            allow play all;
            hls on;
            hls_path /tmp/hls;
            hls_fragment 3s;
        }
    }
}

8.2 缓存策略配置

HLS的TS片段可以被浏览器和CDN缓存,这可能导致观众看到旧片段而不是最新内容。正确配置缓存策略至关重要。

  • m3u8播放列表:必须设置Cache-Control: no-cache, no-store, must-revalidate,确保每次都获取最新列表
  • TS片段:可以设置较短的缓存时间(比如Cache-Control: max-age=3),因为片段内容不会变化
  • 片段命名:使用时间戳命名(timestamp模式)可以天然避免缓存问题

8.3 跨域问题处理

HLS播放列表和TS片段通常托管在CDN域名下,而播放页面可能在另一个域名。浏览器会执行同源策略检查,如果CORS配置不当,播放会被阻止。

Nginx端需要为HLS资源添加CORS头:

# 技术栈:FFmpeg + Nginx-RTMP
# HLS资源的CORS跨域配置

http {
    location /hls/ {
        # 允许所有来源访问
        add_header Access-Control-Allow-Origin "*";
        
        # 或者指定特定来源
        # add_header Access-Control-Allow-Origin "https://your-website.com";
        
        # 缓存控制
        if (-f $request_filename) {
            add_header Cache-Control "no-cache";
            break;
        }
    }
}

8.4 推流端与服务器端编码一致性

推流端使用的编码参数必须与服务器端的转封装逻辑匹配。常见问题包括:

  • 推流端使用H.265编码,但Nginx-RTMP默认只支持H.264转封装HLS,会导致HLS流不可播放
  • 推流端使用了非标准音频格式(如Opus),HLS转封装可能失败
  • GOP大小不一致导致HLS切片的同步点不稳定

最佳实践是:推流端始终使用H.264视频编码 + AAC音频编码,这是RTMP和HLS都支持的通用格式。

8.5 HLS片段时长的选择

片段时长是HLS延迟和带宽效率之间的关键平衡点:

  • 片段太短(1秒):延迟低,但HTTP请求频繁,带宽开销大,服务器压力大
  • 片段太长(10秒):延迟高,但HTTP请求少,带宽效率高
  • 推荐值:标准直播用3~4秒,低延迟直播用1~2秒

8.6 RTMP推流稳定性保障

RTMP是长连接协议,一旦TCP连接断开,推流就会中断。推流端需要做以下保障:

  • 断线重连:FFmpeg的-reconnect 1参数可以实现自动重连
  • 心跳检测:定期发送RTMP心跳包,检测连接是否存活
  • 带宽监测:监控实际推流码率,如果远低于目标码率说明网络有问题
# 技术栈:FFmpeg + Nginx-RTMP
# 推流端稳定性配置示例

ffmpeg \
    -re \
    -stream_loop -1 \
    -i test_video.mp4 \
    -c:v libx264 \
    -preset veryfast \
    -b:v 2500k \
    -c:a aac \
    -b:a 128k \
    -g 60 \
    -f flv \
    -reconnect 1 \                        # 启用自动重连
    -reconnect_streamed 1 \               # 重连时从流恢复
    -reconnect_delay_max 10 \             # 最大重连延迟:10秒
    -reconnect_at_eof 1 \                 # EOF时自动重连
    -loglevel warning \                   # 日志级别:只显示警告
    rtmp://127.0.0.1:1935/live/myStream

九、文章总结

RTMP和HLS不是竞争关系,而是互补关系。在直播系统中,它们各自承担着不可替代的角色:

RTMP负责"推流"——它低延迟、高带宽效率的特性,让它成为推流端的首选协议。从OBS到FFmpeg,从手机SDK到硬件编码器,推流端几乎清一色使用RTMP。

HLS负责"分发"——它基于HTTP、CDN兼容、移动端支持好的特性,让它成为分发端的主流选择。尤其是iOS和Web端,HLS播放器的兼容性和稳定性远超其他方案。

协议适配的核心逻辑就是:推流端走RTMP到直播服务器,服务器接收RTMP流后转封装为HLS,通过CDN分发HLS给观众端播放器。这个架构兼顾了推流的低延迟和分发的可扩展性。

对于实际开发来说,理解这两个协议的差异,能帮你在以下场景中做出更好的决策:

  • 需要极致低延迟?考虑推流端RTMP + 分发端WebRTC或LL-HLS
  • 需要最大兼容性?推流端RTMP + 分发端标准HLS
  • 需要大规模分发?推流端RTMP + 分发端HLS + CDN
  • 需要内容安全?推流端RTMP + 分发端HLS + AES-128加密 + Token鉴权

最后提醒几个容易踩的坑:编码格式一致性(H.264 + AAC)、端口防火墙检查(1935)、CORS配置(跨域播放)、缓存策略(避免看到旧内容)、GOP大小设置(影响HLS切片质量)。把这些细节处理好了,RTMP + HLS的直播架构就能稳定运行。