diff --git a/.ci.yml b/.ci.yml index 0640dfc..82ba998 100644 --- a/.ci.yml +++ b/.ci.yml @@ -12,6 +12,15 @@ before_script: libv4l-dev make rapidjson-dev + libglfw3-dev + libglew-dev + libgl1-mesa-dev + - export CC=/usr/bin/gcc-12 CXX=/usr/bin/g++-12 + - git clone --recursive https://github.com/antmicro/farshow + - cd farshow + - cmake -S . -B ./build + - cmake --build ./build + - cmake --install ./build stages: - lint diff --git a/CMakeLists.txt b/CMakeLists.txt index 91b3526..67b864b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ project(grabthecam VERSION 0.1) find_package(OpenCV REQUIRED) find_package(RapidJSON REQUIRED) +option(ADD_GRABTHECAM_FARSHOW_DEMO "Adds a target for grabthecam-farshow integration demo" OFF) + set(INCLUDE_DIRECTORIES ${OpenCV_INCLUDE_DIRS} $ @@ -50,16 +52,45 @@ target_link_libraries(${PROJECT_NAME}-demo PUBLIC ${OpenCV_LIBS} ) -configure_file( - ${PROJECT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in - ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake @ONLY -) + +if(ADD_GRABTHECAM_FARSHOW_DEMO) + find_package(farshow QUIET) + if(NOT farshow_FOUND) + cmake_policy(SET CMP0002 OLD) + message("farshow not found - building it") + include(FetchContent) + FetchContent_Declare(farshow + GIT_REPOSITORY https://github.com/antmicro/farshow + GIT_TAG main + ) + FetchContent_MakeAvailable(farshow) + endif() + + add_executable(${PROJECT_NAME}-farshow-streamer + src/farshow-streamer-example.cpp + ) + target_include_directories(${PROJECT_NAME}-farshow-streamer PUBLIC ${INCLUDE_DIRECTORIES}) + + target_link_libraries(${PROJECT_NAME}-farshow-streamer PUBLIC + ${PROJECT_NAME} + v4l2 + farshow-connection + ${OpenCV_LIBS} + ) + install(TARGETS ${PROJECT_NAME}-farshow-streamer) +endif() install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}-demo EXPORT ${PROJECT_NAME}-targets LIBRARY DESTINATION lib RUNTIME DESTINATION bin ) + +configure_file( + ${PROJECT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in + ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake @ONLY +) + export( TARGETS ${PROJECT_NAME} FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake ) @@ -75,7 +106,7 @@ install( ) add_custom_target(format - COMMAND bash -c "find ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/include -iname \"*.cpp\" -o -iname \"*.hpp\" |xargs clang-tidy -format-style=file -p ${CMAKE_BINARY_DIR} -fix" - COMMAND bash -c "find ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/include -iname \"*.cpp\" -o -iname \"*.hpp\"| xargs clang-format --style=file -i" + COMMAND bash -c "clang-tidy -format-style=file \$(find ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/include | egrep '(*hpp|*cpp)' | tr '\\n' ' ') -p ${CMAKE_BINARY_DIR} -fix" + COMMAND bash -c "clang-format --style=file -i \$(find ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/include | egrep '(*hpp|*cpp)' | tr '\\n' ' ')" VERBATIM ) diff --git a/README.md b/README.md index 0186d10..84f1c75 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,21 @@ To build the project, go to its root directory and execute: cmake -S . -B build ``` +In order to add the option to build a `grabthecam` - `farshow` integration demo, execute the following command instead: +``` +cmake -S . -B build -DADD_GRABTHECAM_FARSHOW_DEMO=ON +``` + +Next, go to `build` and execute either +``` +make +``` +for the standalone demonstration, or +``` +make grabthecam-farshow-streamer +``` +for the integration demo. + ## Running the demo After a successful build, you can run the demo, e.g.: @@ -43,10 +58,22 @@ The captured frame will be saved as `frame.png`. The configuration will be saved as `.my_configuration`. If you use `-s` it is saved as `.pyvidctrl_` (for compatibility with [pyvidctrl camera management TUI tool](https://github.com/antmicro/pyvidctrl)). Please note that `--save` and `--load` can only have values assigned through the `--param=value` syntax. +Farshow-Grabthecam integration demo follows simmilar syntax, with an addition of `-a
` and `-p ` for configuring the frame destination for the sender +``` +cd build +./grabthecam-farshow-streamer --type YUYV --dims 960,720 -a 0.0.0.0 -p 18881 +``` + +To start the receiver, please follow the README instructions on installation from the [farshow](https://github.com/antmicro/farshow) repository and run +```(bash) +farshow -i 0.0.0.0 -p 18881 +``` + You can find more information about available arguments in command-line help: ``` ./grabthecam-demo --help +./grabthecam-farshow-streamer --help ``` ## Installation diff --git a/include/grabthecam/pixelformatsinfo.hpp b/include/grabthecam/pixelformatsinfo.hpp index 8def3c5..781d599 100644 --- a/include/grabthecam/pixelformatsinfo.hpp +++ b/include/grabthecam/pixelformatsinfo.hpp @@ -10,6 +10,21 @@ namespace grabthecam { +/** + * Parse command line options + * + * @param name Four character name of the V4L2 format + */ +uint32_t convertToV4l2Fourcc(std::string name) +{ + uint32_t fourcc = 0; + for (int i = 0; i < 4; i++) + { + fourcc |= (name[0] << 8 * i); + } + return fourcc; +} + /// Information about converters and input formats assigned to pixel formats static std::unordered_map()>> formats_info = { {V4L2_PIX_FMT_YYUV, [] { return std::make_shared(cv::COLOR_YUV2BGR_YUY2); }}, diff --git a/src/farshow-streamer-example.cpp b/src/farshow-streamer-example.cpp new file mode 100644 index 0000000..ba38d50 --- /dev/null +++ b/src/farshow-streamer-example.cpp @@ -0,0 +1,202 @@ +#include "cxxopts/cxxopts.hpp" +#include "farshow/framesender.hpp" +#include "farshow/streamexception.hpp" +#include "grabthecam/cameracapture.hpp" +#include "grabthecam/cameracapturetemplates.hpp" +#include "grabthecam/frameconverters/anyformat2bgrconverter.hpp" +#include "grabthecam/frameconverters/bayer2bgrconverter.hpp" +#include "grabthecam/frameconverters/yuv2bgrconverter.hpp" +#include "grabthecam/pixelformatsinfo.hpp" +#include +#include + +#include "grabthecam/utils.hpp" + +/** + * User's preferred configuration + */ +typedef struct Config +{ + std::string camera_filename; ///< Path to the camera file + std::string type = ""; ///< Raw frame type + std::vector dims = {}; ///< Frame width and height + unsigned int pix_format = 0; ///< Raw frame type – v4l2 code + bool use_trigger = false; ///< Whether to set up and use an external trigger + std::string ip; ///< IP Address for the farshow streamer + uint16_t port; ///< Port for the farshow streamer + std::string stream_name; ///< Name of the farshow stream + std::optional saveConfig; ///< Where to save the configuration + std::optional loadConfig; ///< Where to load the configuration from +} Config; + +/** + * Parse command line options + * + * @param argc Arguments counter + * @param argv Arguments values + */ +Config parseOptions(int argc, char const *argv[]) +{ + Config config; + cxxopts::ParseResult result; + + // Set available options + cxxopts::Options options(argv[0], "A simple camera frame streaming application with farshow."); + + // clang-format off + options.add_options() + ("c, camera", "Filename of a camera device", + cxxopts::value(config.camera_filename)->default_value("/dev/video0")) + ("t, type", "Frame type in V4L2 FourCC format (eg. RG12, RGGB) or compressed format type (supported: JPG, PNG)", + cxxopts::value(config.type)) + ("d, dims", "Frame width and height (eg. `960,720`)", + cxxopts::value(config.dims)) + ("a, address", "IP address for the farshow streamer", + cxxopts::value(config.ip)) + ("p, port", "Port for the farshow streamer", + cxxopts::value(config.port)) + ("n, name", "Stream name", + cxxopts::value(config.stream_name)->default_value("grabthecam_demo stream")) + // The only working syntax for save and load is --save=value and --load=value, + // it is a known limitation of cxxopts described in + // https://github.com/jarro2783/cxxopts/issues/210 where implicit values have to + // be assigned through the `=` sign or will otherwise be ignored + ("s, save", "Save configuration to the file. You can provide the filename or the " + ".pyvidctrl_ file will be used", + cxxopts::value(config.saveConfig)->implicit_value("")) + ("l, load", "Load the configuration from file. You can provide the filename or the" + " .pyvidctrl_ file will be used", + cxxopts::value(config.loadConfig)->implicit_value("")) + ("use_trigger", "Use any trigger source provided in the configuration file") + ("h, help", "Print usage"); + // clang-format on + + // Get command line parameters and parse them + try + { + result = options.parse(argc, argv); + } + catch (cxxopts::OptionException e) + { + std::cerr << std::endl + << "\033[31mError while parsing command line arguments: " << e.what() << "\033[0m" << std::endl + << std::endl; + std::cout << options.help() << std::endl; + exit(1); + } + catch (grabthecam::CameraException e) + { + std::cerr << "\033[31m" << e.what() << "\033[0m" << std::endl << std::endl; + std::cout << options.help() << std::endl; + exit(1); + } + + if (result.count("type")) + { + try + { + if (config.type.length() == 4) + { + config.pix_format = grabthecam::convertToV4l2Fourcc(config.type); + } + else if (config.type == "JPG") + { + config.pix_format = V4L2_PIX_FMT_MJPEG; + } + else + { + throw(std::out_of_range("Wrong format")); + } + } + catch (std::out_of_range e) + { + std::cerr << std::endl + << "\033[31mError while parsing command line arguments: Wrong value '" << config.type + << "' for parameter 'type'\033[0m" << std::endl + << std::endl; + std::cout << options.help() << std::endl; + exit(1); + } + } + + if (result.count("help")) + { + std::cout << options.help() << std::endl; + exit(0); + } + + if (result.count("use_trigger")) + { + config.use_trigger = true; + } + + return config; +} + +int main(int argc, char const *argv[]) +{ + // SET UP THE CAMERA + Config conf = parseOptions(argc, argv); ///< user's configuration + grabthecam::CameraCapture camera(conf.camera_filename); ///< cameracapture object + farshow::FrameSender streamer(conf.ip, conf.port); + + camera.printControls(); + + // adjust camera settings + if (!conf.dims.empty()) + { + camera.setFormat(conf.dims[0], conf.dims[1], conf.pix_format); // set frame format + } + else + { + camera.setFormat(0, 0, conf.pix_format); + } + auto format = camera.getFormat(); ///< Actually set frame format + double time_perframe; + + /////////////////////////////////////////////////////////////////////////////////////////////// + + std::cout << "Format set to " << conf.type << " " << std::get<0>(format) << " x " << std::get<1>(format) + << std::endl; + + if (conf.loadConfig.has_value()) + { + std::string filename = camera.loadConfig(*conf.loadConfig); + std::cout << "Configuration loaded from " << filename << std::endl; + } + + if (conf.saveConfig.has_value()) + { + std::string filename = camera.saveConfig(*conf.saveConfig); + std::cout << "Configuration saved to " << filename << std::endl; + } + + // detect if trigger information was set + if (camera.getTriggerInfo().has_value() && conf.use_trigger) + { + std::cout << "\nThe camera is now waiting for an external trigger." + "\nIn order to save the frame, please set it off via " + "an external tool\nor provide a triggering implementation " + "to CameraCapture::triggerFrame and call it.\n"; + camera.enableTrigger(); + } + + auto f = [&streamer, conf](cv::Mat foo) { streamer.sendFrame(foo, conf.stream_name); }; + // CAPTURE FRAMES + if (camera.hasConverter()) + { + cv::Mat processed_frame = camera.capture(); + while (1) + { + std::thread sender(f, processed_frame); + processed_frame = camera.capture(); + sender.join(); + } + } + else + { + throw grabthecam::CameraException(std::to_string(conf.pix_format) + + " is not convertable from RAW via CV2. Please specify another pixel format."); + } + return 0; +}