Files
DisplayFlow/docs/VIDEO_FLOW_WINDOWS_TO_ANDROID.md
huanglinhuan 30f45f8397 更新文档
2025-12-18 21:30:08 +08:00

529 lines
19 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.
# Windows Host → Android Client 视频流向详解
本文档详细说明 Windows 设备作为 HostAndroid 设备作为 Client 时的完整视频流向。
## 完整数据流图
```
┌─────────────────────────────────────────────────────────────────┐
│ Windows Host 设备(发送端) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [1] 屏幕捕获层 │
│ Desktop Duplication API / GDI BitBlt │
│ ↓ 输出D3D11 纹理GPU 纹理,零拷贝) │
│ │
│ [2] 编码层 │
│ Media Foundation H.264 硬件编码器 / D3D11 Video Encoder │
│ ↓ 输出H.264 NAL 单元(压缩后的视频数据) │
│ │
│ [3] 协议封装层 │
│ FlatBuffers 序列化 │
│ - 帧头信息(时间戳、分辨率、帧类型 I/P
│ - 编码数据NAL 单元) │
│ ↓ 输出:序列化的协议消息(二进制数据) │
│ │
│ [4] 网络传输层 │
│ UDP Socket (USB RNDIS / Wi-Fi / 以太网) │
│ - 分包处理MTU 限制,通常 1500 字节) │
│ - 重传机制(关键帧 I-frame
│ ↓ 输出UDP 数据包 │
│ │
└─────────────────────────────────────────────────────────────────┘
↓ 网络传输
↓ 延迟1-5ms (USB RNDIS) / 5-20ms (Wi-Fi)
┌─────────────────────────────────────────────────────────────────┐
│ Android Client 设备(接收端) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [5] 网络接收层 │
│ UDP Socket 接收 │
│ - 包重组(处理分包) │
│ - 顺序保证(处理乱序) │
│ ↓ 输出:完整的协议消息 │
│ │
│ [6] 协议解析层 │
│ FlatBuffers 反序列化 │
│ - 提取帧头信息 │
│ - 提取编码数据NAL 单元) │
│ ↓ 输出H.264 NAL 单元 │
│ │
│ [7] 解码层 │
│ AMediaCodec (H.264 硬件解码器) │
│ ↓ 输出GPU 纹理AHardwareBuffer零拷贝
│ │
│ [8] 渲染层 │
│ OpenGL ES / ANativeWindow │
│ - 纹理复制到渲染目标 │
│ - Surface 更新 │
│ ↓ 输出:渲染后的画面 │
│ │
│ [9] 显示输出 │
│ Android Surface / SurfaceView │
│ - 直接显示在 Android 屏幕上 │
│ - 支持全屏或窗口模式 │
│ ↓ 最终显示在 Android 设备屏幕上 │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## 各阶段详细说明
### Windows Host 端
#### [1] 屏幕捕获层
**技术方案Desktop Duplication API推荐**
```cpp
// platforms/windows/src/capture/screen_capture.cpp
#include <dxgi1_2.h>
#include <d3d11.h>
class ScreenCapture {
public:
bool Initialize() {
// 创建 D3D11 设备
D3D11CreateDevice(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
0, nullptr, 0, D3D11_SDK_VERSION,
&device_, nullptr, &context_
);
// 获取 DXGI 输出
IDXGIOutput* output = nullptr;
// ... 获取主显示器输出
// 创建 Desktop Duplication
IDXGIOutput1* output1 = nullptr;
output->QueryInterface(__uuidof(IDXGIOutput1), (void**)&output1);
output1->DuplicateOutput(device_, &duplication_);
return true;
}
bool CaptureFrame(ID3D11Texture2D** texture) {
DXGI_OUTDUPL_FRAME_INFO frameInfo;
IDXGIResource* resource = nullptr;
// 获取桌面帧
HRESULT hr = duplication_->AcquireNextFrame(
0, &frameInfo, &resource
);
if (SUCCEEDED(hr)) {
// 获取纹理
resource->QueryInterface(__uuidof(ID3D11Texture2D), (void**)texture);
duplication_->ReleaseFrame();
return true;
}
return false;
}
private:
ID3D11Device* device_ = nullptr;
ID3D11DeviceContext* context_ = nullptr;
IDXGIOutputDuplication* duplication_ = nullptr;
};
```
**特点:**
- 使用 Desktop Duplication APIWindows 8+
- 直接获取 GPU 纹理,零拷贝
- 支持多显示器
- 性能最优
**备选方案GDI BitBlt**
- 适用于较旧的 Windows 版本
- 需要 CPU 拷贝,性能较低
#### [2] 编码层
**技术方案Media Foundation H.264 硬件编码器**
```cpp
// platforms/windows/src/codec/windows_h264_encoder.cpp
#include <mfapi.h>
#include <mftransform.h>
#include <mferror.h>
class WindowsH264Encoder {
public:
bool Initialize(int width, int height, int bitrate, int fps) {
// 创建 Media Foundation 编码器
IMFActivate** activates = nullptr;
UINT32 count = 0;
MFTEnum(
MFT_CATEGORY_VIDEO_ENCODER,
MFT_ENUM_FLAG_HARDWARE,
&inputType, nullptr,
&activates, &count
);
// 选择 H.264 编码器
activates[0]->ActivateObject(IID_PPV_ARGS(&encoder_));
// 配置编码器
IMFMediaType* inputType = nullptr;
MFCreateMediaType(&inputType);
inputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
inputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12);
inputType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
MFSetAttributeSize(inputType, MF_MT_FRAME_SIZE, width, height);
MFSetAttributeRatio(inputType, MF_MT_FRAME_RATE, fps, 1);
IMFMediaType* outputType = nullptr;
MFCreateMediaType(&outputType);
outputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
outputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
outputType->SetUINT32(MF_MT_AVG_BITRATE, bitrate);
outputType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
encoder_->SetInputType(0, inputType, 0);
encoder_->SetOutputType(0, outputType, 0);
encoder_->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
encoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
encoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
return true;
}
bool EncodeFrame(ID3D11Texture2D* inputTexture, std::vector<uint8_t>& output) {
// 将 D3D11 纹理转换为 Media Foundation Sample
IMFSample* sample = nullptr;
// ... 转换逻辑
// 编码
encoder_->ProcessInput(0, sample, 0);
// 获取编码输出
MFT_OUTPUT_DATA_BUFFER outputBuffer = {};
DWORD status = 0;
encoder_->ProcessOutput(0, 1, &outputBuffer, &status);
// 提取编码数据
IMFMediaBuffer* buffer = nullptr;
outputBuffer.pSample->ConvertToContiguousBuffer(&buffer);
BYTE* data = nullptr;
DWORD length = 0;
buffer->Lock(&data, nullptr, &length);
output.assign(data, data + length);
buffer->Unlock();
return true;
}
private:
IMFTransform* encoder_ = nullptr;
};
```
**特点:**
- 使用 Media Foundation 硬件编码器
- 支持 H.264 编码
- 硬件加速CPU 占用低
- 支持动态码率调整
**备选方案D3D11 Video Encoder**
- 更底层的 API
- 性能可能更好,但实现更复杂
#### [3] 协议封装层
```cpp
// core/src/protocol/message_serializer.cpp
#include "displayflow/core/protocol/message_serializer.h"
class MessageSerializer {
public:
ByteArray SerializeVideoFrame(const VideoFrame& frame) {
// 使用 FlatBuffers 序列化
flatbuffers::FlatBufferBuilder builder;
auto frameData = builder.CreateVector(frame.data.data(), frame.data.size());
auto message = CreateVideoFrameMessage(
builder,
frame.timestamp,
frame.width,
frame.height,
frame.frameType, // I-frame or P-frame
frameData
);
builder.Finish(message);
return ByteArray(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
};
```
#### [4] 网络传输层
```cpp
// core/src/network/network_manager.cpp
class NetworkManager {
public:
bool SendVideoFrame(const ByteArray& data) {
// UDP Socket 发送
// 处理分包MTU 限制)
const size_t MTU = 1500;
size_t offset = 0;
while (offset < data.size()) {
size_t chunkSize = std::min(MTU, data.size() - offset);
sendto(socket_,
data.data() + offset, chunkSize,
0, (sockaddr*)&clientAddr_, sizeof(clientAddr_));
offset += chunkSize;
}
return true;
}
};
```
### Android Client 端
#### [5] 网络接收层
```cpp
// core/src/network/network_manager.cpp
class NetworkManager {
public:
bool ReceiveVideoFrame(ByteArray& data) {
// UDP Socket 接收
// 包重组和顺序保证
char buffer[MTU];
sockaddr_in fromAddr;
socklen_t addrLen = sizeof(fromAddr);
ssize_t received = recvfrom(socket_, buffer, MTU, 0,
(sockaddr*)&fromAddr, &addrLen);
if (received > 0) {
// 处理分包重组
// 保证顺序
data.insert(data.end(), buffer, buffer + received);
return true;
}
return false;
}
};
```
#### [6] 协议解析层
```cpp
// core/src/protocol/message_serializer.cpp
class MessageSerializer {
public:
bool DeserializeVideoFrame(const ByteArray& data, VideoFrame& frame) {
// FlatBuffers 反序列化
auto message = GetVideoFrameMessage(data.data());
frame.timestamp = message->timestamp();
frame.width = message->width();
frame.height = message->height();
frame.frameType = message->frameType();
auto frameData = message->data();
frame.data.assign(frameData->begin(), frameData->end());
return true;
}
};
```
#### [7] 解码层
**技术方案AMediaCodec 硬件解码器**
```cpp
// platforms/android/src/render/render_engine.cpp
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaFormat.h>
class RenderEngine {
public:
bool Initialize(int width, int height) {
// 创建解码器
const char* mimeType = "video/avc";
decoder_ = AMediaCodec_createDecoderByType(mimeType);
// 配置解码器
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);
// 获取输出 Surface用于渲染
ANativeWindow* surface = GetOutputSurface();
AMediaCodec_configure(decoder_, format, surface, nullptr, 0);
AMediaCodec_start(decoder_);
return true;
}
bool DecodeFrame(const ByteArray& h264Data) {
// 输入编码数据
ssize_t inputBufferId = AMediaCodec_dequeueInputBuffer(decoder_, 10000);
if (inputBufferId >= 0) {
size_t inputSize = 0;
uint8_t* inputBuffer = AMediaCodec_getInputBuffer(
decoder_, inputBufferId, &inputSize);
memcpy(inputBuffer, h264Data.data(), h264Data.size());
AMediaCodec_queueInputBuffer(
decoder_, inputBufferId, 0, h264Data.size(),
0, 0);
}
// 获取解码输出
AMediaCodecBufferInfo bufferInfo;
ssize_t outputBufferId = AMediaCodec_dequeueOutputBuffer(
decoder_, &bufferInfo, 10000);
if (outputBufferId >= 0) {
// 解码完成,直接渲染到 Surface零拷贝
AMediaCodec_releaseOutputBuffer(decoder_, outputBufferId, true);
return true;
}
return false;
}
private:
AMediaCodec* decoder_ = nullptr;
};
```
**特点:**
- 使用 AMediaCodec 硬件解码器
- 直接输出到 Surface零拷贝
- 硬件加速,性能最优
#### [8] 渲染层
**技术方案ANativeWindow + OpenGL ES**
```cpp
// platforms/android/src/render/render_engine.cpp
#include <android/native_window.h>
#include <EGL/egl.h>
class RenderEngine {
public:
bool Initialize(ANativeWindow* window) {
nativeWindow_ = window;
// 创建 EGL 上下文
display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display_, nullptr, nullptr);
EGLConfig config;
EGLint numConfigs;
eglChooseConfig(display_, attribs, &config, 1, &numConfigs);
surface_ = eglCreateWindowSurface(display_, config, nativeWindow_, nullptr);
context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttribs);
eglMakeCurrent(display_, surface_, surface_, context_);
return true;
}
void RenderFrame() {
// 如果使用 AMediaCodec 直接输出到 Surface这里可能不需要额外渲染
// 或者使用 OpenGL ES 进行后处理
eglSwapBuffers(display_, surface_);
}
private:
ANativeWindow* nativeWindow_ = nullptr;
EGLDisplay display_ = nullptr;
EGLSurface surface_ = nullptr;
EGLContext context_ = nullptr;
};
```
**特点:**
- 使用 ANativeWindow 作为渲染目标
- 支持 OpenGL ES 后处理
- 直接显示在 Android Surface 上
#### [9] 显示输出
**技术方案Android Surface / SurfaceView**
```cpp
// 在 Java/Kotlin 层创建 SurfaceView
// 或使用 ANativeWindow 直接显示
// 在 C++ 层获取 Surface
ANativeWindow* GetOutputSurface() {
// 从 Java 层传入 Surface转换为 ANativeWindow
// 或直接使用 ANativeWindow
return nativeWindow_;
}
```
## 延迟分析
| 阶段 | 操作 | 典型延迟 | 优化措施 |
|------|------|----------|----------|
| 1. 屏幕捕获 | Desktop Duplication API | 1-3ms | GPU 纹理,零拷贝 |
| 2. 编码 | H.264 硬件编码 | 2-5ms | Media Foundation 硬件编码器 |
| 3. 协议封装 | FlatBuffers 序列化 | <1ms | 零拷贝序列化 |
| 4. 网络传输 | UDP 传输 | 1-20ms | USB RNDIS 优先 |
| 5. 网络接收 | UDP 接收 | <1ms | 高效 Socket 处理 |
| 6. 协议解析 | FlatBuffers 反序列化 | <1ms | 零拷贝反序列化 |
| 7. 解码 | H.264 硬件解码 | 2-5ms | AMediaCodec 硬件解码器 |
| 8. 渲染 | OpenGL ES / Surface | 1-2ms | GPU 渲染 |
| 9. 显示 | Android Surface 输出 | 0-1ms | 直接 Surface 显示 |
| **总计** | **端到端延迟** | **<30ms** | **USB RNDIS 模式)** |
## 关键特性
1. **零拷贝**
- Windows 端Desktop Duplication → D3D11 纹理 → 编码器GPU
- Android 端:解码器 → SurfaceGPU无需 CPU 拷贝
2. **硬件加速**
- WindowsMedia Foundation 硬件编码器
- AndroidAMediaCodec 硬件解码器
3. **自适应码率**
- 根据网络状况动态调整编码参数
- 关键帧I-frame重传机制
4. **多显示器支持**
- Windows 端支持选择特定显示器捕获
- 支持多显示器扩展模式
## 实现优先级
1. **第一阶段**:基础功能
- [ ] Windows 屏幕捕获Desktop Duplication API
- [ ] Windows H.264 编码Media Foundation
- [ ] Android H.264 解码AMediaCodec
- [ ] Android Surface 显示
2. **第二阶段**:优化
- [ ] 零拷贝优化
- [ ] 帧率控制
- [ ] 自适应码率
3. **第三阶段**:高级功能
- [ ] 多显示器支持
- [ ] 延迟优化
- [ ] 错误恢复