// This file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html. #include "precomp.hpp" #include "backend.hpp" #include "plugin_api.hpp" #include "plugin_capture_api.hpp" #include "plugin_writer_api.hpp" #include "opencv2/core/utils/configuration.private.hpp" #include "opencv2/core/utils/logger.hpp" #include "opencv2/core/private.hpp" #include "videoio_registry.hpp" //================================================================================================== // Dynamic backend implementation #include "opencv2/core/utils/plugin_loader.private.hpp" #include "backend_plugin_legacy.impl.hpp" namespace cv { namespace impl { #if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) using namespace cv::plugin::impl; // plugin_loader.hpp static Mutex& getInitializationMutex() { static Mutex initializationMutex; return initializationMutex; } class PluginBackend: public IBackend { protected: void initCaptureAPI() { const char* init_name = "opencv_videoio_capture_plugin_init_v1"; FN_opencv_videoio_capture_plugin_init_t fn_init = reinterpret_cast(lib_->getSymbol(init_name)); if (fn_init) { CV_LOG_INFO(NULL, "Found entry: '" << init_name << "'"); for (int supported_api_version = CAPTURE_API_VERSION; supported_api_version >= 0; supported_api_version--) { capture_api_ = fn_init(CAPTURE_ABI_VERSION, supported_api_version, NULL); if (capture_api_) break; } if (!capture_api_) { CV_LOG_INFO(NULL, "Video I/O: plugin is incompatible (can't be initialized): " << lib_->getName()); return; } if (!checkCompatibility( capture_api_->api_header, CAPTURE_ABI_VERSION, CAPTURE_API_VERSION, capture_api_->v0.id != CAP_FFMPEG)) { capture_api_ = NULL; return; } CV_LOG_INFO(NULL, "Video I/O: plugin is ready to use '" << capture_api_->api_header.api_description << "'"); } else { CV_LOG_INFO(NULL, "Video I/O: missing plugin init function: '" << init_name << "', file: " << lib_->getName()); } } void initWriterAPI() { const char* init_name = "opencv_videoio_writer_plugin_init_v1"; FN_opencv_videoio_writer_plugin_init_t fn_init = reinterpret_cast(lib_->getSymbol(init_name)); if (fn_init) { CV_LOG_INFO(NULL, "Found entry: '" << init_name << "'"); for (int supported_api_version = WRITER_API_VERSION; supported_api_version >= 0; supported_api_version--) { writer_api_ = fn_init(WRITER_ABI_VERSION, supported_api_version, NULL); if (writer_api_) break; } if (!writer_api_) { CV_LOG_INFO(NULL, "Video I/O: plugin is incompatible (can't be initialized): " << lib_->getName()); return; } if (!checkCompatibility( writer_api_->api_header, WRITER_ABI_VERSION, WRITER_API_VERSION, writer_api_->v0.id != CAP_FFMPEG)) { writer_api_ = NULL; return; } CV_LOG_INFO(NULL, "Video I/O: plugin is ready to use '" << writer_api_->api_header.api_description << "'"); } else { CV_LOG_INFO(NULL, "Video I/O: missing plugin init function: '" << init_name << "', file: " << lib_->getName()); } } void initPluginLegacyAPI() { const char* init_name = "opencv_videoio_plugin_init_v0"; FN_opencv_videoio_plugin_init_t fn_init = reinterpret_cast(lib_->getSymbol(init_name)); if (fn_init) { CV_LOG_INFO(NULL, "Found entry: '" << init_name << "'"); for (int supported_api_version = API_VERSION; supported_api_version >= 0; supported_api_version--) { plugin_api_ = fn_init(ABI_VERSION, supported_api_version, NULL); if (plugin_api_) break; } if (!plugin_api_) { CV_LOG_INFO(NULL, "Video I/O: plugin is incompatible (can't be initialized): " << lib_->getName()); return; } if (!checkCompatibility( plugin_api_->api_header, ABI_VERSION, API_VERSION, plugin_api_->v0.captureAPI != CAP_FFMPEG)) { plugin_api_ = NULL; return; } CV_LOG_INFO(NULL, "Video I/O: plugin is ready to use '" << plugin_api_->api_header.api_description << "'"); } else { CV_LOG_INFO(NULL, "Video I/O: plugin is incompatible, missing init function: '" << init_name << "', file: " << lib_->getName()); } } bool checkCompatibility(const OpenCV_API_Header& api_header, unsigned int abi_version, unsigned int api_version, bool checkMinorOpenCVVersion) { if (api_header.opencv_version_major != CV_VERSION_MAJOR) { CV_LOG_ERROR(NULL, "Video I/O: wrong OpenCV major version used by plugin '" << api_header.api_description << "': " << cv::format("%d.%d, OpenCV version is '" CV_VERSION "'", api_header.opencv_version_major, api_header.opencv_version_minor)) return false; } if (!checkMinorOpenCVVersion) { // no checks for OpenCV minor version } else if (api_header.opencv_version_minor != CV_VERSION_MINOR) { CV_LOG_ERROR(NULL, "Video I/O: wrong OpenCV minor version used by plugin '" << api_header.api_description << "': " << cv::format("%d.%d, OpenCV version is '" CV_VERSION "'", api_header.opencv_version_major, api_header.opencv_version_minor)) return false; } CV_LOG_INFO(NULL, "Video I/O: initialized '" << api_header.api_description << "': built with " << cv::format("OpenCV %d.%d (ABI/API = %d/%d)", api_header.opencv_version_major, api_header.opencv_version_minor, api_header.min_api_version, api_header.api_version) << ", current OpenCV version is '" CV_VERSION "' (ABI/API = " << abi_version << "/" << api_version << ")" ); if (api_header.min_api_version != abi_version) // future: range can be here { // actually this should never happen due to checks in plugin's init() function CV_LOG_ERROR(NULL, "Video I/O: plugin is not supported due to incompatible ABI = " << api_header.min_api_version); return false; } if (api_header.api_version != api_version) { CV_LOG_INFO(NULL, "Video I/O: NOTE: plugin is supported, but there is API version mismath: " << cv::format("plugin API level (%d) != OpenCV API level (%d)", api_header.api_version, api_version)); if (api_header.api_version < api_version) { CV_LOG_INFO(NULL, "Video I/O: NOTE: some functionality may be unavailable due to lack of support by plugin implementation"); } } return true; } public: Ptr lib_; const OpenCV_VideoIO_Capture_Plugin_API* capture_api_; const OpenCV_VideoIO_Writer_Plugin_API* writer_api_; const OpenCV_VideoIO_Plugin_API_preview* plugin_api_; //!< deprecated PluginBackend(const Ptr& lib) : lib_(lib) , capture_api_(NULL), writer_api_(NULL) , plugin_api_(NULL) { initCaptureAPI(); initWriterAPI(); if (capture_api_ == NULL && writer_api_ == NULL) { initPluginLegacyAPI(); } } Ptr createCapture(int camera) const; Ptr createCapture(int camera, const VideoCaptureParameters& params) const CV_OVERRIDE; Ptr createCapture(const std::string &filename) const; Ptr createCapture(const std::string &filename, const VideoCaptureParameters& params) const CV_OVERRIDE; Ptr createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const CV_OVERRIDE; }; class PluginBackendFactory : public IBackendFactory { public: VideoCaptureAPIs id_; const char* baseName_; Ptr backend; bool initialized; public: PluginBackendFactory(VideoCaptureAPIs id, const char* baseName) : id_(id), baseName_(baseName), initialized(false) { // nothing, plugins are loaded on demand } Ptr getBackend() const CV_OVERRIDE { if (!initialized) { const_cast(this)->initBackend(); } return backend.staticCast(); } protected: void initBackend() { AutoLock lock(getInitializationMutex()); try { if (!initialized) loadPlugin(); } catch (...) { CV_LOG_INFO(NULL, "Video I/O: exception during plugin loading: " << baseName_ << ". SKIP"); } initialized = true; } void loadPlugin(); }; static std::vector getPluginCandidates(const std::string& baseName) { using namespace cv::utils; using namespace cv::utils::fs; const std::string baseName_l = toLowerCase(baseName); const std::string baseName_u = toUpperCase(baseName); const FileSystemPath_t baseName_l_fs = toFileSystemPath(baseName_l); std::vector paths; const std::vector paths_ = getConfigurationParameterPaths("OPENCV_VIDEOIO_PLUGIN_PATH", std::vector()); if (paths_.size() != 0) { for (size_t i = 0; i < paths_.size(); i++) { paths.push_back(toFileSystemPath(paths_[i])); } } else { FileSystemPath_t binaryLocation; if (getBinLocation(binaryLocation)) { binaryLocation = getParent(binaryLocation); #ifndef CV_VIDEOIO_PLUGIN_SUBDIRECTORY paths.push_back(binaryLocation); #else paths.push_back(binaryLocation + toFileSystemPath("/") + toFileSystemPath(CV_VIDEOIO_PLUGIN_SUBDIRECTORY_STR)); #endif } } const std::string default_expr = libraryPrefix() + "opencv_videoio_" + baseName_l + "*" + librarySuffix(); const std::string plugin_expr = getConfigurationParameterString((std::string("OPENCV_VIDEOIO_PLUGIN_") + baseName_u).c_str(), default_expr.c_str()); std::vector results; #ifdef _WIN32 FileSystemPath_t moduleName = toFileSystemPath(libraryPrefix() + "opencv_videoio_" + baseName_l + librarySuffix()); #ifndef WINRT if (baseName_u == "FFMPEG") // backward compatibility { const wchar_t* ffmpeg_env_path = _wgetenv(L"OPENCV_FFMPEG_DLL_DIR"); if (ffmpeg_env_path) { results.push_back(FileSystemPath_t(ffmpeg_env_path) + L"\\" + moduleName); } } #endif if (plugin_expr != default_expr) { moduleName = toFileSystemPath(plugin_expr); results.push_back(moduleName); } for (const FileSystemPath_t& path : paths) { results.push_back(path + L"\\" + moduleName); } results.push_back(moduleName); #if defined(_DEBUG) && defined(DEBUG_POSTFIX) if (baseName_u == "FFMPEG") // backward compatibility { const FileSystemPath_t templ = toFileSystemPath(CVAUX_STR(DEBUG_POSTFIX) ".dll"); FileSystemPath_t nonDebugName(moduleName); size_t suf = nonDebugName.rfind(templ); if (suf != FileSystemPath_t::npos) { nonDebugName.replace(suf, suf + templ.size(), L".dll"); results.push_back(nonDebugName); } } #endif // _DEBUG && DEBUG_POSTFIX #else CV_LOG_INFO(NULL, "VideoIO plugin (" << baseName << "): glob is '" << plugin_expr << "', " << paths.size() << " location(s)"); for (const std::string& path : paths) { if (path.empty()) continue; std::vector candidates; cv::glob(utils::fs::join(path, plugin_expr), candidates); CV_LOG_INFO(NULL, " - " << path << ": " << candidates.size()); copy(candidates.begin(), candidates.end(), back_inserter(results)); } #endif CV_LOG_INFO(NULL, "Found " << results.size() << " plugin(s) for " << baseName); return results; } void PluginBackendFactory::loadPlugin() { for (const FileSystemPath_t& plugin : getPluginCandidates(baseName_)) { auto lib = makePtr(plugin); if (!lib->isLoaded()) continue; try { Ptr pluginBackend = makePtr(lib); if (!pluginBackend) return; if (pluginBackend->capture_api_) { if (pluginBackend->capture_api_->v0.id != id_) { CV_LOG_ERROR(NULL, "Video I/O: plugin '" << pluginBackend->capture_api_->api_header.api_description << "': unexpected backend ID: " << pluginBackend->capture_api_->v0.id << " vs " << (int)id_ << " (expected)"); return; } } if (pluginBackend->writer_api_) { if (pluginBackend->writer_api_->v0.id != id_) { CV_LOG_ERROR(NULL, "Video I/O: plugin '" << pluginBackend->writer_api_->api_header.api_description << "': unexpected backend ID: " << pluginBackend->writer_api_->v0.id << " vs " << (int)id_ << " (expected)"); return; } } if (pluginBackend->plugin_api_) { if (pluginBackend->plugin_api_->v0.captureAPI != id_) { CV_LOG_ERROR(NULL, "Video I/O: plugin '" << pluginBackend->plugin_api_->api_header.api_description << "': unexpected backend ID: " << pluginBackend->plugin_api_->v0.captureAPI << " vs " << (int)id_ << " (expected)"); return; } } if (pluginBackend->capture_api_ == NULL && pluginBackend->writer_api_ == NULL && pluginBackend->plugin_api_ == NULL) { CV_LOG_ERROR(NULL, "Video I/O: no compatible plugin API for backend ID: " << (int)id_); return; } backend = pluginBackend; return; } catch (...) { CV_LOG_WARNING(NULL, "Video I/O: exception during plugin initialization: " << toPrintablePath(plugin) << ". SKIP"); } } } //================================================================================================== class PluginCapture : public cv::IVideoCapture { const OpenCV_VideoIO_Capture_Plugin_API* plugin_api_; CvPluginCapture capture_; public: static Ptr create(const OpenCV_VideoIO_Capture_Plugin_API* plugin_api, const std::string &filename, int camera, const VideoCaptureParameters& params) { CV_Assert(plugin_api); CV_Assert(plugin_api->v0.Capture_release); CvPluginCapture capture = NULL; if (plugin_api->api_header.api_version >= 1 && plugin_api->v1.Capture_open_with_params) { std::vector vint_params = params.getIntVector(); int* c_params = vint_params.data(); unsigned n_params = (unsigned)(vint_params.size() / 2); if (CV_ERROR_OK == plugin_api->v1.Capture_open_with_params( filename.empty() ? 0 : filename.c_str(), camera, c_params, n_params, &capture)) { CV_Assert(capture); return makePtr(plugin_api, capture); } } else if (plugin_api->v0.Capture_open) { if (CV_ERROR_OK == plugin_api->v0.Capture_open(filename.empty() ? 0 : filename.c_str(), camera, &capture)) { CV_Assert(capture); Ptr cap = makePtr(plugin_api, capture); if (cap && !params.empty()) { applyParametersFallback(cap, params); } return cap; } } return Ptr(); } PluginCapture(const OpenCV_VideoIO_Capture_Plugin_API* plugin_api, CvPluginCapture capture) : plugin_api_(plugin_api), capture_(capture) { CV_Assert(plugin_api_); CV_Assert(capture_); } ~PluginCapture() { CV_DbgAssert(plugin_api_->v0.Capture_release); if (CV_ERROR_OK != plugin_api_->v0.Capture_release(capture_)) CV_LOG_ERROR(NULL, "Video I/O: Can't release capture by plugin '" << plugin_api_->api_header.api_description << "'"); capture_ = NULL; } double getProperty(int prop) const CV_OVERRIDE { double val = -1; if (plugin_api_->v0.Capture_getProperty) if (CV_ERROR_OK != plugin_api_->v0.Capture_getProperty(capture_, prop, &val)) val = -1; return val; } bool setProperty(int prop, double val) CV_OVERRIDE { if (plugin_api_->v0.Capture_setProperty) if (CV_ERROR_OK == plugin_api_->v0.Capture_setProperty(capture_, prop, val)) return true; return false; } bool grabFrame() CV_OVERRIDE { if (plugin_api_->v0.Capture_grab) if (CV_ERROR_OK == plugin_api_->v0.Capture_grab(capture_)) return true; return false; } static CvResult CV_API_CALL retrieve_callback(int stream_idx, const unsigned char* data, int step, int width, int height, int type, void* userdata) { CV_UNUSED(stream_idx); cv::_OutputArray* dst = static_cast(userdata); if (!dst) return CV_ERROR_FAIL; cv::Mat(cv::Size(width, height), type, (void*)data, step).copyTo(*dst); return CV_ERROR_OK; } bool retrieveFrame(int idx, cv::OutputArray img) CV_OVERRIDE { bool res = false; if (plugin_api_->v0.Capture_retreive) if (CV_ERROR_OK == plugin_api_->v0.Capture_retreive(capture_, idx, retrieve_callback, (cv::_OutputArray*)&img)) res = true; return res; } bool isOpened() const CV_OVERRIDE { return capture_ != NULL; // TODO always true } int getCaptureDomain() CV_OVERRIDE { return plugin_api_->v0.id; } }; //================================================================================================== class PluginWriter : public cv::IVideoWriter { const OpenCV_VideoIO_Writer_Plugin_API* plugin_api_; CvPluginWriter writer_; public: static Ptr create(const OpenCV_VideoIO_Writer_Plugin_API* plugin_api, const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) { CV_Assert(plugin_api); CV_Assert(plugin_api->v0.Writer_release); CV_Assert(!filename.empty()); CvPluginWriter writer = NULL; if (plugin_api->api_header.api_version >= 1 && plugin_api->v1.Writer_open_with_params) { std::vector vint_params = params.getIntVector(); int* c_params = &vint_params[0]; unsigned n_params = (unsigned)(vint_params.size() / 2); if (CV_ERROR_OK == plugin_api->v1.Writer_open_with_params(filename.c_str(), fourcc, fps, sz.width, sz.height, c_params, n_params, &writer)) { CV_Assert(writer); return makePtr(plugin_api, writer); } } else if (plugin_api->v0.Writer_open) { const bool isColor = params.get(VIDEOWRITER_PROP_IS_COLOR, true); const int depth = params.get(VIDEOWRITER_PROP_DEPTH, CV_8U); if (depth != CV_8U) { CV_LOG_WARNING(NULL, "Video I/O plugin doesn't support (due to lower API level) creation of VideoWriter with depth != CV_8U"); return Ptr(); } if (params.warnUnusedParameters()) { CV_LOG_ERROR(NULL, "VIDEOIO: unsupported parameters in VideoWriter, see logger INFO channel for details"); return Ptr(); } if (CV_ERROR_OK == plugin_api->v0.Writer_open(filename.c_str(), fourcc, fps, sz.width, sz.height, isColor, &writer)) { CV_Assert(writer); return makePtr(plugin_api, writer); } } return Ptr(); } PluginWriter(const OpenCV_VideoIO_Writer_Plugin_API* plugin_api, CvPluginWriter writer) : plugin_api_(plugin_api), writer_(writer) { CV_Assert(plugin_api_); CV_Assert(writer_); } ~PluginWriter() { CV_DbgAssert(plugin_api_->v0.Writer_release); if (CV_ERROR_OK != plugin_api_->v0.Writer_release(writer_)) CV_LOG_ERROR(NULL, "Video I/O: Can't release writer by plugin '" << plugin_api_->api_header.api_description << "'"); writer_ = NULL; } double getProperty(int prop) const CV_OVERRIDE { double val = -1; if (plugin_api_->v0.Writer_getProperty) if (CV_ERROR_OK != plugin_api_->v0.Writer_getProperty(writer_, prop, &val)) val = -1; return val; } bool setProperty(int prop, double val) CV_OVERRIDE { if (plugin_api_->v0.Writer_setProperty) if (CV_ERROR_OK == plugin_api_->v0.Writer_setProperty(writer_, prop, val)) return true; return false; } bool isOpened() const CV_OVERRIDE { return writer_ != NULL; // TODO always true } void write(cv::InputArray arr) CV_OVERRIDE { cv::Mat img = arr.getMat(); CV_DbgAssert(writer_); CV_Assert(plugin_api_->v0.Writer_write); if (CV_ERROR_OK != plugin_api_->v0.Writer_write(writer_, img.data, (int)img.step[0], img.cols, img.rows, img.channels())) { CV_LOG_DEBUG(NULL, "Video I/O: Can't write frame by plugin '" << plugin_api_->api_header.api_description << "'"); } // TODO return bool result? } int getCaptureDomain() const CV_OVERRIDE { return plugin_api_->v0.id; } }; Ptr PluginBackend::createCapture(int camera, const VideoCaptureParameters& params) const { try { if (capture_api_) return PluginCapture::create(capture_api_, std::string(), camera, params); //.staticCast(); if (plugin_api_) { Ptr cap = legacy::PluginCapture::create(plugin_api_, std::string(), camera); //.staticCast(); if (cap && !params.empty()) { applyParametersFallback(cap, params); } return cap; } } catch (...) { CV_LOG_DEBUG(NULL, "Video I/O: can't create camera capture: " << camera); throw; } return Ptr(); } Ptr PluginBackend::createCapture(const std::string &filename, const VideoCaptureParameters& params) const { try { if (capture_api_) return PluginCapture::create(capture_api_, filename, 0, params); //.staticCast(); if (plugin_api_) { Ptr cap = legacy::PluginCapture::create(plugin_api_, filename, 0); //.staticCast(); if (cap && !params.empty()) { applyParametersFallback(cap, params); } return cap; } } catch (...) { CV_LOG_DEBUG(NULL, "Video I/O: can't open file capture: " << filename); throw; } return Ptr(); } Ptr PluginBackend::createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const { try { if (writer_api_) return PluginWriter::create(writer_api_, filename, fourcc, fps, sz, params); //.staticCast(); if (plugin_api_) return legacy::PluginWriter::create(plugin_api_, filename, fourcc, fps, sz, params); //.staticCast(); } catch (...) { CV_LOG_DEBUG(NULL, "Video I/O: can't open writer: " << filename); } return Ptr(); } #endif // OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) } // namespace Ptr createPluginBackendFactory(VideoCaptureAPIs id, const char* baseName) { #if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) return makePtr(id, baseName); //.staticCast(); #else return Ptr(); #endif } } // namespace