首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Andorid平台实现高性能低延迟的多路RTSP播放器

  • 25-04-25 15:00
  • 2657
  • 6989
juejin.cn

​在当今的视频监控、流媒体传输等领域,RTSP(Real Time Streaming Protocol)协议被广泛用于音视频数据的实时传输。为了满足多路 RTSP 流的同时播放需求,基于大牛直播SDK开发了一款功能丰富、性能稳定的多路 RTSP 播放器。本文将深入解析该播放器的实现原理、代码架构以及关键功能模块。

一、项目背景与需求

随着视频监控系统的规模不断扩大,用户需要一个能够同时处理多路 RTSP 流的播放器,以实现对多个监控摄像头或流媒体源的集中监控与管理。传统的单路播放器已无法满足此类需求,因此开发一个多路 RTSP 播放器显得尤为必要。

该播放器主要面向以下场景:

  • 视频监控中心 :对多个监控摄像头进行实时监控,要求低延迟、高稳定性。

  • 流媒体服务器测试 :在测试流媒体服务器时,需要模拟多个客户端同时播放 RTSP 流,以评估服务器性能。

  • 多媒体展示系统 :在某些展览、展示场景中,需要在多个屏幕上同时播放不同的 RTSP 流媒体内容。

二、整体架构设计

(一)核心组件

  1. SDK 封装层 :利用大牛直播SDK的SmartMediakit框架提供的 SmartPlayerJniV2 类,通过 JNI 调用原生库实现 RTSP 流的底层处理,包括播放、截图、录像等功能。

  2. 播放器封装类(LibPlayerWrapper) :对 SDK 的功能进行二次封装,提供更简洁易用的接口,管理播放器的生命周期、状态以及与 SDK 的交互逻辑。

  3. UI 层(SmartPlayer) :基于 Android 的 Activity 构建,负责与用户交互,展示播放画面,控制播放器的启动、停止、录像等操作。

(二)架构图

markdown
代码解读
复制代码
UI 层(SmartPlayer) │ ├─ 播放控制(按钮点击事件) ├─ 播放画面展示(SurfaceView) └─ 事件显示(文本信息) LibPlayerWrapper 层 │ ├─ 播放器生命周期管理 ├─ 播放状态控制(播放、暂停、停止) ├─ 录像功能管理 ├─ 截图功能实现 └─ 事件回调处理 SDK 封装层(SmartPlayerJniV2) │ ├─ RTSP 流处理(播放、暂停、停止) ├─ 视频渲染(Surface 设置) ├─ 音频处理 ├─ 录像功能实现 └─ 截图功能实现

三、关键代码解析

(一)播放器初始化

在 SmartPlayer 类的 onCreate 方法中,完成播放器的初始化工作。

scss
代码解读
复制代码
libPlayer = new SmartPlayerJniV2(); context_ = this.getApplicationContext(); initView(); initPlayUrls(); setupSurfaceCallbacks(); createPlayerInstances(); setupButtonListeners();

首先,创建 SmartPlayerJniV2 对象,这是与 SDK 进行交互的入口。接着,初始化 UI 组件,加载播放地址列表,并为 SurfaceView 设置回调函数。然后,创建多个 LibPlayerWrapper 实例,每个实例对应一个播放器实例,用于管理单个 RTSP 流的播放。

(二)播放控制

在 LibPlayerWrapper 类中,startPlayer 方法实现了播放功能。

kotlin
代码解读
复制代码
public boolean startPlayer(boolean is_hardware_decoder, boolean is_enable_hardware_render_mode, boolean is_mute) { if (is_playing()) { Log.e(TAG, "already playing, native_handle:" + get()); return false; } setPlayerParam(is_hardware_decoder, is_enable_hardware_render_mode, is_mute); int ret = lib_player_.SmartPlayerStartPlay(get()); if (ret != OK) { Log.e(TAG, "call StartPlay failed, native_handle:" + get() + ", ret:" + ret); return false; } write_lock_.lock(); try { this.is_playing_ = true; } finally { write_lock_.unlock(); } Log.i(TAG, "call StartPlayer OK, native_handle:" + get()); return true; }

在 startPlayer 方法中,首先检查播放器是否已经在播放状态。然后,通过 setPlayerParam 方法设置播放器参数,如硬件解码、硬件渲染模式和静音等。接着,调用 SDK 提供的 SmartPlayerStartPlay 方法开始播放 RTSP 流。如果播放成功,更新播放器的状态为正在播放。

(三)录像功能

LibPlayerWrapper 类中的 configRecorderParam 方法用于配置录像参数。

csharp
代码解读
复制代码
public boolean configRecorderParam(String rec_dir, int file_max_size, int is_transcode_aac, int is_record_video, int is_record_audio) { if(!check_native_handle()) return false; if (null == rec_dir || rec_dir.isEmpty()) return false; int ret = lib_player_.SmartPlayerCreateFileDirectory(rec_dir); if (ret != 0) { Log.e(TAG, "Create record dir failed, path:" + rec_dir); return false; } if (lib_player_.SmartPlayerSetRecorderDirectory(get(), rec_dir) != 0) { Log.e(TAG, "Set record dir failed , path:" + rec_dir); return false; } if (lib_player_.SmartPlayerSetRecorderFileMaxSize(get(),file_max_size) != 0) { Log.e(TAG, "SmartPlayerSetRecorderFileMaxSize failed."); return false; } lib_player_.SmartPlayerSetRecorderAudioTranscodeAAC(get(), is_transcode_aac); // 更细粒度控制录像的, 一般情况无需调用 lib_player_.SmartPlayerSetRecorderVideo(get(), is_record_video); lib_player_.SmartPlayerSetRecorderAudio(get(), is_record_audio); return true; }

该方法首先检查原生句柄是否有效以及录像目录是否合法。然后,调用 SDK 的相关方法创建录像目录、设置录像文件最大大小、音频转码 AAC 参数以及是否录制视频和音频。通过这些参数的配置,可以灵活地控制录像的各个方面。

startRecorder 方法用于启动录像功能。

csharp
代码解读
复制代码
public boolean startRecorder() { if (is_recording()) { Log.e(TAG, "already recording, native_handle:" + get()); return false; } int ret = lib_player_.SmartPlayerStartRecorder(get()); if (ret != OK) { Log.e(TAG, "call SmartPlayerStartRecorder failed, native_handle:" + get() + ", ret:" + ret); return false; } write_lock_.lock(); try { this.is_recording_ = true; } finally { write_lock_.unlock(); } Log.i(TAG, "call SmartPlayerStartRecorder OK, native_handle:" + get()); return true; }

在启动录像之前,检查是否已经在录像状态。然后,调用 SDK 的 SmartPlayerStartRecorder 方法开始录像,并更新播放器的录像状态。

(四)截图功能

LibPlayerWrapper 类中的 captureImage 方法实现了截图功能。

arduino
代码解读
复制代码
public boolean captureImage(int compress_format, int quality, String file_name, String user_data_string) { if (!check_native_handle()) return false; return OK == lib_player_.CaptureImage(get(), compress_format, quality, file_name, user_data_string); }

该方法通过调用 SDK 的 CaptureImage 方法进行截图操作。参数包括压缩格式(JPEG 或 PNG)、图片质量、文件名和用户自定义字符串。用户可以根据需要选择截图的格式和质量,并指定截图保存的路径和文件名。

(五)事件回调

在 SmartPlayer 类中,实现了 EventListener 接口的 onPlayerEventCallback 方法,用于处理播放器的各种事件回调。

scss
代码解读
复制代码
@Override public void onPlayerEventCallback(long handle, int id, long param1, long param2, String param3, String param4, Object param5) { StringBuilder sb = new StringBuilder(256); sb.append("PlayerHandle: ").append(handle).append(" "); switch (id) { case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STARTED: sb.append("开始.."); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING: sb.append("连接中.."); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED: sb.append("连接失败.."); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED: sb.append("连接成功.."); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED: sb.append("连接断开.."); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP: sb.append("连接播放.."); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO: sb.append("分辨率信息: width: ").append(param1).append(", height: ").append(param2); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED: sb.append("收不到媒体数据,可能是url错误.."); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL: sb.append("切换播放URL.."); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE: sb.append("快照: ").append(param1).append(" 路径: ").append(param3); if (param1 == 0) sb.append("截取快照成功"); else sb.append("截取快照失败"); if (param4 != null && !param4.isEmpty()) sb.append(", user data:").append(param4); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE: sb.append("[record]开始一个新的录像文件 :").append(param3); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED: sb.append("[record]已生成一个录像文件 :").append(param3); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING: sb.append("Start Buffering"); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING: sb.append("Buffering: ").append(param1).append("%"); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING: sb.append("Stop Buffering"); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED: sb.append("download_speed:").append(param1).append("Byte/s, ").append((param1 * 8 / 1000)).append("kbps").append((param1 / 1024)).append("KB/s"); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RTSP_STATUS_CODE: Log.e(TAG, "RTSP error code received, please make sure username/password is correct, error code:" + param1); sb.append("RTSP error code: ").append(param1); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NEED_KEY: Log.e(TAG, "RTMP加密流,请设置播放需要的Key.."); sb.append("RTMP加密流,请设置播放需要的Key.."); break; case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_KEY_ERROR: Log.e(TAG, "RTMP加密流,Key错误,请重新设置.."); sb.append("RTMP加密流,Key错误,请重新设置.."); break; } Log.i(TAG, "onPlayerEventCallback: " + sb.toString()); Message message = new Message(); message.what = PLAYER_EVENT_MSG; message.obj = sb.toString(); handler_.sendMessage(message); }

在该方法中,根据不同类型的事件 ID,构建相应的事件信息字符串,并通过 Handler 将事件信息发送到 UI 线程进行显示。这样用户可以在界面上实时查看播放器的各种状态变化和事件信息。

四、性能优化与注意事项

(一)硬件加速

在播放高清视频流时,开启硬件解码可以显著降低设备的 CPU 负载,提高播放性能。在 LibPlayerWrapper 类的 setPlayerParam 方法中,通过调用 SDK 的相关方法设置硬件解码和硬件渲染模式。

scss
代码解读
复制代码
if (is_hardware_decoder && is_enable_hardware_render_mode) { lib_player_.SmartPlayerSetHWRenderMode(get(), 1); }

(二)低延迟模式

为了满足实时性要求较高的场景,可以启用低延迟模式。在 configurePlayer 方法中设置低延迟模式。

ini
代码解读
复制代码
boolean isLowLatency = true; lib_player_.SmartPlayerSetLowLatencyMode(get(), isLowLatency ? 1 : 0);

(三)资源管理

在播放器的生命周期管理中,合理地分配和释放资源至关重要。在 LibPlayerWrapper 类的 release 方法中,释放播放器占用的资源。

ini
代码解读
复制代码
public void release() { if (empty()) return; if(is_playing()) stopPlayer(); if (is_recording()) stopRecorder(); long handle; write_lock_.lock(); try { handle = this.native_handle_; this.native_handle_ = 0; clear_all_playing_flags(); } finally { write_lock_.unlock(); } if (lib_player_ != null && handle != 0) lib_player_.SmartPlayerClose(handle); event_listener_ = null; }

在释放资源时,先停止播放和录像操作,然后将原生句柄设置为 0,并调用 SDK 的 SmartPlayerClose 方法关闭播放器实例,最后将事件监听器设置为 null,避免内存泄漏。

(四)线程安全

在多线程环境下,对播放器状态和资源的访问需要保证线程安全。在 LibPlayerWrapper 类中,使用 ReentrantReadWriteLock 来保护对播放器状态和原生句柄的访问。

java
代码解读
复制代码
private final ReadWriteLock rw_lock_ = new ReentrantReadWriteLock(true); private final java.util.concurrent.locks.Lock write_lock_ = rw_lock_.writeLock(); private final java.util.concurrent.locks.Lock read_lock_ = rw_lock_.readLock();

在修改播放器状态或原生句柄时,获取写锁;在读取这些变量时,获取读锁,确保线程安全。

五、总结与展望

通过以上基于大牛直播 SDK 的多路 RTSP 播放器的实现与解析,我们深入了解了其架构设计、关键功能模块以及性能优化策略。该播放器具有以下优势:

  • 多路播放能力 :能够同时播放多路 RTSP 流,满足视频监控、流媒体测试等场景的需求。

  • 功能丰富 :支持播放、停止、截图、录像等多种功能,满足不同用户的使用需求。

  • 性能优化 :采用硬件加速、低延迟模式等技术手段,提高播放性能和实时性。

  • 良好的资源管理 :合理管理播放器的生命周期和资源,避免内存泄漏和资源浪费。

在未来的工作中,我们可以进一步扩展该播放器的功能,如支持更多的视频格式、实现自适应 bitrate 播放、优化在弱网络环境下的播放体验等。

​

注:本文转载自juejin.cn的音视频牛哥的文章"https://juejin.cn/post/7496484123247476773"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top