ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深度解析QtScrcpy:C++与Qt实现Android实时投屏与远程控制

深度解析QtScrcpy:C++与Qt实现Android实时投屏与远程控制 简介这是一套面向C/Qt开发者与Android投屏工具爱好者的开源实战项目源码聚焦于无需Root权限的跨平台实时投屏解决方案适用于移动开发调试、远程教学演示及企业级设备管控等场景。资源共126个文件含15个核心C源文件如videoform.cpp、config.cpp、16个头文件、9个Shell脚本含build_for_win.bat、publish_for_win.bat等构建与发布脚本、6个JSON/YAML配置文件、30张PNG与4张JPG界面资源图以及TypeScript、Python、CSS等多语言协同模块完整覆盖UI层、控制逻辑、设备通信与构建部署全流程压缩包大小为36.03MB。已有416人学习下载。读者可直接复用其USB/TCP双模连接架构、Qt多线程视频渲染方案、ADB指令封装逻辑及跨平台构建脚本快速掌握高性能投屏软件的工程组织方式与多语言协作实践。1. 项目概述这到底是什么能拿来干什么先给结论QtScrcpy 是一个用 C 和 Qt 框架实现的 Android 实时投屏开源项目底层数据链路沿用 scrcpy 的思路——通过 adb 协议与 Android 设备通信把设备的屏幕内容以 H.264 编码流的形式送出来再由 PC 端解码渲染。换句话说它不是简单的“截屏工具”而是一套完整的远程控制协议实现。我第一次接触这个项目时想到的并不是“投个屏有什么难的”而是“在投屏之外它能不能变成一套可扩展的 Android 设备控制基座”。如果你在测试部门待过一定经历过那种场景桌上十几台 Android 手机每台都要装包、点按钮、看日志人眼和手根本忙不过来。QtScrcpy 这类工具的价值就是让你在 PC 上完成所有这些操作还能录屏、反向传输文件、执行 shell 命令。它适合的人也很明确Android 开发、测试工程师、自动化脚本编写者以及想研究“C 怎么跟 Android 系统通信”底层原理的嵌入式或客户端开发者。有一点需要提前说明QtScrcpy 跟原版 scrcpy 有两个明显差异。第一原版基于 SDL2QtScrcpy 整个 UI 用 Qt Widgets 重写看惯了原生 scrcpy 命令行的用户会在这里得到一个完整的图形界面第二QtScrcpy 的代码模块更贴近“桌面应用工程化”这意味着读它的源码你能学到的不只是投屏还有 Qt 线程模型、跨平台编译、协议解析这一类通用技能。2. 整体设计与核心架构拆解2.1 数据链路Android 端把画面送出来的完整过程很多人觉得投屏是“截屏 图片传输”这是最大的误解。实时性要求下逐帧传 PNG 或 JPEG 根本不可行。QtScrcpy 采用的是 video stream 方案Android 端通过screenrecord命令配合SurfaceFlinger的虚拟显示节点把屏幕内容交给硬件编码器输出 H.264 格式的二进制流PC 端接收这个流再用 ffmpeg 或系统解码器还原成可显示的图像帧。具体到 adb 侧这条链路大致是PC 端通过 adb push 把 server 文件一个 jar 包传到 Android 的/data/local/tmp。adb shell 执行CLASSPATH/data/local/tmp/server.jar app_process / com.genymobile.scrcpy.Server启动 Java 服务。Java 服务向 Android 的 SurfaceControl 申请一个虚拟 Display把屏幕内容投到编码器。编码器输出 H.264 流经过本地 socket 回传。PC 端 client 读取这个 socket解码后渲染到窗口。这里有个值得新学者注意的点adb 提供了adb forward转发机制可以让 PC 端和 Android 端通过本地端口通信。QtScrcpy 启动时会在 PC 端监听一个随机端口同时把这个端口 forward 到 Android 设备的 tcp 端口两条通道各自独立。控制指令触摸、按键和视频流走的都是这个 socket但通过字节流前缀区分消息类型。2.2 Client 端怎么“接住”并渲染QtScrcpy 的客户端不是单纯地开一个线程去recv()数据它拆了三层网络层负责连接与字节收发用 Qt 的QTcpSocket支持读写缓冲区管理。解码层把 H.264 裸流交给QVideoStreamReader做 AVCodec 解码产出QAbstractVideoBuffer或QVideoFrame。渲染层通过QVideoWidget显示同时处理窗口缩放、封面模式、黑边去除。这三层分离的直接好处是你可以替换掉任意一层。比如解码层直接硬件解码渲染层改成 OpenGL 纹理网络层支持 UDP 局域网直连扩展性极强。音频部分QtScrcpy 新版也做了接收处理。Android 端把 PCM 音频封装成数据块通过控制 socket 的另一个子通道发送PC 端用QAudioOutput播放。因为 C 侧跟 Java 侧的数据格式约定必须完全一致这里对字节序、采样位深、声道数这些细节要求非常高是移植时最容易踩坑的地方之一。2.3 为什么用 C 和 Qt而不是 Electron 或 Java这个问题我聊过很多次QtScrcpy 选择 C/Qt 并非偶然而是由性能要求和开发效率共同决定的。视频解码后是一个相对高频的帧回调流程Electron 的 Node 层和浏览器渲染层之间有额外拷贝延迟会高 20~50ms 甚至更多。C 处理视频帧可以直接用内存指针引用避免了多次 memcpy。Qt 的QThread、信号槽在跨线程传递帧数据时非常方便不必自己写复杂锁。Qt 本身就是跨平台的Windows/macOS/Linux 下行为一致这对投屏这种强依赖设备生态的工具几乎是必需属性。底层解码库ffmpeg本身就是 C 库C 直接调用节省了跨语言绑定的成本。如果用 Electron开发 GUI 确实快但你会发现解码帧要先把 C 侧的数据转成 JS 侧能访问的 ArrayBuffer再传给 canvas每一步都在复制内存。你用 Qt 直接操作QVideoFrame的bits()指针性能差距非常直观。3. 环境准备、源码结构与编译流程3.1 工具链选型从零搭一套可编译环境先说你最需要准备的东西操作系统Windows 10/11 或 Ubuntu 20.04编译器MSVC 2019/2022Windows或 GCC 9LinuxQt 版本5.15 或更高6.x 理论可行但部分控件 API 需要调整Android SDK 与 Platform Toolsadb 是核心依赖构建工具CMake 3.16Ninja 或 make我自己在 Windows 上的推荐组合是 Visual Studio 2022 Qt 5.15.2msvc2019_64 CMake。有一个容易忽略的坑Qt 的 msvc 和 MinGW 版本不能混用如果你下载的是 MinGW 版 Qt编译器必须用 MinGW 的 g否则链接阶段会报一堆“无法解析的外部符号”。3.2 源码目录与模块划分QtScrcpy 的源码组织方式非常清晰值得在头脑里建立一个地图main目录客户端主程序Qt Widgets 界面入口。core目录核心网络、连接池、控制逻辑。dialog目录设置对话框。device目录设备管理与 adb 交互。decoder目录视频解码封装。server目录Android 端 Java 服务源码最终打包成 jar。读代码时我的建议顺序是先看core里的connection和server的Server.java把两端的通信协议理解清楚再看decoder和device的连接初始化流程。一开始就扎进窗口布局容易迷失。3.3 编译流程两条路径选适合自己的路径一直接用 Qt Creator 构建用 Qt Creator 打开CMakeLists.txt选择 kit 为Desktop Qt 5.15.2 MSVC2019 64bit直接构建就行。这种方式最直观适合想快速跑起来的读者。路径二命令行 CMakemkdir build cd build cmake .. -DCMAKE_PREFIX_PATHC:/Qt/5.15.2/msvc2019_64 cmake --build . --config Release这里CMAKE_PREFIX_PATH必须指向 Qt 的编译器对应目录。如果你装了多个 Qt 版本这个路径写错会直接导致找不到 Qt6Core.dll 之类的错误。编译完成后还需要单独处理 server 端。因为 Android 端跑的是 Java需要 JDK 环境和 Android SDK 提供的dx或d8工具来把 class 文件打包进 jar。QtScrcpy 的CMakeLists.txt里已经写好了相关步骤只要你的环境变量里配了ANDROID_HOME和JAVA_HOME构建过程会自动生成server.jar。4. 核心功能实现细节与实操要点4.1 adb 设备接入的底层实现设备接入不是简单地调一次adb devices就结束。QtScrcpy 里维护了一个设备列表实时监听设备的插拔事件。这里有一个比较隐蔽的技术点adb 的 server 本身就是一个常驻进程PC 端工具启动时会尝试启动它然后通过 5037 端口跟 adb server 通信。如果你的设备没有开启 USB 调试投屏请求就会返回unauthorized状态。QtScrcpy 的处理是弹出提示并将设备标记为不可用。实操中我建议把adb keys目录下的公钥复制到所有测试机上这样可以避免每台新设备都去点“允许调试”弹窗。有一个高频操作是adb forward命令。QtScrcpy 在一次连接中可以同时 forward 多个端口每个都对应不同的服务类型。查看端口转发状态可以用adb forward --list如果发现端口被占用可以先adb forward --remove-all再重新连接。这个“先清空再分配”的思路在多设备同时接入时能避免很多端口冲突的诡异问题。4.2 视频解码与渲染延迟控制的关键很多人问“为什么我的投屏延迟总是 200ms 以上”其实问题往往不在网络而在解码线程和渲染线程的帧滞留策略。QtScrcpy 的解码线程会尽量只保留最新帧丢弃积压的旧帧。但如果你把它改成“所有帧都渲染”立刻就能感受到严重卡顿。渲染时容易遇到的一个坑是纹理格式不匹配。Android 端输出的往往是H.264裸流解码后的像素格式可能是YUV420P而 Qt 的QVideoWidget默认期望的是RGB32或ARGB32。这里需要做一次颜色空间转换。QtScrcpy 用了libyuv做转换你也可以用 ffmpeg 的sws_scale。相比逐像素手动转换这两个库的速度差了一个数量级。帧率控制上QtScrcpy 默认会从流信息里读帧率。如果你的设备原生支持 60fps而 PC 端因为屏幕刷新率是 144Hz那么解码器会多出不少空闲间隔。这个没有问题真正要关注的是maxFps的设置。降低帧率可以节省大量 USB 带宽和 CPU 占用尤其是在无线投屏时非常实用。4.3 反向控制触摸、按键和文本输入怎么实现投屏的意义不只在于“看”更在于“操作”。QtScrcpy 把鼠标事件、键盘事件、剪贴板事件都封装成控制消息通过 socket 发给 Android 端的 server再由 server 调用 InputManager 注入事件。触摸注入的核心是坐标系转换。PC 窗口的坐标是相对窗口左上角的但 Android 设备的坐标是相对屏幕左上角的像素值两者需要根据实际窗口大小和设备分辨率做缩放。QtScrcpy 里有一个MotionEvent类专门处理这个映射。这里有一个细节如果窗口保持 16:9 比例但设备是 18:9屏幕两侧会出现黑边。QtScrcpy 提供“裁剪”模式和“填充”模式二选一都会影响坐标换算代码里是按 width/height 比来动态修正的并没有一刀切。文字输入这块QtScrcpy 用的是 Android 的adb shell input text方式但遇到特殊字符时非常容易踩坑。空格需要转义成%s中文需要用ADBKeyboard.apk这类 IME 支持。如果你做的是自动化测试框架建议仔细看下这段逻辑否则脚本里输入带、#的密码时会莫名失败。4.4 Server 端注入Java 与 C 的配合方式很多人读 QtScrcpy 源码时最看不懂的就是 server 端——它是一段写好的 Java 代码但运行方式不是常规的Class.forName或Activity而是通过app_process启动。app_process是 Android 系统启动 zygote 时使用的进程入口可以直接在非 Activity 环境下运行 Java 类这让投屏服务具备了“无界面后台运行”的能力。Server 启动后的工作流程可以这样理解解析命令行参数拿到视频位宽、码率、帧率、连接端口。创建一个虚拟 Display与设备的真实屏幕同步刷新。启动MediaCodec编码器把虚拟 Display 的内容编码成 H.264。与 PC 端的 socket 建立连接开始双向读写。这里有个常见误区MediaCodec编码出来的流是带 SPS/PPS 的 AVCC 格式而 PC 端的 ffmpeg 解码时如果是 Annex-B 格式的配置两者不能直接互通。QtScrcpy 里对csd-0、csd-1的处理就是专门解决这个问题的。如果你自己写了一个新的投屏协议但画面一直黑屏、只有音频先查 SPS/PPS 的封装格式大概率就是这个问题。5. 实操记录从源码编译到首次投屏5.1 编译所踩的坑与应对我第一次在 Windows 上编译 QtScrcpy整体流程还算顺利但有几个环节很有代表性值得写下来。CMake找不到 Qt这是最常见的失败原因。经验是先在 CMake 的CMAKE_PREFIX_PATH里填路径时不要指到C:/Qt/5.15.2这种总目录而要指到具体编译器架构的目录比如msvc2019_64。因为 Qt 提供的.cmake文件都在该子目录下。链接时缺少winmm.lib这是 Qt 5.15 在 Windows 上经常出现的情况通常跟音频模块有关。解决方法是加一条target_link_libraries(... winmm)或者在 Qt Creator 的 pro 文件里加LIBS -lwinmm。缺少platforms/qwindows.dll编译过了但运行报错本质是 Qt 插件路径没配置。将编译完的 dll 放在正确目录下通常能解决如果不行就在代码里强制QApplication::addLibraryPath()。5.2 首次连接与高清画质调试编译成功后我直接插上一台 Android 测试机打开 QtScrcpy设备列表里立刻显示序列号。点击“开始投屏”大约 1~2 秒后窗口出来画面默认是 720p 的标准模式。如果你想投 2K 或 4K必须在点击“开始投屏”前设置分辨率。注意一点分辨率不是越低越流畅也不是越高越清晰。它跟设备实际屏幕大小、PC 端窗口大小、USB 带宽三者都有关系。实测下来电脑窗口只有 800x600 时投 4K 纯属浪费带宽反之如果用 4K 大屏全屏显示只投 720p 会明显模糊。码率这块我用-b 8M起步无线场景下降到4M仍然能保持不错的清晰度。如果你要投屏录屏做教程建议直接开-b 20M细节保留程度会好很多但延迟也会略微上升。5.3 无线模式让手机离开 USB 线QtScrcpy 是支持无线投屏的原理是先通过 USB 建立连接然后执行adb tcpip 5555 adb connect 设备IP:5555一旦连接成功拔掉 USB 线重新在 QtScrcpy 里连接设备就行。我实测的延迟大约比 USB 多 30~60ms这取决于你的路由器。如果是 5G Wi-Fi 局域网基本感受不到差别如果是 2.4G Wi-Fi 又离得远延迟会明显增加画面也会产生马赛克。网络环境差时最有效的优化是把投屏分辨率降到 1080p、码率降到 4M 以下。这样画质会有一定损失但操作响应速度比什么都重要。6. 常见问题与排查技巧实录6.1 设备连接与权限类Qadb devices 能看到设备但 QtScrcpy 一直提示未授权。原因通常是 PC 端的 RSA 指纹跟设备端不匹配。手机上撤销 USB 调试授权重新插拔并在设备上点“允许”。如果有多台设备建议把~/.android/adbkey.pub内容追加到每台设备的/data/misc/adb/adb_keys但需要 root 权限。没有 root 的测试机最好还是手动在每台设备上授权一次。Q启动时提示 adb server 版本过旧。Android SDK 的 platform-tools 更新频率很高老版本 adb 无法解析新设备的协议。直接下载最新 platform-tools 并替换系统 PATH 里的 adb 文件即可。注意 64 位系统千万不要用 32 位 adb否则会提示 Exec format error。6.2 画面与性能类Q投屏成功但画面是黑屏只有声音或完全不显示。先查 H.264 流是否正常输出。如果设备接口正常、编码器正常黑屏大概率是解码或渲染环节的问题。可以试着用 ffprobe 看一下流信息adb shell screenrecord --time-limit 5 /sdcard/test.mp4 adb pull /sdcard/test.mp4如果录出的 mp4 在本地播放正常说明编码侧没问题问题出在 PC 端解码器。推荐换用硬件解码或者更新显卡驱动。Q投屏延迟很高触摸明显跟不上。先把分辨率降下来再把码率降下来。如果仍然卡顿检查是否有后台进程占用 CPU。QtScrcpy 的解码在最坏情况下是单线程满负荷的音频解码又会跟视频解码抢 CPU 时间可以在设置里关闭声音看延迟是否明显降低。Q窗口缩放后画面发虚。Qt 的QVideoWidget默认按设备像素比渲染窗口缩小到一定程度后像素堆积明显。可以在渲染前把目标窗口大小设置为固定的 2x 或 3x 缩放倍率避免花式差值造成画面发虚。更高清的体验是用 OpenGL 纹理渲染再做双线性过滤。6.3 音频与录屏类Q画面正常但没有声音。先确认工具已开启音频转发。其次检查 PC 端默认播放设备是否为“扬声器/耳机”。如果你用的是虚拟声卡或蓝牙音箱Android 的声音数据可能被系统吞掉。另一个容易忽略的因素是 QtScrcpy 不支持所有音频格式部分设备的采样率如果比较特殊可以在设置里手动指定 44100 或 48000。Q录屏文件打开后没有声音。录屏文件通常将视频和音频混流成 mp4。如果文件能打开但声音缺失说明混流时的音频解析有问题。建议先检查录屏期间 QtScrcpy 的日志里有没有“audio decode error”。这类问题多半是 PCM 采样格式不匹配需要查看音频子通道的参数是否与 PC 端期望一致。7. 从“跑通”到“好用”的扩展方向如果你只是把 QtScrcpy 跑起来那收获有限。真正有价值的是把它当成一个可二次开发的投屏基座。我根据自己的实践列几个有代表性的扩展方向希望对你的项目设计有启发。7.1 多设备并发管理从“单投屏”到“设备墙”QtScrcpy 天然支持多开只要每个实例连接不同设备。你可以做一个简单的主控程序一次性拉起 N 个子进程每个子进程对应一台设备。这样测试一个 App 在不同机型上的兼容性时可以同时观察所有屏幕的操作反馈效率直接翻倍。实现时的一个关键点是每个实例的 adb forward 端口不能冲突。启动前先检查端口占用或者让每个实例动态请求一个空闲端口。否则第二台设备启动时会报 bind 失败。7.2 自定义按键协议接入自动化框架原版 QtScrcpy 的按键事件是写死的。你可以扩展控制消息协议比如在鼠标事件结构体里追加一个“宏指令”字段告诉 Android 端执行一次滑动、长按或多点触控。这样测试脚本就不需要再走一遍 UI 自动化框架直接把精确的触摸坐标发过去就行。这种改法要注意消息头的对齐。C 端使用#pragma pack(push, 1)来紧凑定义结构体Java 端同样按字节读取。否则两端对结构体大小的理解不一致会读到错误的数据。7.3 高清投屏优化与低延迟改造如果你对延迟有极致要求可以跳过 Qt 的QVideoWidget默认绘制路径改用QOpenGLWidget直接接收QVideoFrame通过 OpenGL 纹理上传并渲染。这样绕开了 Qt 内部的一次绘图拷贝实测可以降低约 5~10ms 延迟。同时在 Android 端把编码器的预期帧率设置为 60fps并把i-frame-interval调小这样远程操作时画面不会长时间卡在旧帧上。另一个技巧是把编码器bitrate-mode设为 CQConstant Quality码率会自动在复杂画面和静止画面间调整比恒定码率更节约带宽。7.4 跨平台 UI 的细节打磨QtScrcpy 在 Linux 和 macOS 上同样可以编译运行但有几个平台差异需要注意macOS 上需要开启APP_SANDBOX权限描述否则无法使用 local network。Linux 上如果使用 Wayland部分鼠标事件坐标会异常需要改用 X11。字体渲染在不同平台差异明显如果 UI 上要显示设备型号和状态信息建议用系统默认字体替代自定义字体。这些坑都不会在 Windows 上遇到真正做跨平台发布时才能体会到“写一次代码到处调试”的含义。写在最后的一点心得研究 QtScrcpy 源码这件事让我真正理解了一个现代桌面应用应该如何设计网络层、解码层、渲染层、控制层每一层都独立、稳定、可替换而不是所有代码混在一个类里。如果你最近在看 C 项目我的建议是把 QtScrcpy 的core模块拆出来一行行读通再对照server里的 Java 代码理解后端服务。两端的协议定义基本就是一套字节流封装的约定吃透之后你自己写一个局域网无线投屏工具也只是时间问题。另外想提醒一点Android 手机开启 USB 调试会存在一定的安全风险尽量只在可信的设备和测试环境中使用。投屏软件的边界是工具本身怎么用它完全取决于你的目标场景。本文还有配套的精品资源点击获取
返回列表