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

33 KiB
Raw Permalink Blame History

DisplayFlow 实现原理与视频流向

本文档详细说明 Windows 和 Android 平台的实现原理,以及视频数据在系统中的流向。

0. 技术栈说明

核心原则:纯 C++ 实现

DisplayFlow 的核心业务逻辑完全由 C++ 实现,包括:

  • 网络抽象层
  • 协议处理FlatBuffers
  • 会话管理
  • 编解码器抽象
  • 角色管理

平台适配层:

  • Windows:完全使用 C++ 和 Windows APIDirectX、IddCx、Win32 API
  • Android完全使用 C++ 和 Android NDK Native API
    • 使用 AMediaCodecNDK MediaCodec API进行硬件编码
    • 使用 AImageReaderNDK ImageReader API获取屏幕帧
    • 使用 ANativeWindowAHardwareBuffer 进行 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++

    // 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 层(仅权限请求)

    // 通过 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

    // 通过 Android 系统服务直接创建 VirtualDisplay
    // 使用 AIDL 接口或直接调用系统服务
    // 这需要系统权限或 root 权限
    
  3. 零拷贝帧获取

    // 使用 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 使用 AMediaCodecAndroid NDK Native API进行硬件加速编码完全在 C++ 层实现,支持 H.264 编码。

编码流程:

  1. 创建编码器(纯 C++

    // 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 连接(零拷贝)

    // 将 AImageReader 的 Surface 直接连接到编码器
    // 实现零拷贝GPU -> 编码器
    void ConnectSurfaces(ANativeWindow* imageReaderSurface, ANativeWindow* encoderSurface) {
        // 在创建 VirtualDisplay 时,直接使用编码器的 Surface
        // 这样屏幕内容直接输出到编码器,无需中间拷贝
    
        // 或者使用 EGL 将 AImageReader 的输出复制到编码器 Surface
        // 这仍然在 GPU 上完成,零拷贝
    }
    
  3. 获取编码数据(纯 C++

    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

// 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 服务(需要系统权限)

// 通过 Android InputManagerService 注入事件
// 需要系统权限或使用系统服务接口
// 可以通过 AIDL 接口调用系统服务

方案 C最小 JNI 层(仅输入注入)

如果必须使用 Java API可以创建一个最小的 JNI 包装器:

// 通过 JNI 调用 InputManager 或 Instrumentation
// 但这是最后的选择,优先使用 native 方案

推荐方案:

  • 优先使用方案 ALinux 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 文件)
    • 使用 devconpnputil 安装驱动
    • 驱动注册虚拟显示器设备
  2. 创建虚拟显示器

    // 通过 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. 驱动端处理

    // 在驱动中实现 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

    // 创建 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. 视频解码

    // 使用 Media Foundation 或 D3D11 Video Decoder
    // 创建视频解码器
    IMFTransform* decoder = nullptr;
    MFCreateVideoDecoderActivate(...);
    
    // 解码 H.264 数据
    decoder->ProcessInput(0, sample, 0);
    decoder->ProcessOutput(0, &outputSample, &status);
    
  3. 渲染到纹理

    // 创建渲染目标纹理
    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 设备。

键盘事件捕获:

// 使用低级键盘钩子
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);
}

鼠标事件捕获:

// 使用低级鼠标钩子
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 网络适配器。

实现方式:

// 使用 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
  • 总延迟:<25msUSB 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 的端到端延迟,满足实时显示协作的需求。