From 9702987209a9a17ee2d0626c535d2b7bbc6ba1bc Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Mon, 14 Jan 2019 13:33:38 +0300 Subject: [PATCH] Framework for dynamic videoio backends, FFmpeg and GStreamer plugins --- modules/core/include/opencv2/core/private.hpp | 4 + .../include/opencv2/core/utils/filesystem.hpp | 3 + modules/core/src/utils/datafile.cpp | 7 +- modules/core/src/utils/filesystem.cpp | 8 + modules/videoio/CMakeLists.txt | 34 +- modules/videoio/cmake/detect_ffmpeg.cmake | 5 +- modules/videoio/cmake/init.cmake | 2 + modules/videoio/cmake/plugin.cmake | 99 +++ .../include/opencv2/videoio/registry.hpp | 10 + modules/videoio/misc/build_plugins.sh | 81 +++ modules/videoio/misc/plugin_ffmpeg/.gitignore | 1 + .../videoio/misc/plugin_ffmpeg/CMakeLists.txt | 13 + .../misc/plugin_ffmpeg/Dockerfile-ffmpeg | 45 ++ .../misc/plugin_ffmpeg/Dockerfile-ubuntu | 17 + .../misc/plugin_ffmpeg/build-standalone.sh | 24 + .../misc/plugin_ffmpeg/build-ubuntu.sh | 10 + .../misc/plugin_gstreamer/CMakeLists.txt | 8 + .../videoio/misc/plugin_gstreamer/Dockerfile | 13 + .../videoio/misc/plugin_gstreamer/build.sh | 11 + modules/videoio/src/backend.cpp | 491 ++++++++++++++ modules/videoio/src/backend.hpp | 85 +++ modules/videoio/src/cap.cpp | 110 +--- modules/videoio/src/cap_aravis.cpp | 5 +- modules/videoio/src/cap_avfoundation.mm | 33 +- modules/videoio/src/cap_avfoundation_mac.mm | 31 +- modules/videoio/src/cap_dc1394_v2.cpp | 7 +- modules/videoio/src/cap_dshow.cpp | 6 + modules/videoio/src/cap_dshow.hpp | 2 +- modules/videoio/src/cap_ffmpeg.cpp | 171 ++++- modules/videoio/src/cap_ffmpeg_impl.hpp | 50 +- modules/videoio/src/cap_gphoto2.cpp | 2 +- modules/videoio/src/cap_gstreamer.cpp | 250 +++++-- modules/videoio/src/cap_images.cpp | 18 +- modules/videoio/src/cap_interface.hpp | 211 ++++++ modules/videoio/src/cap_librealsense.cpp | 6 + modules/videoio/src/cap_librealsense.hpp | 1 + modules/videoio/src/cap_mfx_common.hpp | 4 +- modules/videoio/src/cap_mfx_reader.cpp | 6 + modules/videoio/src/cap_mfx_reader.hpp | 14 +- modules/videoio/src/cap_mfx_writer.cpp | 3 +- modules/videoio/src/cap_mfx_writer.hpp | 14 +- modules/videoio/src/cap_mjpeg_encoder.cpp | 2 +- modules/videoio/src/cap_msmf.cpp | 6 +- modules/videoio/src/cap_openni2.cpp | 11 +- modules/videoio/src/cap_pvapi.cpp | 5 +- modules/videoio/src/cap_v4l.cpp | 16 +- modules/videoio/src/cap_winrt_capture.cpp | 8 +- modules/videoio/src/cap_winrt_capture.hpp | 2 +- modules/videoio/src/cap_ximea.cpp | 12 +- modules/videoio/src/cap_xine.cpp | 4 +- modules/videoio/src/plugin_api.cpp | 19 + modules/videoio/src/plugin_api.hpp | 71 ++ modules/videoio/src/precomp.hpp | 117 +--- modules/videoio/src/videoio_c.cpp | 75 +-- modules/videoio/src/videoio_registry.cpp | 612 +++++------------- modules/videoio/src/videoio_registry.hpp | 35 +- modules/videoio/test/test_camera.cpp | 10 +- modules/videoio/test/test_container_avi.cpp | 13 +- modules/videoio/test/test_dynamic.cpp | 83 +++ modules/videoio/test/test_ffmpeg.cpp | 555 +++++----------- modules/videoio/test/test_fourcc.cpp | 118 ---- modules/videoio/test/test_gstreamer.cpp | 12 +- modules/videoio/test/test_mfx.cpp | 10 +- modules/videoio/test/test_video_io.cpp | 43 +- 64 files changed, 2278 insertions(+), 1476 deletions(-) create mode 100644 modules/videoio/cmake/plugin.cmake create mode 100755 modules/videoio/misc/build_plugins.sh create mode 100644 modules/videoio/misc/plugin_ffmpeg/.gitignore create mode 100644 modules/videoio/misc/plugin_ffmpeg/CMakeLists.txt create mode 100644 modules/videoio/misc/plugin_ffmpeg/Dockerfile-ffmpeg create mode 100644 modules/videoio/misc/plugin_ffmpeg/Dockerfile-ubuntu create mode 100755 modules/videoio/misc/plugin_ffmpeg/build-standalone.sh create mode 100755 modules/videoio/misc/plugin_ffmpeg/build-ubuntu.sh create mode 100644 modules/videoio/misc/plugin_gstreamer/CMakeLists.txt create mode 100644 modules/videoio/misc/plugin_gstreamer/Dockerfile create mode 100755 modules/videoio/misc/plugin_gstreamer/build.sh create mode 100644 modules/videoio/src/backend.cpp create mode 100644 modules/videoio/src/backend.hpp create mode 100644 modules/videoio/src/cap_interface.hpp create mode 100644 modules/videoio/src/plugin_api.cpp create mode 100644 modules/videoio/src/plugin_api.hpp create mode 100644 modules/videoio/test/test_dynamic.cpp delete mode 100644 modules/videoio/test/test_fourcc.cpp diff --git a/modules/core/include/opencv2/core/private.hpp b/modules/core/include/opencv2/core/private.hpp index b451875a2b1f..32a5e6b6bdc3 100644 --- a/modules/core/include/opencv2/core/private.hpp +++ b/modules/core/include/opencv2/core/private.hpp @@ -865,6 +865,10 @@ Passed subdirectories are used in LIFO order. */ CV_EXPORTS void addDataSearchSubDirectory(const cv::String& subdir); +/** @brief Return location of OpenCV libraries or current executable + */ +CV_EXPORTS std::string getBinLocation(); + //! @} } // namespace utils diff --git a/modules/core/include/opencv2/core/utils/filesystem.hpp b/modules/core/include/opencv2/core/utils/filesystem.hpp index 00b0dd1c123e..9f043bef5072 100644 --- a/modules/core/include/opencv2/core/utils/filesystem.hpp +++ b/modules/core/include/opencv2/core/utils/filesystem.hpp @@ -26,6 +26,9 @@ CV_EXPORTS cv::String canonical(const cv::String& path); /** Join path components */ CV_EXPORTS cv::String join(const cv::String& base, const cv::String& path); +/** Get parent directory */ +CV_EXPORTS cv::String getParent(const cv::String &path); + /** * Generate a list of all files that match the globbing pattern. * diff --git a/modules/core/src/utils/datafile.cpp b/modules/core/src/utils/datafile.cpp index f1107b097a1c..b3dc31f52b2d 100644 --- a/modules/core/src/utils/datafile.cpp +++ b/modules/core/src/utils/datafile.cpp @@ -144,6 +144,11 @@ static cv::String getModuleLocation(const void* addr) return cv::String(); } +std::string getBinLocation() +{ + return getModuleLocation((void*)getModuleLocation); // use code addr, doesn't work with static linkage! +} + cv::String findDataFile(const cv::String& relative_path, const char* configuration_parameter, const std::vector* search_paths, @@ -287,7 +292,7 @@ cv::String findDataFile(const cv::String& relative_path, } } - cv::String module_path = getModuleLocation((void*)getModuleLocation); // use code addr, doesn't work with static linkage! + cv::String module_path = getBinLocation(); CV_LOG_DEBUG(NULL, "Detected module path: '" << module_path << '\''); if (!has_tested_build_directory && diff --git a/modules/core/src/utils/filesystem.cpp b/modules/core/src/utils/filesystem.cpp index 43503f1d7211..b0dc9d559cd7 100644 --- a/modules/core/src/utils/filesystem.cpp +++ b/modules/core/src/utils/filesystem.cpp @@ -83,6 +83,14 @@ cv::String join(const cv::String& base, const cv::String& path) return result; } +CV_EXPORTS cv::String getParent(const cv::String &path) +{ + std::string::size_type loc = path.find_last_of("/\\"); + if (loc == std::string::npos) + return std::string(); + return std::string(path, 0, loc); +} + #if OPENCV_HAVE_FILESYSTEM_SUPPORT cv::String canonical(const cv::String& path) diff --git a/modules/videoio/CMakeLists.txt b/modules/videoio/CMakeLists.txt index ebec00f1daa4..7dcabb335d03 100644 --- a/modules/videoio/CMakeLists.txt +++ b/modules/videoio/CMakeLists.txt @@ -1,3 +1,8 @@ +set(VIDEOIO_PLUGIN_LIST "" CACHE STRING "List of videoio backends to be compiled as plugins (ffmpeg, gstreamer)") +set(VIDEOIO_ENABLE_PLUGINS "ON" CACHE BOOL "Allow building videoio plugin support") +set(VIDEOIO_ENABLE_STRICT_PLUGIN_CHECK "ON" CACHE BOOL "Make sure OpenCV version is the same in plugin and host code") +mark_as_advanced(VIDEOIO_PLUGIN_LIST VIDEOIO_ENABLE_PLUGINS VIDEOIO_ENABLE_STRICT_PLUGIN_CHECK) + ocv_add_module(videoio opencv_imgproc opencv_imgcodecs WRAP java python) set(videoio_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/precomp.hpp) @@ -9,6 +14,7 @@ set(videoio_srcs "${CMAKE_CURRENT_LIST_DIR}/src/cap_images.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/cap_mjpeg_encoder.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/cap_mjpeg_decoder.cpp" + "${CMAKE_CURRENT_LIST_DIR}/src/backend.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/container_avi.cpp") file(GLOB videoio_ext_hdrs @@ -86,9 +92,15 @@ if(TARGET ocv.3rdparty.dc1394_2) list(APPEND tgts ocv.3rdparty.dc1394_2) endif() +include(${CMAKE_CURRENT_LIST_DIR}/cmake/plugin.cmake) + if(TARGET ocv.3rdparty.gstreamer) - list(APPEND videoio_srcs ${CMAKE_CURRENT_LIST_DIR}/src/cap_gstreamer.cpp) - list(APPEND tgts ocv.3rdparty.gstreamer) + if("gstreamer" IN_LIST VIDEOIO_PLUGIN_LIST) + ocv_create_builtin_videoio_plugin("opencv_videoio_gstreamer" ocv.3rdparty.gstreamer "cap_gstreamer.cpp") + else() + list(APPEND videoio_srcs ${CMAKE_CURRENT_LIST_DIR}/src/cap_gstreamer.cpp) + list(APPEND tgts ocv.3rdparty.gstreamer) + endif() endif() if(TARGET ocv.3rdparty.v4l) @@ -107,9 +119,13 @@ if(TARGET ocv.3rdparty.ximea) endif() if(TARGET ocv.3rdparty.ffmpeg) - list(APPEND videoio_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/cap_ffmpeg_impl.hpp) - list(APPEND videoio_srcs ${CMAKE_CURRENT_LIST_DIR}/src/cap_ffmpeg.cpp) - list(APPEND tgts ocv.3rdparty.ffmpeg) + if("ffmpeg" IN_LIST VIDEOIO_PLUGIN_LIST) + ocv_create_builtin_videoio_plugin("opencv_videoio_ffmpeg" ocv.3rdparty.ffmpeg "cap_ffmpeg.cpp") + else() + list(APPEND videoio_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/cap_ffmpeg_impl.hpp) + list(APPEND videoio_srcs ${CMAKE_CURRENT_LIST_DIR}/src/cap_ffmpeg.cpp) + list(APPEND tgts ocv.3rdparty.ffmpeg) + endif() endif() if(TARGET ocv.3rdparty.pvapi) @@ -155,6 +171,14 @@ ocv_create_module() ocv_add_accuracy_tests(${tgts}) ocv_add_perf_tests(${tgts}) +if(VIDEOIO_ENABLE_PLUGINS) + ocv_target_compile_definitions(${the_module} PRIVATE ENABLE_PLUGINS) +endif() + +if(VIDEOIO_ENABLE_STRICT_PLUGIN_CHECK) + ocv_target_compile_definitions(${the_module} PRIVATE STRICT_PLUGIN_CHECK) +endif() + ocv_target_link_libraries(${the_module} LINK_PRIVATE ${tgts}) # copy FFmpeg dll to the output folder diff --git a/modules/videoio/cmake/detect_ffmpeg.cmake b/modules/videoio/cmake/detect_ffmpeg.cmake index 46acaaa70e67..ea1295d820bf 100644 --- a/modules/videoio/cmake/detect_ffmpeg.cmake +++ b/modules/videoio/cmake/detect_ffmpeg.cmake @@ -31,10 +31,9 @@ if(NOT HAVE_FFMPEG AND PKG_CONFIG_FOUND) if(FFMPEG_libavresample_FOUND) list(APPEND FFMPEG_LIBRARIES ${FFMPEG_libavresample_LIBRARIES}) endif() - # rewrite libraries to absolute paths foreach(lib ${FFMPEG_LIBRARIES}) - find_library(FFMPEG_ABSOLUTE_${lib} "${lib}" PATHS "${FFMPEG_lib${lib}_LIBDIR}" NO_DEFAULT_PATH) + find_library(FFMPEG_ABSOLUTE_${lib} "${lib}" PATHS "${FFMPEG_lib${lib}_LIBDIR}" "${FFMPEG_LIBRARY_DIRS}" NO_DEFAULT_PATH) if(FFMPEG_ABSOLUTE_${lib}) list(APPEND ffmpeg_abs_libs "${FFMPEG_ABSOLUTE_${lib}}") else() @@ -49,7 +48,7 @@ endif() #================================== -if(HAVE_FFMPEG AND NOT HAVE_FFMPEG_WRAPPER) +if(HAVE_FFMPEG AND NOT HAVE_FFMPEG_WRAPPER AND NOT OPENCV_FFMPEG_SKIP_BUILD_CHECK) try_compile(__VALID_FFMPEG "${OpenCV_BINARY_DIR}" "${OpenCV_SOURCE_DIR}/cmake/checks/ffmpeg_test.cpp" diff --git a/modules/videoio/cmake/init.cmake b/modules/videoio/cmake/init.cmake index 5e71c2e72479..56ff4560c4d6 100644 --- a/modules/videoio/cmake/init.cmake +++ b/modules/videoio/cmake/init.cmake @@ -19,6 +19,8 @@ function(ocv_add_external_target name inc link def) endif() endfunction() +include(FindPkgConfig) + add_backend("ffmpeg" WITH_FFMPEG) add_backend("gstreamer" WITH_GSTREAMER) add_backend("v4l" WITH_V4L) diff --git a/modules/videoio/cmake/plugin.cmake b/modules/videoio/cmake/plugin.cmake new file mode 100644 index 000000000000..8aa05c29782d --- /dev/null +++ b/modules/videoio/cmake/plugin.cmake @@ -0,0 +1,99 @@ +#============================================= +# build with OpenCV + +function(ocv_create_builtin_videoio_plugin name target videoio_src_file) + + if(NOT TARGET ${target}) + message(FATAL_ERROR "${target} does not exist!") + endif() + if(NOT OpenCV_SOURCE_DIR) + message(FATAL_ERROR "OpenCV_SOURCE_DIR must be set to build the plugin!") + endif() + + add_library(${name} MODULE + "${CMAKE_CURRENT_LIST_DIR}/src/${videoio_src_file}" + "${CMAKE_CURRENT_LIST_DIR}/src/plugin_api.cpp") + target_include_directories(${name} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + target_compile_definitions(${name} PRIVATE BUILD_PLUGIN) + target_link_libraries(${name} PRIVATE ${target}) + + foreach(mod opencv_videoio opencv_core opencv_imgproc opencv_imgcodecs) + target_link_libraries(${name} PRIVATE ${mod}) + target_include_directories(${name} PRIVATE "${OPENCV_MODULE_${mod}_LOCATION}/include") + endforeach() + + set_target_properties(${name} PROPERTIES + CXX_STANDARD 11 + CXX_VISIBILITY_PRESET hidden + ) + install(TARGETS ${name} LIBRARY DESTINATION ${OPENCV_LIB_INSTALL_PATH} COMPONENT plugins OPTIONAL) + +endfunction() + +#============================================= +# standalone build + +function(ocv_create_videoio_plugin default_name target target_desc videoio_src_file) + + set(OPENCV_PLUGIN_NAME ${default_name} CACHE STRING "") + set(OPENCV_PLUGIN_DESTINATION "" CACHE PATH "") + project(${OPENCV_PLUGIN_NAME} LANGUAGES CXX) + + set(BUILD_SHARED_LIBS ON CACHE BOOL "") + if(NOT BUILD_SHARED_LIBS) + message(FATAL_ERROR "Static plugin build does not make sense") + endif() + + if(NOT OpenCV_SOURCE_DIR) + message(FATAL_ERROR "OpenCV_SOURCE_DIR must be set to build the plugin!") + endif() + + include("${OpenCV_SOURCE_DIR}/modules/videoio/cmake/init.cmake") + + if(NOT TARGET ${target}) + message(FATAL_ERROR "${target_desc} was not found!") + endif() + + set(modules_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../..") + set(videoio_ROOT "${modules_ROOT}/videoio") + set(core_ROOT "${modules_ROOT}/core") + set(imgproc_ROOT "${modules_ROOT}/imgproc") + set(imgcodecs_ROOT "${modules_ROOT}/imgcodecs") + + add_library(${OPENCV_PLUGIN_NAME} MODULE "${videoio_ROOT}/src/${videoio_src_file}" "${videoio_ROOT}/src/plugin_api.cpp") + target_include_directories(${OPENCV_PLUGIN_NAME} PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}" + "${videoio_ROOT}/src" + "${videoio_ROOT}/include" + "${core_ROOT}/include" + "${imgproc_ROOT}/include" + "${imgcodecs_ROOT}/include" + ) + target_compile_definitions(${OPENCV_PLUGIN_NAME} PRIVATE BUILD_PLUGIN) + + # Fixes for build + target_compile_definitions(${OPENCV_PLUGIN_NAME} PRIVATE __OPENCV_BUILD) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cvconfig.h" "#pragma once") + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cv_cpu_config.h" "#pragma once") + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/opencv2/opencv_modules.hpp" "#pragma once") + + target_link_libraries(${OPENCV_PLUGIN_NAME} PRIVATE ${target}) + set_target_properties(${OPENCV_PLUGIN_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_VISIBILITY_PRESET hidden + ) + + # Hack for Windows + if(WIN32) + find_package(OpenCV REQUIRED core imgproc videoio) + target_link_libraries(${OPENCV_PLUGIN_NAME} ${OpenCV_LIBS}) + endif() + + if(OPENCV_PLUGIN_DESTINATION) + set_target_properties(${OPENCV_PLUGIN_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${OPENCV_PLUGIN_DESTINATION}") + message(STATUS "Output destination: ${OPENCV_PLUGIN_DESTINATION}") + endif() + + message(STATUS "Library name: ${OPENCV_PLUGIN_NAME}") + +endfunction() diff --git a/modules/videoio/include/opencv2/videoio/registry.hpp b/modules/videoio/include/opencv2/videoio/registry.hpp index 7404c6811646..0bc6ec5bad6b 100644 --- a/modules/videoio/include/opencv2/videoio/registry.hpp +++ b/modules/videoio/include/opencv2/videoio/registry.hpp @@ -38,6 +38,16 @@ CV_EXPORTS_W std::vector getStreamBackends(); /** @brief Returns list of available backends which works via `cv::VideoWriter()` */ CV_EXPORTS_W std::vector getWriterBackends(); +enum Capability +{ + Read, + Write, + ReadWrite +}; + +/** @brief Returns true if backend is available */ +CV_EXPORTS bool hasBackend(VideoCaptureAPIs api, Capability cap = ReadWrite); + //! @} }} // namespace diff --git a/modules/videoio/misc/build_plugins.sh b/modules/videoio/misc/build_plugins.sh new file mode 100755 index 000000000000..938ae7643bdc --- /dev/null +++ b/modules/videoio/misc/build_plugins.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +set -e + +if [ -z $1 ] ; then + echo "