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的脉络。

直播推流App 架构分层图 UI层 (Activity / Fragment / Compose) ViewModel层 (LiveData / StateFlow / 状态管理) 业务层 (推流管理器 / 美颜管理器 / 网络监听) 引擎层 (CameraEngine / AudioEngine / RTMPEngine) 底层 (FFmpeg / MediaCodec / OpenGL ES / 网络库)

每一层只依赖它的下一层,不能跨层调用。比如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库)。核心逻辑是:

  1. 初始化RTMP连接(设置URL、超时时间)
  2. 循环从编码器取出视频帧(H.264/H.265)和音频帧(AAC)
  3. 封装成FLV tag,通过RTMP发送
  4. 监听连接状态,断线自动重连

伪代码大概长这样:

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 发布:最后一步,也是最容易出问题的一步

发布到应用商店之前,有几个事一定要做:

  1. 混淆配置:确保RTMP库、编码器相关的类不被混淆。我一般会在proguard-rules.pro里加一行 -keep class com.example.rtmp.** { *; }
  2. 签名:用正式签名文件打包,别用debug签名。debug签名的证书有效期短,而且有些SDK(比如地图、推送)会校验签名
  3. 版本号:versionCode和versionName要递增。我习惯versionCode用整数递增(1,2,3...),versionName用语义化版本(1.0.0, 1.1.0)
  4. 隐私政策:如果App需要摄像头、麦克风权限,一定要在隐私政策里说明用途。现在应用商店审核很严,说不清楚会被拒

嗯,到这里,整个直播推流App的开发流程就讲完了。从需求分析到架构设计,从功能实现到测试发布,每一步都有坑,但也都有解法。希望你能拿着这些经验,做出自己的直播App。


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