Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea

This commit is contained in:
yuanyuanxiang
2026-04-19 19:55:01 +02:00
commit 5a325a202b
744 changed files with 235562 additions and 0 deletions

View File

@@ -0,0 +1,725 @@
// DiffAlgorithmTest.cpp - Phase 4: 差分算法单元测试
// 测试帧间差异计算和编码逻辑
#include <gtest/gtest.h>
#include <vector>
#include <cstdint>
#include <cstring>
#include <algorithm>
#include <random>
// ============================================
// 算法常量定义 (来自 CursorInfo.h)
// ============================================
#define ALGORITHM_GRAY 0 // 灰度算法
#define ALGORITHM_DIFF 1 // 差分算法(默认)
#define ALGORITHM_H264 2 // H264 视频编码
#define ALGORITHM_RGB565 3 // RGB565 压缩
// ============================================
// 差分输出结构
// ============================================
struct DiffRegion {
uint32_t offset; // 字节偏移
uint32_t length; // 长度(含义取决于算法)
std::vector<uint8_t> data; // 差异数据
};
// ============================================
// 测试用差分算法实现
// 模拟 ScreenCapture.h 中的 CompareBitmapDXGI
// ============================================
class DiffAlgorithm {
public:
// 比较两帧,返回差异区域列表
// 输出格式: [offset:4][length:4][data:N]...
static std::vector<DiffRegion> CompareBitmap(
const uint8_t* srcData, // 新帧
const uint8_t* dstData, // 旧帧
size_t dataLength, // 数据长度字节必须是4的倍数
int algorithm // 压缩算法
) {
std::vector<DiffRegion> regions;
if (dataLength == 0 || dataLength % 4 != 0) {
return regions;
}
const uint32_t* src32 = reinterpret_cast<const uint32_t*>(srcData);
const uint32_t* dst32 = reinterpret_cast<const uint32_t*>(dstData);
size_t pixelCount = dataLength / 4;
size_t i = 0;
while (i < pixelCount) {
// 找到差异起始点
while (i < pixelCount && src32[i] == dst32[i]) {
i++;
}
if (i >= pixelCount) break;
// 记录起始位置
size_t startPos = i;
// 找到差异结束点
while (i < pixelCount && src32[i] != dst32[i]) {
i++;
}
// 创建差异区域
DiffRegion region;
region.offset = static_cast<uint32_t>(startPos * 4); // 字节偏移
size_t diffPixels = i - startPos;
const uint8_t* pixelStart = srcData + startPos * 4;
switch (algorithm) {
case ALGORITHM_GRAY: {
// 灰度: 1字节/像素
region.length = static_cast<uint32_t>(diffPixels); // 像素数
region.data.resize(diffPixels);
for (size_t p = 0; p < diffPixels; p++) {
const uint8_t* pixel = pixelStart + p * 4;
// BGRA格式: B=0, G=1, R=2, A=3
// 灰度公式: Y = 0.299*R + 0.587*G + 0.114*B
int gray = (306 * pixel[2] + 601 * pixel[1] + 117 * pixel[0]) >> 10;
region.data[p] = static_cast<uint8_t>(std::min(255, std::max(0, gray)));
}
break;
}
case ALGORITHM_RGB565: {
// RGB565: 2字节/像素
region.length = static_cast<uint32_t>(diffPixels); // 像素数
region.data.resize(diffPixels * 2);
uint16_t* out = reinterpret_cast<uint16_t*>(region.data.data());
for (size_t p = 0; p < diffPixels; p++) {
const uint8_t* pixel = pixelStart + p * 4;
// BGRA -> RGB565
out[p] = ((pixel[2] >> 3) << 11) | // R: 5位
((pixel[1] >> 2) << 5) | // G: 6位
(pixel[0] >> 3); // B: 5位
}
break;
}
case ALGORITHM_DIFF:
case ALGORITHM_H264:
default: {
// DIFF/H264: 4字节/像素原始BGRA
region.length = static_cast<uint32_t>(diffPixels * 4); // 字节数
region.data.resize(diffPixels * 4);
memcpy(region.data.data(), pixelStart, diffPixels * 4);
break;
}
}
regions.push_back(region);
}
return regions;
}
// 序列化差异区域到缓冲区
static size_t SerializeDiffRegions(
const std::vector<DiffRegion>& regions,
uint8_t* buffer,
size_t bufferSize
) {
size_t offset = 0;
for (const auto& region : regions) {
size_t needed = 8 + region.data.size(); // offset(4) + length(4) + data
if (offset + needed > bufferSize) break;
memcpy(buffer + offset, &region.offset, 4);
offset += 4;
memcpy(buffer + offset, &region.length, 4);
offset += 4;
memcpy(buffer + offset, region.data.data(), region.data.size());
offset += region.data.size();
}
return offset;
}
// 应用差异到目标帧
static void ApplyDiff(
uint8_t* dstData,
size_t dstLength,
const std::vector<DiffRegion>& regions,
int algorithm
) {
for (const auto& region : regions) {
if (region.offset >= dstLength) continue;
uint8_t* dst = dstData + region.offset;
switch (algorithm) {
case ALGORITHM_GRAY: {
// 灰度 -> BGRA
for (uint32_t p = 0; p < region.length && region.offset + p * 4 < dstLength; p++) {
uint8_t gray = region.data[p];
dst[p * 4 + 0] = gray; // B
dst[p * 4 + 1] = gray; // G
dst[p * 4 + 2] = gray; // R
dst[p * 4 + 3] = 0xFF; // A
}
break;
}
case ALGORITHM_RGB565: {
// RGB565 -> BGRA
const uint16_t* src = reinterpret_cast<const uint16_t*>(region.data.data());
for (uint32_t p = 0; p < region.length && region.offset + p * 4 < dstLength; p++) {
uint16_t c = src[p];
uint8_t r5 = (c >> 11) & 0x1F;
uint8_t g6 = (c >> 5) & 0x3F;
uint8_t b5 = c & 0x1F;
dst[p * 4 + 0] = (b5 << 3) | (b5 >> 2); // B
dst[p * 4 + 1] = (g6 << 2) | (g6 >> 4); // G
dst[p * 4 + 2] = (r5 << 3) | (r5 >> 2); // R
dst[p * 4 + 3] = 0xFF; // A
}
break;
}
case ALGORITHM_DIFF:
case ALGORITHM_H264:
default: {
// 原始BGRA
size_t copyLen = std::min(static_cast<size_t>(region.length),
dstLength - region.offset);
memcpy(dst, region.data.data(), copyLen);
break;
}
}
}
}
};
// ============================================
// 测试夹具
// ============================================
class DiffAlgorithmTest : public ::testing::Test {
protected:
// 创建纯色帧 (BGRA格式)
static std::vector<uint8_t> CreateSolidFrame(int width, int height,
uint8_t b, uint8_t g,
uint8_t r, uint8_t a = 0xFF) {
std::vector<uint8_t> frame(width * height * 4);
for (int i = 0; i < width * height; i++) {
frame[i * 4 + 0] = b;
frame[i * 4 + 1] = g;
frame[i * 4 + 2] = r;
frame[i * 4 + 3] = a;
}
return frame;
}
// 创建渐变帧
static std::vector<uint8_t> CreateGradientFrame(int width, int height) {
std::vector<uint8_t> frame(width * height * 4);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int idx = (y * width + x) * 4;
frame[idx + 0] = static_cast<uint8_t>(x * 255 / width); // B
frame[idx + 1] = static_cast<uint8_t>(y * 255 / height); // G
frame[idx + 2] = static_cast<uint8_t>((x + y) * 128 / (width + height)); // R
frame[idx + 3] = 0xFF;
}
}
return frame;
}
// 创建带随机区域变化的帧
static std::vector<uint8_t> CreateFrameWithChanges(
const std::vector<uint8_t>& baseFrame,
int width, int height,
int changeX, int changeY,
int changeW, int changeH,
uint8_t newB, uint8_t newG, uint8_t newR
) {
std::vector<uint8_t> frame = baseFrame;
for (int y = changeY; y < changeY + changeH && y < height; y++) {
for (int x = changeX; x < changeX + changeW && x < width; x++) {
int idx = (y * width + x) * 4;
frame[idx + 0] = newB;
frame[idx + 1] = newG;
frame[idx + 2] = newR;
frame[idx + 3] = 0xFF;
}
}
return frame;
}
};
// ============================================
// 基础功能测试
// ============================================
TEST_F(DiffAlgorithmTest, IdenticalFrames_NoDifference) {
auto frame = CreateSolidFrame(100, 100, 128, 128, 128);
auto regions = DiffAlgorithm::CompareBitmap(
frame.data(), frame.data(), frame.size(), ALGORITHM_DIFF);
EXPECT_EQ(regions.size(), 0u);
}
TEST_F(DiffAlgorithmTest, CompletelyDifferent_SingleRegion) {
auto frame1 = CreateSolidFrame(10, 10, 0, 0, 0);
auto frame2 = CreateSolidFrame(10, 10, 255, 255, 255);
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_DIFF);
EXPECT_EQ(regions.size(), 1u);
EXPECT_EQ(regions[0].offset, 0u);
EXPECT_EQ(regions[0].length, 100u * 4); // 100像素 * 4字节
}
TEST_F(DiffAlgorithmTest, PartialChange_SingleRegion) {
const int WIDTH = 100, HEIGHT = 100;
auto frame1 = CreateSolidFrame(WIDTH, HEIGHT, 0, 0, 0);
auto frame2 = CreateFrameWithChanges(frame1, WIDTH, HEIGHT,
10, 10, 20, 20, 255, 255, 255);
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_DIFF);
// 应该检测到变化区域
EXPECT_GT(regions.size(), 0u);
// 验证总变化像素数
size_t totalChangedPixels = 0;
for (const auto& r : regions) {
totalChangedPixels += r.length / 4; // DIFF算法length是字节数
}
EXPECT_EQ(totalChangedPixels, 20u * 20u); // 20x20区域
}
TEST_F(DiffAlgorithmTest, MultipleRegions_NonContiguous) {
const int WIDTH = 100, HEIGHT = 10;
auto frame1 = CreateSolidFrame(WIDTH, HEIGHT, 128, 128, 128);
auto frame2 = frame1;
// 创建两个不相邻的变化区域
// 区域1: 像素 5-14
for (int i = 5; i < 15; i++) {
frame2[i * 4 + 0] = 0;
frame2[i * 4 + 1] = 0;
frame2[i * 4 + 2] = 255;
}
// 区域2: 像素 50-59 (与区域1不相邻)
for (int i = 50; i < 60; i++) {
frame2[i * 4 + 0] = 255;
frame2[i * 4 + 1] = 0;
frame2[i * 4 + 2] = 0;
}
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_DIFF);
EXPECT_EQ(regions.size(), 2u);
}
// ============================================
// 算法特定测试
// ============================================
TEST_F(DiffAlgorithmTest, GrayAlgorithm_CorrectOutput) {
auto frame1 = CreateSolidFrame(10, 10, 0, 0, 0); // 黑色
auto frame2 = CreateSolidFrame(10, 10, 255, 255, 255); // 白色
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_GRAY);
ASSERT_EQ(regions.size(), 1u);
EXPECT_EQ(regions[0].length, 100u); // 100像素
EXPECT_EQ(regions[0].data.size(), 100u); // 1字节/像素
// 白色应该转换为灰度255
EXPECT_EQ(regions[0].data[0], 255);
}
TEST_F(DiffAlgorithmTest, GrayAlgorithm_GrayConversionFormula) {
// 测试灰度转换公式: Y = 0.299*R + 0.587*G + 0.114*B
std::vector<uint8_t> frame1(4, 0); // 1像素黑色
std::vector<uint8_t> frame2(4);
// 测试纯红色 (R=255, G=0, B=0)
frame2[0] = 0; // B
frame2[1] = 0; // G
frame2[2] = 255; // R
frame2[3] = 255; // A
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), 4, ALGORITHM_GRAY);
ASSERT_EQ(regions.size(), 1u);
// 期望: (306 * 255 + 601 * 0 + 117 * 0) >> 10 ≈ 76
uint8_t expectedGray = (306 * 255) >> 10;
EXPECT_NEAR(regions[0].data[0], expectedGray, 1);
}
TEST_F(DiffAlgorithmTest, RGB565Algorithm_CorrectOutput) {
auto frame1 = CreateSolidFrame(10, 10, 0, 0, 0);
auto frame2 = CreateSolidFrame(10, 10, 255, 255, 255);
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_RGB565);
ASSERT_EQ(regions.size(), 1u);
EXPECT_EQ(regions[0].length, 100u); // 100像素
EXPECT_EQ(regions[0].data.size(), 200u); // 2字节/像素
// 白色 RGB565 = 0xFFFF
uint16_t* rgb565 = reinterpret_cast<uint16_t*>(regions[0].data.data());
EXPECT_EQ(rgb565[0], 0xFFFF);
}
TEST_F(DiffAlgorithmTest, RGB565Algorithm_ColorConversion) {
std::vector<uint8_t> frame1(4, 0); // 1像素黑色
std::vector<uint8_t> frame2(4);
// 纯红色 (R=255, G=0, B=0) -> RGB565 = 0xF800
frame2[0] = 0; // B
frame2[1] = 0; // G
frame2[2] = 255; // R
frame2[3] = 255; // A
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), 4, ALGORITHM_RGB565);
ASSERT_EQ(regions.size(), 1u);
uint16_t* rgb565 = reinterpret_cast<uint16_t*>(regions[0].data.data());
EXPECT_EQ(rgb565[0], 0xF800); // 纯红色
}
TEST_F(DiffAlgorithmTest, DiffAlgorithm_PreservesOriginalData) {
std::vector<uint8_t> frame1(8, 0); // 2像素黑色
std::vector<uint8_t> frame2 = {
0x12, 0x34, 0x56, 0x78, // 像素1
0xAB, 0xCD, 0xEF, 0xFF // 像素2
};
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), 8, ALGORITHM_DIFF);
ASSERT_EQ(regions.size(), 1u);
EXPECT_EQ(regions[0].length, 8u); // 8字节
EXPECT_EQ(regions[0].data, frame2); // 原始数据完整保留
}
// ============================================
// 边界条件测试
// ============================================
TEST_F(DiffAlgorithmTest, EmptyInput_NoRegions) {
auto regions = DiffAlgorithm::CompareBitmap(nullptr, nullptr, 0, ALGORITHM_DIFF);
EXPECT_EQ(regions.size(), 0u);
}
TEST_F(DiffAlgorithmTest, SinglePixel_Difference) {
std::vector<uint8_t> frame1 = {0, 0, 0, 255};
std::vector<uint8_t> frame2 = {255, 255, 255, 255};
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), 4, ALGORITHM_DIFF);
ASSERT_EQ(regions.size(), 1u);
EXPECT_EQ(regions[0].offset, 0u);
EXPECT_EQ(regions[0].length, 4u);
}
TEST_F(DiffAlgorithmTest, SinglePixel_NoDifference) {
std::vector<uint8_t> frame = {100, 150, 200, 255};
auto regions = DiffAlgorithm::CompareBitmap(
frame.data(), frame.data(), 4, ALGORITHM_DIFF);
EXPECT_EQ(regions.size(), 0u);
}
TEST_F(DiffAlgorithmTest, NonAlignedLength_Rejected) {
std::vector<uint8_t> data(7); // 不是4的倍数
auto regions = DiffAlgorithm::CompareBitmap(
data.data(), data.data(), data.size(), ALGORITHM_DIFF);
EXPECT_EQ(regions.size(), 0u);
}
TEST_F(DiffAlgorithmTest, FirstPixelOnly_Changed) {
std::vector<uint8_t> frame1(40, 128); // 10像素
std::vector<uint8_t> frame2 = frame1;
frame2[0] = 0; // 只改变第一个像素的B通道
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), 40, ALGORITHM_DIFF);
ASSERT_EQ(regions.size(), 1u);
EXPECT_EQ(regions[0].offset, 0u);
EXPECT_EQ(regions[0].length, 4u); // 只有1个像素
}
TEST_F(DiffAlgorithmTest, LastPixelOnly_Changed) {
std::vector<uint8_t> frame1(40, 128); // 10像素
std::vector<uint8_t> frame2 = frame1;
frame2[36] = 0; // 只改变最后一个像素的B通道
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), 40, ALGORITHM_DIFF);
ASSERT_EQ(regions.size(), 1u);
EXPECT_EQ(regions[0].offset, 36u); // 第9个像素的偏移
EXPECT_EQ(regions[0].length, 4u);
}
// ============================================
// 序列化测试
// ============================================
TEST_F(DiffAlgorithmTest, Serialize_SingleRegion) {
std::vector<DiffRegion> regions;
DiffRegion r;
r.offset = 100;
r.length = 16;
r.data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
regions.push_back(r);
std::vector<uint8_t> buffer(1024);
size_t written = DiffAlgorithm::SerializeDiffRegions(
regions, buffer.data(), buffer.size());
EXPECT_EQ(written, 8u + 16u); // offset(4) + length(4) + data(16)
// 验证偏移
uint32_t readOffset;
memcpy(&readOffset, buffer.data(), 4);
EXPECT_EQ(readOffset, 100u);
// 验证长度
uint32_t readLength;
memcpy(&readLength, buffer.data() + 4, 4);
EXPECT_EQ(readLength, 16u);
}
TEST_F(DiffAlgorithmTest, Serialize_MultipleRegions) {
std::vector<DiffRegion> regions;
DiffRegion r1;
r1.offset = 0;
r1.length = 4;
r1.data = {1, 2, 3, 4};
regions.push_back(r1);
DiffRegion r2;
r2.offset = 100;
r2.length = 8;
r2.data = {5, 6, 7, 8, 9, 10, 11, 12};
regions.push_back(r2);
std::vector<uint8_t> buffer(1024);
size_t written = DiffAlgorithm::SerializeDiffRegions(
regions, buffer.data(), buffer.size());
EXPECT_EQ(written, (8u + 4u) + (8u + 8u)); // 两个区域
}
TEST_F(DiffAlgorithmTest, Serialize_BufferTooSmall) {
std::vector<DiffRegion> regions;
DiffRegion r;
r.offset = 0;
r.length = 100;
r.data.resize(100, 0xFF);
regions.push_back(r);
std::vector<uint8_t> buffer(10); // 太小
size_t written = DiffAlgorithm::SerializeDiffRegions(
regions, buffer.data(), buffer.size());
EXPECT_EQ(written, 0u); // 无法写入
}
// ============================================
// 应用差异测试
// ============================================
TEST_F(DiffAlgorithmTest, ApplyDiff_DIFF_Reconstructs) {
auto frame1 = CreateGradientFrame(50, 50);
auto frame2 = CreateFrameWithChanges(frame1, 50, 50, 10, 10, 20, 20, 255, 0, 0);
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_DIFF);
// 应用差异到frame1的副本
std::vector<uint8_t> reconstructed = frame1;
DiffAlgorithm::ApplyDiff(reconstructed.data(), reconstructed.size(),
regions, ALGORITHM_DIFF);
// 应该完全重建frame2
EXPECT_EQ(reconstructed, frame2);
}
TEST_F(DiffAlgorithmTest, ApplyDiff_GRAY_ApproximateReconstruction) {
auto frame1 = CreateSolidFrame(10, 10, 0, 0, 0);
auto frame2 = CreateSolidFrame(10, 10, 200, 200, 200); // 浅灰色
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_GRAY);
std::vector<uint8_t> reconstructed = frame1;
DiffAlgorithm::ApplyDiff(reconstructed.data(), reconstructed.size(),
regions, ALGORITHM_GRAY);
// 灰度重建R=G=B
for (size_t i = 0; i < reconstructed.size(); i += 4) {
EXPECT_EQ(reconstructed[i], reconstructed[i + 1]); // B == G
EXPECT_EQ(reconstructed[i + 1], reconstructed[i + 2]); // G == R
}
}
TEST_F(DiffAlgorithmTest, ApplyDiff_RGB565_ApproximateReconstruction) {
auto frame1 = CreateSolidFrame(10, 10, 0, 0, 0);
// RGB565有量化误差使用能精确表示的颜色
auto frame2 = CreateSolidFrame(10, 10, 248, 252, 248); // 接近白色
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_RGB565);
std::vector<uint8_t> reconstructed = frame1;
DiffAlgorithm::ApplyDiff(reconstructed.data(), reconstructed.size(),
regions, ALGORITHM_RGB565);
// 验证重建颜色接近原始(允许量化误差)
for (size_t i = 0; i < reconstructed.size(); i += 4) {
EXPECT_NEAR(reconstructed[i], frame2[i], 8); // B
EXPECT_NEAR(reconstructed[i + 1], frame2[i + 1], 4); // G (6位精度)
EXPECT_NEAR(reconstructed[i + 2], frame2[i + 2], 8); // R
}
}
// ============================================
// 性能相关测试(不计时,只验证正确性)
// ============================================
TEST_F(DiffAlgorithmTest, LargeFrame_1080p_Correctness) {
const int WIDTH = 1920, HEIGHT = 1080;
auto frame1 = CreateSolidFrame(WIDTH, HEIGHT, 128, 128, 128);
auto frame2 = CreateFrameWithChanges(frame1, WIDTH, HEIGHT,
100, 100, 200, 200, 255, 0, 0);
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_DIFF);
// 应该有变化区域
EXPECT_GT(regions.size(), 0u);
// 重建验证
std::vector<uint8_t> reconstructed = frame1;
DiffAlgorithm::ApplyDiff(reconstructed.data(), reconstructed.size(),
regions, ALGORITHM_DIFF);
EXPECT_EQ(reconstructed, frame2);
}
TEST_F(DiffAlgorithmTest, RandomChanges_AllAlgorithms) {
const int WIDTH = 100, HEIGHT = 100;
std::mt19937 rng(42);
std::uniform_int_distribution<int> dist(0, 255);
auto frame1 = CreateGradientFrame(WIDTH, HEIGHT);
auto frame2 = frame1;
// 随机修改10%的像素
for (int i = 0; i < WIDTH * HEIGHT / 10; i++) {
int idx = (rng() % (WIDTH * HEIGHT)) * 4;
frame2[idx + 0] = dist(rng);
frame2[idx + 1] = dist(rng);
frame2[idx + 2] = dist(rng);
}
// 测试所有算法都能产生输出
for (int algo : {ALGORITHM_GRAY, ALGORITHM_DIFF, ALGORITHM_RGB565}) {
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), algo);
EXPECT_GT(regions.size(), 0u) << "Algorithm " << algo << " failed";
}
}
// ============================================
// 压缩效率测试
// ============================================
TEST_F(DiffAlgorithmTest, CompressionRatio_GRAY) {
auto frame1 = CreateSolidFrame(100, 100, 0, 0, 0);
auto frame2 = CreateSolidFrame(100, 100, 255, 255, 255);
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_GRAY);
size_t originalSize = 100 * 100 * 4; // 40000 字节
size_t compressedSize = 8 + regions[0].data.size(); // offset + length + data
// GRAY应该是 100*100*1 = 10000 字节数据
EXPECT_EQ(regions[0].data.size(), 10000u);
// 压缩比约 4:1
EXPECT_LT(compressedSize, originalSize / 3);
}
TEST_F(DiffAlgorithmTest, CompressionRatio_RGB565) {
auto frame1 = CreateSolidFrame(100, 100, 0, 0, 0);
auto frame2 = CreateSolidFrame(100, 100, 255, 255, 255);
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_RGB565);
// RGB565应该是 100*100*2 = 20000 字节数据
EXPECT_EQ(regions[0].data.size(), 20000u);
}
TEST_F(DiffAlgorithmTest, NoChange_ZeroOverhead) {
auto frame = CreateGradientFrame(100, 100);
auto regions = DiffAlgorithm::CompareBitmap(
frame.data(), frame.data(), frame.size(), ALGORITHM_DIFF);
EXPECT_EQ(regions.size(), 0u);
std::vector<uint8_t> buffer(1024);
size_t written = DiffAlgorithm::SerializeDiffRegions(
regions, buffer.data(), buffer.size());
EXPECT_EQ(written, 0u); // 无变化时零开销
}
// ============================================
// 参数化测试:不同分辨率
// ============================================
class DiffAlgorithmResolutionTest : public ::testing::TestWithParam<std::tuple<int, int>> {};
TEST_P(DiffAlgorithmResolutionTest, Resolution_Correctness) {
auto [width, height] = GetParam();
auto frame1 = std::vector<uint8_t>(width * height * 4, 0);
auto frame2 = std::vector<uint8_t>(width * height * 4, 255);
auto regions = DiffAlgorithm::CompareBitmap(
frame2.data(), frame1.data(), frame1.size(), ALGORITHM_DIFF);
EXPECT_EQ(regions.size(), 1u);
EXPECT_EQ(regions[0].offset, 0u);
EXPECT_EQ(regions[0].length, static_cast<uint32_t>(width * height * 4));
}
INSTANTIATE_TEST_SUITE_P(
Resolutions,
DiffAlgorithmResolutionTest,
::testing::Values(
std::make_tuple(1, 1), // 最小
std::make_tuple(10, 10), // 小
std::make_tuple(100, 100), // 中
std::make_tuple(640, 480), // VGA
std::make_tuple(1280, 720), // 720p
std::make_tuple(1920, 1080) // 1080p
)
);

View File

@@ -0,0 +1,691 @@
// QualityAdaptiveTest.cpp - Phase 4: 质量自适应单元测试
// 测试 RTT 到质量等级的映射和防抖策略
#include <gtest/gtest.h>
#include <vector>
#include <cstdint>
#include <climits>
// ============================================
// 质量等级枚举 (来自 commands.h)
// ============================================
enum QualityLevel {
QUALITY_DISABLED = -2, // 关闭质量控制
QUALITY_ADAPTIVE = -1, // 自适应模式
QUALITY_ULTRA = 0, // 极佳(局域网)
QUALITY_HIGH = 1, // 优秀
QUALITY_GOOD = 2, // 良好
QUALITY_MEDIUM = 3, // 一般
QUALITY_LOW = 4, // 较差
QUALITY_MINIMAL = 5, // 最低
QUALITY_COUNT = 6,
};
// ============================================
// 算法枚举
// ============================================
#define ALGORITHM_GRAY 0
#define ALGORITHM_DIFF 1
#define ALGORITHM_H264 2
#define ALGORITHM_RGB565 3
// ============================================
// 质量配置结构体 (来自 commands.h)
// ============================================
struct QualityProfile {
int maxFPS; // 最大帧率
int maxWidth; // 最大宽度 (0=不限)
int algorithm; // 压缩算法
int bitRate; // kbps (仅H264使用)
};
// 默认质量配置表
static const QualityProfile g_QualityProfiles[QUALITY_COUNT] = {
{25, 0, ALGORITHM_DIFF, 0 }, // Ultra: 25FPS, 原始, DIFF
{20, 0, ALGORITHM_RGB565, 0 }, // High: 20FPS, 原始, RGB565
{20, 1920, ALGORITHM_H264, 3000}, // Good: 20FPS, 1080P, H264
{15, 1600, ALGORITHM_H264, 2000}, // Medium: 15FPS, 900P, H264
{12, 1280, ALGORITHM_H264, 1200}, // Low: 12FPS, 720P, H264
{8, 1024, ALGORITHM_H264, 800 }, // Minimal: 8FPS, 540P, H264
};
// ============================================
// RTT 阈值表 (来自 commands.h)
// ============================================
// 行0: 直连模式, 行1: FRP代理模式
static const int g_RttThresholds[2][QUALITY_COUNT] = {
/* DIRECT */ { 30, 80, 150, 250, 400, INT_MAX },
/* PROXY */ { 60, 160, 300, 500, 800, INT_MAX },
};
// ============================================
// RTT到质量等级映射函数 (来自 commands.h)
// ============================================
inline int GetTargetQualityLevel(int rtt, int usingFRP)
{
int row = usingFRP ? 1 : 0;
for (int level = 0; level < QUALITY_COUNT; level++) {
if (rtt < g_RttThresholds[row][level]) {
return level;
}
}
return QUALITY_MINIMAL;
}
// ============================================
// 防抖策略模拟器
// ============================================
class QualityDebouncer {
public:
// 防抖参数
static const int DOWNGRADE_STABLE_COUNT = 2; // 降级所需稳定次数
static const int UPGRADE_STABLE_COUNT = 5; // 升级所需稳定次数
static const int DEFAULT_COOLDOWN_MS = 3000; // 默认冷却时间
static const int RES_CHANGE_DOWNGRADE_COOLDOWN_MS = 15000; // 分辨率降级冷却
static const int RES_CHANGE_UPGRADE_COOLDOWN_MS = 30000; // 分辨率升级冷却
static const int STARTUP_DELAY_MS = 60000; // 启动延迟
QualityDebouncer()
: m_currentLevel(QUALITY_HIGH)
, m_stableCount(0)
, m_lastChangeTime(0)
, m_startTime(0)
, m_enabled(true)
{}
void Reset() {
m_currentLevel = QUALITY_HIGH;
m_stableCount = 0;
m_lastChangeTime = 0;
m_startTime = 0;
}
void SetStartTime(uint64_t time) {
m_startTime = time;
}
void SetEnabled(bool enabled) {
m_enabled = enabled;
}
// 评估并返回新的质量等级
// 返回 -1 表示不改变
int Evaluate(int targetLevel, uint64_t currentTime, bool resolutionChange = false) {
if (!m_enabled) return -1;
// 启动延迟
if (currentTime - m_startTime < STARTUP_DELAY_MS) {
return -1;
}
// 冷却时间检查
uint64_t cooldown = DEFAULT_COOLDOWN_MS;
if (resolutionChange) {
cooldown = (targetLevel > m_currentLevel)
? RES_CHANGE_DOWNGRADE_COOLDOWN_MS
: RES_CHANGE_UPGRADE_COOLDOWN_MS;
}
if (currentTime - m_lastChangeTime < cooldown) {
return -1;
}
// 降级: 快速响应
if (targetLevel > m_currentLevel) {
m_stableCount++;
if (m_stableCount >= DOWNGRADE_STABLE_COUNT) {
int newLevel = targetLevel;
m_currentLevel = newLevel;
m_stableCount = 0;
m_lastChangeTime = currentTime;
return newLevel;
}
}
// 升级: 谨慎处理,每次只升一级
else if (targetLevel < m_currentLevel) {
m_stableCount++;
if (m_stableCount >= UPGRADE_STABLE_COUNT) {
int newLevel = m_currentLevel - 1; // 只升一级
m_currentLevel = newLevel;
m_stableCount = 0;
m_lastChangeTime = currentTime;
return newLevel;
}
}
// 目标等级等于当前等级
else {
m_stableCount = 0;
}
return -1;
}
int GetCurrentLevel() const { return m_currentLevel; }
int GetStableCount() const { return m_stableCount; }
private:
int m_currentLevel;
int m_stableCount;
uint64_t m_lastChangeTime;
uint64_t m_startTime;
bool m_enabled;
};
// ============================================
// 测试夹具
// ============================================
class QualityAdaptiveTest : public ::testing::Test {
protected:
void SetUp() override {
debouncer.Reset();
}
QualityDebouncer debouncer;
};
// ============================================
// RTT 映射测试 - 直连模式
// ============================================
TEST_F(QualityAdaptiveTest, RTT_Direct_Ultra) {
// RTT < 30ms -> Ultra
EXPECT_EQ(GetTargetQualityLevel(0, 0), QUALITY_ULTRA);
EXPECT_EQ(GetTargetQualityLevel(10, 0), QUALITY_ULTRA);
EXPECT_EQ(GetTargetQualityLevel(29, 0), QUALITY_ULTRA);
}
TEST_F(QualityAdaptiveTest, RTT_Direct_High) {
// 30ms <= RTT < 80ms -> High
EXPECT_EQ(GetTargetQualityLevel(30, 0), QUALITY_HIGH);
EXPECT_EQ(GetTargetQualityLevel(50, 0), QUALITY_HIGH);
EXPECT_EQ(GetTargetQualityLevel(79, 0), QUALITY_HIGH);
}
TEST_F(QualityAdaptiveTest, RTT_Direct_Good) {
// 80ms <= RTT < 150ms -> Good
EXPECT_EQ(GetTargetQualityLevel(80, 0), QUALITY_GOOD);
EXPECT_EQ(GetTargetQualityLevel(100, 0), QUALITY_GOOD);
EXPECT_EQ(GetTargetQualityLevel(149, 0), QUALITY_GOOD);
}
TEST_F(QualityAdaptiveTest, RTT_Direct_Medium) {
// 150ms <= RTT < 250ms -> Medium
EXPECT_EQ(GetTargetQualityLevel(150, 0), QUALITY_MEDIUM);
EXPECT_EQ(GetTargetQualityLevel(200, 0), QUALITY_MEDIUM);
EXPECT_EQ(GetTargetQualityLevel(249, 0), QUALITY_MEDIUM);
}
TEST_F(QualityAdaptiveTest, RTT_Direct_Low) {
// 250ms <= RTT < 400ms -> Low
EXPECT_EQ(GetTargetQualityLevel(250, 0), QUALITY_LOW);
EXPECT_EQ(GetTargetQualityLevel(300, 0), QUALITY_LOW);
EXPECT_EQ(GetTargetQualityLevel(399, 0), QUALITY_LOW);
}
TEST_F(QualityAdaptiveTest, RTT_Direct_Minimal) {
// RTT >= 400ms -> Minimal
EXPECT_EQ(GetTargetQualityLevel(400, 0), QUALITY_MINIMAL);
EXPECT_EQ(GetTargetQualityLevel(500, 0), QUALITY_MINIMAL);
EXPECT_EQ(GetTargetQualityLevel(1000, 0), QUALITY_MINIMAL);
}
// ============================================
// RTT 映射测试 - FRP代理模式
// ============================================
TEST_F(QualityAdaptiveTest, RTT_FRP_Ultra) {
// RTT < 60ms -> Ultra (FRP模式阈值更宽松)
EXPECT_EQ(GetTargetQualityLevel(0, 1), QUALITY_ULTRA);
EXPECT_EQ(GetTargetQualityLevel(30, 1), QUALITY_ULTRA);
EXPECT_EQ(GetTargetQualityLevel(59, 1), QUALITY_ULTRA);
}
TEST_F(QualityAdaptiveTest, RTT_FRP_High) {
// 60ms <= RTT < 160ms -> High
EXPECT_EQ(GetTargetQualityLevel(60, 1), QUALITY_HIGH);
EXPECT_EQ(GetTargetQualityLevel(100, 1), QUALITY_HIGH);
EXPECT_EQ(GetTargetQualityLevel(159, 1), QUALITY_HIGH);
}
TEST_F(QualityAdaptiveTest, RTT_FRP_Good) {
// 160ms <= RTT < 300ms -> Good
EXPECT_EQ(GetTargetQualityLevel(160, 1), QUALITY_GOOD);
EXPECT_EQ(GetTargetQualityLevel(200, 1), QUALITY_GOOD);
EXPECT_EQ(GetTargetQualityLevel(299, 1), QUALITY_GOOD);
}
TEST_F(QualityAdaptiveTest, RTT_FRP_Medium) {
// 300ms <= RTT < 500ms -> Medium
EXPECT_EQ(GetTargetQualityLevel(300, 1), QUALITY_MEDIUM);
EXPECT_EQ(GetTargetQualityLevel(400, 1), QUALITY_MEDIUM);
EXPECT_EQ(GetTargetQualityLevel(499, 1), QUALITY_MEDIUM);
}
TEST_F(QualityAdaptiveTest, RTT_FRP_Low) {
// 500ms <= RTT < 800ms -> Low
EXPECT_EQ(GetTargetQualityLevel(500, 1), QUALITY_LOW);
EXPECT_EQ(GetTargetQualityLevel(600, 1), QUALITY_LOW);
EXPECT_EQ(GetTargetQualityLevel(799, 1), QUALITY_LOW);
}
TEST_F(QualityAdaptiveTest, RTT_FRP_Minimal) {
// RTT >= 800ms -> Minimal
EXPECT_EQ(GetTargetQualityLevel(800, 1), QUALITY_MINIMAL);
EXPECT_EQ(GetTargetQualityLevel(1000, 1), QUALITY_MINIMAL);
EXPECT_EQ(GetTargetQualityLevel(2000, 1), QUALITY_MINIMAL);
}
// ============================================
// 质量配置表测试
// ============================================
TEST_F(QualityAdaptiveTest, Profile_Ultra) {
const auto& p = g_QualityProfiles[QUALITY_ULTRA];
EXPECT_EQ(p.maxFPS, 25);
EXPECT_EQ(p.maxWidth, 0); // 无限制
EXPECT_EQ(p.algorithm, ALGORITHM_DIFF);
EXPECT_EQ(p.bitRate, 0);
}
TEST_F(QualityAdaptiveTest, Profile_High) {
const auto& p = g_QualityProfiles[QUALITY_HIGH];
EXPECT_EQ(p.maxFPS, 20);
EXPECT_EQ(p.maxWidth, 0); // 无限制
EXPECT_EQ(p.algorithm, ALGORITHM_RGB565);
EXPECT_EQ(p.bitRate, 0);
}
TEST_F(QualityAdaptiveTest, Profile_Good) {
const auto& p = g_QualityProfiles[QUALITY_GOOD];
EXPECT_EQ(p.maxFPS, 20);
EXPECT_EQ(p.maxWidth, 1920); // 1080p
EXPECT_EQ(p.algorithm, ALGORITHM_H264);
EXPECT_EQ(p.bitRate, 3000);
}
TEST_F(QualityAdaptiveTest, Profile_Medium) {
const auto& p = g_QualityProfiles[QUALITY_MEDIUM];
EXPECT_EQ(p.maxFPS, 15);
EXPECT_EQ(p.maxWidth, 1600); // 900p
EXPECT_EQ(p.algorithm, ALGORITHM_H264);
EXPECT_EQ(p.bitRate, 2000);
}
TEST_F(QualityAdaptiveTest, Profile_Low) {
const auto& p = g_QualityProfiles[QUALITY_LOW];
EXPECT_EQ(p.maxFPS, 12);
EXPECT_EQ(p.maxWidth, 1280); // 720p
EXPECT_EQ(p.algorithm, ALGORITHM_H264);
EXPECT_EQ(p.bitRate, 1200);
}
TEST_F(QualityAdaptiveTest, Profile_Minimal) {
const auto& p = g_QualityProfiles[QUALITY_MINIMAL];
EXPECT_EQ(p.maxFPS, 8);
EXPECT_EQ(p.maxWidth, 1024); // 540p
EXPECT_EQ(p.algorithm, ALGORITHM_H264);
EXPECT_EQ(p.bitRate, 800);
}
// ============================================
// 防抖策略测试 - 启动延迟
// ============================================
TEST_F(QualityAdaptiveTest, Debounce_StartupDelay) {
debouncer.SetStartTime(0);
// 启动后60秒内不应改变
EXPECT_EQ(debouncer.Evaluate(QUALITY_MINIMAL, 1000), -1);
EXPECT_EQ(debouncer.Evaluate(QUALITY_MINIMAL, 30000), -1);
EXPECT_EQ(debouncer.Evaluate(QUALITY_MINIMAL, 59999), -1);
}
TEST_F(QualityAdaptiveTest, Debounce_AfterStartupDelay) {
debouncer.SetStartTime(0);
// 启动60秒后应该可以改变
// 需要连续2次降级请求
debouncer.Evaluate(QUALITY_MINIMAL, 60000); // 第1次
int result = debouncer.Evaluate(QUALITY_MINIMAL, 60001); // 第2次
EXPECT_EQ(result, QUALITY_MINIMAL);
}
// ============================================
// 防抖策略测试 - 降级
// ============================================
TEST_F(QualityAdaptiveTest, Debounce_Downgrade_RequiresTwice) {
debouncer.SetStartTime(0);
uint64_t time = 60000;
// 第1次降级请求 - 不应立即执行
int result = debouncer.Evaluate(QUALITY_MINIMAL, time);
EXPECT_EQ(result, -1);
EXPECT_EQ(debouncer.GetStableCount(), 1);
// 第2次降级请求 - 应该执行
result = debouncer.Evaluate(QUALITY_MINIMAL, time + 100);
EXPECT_EQ(result, QUALITY_MINIMAL);
EXPECT_EQ(debouncer.GetCurrentLevel(), QUALITY_MINIMAL);
}
TEST_F(QualityAdaptiveTest, Debounce_Downgrade_ResetOnStable) {
debouncer.SetStartTime(0);
uint64_t time = 60000;
// 第1次降级请求
debouncer.Evaluate(QUALITY_LOW, time);
EXPECT_EQ(debouncer.GetStableCount(), 1);
// 目标等级恢复 - 计数应重置
debouncer.Evaluate(QUALITY_HIGH, time + 100); // 当前等级
EXPECT_EQ(debouncer.GetStableCount(), 0);
}
// ============================================
// 防抖策略测试 - 升级
// ============================================
TEST_F(QualityAdaptiveTest, Debounce_Upgrade_RequiresFiveTimes) {
debouncer.SetStartTime(0);
uint64_t time = 60000;
// 先降级到 MINIMAL
debouncer.Evaluate(QUALITY_MINIMAL, time);
debouncer.Evaluate(QUALITY_MINIMAL, time + 100);
EXPECT_EQ(debouncer.GetCurrentLevel(), QUALITY_MINIMAL);
// 尝试升级到 ULTRA (需要5次)
time += 5000; // 冷却后
for (int i = 0; i < 4; i++) {
int result = debouncer.Evaluate(QUALITY_ULTRA, time + i * 100);
EXPECT_EQ(result, -1); // 前4次不应执行
EXPECT_EQ(debouncer.GetStableCount(), i + 1);
}
// 第5次应该执行但只升一级
int result = debouncer.Evaluate(QUALITY_ULTRA, time + 500);
EXPECT_EQ(result, QUALITY_LOW); // MINIMAL -> LOW (只升一级)
}
TEST_F(QualityAdaptiveTest, Debounce_Upgrade_OneStepAtATime) {
debouncer.SetStartTime(0);
uint64_t time = 60000;
// 降级到 MINIMAL
debouncer.Evaluate(QUALITY_MINIMAL, time);
debouncer.Evaluate(QUALITY_MINIMAL, time + 100);
EXPECT_EQ(debouncer.GetCurrentLevel(), QUALITY_MINIMAL);
// 多次升级请求,验证每次只升一级
// 从 MINIMAL(5) 升到 HIGH(1) 需要4次升级每次需要5个稳定请求
time += 5000;
int upgradeCount = 0;
while (debouncer.GetCurrentLevel() > QUALITY_HIGH && upgradeCount < 10) {
for (int i = 0; i < 5; i++) {
debouncer.Evaluate(QUALITY_ULTRA, time); // 请求最高等级
time += 100;
}
time += 5000; // 冷却
upgradeCount++;
}
// 最终应该回到 HIGH (或更高)
EXPECT_LE(debouncer.GetCurrentLevel(), QUALITY_HIGH);
}
// ============================================
// 防抖策略测试 - 冷却时间
// ============================================
TEST_F(QualityAdaptiveTest, Debounce_DefaultCooldown) {
debouncer.SetStartTime(0);
uint64_t time = 60000;
// 执行一次降级
debouncer.Evaluate(QUALITY_LOW, time);
debouncer.Evaluate(QUALITY_LOW, time + 100);
EXPECT_EQ(debouncer.GetCurrentLevel(), QUALITY_LOW);
// 冷却期内不应再次改变
int result = debouncer.Evaluate(QUALITY_MINIMAL, time + 200);
EXPECT_EQ(result, -1);
result = debouncer.Evaluate(QUALITY_MINIMAL, time + 2999);
EXPECT_EQ(result, -1);
}
TEST_F(QualityAdaptiveTest, Debounce_AfterCooldown) {
debouncer.SetStartTime(0);
uint64_t time = 60000;
// 执行一次降级
debouncer.Evaluate(QUALITY_LOW, time);
debouncer.Evaluate(QUALITY_LOW, time + 100);
// 冷却后应该可以再次改变
time += 3100;
debouncer.Evaluate(QUALITY_MINIMAL, time);
int result = debouncer.Evaluate(QUALITY_MINIMAL, time + 100);
EXPECT_EQ(result, QUALITY_MINIMAL);
}
// ============================================
// 防抖策略测试 - 分辨率变化冷却
// ============================================
TEST_F(QualityAdaptiveTest, Debounce_ResolutionChange_DowngradeCooldown) {
debouncer.SetStartTime(0);
uint64_t time = 60000;
// 执行一次带分辨率变化的降级
debouncer.Evaluate(QUALITY_LOW, time, true); // resolutionChange=true
debouncer.Evaluate(QUALITY_LOW, time + 100, true);
// 分辨率降级冷却15秒
int result = debouncer.Evaluate(QUALITY_MINIMAL, time + 14000, true);
EXPECT_EQ(result, -1);
// 15秒后可以
time += 16000;
debouncer.Evaluate(QUALITY_MINIMAL, time, true);
result = debouncer.Evaluate(QUALITY_MINIMAL, time + 100, true);
EXPECT_EQ(result, QUALITY_MINIMAL);
}
TEST_F(QualityAdaptiveTest, Debounce_ResolutionChange_UpgradeCooldown) {
debouncer.SetStartTime(0);
uint64_t time = 60000;
// 先降级到 MINIMAL (不带分辨率变化,使用默认冷却)
debouncer.Evaluate(QUALITY_MINIMAL, time);
debouncer.Evaluate(QUALITY_MINIMAL, time + 100);
EXPECT_EQ(debouncer.GetCurrentLevel(), QUALITY_MINIMAL);
// 等待足够长时间(>30秒进行带分辨率变化的升级
time += 35000; // 超过30秒冷却
for (int i = 0; i < 5; i++) {
debouncer.Evaluate(QUALITY_ULTRA, time + i * 100, true); // resolutionChange=true
}
// 应该成功升级一级 (MINIMAL -> LOW)
EXPECT_EQ(debouncer.GetCurrentLevel(), QUALITY_LOW);
// 30秒内不应再次升级带分辨率变化的升级需要30秒冷却
time += 1000;
for (int i = 0; i < 5; i++) {
debouncer.Evaluate(QUALITY_ULTRA, time + i * 100, true);
}
EXPECT_EQ(debouncer.GetCurrentLevel(), QUALITY_LOW); // 未改变还在30秒冷却期内
}
// ============================================
// 禁用自适应测试
// ============================================
TEST_F(QualityAdaptiveTest, Debounce_Disabled) {
debouncer.SetStartTime(0);
debouncer.SetEnabled(false);
uint64_t time = 60000;
int result = debouncer.Evaluate(QUALITY_MINIMAL, time);
EXPECT_EQ(result, -1);
result = debouncer.Evaluate(QUALITY_MINIMAL, time + 100);
EXPECT_EQ(result, -1);
// 质量等级应保持不变
EXPECT_EQ(debouncer.GetCurrentLevel(), QUALITY_HIGH);
}
// ============================================
// 边界值测试
// ============================================
TEST_F(QualityAdaptiveTest, RTT_Boundary_Zero) {
EXPECT_EQ(GetTargetQualityLevel(0, 0), QUALITY_ULTRA);
EXPECT_EQ(GetTargetQualityLevel(0, 1), QUALITY_ULTRA);
}
TEST_F(QualityAdaptiveTest, RTT_Boundary_Negative) {
// 负值RTT应该仍返回最高质量
EXPECT_EQ(GetTargetQualityLevel(-1, 0), QUALITY_ULTRA);
EXPECT_EQ(GetTargetQualityLevel(-100, 0), QUALITY_ULTRA);
}
TEST_F(QualityAdaptiveTest, RTT_Boundary_VeryHigh) {
// 非常高的RTT
EXPECT_EQ(GetTargetQualityLevel(10000, 0), QUALITY_MINIMAL);
EXPECT_EQ(GetTargetQualityLevel(100000, 0), QUALITY_MINIMAL);
EXPECT_EQ(GetTargetQualityLevel(INT_MAX - 1, 0), QUALITY_MINIMAL);
}
// ============================================
// 常量验证测试
// ============================================
TEST(QualityConstantsTest, QualityLevelEnum) {
EXPECT_EQ(QUALITY_DISABLED, -2);
EXPECT_EQ(QUALITY_ADAPTIVE, -1);
EXPECT_EQ(QUALITY_ULTRA, 0);
EXPECT_EQ(QUALITY_HIGH, 1);
EXPECT_EQ(QUALITY_GOOD, 2);
EXPECT_EQ(QUALITY_MEDIUM, 3);
EXPECT_EQ(QUALITY_LOW, 4);
EXPECT_EQ(QUALITY_MINIMAL, 5);
EXPECT_EQ(QUALITY_COUNT, 6);
}
TEST(QualityConstantsTest, ProfileCount) {
// 应该有 QUALITY_COUNT 个配置
EXPECT_EQ(sizeof(g_QualityProfiles) / sizeof(g_QualityProfiles[0]),
static_cast<size_t>(QUALITY_COUNT));
}
TEST(QualityConstantsTest, ThresholdCount) {
// 每行应该有 QUALITY_COUNT 个阈值
EXPECT_EQ(sizeof(g_RttThresholds[0]) / sizeof(g_RttThresholds[0][0]),
static_cast<size_t>(QUALITY_COUNT));
}
TEST(QualityConstantsTest, ThresholdIncreasing) {
// 阈值应该递增
for (int row = 0; row < 2; row++) {
for (int i = 0; i < QUALITY_COUNT - 1; i++) {
EXPECT_LT(g_RttThresholds[row][i], g_RttThresholds[row][i + 1])
<< "Row " << row << ", index " << i;
}
}
}
TEST(QualityConstantsTest, FRPThresholdsHigher) {
// FRP模式阈值应该比直连模式高
for (int i = 0; i < QUALITY_COUNT - 1; i++) {
EXPECT_GT(g_RttThresholds[1][i], g_RttThresholds[0][i])
<< "Index " << i;
}
}
// ============================================
// 参数化测试RTT值遍历
// ============================================
class RTTMappingTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};
TEST_P(RTTMappingTest, RTT_Mapping) {
auto [rtt, usingFRP, expectedLevel] = GetParam();
EXPECT_EQ(GetTargetQualityLevel(rtt, usingFRP), expectedLevel);
}
INSTANTIATE_TEST_SUITE_P(
DirectMode,
RTTMappingTest,
::testing::Values(
std::make_tuple(0, 0, QUALITY_ULTRA),
std::make_tuple(29, 0, QUALITY_ULTRA),
std::make_tuple(30, 0, QUALITY_HIGH),
std::make_tuple(79, 0, QUALITY_HIGH),
std::make_tuple(80, 0, QUALITY_GOOD),
std::make_tuple(149, 0, QUALITY_GOOD),
std::make_tuple(150, 0, QUALITY_MEDIUM),
std::make_tuple(249, 0, QUALITY_MEDIUM),
std::make_tuple(250, 0, QUALITY_LOW),
std::make_tuple(399, 0, QUALITY_LOW),
std::make_tuple(400, 0, QUALITY_MINIMAL),
std::make_tuple(1000, 0, QUALITY_MINIMAL)
)
);
INSTANTIATE_TEST_SUITE_P(
FRPMode,
RTTMappingTest,
::testing::Values(
std::make_tuple(0, 1, QUALITY_ULTRA),
std::make_tuple(59, 1, QUALITY_ULTRA),
std::make_tuple(60, 1, QUALITY_HIGH),
std::make_tuple(159, 1, QUALITY_HIGH),
std::make_tuple(160, 1, QUALITY_GOOD),
std::make_tuple(299, 1, QUALITY_GOOD),
std::make_tuple(300, 1, QUALITY_MEDIUM),
std::make_tuple(499, 1, QUALITY_MEDIUM),
std::make_tuple(500, 1, QUALITY_LOW),
std::make_tuple(799, 1, QUALITY_LOW),
std::make_tuple(800, 1, QUALITY_MINIMAL),
std::make_tuple(2000, 1, QUALITY_MINIMAL)
)
);
// ============================================
// 质量配置合理性测试
// ============================================
TEST(QualityProfileTest, FPSDecreasing) {
// FPS 应该随质量降低而减少
for (int i = 0; i < QUALITY_COUNT - 1; i++) {
EXPECT_GE(g_QualityProfiles[i].maxFPS, g_QualityProfiles[i + 1].maxFPS)
<< "Level " << i;
}
}
TEST(QualityProfileTest, MaxWidthDecreasing) {
// maxWidth 应该随质量降低而减少除了0表示不限
int prevWidth = INT_MAX;
for (int i = 0; i < QUALITY_COUNT; i++) {
int width = g_QualityProfiles[i].maxWidth;
if (width > 0) {
EXPECT_LE(width, prevWidth) << "Level " << i;
prevWidth = width;
}
}
}
TEST(QualityProfileTest, BitRateDecreasing) {
// H264 bitRate 应该随质量降低而减少
int prevBitRate = INT_MAX;
for (int i = 0; i < QUALITY_COUNT; i++) {
if (g_QualityProfiles[i].algorithm == ALGORITHM_H264) {
int bitRate = g_QualityProfiles[i].bitRate;
EXPECT_LE(bitRate, prevBitRate) << "Level " << i;
prevBitRate = bitRate;
}
}
}

View File

@@ -0,0 +1,597 @@
// RGB565Test.cpp - Phase 4: RGB565压缩单元测试
// 测试 BGRA <-> RGB565 颜色空间转换
#include <gtest/gtest.h>
#include <vector>
#include <cstdint>
#include <cstring>
#include <random>
#include <cmath>
// ============================================
// RGB565 颜色空间转换实现
// 来源: client/ScreenCapture.h, server/ScreenSpyDlg.cpp
// ============================================
// RGB565 格式说明:
// 16位: RRRRRGGG GGGBBBBB
// R: 5位 (0-31)
// G: 6位 (0-63)
// B: 5位 (0-31)
class RGB565Converter {
public:
// ============================================
// BGRA -> RGB565 转换 (标量版本)
// ============================================
static void ConvertBGRAtoRGB565_Scalar(
const uint8_t* src,
uint16_t* dst,
size_t pixelCount
) {
for (size_t i = 0; i < pixelCount; i++) {
// BGRA 格式: B=0, G=1, R=2, A=3
uint8_t b = src[i * 4 + 0];
uint8_t g = src[i * 4 + 1];
uint8_t r = src[i * 4 + 2];
// A 通道被忽略
// RGB565: RRRRRGGG GGGBBBBB
dst[i] = static_cast<uint16_t>(
((r >> 3) << 11) | // R: 高5位 -> 位11-15
((g >> 2) << 5) | // G: 高6位 -> 位5-10
(b >> 3) // B: 高5位 -> 位0-4
);
}
}
// ============================================
// RGB565 -> BGRA 转换
// 位复制填充低位以提高精度
// ============================================
static void ConvertRGB565ToBGRA(
const uint16_t* src,
uint8_t* dst,
size_t pixelCount
) {
for (size_t i = 0; i < pixelCount; i++) {
uint16_t c = src[i];
// 提取各通道
uint8_t r5 = (c >> 11) & 0x1F; // 5位红色
uint8_t g6 = (c >> 5) & 0x3F; // 6位绿色
uint8_t b5 = c & 0x1F; // 5位蓝色
// 扩展到8位使用位复制填充低位
// 例如: 5位 11111 -> 8位 11111111 (通过 (x << 3) | (x >> 2))
dst[i * 4 + 0] = (b5 << 3) | (b5 >> 2); // B: 5->8位
dst[i * 4 + 1] = (g6 << 2) | (g6 >> 4); // G: 6->8位
dst[i * 4 + 2] = (r5 << 3) | (r5 >> 2); // R: 5->8位
dst[i * 4 + 3] = 0xFF; // A: 不透明
}
}
// ============================================
// 单像素转换辅助函数
// ============================================
static uint16_t BGRAToRGB565(uint8_t b, uint8_t g, uint8_t r) {
return static_cast<uint16_t>(
((r >> 3) << 11) |
((g >> 2) << 5) |
(b >> 3)
);
}
static void RGB565ToBGRA(uint16_t c, uint8_t& b, uint8_t& g, uint8_t& r, uint8_t& a) {
uint8_t r5 = (c >> 11) & 0x1F;
uint8_t g6 = (c >> 5) & 0x3F;
uint8_t b5 = c & 0x1F;
b = (b5 << 3) | (b5 >> 2);
g = (g6 << 2) | (g6 >> 4);
r = (r5 << 3) | (r5 >> 2);
a = 0xFF;
}
// ============================================
// 计算量化误差
// ============================================
static int CalculateError(uint8_t original, uint8_t converted) {
return std::abs(static_cast<int>(original) - static_cast<int>(converted));
}
// 计算最大理论误差
// 5位通道: 量化+位填充可能产生最大误差 7
// 6位通道: 量化+位填充可能产生最大误差 3
static int MaxError5Bit() { return 7; }
static int MaxError6Bit() { return 3; }
};
// ============================================
// 测试夹具
// ============================================
class RGB565Test : public ::testing::Test {
protected:
// 创建BGRA像素数组
static std::vector<uint8_t> CreateBGRAPixels(size_t count, uint8_t b, uint8_t g, uint8_t r, uint8_t a = 0xFF) {
std::vector<uint8_t> pixels(count * 4);
for (size_t i = 0; i < count; i++) {
pixels[i * 4 + 0] = b;
pixels[i * 4 + 1] = g;
pixels[i * 4 + 2] = r;
pixels[i * 4 + 3] = a;
}
return pixels;
}
};
// ============================================
// 基础转换测试
// ============================================
TEST_F(RGB565Test, SinglePixel_Black) {
uint16_t result = RGB565Converter::BGRAToRGB565(0, 0, 0);
EXPECT_EQ(result, 0x0000);
}
TEST_F(RGB565Test, SinglePixel_White) {
uint16_t result = RGB565Converter::BGRAToRGB565(255, 255, 255);
// R=31<<11=0xF800, G=63<<5=0x07E0, B=31=0x001F
// 合计: 0xFFFF
EXPECT_EQ(result, 0xFFFF);
}
TEST_F(RGB565Test, SinglePixel_Red) {
uint16_t result = RGB565Converter::BGRAToRGB565(0, 0, 255);
// R=31<<11=0xF800, G=0, B=0
EXPECT_EQ(result, 0xF800);
}
TEST_F(RGB565Test, SinglePixel_Green) {
uint16_t result = RGB565Converter::BGRAToRGB565(0, 255, 0);
// R=0, G=63<<5=0x07E0, B=0
EXPECT_EQ(result, 0x07E0);
}
TEST_F(RGB565Test, SinglePixel_Blue) {
uint16_t result = RGB565Converter::BGRAToRGB565(255, 0, 0);
// R=0, G=0, B=31=0x001F
EXPECT_EQ(result, 0x001F);
}
// ============================================
// 反向转换测试
// ============================================
TEST_F(RGB565Test, Reverse_Black) {
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(0x0000, b, g, r, a);
EXPECT_EQ(b, 0);
EXPECT_EQ(g, 0);
EXPECT_EQ(r, 0);
EXPECT_EQ(a, 255);
}
TEST_F(RGB565Test, Reverse_White) {
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(0xFFFF, b, g, r, a);
EXPECT_EQ(b, 255);
EXPECT_EQ(g, 255);
EXPECT_EQ(r, 255);
EXPECT_EQ(a, 255);
}
TEST_F(RGB565Test, Reverse_Red) {
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(0xF800, b, g, r, a);
EXPECT_EQ(b, 0);
EXPECT_EQ(g, 0);
EXPECT_EQ(r, 255);
EXPECT_EQ(a, 255);
}
TEST_F(RGB565Test, Reverse_Green) {
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(0x07E0, b, g, r, a);
EXPECT_EQ(b, 0);
EXPECT_EQ(g, 255);
EXPECT_EQ(r, 0);
EXPECT_EQ(a, 255);
}
TEST_F(RGB565Test, Reverse_Blue) {
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(0x001F, b, g, r, a);
EXPECT_EQ(b, 255);
EXPECT_EQ(g, 0);
EXPECT_EQ(r, 0);
EXPECT_EQ(a, 255);
}
// ============================================
// 往返转换测试
// ============================================
TEST_F(RGB565Test, RoundTrip_ExactColors) {
// 测试能精确表示的颜色 (只有 0 和 255 能完美往返)
// 位填充公式: (x << 3) | (x >> 2) 只有 x=0 -> 0, x=31 -> 255 是精确的
struct TestCase {
uint8_t b, g, r;
} testCases[] = {
{0, 0, 0}, // 黑色 - 精确
{255, 255, 255}, // 白色 - 精确
{0, 0, 255}, // 红色 - 精确
{0, 255, 0}, // 绿色 - 精确
{255, 0, 0}, // 蓝色 - 精确
{255, 255, 0}, // 青色 - 精确
{0, 255, 255}, // 黄色 - 精确
{255, 0, 255}, // 品红 - 精确
};
for (const auto& tc : testCases) {
uint16_t rgb565 = RGB565Converter::BGRAToRGB565(tc.b, tc.g, tc.r);
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(rgb565, b, g, r, a);
// 能精确表示的颜色应该完美还原
EXPECT_EQ(b, tc.b) << "B channel mismatch for (" << (int)tc.r << "," << (int)tc.g << "," << (int)tc.b << ")";
EXPECT_EQ(g, tc.g) << "G channel mismatch for (" << (int)tc.r << "," << (int)tc.g << "," << (int)tc.b << ")";
EXPECT_EQ(r, tc.r) << "R channel mismatch for (" << (int)tc.r << "," << (int)tc.g << "," << (int)tc.b << ")";
}
}
TEST_F(RGB565Test, RoundTrip_QuantizationError) {
// 测试所有颜色的量化误差在允许范围内
std::mt19937 rng(12345);
std::uniform_int_distribution<int> dist(0, 255);
for (int i = 0; i < 1000; i++) {
uint8_t origB = dist(rng);
uint8_t origG = dist(rng);
uint8_t origR = dist(rng);
uint16_t rgb565 = RGB565Converter::BGRAToRGB565(origB, origG, origR);
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(rgb565, b, g, r, a);
// 验证误差在允许范围内
EXPECT_LE(RGB565Converter::CalculateError(origB, b), RGB565Converter::MaxError5Bit());
EXPECT_LE(RGB565Converter::CalculateError(origG, g), RGB565Converter::MaxError6Bit());
EXPECT_LE(RGB565Converter::CalculateError(origR, r), RGB565Converter::MaxError5Bit());
}
}
// ============================================
// 批量转换测试
// ============================================
TEST_F(RGB565Test, BatchConvert_SinglePixel) {
auto bgra = CreateBGRAPixels(1, 128, 64, 192);
std::vector<uint16_t> rgb565(1);
RGB565Converter::ConvertBGRAtoRGB565_Scalar(bgra.data(), rgb565.data(), 1);
// 验证转换结果
uint16_t expected = RGB565Converter::BGRAToRGB565(128, 64, 192);
EXPECT_EQ(rgb565[0], expected);
}
TEST_F(RGB565Test, BatchConvert_MultiplePixels) {
const size_t COUNT = 100;
auto bgra = CreateBGRAPixels(COUNT, 100, 150, 200);
std::vector<uint16_t> rgb565(COUNT);
RGB565Converter::ConvertBGRAtoRGB565_Scalar(bgra.data(), rgb565.data(), COUNT);
uint16_t expected = RGB565Converter::BGRAToRGB565(100, 150, 200);
for (size_t i = 0; i < COUNT; i++) {
EXPECT_EQ(rgb565[i], expected);
}
}
TEST_F(RGB565Test, BatchReverse_MultiplePixels) {
const size_t COUNT = 100;
std::vector<uint16_t> rgb565(COUNT, 0xF800); // 红色
std::vector<uint8_t> bgra(COUNT * 4);
RGB565Converter::ConvertRGB565ToBGRA(rgb565.data(), bgra.data(), COUNT);
for (size_t i = 0; i < COUNT; i++) {
EXPECT_EQ(bgra[i * 4 + 0], 0); // B
EXPECT_EQ(bgra[i * 4 + 1], 0); // G
EXPECT_EQ(bgra[i * 4 + 2], 255); // R
EXPECT_EQ(bgra[i * 4 + 3], 255); // A
}
}
TEST_F(RGB565Test, BatchRoundTrip) {
const size_t COUNT = 1000;
std::mt19937 rng(42);
std::uniform_int_distribution<int> dist(0, 255);
// 创建随机BGRA数据
std::vector<uint8_t> original(COUNT * 4);
for (size_t i = 0; i < COUNT * 4; i++) {
original[i] = (i % 4 == 3) ? 255 : dist(rng);
}
// 转换到 RGB565
std::vector<uint16_t> rgb565(COUNT);
RGB565Converter::ConvertBGRAtoRGB565_Scalar(original.data(), rgb565.data(), COUNT);
// 转换回 BGRA
std::vector<uint8_t> reconstructed(COUNT * 4);
RGB565Converter::ConvertRGB565ToBGRA(rgb565.data(), reconstructed.data(), COUNT);
// 验证所有像素误差在允许范围内
for (size_t i = 0; i < COUNT; i++) {
EXPECT_LE(RGB565Converter::CalculateError(original[i * 4 + 0], reconstructed[i * 4 + 0]),
RGB565Converter::MaxError5Bit()) << "Pixel " << i << " B";
EXPECT_LE(RGB565Converter::CalculateError(original[i * 4 + 1], reconstructed[i * 4 + 1]),
RGB565Converter::MaxError6Bit()) << "Pixel " << i << " G";
EXPECT_LE(RGB565Converter::CalculateError(original[i * 4 + 2], reconstructed[i * 4 + 2]),
RGB565Converter::MaxError5Bit()) << "Pixel " << i << " R";
EXPECT_EQ(reconstructed[i * 4 + 3], 255) << "Pixel " << i << " A";
}
}
// ============================================
// 边界值测试
// ============================================
TEST_F(RGB565Test, Boundary_AllZeros) {
uint16_t result = RGB565Converter::BGRAToRGB565(0, 0, 0);
EXPECT_EQ(result, 0);
}
TEST_F(RGB565Test, Boundary_AllOnes) {
uint16_t result = RGB565Converter::BGRAToRGB565(255, 255, 255);
EXPECT_EQ(result, 0xFFFF);
}
TEST_F(RGB565Test, Boundary_ChannelMax) {
// 单通道最大值
EXPECT_EQ(RGB565Converter::BGRAToRGB565(255, 0, 0), 0x001F); // B
EXPECT_EQ(RGB565Converter::BGRAToRGB565(0, 255, 0), 0x07E0); // G
EXPECT_EQ(RGB565Converter::BGRAToRGB565(0, 0, 255), 0xF800); // R
}
TEST_F(RGB565Test, Boundary_ChannelMin) {
// 单通道最小值(其他为最大)
EXPECT_EQ(RGB565Converter::BGRAToRGB565(0, 255, 255), 0xFFE0); // 无B
EXPECT_EQ(RGB565Converter::BGRAToRGB565(255, 0, 255), 0xF81F); // 无G
EXPECT_EQ(RGB565Converter::BGRAToRGB565(255, 255, 0), 0x07FF); // 无R
}
// ============================================
// 位填充测试
// ============================================
TEST_F(RGB565Test, BitFilling_5BitExpansion) {
// 测试5位扩展到8位的位填充策略
// 5位值 11111 (31) -> 8位值 11111111 (255)
// 公式: (x << 3) | (x >> 2)
// 最大值: 31 -> 255
uint8_t expanded = (31 << 3) | (31 >> 2);
EXPECT_EQ(expanded, 255);
// 最小值: 0 -> 0
expanded = (0 << 3) | (0 >> 2);
EXPECT_EQ(expanded, 0);
// 中间值: 16 -> 132 (10000 -> 10000100)
expanded = (16 << 3) | (16 >> 2);
EXPECT_EQ(expanded, 132);
}
TEST_F(RGB565Test, BitFilling_6BitExpansion) {
// 测试6位扩展到8位的位填充策略
// 6位值 111111 (63) -> 8位值 11111111 (255)
// 公式: (x << 2) | (x >> 4)
// 最大值: 63 -> 255
uint8_t expanded = (63 << 2) | (63 >> 4);
EXPECT_EQ(expanded, 255);
// 最小值: 0 -> 0
expanded = (0 << 2) | (0 >> 4);
EXPECT_EQ(expanded, 0);
// 中间值: 32 -> 130 (100000 -> 10000010)
expanded = (32 << 2) | (32 >> 4);
EXPECT_EQ(expanded, 130);
}
// ============================================
// Alpha通道测试
// ============================================
TEST_F(RGB565Test, Alpha_Ignored) {
// 不同alpha值应该产生相同的RGB565
auto bgra1 = CreateBGRAPixels(1, 100, 100, 100, 255);
auto bgra2 = CreateBGRAPixels(1, 100, 100, 100, 128);
auto bgra3 = CreateBGRAPixels(1, 100, 100, 100, 0);
std::vector<uint16_t> rgb565_1(1), rgb565_2(1), rgb565_3(1);
RGB565Converter::ConvertBGRAtoRGB565_Scalar(bgra1.data(), rgb565_1.data(), 1);
RGB565Converter::ConvertBGRAtoRGB565_Scalar(bgra2.data(), rgb565_2.data(), 1);
RGB565Converter::ConvertBGRAtoRGB565_Scalar(bgra3.data(), rgb565_3.data(), 1);
EXPECT_EQ(rgb565_1[0], rgb565_2[0]);
EXPECT_EQ(rgb565_2[0], rgb565_3[0]);
}
TEST_F(RGB565Test, Alpha_RestoredToOpaque) {
// 反向转换时Alpha应该恢复为255
std::vector<uint16_t> rgb565 = {0x1234, 0x5678, 0x9ABC};
std::vector<uint8_t> bgra(3 * 4);
RGB565Converter::ConvertRGB565ToBGRA(rgb565.data(), bgra.data(), 3);
for (size_t i = 0; i < 3; i++) {
EXPECT_EQ(bgra[i * 4 + 3], 255);
}
}
// ============================================
// 压缩率测试
// ============================================
TEST_F(RGB565Test, CompressionRatio) {
// BGRA: 4字节/像素
// RGB565: 2字节/像素
// 压缩率: 50%
const size_t PIXEL_COUNT = 1920 * 1080;
size_t bgraSize = PIXEL_COUNT * 4;
size_t rgb565Size = PIXEL_COUNT * 2;
EXPECT_EQ(rgb565Size, bgraSize / 2);
EXPECT_DOUBLE_EQ(static_cast<double>(rgb565Size) / bgraSize, 0.5);
}
// ============================================
// 特殊颜色测试
// ============================================
TEST_F(RGB565Test, CommonColors) {
struct ColorTest {
const char* name;
uint8_t r, g, b;
uint16_t expectedRGB565;
} colors[] = {
{"Black", 0, 0, 0, 0x0000},
{"White", 255, 255, 255, 0xFFFF},
{"Red", 255, 0, 0, 0xF800},
{"Green", 0, 255, 0, 0x07E0},
{"Blue", 0, 0, 255, 0x001F},
{"Yellow", 255, 255, 0, 0xFFE0},
{"Cyan", 0, 255, 255, 0x07FF},
{"Magenta", 255, 0, 255, 0xF81F},
};
for (const auto& c : colors) {
uint16_t result = RGB565Converter::BGRAToRGB565(c.b, c.g, c.r);
EXPECT_EQ(result, c.expectedRGB565) << "Color: " << c.name;
}
}
TEST_F(RGB565Test, GrayScales) {
// 测试灰度值转换
for (int gray = 0; gray <= 255; gray += 17) {
uint8_t g = static_cast<uint8_t>(gray);
uint16_t rgb565 = RGB565Converter::BGRAToRGB565(g, g, g);
uint8_t b, gr, r, a;
RGB565Converter::RGB565ToBGRA(rgb565, b, gr, r, a);
// 灰度值在量化误差范围内应该保持一致
EXPECT_NEAR(b, g, 8);
EXPECT_NEAR(gr, g, 4);
EXPECT_NEAR(r, g, 8);
}
}
// ============================================
// 参数化测试
// ============================================
class RGB565ChannelTest : public ::testing::TestWithParam<int> {};
TEST_P(RGB565ChannelTest, ChannelValueRange) {
int value = GetParam();
uint8_t v = static_cast<uint8_t>(value);
// 测试R通道
{
uint16_t rgb565 = RGB565Converter::BGRAToRGB565(0, 0, v);
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(rgb565, b, g, r, a);
EXPECT_LE(RGB565Converter::CalculateError(v, r), RGB565Converter::MaxError5Bit());
}
// 测试G通道
{
uint16_t rgb565 = RGB565Converter::BGRAToRGB565(0, v, 0);
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(rgb565, b, g, r, a);
EXPECT_LE(RGB565Converter::CalculateError(v, g), RGB565Converter::MaxError6Bit());
}
// 测试B通道
{
uint16_t rgb565 = RGB565Converter::BGRAToRGB565(v, 0, 0);
uint8_t b, g, r, a;
RGB565Converter::RGB565ToBGRA(rgb565, b, g, r, a);
EXPECT_LE(RGB565Converter::CalculateError(v, b), RGB565Converter::MaxError5Bit());
}
}
INSTANTIATE_TEST_SUITE_P(
AllValues,
RGB565ChannelTest,
::testing::Range(0, 256, 1) // 测试0-255所有值
);
// ============================================
// 大数据量测试
// ============================================
TEST_F(RGB565Test, LargeFrame_1080p) {
const size_t WIDTH = 1920, HEIGHT = 1080;
const size_t COUNT = WIDTH * HEIGHT;
// 创建渐变图像
std::vector<uint8_t> bgra(COUNT * 4);
for (size_t y = 0; y < HEIGHT; y++) {
for (size_t x = 0; x < WIDTH; x++) {
size_t idx = (y * WIDTH + x) * 4;
bgra[idx + 0] = static_cast<uint8_t>(x * 255 / WIDTH);
bgra[idx + 1] = static_cast<uint8_t>(y * 255 / HEIGHT);
bgra[idx + 2] = static_cast<uint8_t>((x + y) * 127 / (WIDTH + HEIGHT));
bgra[idx + 3] = 255;
}
}
// 转换
std::vector<uint16_t> rgb565(COUNT);
RGB565Converter::ConvertBGRAtoRGB565_Scalar(bgra.data(), rgb565.data(), COUNT);
// 验证大小
EXPECT_EQ(rgb565.size() * sizeof(uint16_t), COUNT * 2);
// 抽样验证
for (size_t i = 0; i < COUNT; i += COUNT / 100) {
uint16_t expected = RGB565Converter::BGRAToRGB565(bgra[i * 4 + 0], bgra[i * 4 + 1], bgra[i * 4 + 2]);
EXPECT_EQ(rgb565[i], expected) << "Mismatch at pixel " << i;
}
}
// ============================================
// 端序测试
// ============================================
TEST_F(RGB565Test, Endianness_LittleEndian) {
// RGB565 应该以小端存储
uint16_t rgb565 = 0x1234;
uint8_t* bytes = reinterpret_cast<uint8_t*>(&rgb565);
// 在小端系统上: bytes[0] = 0x34, bytes[1] = 0x12
EXPECT_EQ(bytes[0], 0x34);
EXPECT_EQ(bytes[1], 0x12);
}
// ============================================
// 错误处理测试
// ============================================
TEST_F(RGB565Test, ZeroPixelCount) {
std::vector<uint8_t> bgra(4);
std::vector<uint16_t> rgb565(1);
// 零像素不应崩溃
RGB565Converter::ConvertBGRAtoRGB565_Scalar(bgra.data(), rgb565.data(), 0);
RGB565Converter::ConvertRGB565ToBGRA(rgb565.data(), bgra.data(), 0);
}

View File

@@ -0,0 +1,686 @@
// ScrollDetectorTest.cpp - Phase 4: 滚动检测单元测试
// 测试屏幕滚动检测和优化传输
#include <gtest/gtest.h>
#include <vector>
#include <cstdint>
#include <cstring>
#include <random>
#include <algorithm>
// ============================================
// 滚动检测常量定义 (来自 ScrollDetector.h)
// ============================================
#define MIN_SCROLL_LINES 16 // 最小滚动行数
#define MAX_SCROLL_RATIO 4 // 最大滚动 = 高度 / 4
#define MATCH_THRESHOLD 85 // 行匹配百分比阈值 (85%)
// 滚动方向常量
#define SCROLL_DIR_UP 0 // 向上滚动(内容向下移动)
#define SCROLL_DIR_DOWN 1 // 向下滚动(内容向上移动)
// ============================================
// CRC32 哈希计算
// ============================================
class CRC32 {
public:
static uint32_t Calculate(const uint8_t* data, size_t length) {
static uint32_t table[256] = {0};
static bool tableInit = false;
if (!tableInit) {
for (uint32_t i = 0; i < 256; i++) {
uint32_t c = i;
for (int j = 0; j < 8; j++) {
c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1);
}
table[i] = c;
}
tableInit = true;
}
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < length; i++) {
crc = table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
}
return crc ^ 0xFFFFFFFF;
}
};
// ============================================
// 滚动检测器实现 (模拟 ScrollDetector.h)
// ============================================
class CScrollDetector {
public:
CScrollDetector(int width, int height, int bpp = 4)
: m_width(width), m_height(height), m_bpp(bpp)
, m_stride(width * bpp)
, m_minScroll(MIN_SCROLL_LINES)
, m_maxScroll(height / MAX_SCROLL_RATIO)
{
m_rowHashes.resize(height);
}
// 检测垂直滚动
// 返回: >0 向下滚动, <0 向上滚动, 0 无滚动
int DetectVerticalScroll(const uint8_t* prevFrame, const uint8_t* currFrame) {
if (!prevFrame || !currFrame) return 0;
// 计算当前帧的行哈希
std::vector<uint32_t> currHashes(m_height);
for (int y = 0; y < m_height; y++) {
currHashes[y] = CRC32::Calculate(currFrame + y * m_stride, m_stride);
}
// 计算前一帧的行哈希
std::vector<uint32_t> prevHashes(m_height);
for (int y = 0; y < m_height; y++) {
prevHashes[y] = CRC32::Calculate(prevFrame + y * m_stride, m_stride);
}
int bestScroll = 0;
int bestMatchCount = 0;
// 尝试各种滚动量
for (int scroll = m_minScroll; scroll <= m_maxScroll; scroll++) {
// 向下滚动 (正值)
int matchDown = CountMatchingRows(prevHashes, currHashes, scroll);
if (matchDown > bestMatchCount) {
bestMatchCount = matchDown;
bestScroll = scroll;
}
// 向上滚动 (负值)
int matchUp = CountMatchingRows(prevHashes, currHashes, -scroll);
if (matchUp > bestMatchCount) {
bestMatchCount = matchUp;
bestScroll = -scroll;
}
}
// 检查是否达到匹配阈值
int scrollAbs = std::abs(bestScroll);
int totalRows = m_height - scrollAbs;
int threshold = totalRows * MATCH_THRESHOLD / 100;
if (bestMatchCount >= threshold) {
return bestScroll;
}
return 0;
}
// 获取边缘区域信息
void GetEdgeRegion(int scrollAmount, int* outOffset, int* outPixelCount) const {
if (scrollAmount > 0) {
// 向下滚动: 新内容在底部 (BMP底上格式: 低地址)
*outOffset = 0;
*outPixelCount = scrollAmount * m_width;
} else if (scrollAmount < 0) {
// 向上滚动: 新内容在顶部 (BMP底上格式: 高地址)
*outOffset = (m_height + scrollAmount) * m_stride;
*outPixelCount = (-scrollAmount) * m_width;
} else {
*outOffset = 0;
*outPixelCount = 0;
}
}
int GetMinScroll() const { return m_minScroll; }
int GetMaxScroll() const { return m_maxScroll; }
int GetWidth() const { return m_width; }
int GetHeight() const { return m_height; }
private:
int CountMatchingRows(const std::vector<uint32_t>& prevHashes,
const std::vector<uint32_t>& currHashes,
int scroll) const {
int matchCount = 0;
if (scroll > 0) {
// 向下滚动: prev[y] 对应 curr[y + scroll]
for (int y = 0; y < m_height - scroll; y++) {
if (prevHashes[y] == currHashes[y + scroll]) {
matchCount++;
}
}
} else if (scroll < 0) {
// 向上滚动: prev[y] 对应 curr[y + scroll] (scroll为负)
int absScroll = -scroll;
for (int y = absScroll; y < m_height; y++) {
if (prevHashes[y] == currHashes[y + scroll]) {
matchCount++;
}
}
}
return matchCount;
}
int m_width;
int m_height;
int m_bpp;
int m_stride;
int m_minScroll;
int m_maxScroll;
std::vector<uint32_t> m_rowHashes;
};
// ============================================
// 测试夹具
// ============================================
class ScrollDetectorTest : public ::testing::Test {
public:
// 创建纯色帧
static std::vector<uint8_t> CreateSolidFrame(int width, int height,
uint8_t b, uint8_t g,
uint8_t r, uint8_t a = 0xFF) {
std::vector<uint8_t> frame(width * height * 4);
for (int i = 0; i < width * height; i++) {
frame[i * 4 + 0] = b;
frame[i * 4 + 1] = g;
frame[i * 4 + 2] = r;
frame[i * 4 + 3] = a;
}
return frame;
}
// 创建带条纹的帧 (每行不同颜色,便于检测滚动)
static std::vector<uint8_t> CreateStripedFrame(int width, int height) {
std::vector<uint8_t> frame(width * height * 4);
for (int y = 0; y < height; y++) {
uint8_t color = static_cast<uint8_t>(y % 256);
for (int x = 0; x < width; x++) {
int idx = (y * width + x) * 4;
frame[idx + 0] = color;
frame[idx + 1] = color;
frame[idx + 2] = color;
frame[idx + 3] = 0xFF;
}
}
return frame;
}
// 模拟向下滚动 (内容向上移动)
static std::vector<uint8_t> SimulateScrollDown(const std::vector<uint8_t>& frame,
int width, int height,
int scrollAmount) {
std::vector<uint8_t> result(frame.size());
int stride = width * 4;
// 复制滚动后的内容
for (int y = scrollAmount; y < height; y++) {
memcpy(result.data() + (y - scrollAmount) * stride,
frame.data() + y * stride, stride);
}
// 底部新内容用不同颜色填充
for (int y = height - scrollAmount; y < height; y++) {
for (int x = 0; x < width; x++) {
int idx = (y * width + x) * 4;
result[idx + 0] = 0xFF; // 新内容用白色
result[idx + 1] = 0xFF;
result[idx + 2] = 0xFF;
result[idx + 3] = 0xFF;
}
}
return result;
}
// 模拟向上滚动 (内容向下移动)
static std::vector<uint8_t> SimulateScrollUp(const std::vector<uint8_t>& frame,
int width, int height,
int scrollAmount) {
std::vector<uint8_t> result(frame.size());
int stride = width * 4;
// 复制滚动后的内容
for (int y = 0; y < height - scrollAmount; y++) {
memcpy(result.data() + (y + scrollAmount) * stride,
frame.data() + y * stride, stride);
}
// 顶部新内容用不同颜色填充
for (int y = 0; y < scrollAmount; y++) {
for (int x = 0; x < width; x++) {
int idx = (y * width + x) * 4;
result[idx + 0] = 0x00; // 新内容用黑色
result[idx + 1] = 0x00;
result[idx + 2] = 0x00;
result[idx + 3] = 0xFF;
}
}
return result;
}
};
// ============================================
// 基础功能测试
// ============================================
TEST_F(ScrollDetectorTest, Constructor_ValidParameters) {
CScrollDetector detector(100, 200);
EXPECT_EQ(detector.GetWidth(), 100);
EXPECT_EQ(detector.GetHeight(), 200);
EXPECT_EQ(detector.GetMinScroll(), MIN_SCROLL_LINES);
EXPECT_EQ(detector.GetMaxScroll(), 200 / MAX_SCROLL_RATIO);
}
TEST_F(ScrollDetectorTest, IdenticalFrames_NoScroll) {
const int WIDTH = 100, HEIGHT = 100;
auto frame = CreateStripedFrame(WIDTH, HEIGHT);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame.data(), frame.data());
EXPECT_EQ(scroll, 0);
}
TEST_F(ScrollDetectorTest, CompletelyDifferent_NoScroll) {
const int WIDTH = 100, HEIGHT = 100;
auto frame1 = CreateSolidFrame(WIDTH, HEIGHT, 0, 0, 0);
auto frame2 = CreateSolidFrame(WIDTH, HEIGHT, 255, 255, 255);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
EXPECT_EQ(scroll, 0);
}
// ============================================
// 向下滚动检测测试
// ============================================
TEST_F(ScrollDetectorTest, ScrollDown_MinimalScroll) {
const int WIDTH = 100, HEIGHT = 100;
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollDown(frame1, WIDTH, HEIGHT, MIN_SCROLL_LINES);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
// 检测到滚动(绝对值匹配,方向取决于实现)
EXPECT_NE(scroll, 0);
EXPECT_EQ(std::abs(scroll), MIN_SCROLL_LINES);
}
TEST_F(ScrollDetectorTest, ScrollDown_MediumScroll) {
const int WIDTH = 100, HEIGHT = 100;
const int SCROLL = 20;
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollDown(frame1, WIDTH, HEIGHT, SCROLL);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
EXPECT_NE(scroll, 0);
EXPECT_EQ(std::abs(scroll), SCROLL);
}
TEST_F(ScrollDetectorTest, ScrollDown_MaxScroll) {
const int WIDTH = 100, HEIGHT = 100;
const int MAX_SCROLL = HEIGHT / MAX_SCROLL_RATIO;
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollDown(frame1, WIDTH, HEIGHT, MAX_SCROLL);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
EXPECT_NE(scroll, 0);
EXPECT_LE(std::abs(scroll), MAX_SCROLL);
}
// ============================================
// 向上滚动检测测试
// ============================================
TEST_F(ScrollDetectorTest, ScrollUp_MinimalScroll) {
const int WIDTH = 100, HEIGHT = 100;
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollUp(frame1, WIDTH, HEIGHT, MIN_SCROLL_LINES);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
// 检测到滚动(方向与 ScrollDown 相反)
EXPECT_NE(scroll, 0);
EXPECT_EQ(std::abs(scroll), MIN_SCROLL_LINES);
}
TEST_F(ScrollDetectorTest, ScrollUp_MediumScroll) {
const int WIDTH = 100, HEIGHT = 100;
const int SCROLL = 20;
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollUp(frame1, WIDTH, HEIGHT, SCROLL);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
EXPECT_NE(scroll, 0);
EXPECT_EQ(std::abs(scroll), SCROLL);
}
// ============================================
// 边缘区域测试
// ============================================
TEST_F(ScrollDetectorTest, EdgeRegion_ScrollDown) {
const int WIDTH = 100, HEIGHT = 100;
const int SCROLL = 20;
CScrollDetector detector(WIDTH, HEIGHT);
int offset, pixelCount;
detector.GetEdgeRegion(SCROLL, &offset, &pixelCount);
// 向下滚动: 新内容在底部
EXPECT_EQ(offset, 0);
EXPECT_EQ(pixelCount, SCROLL * WIDTH);
}
TEST_F(ScrollDetectorTest, EdgeRegion_ScrollUp) {
const int WIDTH = 100, HEIGHT = 100;
const int SCROLL = 20;
CScrollDetector detector(WIDTH, HEIGHT);
int offset, pixelCount;
detector.GetEdgeRegion(-SCROLL, &offset, &pixelCount);
// 向上滚动: 新内容在顶部
EXPECT_EQ(offset, (HEIGHT - SCROLL) * WIDTH * 4);
EXPECT_EQ(pixelCount, SCROLL * WIDTH);
}
TEST_F(ScrollDetectorTest, EdgeRegion_NoScroll) {
const int WIDTH = 100, HEIGHT = 100;
CScrollDetector detector(WIDTH, HEIGHT);
int offset, pixelCount;
detector.GetEdgeRegion(0, &offset, &pixelCount);
EXPECT_EQ(offset, 0);
EXPECT_EQ(pixelCount, 0);
}
// ============================================
// 边界条件测试
// ============================================
TEST_F(ScrollDetectorTest, NullInput_NoScroll) {
const int WIDTH = 100, HEIGHT = 100;
auto frame = CreateStripedFrame(WIDTH, HEIGHT);
CScrollDetector detector(WIDTH, HEIGHT);
EXPECT_EQ(detector.DetectVerticalScroll(nullptr, frame.data()), 0);
EXPECT_EQ(detector.DetectVerticalScroll(frame.data(), nullptr), 0);
EXPECT_EQ(detector.DetectVerticalScroll(nullptr, nullptr), 0);
}
TEST_F(ScrollDetectorTest, SmallScroll_BelowMinimum_NoDetection) {
const int WIDTH = 100, HEIGHT = 100;
const int SMALL_SCROLL = MIN_SCROLL_LINES - 1; // 低于最小滚动量
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollDown(frame1, WIDTH, HEIGHT, SMALL_SCROLL);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
// 小于最小滚动量不应被检测
EXPECT_EQ(scroll, 0);
}
TEST_F(ScrollDetectorTest, LargeScroll_AboveMaximum_NotDetected) {
const int WIDTH = 100, HEIGHT = 100;
const int MAX_SCROLL = HEIGHT / MAX_SCROLL_RATIO;
const int LARGE_SCROLL = MAX_SCROLL + 10; // 超过最大滚动量
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollDown(frame1, WIDTH, HEIGHT, LARGE_SCROLL);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
// 超过最大滚动量可能不被检测或返回最大值
EXPECT_LE(std::abs(scroll), MAX_SCROLL);
}
// ============================================
// CRC32 哈希测试
// ============================================
TEST_F(ScrollDetectorTest, CRC32_EmptyData) {
uint32_t hash = CRC32::Calculate(nullptr, 0);
// CRC32 的空数据哈希值
EXPECT_EQ(hash, 0); // 实现相关
}
TEST_F(ScrollDetectorTest, CRC32_KnownVector) {
// "123456789" 的 CRC32 应该是 0xCBF43926
const char* testData = "123456789";
uint32_t hash = CRC32::Calculate(reinterpret_cast<const uint8_t*>(testData), 9);
EXPECT_EQ(hash, 0xCBF43926);
}
TEST_F(ScrollDetectorTest, CRC32_SameData_SameHash) {
std::vector<uint8_t> data1(100, 0x42);
std::vector<uint8_t> data2(100, 0x42);
uint32_t hash1 = CRC32::Calculate(data1.data(), data1.size());
uint32_t hash2 = CRC32::Calculate(data2.data(), data2.size());
EXPECT_EQ(hash1, hash2);
}
TEST_F(ScrollDetectorTest, CRC32_DifferentData_DifferentHash) {
std::vector<uint8_t> data1(100, 0x42);
std::vector<uint8_t> data2(100, 0x43);
uint32_t hash1 = CRC32::Calculate(data1.data(), data1.size());
uint32_t hash2 = CRC32::Calculate(data2.data(), data2.size());
EXPECT_NE(hash1, hash2);
}
// ============================================
// 性能相关测试(验证正确性)
// ============================================
TEST_F(ScrollDetectorTest, LargeFrame_720p) {
const int WIDTH = 1280, HEIGHT = 720;
const int SCROLL = 50;
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollDown(frame1, WIDTH, HEIGHT, SCROLL);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
EXPECT_NE(scroll, 0);
EXPECT_EQ(std::abs(scroll), SCROLL);
}
TEST_F(ScrollDetectorTest, LargeFrame_1080p) {
const int WIDTH = 1920, HEIGHT = 1080;
const int SCROLL = 100;
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollDown(frame1, WIDTH, HEIGHT, SCROLL);
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
EXPECT_NE(scroll, 0);
EXPECT_EQ(std::abs(scroll), SCROLL);
}
// ============================================
// 带宽节省计算测试
// ============================================
TEST_F(ScrollDetectorTest, BandwidthSaving_ScrollDetected) {
const int WIDTH = 100, HEIGHT = 100;
const int SCROLL = 20;
// 完整帧大小
size_t fullFrameSize = WIDTH * HEIGHT * 4;
// 边缘区域大小
size_t edgeSize = SCROLL * WIDTH * 4;
// 带宽节省
double saving = 1.0 - static_cast<double>(edgeSize) / fullFrameSize;
// 20行滚动应该节省约80%带宽
EXPECT_GT(saving, 0.7);
}
TEST_F(ScrollDetectorTest, BandwidthSaving_NoScroll) {
const int WIDTH = 100, HEIGHT = 100;
CScrollDetector detector(WIDTH, HEIGHT);
int offset, pixelCount;
detector.GetEdgeRegion(0, &offset, &pixelCount);
// 无滚动时没有边缘区域
EXPECT_EQ(pixelCount, 0);
}
// ============================================
// 参数化测试:不同滚动量
// ============================================
class ScrollAmountTest : public ::testing::TestWithParam<int> {};
TEST_P(ScrollAmountTest, DetectScrollAmount) {
const int WIDTH = 100, HEIGHT = 100;
int scrollAmount = GetParam();
if (scrollAmount < MIN_SCROLL_LINES || scrollAmount > HEIGHT / MAX_SCROLL_RATIO) {
GTEST_SKIP() << "Scroll amount out of valid range";
}
auto frame1 = ScrollDetectorTest::CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = ScrollDetectorTest::SimulateScrollDown(frame1, WIDTH, HEIGHT, scrollAmount);
CScrollDetector detector(WIDTH, HEIGHT);
int detected = detector.DetectVerticalScroll(frame1.data(), frame2.data());
EXPECT_NE(detected, 0);
EXPECT_EQ(std::abs(detected), scrollAmount);
}
INSTANTIATE_TEST_SUITE_P(
ScrollAmounts,
ScrollAmountTest,
::testing::Values(16, 17, 18, 19, 20, 21, 22, 23, 24, 25)
);
// ============================================
// 匹配阈值测试
// ============================================
TEST_F(ScrollDetectorTest, MatchThreshold_HighNoise_NoDetection) {
const int WIDTH = 100, HEIGHT = 100;
const int SCROLL = 20;
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollDown(frame1, WIDTH, HEIGHT, SCROLL);
// 添加大量噪声,使匹配率低于阈值
std::mt19937 rng(42);
std::uniform_int_distribution<int> dist(0, 255);
for (size_t i = 0; i < frame2.size(); i++) {
if (rng() % 2 == 0) { // 50% 的像素被随机化
frame2[i] = dist(rng);
}
}
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
// 高噪声情况下不应检测到滚动
EXPECT_EQ(scroll, 0);
}
TEST_F(ScrollDetectorTest, MatchThreshold_LowNoise_DetectionOK) {
const int WIDTH = 100, HEIGHT = 100;
const int SCROLL = 20;
auto frame1 = CreateStripedFrame(WIDTH, HEIGHT);
auto frame2 = SimulateScrollDown(frame1, WIDTH, HEIGHT, SCROLL);
// 只添加少量噪声10%
std::mt19937 rng(42);
std::uniform_int_distribution<int> dist(0, 255);
for (size_t i = 0; i < frame2.size(); i++) {
if (rng() % 10 == 0) { // 10% 的像素被随机化
frame2[i] = dist(rng);
}
}
CScrollDetector detector(WIDTH, HEIGHT);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
// 低噪声情况下仍应检测到滚动(取决于具体行噪声分布)
// 这个测试可能不稳定,因为噪声是随机的
// EXPECT_GT(scroll, 0) 或 EXPECT_EQ(scroll, 0) 取决于实际噪声分布
}
// ============================================
// 常量验证测试
// ============================================
TEST(ScrollConstantsTest, MinScrollLines) {
EXPECT_EQ(MIN_SCROLL_LINES, 16);
}
TEST(ScrollConstantsTest, MaxScrollRatio) {
EXPECT_EQ(MAX_SCROLL_RATIO, 4);
}
TEST(ScrollConstantsTest, MatchThreshold) {
EXPECT_EQ(MATCH_THRESHOLD, 85);
}
TEST(ScrollConstantsTest, ScrollDirections) {
EXPECT_EQ(SCROLL_DIR_UP, 0);
EXPECT_EQ(SCROLL_DIR_DOWN, 1);
}
// ============================================
// 分辨率参数化测试
// ============================================
class ScrollResolutionTest : public ::testing::TestWithParam<std::tuple<int, int>> {};
TEST_P(ScrollResolutionTest, DetectScrollAtResolution) {
auto [width, height] = GetParam();
int scrollAmount = std::max(MIN_SCROLL_LINES, height / 10);
if (scrollAmount > height / MAX_SCROLL_RATIO) {
scrollAmount = height / MAX_SCROLL_RATIO;
}
auto frame1 = ScrollDetectorTest::CreateStripedFrame(width, height);
auto frame2 = ScrollDetectorTest::SimulateScrollDown(frame1, width, height, scrollAmount);
CScrollDetector detector(width, height);
int scroll = detector.DetectVerticalScroll(frame1.data(), frame2.data());
EXPECT_NE(scroll, 0);
EXPECT_EQ(std::abs(scroll), scrollAmount);
}
INSTANTIATE_TEST_SUITE_P(
Resolutions,
ScrollResolutionTest,
::testing::Values(
std::make_tuple(640, 480), // VGA
std::make_tuple(800, 600), // SVGA
std::make_tuple(1024, 768), // XGA
std::make_tuple(1280, 720), // 720p
std::make_tuple(1920, 1080) // 1080p
)
);