简介:面向Android直播开发者的RTMP推流资料包,围绕“服务器搭建—图像采集—视频编码—音频采集—音频编码—RTMP封装推流”全链路整理。内容覆盖Nginx/RTMPDump/x264/FAAC等源码与编译产物,并附带可直接导入Android Studio的工程源码、远程Linux控制工具以及FLV/二进制分析工具,适合具备一定基础、希望搭建完整安卓直播推流环境的开发者参考。通过对照源码与编译库,可以快速搭建Android端采集、编码、推流闭环,并理解H.264/AAC与RTMP封装之间的配合关系。压缩包大小112.53MB,内含源码、静态库、分析工具等多类文件,目录按服务器、编码库、应用源码、工具链分模块组织,便于按需取用。已有1295人学习下载,资料源自配套博文,可配合文中步骤系统梳理RTMP直播实现中的关键环节与常见问题。 做Android端直播相关开发,RTMP是一个绕不开的协议。我这里说的不是纯概念层面的RTMP,而是你真的要在App里把摄像头画面推到服务器,或者要拉一个RTMP流在手机上播放出来,整个流程会遇到什么、要准备什么、有哪些参数可以直接抄。这两年我一直在折腾移动端推流和播放相关的东西,踩了不少坑,也攒了不少能直接用的配置和代码片段。
这篇东西的目标读者很明确:正在做Android端推流、拉流播放,或者被“RTMP老是断”“OpenCV打开流失败”“音画不同步”这类问题折磨的开发者。如果你只是听说过RTMP想入门,也能从里面找到一条可以照着走通的路线,不至于一上来就被各种概念劝退。下面我尽量按实际动手的顺序讲,从环境准备、服务器搭到代码实现、问题排查一条龙梳理清楚。
1. RTMP在Android端的定位与选型思路
1.1 为什么现在还选RTMP
虽然现在HLS、WebRTC、SRT这些协议各有各的拥趸,但RTMP在直播推流端依然是事实标准。原因很直接:RTMP基于TCP,链路稳定,延迟能做到1到3秒,而且几乎所有CDN、云直播服务都支持RTMP推流。在Android端做直播SDK,推流协议首选还是RTMP,播放端则可以HLS和RTMP混用,看业务场景。
很多初学者会纠结“RTMP是不是太老了”,实际做项目你会发现,老不代表不能用。RTMP的命令通道和数据通道设计得很清晰,AMF协议虽有点啰嗦但好在稳定,调试工具也丰富。Android端不像PC端可以用OBS随意推流,你需要在手机上完成采集、编码、封装、发送这一整条链路,RTMP相对简单的封装格式反而让接入工作变得可控。
1.2 常用开源库横向对比
Android端RTMP相关的开源方案,我实际用过并且觉得值得关注的有这么几个:
- librestreaming:代码比较清晰,基于Java和JNI混合实现,内部RTMP封装是纯Java的,容易改。缺点是摄像头采集部分依赖Camera1 API,在新设备上兼容性一般。
- yasea:这个库比较完整,支持AAC音频编码和H.264视频编码,作者把MediaCodec硬编和RTMP发送整合得很好。我早期很多项目直接基于它改的,如果要快速验证推流可行,这是首选。
- rtmp-rtsp-stream-client-java:功能更全面,不仅支持RTMP,还支持RTSP,底层用底层是封装好的native库,接口设计比较现代。适合需要同时支持两种协议的场景。
选型建议很简单:你要是只想快速把推流跑通验证业务,直接选yasea;要是想长期维护、深度定制,比如加美颜、加水印,那最好基于librestreaming自研,因为它的结构最容易被拆开改。网络上传这个环节,Java实现和Native实现差别不大,瓶颈基本都在编码器和上行带宽。
1.3 硬编码比软编码强在哪
Android端的编码器选择是推流质量的关键决定因素。MediaCodec硬编码和x264软编码之间的差异,用一句话概括:硬编码吃硬件专用单元,速度快、省电,但画质码率控制粗;软编码吃CPU,画质细腻,但发热严重。
实际项目中,像中低端机型如果用软编码推720P,手机温度几分钟就能飙上去,接着系统开始降频,推流帧率跟着掉到15fps以下,音画不同步就来了。所以我的建议是:移动端推流默认走MediaCodec硬编,只把软编码作为兼容兜底方案,而且软编码建议限制在360P、15fps这样的低规格。
2. 测试环境搭建:先把拉流和推流环境跑通
2.1 没有服务器怎么测:现成RTMP测试地址与注意事项
推流代码写完了,第一步不是连正式服务器,而是先接一个能看的地址验证链路。很多刚接触RTMP的开发者卡在第一步:手里没有服务器,也不知道推到哪里看效果。
这里给出两种测试思路。第一种是用公共测试流,比如Mux提供的测试RTMP流地址(mux.com的test stream页面可以拿到),但这个地址是拉流用的,不能拿来推流。第二种是自己搭一个本地RTMP服务,这也是我推荐的方式,因为公共测试地址经常有地域和有效性限制,网络一波动排查问题特别费劲。
如果你只是做播放端开发,想测播放器能不能正常拉流,可以先用公共测试流把播放端代码调通。常见的测试流有Mux Test Stream、Akamai的测试流,直接在播放器里填上播放地址就行。要注意有些测试流会间歇性断流,这属于正常现象,别一断就怀疑自己代码有bug。
2.2 自建nginx-rtmp服务器
自建RTMP服务器,最省事的方案就是nginx-rtmp模块。我在Ubuntu上搭过多次,总结下来大概就三步。
第一步是安装nginx和rtmp模块。以Ubuntu为例,可以编译安装也可以直接装带rtmp模块的第三方包,我习惯直接用源码编译,因为后期加SSL、加HLS切片支持都方便。编译命令大概是这样的:
git clone https://github.com/arut/nginx-rtmp-module.git git clone https://github.com/nginx/nginx.git cd nginx ./configure --add-module=../nginx-rtmp-module --with-http_ssl_module make -j4 make install第二步是改nginx.conf,加一段RTMP配置。下面是我常用的一份最小配置:
rtmp { server { listen 1935; chunk_size 4096; application live { live on; record off; allow play all; } } }这段配置的意思是:监听1935端口,应用名是live,开启直播模式,关闭录像。推流地址就是rtmp://你的IP:1935/live/房间号,播放地址也一样。霍,就这么简单,一个能用的RTMP服务就已经跑起来了。
第三步验证服务是否正常。在服务器上执行netstat -tlnp | grep 1935,能看到端口在监听,基本就成了。推流的时候,nginx的日志会实时打印连接信息,这对接下来的问题排查非常重要。别忘了在防火墙里放行1935端口,不然手机连不上。
2.3 实测推流前的本地校验流程
服务器搭好了,我建议先用PC端工具推一次流,确认服务器链路是通的,再去折腾手机端代码。这一步能帮你把问题分层:PC推流成功,说明服务器和网络没问题,问题在App代码;PC推流失败,那问题在服务器配置或网络环境。
PC端推流工具我用过OBS和ffmpeg。ffmpeg的命令行方式更适合快速测试,比如推一个摄像头画面:
ffmpeg -re -i test.mp4 -c copy -f flv rtmp://127.0.0.1:1935/live/test用-c copy可以不做转码直接把本地视频推上去,这样能排除编码器因素干扰,定位问题更快。播放端可以用ffplay或者VLC,能正常出画面就说明这一层链路完全通了。手机端推流调试也可以参考这个思路:先用自带的摄像头预览确认采集正常,再开启推流,比对预览和远端画面,哪个不对就重点查哪一层。
3. Android推流端的完整实现
3.1 摄像头采集与麦克风采集
采集这块,老项目用Camera1 API的多,新项目建议直接用CameraX。CameraX对生命周期管理做得更好,代码也简洁,而且能自动适配旋转。
摄像头采集到数据之后,一般要做一次图像旋转处理。竖屏推流时,摄像头传感器默认是横屏的,需要把预览和编码的帧数据旋转90度或270度,不然推出去的画面是歪的。这个旋转可以在相机回调里做,也可以直接让编码器读取带有旋转信息的Buffer。CameraX的ImageAnalysis拿到的是ImageProxy,用起来比Camera1的onPreviewFrame顺手。
麦克风采集就是标准的AudioRecord,设置采样率44100Hz、单声道、16bit就够了,这个配置在绝大多数Android设备上都支持。AudioRecord读出来的PCM数据不能直接推流,要先经过AAC编码。编码后要处理一个细节:AAC的AudioSpecificConfig需要手动拼出来,包括音频对象类型、采样率索引、声道配置,这个配置要在推流握手完成后立刻发给服务器。
3.2 MediaCodec硬编码参数与关键坑
MediaCodec硬编H.264是我花时间最多的地方,几个关键参数必须设置正确。首先是码率控制,一般用BITRATE_MODE_VBR或CBR。直播场景建议用CBR(恒定码率),因为RTMP传输需要稳定的数据输出,VBR码率波动大,弱网下更容易卡顿。
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_BIT_RATE, 1500_000); // 1.5Mbps format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2); // 关键帧间隔2秒 format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);这里有个新手必踩的坑:KEY_I_FRAME_INTERVAL单位是秒,不是帧。很多人以为是帧数,结果设置的GOP大小完全不对,推出去的画面花屏或者延迟越来越大。第二个坑是SPS/PPS的获取时机。MediaCodec刚启动时输出的第一个Buffer通常是编码配置信息,里面包含SPS和PPS,必须缓存下来,等RTMP握手完成之后把这两个NALU发给服务器,播放端才能解码。这个配置信息是带BUFFER_FLAG_CODEC_CONFIG标志的,代码里要判断这个标志。
3.3 时间戳同步与断线重连
RTMP推流端的音画同步比本地播放更难搞,因为网络传输有抖动,而服务器和播放端是按时间戳来排布数据的。我的做法是统一用System.nanoTime()生成参考时间戳,音频和视频编码器分别用相同的时间基准来打时间戳。
具体来说,视频帧的时间戳从MediaCodec.BufferInfo.presentationTimeUs拿来,音频帧同理。传给RTMP封装层时,要把纳秒或微秒单位换算成RTMP的时间戳单位。yasea和librestreaming的RTMP包封装层都处理过这个换算,如果自己写,千万注意单位一致性,不然播放端会出现声音先出来一个世纪再出画面的奇观。
断线重连是另一个重点。移动端网络切换频繁,WiFi切流量、隧道场景、弱网丢包,都会导致RTMP连接断开。如果业务不允许断流,一定要做重连逻辑。重连思路就是检测到网络不可达或RTMP连接异常后,关闭当前连接线程,清理编码器队列,然后重新建立连接、重新设置SPS/PPS。注意必须重建连接,不能复用旧连接。重连的时间间隔建议做成退避策略,第一次2秒,第二次4秒,最多延迟不再增加,避免在弱网环境下疯狂重连导致系统资源耗光。
4. Android播放端的接入方案
4.1 ijkplayer接入要点
拉流播放这块,Android端可选的方案也不少,但要做到延迟低、延迟稳定,ijkplayer依然是很稳的选择。它是B站开源的基于FFmpeg的播放器,支持RTMP协议,硬解软解都能配。
接入ijkplayer时分两个层面:一是编译so库,二是代码集成。如果你不想自己编译,可以直接用现成的release so库,但依赖库版本可能不匹配导致崩溃。我的建议是,如果只做简单播放,用官方release包没问题;如果要定制协议、加首屏秒开逻辑,就得自己编so。
ijkplayer在gradle里的基础依赖大概是这样:
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'不同类型机型加载的so库不一样,为了减少包体积,arm64和armv7a同时带上,x86的库只在模拟器调试时加,打包时通过abiFilters过滤掉。
4.2 setOption参数怎么调
ijkplayer强大在setOption,大量播放行为都可以通过option控制。做RTMP拉流时我常用这几个:
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer"); // 关闭缓冲,降低延迟 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", "1000000"); // 只分析前1秒数据 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", "1024"); // 限制探测数据量 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "sync_audio", "1"); // 以音频为基准同步这里fflags nobuffer是关键项,不加这个,RTMP首屏延迟可能拉到5秒以上,用户是忍不了的。有个坑是,probesize设置太小,可能有些流解析不了,这时候要适当调大一点,比如4096。不同播放地址表现不一样,调试时要灵活调。
4.3 硬解/软解选择策略
ijkplayer支持硬解和软解。软解基于FFmpeg内置解码器,兼容性好,任何设备都能解;硬解走MediaCodec,稳定性和画质跟设备解码器强相关,但性能和功耗好。我的经验是:中高端机用硬解,老机型软解更稳,判断标准可以看设备型号或系统版本。
RTMP流大都是H.264 High Profile,硬解可能遇到部分老设备的兼容性问题,表现为花屏或者直接黑屏。这种情况下,快捷方案是加一个开关,让用户在设置页切换解码方式。千万别偷懒只支持一种解码方式,我见过好几次线上反馈黑屏,最后都是硬解兼容性导致的。
5. 常见问题与排查技巧实录
5.1 常见问题速查表
我把自己踩过的坑和同事常遇到的问题整理成了一张表,方便直接用。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 推流成功但播放端黑屏 | 未正确发送SPS/PPS | 检查编码器输出是否缓存配置帧,在握手后发送 |
| 播放端音画不同步 | 时间戳单位不一致或同步基准混乱 | 统一时间基准,RTMP时间戳使用毫秒,检查音频帧时间戳 |
| 推流卡顿严重 | 上行带宽不足或码率设置过高 | 降低码率或分辨率,建议先测网络上行速度 |
| 连接经常断开 | 弱网环境或RTMP超时设置过短 | 延长socket超时时间,加断线自动重连逻辑 |
| 打开RTMP地址报错-2 | 地址格式错误或服务器不可达 | 用ffprobe检查地址,确认网络能通到目标端口 |
| 首屏加载要5秒以上 | 播放器缓存策略未关闭 | 配置fflags nobuffer,降低分析的duration |
这个表只是快速定位用,真正解决问题还要按链路逐层排查。推流端的排查顺序一般是:采集画面是否正常、编码是否输出、RTMP连接是否握手成功、服务器日志是否有报错、播放端是否能收到数据。任何一层出了问题,表现都是断流或黑屏,但定位路径完全不同。
5.2 OpenCV打开RTMP失败的两种替代思路
OpenCV在Android上打开RTMP流失败,这是个高频问题。原因其实不在OpenCV本身,OpenCV里的VideoCapture是基于FFmpeg编译的,很多官方预编译的OpenCV包压根没有启用RTMP协议支持,所以传入RTMP地址它会直接返回false。
网上有人建议重新编译OpenCV,把FFmpeg的RTMP模块加进去,这在PC上可行,但在Android上非常痛苦,交叉编译环境,各种依赖版本,折腾一天未必跑通。我实际项目中用了两种更务实的思路。
第一种是把拉流这层从OpenCV里拿出来,单独用ffmpeg或ijkplayer去拉RTMP流,解码后的YUV或RGB数据再传给OpenCV做后续图像处理。这样OpenCV只负责处理图像帧,不做网络传输,问题彻底规避。第二种是使用专门的RTMP拉流库,比如用Librtmp或者GCDAsyncSocket之类的库自己实现RTMP拉流,拿到视频帧之后再解码。两条路线的本质都是不让OpenCV碰网络流。
5.3 还有几个容易忽视的坑
最后说几个不太容易注意到但确实踩过的坑。
第一个是关于H.264的B帧问题。部分编码器默认会开启B帧,可以提升同码率下的画质,但对低延迟直播来说,B帧会引入额外的编码延迟和乱序问题,很多播放端处理不好,轻则画面轻微卡顿,重则花屏。推流端可以在MediaFormat加一个KEY_LATENCY参数,或者在编码器配置里关闭B帧,我用过的多数设备传KEY_MAX_B_FRAMES设为0能解决。
第二个是音频编码的Profile。AAC-LC是兼容性最好的选项,HE-AAC虽然压缩率更高,但部分播放器解不了。为了稳妥,推流端固定用AAC-LC,不要为了省码率去开HE-AAC。
第三个是网络切换。用户从WiFi切到4G/5G,IP变了,TCP连接直接断,RTMP重连逻辑必须做好。而“断网重连”四个字听起来简单,实际上要处理编码器内部缓冲的清理、序列号重置、通知UI层展示重连状态,每一个环节都有细节要抠。我当时的做法是封装一个RtmpSessionManager,统一管理连接状态、重连次数、退避时间,把重连逻辑从推流发送线程里抽出来,不然会在测试中发现线程并发问题一堆。
结尾
做Android直播推流这个方向,说难不难,说简单也不简单。协议栈是固定的,代码模式也相对成熟,真正拉开差距的是对各种异常情况的处理,弱网下的表现、老机型的兼容、播放器的参数调优,这些都是文档里查不到但实际又绕不开的东西。我自己在项目里感受最深的一点是:RTMP侧的调优一定要有“足够烂的网络”测试环境,千万别只在公司WiFi下调试,拿手机开热点压低信号后再推流,很多问题一下就暴露出来了。如果你们团队有精力,建议把直播这块的日志体系做全,连接时间、编码参数、码率曲线、断连原因全部打出来,后续排查问题的速度会快很多,这个钱花得值。
本文还有配套的精品资源,点击获取