实现字节火山美颜

  • Android

更新时间不可用

了解秀场直播在不同平台上的字节火山美颜方式,包括接入流程、关键配置和平台差异。

示例项目

本文介绍如何通过声网美颜场景化 API (Beauty API) 集成字节火山美颜功能到实时音视频互动中。声网在 GitHub 上提供开源 BeautyAPI 示例项目供你参考。

准备开发环境

前提条件

开始前,请确保满足以下前提条件:

创建项目并集成 SDK

按照如下步骤准备开发环境:

  1. 如需创建新项目,在 Android Studio 里,依次选择 Phone and Tablet > Empty Activity,创建 Android 项目。

    注意

    创建项目后,Android Studio 会自动开始同步 Gradle,稍等片刻,待同步成功后再进行下一步操作。

  2. 使用 Maven Central 将声网 RTC SDK 集成到你的项目中。

    1. 在 /Gradle Scripts/build.gradle(Project: <projectname>) 文件中添加如下代码,添加 Maven Central 依赖:

      Kotlin
      buildscript {
          repositories {
              ...
              mavenCentral()
          }
          ...
      }
      allprojects {
          repositories {
              ...
              mavenCentral()
          }
      }

      注意

      如果你的 Android 项目设置了 dependencyResolutionManagement,添加 Maven Central 依赖的方式可能存在差异。

    2. 在 /Gradle Scripts/build.gradle(Module: <projectname>.app) 文件中添加如下代码,将声网 RTC SDK 集成到你的 Android 项目中:

      Kotlin
      ...
      dependencies {
          ...
          // x.y.z,请填写具体的 SDK 版本号,如:4.0.0 或 4.1.0-1
          // 通过实时互动产品发版说明获取最新版本号
          implementation 'io.agora.rtc:full-sdk:x.y.z'
      }
  3. 将字节火山美颜 SDK 复制到你的项目中:请联系字节技术支持获取美颜 SDK、美颜资源、证书等文件。下载并解压文件,然后添加到美颜项目对应的文件路径下:

    文件项目路径
    resource/LicenseBag.bundleapp/src/main/assets/beauty_bytedance
    resource/ModelResource.bundle
    resource/ComposeMakeup.bundle
    resource/StickerResource.bundle
    表格内容超出当前宽度,可横向滚动查看更多列。
  4. 将声网美颜场景化 API 集成到你的项目中。添加 Android/lib_bytedance/src/main/java/io/agora/beautyapi/bytedance 目录下的文件到项目中,具体文件如下:

    • utils 文件夹
    • ByteDanceBeautyAPI.kt 文件
    • ByteDanceBeautyAPIImpl.kt 文件

    注意

    为方便后续代码升级,请不要修改你添加的这些文件的名称和路径。

  5. 添加网络及设备权限。

    在 /app/Manifests/AndroidManifest.xml 文件中,在 </application> 后面添加如下权限:

    XML
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <!-- 对于 Android 12.0 及以上且集成 v4.1.0 以下声网 RTC SDK 的设备,还需要添加以下权限 -->
     <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
     <!-- 对于 Android 12.0 及以上设备,还需要添加以下权限 -->
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
  6. 在 /Gradle Scripts/proguard-rules.pro 文件中添加如下行,以防止声网 RTC SDK 的代码被混淆:

    Kotlin
    -keep class io.agora.**{*;}
    -dontwarn javax.**
    -dontwarn com.google.devtools.build.android.**

实现美颜

本节展示如何在直播间内实现美颜功能,参考 API 时序图可查看总览。声网 RTC SDK 承担实时音视频的业务,字节火山美颜 SDK 提供美颜功能,声网 Beauty API 封装了两个 SDK 中的 API 调用逻辑以简化你需要实现的代码逻辑。通过 Beauty API,你可以实现基础美颜功能,但是如果你还需要更丰富的美颜效果,例如贴纸、美妆风格,请直接调用美颜 SDK 中的 API。

1. 初始化 RtcEngine

调用声网 RTC SDK 中的 create 创建并初始化 RtcEngine 对象。

Kotlin
// 初始化声网 RtcEngine
private val mRtcEngine by lazy {
    RtcEngine.create(RtcEngineConfig().apply {
        mContext = applicationContext
        // 传入你从控制台获取的声网项目的 APP ID
        mAppId = BuildConfig.AGORA_APP_ID
        mEventHandler = object : IRtcEngineEventHandler() {}
    })
}

2. 初始化 RenderManager

复制美颜证书、美颜资源到 sdcard 存储中。

Kotlin
// 初始化美颜 SDK
fun initBeautySDK(context: Context){
    val storagePath = context.getExternalFilesDir("")?.absolutePath ?: return
    val assetsPath = "beauty_bytedance"

    workerThread.execute {
        // copy license
        licensePath = "$storagePath/beauty_bytedance/LicenseBag.bundle/$LICENSE_NAME"
        FileUtils.copyAssets(context, "$assetsPath/LicenseBag.bundle/$LICENSE_NAME", licensePath)

        // copy models
        modelsPath = "$storagePath/beauty_bytedance/ModelResource.bundle"
        FileUtils.copyAssets(context, "$assetsPath/ModelResource.bundle", modelsPath)

        // copy beauty node
        beautyNodePath = "$storagePath/beauty_bytedance/ComposeMakeup.bundle/ComposeMakeup/beauty_Android_lite"
        FileUtils.copyAssets(context, "$assetsPath/ComposeMakeup.bundle/ComposeMakeup/beauty_Android_lite", beautyNodePath)

        // copy beauty 4items node
        beauty4ItemsNodePath = "$storagePath/beauty_bytedance/ComposeMakeup.bundle/ComposeMakeup/beauty_4Items"
        FileUtils.copyAssets(context, "$assetsPath/ComposeMakeup.bundle/ComposeMakeup/beauty_4Items", beauty4ItemsNodePath)

        // copy resharp node
        reSharpNodePath = "$storagePath/beauty_bytedance/ComposeMakeup.bundle/ComposeMakeup/reshape_lite"
        FileUtils.copyAssets(context, "$assetsPath/ComposeMakeup.bundle/ComposeMakeup/reshape_lite", reSharpNodePath)

        // copy makeup node
        makeupTianmeiNodePath = "$storagePath/beauty_bytedance/ComposeMakeup.bundle/ComposeMakeup/style_makeup/tianmei"
        FileUtils.copyAssets(context, "$assetsPath/ComposeMakeup.bundle/ComposeMakeup/style_makeup/tianmei", makeupTianmeiNodePath)

        // copy stickers
        stickerPath = "$storagePath/beauty_bytedance/StickerResource.bundle/stickers"
        FileUtils.copyAssets(context, "$assetsPath/StickerResource.bundle/stickers", stickerPath)
    }
}

在 GL 线程 (Graphics Library Thread) 中通过美颜 SDK 的 initEffect 方法初始化 RenderManager 实例。

Kotlin
// GL 线程中
// 初始化美颜 SDK 中的 RenderManager 实例
fun initEffect(context: Context){
    val ret = renderManager.init(
        context,
        modelsPath, licensePath, false, false, 0
    )
    if(!checkResult("RenderManager init ", ret)){
        return
    }
    renderManager.useBuiltinSensor(true)
    renderManager.set3Buffer(false)
    renderManager.appendComposerNodes(arrayOf(beautyNodePath, beauty4ItemsNodePath, reSharpNodePath))
    renderManager.loadResourceWithTimeout(-1)
}

3. 初始化 Beauty API

调用 createByteDanceBeautyAPI 创建 Beauty API 对象。Beauty API 对象基于 RenderManager 对象封装。

Kotlin
// 创建 Beauty API 对象
private val mByteDanceApi by lazy {
    createByteDanceBeautyAPI()
}

调用 initialize 初始化 Beauty API 对象。你需要在 config 参数中传入如下字段:

  • applicationContext:传入 Android Context(上下文)。
  • mRtcEngine:传入之前初始化的 RtcEngine 对象。
  • renderManager:传入之前初始化的 RenderManager 对象。
  • captureMode:视频的采集模式:
    • 如果你使用声网模块采集视频,请传入 CaptureMode.Agora。
    • 如果自定义采集视频,请传入 CaptureMode.Custom。
  • statsEnable:是否开启美颜统计数据回调。true 代表开启,false 代表不开启。开启后,会有周期性的 onBeautyStats 回调事件。
  • statsDuration:美颜统计数据回调的周期。单位为毫秒。
  • cameraConfig:设置视频镜像模式。如果在初始化 Beauty API 后你想修改镜像模式,可以调用 Beauty API 的 updateCameraConfig。
  • eventCallback:你希望监听的回调事件。
Kotlin
// 初始化 Beauty API 对象
mByteDanceApi.initialize(
    Config(
        // Android Context(上下文)
        applicationContext,
        // RtcEngine
        mRtcEngine,
        // 美颜特效管理器
        renderManager,
        // 设置视频采集模式
        // CaptureMode.Agora 意味着使用声网模块采集视频
        // CaptureMode.Custom 意味着使用开发者自定义采集视频
        captureMode = if (isCustomCaptureMode) CaptureMode.Custom else
        CaptureMode.Agora,
        // 是否开启美颜统计数据
        // 开启后,会有周期性的 onBeautyStats 回调事件
        statsEnable = true,
        // 设置美颜统计数据的统计区间为 1000 毫秒(默认)
        statsDuration = 1000,
        // 配置视频镜像模式
        cameraConfig = cameraConfig,
        // 用于监听 Beauty API 的回调事件
        eventCallback = EventCallback(
            // 美颜统计数据回调
            onBeautyStats = {stats ->
                Log.d(TAG, "BeautyStats stats = $stats")
            },
            // GL 线程中
            // 美颜特效初始化完成回调
            onEffectInitialized = {
                ByteDanceBeautySDK.initEffect(applicationContext)
                Log.d(TAG, "onEffectInitialized")
            },
            // GL 线程中
            // 美颜特效销毁回调
            onEffectDestroyed = {
                ByteDanceBeautySDK.unInitEffect()
                Log.d(TAG, "onEffectInitialized")
            }
        )
    ))

4. 开启美颜

调用 Beauty API 的 enable 方法并将参数设为 true 开启美颜。

Kotlin
mByteDanceApi.enable(true)

5. 开启视频采集

开发者可以使用声网模块采集视频,也可以自定义采集视频。本节介绍在这两种场景下如何开启视频采集。

使用声网模块采集视频

使用声网模块采集视频时,你需要先调用 RtcEngine 类的 enableVideo 开启声网 RTC SDK 的视频模块,然后调用 Beauty API 的 setupLocalVideo 开启本地视图。

Kotlin
// 开启视频模块
mRtcEngine.enableVideo()
// 设置本地视图
mByteDanceApi.setupLocalVideo(mBinding.localVideoView, Constants.RENDER_MODE_FIT)

自定义视频采集

使用自定义视频采集时,你需要进行如下操作:

  1. 调用 RtcEngine 类的 enableVideo 开启声网 RTC SDK 的视频模块。
  2. 开启自定义视频采集。可以使用声网 RTC SDK 的 API 开启视频自采集,也可以使用第三方视频自采集。如下示例代码展示通过声网 RTC SDK 的 API 进行的自定义视频采集,具体逻辑为通过 RtcEngine 类的 registerVideoFrameObserver 注册原始视频数据观测器并在其中实现 onCaptureVideoFrame 函数。
  3. 完成视频自采集后,通过 Beauty API 的 onFrame 函数将外部自采集的视频数据包装成 VideoFrame 传入并进行处理。当处理结果不为 SKIPPED(忽略)时,VideoFrame 里的视频 Buffer 数据会被替换成美颜处理过后的数据,你可以取出处理后的 Buffer 数据传入声网 RTC SDK。
Kotlin
// 开启视频模块
mRtcEngine.enableVideo()
// 注册原始视频数据观测器
// 自定义视频采集时,即 CaptureMode 为 Custom 时,你需要注册原始视频观测器
mRtcEngine.registerVideoFrameObserver(object : IVideoFrameObserver {

    override fun onCaptureVideoFrame(
        sourceType: Int,
        videoFrame: VideoFrame?
        ) = when (mByteDanceApi.onFrame(videoFrame!!)) {
            // 当处理结果为 SKIPPED(忽略)时,代表你丢帧,即外部自采集的视频数据不传入声网 RTC SDK
            // 当处理结果为其他时,外部自采集的视频数据传入声网 RTC SDK
            ErrorCode.ERROR_FRAME_SKIPPED.value -> false
            else -> true
        }

    // 设置是否对原始视频数据作镜像处理
    override fun getMirrorApplied() = mByteDanceApi.getMirrorApplied()

    // 设置观测点为本地采集时的视频数据
    override fun getObservedFramePosition() = IVideoFrameObserver.POSITION_POST_CAPTURER

    // override 视频观测器中的其他回调函数
    ...
})

6. 加入频道

调用 RtcEngine 类的 joinChannel 加入频道,同时传入如下参数:

  • token:用于鉴权的动态密钥。如果在创建声网项目时启用调试模式,那么将 token 参数传空。如果启用安全模式,那么你先参考使用 Token 鉴权在你的业务服务端生成 Token,然后将生成的 Token 传入该参数。
  • channelId:频道名。
  • options:频道媒体设置选项。
Kotlin
mRtcEngine.joinChannel(null, mChannelName, 0, ChannelMediaOptions().apply {
    // 设置频道场景为直播
    channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING
    // 设置用户角色为主播,主播可以在频道里发布和订阅音视频流
    clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
    // 设置是否发布摄像头采集的视频流(适用于使用声网模块采集视频的情况)
    publishCameraTrack = true
    // 设置是否发布自定义采集的视频流(适用于自定义采集视频的情况)
    publishCustomVideoTrack = false
    // 设置是否发布麦克风采集的音频流
    publishMicrophoneTrack = false
    // 设置进入频道时是否自动订阅频道内其他用户的音频流
    autoSubscribeAudio = false
    // 设置进入频道时是否自动订阅频道内其他用户的视频流
    autoSubscribeVideo = true
})

7.(可选)更新摄像头配置

Beauty API 默认的镜像行为如下:

  • 使用设备前置摄像头时,本地和远端视图都使用镜像。
  • 使用设备后置摄像头时,本地和远端视图都不使用镜像。

如果默认的摄像头配置不满足你的业务需求,你可以参考本节更新摄像头配置。具体步骤如下:

调用 Beauty API 的 updateCameraConfig 并通过 cameraConfig 配置前置和后置摄像头的镜像效果。所有镜像类型包含如下:

  • MIRROR_LOCAL_REMOTE:本地和远端视图都使用镜像。前置摄像头默认使用该配置。
  • MIRROR_LOCAL_ONLY:本地视图使用镜像,远端视图不使用镜像。建议在直播画面中会出现告示牌或者文字的情况下使用该配置。
  • MIRROR_REMOTE_ONLY:本地视图不使用镜像,远端视图使用镜像。
  • MIRROR_NONE:本地和远端视图都不使用镜像。后置摄像头默认使用该配置。
Kotlin
val isFront = mByteDanceApi.isFrontCamera()
// 判断 isFrontCamera
if (isFront) {
    cameraConfig = CameraConfig(
        // 通过 frontMirror 配置前置摄像头
        frontMirror = when (cameraConfig.frontMirror) {
            MirrorMode.MIRROR_LOCAL_REMOTE -> MirrorMode.MIRROR_LOCAL_ONLY
            MirrorMode.MIRROR_LOCAL_ONLY -> MirrorMode.MIRROR_REMOTE_ONLY
            MirrorMode.MIRROR_REMOTE_ONLY -> MirrorMode.MIRROR_NONE
            MirrorMode.MIRROR_NONE -> MirrorMode.MIRROR_LOCAL_REMOTE
        },
        backMirror = cameraConfig.backMirror
    )
} else {
    cameraConfig = CameraConfig(
        frontMirror = cameraConfig.frontMirror,
        // 通过 backMirror 配置后置摄像头
        backMirror = when (cameraConfig.backMirror) {
            MirrorMode.MIRROR_NONE -> MirrorMode.MIRROR_LOCAL_REMOTE
            MirrorMode.MIRROR_LOCAL_REMOTE -> MirrorMode.MIRROR_LOCAL_ONLY
            MirrorMode.MIRROR_LOCAL_ONLY -> MirrorMode.MIRROR_REMOTE_ONLY
            MirrorMode.MIRROR_REMOTE_ONLY -> MirrorMode.MIRROR_NONE
        }
    )
}
// 更新摄像头配置
mByteDanceApi.updateCameraConfig(cameraConfig)

8. 离开频道

调用 RtcEngine 类的 leaveChannel 离开频道。

Kotlin
mRtcEngine.leaveChannel()

9. 销毁资源

调用 Beauty API 的 release 销毁 Beauty API。

Kotlin
mByteDanceApi.release()

在 GL 线程 (Graphics Library Thread) 中调用美颜 SDK 的 unInitEffect 销毁 RenderManager。

Kotlin
fun unInitEffect(){
    renderManager.release()
}

调用 RtcEngine 的 destroy 销毁 RtcEngine。

Kotlin
RtcEngine.destroy()

API 时序图