ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

QtScrcpy 开发者指南:深入 Android 投屏的 Client/Server 架构与调试实践

QtScrcpy 开发者指南:深入 Android 投屏的 Client/Server 架构与调试实践 QtScrcpy 开发者指南深入 Android 投屏的 Client/Server 架构与调试实践【免费下载链接】QtScrcpyAndroid实时投屏软件此应用程序提供USB(或通过TCP/IP)连接的Android设备的显示和控制。它不需要任何root访问权限项目地址: https://gitcode.com/barry-ran/QtScrcpy本文以仓库 docs/DEVELOP.md 为骨架结合 QtScrcpy 当前源码系统讲解这款 Android 实时投屏软件的双端架构设备端scrcpy-server与宿主机端QtScrcpy客户端、线程模型、H.264 视频链路与输入事件注入机制并给出服务端调试的完整实战步骤。读完本文你将理解投屏软件采集-编码-传输-解码-渲染-回控的完整链路是如何在 QtScrcpy 中被实现的并掌握在 Android Studio 中远程调试设备端服务的方法。总体架构由 Server 与 Client 组成的双进程系统与上游 scrcpy 一脉相承QtScrcpy 由两个独立部分组成Serverscrcpy-server一个运行在 Android 设备上的 Java 应用负责采集屏幕、编码 H.264、接收并注入输入事件ClientQtScrcpy主程序运行在宿主机Windows / macOS / GNU/Linux上的 C 客户端负责推送并启动 Server、解码视频、渲染显示、捕获键盘鼠标输入并回传设备。连接建立后Server 首先发送设备信息设备名称与初始屏幕尺寸随后持续推送屏幕的原始 H.264 视频流。客户端解码视频帧并不做缓冲、尽快显示以最小化延迟客户端对设备旋转无感知——旋转由 Server 处理客户端只知道视频帧的尺寸变化。反向通道上客户端捕获的键盘与鼠标事件被封装成控制消息发送给 Server由 Server 注入到 Android 系统中。在 QtScrcpy 的实现中这一推送 Server 建立连接的职责清晰体现在客户端入口 QtScrcpy/main.cpp程序启动时通过环境变量定位 adb、scrcpy-server、按键映射脚本与配置文件Windows 在 main.cpp 中直接写入Linux 则允许被 AppImage 等外部环境预先覆盖见 main.cpp而服务端的设备端落盘路径则由配置文件 config/config.ini 的ServerPath指定默认值为/data/local/tmp/scrcpy-server.jar。Server运行在设备上的 Java 应用权限与app_process启动方式屏幕采集需要特定权限这些权限被授予给shell用户。Server 是编译时链接 Android framework 的 Java 应用具有public static void main(String... args)入口以shell身份在设备上运行。运行此类 Java 应用前class 必须先被dex化通常生成classes.dex。假设主类为my.package.MainClass编译得到classes.dex并推送到设备/data/local/tmp即可用以下命令启动adb shell CLASSPATH/data/local/tmp/classes.dex \ app_process / my.package.MainClass选择/data/local/tmp作为推送路径是有安全考量的它可被shell读写但不是全局可写的因此恶意应用无法在客户端执行 Server 之前替换它。app_process除了接受裸 dex 文件也接受包含classes.dex的 jar例如 APK。QtScrcpy 沿用了这一思路为利用 Gradle 构建体系Server 被打包为一个未签名的 APK 并重命名为scrcpy-server。在 QtScrcpy 中该文件由客户端在启动时推送到设备见 main.cpp 中的QTSCRCPY_SERVER_PATH环境变量。隐藏 API反射与 Wrapper 封装Server 虽然编译时链接了 Android framework但框架中的[隐藏方法]hidden methods与隐藏类并不能被直接调用——它们在不同 Android 版本上可能各不相同。因此 Server 通过反射调用这些接口与系统隐藏组件通信的能力由一组Wrapper类与 aidl 接口封装提供。这种反射 封装是投屏工具在非 root、非侵入条件下访问系统能力如屏幕采集、输入注入的核心手段。服务端线程模型3 个线程Server 内部使用 3 个线程主线程main负责视频编码并向客户端推流控制线程controller监听来自客户端的控制消息典型如键盘、鼠标事件接收线程receiver由控制线程管理向客户端发送设备消息当前仅用于回传设备剪贴板内容。由于现代 Android 设备的视频编码通常是硬件编码将编码与推流拆到两个线程并无收益因此单线程即可完成。屏幕视频编码MediaCodec 与 surface编码由ScreenEncoder管理视频通过 Android 的MediaCodecAPI 编码codec 从与显示关联的surface取输入把产生的 H.264 流写入提供给它的输出流即连接到客户端的 socket。两点关键行为值得注意旋转处理设备旋转时codec、surface 与 display 会被重新初始化并产生一条新的视频流按需出帧只有 surface 内容发生变化时才产生新帧这避免了发送无意义的帧但也带来两个副作用——启动时若屏幕无变化则不发送任何帧快速运动之后最后一帧画质可能较差。这两个问题由MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER标志解决它让编码器在画面静止一段时间后主动重复上一帧保证流能立即开始、且末帧质量稳定。在 QtScrcpy 中编码行为是可配置的。配置文件 config/config.ini 提供了与编码相关的参数# 最大fps仅支持Android 10以上 MaxFps0 # 编码选项 表示默认 # 例如 CodecOptionsprofile1,level2 CodecOptions # 指定编码器名称(必须是H.264编码器)表示默认 # 例如 CodecNameOMX.qcom.video.encoder.avc CodecName其中CodecOptions直接对应MediaFormat的键值如profile、levelCodecName可用于强制指定设备上的某个 H.264 硬件编码器例如高通的OMX.qcom.video.encoder.avcMaxFps可限制推流帧率。此外投屏窗口中的启动配置还允许设置录制比特率、分辨率与录制格式对应仅后台录制等功能这些参数最终都会在启动服务时随控制消息下发到 Server 端参与编码初始化。输入事件注入控制消息由Controller在一个独立线程中接收消息类型包括keycode按键对应 AndroidKeyEventtext文本特殊字符无法直接用 keycode 表达时使用mouse motion/click鼠标移动与点击mouse scroll鼠标滚轮其他命令如点亮屏幕、复制剪贴板等。其中需要真正注入系统输入的事件通过隐藏方法InputManager.injectInputEvent完成——该方法由 Wrapper 类InputManager以反射方式暴露。QtScrcpy 在此基础上进一步扩展了回控能力keymap/目录下的 JSON 映射脚本可以把键盘按键映射为设备触摸点击例如gameforpeace.json、tiktok.json等自定义映射规则见 docs/KeyMapDes_zh.md桌面端的鼠标事件钩子由 QtScrcpy/QtScrcpy/util/mousetap/ 按平台实现Windows 的winmousetap、Linux 的xmousetap、macOS 的cocoamousetap并由入口 main.cpp 在 Windows/macOS 上初始化。Client运行在宿主机上的 C 客户端QtScrcpy 客户端以 Qt 为跨平台基础设施视频流由 FFmpeglibav解码。与上游 scrcpy 的 SDL C 技术栈不同对比表见 README_zh.mdQtScrcpy 采用C Qt OpenGL FFmpeg的组合并基于 Qt 信号槽机制实现了异步编程模型。初始化流程应用层与网络层的角色反转启动时客户端除初始化 FFmpeg 与 Qt 环境外还必须把 Server 推送到设备并启动它同时打开两个 socket——一个承载视频流一个承载控制消息。这里有一个容易混淆的设计点应用层角色与网络层角色是相反的。应用层上Server服务视频流并处理客户端请求客户端控制设备网络层上却是客户端先监听一个端口作为服务端 socketServer 主动连接客户端。这种角色反转保证了连接不会因竞态条件而失败也避免了轮询。需要特别注意的是在 TCP/IP无线连接模式下角色不再反转——这是上游 scrcpy 中adb reverse的一个已知 bug 所致见上游 commit 1038bad 与 issue #5。QtScrcpy 的 main.cpp 直接引用了QTcpServer与QTcpSocket正是这套 socket 通信的基础设施。Server 连接后随即发送设备信息名称与初始屏幕尺寸因此客户端可以在第一帧到达之前就完成窗口与渲染器的初始化。在 QtScrcpy 中这一设备信息 → 窗口适配的流程体现为VideoForm对设备会话变化的响应——videoform.h 中的onVideoSessionChanged回调会在视频会话尺寸变化含旋转、客户端窗口调整时被触发。客户端线程模型4 个线程客户端使用 4 个线程主线程main执行 Qt 事件循环流线程stream接收视频用于解码与录制控制线程controller向 Server 发送控制消息接收线程receiver由控制线程管理接收来自 Server 的设备消息。此外必要时会再启动额外线程处理 APK 安装 / 文件推送请求在主窗口拖放触发或定期在控制台打印帧率。Stream解码与录制并行视频流在独立线程中从 socket 接收。若存在解码器即未启用仅后台录制/无显示模式则用 FFmpeg 解码来自 socket 的 H.264 流并在新帧可用时通知主线程。解码采用双帧缓冲策略同一时刻内存中保留两帧解码帧decoding由解码线程写入渲染帧rendering由主线程渲染为纹理。当新解码帧就绪时解码器在恰当同步下交换解码帧与渲染帧——于是解码线程立刻开始解码下一帧主线程同时渲染上一帧两者互不阻塞。若启用了录制则录制的原始 H.264 包被 mux 到输出视频文件中。数据流示意如下---------- ---------- --- | decoder | --- | screen | --------- / ---------- ---------- socket --- | stream | ---- --------- \ ---------- --- | recorder | ----------QtScrcpy 渲染层在 QtScrcpy/QtScrcpy/render/ 目录下通用平台使用QYUVOpenGLWidget通过 OpenGL 将 YUV 帧渲染上屏macOS Apple Silicon 上则额外实现了基于 VideoToolbox 硬件解码 Metal 渲染的MetalVideoWindow路径见 metalvideowindow.h。两条渲染路径的分流体现在 videoform.honFrameYUV 软解帧回调与onFrameMetalCVPixelBuffer 回调仅 macOS arm64构建时 macOS 会链接 Metal、CoreVideo、QuartzCore 框架见 QtScrcpy/CMakeLists.txt。ControllerSDL/Qt 事件到 Android 事件的转换控制线程负责把控制消息发送到设备独立线程避免了在主线程上做 I/O。主线程收到输入事件后输入管理器负责把 Qt 事件转换为 Android 事件如鼠标/键盘/滚轮事件生成对应的控制消息推入由控制线程持有的消息队列。控制线程在自己的线程中从队列取消息、序列化并发送给 Server。这一主线程入队、控制线程出队发送的生产者-消费者模式使大量高频输入事件不会阻塞 UI 线程。VideoForm对鼠标按下/移动/双击/滚轮、键盘按下/释放等事件的接管可见于 videoform.h剪贴板同步CtrlC取设备剪贴板、CtrlShiftV注入电脑剪贴板文本、CtrlV以文本事件序列发送则复用了 Server 接收线程回传剪贴板内容的通道完整快捷键表见 README_zh.md。UI 与事件循环初始化、输入事件与渲染全部在主线程中管理。事件循环负责更新画面或将事件委托给输入管理器。在 QtScrcpy 中VideoForm继承自qsc::DeviceObserver见 videoform.h通过观察者接口接收帧数据、FPS 统计与视频会话变化回调再驱动内部的QYUVOpenGLWidget/MetalVideoWidget刷新同时ToolForm、Dialog等 UI 组件QtScrcpy/QtScrcpy/ui/提供启动配置、设备列表与批量控制入口。Hack服务端调试实战Server 由客户端在启动时推送到设备。调试它的思路是让 Server 在设备上启动调试器再把端口转发回宿主机用 Android Studio 的 Remote Debug 附加。需要提前说明的是docs/DEVELOP.md 中给出的meson构建选项来自上游 scrcpy 的 Meson 构建系统QtScrcpy 的客户端采用CMake构建见 QtScrcpy/CMakeLists.txt因此server_debugger这类 meson 选项需要映射到 QtScrcpy 的构建流程中。不过下面的端口转发 远程调试方法论是通用且可直接照做的。步骤一让 Server 以可调试模式启动在原版 scrcpy 的 meson 构建中启用服务端调试器meson x -Dserver_debuggertrue # 或者若 x 已配置过 meson configure x -Dserver_debuggertrue如果你的设备运行 Android 8 或更低版本还需要把server_debugger_method设为oldmeson x -Dserver_debuggertrue -Dserver_debugger_methodold # 或者若 x 已配置过 meson configure x -Dserver_debuggertrue -Dserver_debugger_methodold随后重新编译。在 QtScrcpy 工程中Server 使用 Android Studio Gradle 构建步骤见 README_zh.md 的编译章节用 Android Studio 打开仓库中的 server 工程编译出 APK 后改名为scrcpy-server替换第三方依赖对应地应在 Gradle / Android Studio 的 Run 配置中开启 Debug 模式或注入调试参数使设备端的 Server 进程启动一个监听调试器。步骤二转发调试端口当 scrcpy/QtScrcpy 启动后Server 会在设备上于5005 端口启动调试器。将设备端口转发到电脑adb forward tcp:5005 tcp:5005步骤三在 Android Studio 中附加 Remote Debugger在 Android Studio 中依次进入RunDebugEdit configurations...点击左侧选择Remote填写HostlocalhostPort5005然后点击Debug。此后便可在 IDE 中为 Server 的 Java 代码打断点、单步调试观察屏幕采集、编码与输入注入的执行路径。从源码出发的进一步探索客户端入口与环境变量约定QtScrcpy/main.cpp构建系统与平台差异macOS Metal、Windows/Linux 资源拷贝QtScrcpy/CMakeLists.txt视频窗口、观察者回调与输入事件接管QtScrcpy/QtScrcpy/ui/videoform.h、QtScrcpy/QtScrcpy/ui/videoform.cpp渲染路径OpenGL / MetalQtScrcpy/QtScrcpy/render/服务端落盘路径、编码器与帧率等运行参数config/config.ini按键映射扩展docs/KeyMapDes_zh.md 与 keymap/常见问题与后续计划docs/FAQ.md、docs/TODO.md理解了 Server 的采集-编码-注入与 Client 的解码-渲染-回控两条链路以及它们之间的角色反转连接模型再结合本仓库源码逐行对照你就能完整掌握 QtScrcpy 的投屏实现原理并在此基础上进行二次开发与调试。【免费下载链接】QtScrcpyAndroid实时投屏软件此应用程序提供USB(或通过TCP/IP)连接的Android设备的显示和控制。它不需要任何root访问权限项目地址: https://gitcode.com/barry-ran/QtScrcpy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表