Android端RTSP/RTMP视频播放实践:IjkPlayer集成与调优指南

发布时间:2026/9/3 18:28:54
Android端RTSP/RTMP视频播放实践:IjkPlayer集成与调优指南 简介面向需要处理实时流媒体播放的Android开发者这套可运行Demo集成IjkPlayer用于播放RTSP/RTMP视频流适合在视频监控、直播推拉流等场景中快速起步。压缩包共147个文件大小13.68MB包含aar库、so动态库、kotlin/Java源码、xml布局与配置、gradle构建脚本及png图标等可直接导入Android工程运行调试。已有980人学习下载。Demo以MyIjkPlayerTest-RTSP为例演示了从SurfaceView/TextureView绑定、setDataSource设置RTSP地址到prepareAsync异步准备、播放监听与资源释放的完整流程同时提供ijkplayer-java-release.aar和基础排错逻辑开发者可对照源码理解IjkPlayer的集成方式并迁移到RTMP等协议的播放实现中。 做安防类App或者直播客户端的Android开发十有八九会碰到同一个需求在手机端直接播放摄像头的RTSP视频流或者播放RTMP直播源。我最早接这个需求的时候第一反应是直接用系统自带的MediaPlayer结果被协议兼容性和延迟问题来回教育。后来换成IjkPlayer这套方案配合FFmpeg底层的能力才算是把RTSP和RTMP的播放彻底跑通。这篇文章就围绕一个可运行的Android Demo展开从环境配置、核心代码到排障经验一次讲清楚。如果你正在做IPC摄像头对接、监控客户端或者需要在App里嵌入直播播放能力这篇内容应该能帮你省掉不少弯路。1. 项目由来与播放器选型逻辑1.1 这个Demo到底解决什么问题先把需求拆开看RTSP和RTMP其实是两条路线的产物。RTSPReal Time Streaming Protocol是安防领域的“老大哥”海康、大华这些厂商的IP摄像头基本都支持RTSP取流URL大概长这样rtsp://admin:password192.168.1.64:554/Streaming/Channels/101。RTMPReal Time Messaging Protocol则是直播行业的“老兵”很多推流端、CDN、直播平台到今天还在用它做音视频上行传输常见格式是rtmp://192.168.1.100:1935/live/stream。这两类协议浏览器默认都不支持但在Android原生端只要选对播放器内核就能直接解码渲染。这个Demo的目标就是拿到一个RTSP或RTMP地址打开App点一下播放按钮画面就出来。适合作为视频类项目的基础模块往后再去扩展什么云台控制、录像回放、码流切换都是在它上面做加法。1.2 播放器方案横评IjkPlayer赢在哪动手之前我对比过三个主流方案系统MediaPlayer、Google的ExoPlayer、B站开源的IjkPlayer。表格可能更直观对比维度系统MediaPlayerExoPlayerIjkPlayerRTSP支持很弱依赖硬件厂商实现需要扩展默认不友好FFmpeg内核开箱即用RTMP支持基本不支持有限支持原生支持兼容性好延迟控制不可控可调但有上限可通过参数精准控制自定义能力极弱强极强可改源码重编译集成难度最低中等中等但资料多IjkPlayer底层是FFmpeg对RTSP和RTMP这类非HTTP协议的解析能力是另外两个方案没法比的。实际测试下来同一台海康摄像头用系统MediaPlayer十次有八次弹“无法播放”ExoPlayer倒是能出画面但延迟和缓冲策略不好调IjkPlayer基本是即插即用。另外IjkPlayer在硬解码和软解码之间可以自由切换这也是后面排查兼容性问题时的一个关键开关。1.3 “可运行”这几个字的份量说实话网上打着“可运行Demo”旗号的仓库很多但clone下来经常跑不起来要么so库没打全要么本地地址已经失效要么漏了权限声明。我这边的经验是要让Demo真正“可运行”得把三件事做扎实依赖完整、地址可换、设备适配。尤其是RTSP地址如果你没有真实摄像头可以先用公开测试源或本地搭建的流媒体服务器顶上后面会具体说怎么找可用的测试地址。2. 工程搭建与环境准备2.1 依赖引入Gradle一行配置与局限IjkPlayer在JCenter上有一个历史版本0.8.8虽然仓库后来不更新了但社区还在用常规播放需求完全够。在build.gradle里加依赖dependencies { implementation tv.danmaku.ijk.media:ijkplayer-java:0.8.8 implementation tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8 // 真机以arm64为主建议加上arm64 implementation tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8 // 如果你用模拟器调试才需要x86真机不用加 // implementation tv.danmaku.ijk.media:ijkplayer-x86:0.8.8 }这里有个坑要提前说清楚JCenter已经停止服务很久了老项目如果仓库源里没有JCenter需要显式加上repositories { maven { url https://jitpack.io } mavenCentral() }如果Gradle同步失败或者你需要自定义FFmpeg模块比如裁剪协议、控制openssl就得走源码编译路线下载IjkPlayer源码Android NDK自己去编so库。这个过程比较折腾ButterKnife时代的项目用老NDK版本会有一堆兼容报错我建议优先用0.8.8的现成库踩坑成本最低。2.2 AndroidManifest权限与网络配置播放网络视频流最基本的权限就一个uses-permission android:nameandroid.permission.INTERNET /如果Demo里有截图、录像之类的功能再额外申请存储权限。还有一个容易漏掉的地方Android 9之后默认禁止明文HTTP流量而RTSP和RTMP虽然不是HTTP但某些本地资源如果用明文HTTP地址必须开启明文流量。application android:usesCleartextTraffictrue ... /如果是Android 7及以上的系统使用FileProvider加载本地视频文件时Uri配置也得在Manifest里声明这部分我遇到过一个非常典型的错误Content Uri拼接错误导致系统播放器找不到文件后面排查章节会展开。2.3 工程结构设计一个能跑的Demo不需要复杂的架构我习惯建一个PlayActivity承载播放再写一个IjkPlayerHelper做播放器封装。目录大致这样app/src/main/java/com/example/ijkplayerdemo/ ├── MainActivity.java // 主界面输入地址播放按钮 ├── PlayActivity.java // 全屏播放页 └── helper/ └── IjkPlayerHelper.java // 播放器初始化、销毁、重连封装MainActivity放一个EditText用来输入RTSP/RTMP地址一个Spinner切换协议一个按钮跳转PlayActivity。PlayActivity就是一块SurfaceView/TextureView加一个加载进度条。这里不塞太多功能保证核心链路简单可靠。3. 播放器核心实现与关键参数3.1 初始化播放器与参数配置IjkMediaPlayer的初始化和系统MediaPlayer不太一样它多了setOption这个环节很多播放问题都是从这里调好的。核心配置如下public IjkMediaPlayer createPlayer() { IjkMediaPlayer player new IjkMediaPlayer(); // 硬解码开关true走MediaCodecfalse走FFmpeg软解 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec, 1); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec-auto-rotate, 1); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec-handle-error, 1); // 音频输出方案1为OpenSL ES延迟更低 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, opensles, 1); // 渲染格式RGB565在低端机上更省带宽 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, overlay-format, IjkMediaPlayer.SDL_FCC_RV32); // 关键RTSP传输协议tcp比udp稳定很多 player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, rtsp_transport, tcp); // 弱网丢帧策略直播场景建议打开 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, framedrop, 1); // 降低缓冲延迟 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, packet-buffering, 0); player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, timeout, 10000000); // 硬解失败自动回退软解的风险控制 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec-handle-error, 1); return player; }rtsp_transport这个参数尤其重要。默认RTSP走UDPUDP在局域网内延迟低但跨网段、有防火墙的场景下丢包严重画面全是马赛克或者卡在缓冲。强制走TCP之后虽然延迟会多几十毫秒但稳定性提升非常明显我接的十几个项目里有一半以上是靠这句话救回来的。3.2 渲染层选择SurfaceView还是TextureViewIjkPlayer可以直接setSurface给SurfaceView也可以用TextureView。我的建议是普通播放用SurfaceView要做弹幕、缩放动画、双指手势就用TextureView。SurfaceView的渲染性能更好内存开销更低但有一个特性它不在View树中而是单独有一个Window所以做旋转动画时会有“置顶”问题。TextureView可以当作普通View参与动画但性能略低。Demo里我用了TextureView因为方便往外面包一层GestureDetector做双击全屏和手势缩放体验更完整。player.setSurface(holder.getSurface());用TextureView的话需要在TextureView的onSurfaceTextureAvailable回调里再把Surface传给播放器否则会出现“界面黑屏但音频正常”的问题。3.3 地址拼接规则海康/大华/公共测试源写代码之前先搞清楚地址怎么来。海康摄像头的RTSP规范是rtsp://用户名:密码IP:554/Streaming/Channels/101 rtsp://用户名:密码IP:554/Streaming/Channels/102101代表通道1的主码流102是通道1的子码流。主码流分辨率高、码率大适合录像和全屏查看子码流适合网格预览和弱网环境。大华的地址格式是rtsp://用户名:密码IP:554/cam/realmonitor?channel1subtype0 rtsp://用户名:密码IP:554/cam/realmonitor?channel1subtype1subtype0是主码流subtype1是子码流。如果你拿到的摄像头地址带端口号或者认证信息照原样填进去就好。没有摄像头时可以用几个公开测试源来验证流程协议公开测试地址RTSPrtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4RTMPrtmp://ns8.51.ru:1935/live/000RTMPrtmp://202.69.69.180:443/webcast/bshdlive-pc不过公开源说挂就挂尤其是RTMP源可能今天能播明天就停。我遇到这种情况一般直接用mediamtx原rtsp-simple-server在本地起一个RTSP服务端用FFmpeg推一路视频进去再让IjkPlayer去拉整个链路完全可控。FFmpeg推流命令是这样ffmpeg -re -i test.mp4 -c copy -f rtsp rtsp://192.168.1.100:8554/live/test3.4 生命周期管理与资源释放播放器这块最容易被初学者忽略的是生命周期。Activity onDestroy时必须释放播放器否则底层FFmpeg的线程不会自动退出长时间操作会闪退或者内存泄漏。Override protected void onResume() { super.onResume(); if (mPlayer ! null !mPlayer.isPlaying()) { mPlayer.start(); } } Override protected void onPause() { super.onPause(); if (mPlayer ! null) { mPlayer.pause(); } } Override protected void onDestroy() { super.onDestroy(); if (mPlayer ! null) { mPlayer.stop(); mPlayer.release(); mPlayer null; } }值得注意release()后不要再调用任何播放器方法否则会抛IllegalStateException。我习惯在释放前把setDisplay(null)清一下Surface引用防止Surface销毁顺序问题。4. 双场景实测RTSP摄像头流和RTMP直播流4.1 RTSP拉流实测记录我拿了一台海康DS-2CD3T系列摄像头做测试IP为192.168.1.64主码流地址rtsp://admin:password192.168.1.64:554/Streaming/Channels/101实测结果指标主码流101子码流102分辨率2560x1440640x360打开耗时TCP约700ms约350ms画面流畅度稳定25fps稳定25fps首帧显示约1s以内500ms左右打开主码流时体积较大的TCP包在弱网下可能触发缓冲可以适当把max-fps调低player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, max-fps, 30);如果摄像头有H.265编码的主码流IjkPlayer 0.8.8自带的FFmpeg默认可能不支持解码表现就是黑屏或者只有声音。这种场景建议切换到H.264编码的码流或者自己编译带H.265解码的FFmpeg版本。4.2 RTMP播放实测记录用SRS或nginx-rtmp起一个本地RTMP服务端推流地址rtmp://192.168.1.100:1935/live/testIjkPlayer播放这个地址延迟大致在500ms到1s之间相比RTSP的200ms左右略高。RTMP走的是TCP所以不存在UDP被防火墙丢弃的问题弱网下的表现反而比RTSP更稳。IjkPlayer对RTMP内部封装协议处理得比较完整常见的FLV封装、AAC音频、H.264视频都能直接解。4.3 延迟表现与调优心得有的场景对延迟极其敏感比如远程遥控车、无人机图传这时可以通过三个参数压低延迟player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, packet-buffering, 0); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, framedrop, 1); player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, fflags, nobuffer);实测下来RTSP可以把延迟压到200ms以内RTMP能压到400ms左右。这组参数只适合实时性要求高的场景普通直播可以稍微留一点缓冲否则网络抖动会特别明显。5. 高频报错与排查手记5.1 黑屏、无画面的第一排查顺序遇到黑屏先别调代码按这个顺序排查确认没有在onSurfaceTextureAvailable之前设置Surface。TextureView的Surface不是立刻可用的一定要在回调里接管。确认地址是否真的通。手机上装个VLC输入同一个RTSP地址如果VLC也放不了那就是地址或网络问题不是IjkPlayer的锅。确认硬解是否有兼容问题。把mediacodec设为0强制软解如果画面出来了说明硬解码器对该分辨率或编码格式支持不好。提示软解能放、硬解黑屏时不要盲目怀疑IjkPlayer多半是设备厂商的MediaCodec实现有bug。这种情况下可以保留硬解开关在应用设置里加一个“硬解/软解切换”项用户自己选。5.2 一直缓冲进度条不动这是RTSP最常见的连接问题。原因按概率排序UDP传输被防火墙丢包、摄像头在线路数超限、网关路由MTU问题。我的第一动作永远是player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, rtsp_transport, tcp);换成TCP后90%的缓冲问题能解决。如果还有剩余情况检查摄像头是不是被其他客户端占用了海康设备普遍有在线用户数上限超了就直接拒绝新连接。另外部分摄像头在工地等复杂网络环境中对MTU很敏感可以把本地路由器MTU调整到1400左右再试。5.3 花屏、音画不同步花屏大概率是网络丢包播放器来不及重传导致的。RTSP的UDP模式不保证可靠性丢几个RTP包就会出现花屏。之前提过切TCP基本能解决。音画不同步则更多是音频缓冲策略的问题。IjkPlayer的音频默认走OpenSL ES延迟低但某些设备上解码格外慢。可以在初始化时打开player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, opensles, 1); player.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, aformat, s160le); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, sync-av-start, 0);还有一个细节部分RTMP源会把音频封装为AAC LATM格式IjkPlayer可能不识别。如果遇到“播放网络直播流有画面无声音”优先向服务端要标准的AAC ADTS格式或者检查推流端编码参数。5.4 常见问题速查表现象可能原因解决办法黑屏无声音硬解兼容问题关闭mediacodec强制软解一直缓冲RTSP默认UDP被防火墙拦截设置rtsp_transporttcp花屏网络丢包切TCP降低码流档位有画面无声音音频编码格式不标准统一为AAC ADTS检查opensles切换地址崩溃未release旧player先stop再release再创建新实例RTSP地址带空格或特殊字符URL编码问题使用Uri.encode处理地址6. 从Demo到生产还需要补齐的能力6.1 自动重连与播放状态机Demo能跑只是开始。真实项目中摄像头断网、断电、重启是家常便饭播放器必须能自己重连。IjkPlayer播放失败会回调onError此时不要直接重建播放器而是进入一个重连状态机先检查网络再指数退避重连间隔从1秒逐步增加到10秒直到成功或用户主动取消。封装的时候记一下当前播放地址重连后继续用同一个地址就好。6.2 多路视频与码流切换监控类App经常要同时看四路、九路画面。IjkPlayer没提供GPU硬解码的零拷贝方案多路同时解码极其吃CPU主流做法是预览画面统一用子码流全屏时才切主码流播放器实例池化切流时复用每路最多保留两个播放器实例超过就关掉最久没看的码流切换的核心操作是先reset()再setDataSource最后prepareAsync不能直接改URL。6.3 与流媒体服务端配合的扩展方向很多人做完Android拉流后发现产品经理转头就要“网页上也能看”。RTSP协议浏览器原生不支持这时候就需要后端转发。我常用的方案是用mediamtx或ZLMediaKit这类开源流媒体服务把RTSP/RTMP源统一转成HLS或WebRTC再给Web端播放。IjkPlayer的拉流能力没有浪费它可以直接对接这类服务器的输出地址反而变成了整个视频链路里的一个高可靠客户端模块。另外IjkPlayer也支持播放本地文件、HLS.m3u8地址、HTTP-FLV地址所以从监控到直播到点播一套Demo的播放内核基本都能覆盖。把这个模块沉淀好后面接什么业务都不慌。最后分享一个实操细节把播放器常用参数比如rtsp_transport、mediacodec、timeout放到一个配置类里管理而不是散落在Activity代码里调优的时候效率会高很多。我每次接新项目第一件事就是复制这套播放器封装然后是改参数、测试、迭代基本上一个下午就能交付一个可用的播放模块。本文还有配套的精品资源点击获取