30、项目实战:直播推流App开发:需求分析、架构设计、功能实现、测试与发布
终于到了最后一章。说实话,每次带团队做直播推流App,我都会把这一章的内容反复讲给新人听。为什么?因为前面29章你学的是「零件」,这一章我们要把它们组装成一台能跑的车。
一个完整的直播推流App,不是把摄像头画面扔给RTMP库就完事了。你想想看,用户打开App,要看到预览、能切换摄像头、能开美颜、能调码率、能看推流状态……这些功能背后,是一整套架构在支撑。
我个人习惯,在写第一行代码之前,先把需求文档和架构图画清楚。磨刀不误砍柴工,这话一点不假。
30.1 需求分析:别漏掉任何一个「小」功能
先列一个功能清单。我把它分成三类:核心功能、增强功能、运营功能。
| 分类 | 功能点 | 说明 |
|---|---|---|
| 核心 | 摄像头预览 | 支持前置/后置,分辨率可调 |
| 核心 | RTMP推流 | 支持RTMP/RTMPS,断线重连 |
| 核心 | 音频采集 | 麦克风采集,支持AAC编码 |
| 增强 | 美颜滤镜 | 磨皮、美白、瘦脸(基于GPUImage) |
| 增强 | 码率自适应 | 根据网络状况动态调整 |
| 增强 | 推流状态显示 | 实时帧率、码率、丢包率 |
| 运营 | 推流日志 | 上传到服务器,方便排查问题 |
| 运营 | 崩溃上报 | 集成Bugly或自研上报 |
这里有个坑,我曾经踩过:美颜和码率自适应这两个功能,看起来是「增强」,但用户感知极强。如果你只做核心功能,用户会觉得「这App好简陋」。所以,哪怕第一版只做基础美颜,也一定要有。
30.2 架构设计:分层、解耦、可测试
架构这件事,说白了就是「把代码按职责分开,让它们各干各的,互不干扰」。我推荐的分层方式是这样的:
架构分层(从上到下)
- UI层:Activity/Fragment,只负责界面展示和用户交互
- ViewModel层:持有LiveData/StateFlow,管理UI状态
- 业务层:推流管理器、美颜管理器、网络状态监听器
- 引擎层:CameraEngine、AudioEngine、RTMPEngine
- 底层:FFmpeg、MediaCodec、OpenGL ES
下面这张图,是我做这个项目时画的架构图。你看一眼,基本就能明白整个App的脉络。
每一层只依赖它的下一层,不能跨层调用。比如UI层不能直接调CameraEngine,必须通过ViewModel和业务层。这样做的好处是:你可以单独替换某一层的实现。比如今天用MediaCodec,明天想换硬件编码器,只需要改引擎层,上层代码不用动。
30.3 功能实现:从预览到推流,一步步来
30.3.1 摄像头预览
我习惯用CameraX,因为它生命周期管理做得很好。核心代码就几行:
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
preview.setSurfaceProvider(previewView.surfaceProvider)
cameraProvider.bindToLifecycle(this, cameraSelector, preview)
}, ContextCompat.getMainExecutor(this))
嗯,这里要注意:别忘了在AndroidManifest里声明摄像头权限,并且Android 6.0以上要动态申请。我曾经有个同事,调试了半天预览黑屏,结果就是忘了加权限。
30.3.2 音频采集与编码
音频这块,我用的是AudioRecord + MediaCodec。AudioRecord采集PCM数据,然后喂给MediaCodec做AAC编码。关键参数如下:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 采样率 | 44100 Hz | CD音质,兼容性最好 |
| 声道数 | 1(单声道) | 直播场景单声道足够,省带宽 |
| 位深 | 16 bit | 标准PCM格式 |
| 编码码率 | 64 kbps ~ 128 kbps | 根据网络动态调整 |
小技巧:音频采集线程不要放在主线程。我一般用HandlerThread,把AudioRecord的循环读取放到子线程里,避免卡UI。
30.3.3 RTMP推流
推流这块,我选的是librtmp(或者用封装好的rtmp-rtsp-stream-client-java库)。核心逻辑是:
- 初始化RTMP连接(设置URL、超时时间)
- 循环从编码器取出视频帧(H.264/H.265)和音频帧(AAC)
- 封装成FLV tag,通过RTMP发送
- 监听连接状态,断线自动重连
伪代码大概长这样:
class RTMPPusher(private val url: String) {
private var isPushing = false
private var reconnectCount = 0
fun start() {
isPushing = true
thread {
while (isPushing) {
val videoFrame = videoQueue.take() // 从编码队列取数据
val audioFrame = audioQueue.take()
// 封装FLV tag并发送
sendFlvTag(videoFrame)
sendFlvTag(audioFrame)
}
}
}
fun stop() {
isPushing = false
// 关闭连接,释放资源
}
private fun sendFlvTag(frame: Frame) {
// 调用librtmp的RTMP_SendPacket
}
}
注意:断线重连不能无限重试。我一般设置最大重试次数为5次,每次间隔递增(1s、2s、4s、8s、16s)。超过5次就提示用户「网络异常,请检查网络」。否则一直重连,用户手机电量扛不住。
30.4 测试:不只是「能跑就行」
测试这件事,我吃过不少亏。以前有个版本,本地测试一切正常,上线后用户反馈「推流卡顿、声音不同步」。后来发现是某些低端手机的MediaCodec实现有bug,编码出来的时间戳不对。
所以,我建议至少做这几类测试:
- 功能测试:每个按钮点一遍,推流、停止、切换摄像头、美颜开关……确保不崩溃
- 兼容性测试:找5~10款不同品牌、不同Android版本的手机,重点测摄像头和编码器
- 网络测试:用Charles或Network Link Conditioner模拟弱网(丢包、高延迟、低带宽)
- 长时间测试:连续推流1小时以上,看内存、CPU、温度是否正常
我的经验:弱网测试是最容易翻车的。很多App在WiFi下跑得飞起,一上4G就卡成PPT。建议在代码里加一个「网络质量探测器」,实时监测RTT和丢包率,动态调整视频码率和帧率。
30.5 发布:最后一步,也是最容易出问题的一步
发布到应用商店之前,有几个事一定要做:
- 混淆配置:确保RTMP库、编码器相关的类不被混淆。我一般会在proguard-rules.pro里加一行
-keep class com.example.rtmp.** { *; } - 签名:用正式签名文件打包,别用debug签名。debug签名的证书有效期短,而且有些SDK(比如地图、推送)会校验签名
- 版本号:versionCode和versionName要递增。我习惯versionCode用整数递增(1,2,3...),versionName用语义化版本(1.0.0, 1.1.0)
- 隐私政策:如果App需要摄像头、麦克风权限,一定要在隐私政策里说明用途。现在应用商店审核很严,说不清楚会被拒
嗯,到这里,整个直播推流App的开发流程就讲完了。从需求分析到架构设计,从功能实现到测试发布,每一步都有坑,但也都有解法。希望你能拿着这些经验,做出自己的直播App。
公众号:蓝海资料掘金营,微信deep3321