Android平台RTSP|RTMP直播播放器技术接入说明
技术背景
大牛直播SDK自2015年发布RTSP、RTMP直播播放模块,迭代从未停止,SmartPlayer功能强大、性能强劲、高稳定、超低延迟、超低资源占用。无需赘述,全自研内核,行业内一致认可的跨平台RTSP、RTMP直播播放器。本文以Android平台为例,介绍下如何集成RTSP、RTMP播放模块。
技术对接
系统要求
- SDK支持Android5.1及以上版本;
- 支持的CPU架构:armv7, arm64, x86, x86_64。
准备工作
- 确保SmartPlayerJniV2.java放到com.daniulive.smartplayer包名下(可在其他包名下调用);
- Smartavengine.jar加入到工程;
- 拷贝SmartPlayerV2\app\src\main\jniLibs\armeabi-v7a、 SmartPlayerV2\app\src\main\jniLibs\arm64-v8a、SmartPlayerV2\app\src\main\jniLibs\x86和SmartPlayerV2\app\src\main\jniLibs\x86_64 下 libSmartPlayer.so到工程;
- AndroidManifast.xml添加相关权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" >
</uses-permission>
<uses-permission android:name="android.permission.INTERNET" >
</uses-permission>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
- Load相关so:
static { System.loadLibrary("SmartPlayer");
}
- build.gradle配置32/64位库:
splits {abi {enable truereset()// Specifies a list of ABIs that Gradle should create APKs forinclude 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' //select ABIs to build APKs for// Specify that we do not want to also generate a universal APK that includes all ABIsuniversalApk true}
}
- 如需集成到自己系统测试,请用大牛直播SDK的app name,授权版按照授权app name正常使用即可;
- 如何改app-name,strings.xml做以下修改:
<string name="app_name">SmartPlayerSDKDemo</string>
接口设计
| Android RTSP|RTMP播放端SDK接口详解 | |||
| 调用描述 | 接口 | 接口描述 | |
| 最先调用,如成功返回播放实例 | SmartPlayerOpen | player初始化,设置上下文信息,返回player句柄 | |
| Event回调 | SetSmartPlayerEventCallbackV2 | 设置event callback | |
| 硬解码设置(H.264) | SetSmartPlayerVideoHWDecoder | 设置是否用H.264硬解码播放,如硬解码不支持,自动适配到软解码 | |
| 硬解码设置(H.265) | SetSmartPlayerVideoHevcHWDecoder | 设置是否用H.265硬解码播放,如硬解码不支持,自动适配到软解码 | |
| 视频画面 填充模式 | SmartPlayerSetRenderScaleMode | 设置视频画面的填充模式,如填充整个view、等比例填充view,如不设置,默认填充整个view | |
| 设置SurfaceView模式下render类型 | SmartPlayerSetSurfaceRenderFormat | 设置SurfaceView模式下(NTRenderer.CreateRenderer第二个参数传false的情况),render类型 0: RGB565格式,如不设置,默认此模式; 1: ARGB8888格式 | |
| 设置SurfaceView模式下抗锯齿效果 | SmartPlayerSetSurfaceAntiAlias | 设置SurfaceView模式下(NTRenderer.CreateRenderer第二个参数传false的情况),抗锯齿效果,注意:抗锯齿模式开启后,可能会影像性能,请慎用 | |
| 设置播放的surface | SmartPlayerSetSurface | 设置播放的surface,如果为null,则播放纯音频 | |
| 设置视频硬解码下Mediacodec自行绘制模式 | SmartPlayerSetHWRenderMode | 此种模式下,硬解码兼容性和效率更好,回调YUV/RGB、快照和图像等比例缩放功能将不可用 | |
| 更新硬解码surface | SmartPlayerUpdateHWRenderSurface | 设置更新硬解码surface | |
| 音频回调 | YUV/RGB | SmartPlayerSetExternalRender | 提供解码后YUV/RGB数据接口,供用户自己render或进一步处理(如视频分析) |
| Audio | SmartPlayerSetExternalAudioOutput | 回调audio数据到上层(供二次处理之用) | |
| audio输出类型 | SmartPlayerSetAudioOutputType | 如果use_audiotrack设置为0,将会自动选择输出设备,如果设置为1,使用audiotrack模式,一对一回音消除模式下,请选用audiotrack模式 | |
| Video输出类型 | NTRenderer.CreateRenderer(上层demo内) | 第二个参数,如果是true,用openGLES绘制,false则用默认surfaceView | |
| 播放模式 | 缓冲时间设置 | SmartPlayerSetBuffer | 设置播放端缓存数据buffer,单位:毫秒,如不需buffer,设置为0 |
| 首屏秒开 | SmartPlayerSetFastStartup | 设置快速启动后,如果CDN缓存GOP,实现首屏秒开 | |
| 低延迟模式 | SmartPlayerSetLowLatencyMode | 针对类似于直播娃娃机等期待超低延迟的使用场景,超低延迟播放模式下,延迟可达到200~400ms | |
| 快速切换URL | SmartPlayerSwitchPlaybackUrl | 快速切换播放url,快速切换时,只换播放source部分,适用于不同数据流之间,快速切换(如娃娃机双摄像头切换或高低分辨率流切换) | |
| RTSP TCP/UDP模式设置 | SmartPlayerSetRTSPTcpMode | 设置RTSP TCP/UDP模式,如不设置,默认UDP模式 | |
| RTSP超时时间设置 | SmartPlayerSetRTSPTimeout | 设置RTSP超时时间,timeout单位为秒,必须大于0 | |
| 设置RTSP TCP/UDP自动切换 | SmartPlayerSetRTSPAutoSwitchTcpUdp | 对于RTSP来说,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式 为了方便使用,有些场景下可以开启自动尝试切换开关, 打开后如果udp无法播放,sdk会自动尝试tcp, 如果tcp方式播放不了,sdk会自动尝试udp. | |
| 设置RTSP用户名和密码 | SetRTSPAuthenticationInfo | 如果RTSP URL已包含用户名和密码, 此接口设置的用户名和密码将无效. 就是说要用这个接口设置的用户名和密码去做认证, RTSP URL不能包含用户名和密码. | |
| 实时静音 | SmartPlayerSetMute | 实时静音 | |
| 设置播放音量 | SmartPlayerSetAudioVolume | 播放端音量实时调节,范围[0,100],0时为静音,100为原始流数据最大音量 | |
| 设置是否禁用 Enhanced RTMP | DisableEnhancedRTMP | disable enhanced RTMP, SDK默认是开启enhanced RTMP的 | |
| 实时截图 | CaptureImage | 支持JPEG和PNG两种格式 | |
| 视频镜像旋转 | 旋转 | SmartPlayerSetRotation | 设置顺时针旋转, 注意除了0度之外, 其他角度都会额外消耗性能,当前支持 0度,90度, 180度, 270度 旋转 |
| 水平反转 | SmartPlayerSetFlipHorizontal | 设置视频水平反转 | |
| 垂直反转 | SmartPlayerSetFlipVertical | 设置视频垂直反转 | |
| 设置URL | SmartPlayerSetUrl | 设置需要播放或录像的RTMP/RTSP url | |
| 开始播放 | SmartPlayerStartPlay | 开始播放RTSP/RTMP流 | |
| 停止播放 | SmartPlayerStopPlay | 停止播放RTSP/RTMP流 | |
| 关闭播放实例 | SmartPlayerClose | 结束时必须调用close接口释放资源 | |
功能支持
- 音频: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及以上版本。
接口调用详解
本文以大牛直播SDK Android平台SmartPlayerV2为例,播放之前,设置初始化参数配置(软解还是硬解、buffer time等)和需要播放的RTSP或RTMP URL,点开始播放即可。

onCreate()时,先new SmartPlayerJniV2():
/** SmartPlayer.java* Author: daniusdk.com*/
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_smart_player);...libPlayer = new SmartPlayerJniV2();myContext = this.getApplicationContext();
}
开始播放、停止播放实现,开始播放的时候,调用InitAndSetConfig(),完成常规参数初始化,然后调用仅播放相关的其他接口。
btnStartStopPlayback.setOnClickListener(new Button.OnClickListener() {// @Overridepublic void onClick(View v) {if (isPlaying) {Log.i(TAG, "Stop playback stream++");int iRet = libPlayer.SmartPlayerStopPlay(playerHandle);if (iRet != 0) {Log.e(TAG, "Call SmartPlayerStopPlay failed..");return;}btnHardwareDecoder.setEnabled(true);btnLowLatency.setEnabled(true);if (!isRecording) {btnPopInputUrl.setEnabled(true);btnSetPlayBuffer.setEnabled(true);btnFastStartup.setEnabled(true);btnRecoderMgr.setEnabled(true);libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;}isPlaying = false;btnStartStopPlayback.setText("开始播放 ");if (is_enable_hardware_render_mode && sSurfaceView != null) {sSurfaceView.setVisibility(View.GONE);sSurfaceView.setVisibility(View.VISIBLE);}Log.i(TAG, "Stop playback stream--");} else {Log.i(TAG, "Start playback stream++");if (!isRecording) {InitAndSetConfig();}// 如果第二个参数设置为null,则播放纯音频libPlayer.SmartPlayerSetSurface(playerHandle, sSurfaceView);libPlayer.SmartPlayerSetRenderScaleMode(playerHandle, 1);//int render_format = 1;//libPlayer.SmartPlayerSetSurfaceRenderFormat(playerHandle, render_format);//int is_enable_anti_alias = 1;//libPlayer.SmartPlayerSetSurfaceAntiAlias(playerHandle, is_enable_anti_alias);if (isHardwareDecoder && is_enable_hardware_render_mode) {libPlayer.SmartPlayerSetHWRenderMode(playerHandle, 1);}// External Render test//libPlayer.SmartPlayerSetExternalRender(playerHandle, new RGBAExternalRender(imageSavePath));//libPlayer.SmartPlayerSetExternalRender(playerHandle, new I420ExternalRender(imageSavePath));libPlayer.SmartPlayerSetUserDataCallback(playerHandle, new UserDataCallback());//libPlayer.SmartPlayerSetSEIDataCallback(playerHandle, new SEIDataCallback());libPlayer.SmartPlayerSetAudioOutputType(playerHandle, 1);if (isMute) {libPlayer.SmartPlayerSetMute(playerHandle, isMute ? 1: 0);}if (isHardwareDecoder) {int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(playerHandle, 1);int isSupportH264HwDecoder = libPlayer.SetSmartPlayerVideoHWDecoder(playerHandle, 1);Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);}libPlayer.SmartPlayerSetLowLatencyMode(playerHandle, isLowLatency ? 1: 0);libPlayer.SmartPlayerSetFlipVertical(playerHandle, is_flip_vertical ? 1 : 0);libPlayer.SmartPlayerSetFlipHorizontal(playerHandle, is_flip_horizontal ? 1 : 0);libPlayer.SmartPlayerSetRotation(playerHandle, rotate_degrees);libPlayer.SmartPlayerSetAudioVolume(playerHandle, curAudioVolume);int iPlaybackRet = libPlayer.SmartPlayerStartPlay(playerHandle);if (iPlaybackRet != 0) {Log.e(TAG, "Call SmartPlayerStartPlay failed..");return;}btnStartStopPlayback.setText("停止播放 ");btnPopInputUrl.setEnabled(false);btnPopInputKey.setEnabled(false);btnSetPlayBuffer.setEnabled(false);btnLowLatency.setEnabled(false);btnFastStartup.setEnabled(false);btnRecoderMgr.setEnabled(false);isPlaying = true;Log.i(TAG, "Start playback stream--");}}
});
由于RTSP、RTMP播放模块,除了常规的直播播放外,也可能录像、或者实时拉流转发到RTMP服务器或轻量级RTSP服务,所以,和录像、转发相关的播放端基础参数配置,放到InitAndSetConfig()实现:
private void InitAndSetConfig() {playerHandle = libPlayer.SmartPlayerOpen(myContext);if (playerHandle == 0) {Log.e(TAG, "surfaceHandle with nil..");return;}libPlayer.SetSmartPlayerEventCallbackV2(playerHandle,new EventHandeV2());libPlayer.SmartPlayerSetBuffer(playerHandle, playBuffer);// set report download speed(默认2秒一次回调 用户可自行调整report间隔)libPlayer.SmartPlayerSetReportDownloadSpeed(playerHandle, 1, 2);libPlayer.SmartPlayerSetFastStartup(playerHandle, isFastStartup ? 1 : 0);//设置RTSP超时时间int rtsp_timeout = 10;libPlayer.SmartPlayerSetRTSPTimeout(playerHandle, rtsp_timeout);//设置RTSP TCP/UDP模式自动切换int is_auto_switch_tcp_udp = 1;libPlayer.SmartPlayerSetRTSPAutoSwitchTcpUdp(playerHandle, is_auto_switch_tcp_udp);libPlayer.SmartPlayerSaveImageFlag(playerHandle, 1);// It only used when playback RTSP stream..// libPlayer.SmartPlayerSetRTSPTcpMode(playerHandle, 1);// playbackUrl = "rtmp://localhost:1935/live/stream1";if (playbackUrl == null) {Log.e(TAG, "playback URL with NULL...");return;}libPlayer.SmartPlayerSetUrl(playerHandle, playbackUrl);// try_set_rtsp_url(playbackUrl);
}
EventHandle播放端事件回调处理,是底层状态反馈非常重要的媒介,除了网络状态、buffering状态回调外、还有录像状态、快照状态等回调:
class EventHandeV2 implements NTSmartEventCallbackV2 {@Overridepublic void onNTSmartEventCallbackV2(long handle, int id, long param1,long param2, String param3, String param4, Object param5) {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 + ", 截取快照成功";elseplayer_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);}}
}
如果RTSP、RTMP流需要录像:
btnStartStopRecorder.setOnClickListener(new Button.OnClickListener() {// @Overridepublic void onClick(View v) {if (isRecording) {int iRet = libPlayer.SmartPlayerStopRecorder(playerHandle);if (iRet != 0) {Log.e(TAG, "Call SmartPlayerStopRecorder failed..");return;}if (!isPlaying) {btnPopInputUrl.setEnabled(true);btnSetPlayBuffer.setEnabled(true);btnFastStartup.setEnabled(true);btnRecoderMgr.setEnabled(true);libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;}btnStartStopRecorder.setText(" 开始录像");isRecording = false;} else {Log.i(TAG, "onClick start recorder..");if (!isPlaying) {InitAndSetConfig();}ConfigRecorderFunction();int startRet = libPlayer.SmartPlayerStartRecorder(playerHandle);if (startRet != 0) {Log.e(TAG, "Failed to start recorder.");return;}btnPopInputUrl.setEnabled(false);btnSetPlayBuffer.setEnabled(false);btnFastStartup.setEnabled(false);btnRecoderMgr.setEnabled(false);isRecording = true;btnStartStopRecorder.setText("停止录像");}}
});
其中,录像参数配置选项设置如下,除了下面演示接口外,还可以设置仅录视频或音频:
void ConfigRecorderFunction() {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);}}}
}
如需播放过程中实时截图:
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);}
});
如需对视频view做水平、垂直翻转或旋转:
btnFlipVertical.setOnClickListener(new Button.OnClickListener() {public void onClick(View v) {is_flip_vertical = !is_flip_vertical;if (is_flip_vertical) {btnFlipVertical.setText("取消反转");} else {btnFlipVertical.setText("垂直反转");}if (playerHandle != 0) {libPlayer.SmartPlayerSetFlipVertical(playerHandle,is_flip_vertical ? 1 : 0);}}
});btnFlipHorizontal.setOnClickListener(new Button.OnClickListener() {public void onClick(View v) {is_flip_horizontal = !is_flip_horizontal;if (is_flip_horizontal) {btnFlipHorizontal.setText("取消反转");} else {btnFlipHorizontal.setText("水平反转");}if (playerHandle != 0) {libPlayer.SmartPlayerSetFlipHorizontal(playerHandle,is_flip_horizontal ? 1 : 0);}}
});btnRotation.setOnClickListener(new Button.OnClickListener() {public void onClick(View v) {rotate_degrees += 90;rotate_degrees = rotate_degrees % 360;if (0 == rotate_degrees) {btnRotation.setText("旋转90度");} else if (90 == rotate_degrees) {btnRotation.setText("旋转180度");} else if (180 == rotate_degrees) {btnRotation.setText("旋转270度");} else if (270 == rotate_degrees) {btnRotation.setText("不旋转");}if (playerHandle != 0) {libPlayer.SmartPlayerSetRotation(playerHandle,rotate_degrees);}}
});
onDestroy() 的时候,停掉播放、录像、释放播放端实例句柄:
@Override
protected void onDestroy() {Log.i(TAG, "Run into activity destory++");if (playerHandle != 0) {if (isPlaying) {libPlayer.SmartPlayerStopPlay(playerHandle);}if (isRecording) {libPlayer.SmartPlayerStopRecorder(playerHandle);}libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;}super.onDestroy();finish();System.exit(0);
}
以上是大概的流程,如果需要播放多实例,可以做个简单的封装,多实例效果如下:

LibPlayerWrapper.java参考封装代码如下,如需额外功能,只要按照设计框架,添加进去即可:
/** LibPlayerWrapper.java.java* Author: daniusdk.com*/
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 view_;private volatile boolean is_playing_;private volatile boolean is_recording_;private WeakReference<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);if (listener == null ) {this.event_listener_ = null;}else {this.event_listener_ = new WeakReference<>(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);}public void SetView(View view) {Log.i(TAG, "SetView: " + view);this.view_ = view;}@Overrideprotected 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);}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.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() {if ( this.event_listener_ == null )return null;return this.event_listener_.get();}protected final Context application_context() {if (null == context_)return null;return context_.get();}public boolean OpenPlayerHandle(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;}lib_player_.SetSmartPlayerEventCallbackV2(handle, new EventHandleV2());lib_player_.SmartPlayerSetBuffer(handle, play_buffer);// set report download speed(默认2秒一次回调 用户可自行调整report间隔)lib_player_.SmartPlayerSetReportDownloadSpeed(handle, 1, 4);boolean isFastStartup = true;lib_player_.SmartPlayerSetFastStartup(handle, isFastStartup ? 1 : 0);//设置RTSP超时时间int rtsp_timeout = 10;lib_player_.SmartPlayerSetRTSPTimeout(handle, rtsp_timeout);//设置RTSP TCP/UDP模式自动切换int is_auto_switch_tcp_udp = 1;lib_player_.SmartPlayerSetRTSPAutoSwitchTcpUdp(handle, is_auto_switch_tcp_udp);lib_player_.SmartPlayerSaveImageFlag(handle, 1);// It only used when playback RTSP stream..lib_player_.SmartPlayerSetRTSPTcpMode(handle, is_using_tcp);lib_player_.DisableEnhancedRTMP(handle, 0);lib_player_.SmartPlayerSetUrl(handle, playback_url);set(handle);return true;}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 (view_ != null && view_ instanceof SurfaceView && ((SurfaceView) view_).getHolder() != null)surface = ((SurfaceView) 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 {@Overridepublic void onNTSmartEventCallbackV2(long handle, int id, long param1,long param2, String param3, String param4, Object param5) {if(event_listener_.get() != null){event_listener_.get().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 SetInputAudioVolume(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;}private static boolean is_null_or_empty(String val) {return null == val || val.isEmpty();}
}
总结
以上是Android平台RTSP、RTMP直播播放模块对接说明,在此之前,我们针对SmartPlayer做过一些技术方面的探讨,从低延迟、音视频同步处理、多实例实现、解码效率、性能占用、解码后数据对接、实时截图、录像、网络抖动处理等各个维度,做过相关的技术分享。感兴趣的开发者,可以单独跟我们探讨。
相关文章:
Android平台RTSP|RTMP直播播放器技术接入说明
技术背景 大牛直播SDK自2015年发布RTSP、RTMP直播播放模块,迭代从未停止,SmartPlayer功能强大、性能强劲、高稳定、超低延迟、超低资源占用。无需赘述,全自研内核,行业内一致认可的跨平台RTSP、RTMP直播播放器。本文以Android平台…...
数据结构——栈(顺序结构)
一、栈的定义 栈是一种数据结构,它是一种只能在一端进行插入和删除操作的特殊线性表。这一端被称为栈顶,另一端被称为栈底。栈按照后进先出(LIFO)的原则进行操作(类似与手枪装弹后射出子弹的顺序)。在计算…...
速盾:cdn能防御ddos吗?
CDN(内容分发网络)是一种广泛应用于互联网中的技术,它通过将内容分发到全球各地的服务器上,以提高用户在访问网站时的加载速度和稳定性。然而,CDN是否能够有效防御DDoS(分布式拒绝服务)攻击是一…...
分享 2 个 .NET EF 6 只更新某些字段的方法
前言 EF 更新数据时,通常情况下,是更新全部字段的,但实际业务中,更新全部字段的情况其实很少,一般都是修改其中某些字段,所以为了实现这个目标,很多程序员通常会这样作: 先从数据库…...
vs code解决报错 (c/c++的配置环境 远端机器为Linux ubuntu)
参考链接:https://blog.csdn.net/fightfightfight/article/details/82857397 https://blog.csdn.net/m0_38055352/article/details/105375367 可以按照步骤确定那一步不对,如果一个可以就不用往下看了 目录 一、检查一下文件扩展名 二、安装扩展包并…...
08 字符串和字节串
使用单引号、双引号、三单引号、三双引号作为定界符(delimiter)来表示字符串,并且不同的定界符之间可以相互嵌套。 很多内置函数和标准库对象也都支持对字符串的操作。 x hello world y Python is a great language z Tom said, "Le…...
vue使用mavonEditor(流程图、时序图、甘特图实现)
mavonEditor 安装mavonEditor $ npm install mavon-editor --save使用 // 全局注册import Vue from vueimport mavonEditor from mavon-editorimport mavon-editor/dist/css/index.css// useVue.use(mavonEditor)new Vue({el: #main,data() {return { value: }}})//局部使用…...
Java实现短信验证码服务
1.首先这里使用的是阿里云的短信服务。 package com.wzy.util;; import cn.hutool.captcha.generator.RandomGenerator; import com.aliyun.dysmsapi20170525.Client; import com.wzy.entity.Ali; import org.springframework.stereotype.Component;/*** Author: 顾安* Descri…...
python中的线程
线程 线程概念 线程 在一个进程的内部, 要同时干多件事, 就需要同时运行多个"子任务", 我们把进程内的这些"子任务"叫做线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中, 是进程的实际运作单位。一条线程指的是进程中一个单一顺序的控制流…...
hcip学习 多实例生成树,VRRP工作原理
一、STP 和 RSTP 解决了什么问题 1、STP:解决了在冗余的二层网络中所出现的环路问题 2、RSTP:在 STP 的基础上,解决了 STP 收敛速度慢的问题,引入了一些 STP 保护机制,使其网络更加稳定 二、MSTP 针对 RSTP 的改进 …...
Docker搭建群晖
Docker搭建群晖 本博客介绍在docker下搭建群晖 1.编辑docker-compose.yml文件 version: "3" services:dsm:container_name: dsmimage: vdsm/virtual-dsm:latestenvironment:DISK_SIZE: "16G"cap_add:- NET_ADMIN ports:- 8080:50…...
【java】BIO,NIO,多路IO复用,AIO
在Java中,处理I/O操作的模型主要有四种:阻塞I/O (BIO), 非阻塞I/O (NIO), 异步I/O (AIO), 以及IO多路复用。下面详细介绍这四种I/O模型的工作原理和应用场景。 1. 阻塞I/O (BIO) 工作原理 阻塞I/O是最传统的I/O模型。在这种模型中,当一个线…...
服务器怎样减少带宽消耗的问题?
择业在使用服务器的过程中会消耗大量的带宽资源,而减少服务器的带宽消耗则可以帮助企业降低经济成本,同时还能够提高用户的访问速度,那么服务器怎样能减少带宽的消耗呢?本文就来带领大家一起来探讨一下吧! 企业可以选择…...
linux 报错:bash: /etc/profile: 行 32: 语法错误:未预期的文件结束符
目录 注意错误不一定错在最后一行 i进入编辑 esc退出编辑 :wq 保存编辑退出 :q!不保存退出 if [ $# -eq 3 ] then if [ ! -e "$1" ]; then miss1 $1 elif [ ! -e "$2" -a ! -e "$3" ]; then miss2and3…...
MySQL练习(5)
作业要求: 实现过程: 一、触发器 (1)建立两个表:goods(商品表)、orders(订单表) (2)在商品表中导入商品记录 (3)建立触发…...
泛型新理解
1.创建三个类,并写好对应关系 package com.jmj.gulimall.study;public class People { }package com.jmj.gulimall.study;public class Student extends People{ }package com.jmj.gulimall.study;public class Teacher extends People{ }2.解释一下这三个方法 pub…...
JavaSE--基础语法--继承和多态(第三期)
一.继承 1.1我们为什么需要继承? 首先,Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是 现实世界错综复杂,事物之间可能会存在一些关联,那在设计程…...
高级java每日一道面试题-2024年7月23日-什么时候用包装类, 什么时候用原始类
面试官: 你在什么时候用包装类, 什么时候用原始类? 我回答: 在Java开发中,理解何时使用包装类(Wrapper Classes)和何时使用原始类(Primitive Types)是非常重要的。这主要取决于你的具体需求以及Java语言本身的一些限…...
LINUX之MMC子系统分析
目录 1. 概念1.1 MMC卡1.2 SD卡1.3 SDIO 2. 总线协议2.1 协议2.2 一般协议2.3 写数据2.4 读数据2.5 卡模式2.5.1 SD卡模式2.5.2 eMMC模式 2.6 命令2.6.1 命令类2.6.2 详细命令 2.7 应答2.8 寄存器2.8.1 OCR2.8.2 CID2.8.3 CSD2.8.4 RCA2.8.5 扩展CSD 3. 关键结构3.1 struct sdh…...
VulnHub:cengbox1
靶机下载地址,下载完成后,用VirtualBox打开靶机并修改网络为桥接即可搭建成功。 信息收集 主机发现和端口扫描 扫描攻击机(192.168.31.218)同网段存活主机确认目标机ip,并对目标机进行全面扫描。 nmap 192.168.31.…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
