Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50680ee854 | ||
|
|
2066b3b124 | ||
|
|
27b7075818 | ||
|
|
fb434f09b8 | ||
|
|
7ab4274e4f | ||
|
|
5d8e108306 | ||
|
|
52c9da08ed | ||
|
|
496d56f9c6 | ||
|
|
60b40bfd5f | ||
|
|
b44162e6cb | ||
|
|
6ef6e012bd | ||
|
|
f5b2d490c0 | ||
|
|
4c5a05ae21 | ||
|
|
1f9e4939b9 | ||
|
|
c83f36c976 | ||
|
|
29e0c1e9a8 | ||
|
|
7b07e7fbc1 | ||
|
|
b6ac86a134 | ||
|
|
6bbbb3c3ef | ||
|
|
e38da96e37 | ||
|
|
9ee028248d | ||
|
|
84cc693593 | ||
|
|
4cf1990b16 | ||
|
|
2130cf0e6b | ||
|
|
6717047d1d | ||
|
|
da2c97e412 | ||
|
|
db80a5dbbd | ||
|
|
634d22f3be | ||
|
|
835f768a4a | ||
|
|
1859706412 | ||
|
|
9c71e40c97 | ||
|
|
84d567674f | ||
|
|
43b00c8446 | ||
|
|
e23ccd75cd | ||
|
|
8623b71ab7 | ||
|
|
16efcbcff3 | ||
|
|
4f62081239 | ||
|
|
9f29667b73 | ||
|
|
0c7455650d | ||
|
|
00aabfc6d9 | ||
|
|
02a005f5b1 | ||
|
|
6495a27658 | ||
|
|
263a061d27 | ||
|
|
2555047190 | ||
|
|
dfbef7caf1 | ||
|
|
13357ab256 | ||
|
|
1dfc63543f | ||
|
|
10505c616f | ||
|
|
300340bb19 | ||
|
|
57b7c40835 | ||
|
|
b2da84f903 | ||
|
|
41a659bd6d | ||
|
|
afd02550a8 | ||
|
|
b73e94b7bf | ||
|
|
2876599568 | ||
|
|
f6dd3ab38e | ||
|
|
ce2444bd07 | ||
|
|
25fd6b4a32 | ||
|
|
bdcf466b73 | ||
|
|
8a1d39ee94 | ||
|
|
438d1c246d | ||
|
|
eb507c0529 | ||
|
|
220e1873f9 | ||
|
|
361708bd11 | ||
|
|
87178587d3 | ||
|
|
576c302cd1 | ||
|
|
1f0422072a | ||
|
|
e6a776665f | ||
|
|
d2da523636 | ||
|
|
3c63027bf5 | ||
|
|
572c53c0f4 | ||
|
|
2284911161 | ||
|
|
234e30bd50 |
@@ -12,4 +12,5 @@
|
|||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
|
.idea
|
||||||
local.properties
|
local.properties
|
||||||
|
|||||||
@@ -1,25 +1,44 @@
|
|||||||
cmake_minimum_required(VERSION 3.22.1)
|
cmake_minimum_required(VERSION 3.22.1)
|
||||||
|
|
||||||
project("sample")
|
project("face_sdk")
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
add_compile_options(/Zc:__cplusplus)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Android")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Android")
|
||||||
message(STATUS "Building for Android")
|
message(STATUS "Building for Android")
|
||||||
|
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
vulkan
|
vulkan
|
||||||
|
third_party/glm
|
||||||
|
third_party/vma
|
||||||
|
third_party/vma/include
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(sample SHARED
|
add_library(face_sdk SHARED
|
||||||
app/src/main/cpp/main.cpp
|
app/src/main/cpp/main.cpp
|
||||||
app/src/main/cpp/AndroidOut.cpp
|
app/src/main/cpp/AndroidOut.cpp
|
||||||
|
app/src/main/cpp/DebugLog.h
|
||||||
|
app/src/main/cpp/DebugLog.cpp
|
||||||
|
app/src/main/cpp/CrashHandler.h
|
||||||
|
app/src/main/cpp/CrashHandler.cpp
|
||||||
vulkan/AppBase.h
|
vulkan/AppBase.h
|
||||||
vulkan/AppBase.cpp
|
vulkan/AppBase.cpp
|
||||||
vulkan/Application.h
|
vulkan/Application.h
|
||||||
vulkan/Application.cpp
|
vulkan/Application.cpp
|
||||||
|
vulkan/FaceApp.h
|
||||||
|
vulkan/FaceApp.cpp
|
||||||
|
vulkan/lodepng.cpp
|
||||||
|
third_party/vma/src/VmaUsage.h
|
||||||
|
third_party/vma/src/VmaUsage.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(game-activity REQUIRED CONFIG)
|
find_package(game-activity REQUIRED CONFIG)
|
||||||
@@ -30,22 +49,39 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Android")
|
|||||||
|
|
||||||
find_package(Vulkan REQUIRED)
|
find_package(Vulkan REQUIRED)
|
||||||
|
|
||||||
target_link_libraries(sample
|
target_link_libraries(face_sdk
|
||||||
game-activity::game-activity_static
|
game-activity::game-activity_static
|
||||||
Vulkan::Vulkan
|
Vulkan::Vulkan
|
||||||
android
|
android
|
||||||
log)
|
log
|
||||||
|
# dl: needed by CrashHandler::dumpFrame -> dladdr() to map a
|
||||||
|
# PC back to "module + symbol + offset" in the crash log.
|
||||||
|
dl)
|
||||||
|
|
||||||
|
target_compile_definitions(face_sdk PRIVATE
|
||||||
|
VMA_STATIC_VULKAN_FUNCTIONS=0
|
||||||
|
VMA_DYNAMIC_VULKAN_FUNCTIONS=1
|
||||||
|
)
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows" OR WIN32)
|
if(CMAKE_SYSTEM_NAME STREQUAL "Windows" OR WIN32)
|
||||||
message(STATUS "Building for Windows")
|
message(STATUS "Building for Windows")
|
||||||
|
add_compile_options(/utf-8)
|
||||||
|
add_compile_definitions(_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/vulkan
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/third_party
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/third_party/glm
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/third_party/vma
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/third_party/vma/include
|
||||||
"third_party/glfw-3.4/output/include"
|
"third_party/glfw-3.4/output/include"
|
||||||
"C:/VulkanSDK/1.4.321.1/Include"
|
"C:/VulkanSDK/1.4.321.1/Include"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
link_libraries(
|
link_libraries(
|
||||||
# "${CMAKE_CURRENT_LIST_DIR}/third_party/glfw-3.4/output/lib/glfw3.lib"
|
# "${CMAKE_CURRENT_LIST_DIR}/third_party/glfw-3.4/output/lib/glfw3.lib"
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/third_party/glfw-3.4/output/glfw3.lib"
|
"${CMAKE_CURRENT_LIST_DIR}/third_party/glfw-3.4/output/glfw3.lib"
|
||||||
@@ -53,11 +89,22 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows" OR WIN32)
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 将源代码添加到此项目的可执行文件。
|
# 将源代码添加到此项目的可执行文件。
|
||||||
add_executable (sample
|
add_executable (face_sdk
|
||||||
"vulkan/main.cpp"
|
vulkan/AppBase.h
|
||||||
"vulkan/AppBase.h"
|
vulkan/AppBase.cpp
|
||||||
"vulkan/AppBase.cpp"
|
vulkan/Application.h
|
||||||
"vulkan/Application.h"
|
vulkan/Application.cpp
|
||||||
"vulkan/Application.cpp"
|
vulkan/FaceApp.h
|
||||||
|
vulkan/FaceApp.cpp
|
||||||
|
vulkan/main.cpp
|
||||||
|
vulkan/lodepng.cpp
|
||||||
|
third_party/vma/src/VmaUsage.h
|
||||||
|
third_party/vma/src/VmaUsage.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
# target_compile_definitions(face_sdk PRIVATE VK_NO_PROTOTYPES) 使用volk 动态加载的时候需要定义,不自动生成vulkan接口
|
||||||
|
target_compile_definitions(face_sdk PRIVATE GLM_ENABLE_EXPERIMENTAL)
|
||||||
|
# target_compile_definitions(face_sdk PRIVATE VULKAN_HPP_NO_STRUCT_CONSTRUCTORS) 解决vs 编译c++ 20 不支持新构造函数的特性
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
# Face SDK 崩溃调试指南
|
||||||
|
|
||||||
|
本文档说明如何配合 AI 定位 `libface_sdk.so` 里的崩溃 / 异常。核心思路:**把 Vulkan 每一次异常的上下文写进一个持久日志文件里,崩溃或画面卡住后把这份文件发给 AI 分析。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、已经加好的东西(不需要你做什么)
|
||||||
|
|
||||||
|
### 1. `DebugLog` 模块(`app/src/main/cpp/DebugLog.{h,cpp}`)
|
||||||
|
|
||||||
|
- 线程安全;
|
||||||
|
- 每条日志带**时间戳 + 线程 ID(tid)**;
|
||||||
|
- 写到 App **私有目录下的文件** —— 不会被 logcat 自动清掉;
|
||||||
|
- 每写一行 `fflush`,进程挂了也不丢最后几行;
|
||||||
|
- 文件大小超过 2MB 自动滚动到 `.old`;
|
||||||
|
- 同步把日志镜像到 logcat,tag 是 `FACE_DBG`。
|
||||||
|
|
||||||
|
### 2. 打点位置
|
||||||
|
|
||||||
|
| 位置 | 打点内容 | 用途 |
|
||||||
|
|---|---|---|
|
||||||
|
| `android_main` 启动 | pid、tid、日志文件绝对路径 | 每次启动一条,作为分段锚点 |
|
||||||
|
| 渲染循环心跳 | 每 600 帧一条:`frame=X exceptions_so_far=Y` | 判断启动多久后崩溃 / 异常持续性 |
|
||||||
|
| `try/catch` 兜底 | 首 10 次异常每次都打,之后每 120 次打一次 | **Vulkan 异常不会再让进程硬崩**,留住现场 |
|
||||||
|
| `handle_cmd` | 每个 `APP_CMD_*` 事件(尤其 `INIT_WINDOW`/`TERM_WINDOW`/`CONFIG_CHANGED`) | 旋转 / 切后台 / 锁屏场景的时间线 |
|
||||||
|
| `FaceApp::initVulkan` 入/出口 | 调用次数、`_applicationInited` / `_faceAppInited` / `_sceondInited` 状态 | 判断是否被多次重复调用 |
|
||||||
|
| `processImageNative` / `passDataToNative` | 每 300 次调用打一条 tid | 验证"Java 两个回调在不同线程并发调 Vulkan"假设 |
|
||||||
|
| `Application::drawFrame` 里的 4 次 Vulkan 调用 | 非 `VK_SUCCESS` 时打印数值 + 字符串名(例如 `-4 (VK_ERROR_DEVICE_LOST)`) | **最关键的证据** |
|
||||||
|
|
||||||
|
### 3. 关键行为变化
|
||||||
|
|
||||||
|
**⚠️ 现在 `drawFrame` 里 Vulkan 抛出的异常会被 `try/catch` 吃掉 —— App 不会再因为这类问题硬崩溃。**
|
||||||
|
|
||||||
|
- 好处:你能继续跑、继续复现、日志一直在写;
|
||||||
|
- 代价:出问题时表现从"崩溃"变成"**画面卡住 / 黑屏 / 不再更新**"。
|
||||||
|
- 如果想暂时回到硬崩行为:把 `app/src/main/cpp/main.cpp` 里那段 `try { g_Application->drawFrame(...); } catch (...) { ... }` 改回直接 `g_Application->drawFrame(frameTime);` 即可。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、怎么把日志文件取出来
|
||||||
|
|
||||||
|
### 方式 A:`run-as`(推荐,不需要 root)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb shell "run-as com.inewme.uvmirror cat files/face_sdk_debug.log" > face_sdk_debug.log
|
||||||
|
adb shell "run-as com.inewme.uvmirror cat files/face_sdk_debug.log.old" > face_sdk_debug.log.old
|
||||||
|
```
|
||||||
|
|
||||||
|
> `.old` 只有文件滚动过一次才存在,没有可以忽略错误。
|
||||||
|
|
||||||
|
### 方式 B:直接看 logcat(实时)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb logcat -s FACE_DBG
|
||||||
|
```
|
||||||
|
|
||||||
|
和文件内容基本一致。跑较久的话还是文件更可靠。
|
||||||
|
|
||||||
|
### 方式 C:从 App 里拿路径
|
||||||
|
|
||||||
|
启动后 `FACE_DBG` 第一条日志长这样:
|
||||||
|
|
||||||
|
```
|
||||||
|
[10:12:03.041][tid=12345] android_main start, pid=23456 tid=12345, logPath=/data/user/0/com.inewme.uvmirror/files/face_sdk_debug.log
|
||||||
|
```
|
||||||
|
|
||||||
|
照这个路径走就对了。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、你现在要做的事(B 方案:两种场景都复现一次)
|
||||||
|
|
||||||
|
### 场景 1:旋转崩溃(已确认必现)
|
||||||
|
|
||||||
|
1. 打开 App,等初始化完成(画面能看到人脸渲染)。
|
||||||
|
2. **旋转一次屏幕**。
|
||||||
|
3. 等 3~5 秒让日志写进去。
|
||||||
|
4. 取 `face_sdk_debug.log`,**不要清文件**,继续场景 2。
|
||||||
|
|
||||||
|
期望看到的关键行:
|
||||||
|
```
|
||||||
|
>> handle_cmd APP_CMD_TERM_WINDOW(...)
|
||||||
|
>> handle_cmd APP_CMD_INIT_WINDOW(...)
|
||||||
|
FaceApp::initVulkan enter, call#2 ...
|
||||||
|
drawFrame[NNN] vkAcquireNextImageKHR -> ??? (...)
|
||||||
|
!!! drawFrame std::exception (frame=NNN count=M): failed to ...
|
||||||
|
```
|
||||||
|
|
||||||
|
注意里面那个 `???` —— 这是整件事的核心证据。
|
||||||
|
|
||||||
|
### 场景 2:长时间运行崩溃(之前那个 10 分钟左右的)
|
||||||
|
|
||||||
|
**不要退出 App,接着跑**。现在即使旋转触发了异常,进程没死,我们希望看后续会不会再出另一种 VkResult:
|
||||||
|
|
||||||
|
1. 尽量不操作(不切后台、不旋转、不锁屏,让 App 持续渲染)。
|
||||||
|
2. 连续跑 **15~20 分钟**,观察是否复现之前的"无外部事件崩溃"。
|
||||||
|
3. 复现后(或稳定 20 分钟未复现也行),再取一次 `face_sdk_debug.log`(这次会覆盖场景 1 的那份,所以务必**先把场景 1 的单独保存好**)。
|
||||||
|
|
||||||
|
期望看到的关键行(如果真的是 DEVICE_LOST):
|
||||||
|
```
|
||||||
|
drawFrame[NNN] vkQueueSubmit -> -4 (VK_ERROR_DEVICE_LOST) ...
|
||||||
|
```
|
||||||
|
或者某个 Vulkan 调用返回其他错误码。
|
||||||
|
|
||||||
|
### 辅助信息(手边有就顺便记一下)
|
||||||
|
|
||||||
|
| 项 | 为什么要 |
|
||||||
|
|---|---|
|
||||||
|
| 机型 + Android 版本 | GPU 驱动差异;Adreno / Mali / Xclipse 表现差别大 |
|
||||||
|
| 复现时手机是不是很烫 | 排查 GPU 温控 / thermal throttling |
|
||||||
|
| 旋转时是"横屏 → 竖屏"还是"竖屏 → 横屏" / 多次连续旋转? | 某些机型一次 vs 多次触发行为不同 |
|
||||||
|
| 10 分钟崩溃时 App 在做什么(有在切换 motion 吗?) | 帮助排除 `changeMotionList` 相关路径 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、发给 AI 的时候包含什么
|
||||||
|
|
||||||
|
最小集:
|
||||||
|
|
||||||
|
1. `face_sdk_debug.log`(场景 1 旋转那份)
|
||||||
|
2. `face_sdk_debug.log`(场景 2 长跑那份)
|
||||||
|
3. 如果文件很大,**完整发**比截断发强(AI 主要关心最后几千行)
|
||||||
|
4. 一句话说明这份日志对应场景 1 还是场景 2,是否复现了
|
||||||
|
|
||||||
|
不需要的:
|
||||||
|
|
||||||
|
- logcat 完整 dump(体积太大且大多无关,除非 AI 主动问特定 tag)
|
||||||
|
- 录屏
|
||||||
|
- 源代码(AI 已经能看到仓库)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、当前未解决的假设,等日志验证
|
||||||
|
|
||||||
|
| 假设 | 对应 VkResult | 修法 |
|
||||||
|
|---|---|---|
|
||||||
|
| 旋转导致 surface 失效未处理 | `VK_ERROR_OUT_OF_DATE_KHR` (-1000001004) 或 `VK_ERROR_SURFACE_LOST_KHR` (-1000000000) 或 `VK_SUBOPTIMAL_KHR` (1000001003) | 实现 `APP_CMD_TERM_WINDOW` 销毁流程 + swapchain 重建 |
|
||||||
|
| Java 多线程并发提交 graphicsQueue 导致 GPU 长时间后挂 | `VK_ERROR_DEVICE_LOST` (-4),且 `processImageNative` / `passDataToNative` 日志里 tid 不同 | 给所有 `vkQueueSubmit`/`vkQueuePresentKHR` 加全局 queue mutex |
|
||||||
|
| 驱动 bug / 温度导致 GPU hang | `VK_ERROR_DEVICE_LOST` (-4),单线程也出 | 捕获并尝试重建 device,或上报给机型厂商 |
|
||||||
|
| 资源累积泄漏 | `VK_ERROR_OUT_OF_DEVICE_MEMORY` (-2) 或 `VK_ERROR_TOO_MANY_OBJECTS` | 排查 `beginSingleTimeCommands`/`processWithVulkan` 路径 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、日志样例(便于你确认打印是否正常)
|
||||||
|
|
||||||
|
正常启动应该看到:
|
||||||
|
```
|
||||||
|
========== DebugLog opened @ 2026-04-23 14:05:12 (pid=12345) ==========
|
||||||
|
[14:05:12.019][tid=12345] android_main start, pid=12345 tid=12345, logPath=/data/user/0/com.inewme.uvmirror/files/face_sdk_debug.log
|
||||||
|
[14:05:12.155][tid=12345] >> handle_cmd APP_CMD_START(10) tid=12345
|
||||||
|
[14:05:12.155][tid=12345] << handle_cmd APP_CMD_START done
|
||||||
|
[14:05:12.210][tid=12345] >> handle_cmd APP_CMD_INIT_WINDOW(1) tid=12345
|
||||||
|
[14:05:12.210][tid=12345] handle_cmd APP_CMD_INIT_WINDOW: calling initVulkan()
|
||||||
|
[14:05:12.210][tid=12345] FaceApp::initVulkan enter, call#1 _applicationInited=0 _faceAppInited=0 _secondfaceAppInited=0
|
||||||
|
[14:05:13.420][tid=12345] FaceApp::initVulkan exit, call#1 _applicationInited=1 _faceAppInited=1 _secondfaceAppInited=1
|
||||||
|
[14:05:13.420][tid=12345] handle_cmd APP_CMD_INIT_WINDOW: initVulkan() returned, isInited=1
|
||||||
|
[14:05:13.421][tid=12345] << handle_cmd APP_CMD_INIT_WINDOW done
|
||||||
|
[14:05:14.005][tid=67890] processImageNative #1 tid=67890 w=480 h=480
|
||||||
|
[14:05:14.008][tid=67891] passDataToNative #1 tid=67891 point_count=468 w=480 h=480
|
||||||
|
[14:05:35.200][tid=12345] heartbeat: frame=600 exceptions_so_far=0
|
||||||
|
```
|
||||||
|
|
||||||
|
旋转时如果触发异常应该看到:
|
||||||
|
```
|
||||||
|
[14:06:10.112][tid=12345] >> handle_cmd APP_CMD_CONFIG_CHANGED(8) tid=12345
|
||||||
|
[14:06:10.113][tid=12345] >> handle_cmd APP_CMD_TERM_WINDOW(2) tid=12345
|
||||||
|
[14:06:10.113][tid=12345] handle_cmd APP_CMD_TERM_WINDOW: (no handler yet, ...)
|
||||||
|
[14:06:10.113][tid=12345] << handle_cmd APP_CMD_TERM_WINDOW done
|
||||||
|
[14:06:10.250][tid=12345] >> handle_cmd APP_CMD_INIT_WINDOW(1) tid=12345
|
||||||
|
[14:06:10.250][tid=12345] FaceApp::initVulkan enter, call#2 _applicationInited=1 _faceAppInited=1 _secondfaceAppInited=0
|
||||||
|
[14:06:10.250][tid=12345] FaceApp::initVulkan exit, call#2 _applicationInited=1 _faceAppInited=1 _secondfaceAppInited=1
|
||||||
|
[14:06:10.280][tid=12345] drawFrame[1234] vkAcquireNextImageKHR -> -1000001004 (VK_ERROR_OUT_OF_DATE_KHR) currentFrame=0
|
||||||
|
[14:06:10.280][tid=12345] !!! drawFrame std::exception (frame=1234 count=1): failed to acquire swap chain image!
|
||||||
|
```
|
||||||
|
|
||||||
|
**只要日志里能看到 `vkXxxKHR -> <数值> (<名字>)` 这一行,诊断就成立了。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、文件清单(仅供参考,日常不用动)
|
||||||
|
|
||||||
|
```
|
||||||
|
app/src/main/cpp/
|
||||||
|
DebugLog.h 新增
|
||||||
|
DebugLog.cpp 新增
|
||||||
|
main.cpp 改:日志初始化 / 心跳 / try-catch / cmd 日志 / JNI tid
|
||||||
|
vulkan/
|
||||||
|
Application.cpp 改:drawFrame 4 处 VkResult 日志 + VkResultStr
|
||||||
|
FaceApp.cpp 改:initVulkan 入/出口日志
|
||||||
|
CMakeLists.txt 改:加入 DebugLog.cpp 到 Android 构建
|
||||||
|
```
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
//alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
//id 'org.jetbrains.kotlin.android'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.vvl_version='1.4.321.0'
|
ext.vvl_version='1.4.321.0'
|
||||||
apply from: "./download_vvl.gradle"
|
apply from: "./download_vvl.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'com.hmwl.sample'
|
namespace 'com.hmwl.face_sdk'
|
||||||
compileSdk 35
|
compileSdk 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.hmwl.sample"
|
//applicationId "com.hmwl.face_sdk"
|
||||||
minSdk 30
|
minSdk 30
|
||||||
targetSdk 35
|
targetSdk 36
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
||||||
@@ -21,12 +23,25 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
consumerProguardFiles "consumer-rules.pro"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
debuggable true
|
debuggable true
|
||||||
jniDebuggable true
|
jniDebuggable true
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments "-DCMAKE_BUILD_TYPE=Debug"
|
||||||
|
cppFlags "-DDEBUG -O0 -g"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
debugSymbolLevel 'FULL' // Debug 版本包含完整符号
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
release {
|
release {
|
||||||
@@ -35,18 +50,54 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_21
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_21
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加 Kotlin 编译选项
|
||||||
|
// kotlinOptions {
|
||||||
|
// jvmTarget = '17' // 与 Java 版本保持一致
|
||||||
|
// }
|
||||||
|
|
||||||
|
// kotlin {
|
||||||
|
// jvmToolchain(17)
|
||||||
|
// }
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
prefab true
|
prefab true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
jniLibs.srcDirs += [
|
||||||
|
'libs/tasks-vision/jni',
|
||||||
|
'libs/tasks-core/jni'
|
||||||
|
]
|
||||||
|
//java.srcDirs += [
|
||||||
|
// 'libs/tasks-vision/vision_classes',
|
||||||
|
// 'libs/tasks-core/core_classes'
|
||||||
|
//]
|
||||||
|
res.srcDirs += [
|
||||||
|
'libs/tasks-vision/res',
|
||||||
|
'libs/tasks-core/res'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path file('../CMakeLists.txt')
|
path file('../CMakeLists.txt')
|
||||||
version '3.22.1'
|
version '3.22.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
pickFirst '**/*.jar'
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '17'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -54,7 +105,23 @@ dependencies {
|
|||||||
implementation libs.appcompat
|
implementation libs.appcompat
|
||||||
implementation libs.material
|
implementation libs.material
|
||||||
implementation libs.games.activity
|
implementation libs.games.activity
|
||||||
|
implementation libs.androidx.core.core.ktx
|
||||||
testImplementation libs.junit
|
testImplementation libs.junit
|
||||||
androidTestImplementation libs.ext.junit
|
androidTestImplementation libs.ext.junit
|
||||||
androidTestImplementation libs.espresso.core
|
androidTestImplementation libs.espresso.core
|
||||||
|
|
||||||
|
|
||||||
|
implementation 'com.google.protobuf:protobuf-javalite:3.21.12'
|
||||||
|
implementation files('libs/tasks-vision.jar', 'libs/tasks-core.jar')
|
||||||
|
|
||||||
|
//implementation fileTree(dir: 'libs/tasks-vision', include: ['*.jar'])
|
||||||
|
//implementation fileTree(dir: 'libs/tasks-core', include: ['*.jar'])
|
||||||
|
|
||||||
|
|
||||||
|
def camerax_version = '1.4.2'
|
||||||
|
implementation "androidx.camera:camera-core:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-camera2:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-view:$camerax_version"
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
apply plugin: 'com.android.application'
|
//apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022-2024, The Android Open Source Project.
|
* Copyright (c) 2022-2024, The Android Open Source Project.
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.google.mediapipe.tasks.core" >
|
||||||
|
|
||||||
|
<uses-sdk
|
||||||
|
android:minSdkVersion="24"
|
||||||
|
android:targetSdkVersion="34" />
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
int anim fragment_fast_out_extra_slow_in 0x00000000
|
||||||
|
int animator fragment_close_enter 0x00000000
|
||||||
|
int animator fragment_close_exit 0x00000000
|
||||||
|
int animator fragment_fade_enter 0x00000000
|
||||||
|
int animator fragment_fade_exit 0x00000000
|
||||||
|
int animator fragment_open_enter 0x00000000
|
||||||
|
int animator fragment_open_exit 0x00000000
|
||||||
|
int attr alpha 0x00000000
|
||||||
|
int attr coordinatorLayoutStyle 0x00000000
|
||||||
|
int attr font 0x00000000
|
||||||
|
int attr fontProviderAuthority 0x00000000
|
||||||
|
int attr fontProviderCerts 0x00000000
|
||||||
|
int attr fontProviderFetchStrategy 0x00000000
|
||||||
|
int attr fontProviderFetchTimeout 0x00000000
|
||||||
|
int attr fontProviderPackage 0x00000000
|
||||||
|
int attr fontProviderQuery 0x00000000
|
||||||
|
int attr fontStyle 0x00000000
|
||||||
|
int attr fontVariationSettings 0x00000000
|
||||||
|
int attr fontWeight 0x00000000
|
||||||
|
int attr keylines 0x00000000
|
||||||
|
int attr layout_anchor 0x00000000
|
||||||
|
int attr layout_anchorGravity 0x00000000
|
||||||
|
int attr layout_behavior 0x00000000
|
||||||
|
int attr layout_dodgeInsetEdges 0x00000000
|
||||||
|
int attr layout_insetEdge 0x00000000
|
||||||
|
int attr layout_keyline 0x00000000
|
||||||
|
int attr statusBarBackground 0x00000000
|
||||||
|
int attr ttcIndex 0x00000000
|
||||||
|
int color notification_action_color_filter 0x00000000
|
||||||
|
int color notification_icon_bg_color 0x00000000
|
||||||
|
int color notification_material_background_media_default_color 0x00000000
|
||||||
|
int color primary_text_default_material_dark 0x00000000
|
||||||
|
int color ripple_material_light 0x00000000
|
||||||
|
int color secondary_text_default_material_dark 0x00000000
|
||||||
|
int color secondary_text_default_material_light 0x00000000
|
||||||
|
int dimen compat_button_inset_horizontal_material 0x00000000
|
||||||
|
int dimen compat_button_inset_vertical_material 0x00000000
|
||||||
|
int dimen compat_button_padding_horizontal_material 0x00000000
|
||||||
|
int dimen compat_button_padding_vertical_material 0x00000000
|
||||||
|
int dimen compat_control_corner_material 0x00000000
|
||||||
|
int dimen compat_notification_large_icon_max_height 0x00000000
|
||||||
|
int dimen compat_notification_large_icon_max_width 0x00000000
|
||||||
|
int dimen notification_action_icon_size 0x00000000
|
||||||
|
int dimen notification_action_text_size 0x00000000
|
||||||
|
int dimen notification_big_circle_margin 0x00000000
|
||||||
|
int dimen notification_content_margin_start 0x00000000
|
||||||
|
int dimen notification_large_icon_height 0x00000000
|
||||||
|
int dimen notification_large_icon_width 0x00000000
|
||||||
|
int dimen notification_main_column_padding_top 0x00000000
|
||||||
|
int dimen notification_media_narrow_margin 0x00000000
|
||||||
|
int dimen notification_right_icon_size 0x00000000
|
||||||
|
int dimen notification_right_side_padding_top 0x00000000
|
||||||
|
int dimen notification_small_icon_background_padding 0x00000000
|
||||||
|
int dimen notification_small_icon_size_as_large 0x00000000
|
||||||
|
int dimen notification_subtext_size 0x00000000
|
||||||
|
int dimen notification_top_pad 0x00000000
|
||||||
|
int dimen notification_top_pad_large_text 0x00000000
|
||||||
|
int dimen subtitle_corner_radius 0x00000000
|
||||||
|
int dimen subtitle_outline_width 0x00000000
|
||||||
|
int dimen subtitle_shadow_offset 0x00000000
|
||||||
|
int dimen subtitle_shadow_radius 0x00000000
|
||||||
|
int drawable notification_action_background 0x00000000
|
||||||
|
int drawable notification_bg 0x00000000
|
||||||
|
int drawable notification_bg_low 0x00000000
|
||||||
|
int drawable notification_bg_low_normal 0x00000000
|
||||||
|
int drawable notification_bg_low_pressed 0x00000000
|
||||||
|
int drawable notification_bg_normal 0x00000000
|
||||||
|
int drawable notification_bg_normal_pressed 0x00000000
|
||||||
|
int drawable notification_icon_background 0x00000000
|
||||||
|
int drawable notification_template_icon_bg 0x00000000
|
||||||
|
int drawable notification_template_icon_low_bg 0x00000000
|
||||||
|
int drawable notification_tile_bg 0x00000000
|
||||||
|
int drawable notify_panel_notification_icon_bg 0x00000000
|
||||||
|
int id accessibility_action_clickable_span 0x00000000
|
||||||
|
int id accessibility_custom_action_0 0x00000000
|
||||||
|
int id accessibility_custom_action_1 0x00000000
|
||||||
|
int id accessibility_custom_action_10 0x00000000
|
||||||
|
int id accessibility_custom_action_11 0x00000000
|
||||||
|
int id accessibility_custom_action_12 0x00000000
|
||||||
|
int id accessibility_custom_action_13 0x00000000
|
||||||
|
int id accessibility_custom_action_14 0x00000000
|
||||||
|
int id accessibility_custom_action_15 0x00000000
|
||||||
|
int id accessibility_custom_action_16 0x00000000
|
||||||
|
int id accessibility_custom_action_17 0x00000000
|
||||||
|
int id accessibility_custom_action_18 0x00000000
|
||||||
|
int id accessibility_custom_action_19 0x00000000
|
||||||
|
int id accessibility_custom_action_2 0x00000000
|
||||||
|
int id accessibility_custom_action_20 0x00000000
|
||||||
|
int id accessibility_custom_action_21 0x00000000
|
||||||
|
int id accessibility_custom_action_22 0x00000000
|
||||||
|
int id accessibility_custom_action_23 0x00000000
|
||||||
|
int id accessibility_custom_action_24 0x00000000
|
||||||
|
int id accessibility_custom_action_25 0x00000000
|
||||||
|
int id accessibility_custom_action_26 0x00000000
|
||||||
|
int id accessibility_custom_action_27 0x00000000
|
||||||
|
int id accessibility_custom_action_28 0x00000000
|
||||||
|
int id accessibility_custom_action_29 0x00000000
|
||||||
|
int id accessibility_custom_action_3 0x00000000
|
||||||
|
int id accessibility_custom_action_30 0x00000000
|
||||||
|
int id accessibility_custom_action_31 0x00000000
|
||||||
|
int id accessibility_custom_action_4 0x00000000
|
||||||
|
int id accessibility_custom_action_5 0x00000000
|
||||||
|
int id accessibility_custom_action_6 0x00000000
|
||||||
|
int id accessibility_custom_action_7 0x00000000
|
||||||
|
int id accessibility_custom_action_8 0x00000000
|
||||||
|
int id accessibility_custom_action_9 0x00000000
|
||||||
|
int id action0 0x00000000
|
||||||
|
int id action_container 0x00000000
|
||||||
|
int id action_divider 0x00000000
|
||||||
|
int id action_image 0x00000000
|
||||||
|
int id action_text 0x00000000
|
||||||
|
int id actions 0x00000000
|
||||||
|
int id all 0x00000000
|
||||||
|
int id async 0x00000000
|
||||||
|
int id blocking 0x00000000
|
||||||
|
int id bottom 0x00000000
|
||||||
|
int id cancel_action 0x00000000
|
||||||
|
int id center 0x00000000
|
||||||
|
int id center_horizontal 0x00000000
|
||||||
|
int id center_vertical 0x00000000
|
||||||
|
int id chronometer 0x00000000
|
||||||
|
int id clip_horizontal 0x00000000
|
||||||
|
int id clip_vertical 0x00000000
|
||||||
|
int id dialog_button 0x00000000
|
||||||
|
int id end 0x00000000
|
||||||
|
int id end_padder 0x00000000
|
||||||
|
int id fill 0x00000000
|
||||||
|
int id fill_horizontal 0x00000000
|
||||||
|
int id fill_vertical 0x00000000
|
||||||
|
int id forever 0x00000000
|
||||||
|
int id fragment_container_view_tag 0x00000000
|
||||||
|
int id icon 0x00000000
|
||||||
|
int id icon_group 0x00000000
|
||||||
|
int id info 0x00000000
|
||||||
|
int id italic 0x00000000
|
||||||
|
int id left 0x00000000
|
||||||
|
int id line1 0x00000000
|
||||||
|
int id line3 0x00000000
|
||||||
|
int id media_actions 0x00000000
|
||||||
|
int id none 0x00000000
|
||||||
|
int id normal 0x00000000
|
||||||
|
int id notification_background 0x00000000
|
||||||
|
int id notification_main_column 0x00000000
|
||||||
|
int id notification_main_column_container 0x00000000
|
||||||
|
int id right 0x00000000
|
||||||
|
int id right_icon 0x00000000
|
||||||
|
int id right_side 0x00000000
|
||||||
|
int id special_effects_controller_view_tag 0x00000000
|
||||||
|
int id start 0x00000000
|
||||||
|
int id status_bar_latest_event_content 0x00000000
|
||||||
|
int id tag_accessibility_actions 0x00000000
|
||||||
|
int id tag_accessibility_clickable_spans 0x00000000
|
||||||
|
int id tag_accessibility_heading 0x00000000
|
||||||
|
int id tag_accessibility_pane_title 0x00000000
|
||||||
|
int id tag_screen_reader_focusable 0x00000000
|
||||||
|
int id tag_transition_group 0x00000000
|
||||||
|
int id tag_unhandled_key_event_manager 0x00000000
|
||||||
|
int id tag_unhandled_key_listeners 0x00000000
|
||||||
|
int id text 0x00000000
|
||||||
|
int id text2 0x00000000
|
||||||
|
int id time 0x00000000
|
||||||
|
int id title 0x00000000
|
||||||
|
int id top 0x00000000
|
||||||
|
int id view_tree_lifecycle_owner 0x00000000
|
||||||
|
int id view_tree_saved_state_registry_owner 0x00000000
|
||||||
|
int id view_tree_view_model_store_owner 0x00000000
|
||||||
|
int id visible_removing_fragment_view_tag 0x00000000
|
||||||
|
int integer cancel_button_image_alpha 0x00000000
|
||||||
|
int integer status_bar_notification_info_maxnum 0x00000000
|
||||||
|
int layout custom_dialog 0x00000000
|
||||||
|
int layout notification_action 0x00000000
|
||||||
|
int layout notification_action_tombstone 0x00000000
|
||||||
|
int layout notification_media_action 0x00000000
|
||||||
|
int layout notification_media_cancel_action 0x00000000
|
||||||
|
int layout notification_template_big_media 0x00000000
|
||||||
|
int layout notification_template_big_media_custom 0x00000000
|
||||||
|
int layout notification_template_big_media_narrow 0x00000000
|
||||||
|
int layout notification_template_big_media_narrow_custom 0x00000000
|
||||||
|
int layout notification_template_custom_big 0x00000000
|
||||||
|
int layout notification_template_icon_group 0x00000000
|
||||||
|
int layout notification_template_lines_media 0x00000000
|
||||||
|
int layout notification_template_media 0x00000000
|
||||||
|
int layout notification_template_media_custom 0x00000000
|
||||||
|
int layout notification_template_part_chronometer 0x00000000
|
||||||
|
int layout notification_template_part_time 0x00000000
|
||||||
|
int string status_bar_notification_info_overflow 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Info 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Info_Media 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Line2 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Line2_Media 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Media 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Time 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Time_Media 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Title 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Title_Media 0x00000000
|
||||||
|
int style Widget_Compat_NotificationActionContainer 0x00000000
|
||||||
|
int style Widget_Compat_NotificationActionText 0x00000000
|
||||||
|
int style Widget_Support_CoordinatorLayout 0x00000000
|
||||||
|
int[] styleable ColorStateListItem { 0x010101a5, 0x0101031f, 0x00000000 }
|
||||||
|
int styleable ColorStateListItem_android_color 0
|
||||||
|
int styleable ColorStateListItem_android_alpha 1
|
||||||
|
int styleable ColorStateListItem_alpha 2
|
||||||
|
int[] styleable CoordinatorLayout { 0x00000000, 0x00000000 }
|
||||||
|
int styleable CoordinatorLayout_keylines 0
|
||||||
|
int styleable CoordinatorLayout_statusBarBackground 1
|
||||||
|
int[] styleable CoordinatorLayout_Layout { 0x010100b3, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }
|
||||||
|
int styleable CoordinatorLayout_Layout_android_layout_gravity 0
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_anchor 1
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_anchorGravity 2
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_behavior 3
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_dodgeInsetEdges 4
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_insetEdge 5
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_keyline 6
|
||||||
|
int[] styleable FontFamily { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }
|
||||||
|
int styleable FontFamily_fontProviderAuthority 0
|
||||||
|
int styleable FontFamily_fontProviderCerts 1
|
||||||
|
int styleable FontFamily_fontProviderFetchStrategy 2
|
||||||
|
int styleable FontFamily_fontProviderFetchTimeout 3
|
||||||
|
int styleable FontFamily_fontProviderPackage 4
|
||||||
|
int styleable FontFamily_fontProviderQuery 5
|
||||||
|
int[] styleable FontFamilyFont { 0x01010532, 0x01010533, 0x0101053f, 0x0101056f, 0x01010570, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }
|
||||||
|
int styleable FontFamilyFont_android_font 0
|
||||||
|
int styleable FontFamilyFont_android_fontWeight 1
|
||||||
|
int styleable FontFamilyFont_android_fontStyle 2
|
||||||
|
int styleable FontFamilyFont_android_ttcIndex 3
|
||||||
|
int styleable FontFamilyFont_android_fontVariationSettings 4
|
||||||
|
int styleable FontFamilyFont_font 5
|
||||||
|
int styleable FontFamilyFont_fontStyle 6
|
||||||
|
int styleable FontFamilyFont_fontVariationSettings 7
|
||||||
|
int styleable FontFamilyFont_fontWeight 8
|
||||||
|
int styleable FontFamilyFont_ttcIndex 9
|
||||||
|
int[] styleable Fragment { 0x01010003, 0x010100d0, 0x010100d1 }
|
||||||
|
int styleable Fragment_android_name 0
|
||||||
|
int styleable Fragment_android_id 1
|
||||||
|
int styleable Fragment_android_tag 2
|
||||||
|
int[] styleable FragmentContainerView { 0x01010003, 0x010100d1 }
|
||||||
|
int styleable FragmentContainerView_android_name 0
|
||||||
|
int styleable FragmentContainerView_android_tag 1
|
||||||
|
int[] styleable GradientColor { 0x0101019d, 0x0101019e, 0x010101a1, 0x010101a2, 0x010101a3, 0x010101a4, 0x01010201, 0x0101020b, 0x01010510, 0x01010511, 0x01010512, 0x01010513 }
|
||||||
|
int styleable GradientColor_android_startColor 0
|
||||||
|
int styleable GradientColor_android_endColor 1
|
||||||
|
int styleable GradientColor_android_type 2
|
||||||
|
int styleable GradientColor_android_centerX 3
|
||||||
|
int styleable GradientColor_android_centerY 4
|
||||||
|
int styleable GradientColor_android_gradientRadius 5
|
||||||
|
int styleable GradientColor_android_tileMode 6
|
||||||
|
int styleable GradientColor_android_centerColor 7
|
||||||
|
int styleable GradientColor_android_startX 8
|
||||||
|
int styleable GradientColor_android_startY 9
|
||||||
|
int styleable GradientColor_android_endX 10
|
||||||
|
int styleable GradientColor_android_endY 11
|
||||||
|
int[] styleable GradientColorItem { 0x010101a5, 0x01010514 }
|
||||||
|
int styleable GradientColorItem_android_color 0
|
||||||
|
int styleable GradientColorItem_android_offset 1
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.google.mediapipe.tasks.vision" >
|
||||||
|
|
||||||
|
<uses-sdk
|
||||||
|
android:minSdkVersion="24"
|
||||||
|
android:targetSdkVersion="34" />
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
int anim fragment_fast_out_extra_slow_in 0x00000000
|
||||||
|
int animator fragment_close_enter 0x00000000
|
||||||
|
int animator fragment_close_exit 0x00000000
|
||||||
|
int animator fragment_fade_enter 0x00000000
|
||||||
|
int animator fragment_fade_exit 0x00000000
|
||||||
|
int animator fragment_open_enter 0x00000000
|
||||||
|
int animator fragment_open_exit 0x00000000
|
||||||
|
int attr alpha 0x00000000
|
||||||
|
int attr coordinatorLayoutStyle 0x00000000
|
||||||
|
int attr font 0x00000000
|
||||||
|
int attr fontProviderAuthority 0x00000000
|
||||||
|
int attr fontProviderCerts 0x00000000
|
||||||
|
int attr fontProviderFetchStrategy 0x00000000
|
||||||
|
int attr fontProviderFetchTimeout 0x00000000
|
||||||
|
int attr fontProviderPackage 0x00000000
|
||||||
|
int attr fontProviderQuery 0x00000000
|
||||||
|
int attr fontStyle 0x00000000
|
||||||
|
int attr fontVariationSettings 0x00000000
|
||||||
|
int attr fontWeight 0x00000000
|
||||||
|
int attr keylines 0x00000000
|
||||||
|
int attr layout_anchor 0x00000000
|
||||||
|
int attr layout_anchorGravity 0x00000000
|
||||||
|
int attr layout_behavior 0x00000000
|
||||||
|
int attr layout_dodgeInsetEdges 0x00000000
|
||||||
|
int attr layout_insetEdge 0x00000000
|
||||||
|
int attr layout_keyline 0x00000000
|
||||||
|
int attr statusBarBackground 0x00000000
|
||||||
|
int attr ttcIndex 0x00000000
|
||||||
|
int color notification_action_color_filter 0x00000000
|
||||||
|
int color notification_icon_bg_color 0x00000000
|
||||||
|
int color notification_material_background_media_default_color 0x00000000
|
||||||
|
int color primary_text_default_material_dark 0x00000000
|
||||||
|
int color ripple_material_light 0x00000000
|
||||||
|
int color secondary_text_default_material_dark 0x00000000
|
||||||
|
int color secondary_text_default_material_light 0x00000000
|
||||||
|
int dimen compat_button_inset_horizontal_material 0x00000000
|
||||||
|
int dimen compat_button_inset_vertical_material 0x00000000
|
||||||
|
int dimen compat_button_padding_horizontal_material 0x00000000
|
||||||
|
int dimen compat_button_padding_vertical_material 0x00000000
|
||||||
|
int dimen compat_control_corner_material 0x00000000
|
||||||
|
int dimen compat_notification_large_icon_max_height 0x00000000
|
||||||
|
int dimen compat_notification_large_icon_max_width 0x00000000
|
||||||
|
int dimen notification_action_icon_size 0x00000000
|
||||||
|
int dimen notification_action_text_size 0x00000000
|
||||||
|
int dimen notification_big_circle_margin 0x00000000
|
||||||
|
int dimen notification_content_margin_start 0x00000000
|
||||||
|
int dimen notification_large_icon_height 0x00000000
|
||||||
|
int dimen notification_large_icon_width 0x00000000
|
||||||
|
int dimen notification_main_column_padding_top 0x00000000
|
||||||
|
int dimen notification_media_narrow_margin 0x00000000
|
||||||
|
int dimen notification_right_icon_size 0x00000000
|
||||||
|
int dimen notification_right_side_padding_top 0x00000000
|
||||||
|
int dimen notification_small_icon_background_padding 0x00000000
|
||||||
|
int dimen notification_small_icon_size_as_large 0x00000000
|
||||||
|
int dimen notification_subtext_size 0x00000000
|
||||||
|
int dimen notification_top_pad 0x00000000
|
||||||
|
int dimen notification_top_pad_large_text 0x00000000
|
||||||
|
int dimen subtitle_corner_radius 0x00000000
|
||||||
|
int dimen subtitle_outline_width 0x00000000
|
||||||
|
int dimen subtitle_shadow_offset 0x00000000
|
||||||
|
int dimen subtitle_shadow_radius 0x00000000
|
||||||
|
int drawable notification_action_background 0x00000000
|
||||||
|
int drawable notification_bg 0x00000000
|
||||||
|
int drawable notification_bg_low 0x00000000
|
||||||
|
int drawable notification_bg_low_normal 0x00000000
|
||||||
|
int drawable notification_bg_low_pressed 0x00000000
|
||||||
|
int drawable notification_bg_normal 0x00000000
|
||||||
|
int drawable notification_bg_normal_pressed 0x00000000
|
||||||
|
int drawable notification_icon_background 0x00000000
|
||||||
|
int drawable notification_template_icon_bg 0x00000000
|
||||||
|
int drawable notification_template_icon_low_bg 0x00000000
|
||||||
|
int drawable notification_tile_bg 0x00000000
|
||||||
|
int drawable notify_panel_notification_icon_bg 0x00000000
|
||||||
|
int id accessibility_action_clickable_span 0x00000000
|
||||||
|
int id accessibility_custom_action_0 0x00000000
|
||||||
|
int id accessibility_custom_action_1 0x00000000
|
||||||
|
int id accessibility_custom_action_10 0x00000000
|
||||||
|
int id accessibility_custom_action_11 0x00000000
|
||||||
|
int id accessibility_custom_action_12 0x00000000
|
||||||
|
int id accessibility_custom_action_13 0x00000000
|
||||||
|
int id accessibility_custom_action_14 0x00000000
|
||||||
|
int id accessibility_custom_action_15 0x00000000
|
||||||
|
int id accessibility_custom_action_16 0x00000000
|
||||||
|
int id accessibility_custom_action_17 0x00000000
|
||||||
|
int id accessibility_custom_action_18 0x00000000
|
||||||
|
int id accessibility_custom_action_19 0x00000000
|
||||||
|
int id accessibility_custom_action_2 0x00000000
|
||||||
|
int id accessibility_custom_action_20 0x00000000
|
||||||
|
int id accessibility_custom_action_21 0x00000000
|
||||||
|
int id accessibility_custom_action_22 0x00000000
|
||||||
|
int id accessibility_custom_action_23 0x00000000
|
||||||
|
int id accessibility_custom_action_24 0x00000000
|
||||||
|
int id accessibility_custom_action_25 0x00000000
|
||||||
|
int id accessibility_custom_action_26 0x00000000
|
||||||
|
int id accessibility_custom_action_27 0x00000000
|
||||||
|
int id accessibility_custom_action_28 0x00000000
|
||||||
|
int id accessibility_custom_action_29 0x00000000
|
||||||
|
int id accessibility_custom_action_3 0x00000000
|
||||||
|
int id accessibility_custom_action_30 0x00000000
|
||||||
|
int id accessibility_custom_action_31 0x00000000
|
||||||
|
int id accessibility_custom_action_4 0x00000000
|
||||||
|
int id accessibility_custom_action_5 0x00000000
|
||||||
|
int id accessibility_custom_action_6 0x00000000
|
||||||
|
int id accessibility_custom_action_7 0x00000000
|
||||||
|
int id accessibility_custom_action_8 0x00000000
|
||||||
|
int id accessibility_custom_action_9 0x00000000
|
||||||
|
int id action0 0x00000000
|
||||||
|
int id action_container 0x00000000
|
||||||
|
int id action_divider 0x00000000
|
||||||
|
int id action_image 0x00000000
|
||||||
|
int id action_text 0x00000000
|
||||||
|
int id actions 0x00000000
|
||||||
|
int id all 0x00000000
|
||||||
|
int id async 0x00000000
|
||||||
|
int id blocking 0x00000000
|
||||||
|
int id bottom 0x00000000
|
||||||
|
int id cancel_action 0x00000000
|
||||||
|
int id center 0x00000000
|
||||||
|
int id center_horizontal 0x00000000
|
||||||
|
int id center_vertical 0x00000000
|
||||||
|
int id chronometer 0x00000000
|
||||||
|
int id clip_horizontal 0x00000000
|
||||||
|
int id clip_vertical 0x00000000
|
||||||
|
int id dialog_button 0x00000000
|
||||||
|
int id end 0x00000000
|
||||||
|
int id end_padder 0x00000000
|
||||||
|
int id fill 0x00000000
|
||||||
|
int id fill_horizontal 0x00000000
|
||||||
|
int id fill_vertical 0x00000000
|
||||||
|
int id forever 0x00000000
|
||||||
|
int id fragment_container_view_tag 0x00000000
|
||||||
|
int id icon 0x00000000
|
||||||
|
int id icon_group 0x00000000
|
||||||
|
int id info 0x00000000
|
||||||
|
int id italic 0x00000000
|
||||||
|
int id left 0x00000000
|
||||||
|
int id line1 0x00000000
|
||||||
|
int id line3 0x00000000
|
||||||
|
int id media_actions 0x00000000
|
||||||
|
int id none 0x00000000
|
||||||
|
int id normal 0x00000000
|
||||||
|
int id notification_background 0x00000000
|
||||||
|
int id notification_main_column 0x00000000
|
||||||
|
int id notification_main_column_container 0x00000000
|
||||||
|
int id right 0x00000000
|
||||||
|
int id right_icon 0x00000000
|
||||||
|
int id right_side 0x00000000
|
||||||
|
int id special_effects_controller_view_tag 0x00000000
|
||||||
|
int id start 0x00000000
|
||||||
|
int id status_bar_latest_event_content 0x00000000
|
||||||
|
int id tag_accessibility_actions 0x00000000
|
||||||
|
int id tag_accessibility_clickable_spans 0x00000000
|
||||||
|
int id tag_accessibility_heading 0x00000000
|
||||||
|
int id tag_accessibility_pane_title 0x00000000
|
||||||
|
int id tag_screen_reader_focusable 0x00000000
|
||||||
|
int id tag_transition_group 0x00000000
|
||||||
|
int id tag_unhandled_key_event_manager 0x00000000
|
||||||
|
int id tag_unhandled_key_listeners 0x00000000
|
||||||
|
int id text 0x00000000
|
||||||
|
int id text2 0x00000000
|
||||||
|
int id time 0x00000000
|
||||||
|
int id title 0x00000000
|
||||||
|
int id top 0x00000000
|
||||||
|
int id view_tree_lifecycle_owner 0x00000000
|
||||||
|
int id view_tree_saved_state_registry_owner 0x00000000
|
||||||
|
int id view_tree_view_model_store_owner 0x00000000
|
||||||
|
int id visible_removing_fragment_view_tag 0x00000000
|
||||||
|
int integer cancel_button_image_alpha 0x00000000
|
||||||
|
int integer status_bar_notification_info_maxnum 0x00000000
|
||||||
|
int layout custom_dialog 0x00000000
|
||||||
|
int layout notification_action 0x00000000
|
||||||
|
int layout notification_action_tombstone 0x00000000
|
||||||
|
int layout notification_media_action 0x00000000
|
||||||
|
int layout notification_media_cancel_action 0x00000000
|
||||||
|
int layout notification_template_big_media 0x00000000
|
||||||
|
int layout notification_template_big_media_custom 0x00000000
|
||||||
|
int layout notification_template_big_media_narrow 0x00000000
|
||||||
|
int layout notification_template_big_media_narrow_custom 0x00000000
|
||||||
|
int layout notification_template_custom_big 0x00000000
|
||||||
|
int layout notification_template_icon_group 0x00000000
|
||||||
|
int layout notification_template_lines_media 0x00000000
|
||||||
|
int layout notification_template_media 0x00000000
|
||||||
|
int layout notification_template_media_custom 0x00000000
|
||||||
|
int layout notification_template_part_chronometer 0x00000000
|
||||||
|
int layout notification_template_part_time 0x00000000
|
||||||
|
int string status_bar_notification_info_overflow 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Info 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Info_Media 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Line2 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Line2_Media 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Media 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Time 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Time_Media 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Title 0x00000000
|
||||||
|
int style TextAppearance_Compat_Notification_Title_Media 0x00000000
|
||||||
|
int style Widget_Compat_NotificationActionContainer 0x00000000
|
||||||
|
int style Widget_Compat_NotificationActionText 0x00000000
|
||||||
|
int style Widget_Support_CoordinatorLayout 0x00000000
|
||||||
|
int[] styleable ColorStateListItem { 0x010101a5, 0x0101031f, 0x00000000 }
|
||||||
|
int styleable ColorStateListItem_android_color 0
|
||||||
|
int styleable ColorStateListItem_android_alpha 1
|
||||||
|
int styleable ColorStateListItem_alpha 2
|
||||||
|
int[] styleable CoordinatorLayout { 0x00000000, 0x00000000 }
|
||||||
|
int styleable CoordinatorLayout_keylines 0
|
||||||
|
int styleable CoordinatorLayout_statusBarBackground 1
|
||||||
|
int[] styleable CoordinatorLayout_Layout { 0x010100b3, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }
|
||||||
|
int styleable CoordinatorLayout_Layout_android_layout_gravity 0
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_anchor 1
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_anchorGravity 2
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_behavior 3
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_dodgeInsetEdges 4
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_insetEdge 5
|
||||||
|
int styleable CoordinatorLayout_Layout_layout_keyline 6
|
||||||
|
int[] styleable FontFamily { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }
|
||||||
|
int styleable FontFamily_fontProviderAuthority 0
|
||||||
|
int styleable FontFamily_fontProviderCerts 1
|
||||||
|
int styleable FontFamily_fontProviderFetchStrategy 2
|
||||||
|
int styleable FontFamily_fontProviderFetchTimeout 3
|
||||||
|
int styleable FontFamily_fontProviderPackage 4
|
||||||
|
int styleable FontFamily_fontProviderQuery 5
|
||||||
|
int[] styleable FontFamilyFont { 0x01010532, 0x01010533, 0x0101053f, 0x0101056f, 0x01010570, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }
|
||||||
|
int styleable FontFamilyFont_android_font 0
|
||||||
|
int styleable FontFamilyFont_android_fontWeight 1
|
||||||
|
int styleable FontFamilyFont_android_fontStyle 2
|
||||||
|
int styleable FontFamilyFont_android_ttcIndex 3
|
||||||
|
int styleable FontFamilyFont_android_fontVariationSettings 4
|
||||||
|
int styleable FontFamilyFont_font 5
|
||||||
|
int styleable FontFamilyFont_fontStyle 6
|
||||||
|
int styleable FontFamilyFont_fontVariationSettings 7
|
||||||
|
int styleable FontFamilyFont_fontWeight 8
|
||||||
|
int styleable FontFamilyFont_ttcIndex 9
|
||||||
|
int[] styleable Fragment { 0x01010003, 0x010100d0, 0x010100d1 }
|
||||||
|
int styleable Fragment_android_name 0
|
||||||
|
int styleable Fragment_android_id 1
|
||||||
|
int styleable Fragment_android_tag 2
|
||||||
|
int[] styleable FragmentContainerView { 0x01010003, 0x010100d1 }
|
||||||
|
int styleable FragmentContainerView_android_name 0
|
||||||
|
int styleable FragmentContainerView_android_tag 1
|
||||||
|
int[] styleable GradientColor { 0x0101019d, 0x0101019e, 0x010101a1, 0x010101a2, 0x010101a3, 0x010101a4, 0x01010201, 0x0101020b, 0x01010510, 0x01010511, 0x01010512, 0x01010513 }
|
||||||
|
int styleable GradientColor_android_startColor 0
|
||||||
|
int styleable GradientColor_android_endColor 1
|
||||||
|
int styleable GradientColor_android_type 2
|
||||||
|
int styleable GradientColor_android_centerX 3
|
||||||
|
int styleable GradientColor_android_centerY 4
|
||||||
|
int styleable GradientColor_android_gradientRadius 5
|
||||||
|
int styleable GradientColor_android_tileMode 6
|
||||||
|
int styleable GradientColor_android_centerColor 7
|
||||||
|
int styleable GradientColor_android_startX 8
|
||||||
|
int styleable GradientColor_android_startY 9
|
||||||
|
int styleable GradientColor_android_endX 10
|
||||||
|
int styleable GradientColor_android_endY 11
|
||||||
|
int[] styleable GradientColorItem { 0x010101a5, 0x01010514 }
|
||||||
|
int styleable GradientColorItem_android_color 0
|
||||||
|
int styleable GradientColorItem_android_offset 1
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.hmwl.sample;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
public void useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
||||||
assertEquals("com.hmwl.sample", appContext.getPackageName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,27 +2,28 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<application
|
<!-- 声明摄像头硬件特性 -->
|
||||||
android:allowBackup="true"
|
<uses-feature
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:name="android.hardware.camera"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:required="false" />
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:theme="@style/Theme.Sample">
|
|
||||||
<activity
|
|
||||||
android:name=".MainActivity"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<!-- 声明摄像头权限 -->
|
||||||
</intent-filter>
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<!-- 声明 MainActivity -->
|
||||||
|
<!-- 作为库,exported 通常设为 false,除非明确需要外部应用启动 -->
|
||||||
|
<activity
|
||||||
|
android:name=".FaceActivity"
|
||||||
|
android:exported="true"
|
||||||
|
tools:ignore="MissingClass"> <!-- 如果IDE报类找不到,可以添加这个 -->
|
||||||
|
|
||||||
|
<!-- 重要:移除 MAIN/LAUNCHER intent-filter -->
|
||||||
|
<!-- 让使用库的应用来决定哪个是主Activity -->
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.lib_name"
|
android:name="android.app.lib_name"
|
||||||
android:value="sample" />
|
android:value="face_sdk" />
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,41 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// 输入变量(与顶点着色器输出对应)
|
||||||
|
layout(location = 0) in vec2 inTexCoord;
|
||||||
|
|
||||||
|
// 输出颜色
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
// 纹理采样器
|
||||||
|
layout(binding = 0) uniform sampler2D texSampler;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float zoom;
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
float radius;
|
||||||
|
float ux;
|
||||||
|
float uy;
|
||||||
|
} pushConstants;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// outFragColor = color;
|
||||||
|
// 获取当前片元的屏幕坐标(左下角为原点)
|
||||||
|
vec2 fragCoord = gl_FragCoord.xy;
|
||||||
|
|
||||||
|
// 屏幕中心(480x480 的中心是 (240, 240))
|
||||||
|
vec2 center = vec2(240.0, 240.0);
|
||||||
|
|
||||||
|
// 计算到中心的距离(欧氏距离,单位:像素)
|
||||||
|
float dist = length(fragCoord - center);
|
||||||
|
|
||||||
|
// 判断是否在半径之外
|
||||||
|
if (dist > pushConstants.radius) {
|
||||||
|
// 使用 push constant 中的 RGB 颜色(注意 alpha 设为 1.0)
|
||||||
|
outColor = vec4(pushConstants.r, pushConstants.g, pushConstants.b, 1.0);
|
||||||
|
} else {
|
||||||
|
// 采样纹理
|
||||||
|
outColor = texture(texSampler, inTexCoord);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// 硬编码的顶点数据 - 4个顶点组成三角形带覆盖整个屏幕
|
||||||
|
// const vec2 positions[6] = vec2[6](
|
||||||
|
// vec2( 1.0, 1.0), // 右上 - 三角形1
|
||||||
|
// vec2(-1.0, 1.0), // 左上 - 三角形1
|
||||||
|
// vec2(-1.0, -1.0), // 左下 - 三角形1
|
||||||
|
|
||||||
|
// vec2(-1.0, -1.0), // 左下 - 三角形2
|
||||||
|
// vec2( 1.0, -1.0), // 右下 - 三角形2
|
||||||
|
// vec2( 1.0, 1.0) // 右上 - 三角形2
|
||||||
|
// );
|
||||||
|
|
||||||
|
const float offset = (640-480)/(480.0);
|
||||||
|
|
||||||
|
|
||||||
|
const vec2 positions[6] = vec2[6](
|
||||||
|
vec2( 1.0, -1.0 -offset), // 右下
|
||||||
|
vec2(1.0, 1.0 + offset), // 右上 - 三角形1
|
||||||
|
vec2(-1.0, 1.0 + offset), // 左上 - 三角形1
|
||||||
|
|
||||||
|
vec2(-1.0, 1.0 + offset), // 左上 - 三角形2
|
||||||
|
vec2( -1.0, -1.0-offset), // 左下 - 三角形2
|
||||||
|
vec2( 1.0, -1.0-offset) // 右下 - 三角形2
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const vec2 texCoords[6] = vec2[6](
|
||||||
|
vec2(1.0, 1.0), // 右上
|
||||||
|
vec2(0.0, 1.0), // 左上
|
||||||
|
vec2(0.0, 0.0), // 左下
|
||||||
|
|
||||||
|
vec2(0.0, 0.0), // 左下
|
||||||
|
vec2(1.0, 0.0), // 右下
|
||||||
|
vec2(1.0, 1.0) // 右上
|
||||||
|
);
|
||||||
|
|
||||||
|
// 定义 Push Constant,只有一个 float
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float zoom;
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
float radius;
|
||||||
|
float ux;
|
||||||
|
float uy;
|
||||||
|
float offset_x;
|
||||||
|
float offset_y;
|
||||||
|
} pushConstants;
|
||||||
|
|
||||||
|
|
||||||
|
// 输出变量
|
||||||
|
layout(location = 0) out vec2 outTexCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// 获取顶点位置
|
||||||
|
vec2 position = positions[gl_VertexIndex];
|
||||||
|
position = position * pushConstants.zoom;
|
||||||
|
position.x = position.x + (pushConstants.offset_x/480);
|
||||||
|
position.y = position.y + (pushConstants.offset_y/480);
|
||||||
|
|
||||||
|
// 设置输出位置(Vulkan使用不同的坐标系)
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
|
||||||
|
// 输出纹理坐标
|
||||||
|
outTexCoord = texCoords[gl_VertexIndex];
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 fragColor;
|
||||||
|
layout(location = 1) in vec3 fragLineStart;
|
||||||
|
layout(location = 2) in vec3 fragLineEnd;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(binding = 0) uniform UniformBufferObject {
|
||||||
|
mat4 proj;
|
||||||
|
mat4 model;
|
||||||
|
mat4 view;
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
layout(binding = 1) uniform DashParameters {
|
||||||
|
float dashSize;
|
||||||
|
float gapSize;
|
||||||
|
float dashOffset;
|
||||||
|
float uAASize;
|
||||||
|
int useWorldSpace;
|
||||||
|
} dashParams;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 fragPos = gl_FragCoord.xy;
|
||||||
|
|
||||||
|
// 网格参数
|
||||||
|
float gridSize = 14.0; // 网格大小
|
||||||
|
float dotRadius = 6.0; // 点状半径
|
||||||
|
|
||||||
|
// 计算当前片段所在的网格和网格中心坐标
|
||||||
|
vec2 gridCoord = fragPos / gridSize;
|
||||||
|
vec2 gridCenter = (floor(gridCoord) + 0.5) * gridSize;
|
||||||
|
|
||||||
|
// 计算片段到网格中心的距离
|
||||||
|
float distanceToCenter = length(fragPos - gridCenter);
|
||||||
|
|
||||||
|
// 如果距离超过点状半径,丢弃(创建点状虚线)
|
||||||
|
if (distanceToCenter > dotRadius && fragColor.y > 0.5) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
outColor = vec4(fragColor, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 inPosition;
|
||||||
|
layout(location = 1) in vec3 inColor;
|
||||||
|
layout(location = 2) in vec3 inLineStart;
|
||||||
|
layout(location = 3) in vec3 inLineEnd;
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 fragColor;
|
||||||
|
layout(location = 1) out vec3 fragLineStart;
|
||||||
|
layout(location = 2) out vec3 fragLineEnd;
|
||||||
|
|
||||||
|
|
||||||
|
layout(binding = 0) uniform UniformBufferObject {
|
||||||
|
mat4 proj;
|
||||||
|
mat4 model;
|
||||||
|
mat4 view;
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float myValue;
|
||||||
|
} pushConstants;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
mat4 worldviewproj = ubo.proj * ubo.view * ubo.model;
|
||||||
|
//gl_Position = worldviewproj * vec4(inPosition, 1.0);
|
||||||
|
|
||||||
|
vec2 position = vec2(inPosition.xy*2 -1);
|
||||||
|
//gl_Position = vec4(inPosition.xy*2 -1, 0.5, 1.0);
|
||||||
|
position = position * pushConstants.myValue;
|
||||||
|
gl_Position = vec4(position, 0.5, 1.0);
|
||||||
|
|
||||||
|
fragColor = inColor;
|
||||||
|
|
||||||
|
fragLineStart = (worldviewproj * vec4(inLineStart, 1.0)).xyz;
|
||||||
|
fragLineEnd = (worldviewproj * vec4(inLineEnd, 1.0)).xyz;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 fragColor;
|
||||||
|
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
outColor = vec4(1, 0, 0, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 inPosition;
|
||||||
|
layout(location = 1) in vec3 inColor;
|
||||||
|
layout(location = 2) in vec3 inLineStart;
|
||||||
|
layout(location = 3) in vec3 inLineEnd;
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 fragColor;
|
||||||
|
|
||||||
|
layout(binding = 0) uniform UniformBufferObject {
|
||||||
|
mat4 proj;
|
||||||
|
mat4 model;
|
||||||
|
mat4 view;
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
//mat4 worldviewproj = ubo.proj * ubo.view * ubo.model;
|
||||||
|
mat4 mvp = ubo.proj * ubo.view * ubo.model;
|
||||||
|
//gl_Position = worldviewproj * vec4(inPosition, 1.0);
|
||||||
|
gl_Position = mvp * vec4(inPosition, 1.0);
|
||||||
|
|
||||||
|
fragColor = inColor;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// 从顶点着色器接收的颜色
|
||||||
|
layout(location = 0) in vec3 fragColor;
|
||||||
|
|
||||||
|
// 输出颜色
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
outColor = vec4(fragColor, 1.0); // 使用传入的颜色
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 inPosition;
|
||||||
|
layout(location = 1) in vec3 inColor;
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 fragColor;
|
||||||
|
|
||||||
|
layout(binding = 0) uniform UniformBufferObject {
|
||||||
|
mat4 proj;
|
||||||
|
mat4 model;
|
||||||
|
mat4 view;
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float myValue;
|
||||||
|
} pushConstants;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// 预计算MVP矩阵以减少乘法次数
|
||||||
|
//mat4 mvp = ubo.proj * ubo.view * ubo.model;
|
||||||
|
//vec3 pos = vec3(2.0, inPosition.y, inPosition.z);
|
||||||
|
//gl_Position = mvp * vec4(inPosition, 1.0);
|
||||||
|
|
||||||
|
vec2 position = vec2(inPosition.xy*2 -1);
|
||||||
|
//gl_Position = vec4(inPosition.xy*2 -1, 0.5, 1.0);
|
||||||
|
position = position * pushConstants.myValue;
|
||||||
|
gl_Position = vec4(position, 0.5, 1.0);
|
||||||
|
|
||||||
|
fragColor = inColor;
|
||||||
|
gl_PointSize = 2.0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (binding = 1) uniform sampler2D samplerColor;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 inUV;
|
||||||
|
layout (location = 1) in vec3 inNormal;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float zoom;
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
float radius;
|
||||||
|
float ux;
|
||||||
|
float uy;
|
||||||
|
} pushConstants;
|
||||||
|
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 outFragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// outFragColor = color;
|
||||||
|
// 获取当前片元的屏幕坐标(左下角为原点)
|
||||||
|
vec2 fragCoord = gl_FragCoord.xy;
|
||||||
|
|
||||||
|
// 屏幕中心(480x480 的中心是 (240, 240))
|
||||||
|
vec2 center = vec2(240.0, 240.0);
|
||||||
|
|
||||||
|
// 计算到中心的距离(欧氏距离,单位:像素)
|
||||||
|
float dist = length(fragCoord - center);
|
||||||
|
|
||||||
|
// 判断是否在半径之外
|
||||||
|
if (dist > pushConstants.radius) {
|
||||||
|
// 使用 push constant 中的 RGB 颜色(注意 alpha 设为 1.0)
|
||||||
|
outFragColor = vec4(pushConstants.r, pushConstants.g, pushConstants.b, 1.0);
|
||||||
|
} else {
|
||||||
|
vec2 pos = vec2(pushConstants.ux/4096.f, pushConstants.uy/2048.f);
|
||||||
|
vec2 uv = pos + vec2(inUV.x/8.f, inUV.y/4.f);
|
||||||
|
vec4 color = texture(samplerColor, uv);
|
||||||
|
outFragColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 inPos;
|
||||||
|
layout (location = 1) in vec2 inUV;
|
||||||
|
layout (location = 2) in vec3 inNormal;
|
||||||
|
|
||||||
|
layout (binding = 0) uniform UBO
|
||||||
|
{
|
||||||
|
mat4 projection;
|
||||||
|
mat4 model;
|
||||||
|
vec4 viewPos;
|
||||||
|
float lodBias;
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
layout (location = 0) out vec2 outUV;
|
||||||
|
layout (location = 1) out vec3 outNormal;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float zoom;
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
float radius;
|
||||||
|
float ux;
|
||||||
|
float uy;
|
||||||
|
float offset_x;
|
||||||
|
float offset_y;
|
||||||
|
} pushConstants;
|
||||||
|
|
||||||
|
out gl_PerVertex
|
||||||
|
{
|
||||||
|
vec4 gl_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
outUV = inUV;
|
||||||
|
|
||||||
|
vec2 position = vec2(inPos.xy*2 -1);
|
||||||
|
position = position * pushConstants.zoom;
|
||||||
|
position.x = position.x + (pushConstants.offset_x/480);
|
||||||
|
position.y = position.y + (pushConstants.offset_y/480);
|
||||||
|
gl_Position = vec4(position, 0.5, 1.0);
|
||||||
|
|
||||||
|
vec4 pos = ubo.model * vec4(inPos, 1.0);
|
||||||
|
outNormal = mat3(inverse(transpose(ubo.model))) * inNormal;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (binding = 1) uniform sampler2D samplerColor;
|
||||||
|
layout (binding = 2) uniform sampler2D samplerColor_ex;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 inUV;
|
||||||
|
layout (location = 1) in vec3 inNormal;
|
||||||
|
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 outFragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 color = texture(samplerColor, inUV);
|
||||||
|
vec4 color_ex = texture(samplerColor_ex, inUV);
|
||||||
|
|
||||||
|
vec3 N = normalize(inNormal);
|
||||||
|
|
||||||
|
// 计算与 (0,0,1) 的点乘
|
||||||
|
float ndot = dot(N, vec3(0.0, 0.0, -1.0));
|
||||||
|
|
||||||
|
// 保证结果 >= 0
|
||||||
|
ndot = max(ndot, 0.0);
|
||||||
|
|
||||||
|
// 重新映射ndot值,让中间2/3部分进行融合
|
||||||
|
// 当ndot在[0.166, 0.833]范围内时进行混合,两端保持不变
|
||||||
|
float blendFactor;
|
||||||
|
if (ndot < 0.0) {
|
||||||
|
blendFactor = 0.0; // 完全使用color_ex
|
||||||
|
} else if (ndot > 0.1) {
|
||||||
|
blendFactor = 1.0; // 完全使用color
|
||||||
|
} else {
|
||||||
|
// 在中间2/3部分进行平滑过渡
|
||||||
|
blendFactor = (ndot - 0.0) / (1 - 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用smoothstep创建中间2/3的融合区域
|
||||||
|
// 参数说明:smoothstep(edge0, edge1, x)
|
||||||
|
// 当x < edge0时返回0,当x > edge1时返回1,在中间时平滑过渡
|
||||||
|
//float blendFactor = smoothstep(0.166, 0.833, ndot);
|
||||||
|
|
||||||
|
|
||||||
|
// vec4 blendedColor = mix(color_ex, color, ndot);
|
||||||
|
|
||||||
|
// outFragColor = blendedColor;
|
||||||
|
|
||||||
|
if (ndot < 0.3){
|
||||||
|
outFragColor = color_ex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outFragColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// outFragColor = vec4(1, 0, 0, 1);
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,512 @@
|
|||||||
|
// CrashHandler.cpp
|
||||||
|
//
|
||||||
|
// Async-signal-safe crash dumper for the face_sdk native library. The goal
|
||||||
|
// is that the next time the app dies (e.g. another FORTIFY: pthread_mutex_lock
|
||||||
|
// called on a destroyed mutex from inside the Vulkan driver) we don't only
|
||||||
|
// have logcat — we also have a self-contained dump on the device's app data
|
||||||
|
// directory that the user can pull off and send back, even after a reboot.
|
||||||
|
//
|
||||||
|
// Why we need this:
|
||||||
|
// - DebugLog only writes "happy path" events. The Vulkan crash happens
|
||||||
|
// synchronously inside vkQueueSubmit / vkFreeCommandBuffers — there is
|
||||||
|
// no DebugLog call near the crash site, so the file log just stops.
|
||||||
|
// - logcat survives across the crash (debuggerd dumps the backtrace there)
|
||||||
|
// but logcat is volatile: a couple of reboots, a logcat -c, or a long
|
||||||
|
// idle period and it's gone. We want a persistent file.
|
||||||
|
// - tombstones in /data/tombstones/ are root-only on consumer devices.
|
||||||
|
//
|
||||||
|
// Implementation notes:
|
||||||
|
// - Inside a signal handler we MUST stick to async-signal-safe APIs
|
||||||
|
// (man 7 signal-safety). That rules out fprintf / snprintf / malloc.
|
||||||
|
// We use write(), our own integer-to-string conversion, and a single
|
||||||
|
// pre-opened fd. dladdr() is technically not on the POSIX safe list but
|
||||||
|
// bionic's implementation only takes one rwlock and is widely used in
|
||||||
|
// other crash dumpers (breakpad, crashpad, libunwindstack). We accept
|
||||||
|
// that risk because the alternative is no symbol at all.
|
||||||
|
// - We use <unwind.h> (_Unwind_Backtrace) instead of execinfo.h because
|
||||||
|
// bionic doesn't ship execinfo.h on all NDK levels, and _Unwind_Backtrace
|
||||||
|
// is the same primitive Android's own tombstoned uses.
|
||||||
|
// - We re-raise the original signal with the default handler at the end so
|
||||||
|
// the OS still produces a tombstone / ANR record for vendors that
|
||||||
|
// read /data/tombstones/.
|
||||||
|
|
||||||
|
#include "CrashHandler.h"
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <unwind.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// ---------- Globals (touched from the handler -> only POD/atomics) ---------
|
||||||
|
|
||||||
|
constexpr size_t kCrashStackSize = 64 * 1024; // sigaltstack
|
||||||
|
constexpr size_t kMaxFrames = 64;
|
||||||
|
constexpr size_t kNoteCapacity = 256;
|
||||||
|
|
||||||
|
uint8_t g_sigStack[kCrashStackSize];
|
||||||
|
int g_crashFd = -1; // crash log fd (append, sync)
|
||||||
|
int g_debugFd = -1; // optional: also dup write to debug log
|
||||||
|
std::atomic<bool> g_installed{false};
|
||||||
|
std::atomic<bool> g_handlingCrash{false};
|
||||||
|
|
||||||
|
// Single writer of g_note: setNote() (uses memcpy under a tiny lock, but the
|
||||||
|
// handler reads byte-by-byte so a torn read at most produces a truncated
|
||||||
|
// note, never a deref of bad memory).
|
||||||
|
char g_note[kNoteCapacity] = {0};
|
||||||
|
pthread_mutex_t g_noteMtx = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
const int g_signals[] = { SIGSEGV, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSYS };
|
||||||
|
constexpr size_t kNumSignals = sizeof(g_signals) / sizeof(g_signals[0]);
|
||||||
|
|
||||||
|
// ----------------------- Async-signal-safe writers -------------------------
|
||||||
|
|
||||||
|
// Write a NUL-terminated string. Drops the trailing NUL.
|
||||||
|
void sigWrite(int fd, const char* s) {
|
||||||
|
if (fd < 0 || !s) return;
|
||||||
|
size_t n = 0;
|
||||||
|
while (s[n]) ++n;
|
||||||
|
if (n == 0) return;
|
||||||
|
// Loop until everything is written or we error out. Async-safe.
|
||||||
|
while (n > 0) {
|
||||||
|
ssize_t w = write(fd, s, n);
|
||||||
|
if (w <= 0) {
|
||||||
|
if (w < 0 && errno == EINTR) continue;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s += w;
|
||||||
|
n -= (size_t)w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write n bytes from buf. Used for the note (may contain anything).
|
||||||
|
void sigWriteN(int fd, const char* buf, size_t n) {
|
||||||
|
if (fd < 0 || !buf) return;
|
||||||
|
while (n > 0) {
|
||||||
|
ssize_t w = write(fd, buf, n);
|
||||||
|
if (w <= 0) {
|
||||||
|
if (w < 0 && errno == EINTR) continue;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buf += w;
|
||||||
|
n -= (size_t)w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the same string to both the crash log and the debug log (if any).
|
||||||
|
void sigDump(const char* s) {
|
||||||
|
sigWrite(g_crashFd, s);
|
||||||
|
sigWrite(g_debugFd, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert an unsigned integer to its decimal representation. Returns the
|
||||||
|
// number of characters written into buf (without a NUL).
|
||||||
|
size_t u64ToDec(uint64_t v, char* buf, size_t cap) {
|
||||||
|
if (cap == 0) return 0;
|
||||||
|
char tmp[32];
|
||||||
|
size_t i = 0;
|
||||||
|
if (v == 0) {
|
||||||
|
tmp[i++] = '0';
|
||||||
|
} else {
|
||||||
|
while (v && i < sizeof(tmp)) {
|
||||||
|
tmp[i++] = (char)('0' + (v % 10));
|
||||||
|
v /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t out = (i < cap) ? i : cap;
|
||||||
|
for (size_t k = 0; k < out; ++k) {
|
||||||
|
buf[k] = tmp[i - 1 - k];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert an unsigned 64-bit value to a fixed-width 16-digit hex string.
|
||||||
|
// Useful for PC values.
|
||||||
|
size_t u64ToHex16(uint64_t v, char* buf, size_t cap) {
|
||||||
|
static const char digits[] = "0123456789abcdef";
|
||||||
|
if (cap < 16) return 0;
|
||||||
|
for (int i = 15; i >= 0; --i) {
|
||||||
|
buf[i] = digits[v & 0xF];
|
||||||
|
v >>= 4;
|
||||||
|
}
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write "<key>=<u64>\n".
|
||||||
|
void sigWriteKV_u64(int fd, const char* key, uint64_t v) {
|
||||||
|
sigWrite(fd, key);
|
||||||
|
sigWrite(fd, "=");
|
||||||
|
char num[24];
|
||||||
|
size_t n = u64ToDec(v, num, sizeof(num));
|
||||||
|
sigWriteN(fd, num, n);
|
||||||
|
sigWrite(fd, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write "<key>=0x<hex>\n".
|
||||||
|
void sigWriteKV_ptr(int fd, const char* key, uint64_t v) {
|
||||||
|
sigWrite(fd, key);
|
||||||
|
sigWrite(fd, "=0x");
|
||||||
|
char hx[16];
|
||||||
|
u64ToHex16(v, hx, sizeof(hx));
|
||||||
|
sigWriteN(fd, hx, 16);
|
||||||
|
sigWrite(fd, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------- Signal name lookup ------------------------------
|
||||||
|
|
||||||
|
const char* signalName(int signo) {
|
||||||
|
switch (signo) {
|
||||||
|
case SIGSEGV: return "SIGSEGV";
|
||||||
|
case SIGABRT: return "SIGABRT";
|
||||||
|
case SIGBUS: return "SIGBUS";
|
||||||
|
case SIGFPE: return "SIGFPE";
|
||||||
|
case SIGILL: return "SIGILL";
|
||||||
|
case SIGSYS: return "SIGSYS";
|
||||||
|
case SIGTRAP: return "SIGTRAP";
|
||||||
|
default: return "SIG?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// si_code to short string. Only the most common ones; everything else falls
|
||||||
|
// back to the numeric value via sigWriteKV_u64.
|
||||||
|
const char* siCodeName(int signo, int code) {
|
||||||
|
switch (signo) {
|
||||||
|
case SIGSEGV:
|
||||||
|
if (code == SEGV_MAPERR) return "SEGV_MAPERR";
|
||||||
|
if (code == SEGV_ACCERR) return "SEGV_ACCERR";
|
||||||
|
break;
|
||||||
|
case SIGBUS:
|
||||||
|
if (code == BUS_ADRALN) return "BUS_ADRALN";
|
||||||
|
if (code == BUS_ADRERR) return "BUS_ADRERR";
|
||||||
|
if (code == BUS_OBJERR) return "BUS_OBJERR";
|
||||||
|
break;
|
||||||
|
case SIGFPE:
|
||||||
|
if (code == FPE_INTDIV) return "FPE_INTDIV";
|
||||||
|
if (code == FPE_INTOVF) return "FPE_INTOVF";
|
||||||
|
if (code == FPE_FLTDIV) return "FPE_FLTDIV";
|
||||||
|
break;
|
||||||
|
case SIGILL:
|
||||||
|
if (code == ILL_ILLOPC) return "ILL_ILLOPC";
|
||||||
|
if (code == ILL_ILLOPN) return "ILL_ILLOPN";
|
||||||
|
break;
|
||||||
|
case SIGABRT:
|
||||||
|
if (code == SI_TKILL) return "SI_TKILL";
|
||||||
|
if (code == SI_USER) return "SI_USER";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------- Backtrace via _Unwind_Backtrace -----------------
|
||||||
|
|
||||||
|
struct UnwindCtx {
|
||||||
|
uintptr_t* frames;
|
||||||
|
size_t count;
|
||||||
|
size_t cap;
|
||||||
|
};
|
||||||
|
|
||||||
|
_Unwind_Reason_Code unwindCallback(_Unwind_Context* ctx, void* arg) {
|
||||||
|
UnwindCtx* uc = static_cast<UnwindCtx*>(arg);
|
||||||
|
if (uc->count >= uc->cap) return _URC_END_OF_STACK;
|
||||||
|
|
||||||
|
uintptr_t pc = _Unwind_GetIP(ctx);
|
||||||
|
if (pc) {
|
||||||
|
// Trim Thumb bit on 32-bit ARM. No-op on aarch64/x86_64.
|
||||||
|
pc &= ~(uintptr_t)1;
|
||||||
|
uc->frames[uc->count++] = pc;
|
||||||
|
}
|
||||||
|
return _URC_NO_REASON;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t captureBacktrace(uintptr_t* out, size_t cap) {
|
||||||
|
UnwindCtx uc{out, 0, cap};
|
||||||
|
_Unwind_Backtrace(&unwindCallback, &uc);
|
||||||
|
return uc.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump one frame: " #02 pc 000000000000abcd /path/lib.so (Symbol+0x10)"
|
||||||
|
void dumpFrame(int fd, size_t idx, uintptr_t pc) {
|
||||||
|
sigWrite(fd, " #");
|
||||||
|
char num[8];
|
||||||
|
if (idx < 10) {
|
||||||
|
num[0] = '0';
|
||||||
|
num[1] = (char)('0' + idx);
|
||||||
|
sigWriteN(fd, num, 2);
|
||||||
|
} else {
|
||||||
|
size_t n = u64ToDec(idx, num, sizeof(num));
|
||||||
|
sigWriteN(fd, num, n);
|
||||||
|
}
|
||||||
|
sigWrite(fd, " pc ");
|
||||||
|
char hx[16];
|
||||||
|
u64ToHex16((uint64_t)pc, hx, sizeof(hx));
|
||||||
|
sigWriteN(fd, hx, 16);
|
||||||
|
|
||||||
|
Dl_info info;
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
if (dladdr(reinterpret_cast<void*>(pc), &info) && info.dli_fname) {
|
||||||
|
sigWrite(fd, " ");
|
||||||
|
sigWrite(fd, info.dli_fname);
|
||||||
|
|
||||||
|
if (info.dli_sname) {
|
||||||
|
uintptr_t sym = reinterpret_cast<uintptr_t>(info.dli_saddr);
|
||||||
|
uintptr_t off = (sym && pc >= sym) ? (pc - sym) : 0;
|
||||||
|
sigWrite(fd, " (");
|
||||||
|
sigWrite(fd, info.dli_sname);
|
||||||
|
sigWrite(fd, "+0x");
|
||||||
|
char ohx[16];
|
||||||
|
u64ToHex16((uint64_t)off, ohx, sizeof(ohx));
|
||||||
|
sigWriteN(fd, ohx, 16);
|
||||||
|
sigWrite(fd, ")");
|
||||||
|
} else if (info.dli_fbase) {
|
||||||
|
uintptr_t base = reinterpret_cast<uintptr_t>(info.dli_fbase);
|
||||||
|
uintptr_t off = (pc >= base) ? (pc - base) : 0;
|
||||||
|
sigWrite(fd, " (offset 0x");
|
||||||
|
char ohx[16];
|
||||||
|
u64ToHex16((uint64_t)off, ohx, sizeof(ohx));
|
||||||
|
sigWriteN(fd, ohx, 16);
|
||||||
|
sigWrite(fd, ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sigWrite(fd, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------- Time + tid helpers ------------------------------
|
||||||
|
|
||||||
|
// Builds "YYYY-MM-DD HH:MM:SS.mmm UTC" into the given buffer (no NUL).
|
||||||
|
// Returns the number of bytes written. Async-signal-safe (no stdio, no
|
||||||
|
// localtime_r tz lookups).
|
||||||
|
size_t formatTimestamp(char* b, size_t cap) {
|
||||||
|
timespec ts{};
|
||||||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
struct tm tm_info{};
|
||||||
|
time_t s = ts.tv_sec;
|
||||||
|
gmtime_r(&s, &tm_info);
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
auto putUInt = [&](unsigned v, int width) {
|
||||||
|
char tmp[8];
|
||||||
|
size_t n = u64ToDec(v, tmp, sizeof(tmp));
|
||||||
|
while ((int)n < (size_t)width) {
|
||||||
|
if (i < cap) b[i++] = '0';
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
for (size_t k = 0; k < n; ++k) {
|
||||||
|
if (i < cap) b[i++] = tmp[k];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
putUInt((unsigned)(tm_info.tm_year + 1900), 4); if (i < cap) b[i++] = '-';
|
||||||
|
putUInt((unsigned)(tm_info.tm_mon + 1), 2); if (i < cap) b[i++] = '-';
|
||||||
|
putUInt((unsigned)(tm_info.tm_mday), 2); if (i < cap) b[i++] = ' ';
|
||||||
|
putUInt((unsigned)(tm_info.tm_hour), 2); if (i < cap) b[i++] = ':';
|
||||||
|
putUInt((unsigned)(tm_info.tm_min), 2); if (i < cap) b[i++] = ':';
|
||||||
|
putUInt((unsigned)(tm_info.tm_sec), 2); if (i < cap) b[i++] = '.';
|
||||||
|
putUInt((unsigned)(ts.tv_nsec / 1000000), 3);
|
||||||
|
static const char kSuffix[] = " UTC";
|
||||||
|
for (size_t k = 0; k < sizeof(kSuffix) - 1; ++k) {
|
||||||
|
if (i < cap) b[i++] = kSuffix[k];
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t currentTid() {
|
||||||
|
return static_cast<pid_t>(syscall(SYS_gettid));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------- Signal handler ----------------------------------
|
||||||
|
|
||||||
|
void crashHandler(int signo, siginfo_t* info, void* ucontext) {
|
||||||
|
(void)ucontext;
|
||||||
|
|
||||||
|
// Re-entry guard: if a second signal fires while we're dumping (e.g. our
|
||||||
|
// own dladdr trips a SIGSEGV) just chain to the default handler.
|
||||||
|
bool expected = false;
|
||||||
|
if (!g_handlingCrash.compare_exchange_strong(expected, true,
|
||||||
|
std::memory_order_acq_rel)) {
|
||||||
|
// Already in handler -> default + bail.
|
||||||
|
signal(signo, SIG_DFL);
|
||||||
|
raise(signo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sigDump("\n========== FACE_SDK CRASH ==========\n");
|
||||||
|
sigDump("time=");
|
||||||
|
{
|
||||||
|
char tsbuf[48];
|
||||||
|
size_t tn = formatTimestamp(tsbuf, sizeof(tsbuf));
|
||||||
|
sigWriteN(g_crashFd, tsbuf, tn);
|
||||||
|
sigWriteN(g_debugFd, tsbuf, tn);
|
||||||
|
}
|
||||||
|
sigDump("\n");
|
||||||
|
|
||||||
|
sigDump("signal=");
|
||||||
|
sigDump(signalName(signo));
|
||||||
|
sigDump(" code=");
|
||||||
|
sigDump(siCodeName(signo, info ? info->si_code : 0));
|
||||||
|
sigDump("\n");
|
||||||
|
|
||||||
|
if (info) {
|
||||||
|
sigWriteKV_u64(g_crashFd, "si_signo", (uint64_t)info->si_signo);
|
||||||
|
sigWriteKV_u64(g_debugFd, "si_signo", (uint64_t)info->si_signo);
|
||||||
|
sigWriteKV_u64(g_crashFd, "si_code", (uint64_t)info->si_code);
|
||||||
|
sigWriteKV_u64(g_debugFd, "si_code", (uint64_t)info->si_code);
|
||||||
|
sigWriteKV_ptr(g_crashFd, "si_addr", (uint64_t)(uintptr_t)info->si_addr);
|
||||||
|
sigWriteKV_ptr(g_debugFd, "si_addr", (uint64_t)(uintptr_t)info->si_addr);
|
||||||
|
}
|
||||||
|
sigWriteKV_u64(g_crashFd, "pid", (uint64_t)getpid());
|
||||||
|
sigWriteKV_u64(g_debugFd, "pid", (uint64_t)getpid());
|
||||||
|
sigWriteKV_u64(g_crashFd, "tid", (uint64_t)currentTid());
|
||||||
|
sigWriteKV_u64(g_debugFd, "tid", (uint64_t)currentTid());
|
||||||
|
|
||||||
|
// Note (current frame index, current motion, ...).
|
||||||
|
sigDump("note=");
|
||||||
|
sigWriteN(g_crashFd, g_note, strnlen(g_note, kNoteCapacity));
|
||||||
|
sigWriteN(g_debugFd, g_note, strnlen(g_note, kNoteCapacity));
|
||||||
|
sigDump("\n");
|
||||||
|
|
||||||
|
sigDump("backtrace:\n");
|
||||||
|
uintptr_t frames[kMaxFrames];
|
||||||
|
size_t nf = captureBacktrace(frames, kMaxFrames);
|
||||||
|
for (size_t i = 0; i < nf; ++i) {
|
||||||
|
dumpFrame(g_crashFd, i, frames[i]);
|
||||||
|
dumpFrame(g_debugFd, i, frames[i]);
|
||||||
|
}
|
||||||
|
sigDump("==================================\n");
|
||||||
|
|
||||||
|
// Make sure everything reaches disk before we self-destruct.
|
||||||
|
if (g_crashFd >= 0) fsync(g_crashFd);
|
||||||
|
if (g_debugFd >= 0) fsync(g_debugFd);
|
||||||
|
|
||||||
|
// Mirror to logcat too so a quick `adb logcat -d` after a reboot still
|
||||||
|
// shows the SIGNAL line (helps cross-checking the file).
|
||||||
|
__android_log_print(ANDROID_LOG_FATAL, "FACE_DBG_CRASH",
|
||||||
|
"fatal %s @ tid=%d, see face_sdk_crash.log",
|
||||||
|
signalName(signo), currentTid());
|
||||||
|
|
||||||
|
// Restore default handler and re-raise. This produces /data/tombstones/*
|
||||||
|
// on rooted devices and tells debuggerd to print the official Android
|
||||||
|
// backtrace into logcat (`crash_dump64 ... DEBUG`).
|
||||||
|
struct sigaction dfl{};
|
||||||
|
dfl.sa_handler = SIG_DFL;
|
||||||
|
sigemptyset(&dfl.sa_mask);
|
||||||
|
sigaction(signo, &dfl, nullptr);
|
||||||
|
raise(signo);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace CrashHandler {
|
||||||
|
|
||||||
|
void install(const std::string& internalDataPath,
|
||||||
|
const std::string& debugLogPath) {
|
||||||
|
bool expected = false;
|
||||||
|
if (!g_installed.compare_exchange_strong(expected, true,
|
||||||
|
std::memory_order_acq_rel)) {
|
||||||
|
return; // already installed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the persistent crash log file in append mode. We keep it open
|
||||||
|
// forever so the signal handler doesn't have to call open() (which is
|
||||||
|
// safe but slow).
|
||||||
|
{
|
||||||
|
std::string path = internalDataPath.empty()
|
||||||
|
? std::string("/data/local/tmp/face_sdk_crash.log")
|
||||||
|
: internalDataPath + "/face_sdk_crash.log";
|
||||||
|
g_crashFd = open(path.c_str(),
|
||||||
|
O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC,
|
||||||
|
0644);
|
||||||
|
if (g_crashFd >= 0) {
|
||||||
|
// Header for the new run.
|
||||||
|
sigWrite(g_crashFd, "\n=== CrashHandler installed pid=");
|
||||||
|
char pidbuf[16];
|
||||||
|
size_t n = u64ToDec((uint64_t)getpid(), pidbuf, sizeof(pidbuf));
|
||||||
|
sigWriteN(g_crashFd, pidbuf, n);
|
||||||
|
sigWrite(g_crashFd, " ===\n");
|
||||||
|
fsync(g_crashFd);
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG",
|
||||||
|
"CrashHandler log file: %s", path.c_str());
|
||||||
|
} else {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
|
||||||
|
"CrashHandler failed to open %s: %s",
|
||||||
|
path.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also keep a writable fd to the DebugLog file (if any) so dumps land
|
||||||
|
// next to the regular tail of the log. We don't touch g_fp inside
|
||||||
|
// DebugLog because that would need its mutex — not safe in handler.
|
||||||
|
if (!debugLogPath.empty()) {
|
||||||
|
g_debugFd = open(debugLogPath.c_str(),
|
||||||
|
O_WRONLY | O_APPEND | O_CLOEXEC,
|
||||||
|
0644);
|
||||||
|
if (g_debugFd < 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
|
||||||
|
"CrashHandler: cannot open debug log %s: %s",
|
||||||
|
debugLogPath.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up an alternate stack so we still have stack space if the original
|
||||||
|
// thread ran out (very common for Vulkan crashes inside deep driver
|
||||||
|
// call chains).
|
||||||
|
stack_t ss{};
|
||||||
|
ss.ss_sp = g_sigStack;
|
||||||
|
ss.ss_size = sizeof(g_sigStack);
|
||||||
|
ss.ss_flags = 0;
|
||||||
|
if (sigaltstack(&ss, nullptr) != 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
|
||||||
|
"CrashHandler: sigaltstack failed: %s",
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigaction sa{};
|
||||||
|
sa.sa_sigaction = &crashHandler;
|
||||||
|
sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kNumSignals; ++i) {
|
||||||
|
if (sigaction(g_signals[i], &sa, nullptr) != 0) {
|
||||||
|
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
|
||||||
|
"CrashHandler: sigaction(%d) failed: %s",
|
||||||
|
g_signals[i], strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG",
|
||||||
|
"CrashHandler installed for SIGSEGV/SIGABRT/SIGBUS/SIGFPE/SIGILL/SIGSYS");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNote(const char* note) {
|
||||||
|
if (!note) note = "";
|
||||||
|
pthread_mutex_lock(&g_noteMtx);
|
||||||
|
size_t n = strnlen(note, kNoteCapacity - 1);
|
||||||
|
memcpy(g_note, note, n);
|
||||||
|
g_note[n] = '\0';
|
||||||
|
pthread_mutex_unlock(&g_noteMtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace CrashHandler
|
||||||
|
|
||||||
|
#else // _WIN32 — no-op on host build
|
||||||
|
|
||||||
|
namespace CrashHandler {
|
||||||
|
void install(const std::string&, const std::string&) {}
|
||||||
|
void setNote(const char*) {}
|
||||||
|
} // namespace CrashHandler
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef FACE_SDK_CRASH_HANDLER_H
|
||||||
|
#define FACE_SDK_CRASH_HANDLER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace CrashHandler {
|
||||||
|
|
||||||
|
// Install signal handlers for SIGSEGV / SIGABRT / SIGBUS / SIGFPE / SIGILL.
|
||||||
|
//
|
||||||
|
// On a fatal signal we:
|
||||||
|
// 1) Switch to a pre-allocated 64KB sigaltstack so we still have stack
|
||||||
|
// space even if the original thread's stack was the cause.
|
||||||
|
// 2) Write a self-contained crash record to <internalDataPath>/face_sdk_crash.log
|
||||||
|
// (and also try to append it to the DebugLog file passed in).
|
||||||
|
// The record contains: signal name, si_code, si_addr, pid, tid,
|
||||||
|
// a synchronous unwound backtrace (PC + module + offset for each frame),
|
||||||
|
// and the value of any extra context the caller registered via setNote().
|
||||||
|
// 3) Re-raise the original signal with the default handler so that the
|
||||||
|
// Android tombstone pipeline still produces /data/tombstones/* files.
|
||||||
|
//
|
||||||
|
// Safe to call once. Subsequent calls are no-ops. Must be called AFTER
|
||||||
|
// DebugLog::init() so we know the log directory.
|
||||||
|
void install(const std::string& internalDataPath,
|
||||||
|
const std::string& debugLogPath = "");
|
||||||
|
|
||||||
|
// Optional short note (<= 256 chars) appended to every crash dump from now on.
|
||||||
|
// Useful to record "current frame index", "current motion", etc. so we know
|
||||||
|
// what was happening at the moment of the crash. Async-signal-safe to read.
|
||||||
|
void setNote(const char* note);
|
||||||
|
|
||||||
|
} // namespace CrashHandler
|
||||||
|
|
||||||
|
#endif // FACE_SDK_CRASH_HANDLER_H
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#include "DebugLog.h"
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <ctime>
|
||||||
|
#include <cstring>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::mutex g_mtx;
|
||||||
|
FILE* g_fp = nullptr;
|
||||||
|
std::string g_path;
|
||||||
|
constexpr size_t kMaxSize = 2 * 1024 * 1024; // 2 MB before rotation
|
||||||
|
|
||||||
|
// Throttle bookkeeping: one slot per distinct key passed to log_throttled().
|
||||||
|
struct ThrottleSlot {
|
||||||
|
int64_t last_emit_ms = 0; // when we last actually wrote a line for this key
|
||||||
|
uint32_t suppressed = 0; // number of calls swallowed since last_emit_ms
|
||||||
|
};
|
||||||
|
std::unordered_map<std::string, ThrottleSlot> g_throttle;
|
||||||
|
constexpr int64_t kThrottleWindowMs = 2000; // 2 seconds
|
||||||
|
|
||||||
|
pid_t currentTid() {
|
||||||
|
return static_cast<pid_t>(syscall(SYS_gettid));
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t nowMs() {
|
||||||
|
timespec ts{};
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return static_cast<int64_t>(ts.tv_sec) * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes g_mtx is held. Writes a single pre-formatted line to disk + logcat.
|
||||||
|
void writeLineLocked(const char* body) {
|
||||||
|
pid_t tid = currentTid();
|
||||||
|
|
||||||
|
timespec ts{};
|
||||||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
struct tm tm_info{};
|
||||||
|
localtime_r(&ts.tv_sec, &tm_info);
|
||||||
|
char timebuf[32];
|
||||||
|
strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm_info);
|
||||||
|
|
||||||
|
if (g_fp) {
|
||||||
|
std::fprintf(g_fp, "[%s.%03ld][tid=%d] %s\n",
|
||||||
|
timebuf, ts.tv_nsec / 1000000, tid, body);
|
||||||
|
std::fflush(g_fp);
|
||||||
|
}
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG", "[tid=%d] %s", tid, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace DebugLog {
|
||||||
|
|
||||||
|
void init(const char* internalDataPath) {
|
||||||
|
std::lock_guard<std::mutex> lk(g_mtx);
|
||||||
|
if (g_fp) {
|
||||||
|
return; // already initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* base = (internalDataPath && *internalDataPath) ? internalDataPath : "/data/local/tmp";
|
||||||
|
g_path = std::string(base) + "/face_sdk_debug.log";
|
||||||
|
|
||||||
|
// Rotate once if current file is too large.
|
||||||
|
struct stat st{};
|
||||||
|
if (stat(g_path.c_str(), &st) == 0 && static_cast<size_t>(st.st_size) > kMaxSize) {
|
||||||
|
std::string backup = g_path + ".old";
|
||||||
|
std::remove(backup.c_str());
|
||||||
|
std::rename(g_path.c_str(), backup.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
g_fp = std::fopen(g_path.c_str(), "a");
|
||||||
|
if (g_fp) {
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
struct tm tm_info{};
|
||||||
|
localtime_r(&now, &tm_info);
|
||||||
|
char tbuf[64];
|
||||||
|
strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_info);
|
||||||
|
std::fprintf(g_fp,
|
||||||
|
"\n========== DebugLog opened @ %s (pid=%d) ==========\n",
|
||||||
|
tbuf, getpid());
|
||||||
|
std::fflush(g_fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG",
|
||||||
|
"DebugLog file path: %s", g_path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void log(const char* fmt, ...) {
|
||||||
|
char buf[1024];
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
int n = std::vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
if (n < 0) return;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lk(g_mtx);
|
||||||
|
writeLineLocked(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_throttled(const char* key, const char* fmt, ...) {
|
||||||
|
char buf[1024];
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
int n = std::vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
if (n < 0) return;
|
||||||
|
|
||||||
|
const char* k = key ? key : "";
|
||||||
|
int64_t now = nowMs();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lk(g_mtx);
|
||||||
|
auto it = g_throttle.find(k);
|
||||||
|
if (it == g_throttle.end()) {
|
||||||
|
// First time we see this key -> always log.
|
||||||
|
ThrottleSlot slot;
|
||||||
|
slot.last_emit_ms = now;
|
||||||
|
slot.suppressed = 0;
|
||||||
|
g_throttle.emplace(k, slot);
|
||||||
|
writeLineLocked(buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrottleSlot& slot = it->second;
|
||||||
|
int64_t elapsed = now - slot.last_emit_ms;
|
||||||
|
if (elapsed < kThrottleWindowMs) {
|
||||||
|
// Still inside the 2s window -> swallow.
|
||||||
|
++slot.suppressed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window elapsed: emit the line, annotated with how many we swallowed.
|
||||||
|
if (slot.suppressed > 0) {
|
||||||
|
char annotated[1200];
|
||||||
|
std::snprintf(annotated, sizeof(annotated),
|
||||||
|
"%s (repeated %u times in last %lldms, key=%s)",
|
||||||
|
buf, slot.suppressed, (long long)elapsed, k);
|
||||||
|
writeLineLocked(annotated);
|
||||||
|
} else {
|
||||||
|
writeLineLocked(buf);
|
||||||
|
}
|
||||||
|
slot.last_emit_ms = now;
|
||||||
|
slot.suppressed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush() {
|
||||||
|
std::lock_guard<std::mutex> lk(g_mtx);
|
||||||
|
if (g_fp) std::fflush(g_fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getLogPath() {
|
||||||
|
std::lock_guard<std::mutex> lk(g_mtx);
|
||||||
|
return g_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace DebugLog
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef FACE_SDK_DEBUGLOG_H
|
||||||
|
#define FACE_SDK_DEBUGLOG_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace DebugLog {
|
||||||
|
|
||||||
|
// Call once (e.g. in android_main) with android_app->activity->internalDataPath.
|
||||||
|
// Safe to call multiple times; subsequent calls are no-ops.
|
||||||
|
void init(const char* internalDataPath);
|
||||||
|
|
||||||
|
// Thread-safe. Also mirrored to logcat with tag "FACE_DBG".
|
||||||
|
// Every line is prefixed with timestamp + thread id and flushed to disk immediately,
|
||||||
|
// so even a hard crash will preserve the tail.
|
||||||
|
void log(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||||
|
|
||||||
|
// Throttled variant keyed by a caller-supplied string.
|
||||||
|
// - The first call with a given key is always written.
|
||||||
|
// - Subsequent calls with the same key within 2 seconds are suppressed.
|
||||||
|
// - When the throttle window elapses, the next matching call is written with a
|
||||||
|
// "(repeated N times in last Xms)" suffix describing the suppressed ones.
|
||||||
|
// Use stable, short keys (e.g. "drawFrame.acquire", "drawFrame.present").
|
||||||
|
void log_throttled(const char* key, const char* fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||||
|
|
||||||
|
// Force flush to disk.
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
// Absolute path of the log file (valid after init()).
|
||||||
|
std::string getLogPath();
|
||||||
|
|
||||||
|
} // namespace DebugLog
|
||||||
|
|
||||||
|
#endif // FACE_SDK_DEBUGLOG_H
|
||||||
@@ -3,44 +3,112 @@
|
|||||||
#include <game-activity/native_app_glue/android_native_app_glue.h>
|
#include <game-activity/native_app_glue/android_native_app_glue.h>
|
||||||
#include <game-activity/GameActivity.h>
|
#include <game-activity/GameActivity.h>
|
||||||
#include "AndroidOut.h"
|
#include "AndroidOut.h"
|
||||||
#include "Application.h"
|
#include "DebugLog.h"
|
||||||
|
#include "CrashHandler.h"
|
||||||
|
#include "FaceApp.h"
|
||||||
#include <android/asset_manager.h>
|
#include <android/asset_manager.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
static inline pid_t dbg_tid() {
|
||||||
|
return static_cast<pid_t>(syscall(SYS_gettid));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* appCmdName(int32_t cmd) {
|
||||||
|
switch (cmd) {
|
||||||
|
case APP_CMD_INIT_WINDOW: return "APP_CMD_INIT_WINDOW";
|
||||||
|
case APP_CMD_TERM_WINDOW: return "APP_CMD_TERM_WINDOW";
|
||||||
|
case APP_CMD_WINDOW_RESIZED: return "APP_CMD_WINDOW_RESIZED";
|
||||||
|
case APP_CMD_WINDOW_REDRAW_NEEDED: return "APP_CMD_WINDOW_REDRAW_NEEDED";
|
||||||
|
case APP_CMD_CONTENT_RECT_CHANGED: return "APP_CMD_CONTENT_RECT_CHANGED";
|
||||||
|
case APP_CMD_GAINED_FOCUS: return "APP_CMD_GAINED_FOCUS";
|
||||||
|
case APP_CMD_LOST_FOCUS: return "APP_CMD_LOST_FOCUS";
|
||||||
|
case APP_CMD_CONFIG_CHANGED: return "APP_CMD_CONFIG_CHANGED";
|
||||||
|
case APP_CMD_LOW_MEMORY: return "APP_CMD_LOW_MEMORY";
|
||||||
|
case APP_CMD_START: return "APP_CMD_START";
|
||||||
|
case APP_CMD_RESUME: return "APP_CMD_RESUME";
|
||||||
|
case APP_CMD_SAVE_STATE: return "APP_CMD_SAVE_STATE";
|
||||||
|
case APP_CMD_PAUSE: return "APP_CMD_PAUSE";
|
||||||
|
case APP_CMD_STOP: return "APP_CMD_STOP";
|
||||||
|
case APP_CMD_DESTROY: return "APP_CMD_DESTROY";
|
||||||
|
default: return "APP_CMD_???";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
android_app* g_android_app = nullptr;
|
android_app* g_android_app = nullptr;
|
||||||
Application* g_Application = nullptr;
|
FaceApp* g_Application = nullptr;
|
||||||
AAssetManager* g_assetManager = nullptr;
|
AAssetManager* g_assetManager = nullptr;
|
||||||
|
|
||||||
|
jobject g_callback = nullptr;
|
||||||
|
jobject g_callbackAnimationFinished = nullptr;
|
||||||
|
|
||||||
void handle_cmd(android_app *pApp, int32_t cmd) {
|
void handle_cmd(android_app *pApp, int32_t cmd) {
|
||||||
|
DebugLog::log(">> handle_cmd %s(%d) tid=%d", appCmdName(cmd), (int)cmd, dbg_tid());
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case APP_CMD_INIT_WINDOW:
|
case APP_CMD_INIT_WINDOW:
|
||||||
aout << "APP_CMD_INIT_WINDOW" << std::endl;
|
aout << "APP_CMD_INIT_WINDOW" << std::endl;
|
||||||
g_Application->initVulkan();
|
DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: calling onWindowInit()");
|
||||||
|
if (g_Application != nullptr) {
|
||||||
|
g_Application->onWindowInit();
|
||||||
|
DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: onWindowInit() returned, isInited=%d",
|
||||||
|
(int)g_Application->isInited());
|
||||||
|
} else {
|
||||||
|
DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: g_Application is null, skipping");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case APP_CMD_TERM_WINDOW:
|
case APP_CMD_TERM_WINDOW:
|
||||||
aout << "APP_CMD_TERM_WINDOW" << std::endl;
|
aout << "APP_CMD_TERM_WINDOW" << std::endl;
|
||||||
|
DebugLog::log("handle_cmd APP_CMD_TERM_WINDOW: calling onWindowLost()");
|
||||||
|
if (g_Application != nullptr) {
|
||||||
|
g_Application->onWindowLost();
|
||||||
|
}
|
||||||
|
DebugLog::log("handle_cmd APP_CMD_TERM_WINDOW: onWindowLost() returned");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
DebugLog::log("<< handle_cmd %s done", appCmdName(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int ArgLen = 128*1024;
|
||||||
|
char g_InitArgString[ArgLen] = {0};
|
||||||
|
|
||||||
void android_main(struct android_app *pApp) {
|
void android_main(struct android_app *pApp) {
|
||||||
|
DebugLog::init(pApp->activity->internalDataPath);
|
||||||
|
// 安装信号处理器越早越好:之前的 FORTIFY: pthread_mutex_lock 崩溃
|
||||||
|
// 是裸的 SIGABRT,没有任何 signal handler,所以 DebugLog 文件停在最后
|
||||||
|
// 一条业务日志、看不到任何崩溃栈。装上之后任何 fatal signal 都会把:
|
||||||
|
// * 信号名 / si_code / si_addr / pid / tid / 当前 note
|
||||||
|
// * 完整 backtrace(PC + 模块 + 符号 + 偏移)
|
||||||
|
// 同步落到 internalDataPath/face_sdk_crash.log(以及 DebugLog 文件尾),
|
||||||
|
// 然后再走默认 handler 让 debuggerd 产生 tombstone。
|
||||||
|
CrashHandler::install(pApp->activity->internalDataPath,
|
||||||
|
DebugLog::getLogPath());
|
||||||
|
DebugLog::log("android_main start, pid=%d tid=%d, logPath=%s",
|
||||||
|
getpid(), dbg_tid(), DebugLog::getLogPath().c_str());
|
||||||
aout << "Welcome to android_main" << std::endl;
|
aout << "Welcome to android_main" << std::endl;
|
||||||
g_android_app = pApp;
|
g_android_app = pApp;
|
||||||
g_assetManager = pApp->activity->assetManager;
|
g_assetManager = pApp->activity->assetManager;
|
||||||
Application application;
|
//FaceApp application;
|
||||||
g_Application = &application;
|
//g_Application = &application;
|
||||||
|
if(g_Application == nullptr) {
|
||||||
|
g_Application = new FaceApp();
|
||||||
|
}
|
||||||
|
g_Application->SetInitArg(g_InitArgString);
|
||||||
pApp->onAppCmd = handle_cmd;
|
pApp->onAppCmd = handle_cmd;
|
||||||
|
long long last_update_time = 0;
|
||||||
android_app_set_motion_event_filter(pApp, nullptr);
|
android_app_set_motion_event_filter(pApp, nullptr);
|
||||||
|
long long _lastDrawFrameTime = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
|
||||||
do {
|
do {
|
||||||
auto ms = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
|
auto start_time = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
|
||||||
// Process all pending events before running game logic.
|
// Process all pending events before running game logic.
|
||||||
bool done = false;
|
bool done = false;
|
||||||
while (!done) {
|
while (!done) {
|
||||||
@@ -66,13 +134,361 @@ void android_main(struct android_app *pApp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(application.isInited())
|
// isInited() checks _applicationInited && _faceAppInited && _sceondInited,
|
||||||
|
// so while the window is gone (between TERM_WINDOW and the next
|
||||||
|
// INIT_WINDOW) this is false and we skip drawFrame entirely — no
|
||||||
|
// exceptions, no log spam. The outer sleep at the bottom already
|
||||||
|
// caps the loop frequency.
|
||||||
|
if(g_Application->isInited())
|
||||||
{
|
{
|
||||||
application.drawFrame();
|
auto frameTime = (start_time - _lastDrawFrameTime);
|
||||||
|
_lastDrawFrameTime = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
|
||||||
|
static uint64_t s_frameIdx = 0;
|
||||||
|
static uint64_t s_excCount = 0;
|
||||||
|
++s_frameIdx;
|
||||||
|
if (s_frameIdx % 600 == 0) {
|
||||||
|
DebugLog::log("heartbeat: frame=%llu exceptions_so_far=%llu",
|
||||||
|
(unsigned long long)s_frameIdx,
|
||||||
|
(unsigned long long)s_excCount);
|
||||||
|
}
|
||||||
|
// 每 60 帧刷新一次崩溃 note,崩溃时 dump 里能看到「最后一次活着」
|
||||||
|
// 的帧号和异常数,定位是不是在某一个固定帧附近卡死。
|
||||||
|
if (s_frameIdx % 60 == 0) {
|
||||||
|
char note[128];
|
||||||
|
std::snprintf(note, sizeof(note),
|
||||||
|
"drawFrame frame=%llu exc=%llu",
|
||||||
|
(unsigned long long)s_frameIdx,
|
||||||
|
(unsigned long long)s_excCount);
|
||||||
|
CrashHandler::setNote(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
g_Application->drawFrame(frameTime);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
++s_excCount;
|
||||||
|
DebugLog::log_throttled("main.drawFrame.stdexc",
|
||||||
|
"!!! drawFrame std::exception (frame=%llu count=%llu): %s",
|
||||||
|
(unsigned long long)s_frameIdx,
|
||||||
|
(unsigned long long)s_excCount,
|
||||||
|
e.what());
|
||||||
|
} catch (...) {
|
||||||
|
++s_excCount;
|
||||||
|
DebugLog::log_throttled("main.drawFrame.unknown",
|
||||||
|
"!!! drawFrame unknown exception (frame=%llu count=%llu)",
|
||||||
|
(unsigned long long)s_frameIdx,
|
||||||
|
(unsigned long long)s_excCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto end_time = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
auto frameTime = end_time - last_update_time;
|
||||||
|
last_update_time = end_time;
|
||||||
|
auto function_time = (end_time - start_time);
|
||||||
|
//aout << "function_time:" << function_time << " fps:" << (1000/frameTime) << std::endl;
|
||||||
|
if(function_time < 35)
|
||||||
|
{
|
||||||
|
this_thread::sleep_for(chrono::milliseconds((35-function_time)));
|
||||||
}
|
}
|
||||||
auto end_ms = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
|
|
||||||
this_thread::sleep_for(chrono::milliseconds((end_ms-ms)));
|
|
||||||
} while (!pApp->destroyRequested);
|
} while (!pApp->destroyRequested);
|
||||||
application.cleanup();
|
DebugLog::log("android_main: destroyRequested, running cleanup");
|
||||||
|
//application.cleanup();
|
||||||
|
g_Application->cleanupSecondInit();
|
||||||
|
g_Application->clearnSecondFaceApp();
|
||||||
|
DebugLog::log("android_main: exit");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern void TextureLoadProcessWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize);
|
||||||
|
extern void ReceiveFacePoint(float* pos, int count, int width, int height);
|
||||||
|
|
||||||
|
void processWithVulkan(uint8_t* data, int width, int height, int format,
|
||||||
|
int rowStride, size_t dataSize) {
|
||||||
|
TextureLoadProcessWithVulkan(data, width, height, rowStride, dataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_hmwl_face_1sdk_FaceActivity_processImageNative(JNIEnv *env, jobject thiz, jobject buffer,
|
||||||
|
jint width, jint height, jint format,
|
||||||
|
jint row_stride, jint pixel_stride,
|
||||||
|
jint rotation) {
|
||||||
|
static std::atomic<uint64_t> s_imgCount{0};
|
||||||
|
uint64_t n = s_imgCount.fetch_add(1) + 1;
|
||||||
|
if (n == 1 || n % 300 == 0) {
|
||||||
|
DebugLog::log("processImageNative #%llu tid=%d w=%d h=%d",
|
||||||
|
(unsigned long long)n, dbg_tid(), width, height);
|
||||||
|
}
|
||||||
|
// TODO: implement processImageNative()
|
||||||
|
uint8_t* imageData = static_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
|
||||||
|
jlong capacity = env->GetDirectBufferCapacity(buffer);
|
||||||
|
|
||||||
|
if (imageData == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在这里将图像数据传递给 Vulkan
|
||||||
|
// 创建 Vulkan 图像或更新现有图像
|
||||||
|
processWithVulkan(imageData, width, height, format, row_stride, capacity);
|
||||||
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_hmwl_face_1sdk_FaceActivity_passDataToNative(JNIEnv *env, jobject thiz, jobject buffer,
|
||||||
|
jint point_count, jint width, jint height) {
|
||||||
|
static std::atomic<uint64_t> s_ptCount{0};
|
||||||
|
uint64_t n = s_ptCount.fetch_add(1) + 1;
|
||||||
|
if (n == 1 || n % 300 == 0) {
|
||||||
|
DebugLog::log("passDataToNative #%llu tid=%d point_count=%d w=%d h=%d",
|
||||||
|
(unsigned long long)n, dbg_tid(), point_count, width, height);
|
||||||
|
}
|
||||||
|
// TODO: implement passDataToNative()
|
||||||
|
float* pos = static_cast<float*>(env->GetDirectBufferAddress(buffer));
|
||||||
|
|
||||||
|
if (pos == nullptr) {
|
||||||
|
// 处理错误
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 或者直接将指针传递给Vulkan
|
||||||
|
ReceiveFacePoint(pos, point_count, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_hmwl_face_1sdk_FaceActivity_SetCppInitArg(JNIEnv *env, jobject thiz, jstring json) {
|
||||||
|
const char *nativeString = env->GetStringUTFChars(json, nullptr);
|
||||||
|
jsize len = env->GetStringUTFLength(json);
|
||||||
|
DebugLog::log("JNI SetCppInitArg tid=%d len=%d", dbg_tid(), (int)len);
|
||||||
|
if(len > ArgLen)
|
||||||
|
{
|
||||||
|
aout << "ArgLen to long:" << len << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(g_InitArgString, nativeString, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_hmwl_face_1sdk_FaceActivity_StopRunning(JNIEnv *env, jobject thiz) {
|
||||||
|
DebugLog::log("JNI StopRunning tid=%d", dbg_tid());
|
||||||
|
g_Application->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
JavaVM* g_jvm = nullptr;
|
||||||
|
|
||||||
|
// 添加 JNI_OnLoad 函数
|
||||||
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||||
|
g_jvm = vm; // 保存 JavaVM 指针,供其他线程使用
|
||||||
|
return JNI_VERSION_1_6;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CppCallback()
|
||||||
|
{
|
||||||
|
JNIEnv* env = nullptr;
|
||||||
|
bool needDetach = false;
|
||||||
|
|
||||||
|
// 获取当前线程的 JNIEnv
|
||||||
|
JavaVM* vm = g_jvm; // 你需要提前保存 JavaVM* 到全局变量 g_jvm
|
||||||
|
int status = vm->GetEnv((void**)&env, JNI_VERSION_1_6);
|
||||||
|
if (status == JNI_EDETACHED) {
|
||||||
|
// 当前线程未 attach,需 attach
|
||||||
|
if (vm->AttachCurrentThread(&env, nullptr) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
needDetach = true;
|
||||||
|
} else if (status != JNI_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_callback == nullptr) {
|
||||||
|
if (needDetach) vm->DetachCurrentThread();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 获取 CallbackInterface 的 class
|
||||||
|
jclass callbackClass = env->GetObjectClass(g_callback);
|
||||||
|
if (callbackClass == nullptr) {
|
||||||
|
if (needDetach) vm->DetachCurrentThread();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取 OnLoadActionFinished 方法 ID(注意方法名大小写必须一致!)
|
||||||
|
jmethodID methodId = env->GetMethodID(callbackClass, "OnLoadMotionListFinished", "(Ljava/lang/String;)V");
|
||||||
|
if (methodId == nullptr) {
|
||||||
|
env->ExceptionDescribe(); // 打印异常
|
||||||
|
env->ExceptionClear();
|
||||||
|
if (needDetach) vm->DetachCurrentThread();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 构造 jstring 参数
|
||||||
|
jstring jResult = env->NewStringUTF("ok");
|
||||||
|
|
||||||
|
// 4. 调用 Java 回调
|
||||||
|
env->CallVoidMethod(g_callback, methodId, jResult);
|
||||||
|
|
||||||
|
// 5. 检查是否抛出异常
|
||||||
|
if (env->ExceptionCheck()) {
|
||||||
|
env->ExceptionDescribe();
|
||||||
|
env->ExceptionClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 释放局部引用(可选,但推荐)
|
||||||
|
env->DeleteLocalRef(jResult);
|
||||||
|
env->DeleteLocalRef(callbackClass);
|
||||||
|
|
||||||
|
// 7. 如果是临时 attach 的线程,要 detach
|
||||||
|
if (needDetach) {
|
||||||
|
vm->DetachCurrentThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CppAnimationFinishedCallback()
|
||||||
|
{
|
||||||
|
JNIEnv* env = nullptr;
|
||||||
|
bool needDetach = false;
|
||||||
|
|
||||||
|
// 获取当前线程的 JNIEnv
|
||||||
|
JavaVM* vm = g_jvm; // 你需要提前保存 JavaVM* 到全局变量 g_jvm
|
||||||
|
int status = vm->GetEnv((void**)&env, JNI_VERSION_1_6);
|
||||||
|
if (status == JNI_EDETACHED) {
|
||||||
|
// 当前线程未 attach,需 attach
|
||||||
|
if (vm->AttachCurrentThread(&env, nullptr) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
needDetach = true;
|
||||||
|
} else if (status != JNI_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_callbackAnimationFinished == nullptr) {
|
||||||
|
if (needDetach) vm->DetachCurrentThread();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 获取 CallbackInterface 的 class
|
||||||
|
jclass callbackClass = env->GetObjectClass(g_callbackAnimationFinished);
|
||||||
|
if (callbackClass == nullptr) {
|
||||||
|
if (needDetach) vm->DetachCurrentThread();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取 OnLoadActionFinished 方法 ID(注意方法名大小写必须一致!)
|
||||||
|
jmethodID methodId = env->GetMethodID(callbackClass, "OnPlayMotionListFinished", "()V");
|
||||||
|
if (methodId == nullptr) {
|
||||||
|
env->ExceptionDescribe(); // 打印异常
|
||||||
|
env->ExceptionClear();
|
||||||
|
if (needDetach) vm->DetachCurrentThread();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 调用 Java 回调
|
||||||
|
env->CallVoidMethod(g_callbackAnimationFinished, methodId);
|
||||||
|
|
||||||
|
// 5. 检查是否抛出异常
|
||||||
|
if (env->ExceptionCheck()) {
|
||||||
|
env->ExceptionDescribe();
|
||||||
|
env->ExceptionClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
env->DeleteLocalRef(callbackClass);
|
||||||
|
|
||||||
|
// 7. 如果是临时 attach 的线程,要 detach
|
||||||
|
if (needDetach) {
|
||||||
|
vm->DetachCurrentThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jstring JNICALL
|
||||||
|
Java_com_hmwl_face_1sdk_FaceActivity_PreReadAction(JNIEnv *env, jobject thiz, jstring motion,
|
||||||
|
jobject callback) {
|
||||||
|
DebugLog::log("JNI PreReadAction tid=%d", dbg_tid());
|
||||||
|
// 清理旧的全局引用
|
||||||
|
if (g_callback != nullptr) {
|
||||||
|
env->DeleteGlobalRef(g_callback);
|
||||||
|
g_callback = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存新的回调
|
||||||
|
if (callback != nullptr) {
|
||||||
|
g_callback = env->NewGlobalRef(callback);
|
||||||
|
} else {
|
||||||
|
g_callback = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *nativeString = env->GetStringUTFChars(motion, nullptr);
|
||||||
|
jsize len = env->GetStringUTFLength(motion);
|
||||||
|
if(len > ArgLen)
|
||||||
|
{
|
||||||
|
aout << "ArgLen to long:" << len << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ret = g_Application->preLoadMotionList(nativeString, CppCallback);
|
||||||
|
return env->NewStringUTF(ret.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const char DELIMITER = 0x1F; // same as \u001F
|
||||||
|
|
||||||
|
std::vector<std::string> splitMotionString(const std::string& input) {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
if (input.empty()) return result;
|
||||||
|
|
||||||
|
size_t start = 0;
|
||||||
|
size_t pos = input.find(DELIMITER);
|
||||||
|
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
result.push_back(input.substr(start, pos - start));
|
||||||
|
start = pos + 1;
|
||||||
|
pos = input.find(DELIMITER, start);
|
||||||
|
}
|
||||||
|
// Add last part
|
||||||
|
result.push_back(input.substr(start));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_hmwl_face_1sdk_FaceActivity_ChangeMotionCpp(JNIEnv *env, jobject thiz, jstring json,
|
||||||
|
jobject callback, jboolean loop) {
|
||||||
|
const char *nativeString = env->GetStringUTFChars(json, nullptr);
|
||||||
|
jsize len = env->GetStringUTFLength(json);
|
||||||
|
DebugLog::log_throttled("JNI.ChangeMotionCpp",
|
||||||
|
"JNI ChangeMotionCpp tid=%d len=%d loop=%d",
|
||||||
|
dbg_tid(), (int)len, (int)loop);
|
||||||
|
if(len > ArgLen)
|
||||||
|
{
|
||||||
|
aout << "ArgLen to long:" << len << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存新的回调
|
||||||
|
if (callback != nullptr) {
|
||||||
|
g_callbackAnimationFinished = env->NewGlobalRef(callback);
|
||||||
|
} else {
|
||||||
|
g_callbackAnimationFinished = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(g_Application->isInited())
|
||||||
|
{
|
||||||
|
std::vector<std::string> motions = splitMotionString(nativeString);
|
||||||
|
g_Application->changeMotionList(motions, CppAnimationFinishedCallback, loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_hmwl_face_1sdk_FaceActivity_StopMotionNative(JNIEnv *env, jobject thiz) {
|
||||||
|
DebugLog::log("JNI StopMotionNative tid=%d", dbg_tid());
|
||||||
|
g_Application->StopMotion();
|
||||||
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_hmwl_face_1sdk_FaceActivity_ResumeMotionNative(JNIEnv *env, jobject thiz) {
|
||||||
|
DebugLog::log("JNI ResumeMotionNative tid=%d", dbg_tid());
|
||||||
|
g_Application->ResumeMotion();
|
||||||
|
}
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
package com.hmwl.face_sdk;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.camera.core.AspectRatio;
|
||||||
|
import androidx.camera.core.CameraSelector;
|
||||||
|
import androidx.camera.core.ImageAnalysis;
|
||||||
|
import androidx.camera.core.ImageProxy;
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.google.androidgamesdk.GameActivity;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.mediapipe.tasks.components.containers.NormalizedLandmark;
|
||||||
|
import com.google.mediapipe.tasks.vision.core.RunningMode;
|
||||||
|
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarkerResult;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class FaceActivity extends GameActivity implements FaceLandmarkerHelper.LandmarkerListener {
|
||||||
|
private static final String TAG = "FaceActivity";
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.loadLibrary("face_sdk");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcessCameraProvider cameraProvider;
|
||||||
|
private void initCamera(){
|
||||||
|
backgroundExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
if (hasCameraPermission()) {
|
||||||
|
startCamera();
|
||||||
|
} else {
|
||||||
|
requestCameraPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
|
||||||
|
if (requestCode == REQUEST_CAMERA_PERMISSION) {
|
||||||
|
// 检查权限是否被授予
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// 用户授予了权限,启动相机
|
||||||
|
startCamera();
|
||||||
|
} else {
|
||||||
|
requestCameraPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startCamera() {
|
||||||
|
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
|
||||||
|
ProcessCameraProvider.getInstance(this);
|
||||||
|
|
||||||
|
cameraProviderFuture.addListener(() -> {
|
||||||
|
try {
|
||||||
|
cameraProvider = cameraProviderFuture.get();
|
||||||
|
|
||||||
|
CameraSelector cameraSelector = new CameraSelector.Builder()
|
||||||
|
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
imageAnalyzer = new ImageAnalysis.Builder()
|
||||||
|
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
|
||||||
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
|
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
imageAnalyzer.setAnalyzer(backgroundExecutor, new ImageAnalysis.Analyzer() {
|
||||||
|
private int frameCount = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void analyze(@NonNull ImageProxy image) {
|
||||||
|
frameCount++;
|
||||||
|
try {
|
||||||
|
processImageForVulkan(image);
|
||||||
|
if(frameCount %3 == 0)
|
||||||
|
{
|
||||||
|
detectFace(image);
|
||||||
|
}
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// image.close();
|
||||||
|
// }
|
||||||
|
} finally {
|
||||||
|
image.close(); // 确保在这里关闭
|
||||||
|
//Log.d("Camera", "Image closed, ready for next frame");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cameraProvider.bindToLifecycle(this, cameraSelector, imageAnalyzer);
|
||||||
|
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
Log.e("MainActivity", "Error starting camera: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}, ContextCompat.getMainExecutor(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processImageForVulkan(ImageProxy image) {
|
||||||
|
// 获取图像信息
|
||||||
|
int width = image.getWidth();
|
||||||
|
int height = image.getHeight();
|
||||||
|
int format = image.getFormat();
|
||||||
|
|
||||||
|
// 获取图像数据平面
|
||||||
|
ImageProxy.PlaneProxy[] planes = image.getPlanes();
|
||||||
|
|
||||||
|
// 对于 RGBA_8888 格式,通常只有一个平面
|
||||||
|
if (planes.length > 0) {
|
||||||
|
ImageProxy.PlaneProxy plane = planes[0];
|
||||||
|
ByteBuffer buffer = plane.getBuffer();
|
||||||
|
int rowStride = plane.getRowStride();
|
||||||
|
int pixelStride = plane.getPixelStride();
|
||||||
|
|
||||||
|
// 调用 Native 方法处理图像
|
||||||
|
processImageNative(buffer, width, height, format,
|
||||||
|
rowStride, pixelStride, image.getImageInfo().getRotationDegrees());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop(){
|
||||||
|
StopRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
private native void StopMotionNative();
|
||||||
|
public void StopMotion(){
|
||||||
|
StopMotionNative();
|
||||||
|
}
|
||||||
|
|
||||||
|
private native void ResumeMotionNative();
|
||||||
|
public void ResumeMotion(){
|
||||||
|
ResumeMotionNative();
|
||||||
|
}
|
||||||
|
|
||||||
|
private native void StopRunning();
|
||||||
|
|
||||||
|
// Native 方法
|
||||||
|
private native void processImageNative(ByteBuffer buffer, int width, int height,
|
||||||
|
int format, int rowStride, int pixelStride,
|
||||||
|
int rotation);
|
||||||
|
|
||||||
|
private void detectFace(ImageProxy imageProxy) {
|
||||||
|
faceLandmarkerHelper.detectLiveStream(
|
||||||
|
imageProxy,false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCameraPermission() {
|
||||||
|
return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestCameraPermission() {
|
||||||
|
ActivityCompat.requestPermissions(this,
|
||||||
|
new String[]{Manifest.permission.CAMERA},
|
||||||
|
REQUEST_CAMERA_PERMISSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int REQUEST_CAMERA_PERMISSION = 1001;
|
||||||
|
ImageAnalysis imageAnalyzer;
|
||||||
|
ExecutorService backgroundExecutor;
|
||||||
|
FaceLandmarkerHelper faceLandmarkerHelper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
initCamera();
|
||||||
|
|
||||||
|
backgroundExecutor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
faceLandmarkerHelper = new FaceLandmarkerHelper(
|
||||||
|
0.5f,
|
||||||
|
0.5f,
|
||||||
|
0.5f,
|
||||||
|
1,
|
||||||
|
RunningMode.LIVE_STREAM,
|
||||||
|
FaceActivity.this,
|
||||||
|
FaceActivity.this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWindowFocusChanged(boolean hasFocus) {
|
||||||
|
super.onWindowFocusChanged(hasFocus);
|
||||||
|
|
||||||
|
if (hasFocus) {
|
||||||
|
hideSystemUi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideSystemUi() {
|
||||||
|
View decorView = getWindow().getDecorView();
|
||||||
|
decorView.setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NotNull String error, int errorCode) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private ByteBuffer nativeBuffer = null;
|
||||||
|
float[] points = new float[480 * 3];
|
||||||
|
|
||||||
|
|
||||||
|
public class Vector {
|
||||||
|
public float x, y, z;
|
||||||
|
|
||||||
|
public Vector(float x, float y, float z) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float z_rate = -1.0f;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResults(FaceLandmarkerHelper.@NotNull ResultBundle resultBundle) {
|
||||||
|
FaceLandmarkerResult faceLandmarkerResult = resultBundle.getResult();
|
||||||
|
int height = resultBundle.getInputImageHeight();
|
||||||
|
int width = resultBundle.getInputImageWidth();
|
||||||
|
int bufferSize = 480 * 3 * 4;
|
||||||
|
|
||||||
|
if(nativeBuffer == null)
|
||||||
|
{
|
||||||
|
// 创建直接ByteBuffer(在native内存中)
|
||||||
|
nativeBuffer = ByteBuffer.allocateDirect(bufferSize);
|
||||||
|
nativeBuffer.order(ByteOrder.nativeOrder());
|
||||||
|
}
|
||||||
|
|
||||||
|
long start_time = System.currentTimeMillis();
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
// Iterate through each detected face
|
||||||
|
for (List<NormalizedLandmark> faceLandmarks : faceLandmarkerResult.faceLandmarks()) {
|
||||||
|
//should be 1
|
||||||
|
|
||||||
|
int arrayIndex = 0;
|
||||||
|
for (NormalizedLandmark p : faceLandmarks)
|
||||||
|
{
|
||||||
|
//sb.append("[");
|
||||||
|
points[arrayIndex++] = p.x();
|
||||||
|
//sb.append(""+p.x()+",");
|
||||||
|
points[arrayIndex++] = p.y();
|
||||||
|
//sb.append(""+p.y()+",");
|
||||||
|
points[arrayIndex++] = p.z() * z_rate;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FloatBuffer floatBuffer = nativeBuffer.asFloatBuffer();
|
||||||
|
floatBuffer.put(points);
|
||||||
|
floatBuffer.position(0);
|
||||||
|
long cur_time = System.currentTimeMillis();
|
||||||
|
Log.i("TimeLatency","Result Data ProcessTime:" + (cur_time-start_time));
|
||||||
|
passDataToNative(nativeBuffer, index, width, height);
|
||||||
|
Log.i("TimeLatency","passDataToNative ProcessTime:" + (System.currentTimeMillis() - cur_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
public native void passDataToNative(ByteBuffer buffer, int pointCount, int width, int height);
|
||||||
|
|
||||||
|
protected native void SetCppInitArg(String json);
|
||||||
|
|
||||||
|
public void SetInitArg(String json)
|
||||||
|
{
|
||||||
|
SetCppInitArg(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LoadMotionListFinishedCallback {
|
||||||
|
void OnLoadMotionListFinished(String result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface PlayMotionListFinishedCallback {
|
||||||
|
void OnPlayMotionListFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected native String PreReadAction(String motion, LoadMotionListFinishedCallback callback);
|
||||||
|
public String PreLoadAction(String json, LoadMotionListFinishedCallback callback){
|
||||||
|
return PreReadAction(json, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected native void ChangeMotionCpp(String json, PlayMotionListFinishedCallback callback, boolean loop);
|
||||||
|
public void PlayMotionList(List<String> motionList, boolean loop, PlayMotionListFinishedCallback callback)
|
||||||
|
{
|
||||||
|
if(callback == null)
|
||||||
|
{
|
||||||
|
callback = new PlayMotionListFinishedCallback() {
|
||||||
|
@Override
|
||||||
|
public void OnPlayMotionListFinished() {
|
||||||
|
Log.i(TAG, "OnPlayMotionListFinished: ");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
final String DELIMITER = "\u001F"; // ASCII Unit Separator
|
||||||
|
String motion_list_str = String.join(DELIMITER, motionList);
|
||||||
|
ChangeMotionCpp(motion_list_str, callback, loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加这个缺失的方法
|
||||||
|
@Override
|
||||||
|
public void onEmpty() {
|
||||||
|
// 当没有检测到人脸时的处理逻辑
|
||||||
|
//Log.d(TAG, "No face detected in the current frame");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
package com.hmwl.face_sdk
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Matrix
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.camera.core.ImageProxy
|
||||||
|
import com.google.mediapipe.framework.image.BitmapImageBuilder
|
||||||
|
import com.google.mediapipe.framework.image.MPImage
|
||||||
|
import com.google.mediapipe.tasks.core.BaseOptions
|
||||||
|
import com.google.mediapipe.tasks.core.Delegate
|
||||||
|
import com.google.mediapipe.tasks.vision.core.RunningMode
|
||||||
|
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarker
|
||||||
|
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarkerResult
|
||||||
|
|
||||||
|
class FaceLandmarkerHelper(
|
||||||
|
var minFaceDetectionConfidence: Float = DEFAULT_FACE_DETECTION_CONFIDENCE,
|
||||||
|
var minFaceTrackingConfidence: Float = DEFAULT_FACE_TRACKING_CONFIDENCE,
|
||||||
|
var minFacePresenceConfidence: Float = DEFAULT_FACE_PRESENCE_CONFIDENCE,
|
||||||
|
var maxNumFaces: Int = DEFAULT_NUM_FACES,
|
||||||
|
var runningMode: RunningMode = RunningMode.LIVE_STREAM,
|
||||||
|
val context: Context,
|
||||||
|
// this listener is only used when running in RunningMode.LIVE_STREAM
|
||||||
|
val faceLandmarkerHelperListener: LandmarkerListener? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
// For this example this needs to be a var so it can be reset on changes.
|
||||||
|
// If the Face Landmarker will not change, a lazy val would be preferable.
|
||||||
|
private var faceLandmarker: FaceLandmarker? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
setupFaceLandmarker()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearFaceLandmarker() {
|
||||||
|
faceLandmarker?.close()
|
||||||
|
faceLandmarker = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return running status of FaceLandmarkerHelper
|
||||||
|
fun isClose(): Boolean {
|
||||||
|
return faceLandmarker == null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setupFaceLandmarker() {
|
||||||
|
// Set general face landmarker options
|
||||||
|
val baseOptionBuilder = BaseOptions.builder()
|
||||||
|
|
||||||
|
// Use the specified hardware for running the model. Default to CPU
|
||||||
|
baseOptionBuilder.setDelegate(Delegate.GPU)
|
||||||
|
|
||||||
|
baseOptionBuilder.setModelAssetPath(MP_FACE_LANDMARKER_TASK)
|
||||||
|
|
||||||
|
// Check if runningMode is consistent with faceLandmarkerHelperListener
|
||||||
|
when (runningMode) {
|
||||||
|
RunningMode.LIVE_STREAM -> {
|
||||||
|
if (faceLandmarkerHelperListener == null) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"faceLandmarkerHelperListener must be set when runningMode is LIVE_STREAM."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minFaceDetectionConfidence = 0.1f;
|
||||||
|
minFaceTrackingConfidence = 0.1f;
|
||||||
|
minFacePresenceConfidence = 0.1f;
|
||||||
|
try {
|
||||||
|
val baseOptions = baseOptionBuilder.build()
|
||||||
|
// Create an option builder with base options and specific
|
||||||
|
// options only use for Face Landmarker.
|
||||||
|
val optionsBuilder =
|
||||||
|
FaceLandmarker.FaceLandmarkerOptions.builder()
|
||||||
|
.setBaseOptions(baseOptions)
|
||||||
|
.setMinFaceDetectionConfidence(minFaceDetectionConfidence)
|
||||||
|
.setMinTrackingConfidence(minFaceTrackingConfidence)
|
||||||
|
.setMinFacePresenceConfidence(minFacePresenceConfidence)
|
||||||
|
.setNumFaces(maxNumFaces)
|
||||||
|
.setOutputFaceBlendshapes(false)
|
||||||
|
.setOutputFacialTransformationMatrixes(false)
|
||||||
|
.setRunningMode(runningMode)
|
||||||
|
|
||||||
|
// The ResultListener and ErrorListener only use for LIVE_STREAM mode.
|
||||||
|
if (runningMode == RunningMode.LIVE_STREAM) {
|
||||||
|
optionsBuilder
|
||||||
|
.setResultListener(this::returnLivestreamResult)
|
||||||
|
.setErrorListener(this::returnLivestreamError)
|
||||||
|
}
|
||||||
|
|
||||||
|
val options = optionsBuilder.build()
|
||||||
|
faceLandmarker =
|
||||||
|
FaceLandmarker.createFromOptions(context, options)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
faceLandmarkerHelperListener?.onError(
|
||||||
|
"Face Landmarker failed to initialize. See error logs for " +
|
||||||
|
"details"
|
||||||
|
)
|
||||||
|
Log.e(
|
||||||
|
TAG, "MediaPipe failed to load the task with error: " + e
|
||||||
|
.message
|
||||||
|
)
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
// This occurs if the model being used does not support GPU
|
||||||
|
faceLandmarkerHelperListener?.onError(
|
||||||
|
"Face Landmarker failed to initialize. See error logs for " +
|
||||||
|
"details", GPU_ERROR
|
||||||
|
)
|
||||||
|
Log.e(
|
||||||
|
TAG,
|
||||||
|
"Face Landmarker failed to load model with error: " + e.message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var frameId: Long = 0
|
||||||
|
// 简化的延时跟踪
|
||||||
|
private val frameTimings = mutableMapOf<Long, FrameTiming>()
|
||||||
|
private var lastFpsUpdateTime: Long = 0
|
||||||
|
private var frameCount: Int = 0
|
||||||
|
private var currentFps: Double = 0.0
|
||||||
|
|
||||||
|
// 内部数据类用于存储时间信息
|
||||||
|
private data class FrameTiming(
|
||||||
|
var captureTime: Long = 0,
|
||||||
|
var preprocessStartTime: Long = 0,
|
||||||
|
var preprocessEndTime: Long = 0,
|
||||||
|
var detectionStartTime: Long = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// 辅助方法:记录帧时间信息
|
||||||
|
private fun recordFrameTiming(frameId: Long, block: (FrameTiming) -> Unit) {
|
||||||
|
val timing = frameTimings.getOrPut(frameId) { FrameTiming() }
|
||||||
|
block(timing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:更新FPS计算
|
||||||
|
private fun updateFps() {
|
||||||
|
frameCount++
|
||||||
|
val currentTime = SystemClock.uptimeMillis()
|
||||||
|
|
||||||
|
if (lastFpsUpdateTime == 0L) {
|
||||||
|
lastFpsUpdateTime = currentTime
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val timeDiff = currentTime - lastFpsUpdateTime
|
||||||
|
if (timeDiff >= 1000) { // 每秒更新一次FPS
|
||||||
|
currentFps = frameCount * 1000.0 / timeDiff
|
||||||
|
frameCount = 0
|
||||||
|
lastFpsUpdateTime = currentTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:清理旧的时间记录
|
||||||
|
private fun cleanupOldTimings(currentFrameId: Long) {
|
||||||
|
val framesToRemove = frameTimings.keys.filter { it < currentFrameId - 10 }
|
||||||
|
framesToRemove.forEach { frameTimings.remove(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the ImageProxy to MP Image and feed it to FacelandmakerHelper.
|
||||||
|
fun detectLiveStream(
|
||||||
|
imageProxy: ImageProxy,
|
||||||
|
isFrontCamera: Boolean
|
||||||
|
) {
|
||||||
|
if (runningMode != RunningMode.LIVE_STREAM) {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Attempting to call detectLiveStream" +
|
||||||
|
" while not using RunningMode.LIVE_STREAM"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val frameTime = SystemClock.uptimeMillis()
|
||||||
|
|
||||||
|
val currentFrameId = frameId++
|
||||||
|
val captureTime = SystemClock.uptimeMillis()
|
||||||
|
|
||||||
|
// 记录捕获时间
|
||||||
|
recordFrameTiming(currentFrameId) {
|
||||||
|
it.captureTime = captureTime
|
||||||
|
it.preprocessStartTime = SystemClock.uptimeMillis()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy out RGB bits from the frame to a bitmap buffer
|
||||||
|
val bitmapBuffer =
|
||||||
|
Bitmap.createBitmap(
|
||||||
|
imageProxy.width,
|
||||||
|
imageProxy.height,
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
|
)
|
||||||
|
imageProxy.use { bitmapBuffer.copyPixelsFromBuffer(imageProxy.planes[0].buffer) }
|
||||||
|
//imageProxy.close()
|
||||||
|
|
||||||
|
val matrix = Matrix().apply {
|
||||||
|
// Rotate the frame received from the camera to be in the same direction as it'll be shown
|
||||||
|
postRotate(imageProxy.imageInfo.rotationDegrees.toFloat())
|
||||||
|
|
||||||
|
// flip image if user use front camera
|
||||||
|
if (isFrontCamera) {
|
||||||
|
postScale(
|
||||||
|
-1f,
|
||||||
|
1f,
|
||||||
|
imageProxy.width.toFloat(),
|
||||||
|
imageProxy.height.toFloat()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val rotatedBitmap = Bitmap.createBitmap(
|
||||||
|
bitmapBuffer, (640-480)/2, 0, bitmapBuffer.height, bitmapBuffer.height,
|
||||||
|
matrix, true
|
||||||
|
)
|
||||||
|
|
||||||
|
//val bitmap = BitmapFactory.decodeFile("/sdcard/Android/data/com.khronos.vulkan_samples/files/assets/face480.png")
|
||||||
|
//val mpImage = BitmapImageBuilder(bitmap).build()
|
||||||
|
|
||||||
|
// Convert the input Bitmap object to an MPImage object to run inference
|
||||||
|
val mpImage = BitmapImageBuilder(rotatedBitmap).build()
|
||||||
|
|
||||||
|
detectAsync(mpImage, frameTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run face face landmark using MediaPipe Face Landmarker API
|
||||||
|
@VisibleForTesting
|
||||||
|
fun detectAsync(mpImage: MPImage, frameTime: Long) {
|
||||||
|
faceLandmarker?.detectAsync(mpImage, frameTime)
|
||||||
|
// As we're using running mode LIVE_STREAM, the landmark result will
|
||||||
|
// be returned in returnLivestreamResult function
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Return the landmark result to this FaceLandmarkerHelper's caller
|
||||||
|
private fun returnLivestreamResult(
|
||||||
|
result: FaceLandmarkerResult,
|
||||||
|
input: MPImage
|
||||||
|
) {
|
||||||
|
val resultTime = SystemClock.uptimeMillis()
|
||||||
|
val frameId = result.timestampMs()
|
||||||
|
if( result.faceLandmarks().size > 0 ) {
|
||||||
|
// 计算各种延时
|
||||||
|
// val timings = frameTimings[frameId]
|
||||||
|
// if (timings != null) {
|
||||||
|
// val totalLatency = resultTime - timings.captureTime
|
||||||
|
// val preprocessLatency = timings.preprocessEndTime - timings.preprocessStartTime
|
||||||
|
// val detectionLatency = resultTime - timings.detectionStartTime
|
||||||
|
// val captureToDetectionLatency = timings.detectionStartTime - timings.captureTime
|
||||||
|
//
|
||||||
|
// Log.i("TimeLatency",
|
||||||
|
// "总延时: ${totalLatency}ms | " +
|
||||||
|
// "预处理: ${preprocessLatency}ms | " +
|
||||||
|
// "检测: ${detectionLatency}ms | " +
|
||||||
|
// "捕获到检测: ${captureToDetectionLatency}ms | " +
|
||||||
|
// "FPS: ${String.format("%.1f", currentFps)}"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// val finishTimeMs = SystemClock.uptimeMillis()
|
||||||
|
// val inferenceTime = finishTimeMs - result.timestampMs()
|
||||||
|
// faceLandmarkerHelperListener?.onResults(
|
||||||
|
// ResultBundle(
|
||||||
|
// result,
|
||||||
|
// inferenceTime, // 主要是检测时间
|
||||||
|
// input.height,
|
||||||
|
// input.width,
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// } else
|
||||||
|
//{
|
||||||
|
val finishTimeMs = SystemClock.uptimeMillis()
|
||||||
|
val inferenceTime = finishTimeMs - result.timestampMs()
|
||||||
|
Log.i("TimeLatency",
|
||||||
|
"总延时: ${inferenceTime}ms | "
|
||||||
|
)
|
||||||
|
faceLandmarkerHelperListener?.onResults(
|
||||||
|
ResultBundle(
|
||||||
|
result,
|
||||||
|
inferenceTime,
|
||||||
|
input.height,
|
||||||
|
input.width
|
||||||
|
)
|
||||||
|
)
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
faceLandmarkerHelperListener?.onEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return errors thrown during detection to this FaceLandmarkerHelper's
|
||||||
|
// caller
|
||||||
|
private fun returnLivestreamError(error: RuntimeException) {
|
||||||
|
faceLandmarkerHelperListener?.onError(
|
||||||
|
error.message ?: "An unknown error has occurred"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "FaceLandmarkerHelper"
|
||||||
|
private const val MP_FACE_LANDMARKER_TASK = "face_landmarker.task"
|
||||||
|
|
||||||
|
const val DELEGATE_CPU = 0
|
||||||
|
const val DELEGATE_GPU = 1
|
||||||
|
const val DEFAULT_FACE_DETECTION_CONFIDENCE = 0.5F
|
||||||
|
const val DEFAULT_FACE_TRACKING_CONFIDENCE = 0.5F
|
||||||
|
const val DEFAULT_FACE_PRESENCE_CONFIDENCE = 0.5F
|
||||||
|
const val DEFAULT_NUM_FACES = 1
|
||||||
|
const val OTHER_ERROR = 0
|
||||||
|
const val GPU_ERROR = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ResultBundle(
|
||||||
|
val result: FaceLandmarkerResult,
|
||||||
|
val inferenceTime: Long,
|
||||||
|
val inputImageHeight: Int,
|
||||||
|
val inputImageWidth: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface LandmarkerListener {
|
||||||
|
fun onError(error: String, errorCode: Int = OTHER_ERROR)
|
||||||
|
fun onResults(resultBundle: ResultBundle)
|
||||||
|
|
||||||
|
fun onEmpty() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.hmwl.face_sdk;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class Frame {
|
||||||
|
public String name;
|
||||||
|
public float x;
|
||||||
|
public float y;
|
||||||
|
public Frame(String name, float x, float y)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
public JSONObject toJsonObject(){
|
||||||
|
try {
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("name", name);
|
||||||
|
jsonObject.put("x", x);
|
||||||
|
jsonObject.put("y", y);
|
||||||
|
return jsonObject;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.hmwl.face_sdk;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class InitArg {
|
||||||
|
public int action_fps;
|
||||||
|
public float zoom;
|
||||||
|
public float r;
|
||||||
|
public float g;
|
||||||
|
public float b;
|
||||||
|
public float radius;
|
||||||
|
public float offset_x;
|
||||||
|
public float offset_y;
|
||||||
|
public String toJson() {
|
||||||
|
try {
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("action_fps", action_fps);
|
||||||
|
jsonObject.put("zoom", zoom);
|
||||||
|
jsonObject.put("r", r);
|
||||||
|
jsonObject.put("g", g);
|
||||||
|
jsonObject.put("b", b);
|
||||||
|
jsonObject.put("radius", radius);
|
||||||
|
jsonObject.put("offset_x", offset_x);
|
||||||
|
jsonObject.put("offset_y", offset_y);
|
||||||
|
return jsonObject.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.hmwl.face_sdk;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class Motion {
|
||||||
|
public String name = "no";
|
||||||
|
public List<Frame> frames = new ArrayList<>();
|
||||||
|
public Motion(String name, List<Frame> frames)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.frames = frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject toJsonObject() {
|
||||||
|
try {
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("name", name);
|
||||||
|
JSONArray jframes = new JSONArray();
|
||||||
|
for(int i = 0; i < frames.size(); ++i){
|
||||||
|
jframes.put(frames.get(i).toJsonObject());
|
||||||
|
}
|
||||||
|
jsonObject.put("frames", jframes);
|
||||||
|
return jsonObject;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.hmwl.face_sdk;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MotionList {
|
||||||
|
public List<Motion> motions = new ArrayList<>();
|
||||||
|
public JSONObject toJsonObject() {
|
||||||
|
try {
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
JSONArray jMotions = new JSONArray();
|
||||||
|
for(int i = 0; i < motions.size(); ++i){
|
||||||
|
jMotions.put(motions.get(i).toJsonObject());
|
||||||
|
}
|
||||||
|
jsonObject.put("motions", jMotions);
|
||||||
|
return jsonObject;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return new JSONObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public String toJsonString(){
|
||||||
|
return toJsonObject().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package com.hmwl.sample;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.google.androidgamesdk.GameActivity;
|
|
||||||
|
|
||||||
public class MainActivity extends GameActivity{
|
|
||||||
private static final String TAG = "MainActivity";
|
|
||||||
|
|
||||||
static {
|
|
||||||
System.loadLibrary("sample");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
|
||||||
int action = event.getActionMasked();
|
|
||||||
float x = event.getX();
|
|
||||||
float y = event.getY();
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
Log.d(TAG, "Java Layer: Touch DOWN at (" + x + ", " + y + ")");
|
|
||||||
// ✅ 你可以在这里做些事,比如发消息给 C++,或 UI 反馈
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
Log.d(TAG, "Java Layer: Touch MOVE");
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
Log.d(TAG, "Java Layer: Touch UP");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ⚠️ 关键:返回 false 才能让事件继续传递给 Native 层(C++)
|
|
||||||
// 如果返回 true,表示事件已被消费,C++ 层将收不到!
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWindowFocusChanged(boolean hasFocus) {
|
|
||||||
super.onWindowFocusChanged(hasFocus);
|
|
||||||
|
|
||||||
if (hasFocus) {
|
|
||||||
hideSystemUi();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideSystemUi() {
|
|
||||||
View decorView = getWindow().getDecorView();
|
|
||||||
decorView.setSystemUiVisibility(
|
|
||||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
||||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
||||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
||||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
||||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
||||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -1,17 +0,0 @@
|
|||||||
package com.hmwl.sample;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
public class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
public void addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,19 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.3.2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
|
alias(libs.plugins.android.library) apply false
|
||||||
|
alias(libs.plugins.kotlin.android) apply false // 添加这行
|
||||||
|
//alias(libs.plugins.kotlin.android) apply false
|
||||||
|
//id 'org.jetbrains.kotlin.android' version '2.2.0' apply false
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
glslangValidator -V app/src/main/assets/shaders/bg.frag -o app/src/main/assets/shaders/bg.frag.spv
|
||||||
|
glslangValidator -V app/src/main/assets/shaders/bg.vert -o app/src/main/assets/shaders/bg.vert.spv
|
||||||
|
|
||||||
|
glslangValidator -V app/src/main/assets/shaders/texture.frag -o app/src/main/assets/shaders/texture.frag.spv
|
||||||
|
glslangValidator -V app/src/main/assets/shaders/texture.vert -o app/src/main/assets/shaders/texture.vert.spv
|
||||||
|
|
||||||
|
glslangValidator -V app/src/main/assets/shaders/simple_shader.frag -o app/src/main/assets/shaders/simple_shader.frag.spv
|
||||||
|
glslangValidator -V app/src/main/assets/shaders/simple_shader.vert -o app/src/main/assets/shaders/simple_shader.vert.spv
|
||||||
|
|
||||||
|
glslangValidator -V app/src/main/assets/shaders/texture_thick.frag -o app/src/main/assets/shaders/texture_thick.frag.spv
|
||||||
|
rem glslangValidator -V app/src/main/assets/shaders/texture_thick.vert -o app/src/main/assets/shaders/texture_thick.vert.spv
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
//alias(libs.plugins.kotlin.android)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'com.inewme.uvmirror'
|
||||||
|
compileSdk 36
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.inewme.uvmirror.f20260113"
|
||||||
|
minSdk 30
|
||||||
|
targetSdk 36
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose true
|
||||||
|
}
|
||||||
|
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion '1.5.1'
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '17'
|
||||||
|
}
|
||||||
|
// kotlinOptions {
|
||||||
|
// jvmTarget = '17'
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation libs.appcompat
|
||||||
|
implementation libs.material
|
||||||
|
implementation libs.games.activity
|
||||||
|
implementation libs.core.ktx
|
||||||
|
implementation libs.androidx.core.ktx
|
||||||
|
testImplementation libs.junit
|
||||||
|
androidTestImplementation libs.ext.junit
|
||||||
|
androidTestImplementation libs.espresso.core
|
||||||
|
|
||||||
|
implementation project(':app')
|
||||||
|
// Jetpack Compose
|
||||||
|
implementation "androidx.compose.ui:ui:1.5.0"
|
||||||
|
implementation "androidx.compose.material3:material3:1.0.0"
|
||||||
|
implementation "androidx.compose.foundation:foundation:1.5.0"
|
||||||
|
implementation "androidx.compose.runtime:runtime:1.5.0"
|
||||||
|
implementation "androidx.activity:activity-compose:1.7.2" // �ؼ����ṩ setContent
|
||||||
|
|
||||||
|
implementation "androidx.compose.ui:ui-tooling:1.5.4"
|
||||||
|
|
||||||
|
def camerax_version = '1.4.2'
|
||||||
|
implementation "androidx.camera:camera-core:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-camera2:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-view:$camerax_version"
|
||||||
|
|
||||||
|
implementation 'com.google.protobuf:protobuf-javalite:3.21.12'
|
||||||
|
implementation 'com.google.android.datatransport:transport-runtime:3.1.9'
|
||||||
|
implementation 'com.google.android.datatransport:transport-backend-cct:3.1.9'
|
||||||
|
|
||||||
|
implementation 'com.google.flogger:flogger:0.7.4'
|
||||||
|
implementation 'com.google.flogger:flogger-system-backend:0.7.4'
|
||||||
|
|
||||||
|
implementation 'com.google.guava:guava:31.1-android'
|
||||||
|
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") // 提供 lifecycleScope
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.Sample" >
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.lib_name"
|
||||||
|
android:value="face_sdk" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MakeupActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |