集成完直播后提交代码
This commit is contained in:
190
LiveBeauty/README.md
Normal file
190
LiveBeauty/README.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# **LiveBeauty**
|
||||
|
||||
## **一、模块介绍**
|
||||
|
||||
### **模块职责**
|
||||
|
||||
**LiveBeauty**模块,为**美颜前处理**模块,主要负责对Queen SDK的封装,一站式使用美颜处理。
|
||||
|
||||
## **二、前置条件**
|
||||
|
||||
**1.申请License**
|
||||
|
||||
参考文档:[获取美颜特效SDK License](https://help.aliyun.com/zh/live/user-guide/obtain-a-license-of-queen-sdk/)
|
||||
|
||||
**2.配置License**
|
||||
|
||||
参考项目 README.md 文档里面的**配置License**环节。
|
||||
|
||||
## **三、接入流程**
|
||||
|
||||
**1.引入插件模块**
|
||||
|
||||
如果使用 LiveBeauty 功能,请注意引入 live_queenbeauty 模块:
|
||||
|
||||
```groovy
|
||||
implementation project(':LiveBeauty:live_queenbeauty')
|
||||
```
|
||||
|
||||
**2.美颜处理逻辑**
|
||||
|
||||
```java
|
||||
private BeautyInterface mBeautyManager;
|
||||
|
||||
mAlivcLivePusher.setCustomFilter(new AlivcLivePushCustomFilter() {
|
||||
@Override
|
||||
public void customFilterCreate() {
|
||||
initBeautyManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int customFilterProcess(int inputTexture, int textureWidth, int textureHeight, long extra) {
|
||||
if (mBeautyManager == null) {
|
||||
return inputTexture;
|
||||
}
|
||||
|
||||
return mBeautyManager.onTextureInput(inputTexture, textureWidth, textureHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customFilterDestroy() {
|
||||
destroyBeautyManager();
|
||||
Log.d(TAG, "customFilterDestroy---> thread_id: " + Thread.currentThread().getId());
|
||||
}
|
||||
});
|
||||
|
||||
private void initBeautyManager() {
|
||||
if (mBeautyManager == null) {
|
||||
Log.d(TAG, "initBeautyManager start");
|
||||
// 从v6.2.0开始,基础模式下的美颜,和互动模式下的美颜,处理逻辑保持一致,即:QueenBeautyImpl;
|
||||
mBeautyManager = BeautyFactory.createBeauty(BeautySDKType.QUEEN, mContext);
|
||||
// initialize in texture thread.
|
||||
mBeautyManager.init();
|
||||
mBeautyManager.setBeautyEnable(isBeautyEnable);
|
||||
mBeautyManager.switchCameraId(mCameraId);
|
||||
Log.d(TAG, "initBeautyManager end");
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyBeautyManager() {
|
||||
if (mBeautyManager != null) {
|
||||
mBeautyManager.release();
|
||||
mBeautyManager = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3.美颜UI面板逻辑**
|
||||
|
||||
* **UI布局**
|
||||
|
||||
```xml
|
||||
<com.aliyunsdk.queen.menu.QueenBeautyMenu
|
||||
android:id="@+id/beauty_beauty_menuPanel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true" />
|
||||
```
|
||||
|
||||
* **UI声明**
|
||||
|
||||
```java
|
||||
QueenMenuPanel beautyMenuPanel = QueenBeautyMenu.getPanel(context);
|
||||
beautyMenuPanel.onHideMenu();
|
||||
beautyMenuPanel.onHideValidFeatures();
|
||||
beautyMenuPanel.onHideCopyright();
|
||||
|
||||
QueenBeautyMenu beautyBeautyContainerView = findViewById(R.id.beauty_beauty_menuPanel);
|
||||
beautyBeautyContainerView.addView(beautyMenuPanel);
|
||||
```
|
||||
|
||||
## **四、模块实现**
|
||||
|
||||
### **模块介绍**
|
||||
|
||||
LiveBeauty,负责美颜处理的基础模块,分为live_beauty和live_queenbeauty两个模块。
|
||||
|
||||
live_beauty模块,主要负责对于直播场景下美颜接口的抽象;
|
||||
|
||||
live_queenbeauty是基于live_beauty抽象接口,对Queen美颜SDK的封装与具体实现。
|
||||
|
||||
### **实现逻辑**
|
||||
|
||||
* 对外接口:BeautyInterface
|
||||
* 核心实现:QueenBeautyImpl
|
||||
* 创建实例:BeautyFactory
|
||||
|
||||
由于模块实现了插件化,因此beauty实例是通过反射进行实例化,即:
|
||||
|
||||
BeautyInterface类,负责抽象出一套统一的对外接口;
|
||||
|
||||
BeautyFactory类,通过反射创建实例;
|
||||
|
||||
QueenBeautyImpl类,为Queen SDK美颜实现;**(核心逻辑)**
|
||||
|
||||
**注意:如果QueenBeautyImpl的包名被修改,请注意同步在代码中修改包名,否则在实例化失败,导致美颜调用无效!**
|
||||
|
||||
```java
|
||||
public class BeautyConstant {
|
||||
// 由于beauty模块是插件化,因此beauty实例是通过反射进行实例化,请注意修改美颜具体实现(impl)类名,以免出现美颜初始化失败导致美颜失效的问题
|
||||
public static final String BEAUTY_QUEEN_MANAGER_CLASS_NAME = "com.alivc.live.queenbeauty.QueenBeautyImpl";
|
||||
}
|
||||
```
|
||||
|
||||
### 依赖关系
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
api project(':LiveBeauty:live_beauty')
|
||||
|
||||
// 美颜UI面板
|
||||
api "com.aliyun.maliang.android:queen_menu:6.7.0-official-pro-tiny"
|
||||
|
||||
// 一体化SDK,包含基础美颜功能
|
||||
implementation "com.aliyun.aio:AliVCSDK_InteractiveLive:6.7.0"
|
||||
|
||||
// 此处引用外部独立版本高级功能Queen
|
||||
implementation "com.aliyun.maliang.android:queen:6.7.0-official-pro"
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** 直播独立SDK不包含基础美颜功能,一体化SDK包含基础美颜功能;Queen SDK包含基础美颜+高级美颜功能。
|
||||
|
||||
**注明:** [Queen SDK](https://www.aliyun.com/activity/cdn/video/rtc_race)基础版和高级版区别,详见:[Android端集成美颜特效SDK](https://help.aliyun.com/zh/live/user-guide/integrate-queen-sdk-for-android)
|
||||
|
||||
* **queen_menu**
|
||||
|
||||
Queen SDK官网提供的UI库,美颜UI面板及美颜资源加载库
|
||||
|
||||
### **可扩展**
|
||||
|
||||
BeautyInterface为抽象化的美颜接口类,客户可以基于该接口类,对接其它美颜SDK,实现一套基于其它美颜SDK的实现(参考QueenBeautyImpl)。
|
||||
|
||||
在BeautyConstant里面定义实现类的包路径,在BeautySDKType里面定义美颜SDK类型,通过BeautyFactory指定美颜SDK类型,完成反射实例化。
|
||||
|
||||
## 五、重要更新
|
||||
|
||||
* v4.4.4~v6.1.0:基础直播下的美颜,处理逻辑参考BeautySDKType.QUEEN,即:QueenBeautyImpl;互动直播下的美颜,处理逻辑参考BeautySDKType.INTERACT_QUEEN,即:InteractQueenBeautyImpl;
|
||||
|
||||
* v6.2.0~v6.6.0:互动直播下的美颜,与基础直播下的美颜,完成统一,处理逻辑保持一致,即:QueenBeautyImpl;
|
||||
|
||||
* v6.7.0开始,一体化SDK只包含基础美颜功能,高级美颜功能需要单独集成美颜SDK,详见模块文档;
|
||||
|
||||
## 六、用户指引
|
||||
|
||||
### **文档**
|
||||
|
||||
[推流SDK](https://help.aliyun.com/zh/live/developer-reference/push-sdk)
|
||||
|
||||
[音视频终端SDK](https://help.aliyun.com/product/261167.html)
|
||||
|
||||
[美颜特效SDK](https://help.aliyun.com/zh/apsara-video-sdk/developer-reference/queen-sdk/)
|
||||
|
||||
[美颜特效SDK通用问题](https://help.aliyun.com/zh/apsara-video-sdk/developer-reference/faq-related-to-queen-sdk)
|
||||
|
||||
### **FAQ**
|
||||
|
||||
如果您在使用推流SDK有任何问题或建议,欢迎通过钉钉搜索群号32825314或44911608加入推流SDK开发者生态群。
|
||||
|
||||
您在美颜特效SDK使用过程中有任何问题或建议,请通过开发者支持群联系我们,钉钉搜索群号34197869加入。
|
||||
1
LiveBeauty/live_beauty/.gitignore
vendored
Normal file
1
LiveBeauty/live_beauty/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
30
LiveBeauty/live_beauty/build.gradle
Normal file
30
LiveBeauty/live_beauty/build.gradle
Normal file
@@ -0,0 +1,30 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion androidCompileSdkVersion
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion androidMinSdkVersion
|
||||
targetSdkVersion androidTargetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation externalAndroidAnnotation
|
||||
}
|
||||
0
LiveBeauty/live_beauty/consumer-rules.pro
Normal file
0
LiveBeauty/live_beauty/consumer-rules.pro
Normal file
21
LiveBeauty/live_beauty/proguard-rules.pro
vendored
Normal file
21
LiveBeauty/live_beauty/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
2
LiveBeauty/live_beauty/src/main/AndroidManifest.xml
Normal file
2
LiveBeauty/live_beauty/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.alivc.live.beauty" />
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.alivc.live.beauty;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.alivc.live.beauty.constant.BeautySDKType;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
public class BeautyFactory {
|
||||
private BeautyFactory() {
|
||||
}
|
||||
|
||||
public static BeautyInterface createBeauty(BeautySDKType type, @NonNull Context context) {
|
||||
BeautyInterface itf = null;
|
||||
if (type == BeautySDKType.QUEEN) {
|
||||
Object[] values = {context};
|
||||
Class<?>[] params = {Context.class};
|
||||
itf = reflectInitBeauty(BeautySDKType.QUEEN.getManagerClassName(), values, params);
|
||||
}
|
||||
return itf;
|
||||
}
|
||||
|
||||
private static BeautyInterface reflectInitBeauty(@NonNull String className, @NonNull Object[] values, @NonNull Class<?>[] params) {
|
||||
Object obj = null;
|
||||
try {
|
||||
Class<?> cls = Class.forName(className);
|
||||
Constructor<?> constructor = cls.getDeclaredConstructor(params);
|
||||
obj = constructor.newInstance(values);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InstantiationException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return (BeautyInterface) obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.alivc.live.beauty;
|
||||
|
||||
import com.alivc.live.beauty.constant.BeautyImageFormat;
|
||||
|
||||
/**
|
||||
* 美颜基础接口类
|
||||
* <p>
|
||||
* 实现:不同厂商的美颜,须继承自该类进行扩展实现;
|
||||
*/
|
||||
public interface BeautyInterface {
|
||||
|
||||
//////////// 1、美颜生命周期相关接口 ////////////
|
||||
|
||||
/**
|
||||
* 初始化美颜
|
||||
*
|
||||
* @note 注意:直播SDK 从v6.2.0 开始,接口发生变动,不再透出内部glcontext,以供外部进行上下文切换,并进行美颜处理
|
||||
* @note 后续所有video texture回调,将在gl线程回调;
|
||||
* @note 普通直播模式/直播连麦模式,应用层接入美颜Queen SDK的逻辑,将共享使用QueenBeautyImpl中(即:BeautySDKType.QUEEN)
|
||||
*/
|
||||
void init();
|
||||
|
||||
/**
|
||||
* 销毁、释放美颜
|
||||
*/
|
||||
void release();
|
||||
|
||||
//////////// 2、美颜接口 ////////////
|
||||
|
||||
/**
|
||||
* 开启/关闭美颜
|
||||
*
|
||||
* @param enable 是否开启
|
||||
*/
|
||||
void setBeautyEnable(boolean enable);
|
||||
|
||||
//////////// 4、美颜输入输出 ////////////
|
||||
|
||||
/**
|
||||
* 纹理输入接口,用于图像处理
|
||||
*
|
||||
* @param inputTexture 输入纹理id
|
||||
* @param textureWidth 纹理宽度
|
||||
* @param textureHeight 纹理高度
|
||||
* @return 输出纹理
|
||||
* @note 默认输出为Sample2D格式的纹理
|
||||
*/
|
||||
int onTextureInput(int inputTexture, int textureWidth, int textureHeight);
|
||||
|
||||
/**
|
||||
* 帧数据输入接口,用于图像处理
|
||||
*
|
||||
* @param image byte数组形式的帧数据
|
||||
* @param format 帧数据类型,rgba、rgb、nv21等,参考{@linkplain BeautyImageFormat}
|
||||
* @param width 帧宽
|
||||
* @param height 帧高
|
||||
* @param stride 顶点间隔
|
||||
*/
|
||||
void onDrawFrame(byte[] image, @BeautyImageFormat int format, int width, int height, int stride);
|
||||
|
||||
void switchCameraId(int cameraId);
|
||||
|
||||
//////////// 5、美颜其它接口 ////////////
|
||||
|
||||
/**
|
||||
* 获取版本号
|
||||
*
|
||||
* @return 版本号
|
||||
*/
|
||||
String getVersion();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.alivc.live.beauty.constant;
|
||||
|
||||
public class BeautyConstant {
|
||||
public static final String BEAUTY_QUEEN_MANAGER_CLASS_NAME = "com.alivc.live.queenbeauty.QueenBeautyImpl";
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.alivc.live.beauty.constant;
|
||||
|
||||
import static com.alivc.live.beauty.constant.BeautyImageFormat.kDefault;
|
||||
import static com.alivc.live.beauty.constant.BeautyImageFormat.kNV21;
|
||||
import static com.alivc.live.beauty.constant.BeautyImageFormat.kRGB;
|
||||
import static com.alivc.live.beauty.constant.BeautyImageFormat.kRGBA;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({kDefault, kRGB, kNV21, kRGBA})
|
||||
public @interface BeautyImageFormat {
|
||||
int kDefault = -1;
|
||||
int kRGB = 0;
|
||||
int kNV21 = 1;
|
||||
int kRGBA = 2;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.alivc.live.beauty.constant;
|
||||
|
||||
public enum BeautySDKType {
|
||||
// should be kept!
|
||||
QUEEN(BeautyConstant.BEAUTY_QUEEN_MANAGER_CLASS_NAME),
|
||||
|
||||
;
|
||||
|
||||
private final String managerClassName;
|
||||
|
||||
BeautySDKType(String managerClassName) {
|
||||
this.managerClassName = managerClassName;
|
||||
}
|
||||
|
||||
public String getManagerClassName() {
|
||||
return managerClassName;
|
||||
}
|
||||
}
|
||||
1
LiveBeauty/live_queenbeauty/.gitignore
vendored
Normal file
1
LiveBeauty/live_queenbeauty/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
48
LiveBeauty/live_queenbeauty/build.gradle
Normal file
48
LiveBeauty/live_queenbeauty/build.gradle
Normal file
@@ -0,0 +1,48 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion androidCompileSdkVersion
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion androidMinSdkVersion
|
||||
targetSdkVersion androidTargetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation externalAndroidAnnotation
|
||||
|
||||
api project(':LiveBeauty:live_beauty')
|
||||
|
||||
// 此处修改:根据自身项目需要,指定依赖的菜单组件sdk
|
||||
api(externalAliyunQueenUI) {
|
||||
exclude group: 'com.aliyun.maliang.android', module: 'queen'
|
||||
}
|
||||
|
||||
// 此处引用外部独立版本高级功能Queen
|
||||
implementation externalAliyunQueen
|
||||
|
||||
// 一体化SDK,包含基础美颜功能
|
||||
if (!"true".equalsIgnoreCase(allInOne)) {
|
||||
// 不含一体化,独立接入Queen时,需带上该framework
|
||||
implementation externalAliyunQueenFramwork
|
||||
} else {
|
||||
implementation externalAllInOne
|
||||
}
|
||||
}
|
||||
0
LiveBeauty/live_queenbeauty/consumer-rules.pro
Normal file
0
LiveBeauty/live_queenbeauty/consumer-rules.pro
Normal file
21
LiveBeauty/live_queenbeauty/proguard-rules.pro
vendored
Normal file
21
LiveBeauty/live_queenbeauty/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
4
LiveBeauty/live_queenbeauty/src/main/AndroidManifest.xml
Normal file
4
LiveBeauty/live_queenbeauty/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.alivc.live.queenbeauty">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,332 @@
|
||||
package com.alivc.live.queenbeauty;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.Surface;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.alivc.live.beauty.BeautyInterface;
|
||||
import com.aliyun.android.libqueen.aio.IBeautyParamsHolder;
|
||||
import com.aliyun.android.libqueen.aio.QueenBeautyInterface;
|
||||
import com.aliyun.android.libqueen.aio.QueenBeautyWrapper;
|
||||
import com.aliyun.android.libqueen.aio.QueenConfig;
|
||||
import com.aliyun.android.libqueen.aio.QueenFlip;
|
||||
import com.aliyunsdk.queen.param.QueenParam;
|
||||
import com.aliyunsdk.queen.param.QueenParamFactory;
|
||||
import com.aliyunsdk.queen.param.QueenParamHolder;
|
||||
|
||||
/**
|
||||
* 美颜实现相关类
|
||||
*
|
||||
* @note v4.4.4~v6.1.0:基础直播下的美颜,处理逻辑参考BeautySDKType.QUEEN,即:QueenBeautyImpl;互动直播下的美颜,处理逻辑参考BeautySDKType.INTERACT_QUEEN,即:InteractQueenBeautyImpl;
|
||||
* @note v6.2.0~v6.6.0:互动直播下的美颜,与基础直播下的美颜,完成统一,处理逻辑保持一致,即:QueenBeautyImpl;
|
||||
* @note v6.7.0开始,一体化SDK只包含基础美颜功能,高级美颜功能需要单独集成美颜SDK,详见模块文档;
|
||||
*/
|
||||
@Keep
|
||||
public class QueenBeautyImpl implements BeautyInterface {
|
||||
private static final String TAG = "QueenBeautyImpl";
|
||||
|
||||
// Note keria, 非必要不打开,日志爆表
|
||||
private static final boolean FLAG_ENABLE_DEBUG_LOG = false;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private QueenBeautyInterface mBeautyImpl;
|
||||
|
||||
private int mLastTextureId = -1;
|
||||
private int mLastTextureWidth = 0;
|
||||
private int mLastTextureHeight = 0;
|
||||
|
||||
private int mDeviceOrientation = 0;
|
||||
private OrientationEventListener mOrientationListener;
|
||||
|
||||
private volatile boolean isBeautyEnable = false;
|
||||
|
||||
public QueenBeautyImpl(Context context) {
|
||||
mContext = context;
|
||||
initOrientationListener(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
if (mBeautyImpl == null) {
|
||||
mBeautyImpl = new QueenBeautyWrapper();
|
||||
QueenConfig queenConfig = new QueenConfig();
|
||||
queenConfig.enableDebugLog = FLAG_ENABLE_DEBUG_LOG;
|
||||
mBeautyImpl.init(mContext, queenConfig);
|
||||
// mBeautyImpl.init(mContext);
|
||||
mBeautyImpl.setBeautyParams(new IBeautyParamsHolder() {
|
||||
@Override
|
||||
public void onWriteParamsToBeauty(Object o) {
|
||||
QueenParamHolder.writeParamToEngine(o);
|
||||
}
|
||||
});
|
||||
isBeautyEnable = true;
|
||||
}
|
||||
|
||||
// 是否开启美颜SDK的debug日志
|
||||
if (mBeautyImpl != null && FLAG_ENABLE_DEBUG_LOG) {
|
||||
mBeautyImpl.enableDebugMode();
|
||||
}
|
||||
|
||||
// engine初始化后,更新当前默认的美颜效果
|
||||
refreshDefaultQueenParam();
|
||||
}
|
||||
|
||||
/**
|
||||
* engine初始化后,更新当前默认的美颜效果
|
||||
*
|
||||
* @note 请自行调整直播状态下的初始美颜效果
|
||||
*/
|
||||
private void refreshDefaultQueenParam() {
|
||||
QueenParam defaultQueenParam = QueenParamHolder.getQueenParam();
|
||||
// 更新默认美颜为流行风格
|
||||
defaultQueenParam.basicBeautyRecord = QueenParamFactory.BeautyParams.getParams(QueenParamFactory.BeautyParams.ID_SIMPLE);
|
||||
// 更新默认美型为可爱风格
|
||||
defaultQueenParam.faceShapeRecord = QueenParamFactory.FaceShapeParams.getParams(QueenParamFactory.FaceShapeParams.TAG_SHAPE_AUTO);
|
||||
// 若需要微调细节参数-磨皮
|
||||
defaultQueenParam.basicBeautyRecord.skinBuffingParam = 0.4f;
|
||||
// 若需要微调细节参数-美白
|
||||
defaultQueenParam.basicBeautyRecord.skinWhitingParam = 0.6f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
Log.d(TAG, "release");
|
||||
|
||||
destroyOrientationListener();
|
||||
|
||||
if (mBeautyImpl != null) {
|
||||
mBeautyImpl.release();
|
||||
mBeautyImpl = null;
|
||||
}
|
||||
|
||||
// 重置美颜设置,美颜参数效果在内部以静态变量形式保存
|
||||
QueenParamHolder.relaseQueenParams();
|
||||
|
||||
isBeautyEnable = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeautyEnable(boolean enable) {
|
||||
Log.d(TAG, "setBeautyEnable: " + enable);
|
||||
isBeautyEnable = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onTextureInput(int inputTexture, int textureWidth, int textureHeight) {
|
||||
// 处理美颜逻辑
|
||||
if (mBeautyImpl == null || !isBeautyEnable) {
|
||||
return inputTexture;
|
||||
}
|
||||
|
||||
handleTextureSizeChange(textureWidth, textureHeight);
|
||||
|
||||
mLastTextureId = inputTexture;
|
||||
|
||||
// 刷新摄像头角度
|
||||
refreshCameraAngles();
|
||||
|
||||
adjustAngles();
|
||||
|
||||
long now = SystemClock.uptimeMillis();
|
||||
|
||||
int result = mBeautyImpl.onProcessTexture(inputTexture, false, null, textureWidth, textureHeight, inputAngle, outAngle, 2);
|
||||
|
||||
if (FLAG_ENABLE_DEBUG_LOG) {
|
||||
Log.i(TAG, String.format("[%d][%dms], [%d][%dx%d]", Thread.currentThread().getId(), (SystemClock.uptimeMillis() - now), inputTexture, textureWidth, textureHeight));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void handleTextureSizeChange(int textureWidth, int textureHeight) {
|
||||
// 验证纹理的宽度和高度有效
|
||||
if (textureWidth <= 0 || textureHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 仅在纹理尺寸变化时处理
|
||||
if (mLastTextureWidth != textureWidth || mLastTextureHeight != textureHeight) {
|
||||
// 如果是首次设置,或之前的纹理尺寸有效,将释放之前的美颜实现
|
||||
if (mLastTextureWidth > 0 && mLastTextureHeight > 0) {
|
||||
if (mBeautyImpl != null) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
mBeautyImpl.release();
|
||||
mBeautyImpl = null;
|
||||
init(); // 重新初始化美颜实现
|
||||
Log.e(TAG, String.format("reset queen beauty impl: [%d][%dms], [%dx%d->%dx%d]", Thread.currentThread().getId(), (SystemClock.uptimeMillis() - now), mLastTextureWidth, mLastTextureHeight, textureWidth, textureHeight));
|
||||
}
|
||||
}
|
||||
// 更新最后的纹理尺寸
|
||||
mLastTextureWidth = textureWidth;
|
||||
mLastTextureHeight = textureHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustAngles() {
|
||||
// 根据摄像头方向设置输入和输出角度
|
||||
if (outAngle == 90 || outAngle == 270) {// 右 out = 90 / 左 out = 270
|
||||
// 推流的输入纹理经过处理,非原始摄像头采集纹理,这里单独针对角度适配: 右 out = 90 / 左 out = 270
|
||||
inputAngle = outAngle;
|
||||
outAngle = (outAngle + 180) % 360;
|
||||
} else { // 正 out = 180 / 倒立 out = 0
|
||||
// 解决抠图和美发头像上下翻转的问题
|
||||
// 推流的输入纹理经过处理,非原始摄像头采集纹理,这里单独针对角度适配: 正 out = 180 : 倒立 out = 0
|
||||
inputAngle = outAngle;
|
||||
outAngle = 180 - outAngle;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(byte[] image, int format, int width, int height, int stride) {
|
||||
if (mBeautyImpl != null && isBeautyEnable) {
|
||||
// @keria, reference: https://www.atatech.org/articles/123323
|
||||
// inputAngle, outputAngle, flipAxis,太TM绕了...(〒︿〒)
|
||||
int displayOrientation = getDisplayOrientation();
|
||||
|
||||
int inputAngle = 0;
|
||||
int outputAngle = 0;
|
||||
|
||||
if (displayOrientation == 90 || displayOrientation == 270) { //横屏
|
||||
inputAngle = (270 - mDeviceOrientation + 360) % 360;
|
||||
outputAngle = (displayOrientation - mDeviceOrientation + 360) % 360;
|
||||
} else if (displayOrientation == 0 || displayOrientation == 180) { //竖屏
|
||||
inputAngle = (270 - mDeviceOrientation + 360) % 360;
|
||||
outputAngle = (180 + displayOrientation - mDeviceOrientation + 360) % 360;
|
||||
}
|
||||
|
||||
Log.d(TAG, "inputAngle=" + inputAngle + ", outputAngle=" + outputAngle);
|
||||
|
||||
mBeautyImpl.onProcessTextureAndBuffer(mLastTextureId, false, null, width, height, inputAngle, outputAngle, 0, image, format);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchCameraId(int cameraId) {
|
||||
mCurCameraId = cameraId;
|
||||
boolean isCameraFront = mCurCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||
if (isCameraFront) {
|
||||
setCameraAngles4Front();
|
||||
} else {
|
||||
setCameraAngles4Back();
|
||||
}
|
||||
}
|
||||
|
||||
private Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
|
||||
private int mCurCameraId = -1;
|
||||
private int inputAngle;
|
||||
private int outAngle;
|
||||
private int flipAxis;
|
||||
// private long mAlgNativeBufferPtr;
|
||||
// private int mAlgDataFormat, mAlgDataWidth, mAlgDataHeight, nAlgDataStride;
|
||||
|
||||
private void refreshCameraAngles() {
|
||||
switchCameraId(mCurCameraId);
|
||||
}
|
||||
|
||||
private void setCameraAngles4Back() {
|
||||
int displayOrientation = getDisplayOrientation();
|
||||
inputAngle = (mCameraInfo.orientation + mDeviceOrientation) % 360;
|
||||
int angle = mDeviceOrientation % 360;
|
||||
outAngle = (angle - displayOrientation + 360) % 360;
|
||||
|
||||
if (displayOrientation == 0 || displayOrientation == 180) { //竖屏
|
||||
outAngle = (180 + displayOrientation - mDeviceOrientation + 360) % 360;
|
||||
if (mDeviceOrientation % 180 == 90) {
|
||||
outAngle = (180 + mDeviceOrientation) % 360;
|
||||
}
|
||||
}
|
||||
flipAxis = QueenFlip.kFlipY;
|
||||
}
|
||||
|
||||
private void setCameraAngles4Front() {
|
||||
// @keria, reference: https://www.atatech.org/articles/123323
|
||||
// inputAngle, outputAngle, flipAxis,太TM绕了...(〒︿〒)
|
||||
|
||||
inputAngle = 0;
|
||||
outAngle = 0;
|
||||
|
||||
int displayOrientation = getDisplayOrientation();
|
||||
if (displayOrientation == 90 || displayOrientation == 270) { //横屏
|
||||
inputAngle = (270 - mDeviceOrientation + 360) % 360;
|
||||
outAngle = (displayOrientation - mDeviceOrientation + 360) % 360;
|
||||
} else if (displayOrientation == 0 || displayOrientation == 180) { //竖屏
|
||||
inputAngle = (270 - mDeviceOrientation + 360) % 360;
|
||||
outAngle = (180 + displayOrientation - mDeviceOrientation + 360) % 360;
|
||||
}
|
||||
|
||||
flipAxis = QueenFlip.kFlipY;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Warning: patch code, need to be replaced next version. We should get camera orientation by texture callback.
|
||||
private void initOrientationListener(@NonNull Context context) {
|
||||
mOrientationListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
// This is a method called frequently, which is bad for performance.
|
||||
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
orientation = (orientation + 45) / 90 * 90;
|
||||
if (mDeviceOrientation != orientation) {
|
||||
// 不能在此处更改mDisplayOrientation,屏幕旋转可能还未生效
|
||||
Log.d(TAG, "Orientation Changed! displayOrientation: " + mDeviceOrientation + "->" + orientation);
|
||||
mDeviceOrientation = orientation;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (mOrientationListener.canDetectOrientation()) {
|
||||
Log.d(TAG, "Can detect orientation");
|
||||
mOrientationListener.enable();
|
||||
} else {
|
||||
Log.d(TAG, "Cannot detect orientation");
|
||||
mOrientationListener.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyOrientationListener() {
|
||||
if (mOrientationListener != null) {
|
||||
mOrientationListener.disable();
|
||||
mOrientationListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
private int getDisplayOrientation() {
|
||||
int displayOrientation = 0;
|
||||
if (mContext != null) {
|
||||
int angle = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
|
||||
switch (angle) {
|
||||
case Surface.ROTATION_0:
|
||||
displayOrientation = 0;
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
displayOrientation = 90;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
displayOrientation = 180;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
displayOrientation = 270;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return displayOrientation;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user