Avatar 虚拟形象
  • iOS
  • Android : Java
  • 产品简介
  • 下载
  • 下载体验 App
  • 快速开始
    • 跑通示例源码
    • 集成 SDK
    • 创建虚拟形象
  • 基本功能
  • 最佳实践
  • 客户端 API
  • 服务端 API
  • 常见错误码
  • 常见问题
  • 文档中心
  • Avatar 虚拟形象
  • 快速开始
  • 创建虚拟形象

创建虚拟形象

更新时间:2023-11-10 15:39

前提条件

在实现基本的 Avatar 功能之前,请确保:

  • 已在项目中集成了 Avatar SDK,详情请参考 集成 SDK
  • 已开启摄像头权限。

开通 ZegoAvatar 权限

  1. 请先在 ZEGO 控制台 创建项目,并申请有效的 AppID 和 AppSign,详情请参考 控制台 - 项目管理 中的“项目信息”。

  2. 请联系 ZEGO 商务人员,提供自己项目的包名(ApplicationID),开通相关权限。

最简代码

ZEGO 提供了一个创建虚拟形象的最简示例代码,可作为开发中的参考。

创建虚拟形象代码 - Java XML 界面代码
package com.aws.demoqavatar;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.zego.avatar.ICharacterCallback;
import com.zego.avatar.IZegoInteractEngine;
import com.zego.avatar.ZegoAvatarErrorCode;
import com.zego.avatar.ZegoAvatarService;
import com.zego.avatar.ZegoAvatarServiceDelegate;
import com.zego.avatar.ZegoAvatarView;
import com.zego.avatar.ZegoCharacter;
import com.zego.avatar.bean.ZegoAvatarServiceState;
import com.zego.avatar.bean.ZegoExpressionDetectMode;
import com.zego.avatar.bean.ZegoServiceConfigV2;
import com.zego.core.ZALog;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;


public class FastActivity extends AppCompatActivity {

  // 从 ZEGO 控制台获取的 AppID,并已联系 ZEGO 技术支持开通 Avatar 权限
  // AppID 跟 applicationId 有绑定关系, 记得修改 app/build.gradle 下的 applicationId 为申请 AppID 时提供的值
  // 格式为 您的 AppID + L
  public final static long APP_ID = xxxxxxxxxxL;
  // 从 ZEGO 控制台获取的  AppSign
  public final static String APP_SIGN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

  private final static int CAMERA_CODE = 101;
  private String permissions[] = new String[]{
          Manifest.permission.INTERNET,
          Manifest.permission.CAMERA,
          Manifest.permission.WRITE_EXTERNAL_STORAGE,
          Manifest.permission.READ_EXTERNAL_STORAGE,
          Manifest.permission.RECORD_AUDIO};
  private Handler mMainHandler;
  // 用于显示虚拟形象的 View
  private ZegoAvatarView mZegoAvatarView;
  // 虚拟形象对象
  private ZegoCharacter mCharacter;
  // 表情随动引擎
  private IZegoInteractEngine mZegoInteractEngine;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_fast);
      mMainHandler = new Handler(Looper.getMainLooper());
      mZegoAvatarView = findViewById(R.id.zego_avatar_view);


      checkPermission();    
  }    
  public void checkPermission(){    
      // 检查是否有权限    
      if (checkCameraPermission()) {
          // 有权限,初始化 Avatar    
          init();    
      } else {
          // 没有权限,去申请权限    
          getPermission();    
      }    
  }    
  public void init(){    
      // 拷贝资源    
      copyAvatarRes();    
      // 初始化 Avatar 并创建虚拟形象    
      initAvatar();    
  }

  public void initAvatar(){
      String AIPath = getFilesDir().getAbsolutePath() + "/assets/AIModel.bundle"; //   AI 模型的绝对路径
      // 1 鉴权并初始化 ZegoAvatar SDK
      ZegoAvatarService.initWithConfigV2(this.getApplication(),new ZegoServiceConfigV2(AIPath, APP_ID,APP_SIGN));
      createCharacter();
  }

  // 接收 SDK 初始化结果回调并创建虚拟形象
  public void createCharacter(){
      // 注册 ZegoAvatarService 初始化状态回调
      ZegoAvatarService.addServiceObserver(new ZegoAvatarServiceDelegate(){
          @Override
          public void onError(ZegoAvatarErrorCode code, String desc) {
              mMainHandler.post(() ->{
                  ZALog.e("errorcode : " + code.getErrorCode() + ",desc : " + desc);
              });
          }

          @Override
          public void onStateChange(ZegoAvatarServiceState state) {
              // 2 创建虚拟形象
              if (state == ZegoAvatarServiceState.InitSucceed) {
                  FastActivity.this.runOnUiThread(() -> {

                      // Q 版男模基础资源的绝对路径
                      String boyPath = getFilesDir().getAbsolutePath() + "/assets/qhuman.bundle" + "/AssetBundles/bundle/qbody/Boy.prefab";
                      // Q 版女模基础资源的绝对路径
                      String girlPath = getFilesDir().getAbsolutePath() + "/assets/qhuman.bundle" + "/AssetBundles/bundle/qbody/Girl.prefab";

                      // 创建虚拟形象,以 Q 版男性角色为例
                      mCharacter = ZegoAvatarService.createCharacter(boyPath);

                      // 角色上屏,需要在 UI 线程调用。
                      mZegoAvatarView.setCharacter(mCharacter, mCharacterCallback);
                  });
                  ZegoAvatarService.removeServiceObserver(this);
              }}
      });
  }

  // 虚拟形象上屏成功回调
  private ICharacterCallback mCharacterCallback = new ICharacterCallback() {
      @Override
      public void onShow() {
          ZALog.e("onShow : ");
      }
  };


  // 申请权限
  private void getPermission() {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          if (!checkCameraPermission()) {
              // 先判断有没有权限 ,没有就在这里进行权限的申请
              ActivityCompat.requestPermissions(this, permissions, CAMERA_CODE);

          } else {
              // 说明已经获取到摄像头权限了
              Log.i("MainActivity", "已经获取了权限");
          }
      } else {
          // 说明 Android 系统版本低于 6.0,不需要动态获取权限。
          Log.i("MainActivity", "这个说明系统版本在6.0之下,不需要动态获取权限。");
      }
  }

  // 检查是否有权限
  private boolean checkCameraPermission() {
      for (String p : permissions) {
          if (ContextCompat.checkSelfPermission(this, p) != PackageManager.PERMISSION_GRANTED) {
              return false;
          }
      }
      return true;
  }

  /**    
   * 申请权限回调    
   * @param requestCode    
   * @param permissions    
   * @param grantResults    
   */    
  @Override    
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {    
      super.onRequestPermissionsResult(requestCode, permissions, grantResults);    
      if (requestCode == CAMERA_CODE) {    
          boolean isAllGrant = true;    
          for (int i = 0;i<grantResults.length ;i++) {    
              if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {    
                  isAllGrant = false;    
                  break;    
              }    
          }    
          if (isAllGrant) {    
              init();    
          }    
      }    
  }

  // 拷贝 Avatar 所需的资源
  // 较为耗时,可以选择子线程中拷贝。注意:子线程中拷贝的话,一定要等拷贝完成后才能使用 ZEGO Avatar SDK 相关接口。
  public void copyAvatarRes(){
      copyAssetsDir2Phone(this.getApplication(),
              "AIModel.bundle"/*apk 里的 assets 根目录*/,
              "assets"/* sd 卡里的目录, 值为:getFilesDir().getAbsolutePath() + File.separator + destPath */);
      copyAssetsDir2Phone(this.getApplication(),
              "qhuman.bundle", "assets");
      // 请先联系 ZEGO 技术支持获取 Packages 资源,并已参考 [集成 SDK](https://doc-zh.zego.im/article/14882) 文档,将该文件夹拷贝到您项目的 “assets” 文件夹下。
      copyAssetsDir2Phone(this.getApplication(),
              "Packages", "assets");
  }

  /**
   * 把 assets/${filePath} 目录中的所有内容拷贝到 手机的 Storage 的 ${destPath}/ 目录中
   *
   * @param activity activity 使用 CopyFiles 类的 Activity
   * @param filePath String 相对于 Android APK 内的 assets 目录的文件路径,如:AIModel.bundle
   * @param destPath String 拷贝的目标,如:/data/data/包名/files/assets/
   */
  public static void copyAssetsDir2Phone(Context activity, String filePath, String destPath) {
      try {
          String[] fileList = activity.getAssets().list(filePath);
          if (fileList.length > 0) { // 如果是目录
              File file = new File(activity.getFilesDir().getAbsolutePath() + File.separator + destPath + File.separator + filePath);
              if (file.exists()) {
                  deleteAllFiles(file);
              }
              file.mkdirs();// 如果文件夹不存在,则递归
              for (String fileName : fileList) {
                  filePath = filePath + File.separator + fileName;

                  copyAssetsDir2Phone(activity, filePath, destPath);

                  filePath = filePath.substring(0, filePath.lastIndexOf(File.separator));
                  Log.i("copyAssetsDir2Phone", filePath);
              }
          } else { // 如果是文件
              InputStream inputStream = activity.getAssets().open(filePath);
              File file = new File(activity.getFilesDir().getAbsolutePath() + File.separator + destPath + File.separator + filePath);
              if (file.exists()) {
                  boolean delete = file.delete();
              }
              if (!file.exists() || file.length() == 0) {
                  FileOutputStream fos = new FileOutputStream(file);
                  int len = -1;
                  byte[] buffer = new byte[1024];
                  while ((len = inputStream.read(buffer)) != -1) {
                      fos.write(buffer, 0, len);
                  }
                  fos.flush();
                  inputStream.close();
                  fos.close();
              }
          }
      } catch (IOException e) {
          Log.e("copyAssetsDir2Phone", "copy file faild, src:" + filePath + " dest:" + destPath);
          e.printStackTrace();
      }
  }

  private static void deleteAllFiles(File root) {
      File files[] = root.listFiles();
      if (files != null)
          for (File f : files) {
              if (f.isDirectory()) { // 判断是否为文件夹
                  deleteAllFiles(f);
                  try {
                      f.delete();
                  } catch (Exception e) {
                  }
              } else {
                  if (f.exists()) { // 判断是否存在
                      deleteAllFiles(f);
                      try {
                          f.delete();
                      } catch (Exception e) {
                      }
                  }
              }
          }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".FastActivity">

  <com.zego.avatar.ZegoAvatarView
      android:id="@+id/zego_avatar_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_constraintTop_toTopOf="parent"
      />


</androidx.constraintlayout.widget.ConstraintLayout>

如需了解最简代码各步骤说明,请参考 实现流程

实现流程

本节介绍如何使用 ZegoAvatar SDK 实现基本的图像处理功能,API 调用时序如下图:

1 鉴权并初始化 SDK

  1. 请确认您项目的 applicationId 是否为申请权限时所提供的包名。(在“app/build.gradle”文件中查看)

  2. 引入头文件,准备基础工作。

    import com.zego.avatar.ICharacterCallback;
    import com.zego.avatar.ZegoAvatarErrorCode;
    import com.zego.avatar.ZegoAvatarService;
    import com.zego.avatar.ZegoAvatarServiceDelegate;
    import com.zego.avatar.ZegoAvatarView;
    import com.zego.avatar.ZegoCharacter;
    import com.zego.avatar.bean.ZegoAvatarServiceState;
    import com.zego.avatar.bean.ZegoExpressionDetectMode;
    import com.zego.avatar.bean.ZegoServiceConfigV2;
    import com.zego.core.ZALog;
  3. 已参考 集成 SDK 文档把资源拷贝到 SD 卡并申请相关权限,随后调用 initWithConfigV2 接口,初始化 AvatarService。

    // 已把资源拷贝到 SD 卡,详情请参考 [集成 SDK\|_blank](https://doc-zh.zego.im/article/14882) 文档
    // 注意:线上使用时,拷贝前添加判断逻辑,避免多次拷贝。资源也可以从网络上动态下载。
    
    // 已获取相关权限(摄像头等),详情请参考 [集成 SDK\|_blank](https://doc-zh.zego.im/article/14882) 文档
    
    String AIPath = getFilesDir().getAbsolutePath() + "/assets/AIModel.bundle"; //   AI 模型的绝对路径
    // 从 ZEGO 控制台获取的 AppID,并已联系 ZEGO 技术支持开通 Avatar 权限
    // AppID 跟 applicationId 有绑定关系, 记得修改 app/build.gradle 下的 applicationId 为申请 AppID 时提供的值
    // 格式为 您的 AppID + L
    long APP_ID = xxxxxxxxxxL;
    // 从 ZEGO 控制台获取的  AppSign
    String APP_SIGN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    // 鉴权并初始化 ZegoAvatarService
    ZegoAvatarService.initWithConfigV2(this, AIPath, APP_ID, APP_SIGN));
  4. 调用 addServiceObserver 接口设置 ZegoAvatarServiceDelegate 代理。注册 onStateChange 回调,接收初始化状态的相关回调通知。

    mMainHandler = new Handler(Looper.getMainLooper());
    
    // 注册 ZegoAvatarService 初始化状态回调
    ZegoAvatarService.addServiceObserver(new ZegoAvatarServiceDelegate(){
        @Override
        public void onError(ZegoAvatarErrorCode code, String desc) {
            mMainHandler.post(() ->{
                ZALog.e("errorcode : " + code.getErrorCode() + ",desc : " + desc);
            });
        }
    
        @Override
        public void onStateChange(ZegoAvatarServiceState state) {
            // 初始化 ZegoAvatarService 的状态回调。
            // 可能会收到多次成功或者失败的消息回调
            // 如果在 Activity 中注册该回调,请注意回收以防止内存泄露。
        }
    });

2 创建虚拟形象

通过 onStateChange 收到初始化 ZegoAvatarService 成功的通知后:

  1. 调用 createAvatarView 创建一个 ZegoAvatarView 实例对象,用于后续展示虚拟形象创建传入虚拟人物形象的外观数据(捏脸、换装、妆容等)。
  2. 调用 createCharacter 接口,传入模型绝对路径,创建一个虚拟形象。
  3. 调用 setCharacter 接口,设置虚拟形象视图,让虚拟形象上屏。
// 虚拟形象对象
private ZegoCharacter mCharacter;
// 用于显示虚拟形象的 View
private ZegoAvatarView mZegoAvatarView;
mZegoAvatarView = findViewById(R.id.zego_avatar_view);


public void createCharacter(){
    // ZegoAvatarService 初始化状态回调
    ZegoAvatarService.addServiceObserver(new ZegoAvatarServiceDelegate(){
        @Override
        public void onError(ZegoAvatarErrorCode code, String desc) {
            mMainHandler.post(() ->{
                ZALog.e("errorcode : " + code.getErrorCode() + ",desc : " + desc);
            });
        }

        @Override
        public void onStateChange(ZegoAvatarServiceState state) {
            /// 2 初始化 Avatar 成功后,创建
            if (state == ZegoAvatarServiceState.InitSucceed) {
                FastActivity.this.runOnUiThread(() -> {

                    // Q 版男模基础资源的绝对路径
                    String boyPath = getFilesDir().getAbsolutePath() + "/assets/qhuman.bundle" + "/AssetBundles/bundle/qbody/Boy.prefab";
                    // Q 版女模基础资源的绝对路径
                    String girlPath = getFilesDir().getAbsolutePath() + "/assets/qhuman.bundle" + "/AssetBundles/bundle/qbody/Girl.prefab";

                    // 创建虚拟形象,以 Q 版男性角色为例
                    mCharacter = ZegoAvatarService.createCharacter(boyPath);

                    // 角色上屏,需要在 UI 线程调用。
                    mZegoAvatarView.setCharacter(mCharacter, mCharacterCallback);


                });
                ZegoAvatarService.removeServiceObserver(this);
            }
        }
    });
}

// 虚拟形象上屏成功回调
private ICharacterCallback mCharacterCallback = new ICharacterCallback() {
    @Override
    public void onShow() {
        ZALog.e("onShow : ");
    }
};

了解更多

到此为止,您已成功构建一个简单的虚拟形象 App。接下来,您可通过以下文档, 进一步体验 Avatar 虚拟形象功能:

本篇目录