
简介面向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/5GIP变了TCP连接直接断RTMP重连逻辑必须做好。而“断网重连”四个字听起来简单实际上要处理编码器内部缓冲的清理、序列号重置、通知UI层展示重连状态每一个环节都有细节要抠。我当时的做法是封装一个RtmpSessionManager统一管理连接状态、重连次数、退避时间把重连逻辑从推流发送线程里抽出来不然会在测试中发现线程并发问题一堆。结尾做Android直播推流这个方向说难不难说简单也不简单。协议栈是固定的代码模式也相对成熟真正拉开差距的是对各种异常情况的处理弱网下的表现、老机型的兼容、播放器的参数调优这些都是文档里查不到但实际又绕不开的东西。我自己在项目里感受最深的一点是RTMP侧的调优一定要有“足够烂的网络”测试环境千万别只在公司WiFi下调试拿手机开热点压低信号后再推流很多问题一下就暴露出来了。如果你们团队有精力建议把直播这块的日志体系做全连接时间、编码参数、码率曲线、断连原因全部打出来后续排查问题的速度会快很多这个钱花得值。本文还有配套的精品资源点击获取