深入 scrcpy 架构:客户端-服务端通信协议与开发者实战指南 📅 发布时间:2026/9/15 18:47:23 👁 浏览次数: 深入 scrcpy 架构客户端-服务端通信协议与开发者实战指南【免费下载链接】escrcpy Display and control your Android device graphically with scrcpy.项目地址: https://gitcode.com/GitHub_Trending/es/escrcpy本文以 scrcpy 官方开发者文档docs/en/reference/scrcpy/develop.md为主体骨架结合本仓库Escrcpy基于 Electron 的 scrcpy 图形化客户端的源码实现系统讲解 scrcpy 的服务端/客户端架构、三通道套接字协议、编解码链路、控制消息注入机制以及如何把 scrcpy-server 作为独立服务端使用、如何调试服务端。读完本文你将掌握 scrcpy 从设备推流到主机解码渲染的完整工作原理并能直接运用文档中的命令进行二次开发与调试。总体架构服务端 客户端的两段式设计scrcpy 应用由两部分组成服务端scrcpy-server一个 Java 应用程序运行在 Android 设备上负责采集并编码屏幕画面、音频以及接收并注入输入事件客户端scrcpy可执行文件运行在主机电脑上负责把服务端推送到设备、启动其执行并对视频/音频流进行解码与渲染。客户端与服务端之间通过独立的套接字分别承载视频、音频与控制三类数据。三者都可以被单独禁用但不能全部禁用因此实际建立的套接字数量为 1、2 或 3 个。服务端首先在第一个套接字上发送设备名称用作 scrcpy 窗口标题随后每个套接字各司其职。客户端和服务端均会为每个套接字分配专用线程进行读写互不阻塞。三通道的数据流视频通道默认启用服务端发送设备屏幕的原始视频流默认 H.264 编码每个数据包附带额外头部信息。客户端解码视频帧并尽可能快地显示、不做缓冲除非指定--video-bufferdelay以最小化延迟。值得注意的是客户端不感知设备旋转旋转由服务端处理它只知道收到的视频帧尺寸。音频通道默认启用服务端发送设备音频输出或通过--audio-sourcemic指定麦克风输入的原始音频流默认 OPUS 编码同样带数据包头部。客户端解码流通过维持平均缓冲来把延迟控制在较低水平。控制通道默认启用这是唯一一个双向使用的套接字。客户端捕获键盘、鼠标事件发送给服务端由服务端注入到设备反过来当设备端剪贴板内容变化时新内容会由设备发回客户端实现无缝复制粘贴。应用层角色与网络层角色的反转需要注意客户端/服务端的角色是应用层面的语义服务端服务于视频与音频流并处理客户端的请求客户端通过服务端控制设备。但在网络层面默认情况下未设置--force-adb-forward时角色是反过来的客户端先打开一个服务器套接字并监听端口服务端反过来主动连接客户端。这种角色反转的设计目的是保证连接建立不会因为竞态条件而失败同时无需轮询。这一点在本仓库 Escrcpy 的进程管理里也有呼应——参见 desktop/electron/process/helper.js 中对 scrcpy 进程与环境的统一初始化。服务端Server剖析权限为什么用shell用户执行采集屏幕需要特定权限这些权限被授予给shell用户。服务端是一个带public static void main(String... args)入口的 Java 应用程序针对 Android 框架编译并在 Android 设备上以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用户可读写但不是全局可写因此恶意应用无法在客户端执行服务端之前将其替换。除了原始 dex 文件app_process也接受包含classes.dex的 jar例如一个 APK。为了简化并利用 Gradle 构建体系服务端被构建成一个未签名的 APK随后重命名为scrcpy-server.jar。隐藏方法通过反射与 Wrapper 访问框架内部尽管服务端是针对 Android 框架编译的但 Android 的[隐藏方法]hide接口与类不能直接访问且不同 Android 版本之间可能有差异。scrcpy 通过反射调用它们与隐藏组件的通信由wrapper 类与 AIDLAndroid 接口定义语言提供。例如输入注入使用的InputManager.injectInputEvent()就是隐藏方法由InputManagerwrapper 暴露出来对应原文档中server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java的实现思路。执行客户端启动服务端的三个命令客户端启动服务端本质上是执行以下命令adb push scrcpy-server /data/local/tmp/scrcpy-server.jar adb forward tcp:27183 localabstract:scrcpy adb shell CLASSPATH/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 2.1其中第一个参数示例中的2.1是客户端 scrcpy 版本号。服务端在客户端与服务端版本不一致时会直接失败——因为客户端-服务端之间的协议可能随版本变化见下文协议章节且没有向后/向前兼容混用不同版本的客户端与服务端没有意义。这个版本校验用于尽早发现配置错误例如误运行了旧版或新版服务端。在版本号之后可以跟随任意数量的keyvalue形式参数顺序无关。例如执行scrcpy -m1920 --no-audio时服务端的实际执行形态是# scid 是一个随机数用于区分同一设备上运行的不同客户端 adb shell CLASSPATH/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 2.1 scid12345678 log_levelinfo audiofalse max_size1920在本仓库 Escrcpy 中服务端文件的路径通过环境变量注入到 scrcpy 进程见 desktop/electron/process/helper.js其中SCRCPY_SERVER_PATH指向随应用分发的scrcpy-server文件SCRCPY_ICON_DIR指向图标目录从而让 scrcpy 客户端在推流前能正确找到服务端资源。服务端组件视频流、音频流与控制器执行时服务端的main()方法运行在主线程会解析参数 → 建立与客户端的连接 → 启动以下组件视频流video streamer采集屏幕视频把编码后的视频数据包从video 线程发送到video 套接字音频流audio streamer使用多个线程采集原始音频包 → 提交编码 → 取回编码后的包并发送到audio 套接字控制器controller一个线程从control 套接字接收控制消息典型如输入事件另一个线程通过同一control 套接字发送设备消息如把设备剪贴板内容传给客户端。因此control 套接字是双向的这与单向的 _video/audio套接字形成对比。屏幕视频编码ScreenEncoderMediaCodec视频编码由ScreenEncoder管理。它使用 Android 的MediaCodecAPI编码与显示关联的Surface内容并把编码后的数据包写到video 套接字上发给客户端。关键行为设备旋转或折叠时编码会话会被重置并重启只有当 Surface 内容发生变化时才产生新帧。这避免了发送无意义的帧但默认会有两个缺陷若设备屏幕一开始没有变化启动时不会发送任何帧快速运动后最后一帧质量可能较差。这两个问题由MediaFormat的KEY_REPEAT_PREVIOUS_FRAME_AFTER标志解决它要求编码器在指定时间后重复上一帧从而保证流不会停滞、最后一帧不会长期失真。音频编码AudioRecord采集 MediaCodec异步编码与视频类似音频通过AudioRecord采集并使用MediaCodec的异步 API编码。采集、编码、发送的流水线在服务端被拆分为多个线程协作完成保证吞吐与低延迟。输入事件注入控制消息由服务端的Controller运行在独立线程接收输入事件主要有几类键码KeyEvent文本某些特殊字符无法直接用键码表达需要走文本通道鼠标移动/点击鼠标滚动其他命令例如点亮屏幕、复制剪贴板等。其中需要注入系统输入事件的类型通过隐藏方法InputManager.injectInputEvent()完成——该方法由InputManagerwrapper 类以反射方式暴露。客户端Client剖析客户端基于SDL提供跨平台的 UI、输入事件、线程等 API视频与音频流由FFmpeg解码。初始化两条代码路径客户端解析命令行参数后会走以下两条路径之一normal正常模式即常规的屏幕镜像/控制路径OTG 模式详见 docs/en/reference/scrcpy/otg.md。本文后续按 normal 模式展开OTG 模式请直接阅读源码。启动时客户端依次完成打开video、audio、control套接字把服务端推送到设备并启动初始化自身组件解复用器 demuxer、解码器 decoder、录制器 recorder 等。视频与音频流水线根据传给scrcpy的参数客户端会启用不同的组件组合。整体数据流如下V4L2 sink / decoder / \ VIDEO ------------- demuxer display \ recorder / AUDIO ------------- demuxer \ decoder --- audio player解复用器demuxer负责提取视频/音频数据包读取头部、在正确的边界处切分视频流等解码器decoder每个流一个负责把数据包解出可渲染的帧录制器recorder同时接收视频与音频流录制成单个文件。数据包在设备端由MediaCodec编码但录制时是在客户端把它们异步复用mux进容器MKV 或 MP4显示display视频帧渲染到 scrcpy 窗口也可能转发给 V4L2 sink音频播放器audio player接收解码后的音频样本数组并播放。控制器SDL 事件 → Android 事件的转换管道客户端的controller负责向设备发送控制消息它运行在独立线程避免在主线程上做 I/OSDL 事件到达主线程输入管理器input manager把 SDL 事件转换为 Android 事件即生成对应的控制消息控制消息被推入 controller 持有的队列controller 在自己的线程里从队列取出消息、序列化并发送给设备。通信协议Protocol客户端与服务端之间的协议应被视作内部协议它随时可能因任何原因变更套接字数量、打开顺序、线上数据格式等都可能变。因此客户端必须始终与匹配版本的服务端一起运行。以下描述的是 scrcpy v2.1 的当前协议。连接建立adb 隧道 最多 3 个套接字首先客户端设置 adb 隧道# 默认是反向重定向计算机监听设备主动连接 adb reverse localabstract:scrcpy_SCID tcp:27183 # 作为回退或设置了 --force-adb-forward 时是正向重定向设备监听计算机连接 adb forward tcp:27183 localabstract:scrcpy_SCID其中SCID是一个 31 位随机数用于保证同一设备上同时启动多个 scrcpy 实例时不会互相冲突。随后按固定顺序打开最多 3 个套接字video 套接字audio 套接字control 套接字。每个都可以被禁用分别由--no-video、--no-audio、--no-control直接或间接控制。例如设置了--no-audio时先开 video 套接字再开 control 套接字。在打开的第一个套接字上若隧道是**正向forward的设备会先发一个虚拟字节dummy byte**给客户端。这是为了检测连接错误——只要 adb 正向重定向存在即使设备端没有程序在监听客户端的连接也不会立刻失败虚拟字节能让双方尽早发现异常设备接着发送设备元数据目前仅设备名称用作窗口标题未来可能扩展更多字段。视频与音频线上的数据格式在video与audio套接字上设备首先发送编解码器元数据codec metadatavideo 套接字共 12 字节codec idu32H264、H265 或 AV1初始视频宽度u32初始视频高度u32。audio 套接字共 4 字节codec idu32OPUS、AAC 或 RAW。随后每个由MediaCodec产出的数据包都会被附加一个12 字节的帧头部frame headerconfig packet 标志u1key frame 标志u1PTSu62packet sizeu32。帧头部的位布局示意[. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ... ------------- ----- -----------------------------... PTS packet raw packet size --------------------- frame header PTS 的最高位被用作数据包标志 byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0 CK...... ........ ........ ........ ........ ........ ........ ........ ^^------------------------------------------------------------------- || PTS | - key frame关键帧 -- config packet配置包也就是说PTS 有效位是 62 bit最高两位分别承担config packet与key frame两个布尔标志。控制消息以单元测试为唯一文档的二进制协议控制消息通过自定义二进制协议传输。官方文档明确指出该协议的唯一文档是客户端与服务端两侧的单元测试ControlMessage客户端 → 设备客户端侧的序列化测试、服务端侧的反序列化测试DeviceMessage设备 → 客户端服务端侧的序列化测试、客户端侧的反序列化测试。这意味着如果你想基于该协议做二次开发最可靠的参考就是这两组测试用例分别位于 scrcpy 的app/tests与server/src/test目录。独立服务端把设备变成一台原始流服务器虽然服务端是为 scrcpy 客户端设计的但任何遵循同一协议的客户端都可以使用它。为了简化此类用途scrcpy 提供了一组服务端专用选项用于输出裸流send_device_metafalse禁用第一个套接字上的设备元数据即设备名send_frame_metafalse禁用每个数据包的 12 字节头部send_dummy_bytefalse禁用正向连接时的虚拟字节send_codec_metafalse禁用编解码信息以及视频的初始设备尺寸raw_streamtrue一键禁用以上全部。例如在 TCP 套接字上暴露一条原始 H.264 流adb push scrcpy-server-v2.1 /data/local/tmp/scrcpy-server-manual.jar adb forward tcp:1234 localabstract:scrcpy adb shell CLASSPATH/data/local/tmp/scrcpy-server-manual.jar \ app_process / com.genymobile.scrcpy.Server 2.1 \ tunnel_forwardtrue audiofalse controlfalse cleanupfalse \ raw_streamtrue max_size1920一旦有客户端通过 TCP 连接到本机 1234 端口设备即开始推流。例如用 VLC 播放注意此方式延迟很高仅为演示原始流可用性vlc -Idummy --demuxh264 --network-caching0 tcp://localhost:1234调试服务端Android Studio 远程调试服务端由客户端在启动时推送到设备。要调试它需要在构建阶段启用服务端调试器meson setup x -Dserver_debuggertrue # 或者如果 x 已配置过 meson configure x -Dserver_debuggertrue重新编译并运行 scrcpy 后Android 11设备端会在 5005 端口启动调试器并等待将该端口重定向到电脑adb forward tcp:5005 tcp:5005Android 11先找到监听端口adb jdwp # 按 CtrlC 中断再重定向对应的 PIDadb forward tcp:5005 jdwp:XXXX # 将 XXXX 替换为实际的 PID然后在 Android Studio 中配置远程调试RunDebugEdit configurations...左侧点选择Remote填写HostlocalhostPort5005最后点击Debug即可断点调试服务端。Escrcpy 中的 scrcpy 集成实践回到本仓库Escrcpy 是一个基于 Electron 的 scrcpy 图形化客户端。其 scrcpy 中间件位于 desktop/electron/middleware/scrcpy/index.js可以看到它把前文所述的概念落实到了产品功能中统一进程包装createScrcpyProcess通过sheller以scrcpy args的形式启动客户端进程并监听stdout/stderr通过正则/(?:Renderer:|Texture:|\[server\]\sINFO:\sDevice:)/i判断 scrcpy 是否就绪对应文档中第一个套接字发送设备元数据的时机并做了resolveOnReady/resolveOnSpawn两种就绪策略镜像与录制mirror()组装--serial --window-title参数record()额外附加--recordsavePath对应文档中客户端录制器recorder复用为 MKV/MP4的能力控制面辅助命令helper()使用--no-window --no-video --no-audio仅启用控制通道对应文档中各套接字可独立禁用getEncoders()/getAppList()/getDisplayIds()/getCameraList()分别调用--list-encoders、--list-apps、--list-displays、--list-cameras来枚举设备能力虚拟显示启动launch()支持--new-display、--flex-display、--start-apppackageName等参数组合并通过匹配New display:.?\(id(\d)\)获取新建显示 ID这正是视频流按需开启/禁用在实际产品中的典型用法输出解析desktop/electron/middleware/scrcpy/helper.js 把上述命令的文本输出解析为结构化数据应用列表、编解码器、显示 ID、摄像头规格说明这些 CLI 接口正是 scrcpy 对外暴露的可编程接口。结语从协议到工程的完整链路scrcpy 之所以能实现低延迟镜像本质上是把采集-编码-传输-解码-渲染这条链路拆成了设备端服务端与主机端客户端两个自治子系统用 13 个独立套接字和一套内部二进制协议串接起来。本文沿官方开发者指南梳理了其中的架构、命令、位级协议与服务端调试方法并结合 Escrcpy 仓库源码展示了这些机制在真实产品中的落地方式。若想进一步深入可直接阅读代码——正如官方文档所说For more details, go read the code!【免费下载链接】escrcpy Display and control your Android device graphically with scrcpy.项目地址: https://gitcode.com/GitHub_Trending/es/escrcpy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考