简介面向Android开发者的IjkPlayer播放RTSP/RTMP视频流可运行Demo聚焦实时流媒体播放需求解决系统MediaPlayer对RTSP/RTMP等协议支持不足的问题。项目基于Bilibili开源的IjkPlayer搭建采用Kotlin与Java混合编写集成ijkplayer-java-release.aar及对应so库演示了从初始化、配置数据源、prepareAsync异步准备到播放控制的完整闭环。工程内包含Xml布局资源、Proguard混淆规则、Gradle构建脚本、Properties配置文件及Png图标等可直接导入Android Studio编译运行亦可将核心代码抽取到既有项目中。压缩包共147个文件整体约13.68MBso动态库覆盖不同ABIaar封装播放器核心结构清晰便于定位与裁剪。通过学习可掌握SurfaceView/TextureView渲染、状态回调、错误监听、暂停停止及资源释放等关键操作并理解RTSP与RTMP地址在Android端的接入流程。目前已有980人浏览学习适合需要快速接入实时视频播放的初、中级开发者参考也可作为二次开发的基础模板。1. 项目背景与方案选型为什么盯上 IjkPlayer做Android音视频开发的人基本都会碰到一个绕不开的坎怎么在App里流畅播放RTSP或RTMP流。尤其是这两年摄像头接入、直播拉流、安防大屏这类需求越来越多客户端拿到的往往不是现成的HTTP-FLV或HLS地址而是rtsp://xxx或rtmp://xxx这种带协议的裸流地址。Android自带的MediaPlayer和ExoPlayer虽然对HTTP协议支持不错但碰到RTSP就有点力不从心——ExoPlayer对RTSP的支持直到1.0版本才慢慢成熟RTMP更是基本放弃治疗。这时候B站开源并维护的IjkPlayer就成了一个非常务实的备选方案。我这次做的Demo核心目标就一句话在Android Studio里跑通一个能播放RTSP和RTMP流的可运行工程不搞花里胡哨的UI不堆复杂架构就是把最核心的播放链路打通让大家拿过去能直接用、能改、能跑。为什么选IjkPlayer而不是其他方案我对比过几条路线一是用VLC的libVLC库功能确实强但库体积大初始化慢商用授权也有讲究二是用ExoPlayer扩展RTSP模块需要自己加依赖和配置RTMP依然无解三是自己用FFmpeg底层写解码器那工作量直接起飞。IjkPlayer的好处在于它是基于FFmpeg的封装天然支持RTSP、RTMP、HLS、本地文件等多种协议API设计又高度兼容Android的MediaPlayer老Android开发者几乎零成本上手。虽然项目现在已经停止更新维护最后稳定版本停在0.8.8但在RTSP/RTMP这种偏传统的播放场景里它依然是最稳妥的选择社区踩坑资料也足够多。这个Demo适合谁看我觉得有三类人最合适第一类是刚接触Android音视频的初级开发想找个能跑的工程做参考第二类是做安防、物联网、直播相关项目的工程师需要快速集成RTSP/RTMP播放能力第三类是那些被ExoPlayer折腾得够呛想换个思路的人。如果你只是想播放HTTP-FLV那IjkPlayer不是最优解现在的ExoPlayerDASH或腾讯云超级播放器更合适但如果你手里只有RTSP/RTMP地址那这篇文章应该能帮你省下半天到一天的摸索时间。2. 核心原理与准备工作搞懂RTSP、RTMP和IjkPlayer的三角关系2.1 RTSP和RTMP到底差在哪儿先说RTSP。RTSPReal Time Streaming Protocol是一个网络控制协议它本身不负责传输媒体数据而是负责发起和引导流媒体会话真正的音视频数据是通过RTPReal-time Transport Protocol协议传输的。典型流程是客户端先发一个OPTIONS请求探测服务器能力然后依次发DESCRIBE获取媒体描述SDP格式里面包含了编码格式、分辨率、码率等关键信息再发SETUP建立传输会话最后发PLAY开始拉流。你可以把RTSP理解成看电视时手里的遥控器——换台、音量、暂停这些控制动作走的是RTSP而电视画面本身走的是RTP通道。RTMPReal Time Messaging Protocol则是Adobe推出的流媒体协议基于TCP长连接默认端口1935。它的特点是低延迟、兼容性好早期直播领域几乎被它统治。RTMP传输的不是裸流而是被封装成FLV格式的消息块Chunk通过AMFAction Message Format协议进行握手和命令交互。服务器端通过推流工具如OBS把视频信号推给流媒体服务器如Nginx-RTMP客户端再通过RTMP拉流地址进行播放。用一张表来看它们的核心差异会比较直观对比项RTSPRTMP传输层基于RTP/UDP也可走TCP基于TCP长连接默认端口5541935控制方式独立控制协议OPTIONS/DESCRIBE/SETUP/PLAY内置命令通道FMLE/connect/createStream典型场景安防摄像头、IP Camera、视频监控直播推流、直播拉流、互动直播延迟表现通常在200ms~1s视GOP设置而定通常在1s~3s视缓冲策略而定播放器支持需要支持RTSP/RTP解析需要支持RTMP握手和FLV解封装有一个常见的认知误区需要澄清很多人以为RTSP和RTMP是同一类东西直接互换地址就能播。实际上摄像头厂商给的RTSP地址比如海康威视的格式在浏览器里通常放不了就是因为浏览器原生不支持RTSP协议而IjkPlayer基于FFmpeg做了协议解析才能在Android端直接解码播放。至于RTMP地址浏览器同样是没法直接播放的HTTP-FLV才是浏览器端的常用方案这也是为什么很多在线考场问RTMP地址怎么在浏览器播——本质上需要一个协议转换层。2.2 IjkPlayer的架构与核心模块IjkPlayer的底层核心是FFmpeg它把FFmpeg的解封装和解码能力封装成了一套类似Android MediaPlayer的接口。整个架构分为三层最上层是Java层的IjkMediaPlayer类它继承自MediaPlayer的抽象接口Android开发者熟悉的所有方法——setDataSource、prepareAsync、start、pause、seekTo——在IjkPlayer里全部保留中间层是Native层由C实现的播放器核心负责音视频同步、渲染控制、缓冲策略管理最底层是FFmpeg的各模块包括协议层rtsp、rtmp、http等、解封装层demuxer、解码层decoder。IjkPlayer有几个独门配置项值得关注。以播放RTSP流为例ijkopenssl这个配置项决定是否启用OpenSSL加密如果你的RTSP地址是带鉴权的如rtsp://admin:password192.168.1.64:554/...就需要确认so库中包含了openssl模块rtsp_transport是RTSP传输层协议设置可选tcp或udp默认是udp但实际项目中我通常建议显式设置为tcp后面细说原因probesize和analyzeduration决定探测缓存大小和时长直接影响首屏加载速度对于RTSP这种需要协商的协议来说设置不当容易导致播放器花很长一段时间停在黑屏状态。2.3 运行环境与前置工具准备在动手写代码之前先把环境准备好。这个Demo我用的开发环境如下供参考操作系统Windows 10 / Ubuntu 20.04两个系统都测过Android Studio版本不限建议2021.1以上Android SDKAPI 21~34都行建议minSdk设置21Android 5.0编译工具NDK不是必装项除非你需要自己编译IjkPlayer的so库后面会讲到我在Demo里使用的方式是直接从Maven仓库引入编译好的IjkPlayer依赖省去了最令人头疼的NDK编译过程。如果你不需要定制FFmpeg功能这种方式完全够用。但如果你想深入了解so库的编译流程或者需要裁剪体积那就需要准备NDK r14b以下版本IjkPlayer官方建议克隆ijkplayer的GitHub仓库然后跑编译脚本这个流程比较复杂不是这个Demo的重点我会在后面的注意事项里提一下。3. 快速搭建可运行的Demo工程3.1 新建工程与添加依赖打开Android Studio新建一个Empty Activity项目项目名为IjkPlayerDemo包名随意我这里用com.demo.ijkplayer。语言选Java还是Kotlin都行我这里用Java写核心代码因为IjkPlayer的示例代码和网上资料绝大多数都是Java的直接参考不容易踩坑。工程建好后在app/build.gradle里添加依赖。IjkPlayer的Maven依赖有两个版本要分清tv.danmaku.ijk.media:ijkplayer-java是Java层的播放器APItv.danmaku.ijk.media:ijkplayer-armv7a是ARM平台的so库还有ijkplayer-arm64和ijkplayer-x86等不同CPU架构的包。实际项目中我建议按需引入比如只保留armv7a和arm64可以显著减少APK体积。android { defaultConfig { minSdk 21 targetSdk 33 } } 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 // 如果需要在模拟器上调试再加 x86 架构 // implementation tv.danmaku.ijk.media:ijkplayer-x86:0.8.8 }这里有个细节需要注意0.8.8版本的ijkplayer-java包在Maven仓库里存在但so库的groupId写法是tv.danmaku.ijk.media而不是之前某些教程里写的com.github.bjweishen之类。如果你是从GitHub上找的依赖写法记得核对一下避免依赖下载404。3.2 布局文件一个SurfaceView就够了IjkPlayer的渲染默认依赖TextureView或SurfaceView。这个Demo里我用了TextureView相比SurfaceViewTextureView可以更好支持截图、透明度调节和动画变换在视频播放场景中更灵活。?xml version1.0 encodingutf-8? RelativeLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent TextureView android:idid/video_view android:layout_widthmatch_parent android:layout_heightmatch_parent / ProgressBar android:idid/loading_view android:layout_widthwrap_content android:layout_heightwrap_content android:layout_centerInParenttrue android:visibilitygone / TextView android:idid/status_text android:layout_widthwrap_content android:layout_heightwrap_content android:layout_alignParentBottomtrue android:layout_margin10dp android:text准备播放 android:textColor#FFFFFF android:textSize14sp / EditText android:idid/url_input android:layout_widthmatch_parent android:layout_height48dp android:layout_alignParentToptrue android:hint请输入RTSP/RTMP地址 android:inputTypetextUri android:paddingLeft12dp android:importantForAutofillno / Button android:idid/btn_play android:layout_widthwrap_content android:layout_height48dp android:layout_alignParentToptrue android:layout_alignParentRighttrue android:text播放 / /RelativeLayout这里我特意加了一个EditText用于输入流地址方便测试不同的RTSP/RTMP源。实际项目中你可能需要从扫码、接口或配置中心获取地址这个输入框只是一个调试便利功能正式环境可以去掉。3.3 Java代码核心播放链路写出来Java端的核心代码不复杂关键在于正确初始化IjkPlayer并处理TextureView的Surface生命周期。public class MainActivity extends AppCompatActivity { private IjkMediaPlayer ijkMediaPlayer; private TextureView textureView; private ProgressBar loadingView; private TextView statusText; private EditText urlInput; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textureView findViewById(R.id.video_view); loadingView findViewById(R.id.loading_view); statusText findViewById(R.id.status_text); urlInput findViewById(R.id.url_input); // 测试地址可按实际情况替换 urlInput.setText(rtsp://admin:password192.168.1.64:554/Streaming/Channels/101); findViewById(R.id.btn_play).setOnClickListener(v - playVideo(urlInput.getText().toString())); } private void playVideo(String url) { if (TextUtils.isEmpty(url)) { statusText.setText(地址不能为空); return; } releasePlayer(); textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { startPlay(url, new Surface(surface)); } Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }); if (textureView.isAvailable()) { startPlay(url, new Surface(textureView.getSurfaceTexture())); } } private void startPlay(String url, Surface surface) { try { ijkMediaPlayer new IjkMediaPlayer(); // 开启硬解码部分机型有问题时可改为软解 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec, 1); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec-auto-rotate, 1); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec-handle-resolution-change, 1); // RTSP传输协议建议tcp稳定但延迟稍高udp延迟低但容易丢包 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, rtsp_transport, tcp); // 设置探测时间和缓冲大小降低首屏延迟 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, probesize, 1024 * 1024); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, analyzeduration, 500 * 1000); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, packet-buffering, 0); ijkMediaPlayer.setDataSource(url); ijkMediaPlayer.setSurface(surface); ijkMediaPlayer.prepareAsync(); ijkMediaPlayer.setOnPreparedListener(mp - { loadingView.setVisibility(View.GONE); statusText.setText(播放中); mp.start(); }); ijkMediaPlayer.setOnErrorListener((mp, what, extra) - { loadingView.setVisibility(View.GONE); statusText.setText(播放错误: what what , extra extra); return true; }); ijkMediaPlayer.setOnInfoListener((mp, what, extra) - { if (what IjkMediaPlayer.MEDIA_INFO_BUFFERING_START) { loadingView.setVisibility(View.VISIBLE); statusText.setText(缓冲中); } else if (what IjkMediaPlayer.MEDIA_INFO_BUFFERING_END) { loadingView.setVisibility(View.GONE); statusText.setText(播放中); } return true; }); ijkMediaPlayer.prepareAsync(); } catch (Exception e) { statusText.setText(初始化失败: e.getMessage()); } } private void releasePlayer() { if (ijkMediaPlayer ! null) { ijkMediaPlayer.stop(); ijkMediaPlayer.release(); ijkMediaPlayer null; } } Override protected void onPause() { super.onPause(); if (ijkMediaPlayer ! null) { ijkMediaPlayer.pause(); } } Override protected void onResume() { super.onResume(); if (ijkMediaPlayer ! null) { ijkMediaPlayer.start(); } } Override protected void onDestroy() { super.onDestroy(); releasePlayer(); } }这里有几个关键点必须强调。第一prepareAsync()不能重复调用如果播放完一个流后再播另一个必须先reset()或release()再重新new一个实例我在playVideo方法里先调了releasePlayer()就是为了避免这个坑。第二setSurface需要在prepareAsync之前调用否则画面可能无法渲染。第三硬解码配置项mediacodec在某些低端机或定制系统上会出兼容性问题画面绿屏、花屏如果遇到这种问题把硬解选项关掉即可后面在问题排查部分我会单独说。还需要在AndroidManifest.xml里添加网络权限uses-permission android:nameandroid.permission.INTERNET /如果你的RTSP地址是明文带账号密码的比如rtsp://admin:123456192.168.1.64/...URL中的账号密码会被IjkPlayer解析后以明文形式传输这在公网环境下有泄露风险建议仅在局域网或内网测试时使用这种地址。3.4 可用的测试流地址参考没有摄像头设备验证怎么办我这里提供几个公开可用的测试源地址。需要说明的是公开测试源随时可能失效如果连不上只能换其他地址测试这是所有公开源的通病。类型地址示例说明RTSP本地摄像头模拟rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4Wowza官方测试源延迟较高但稳定RTSP通用测试rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mp4另一组Wowza测试节点RTMP直播流rtmp://live.hkstv.hk.lxdns.com/live/hks香港卫视的测试直播流经常会变RTMP测试流rtmp://ns8.indexforce.com/home/mystream不稳定需要多试我自己实测下来第一组Wowza的RTSP地址在连通性上表现比较稳但因为是跨国节点延迟较高国内网络环境下首屏可能需要2~5秒。如果你手头有海康威视、大华、宇视等品牌摄像头直接用公司内网的RTSP地址测试效果会好得多局域网内拉流延迟基本在500ms以内。4. 传输协议与播放参数调优让RTSP/RTMP播放更流畅4.1 RTSP的TCP和UDP之争到底选哪个这个问题几乎每个做RTSP播放的人都会纠结。官方默认的rtsp_transport参数是UDPUDP的优点是没有TCP的拥塞控制在网络状况良好的局域网里延迟可以做到很低但缺点是丢包不重传一旦网络抖动画面就会出现马赛克、卡顿甚至花屏。TCP则相反虽然因为有握手和重传机制导致延迟略高但数据完整性有保障在跨网段、跨运营商或无线网络场景下表现更稳定。我的实践经验是优先用TCP除非你对延迟有极致要求并且网络环境非常可靠。以安防监控为例绝大多数摄像头都部署在Wi-Fi或复杂网络环境中UDP丢包的概率远比想象中高。我曾经在一个项目中用UDP拉取海康摄像头画面客户反馈画面每隔几分钟就花屏一次后来把rtsp_transport改成TCP就彻底解决了。用TCP的代价是延迟会高出100~200ms但在绝大多数监控场景中这个延迟完全可以接受。还有一个小技巧如果你需要同时拉取多路摄像头画面每路连接都配置TCP会导致摄像头连接数增加。部分摄像头设备有最大连接数限制比如海康某些型号限制在6路左右这时候要么降低码率、要么改用UDP、要么走摄像头厂商的SDK这是方案层面的取舍不单是播放器能解决的。4.2 缓冲参数与首屏延迟的平衡术IjkPlayer有几个参数直接影响首屏加载速度和播放流畅度。第一个是probesize它表示播放器在开始解码前需要探测的最大字节数默认是5000000约5MB对于RTSP这种流媒体来说这么大的探测值会导致首屏延迟非常明显。我把它调成了1024 * 10241MB首屏时间能快不少。第二个是analyzeduration单位微秒默认是50000005秒也就是说播放器最多花5秒时间分析媒体信息。对于已知编码格式的RTSP流可以适当调小这个值我设为500 * 1000500ms首屏基本能在1秒内出画面。第三个参数是packet-buffering默认情况下IjkPlayer会缓冲一定数量的数据包再开始播放好处是播放过程更平滑坏处是延迟增加、且实时性变差播放的可能不是最新画面而是几秒前的画面。如果需要低延迟的实时监控场景把packet-buffering设为0可以显著降低延迟但缺点是网络波动时更容易卡顿。这里还需要注意sync-av-start和start-on-prepared这两个参数。sync-av-start控制是否音视频同步启动start-on-prepared控制prepare完成后是否立即播放。IjkPlayer的典型行为是prepare完成后调用start就开始播放不需要额外设置。如果遇到有声音没画面或有画面没声音的情况多半是音视频同步问题可以尝试调整framedrop参数设置为1表示允许丢帧保证音频节奏优先。4.3 直播模式下的延迟优化配置如果你的场景是直播拉流比如从Nginx-RTMP服务器拉取OBS推的流延迟就是头号痛点。RTMP协议本身延迟一般在1~3秒但我实测发现如果不做任何优化IjkPlayer播放RTMP甚至可能延迟到5秒以上。原因在于IjkPlayer默认采用的缓冲策略是按时间窗口缓冲的网络状况良好时它也会攒够一定量的数据才播放。针对直播场景我常用的优化参数组合如下ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, packet-buffering, 0); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, fflags, nobuffer); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, max-buffer-size, 4096); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, framedrop, 1);其中fflagsnobuffer是FFmpeg层面禁用缓冲max-buffer-size限制最大缓冲字节数这两项配合packet-buffering0能显著降低延迟。不过这种组合方案在弱网环境下会频繁卡顿所以不能盲目照搬需要根据自己的网络状况和业务容忍度来取舍。如果你的业务对延迟要求不高比如点播回放、视频文件播放保留默认缓冲策略反而体验更好。5. 常见问题与排查技巧实录5.1 黑屏但有声音或者直接不播放这是IjkPlayer播放RTSP时最常遇到的问题几乎每个新手都会栽一次。造成黑屏的原因通常有以下几种原因一Surface未正确绑定。IjkPlayer的视频渲染需要Surface如果TextureView的Surface还没准备好就调了setSurface画面就无法显示。解决方法是确保在onSurfaceTextureAvailable回调后再创建播放器并设置Surface而不是在onCreate里直接创建。原因二硬解码兼容性问题。部分设备尤其是一些国产定制ROM的MediaCodec实现有bug对H.264或H.265的高帧率、高分辨率视频硬解会出现黑屏或马赛克。排查方法是把mediacodec选项设为0强制软解如果恢复正常说明是硬解兼容问题。软解的代价是CPU占用升高、发热增加但在兼容性优先的场景下也只能接受。原因三编码格式不支持。IjkPlayer自带的FFmpeg虽然功能完整但某些特殊编码如MJPEG、MPEG4或私有编码可能无法解码。这种情况一般会在logcat里看到Could not open codec之类的报错排查时重点关注ijkplayer标签下的日志信息。5.2 RTSP地址带鉴权播放不了很多摄像头尤其是海康、大华的RTSP地址格式是rtsp://username:passwordip:port/...。如果你在浏览器或VLC里都能成功播放但在IjkPlayer里却报错大概率是URL中的特殊字符比如、#、空格没有被正确编码。正确的做法是对密码部分做URL编码例如String username admin; String password password; String encodedPwd Uri.encode(password); String url rtsp:// username : encodedPwd 192.168.1.64:554/Streaming/Channels/101;另外有些摄像头开启了RTSP加密RTSP over TLS这类地址在IjkPlayer里默认不支持需要在编译so库时启用openssl并且播放器端配置证书相关参数。这个操作比较进阶一般项目里遇到的可能性不高但如果碰到rtsp://但实际是加密流的情况排查起来会非常痛苦。5.3 播放过程中画面卡顿、延迟越来越高直播场景下经常出现延迟越播越大的现象这其实是播放器持续缓冲导致的。发生这种情况时我一般会按这个顺序排查先看网络质量和摄像头码率设置如果摄像头码率设得过高比如2K分辨率还推8Mbps码率在普通Wi-Fi环境下很容易卡顿然后检查播放器是否启用了packet-buffering如果启用了尝试关闭最后检查是否有内存泄漏长时间播放后内存飙升导致GC频繁IjkPlayer在长时间运行后确实需要关注内存回收问题。如果画面出现花屏或马赛克优先怀疑UDP丢包改TCP基本能解决。如果画面出现周期性卡顿比如每10秒卡一下大概率是摄像头GOP关键帧间隔设置过大或网络拥塞可以尝试调整摄像头的关键帧间隔参数一般建议设为1~2秒。5.4 真机安装后闪退或so库加载失败如果你用的是自己编译的IjkPlayer so库而不是Maven依赖很容易遇到UnsatisfiedLinkError或libijkffmpeg.so is not found的错误。这种问题通常是CPU架构不匹配导致的比如在64位手机arm64-v8a上安装了只含armv7a so库的APK系统会尝试加载armv7a的库但Android 8.0以上系统对32位库的限制越来越多更容易出问题。解决方案有三种一是引入全部架构的依赖armv7a、arm64、x86让系统自动选择兼容的架构二是检查APK内libs目录的so库是否完整三是看是否有第三方SDK内置了不同版本的FFmpeg导致so文件冲突。最后一种情况排查起来最麻烦我曾经遇到过项目里同时集成了IjkPlayer和一个OCR库两个库各自带了一份不同版本的FFmpeg导致运行时崩溃最后只能通过exclude排除冲突依赖才解决。5.5 常见问题速查表问题现象可能原因排查/解决方案黑屏但有声音Surface绑定时机不对/硬解兼容性问题确保onSurfaceTextureAvailable后再播放关掉mediacodec硬解prepare后一直缓冲网络慢/摄像头码率过高/缓冲参数不合理检查网络降低码率调整probesize和analyzeduration播放过程中花屏UDP丢包/网络抖动改用rtsp_transporttcp延迟越来越大packet-buffering开启/缓冲策略积累关闭packet-buffering设置fflagsnobuffer部分地址能播放部分不能编码格式不支持/地址格式错误看logcat报错检查URL编码确认编码格式安装后闪退so库架构不匹配/依赖冲突引入全架构依赖检查so库完整性排除冲突依赖有画面无声音音频解码失败/声道配置异常检查音频编码尝试设置audiotrack参数6. 扩展思考这个Demo还能往哪些方向走6.1 多路播放与画面拼接单路播放跑通之后很多人第一个想法就是做多画面拼接墙比如4路、9路摄像头同时显示。Android端的IjkPlayer支持创建多个播放器实例但受限于移动设备的解码能力和内存同时硬解4路1080P已经是极限再往上就会明显发热和掉帧。实际项目中更可行的方法是在服务器端做视频合成或转码再将合成后的单路流推给Android端播放。这涉及服务器转码设计不在Demo范围内但可以作为方案储备。6.2 播放器UI与交互封装这个Demo的UI刻意保持极简实际项目中需要封装控制条播放/暂停、进度拖动、全屏切换、手势调节音量/亮度、手势控制拖动等能力。IjkPlayer的进度控制方法seekTo是直接可用的但对于RTSP实时流很多摄像头设备并不支持seek操作开发时需要对可拖动和不可拖动的流做差异化处理。还有一点全屏切换时需要动态调整TextureView的尺寸和Surface的宽高比避免画面拉伸变形。6.3 低延迟方案的更多选择虽然IjkPlayer在这个Demo里表现不错但如果你对延迟极其敏感比如远程操控、语音对讲场景RTSP/RTMP这类传统的推拉流协议可能已经不适合了。近年来WebRTC在低延迟领域越来越流行通过WebRTC可以实现端到端500ms以内的低延迟传输。不过WebRTC在Android端的集成复杂度比IjkPlayer高出不少需要处理信令服务器、STUN/TURN穿透、音视频引擎对接等不适合作为入门Demo的一部分。我的建议是先把IjkPlayer这个扎实的思路吃透再根据实际业务需求决定是否升级方案。最后再分享一个实际项目中的细节IjkPlayer的日志输出默认非常完整调试时务必在logcat里过滤ijkplayer这个tag它会输出FFmpeg上报的详细错误信息比如Could not find codec parameters、Failed to open RTSP connection这些信息在排查问题时远比播放器的顶层错误码更有价值。我经常看到有人对着Error -10000这种通用错误码一头雾水其实真相就在日志里只是没人教他们怎么去看。希望这篇Demo记录能让你少走几步弯路。本文还有配套的精品资源点击获取