提交工单
咨询集成、功能及报价等问题
在实现基本的 Avatar 功能之前,请确保:
请先在 ZEGO 控制台 创建项目,并申请有效的 AppID 和 AppSign,详情请参考 控制台 - 项目管理 中的“项目信息”。
请联系 ZEGO 商务人员,提供自己项目的包名(ApplicationID),开通相关权限。
ZEGO 提供了一个创建虚拟形象的最简示例代码,可作为开发中的参考。
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 调用时序如下图:
请确认您项目的 applicationId
是否为申请权限时所提供的包名。(在“app/build.gradle”文件中查看)
引入头文件,准备基础工作。
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;
已参考 集成 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));
调用 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 中注册该回调,请注意回收以防止内存泄露。
}
});
通过 onStateChange 收到初始化 ZegoAvatarService
成功的通知后:
ZegoAvatarView
实例对象,用于后续展示虚拟形象创建传入虚拟人物形象的外观数据(捏脸、换装、妆容等)。// 虚拟形象对象
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 虚拟形象功能:
联系我们
文档反馈