Files
DisplayFlow/docs/IMPLEMENTATION.md
2025-12-11 22:58:25 +08:00

900 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DisplayFlow 实现原理与视频流向
本文档详细说明 Windows 和 Android 平台的实现原理,以及视频数据在系统中的流向。
## 0. 技术栈说明
**核心原则:纯 C++ 实现**
DisplayFlow 的核心业务逻辑完全由 C++ 实现,包括:
- 网络抽象层
- 协议处理FlatBuffers
- 会话管理
- 编解码器抽象
- 角色管理
**平台适配层:**
- **Windows**:完全使用 C++ 和 Windows APIDirectX、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 APIAMediaCodec、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 连接时,可以启用 RNDISRemote 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+ 提供了 IddCxIndirect 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 的端到端延迟**,满足实时显示协作的需求。