10、Android端RTMP推流器架构设计:推流器整体架构

各位同学,今天我们来聊聊推流器的架构设计。说实话,这部分内容是我在课程里最想讲透的一块。为什么呢?因为很多开发者写推流代码,往往是「能跑就行」,结果一到线上就各种卡顿、花屏、延迟飙升。

我自己在早期做直播SDK的时候,就踩过这个坑。当时赶工期,把采集、编码、推流全塞在一个线程里,结果手机发热严重,推流不到十分钟就开始丢帧。后来痛定思痛,重新设计了架构,才真正理解了「架构设计」这四个字的分量。

好,我们直接进入正题。一个完整的RTMP推流器,到底应该长什么样?

推流器整体架构:一条流水线

说白了,推流器就是一条数据流水线。从摄像头和麦克风采集原始数据,经过编码压缩,再封装成FLV格式,最后通过RTMP协议发送到服务器。我习惯把它画成下面这样:

RTMP推流器整体架构 采集模块 Camera + AudioRecord 编码模块 MediaCodec H.264/AAC 封装模块 FLV Muxer 推流模块 RTMP Sender 采集线程 编码线程 封装线程 推流线程 原始帧 编码帧 FLV包 数据流方向:采集 → 编码 → 封装 → 推流 每个模块独立线程,通过队列解耦

你看,这个图很直观。四个模块串成一条链,每个模块都有自己的线程,模块之间通过队列传递数据。这样做的好处是什么?我举个例子你就明白了。

假设采集线程卡了100毫秒,如果所有事情都在一个线程里做,那编码、封装、推流全得等着。但如果我们把线程分开,采集卡了,编码线程还能继续处理队列里已有的数据,推流线程也不会断。这就是「解耦」的力量。

模块划分与职责

每个模块的职责必须清晰,不能越界。我见过一些项目,编码模块里居然写了网络重连的逻辑,这就不对了。我们来逐一拆解。

1. 采集模块

这个模块负责从硬件获取原始数据。视频走Camera,音频走AudioRecord。它的输出是原始的NV21或YUV帧,以及PCM音频数据。

嗯,这里要注意一点:采集模块不要做任何格式转换。把YUV转成RGB?那是编码模块的事。采集只管「拿到数据,扔进队列」,越简单越好。

核心职责:

  • 管理Camera和AudioRecord的生命周期
  • 设置采集参数(分辨率、帧率、采样率)
  • 将原始数据放入共享队列
  • 处理采集异常(如摄像头被占用)

2. 编码模块

编码模块是性能瓶颈的重灾区。我用MediaCodec做硬编码,但硬编码有个坑——它输出的数据格式不是我们直接能用的。视频编码器输出的是H.264的NALU,音频输出的是AAC裸流。

我曾经遇到一个问题:编码器输出的SPS/PPS信息,有时候在关键帧里,有时候单独发。如果不处理好这个,推流端解码就会花屏。我的做法是:在编码器刚启动时,强制缓存第一帧的SPS/PPS,然后在封装时主动插入。

避坑指南:

我曾经在编码模块里直接调用了网络接口去发数据,结果编码线程被网络阻塞,导致编码器缓冲区溢出。记住:编码模块只负责编码,不负责发送。

3. 封装模块

封装模块的任务是把编码后的H.264和AAC数据,按照FLV格式打包。FLV格式其实不复杂,就是一个个的Tag:视频Tag、音频Tag、脚本Tag。

这里有个细节:时间戳的计算。采集时间戳、编码时间戳、封装时间戳,这三者必须对齐。我习惯以采集时间戳为基准,编码和封装都沿用这个时间戳,不做二次计算。否则累积误差会让你崩溃。

FLV Tag类型 数据来源 说明
视频Tag (0x09) H.264编码器 包含SPS/PPS和帧数据
音频Tag (0x08) AAC编码器 包含ADTS头或裸流
脚本Tag (0x12) 元数据 分辨率、帧率、duration等

4. 推流模块

推流模块负责把封装好的FLV数据,通过RTMP协议发送到服务器。这里涉及网络IO,是最容易出问题的地方。

你想想看,网络环境千变万化。WiFi断了怎么办?4G信号弱怎么办?服务器超时怎么办?推流模块必须有一套健壮的重连机制。

我个人习惯的做法是:推流线程内部维护一个状态机。正常推流 → 网络断开 → 尝试重连 → 重连成功继续推流 / 重连失败回调错误。重连间隔从1秒开始,指数退避,最大不超过30秒。

注意:

不要在推流线程里做UI操作,也不要在UI线程里直接调用推流接口。我曾经见过有人在onClick里直接new了一个RTMP连接,结果ANR了。推流操作必须异步。

线程模型设计

线程模型是架构设计的灵魂。我见过三种常见的线程模型,各有优劣。

模型一:单线程模型

所有操作都在一个线程里完成。优点是简单,缺点是性能差。只适合Demo演示,线上千万别用。

模型二:双线程模型

一个线程做采集+编码,另一个线程做封装+推流。这种模型在早期的一些SDK里见过,但编码和采集耦合在一起,编码卡顿时采集也会受影响。

模型三:多线程流水线模型(推荐)

就是我上面画的那个:采集线程、编码线程、封装线程、推流线程,各司其职。每个线程之间用阻塞队列(BlockingQueue)传递数据。

为什么推荐这个?我给你算笔账。假设采集一帧需要10ms,编码一帧需要20ms,封装需要5ms,推流需要15ms(网络延迟)。如果串行执行,一帧的总耗时是10+20+5+15=50ms,也就是20fps。但如果用流水线,最慢的环节是编码的20ms,理论上可以达到50fps。当然实际没那么理想,但提升是实实在在的。

线程模型核心设计原则:

  • 每个模块一个独立线程,线程名要有意义(如"VideoEncodeThread")
  • 模块间通过队列解耦,队列大小要有限制(防止OOM)
  • 使用Handler或回调进行跨线程通信
  • 所有线程必须可被优雅停止(interrupt机制)

最后说一句,架构设计不是一蹴而就的。我刚开始做推流器的时候,也是从单线程开始,慢慢演变成多线程流水线。你可以在初期先用简单的模型跑通流程,然后再逐步优化。但心里要清楚,最终的目标是什么。

好了,这一章的内容就到这里。记住:好的架构设计,能让你的推流器在恶劣的网络环境下依然稳定工作。这不是一句空话,是我在无数个深夜调试中验证出来的。


公众号:蓝海资料掘金营,微信deep3321