Android

商汤美颜

更新时间: 2025/06/12 14:21:35

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

准备工作

根据本文操作前,请确保您已经完成了以下设置:

功能原理

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

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

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

示例项目源码

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

第一步:集成商汤美颜

导入商汤美颜 SDK

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

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

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

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

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

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

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

获得商汤的 License 授权

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

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

  2. 调用如下方法检查 License 授权。

    Java// 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 表示授权成功。

第二步:初始化商汤美颜

  1. 初始化商汤美颜。

    • 初始化时需要加载所需模型,可能需要较长时间,会阻塞当前线程。因此,不建议在主线程或 RTC 的视频采集回调方法中执行此方法,建议在子线程中执行。
    • 如果在 RTC 视频回调触发时,模型加载尚未完成,则最初几帧可能无法进行美颜处理。因此,建议尽早初始化美颜 SDK。
    Javaif(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. 实现人脸检测。

    JavaNERtcEx.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);
    

第三步:图像处理和渲染

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

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

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

    JavaNERtcEx.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

    JavaNERtcEx.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 纹理格式。

示例代码 如下:

Javaprivate 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。
  • 如果传入的图像与检测方向不一致,则视频渲染时需要传入参数告诉渲染接口图像的方向,内部会根据传入的方向进行处理。

以下示例代码展示如何获取手机方向:

Javaprotected int getCurrentOrientation() {
    int dir = Accelerometer.getDirection();
    int orientation = dir - 1;
    if (orientation < 0) {
        orientation = dir ^ 3;
    }

    return orientation;
}
此文档是否对你有帮助?
有帮助
去反馈
  • 准备工作
  • 功能原理
  • 示例项目源码
  • 第一步:集成商汤美颜
  • 导入商汤美颜 SDK
  • 获得商汤的 License 授权
  • 第二步:初始化商汤美颜
  • 第三步:图像处理和渲染
  • 辅助工具
  • texture_oes 转 texture_2d
  • 获取手机方向角度