#include #include #include #include #include #include #include #include namespace aethera::web { namespace { std::vector test_pattern(std::uint32_t width, std::uint32_t height) { std::vector result( static_cast(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(y) * width + x) * 4U; result[offset] = std::byte{static_cast(x)}; result[offset + 1U] = std::byte{static_cast(y)}; result[offset + 2U] = std::byte{static_cast(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(encoder.encode( pixels, 319, 192, Video_Pixel_Layout::rgba, 1, std::chrono::microseconds{10'000})), std::invalid_argument); EXPECT_THROW(static_cast(encoder.encode( std::span{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 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(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 backend; const auto started = std::chrono::steady_clock::now(); for (std::uint64_t sequence = 1; sequence <= 120; ++sequence) { pixels[0] = std::byte{static_cast(sequence)}; const auto frame = encoder.encode( pixels, width, height, Video_Pixel_Layout::rgba, sequence, std::chrono::microseconds{ static_cast(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}); } }