CDN加速

Android推流器

更新时间:2021-10-18 17:40:27

!注意:Android推流器免费提供,不再提供技术支持

一、SDK下载地址

二、SDK 结构

  • StreamPusher_V1.x.x.jar SDK 核心部分,后缀标注版本号

  • Armeabi-v7a 底层核心库

    • libadapter.so
    • libcrypto.so
    • librtmp.so
    • libssl.so
    • libwsvd.so
  • StreamPusherDemo 使用SDK的简易demo

三、SDK 配置

SDK 嵌入所需改动:

  • 将libs目录下的所有jar包拷贝到工程目录/libs文件夹下,将so拷贝到与java同级jniLibs 目录下

    网宿科技第四季度互联网报告对外正式发布

  • 添加如下代码到 AndroidManifest.xml 中

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
  • 如果要混淆代码,添加如下混淆规则:

-keep class com.chinanetcenter.StreamPusher.** {
*;
}
-keep class cnc.cad.validsdk.** {
*;
}
-keepattributes InnerClasses
-dontoptimize

  • android 9.0 适配 http 被禁用问题:
    在 AndroidManifest 中的 application 中加入 android:usesCleartextTraffic="true"

四、基本类简介

1、推流器配置类 SPConfig

1)setRtmpUrl: 设置推流 rtmp 地址(选填,可在动态调整参数调整)

2)setAppIdAndAuthKey:设置鉴权 id 和鉴权 key,需向 sdk 提供者申请(必填)

3)setCameraId : 前后置摄像头配置,CameraInfo.CAMERA_FACING_FRONT或CameraInfo.CAMERA_FACING_BACK

4)setVideoResolution:视频分辨率,这是一个枚举,定义在SPManager内部,
VideoResolution{ VIDEO_RESOLUTION_360P,VIDEO_RESOLUTION_480P,VIDEO_RESOLUTION_540P,VIDEO_RESOLUTION_720P; }
同时要指定分辨率的比例,VideoRatio{ RATIO_4_3,RATIO_16_9 }

5)setVideoBitrate:视频码率 在开启码率自适应的情况下,码率会存在自适应上下限。在码率自适应关闭的情况下, 码率会以用户设置的为准。

分辨率 自适应下限 自适应上限
360P 400k 700k
480P 500k 800k
540P 600k 1000k
720P 800k 1500k

6) setEncoderMode:软硬编配置 目 前 支 持 两 种 编 码 模 式 SPConfig.ENCODER_MODE_HARD 和 SPConfig.ENCODER_MODE_SOFT,VERSION_CODES.JELLY_BEAN(4.1)及以上版本支持硬编, 建议使用硬编

7)setMinFps:设置预览帧率,支持 15~30 帧

8)setCameraManualFocusMode: 摄像头是否开启手动聚焦模式,(默认 false,自动聚焦)。 开启手动聚焦后,SPSurfaceView 会拦截点击事件,当用户点击 view 时触发聚焦。

9)setHasAudio:是否推音频 setHasVideo:是否推视频

10)setAutoBitrate:是否开启自适应码率(默认关闭),自适应码率会根据网络情况,自动 调节视频码率,以保证推流的流畅。
setDecoderMode:设置播放时解码方式 , DECODER_MODE_HARD或DECODER_MODE_SOFT

11)setAudioPlayMode :设置音频的播放方式,AUDIO_PLAY_AUDIOTRACK或AUDIO_PLAY_OPENSL

12)setMaxRecordFileSize:设置录制的最大文件大小

13)setMaxRecordDuration:设置录制的最长录制时间

14)setGIFMaxRecordDuration:设置 GIF 录制的最长录制时间

15)setRecordVideoType:设置录制视频的类型,TYPE_LONG_VIDEO或TYPE_SHORT_VIDEO

16)setVarFramerate:设置可变帧率,环境光线弱时,可通过增加曝光时间,降低帧率,保证 预览效果;

17)setEchoCancellation:设置是否开启回声消除

18)setSocks5Proxy:设置 socks5,参数通过 Socks5Proxy 对象传入;

2、状态类 PushState

1)isPushing
是否正在推流,一般调用startPushStream成功后被置为true。

2)isMute
推流时是否静音,一般在调用muteMic后发生改变。

3)isFlashing
闪光灯是否开启,一般在调用 flashCamera 后发生改变。

4)filter
当前美颜模式,一般在调用 switchFilter 后发生改变。

5)hasVideo
推流是否包含视频,正在情况下 setHasVideo(false)后会出现会变为 false。异常情况下,当camera无法打开的时候,将单推音频。

6)hasAudio
推流是否包含音频,正在情况下setHasAudio(false)后会出现会变为false。 异常情况下,当麦克风无法打开的时候,将单推视频频。

7)isSupportFlash
摄像头打开后,该标志位会指示当前摄像头是否支持闪光灯。

8)audioLoopActive
耳返状态,一般在调用 switchAudioLoop 方法后发生改变

9)video_height、video_width
视频编码的宽高,在 SPManager.init()之后调用 SPManager.getPushState()才能得到正确的编码宽高;

10)preview_height、preview_width
视频预览的宽高,在SPManager.startPushStream()之后调用 SPManager.getPushState()才能得到正确的预览宽高,在获取宽高之后需要判断获取宽高是否为0,如果不为0,则获取成功,否则需要重复请求,直到获取到的宽高大于0为止;

3、功能类 SPManager

SPManager 是主要的功能类,负责推流控制和配置修改。
由于 sdk 内部维护了一个线性消息队列,所以 SPManager 中的方法都是运行在异步线程的,所以用户调用时不需要考虑阻塞 UI 线程的问题。SPManager 中的方法的返回值只能指示消息是否被成功加入队列,并不能代表真正被成功执行,如果执行中遇到错误,会在OnErrorListener的回调中进行回调。

以下是 SPManager 方法的简单介绍:
  SPManager.init(SPConfig)  初始化。
  SPManager.setOnErrorListener(OnErrorListene);  注册推流错误监听器
  SPManager.setOnStateListener(OnStateListene);  注册推流状态监听器
  SPManager.startPushStream();   开始推流。
  SPManager.stopPushStream();   结束推流。
  SPManager.switchCamera();  切换前后置摄像头
  SPManager.flashCamera(int);  打开关闭闪光灯
  SPManager.muteMic(int);  麦克风静音
  SPManager.flipCamera();  摄像头上下颠倒校准
  SPManager.switchFilter(int);  设置美颜模式
  SPManager. getConfig();  获取当前的配置
  SPManager. setConfig(SPConfig);  非推流状态下设置配置,当前仅支持
    /*SPConfig#setRtmpUrl(String);
    SPConfig#setVideoBitrate(int);
    SPConfig#setAutoBitrate (boolean);
    SPConfig#setMinFps(int)*/
  SPManager. cameraManualFocusMode(int);  设置摄像头聚焦模式
  SPManager. getPushState();  获取当前推流状态参数
  SPManager. onResume();  在 activity 的 onresume()方法中调用
  SPManager. onPause();  在 activity 的 onPause()方法中调用
  SPManager. switchAudioLoop(int);  打开/关闭耳返
  SPManager. setAudioReverbLevel(int);  设置混响等级,0 未关闭、5 是最大级别
  SPManager. startBgm(String);  开始播放背景音乐
  SPManager. stopBgm();  停止播放背景音乐
  SPManger. setBgmVolume();  设置背景音乐的推流音量
  SPManager.setMicVolume();   设置 mic 采集声音的大小
  SPManager.startRecord(OutputFormat);  开始录制视频,OutputForamt为录制格式
  SPManager.stopRecord();  停止录制
  SPManager.showWaterMarkTime(float,float,float,int,float);  显示时间水印;
  SPManager.hideWaterMarkTime();  关闭时间水印
  SPManager.showWaterMarkLogo(String,float,float,float,float,float);  显示图片水印
  SPManager. hideWaterMarkLogo();  关闭图片水印
  SPManager.setFilter(SPVideoFilter);  设置自定义滤镜
  SPManager.setFilter(List);  设置自定义滤镜组
  SPManager.setStyleFilterModel(String,int);  设置滤镜 model 文件路径和滤镜等级(0~10)
  SPManager.pushYuvFrame(int,byte[],int,int,long);  自定义视频源 yuv 送编码器接口

4、显示类 SPSurfaceView

主要用于推流时视频的展示,用户可以将该 View 写在布局文件中,然后将 View 的实例通过 SPManager#init 方法传递给 SDK,SDK 将完成视频的显示与渲染。

五、功能使用

推流功能

1、SPManager 初始化
初始化是由 init(Context context,SPConfig config) 方法完成的, 该方法建议在 Activity 的 onCreate 方法中被调用。
调用 init 方法必须要先初始化一个 SPConfig 对象,具体 SPConfig 的配置可查看 SPConfig 类相关介绍。
以下是 demo 中的初始化示例代码

mSPConfig = SPManager.getConfig();//获取一个默认配置的SPConfig对象
mSPConfig.setRtmpUrl(url); mSPConfig.setSurfaceView(mPreviewView);
mSPConfig.setCameraId(mCurrentCameraId);
mSPConfig.setEncoderMode(encoderState);
mSPConfig.setMinFps(frameRate);
mSPConfig.setVideoBitrate(bitrate);
mSPConfig.setVideoResolution(videoResolution, VideoRatio.RATIO_16_9);
mSPConfig.setAppIdAndAuthKey(appId, authKey);
mSPConfig.setHasVideo(true); mSPConfig.setHasAudio(true);

2、监听推流器状态
为了更好的和 SDK 交互,接受各种状态和其他信息,需要注册对应的 Listener:
目前我们提供了 OnStateListener 和 OnErrorListener,
OnStateListener:主要负责一些推流过程中状态的监听。
OnErrorListener:主要负责监听致命错误,以便进行相应处理,提示用户。
Listener 两个参数:
what 错误码;
extra 错误信息。
由于回调是在异步线程中进行的,所以不要在回调中直接进行 UI 操作,以避免可能造成的错误。

SPManager.setOnErrorListener(new OnErrorListener() {
    @Override
    publicvoid onError(int what, String extra){
        mErrorHandler.obtainMessage(what, extra).sendToTarget();
    }
    });
SPManager.setOnStateListener(new OnStateListener() {
    @Override
    publicvoid onState(int what, String extra) {
        mStateHandler.obtainMessage(what, extra).sendToTarget();
    }
});

一般在开始推流后,就会进入推流状态,但在出现错误等现象后,建议通过 getPushState() 方法重新获取当前推流状态,以保证状态的正确性。关于 PushState 类的介绍可参考其简介。

3、开始/停止推流
当调用过 init 方法,初始化完成后,就可以调用 startPushStream 开始推流了,由于 sdk 内部维护了一个线性消息队列,所以 SPManager 中的所有方法都运行在异步,且是线性执行的。 如果想停止推流,请调用 stopPushStream 方法。

普通推流
直接推摄像头采集到的图像:

SPManager.startPushStream(SPManager.PushStreamType.TYPE_CAMERA);

录屏推流
传入 surfaceView 即可开启摄像头预览,传入 null 则不开启摄像头

SPManager.startPushStream(SPManager.PushStreamType.TYPE_SCREEN);

录屏功能使用的是 Android 5.0 提供接口 MediaProjection。
这种方式的特点就是可以实现手机整个屏幕(包括虚拟键和状态栏等系统视图)内容的抓取。
api 的具体使用方法如下:

1. 在AndroidManifest主配置文件中注册activity
<activity   android:name="com.chinanetcenter.StreamPusher.sdk.SPScreenShot$SPScreenCaptureActivity"
    android:theme="@android:style/Theme.Translucent" />
这个activity在SDK内部生命,他是一个透明的activity,主要用于向用户请求抓取屏幕内容的权限。

2. 调用录屏推流接口
SPManager.startPushStream(SPManager.PushStreamType.TYPE_SCREEN);

注意:Android 10 以后,该录屏接口必须依赖前端 Service 才能被调用,否则将抛出安全异 常:

java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION

此时需要客户自己注册一个 foregroundServiceType 为 mediaProjection 的 Service,并在调用录屏接口前,调用 startForeground 方法,将 Service 置为前端服务。

//1、在主配置文件中注册 Service
<service android:name=".service.FloatWindowService"
    android:foregroundServiceType="mediaProjection">
</service>

// 2、在调用接口前调用 Service 的 startForeground 接口
startForeground(1, notification);

//3、调用截屏接口 SPManager.startPushStream(SPManager.PushStreamType.TYPE_SCREEN);

4、前后台切换 resume/pause
由于 Surface 的显示需要大量的系统资源, 若在一个 Activity 中进行推流操作, 建议在 Activity#onStop 中调用 SPManager#onPause(),以便更好的节约系统资源。在 Activity 重新回 到前台时,调用 SPManager# onResume ()重新申请 Surface 资源。
根据 Android 官方文档要求规范,在 Activity 退到后台,即系统调用 onStop 之后,应用要释 放 camera 资源,因此,建议在 onStop 之后调用 SPManager#stopPushStream 结束推流,在 Activity 回到前台后重新开始推流。如果在 Activity#onStop 后继续推流的话,你推出去的将 只有音频。
为了防止一些弹框导致 Activity 退到后台,建议弹框使用 Dialog 而非 Dialog 样式的 Activity。

5、切换前后置摄像头
CameraInfo.CAMERA_FACING_FRONT 或 CameraInfo.CAMERA_FACING_BACK
前后置摄像头可以在初始化的时候指定,SPConfig.setCameraId();
也可以在开始推流后使用 switchCamera()进行切换。

6、手动对焦
系统默认是自动聚焦,如果你想使用手动聚焦,则可以在初始化是将SPConfig#cameraManualFocusMode字段这是为true。
这样在开始推流后就可以使用手动聚焦了,手动聚焦开启后,用户点击 SPSurfaceView 区域 后,将触发一次聚焦。
在 init 调用之后或开始推流之后,你也可以通过 SPManager. cameraManualFocusMode 方法打开和关闭手动聚焦。

7、Zoom
SPSurfaceView 内置了 Zoom 功能,用户使用双指操作便可触发此功能。
目前该方法是内部实现的,没有暴露给用户调用,如果由于 SPSurfaceView 内部拦截了手指 触摸事件,建议用户复写 SPSurfaceView 的 onTouchEvent 事件。

8、闪光灯操作
开启闪光灯。
SPManager.flashCamera(SPManager.SWITCH_ON)
关闭闪光灯。
SPManager.flashCamera(SPManager.SWITCH_OFF)

9、音视频单推
用户开始推流前可以配置只推音频和直推视频。

mSPConfig.setHasVideo(hasVideo);
mSPConfig.setHasAudio(hasAudio);

异常情况下,当麦克风无法打开的时候,系统将自动单推视频流,当摄像头无法打开的时候, 系统将自动单推音频流。
如果即没有视频又没有音频将无法开启推流。
如果用户不希望在异常情况下自动切换音视频单推,可以在接收到系统抛出的摄像头或麦克 风打开失败错误码后发起停止推流消息。
示例代码:

case SPManager.ERROR_CAMERA_OPEN_FAILED:
if (SPManager.stopPushStream()) {
mIsUserPushing = false;
btn_record.setSelected(mIsUserPushing);
}
break;
case SPManager.ERROR_MIC_OPEN_FAILED:
if (SPManager.stopPushStream()) {
mIsUserPushing = false;
btn_record.setSelected(mIsUserPushing);
}
break;

10、禁音推流
在推流过程中,将声音禁用掉:
SPManager. muteMic (SPManager.SWITCH_ON)
恢复声音:
SPManager. muteMic (SPManager.SWITCH_OFF)

11、使用美颜滤镜
在开始推流后,用户可以使用美颜滤镜,以优化视频效果。
已实现的美颜滤镜定义在 SPMananger 内部枚举类 FilterType 中:

publicenum FilterType {
NONE("原图", -1), BEAUTYG("美颜普通1", 6), BEAUTYG1("美颜普通2", 6), BEAUTYB("美颜平滑", 6), SKINWHITEN("美白", 6), BEAUTYWHITEN("磨皮美白", 6);

private String name;
privateintlevel = -1;
private FilterType(String name, int level) {
this.name = name;
this.level = level;
  }
publicvoid setLevel(int level) {
if (level > 10) {
            level = 10;
        }
if (level < 0) {
            level = 0;
        }
this.level = level;
    }

publicint getLevel() {
returnlevel;
    }

@Override
public String toString() {
returnname;
    }
}

使用美颜滤镜:
SPManager. switchFilter (FilterType type);

在美颜的滤镜类中新增 setLevel(int)接口,通过该接口可实现美颜等级调节,美颜等级范 围为 0~10,0 代表美颜等级最小,10 代表美颜等级最大。在 SDK 中我们为每个美颜模式设 置了默认的等级,调用 setLevel 前可先通过 getLevel()方法查询下当前美颜模式的美颜等级, 如果美颜等级小于 0,说明当前美颜模式不支持等级的调节,SDK 将会忽略该模式的美颜等级参数。具体实现如下:

if(mCurrentFilter.getLevel() < 0) {
//该美颜模式不支持等级调节
return;
}
mCurrentFilter.setLevel(progress);//设置当前美颜模式的美颜等级
SPManager.switchFilter(mCurrentFilter);//将美颜滤镜及等级设置到SDK

12、动态调整参数
推流参数绝大部分是通过 SPConfig 在 init 的时候传入的,也有少数是可以通过 SPManager.setConfig(SPConfig spConfig)方法在 init 之后进行重新调整设置的,但这些参数必须在非推流 状态下设置配置,即调用 startPushStream 前或调用 stopPushStream 后。
目前可以调整的参数只有四个,设置其他参数将会被忽略:
SPConfig#setRtmpUrl(String);
SPConfig#setVideoBitrate(int);
SPConfig#setAutoBitrate (boolean);
SPConfig#setMinFps(int)
以设置 url 为例,具体设置方法:

mSPConfig = SPManager.getConfig();  //获取 SPConfig 实例
mSPConfig.setRtmpUrl(mPushUrl);  //更改 SPConfig 配置参数
SPManager.setConfig(mSPConfig);  //使 SPConfig 作用于推流器

13、码率自适应
保证推流的流畅性,减少因网络波动导致的丢帧现象。
在初始化时通过 SPConfig# setAutoBitrate(boolean on)方法开启和关闭自适应码率。
自适应码率关闭时,视频码率为用户设置的码率,当视频打开码率自适应功能后,视频码率 将在预期范围内自动调整。

分辨率 自适应下限 自适应上限
360P 400k 700k
480P 500k 800k
540P 600k 1000k
720P 800k 1500k
custom 400k 1500k

14、自定义码率自适应上下限
虽然我们已经定义好了一套码率自适应上下限,但这只是我们的推荐值,客户也可以自定义自己的码率自适应上下限,但用户自定义的上下限已经被我们约束到了我们现有上下限范围内。
如 360P 的下限为 400k~700k,如果用户定义了 300k~600k 的自定义范围,则程序在运行时会自动取 400k~600k。 自定义示例代码:

//自定义指定分辨率的码率自适应范围
VideoResolution.VIDEO_RESOLUTION_360P.setCustomBitrate(400*1024, 600*1024);
...//同理可设置其他分辨率

//当然你也可以在设置好分辨率时用如下代码设置,其实是一样的
mSPConfig.getVideoResolution().setCustomBitrate(customMinBitrate*1024, customMaxBitrate * 1024);

需要注意的是设置好的自定义码率上下限在程序运行期间会一直有效,如果用户想恢复默认 码率上下限则需要显式调用 resetCustomBitrate()方法。

VideoResolution.VIDEO_RESOLUTION_360P.resetCustomBitrate();

15、flipCamera
android 某些特殊机型,可能会出现主播预览画面上下翻转的现象,可以通过此功能校正该问题。
SPManager. flipCamera()

16、播放背景音乐
背景音的播放需要创建 SPAudioPlayer 对象,并通过 SPAudioPlayer 接口暴露的方法控制背景音乐播放。
通过 SPManager.createBgmPlayer (filePathOrUriStr) 创建 SPAudioPlayer对象 , 并通过 SPAudioPlayer. setPlayerListener() 方法设置监听器,在监听器的 onPrepared 方法中调用 SPAudioPlayer.start()方法启动背景音播放,另外 onCompletion 可监听歌曲播放完成,onError 可监听错误信息。
另外,通过 SPAudioPlayer.stop()方法停止播放,SPAudioPlayer.pause()方法暂停播放。调节背景音音量仍然沿用原来的SPManager.setBgmVolume(float)。
使用音量键调节音量只是调节的播放的音量,通过此方法调节,是调节的背景音乐和mic录入声 音的比例,该数值在0~1之间,0代表没有背景音乐,1代表背景音乐以原始音量播放。
示例代码如下:

//创建监听器
mBgmListener = new SPAudioPlayer.SPAudioPlayerListener() {
    @Override
    public void onError(SPAudioPlayer ap, int what, String extra) {
        //背景音乐解析失败
        SPManager.releaseBgmPlayer(ap);
    }

    @Override
    public void onPrepared(SPAudioPlayer ap) {
        if(mBgmPlayer != ap) return;
        //必须等待onPrepared回调后才可以播放
        ap.start();

    }

    @Override
    public void onCompletion(SPAudioPlayer ap) {
        //必须先释放播放结束的player对象
        if(mBgmPlayer != ap) {
            SPManager.releaseBgmPlayer(ap);
        }
        if(mBgmPlayer != null) {
            SPManager.releaseBgmPlayer(mBgmPlayer);
        }
        //这里可以创建下一首歌曲的播放对象,然后在onPrepared方法中开始播放,达到连续播放歌曲的效果
        mBgmPlayer = SPManager.createBgmPlayer(mBgmFiles.get(mCurrentBgmIndex));
        mBgmPlayer.setPlayerListener(mBgmListener);

    }

    @Override
    public void onProgressChanged(SPAudioPlayer ap, long progress) {
        //do not call SPAudioPlayer.start(),SPAudioPlayer.stop(),SPManager.createBgmPlayer(),SPManager.releaseBgmPlayer() in this method
        //it will block the parse thread.
    }
};

//启动背景音
mBgmPlayer = SPManager.createBgmPlayer(filePaht);
mBgmPlayer.setPlayerListener(mBgmListener);

//释放背景音
SPManager.releaseBgmPlayer(mBgmPlayer);

17、耳返
耳返是将主播的声音从耳机播放给主播的功能,不插入耳机,耳返将不生效。
目前只提供了有线耳机和蓝牙耳机的耳返功能。
使用 SPManager.switchAudioLoop(int);打开关闭耳返功能,默认关闭。

18、混响
混响是在不同建筑内声音的反射效果。可通过 SPManager.setAudioReverbLevel(level); 来设置。
混响的等级,等级为0~5。0为关闭,等级越高,混响对应的建筑空间越大。

19、水印
支持为视频添加水印效果,包含了时间水印和图片水印。
显示时间水印:

/**
* 在视频中显示时间水印
*
* @param x  时间戳的显示位置,0-1 之间,相对于视频
* @param y  时间戳的显示位置,0-1 之间,相对于视频
* @param w  时间戳的显示宽度,0-1 之间,相对于视频,高度会自适应
* @param color 时间戳的颜色
* @param alpha 时间戳的显示透明度,0-1 之间
*/

boolean showWaterMarkTime(float x, float y, float w, int color, float alpha)
关闭时间水印:boolean hideWaterMarkTime()

显示图片水印:

/**
* 设置并显示图片水印
*
* @param path  logo 图片文件的路径
* @param x  logo 的显示位置,0-1 之间,相对于视频
* @param y  logo 的显示位置,0-1 之间,相对于视频
* @param w  logo 的显示宽度,0-1 之间,相对于视频
* @param h  logo 的显示高度,0-1 之间,相对于视应
* @param alpha logo 的透明度,0-1 之间
*/

boolean showWaterMarkLogo(String path, float x, float y, float w, float h, float alpha)
其中图片水印的路径设置有以下两种:方式一为指定 sdcard 目录下的文件,需要指定前缀 file://,例如 mLogoPath = "file:///sdcard/test.png";方式二为指定 assets 目录下面的文件,需 要指定前缀 assets://,例如 mLogoPath = "assets://test.png"。

关闭图片水印:boolean hideWaterMarkLogo()

20、录制
支持主播在推流的同时,将推流的视频录制并保存到本地;

支持的视频格式: FLV、MP4、GIF;

录制分为两种类型:短视频、长视频;
在开始推流前,可通过如下方式设置录制类型和录制格式;

通过 SPConfig 的 setRecordVideoType 选择录制类型;
public SPConfig setRecordVideoType(VideoType type)
录制类型的取值放在 SPMananger 内部枚举类 VideoType 中:

publicenum VideoType { TYPE_LONG_VIDEO, TYPE_SHORT_VIDEO };

VideoType.TYPE_SHORT_VIDEO: 短视频类型
VideoType.TYPE_LONG_VIDEO: 长视频类型

短视频,允许设定的范围时长为 3 秒至 60 秒,以毫秒为单位,当达到最大时长时,则会停止录制,并会以 StateListener 的 SPManager.STATE_RECORD_REACHE_MAXDURATION 消息通知,可以在这里做更新 UI 的操作;未达到最短时长时会删除录制文件,并会以StateListener 的 SPManager.STATE_RECORD_UNREACHED_MINLIMIT 消息通知,可以在这里做更新 UI 的操作;
通过 SPConfig 的 setMaxRecordDuration 配置录制的最大时长,最长为 60s;
public SPConfig setMaxRecordDuration(long duration)

长视频,可以设置录制的最大文件大小,单位 byte,当达到最大时长时,则会停止录制,并会以 StateListener 的 SPManager.STATE_RECORD_REACHE_MAXFILESIZE 消息通知,可以在这里做更新 UI 的操作;

注意,在选择短视频类型后,虽然也可以设置最大文件大小,但是录制过程中只会对录制时长做检查,即设置的最大文件大小对短视频类型无效;同理,录制时长的设置对长视频类型也无效;
我们会对磁盘大小进行检查,防止磁盘因为视频录制被写满,规则如下:
磁盘剩余空间大小最小保证 100M,如果不足 100M,则会停止录制,并会以 StateListener 的 SPManager.STATE_RECORD_REACHE_MAXLIMIT 消息通知,可以在这里做更新 UI 的操作;

开始录制
通过 SPManager 的 startRecord 方法来开始录制,其中 OutputFormat 用于指定录制格式;
public static boolean startRecord(OutputFormat outputformat)
录制格式的取值放在 SPMananger 内部枚举类 OutputFormat 中: OutputFormat.MUXER_OUTPUT_MPEG_4:MP4格式 OutputFormat.MUXER_OUTPUT_FLV:FLV格式
OutputFormat.MUXER_OUTPUT_GIF:GIF格式

publicenum OutputFormat {
/** MPEG4 media file format */
MUXER_OUTPUT_MPEG_4, MUXER_OUTPUT_FLV, MUXER_OUTPUT_GIF
};

停止录制
通过 SPManager 的 stopRecord 方法来开始录制,其中 OutputFormat 用于指定录制格式;
public static boolean stopRecord()

如果录制的视频没有一帧视频数据,则视为无效的录制,会删除文件,并以 StateListener 的 SPManager.STATE_RECORD_UNREACHED_MINFILESIZE 消息通知;
如果因为进程异常结束等原因停止录制,则文件以.streampusher.tmp 后缀名存储;
如果录制视频结束,会以 StateListener 的 SPManager.STATE_RECORD_STOP 消息通知,并在消 息 msg 的 obj 中携带录制文件的地址,录制时长,录制文件大小,可以在这里对文件进行重命名或修改文件路径的操作;

21、自定义视频滤镜
硬编及软编均支持自定义 GPU 滤镜,为开发者提供基类 SPVideoFilter 自定义视频滤镜,滤 镜会在预览和推流端都生效。
具体步骤如下:
1)继承 SPVideoFilter 类创建自己的类,如 DemoFilter
2)调用 SPManager 中的 setFilter 接口将自己的 filter 设置给 SDK
setFilter(SPVideoFilter filter)设置单个 filter 生效,若 filter 为 null 则不生效
setFilter(List filter)设置单个 filter 生效
以下是 SPVideoFilter 类的分析

publicstaticfinal String NO_FILTER_VERTEX_SHADER =
"" + "attribute vec4 position;\n"
            + "attribute vec4 inputTextureCoordinate;\n" + "\n"
+ "varying vec2 textureCoordinate;\n" + "\n"
            + "void main()\n" + "{\n"
+ "gl_Position = position;\n"
            + "textureCoordinate = inputTextureCoordinate.xy;\n"
+ "}";
publicstaticfinal String NO_FILTER_FRAGMENT_SHADER =
"" + "varying highp vec2 textureCoordinate;\n" + "\n"
            + "uniform sampler2D inputImageTexture;\n" + "\n"
 + "void main()\n" + "{\n"
            + "gl_FragColor = texture2D(inputImageTexture,textureCoordinate);\n"
+ "}";
//构造方法,此处需要传入顶点和片段着色器,如传入为null,则用默认的NO_FILTER_VERTEX_SHADER、NO_FILTER_FRAGMENT_SHADER,如果是自己定义的着色器则要注意以下几点:顶点着色器中的定义的变量要和NO_FILTER_VERTEX_SHADER一样,如position、inputTextureCoordinate;片段着色器中定义的纹理ID也要和NO_FILTER_FRAGMENT_SHADER一样,如inputImageTexture

public SPVideoFilter(String vertex_shader, String fragment_shader)

    //编译顶点着色器和片段着色器之前回调
    protectedvoid onInit()

    //编译顶点着色器和片段着色器之后回调
    publicvoid onInitialized()

    //销毁滤镜,主要用来清理和释放资源
    protectedvoid onDestroy()

    //glDrawArrays之前调用
    protectedvoid onDrawArraysAfter()
    // glDrawArrays之后调用
    protectedvoid onDrawArraysPre()

    //获得uniform在shader中的位置指针
    protectedint getUniformLocation(String str)

    //设置uniform变量,location为uniform在shader中的位置指针
    protectedvoid set* (int location, ...)

demo 示例如下

List<SPVideoFilter>filters = new ArrayList<SPVideoFilter>();
filters.add(new VideoFilterDemo2());
filters.add(new VideoFilterDemo3());

//激活自定义滤镜
SPManager.setFilter(filters);

//使用自定义滤镜组替换自定义滤镜
SPManager.setFilter(new VideoFilterDemo1());

//关闭自定义滤镜或滤镜组
SPManager.setFilter(null);

注意:从 1.8.6 版本后,自定义滤镜和自定义滤镜组已从美颜模式中移除,SDK 内部处理流 程改为采集->美颜->滤镜->自定义滤镜->编码。自定义滤镜流程不再是先 setFilter 然后在使用 SPManager.swichFIlter 激活,而是调用 setFilter 接口后立即激活。

22、屏幕截图
我们提供了两种方式来实现截屏。
1)全屏截图
SPScreenShot.takeGlobalScreenShot();
这个截图方式使用 Android 5.0 提供的新接口 MediaProjection 实现。
这种方式的特点就是可以实现手机整个屏幕(包括虚拟键和状态栏等系统视图)内容的抓取。
api 的具体使用方法如下:

1.在AndroidManifest主配置文件中注册activity
<activity android:name="com.chinanetcenter.StreamPusher.sdk.SPScreenShot$SPScreenCaptureActivity"
    android:theme="@android:style/Theme.Translucent" />
这个activity在SDK内部生命,他是一个透明的activity,主要用于向用户请求抓取屏幕内容的权限。

2.调用截图接口
SPScreenShot shotter = new SPScreenShot(mContext);
shotter.takeGlobalScreenShot();

注意:Android 10 以后,该录屏接口必须依赖前端 Service 才能被调用,否则将抛出安全异 常:

java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION

此时需要客户自己注册一个 foregroundServiceType 为 mediaProjection 的 Service,并在调用截屏接口前,调用 startForeground 方法,将 Service 置为前端服务。

//1、在主配置文件中注册Service
<service android:name=".service.FloatWindowService"
android:foregroundServiceType="mediaProjection">
</service>

// 2、在调用接口前调用Service的startForeground接口
startForeground(1, notification);

//3、调用截屏接口
SPManager.takeGlobalScreenShot();

2)视图截图
SPSurfaceView. takeSurfaceShot();
视图截图并不是屏幕截图,它主要是返回 SPSurfaceView 中显示的一帧画面。但是我们可以 使用该接口结合布局文件截图的方法 View.getDrawingCache(true)实现屏幕内容的抓取。
该方法的优点就是可以定制截图显示的内容,可以支持 5.0 以下版本;缺点是无法抓取掉 statusbar 等系统显示的内容、且实现起来比较复杂。
使用此种方式实现屏幕截图可参考 demo 中提供的 ScreenContentRecorder.java 中的实现。

23、自定义视频源
为了满足用户对于视频源的定制化,如用户可能拥有更好的美颜方案等,我们开通了自定义视频源 yuv 接口和自定义视频源纹理接口。
1)开启自定义视频源 yuv 接口
pushYuvFrame(int yuvFormat, byte[] buffer, int pos, int length, long timestamp)
之前我们在设置推流分辨率时使用 SPConfig.setVideoResolution() 接口,通过该接口我们 可 以 设 置 推 流 分 辨 率 , 现 在 我 们 新 增 一 种 分 辨 率 的 枚 举 值 , VideoResolution.VIDEO_RESOLUTION_CUSTOM,当用户将分辨率设置为该值时即表示开 启了自定义视频源。此时初始化 SPManager 时,SDK 内部将跳过内部视频源的初始化过程,内部将不再开启摄像头采集,不再开启美颜功能等跟视频采集相关的功能。
具体开启自定义视频源的方法如下:

//设置自定义视频源的推流宽高,该宽高将会用于解析yuv视频数据
VideoResolution.VIDEO_RESOLUTION_CUSTOM.setWidth(heightInt);
VideoResolution.VIDEO_RESOLUTION_CUSTOM.setHeight(widthInt);
//设置开启自定义视频源mSPConfig.setVideoResolution(VideoResolution.VIDEO_RESOLUTION_CUSTOM, mVideoRatio, true);
...其他参数初始化
//初始化推流SDK
SPManager.init(this, mSPConfig);
//开始推流
SPManager.startPushStream();
//通过pushYuvFrame接口推自定义视频帧,我们提供的demo中是在Camera的
//onPreviewFrame回调方法中不断push自定义视频帧的
SPManager.pushYuvFrame(ImageFormat.NV21, yuvData, 0, data.length, System.nanoTime() / 1000);
//结束推流
SPManager.stopPushStream();
//释放资源
SPManager.release();

2)自定义视频源 yuv 接口支持的格式
目前仅支持 ImageFormat.NV21 和 ImageFormat.YUV_420_888 两种 yuv 格式。
内部推流使用 YUV_420_888 格式,如果传入的是 NV21 内部将转换为 YUV_420_888 格式后再进行推流。

3)自定义视频源纹理接口
pushTextureFrame(int textureId, long timestampNs)
视频宽高的设置与自定义视频源 yuv 接口相同,都是通过 SPConfig.setVideoResolution() 接口设置分辨率为 VideoResolution.VIDEO_RESOLUTION_CUSTOM,需要注意的是宽高要 和纹理宽高相同。

//设置自定义视频源的推流宽高,该宽高将会用于解析yuv视频数据
VideoResolution.VIDEO_RESOLUTION_CUSTOM.setWidth(heightInt);
VideoResolution.VIDEO_RESOLUTION_CUSTOM.setHeight(widthInt);
//设置开启自定义视频源mSPConfig.setVideoResolution(VideoResolution.VIDEO_RESOLUTION_CUSTOM, mVideoRatio, true);
...其他参数初始化
//初始化推流SDK
SPManager.init(this, mSPConfig);
//开始推流
SPManager.startPushStream();
//通过pushTextureFrame接口推自定义视频帧,我们提供的demo中是在
//GLSurfaceView的onDrawFrame回调方法中不断push自定义视频帧的
//该方法必须在opengl上下文环境线程中调用
SPManager.pushTextureFrame(mTimeTextureID[0], System.nanoTime() / 1000);
//结束推流
SPManager.stopPushStream();
//释放资源
SPManager.release();

4)自定义视频源纹理接口注意事项

  • pushTextureFrame 接口仅支持 api18(4.3)及以上版本;
  • 该方法必须在 opengl 上下文线程中调用,SDK 内部会在该线程中获取 opengl 共享上下 文 EGL14.eglGetCurrentContext();
  • 上下文必须是 EGL14 上下文;
  • SDK 内部会使用 opengl 进行绘制,外部调用完此接口后应重置 opengl 环境,防止 SDK 内部影响到外部 opengl 环境。

5)自定义视频源开启后失效的方法

SPManager 描述
switchCamera() 切换摄像头
flashCamera() 开关闪光灯
flipCamera() 摄像头成像校正
switchFilter() 切换美颜模式
cameraManualFocusMode() 手动聚焦与自动聚焦切换
showWaterMarkTime() 显示时间水印
hideWaterMarkTime() 关闭时间水印
showWaterMarkLogo() 显示图片水印
hideWaterMarkLogo() 隐藏图片水印
setFilter(SPVideoFilter filter) 自定义视频滤镜
setFilter(List filters) 自定义视频滤镜组
SPSurfaceView .takeSurfaceShot() 推流时的视图截图

列表中失效的方法都需要开发者自己重新定义。

6)自定义视频源开启后仍然可用的方法
帧率调节: SPConfig.setFps(int fps);
推流器内部将对推流做帧率控制,如果 SPManager.pushYuvFrame()传入的帧率大于设定 的推流帧率,则会开启丢帧策略,以保证推流帧率与设置帧率一致。

自适应码率:
自定义的分辨率和普通分辨率这是自适应码率的方式是一样的,设置方法可以参考码率自适应和自定义码率自适应上下限部分。

//自定义指定分辨率的码率自适应范围
VideoResolution.VIDEO_RESOLUTION_CUSTOM.setCustomBitrate(400*1024, 600 * 1024);

24、socks5 代理
开启 socks5 代理;
1)初始化 Socks5Proxy
设置 ip、port、username、pwd;
设置 enabled 为 true;

2)通过 SPManager 设置代理
SPManager.setConfig(SPManager.getConfig().setSocks5Proxy(proxyConfig));

如果连接不成功,可以在 OnErrorListener 中捕获到代理连接失败的信息,错误码为 1116;

25、YUV 预处理
YUV 预处理功能可以获得摄像头出来之后的 YUV 数据,并对其进行修改(美颜、加水印等), 在处理之后,会将该数据渲染到屏幕上,并推流到服务器;
使用方法:
使用 SPManager.setPreProcessHandler 设置一个 PreProcessHandler,在这里获取到摄像头出来的数据;
注意:
1)该方法需要在 SPManager.init()之后调用;
2)YUV 的预处理之后的数据需要及时渲染到屏幕上,因此不能在异步线程中做预处理;

SPManager.init(this, mSPConfig);
SPManager.setPreProcessHandler(new PreProcessHandler() {
       @Override
       public void handleYuvData(ByteBuffer y, ByteBuffer u, ByteBuffer v, int yStride, int uStride, int vStride, int width, int height) {
                    //在这里做YUV的预处理操作
                }
  });

26、预览自由横竖屏
由于直播前必须要确定视频流的宽高方向等信息,所以通常我们会在 AndroidManifest.xml 文件中 screenOrientation 属性或在 Activity 界面加载时通过 setRequestedOrientation 方法指定 直播推流界面屏幕方向。但是 sdk 本身也是支持预览阶段的横竖屏自动切换的,sdk 内部会 自动检测屏幕方向的变化,并在开始推流时以当前屏幕方向自动设置推流视频的宽高。由于 推流开始后要以固定宽高进行编码,故开始推流后,用户必须锁定屏幕方向,否则观众看到 的视频可能会有未知异常。

需要注意的是横竖屏自由切换后,我们需要更新水印的位置和大小,如果使用横屏的水印参 数到竖屏,水印会被拉伸,反之亦然。
重新调整水印的时机可在 SPManager.OnStateListener 的 onstate 回调中监听SPManager.STATE_VIDEO_RESOLUTION_CHANGED 消息回调。该消息会以 json 对象格式返回当前推流视频的宽高。

case SPManager.STATE_VIDEO_RESOLUTION_CHANGED:
  try {
      JSONObject jsonObject = new JSONObject((String)msg.obj);
      int w = jsonObject.optInt("w");
      int h = jsonObject.optInt("h");
      updateWaterMarkState(h > w ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE);
   } catch (JSONException e) {
        e.printStackTrace();
}
break;

27、使用滤镜
用户在预览和推流过程中可以使用滤镜接口提供的一些滤镜效果。接口如下: SPManager.setStyleFilterModel(String modelPath, int level);
modelPath:
滤镜配置文件路径,现在支持 3 种路径格式
1)以 assets://字符串开头,后面跟文件在工程 assets 目录下的路径
如:assets://filter/filter_antique.model
2)以 file://字符串开头,后面跟设备文件系统路径
如:file:///sdcard/ filter_antique.model
3)空字符串或 null 如
果该参数为空或 null 则表示关闭滤镜效果。
需要注意是 model 文件必须是 sdk 提供的且必须要保证文件存在(空字符串或 null 除外), 文件不存在或解析失败将会导致程序崩溃。

level:滤镜等级(0~10),除复古、怀旧、底片、黑白、素描外的大部分滤镜效果支持等级 的调节,不支持等级调节的滤镜该参数将被忽略。

使用实例:
第一步:将 demo 中 asserts 目录下的 filter 目录拷贝到自己项目中的 asserts 目录下,也可根据项目需要只拷贝自己需要的滤镜 model 文件:

网宿科技第四季度互联网报告对外正式发布
第二步:在资源文件中定义字符串数组(也可从 demo 中直接拷贝),注意和 asserts 目录 下文件对应

<string-array name="filter_path">
    <item ></item><!-- 0 -->
    <item >assets://filter/filter_antique.model</item><!-- 1 -->
    <item >assets://filter/filter_nostalgia.model</item><!-- 2 -->
    <item >assets://filter/filter_invert.model</item><!-- 3 -->
    <item >assets://filter/filter_gray.model</item><!-- 4 -->
    <item >assets://filter/filter_haze.model</item><!-- 5 -->
    <item >assets://filter/filter_brightness.model</item><!-- 6 -->
    <item >assets://filter/filter_cool.model</item><!-- 7 -->
    <item >assets://filter/filter_crayon.model</item><!-- 8 -->
    <item >assets://filter/filter_sketch.model</item><!-- 9 -->
    <item >assets://filter/filter_sepia.model</item><!-- 10 -->
    <item >assets://filter/filter_saturation.model</item><!-- 11 -->
    <item >assets://filter/filter_exposure.model</item><!-- 12 -->
    <item >assets://filter/filter_gamma.model</item><!-- 13 -->
    <item >assets://filter/filter_monochrome.model</item><!-- 14 -->
    <item >assets://filter/filter_swirl.model</item><!-- 15 -->
    <item >assets://filter/filter_vignette.model</item><!-- 16 -->
    <item >assets://filter/filter_fisheye.model</item><!-- 17 -->
    <item >assets://filter/filter_separate.model</item><!-- 18 -->
    <item >assets://filter/filter_emboss.model</item><!-- 19 -->
    <item >assets://filter/filter_sharpen.model</item><!-- 20 -->
    <item >assets://filter/filter_distortion.model</item><!-- 21 -->
    <item >assets://filter/filter_mosaic.model</item><!-- 22 -->
</string-array>

第三步:调用接口将文件路径设置到 sdk 中。

String modelPath = context.getResources().getStringArray(R.array.filter_path)[mCurrenStyleFilter];
SPManager.setStyleFilterModel(modelPath, progress));

28、OpenSL ES声音采集
音频采集可以选择采用 AudioRecord 接口,也可以选择使用 OpenSL ES 接口,默认 AudioRecord接口,配置如下:

//使用AudioRecord接口
AudioSourceMode audioSourceMode = SPManager.AudioSourceMode.AUDIORECORD_MODE;
//使用OpenSL ES接口
AudioSourceMode audioSourceMode = SPManager.AudioSourceMode.OPENSLES_MODE;
mSPConfig.setAudioSourceMode(audioSourceMode);

29、预览编码镜像
镜像分为预览镜像和编码镜像,比如主播使用前置摄像头看自己希望像自己照镜子,这时预览就是镜像的,但主播希望观众就像站在自己对面看自己,此时观众拉流到的就是非镜像的,即编码出的视频是非镜像的。但这只是一种应用场景,为了适应更多的应用场景并满足用户的定制化,我们允许用户自己设置预览和编码的镜像情况。
接口如下:

SPManager.setMirror(boolean previewMirror, boolean encodeMirror);

第一个参数表示主播自己预览到的画面是否镜像
第二个参数表示编码出的视频流的画面是否镜像

当通过该窗口设置了镜像后,只是当前打开的摄像头的预览和编码遵循用户设置的规则,其他摄像头仍然使用默认规则,如果用户调用该方法时没有打开摄像头,则摄像头打开时仍然使用默认规则。如果用户想为摄像头定制不同的规则,需要监听摄像头打开事件,根据打开的摄像头 id 重新设置镜像规则。如果用户从未调用过此接口,sdk内部默认前置摄像头镜像,后置摄像头不镜像,编码出的视频均不镜像。
(注意,sdk 内部也是监听摄像头开启事件设置的镜像参数,客户重新设置只不过是重新覆盖 sdk 内部参数,所以用户定制镜像参数时必须要在监听到摄像头打开事件后调用,否则可能是无效的)。

示例代码如下:

SPManager.setOnStateListener(new OnStateListener() {
    @Override
    public void onState(int what, String extra) {
        switch (what) {
        case SPManager.STATE_CAMERA_OPEN_SUCCESS:
            //从摄像头启动成功消息中拿到启动成功的摄像头ID
            mOpenedCameraId = Integer.parseInt(extra);
            boolean mirrorParams[] = new boolean[2];
            //用户通过此方法获取摄像头对应的镜像条件
            getMirrorParam(mirrorParams);
           //设置预览和推流是否镜像,该消息在摄像头线程回调,为了保证前后置摄像头切换时不会出现mirror切换延时,尽量在该回调中调用setMirror
           SPManager.setMirror(mirrorParams[0]/*previewMirror*/, mirrorParams[1]/*encodeMirror*/);
           break;
       }
   }
});

30、软编策略调整
由于软编画质和性能消耗有较大关系,用户可能对功耗和画面清晰度的权重有不同的权衡,鉴于此种情况的考虑,我们针对软编开发了质量优先和码率优先两种不同的编码策略。
用户可以在初始化的时候配置自己的软编策略(如果使用硬编可以忽略此选项)。

mSPConfig = SPManager.getConfig();//获取一个默认配置的SPConfig对象
mSPConfig.setRtmpUrl(url);
...
// SPConfig.SOFT_ENCODE_STRATEGY_MORE_QUALITY 质量优先(默认)
// SPConfig.SOFT_ENCODE_STRATEGY_MORE_BITRATE 码率优先
mSPConfig.setSoftEncodeStrategy(softEncodeStragety);
...
SPManager.init(this, mSPConfig);

两种策略对比:

软编策略 优势 缺点
质量优先 画面运动时编码视频画质更清晰 1.Cpu 消耗较码率优先略高
2.网络带宽消耗较码率优先略高
码率优先 码率稳定,性能好 画面运动时编码画质略差于质量优先

31、动态贴图
此版本新加动态贴图功能,用来满足客户在视频流中添加滚动条幅、gif 动画、png 动画等需 求。(该功能暂时不支持横竖屏自由切换)
SPManager.addSticker(SPStickerController);  //添加贴纸效果
SPManager.removeSticker(SPStickerController);  //移除贴纸效果

当用户通过 addSticker 方法添加贴纸效果后,系统会初始化一个镜像的和一个非镜像的贴纸 控制器,并回调 addSticker 方法中传入的对象的 onInit 方法,镜像和非镜像由 mirror 参数区 分。同理,销毁时也会回调 onDestroy 两次,并通过 mirror 参数区分。

贴纸控制器类设置到sdk后,系统在绘制每一帧的时候都会回调onDrawSticker方法,如果
编码和预览镜像方向一致,则参数中type为DrawStickerType.DOUBLE,如果编码和预览镜 像方向不一致,则分别回调编码和预览的,类型分别为 DrawStickerType.ENCODE和DrawStickerType.PREVIEW。如果该方法返回true,则立即回调getSticker方法,并将该方法 返回的图像粘贴到界面中,你并不需要每一帧图像都返回true去触发sdk回调 getSticker方法,sdk会自动为每一帧粘贴上一次返回的图像,如果用户想取消掉之前上传的图像,可以通过getSticker方法返回null,如果该贴纸效果不再需要,请调用removeSticker方法移除改贴纸效果。
详细代码可参考 demo 中 sticker 包中实现的演示类

//显示时间贴图
TimeStickerController stickerObject = new TimeStickerController(this);
stickerObject.zOrder = 2;
SPManager.addSticker(stickerObject);

//移除时间贴纸
SPManager.removeSticker(stickerObject);

六、备注

附 SDK 中相关的状态码,以备针对出现的不同情况进行 APP 层级处理。

网宿科技第四季度互联网报告对外正式发布
SDK 中相关方法的具体调用方式可参考提供的 Demo。