DJI SDK 5.1.7 Android主通道RTMP推流致APP崩溃问题排查
DJI SDK 5.1.7 Android应用RTMP推流几分钟后崩溃问题
适配DJI Mini 3,运行在三星Galaxy S22设备上,基于SDK 5.1.7的示例代码开发。将视频通过主通道推流至云端RTMP服务器几分钟后APP崩溃,相关代码和崩溃日志如下:
HomeFragment.onCreateView相关代码
// 流源更新时触发的监听器 MediaDataCenter.getInstance().getVideoStreamManager().addStreamSourcesListener (sources -> getActivity().runOnUiThread(() -> updateFPVWidgetSource(sources))); primaryFpvWidget.setOnFPVStreamSourceListener((devicePosition, lensType) -> { cameraSourceProcessor.onNext(new HomeFragment.CameraSource(devicePosition, lensType)); }); IVideoChannel primaryChannel = MediaDataCenter.getInstance().getVideoStreamManager().getAvailableVideoChannel(VideoChannelType.PRIMARY_STREAM_CHANNEL); if (primaryChannel != null) { primaryChannelStateListener = (from, to) -> { StreamSource primaryStreamSource = primaryChannel.getStreamSource(); if (VideoChannelState.ON == to && primaryStreamSource != null) { getActivity().runOnUiThread(() -> primaryFpvWidget.updateVideoSource(primaryStreamSource, VideoChannelType.PRIMARY_STREAM_CHANNEL)); } }; primaryChannel.addVideoChannelStateChangeListener(primaryChannelStateListener);
独立线程启动推流的代码
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager(); LiveStreamSettings.Builder lssBuilder = new LiveStreamSettings.Builder(); RtmpSettings.Builder b = new RtmpSettings.Builder(); b.setUrl(liveURL); RtmpSettings rtmp = b.build(); lssBuilder.setRtmpSettings(rtmp); lssBuilder.setLiveStreamType(LiveStreamType.RTMP); LiveStreamSettings lss = lssBuilder.build(); liveStreamManager.setLiveStreamSettings(lss); // 本地预览用主通道,必须设置为PRIMARY否则看不到视频 liveStreamManager.setVideoChannelType(VideoChannelType.PRIMARY_STREAM_CHANNEL); liveStreamManager.setLiveStreamQuality(StreamQuality.SD); // 尝试降低画质 //liveStreamManager.setLiveVideoBitrate(2048); // 低比特率 liveStreamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO); liveStreamManager.startStream(new CommonCallbacks.CompletionCallback() {...}
崩溃日志
2023-11-03 14:42:44.584 14829-14829/? A/DEBUG: Cmdline: com.tbolt.main 2023-11-03 14:42:44.584 14829-14829/? A/DEBUG: pid: 12914, tid: 12914, name: com.tbolt.main >>> com.tbolt.main <<< 2023-11-03 14:42:44.589 12914-12988/com.tbolt.main E/fdsan: failed to exchange ownership of file descriptor: fd 297 is owned by unique_fd 0x6e6ca9d75c, was expected to be unowned 2023-11-03 14:42:44.620 12914-14680/com.tbolt.main E/co_b: (co_a:86) checkFrame() lastId = 18150 id=18156 2023-11-03 14:42:44.776 12914-12988/com.tbolt.main E/fdsan: attempted to close file descriptor 297, expected to be owned by unique_fd 0x6e6ca9dd9c, actually owned by unique_fd 0x6e6ca9d75c 2023-11-03 14:42:44.963 14834-14834/? A/DEBUG: Cmdline: com.tbolt.main 2023-11-03 14:42:44.963 14834-14834/? A/DEBUG: pid: 12914, tid: 12914, name: com.tbolt.main >>> com.tbolt.main <<< 2023-11-03 14:42:44.974 12914-14824/com.tbolt.main E/fdsan: failed to exchange ownership of file descriptor: fd 297 is owned by unique_fd 0x6e6ca9d75c, was expected to be unowned 2023-11-03 14:42:45.081 12914-14687/com.tbolt.main W/DJIV5/co_c@1a50a50: (co_b:99) onOutputFrame() frame index:18161 2023-11-03 14:42:45.161 12914-14688/com.tbolt.main E/co_b: (co_a:104) checkFrame() len=6840 checkLen=11802 seiOffset:6854 2023-11-03 14:42:45.167 12914-12985/com.tbolt.main A/libc: Fatal signal 11 (SIGSEGV), code -6 (SI_TKILL) in tid 12985 (com.tbolt.main), pid 12914 (com.tbolt.main) 2023-11-03 14:42:45.201 12914-14685/com.tbolt.main E/co_b: (co_a:86) checkFrame() lastId = 18163 id=18166 2023-11-03 14:42:45.204 12914-12988/com.tbolt.main E/fdsan: attempted to close file descriptor 297, expected to be owned by unique_fd 0x6e6ca9abdc, actually owned by unique_fd 0x6e6ca9d75c 2023-11-03 14:42:45.332 14840-14840/? A/DEBUG: Cmdline: com.tbolt.main 2023-11-03 14:42:45.332 14840-14840/? A/DEBUG: pid: 12914, tid: 12914, name: com.tbolt.main >>> com.tbolt.main <<< 2023-11-03 14:42:45.682 14846-14846/? A/DEBUG: Cmdline: com.tbolt.main 2023-11-03 14:42:45.682 14846-14846/? A/DEBUG: pid: 12914, tid: 12914, name: com.tbolt.main >>> com.tbolt.main <<< 2023-11-03 14:42:45.710 12914-14680/com.tbolt.main E/co_b: (co_a:86) checkFrame() lastId = 18175 id=18178 2023-11-03 14:42:46.049 14852-14852/? A/DEBUG: Cmdline: com.tbolt.main 2023-11-03 14:42:46.049 14852-14852/? A/DEBUG: pid: 12914, tid: 12914, name: com.tbolt.main >>> com.tbolt.main <<< 2023-11-03 14:42:46.368 12914-14687/com.tbolt.main W/DJIV5/co_c@1a50a50: (co_b:99) onOutputFrame() frame index:18194 2023-11-03 14:42:46.429 14859-14859/? A/DEBUG: Cmdline: com.tbolt.main 2023-11-03 14:42:46.429 14859-14859/? A/DEBUG: pid: 12914, tid: 12914, name: com.tbolt.main >>> com.tbolt.main <<< 2023-11-03 14:42:46.812 14864-14864/? A/DEBUG: Cmdline: com.tbolt.main 2023-11-03 14:42:46.812 14864-14864/? A/DEBUG: pid: 12914, tid: 12985, name: com.tbolt.main >>> com.tbolt.main <<< 2023-11-03 14:42:46.856 2299-14876/? W/ActivityManager: crash : com.tbolt.main,10274 2023-11-03 14:42:46.856 2299-14876/? W/ActivityTaskManager: Force finishing activity com.tbolt.main/.MainActivity 2023-11-03 14:42:46.985 2299-5904/? W/InputManager-JNI: Input channel object 'bebdddd com.tbolt.main/com.tbolt.main.MainActivity (client)' was disposed without first being removed with the input manager!
问题:请问我哪里操作出错了?
问题分析与解决方案
从崩溃日志来看,核心是文件描述符所有权冲突,伴随视频帧校验失败,最终触发SIGSEGV崩溃。结合代码和SDK特性,修复方向如下:
1. 主通道资源冲突
你将PRIMARY_STREAM_CHANNEL同时用于本地预览和推流,部分机型上DJI SDK主通道无法同时承载两路视频消费(本地渲染+推流编码),导致底层资源竞争。
修复:改用次要通道推流,主通道仅用于预览:
liveStreamManager.setVideoChannelType(VideoChannelType.SECONDARY_STREAM_CHANNEL);
同时参照主通道逻辑,初始化并监听次要通道的状态,确保推流前通道已就绪。
2. 文件描述符泄漏/重复操作
日志中的fdsan错误表明文件描述符被重复释放或所有权混乱,多因监听器和资源未及时清理导致。
修复:
- 在Fragment销毁时移除所有监听器:
@Override public void onDestroyView() { super.onDestroyView(); if (primaryChannel != null && primaryChannelStateListener != null) { primaryChannel.removeVideoChannelStateChangeListener(primaryChannelStateListener); } MediaDataCenter.getInstance().getVideoStreamManager().removeStreamSourcesListener(/* 对应的监听器实例 */); }
- 停止推流时调用
liveStreamManager.stopStream(),确保资源释放。
3. 视频帧校验异常
checkFrame()的帧长度、ID跳变错误,与比特率设置或通道资源不足有关。
修复:
- 关闭比特率自动模式,手动设置固定值:
liveStreamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.MANUAL); liveStreamManager.setLiveVideoBitrate(1024); // 匹配SD画质的合理值,避免编码压力过大
- 确保视频通道状态稳定为
ON后再启动推流,不要在连接初期仓促触发。
4. 线程安全问题
推流初始化操作跨线程执行可能引发资源竞争,SDK部分操作需在主线程完成。
修复:将推流的配置和启动逻辑移至主线程:
getActivity().runOnUiThread(() -> { // 放置推流初始化和startStream的代码 });
内容的提问来源于stack exchange,提问作者Lillian
相关产品推荐
相关产品推荐

