admin管理员组文章数量:1028916
Android平台RTSP
前言
在移动直播、视频监控等场景中,RTSP(Real Time Streaming Protocol)和 RTMP(Real Time Messaging Protocol)是两种常见的流媒体传输协议。它们能够提供实时、低延迟的音视频传输,但实现高效的播放功能具有一定技术门槛。大牛直播SDK作为行业内备受认可的解决方案,提供了功能强大、性能卓越的 RTSP/RTMP 播放模块。本文将基于大牛直播 SDK,详细讲解如何在 Android 平台开发一个高效的 RTSP|RTMP 播放器。
SDK 集成准备
环境配置
- 系统支持:确保目标设备运行 Android 5.1 及以上版本,支持的 CPU 架构包括 armv7、arm64、x86 和 x86_64。
- 依赖集成:
- 将
Smartavengine.jar
添加至项目依赖。 - 拷贝对应架构的
libSmartPlayer.so
文件至jniLibs
文件夹。 - 在
AndroidManifest.xml
中声明必要权限:
- 将
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
- SO 库加载:在代码中通过
System.loadLibrary("SmartPlayer");
加载原生库。
核心功能实现
Window平台采集毫秒计数器窗口,然后编码打包注入到轻量级RTSP服务,对外提供拉流的RTSP URL,然后Android平台同时播放4路RTSP流,整体延迟如下:
功能支持
- 音频:AAC/Speex(RTMP)/PCMA/PCMU;
- 视频:H.264、H.265;
- 播放协议:RTSP|RTMP;
- 支持纯音频、纯视频、音视频播放;
- 支持多实例播放;
- 支持软解码,特定机型硬解码;
- 支持RTSP TCP、UDP模式设置;
- 支持RTSP TCP、UDP模式自动切换;
- 支持RTSP超时时间设置,单位:秒;
- 支持buffer时间设置,单位:毫秒;
- 支持超低延迟模式;
- 支持断网自动重连、视频追赶,支持buffer状态等回调;
- 支持视频view实时旋转(0° 90° 180° 270°);
- 支持视频view水平反转、垂直反转;
- 支持Surfaceview/OpenGL ES/TextureView绘制;
- 支持视频画面填充模式设置;
- 音频支持AudioTrack、OpenSL ES模式;
- 支持jpeg、png实时截图;
- 支持实时音量调节;
- 支持解码前音视频数据回调;
- 支持解码后YUV/RGB数据回调;
- 支持Enhanced RTMP;
- 支持扩展录像功能;
- 支持Android 5.1及以上版本。
播放器初始化与播放控制
代码语言:java复制SmartPlayerJniV2 libPlayer = new SmartPlayerJniV2();
long playerHandle = libPlayer.SmartPlayerOpen(context);
if (playerHandle == 0) {
Log.e("PlayerDemo", "Failed to initialize player");
return;
}
// 设置播放参数
libPlayer.SmartPlayerSetBuffer(playerHandle, 200); // 设置缓冲时间 200ms
libPlayer.SmartPlayerSetUrl(playerHandle, "rtmp://example/live/stream");
// 设置 SurfaceView 用于视频渲染
libPlayer.SmartPlayerSetSurface(playerHandle, surfaceView);
// 开始播放
int result = libPlayer.SmartPlayerStartPlay(playerHandle);
if (result != 0) {
Log.e("PlayerDemo", "Failed to start playback");
}
事件回调处理
通过实现 NTSmartEventCallbackV2
接口,可以接收播放器状态更新等事件:
class EventHandeV2 implements NTSmartEventCallbackV2 {
@Override
public void onNTSmartEventCallbackV2(long handle, int id, long param1,
long param2, String param3, String param4, Object param5) {
//Log.i(TAG, "EventHandeV2: handle=" + handle + " id:" + id);
String player_event = "";
switch (id) {
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:
player_event = "开始..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:
player_event = "连接中..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:
player_event = "连接失败..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:
player_event = "连接成功..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:
player_event = "连接断开..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP:
player_event = "停止播放..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:
player_event = "分辨率信息: width: " + param1 + ", height: " + param2;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:
player_event = "收不到媒体数据,可能是url错误..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:
player_event = "切换播放URL..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:
player_event = "快照: " + param1 + " 路径:" + param3;
if (param1 == 0)
player_event = player_event + ", 截取快照成功";
else
player_event = player_event + ", 截取快照失败";
if (param4 != null && !param4.isEmpty())
player_event += (", user data:" + param4);
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:
player_event = "[record]开始一个新的录像文件 : " + param3;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:
player_event = "[record]已生成一个录像文件 : " + param3;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:
Log.i(TAG, "Start Buffering");
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:
Log.i(TAG, "Buffering:" + param1 + "%");
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:
Log.i(TAG, "Stop Buffering");
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:
player_event = "download_speed:" + param1 + "Byte/s" + ", "
+ (param1 * 8 / 1000) + "kbps" + ", " + (param1 / 1024)
+ "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);
player_event = "RTSP error code:" + param1;
break;
}
if (player_event.length() > 0) {
Log.i(TAG, player_event);
Message message = new Message();
message.what = PLAYER_EVENT_MSG;
message.obj = player_event;
handler.sendMessage(message);
}
}
}
设置回调:
代码语言:java复制libPlayer.SetSmartPlayerEventCallbackV2(playerHandle, new EventCallback());
录像功能集成
代码语言:java复制@SuppressLint("NewApi")
void ConfigRecorderFuntion() {
if (libPlayer != null) {
int is_rec_trans_code = 1;
libPlayer.SmartPlayerSetRecorderAudioTranscodeAAC(playerHandle, is_rec_trans_code);
if (recDir != null && !recDir.isEmpty()) {
int ret = libPlayer.SmartPlayerCreateFileDirectory(recDir);
if (0 == ret) {
if (0 != libPlayer.SmartPlayerSetRecorderDirectory(
playerHandle, recDir)) {
Log.e(TAG, "Set recoder dir failed , path:" + recDir);
return;
}
if (0 != libPlayer.SmartPlayerSetRecorderFileMaxSize(
playerHandle, 200)) {
Log.e(TAG,
"SmartPublisherSetRecorderFileMaxSize failed.");
return;
}
} else {
Log.e(TAG, "Create recorder dir failed, path:" + recDir);
}
}
}
}
实时截图功能
代码语言:java复制btnCaptureImage.setOnClickListener(new Button.OnClickListener() {
@SuppressLint("SimpleDateFormat")
public void onClick(View v) {
if (0 == playerHandle)
return;
if (null == capture_image_date_format_)
capture_image_date_format_ = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS");
String timestamp = capture_image_date_format_.format(new Date());
String imageFileName = timestamp;
String image_path = imageSavePath + "/" + imageFileName;
int quality;
boolean is_jpeg = true;
if (is_jpeg) {
image_path += ".jpeg";
quality = 100;
}
else {
image_path += ".png";
quality = 100;
}
int capture_ret = libPlayer.CaptureImage(playerHandle,is_jpeg?0:1, quality, image_path, "test cix");
Log.i(TAG, "capture image ret:" + capture_ret + ", file:" + image_path);
}
});
视频画面控制
代码语言:java复制// 水平翻转
libPlayer.SmartPlayerSetFlipHorizontal(playerHandle, 1);
// 垂直翻转
libPlayer.SmartPlayerSetFlipVertical(playerHandle, 1);
// 旋转 90 度
libPlayer.SmartPlayerSetRotation(playerHandle, 90);
性能优化与最佳实践
- 硬解码优先:在支持的设备上优先启用硬解码(H.264/H.265),可显著降低 CPU 负载。
- 缓冲策略调整:根据网络条件动态调整缓冲区大小,平衡延迟与流畅度。
- 多实例管理:通过封装播放器实例,支持多路视频同时播放。
/*
* LibPlayerWrapper.java
* Created by daniusdk
* WeChat: xinsheng120
*/
package com.daniulive.smartplayer;
import android.content.Context;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.View;
import com.eventhandle.NTSmartEventCallbackV2;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LibPlayerWrapper {
private static String TAG = "NTLogLibPlayerW";
private static final int OK = 0;
private WeakReference<Context> context_;
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();
private SmartPlayerJniV2 lib_player_;
private volatile long native_handle_;
private View surface_view_;
private volatile boolean is_playing_;
private volatile boolean is_recording_;
private EventListener event_listener_;
public LibPlayerWrapper(SmartPlayerJniV2 lib_player, Context context, EventListener listener) {
if (!empty())
throw new IllegalStateException("it is not empty");
if (null == lib_player)
throw new NullPointerException("lib_player is null");
this.lib_player_ = lib_player;
if (context != null)
this.context_ = new WeakReference<>(context);
this.event_listener_ = listener;
}
private void clear_all_playing_flags() {
this.is_playing_ = false;
this.is_recording_ = false;
}
public void set(long handle) {
if (!empty())
throw new IllegalStateException("it is not empty");
write_lock_.lock();
try {
clear_all_playing_flags();
this.native_handle_ = handle;
} finally {
write_lock_.unlock();
}
Log.i(TAG, "set native_handle:" + handle);
}
@Override
protected void finalize() throws Throwable {
try {
if (check_native_handle()) {
if(is_playing()) {
lib_player_.SmartPlayerStopPlay(get());
this.is_playing_ = false;
}
if(is_recording()) {
lib_player_.SmartPlayerStopRecorder(get());
this.is_recording_ = false;
}
lib_player_.SmartPlayerClose(this.native_handle_);
Log.i(TAG, "finalize close handle:" + this.native_handle_);
this.native_handle_ = 0;
}
}catch (Exception e) {
}
super.finalize();
}
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;
}
public boolean try_release() {
if (empty())
return false;
if (is_player_running()) {
Log.i(TAG, "try_release it is running, native_handle:" + get());
return false;
}
long handle;
write_lock_.lock();
try {
if (is_player_running())
return false;
handle = this.native_handle_;
this.native_handle_ = 0;
} finally {
write_lock_.unlock();
}
if (lib_player_ != null && handle != 0)
lib_player_.SmartPlayerClose(handle);
return true;
}
public final boolean empty() { return 0 == this.native_handle_; }
public final long get() { return this.native_handle_; }
public View get_view() {return this.surface_view_;}
public final boolean check_native_handle() {
return this.lib_player_ != null && this.native_handle_ != 0;
}
public final boolean is_playing() { return is_playing_; }
public final boolean is_recording() { return is_recording_; }
public final boolean is_player_running() { return is_playing_ || is_recording_; }
private boolean isValidRtspOrRtmpUrl(String url) {
if (url == null || url.isEmpty()) {
return false;
}
return url.trim().startsWith("rtsp://") || url.startsWith("rtmp://");
}
private EventListener getListener() {
return this.event_listener_;
}
private Context application_context() {
return context_ == null ? null : context_.get();
}
public boolean initialize(String playback_url, int play_buffer, int is_using_tcp) {
if (check_native_handle())
return true;
if(!isValidRtspOrRtmpUrl(playback_url))
return false;
long handle = lib_player_.SmartPlayerOpen(application_context());
if (0==handle) {
Log.e(TAG, "sdk open failed!");
return false;
}
set(handle);
configurePlayer(playback_url, play_buffer, is_using_tcp);
return true;
}
private void configurePlayer(String playback_url, int buffer, int is_using_tcp) {
lib_player_.SetSmartPlayerEventCallbackV2(get(), new EventHandleV2());
lib_player_.SmartPlayerSetBuffer(get(), buffer);
// set report download speed(默认2秒一次回调 用户可自行调整report间隔)
lib_player_.SmartPlayerSetReportDownloadSpeed(get(), 1, 4);
boolean isFastStartup = true;
lib_player_.SmartPlayerSetFastStartup(get(), isFastStartup ? 1 : 0);
//设置RTSP超时时间
int rtsp_timeout = 10;
lib_player_.SmartPlayerSetRTSPTimeout(get(), rtsp_timeout);
//设置RTSP TCP/UDP模式自动切换
int is_auto_switch_tcp_udp = 1;
lib_player_.SmartPlayerSetRTSPAutoSwitchTcpUdp(get(), is_auto_switch_tcp_udp);
lib_player_.SmartPlayerSaveImageFlag(get(), 1);
// It only used when playback RTSP stream..
lib_player_.SmartPlayerSetRTSPTcpMode(get(), is_using_tcp);
lib_player_.DisableEnhancedRTMP(get(), 0);
lib_player_.SmartPlayerSetUrl(get(), playback_url);
//try_set_rtsp_url(playback_url);
}
/*
*这里尝试解析rtsp url, 然后设置给sdk, 考虑到各种不规范的URL, 这里解析不一定正确,请按实际情况自行实现
*/
private void try_set_rtsp_url(String in_url) {
if (null == in_url)
return;
final String rtsp_prefix = "rtsp://";
String url = in_url.trim();
if (url.isEmpty() || !url.startsWith(rtsp_prefix))
return;
String str1 = url.substring(rtsp_prefix.length());
if (null== str1 || str1.isEmpty())
return;
int last_at_pos;
int pos = str1.indexOf('/');
if (pos > -1)
last_at_pos = str1.lastIndexOf('@', pos);
else
last_at_pos = str1.lastIndexOf('@');
String new_url = str1;
String user_password = null;
if (last_at_pos > -1) {
new_url = str1.substring(last_at_pos+1);
user_password = str1.substring(0, last_at_pos);
}
if (null == new_url || new_url.isEmpty())
return;
new_url = rtsp_prefix + new_url;
String user = null, password = null;
if (user_password != null && !user_password.isEmpty()) {
int pos1 = user_password.indexOf(':');
if (pos1 >-1) {
user = user_password.substring(0, pos1);
password = user_password.substring(pos1+1);
}else
user = user_password;
if (user !=null && !user.isEmpty()) {
try {
user = java.URLDecoder.decode(user, "UTF-8");
}
catch (Exception e){
Log.e(TAG, "Exception:", e);
}
}
if (password != null && !password.isEmpty()) {
try {
password = java.URLDecoder.decode(password, "UTF-8");
}catch (Exception e) {
Log.e(TAG, "Exception:", e);
}
}
}
Log.i(TAG, "rtsp org_url:" + in_url + ", url:" + new_url + ", user:" + user + ", password:" + password);
lib_player_.SmartPlayerSetUrl(get(), new_url);
lib_player_.SetRTSPAuthenticationInfo(get(), user, password);
}
public void setSurfaceView(View surface_view) {
this.surface_view_ = surface_view;
}
private void setPlayerParam(boolean is_hardware_decoder, boolean is_enable_hardware_render_mode, boolean is_mute)
{
Surface surface = null;
int surface_codec_media_color_format = 0;
if (surface_view_ != null && surface_view_ instanceof SurfaceView && ((SurfaceView) surface_view_).getHolder() != null)
surface = ((SurfaceView) surface_view_).getHolder().getSurface();
lib_player_.SetSurface(get(), surface, surface_codec_media_color_format, 0, 0);
lib_player_.SmartPlayerSetRenderScaleMode(get(), 1);
//int render_format = 1;
//lib_player.SmartPlayerSetSurfaceRenderFormat(handle, render_format);
//int is_enable_anti_alias = 1;
//lib_player.SmartPlayerSetSurfaceAntiAlias(handle, is_enable_anti_alias);
if (is_hardware_decoder && is_enable_hardware_render_mode) {
lib_player_.SmartPlayerSetHWRenderMode(get(), 1);
}
lib_player_.SmartPlayerSetAudioOutputType(get(), 1);
lib_player_.SmartPlayerSetMute(get(), is_mute ? 1 : 0);
if (is_hardware_decoder) {
int isSupportHevcHwDecoder = lib_player_.SetSmartPlayerVideoHevcHWDecoder(get(), 1);
int isSupportH264HwDecoder = lib_player_.SetSmartPlayerVideoHWDecoder(get(), 1);
Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);
}
boolean isLowLatency = true;
lib_player_.SmartPlayerSetLowLatencyMode(get(), isLowLatency ? 1 : 0);
boolean is_flip_vertical = false;
lib_player_.SmartPlayerSetFlipVertical(get(), is_flip_vertical ? 1 : 0);
boolean is_flip_horizontal = false;
lib_player_.SmartPlayerSetFlipHorizontal(get(), is_flip_horizontal ? 1 : 0);
int rotate_degrees = 0;
lib_player_.SmartPlayerSetRotation(get(), rotate_degrees);
int curAudioVolume = 100;
lib_player_.SmartPlayerSetAudioVolume(get(), curAudioVolume);
}
class EventHandleV2 implements NTSmartEventCallbackV2 {
@Override
public void onNTSmartEventCallbackV2(long handle, int id, long param1,
long param2, String param3, String param4, Object param5) {
if(event_listener_ != null)
{
event_listener_.onPlayerEventCallback(handle, id, param1, param2, param3, param4, param5);
}
}
}
public boolean setMute(boolean is_mute) {
if (!check_native_handle())
return false;
return OK == lib_player_.SmartPlayerSetMute(get(), is_mute? 1 : 0);
}
public boolean setAudioVolume(int volume) {
if (!check_native_handle())
return false;
return OK == lib_player_.SmartPlayerSetAudioVolume(get(), volume);
}
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);
}
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;
}
public boolean stopPlayer() {
if (!check_native_handle())
return false;
if (!is_playing()) {
Log.w(TAG, "it's not playing, native_handle:" + get());
return false;
}
boolean is_need_call = false;
write_lock_.lock();
try {
if (this.is_playing_) {
this.is_playing_ = false;
is_need_call = true;
}
} finally {
write_lock_.unlock();
}
if (is_need_call)
lib_player_.SmartPlayerStopPlay(get());
return true;
}
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;
}
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;
}
public boolean stopRecorder() {
if (!check_native_handle())
return false;
if (!is_recording()) {
Log.w(TAG, "it's not recording, native_handle:" + get());
return false;
}
boolean is_need_call = false;
write_lock_.lock();
try {
if (this.is_recording_) {
this.is_recording_ = false;
is_need_call = true;
}
} finally {
write_lock_.unlock();
}
if (is_need_call)
lib_player_.SmartPlayerStopRecorder(get());
return true;
}
public boolean switchPlaybackUrl(String url) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSwitchPlaybackUrl(get(), url);
}
public boolean setRotation(int degrees) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetRotation(get(), degrees);
}
public boolean setFlipVertical(boolean flip) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetFlipVertical(get(), flip ? 1 : 0);
}
public boolean setFlipHorizontal(boolean flip) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetFlipHorizontal(get(), flip ? 1 : 0);
}
public boolean setLowLatencyMode(boolean enable) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetLowLatencyMode(get(), enable ? 1 : 0);
}
public boolean setFastStartup(boolean enable) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetFastStartup(get(), enable ? 1 : 0);
}
private static boolean is_null_or_empty(String val) {
return null == val || val.isEmpty();
}
}
总结
大牛直播 SDK 提供了全面的 RTSP|RTMP 播放功能,包括低延迟播放、录像、截图等。通过合理配置参数和利用其提供的 API,开发者可以快速实现高效稳定的直播播放应用。在实际项目中,建议根据具体需求对播放器进行深度定制,以提升用户体验。无论是播放RTSP还是RTMP流,延迟均控制在100-300ms区间,满足平衡操控等对延迟要求苛刻的使用场景,以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通探讨。
Android平台RTSP
前言
在移动直播、视频监控等场景中,RTSP(Real Time Streaming Protocol)和 RTMP(Real Time Messaging Protocol)是两种常见的流媒体传输协议。它们能够提供实时、低延迟的音视频传输,但实现高效的播放功能具有一定技术门槛。大牛直播SDK作为行业内备受认可的解决方案,提供了功能强大、性能卓越的 RTSP/RTMP 播放模块。本文将基于大牛直播 SDK,详细讲解如何在 Android 平台开发一个高效的 RTSP|RTMP 播放器。
SDK 集成准备
环境配置
- 系统支持:确保目标设备运行 Android 5.1 及以上版本,支持的 CPU 架构包括 armv7、arm64、x86 和 x86_64。
- 依赖集成:
- 将
Smartavengine.jar
添加至项目依赖。 - 拷贝对应架构的
libSmartPlayer.so
文件至jniLibs
文件夹。 - 在
AndroidManifest.xml
中声明必要权限:
- 将
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
- SO 库加载:在代码中通过
System.loadLibrary("SmartPlayer");
加载原生库。
核心功能实现
Window平台采集毫秒计数器窗口,然后编码打包注入到轻量级RTSP服务,对外提供拉流的RTSP URL,然后Android平台同时播放4路RTSP流,整体延迟如下:
功能支持
- 音频:AAC/Speex(RTMP)/PCMA/PCMU;
- 视频:H.264、H.265;
- 播放协议:RTSP|RTMP;
- 支持纯音频、纯视频、音视频播放;
- 支持多实例播放;
- 支持软解码,特定机型硬解码;
- 支持RTSP TCP、UDP模式设置;
- 支持RTSP TCP、UDP模式自动切换;
- 支持RTSP超时时间设置,单位:秒;
- 支持buffer时间设置,单位:毫秒;
- 支持超低延迟模式;
- 支持断网自动重连、视频追赶,支持buffer状态等回调;
- 支持视频view实时旋转(0° 90° 180° 270°);
- 支持视频view水平反转、垂直反转;
- 支持Surfaceview/OpenGL ES/TextureView绘制;
- 支持视频画面填充模式设置;
- 音频支持AudioTrack、OpenSL ES模式;
- 支持jpeg、png实时截图;
- 支持实时音量调节;
- 支持解码前音视频数据回调;
- 支持解码后YUV/RGB数据回调;
- 支持Enhanced RTMP;
- 支持扩展录像功能;
- 支持Android 5.1及以上版本。
播放器初始化与播放控制
代码语言:java复制SmartPlayerJniV2 libPlayer = new SmartPlayerJniV2();
long playerHandle = libPlayer.SmartPlayerOpen(context);
if (playerHandle == 0) {
Log.e("PlayerDemo", "Failed to initialize player");
return;
}
// 设置播放参数
libPlayer.SmartPlayerSetBuffer(playerHandle, 200); // 设置缓冲时间 200ms
libPlayer.SmartPlayerSetUrl(playerHandle, "rtmp://example/live/stream");
// 设置 SurfaceView 用于视频渲染
libPlayer.SmartPlayerSetSurface(playerHandle, surfaceView);
// 开始播放
int result = libPlayer.SmartPlayerStartPlay(playerHandle);
if (result != 0) {
Log.e("PlayerDemo", "Failed to start playback");
}
事件回调处理
通过实现 NTSmartEventCallbackV2
接口,可以接收播放器状态更新等事件:
class EventHandeV2 implements NTSmartEventCallbackV2 {
@Override
public void onNTSmartEventCallbackV2(long handle, int id, long param1,
long param2, String param3, String param4, Object param5) {
//Log.i(TAG, "EventHandeV2: handle=" + handle + " id:" + id);
String player_event = "";
switch (id) {
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:
player_event = "开始..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:
player_event = "连接中..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:
player_event = "连接失败..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:
player_event = "连接成功..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:
player_event = "连接断开..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP:
player_event = "停止播放..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:
player_event = "分辨率信息: width: " + param1 + ", height: " + param2;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:
player_event = "收不到媒体数据,可能是url错误..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:
player_event = "切换播放URL..";
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:
player_event = "快照: " + param1 + " 路径:" + param3;
if (param1 == 0)
player_event = player_event + ", 截取快照成功";
else
player_event = player_event + ", 截取快照失败";
if (param4 != null && !param4.isEmpty())
player_event += (", user data:" + param4);
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:
player_event = "[record]开始一个新的录像文件 : " + param3;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:
player_event = "[record]已生成一个录像文件 : " + param3;
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:
Log.i(TAG, "Start Buffering");
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:
Log.i(TAG, "Buffering:" + param1 + "%");
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:
Log.i(TAG, "Stop Buffering");
break;
case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:
player_event = "download_speed:" + param1 + "Byte/s" + ", "
+ (param1 * 8 / 1000) + "kbps" + ", " + (param1 / 1024)
+ "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);
player_event = "RTSP error code:" + param1;
break;
}
if (player_event.length() > 0) {
Log.i(TAG, player_event);
Message message = new Message();
message.what = PLAYER_EVENT_MSG;
message.obj = player_event;
handler.sendMessage(message);
}
}
}
设置回调:
代码语言:java复制libPlayer.SetSmartPlayerEventCallbackV2(playerHandle, new EventCallback());
录像功能集成
代码语言:java复制@SuppressLint("NewApi")
void ConfigRecorderFuntion() {
if (libPlayer != null) {
int is_rec_trans_code = 1;
libPlayer.SmartPlayerSetRecorderAudioTranscodeAAC(playerHandle, is_rec_trans_code);
if (recDir != null && !recDir.isEmpty()) {
int ret = libPlayer.SmartPlayerCreateFileDirectory(recDir);
if (0 == ret) {
if (0 != libPlayer.SmartPlayerSetRecorderDirectory(
playerHandle, recDir)) {
Log.e(TAG, "Set recoder dir failed , path:" + recDir);
return;
}
if (0 != libPlayer.SmartPlayerSetRecorderFileMaxSize(
playerHandle, 200)) {
Log.e(TAG,
"SmartPublisherSetRecorderFileMaxSize failed.");
return;
}
} else {
Log.e(TAG, "Create recorder dir failed, path:" + recDir);
}
}
}
}
实时截图功能
代码语言:java复制btnCaptureImage.setOnClickListener(new Button.OnClickListener() {
@SuppressLint("SimpleDateFormat")
public void onClick(View v) {
if (0 == playerHandle)
return;
if (null == capture_image_date_format_)
capture_image_date_format_ = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS");
String timestamp = capture_image_date_format_.format(new Date());
String imageFileName = timestamp;
String image_path = imageSavePath + "/" + imageFileName;
int quality;
boolean is_jpeg = true;
if (is_jpeg) {
image_path += ".jpeg";
quality = 100;
}
else {
image_path += ".png";
quality = 100;
}
int capture_ret = libPlayer.CaptureImage(playerHandle,is_jpeg?0:1, quality, image_path, "test cix");
Log.i(TAG, "capture image ret:" + capture_ret + ", file:" + image_path);
}
});
视频画面控制
代码语言:java复制// 水平翻转
libPlayer.SmartPlayerSetFlipHorizontal(playerHandle, 1);
// 垂直翻转
libPlayer.SmartPlayerSetFlipVertical(playerHandle, 1);
// 旋转 90 度
libPlayer.SmartPlayerSetRotation(playerHandle, 90);
性能优化与最佳实践
- 硬解码优先:在支持的设备上优先启用硬解码(H.264/H.265),可显著降低 CPU 负载。
- 缓冲策略调整:根据网络条件动态调整缓冲区大小,平衡延迟与流畅度。
- 多实例管理:通过封装播放器实例,支持多路视频同时播放。
/*
* LibPlayerWrapper.java
* Created by daniusdk
* WeChat: xinsheng120
*/
package com.daniulive.smartplayer;
import android.content.Context;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.View;
import com.eventhandle.NTSmartEventCallbackV2;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LibPlayerWrapper {
private static String TAG = "NTLogLibPlayerW";
private static final int OK = 0;
private WeakReference<Context> context_;
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();
private SmartPlayerJniV2 lib_player_;
private volatile long native_handle_;
private View surface_view_;
private volatile boolean is_playing_;
private volatile boolean is_recording_;
private EventListener event_listener_;
public LibPlayerWrapper(SmartPlayerJniV2 lib_player, Context context, EventListener listener) {
if (!empty())
throw new IllegalStateException("it is not empty");
if (null == lib_player)
throw new NullPointerException("lib_player is null");
this.lib_player_ = lib_player;
if (context != null)
this.context_ = new WeakReference<>(context);
this.event_listener_ = listener;
}
private void clear_all_playing_flags() {
this.is_playing_ = false;
this.is_recording_ = false;
}
public void set(long handle) {
if (!empty())
throw new IllegalStateException("it is not empty");
write_lock_.lock();
try {
clear_all_playing_flags();
this.native_handle_ = handle;
} finally {
write_lock_.unlock();
}
Log.i(TAG, "set native_handle:" + handle);
}
@Override
protected void finalize() throws Throwable {
try {
if (check_native_handle()) {
if(is_playing()) {
lib_player_.SmartPlayerStopPlay(get());
this.is_playing_ = false;
}
if(is_recording()) {
lib_player_.SmartPlayerStopRecorder(get());
this.is_recording_ = false;
}
lib_player_.SmartPlayerClose(this.native_handle_);
Log.i(TAG, "finalize close handle:" + this.native_handle_);
this.native_handle_ = 0;
}
}catch (Exception e) {
}
super.finalize();
}
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;
}
public boolean try_release() {
if (empty())
return false;
if (is_player_running()) {
Log.i(TAG, "try_release it is running, native_handle:" + get());
return false;
}
long handle;
write_lock_.lock();
try {
if (is_player_running())
return false;
handle = this.native_handle_;
this.native_handle_ = 0;
} finally {
write_lock_.unlock();
}
if (lib_player_ != null && handle != 0)
lib_player_.SmartPlayerClose(handle);
return true;
}
public final boolean empty() { return 0 == this.native_handle_; }
public final long get() { return this.native_handle_; }
public View get_view() {return this.surface_view_;}
public final boolean check_native_handle() {
return this.lib_player_ != null && this.native_handle_ != 0;
}
public final boolean is_playing() { return is_playing_; }
public final boolean is_recording() { return is_recording_; }
public final boolean is_player_running() { return is_playing_ || is_recording_; }
private boolean isValidRtspOrRtmpUrl(String url) {
if (url == null || url.isEmpty()) {
return false;
}
return url.trim().startsWith("rtsp://") || url.startsWith("rtmp://");
}
private EventListener getListener() {
return this.event_listener_;
}
private Context application_context() {
return context_ == null ? null : context_.get();
}
public boolean initialize(String playback_url, int play_buffer, int is_using_tcp) {
if (check_native_handle())
return true;
if(!isValidRtspOrRtmpUrl(playback_url))
return false;
long handle = lib_player_.SmartPlayerOpen(application_context());
if (0==handle) {
Log.e(TAG, "sdk open failed!");
return false;
}
set(handle);
configurePlayer(playback_url, play_buffer, is_using_tcp);
return true;
}
private void configurePlayer(String playback_url, int buffer, int is_using_tcp) {
lib_player_.SetSmartPlayerEventCallbackV2(get(), new EventHandleV2());
lib_player_.SmartPlayerSetBuffer(get(), buffer);
// set report download speed(默认2秒一次回调 用户可自行调整report间隔)
lib_player_.SmartPlayerSetReportDownloadSpeed(get(), 1, 4);
boolean isFastStartup = true;
lib_player_.SmartPlayerSetFastStartup(get(), isFastStartup ? 1 : 0);
//设置RTSP超时时间
int rtsp_timeout = 10;
lib_player_.SmartPlayerSetRTSPTimeout(get(), rtsp_timeout);
//设置RTSP TCP/UDP模式自动切换
int is_auto_switch_tcp_udp = 1;
lib_player_.SmartPlayerSetRTSPAutoSwitchTcpUdp(get(), is_auto_switch_tcp_udp);
lib_player_.SmartPlayerSaveImageFlag(get(), 1);
// It only used when playback RTSP stream..
lib_player_.SmartPlayerSetRTSPTcpMode(get(), is_using_tcp);
lib_player_.DisableEnhancedRTMP(get(), 0);
lib_player_.SmartPlayerSetUrl(get(), playback_url);
//try_set_rtsp_url(playback_url);
}
/*
*这里尝试解析rtsp url, 然后设置给sdk, 考虑到各种不规范的URL, 这里解析不一定正确,请按实际情况自行实现
*/
private void try_set_rtsp_url(String in_url) {
if (null == in_url)
return;
final String rtsp_prefix = "rtsp://";
String url = in_url.trim();
if (url.isEmpty() || !url.startsWith(rtsp_prefix))
return;
String str1 = url.substring(rtsp_prefix.length());
if (null== str1 || str1.isEmpty())
return;
int last_at_pos;
int pos = str1.indexOf('/');
if (pos > -1)
last_at_pos = str1.lastIndexOf('@', pos);
else
last_at_pos = str1.lastIndexOf('@');
String new_url = str1;
String user_password = null;
if (last_at_pos > -1) {
new_url = str1.substring(last_at_pos+1);
user_password = str1.substring(0, last_at_pos);
}
if (null == new_url || new_url.isEmpty())
return;
new_url = rtsp_prefix + new_url;
String user = null, password = null;
if (user_password != null && !user_password.isEmpty()) {
int pos1 = user_password.indexOf(':');
if (pos1 >-1) {
user = user_password.substring(0, pos1);
password = user_password.substring(pos1+1);
}else
user = user_password;
if (user !=null && !user.isEmpty()) {
try {
user = java.URLDecoder.decode(user, "UTF-8");
}
catch (Exception e){
Log.e(TAG, "Exception:", e);
}
}
if (password != null && !password.isEmpty()) {
try {
password = java.URLDecoder.decode(password, "UTF-8");
}catch (Exception e) {
Log.e(TAG, "Exception:", e);
}
}
}
Log.i(TAG, "rtsp org_url:" + in_url + ", url:" + new_url + ", user:" + user + ", password:" + password);
lib_player_.SmartPlayerSetUrl(get(), new_url);
lib_player_.SetRTSPAuthenticationInfo(get(), user, password);
}
public void setSurfaceView(View surface_view) {
this.surface_view_ = surface_view;
}
private void setPlayerParam(boolean is_hardware_decoder, boolean is_enable_hardware_render_mode, boolean is_mute)
{
Surface surface = null;
int surface_codec_media_color_format = 0;
if (surface_view_ != null && surface_view_ instanceof SurfaceView && ((SurfaceView) surface_view_).getHolder() != null)
surface = ((SurfaceView) surface_view_).getHolder().getSurface();
lib_player_.SetSurface(get(), surface, surface_codec_media_color_format, 0, 0);
lib_player_.SmartPlayerSetRenderScaleMode(get(), 1);
//int render_format = 1;
//lib_player.SmartPlayerSetSurfaceRenderFormat(handle, render_format);
//int is_enable_anti_alias = 1;
//lib_player.SmartPlayerSetSurfaceAntiAlias(handle, is_enable_anti_alias);
if (is_hardware_decoder && is_enable_hardware_render_mode) {
lib_player_.SmartPlayerSetHWRenderMode(get(), 1);
}
lib_player_.SmartPlayerSetAudioOutputType(get(), 1);
lib_player_.SmartPlayerSetMute(get(), is_mute ? 1 : 0);
if (is_hardware_decoder) {
int isSupportHevcHwDecoder = lib_player_.SetSmartPlayerVideoHevcHWDecoder(get(), 1);
int isSupportH264HwDecoder = lib_player_.SetSmartPlayerVideoHWDecoder(get(), 1);
Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);
}
boolean isLowLatency = true;
lib_player_.SmartPlayerSetLowLatencyMode(get(), isLowLatency ? 1 : 0);
boolean is_flip_vertical = false;
lib_player_.SmartPlayerSetFlipVertical(get(), is_flip_vertical ? 1 : 0);
boolean is_flip_horizontal = false;
lib_player_.SmartPlayerSetFlipHorizontal(get(), is_flip_horizontal ? 1 : 0);
int rotate_degrees = 0;
lib_player_.SmartPlayerSetRotation(get(), rotate_degrees);
int curAudioVolume = 100;
lib_player_.SmartPlayerSetAudioVolume(get(), curAudioVolume);
}
class EventHandleV2 implements NTSmartEventCallbackV2 {
@Override
public void onNTSmartEventCallbackV2(long handle, int id, long param1,
long param2, String param3, String param4, Object param5) {
if(event_listener_ != null)
{
event_listener_.onPlayerEventCallback(handle, id, param1, param2, param3, param4, param5);
}
}
}
public boolean setMute(boolean is_mute) {
if (!check_native_handle())
return false;
return OK == lib_player_.SmartPlayerSetMute(get(), is_mute? 1 : 0);
}
public boolean setAudioVolume(int volume) {
if (!check_native_handle())
return false;
return OK == lib_player_.SmartPlayerSetAudioVolume(get(), volume);
}
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);
}
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;
}
public boolean stopPlayer() {
if (!check_native_handle())
return false;
if (!is_playing()) {
Log.w(TAG, "it's not playing, native_handle:" + get());
return false;
}
boolean is_need_call = false;
write_lock_.lock();
try {
if (this.is_playing_) {
this.is_playing_ = false;
is_need_call = true;
}
} finally {
write_lock_.unlock();
}
if (is_need_call)
lib_player_.SmartPlayerStopPlay(get());
return true;
}
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;
}
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;
}
public boolean stopRecorder() {
if (!check_native_handle())
return false;
if (!is_recording()) {
Log.w(TAG, "it's not recording, native_handle:" + get());
return false;
}
boolean is_need_call = false;
write_lock_.lock();
try {
if (this.is_recording_) {
this.is_recording_ = false;
is_need_call = true;
}
} finally {
write_lock_.unlock();
}
if (is_need_call)
lib_player_.SmartPlayerStopRecorder(get());
return true;
}
public boolean switchPlaybackUrl(String url) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSwitchPlaybackUrl(get(), url);
}
public boolean setRotation(int degrees) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetRotation(get(), degrees);
}
public boolean setFlipVertical(boolean flip) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetFlipVertical(get(), flip ? 1 : 0);
}
public boolean setFlipHorizontal(boolean flip) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetFlipHorizontal(get(), flip ? 1 : 0);
}
public boolean setLowLatencyMode(boolean enable) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetLowLatencyMode(get(), enable ? 1 : 0);
}
public boolean setFastStartup(boolean enable) {
if (!check_native_handle()) return false;
return OK == lib_player_.SmartPlayerSetFastStartup(get(), enable ? 1 : 0);
}
private static boolean is_null_or_empty(String val) {
return null == val || val.isEmpty();
}
}
总结
大牛直播 SDK 提供了全面的 RTSP|RTMP 播放功能,包括低延迟播放、录像、截图等。通过合理配置参数和利用其提供的 API,开发者可以快速实现高效稳定的直播播放应用。在实际项目中,建议根据具体需求对播放器进行深度定制,以提升用户体验。无论是播放RTSP还是RTMP流,延迟均控制在100-300ms区间,满足平衡操控等对延迟要求苛刻的使用场景,以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通探讨。
本文标签: Android平台RTSP
版权声明:本文标题:Android平台RTSP 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1747533195a2171558.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论