搭建代码框架并更新文档
This commit is contained in:
85
docs/API.md
Normal file
85
docs/API.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# DisplayFlow API 文档
|
||||
|
||||
本文档描述 DisplayFlow 的核心 API。
|
||||
|
||||
## 1. 核心 API
|
||||
|
||||
### 1.1 会话管理
|
||||
|
||||
```cpp
|
||||
class SessionManager {
|
||||
public:
|
||||
std::shared_ptr<Session> CreateSession(RoleType role);
|
||||
void DestroySession(SessionId sessionId);
|
||||
std::shared_ptr<Session> GetSession(SessionId sessionId);
|
||||
};
|
||||
```
|
||||
|
||||
### 1.2 角色管理
|
||||
|
||||
```cpp
|
||||
class RoleManager {
|
||||
public:
|
||||
bool RegisterRole(RoleType type, std::shared_ptr<IRole> role);
|
||||
bool SwitchRole(SessionId sessionId, RoleType newRole);
|
||||
};
|
||||
```
|
||||
|
||||
### 1.3 网络管理
|
||||
|
||||
```cpp
|
||||
class NetworkManager {
|
||||
public:
|
||||
bool Initialize();
|
||||
std::vector<NetworkInterface> GetAvailableInterfaces();
|
||||
bool Connect(const NetworkInterface& interface);
|
||||
void Disconnect();
|
||||
};
|
||||
```
|
||||
|
||||
## 2. 平台 API
|
||||
|
||||
### 2.1 Android
|
||||
|
||||
```cpp
|
||||
class AndroidPlatformAdapter {
|
||||
public:
|
||||
bool Initialize();
|
||||
bool StartScreenCapture();
|
||||
void StopScreenCapture();
|
||||
void SetFrameCallback(FrameCallback callback);
|
||||
};
|
||||
```
|
||||
|
||||
### 2.2 Windows
|
||||
|
||||
```cpp
|
||||
class WindowsPlatformAdapter {
|
||||
public:
|
||||
bool Initialize();
|
||||
bool CreateVirtualDisplay(const Resolution& resolution);
|
||||
void DestroyVirtualDisplay();
|
||||
bool RenderFrame(const VideoFrame& frame);
|
||||
};
|
||||
```
|
||||
|
||||
## 3. 使用示例
|
||||
|
||||
### 3.1 创建 Host 会话
|
||||
|
||||
```cpp
|
||||
auto sessionManager = std::make_shared<SessionManager>();
|
||||
auto session = sessionManager->CreateSession(RoleType::Host);
|
||||
auto hostRole = std::make_shared<HostRole>();
|
||||
hostRole->Start(session);
|
||||
```
|
||||
|
||||
### 3.2 创建 Client 会话
|
||||
|
||||
```cpp
|
||||
auto sessionManager = std::make_shared<SessionManager>();
|
||||
auto session = sessionManager->CreateSession(RoleType::Client);
|
||||
auto clientRole = std::make_shared<ClientRole>();
|
||||
clientRole->Start(session);
|
||||
```
|
||||
|
||||
212
docs/ARCHITECTURE.md
Normal file
212
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# DisplayFlow 架构设计文档
|
||||
|
||||
本文档详细描述 DisplayFlow 系统的架构设计。
|
||||
|
||||
## 1. 整体架构
|
||||
|
||||
DisplayFlow 采用分层架构设计,从下到上分为:
|
||||
|
||||
1. **传输层(Transport Layer)**:物理网络连接
|
||||
- 局域网连接(USB RNDIS、Wi-Fi、以太网)
|
||||
- 互联网连接(通过 STUN/TURN/ICE)
|
||||
2. **核心层(Core Layer)**:跨平台业务逻辑
|
||||
3. **平台适配层(Platform Adapter Layer)**:平台特定实现
|
||||
4. **角色层(Role Layer)**:业务角色实现
|
||||
5. **应用层(Application Layer)**:用户界面和应用逻辑
|
||||
|
||||
**网络连接方式:**
|
||||
- **局域网直连**:优先使用,延迟最低(< 5ms)
|
||||
- **公网直连**:通过 STUN 发现,中等延迟(10-50ms)
|
||||
- **TURN 中继**:当直连失败时使用,延迟较高(50-200ms)但最可靠
|
||||
|
||||
## 2. 核心层设计
|
||||
|
||||
### 2.1 网络抽象层
|
||||
|
||||
提供统一的网络接口,屏蔽底层网络差异。
|
||||
|
||||
**NAT 穿透模块:**
|
||||
- **STUN 客户端**:用于 NAT 类型检测和获取公网 IP 地址
|
||||
- **TURN 客户端**:当直连失败时,通过 TURN 服务器中继流量
|
||||
- **ICE 代理**:实现 ICE (Interactive Connectivity Establishment) 协议
|
||||
- 收集候选地址(本地、服务器反射、中继)
|
||||
- 执行连接检查
|
||||
- 选择最佳连接路径
|
||||
- **连接建立**:自动尝试多种连接方式,选择最优路径
|
||||
|
||||
### 2.2 协议层
|
||||
|
||||
基于 FlatBuffers 的跨平台序列化协议。
|
||||
|
||||
### 2.3 会话管理层
|
||||
|
||||
管理显示会话的生命周期。
|
||||
|
||||
### 2.4 编解码抽象层
|
||||
|
||||
统一的编解码器接口,支持多种编解码器。
|
||||
|
||||
### 2.5 文件传输模块
|
||||
|
||||
提供设备间文件传输功能:
|
||||
- 文件传输协议定义
|
||||
- 断点续传机制
|
||||
- 多文件并发传输
|
||||
- 传输进度跟踪
|
||||
- 文件完整性校验
|
||||
|
||||
## 3. 平台适配层设计
|
||||
|
||||
### 3.1 Android 适配层
|
||||
|
||||
- 屏幕捕获:MediaProjection API
|
||||
- 摄像头捕获:Camera2 API / NDK Camera API
|
||||
- 输入处理:触摸事件
|
||||
- 网络:USB RNDIS、Wi-Fi
|
||||
|
||||
### 3.2 Windows 适配层
|
||||
|
||||
- 虚拟显示器:IddCx 驱动框架
|
||||
- 摄像头捕获:DirectShow / Media Foundation
|
||||
- 渲染:DirectX
|
||||
- 输入处理:键盘、鼠标
|
||||
- 网络:USB RNDIS、Wi-Fi、以太网
|
||||
|
||||
## 4. 角色层设计
|
||||
|
||||
### 4.1 Host 角色
|
||||
|
||||
负责屏幕捕获、编码、发送。
|
||||
|
||||
### 4.2 Client 角色
|
||||
|
||||
负责接收、解码、渲染。
|
||||
|
||||
### 4.3 Peer 角色
|
||||
|
||||
同时运行 Host 和 Client 逻辑。
|
||||
|
||||
## 5. 数据流
|
||||
|
||||
### 5.1 Host -> Client 数据流
|
||||
|
||||
1. 平台层捕获屏幕帧
|
||||
2. 编码器编码视频帧
|
||||
3. 协议层序列化
|
||||
4. 网络层发送
|
||||
5. 客户端网络层接收
|
||||
6. 协议层反序列化
|
||||
7. 解码器解码
|
||||
8. 平台层渲染
|
||||
|
||||
### 5.2 Client -> Host 输入流
|
||||
|
||||
1. 平台层捕获输入事件
|
||||
2. 协议层序列化
|
||||
3. 网络层发送
|
||||
4. 主机网络层接收
|
||||
5. 协议层反序列化
|
||||
6. 平台层注入输入事件
|
||||
|
||||
### 5.3 文件传输流
|
||||
|
||||
1. 发送端选择文件
|
||||
2. 文件分块(Chunk)
|
||||
3. 协议层封装文件块
|
||||
4. 网络层发送(可靠传输)
|
||||
5. 接收端接收并重组
|
||||
6. 文件完整性校验
|
||||
7. 保存到目标位置
|
||||
|
||||
### 5.4 远程摄像头流
|
||||
|
||||
1. Host 端枚举摄像头设备
|
||||
2. 选择并启动摄像头
|
||||
3. 摄像头捕获视频帧
|
||||
4. 编码器编码(复用屏幕共享编码器)
|
||||
5. 协议层序列化
|
||||
6. 网络层发送
|
||||
7. Client 端接收、解码、渲染
|
||||
8. 显示摄像头画面(可叠加在屏幕共享上)
|
||||
|
||||
## 6. 扩展功能设计
|
||||
|
||||
### 6.1 文件传输模块
|
||||
|
||||
**架构设计:**
|
||||
- 复用现有的网络抽象层和协议层
|
||||
- 使用可靠传输协议(TCP 或可靠 UDP)
|
||||
- 支持断点续传,记录传输状态
|
||||
- 多文件队列管理,支持优先级
|
||||
|
||||
**协议设计:**
|
||||
- 文件元数据(文件名、大小、校验和)
|
||||
- 文件块(Chunk)传输
|
||||
- 传输控制消息(开始、暂停、恢复、取消)
|
||||
- 进度报告机制
|
||||
|
||||
### 6.2 远程摄像头模块
|
||||
|
||||
**架构设计:**
|
||||
- 复用现有的编码和传输机制
|
||||
- 摄像头作为新的视频源类型
|
||||
- 支持与屏幕共享同时进行(多路视频流)
|
||||
- 摄像头参数控制(分辨率、帧率、对焦、曝光等)
|
||||
|
||||
**实现方式:**
|
||||
- Android:使用 Camera2 API 或 NDK Camera API
|
||||
- Windows:使用 DirectShow 或 Media Foundation
|
||||
- 视频流复用屏幕共享的编码和传输通道
|
||||
- 在协议中区分视频源类型(屏幕/摄像头)
|
||||
|
||||
### 6.3 NAT 穿透模块(TURN/STUN)
|
||||
|
||||
**架构设计:**
|
||||
- 集成到网络抽象层,对上层透明
|
||||
- 支持局域网、公网直连、TURN 中继三种连接方式
|
||||
- 自动选择最佳连接路径
|
||||
- 支持自定义 STUN/TURN 服务器配置
|
||||
|
||||
**连接建立流程:**
|
||||
|
||||
1. **候选地址收集**
|
||||
- 收集本地网络接口地址
|
||||
- 通过 STUN 服务器获取服务器反射地址
|
||||
- 通过 TURN 服务器获取中继地址
|
||||
|
||||
2. **ICE 连接检查**
|
||||
- 向所有候选地址发送连接检查
|
||||
- 等待对端响应
|
||||
- 测量延迟和带宽
|
||||
|
||||
3. **路径选择**
|
||||
- 优先选择局域网直连(延迟最低)
|
||||
- 其次选择公网直连(通过 STUN)
|
||||
- 最后选择 TURN 中继(最可靠但延迟较高)
|
||||
|
||||
4. **连接建立**
|
||||
- 使用选定的路径建立连接
|
||||
- 开始数据传输
|
||||
|
||||
**协议支持:**
|
||||
- STUN (Session Traversal Utilities for NAT) - RFC 5389
|
||||
- TURN (Traversal Using Relays around NAT) - RFC 5766
|
||||
- ICE (Interactive Connectivity Establishment) - RFC 8445
|
||||
|
||||
**配置选项:**
|
||||
- STUN 服务器地址和端口
|
||||
- TURN 服务器地址、端口、用户名、密码
|
||||
- ICE 连接超时时间
|
||||
- 候选地址收集超时时间
|
||||
|
||||
**使用场景:**
|
||||
1. **局域网场景**:自动检测并使用局域网直连(最快)
|
||||
2. **互联网场景**:通过 STUN/TURN 服务器建立连接
|
||||
3. **混合场景**:优先尝试局域网,失败后自动切换到互联网连接
|
||||
|
||||
**集成方式:**
|
||||
- NAT 穿透模块集成到网络抽象层
|
||||
- 对上层应用完全透明
|
||||
- 自动选择最佳连接路径
|
||||
- 支持连接方式动态切换
|
||||
|
||||
899
docs/IMPLEMENTATION.md
Normal file
899
docs/IMPLEMENTATION.md
Normal file
@@ -0,0 +1,899 @@
|
||||
# DisplayFlow 实现原理与视频流向
|
||||
|
||||
本文档详细说明 Windows 和 Android 平台的实现原理,以及视频数据在系统中的流向。
|
||||
|
||||
## 0. 技术栈说明
|
||||
|
||||
**核心原则:纯 C++ 实现**
|
||||
|
||||
DisplayFlow 的核心业务逻辑完全由 C++ 实现,包括:
|
||||
- 网络抽象层
|
||||
- 协议处理(FlatBuffers)
|
||||
- 会话管理
|
||||
- 编解码器抽象
|
||||
- 角色管理
|
||||
|
||||
**平台适配层:**
|
||||
|
||||
- **Windows**:完全使用 C++ 和 Windows API(DirectX、IddCx、Win32 API)
|
||||
- **Android**:**完全使用 C++ 和 Android NDK Native API**
|
||||
- 使用 `AMediaCodec`(NDK MediaCodec API)进行硬件编码
|
||||
- 使用 `AImageReader`(NDK ImageReader API)获取屏幕帧
|
||||
- 使用 `ANativeWindow` 和 `AHardwareBuffer` 进行 GPU 缓冲区操作
|
||||
- **无需 JNI 调用 Java API**,完全在 native 层实现
|
||||
- 仅权限请求需要最小 Java 层(可选,可通过系统服务实现)
|
||||
|
||||
**架构优势:**
|
||||
- 核心代码 100% 跨平台,可复用
|
||||
- 平台特定代码隔离在适配层
|
||||
- **Android 平台完全 native 实现,无 JNI 开销**
|
||||
- 性能最优,直接调用系统底层 API
|
||||
|
||||
## 1. 整体架构概览
|
||||
|
||||
DisplayFlow 采用分层架构,数据流从平台层捕获开始,经过核心层处理,最终在目标平台渲染显示。
|
||||
|
||||
```
|
||||
[平台层捕获] -> [编码] -> [协议封装] -> [网络传输] -> [协议解析] -> [解码] -> [平台层渲染]
|
||||
```
|
||||
|
||||
**Android 平台架构:**
|
||||
```
|
||||
C++ 核心层 (纯 C++)
|
||||
↑↓ 直接调用
|
||||
C++ 平台适配层 (AMediaCodec, AImageReader, ANativeWindow)
|
||||
↑↓
|
||||
Android NDK Native API
|
||||
↑↓
|
||||
Android 系统底层 (硬件编解码器, GPU)
|
||||
```
|
||||
|
||||
**注意:**
|
||||
- MediaProjection 权限请求可能需要一个最小的 Java Activity(一次性操作)
|
||||
- 但实际的屏幕捕获、编码、网络传输等所有核心功能**完全在 C++ native 层实现**
|
||||
- 使用 Android NDK Native API(AMediaCodec、AImageReader、ANativeWindow 等)
|
||||
- **无需 JNI 调用 Java API**,性能最优,代码完全跨平台兼容
|
||||
|
||||
**Windows 平台架构:**
|
||||
```
|
||||
C++ 核心层 (纯 C++)
|
||||
↑↓ 直接调用
|
||||
C++ 平台适配层 (DirectX, IddCx, Win32)
|
||||
↑↓
|
||||
Windows 系统 API
|
||||
```
|
||||
|
||||
## 2. Android 平台实现原理
|
||||
|
||||
### 2.1 屏幕捕获(Host 角色)
|
||||
|
||||
#### 使用 Android NDK Native API(纯 C++ 实现)
|
||||
|
||||
Android 平台使用 **Android NDK Native API** 进行屏幕捕获,完全在 C++ 层实现,无需 JNI 调用 Java API。
|
||||
|
||||
**使用的 NDK API:**
|
||||
- `AImageReader` - 获取屏幕帧(Android API 24+)
|
||||
- `ANativeWindow` - 窗口和 Surface 操作
|
||||
- `AHardwareBuffer` - 硬件缓冲区访问(Android API 26+)
|
||||
- `AMediaCodec` - 硬件视频编码(见 2.2 节)
|
||||
|
||||
**实现流程:**
|
||||
|
||||
1. **创建 AImageReader(纯 C++)**
|
||||
```cpp
|
||||
// platforms/android/src/capture/screen_capture.cpp
|
||||
#include <media/NdkImageReader.h>
|
||||
#include <android/native_window.h>
|
||||
|
||||
class ScreenCapture {
|
||||
public:
|
||||
bool Initialize(int width, int height) {
|
||||
// 创建 AImageReader
|
||||
AImageReader* imageReader = nullptr;
|
||||
media_status_t status = AImageReader_new(
|
||||
width, height,
|
||||
AIMAGE_FORMAT_PRIVATE, // 硬件支持的格式,零拷贝
|
||||
2, // maxImages
|
||||
&imageReader
|
||||
);
|
||||
|
||||
if (status != AMEDIA_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
imageReader_ = imageReader;
|
||||
|
||||
// 设置图像可用回调
|
||||
AImageReader_ImageListener listener = {
|
||||
.context = this,
|
||||
.onImageAvailable = OnImageAvailable
|
||||
};
|
||||
AImageReader_setImageListener(imageReader_, &listener);
|
||||
|
||||
// 获取 ANativeWindow(用于 VirtualDisplay)
|
||||
ANativeWindow* window = nullptr;
|
||||
status = AImageReader_getWindow(imageReader_, &window);
|
||||
if (status != AMEDIA_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nativeWindow_ = window;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 图像可用回调(由系统调用)
|
||||
static void OnImageAvailable(void* context, AImageReader* reader) {
|
||||
auto* capture = static_cast<ScreenCapture*>(context);
|
||||
capture->ProcessFrame(reader);
|
||||
}
|
||||
|
||||
private:
|
||||
void ProcessFrame(AImageReader* reader) {
|
||||
AImage* image = nullptr;
|
||||
media_status_t status = AImageReader_acquireLatestImage(reader, &image);
|
||||
|
||||
if (status == AMEDIA_OK && image != nullptr) {
|
||||
// 获取 AHardwareBuffer(零拷贝访问)
|
||||
AHardwareBuffer* buffer = nullptr;
|
||||
status = AImage_getHardwareBuffer(image, &buffer);
|
||||
|
||||
if (status == AMEDIA_OK && buffer != nullptr) {
|
||||
// 直接传递给编码器,无需 CPU 拷贝
|
||||
OnFrameAvailable(buffer);
|
||||
}
|
||||
|
||||
// 释放图像
|
||||
AImage_delete(image);
|
||||
}
|
||||
}
|
||||
|
||||
AImageReader* imageReader_ = nullptr;
|
||||
ANativeWindow* nativeWindow_ = nullptr;
|
||||
};
|
||||
```
|
||||
|
||||
2. **创建 VirtualDisplay(需要 MediaProjection token)**
|
||||
|
||||
注意:VirtualDisplay 的创建需要 MediaProjection 权限。有两种方案:
|
||||
|
||||
**方案 A:最小 Java 层(仅权限请求)**
|
||||
```cpp
|
||||
// 通过 JNI 获取 MediaProjection token(一次性操作)
|
||||
// 然后使用 native API 创建 VirtualDisplay
|
||||
|
||||
// 获取 MediaProjection token 后,转换为 native handle
|
||||
jobject mediaProjectionToken = GetMediaProjectionToken(); // JNI 调用(仅一次)
|
||||
|
||||
// 使用 native API 创建 VirtualDisplay
|
||||
// 注意:Android NDK 没有直接提供 VirtualDisplay API
|
||||
// 需要通过系统服务或使用 AImageReader + Surface
|
||||
```
|
||||
|
||||
**方案 B:使用系统服务(完全 native)**
|
||||
```cpp
|
||||
// 通过 Android 系统服务直接创建 VirtualDisplay
|
||||
// 使用 AIDL 接口或直接调用系统服务
|
||||
// 这需要系统权限或 root 权限
|
||||
```
|
||||
|
||||
3. **零拷贝帧获取**
|
||||
```cpp
|
||||
// 使用 AHardwareBuffer 直接访问 GPU 缓冲区
|
||||
void ProcessHardwareBuffer(AHardwareBuffer* buffer) {
|
||||
// 获取缓冲区描述
|
||||
AHardwareBuffer_Desc desc;
|
||||
AHardwareBuffer_describe(buffer, &desc);
|
||||
|
||||
// 直接传递给编码器的 Surface
|
||||
// 编码器可以直接从 GPU 缓冲区读取,无需 CPU 拷贝
|
||||
ANativeWindow* encoderSurface = GetEncoderInputSurface();
|
||||
|
||||
// 将硬件缓冲区内容复制到编码器 Surface
|
||||
// 这仍然在 GPU 上完成,零拷贝
|
||||
CopyHardwareBufferToSurface(buffer, encoderSurface);
|
||||
}
|
||||
```
|
||||
|
||||
**技术要点:**
|
||||
- **完全 native 实现**,无 JNI 开销
|
||||
- 使用 `AIMAGE_FORMAT_PRIVATE` 格式,直接传递给硬件编码器,零拷贝
|
||||
- 通过 `AHardwareBuffer` 直接访问 GPU 缓冲区
|
||||
- 异步回调机制,不阻塞主线程
|
||||
- 支持动态分辨率调整
|
||||
|
||||
**权限处理:**
|
||||
- MediaProjection 权限请求需要用户交互(一次性)
|
||||
- 可以通过最小 Java Activity 或系统服务实现
|
||||
- 权限获取后,所有后续操作完全在 native 层
|
||||
|
||||
### 2.2 视频编码
|
||||
|
||||
#### AMediaCodec 硬件编码(纯 C++ Native API)
|
||||
|
||||
Android 使用 **AMediaCodec**(Android NDK Native API)进行硬件加速编码,完全在 C++ 层实现,支持 H.264 编码。
|
||||
|
||||
**编码流程:**
|
||||
|
||||
1. **创建编码器(纯 C++)**
|
||||
```cpp
|
||||
// platforms/android/src/codec/android_h264_encoder.cpp
|
||||
#include <media/NdkMediaCodec.h>
|
||||
#include <media/NdkMediaFormat.h>
|
||||
#include <android/native_window.h>
|
||||
|
||||
class AndroidH264Encoder {
|
||||
public:
|
||||
bool Initialize(int width, int height, int bitrate, int fps) {
|
||||
// 创建编码器(使用 native API)
|
||||
const char* mimeType = "video/avc";
|
||||
encoder_ = AMediaCodec_createEncoderByType(mimeType);
|
||||
|
||||
if (!encoder_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建 MediaFormat
|
||||
AMediaFormat* format = AMediaFormat_new();
|
||||
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mimeType);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);
|
||||
|
||||
// COLOR_FormatSurface - 使用 Surface 作为输入
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, 0x7f000789);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, bitrate);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, fps);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 1);
|
||||
|
||||
// 配置编码器
|
||||
media_status_t status = AMediaCodec_configure(
|
||||
encoder_,
|
||||
format,
|
||||
nullptr, // surface (输入 Surface)
|
||||
nullptr, // crypto
|
||||
AMEDIACODEC_CONFIGURE_FLAG_ENCODE
|
||||
);
|
||||
|
||||
AMediaFormat_delete(format);
|
||||
|
||||
if (status != AMEDIA_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取输入 Surface(用于接收屏幕帧)
|
||||
status = AMediaCodec_createInputSurface(encoder_, &inputSurface_);
|
||||
if (status != AMEDIA_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 启动编码器
|
||||
status = AMediaCodec_start(encoder_);
|
||||
return status == AMEDIA_OK;
|
||||
}
|
||||
|
||||
ANativeWindow* GetInputSurface() const {
|
||||
return inputSurface_;
|
||||
}
|
||||
|
||||
private:
|
||||
AMediaCodec* encoder_ = nullptr;
|
||||
ANativeWindow* inputSurface_ = nullptr;
|
||||
};
|
||||
```
|
||||
|
||||
2. **输入 Surface 连接(零拷贝)**
|
||||
```cpp
|
||||
// 将 AImageReader 的 Surface 直接连接到编码器
|
||||
// 实现零拷贝:GPU -> 编码器
|
||||
void ConnectSurfaces(ANativeWindow* imageReaderSurface, ANativeWindow* encoderSurface) {
|
||||
// 在创建 VirtualDisplay 时,直接使用编码器的 Surface
|
||||
// 这样屏幕内容直接输出到编码器,无需中间拷贝
|
||||
|
||||
// 或者使用 EGL 将 AImageReader 的输出复制到编码器 Surface
|
||||
// 这仍然在 GPU 上完成,零拷贝
|
||||
}
|
||||
```
|
||||
|
||||
3. **获取编码数据(纯 C++)**
|
||||
```cpp
|
||||
bool GetEncodedFrame(std::vector<uint8_t>& outputData, int& flags) {
|
||||
// 出队输出缓冲区
|
||||
ssize_t outputBufferId = AMediaCodec_dequeueOutputBuffer(
|
||||
encoder_,
|
||||
&bufferInfo_,
|
||||
10000 // timeoutUs: 10ms
|
||||
);
|
||||
|
||||
if (outputBufferId >= 0) {
|
||||
// 获取输出缓冲区
|
||||
size_t outputSize = 0;
|
||||
uint8_t* outputBuffer = AMediaCodec_getOutputBuffer(
|
||||
encoder_,
|
||||
outputBufferId,
|
||||
&outputSize
|
||||
);
|
||||
|
||||
if (outputBuffer && bufferInfo_.size > 0) {
|
||||
// 复制编码数据
|
||||
outputData.assign(
|
||||
outputBuffer + bufferInfo_.offset,
|
||||
outputBuffer + bufferInfo_.offset + bufferInfo_.size
|
||||
);
|
||||
|
||||
flags = bufferInfo_.flags;
|
||||
|
||||
// 释放缓冲区
|
||||
AMediaCodec_releaseOutputBuffer(encoder_, outputBufferId, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
// 输出格式改变,获取新格式
|
||||
AMediaFormat* format = AMediaCodec_getOutputFormat(encoder_);
|
||||
// 处理格式变化
|
||||
AMediaFormat_delete(format);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
AMediaCodecBufferInfo bufferInfo_ = {};
|
||||
};
|
||||
```
|
||||
|
||||
**性能优化:**
|
||||
- **完全 native 实现**,无 JNI 开销
|
||||
- 零拷贝:直接从 GPU 到编码器,无需 CPU 参与
|
||||
- 使用硬件编码器,CPU 占用极低
|
||||
- 支持关键帧(I-frame)控制,减少带宽
|
||||
- 直接内存访问,性能最优
|
||||
|
||||
### 2.3 输入处理(Client 角色)
|
||||
|
||||
当 Android 设备作为 Client 时,需要将接收到的输入事件注入到系统中。
|
||||
|
||||
**方案 A:使用 Linux Input 子系统(完全 native,需要 root)**
|
||||
|
||||
```cpp
|
||||
// platforms/android/src/input/touch_handler.cpp
|
||||
// 直接操作 Linux /dev/input/eventX 设备
|
||||
|
||||
class TouchHandler {
|
||||
public:
|
||||
bool Initialize() {
|
||||
// 查找触摸屏输入设备
|
||||
const char* inputDevice = FindTouchInputDevice();
|
||||
if (!inputDevice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 打开输入设备
|
||||
fd_ = open(inputDevice, O_RDWR);
|
||||
return fd_ >= 0;
|
||||
}
|
||||
|
||||
bool InjectTouchEvent(int action, float x, float y) {
|
||||
struct input_event ev[6];
|
||||
memset(ev, 0, sizeof(ev));
|
||||
|
||||
// 同步事件
|
||||
ev[0].type = EV_SYN;
|
||||
ev[0].code = SYN_REPORT;
|
||||
ev[0].value = 0;
|
||||
|
||||
// ABS_MT_TRACKING_ID
|
||||
ev[1].type = EV_ABS;
|
||||
ev[1].code = ABS_MT_TRACKING_ID;
|
||||
ev[1].value = (action == ACTION_DOWN) ? 0 : -1;
|
||||
|
||||
// ABS_MT_POSITION_X
|
||||
ev[2].type = EV_ABS;
|
||||
ev[2].code = ABS_MT_POSITION_X;
|
||||
ev[2].value = (int)(x * 1000); // 转换为设备坐标
|
||||
|
||||
// ABS_MT_POSITION_Y
|
||||
ev[3].type = EV_ABS;
|
||||
ev[3].code = ABS_MT_POSITION_Y;
|
||||
ev[3].value = (int)(y * 1000);
|
||||
|
||||
// ABS_MT_PRESSURE
|
||||
ev[4].type = EV_ABS;
|
||||
ev[4].code = ABS_MT_PRESSURE;
|
||||
ev[4].value = (action == ACTION_UP) ? 0 : 100;
|
||||
|
||||
// BTN_TOUCH
|
||||
ev[5].type = EV_KEY;
|
||||
ev[5].code = BTN_TOUCH;
|
||||
ev[5].value = (action == ACTION_UP) ? 0 : 1;
|
||||
|
||||
// 发送事件
|
||||
for (int i = 0; i < 6; i++) {
|
||||
write(fd_, &ev[i], sizeof(struct input_event));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd_ = -1;
|
||||
|
||||
const char* FindTouchInputDevice() {
|
||||
// 遍历 /dev/input/eventX 查找触摸屏设备
|
||||
// 通过读取 /proc/bus/input/devices 或直接测试设备
|
||||
return "/dev/input/event2"; // 示例
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**方案 B:使用 Android Input 服务(需要系统权限)**
|
||||
|
||||
```cpp
|
||||
// 通过 Android InputManagerService 注入事件
|
||||
// 需要系统权限或使用系统服务接口
|
||||
// 可以通过 AIDL 接口调用系统服务
|
||||
```
|
||||
|
||||
**方案 C:最小 JNI 层(仅输入注入)**
|
||||
|
||||
如果必须使用 Java API,可以创建一个最小的 JNI 包装器:
|
||||
|
||||
```cpp
|
||||
// 通过 JNI 调用 InputManager 或 Instrumentation
|
||||
// 但这是最后的选择,优先使用 native 方案
|
||||
```
|
||||
|
||||
**推荐方案:**
|
||||
- **优先使用方案 A**(Linux Input 子系统):完全 native,性能最优
|
||||
- 需要 root 权限或系统权限
|
||||
- 对于普通应用,可能需要用户授权或使用辅助功能服务
|
||||
|
||||
**限制:**
|
||||
- Linux Input 子系统需要 root 或系统权限
|
||||
- 普通应用可能需要通过辅助功能服务(AccessibilityService)实现
|
||||
- 某些设备可能需要特定的权限配置
|
||||
|
||||
### 2.4 网络通信
|
||||
|
||||
#### USB RNDIS
|
||||
Android 设备通过 USB 连接时,可以启用 RNDIS(Remote Network Driver Interface Specification)功能。
|
||||
|
||||
**实现方式:**
|
||||
1. 通过 USB Host API 检测 USB 连接
|
||||
2. 启用 USB 网络共享功能
|
||||
3. 获取 RNDIS 网络接口的 IP 地址
|
||||
4. 使用 UDP Socket 进行数据传输
|
||||
|
||||
#### Wi-Fi 网络
|
||||
使用标准的 Wi-Fi 网络进行通信。
|
||||
|
||||
**实现方式:**
|
||||
1. 获取 Wi-Fi 网络信息
|
||||
2. 使用 UDP 多播或单播进行数据传输
|
||||
3. 支持一对多广播
|
||||
|
||||
## 3. Windows 平台实现原理
|
||||
|
||||
### 3.1 虚拟显示器创建(Client 角色)
|
||||
|
||||
#### IddCx 驱动框架
|
||||
Windows 10 1809+ 提供了 IddCx(Indirect Display Driver Class eXtension)框架,用于创建虚拟显示器。
|
||||
|
||||
**实现流程:**
|
||||
|
||||
1. **驱动安装**
|
||||
- 开发 IddCx 驱动(.inf 文件)
|
||||
- 使用 `devcon` 或 `pnputil` 安装驱动
|
||||
- 驱动注册虚拟显示器设备
|
||||
|
||||
2. **创建虚拟显示器**
|
||||
```cpp
|
||||
// 通过 IOCTL 与驱动通信
|
||||
HANDLE hDevice = CreateFile(
|
||||
L"\\\\.\\DisplayFlow",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, NULL, OPEN_EXISTING, 0, NULL
|
||||
);
|
||||
|
||||
// 发送创建显示器命令
|
||||
DISPLAYFLOW_CREATE_DISPLAY createCmd = {};
|
||||
createCmd.width = 1920;
|
||||
createCmd.height = 1080;
|
||||
DWORD bytesReturned;
|
||||
DeviceIoControl(
|
||||
hDevice,
|
||||
IOCTL_DISPLAYFLOW_CREATE_DISPLAY,
|
||||
&createCmd, sizeof(createCmd),
|
||||
NULL, 0,
|
||||
&bytesReturned, NULL
|
||||
);
|
||||
```
|
||||
|
||||
3. **驱动端处理**
|
||||
```cpp
|
||||
// 在驱动中实现 IddCxMonitorCreate
|
||||
NTSTATUS IddCxMonitorCreate(
|
||||
IDDCX_MONITOR Monitor,
|
||||
const IDARG_IN_MONITORCREATE* pInArgs,
|
||||
IDARG_OUT_MONITORCREATE* pOutArgs
|
||||
) {
|
||||
// 创建虚拟显示器对象
|
||||
// 注册显示模式
|
||||
// 返回成功
|
||||
}
|
||||
```
|
||||
|
||||
**技术要点:**
|
||||
- IddCx 驱动运行在内核模式,性能高
|
||||
- 创建的虚拟显示器会被系统识别为真实显示器
|
||||
- 支持动态分辨率调整
|
||||
- 支持多显示器扩展
|
||||
|
||||
### 3.2 DirectX 渲染
|
||||
|
||||
#### D3D11 渲染管道
|
||||
Windows 平台使用 DirectX 11 进行高效渲染。
|
||||
|
||||
**渲染流程:**
|
||||
|
||||
1. **初始化 D3D11**
|
||||
```cpp
|
||||
// 创建 D3D11 设备
|
||||
D3D11CreateDevice(
|
||||
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
|
||||
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
|
||||
featureLevels, ARRAYSIZE(featureLevels),
|
||||
D3D11_SDK_VERSION, &device, &featureLevel, &context
|
||||
);
|
||||
|
||||
// 创建交换链(连接到虚拟显示器)
|
||||
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
|
||||
swapChainDesc.BufferDesc.Width = width;
|
||||
swapChainDesc.BufferDesc.Height = height;
|
||||
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
swapChainDesc.BufferCount = 2;
|
||||
swapChainDesc.SampleDesc.Count = 1;
|
||||
swapChainDesc.Windowed = TRUE;
|
||||
swapChainDesc.OutputWindow = hwnd; // 虚拟显示器的窗口句柄
|
||||
|
||||
IDXGIFactory* factory;
|
||||
CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
|
||||
factory->CreateSwapChain(device, &swapChainDesc, &swapChain);
|
||||
```
|
||||
|
||||
2. **视频解码**
|
||||
```cpp
|
||||
// 使用 Media Foundation 或 D3D11 Video Decoder
|
||||
// 创建视频解码器
|
||||
IMFTransform* decoder = nullptr;
|
||||
MFCreateVideoDecoderActivate(...);
|
||||
|
||||
// 解码 H.264 数据
|
||||
decoder->ProcessInput(0, sample, 0);
|
||||
decoder->ProcessOutput(0, &outputSample, &status);
|
||||
```
|
||||
|
||||
3. **渲染到纹理**
|
||||
```cpp
|
||||
// 创建渲染目标纹理
|
||||
ID3D11Texture2D* renderTarget;
|
||||
device->CreateTexture2D(&textureDesc, nullptr, &renderTarget);
|
||||
|
||||
// 创建渲染目标视图
|
||||
ID3D11RenderTargetView* rtv;
|
||||
device->CreateRenderTargetView(renderTarget, nullptr, &rtv);
|
||||
|
||||
// 将解码后的帧复制到纹理
|
||||
context->CopyResource(renderTarget, decodedTexture);
|
||||
|
||||
// 渲染到交换链
|
||||
context->OMSetRenderTargets(1, &rtv, nullptr);
|
||||
swapChain->Present(0, 0);
|
||||
```
|
||||
|
||||
**性能优化:**
|
||||
- 使用硬件视频解码(D3D11 Video Decoder)
|
||||
- GPU 零拷贝:解码直接输出到 GPU 纹理
|
||||
- 双缓冲交换链,减少撕裂
|
||||
- 支持垂直同步(VSync)
|
||||
|
||||
### 3.3 输入处理(Client 角色)
|
||||
|
||||
Windows 作为 Client 时,需要将输入事件转发到 Host 设备。
|
||||
|
||||
**键盘事件捕获:**
|
||||
```cpp
|
||||
// 使用低级键盘钩子
|
||||
HHOOK hHook = SetWindowsHookEx(
|
||||
WH_KEYBOARD_LL,
|
||||
LowLevelKeyboardProc,
|
||||
GetModuleHandle(NULL), 0
|
||||
);
|
||||
|
||||
LRESULT CALLBACK LowLevelKeyboardProc(
|
||||
int nCode, WPARAM wParam, LPARAM lParam
|
||||
) {
|
||||
KBDLLHOOKSTRUCT* kbd = (KBDLLHOOKSTRUCT*)lParam;
|
||||
// 发送键盘事件到 Host
|
||||
sendKeyboardEvent(kbd->vkCode, wParam == WM_KEYDOWN);
|
||||
return CallNextHookEx(hHook, nCode, wParam, lParam);
|
||||
}
|
||||
```
|
||||
|
||||
**鼠标事件捕获:**
|
||||
```cpp
|
||||
// 使用低级鼠标钩子
|
||||
HHOOK hHook = SetWindowsHookEx(
|
||||
WH_MOUSE_LL,
|
||||
LowLevelMouseProc,
|
||||
GetModuleHandle(NULL), 0
|
||||
);
|
||||
|
||||
LRESULT CALLBACK LowLevelMouseProc(
|
||||
int nCode, WPARAM wParam, LPARAM lParam
|
||||
) {
|
||||
MSLLHOOKSTRUCT* mouse = (MSLLHOOKSTRUCT*)lParam;
|
||||
// 发送鼠标事件到 Host
|
||||
sendMouseEvent(mouse->pt.x, mouse->pt.y, wParam);
|
||||
return CallNextHookEx(hHook, nCode, wParam, lParam);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 网络通信
|
||||
|
||||
#### USB RNDIS 检测
|
||||
Windows 检测 USB RNDIS 网络适配器。
|
||||
|
||||
**实现方式:**
|
||||
```cpp
|
||||
// 使用 WMI 查询网络适配器
|
||||
IWbemLocator* locator;
|
||||
CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_IWbemLocator, (LPVOID*)&locator);
|
||||
|
||||
// 查询 RNDIS 适配器
|
||||
IEnumWbemClassObject* enumerator;
|
||||
locator->ExecQuery(
|
||||
L"WQL",
|
||||
L"SELECT * FROM Win32_NetworkAdapter WHERE Description LIKE '%RNDIS%'",
|
||||
WBEM_FLAG_FORWARD_ONLY, NULL, &enumerator
|
||||
);
|
||||
|
||||
// 获取 IP 地址
|
||||
// 使用 GetAdaptersAddresses API
|
||||
```
|
||||
|
||||
#### Wi-Fi 和以太网
|
||||
使用标准的 Windows Socket API 进行网络通信。
|
||||
|
||||
## 4. 视频流向详解
|
||||
|
||||
### 4.1 Host -> Client 完整数据流
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Android Host 设备 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [1] 屏幕捕获层 │
|
||||
│ MediaProjection -> VirtualDisplay -> ImageReader │
|
||||
│ ↓ (GPU 纹理,零拷贝) │
|
||||
│ │
|
||||
│ [2] 编码层 │
|
||||
│ MediaCodec (H.264 硬件编码) │
|
||||
│ ↓ (编码后的 H.264 NAL 单元) │
|
||||
│ │
|
||||
│ [3] 协议封装层 │
|
||||
│ FlatBuffers 序列化 │
|
||||
│ - 帧头信息(时间戳、分辨率、帧类型) │
|
||||
│ - 编码数据(NAL 单元) │
|
||||
│ ↓ (序列化的协议消息) │
|
||||
│ │
|
||||
│ [4] 网络传输层 │
|
||||
│ UDP Socket (USB RNDIS / Wi-Fi) │
|
||||
│ - 分包处理(MTU 限制) │
|
||||
│ - 重传机制(关键帧) │
|
||||
│ ↓ (UDP 数据包) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ 网络传输
|
||||
↓ (延迟: ~1-5ms USB RNDIS, ~5-20ms Wi-Fi)
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Windows Client 设备 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [5] 网络接收层 │
|
||||
│ UDP Socket 接收 │
|
||||
│ - 包重组 │
|
||||
│ - 顺序保证 │
|
||||
│ ↓ (完整的协议消息) │
|
||||
│ │
|
||||
│ [6] 协议解析层 │
|
||||
│ FlatBuffers 反序列化 │
|
||||
│ - 提取帧头信息 │
|
||||
│ - 提取编码数据 │
|
||||
│ ↓ (H.264 NAL 单元) │
|
||||
│ │
|
||||
│ [7] 解码层 │
|
||||
│ D3D11 Video Decoder (硬件解码) │
|
||||
│ ↓ (GPU 纹理,零拷贝) │
|
||||
│ │
|
||||
│ [8] 渲染层 │
|
||||
│ DirectX 11 渲染 │
|
||||
│ - 纹理复制到渲染目标 │
|
||||
│ - 交换链 Present │
|
||||
│ ↓ (显示到虚拟显示器) │
|
||||
│ │
|
||||
│ [9] 显示输出 │
|
||||
│ IddCx 虚拟显示器 │
|
||||
│ - 系统识别为真实显示器 │
|
||||
│ - 支持扩展显示模式 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 各阶段延迟分析
|
||||
|
||||
| 阶段 | 操作 | 典型延迟 | 优化措施 |
|
||||
|------|------|----------|----------|
|
||||
| 1. 屏幕捕获 | MediaProjection 获取帧 | 0-2ms | 使用硬件 Surface,零拷贝 |
|
||||
| 2. 编码 | H.264 硬件编码 | 2-5ms | 硬件编码器,降低码率 |
|
||||
| 3. 协议封装 | FlatBuffers 序列化 | <1ms | 零拷贝序列化 |
|
||||
| 4. 网络传输 | UDP 传输 | 1-20ms | USB RNDIS 优先,优化网络 |
|
||||
| 5. 网络接收 | UDP 接收 | <1ms | 高效 Socket 处理 |
|
||||
| 6. 协议解析 | FlatBuffers 反序列化 | <1ms | 零拷贝反序列化 |
|
||||
| 7. 解码 | H.264 硬件解码 | 2-5ms | D3D11 Video Decoder |
|
||||
| 8. 渲染 | DirectX 渲染 | 1-2ms | GPU 渲染,双缓冲 |
|
||||
| 9. 显示 | 虚拟显示器输出 | 0-1ms | IddCx 驱动优化 |
|
||||
| **总计** | **端到端延迟** | **<30ms** | **(USB RNDIS 模式)** |
|
||||
|
||||
### 4.3 关键帧(I-frame)处理
|
||||
|
||||
为了减少带宽和延迟,系统采用以下策略:
|
||||
|
||||
1. **关键帧间隔**
|
||||
- 默认每 30 帧发送一个 I-frame
|
||||
- 可根据网络状况动态调整
|
||||
|
||||
2. **场景变化检测**
|
||||
- 检测画面变化幅度
|
||||
- 变化大时立即发送 I-frame
|
||||
|
||||
3. **网络重传**
|
||||
- I-frame 丢失时请求重传
|
||||
- P-frame 丢失时等待下一个 I-frame
|
||||
|
||||
### 4.4 自适应码率控制
|
||||
|
||||
系统根据网络状况动态调整编码参数:
|
||||
|
||||
1. **带宽检测**
|
||||
- 监控网络延迟和丢包率
|
||||
- 估算可用带宽
|
||||
|
||||
2. **码率调整**
|
||||
- 高带宽:提高码率,提升画质
|
||||
- 低带宽:降低码率,保证流畅
|
||||
|
||||
3. **分辨率调整**
|
||||
- 极端情况下降低分辨率
|
||||
- 保持帧率稳定
|
||||
|
||||
## 5. 输入事件流向(Client -> Host)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Windows Client 设备 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [1] 输入捕获 │
|
||||
│ - 键盘钩子 (WH_KEYBOARD_LL) │
|
||||
│ - 鼠标钩子 (WH_MOUSE_LL) │
|
||||
│ ↓ (输入事件数据) │
|
||||
│ │
|
||||
│ [2] 事件封装 │
|
||||
│ FlatBuffers 序列化 │
|
||||
│ - 事件类型(按键、鼠标移动、点击) │
|
||||
│ - 坐标、按键码等 │
|
||||
│ ↓ (序列化的输入事件) │
|
||||
│ │
|
||||
│ [3] 网络发送 │
|
||||
│ UDP Socket 发送 │
|
||||
│ ↓ (UDP 数据包) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ 网络传输
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Android Host 设备 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [4] 网络接收 │
|
||||
│ UDP Socket 接收 │
|
||||
│ ↓ (输入事件数据) │
|
||||
│ │
|
||||
│ [5] 事件解析 │
|
||||
│ FlatBuffers 反序列化 │
|
||||
│ ↓ (输入事件对象) │
|
||||
│ │
|
||||
│ [6] 事件注入 │
|
||||
│ - 通过 JNI 调用 Instrumentation.sendPointerSync() (触摸) │
|
||||
│ - 或通过辅助功能服务(AccessibilityService) │
|
||||
│ ↓ (系统输入事件) │
|
||||
│ │
|
||||
│ [7] 系统处理 │
|
||||
│ Android 系统处理输入事件 │
|
||||
│ - 触发应用响应 │
|
||||
│ - 更新屏幕内容 │
|
||||
│ ↓ (屏幕变化) │
|
||||
│ │
|
||||
│ [8] 屏幕捕获 │
|
||||
│ 回到视频流向的步骤 1 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**输入延迟:**
|
||||
- 捕获延迟:<1ms
|
||||
- 网络传输:1-20ms(取决于连接方式)
|
||||
- 事件注入:<1ms
|
||||
- **总延迟:<25ms**(USB RNDIS 模式)
|
||||
|
||||
## 6. 性能优化策略
|
||||
|
||||
### 6.1 零拷贝优化
|
||||
|
||||
1. **Android 端**
|
||||
- GPU 纹理直接传递给编码器
|
||||
- 编码输出直接写入网络缓冲区
|
||||
|
||||
2. **Windows 端**
|
||||
- 解码直接输出到 GPU 纹理
|
||||
- 纹理直接用于渲染,无需 CPU 拷贝
|
||||
|
||||
### 6.2 多线程处理
|
||||
|
||||
1. **捕获线程**:专门处理屏幕捕获
|
||||
2. **编码线程**:处理视频编码
|
||||
3. **网络线程**:处理网络 I/O
|
||||
4. **渲染线程**:处理视频解码和渲染
|
||||
|
||||
### 6.3 缓冲策略
|
||||
|
||||
1. **发送端缓冲**
|
||||
- 最小化缓冲,降低延迟
|
||||
- 关键帧缓冲,支持重传
|
||||
|
||||
2. **接收端缓冲**
|
||||
- 小缓冲,快速响应
|
||||
- 丢帧策略:网络差时丢弃旧帧
|
||||
|
||||
## 7. 错误处理与恢复
|
||||
|
||||
### 7.1 网络错误
|
||||
|
||||
- **连接断开**:自动重连机制
|
||||
- **数据包丢失**:关键帧重传
|
||||
- **网络切换**:自动检测并切换网络接口
|
||||
|
||||
### 7.2 编解码错误
|
||||
|
||||
- **编码失败**:降级到软件编码
|
||||
- **解码失败**:请求关键帧重传
|
||||
|
||||
### 7.3 系统错误
|
||||
|
||||
- **权限丢失**:提示用户重新授权
|
||||
- **资源不足**:降低码率或分辨率
|
||||
|
||||
## 8. 总结
|
||||
|
||||
DisplayFlow 通过以下技术实现低延迟、高质量的跨平台显示协作:
|
||||
|
||||
1. **硬件加速**:充分利用 GPU 和硬件编解码器
|
||||
2. **零拷贝**:最小化数据拷贝,降低延迟
|
||||
3. **高效协议**:FlatBuffers 零拷贝序列化
|
||||
4. **智能网络**:自动选择最佳网络路径
|
||||
5. **自适应优化**:根据网络状况动态调整参数
|
||||
|
||||
通过这些优化,系统在 USB RNDIS 模式下可实现 **<30ms 的端到端延迟**,满足实时显示协作的需求。
|
||||
|
||||
Reference in New Issue
Block a user