技术背景
大牛直播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做过一些技术方面的探讨,从低延迟、音视频同步处理、多实例实现、解码效率、性能占用、解码后数据对接、实时截图、录像、网络抖动处理等各个维度,做过相关的技术分享。感兴趣的开发者,可以单独跟我们探讨。