From c036b005f8998f78d633052687f86aa34f14202d Mon Sep 17 00:00:00 2001 From: Misaki Date: Thu, 25 Jun 2026 00:26:49 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E5=AE=8C=E6=88=90=E4=BA=86=E5=AF=B9?= =?UTF-8?q?=E5=B5=8C=E5=85=A5=E5=BC=8FLinux=E5=B9=B3=E5=8F=B0=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=9A=84=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81=E7=9A=84?= =?UTF-8?q?=E5=90=88=E5=B9=B6=EF=BC=8C=E5=90=8C=E6=97=B6=E5=AF=B9=E4=BA=8E?= =?UTF-8?q?OpenGL=20ES=E5=B9=B3=E5=8F=B0=E7=9A=84shader=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E4=BA=86=E9=87=8D=E5=86=99=E4=BB=A5=E9=80=82=E9=85=8DES?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=EF=BC=8C=E4=B8=8D=E8=BF=87=E6=9A=82=E6=97=B6?= =?UTF-8?q?=E8=BF=98=E6=9C=AA=E5=8A=A0=E5=85=A5=E5=88=B0CMakeLists?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E8=84=9A=E6=9C=AC=E5=BD=93=E4=B8=AD=EF=BC=8C?= =?UTF-8?q?=E9=A2=84=E8=AE=A1=E5=9C=A8=E4=B8=8B=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E4=B8=AD=E5=8A=A0=E5=85=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + .../Src/LAppLive2D/Src/LAppLive2DManager.cpp | 6 + .../Live2D/Src/LAppLive2D/Src/LAppModel.cpp | 5 + CMakePresets.json | 112 ++++++++++++++++++ main.cpp | 5 + other/FrameworkShadersES/FragShaderSrc.frag | 17 +++ .../FrameworkShadersES/FragShaderSrcMask.frag | 24 ++++ .../FragShaderSrcMaskInverted.frag | 24 ++++ ...aderSrcMaskInvertedPremultipliedAlpha.frag | 23 ++++ .../FragShaderSrcMaskPremultipliedAlpha.frag | 23 ++++ .../FragShaderSrcPremultipliedAlpha.frag | 16 +++ .../FragShaderSrcSetupMask.frag | 18 +++ other/FrameworkShadersES/VertShaderSrc.vert | 13 ++ .../VertShaderSrcMasked.vert | 16 +++ .../VertShaderSrcSetupMask.vert | 15 +++ src/Core/Inc/AppCore.h | 9 +- src/Core/Src/AppCore.cpp | 35 +++++- src/Handle/AudioHandle/Inc/AudioInput.h | 6 + src/Handle/AudioHandle/Src/AudioInput.cpp | 36 +++++- src/Handle/AudioHandle/Src/AudioOutput.cpp | 29 +++-- .../DataObjectHandle/Src/AutoAgentHandle.cpp | 6 + 21 files changed, 415 insertions(+), 27 deletions(-) create mode 100644 CMakePresets.json create mode 100644 other/FrameworkShadersES/FragShaderSrc.frag create mode 100644 other/FrameworkShadersES/FragShaderSrcMask.frag create mode 100644 other/FrameworkShadersES/FragShaderSrcMaskInverted.frag create mode 100644 other/FrameworkShadersES/FragShaderSrcMaskInvertedPremultipliedAlpha.frag create mode 100644 other/FrameworkShadersES/FragShaderSrcMaskPremultipliedAlpha.frag create mode 100644 other/FrameworkShadersES/FragShaderSrcPremultipliedAlpha.frag create mode 100644 other/FrameworkShadersES/FragShaderSrcSetupMask.frag create mode 100644 other/FrameworkShadersES/VertShaderSrc.vert create mode 100644 other/FrameworkShadersES/VertShaderSrcMasked.vert create mode 100644 other/FrameworkShadersES/VertShaderSrcSetupMask.vert diff --git a/.gitignore b/.gitignore index fec1618..8d914fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ cmake-build-debug cmake-build-release build +build_linux +build_rk3566 # clion /.idea + +deploy.sh diff --git a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp index b4e170a..7f815f7 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp @@ -284,6 +284,12 @@ void LAppLive2DManager::LoadModelFromPath(const std::string& modelPath, const st _models.PushBack(new LAppModel()); // 这样在加载的时候都使用的models[0]这一个位置,自行实现模型选择器要注意注意 _models[0]->LoadAssets(modelPathStr.GetRawString(), modelJsonName.GetRawString()); + if (_models[0]->GetModel() == nullptr) + { + LAppPal::PrintLogLn("[APP]Failed to load model: %s / %s", modelPath.c_str(), fileName.c_str()); + return; + } + // 加载完后根据模型大小来重新设置当前窗口大小 const int width = static_cast(_models[0]->GetModel()->GetCanvasWidthPixel() / 15.0); const int height = static_cast(_models[0]->GetModel()->GetCanvasHeightPixel() / 15.0); diff --git a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppModel.cpp b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppModel.cpp index 680ab15..aafe91e 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppModel.cpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppModel.cpp @@ -168,6 +168,11 @@ void LAppModel::LoadAssets(const csmChar* dir, const csmChar* fileName) const csmString path = csmString(dir) + fileName; csmByte* buffer = CreateBuffer(path.GetRawString(), &size); + if (buffer == nullptr) + { + LAppPal::PrintLogLn("[APP]Failed to load model setting file: %s", path.GetRawString()); + return; + } ICubismModelSetting* setting = new CubismModelSettingJson(buffer, size); DeleteBuffer(buffer, path.GetRawString()); diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..67ce2b6 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,112 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "description": "所有平台的公共基础配置" + }, + { + "name": "desktop-base", + "hidden": true, + "inherits": "base", + "description": "桌面平台公共基础配置:固定 Qt 6.6.3" + }, + { + "name": "embedded-base", + "hidden": true, + "inherits": "base", + "description": "嵌入式交叉编译公共基础配置,暂时只支持arm64架构", + "generator": "Unix Makefiles", + "cacheVariables": { + "TARGET_ARCH": "arm64", + "GLESv2_FOUND": "TRUE", + "EGL_FOUND": "TRUE", + "HAVE_EGL": "ON", + "HAVE_GLESv2": "ON" + } + }, + { + "name": "linux-native", + "inherits": "desktop-base", + "displayName": "Linux Desktop x86_64", + "description": "本地 Linux 桌面编译 (GCC + Ninja),Qt 6.6.3 固定路径", + "binaryDir": "${sourceDir}/build_linux", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_PREFIX_PATH": "/home/misaki/Qt6.3/6.6.3/gcc_64" + } + }, + { + "name": "windows-mingw", + "inherits": "desktop-base", + "displayName": "Windows MinGW x64", + "description": "Windows 桌面编译 (MinGW-w64 + Ninja),Qt 6.6.3 固定路径", + "binaryDir": "${sourceDir}/build_win_mingw", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_PREFIX_PATH": "C:/Qt/6.6.3/mingw_64", + "CMAKE_C_COMPILER": "C:/mingw64/bin/gcc.exe", + "CMAKE_CXX_COMPILER": "C:/mingw64/bin/g++.exe" + } + }, + { + "name": "windows-msvc", + "inherits": "desktop-base", + "displayName": "Windows MSVC x64", + "description": "Windows 桌面编译 (Visual Studio 2022),Qt 6.6.3 固定路径", + "binaryDir": "${sourceDir}/build_win_msvc", + "generator": "Visual Studio 17 2022", + "architecture": { + "value": "x64", + "strategy": "set" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_PREFIX_PATH": "C:/Qt/6.6.3/msvc2019_64" + } + }, + { + "name": "macos-native", + "inherits": "desktop-base", + "displayName": "macOS x86_64", + "description": "macOS 桌面编译 (Clang + Ninja),Qt 6.6.3 固定路径", + "binaryDir": "${sourceDir}/build_macos", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_PREFIX_PATH": "/Users/misaki/Qt/6.6.3/macos" + } + }, + { + "name": "rk3566-cross", + "inherits": "embedded-base", + "displayName": "RK3566 Cross Compile", + "description": "RK3566 嵌入式交叉编译 (ARM64 + Qt6.6.3 EGLFS)", + "binaryDir": "${sourceDir}/build_rk3566", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": "/home/misaki/MisakiCodes/rk3566-sdk/toolchain/qt_toolchain.cmake", + "CMAKE_PREFIX_PATH": "/home/misaki/MisakiCodes/rk3566-sdk/3rd/Qt6.6.3", + "GLESv2_INCLUDE_DIR": "/home/misaki/MisakiCodes/rk3566-sdk/host/aarch64-buildroot-linux-gnu/sysroot/usr/include", + "GLESv2_LIBRARY": "/home/misaki/MisakiCodes/rk3566-sdk/host/aarch64-buildroot-linux-gnu/sysroot/usr/lib/libGLESv2.so", + "EGL_INCLUDE_DIR": "/home/misaki/MisakiCodes/rk3566-sdk/host/aarch64-buildroot-linux-gnu/sysroot/usr/include", + "EGL_LIBRARY": "/home/misaki/MisakiCodes/rk3566-sdk/host/aarch64-buildroot-linux-gnu/sysroot/usr/lib/libEGL.so" + } + } + ], + "buildPresets": [ + { "name": "linux-debug", "configurePreset": "linux-native", "configuration": "Debug" }, + { "name": "linux-release", "configurePreset": "linux-native", "configuration": "Release" }, + { "name": "win-mingw-debug", "configurePreset": "windows-mingw", "configuration": "Debug" }, + { "name": "win-mingw-release", "configurePreset": "windows-mingw", "configuration": "Release" }, + { "name": "win-msvc-debug", "configurePreset": "windows-msvc", "configuration": "Debug" }, + { "name": "win-msvc-release", "configurePreset": "windows-msvc", "configuration": "Release" }, + { "name": "macos-debug", "configurePreset": "macos-native", "configuration": "Debug" }, + { "name": "macos-release", "configurePreset": "macos-native", "configuration": "Release" }, + { "name": "rk3566-debug", "configurePreset": "rk3566-cross", "configuration": "Debug" }, + { "name": "rk3566-release", "configurePreset": "rk3566-cross", "configuration": "Release" } + ] +} \ No newline at end of file diff --git a/main.cpp b/main.cpp index 03d6271..b16dcc9 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,8 @@ #include +#if !defined(EMBEDDED_LINUX) #include "ElaApplication.h" +#endif + #include #include #include "GLCore.h" @@ -12,9 +15,11 @@ int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // 允许多个窗口使用同一个OpenGL上下文(用于实现Live2D模型的异步加载) QApplication a(argc, argv); +#if !defined(EMBEDDED_LINUX) eApp->init(); // 设置云母效果图片 eApp->setMicaImagePath("Resources/Pic/Others/MicaBase.png"); +#endif #ifdef Q_OS_UNIX // 设置日志过滤规则,将这个特定分类的警告级别设置为“关闭” QLoggingCategory::setFilterRules("qt.multimedia.ffmpeg.libsymbolsresolver.warning=false"); diff --git a/other/FrameworkShadersES/FragShaderSrc.frag b/other/FrameworkShadersES/FragShaderSrc.frag new file mode 100644 index 0000000..aec8feb --- /dev/null +++ b/other/FrameworkShadersES/FragShaderSrc.frag @@ -0,0 +1,17 @@ +#version 100 +precision mediump float; + +varying vec2 v_texCoord; +uniform sampler2D s_texture0; +uniform vec4 u_baseColor; +uniform vec4 u_multiplyColor; +uniform vec4 u_screenColor; + +void main() +{ + vec4 texColor = texture2D(s_texture0, v_texCoord); + texColor.rgb = texColor.rgb * u_multiplyColor.rgb; + texColor.rgb = texColor.rgb + u_screenColor.rgb - (texColor.rgb * u_screenColor.rgb); + vec4 color = texColor * u_baseColor; + gl_FragColor = vec4(color.rgb * color.a, color.a); +} \ No newline at end of file diff --git a/other/FrameworkShadersES/FragShaderSrcMask.frag b/other/FrameworkShadersES/FragShaderSrcMask.frag new file mode 100644 index 0000000..58e0be6 --- /dev/null +++ b/other/FrameworkShadersES/FragShaderSrcMask.frag @@ -0,0 +1,24 @@ +#version 100 +precision mediump float; + +varying vec2 v_texCoord; +varying vec4 v_clipPos; +uniform sampler2D s_texture0; +uniform sampler2D s_texture1; +uniform vec4 u_channelFlag; +uniform vec4 u_baseColor; +uniform vec4 u_multiplyColor; +uniform vec4 u_screenColor; + +void main() +{ + vec4 texColor = texture2D(s_texture0, v_texCoord); + texColor.rgb = texColor.rgb * u_multiplyColor.rgb; + texColor.rgb = texColor.rgb + u_screenColor.rgb - (texColor.rgb * u_screenColor.rgb); + vec4 col_formask = texColor * u_baseColor; + col_formask.rgb = col_formask.rgb * col_formask.a; + vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag; + float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a; + col_formask = col_formask * maskVal; + gl_FragColor = col_formask; +} \ No newline at end of file diff --git a/other/FrameworkShadersES/FragShaderSrcMaskInverted.frag b/other/FrameworkShadersES/FragShaderSrcMaskInverted.frag new file mode 100644 index 0000000..5facfab --- /dev/null +++ b/other/FrameworkShadersES/FragShaderSrcMaskInverted.frag @@ -0,0 +1,24 @@ +#version 100 +precision mediump float; + +varying vec2 v_texCoord; +varying vec4 v_clipPos; +uniform sampler2D s_texture0; +uniform sampler2D s_texture1; +uniform vec4 u_channelFlag; +uniform vec4 u_baseColor; +uniform vec4 u_multiplyColor; +uniform vec4 u_screenColor; + +void main() +{ + vec4 texColor = texture2D(s_texture0, v_texCoord); + texColor.rgb = texColor.rgb * u_multiplyColor.rgb; + texColor.rgb = texColor.rgb + u_screenColor.rgb - (texColor.rgb * u_screenColor.rgb); + vec4 col_formask = texColor * u_baseColor; + col_formask.rgb = col_formask.rgb * col_formask.a; + vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag; + float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a; + col_formask = col_formask * (1.0 - maskVal); + gl_FragColor = col_formask; +} \ No newline at end of file diff --git a/other/FrameworkShadersES/FragShaderSrcMaskInvertedPremultipliedAlpha.frag b/other/FrameworkShadersES/FragShaderSrcMaskInvertedPremultipliedAlpha.frag new file mode 100644 index 0000000..490c310 --- /dev/null +++ b/other/FrameworkShadersES/FragShaderSrcMaskInvertedPremultipliedAlpha.frag @@ -0,0 +1,23 @@ +#version 100 +precision mediump float; + +varying vec2 v_texCoord; +varying vec4 v_clipPos; +uniform sampler2D s_texture0; +uniform sampler2D s_texture1; +uniform vec4 u_channelFlag; +uniform vec4 u_baseColor; +uniform vec4 u_multiplyColor; +uniform vec4 u_screenColor; + +void main() +{ + vec4 texColor = texture2D(s_texture0, v_texCoord); + texColor.rgb = texColor.rgb * u_multiplyColor.rgb; + texColor.rgb = (texColor.rgb + u_screenColor.rgb * texColor.a) - (texColor.rgb * u_screenColor.rgb); + vec4 col_formask = texColor * u_baseColor; + vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag; + float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a; + col_formask = col_formask * (1.0 - maskVal); + gl_FragColor = col_formask; +} \ No newline at end of file diff --git a/other/FrameworkShadersES/FragShaderSrcMaskPremultipliedAlpha.frag b/other/FrameworkShadersES/FragShaderSrcMaskPremultipliedAlpha.frag new file mode 100644 index 0000000..d5fdf43 --- /dev/null +++ b/other/FrameworkShadersES/FragShaderSrcMaskPremultipliedAlpha.frag @@ -0,0 +1,23 @@ +#version 100 +precision mediump float; + +varying vec2 v_texCoord; +varying vec4 v_clipPos; +uniform sampler2D s_texture0; +uniform sampler2D s_texture1; +uniform vec4 u_channelFlag; +uniform vec4 u_baseColor; +uniform vec4 u_multiplyColor; +uniform vec4 u_screenColor; + +void main() +{ + vec4 texColor = texture2D(s_texture0, v_texCoord); + texColor.rgb = texColor.rgb * u_multiplyColor.rgb; + texColor.rgb = (texColor.rgb + u_screenColor.rgb * texColor.a) - (texColor.rgb * u_screenColor.rgb); + vec4 col_formask = texColor * u_baseColor; + vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag; + float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a; + col_formask = col_formask * maskVal; + gl_FragColor = col_formask; +} \ No newline at end of file diff --git a/other/FrameworkShadersES/FragShaderSrcPremultipliedAlpha.frag b/other/FrameworkShadersES/FragShaderSrcPremultipliedAlpha.frag new file mode 100644 index 0000000..df6a12b --- /dev/null +++ b/other/FrameworkShadersES/FragShaderSrcPremultipliedAlpha.frag @@ -0,0 +1,16 @@ +#version 100 +precision mediump float; + +varying vec2 v_texCoord; +uniform sampler2D s_texture0; +uniform vec4 u_baseColor; +uniform vec4 u_multiplyColor; +uniform vec4 u_screenColor; + +void main() +{ + vec4 texColor = texture2D(s_texture0, v_texCoord); + texColor.rgb = texColor.rgb * u_multiplyColor.rgb; + texColor.rgb = (texColor.rgb + u_screenColor.rgb * texColor.a) - (texColor.rgb * u_screenColor.rgb); + gl_FragColor = texColor * u_baseColor; +} \ No newline at end of file diff --git a/other/FrameworkShadersES/FragShaderSrcSetupMask.frag b/other/FrameworkShadersES/FragShaderSrcSetupMask.frag new file mode 100644 index 0000000..8cf8088 --- /dev/null +++ b/other/FrameworkShadersES/FragShaderSrcSetupMask.frag @@ -0,0 +1,18 @@ +#version 100 +precision mediump float; + +varying vec2 v_texCoord; +varying vec4 v_myPos; +uniform sampler2D s_texture0; +uniform vec4 u_channelFlag; +uniform vec4 u_baseColor; + +void main() +{ + float isInside = + step(u_baseColor.x, v_myPos.x / v_myPos.w) + * step(u_baseColor.y, v_myPos.y / v_myPos.w) + * step(v_myPos.x / v_myPos.w, u_baseColor.z) + * step(v_myPos.y / v_myPos.w, u_baseColor.w); + gl_FragColor = u_channelFlag * texture2D(s_texture0, v_texCoord).a * isInside; +} \ No newline at end of file diff --git a/other/FrameworkShadersES/VertShaderSrc.vert b/other/FrameworkShadersES/VertShaderSrc.vert new file mode 100644 index 0000000..e846992 --- /dev/null +++ b/other/FrameworkShadersES/VertShaderSrc.vert @@ -0,0 +1,13 @@ +#version 100 + +attribute vec4 a_position; +attribute vec2 a_texCoord; +varying vec2 v_texCoord; +uniform mat4 u_matrix; + +void main() +{ + gl_Position = u_matrix * a_position; + v_texCoord = a_texCoord; + v_texCoord.y = 1.0 - v_texCoord.y; +} \ No newline at end of file diff --git a/other/FrameworkShadersES/VertShaderSrcMasked.vert b/other/FrameworkShadersES/VertShaderSrcMasked.vert new file mode 100644 index 0000000..190016a --- /dev/null +++ b/other/FrameworkShadersES/VertShaderSrcMasked.vert @@ -0,0 +1,16 @@ +#version 100 + +attribute vec4 a_position; +attribute vec2 a_texCoord; +varying vec2 v_texCoord; +varying vec4 v_clipPos; +uniform mat4 u_matrix; +uniform mat4 u_clipMatrix; + +void main() +{ + gl_Position = u_matrix * a_position; + v_clipPos = u_clipMatrix * a_position; + v_texCoord = a_texCoord; + v_texCoord.y = 1.0 - v_texCoord.y; +} \ No newline at end of file diff --git a/other/FrameworkShadersES/VertShaderSrcSetupMask.vert b/other/FrameworkShadersES/VertShaderSrcSetupMask.vert new file mode 100644 index 0000000..a7ab6ca --- /dev/null +++ b/other/FrameworkShadersES/VertShaderSrcSetupMask.vert @@ -0,0 +1,15 @@ +#version 100 + +attribute vec4 a_position; +attribute vec2 a_texCoord; +varying vec2 v_texCoord; +varying vec4 v_myPos; +uniform mat4 u_clipMatrix; + +void main() +{ + gl_Position = u_clipMatrix * a_position; + v_myPos = u_clipMatrix * a_position; + v_texCoord = a_texCoord; + v_texCoord.y = 1.0 - v_texCoord.y; +} \ No newline at end of file diff --git a/src/Core/Inc/AppCore.h b/src/Core/Inc/AppCore.h index 17e8c23..b95831b 100644 --- a/src/Core/Inc/AppCore.h +++ b/src/Core/Inc/AppCore.h @@ -24,7 +24,7 @@ class DeviceTcpServer; class DeviceWebSocketServer; class AppCore final : public QObject -#ifdef Q_OS_LINUX +#if defined(Q_OS_LINUX) && !defined(EMBEDDED_LINUX) , public QAbstractNativeEventFilter #endif { @@ -54,14 +54,15 @@ public: public: void SingleExchange(); - void tryToInit() { return; } + void tryToInit() { } // PTT 按住说话 void startPttRecording(); void stopPttRecording(); - +#if !defined(EMBEDDED_LINUX) void setupGlobalHotkey(); void cleanupGlobalHotkey(); +#endif #if defined(Q_OS_WIN) private: @@ -70,7 +71,7 @@ private: bool m_isPttDown = false; UINT m_hotkeyVKey = VK_OEM_3; -#elif defined(Q_OS_LINUX) +#elif defined(Q_OS_LINUX) && !defined(EMBEDDED_LINUX) private: bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override; void onDebounceStop(); diff --git a/src/Core/Src/AppCore.cpp b/src/Core/Src/AppCore.cpp index d03be26..da6e6ba 100644 --- a/src/Core/Src/AppCore.cpp +++ b/src/Core/Src/AppCore.cpp @@ -8,8 +8,10 @@ #include #include "AudioDataHandle.h" +#if !defined(EMBEDDED_LINUX) #include "AutoAgentHandle.h" #include "ScreenShotReqDataHandle.h" +#endif #include "DeviceDataHandle.h" #include "AudioInput.h" @@ -18,7 +20,7 @@ #include "DeviceTcpServer.h" #include "DeviceWebSocketServer.h" -#ifdef Q_OS_LINUX +#if defined(Q_OS_LINUX) && !defined(EMBEDDED_LINUX) #include #include #include @@ -88,29 +90,54 @@ AppCore::AppCore(QObject *parent) : QObject(parent) // 初始化业务解析单例 AudioDataHandle::getInstance(); +#if !defined(EMBEDDED_LINUX) AutoAgentHandle::getInstance(); ScreenShotReqDataHandle::getInstance(); +#endif // 注入发送接口 NetworkDO::getInstance()->registerSender([](const QString& type, const QJsonObject& data){ WebSocketClient::getInstance()->sendJson(type, data); }); - // TODO Test +#ifdef EMBEDDED_LINUX + // 嵌入式平台无Settings UI,需手动绑定WebSocket接收链路 + connect(WebSocketClient::getInstance(), &WebSocketClient::jsonReceived, + NetworkDO::getInstance(), &NetworkDO::onDataReceived); + // 自动配置并连接服务端(优先读取环境变量 YOSUGA_SERVER_URL) + { + QString serverUrl = qEnvironmentVariable("YOSUGA_SERVER_URL", "ws://192.168.1.18:8765"); + auto *client = WebSocketClient::getInstance(); + client->setConfiguration(QUrl(serverUrl)); + client->setAutoReconnect(true); + client->connectToServer(); + qDebug() << "[AppCore] Embedded: connecting to server:" << serverUrl; + } +#endif +#ifdef EMBEDDED_LINUX + AudioInput::getInstance()->setAudioSettings(16000, 2); +#endif AudioInput::getInstance()->setAudioPath(QDir::currentPath(), "/temp.wav"); // 连接必要的信号 connect(AudioInput::getInstance(), &AudioInput::recordingFinished_Byte, this, &AppCore::onRecordingFinished_Byte); - +#if !defined(EMBEDDED_LINUX) setupGlobalHotkey(); +#endif + } AppCore::~AppCore() { +#if !defined(EMBEDDED_LINUX) cleanupGlobalHotkey(); +#endif + if (m_deviceTcpServer) m_deviceTcpServer->stop(); if (m_deviceWsServer) m_deviceWsServer->stop(); +#if !defined(EMBEDDED_LINUX) ScreenShotReqDataHandle::destroy(); AutoAgentHandle::destroy(); +#endif AudioDataHandle::destroy(); DeviceDataHandle::destroy(); @@ -198,7 +225,7 @@ LRESULT CALLBACK AppCore::lowLevelKeyboardHook(int nCode, WPARAM wParam, LPARAM } // ===================== Linux (X11) ===================== -#elif defined(Q_OS_LINUX) +#elif defined(Q_OS_LINUX) && !defined(EMBEDDED_LINUX) void AppCore::setupGlobalHotkey() { Display *display = XOpenDisplay(nullptr); if (!display) { diff --git a/src/Handle/AudioHandle/Inc/AudioInput.h b/src/Handle/AudioHandle/Inc/AudioInput.h index b740328..cffe0a0 100644 --- a/src/Handle/AudioHandle/Inc/AudioInput.h +++ b/src/Handle/AudioHandle/Inc/AudioInput.h @@ -12,6 +12,9 @@ #include #include #include +#ifdef EMBEDDED_LINUX +#include +#endif /** * @brief 录音模块 @@ -150,5 +153,8 @@ private: qreal m_smoothRms = 0.0; /// 平滑RMS值(用于防止低频杂波突然打断静音检测) bool m_hasVoiceDetected = false; /// 是否已检测到人声 +#ifdef EMBEDDED_LINUX + QProcess *m_arecordProcess = nullptr; /// 嵌入式平台使用arecord录音 +#endif }; diff --git a/src/Handle/AudioHandle/Src/AudioInput.cpp b/src/Handle/AudioHandle/Src/AudioInput.cpp index b4ee43e..7e9afed 100644 --- a/src/Handle/AudioHandle/Src/AudioInput.cpp +++ b/src/Handle/AudioHandle/Src/AudioInput.cpp @@ -53,7 +53,6 @@ void AudioInput::setAudioSettings(const int rate, const int channels) { m_format.setSampleRate(rate); m_format.setChannelCount(channels); - // 为了生成标准 WAV 且方便计算 RMS,强制设为 Int16 m_format.setSampleFormat(QAudioFormat::Int16); // 检查设备是否支持该格式,不支持则使用最接近的 @@ -72,6 +71,30 @@ void AudioInput::setAudioPath(const QString &path, const QString &fileName) void AudioInput::startAudio() { +#ifdef EMBEDDED_LINUX + m_rawPCMData.clear(); + if (!m_arecordProcess) { + m_arecordProcess = new QProcess(this); + connect(m_arecordProcess, &QProcess::readyReadStandardOutput, this, [this]() { + m_rawPCMData.append(m_arecordProcess->readAllStandardOutput()); + }); + } else if (m_arecordProcess->state() != QProcess::NotRunning) { + m_arecordProcess->terminate(); + m_arecordProcess->waitForFinished(1000); + } + m_arecordProcess->start("arecord", { + "-D", "hw:0,0", "-f", "S16_LE", + "-r", QString::number(m_format.sampleRate()), + "-c", QString::number(m_format.channelCount()), + "-t", "raw", "-q", "-" + }); + if (m_arecordProcess->waitForStarted(1000)) { + qDebug() << "Started recording via arecord: hw:0,0" + << m_format.sampleRate() << "Hz" << m_format.channelCount() << "ch S16_LE"; + } else { + qCritical() << "Failed to start arecord"; + } +#else // 每次开始前重新创建 QAudioSource,确保状态重置 if (m_audioSource) { delete m_audioSource; @@ -79,8 +102,6 @@ void AudioInput::startAudio() } m_audioSource = new QAudioSource(m_currentDevice, m_format, this); - - // 调大缓冲区以避免溢出 m_audioSource->setBufferSize(128000); // start() 返回一个 QIODevice,可以从中读取数据 @@ -92,14 +113,21 @@ void AudioInput::startAudio() } else { qCritical() << "Failed to start audio recording."; } +#endif } void AudioInput::stopAudio() { +#ifdef EMBEDDED_LINUX + if (m_arecordProcess && m_arecordProcess->state() != QProcess::NotRunning) { + m_arecordProcess->terminate(); + m_arecordProcess->waitForFinished(2000); + } +#else if (m_audioSource) { m_audioSource->stop(); - // 注意:不要立即 delete m_audioSource,某些情况下可能导致 crash,停止即可 } +#endif // 停止所有定时器 m_timer->stop(); diff --git a/src/Handle/AudioHandle/Src/AudioOutput.cpp b/src/Handle/AudioHandle/Src/AudioOutput.cpp index 99b0126..d485781 100644 --- a/src/Handle/AudioHandle/Src/AudioOutput.cpp +++ b/src/Handle/AudioHandle/Src/AudioOutput.cpp @@ -29,27 +29,24 @@ void StreamAudioWorker::start(int sampleRate, int channelCount, int bitDepth) { m_firstChunk = true; - // 配置音频格式 QAudioFormat format; format.setSampleRate(sampleRate); format.setChannelCount(channelCount); if (bitDepth == 8) format.setSampleFormat(QAudioFormat::UInt8); else if (bitDepth == 16) format.setSampleFormat(QAudioFormat::Int16); - else if (bitDepth == 32) format.setSampleFormat(QAudioFormat::Float); // 32位通常为Float - else format.setSampleFormat(QAudioFormat::Int16); // 默认回退 + else if (bitDepth == 32) format.setSampleFormat(QAudioFormat::Float); + else format.setSampleFormat(QAudioFormat::Int16); - // 检查设备是否支持 auto device = QMediaDevices::defaultAudioOutput(); if (!device.isFormatSupported(format)) { qWarning() << "[Worker] Device does not support format, using preferred format."; format = device.preferredFormat(); } - // 创建 Sink (必须在 Worker 线程中创建) m_sink.reset(new QAudioSink(device, format)); + m_sink->setBufferSize(sampleRate * channelCount * (bitDepth / 8)); - // 监听状态 connect(m_sink.data(), &QAudioSink::stateChanged, this, [this](QAudio::State state){ if (state == QAudio::IdleState) { emit playbackFinished(); @@ -61,32 +58,34 @@ void StreamAudioWorker::start(int sampleRate, int channelCount, int bitDepth) { } }); - // 启动,获取 IO 设备 m_ioDevice = m_sink->start(); if (!m_ioDevice) { emit errorOccurred("Failed to start audio device"); } else { - qDebug() << "[Worker] Stream started:" << sampleRate << "Hz"; + qDebug() << "[Worker] Stream started:" << sampleRate << "Hz, buffer:" << m_sink->bufferSize(); } } void StreamAudioWorker::processChunk(const QByteArray& chunk) { if (chunk.isEmpty() || !m_ioDevice || !m_sink) return; QByteArray dataToWrite = chunk; - // 智能处理 WAV 头 if (m_firstChunk) { if (hasWavHeader(chunk)) { - qDebug() << "[Worker] Detected WAV header, stripping 44 bytes."; dataToWrite = chunk.mid(44); } m_firstChunk = false; } - // 写入音频设备 (QAudioSink 内部有缓冲区,这里直接 write 即可) - // 如果数据量过大,write 可能会阻塞,但在独立线程中这是可以接受的 - qint64 written = m_ioDevice->write(dataToWrite); - if (written != dataToWrite.size()) { - qWarning() << "[Worker] Incomplete write:" << written << "/" << dataToWrite.size(); + const char *ptr = dataToWrite.constData(); + qint64 remaining = dataToWrite.size(); + while (remaining > 0) { + qint64 written = m_ioDevice->write(ptr, remaining); + if (written > 0) { + ptr += written; + remaining -= written; + } else { + QThread::msleep(5); + } } } diff --git a/src/Handle/DataObjectHandle/Src/AutoAgentHandle.cpp b/src/Handle/DataObjectHandle/Src/AutoAgentHandle.cpp index b843a04..963a7cd 100644 --- a/src/Handle/DataObjectHandle/Src/AutoAgentHandle.cpp +++ b/src/Handle/DataObjectHandle/Src/AutoAgentHandle.cpp @@ -4,7 +4,10 @@ #include "AutoAgentHandle.h" #include "NetWorkDO.h" +#if defined(Q_OS_LINUX) && !defined(EMBEDDED_LINUX) #include // 引入 AutoGUI 头文件 +#endif + // 初始化静态成员 QScopedPointer AutoAgentHandle::m_instance; QMutex AutoAgentHandle::m_mutex; @@ -43,6 +46,7 @@ AutoAgentHandle::~AutoAgentHandle() void AutoAgentHandle::onAutoAgentPacketReceived(const AutoAgentDataObject &packet) { qDebug() << "Received AutoAgent packet: " << packet.getAction(); +#if defined(Q_OS_LINUX) && !defined(EMBEDDED_LINUX) if (packet.getAction() == "click") { // 单击 qDebug() << "Click: " << packet.getX1() << ", " << packet.getY1(); AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.6); @@ -78,4 +82,6 @@ void AutoAgentHandle::onAutoAgentPacketReceived(const AutoAgentDataObject &packe AutoGUI::scroll(40, -1); } } +#endif + } \ No newline at end of file