Files
Aethera/web_server/tests/H264_Encoder_Tests.cpp
T
2026-08-23 23:11:40 +08:00

98 lines
3.9 KiB
C++

#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});
}
}