本文档详细介绍了 scrcpy 如何从 Android 设备捕获屏幕内容并将其编码为视频流以传输到客户端。屏幕捕获和编码系统是服务器端组件的关键部分,负责获取包含设备屏幕(或摄像头)内容的 Surface,并将其转换为编码的视频流。
有关客户端如何处理和显示视频的信息,请参阅视频与音频处理。
Scrcpy 支持多种捕获源,它们都继承自一个通用基类,为编码系统提供了一致的接口。
来源: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() 为新的捕获会话做准备start(Surface) 和 stop() 控制活动的捕获release() 清理资源getSize() 和 setMaxSize() 等方法提供和调整配置Scrcpy 支持三种主要的捕获源
整体屏幕捕获和编码流程经过几个关键步骤
来源: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
编码器启动时
SurfaceEncoder 使用 start(TerminationListener) 创建一个编码线程streamCapture() 来处理核心编码过程SurfaceCapture 实现通过 init() 进行初始化MediaCodec 编码器对于每个捕获会话
prepare() 准备捕获MediaCodec 编码器编码期间
dequeueOutputBuffer() 以获取编码帧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
流程
DisplayManager.createVirtualDisplay() 创建一个虚拟显示器SurfaceControl API 来创建和配置显示器关键方法
createDisplay():使用 SurfaceControl API 创建一个安全显示器setDisplaySurface():使用目标 Surface 配置显示器setDisplayProjection():设置显示器投影(坐标映射)setDisplayLayerStack():设置显示器的图层堆栈以正确渲染NewDisplayCapture 类创建一个新的虚拟显示器(而不是镜像现有显示器)
来源:server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java159-164
主要功能
VIRTUAL_DISPLAY_FLAG_PUBLICVIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLYVIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCHCameraCapture 类从设备摄像头捕获视频
所有捕获都通过 VideoFilter 类支持视频变换
来源:server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java server/src/main/java/com/genymobile/scrcpy/device/Size.java32-81
变换流水线支持
关键方面:
SurfaceEncoder 类使用 Android 的 MediaCodec API 处理视频编码
来源:server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java226-286
配置详情
COLOR_FormatSurface 进行基于 Surface 的编码实际编码发生在 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
编码器包含强大的错误处理机制
来源:server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java149-195 server/src/main/java/com/genymobile/scrcpy/video/CaptureReset.java
为了确保触摸输入在转换后的视频上正常工作,系统会使用一个 PositionMapper
来源:server/src/main/java/com/genymobile/scrcpy/control/PositionMapper.java
位置映射器
几个重要方面会影响捕获和编码系统的性能
maxSize 来设置尺寸限制,以减轻编码负担Size.limit() 方法可在保留宽高比的同时确保尺寸限制max-fps-to-encoder 选项来控制帧率OpenGLRunner 创建一个单独的处理线程,以实现高效变换捕获系统包含处理捕获源更改的机制
来源:server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java14-27 server/src/main/java/com/genymobile/scrcpy/video/CaptureReset.java
失效流程
invalidate() 方法会被调用CaptureReset 会中断当前的编码会话这个系统确保了设备显示屏的变化能够快速反映在视频流中,而无需完全重新启动捕获过程。