更新文档
This commit is contained in:
528
docs/VIDEO_FLOW_WINDOWS_TO_ANDROID.md
Normal file
528
docs/VIDEO_FLOW_WINDOWS_TO_ANDROID.md
Normal file
@@ -0,0 +1,528 @@
|
||||
# Windows Host → Android Client 视频流向详解
|
||||
|
||||
本文档详细说明 Windows 设备作为 Host,Android 设备作为 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 API(Windows 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 端:解码器 → Surface(GPU),无需 CPU 拷贝
|
||||
|
||||
2. **硬件加速**:
|
||||
- Windows:Media Foundation 硬件编码器
|
||||
- Android:AMediaCodec 硬件解码器
|
||||
|
||||
3. **自适应码率**:
|
||||
- 根据网络状况动态调整编码参数
|
||||
- 关键帧(I-frame)重传机制
|
||||
|
||||
4. **多显示器支持**:
|
||||
- Windows 端支持选择特定显示器捕获
|
||||
- 支持多显示器扩展模式
|
||||
|
||||
## 实现优先级
|
||||
|
||||
1. **第一阶段**:基础功能
|
||||
- [ ] Windows 屏幕捕获(Desktop Duplication API)
|
||||
- [ ] Windows H.264 编码(Media Foundation)
|
||||
- [ ] Android H.264 解码(AMediaCodec)
|
||||
- [ ] Android Surface 显示
|
||||
|
||||
2. **第二阶段**:优化
|
||||
- [ ] 零拷贝优化
|
||||
- [ ] 帧率控制
|
||||
- [ ] 自适应码率
|
||||
|
||||
3. **第三阶段**:高级功能
|
||||
- [ ] 多显示器支持
|
||||
- [ ] 延迟优化
|
||||
- [ ] 错误恢复
|
||||
|
||||
Reference in New Issue
Block a user