2 * qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> it
4 * This file is part of qpsnr.
6 * qpsnr is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * qpsnr is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with qpsnr. If not, see <http://www.gnu.org/licenses/>.
29 #include "shared_ptr.h"
35 std::string XtoS(const T& in) {
36 std::ostringstream oss;
41 std::string get_filename(const std::string& in) {
42 const char *pslash = strrchr(in.c_str(), '/');
43 if (pslash) return std::string(pslash+1);
47 class video_producer : public mt::Thread {
49 mt::Semaphore &_s_prod,
51 std::vector<unsigned char> &_buf;
55 video_producer(int& frame, mt::Semaphore& s_prod, mt::Semaphore& s_cons, std::vector<unsigned char>& buf, qav::qvideo& video, bool& __exit) :
56 _frame(frame), _s_prod(s_prod), _s_cons(s_cons), _buf(buf), _video(video), _exit(__exit) {
59 virtual void run(void) {
61 if (!_video.get_frame(_buf, &_frame)) _frame = -1;
62 // signal cons we're done
64 // wait for cons to tell us to go
70 typedef std::vector<unsigned char> VUCHAR;
71 typedef shared_ptr<qav::qvideo> SP_QVIDEO;
79 typedef std::vector<shared_ptr<vp_data> > V_VPDATA;
80 typedef std::vector<shared_ptr<video_producer> > V_VPTH;
82 const std::string __qpsnr__ = "qpsnr",
83 __version__ = "0.2.1";
85 void print_help(void) {
86 std::cerr << __qpsnr__ << " v" << __version__ << " - (C) 2010 E. Oriani\n"
87 "Usage: " << __qpsnr__ << " [options] -r ref.video compare.video1 compare.video2 ...\n\n"
88 "-r,--reference:\n\tset reference video (mandatory)\n"
89 "\n-v,--video-size:\n\tset analysis video size WIDTHxHEIGHT (ie. 1280x720), default is reference video size\n"
90 "\n-s,--skip-frames:\n\tskip n initial frames\n"
91 "\n-m,--max-frames:\n\tset max frames to process before quit\n"
92 "\n-I,--save-frames:\n\tsave frames (ppm format)\n"
93 "\n-G,--ignore-fps:\n\tanalyze videos even if the expected fps are different\n"
95 "\tpsnr : execute the psnr for each frame\n"
96 "\tavg_psnr : take the average of the psnr every n frames (use option \"fpa\" to set it)\n"
97 "\tssim : execute the ssim (Y colorspace) on the frames divided in blocks (use option \"blocksize\" to set the size)\n"
98 "\tavg_ssim : take the average of the ssim (Y colorspace) every n frames (use option \"fpa\" to set it)\n"
99 "\n-o,--aopts: (specify option1=value1:option2=value2:...)\n"
100 "\tfpa : set the frames per average, default 25\n"
101 "\tcolorspace : set the colorspace (\"rgb\", \"hsi\", \"ycbcr\" or \"y\"), default \"rgb\"\n"
102 "\tblocksize : set blocksize for ssim analysis, default 8\n"
103 "\n-l,--log-level:\n"
105 "\t1 : Errors only\n"
106 "\t2 : Warnings and errors\n"
107 "\t3 : Info, warnings and errors (default)\n"
108 "\t4 : Debug, info, warnings and errors\n"
109 "\n-h,--help:\n\tprint this help and exit\n"
113 int parse_options(int argc, char *argv[], std::map<std::string, std::string>& aopt) {
119 static struct option long_options[] =
121 {"analyzer", required_argument, 0, 'a'},
122 {"max-frames", required_argument, 0, 'm'},
123 {"skip-frames", required_argument, 0, 's'},
124 {"reference", required_argument, 0, 'r'},
125 {"log-level", required_argument, 0, 'l'},
126 {"save-frames", no_argument, 0, 'I'},
127 {"video-size", required_argument, 0, 'v'},
128 {"ignore-fps", no_argument, 0, 'G'},
129 {"help", no_argument, 0, 'h'},
130 {"aopts", required_argument, 0, 'o'},
134 while ((c = getopt_long (argc, argv, "a:l:m:o:r:s:v:hIG", long_options, &option_index)) != -1) {
137 settings::ANALYZER = optarg;
141 const char *p_x = strchr(optarg, 'x');
142 if (!p_x) p_x = strchr(optarg, 'X');
143 if (!p_x) throw std::runtime_error("Invalid video size specified (use WIDTHxHEIGHT format, ie. 1280x720)");
144 const std::string s_x(optarg, p_x-optarg),
146 if (s_x.empty() || s_y.empty())
147 throw std::runtime_error("Invalid video size specified (use WIDTHxHEIGHT format, ie. 1280x720)");
148 const int i_x = atoi(s_x.c_str()),
149 i_y = atoi(s_y.c_str());
150 if (i_x <=0 || i_y <=0)
151 throw std::runtime_error("Invalid video size specified, negative or 0 width/height");
152 settings::VIDEO_SIZE_W = i_x;
153 settings::VIDEO_SIZE_H = i_y;
158 const int skip_frames = atoi(optarg);
159 if (skip_frames > 0 ) settings::SKIP_FRAMES = skip_frames;
163 settings::REF_VIDEO = optarg;
167 const int max_frames = atoi(optarg);
168 if (max_frames > 0 ) settings::MAX_FRAMES = max_frames;
172 if (isdigit(optarg[0])) {
174 log_level[0] = optarg[0];
176 const int log_ilev = atoi(optarg);
179 settings::LOG = 0x00;
182 settings::LOG = 0x01;
185 settings::LOG = 0x03;
188 settings::LOG = 0x07;
191 settings::LOG = 0x0F;
203 settings::SAVE_IMAGES = true;
206 settings::IGNORE_FPS = true;
210 const char *p_opts = optarg,
212 while(p_colon = strchr(p_opts, ':')) {
213 const std::string c_opt(p_opts, (p_colon-p_opts));
214 const size_t p_equal = c_opt.find('=');
215 if (std::string::npos != p_equal)
216 aopt[c_opt.substr(0, p_equal)] = c_opt.substr(p_equal+1);
219 const std::string c_opt(p_opts);
220 const size_t p_equal = c_opt.find('=');
221 if (std::string::npos != p_equal)
222 aopt[c_opt.substr(0, p_equal)] = c_opt.substr(p_equal+1);
226 if (strchr("almorsv", optopt)) {
227 std::cerr << "Option -" << (char)optopt << " requires an argument" << std::endl;
230 } else if (isprint (optopt)) {
231 std::cerr << "Option -" << (char)optopt << " is unknown" << std::endl;
237 std::cerr << "Invalid option: " << c << std::endl;
243 // fix here the frame limit
244 if (settings::SKIP_FRAMES > 0 && settings::MAX_FRAMES>0) settings::MAX_FRAMES += settings::SKIP_FRAMES;
248 namespace producers_utils {
249 void start(video_producer& ref_vpth, V_VPTH& v_th) {
251 for(V_VPTH::iterator it = v_th.begin(); it != v_th.end(); ++it)
255 void stop(video_producer& ref_vpth, V_VPTH& v_th) {
257 for(V_VPTH::iterator it = v_th.begin(); it != v_th.end(); ++it)
261 void sync(mt::Semaphore& sem_cons, const int& n_v_th) {
262 for(int i = 0; i < n_v_th; ++i)
266 void lock(mt::Semaphore& sem_cons, mt::Semaphore& ref_prod, V_VPDATA& v_data) {
267 // init all the semaphores
270 for(V_VPDATA::iterator it = v_data.begin(); it != v_data.end(); ++it)
274 void unlock(mt::Semaphore& ref_prod, V_VPDATA& v_data) {
276 for(V_VPDATA::iterator it = v_data.begin(); it != v_data.end(); ++it)
281 int main(int argc, char *argv[]) {
283 std::map<std::string, std::string> aopt;
284 const int param = parse_options(argc, argv, aopt);
285 // Register all formats and codecs
287 if (settings::REF_VIDEO == "")
288 throw std::runtime_error("Reference video not specified");
289 bool glb_exit = false;
290 mt::Semaphore sem_cons;
291 // create data for reference video
292 mt::Semaphore ref_prod;
295 qav::qvideo ref_video(settings::REF_VIDEO.c_str(), settings::VIDEO_SIZE_W, settings::VIDEO_SIZE_H);
297 const qav::scr_size ref_sz = ref_video.get_size();
298 const int ref_fps_k = ref_video.get_fps_k();
300 //ref_video.get_frame(ref_buf);
304 for(int i = param; i < argc; ++i) {
306 shared_ptr<vp_data> vpd(new vp_data);
307 vpd->name = get_filename(argv[i]);
308 vpd->video = new qav::qvideo(argv[i], ref_sz.x, ref_sz.y);
309 if (vpd->video->get_fps_k() != ref_fps_k) {
310 if (settings::IGNORE_FPS) {
311 LOG_WARNING << '[' << argv[i] << "] has different FPS (" << vpd->video->get_fps_k()/1000 << ')' << std::endl;
312 v_data.push_back(vpd);
313 } else LOG_ERROR << '[' << argv[i] << "] skipped different FPS" << std::endl;
314 } else v_data.push_back(vpd);
315 } catch(std::exception& e) {
316 LOG_ERROR << '[' << argv[i] << "] skipped " << e.what() << std::endl;
319 if (v_data.empty()) return 0;
321 LOG_INFO << "Skip frames: " << ((settings::SKIP_FRAMES > 0) ? settings::SKIP_FRAMES : 0) << std::endl;
322 LOG_INFO << "Max frames: " << ((settings::MAX_FRAMES > 0) ? settings::MAX_FRAMES : 0) << std::endl;
323 // create the stats analyzer (like the psnr)
324 LOG_INFO << "Analyzer set: " << settings::ANALYZER << std::endl;
325 std::auto_ptr<stats::s_base> s_analyzer(stats::get_analyzer(settings::ANALYZER.c_str(), v_data.size(), ref_sz.x, ref_sz.y, std::cout));
326 // set the default values, in case will get overwritten
327 s_analyzer->set_parameter("fpa", "25");
328 s_analyzer->set_parameter("blocksize", "8");
329 // load the passed parameters
330 for(std::map<std::string, std::string>::const_iterator it = aopt.begin(); it != aopt.end(); ++it) {
331 LOG_INFO << "Analyzer parameter: " << it->first << " = " << it->second << std::endl;
332 s_analyzer->set_parameter(it->first.c_str(), it->second.c_str());
334 // create all the threads
335 video_producer ref_vpth(ref_frame, ref_prod, sem_cons, ref_buf, ref_video, glb_exit);
337 for(V_VPDATA::iterator it = v_data.begin(); it != v_data.end(); ++it)
338 v_th.push_back(new video_producer((*it)->frame, (*it)->prod, sem_cons, (*it)->buf, *((*it)->video), glb_exit));
339 // we'll need some tmp buffers
341 std::vector<VUCHAR> t_bufs(v_data.size());
342 // and now the core algorithm
343 // init all the semaphores
344 producers_utils::lock(sem_cons, ref_prod, v_data);
346 producers_utils::start(ref_vpth, v_th);
348 std::cout << "Sample,";
349 for(V_VPDATA::const_iterator it = v_data.begin(); it != v_data.end(); ++it)
350 std::cout << (*it)->name << ',';
351 std::cout << std::endl;
353 // wait for the consumer to be signalled 1 + n times
354 const static int CONS_SIG_NUM = 1 + v_th.size();
355 producers_utils::sync(sem_cons, CONS_SIG_NUM);
356 // now check everything is ok
357 const int cur_ref_frame = ref_frame;
358 if (-1 == cur_ref_frame) {
360 // allow the producers to run
361 producers_utils::unlock(ref_prod, v_data);
364 // in case we have to skip frames...
365 if (settings::SKIP_FRAMES > 0 && settings::SKIP_FRAMES >= cur_ref_frame) {
366 // allow the producers to run
367 producers_utils::unlock(ref_prod, v_data);
370 // vector of bool telling if everything is ok
371 std::vector<bool> v_ok;
372 for(V_VPDATA::const_iterator it = v_data.begin(); it != v_data.end(); ++it)
373 if ((*it)->frame == cur_ref_frame) v_ok.push_back(true);
374 else v_ok.push_back(false);
375 // then swap the vectors
376 t_ref_buf.swap(ref_buf);
377 for(int i = 0; i < v_data.size(); ++i)
378 t_bufs[i].swap(v_data[i]->buf);
379 // allow the producers to run
380 producers_utils::unlock(ref_prod, v_data);
381 // finally process data
382 s_analyzer->process(cur_ref_frame, t_ref_buf, v_ok, t_bufs);
383 // check if we have to exit
384 if (settings::MAX_FRAMES > 0 && cur_ref_frame >= settings::MAX_FRAMES) {
386 // allow the producers to run
387 producers_utils::unlock(ref_prod, v_data);
391 // wait for all threads
392 producers_utils::stop(ref_vpth, v_th);
393 } catch(std::exception& e) {
394 LOG_ERROR << e.what() << std::endl;
396 LOG_ERROR << "Unknown exception" << std::endl;