Feat: Implement H264 for Linux client with dynamic libx264 loading

This commit is contained in:
yuanyuanxiang
2026-05-03 12:15:22 +02:00
parent 36423b1c7c
commit ca37fa419a
6 changed files with 646 additions and 38 deletions

View File

@@ -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());