商汤美颜

更新时间: 2024/03/15 17:26:15

NERTC SDK 支持接入商汤(SenseAR Effects SDK)等第三方专业美颜滤镜厂商,实现美颜、美妆、滤镜、贴纸等美颜特效。在娱乐社交、在线教育等场景中,您可以快速构建具备美颜特效能力的应用,让用户在进行视频通话或直播时,呈现更良好的肌肤状态和精神面貌。

准备工作

  • 请联系商汤技术支持获取如下文件:
    • 商汤美颜 SDK
    • 商汤美颜 SDK 的 License 证书
    • 商汤美颜资源文件
  • 已实现音视频通话

功能原理

商汤美颜原理.png

  1. 用户A 通过 NERTC SDK 的 setVideoCallback 接口,将采集到的视频图像数据通过回调传给商汤美颜 SDK。

  2. 商汤美颜 SDK 通过回调获取视频图像数据,进行美颜处理后,通过参数返回给 NERTC SDK。

  3. NERTC SDK 将美颜后的数据进行编码和传输。

示例项目源码

网易云信提供 商汤美颜示例项目源码,您可以参考该源码实现商汤美颜。

步骤1:集成商汤美颜

导入商汤美颜 SDK

  1. 将商汤美颜 SDK 的 STMobileJNI-xxx-release.aar ⽂件保存到项目文件夹的 /app/libs 目录下。

  2. 将商汤美颜的模型⽂件(models)和素材⽂件(贴纸素材、美妆素材和滤镜素材)拷⻉到项目文件夹的 /app/src/main/assets 目录下。

  3. 通过 Gradle 集成商汤美颜 SDK。

    1. 在项目对应模块的 build.gradle 文件中,加⼊查询路径:
    android{
        repositories {
            flatDir {
            dirs 'libs'
            }
        }
    }
    
    1. build.gradle 文件的 dependencies 中添加依赖。
    implementation(name:'STMobileJNI-xxx-release',ext:'aar')
    
  4. 配置防代码混淆

    代码混淆是指使用简短无意义的名称重命名类、方法、属性等,增加逆向工程的难度,保障 Android 程序源码的安全性。为了避免因重命名类,导致调用 商汤美颜 SDK 异常,您需要配置防代码混淆。

    proguard-rules.pro 文件中,为商汤美颜 SDK 添加 -keep 类的配置,防止混淆商汤美颜 SDK 公共类名称。

    -keep class com.sensetime.stmobile.* { *;}
    -keep class com.sensetime.stmobile.model.* { *;}
    

获得商汤的 License 授权

商汤美颜 SDK 必须获得 License 授权后方可使用。

  1. 复制 SenseME.lic 文件到 app 的 assets/license 目录下

  2. 调用如下方法检查 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:初始化商汤美颜

  1. 初始化商汤美颜。

    • 初始化时需要加载所需模型,可能需要较长时间,会阻塞当前线程。因此,不建议在主线程或 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();
    }
    
  2. 实现人脸检测。

    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:图像处理和渲染

  1. 在成功加入房间后,调用 NERTC 的 enableLocalVideo 接口,开启本地视频采集,请设置 streamTypekNERtcVideoStreamTypeMain,否则美颜效果不会生效。

  2. 调用 NERTC 的 setVideoCallback 接口,设置视频采集回调的数据格式。

    setVideoCallbacktextureWithI420 参数请设置为 true,表示 RTC SDK 会返回 texture_oes 和 YUV I420 两种格式的 video frame。texture_oes 纹理格式用于交给商汤美颜处理和渲染, YUV I420 用于人脸识别操作。

    NERtcEx.getInstance().setVideoCallback(videoFrame -> {
        //... 这里是rtc返回的视频前处理回调,绑定了opengl环境
    }, true);
    
  3. 美颜/渲染。

    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;
}
此文档是否对你有帮助?
有帮助
去反馈
  • 准备工作
  • 功能原理
  • 示例项目源码
  • 步骤1:集成商汤美颜
  • 导入商汤美颜 SDK
  • 获得商汤的 License 授权
  • 步骤2:初始化商汤美颜
  • 步骤3:图像处理和渲染
  • 辅助工具
  • texture_oes 转 texture_2d
  • 获取手机方向角度