勉强完成延迟很高

This commit is contained in:
2026-08-23 23:11:40 +08:00
parent 3938e72bb7
commit 933ff2ec08
43 changed files with 4294 additions and 1168 deletions
@@ -0,0 +1,187 @@
#include <gtest/gtest.h>
#include <web_server/src/detail/Gallery_Frame_Atlas.hpp>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <thread>
#include <vector>
namespace aethera::web::detail {
namespace {
std::shared_ptr<const Plot_Pixel_Frame> solid_frame(
std::uint64_t sequence, std::uint64_t correlation_id,
std::uint32_t width, std::uint32_t height,
std::byte red, std::byte green, std::byte blue,
std::uint64_t rendered_sequence = 0,
std::uint64_t rendered_correlation_id = 0) {
auto pixels = std::make_shared<std::vector<std::byte>>(
static_cast<std::size_t>(width) * height * 4U);
for (std::size_t offset = 0; offset < pixels->size(); offset += 4U) {
(*pixels)[offset] = red;
(*pixels)[offset + 1U] = green;
(*pixels)[offset + 2U] = blue;
(*pixels)[offset + 3U] = std::byte{255};
}
return std::make_shared<const Plot_Pixel_Frame>(Plot_Pixel_Frame{
std::move(pixels), {}, sequence, correlation_id,
rendered_sequence == 0 ? sequence : rendered_sequence,
rendered_correlation_id == 0 ? correlation_id
: rendered_correlation_id,
width, height});
}
std::size_t pixel_offset(const Gallery_Atlas_Description& description,
std::uint32_t x, std::uint32_t y) {
return (static_cast<std::size_t>(y) * description.width + x) * 4U;
}
}
TEST(Gallery_Frame_Atlas, Layout_And_Changed_Tiles_Have_One_Authority) {
Gallery_Frame_Atlas atlas(2, 2, 2, {"alpha", "beta", "gamma"});
const auto description = atlas.describe();
ASSERT_EQ(description.columns, 2U);
ASSERT_EQ(description.rows, 2U);
ASSERT_EQ(description.width, 4U);
ASSERT_EQ(description.height, 4U);
ASSERT_EQ(description.sources.size(), 3U);
EXPECT_EQ(description.sources[2].column, 0U);
EXPECT_EQ(description.sources[2].row, 1U);
EXPECT_EQ(atlas.accept_frame(
0, solid_frame(1, 11, 2, 2, std::byte{10},
std::byte{20}, std::byte{30})),
Gallery_Frame_Atlas::Accept_Frame_Result::accepted);
EXPECT_EQ(atlas.accept_frame(
2, solid_frame(7, 17, 2, 2, std::byte{70},
std::byte{80}, std::byte{90})),
Gallery_Frame_Atlas::Accept_Frame_Result::accepted);
const auto first = atlas.compose();
ASSERT_EQ(first.rgba.size(), 4U * 4U * 4U);
EXPECT_EQ(first.fresh_tile_count, 2U);
EXPECT_EQ(first.missing_tile_count, 1U);
EXPECT_EQ(first.sources[0].rendered_correlation_id, 11U);
EXPECT_EQ(first.sources[2].completion_count, 1U);
EXPECT_EQ(first.sources[2].rendered_frame_count, 1U);
const auto alpha = pixel_offset(description, 0, 0);
const auto gamma = pixel_offset(description, 0, 2);
EXPECT_EQ(first.rgba[alpha], std::byte{10});
EXPECT_EQ(first.rgba[alpha + 3U], std::byte{255});
EXPECT_EQ(first.rgba[gamma], std::byte{70});
EXPECT_EQ(first.rgba[gamma + 2U], std::byte{90});
const auto unchanged = atlas.compose();
EXPECT_EQ(unchanged.fresh_tile_count, 0U);
EXPECT_EQ(unchanged.missing_tile_count, 1U);
}
TEST(Gallery_Frame_Atlas, Invalid_And_Stale_Frames_Do_Not_Replace_Latest) {
Gallery_Frame_Atlas atlas(2, 2, 1, {"only"});
EXPECT_EQ(atlas.accept_frame(
0, solid_frame(4, 14, 2, 2, std::byte{4},
std::byte{5}, std::byte{6})),
Gallery_Frame_Atlas::Accept_Frame_Result::accepted);
EXPECT_EQ(atlas.accept_frame(
0, solid_frame(3, 13, 2, 2, std::byte{30},
std::byte{31}, std::byte{32})),
Gallery_Frame_Atlas::Accept_Frame_Result::stale_frame);
EXPECT_EQ(atlas.accept_frame(
0, solid_frame(5, 15, 4, 2, std::byte{50},
std::byte{51}, std::byte{52})),
Gallery_Frame_Atlas::Accept_Frame_Result::invalid_frame);
const auto composition = atlas.compose();
EXPECT_EQ(composition.rejected_frame_count, 2U);
EXPECT_EQ(composition.sources[0].completion_sequence, 4U);
EXPECT_EQ(composition.rgba[0], std::byte{4});
EXPECT_EQ(atlas.compose().rejected_frame_count, 0U);
}
TEST(Gallery_Frame_Atlas, Logical_History_Callback_Does_Not_Fake_A_New_Image) {
Gallery_Frame_Atlas atlas(2, 2, 1, {"only"});
ASSERT_EQ(atlas.accept_frame(
0, solid_frame(4, 14, 2, 2, std::byte{4},
std::byte{5}, std::byte{6})),
Gallery_Frame_Atlas::Accept_Frame_Result::accepted);
ASSERT_EQ(atlas.compose().fresh_tile_count, 1U);
ASSERT_EQ(atlas.accept_frame(
0, solid_frame(5, 15, 2, 2, std::byte{50},
std::byte{51}, std::byte{52}, 4, 14)),
Gallery_Frame_Atlas::Accept_Frame_Result::accepted);
const auto composition = atlas.compose();
EXPECT_EQ(composition.fresh_tile_count, 0U);
EXPECT_EQ(composition.sources[0].completion_count, 2U);
EXPECT_EQ(composition.sources[0].rendered_frame_count, 1U);
EXPECT_EQ(composition.sources[0].completion_sequence, 5U);
EXPECT_EQ(composition.sources[0].rendered_sequence, 4U);
EXPECT_EQ(composition.rgba[0], std::byte{4});
}
TEST(Gallery_Frame_Atlas, Diagnostics_Only_Completion_Preserves_Progress_And_Image) {
Gallery_Frame_Atlas atlas(2, 2, 1, {"only"});
ASSERT_EQ(atlas.accept_frame(
0, solid_frame(1, 11, 2, 2, std::byte{7},
std::byte{8}, std::byte{9})),
Gallery_Frame_Atlas::Accept_Frame_Result::accepted);
ASSERT_EQ(atlas.compose().fresh_tile_count, 1U);
auto diagnostics = std::make_shared<const Plot_Pixel_Frame>(
Plot_Pixel_Frame{{}, {}, 2, 12, 2, 12, 2, 2});
ASSERT_EQ(atlas.accept_frame(0, std::move(diagnostics)),
Gallery_Frame_Atlas::Accept_Frame_Result::accepted);
const auto composition = atlas.compose();
EXPECT_EQ(composition.fresh_tile_count, 0U);
EXPECT_EQ(composition.missing_tile_count, 0U);
EXPECT_EQ(composition.sources[0].completion_sequence, 2U);
EXPECT_EQ(composition.sources[0].rendered_sequence, 2U);
EXPECT_EQ(composition.sources[0].completion_count, 2U);
EXPECT_EQ(composition.sources[0].rendered_frame_count, 2U);
EXPECT_EQ(composition.rgba[0], std::byte{7});
}
TEST(Gallery_Frame_Atlas, Plot_Producers_Accept_In_Parallel_While_Encoding_Composes) {
constexpr std::size_t source_count{8};
constexpr std::uint64_t frame_count{500};
std::vector<std::string> ids;
ids.reserve(source_count);
for (std::size_t slot = 0; slot < source_count; ++slot)
ids.push_back("source-" + std::to_string(slot));
Gallery_Frame_Atlas atlas(2, 2, 4, std::move(ids));
std::atomic_size_t completed_producers{};
std::vector<std::jthread> producers;
producers.reserve(source_count);
for (std::size_t slot = 0; slot < source_count; ++slot) {
producers.emplace_back([&, slot] {
for (std::uint64_t sequence = 1; sequence <= frame_count;
++sequence) {
const auto color = std::byte{
static_cast<unsigned char>((slot + sequence) % 251U)};
EXPECT_EQ(atlas.accept_frame(
slot, solid_frame(
sequence, sequence, 2, 2, color,
std::byte{static_cast<unsigned char>(slot)},
std::byte{0})),
Gallery_Frame_Atlas::Accept_Frame_Result::accepted);
}
completed_producers.fetch_add(1, std::memory_order_release);
});
}
while (completed_producers.load(std::memory_order_acquire) < source_count)
static_cast<void>(atlas.compose());
for (auto& producer : producers) producer.join();
const auto final = atlas.compose();
ASSERT_EQ(final.sources.size(), source_count);
EXPECT_EQ(final.missing_tile_count, 0U);
EXPECT_EQ(final.rejected_frame_count, 0U);
for (const auto& source : final.sources) {
EXPECT_EQ(source.completion_sequence, frame_count);
EXPECT_EQ(source.rendered_sequence, frame_count);
EXPECT_EQ(source.completion_count, frame_count);
EXPECT_EQ(source.rendered_frame_count, frame_count);
}
}
}
@@ -0,0 +1,65 @@
#include <gtest/gtest.h>
#include <web_server/src/detail/Gallery_Frame_Clock.hpp>
#include <asio/thread_pool.hpp>
#include <chrono>
#include <condition_variable>
#include <exception>
#include <mutex>
#include <thread>
#include <vector>
namespace aethera::web::detail {
TEST(Gallery_Frame_Clock, Produces_One_Monotonic_Absolute_Timeline) {
asio::thread_pool pool(1);
std::mutex mutex;
std::condition_variable condition;
std::vector<Plot_Render_Tick> ticks;
std::exception_ptr failure;
{
Gallery_Frame_Clock clock(
pool.get_executor(), 100.0,
[&](Plot_Render_Tick tick) {
std::lock_guard lock(mutex);
ticks.push_back(tick);
condition.notify_all();
},
[&](std::exception_ptr value) {
std::lock_guard lock(mutex);
failure = std::move(value);
condition.notify_all();
});
clock.start();
clock.start();
{
std::unique_lock lock(mutex);
ASSERT_TRUE(condition.wait_for(lock, std::chrono::seconds(2), [&] {
return ticks.size() >= 8 || failure;
}));
ASSERT_FALSE(failure);
ASSERT_GE(ticks.size(), 8U);
for (std::size_t index = 1; index < ticks.size(); ++index) {
EXPECT_GT(ticks[index].sequence, ticks[index - 1].sequence);
EXPECT_GT(ticks[index].time_milliseconds,
ticks[index - 1].time_milliseconds);
}
EXPECT_NEAR(ticks.front().time_milliseconds,
static_cast<double>(ticks.front().sequence) * 10.0,
0.001);
}
clock.stop();
std::this_thread::sleep_for(std::chrono::milliseconds(30));
std::size_t stopped_count{};
{
std::lock_guard lock(mutex);
stopped_count = ticks.size();
}
std::this_thread::sleep_for(std::chrono::milliseconds(30));
{
std::lock_guard lock(mutex);
EXPECT_EQ(ticks.size(), stopped_count);
}
}
pool.stop();
pool.join();
}
}
+97
View File
@@ -0,0 +1,97 @@
#include <gtest/gtest.h>
#include <web_server/src/H264_Encoder.hpp>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <vector>
namespace aethera::web {
namespace {
std::vector<std::byte> test_pattern(std::uint32_t width,
std::uint32_t height) {
std::vector<std::byte> result(
static_cast<std::size_t>(width) * height * 4U);
for (std::uint32_t y = 0; y < height; ++y) {
for (std::uint32_t x = 0; x < width; ++x) {
const auto offset =
(static_cast<std::size_t>(y) * width + x) * 4U;
result[offset] = std::byte{static_cast<std::uint8_t>(x)};
result[offset + 1U] = std::byte{static_cast<std::uint8_t>(y)};
result[offset + 2U] =
std::byte{static_cast<std::uint8_t>(x ^ y)};
result[offset + 3U] = std::byte{255};
}
}
return result;
}
}
TEST(H264_Encoder, Rejects_Invalid_Input_Before_FFmpeg) {
H264_Encoder encoder(100.0);
const auto pixels = test_pattern(320, 192);
EXPECT_THROW(static_cast<void>(encoder.encode(
pixels, 319, 192, Video_Pixel_Layout::rgba,
1, std::chrono::microseconds{10'000})),
std::invalid_argument);
EXPECT_THROW(static_cast<void>(encoder.encode(
std::span<const std::byte>{pixels}.first(pixels.size() - 1U),
320, 192, Video_Pixel_Layout::rgba, 1,
std::chrono::microseconds{10'000})),
std::invalid_argument);
}
TEST(H264_Encoder, Hardware_Backend_Produces_Annex_B_At_One_Hundred_Fps) {
constexpr std::uint32_t width{1'920};
constexpr std::uint32_t height{768};
H264_Encoder encoder(100.0);
auto pixels = test_pattern(width, height);
std::optional<Encoded_Video_Frame> first;
for (std::uint64_t sequence = 1; sequence <= 4 && !first; ++sequence) {
first = encoder.encode(
pixels, width, height, Video_Pixel_Layout::rgba, sequence,
std::chrono::microseconds{static_cast<std::int64_t>(sequence * 10'000)});
}
ASSERT_TRUE(first);
ASSERT_FALSE(first->annex_b.empty());
EXPECT_TRUE(first->key_frame);
ASSERT_GE(first->annex_b.size(), 4U);
EXPECT_EQ(first->annex_b[0], std::byte{0});
EXPECT_EQ(first->annex_b[1], std::byte{0});
EXPECT_TRUE((first->annex_b[2] == std::byte{1}) ||
(first->annex_b[2] == std::byte{0} &&
first->annex_b[3] == std::byte{1}));
EXPECT_NE(video_encoder_backend_name(first->backend), "unknown");
EXPECT_EQ(h264_profile_level_id(), "640033");
}
TEST(H264_Encoder, Hardware_Pipeline_Drains_A_Continuous_Frame_Sequence) {
constexpr std::uint32_t width{1'920};
constexpr std::uint32_t height{768};
H264_Encoder encoder(100.0);
auto pixels = test_pattern(width, height);
std::uint64_t produced{};
std::uint64_t latest_sequence{};
std::optional<Video_Encoder_Backend> backend;
const auto started = std::chrono::steady_clock::now();
for (std::uint64_t sequence = 1; sequence <= 120; ++sequence) {
pixels[0] = std::byte{static_cast<std::uint8_t>(sequence)};
const auto frame = encoder.encode(
pixels, width, height, Video_Pixel_Layout::rgba, sequence,
std::chrono::microseconds{
static_cast<std::int64_t>(sequence * 10'000)});
if (!frame) continue;
++produced;
EXPECT_GT(frame->sequence, latest_sequence);
latest_sequence = frame->sequence;
EXPECT_FALSE(frame->annex_b.empty());
if (!backend) backend = frame->backend;
EXPECT_EQ(frame->backend, *backend);
}
const auto elapsed = std::chrono::steady_clock::now() - started;
EXPECT_GE(produced, 115U);
EXPECT_GE(latest_sequence, 115U);
EXPECT_LE(elapsed, std::chrono::milliseconds{1'200});
}
}