From 186027a275fbb15dfef21b9323989b97c5914a66 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 27 Nov 2024 16:13:48 +0100 Subject: [PATCH 1/3] added unit tests --- .../workflows/{build.yml => build-test.yml} | 2 ++ CMakeLists.txt | 27 +++++++++++++------ flake.nix | 2 ++ src/test/ffmpeg.cpp | 25 +++++++++++++++++ src/test/utils.cpp | 14 ++++++++++ 5 files changed, 62 insertions(+), 8 deletions(-) rename .github/workflows/{build.yml => build-test.yml} (91%) create mode 100644 src/test/ffmpeg.cpp create mode 100644 src/test/utils.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build-test.yml similarity index 91% rename from .github/workflows/build.yml rename to .github/workflows/build-test.yml index a74d53c..acc456e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build-test.yml @@ -24,3 +24,5 @@ jobs: run: cmake -B build && cmake --build build -j4 env: CXX: g++-13 + - name: test + run: ctest --test-dir build diff --git a/CMakeLists.txt b/CMakeLists.txt index 36b75ed..e88e444 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,22 +17,33 @@ include_directories(include) find_package( OpenCV REQUIRED ) include_directories( ${OpenCV_INCLUDE_DIRS} ) + # argparse -include(FetchContent) -FetchContent_Declare( - argparse - GIT_REPOSITORY https://github.com/p-ranav/argparse.git -) -FetchContent_MakeAvailable(argparse) +find_package(argparse REQUIRED) -#spdlog +# spdlog find_package(spdlog REQUIRED) +# catch2 +find_package(Catch2 REQUIRED) + add_executable(sender src/Sender.cpp ${PROJECT_IMPLEMENTATIONS}) +# Linking target_link_libraries(sender ${OpenCV_LIBS} - argparse + argparse::argparse spdlog::spdlog ) +# Tests +enable_testing() +foreach(test ffmpeg utils) + add_executable(test_${test} src/test/${test}.cpp ${PROJECT_IMPLEMENTATIONS}) + target_link_libraries(test_${test} + ${OpenCV_LIBS} + spdlog::spdlog + Catch2::Catch2WithMain + ) + add_test(NAME ${test} COMMAND test_${test}) +endforeach() diff --git a/flake.nix b/flake.nix index a76f65a..52353be 100644 --- a/flake.nix +++ b/flake.nix @@ -15,6 +15,8 @@ ffmpeg cmake spdlog + argparse + catch2_3 ]; }; }; diff --git a/src/test/ffmpeg.cpp b/src/test/ffmpeg.cpp new file mode 100644 index 0000000..37d9ffd --- /dev/null +++ b/src/test/ffmpeg.cpp @@ -0,0 +1,25 @@ +#include + +#include "FFmpeg.hpp" + +TEST_CASE("test ffmpeg constructors") { + std::vector ffmpegs({ + {}, + {{2, 2}}, + {"1G", {2, 2}}, + {60, "1G", {2, 2}}, + {{1920,1080}, 60, "1G", {2, 2}} + }); + for(auto i = 0; i < ffmpegs.size()-1; i++) { + auto [rx1, ry1] = ffmpegs[i].resolution; + auto [rx2, ry2] = ffmpegs[i].resolution; + auto [x1, y1] = ffmpegs[i].position; + auto [x2, y2] = ffmpegs[i].position; + REQUIRE(rx1 == rx2); + REQUIRE(ry1 == ry2); + REQUIRE(ffmpegs[i].framerate == ffmpegs[i+1].framerate); + REQUIRE(ffmpegs[i].bitrate == ffmpegs[i+1].bitrate); + REQUIRE(x1 == x2); + REQUIRE(y1 == y2); + } +} diff --git a/src/test/utils.cpp b/src/test/utils.cpp new file mode 100644 index 0000000..b30bdd8 --- /dev/null +++ b/src/test/utils.cpp @@ -0,0 +1,14 @@ +#include + +#include + +#include "Utils.hpp" + +TEST_CASE("test utility functions: range") { + auto r1 = Util::range(-1000, 1000); + std::vector r2; + for(auto i = -1000; i < 1000; i++) { + r2.push_back(i); + } + REQUIRE(r1 == r2); +} From 76d0eb492c04ed24a2896f45c5ed7f688d3a8125 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 27 Nov 2024 16:16:13 +0100 Subject: [PATCH 2/3] fix ci? --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index acc456e..979609d 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: install dependencies - run: sudo apt-get update && sudo apt-get -y install cmake libopencv-dev libspdlog-dev + run: sudo apt-get update && sudo apt-get -y install cmake libopencv-dev libspdlog-dev libargparse-dev - uses: actions/cache@v4 with: path: build From 449d234ba9c1b4a5064c3138cb1b08a255d2c1c8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 28 Nov 2024 01:48:46 +0100 Subject: [PATCH 3/3] added more unit tests and optimized+redesigned settings to conform to tests --- CMakeLists.txt | 16 ++++++------ include/FFmpeg.hpp | 43 +++++++++++++++---------------- include/Hyperwall.hpp | 21 ++++++--------- include/Settings.hpp | 54 +++++++++++++++++++++++++++++++++++++++ src/Sender.cpp | 25 ++++++++++-------- src/impl/FFmpeg.cpp | 5 ++-- src/impl/Hyperwall.cpp | 58 +++++++++++++++++++----------------------- src/test/ffmpeg.cpp | 10 ++++---- src/test/hyperwall.cpp | 9 +++++++ 9 files changed, 149 insertions(+), 92 deletions(-) create mode 100644 include/Settings.hpp create mode 100644 src/test/hyperwall.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e88e444..3e0cab9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,12 +38,12 @@ target_link_libraries(sender # Tests enable_testing() -foreach(test ffmpeg utils) - add_executable(test_${test} src/test/${test}.cpp ${PROJECT_IMPLEMENTATIONS}) - target_link_libraries(test_${test} - ${OpenCV_LIBS} - spdlog::spdlog - Catch2::Catch2WithMain - ) - add_test(NAME ${test} COMMAND test_${test}) +foreach(test ffmpeg utils hyperwall) +add_executable(test_${test} src/test/${test}.cpp ${PROJECT_IMPLEMENTATIONS}) +target_link_libraries(test_${test} + ${OpenCV_LIBS} + spdlog::spdlog + Catch2::Catch2WithMain +) +add_test(NAME ${test} COMMAND test_${test}) endforeach() diff --git a/include/FFmpeg.hpp b/include/FFmpeg.hpp index 214ed16..ed19279 100644 --- a/include/FFmpeg.hpp +++ b/include/FFmpeg.hpp @@ -4,53 +4,52 @@ #include +#include "Settings.hpp" + namespace Hyperwall { class FFmpeg { FILE* buffer; public: - const std::tuple resolution; - const std::tuple position; + const coordinate resolution; + const coordinate position; + const coordinate dimensions; const int framerate; const std::string input; const std::string bitrate; - constexpr FFmpeg(const std::tuple& resolution, const int& framerate, const std::string& bitrate, const std::tuple& position) : - resolution(resolution), - framerate(framerate), - bitrate(bitrate), - position(position) { - } - constexpr FFmpeg(const int framerate, const std::string bitrate, const std::tuple position) : - resolution(1920, 1080), - framerate(framerate), - bitrate(bitrate), + constexpr FFmpeg(Settings settings, coordinate position) : + resolution(settings.resolution), + dimensions(settings.dimensions), + framerate(settings.framerate), + bitrate(settings.bitrate), position(position) { } - constexpr FFmpeg(const std::string bitrate, const std::tuple position) : - resolution(1920, 1080), - framerate(60), - bitrate(bitrate), - position(position) { - + constexpr FFmpeg(Settings settings) : + resolution(settings.resolution), + dimensions(settings.dimensions), + framerate(settings.framerate), + bitrate(settings.bitrate), + position({0, 0}) { } - constexpr FFmpeg(const std::tuple position) : + constexpr FFmpeg(coordinate position) : resolution(1920, 1080), + dimensions(2, 2), framerate(60), bitrate("1G"), position(position) { - } constexpr FFmpeg() : - resolution({1920, 1080}), + resolution(1920, 1080), + dimensions(0, 0), framerate(60), bitrate("1G"), - position({0, 0}) { + position(0, 0) { } constexpr ~FFmpeg() = default; diff --git a/include/Hyperwall.hpp b/include/Hyperwall.hpp index 993a5bc..8c45a90 100644 --- a/include/Hyperwall.hpp +++ b/include/Hyperwall.hpp @@ -1,33 +1,28 @@ #pragma once -#include - #include "FFmpeg.hpp" #include "Sources/VideoSource.hpp" +#include "Settings.hpp" namespace Hyperwall { class HyperFrame { - const int x; - const int y; - const int X; - const int Y; - const int RES_X; - const int RES_Y; + const coordinate position; + const coordinate dimensions; + const coordinate resolution; FFmpeg ffmpeg; public: HyperFrame(const HyperFrame&); - HyperFrame(const int, const int, std::unordered_map, FFmpeg&); + HyperFrame(const coordinate&, Settings, FFmpeg&); void run(const cv::Mat&); }; class Hyperwall { std::unique_ptr source; - std::unordered_map> frames; - const int X; - const int Y; + std::vector frames; + const coordinate dimensions; public: - Hyperwall(VideoSourceT&, std::unordered_map); + Hyperwall(VideoSourceT&, Settings); void run(); }; diff --git a/include/Settings.hpp b/include/Settings.hpp new file mode 100644 index 0000000..af6c8a2 --- /dev/null +++ b/include/Settings.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +namespace Hyperwall { + +typedef std::tuple coordinate; + +struct Settings { +public: + const coordinate resolution; + const coordinate dimensions; + const std::string bitrate; + const unsigned int framerate; + constexpr Settings(const coordinate& resolution, const coordinate& dimensions, const std::string bitrate, const unsigned int framerate) : + resolution(resolution), + dimensions(dimensions), + bitrate(bitrate), + framerate(framerate) { + } + + constexpr Settings(const coordinate& resolution, const coordinate& dimensions, const std::string bitrate) : + resolution(resolution), + dimensions(dimensions), + bitrate(bitrate), + framerate(60) { + + } + + constexpr Settings(const coordinate& resolution, const coordinate& dimensions) : + resolution(resolution), + dimensions(dimensions), + bitrate("1G"), + framerate(60) { + } + + constexpr Settings(const coordinate& resolution) : + resolution(resolution), + dimensions({2, 2}), + bitrate("1G"), + framerate(60) { + } + + constexpr Settings() : + resolution({1920, 1080}), + dimensions({2, 2}), + bitrate("1G"), + framerate(60) { + + } +}; + +} diff --git a/src/Sender.cpp b/src/Sender.cpp index 61be16d..2c0903d 100644 --- a/src/Sender.cpp +++ b/src/Sender.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include @@ -28,7 +27,7 @@ int main(int argc, char* argv[]) { parser.add_argument("--resolution") .default_value("1920x1080"); parser.add_argument("--framerate") - .default_value("60"); + .default_value(60); parser.add_argument("--file") .default_value("file.mp4"); parser.add_argument("--bitrate") @@ -57,14 +56,20 @@ int main(int argc, char* argv[]) { spdlog::set_level(static_cast(loglevel)); std::cout << "Log level: " << loglevel << std::endl; spdlog::debug("Generating settings"); - std::unordered_map settings({ - {"RES_X", split_res(parser.get("--resolution"), "x")}, - {"RES_Y", split_res(parser.get("--resolution"), "y")}, - {"X", split_res(parser.get("--dimensions"), "x")}, - {"Y", split_res(parser.get("--dimensions"), "y")}, - {"FRAMERATE", parser.get("--framerate")}, - {"BITRATE", parser.get("--bitrate")} - }); + + Hyperwall::Settings settings( + { + stoi(split_res(parser.get("--resolution"), "x")), + stoi(split_res(parser.get("--resolution"), "y")) + }, + { + stoi(split_res(parser.get("--dimensions"), "x")), + stoi(split_res(parser.get("--dimensions"), "y")) + }, + parser.get("--bitrate"), + parser.get("--framerate") + ); + Hyperwall::Hyperwall hyperwall = [&settings, &parser](std::string mode) { spdlog::info("Chosen mode: {}", mode); if(mode == "webcam") { diff --git a/src/impl/FFmpeg.cpp b/src/impl/FFmpeg.cpp index e97c51c..45ba0f4 100644 --- a/src/impl/FFmpeg.cpp +++ b/src/impl/FFmpeg.cpp @@ -8,12 +8,13 @@ const void Hyperwall::FFmpeg::open() { const auto [res_x, res_y] = resolution; const auto [x, y] = position; + const auto [X, Y] = dimensions; std::stringstream ss; ss << "ffmpeg -re -y -f rawvideo -vcodec rawvideo -pix_fmt bgr24 -s " - << res_x << "x" << res_y + << res_x/X << "x" << res_y/Y << " -r " << framerate << " -i - -f mpegts -preset ultrafast -s " - << res_x << "x" << res_y + << res_x/X << "x" << res_y/Y << " -r " << framerate << " -f rtsp -b:v " << bitrate diff --git a/src/impl/Hyperwall.cpp b/src/impl/Hyperwall.cpp index 42f5600..07a9474 100644 --- a/src/impl/Hyperwall.cpp +++ b/src/impl/Hyperwall.cpp @@ -1,6 +1,4 @@ #include -#include -#include #include @@ -8,31 +6,31 @@ #include "FFmpeg.hpp" #include "Hyperwall.hpp" -Hyperwall::HyperFrame::HyperFrame(const int x, const int y, std::unordered_map settings, FFmpeg& ffmpeg) : - x(x), - y(y), - X(stoi(settings["X"])), - Y(stoi(settings["Y"])), +Hyperwall::HyperFrame::HyperFrame(const coordinate& position, Settings settings, FFmpeg& ffmpeg) : + position(position), + dimensions(settings.dimensions), ffmpeg(ffmpeg), - RES_X(stoi(settings["RES_X"])), - RES_Y(stoi(settings["RES_Y"])) { + resolution(settings.resolution) { this->ffmpeg.open(); + const auto& [x, y] = position; spdlog::info("Built HyperFrame: [x: {}, y: {}, uri: {}]", x, y, ffmpeg.uri()); } Hyperwall::HyperFrame::HyperFrame(const HyperFrame& other) : - x(other.x), - y(other.y), - X(other.X), - Y(other.Y), - ffmpeg(other.ffmpeg), - RES_X(other.RES_X), - RES_Y(other.RES_Y) { - spdlog::debug("Copied hyperframe object at: ({},{}) in {}x{}", x,y,X,Y); + position(other.position), + dimensions(other.dimensions), + resolution(other.resolution), + ffmpeg(other.ffmpeg) { + const auto& [x, y] = position; + const auto& [X, Y] = dimensions; + spdlog::debug("Copied hyperframe object at: ({}) in {}", x,y,X,Y); } void Hyperwall::HyperFrame::run(const cv::Mat& image) { - spdlog::debug("Running Hyperframe"); + const auto& [x, y] = position; + const auto& [X, Y] = dimensions; + const auto& [res_x, res_y] = resolution; + spdlog::debug("Running Hyperframe: p: {}x{}, d: {}x{}, r: {}x{}", x, y, X, Y, res_x, res_y); auto start_x = (int)(image.cols * ((0.0 + x)/X)); auto end_x = (int)(image.cols * ((1.0+x)/X)); auto start_y = (int)(image.rows * ((0.0 + y)/Y)); @@ -40,22 +38,20 @@ void Hyperwall::HyperFrame::run(const cv::Mat& image) { auto sub_image = image(cv::Range(start_y, end_y), cv::Range(start_x, end_x)); cv::Mat resized_image; - cv::resize(sub_image, resized_image, cv::Size(RES_X/X, RES_Y/Y)); + cv::resize(sub_image, resized_image, cv::Size(res_x/X, res_y/Y)); ffmpeg.write(resized_image); } -Hyperwall::Hyperwall::Hyperwall(VideoSourceT& source, std::unordered_map settings) : source(source.clone()), X(stoi(settings["X"])), Y(stoi(settings["Y"])) { +Hyperwall::Hyperwall::Hyperwall(VideoSourceT& source, Settings settings) : + source(source.clone()), + dimensions(settings.dimensions) { spdlog::info("Generating hyperwall..."); + const auto& [res_x, res_y] = settings.resolution; + const auto& [X, Y] = settings.dimensions; for(const auto x : Util::range(X)) { - frames.insert({x, {}}); for(const auto y : Util::range(Y)) { - FFmpeg ffmpeg( - {std::stoi(settings["RES_X"])/X, std::stoi(settings["RES_Y"])/Y}, - std::stoi(settings["FRAMERATE"]), - settings["BITRATE"], - {x, y} - ); - frames[x].insert({y, HyperFrame(x, y, settings, ffmpeg)}); + FFmpeg ffmpeg(settings, {x, y}); + frames.push_back({{x, y}, settings, ffmpeg}); } } }; @@ -67,10 +63,8 @@ void Hyperwall::Hyperwall::run() { if (image.rows == 0 || image.cols == 0) { break; } - for(auto& [x, x_frames] : frames) { - for(auto& [y, frame] : x_frames) { - frame.run(image); - } + for(auto& frame : frames) { + frame.run(image); } } spdlog::info("Finished hyperwall execution"); diff --git a/src/test/ffmpeg.cpp b/src/test/ffmpeg.cpp index 37d9ffd..62ccc9f 100644 --- a/src/test/ffmpeg.cpp +++ b/src/test/ffmpeg.cpp @@ -4,11 +4,11 @@ TEST_CASE("test ffmpeg constructors") { std::vector ffmpegs({ - {}, - {{2, 2}}, - {"1G", {2, 2}}, - {60, "1G", {2, 2}}, - {{1920,1080}, 60, "1G", {2, 2}} + {{{1920, 1080}, {2, 2}, "1G", 60}}, + {{{1920, 1080}, {2, 2}, "1G"}}, + {{{1920, 1080}, {2, 2}}}, + {{{1920, 1080}}}, + {} }); for(auto i = 0; i < ffmpegs.size()-1; i++) { auto [rx1, ry1] = ffmpegs[i].resolution; diff --git a/src/test/hyperwall.cpp b/src/test/hyperwall.cpp new file mode 100644 index 0000000..5620ccd --- /dev/null +++ b/src/test/hyperwall.cpp @@ -0,0 +1,9 @@ +#include + +#include "Hyperwall.hpp" +#include "Sources/FileSource.hpp" + +TEST_CASE("Hyperwall") { + Hyperwall::FileSource source("file.mp4"); + Hyperwall::Hyperwall wall(source, {}); +}