Files
DisplayFlow/docs/IMPLEMENTATION.md

900 lines
33 KiB
Markdown
Raw Normal View History

2025-12-11 22:58:25 +08:00
# 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 的端到端延迟**,满足实时显示协作的需求。