十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android集成IjkPlayer实现RTSP/RTMP低延迟视频播放

Android集成IjkPlayer实现RTSP/RTMP低延迟视频播放 简介面向Android开发者的可运行Demo工程演示了如何用Bilibili开源的IjkPlayer播放RTSP与RTMP实时视频流。针对系统MediaPlayer对两类协议支持不佳的痛点工程给出了完整的集成、编译和调试验证方案适合需要接入监控流、直播流或学习播放器二次开发的初中级开发者。压缩包共147个文件、约13.68MB核心包含Kotlin源码、Android布局与配置、IjkPlayer的aar库、so动态库及Gradle构建脚本目录结构清晰可导入Android Studio直接运行。目前已有980人学习或浏览具备一定参考价值。借助这个Demo开发者能快速掌握依赖引入、播放器初始化、setDataSource与prepareAsync调用、播放事件回调、资源释放等关键环节也能对URL设置、异常处理、播放控制等实际落地细节形成直观认识显著降低实时流媒体播放功能的起步门槛。 做安卓音视频开发的基本绕不过 IjkPlayer 这个名字。它是 B 站开源的一款基于 FFmpeg 的播放器对 RTSP、RTMP、HLS、HTTP-FLV 这类流媒体协议支持得非常全面。我最近整理了一个能直接跑起来的 Android 播放 Demo专门用来拉取 RTSP/RTMP 视频流适合摄像头监控、直播流预览这类场景。Demo 里包含了依赖接入、播放器初始化、核心代码实现、低延迟参数调优和常见问题排查照着敲一遍就能在真机上看到画面。如果你正准备做摄像头取流、直播播放或者一直没搞明白 IjkPlayer 那些 setOption 参数到底该怎么配这篇文章可以直接参考。1. 方案选型为什么还是 IjkPlayer1.1 几个候选播放器的对比Android 上能播放视频的组件其实不少但能播 RTSP/RTMP 的就那么几个。先说说系统自带的 MediaPlayer。它支持 HTTP 和 HLS对 RTSP 也有历史支持但实际用起来非常勉强延迟高、兼容性差很多摄像头的 RTSP 流直接打不开。ExoPlayer 是 Google 官方推荐库功能确实强但对 RTSP 的支持也不够彻底需要额外扩展配置起来麻烦不少。Vitamio 当年很火后来基本不维护了新项目没人敢用。VLC 的 Android 库倒是能播 RTSP可体积太大集成成本高也不适合一般业务。IjkPlayer 的优势在于底层直接封装了 FFmpeg你能想到的流媒体协议它基本都支持。RTSP、RTMP、HLS、HTTP-FLV几乎是开箱即用。而且在播放器层面做了很多底层优化Java 层调用也简单对做业务开发的工程师来说很友好。1.2 IjkPlayer 还值不值得用说实话IjkPlayer 官方仓库已经很久没有大版本更新了很多人在新项目里会犹豫一个停更的库还敢用吗我的看法是停更不代表不能用关键在于它解决的核心问题依然没有更好的替代方案。FFmpeg 本身一直在演进而 IjkPlayer 的封装层已经足够稳定社区里也有不少 fork 分支在持续修复 bug、适配新系统。比如 CarGuo 的 ijkplayer 分支就做了很多优化。如果你只是需要在 Android 上稳定播放 RTSP/RTMPIjkPlayer 依然是最省事的选择。当然如果你的场景是纯 HLS/点播那直接用 ExoPlayer 更合适。但如果是安防摄像头取流、RTMP 直播播放这类需求IjkPlayer 目前依然是最靠谱的路径。2. 集成准备依赖引入和初始化2.1 gradle 依赖与 ABI 取舍Demo 的依赖官网上有很多版本我这边用的是经典的 0.8.8 版本集成方式如下android { defaultConfig { ndk { abiFilters armeabi-v7a, arm64-v8a } } } dependencies { 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 }这里有个很关键的点ABI 必须配好。我只保留了 armeabi-v7a 和 arm64-v8a因为现在市面上的手机基本都是 ARM 架构x86 模拟器跑 RTSP 流意义不大。如果不加 abiFilters构建时会尝试打包所有架构要么失败要么包体变得巨大。注意如果你的项目网络环境拉不到 jcenter 上的 0.8.8 依赖就去 GitHub 找 CarGuo 的 ijkplayer 分支里面提供了 AAR 文件手动放到 libs 目录导入即可。2.2 初始化与基础调用IjkPlayer 需要在应用启动时加载底层 native 库最简单的方式是在播放器创建前调用一次IjkMediaPlayer.loadLibrariesOnce();这个方法会加载 ijkffmpeg、ijksdl 等 so 库。注意只需要调用一次重复调用虽然不会崩但没必要。创建播放器实例后常规调用流程和系统 MediaPlayer 很像IjkMediaPlayer player new IjkMediaPlayer(); player.setDataSource(url); player.setDisplay(holder); player.prepareAsync(); player.setOnPreparedListener(mp - mp.start());很多刚接触的人会在 setDataSource 之前漏掉参数设置导致后面调优不生效。IjkPlayer 的参数设置必须放在 prepare 之前最好在new之后立刻设置。3. 核心实现一个可运行的 Demo 怎么写3.1 UI 层地址输入与画面显示Demo 的界面很简单上面一个输入框填 RTSP/RTMP 地址下面一个 SurfaceView 显示画面再放一个播放按钮。EditText android:idid/et_url android:layout_widthmatch_parent android:layout_heightwrap_content android:hint请输入 RTSP/RTMP 地址 / SurfaceView android:idid/surface_view android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 / Button android:idid/btn_play android:layout_widthmatch_parent android:layout_heightwrap_content android:text播放 /为什么用 SurfaceView 而不是 TextureViewSurfaceView 的渲染性能更好内存占用更小适合视频播放这种高频刷新场景。TextureView 支持属性动画、画面旋转但性能略差而且某些老机型上会出现画面撕裂。做监控预览这种需求SurfaceView 足够。3.2 播放器封装与生命周期处理播放器不能直接写死在 Activity 里最好封装一层方便复用和管理生命周期。Demo 里的核心代码大概是这样public class PlayerActivity extends AppCompatActivity implements SurfaceHolder.Callback { private IjkMediaPlayer player; private SurfaceView surfaceView; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_player); surfaceView findViewById(R.id.surface_view); surfaceView.getHolder().addCallback(this); IjkMediaPlayer.loadLibrariesOnce(); } Override public void surfaceCreated(SurfaceHolder holder) { String url getIntent().getStringExtra(url); startPlay(url, holder); } private void startPlay(String url, SurfaceHolder holder) { releasePlayer(); player new IjkMediaPlayer(); // 关键参数后面会详细说明 player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, rtsp_transport, tcp); player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, probesize, 1024L); player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, analyzemaxduration, 100L); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, packet-buffering, 0); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, framedrop, 1); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec, 1); try { player.setDataSource(url); player.setDisplay(holder); player.prepareAsync(); } catch (IOException e) { e.printStackTrace(); } player.setOnPreparedListener(mp - mp.start()); player.setOnErrorListener((mp, what, extra) - { Log.e(Player, play error: what , extra); return true; }); } private void releasePlayer() { if (player ! null) { player.stop(); player.release(); player null; } } Override public void surfaceDestroyed(SurfaceHolder holder) { releasePlayer(); } }生命周期处理这里要特别留意。很多人只写了播放逻辑没处理退出的情况导致 Activity 销毁了播放器还在占用摄像头资源或者继续发声。我这边在surfaceDestroyed里统一释放切后台或者退出界面时画面会被销毁播放器也会被释放避免内存泄漏。提示releasePlayer()里我先调了stop()再release()这两个步骤不能反过来。有些播放状态直接release()会导致底层资源没清理干净后续再创建新播放器时可能闪退。4. 低延迟参数调优RTSP/RTMP 不卡顿的关键4.1 关键参数解析IjkPlayer 之所以让很多人头疼就是因为它暴露了几十个参数。其实真正需要关心的核心参数就这几个参数类别作用rtsp_transporttcpFORMAT强制 RTSP 走 TCP避免 UDP 丢包花屏probesize1024FORMAT减少探测数据量加快起播analyzemaxduration100FORMAT限制流分析时长快速进入播放packet-buffering0PLAYER关闭播放器内部缓冲降低延迟framedrop1PLAYER允许丢帧保证画面实时性max-fps25PLAYER限制输出帧率降低解码压力mediacodec1PLAYER开启硬解码降低 CPU 占用opensles1PLAYER开启 OpenSL ES 音频输出减少音频延迟每个参数都是有存在理由的。比如probesize和analyzemaxduration这两个是 FFmpeg 的 demuxer 参数控制的是打开流时对媒体信息的探测程度。默认值很大导致播放器要花好几秒分析流格式画面迟迟出不来。调小以后播放器会很快完成分析起播速度明显提升。packet-buffering0是降延迟的关键。播放器默认会囤积大量数据包来保证流畅但在实时监控场景下囤积就意味着延迟越来越大你要看的其实是“当前这一刻”的画面不是十秒前的。关掉缓冲后播放器只会保留非常少的数据量画面延迟能控制在几百毫秒内。4.2 两套推荐配置调参不能一套打天下我把 Demo 里准备的配置文件按场景拆分了一下。监控摄像头场景RTSP 为主player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, rtsp_transport, tcp); player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, probesize, 1024L); player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, analyzemaxduration, 100L); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, packet-buffering, 0); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, framedrop, 1); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, max-fps, 25); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec, 1);公网 RTMP 直播场景RTMP 走的是 TCP 链路不需要设置rtsp_transport但网络波动时更容易卡顿所以packet-buffering可以保留为 1用少量缓冲换流畅度延迟还是可以接受的player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, probesize, 1024 * 16L); player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, analyzemaxduration, 200L); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, packet-buffering, 1); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, framedrop, 1);提示max-fps会影响画面流畅度如果你播放的是 60fps 的内容把它设成 25 反而会卡。这个参数适合摄像头常见 25fps 的情况。不确定时可以直接不设让播放器按源帧率输出。5. 测试源没有摄像头也能调试5.1 公共测试源的现状写 Demo 的时候最尴尬的就是没有真实流地址。网上搜到的公共 RTSP/RTMP 测试源大多已经失效或者清晰度极低体验很不好。这里列几个常见的老牌公共源但特别提醒它们随时可能失效只能用来临时验证网络通不通RTSPrtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4RTMPrtmp://58.200.131.2:1935/livetv/hunantv比较靠谱的反而是自己搭一个本地流媒体服务开发调试非常稳定。5.2 用 mediamtx ffmpeg 本地造流本地造流我推荐用 mediamtx这个服务端工具用 Go 写的单文件启动支持 RTSP、RTMP、WebRTC 等一堆协议配置也简单。先启动服务端./mediamtx然后用 ffmpeg 把一个本地视频循环推流到服务端ffmpeg -re -stream_loop -1 -i test.mp4 -c copy -f rtsp rtsp://localhost:8554/demo播放器里直接访问rtsp://192.168.x.x:8554/demo如果你的 test.mp4 编码格式不适合流拷贝可以加上转码参数先用 libx264 压一遍再推ffmpeg -re -stream_loop -1 -i test.mp4 -c:v libx264 -preset ultrafast -tune zerolatency -c:a aac -f rtsp rtsp://localhost:8554/demo如果想测 RTMP推流命令改成ffmpeg -re -stream_loop -1 -i test.mp4 -c:v libx264 -preset ultrafast -f flv rtmp://localhost:1935/live/demo播放地址就是rtmp://192.168.x.x:1935/live/demo注意播放器里不能直接用localhost要用你电脑在局域网里的 IP否则手机上访问不到。有真实摄像头的话海康的 RTSP 取流地址格式一般是rtsp://用户名:密码摄像头IP:554/Streaming/Channels/101其中101是主码流102是子码流。大华的地址格式是rtsp://用户名:密码摄像头IP:554/cam/realmonitor?channel1subtype0知道这两个格式调试安防项目基本够用了。6. 高频问题排查与踩坑实录6.1 黑屏、无声、花屏黑屏起播不出来先确认网络权限和明文传输。Android 9 之后默认禁止明文 HTTP 流量而摄像头地址基本都是rtsp://开头的非加密协议必须在 AndroidManifest.xml 的application标签里加android:usesCleartextTraffictrue否则连接直接被系统拦掉。花屏、马赛克、卡顿严重绝大多数情况是 RTSP 默认走了 UDP 传输。WiFi 或者公网环境下UDP 丢包太严重画面就会花。在setDataSource之前设置rtsp_transporttcp强制走 TCP 传输这个问题基本能解决。有画面没声音先看摄像头的音频编码是什么格式G.711、AAC 都很常见。IjkPlayer 的 so 库如果没编入对应音频解码器就会无声。可以尝试开启opensles1如果还不行就要确认你的 so 库里有没有对应解码器。网上很多教程都忽略了这个点实际做监控项目时遇到率很高。6.2 其他容易踩的坑硬解码绿屏mediacodec1开启后部分老机型或者分辨率切换时会出现绿屏、花屏。遇到这种情况直接把 mediacodec 改成 0走 FFmpeg 软解CPU 占用会高一些但画面正常。播放一段时间后自动断开很多摄像头并发连接数有限或者 RTSP session 有超时机制。播放器报错后不要傻等监听setOnErrorListener和setOnCompletionListener在里面做自动重连。重连之前记得先release()旧播放器否则会越积越多。content URI 无法播放如果你的 Demo 里还接入了系统文件选择器Android 10 以后拿到的本地视频路径可能是content://开头的 URI不能直接丢给setDataSource(String)用。需要通过ContentResolver.openFileDescriptor()拿到FileDescriptor再调用setDataSource(FileDescriptor)播放。这个坑很多人遇到。H.265 摄像头画面解码不出来现在新款摄像头很多默认 H.265 编码。IjkPlayer 的 FFmpeg 底层有软解支持但 1080P 的 H.265 软解在低端机上会很吃力掉帧严重。要么在摄像头后台把编码改成 H.264要么确认你的 so 库支持硬解 H.265。后者的兼容性参差不齐项目里一般优先建议改编码格式。我实际做过的几个项目里摄像头型号从海康、大华到杂牌都有这套 Demo 改改地址和参数基本都能跑起来。最后再分享一个经验如果摄像头偶尔断流单纯靠手动点击播放救不了用户体验在onError回调里做自动重连间隔两三秒试一次用户感知会好很多。这个机制比任何播放器调优都重要。本文还有配套的精品资源点击获取
返回列表