商汤美颜
更新时间: 2024/09/18 16:26:13
NERTC SDK 支持接入商汤(SenseAR Effects SDK)等第三方专业美颜滤镜厂商,实现美颜、美妆、滤镜、贴纸等美颜特效。在娱乐社交、在线教育等场景中,您可以快速构建具备美颜特效能力的应用,让用户在进行视频通话或直播时,呈现更良好的肌肤状态和精神面貌。
准备工作
- 请联系商汤技术支持获取如下文件:
- 商汤美颜 SDK
- 商汤美颜 SDK 的 License 证书
- 商汤美颜资源文件
- 已实现音视频通话
功能原理
-
用户A 通过 NERTC SDK 的
setVideoCallback
接口,将采集到的视频图像数据通过回调传给商汤美颜 SDK。 -
商汤美颜 SDK 通过回调获取视频图像数据,进行美颜处理后,通过参数返回给 NERTC SDK。
-
NERTC SDK 将美颜后的数据进行编码和传输。
示例项目源码
网易云信提供 商汤美颜示例项目源码,您可以参考该源码实现商汤美颜。
步骤1:集成商汤美颜
导入商汤美颜 SDK
-
将商汤美颜 SDK 的
STMobileJNI-xxx-release.aar
⽂件保存到项目文件夹的/app/libs
目录下。 -
将商汤美颜的模型⽂件(models)和素材⽂件(贴纸素材、美妆素材和滤镜素材)拷⻉到项目文件夹的
/app/src/main/assets
目录下。 -
通过 Gradle 集成商汤美颜 SDK。
- 在项目对应模块的
build.gradle
文件中,加⼊查询路径:
android{ repositories { flatDir { dirs 'libs' } } }
- 在
build.gradle
文件的dependencies
中添加依赖。
implementation(name:'STMobileJNI-xxx-release',ext:'aar')
- 在项目对应模块的
-
配置防代码混淆
代码混淆是指使用简短无意义的名称重命名类、方法、属性等,增加逆向工程的难度,保障 Android 程序源码的安全性。为了避免因重命名类,导致调用 商汤美颜 SDK 异常,您需要配置防代码混淆。
在
proguard-rules.pro
文件中,为商汤美颜 SDK 添加 -keep 类的配置,防止混淆商汤美颜 SDK 公共类名称。-keep class com.sensetime.stmobile.* { *;} -keep class com.sensetime.stmobile.model.* { *;}
获得商汤的 License 授权
商汤美颜 SDK 必须获得 License 授权后方可使用。
-
复制
SenseME.lic
文件到 app 的assets/license
目录下 -
调用如下方法检查 License 授权。
// USING_SERVER_LICENSE 改为 false. public synchronized static boolean checkLicense(final Context context){ if(USING_SERVER_LICENSE){ Log.i(TAG,"start checkLicense from Server"); return checkLicenseFromServer(context); }else{ Log.i(TAG, "start checkLicense Local"); return checkLicenseFromLocal(context); } }
返回结果为 true 表示授权成功。
步骤2:初始化商汤美颜
-
初始化商汤美颜。
- 初始化时需要加载所需模型,可能需要较长时间,会阻塞当前线程。因此,不建议在主线程或 RTC 的视频采集回调方法中执行此方法,建议在子线程中执行。
- 如果在 RTC 视频回调触发时,模型加载尚未完成,则最初几帧可能无法进行美颜处理。因此,建议尽早初始化美颜 SDK。
if(licenseCheckResult()){ //初始化非OpengGL相关的句柄,包括人脸检测及人脸属性 NativeManager.getInstance().setListener(status -> { if(NativeManager.STATUS_ADD_MESH_DONE == status) { STFaceMeshList faceMeshList = NativeManager.getInstance().getHumanActionNative().getFaceMeshList(); if (null == faceMeshList) return; NativeManager.getInstance().getEffectNative().setFaceMeshList(faceMeshList); } }); ThreadUtils.getInstance().runOnSubThread(() -> NativeManager.getInstance().initHumanAction(mSTHumanActionNative, mHumanActionCreateConfig)); //人脸检测 ThreadUtils.getInstance().runOnSubThread(() -> NativeManager.getInstance().createFaceAttributeHandle()); //美颜相关 NativeManager.getInstance().createEffectNative(STMobileEffectNative.EFFECT_CONFIG_NONE); //设置美颜相关属性 setBasicBeauty(); }
-
实现人脸检测。
NERtcEx.getInstance().setVideoCallback(neRtcVideoFrame -> { if(NativeManager.getInstance.getEffectNative == null) { Log.i(TAG, "beautiful module not init."); return ; } //人脸检测前,必须要执行,否则美颜不生效。 synchronized (mHumanActionLock) { mDetectConfig = NativeManager.getInstance().getEffectNative().getHumanActionDetectConfig(); mSTHumanActionNative.nativeHumanActionPtrCopy(); } mDetectThreadPool.submit(() -> { //检测人脸 mDetectConfig = NativeManager.getInstance().getEffectNative().getHumanActionDetectConfig(); int ret = mSTHumanActionNative.nativeHumanActionDetectPtr(neRtcVideoFrame.data, STCommonNative.ST_PIX_FMT_YUV420P, mDetectConfig, convertOritation(neRtcVideoFrame.rotation), neRtcVideoFrame.width, neRtcVideoFrame.height); //商汤返回的faceInfo的size不为空,说明检测人脸成功 if(ret == 0) { STHumanAction humanAction = mSTHumanActionNative.getNativeHumanAction(); STMobileFaceInfo[] faceInfos = humanAction.getFaceInfos(); if(faceInfos != null) { Log.i(TAG, "faceInfos size : " + faceInfos.length); } else { Log.i(TAG, "faceInfos is empty."); } } //todo 美颜 && 渲染 见下文 //...... }); },true);
步骤3:图像处理和渲染
-
在成功加入房间后,调用 NERTC 的
enableLocalVideo
接口,开启本地视频采集,请设置streamType
为kNERtcVideoStreamTypeMain
,否则美颜效果不会生效。 -
调用 NERTC 的
setVideoCallback
接口,设置视频采集回调的数据格式。setVideoCallback
的textureWithI420
参数请设置为 true,表示 RTC SDK 会返回 texture_oes 和 YUV I420 两种格式的 video frame。texture_oes 纹理格式用于交给商汤美颜处理和渲染, YUV I420 用于人脸识别操作。NERtcEx.getInstance().setVideoCallback(videoFrame -> { //... 这里是rtc返回的视频前处理回调,绑定了opengl环境 }, true);
-
美颜/渲染。
在
onVideoCallback
回调中,将原始的视频图像数据发给商汤美颜 SDK,美颜 SDK 将美颜处理后的数据返回给 NERTC SDK,NERTC SDK 对美颜后的数据进行预览以及编码发送。由于商汤美颜对外接口,不接受 texture_oes 纹理格式,所以首先要将 RTC 返回的 texture_oes 格式纹理转为 texture_2d 纹理格式,实现方法请参见texture_oes 转 texture_2d。
NERtcEx.getInstance().setVideoCallback(neRtcVideoFrame -> { if(NativeManager.getInstance.getEffectNative == null) { Log.i(TAG, "beautiful module not init."); return ; } //将texture_oes转为texture_2d纹理格式 int originTexture = convertToRGB(neRtcVideoFrame, false); //todo 人脸检测 见上文 //输入纹理 STEffectTexture stEffectTexture = new STEffectTexture(originTexture, neRtcVideoFrame.width, neRtcVideoFrame.height, 0); //输出纹理 if (mBeautifyTextureId == null) { mBeautifyTextureId = new int[1]; com.netease.slbeautify.utils.GlUtil.initEffectTexture(neRtcVideoFrame.width, neRtcVideoFrame.height, mBeautifyTextureId, GLES20.GL_TEXTURE_2D); } STEffectTexture stEffectTextureOut = new STEffectTexture(mBeautifyTextureId[0], neRtcVideoFrame.width, neRtcVideoFrame.height, 0); //渲染接口输入参数 STEffectRenderInParam stEffectRenderInParam = new STEffectRenderInParam(mSTHumanActionNative.getNativeHumanActionPtrCopy(), null, getCurrentOrientation(), getCurrentOrientation(), false, null, stEffectTexture, null); //渲染接口输出参数 STEffectRenderOutParam stEffectRenderOutParam = new STEffectRenderOutParam(stEffectTextureOut, null, null); NativeManager.getInstance().getEffectNative().render(stEffectRenderInParam, stEffectRenderOutParam, false); //更新商汤处理过的textureId,更改纹理格式,将video frame buffer 交给云信侧处理 if (stEffectRenderOutParam.getTexture() != null) { neRtcVideoFrame.textureId = stEffectRenderOutParam.getTexture().getId(); neRtcVideoFrame.format = NERtcVideoFrame.Format.TEXTURE_RGB; return true; } },true);
辅助工具
texture_oes 转 texture_2d
由于商汤美颜接口不接受 texture_oes 纹理格式,所以首先要将 RTC 返回的 texture_oes 格式纹理转为 texture_2d 纹理格式。
示例代码如下:
private int convertToRGB(NERtcVideoFrame videoFrame, boolean mirror) {
Matrix drawMatrix = new Matrix();
drawMatrix.reset();
drawMatrix.preTranslate(0.5f, 0.5f);
if (mirror) {
drawMatrix.preScale(1f, -1f);
}
drawMatrix.preTranslate(-0.5f, -0.5f);
rgbTextureFrameBuffer.setSize(videoFrame.width, videoFrame.height);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, rgbTextureFrameBuffer.getFrameBufferId());
GlUtil.checkNoGLES2Error("glBindFramebuffer");
float[] finalGlMatrix = RendererCommon.convertMatrixFromAndroidGraphicsMatrix(drawMatrix);
drawer.drawOes(videoFrame.textureId, finalGlMatrix, videoFrame.width, videoFrame.height,0,0,videoFrame.width, videoFrame.height);
GlUtil.checkNoGLES2Error("convertToRGB");
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
return rgbTextureFrameBuffer.getTextureId();
}
获取手机方向角度
这个角度取决于图像的方向,检测是单向的。
- 如果传入的图像与检测方向一致,则视频渲染时只需传入 0。
- 如果传入的图像与检测方向不一致,则视频渲染时需要传入参数告诉渲染接口图像的方向,内部会根据传入的方向进行处理。
以下示例代码展示如何获取手机方向:
protected int getCurrentOrientation() {
int dir = Accelerometer.getDirection();
int orientation = dir - 1;
if (orientation < 0) {
orientation = dir ^ 3;
}
return orientation;
}