Feat: Implement H264 for Linux client with dynamic libx264 loading
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include "LinuxConfig.h"
|
||||
#include "ClipboardHandler.h"
|
||||
#include "common/FileTransferV2.h"
|
||||
#include "X264Encoder.h"
|
||||
#include <dlfcn.h>
|
||||
#include <sys/stat.h>
|
||||
#include <thread>
|
||||
@@ -12,6 +13,7 @@
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
|
||||
// 客户端 ID(定义在 main.cpp)
|
||||
extern uint64_t g_myClientID;
|
||||
@@ -110,27 +112,39 @@ struct XGCValues_LNX {
|
||||
#define IncludeInferiors 1
|
||||
|
||||
// ============== 屏幕算法常量 ==============
|
||||
#define ALGORITHM_GRAY 0
|
||||
#define ALGORITHM_DIFF 1
|
||||
#define ALGORITHM_H264 2
|
||||
#define ALGORITHM_RGB565 3
|
||||
// 常量定义已移至 commands.h: ALGORITHM_GRAY, ALGORITHM_DIFF, ALGORITHM_H264, ALGORITHM_RGB565
|
||||
|
||||
// 算法支持表(编译时常量,日后支持 H264 时改为 true)
|
||||
static const bool g_SupportedAlgo[] = {
|
||||
true, // ALGORITHM_GRAY = 0
|
||||
true, // ALGORITHM_DIFF = 1
|
||||
false, // ALGORITHM_H264 = 2
|
||||
true, // ALGORITHM_RGB565 = 3
|
||||
};
|
||||
// 检查算法是否支持(H264 需要运行时检测)
|
||||
inline bool IsAlgorithmSupported(uint8_t algo) {
|
||||
switch (algo) {
|
||||
case ALGORITHM_GRAY:
|
||||
case ALGORITHM_DIFF:
|
||||
case ALGORITHM_RGB565:
|
||||
return true;
|
||||
case ALGORITHM_H264:
|
||||
return X264Encoder::IsAvailable();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 不支持的算法降级为 RGB565
|
||||
inline uint8_t GetEffectiveAlgorithm(uint8_t algo) {
|
||||
if (algo > 3 || !g_SupportedAlgo[algo]) {
|
||||
if (!IsAlgorithmSupported(algo)) {
|
||||
Mprintf(">>> Algorithm %d not supported, fallback to RGB565\n", algo);
|
||||
return ALGORITHM_RGB565;
|
||||
}
|
||||
return algo;
|
||||
}
|
||||
|
||||
// 码率到 CRF 映射 (参考 Windows/macOS 实现)
|
||||
inline int BitRateToCRF(int bitrate) {
|
||||
if (bitrate >= 3000) return 20; // 高质量
|
||||
if (bitrate >= 2000) return 23; // 中等
|
||||
if (bitrate >= 1200) return 26; // 较低
|
||||
return 30; // 最低
|
||||
}
|
||||
|
||||
// ============== 颜色转换函数 ==============
|
||||
|
||||
// BGRA → 灰度 (Y = 0.299R + 0.587G + 0.114B)
|
||||
@@ -538,7 +552,8 @@ public:
|
||||
m_inputDisplay(nullptr),
|
||||
m_width(0), m_height(0),
|
||||
m_pixmap(0), m_gc(nullptr), m_xtestWarned(false),
|
||||
m_bAlgorithm(ALGORITHM_DIFF), m_maxFPS(10), m_qualityLevel(QUALITY_ADAPTIVE)
|
||||
m_bAlgorithm(ALGORITHM_DIFF), m_maxFPS(10), m_qualityLevel(QUALITY_ADAPTIVE),
|
||||
m_h264Bitrate(2000)
|
||||
{
|
||||
if (!client) {
|
||||
throw std::invalid_argument("IOCPClient pointer cannot be null");
|
||||
@@ -883,12 +898,28 @@ public:
|
||||
// 应用帧率
|
||||
m_maxFPS.store(profile.maxFPS);
|
||||
|
||||
// 应用码率(H264 使用)
|
||||
int oldBitrate = m_h264Bitrate;
|
||||
m_h264Bitrate = profile.bitRate;
|
||||
|
||||
// 应用算法(带降级处理)
|
||||
uint8_t algo = GetEffectiveAlgorithm(profile.algorithm);
|
||||
uint8_t oldAlgo = m_bAlgorithm.load();
|
||||
m_bAlgorithm.store(algo);
|
||||
|
||||
Mprintf(">>> Quality: Level=%d, FPS=%d, Algo=%d->%d\n",
|
||||
level, profile.maxFPS, profile.algorithm, algo);
|
||||
// 如果 H264 参数变化,需要重新初始化编码器
|
||||
if (algo == ALGORITHM_H264 && oldAlgo == ALGORITHM_H264 &&
|
||||
(oldBitrate != m_h264Bitrate)) {
|
||||
// 码率变化,重置编码器(下次编码时重新初始化)
|
||||
if (m_h264Encoder) {
|
||||
m_h264Encoder->close();
|
||||
m_h264Encoder.reset();
|
||||
Mprintf(">>> H264 encoder reset due to bitrate change\n");
|
||||
}
|
||||
}
|
||||
|
||||
Mprintf(">>> Quality: Level=%d, FPS=%d, Algo=%d->%d, Bitrate=%d\n",
|
||||
level, profile.maxFPS, profile.algorithm, algo, profile.bitRate);
|
||||
} else {
|
||||
// 自适应模式 (level=-1):由服务端动态调整,不做处理
|
||||
Mprintf(">>> Quality: Adaptive mode\n");
|
||||
@@ -1054,11 +1085,15 @@ private:
|
||||
std::vector<uint8_t> m_diffBuffer;
|
||||
|
||||
// 自适应质量控制
|
||||
std::atomic<uint8_t> m_bAlgorithm; // 当前算法 (ALGORITHM_DIFF/RGB565/GRAY)
|
||||
std::atomic<uint8_t> m_bAlgorithm; // 当前算法 (ALGORITHM_DIFF/RGB565/GRAY/H264)
|
||||
std::atomic<int> m_maxFPS; // 最大帧率
|
||||
int8_t m_qualityLevel; // 当前质量等级 (-1=自适应, 0-5=具体等级)
|
||||
LinuxConfig m_config; // 配置持久化 (~/.config/ghost/config.conf)
|
||||
|
||||
// H264 编码器
|
||||
std::unique_ptr<X264Encoder> m_h264Encoder;
|
||||
int m_h264Bitrate; // 码率 (kbps)
|
||||
|
||||
// X11 截屏,输出 BGRA 格式(自底向上,与 BMP 一致)
|
||||
// 使用 XCopyArea 将 root window 拷贝到离屏 Pixmap,再对 Pixmap 调用 XGetImage
|
||||
// 这样可以避免合成窗口管理器(Mutter 等)导致的 BadMatch 错误
|
||||
@@ -1151,6 +1186,59 @@ private:
|
||||
std::swap(m_prevFrame, m_currFrame);
|
||||
}
|
||||
|
||||
// 发送 H264 编码帧
|
||||
void SendH264Frame(bool forceKeyframe = false)
|
||||
{
|
||||
if (!CaptureScreen(m_currFrame)) return;
|
||||
if (!m_client) return;
|
||||
|
||||
// 惰性初始化编码器
|
||||
if (!m_h264Encoder) {
|
||||
m_h264Encoder.reset(new X264Encoder());
|
||||
int fps = m_maxFPS.load();
|
||||
if (fps <= 0) fps = 20;
|
||||
int crf = BitRateToCRF(m_h264Bitrate);
|
||||
if (!m_h264Encoder->open(m_bmpHeader.biWidth, m_bmpHeader.biHeight, fps, crf)) {
|
||||
Mprintf("*** H264 encoder init failed, falling back to RGB565\n");
|
||||
m_bAlgorithm.store(ALGORITHM_RGB565);
|
||||
m_h264Encoder.reset();
|
||||
SendDiffFrame();
|
||||
return;
|
||||
}
|
||||
Mprintf(">>> H264 encoder initialized: %dx%d @ %d fps, CRF=%d\n",
|
||||
m_bmpHeader.biWidth, m_bmpHeader.biHeight, fps, crf);
|
||||
}
|
||||
|
||||
// 编码当前帧
|
||||
uint8_t* encodedData = nullptr;
|
||||
uint32_t encodedSize = 0;
|
||||
|
||||
// direction=1 表示 bottom-up (BMP 格式)
|
||||
int result = m_h264Encoder->encode(
|
||||
m_currFrame.data(), 32, m_bmpHeader.biWidth * 4,
|
||||
m_bmpHeader.biWidth, m_bmpHeader.biHeight,
|
||||
&encodedData, &encodedSize, 1);
|
||||
|
||||
if (result != 0 || !encodedData || encodedSize == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建数据包: [TOKEN_NEXTSCREEN][algo][cursorX][cursorY][cursorType][H264Data]
|
||||
uint32_t headerSize = 1 + 1 + 2 * sizeof(int32_t) + 1;
|
||||
std::vector<uint8_t> packet(headerSize + encodedSize);
|
||||
|
||||
packet[0] = TOKEN_NEXTSCREEN;
|
||||
packet[1] = ALGORITHM_H264;
|
||||
|
||||
int32_t cursorX = 0, cursorY = 0;
|
||||
memcpy(&packet[2], &cursorX, sizeof(int32_t));
|
||||
memcpy(&packet[6], &cursorY, sizeof(int32_t));
|
||||
packet[10] = 0; // cursorType
|
||||
|
||||
memcpy(&packet[headerSize], encodedData, encodedSize);
|
||||
m_client->Send2Server((char*)packet.data(), packet.size());
|
||||
}
|
||||
|
||||
// 差异比较算法(支持 DIFF/RGB565/GRAY)
|
||||
// 输出格式: [byteOffset(4) + length(4) + pixel data] ...
|
||||
// DIFF: length = 字节数, data = BGRA 原始数据
|
||||
@@ -1243,10 +1331,34 @@ private:
|
||||
// 发送第一帧
|
||||
SendFirstScreen();
|
||||
|
||||
uint8_t currentAlgo = m_bAlgorithm.load();
|
||||
|
||||
while (m_running) {
|
||||
uint64_t start = GetTickMs();
|
||||
uint8_t algo = m_bAlgorithm.load();
|
||||
|
||||
SendDiffFrame();
|
||||
// 算法切换处理
|
||||
if (algo != currentAlgo) {
|
||||
currentAlgo = algo;
|
||||
if (algo == ALGORITHM_H264) {
|
||||
// 切换到 H264,发送关键帧
|
||||
SendH264Frame(true);
|
||||
} else {
|
||||
// 切换离开 H264,关闭编码器并发送完整帧
|
||||
if (m_h264Encoder) {
|
||||
m_h264Encoder->close();
|
||||
m_h264Encoder.reset();
|
||||
}
|
||||
SendFirstScreen();
|
||||
}
|
||||
} else {
|
||||
// 正常帧
|
||||
if (algo == ALGORITHM_H264) {
|
||||
SendH264Frame(false);
|
||||
} else {
|
||||
SendDiffFrame();
|
||||
}
|
||||
}
|
||||
|
||||
// 动态计算帧间隔(根据当前 maxFPS)
|
||||
int fps = m_maxFPS.load();
|
||||
@@ -1260,6 +1372,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// 清理 H264 编码器
|
||||
if (m_h264Encoder) {
|
||||
m_h264Encoder->close();
|
||||
m_h264Encoder.reset();
|
||||
}
|
||||
|
||||
Mprintf(">>> ScreenHandler CaptureLoop stopped\n");
|
||||
} catch (const std::exception& e) {
|
||||
Mprintf("*** CaptureLoop exception: %s ***\n", e.what());
|
||||
|
||||
Reference in New Issue
Block a user