#include "opencv2/core/utility.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" #include #include #include using namespace std; using namespace cv; //================================================================================ template inline typename M::mapped_type getValue(const M &dict, const typename M::key_type &key, const string & errorMessage) { typename M::const_iterator it = dict.find(key); if (it == dict.end()) { CV_Error(Error::StsBadArg, errorMessage); } return it->second; } inline map sizeByResolution() { map res; res["720p"] = Size(1280, 720); res["1080p"] = Size(1920, 1080); res["4k"] = Size(3840, 2160); return res; } inline map fourccByCodec() { map res; res["h264"] = VideoWriter::fourcc('H','2','6','4'); res["h265"] = VideoWriter::fourcc('H','E','V','C'); res["mpeg2"] = VideoWriter::fourcc('M','P','E','G'); res["mpeg4"] = VideoWriter::fourcc('M','P','4','2'); res["mjpeg"] = VideoWriter::fourcc('M','J','P','G'); res["vp8"] = VideoWriter::fourcc('V','P','8','0'); return res; } inline map defaultEncodeElementByCodec() { map res; res["h264"] = "x264enc"; res["h265"] = "x265enc"; res["mpeg2"] = "mpeg2enc"; res["mjpeg"] = "jpegenc"; res["vp8"] = "vp8enc"; return res; } inline map VAAPIEncodeElementByCodec() { map res; res["h264"] = "parsebin ! vaapih264enc"; res["h265"] = "parsebin ! vaapih265enc"; res["mpeg2"] = "parsebin ! vaapimpeg2enc"; res["mjpeg"] = "parsebin ! vaapijpegenc"; res["vp8"] = "parsebin ! vaapivp8enc"; return res; } inline map mfxDecodeElementByCodec() { map res; res["h264"] = "parsebin ! mfxh264dec"; res["h265"] = "parsebin ! mfxhevcdec"; res["mpeg2"] = "parsebin ! mfxmpeg2dec"; res["mjpeg"] = "parsebin ! mfxjpegdec"; return res; } inline map mfxEncodeElementByCodec() { map res; res["h264"] = "mfxh264enc"; res["h265"] = "mfxhevcenc"; res["mpeg2"] = "mfxmpeg2enc"; res["mjpeg"] = "mfxjpegenc"; return res; } inline map libavDecodeElementByCodec() { map res; res["h264"] = "parsebin ! avdec_h264"; res["h265"] = "parsebin ! avdec_h265"; res["mpeg2"] = "parsebin ! avdec_mpeg2video"; res["mpeg4"] = "parsebin ! avdec_mpeg4"; res["mjpeg"] = "parsebin ! avdec_mjpeg"; res["vp8"] = "parsebin ! avdec_vp8"; return res; } inline map libavEncodeElementByCodec() { map res; res["h264"] = "avenc_h264"; res["h265"] = "avenc_h265"; res["mpeg2"] = "avenc_mpeg2video"; res["mpeg4"] = "avenc_mpeg4"; res["mjpeg"] = "avenc_mjpeg"; res["vp8"] = "avenc_vp8"; return res; } inline map demuxPluginByContainer() { map res; res["avi"] = "avidemux"; res["mp4"] = "qtdemux"; res["mov"] = "qtdemux"; res["mkv"] = "matroskademux"; return res; } inline map muxPluginByContainer() { map res; res["avi"] = "avimux"; res["mp4"] = "qtmux"; res["mov"] = "qtmux"; res["mkv"] = "matroskamux"; return res; } //================================================================================ inline string containerByName(const string &name) { size_t found = name.rfind("."); if (found != string::npos) { return name.substr(found + 1); // container type } return string(); } //================================================================================ inline Ptr createCapture(const string &backend, const string &file_name, const string &codec) { if (backend == "gst-default") { cout << "Created GStreamer capture ( " << file_name << " )" << endl; return makePtr(file_name, CAP_GSTREAMER); } else if (backend.find("gst") == 0) { ostringstream line; line << "filesrc location=\"" << file_name << "\""; line << " ! "; line << getValue(demuxPluginByContainer(), containerByName(file_name), "Invalid container"); line << " ! "; if (backend.find("basic") == 4) line << "decodebin"; else if (backend.find("vaapi") == 4) line << "vaapidecodebin"; else if (backend.find("libav") == 4) line << getValue(libavDecodeElementByCodec(), codec, "Invalid codec"); else if (backend.find("mfx") == 4) line << getValue(mfxDecodeElementByCodec(), codec, "Invalid or unsupported codec"); else return Ptr(); line << " ! videoconvert n-threads=" << getNumThreads(); line << " ! appsink sync=false"; cout << "Created GStreamer capture ( " << line.str() << " )" << endl; return makePtr(line.str(), CAP_GSTREAMER); } else if (backend == "ffmpeg") { cout << "Created FFmpeg capture ( " << file_name << " )" << endl; return makePtr(file_name, CAP_FFMPEG); } return Ptr(); } inline Ptr createSynthSource(Size sz, unsigned fps) { ostringstream line; line << "videotestsrc pattern=smpte"; line << " ! video/x-raw"; line << ",width=" << sz.width << ",height=" << sz.height; if (fps > 0) line << ",framerate=" << fps << "/1"; line << " ! appsink sync=false"; cout << "Created synthetic video source ( " << line.str() << " )" << endl; return makePtr(line.str(), CAP_GSTREAMER); } inline Ptr createWriter(const string &backend, const string &file_name, const string &codec, Size sz, unsigned fps) { if (backend == "gst-default") { cout << "Created GStreamer writer ( " << file_name << ", FPS=" << fps << ", Size=" << sz << ")" << endl; return makePtr(file_name, CAP_GSTREAMER, getValue(fourccByCodec(), codec, "Invalid codec"), fps, sz, true); } else if (backend.find("gst") == 0) { ostringstream line; line << "appsrc ! videoconvert n-threads=" << getNumThreads() << " ! "; if (backend.find("basic") == 4) line << getValue(defaultEncodeElementByCodec(), codec, "Invalid codec"); else if (backend.find("vaapi") == 4) line << getValue(VAAPIEncodeElementByCodec(), codec, "Invalid codec"); else if (backend.find("libav") == 4) line << getValue(libavEncodeElementByCodec(), codec, "Invalid codec"); else if (backend.find("mfx") == 4) line << getValue(mfxEncodeElementByCodec(), codec, "Invalid codec"); else return Ptr(); line << " ! "; line << getValue(muxPluginByContainer(), containerByName(file_name), "Invalid container"); line << " ! "; line << "filesink location=\"" << file_name << "\""; cout << "Created GStreamer writer ( " << line.str() << " )" << endl; return makePtr(line.str(), CAP_GSTREAMER, 0, fps, sz, true); } else if (backend == "ffmpeg") { cout << "Created FFMpeg writer ( " << file_name << ", FPS=" << fps << ", Size=" << sz << " )" << endl; return makePtr(file_name, CAP_FFMPEG, getValue(fourccByCodec(), codec, "Invalid codec"), fps, sz, true); } return Ptr(); } //================================================================================ int main(int argc, char *argv[]) { const string keys = "{h help usage ? | | print help messages }" "{m mode |decode | coding mode (supported: encode, decode) }" "{b backend |default | video backend (supported: 'gst-default', 'gst-basic', 'gst-vaapi', 'gst-libav', 'gst-mfx', 'ffmpeg') }" "{c codec |h264 | codec name (supported: 'h264', 'h265', 'mpeg2', 'mpeg4', 'mjpeg', 'vp8') }" "{f file path | | path to file }" "{r resolution |720p | video resolution for encoding (supported: '720p', '1080p', '4k') }" "{fps |30 | fix frame per second for encoding (supported: fps > 0) }" "{fast | | fast measure fps }"; CommandLineParser cmd_parser(argc, argv, keys); cmd_parser.about("This program measures performance of video encoding and decoding using different backends OpenCV."); if (cmd_parser.has("help")) { cmd_parser.printMessage(); return 0; } bool fast_measure = cmd_parser.has("fast"); // fast measure fps unsigned fix_fps = cmd_parser.get("fps"); // fixed frame per second string backend = cmd_parser.get("backend"); // video backend string mode = cmd_parser.get("mode"); // coding mode string codec = cmd_parser.get("codec"); // codec type string file_name = cmd_parser.get("file"); // path to videofile string resolution = cmd_parser.get("resolution"); // video resolution if (!cmd_parser.check()) { cmd_parser.printErrors(); return -1; } if (mode != "encode" && mode != "decode") { cout << "Unsupported mode: " << mode << endl; return -1; } cout << "Mode: " << mode << ", Backend: " << backend << ", File: " << file_name << ", Codec: " << codec << endl; TickMeter total; Ptr cap; Ptr wrt; try { if (mode == "decode") { cap = createCapture(backend, file_name, codec); if (!cap) { cout << "Failed to create video capture" << endl; return -3; } if (!cap->isOpened()) { cout << "Capture is not opened" << endl; return -4; } } else if (mode == "encode") { Size sz = getValue(sizeByResolution(), resolution, "Invalid resolution"); cout << "FPS: " << fix_fps << ", Frame size: " << sz << endl; cap = createSynthSource(sz, fix_fps); wrt = createWriter(backend, file_name, codec, sz, fix_fps); if (!cap || !wrt) { cout << "Failed to create synthetic video source or video writer" << endl; return -3; } if (!cap->isOpened() || !wrt->isOpened()) { cout << "Synthetic video source or video writer is not opened" << endl; return -4; } } } catch (...) { cout << "Unsupported parameters" << endl; return -2; } TickMeter tick; Mat frame; Mat element; total.start(); while(true) { if (mode == "decode") { tick.start(); if (!cap->grab()) { cout << "No more frames - break" << endl; break; } if (!cap->retrieve(frame)) { cout << "Failed to retrieve frame - break" << endl; break; } if (frame.empty()) { cout << "Empty frame received - break" << endl; break; } tick.stop(); } else if (mode == "encode") { int limit = 100; while (!cap->grab() && --limit != 0) { cout << "Skipping empty input frame - " << limit << endl; } cap->retrieve(element); tick.start(); *wrt << element; tick.stop(); } if (fast_measure && tick.getCounter() >= 1000) { cout << "Fast mode frame limit reached - break" << endl; break; } if (mode == "encode" && tick.getCounter() >= 1000) { cout << "Encode frame limit reached - break" << endl; break; } } total.stop(); if (tick.getCounter() == 0) { cout << "No frames have been processed" << endl; return -10; } else { double res_fps = tick.getCounter() / tick.getTimeSec(); cout << tick.getCounter() << " frames in " << tick.getTimeSec() << " sec ~ " << res_fps << " FPS" << " (total time: " << total.getTimeSec() << " sec)" << endl; } return 0; }