菜单

屏幕捕获和编码

相关源文件

本文档详细介绍了 scrcpy 如何从 Android 设备捕获屏幕内容并将其编码为视频流以传输到客户端。屏幕捕获和编码系统是服务器端组件的关键部分,负责获取包含设备屏幕(或摄像头)内容的 Surface,并将其转换为编码的视频流。

有关客户端如何处理和显示视频的信息,请参阅视频与音频处理

1. 捕获架构

Scrcpy 支持多种捕获源,它们都继承自一个通用基类,为编码系统提供了一致的接口。

1.1 捕获层次结构

来源:server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java server/src/main/java/com/genymobile/scrcpy/video/CameraCapture.java

SurfaceCapture 抽象类定义了所有捕获实现必须提供的通用接口

  • 初始化init() 设置捕获系统
  • 准备prepare() 为新的捕获会话做准备
  • Surface 处理start(Surface)stop() 控制活动的捕获
  • 资源管理release() 清理资源
  • 配置getSize()setMaxSize() 等方法提供和调整配置

1.2 捕获源

Scrcpy 支持三种主要的捕获源

  1. ScreenCapture:捕获设备上现有显示器的内容(默认)
  2. NewDisplayCapture:创建一个新的虚拟显示器并从中捕获
  3. CameraCapture:从设备的摄像头捕获视频,而不是屏幕

2. 捕获与编码流程

整体屏幕捕获和编码流程经过几个关键步骤

来源:server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java289-325 server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java65-147 server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java197-224

2.1 初始化

编码器启动时

  1. SurfaceEncoder 使用 start(TerminationListener) 创建一个编码线程
  2. 调用 streamCapture() 来处理核心编码过程
  3. 所选的 SurfaceCapture 实现通过 init() 进行初始化
  4. 为选定的视频编解码器(H.264、H.265 或 AV1)创建一个 MediaCodec 编码器
  5. 创建具有适当编码参数的媒体格式

2.2 捕获循环

对于每个捕获会话

  1. 使用 prepare() 准备捕获
  2. 从捕获实现中获取视频尺寸
  3. 写入视频流的头部
  4. 使用正确的格式配置 MediaCodec 编码器
  5. 为编码器创建输入 Surface
  6. 使用编码器 Surface 启动捕获
  7. 编码过程开始

2.3 编码与流式传输

编码期间

  1. 编码器重复调用 dequeueOutputBuffer() 以获取编码帧
  2. 每个编码帧都会写入流式传输器
  3. 编码完成时(由于停止或错误),捕获和编码器将被停止和重置
  4. 该过程可能会重复(例如,当显示方向改变时)

3. 屏幕捕获实现

3.1 ScreenCapture

ScreenCapture 类从设备上现有的显示器捕获内容。它使用两种可能的方法

来源:server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java196-213

流程

  1. 尝试使用 DisplayManager.createVirtualDisplay() 创建一个虚拟显示器
  2. 如果失败,则回退使用 SurfaceControl API 来创建和配置显示器
  3. 使用适当的 Surface、投影和图层堆栈设置显示器
  4. 如果需要变换(裁剪、旋转等),则创建一个 OpenGL 流水线来处理帧

关键方法

  • createDisplay():使用 SurfaceControl API 创建一个安全显示器
  • setDisplaySurface():使用目标 Surface 配置显示器
  • setDisplayProjection():设置显示器投影(坐标映射)
  • setDisplayLayerStack():设置显示器的图层堆栈以正确渲染

3.2 NewDisplayCapture

NewDisplayCapture 类创建一个新的虚拟显示器(而不是镜像现有显示器)

来源:server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java159-164

主要功能

  1. 创建全新的虚拟显示器,而不是镜像
  2. 支持各种显示标志以进行配置
    • VIRTUAL_DISPLAY_FLAG_PUBLIC
    • VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
    • VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
    • 其他基于 Android 版本(受信任显示、自有显示组等)
  3. 如果指定,则配置 IME(输入法编辑器)策略
  4. 设置位置映射以确保用户输入正常工作

3.3 CameraCapture

CameraCapture 类从设备摄像头捕获视频

  1. 根据提供的 ID 或朝向选择摄像头
  2. 使用 Camera2 API 打开摄像头
  3. 根据配置选择合适的捕获尺寸
  4. 创建捕获会话并开始向提供的 Surface 捕获

4. 视频变换流水线

所有捕获都通过 VideoFilter 类支持视频变换

来源:server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java server/src/main/java/com/genymobile/scrcpy/device/Size.java32-81

变换流水线支持

  1. 裁剪:将捕获限制在特定矩形区域
  2. 旋转:处理设备旋转和方向更改
  3. 角度调整:应用任意旋转角度
  4. 缩放:调整视频尺寸以满足约束

关键方面:

  • 变换被累积到单个仿射变换矩阵中
  • 计算变换的逆变换以用于输入映射(以便触摸正常工作)
  • 使用 OpenGL 高效地应用变换
  • 尺寸变换确保编码器兼容性(8 的倍数)

5. 视频编码

SurfaceEncoder 类使用 Android 的 MediaCodec API 处理视频编码

5.1 编码器选择与配置

来源:server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java226-286

配置详情

  1. 编码器选择
    • 如果提供,则使用指定的编码器名称
    • 对于选定的编解码器类型,则回退到默认编码器
  2. 格式配置
    • 根据选定的编解码器(H.264、H.265 或 AV1)设置 MIME 类型
    • 从选项配置比特率
    • 将帧率设置为 60(尽管实际帧率是可变的)
    • 使用 COLOR_FormatSurface 进行基于 Surface 的编码
    • 设置其他参数,如 I 帧间隔和帧重复
  3. 可选编解码器选项
    • 可以提供自定义编解码器选项以微调编码器

5.2 编码过程

实际编码发生在 encode() 方法中

1. Create a BufferInfo to receive encoded frame information
2. Dequeue output buffers from the MediaCodec encoder
3. For each buffer:
   a. Check for end-of-stream flag
   b. Extract the encoded data
   c. Check if it's a configuration packet or a frame
   d. Write the packet to the streamer
4. Release output buffers back to the encoder
5. Continue until end-of-stream is received

来源:server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java197-224

5.3 错误处理与恢复

编码器包含强大的错误处理机制

  1. 错误检测:捕获编码器和捕获过程中的异常
  2. 重试逻辑:尝试使用调整后的参数重试捕获
  3. 缩减尺寸:如果编码失败,可以自动减小视频尺寸
  4. 连续错误:跟踪并限制连续错误的次数
  5. 重置处理:在出错后正确重置捕获和编码器

来源:server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java149-195 server/src/main/java/com/genymobile/scrcpy/video/CaptureReset.java

6. 位置映射

为了确保触摸输入在转换后的视频上正常工作,系统会使用一个 PositionMapper

来源:server/src/main/java/com/genymobile/scrcpy/control/PositionMapper.java

位置映射器

  1. 接收来自客户端的输入坐标
  2. 应用视频转换的逆变换(裁剪、旋转等)
  3. 将归一化的设备坐标转换为像素坐标
  4. 返回设备特定的坐标以进行准确的输入处理

7. 性能考量

几个重要方面会影响捕获和编码系统的性能

7.1 尺寸和分辨率

  • 可以使用 maxSize 来设置尺寸限制,以减轻编码负担
  • 尺寸会调整为 8 的倍数,以兼容编码器
  • Size.limit() 方法可在保留宽高比的同时确保尺寸限制

7.2 编码参数

  • 比特率直接影响视频质量和带宽使用量
  • 可以通过 max-fps-to-encoder 选项来控制帧率
  • 编解码器的选择会在质量、兼容性和性能之间进行权衡

7.3 OpenGL 处理

  • OpenGL 变换仅在需要时使用(裁剪、旋转、缩放)
  • 在可能的情况下使用直接渲染以最小化处理开销
  • OpenGLRunner 创建一个单独的处理线程,以实现高效变换

8. 捕获重置和失效

捕获系统包含处理捕获源更改的机制

来源:server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java14-27 server/src/main/java/com/genymobile/scrcpy/video/CaptureReset.java

失效流程

  1. 显示屏的变化(旋转、尺寸更改)会触发 DisplayListener
  2. 捕获的 invalidate() 方法会被调用
  3. CaptureReset 会中断当前的编码会话
  4. 编码器会使用更新后的参数启动新的捕获
  5. 视频捕获会以新的配置继续进行

这个系统确保了设备显示屏的变化能够快速反映在视频流中,而无需完全重新启动捕获过程。