Android平台RTSP|RTMP直播播放器技术接入说明
以上是Android平台RTSP、RTMP直播播放模块对接说明,在此之前,我们针对SmartPlayer做过一些技术方面的探讨,从低延迟、音视频同步处理、多实例实现、解码效率、性能占用、解码后数据对接、实时截图、录像、网络抖动处理等各个维度,做过相关的技术分享。感兴趣的开发者,可以单独跟我们探讨。
技术背景
大牛直播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 true
reset()
// Specifies a list of ABIs that Gradle should create APKs for
include '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 ABIs
universalApk 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() {
// @Override
public 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 {
@Override
public 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 + ", 截取快照成功";
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);
}
}
}
如果RTSP、RTMP流需要录像:
btnStartStopRecorder.setOnClickListener(new Button.OnClickListener() {
// @Override
public 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;
}
@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);
}
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 {
@Override
public 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做过一些技术方面的探讨,从低延迟、音视频同步处理、多实例实现、解码效率、性能占用、解码后数据对接、实时截图、录像、网络抖动处理等各个维度,做过相关的技术分享。感兴趣的开发者,可以单独跟我们探讨。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)