当前位置: 首页 > news >正文

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直播播放模块&#xff0c;迭代从未停止&#xff0c;SmartPlayer功能强大、性能强劲、高稳定、超低延迟、超低资源占用。无需赘述&#xff0c;全自研内核&#xff0c;行业内一致认可的跨平台RTSP、RTMP直播播放器。本文以Android平台…...

数据结构——栈(顺序结构)

一、栈的定义 栈是一种数据结构&#xff0c;它是一种只能在一端进行插入和删除操作的特殊线性表。这一端被称为栈顶&#xff0c;另一端被称为栈底。栈按照后进先出&#xff08;LIFO&#xff09;的原则进行操作&#xff08;类似与手枪装弹后射出子弹的顺序&#xff09;。在计算…...

速盾:cdn能防御ddos吗?

CDN&#xff08;内容分发网络&#xff09;是一种广泛应用于互联网中的技术&#xff0c;它通过将内容分发到全球各地的服务器上&#xff0c;以提高用户在访问网站时的加载速度和稳定性。然而&#xff0c;CDN是否能够有效防御DDoS&#xff08;分布式拒绝服务&#xff09;攻击是一…...

分享 2 个 .NET EF 6 只更新某些字段的方法

前言 EF 更新数据时&#xff0c;通常情况下&#xff0c;是更新全部字段的&#xff0c;但实际业务中&#xff0c;更新全部字段的情况其实很少&#xff0c;一般都是修改其中某些字段&#xff0c;所以为了实现这个目标&#xff0c;很多程序员通常会这样作&#xff1a; 先从数据库…...

vs code解决报错 (c/c++的配置环境 远端机器为Linux ubuntu)

参考链接&#xff1a;https://blog.csdn.net/fightfightfight/article/details/82857397 https://blog.csdn.net/m0_38055352/article/details/105375367 可以按照步骤确定那一步不对&#xff0c;如果一个可以就不用往下看了 目录 一、检查一下文件扩展名 二、安装扩展包并…...

08 字符串和字节串

使用单引号、双引号、三单引号、三双引号作为定界符&#xff08;delimiter&#xff09;来表示字符串&#xff0c;并且不同的定界符之间可以相互嵌套。 很多内置函数和标准库对象也都支持对字符串的操作。 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&#xff1a;解决了在冗余的二层网络中所出现的环路问题 2、RSTP&#xff1a;在 STP 的基础上&#xff0c;解决了 STP 收敛速度慢的问题&#xff0c;引入了一些 STP 保护机制&#xff0c;使其网络更加稳定 二、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中&#xff0c;处理I/O操作的模型主要有四种&#xff1a;阻塞I/O (BIO), 非阻塞I/O (NIO), 异步I/O (AIO), 以及IO多路复用。下面详细介绍这四种I/O模型的工作原理和应用场景。 1. 阻塞I/O (BIO) 工作原理 阻塞I/O是最传统的I/O模型。在这种模型中&#xff0c;当一个线…...

服务器怎样减少带宽消耗的问题?

择业在使用服务器的过程中会消耗大量的带宽资源&#xff0c;而减少服务器的带宽消耗则可以帮助企业降低经济成本&#xff0c;同时还能够提高用户的访问速度&#xff0c;那么服务器怎样能减少带宽的消耗呢&#xff1f;本文就来带领大家一起来探讨一下吧&#xff01; 企业可以选择…...

linux 报错:bash: /etc/profile: 行 32: 语法错误:未预期的文件结束符

目录 注意错误不一定错在最后一行 i进入编辑 esc退出编辑 &#xff1a;wq 保存编辑退出 &#xff1a;q&#xff01;不保存退出 if [ $# -eq 3 ] then if [ ! -e "$1" ]; then miss1 $1 elif [ ! -e "$2" -a ! -e "$3" ]; then miss2and3…...

MySQL练习(5)

作业要求&#xff1a; 实现过程&#xff1a; 一、触发器 &#xff08;1&#xff09;建立两个表&#xff1a;goods&#xff08;商品表&#xff09;、orders&#xff08;订单表&#xff09; &#xff08;2&#xff09;在商品表中导入商品记录 &#xff08;3&#xff09;建立触发…...

泛型新理解

1.创建三个类&#xff0c;并写好对应关系 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我们为什么需要继承? 首先&#xff0c;Java中使用类对现实世界中实体来进行描述&#xff0c;类经过实例化之后的产物对象&#xff0c;则可以用来表示现实中的实体&#xff0c;但是 现实世界错综复杂&#xff0c;事物之间可能会存在一些关联&#xff0c;那在设计程…...

高级java每日一道面试题-2024年7月23日-什么时候用包装类, 什么时候用原始类

面试官: 你在什么时候用包装类, 什么时候用原始类? 我回答: 在Java开发中&#xff0c;理解何时使用包装类&#xff08;Wrapper Classes&#xff09;和何时使用原始类&#xff08;Primitive Types&#xff09;是非常重要的。这主要取决于你的具体需求以及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

靶机下载地址&#xff0c;下载完成后&#xff0c;用VirtualBox打开靶机并修改网络为桥接即可搭建成功。 信息收集 主机发现和端口扫描 扫描攻击机&#xff08;192.168.31.218&#xff09;同网段存活主机确认目标机ip&#xff0c;并对目标机进行全面扫描。 nmap 192.168.31.…...

MySQL第一阶段:多表查询、事务

继续我的MySQL之旅&#xff0c;继续上篇的DDL、DML、DQL、以及一些约束&#xff0c;该到了多表查询和事务的学习总结&#xff0c;以及相关的案例实现&#xff0c;为未来的复习以及深入的理解做好知识储备。 目录 多表查询 连接查询 内连接 外连接 子查询 事务 事务简介…...

Java的序列化和反序列化

序列化&#xff1a; 将数据结构或对象转换成二进制串的过程 反序列化&#xff1a;将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程 至于为什么要序列化和反序列化呢&#xff1f; 因为互联网的产生带来了机器间通讯的需求&#xff0c;而互联通讯的双方需要采用约…...

本地连接远程阿里云K8S

1.首先安装kubectl 1.1验证自己系统 uname -m 1.2 按照步骤安装 在 Linux 系统中安装并设置 kubectl | Kubernetes 1.3 阿里云配置 通过kubectl连接Kubernetes集群_容器服务 Kubernetes 版 ACK(ACK)-阿里云帮助中心 2.验证 阿里云config直接导出&#xff0c;直接扔到.…...

CasaOS设备使用Docker安装SyncThing文件同步神器并实现远程管理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

k210 图像操作详解(一)(直线检测、边缘检测、色块追踪)

1、直线检测 ##################################################################################################### # file main.py # author 正点原子团队(ALIENTEK) # version V1.0 # date 2024-01-17 # brief image图像特征检测实…...

【Java版数据结构】初识泛型

看到这句话的时候证明&#xff1a;此刻你我都在努力 加油陌生人 br />个人主页&#xff1a;Gu Gu Study专栏&#xff1a;Java版数据结构 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者&#xff1…...

DevExpress WinForms自动表单布局,创建高度可定制用户体验(二)

使用DevExpress WinForms的表单布局组件可以创建高度可定制的应用程序用户体验&#xff0c;从自动安排UI控件到按比例调整大小&#xff0c;DevExpress布局和数据布局控件都可以让您消除与基于像素表单设计相关的麻烦。 P.S&#xff1a;DevExpress WinForms拥有180组件和UI库&a…...

vue中v-if和v-for

vue中v-if和v-for Vue 官方建议不要在同一个元素上同时使用 v-if 和 v-for 指令&#xff0c;主要有以下几个原因&#xff1a; 性能问题&#xff1a; 当 v-if 和 v-for 一起使用时&#xff0c;Vue 在每次渲染时都需要先执行循环&#xff0c;然后再对每个元素进行条件判断。这可能…...

【MySQL】根据binlog日志获取回滚sql的一个开发思路

根据binlog日志获取回滚sql的一个开发思路 需要获取的信息 thread_id 打开 mysql 客户端 开始时间 关闭 mysql 客户端 结束时间 binlog 匹配流程 指定 mysql 客户端 开始时间和结束时间 先匹配 thread_id 相同的 然后匹配 ^BEGIN$行和 ^COMMIT/*!*/;$行之间的数据 当匹…...

Kafka快速入门+SpringBoot简单的秒杀案例

1. 主题相关 1.1 创建主题 kafka-topics.sh --create --bootstrap-server [服务器地址] --replication-factor [副本数] --partitions [分区数] --topic [主题名]liberliber-VMware-Virtual-Platform:/home/zookeeper$ docker-compose exec kafka /bin/bash #进入kafka容器 b…...