# 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 #include 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(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 #include #include 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& 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 的端到端延迟**,满足实时显示协作的需求。