18、RTMP与HLS协议:流媒体协议对比、RTMP推流、HLS拉流、直播架构
做直播开发,绕不开两个协议——RTMP和HLS。我刚开始接触流媒体时,也搞不清它们到底有什么区别,什么时候该用哪个。后来踩了不少坑,才慢慢摸清楚门道。
今天咱们就把这两个协议掰开揉碎了讲。说白了,RTMP是推流用的,HLS是拉流用的。但实际场景里,远不止这么简单。
RTMP协议:实时消息传输协议
RTMP是Adobe搞出来的东西,基于TCP长连接。它的核心优势就是低延迟。我做过一个互动直播项目,要求端到端延迟控制在3秒以内,当时选的就是RTMP。
RTMP的工作流程是这样的:
- 客户端与服务器建立TCP连接
- 通过握手确认版本号
- 建立NetConnection和NetStream
- 开始推送音视频数据
它的数据格式很有意思。RTMP把数据切成一个个Chunk(块),每个Chunk有固定的头部和可变长度的负载。这样做的好处是——可以动态调整数据包大小,适应不同网络状况。
核心要点:RTMP默认使用1935端口。如果被防火墙封了,也可以用RTMPT(RTMP over HTTP)走80端口,但性能会下降。
HLS协议:HTTP直播流
HLS是苹果推出的协议,思路完全不同。它不是靠长连接推流,而是把视频切成一个个小片段(通常是10秒一段),通过HTTP协议分发。
我印象很深,第一次看HLS的m3u8文件时,觉得这设计真巧妙。它本质上就是一个播放列表:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
segment_000.ts
#EXTINF:10.0,
segment_001.ts
#EXTINF:10.0,
segment_002.ts
#EXT-X-ENDLIST
HLS最大的优势是兼容性好。所有主流浏览器都支持,不需要任何插件。而且它天然支持自适应码率——你可以准备多个分辨率的切片,客户端根据网络状况自动切换。
我的经验:HLS的延迟通常在15-30秒。如果你做的是点播或者非互动直播,这个延迟完全可以接受。但如果是连麦互动,那就得另想办法了。
RTMP vs HLS:核心对比
| 对比维度 | RTMP | HLS |
|---|---|---|
| 传输层 | TCP长连接 | HTTP短连接 |
| 延迟 | 1-3秒 | 15-30秒 |
| 兼容性 | 需要Flash或原生SDK | 浏览器原生支持 |
| 自适应码率 | 不支持 | 原生支持 |
| 防火墙友好 | 一般 | 好(走80/443端口) |
| 典型场景 | 推流、互动直播 | 点播、大规模分发 |
你看这个表格就清楚了。RTMP适合上行,HLS适合下行。实际生产环境中,我见过最多的架构是——推流用RTMP,服务端转码后切成HLS分发给用户。
RTMP推流实战
在Android端做RTMP推流,核心工作就是采集音视频数据,然后封装成RTMP格式发出去。我习惯用FFmpeg来做这个事。
一个简单的推流流程:
- 打开摄像头和麦克风,获取原始数据
- 用MediaCodec硬编码成H.264和AAC
- 把编码后的数据喂给FFmpeg的muxer
- FFmpeg封装成FLV格式,通过RTMP发送
代码层面,关键部分是这样的:
// 初始化FFmpeg的AVFormatContext
AVFormatContext *fmt_ctx = avformat_alloc_context();
avformat_alloc_output_context2(&fmt_ctx, NULL, "flv", rtmp_url);
// 添加视频流
AVStream *video_stream = avformat_new_stream(fmt_ctx, NULL);
video_stream->codecpar->codec_id = AV_CODEC_ID_H264;
video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
// 打开IO并写入头部
avio_open(&fmt_ctx->pb, rtmp_url, AVIO_FLAG_WRITE);
avformat_write_header(fmt_ctx, NULL);
// 循环写入数据包
while (推流中) {
AVPacket pkt;
// 从编码器获取数据包
pkt.data = encoded_data;
pkt.size = data_size;
pkt.pts = pts;
pkt.dts = dts;
av_interleaved_write_frame(fmt_ctx, &pkt);
}
// 写入尾部
av_write_trailer(fmt_ctx);
注意:我曾经在推流时遇到过音视频不同步的问题。后来发现是PTS和DTS没有正确设置。记住,H.264的B帧会导致DTS和PTS不一致,一定要从编码器拿到正确的值。
HLS拉流实现
Android端拉HLS流就简单多了。系统自带的MediaPlayer就支持HLS协议。你只需要把m3u8的URL传进去就行。
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(context, Uri.parse("https://example.com/live/stream.m3u8"));
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(mp -> mp.start());
但如果你需要更精细的控制,比如切换码率、获取实时信息,那就得用ExoPlayer了。Google官方推荐的播放器,对HLS支持非常好。
// 使用ExoPlayer播放HLS
HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(
new DefaultHttpDataSource.Factory()
).createMediaSource(MediaItem.fromUri(hlsUrl));
ExoPlayer player = new ExoPlayer.Builder(context).build();
player.setMediaSource(hlsMediaSource);
player.prepare();
player.play();
ExoPlayer还支持自适应码率切换。你可以在m3u8里配置多个码率的流,播放器会根据网络状况自动选择最合适的那个。
直播架构设计
一个完整的直播系统,通常包含这几个环节:
- 推流端:主播的手机或电脑,用RTMP推流
- 边缘节点:接收RTMP流,做转码和切片
- CDN分发:把HLS切片分发到各个节点
- 拉流端:观众用HLS拉流观看
我参与过一个百万级并发的直播项目,架构大概是这样的:
这个架构里,RTMP只负责从推流端到边缘节点这一段。边缘节点收到流之后,立刻转码成多码率的HLS切片,然后推送到CDN。观众从CDN拉流,压力全在CDN上,源站很轻松。
避坑指南:我曾经在边缘节点转码时,发现CPU占用率飙升。后来改用GPU硬编码,问题就解决了。如果你做的是高并发直播,一定要考虑转码性能。
延迟优化技巧
HLS的延迟主要来自两个地方:切片时长和播放器缓冲。想降低延迟,可以从这两方面入手。
切片时长方面,我建议把每个切片控制在2-4秒。虽然会增加服务器压力,但延迟能从30秒降到10秒左右。
播放器缓冲方面,ExoPlayer有个参数可以调:
// 减少缓冲大小,降低延迟
HlsMediaSource.Factory factory = new HlsMediaSource.Factory(
new DefaultHttpDataSource.Factory()
);
factory.setAllowChunklessPreparation(true);
// 设置最小缓冲时长
player.setPlaybackParameters(
new PlaybackParameters(1f, 1f, false, 0)
);
不过要注意,减少缓冲意味着抗网络抖动能力下降。你得在延迟和稳定性之间找个平衡点。
嗯,RTMP和HLS就讲这么多。这两个协议是直播开发的基石,搞懂了它们,后面学WebRTC、SRT这些新协议就会轻松很多。