From 593e12fb332a89b13b404fc59a40c60f810d2b1f Mon Sep 17 00:00:00 2001 From: Samuel Jero Date: Mon, 11 Mar 2013 21:47:31 -0400 Subject: [PATCH 1/1] Initial Commit of QPSNR (version 0.2.1) --- .cproject | 44 ++++ .project | 27 +++ Makefile | 44 ++++ src/gpl_v3 | 19 ++ src/main.cpp | 398 ++++++++++++++++++++++++++++++++++ src/mt.h | 315 +++++++++++++++++++++++++++ src/qav.cpp | 190 +++++++++++++++++ src/qav.h | 68 ++++++ src/settings.cpp | 32 +++ src/settings.h | 52 +++++ src/shared_ptr.h | 149 +++++++++++++ src/stats.cpp | 539 +++++++++++++++++++++++++++++++++++++++++++++++ src/stats.h | 53 +++++ 13 files changed, 1930 insertions(+) create mode 100644 .cproject create mode 100644 .project create mode 100644 Makefile create mode 100644 src/gpl_v3 create mode 100644 src/main.cpp create mode 100644 src/mt.h create mode 100644 src/qav.cpp create mode 100644 src/qav.h create mode 100644 src/settings.cpp create mode 100644 src/settings.h create mode 100644 src/shared_ptr.h create mode 100644 src/stats.cpp create mode 100644 src/stats.h diff --git a/.cproject b/.cproject new file mode 100644 index 0000000..c0e81ea --- /dev/null +++ b/.cproject @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..f67097f --- /dev/null +++ b/.project @@ -0,0 +1,27 @@ + + + QPSNR + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aae3565 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +#Makefile generated by amake +#On Wed Feb 17 10:48:47 2010 +#To print amake help use 'amake --help'. +CC=gcc +CPPC=g++ +LINK=g++ +SRCDIR=src +OBJDIR=obj +FLAGS=-O2 -g -pthread -D__STDC_CONSTANT_MACROS +LIBS=-lavcodec -lavformat -lswscale +OBJS=$(OBJDIR)/qav.o $(OBJDIR)/stats.o $(OBJDIR)/main.o $(OBJDIR)/settings.o +EXEC=qpsnr + +$(EXEC) : $(OBJS) + $(LINK) $(OBJS) -o $(EXEC) $(FLAGS) $(LIBS) + +$(OBJDIR)/qav.o: src/qav.cpp src/qav.h src/settings.h $(OBJDIR)/__setup_obj_dir + $(CPPC) $(FLAGS) src/qav.cpp -c -o $@ + +$(OBJDIR)/stats.o: src/stats.cpp src/stats.h src/mt.h src/shared_ptr.h \ + src/settings.h $(OBJDIR)/__setup_obj_dir + $(CPPC) $(FLAGS) src/stats.cpp -c -o $@ + +$(OBJDIR)/main.o: src/main.cpp src/mt.h src/shared_ptr.h src/qav.h src/settings.h \ + src/stats.h $(OBJDIR)/__setup_obj_dir + $(CPPC) $(FLAGS) src/main.cpp -c -o $@ + +$(OBJDIR)/settings.o: src/settings.cpp src/settings.h $(OBJDIR)/__setup_obj_dir + $(CPPC) $(FLAGS) src/settings.cpp -c -o $@ + +$(OBJDIR)/__setup_obj_dir : + mkdir -p $(OBJDIR) + touch $(OBJDIR)/__setup_obj_dir + +.PHONY: clean bzip + +clean : + rm -rf $(OBJDIR)/*.o + rm -rf $(EXEC) + +bzip : + tar -cvf $(EXEC).tar $(SRCDIR)/* Makefile + bzip2 $(EXEC).tar + diff --git a/src/gpl_v3 b/src/gpl_v3 new file mode 100644 index 0000000..a5d093e --- /dev/null +++ b/src/gpl_v3 @@ -0,0 +1,19 @@ +/* +* qpsnr (C) 2010 E. Oriani, ema fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..b5fa48d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,398 @@ +/* +* qpsnr (C) 2010 E. Oriani, ema fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mt.h" +#include "shared_ptr.h" +#include "qav.h" +#include "settings.h" +#include "stats.h" + +template +std::string XtoS(const T& in) { + std::ostringstream oss; + oss << in; + return in.str(); +} + +std::string get_filename(const std::string& in) { + const char *pslash = strrchr(in.c_str(), '/'); + if (pslash) return std::string(pslash+1); + return in; +} + +class video_producer : public mt::Thread { + int &_frame; + mt::Semaphore &_s_prod, + &_s_cons; + std::vector &_buf; + qav::qvideo &_video; + bool &_exit; +public: + video_producer(int& frame, mt::Semaphore& s_prod, mt::Semaphore& s_cons, std::vector& buf, qav::qvideo& video, bool& __exit) : + _frame(frame), _s_prod(s_prod), _s_cons(s_cons), _buf(buf), _video(video), _exit(__exit) { + } + + virtual void run(void) { + while(!_exit) { + if (!_video.get_frame(_buf, &_frame)) _frame = -1; + // signal cons we're done + _s_cons.pop(); + // wait for cons to tell us to go + _s_prod.push(); + } + } +}; + +typedef std::vector VUCHAR; +typedef shared_ptr SP_QVIDEO; +struct vp_data { + mt::Semaphore prod; + VUCHAR buf; + int frame; + SP_QVIDEO video; + std::string name; +}; +typedef std::vector > V_VPDATA; +typedef std::vector > V_VPTH; + +const std::string __qpsnr__ = "qpsnr", + __version__ = "0.2.1"; + +void print_help(void) { + std::cerr << __qpsnr__ << " v" << __version__ << " - (C) 2010 E. Oriani\n" + "Usage: " << __qpsnr__ << " [options] -r ref.video compare.video1 compare.video2 ...\n\n" + "-r,--reference:\n\tset reference video (mandatory)\n" + "\n-v,--video-size:\n\tset analysis video size WIDTHxHEIGHT (ie. 1280x720), default is reference video size\n" + "\n-s,--skip-frames:\n\tskip n initial frames\n" + "\n-m,--max-frames:\n\tset max frames to process before quit\n" + "\n-I,--save-frames:\n\tsave frames (ppm format)\n" + "\n-G,--ignore-fps:\n\tanalyze videos even if the expected fps are different\n" + "\n-a,--analyzer:\n" + "\tpsnr : execute the psnr for each frame\n" + "\tavg_psnr : take the average of the psnr every n frames (use option \"fpa\" to set it)\n" + "\tssim : execute the ssim (Y colorspace) on the frames divided in blocks (use option \"blocksize\" to set the size)\n" + "\tavg_ssim : take the average of the ssim (Y colorspace) every n frames (use option \"fpa\" to set it)\n" + "\n-o,--aopts: (specify option1=value1:option2=value2:...)\n" + "\tfpa : set the frames per average, default 25\n" + "\tcolorspace : set the colorspace (\"rgb\", \"hsi\", \"ycbcr\" or \"y\"), default \"rgb\"\n" + "\tblocksize : set blocksize for ssim analysis, default 8\n" + "\n-l,--log-level:\n" + "\t0 : No log\n" + "\t1 : Errors only\n" + "\t2 : Warnings and errors\n" + "\t3 : Info, warnings and errors (default)\n" + "\t4 : Debug, info, warnings and errors\n" + "\n-h,--help:\n\tprint this help and exit\n" + << std::flush; +} + +int parse_options(int argc, char *argv[], std::map& aopt) { + aopt.clear(); + opterr = 0; + int c = 0, + option_index = 0; + + static struct option long_options[] = + { + {"analyzer", required_argument, 0, 'a'}, + {"max-frames", required_argument, 0, 'm'}, + {"skip-frames", required_argument, 0, 's'}, + {"reference", required_argument, 0, 'r'}, + {"log-level", required_argument, 0, 'l'}, + {"save-frames", no_argument, 0, 'I'}, + {"video-size", required_argument, 0, 'v'}, + {"ignore-fps", no_argument, 0, 'G'}, + {"help", no_argument, 0, 'h'}, + {"aopts", required_argument, 0, 'o'}, + {0, 0, 0, 0} + }; + + while ((c = getopt_long (argc, argv, "a:l:m:o:r:s:v:hIG", long_options, &option_index)) != -1) { + switch (c) { + case 'a': + settings::ANALYZER = optarg; + break; + case 'v': + { + const char *p_x = strchr(optarg, 'x'); + if (!p_x) p_x = strchr(optarg, 'X'); + if (!p_x) throw std::runtime_error("Invalid video size specified (use WIDTHxHEIGHT format, ie. 1280x720)"); + const std::string s_x(optarg, p_x-optarg), + s_y(p_x+1); + if (s_x.empty() || s_y.empty()) + throw std::runtime_error("Invalid video size specified (use WIDTHxHEIGHT format, ie. 1280x720)"); + const int i_x = atoi(s_x.c_str()), + i_y = atoi(s_y.c_str()); + if (i_x <=0 || i_y <=0) + throw std::runtime_error("Invalid video size specified, negative or 0 width/height"); + settings::VIDEO_SIZE_W = i_x; + settings::VIDEO_SIZE_H = i_y; + } + break; + case 's': + { + const int skip_frames = atoi(optarg); + if (skip_frames > 0 ) settings::SKIP_FRAMES = skip_frames; + } + break; + case 'r': + settings::REF_VIDEO = optarg; + break; + case 'm': + { + const int max_frames = atoi(optarg); + if (max_frames > 0 ) settings::MAX_FRAMES = max_frames; + } + break; + case 'l': + if (isdigit(optarg[0])) { + char log_level[2]; + log_level[0] = optarg[0]; + log_level[1] = '\0'; + const int log_ilev = atoi(optarg); + switch (log_ilev) { + case 0: + settings::LOG = 0x00; + break; + case 1: + settings::LOG = 0x01; + break; + case 2: + settings::LOG = 0x03; + break; + case 3: + settings::LOG = 0x07; + break; + case 4: + settings::LOG = 0x0F; + break; + default: + break; + } + } + break; + case 'h': + print_help(); + exit(0); + break; + case 'I': + settings::SAVE_IMAGES = true; + break; + case 'G': + settings::IGNORE_FPS = true; + break; + case 'o': + { + const char *p_opts = optarg, + *p_colon = 0; + while(p_colon = strchr(p_opts, ':')) { + const std::string c_opt(p_opts, (p_colon-p_opts)); + const size_t p_equal = c_opt.find('='); + if (std::string::npos != p_equal) + aopt[c_opt.substr(0, p_equal)] = c_opt.substr(p_equal+1); + p_opts = p_colon+1; + } + const std::string c_opt(p_opts); + const size_t p_equal = c_opt.find('='); + if (std::string::npos != p_equal) + aopt[c_opt.substr(0, p_equal)] = c_opt.substr(p_equal+1); + } + break; + case '?': + if (strchr("almorsv", optopt)) { + std::cerr << "Option -" << (char)optopt << " requires an argument" << std::endl; + print_help(); + exit(1); + } else if (isprint (optopt)) { + std::cerr << "Option -" << (char)optopt << " is unknown" << std::endl; + print_help(); + exit(1); + } + break; + default: + std::cerr << "Invalid option: " << c << std::endl; + print_help(); + exit(1); + break; + } + } + // fix here the frame limit + if (settings::SKIP_FRAMES > 0 && settings::MAX_FRAMES>0) settings::MAX_FRAMES += settings::SKIP_FRAMES; + return optind; +} + +namespace producers_utils { + void start(video_producer& ref_vpth, V_VPTH& v_th) { + ref_vpth.start(); + for(V_VPTH::iterator it = v_th.begin(); it != v_th.end(); ++it) + (*it)->start(); + } + + void stop(video_producer& ref_vpth, V_VPTH& v_th) { + ref_vpth.join(); + for(V_VPTH::iterator it = v_th.begin(); it != v_th.end(); ++it) + (*it)->join(); + } + + void sync(mt::Semaphore& sem_cons, const int& n_v_th) { + for(int i = 0; i < n_v_th; ++i) + sem_cons.push(); + } + + void lock(mt::Semaphore& sem_cons, mt::Semaphore& ref_prod, V_VPDATA& v_data) { + // init all the semaphores + sem_cons.push(); + ref_prod.push(); + for(V_VPDATA::iterator it = v_data.begin(); it != v_data.end(); ++it) + (*it)->prod.push(); + } + + void unlock(mt::Semaphore& ref_prod, V_VPDATA& v_data) { + ref_prod.pop(); + for(V_VPDATA::iterator it = v_data.begin(); it != v_data.end(); ++it) + (*it)->prod.pop(); + } +} + +int main(int argc, char *argv[]) { + try { + std::map aopt; + const int param = parse_options(argc, argv, aopt); + // Register all formats and codecs + av_register_all(); + if (settings::REF_VIDEO == "") + throw std::runtime_error("Reference video not specified"); + bool glb_exit = false; + mt::Semaphore sem_cons; + // create data for reference video + mt::Semaphore ref_prod; + VUCHAR ref_buf; + int ref_frame; + qav::qvideo ref_video(settings::REF_VIDEO.c_str(), settings::VIDEO_SIZE_W, settings::VIDEO_SIZE_H); + // get const values + const qav::scr_size ref_sz = ref_video.get_size(); + const int ref_fps_k = ref_video.get_fps_k(); + // + //ref_video.get_frame(ref_buf); + //return 0; + // + V_VPDATA v_data; + for(int i = param; i < argc; ++i) { + try { + shared_ptr vpd(new vp_data); + vpd->name = get_filename(argv[i]); + vpd->video = new qav::qvideo(argv[i], ref_sz.x, ref_sz.y); + if (vpd->video->get_fps_k() != ref_fps_k) { + if (settings::IGNORE_FPS) { + LOG_WARNING << '[' << argv[i] << "] has different FPS (" << vpd->video->get_fps_k()/1000 << ')' << std::endl; + v_data.push_back(vpd); + } else LOG_ERROR << '[' << argv[i] << "] skipped different FPS" << std::endl; + } else v_data.push_back(vpd); + } catch(std::exception& e) { + LOG_ERROR << '[' << argv[i] << "] skipped " << e.what() << std::endl; + } + } + if (v_data.empty()) return 0; + // print some infos + LOG_INFO << "Skip frames: " << ((settings::SKIP_FRAMES > 0) ? settings::SKIP_FRAMES : 0) << std::endl; + LOG_INFO << "Max frames: " << ((settings::MAX_FRAMES > 0) ? settings::MAX_FRAMES : 0) << std::endl; + // create the stats analyzer (like the psnr) + LOG_INFO << "Analyzer set: " << settings::ANALYZER << std::endl; + std::auto_ptr s_analyzer(stats::get_analyzer(settings::ANALYZER.c_str(), v_data.size(), ref_sz.x, ref_sz.y, std::cout)); + // set the default values, in case will get overwritten + s_analyzer->set_parameter("fpa", "25"); + s_analyzer->set_parameter("blocksize", "8"); + // load the passed parameters + for(std::map::const_iterator it = aopt.begin(); it != aopt.end(); ++it) { + LOG_INFO << "Analyzer parameter: " << it->first << " = " << it->second << std::endl; + s_analyzer->set_parameter(it->first.c_str(), it->second.c_str()); + } + // create all the threads + video_producer ref_vpth(ref_frame, ref_prod, sem_cons, ref_buf, ref_video, glb_exit); + V_VPTH v_th; + for(V_VPDATA::iterator it = v_data.begin(); it != v_data.end(); ++it) + v_th.push_back(new video_producer((*it)->frame, (*it)->prod, sem_cons, (*it)->buf, *((*it)->video), glb_exit)); + // we'll need some tmp buffers + VUCHAR t_ref_buf; + std::vector t_bufs(v_data.size()); + // and now the core algorithm + // init all the semaphores + producers_utils::lock(sem_cons, ref_prod, v_data); + // start the threads + producers_utils::start(ref_vpth, v_th); + // print header + std::cout << "Sample,"; + for(V_VPDATA::const_iterator it = v_data.begin(); it != v_data.end(); ++it) + std::cout << (*it)->name << ','; + std::cout << std::endl; + while(!glb_exit) { + // wait for the consumer to be signalled 1 + n times + const static int CONS_SIG_NUM = 1 + v_th.size(); + producers_utils::sync(sem_cons, CONS_SIG_NUM); + // now check everything is ok + const int cur_ref_frame = ref_frame; + if (-1 == cur_ref_frame) { + glb_exit = true; + // allow the producers to run + producers_utils::unlock(ref_prod, v_data); + continue; + } + // in case we have to skip frames... + if (settings::SKIP_FRAMES > 0 && settings::SKIP_FRAMES >= cur_ref_frame) { + // allow the producers to run + producers_utils::unlock(ref_prod, v_data); + continue; + } + // vector of bool telling if everything is ok + std::vector v_ok; + for(V_VPDATA::const_iterator it = v_data.begin(); it != v_data.end(); ++it) + if ((*it)->frame == cur_ref_frame) v_ok.push_back(true); + else v_ok.push_back(false); + // then swap the vectors + t_ref_buf.swap(ref_buf); + for(int i = 0; i < v_data.size(); ++i) + t_bufs[i].swap(v_data[i]->buf); + // allow the producers to run + producers_utils::unlock(ref_prod, v_data); + // finally process data + s_analyzer->process(cur_ref_frame, t_ref_buf, v_ok, t_bufs); + // check if we have to exit + if (settings::MAX_FRAMES > 0 && cur_ref_frame >= settings::MAX_FRAMES) { + glb_exit = true; + // allow the producers to run + producers_utils::unlock(ref_prod, v_data); + break; + } + } + // wait for all threads + producers_utils::stop(ref_vpth, v_th); + } catch(std::exception& e) { + LOG_ERROR << e.what() << std::endl; + } catch(...) { + LOG_ERROR << "Unknown exception" << std::endl; + } +} diff --git a/src/mt.h b/src/mt.h new file mode 100644 index 0000000..cb2eb7a --- /dev/null +++ b/src/mt.h @@ -0,0 +1,315 @@ +/* +* qpsnr (C) 2010 E. Oriani, ema fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + +/* +* myNZB (C) 2009 E. Oriani, ema fastwebnet it +* +* This file is part of myNZB. +* +* myNZB is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* myNZB is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with myNZB. If not, see . +*/ + +#ifndef _MT_H_ +#define _MT_H_ + +#include +#include +#include +#include +#include +#include +#include + +namespace mt { + + class mt_exception : public std::exception { + const std::string _what; + public: + mt_exception(const std::string& what) : _what(what) { + } + + virtual const char* what() const throw() { + return _what.c_str(); + } + + ~mt_exception() throw () { + } + }; + + class Semaphore { + sem_t _sem; + + Semaphore(const Semaphore&); + Semaphore& operator=(const Semaphore&); + public: + Semaphore() { + if (0 != sem_init(&_sem, 0, 1)) + throw mt_exception("Semaphore: Semaphore()"); + } + + void push(void) { + if (0 != sem_wait(&_sem)) + throw mt_exception("Sempahore: push"); + } + + bool trypush(void) { + if (0 == sem_trywait(&_sem)) { + return true; + } else if (errno == EAGAIN) { + return false; + } + throw mt_exception("Semaphore: trywait"); + } + + void pop(void) { + if (0 != sem_post(&_sem)) + throw mt_exception("Sempahore: pop"); + } + + ~Semaphore() { + sem_destroy(&_sem); + } + }; + + class Mutex { + pthread_mutex_t _mtx; + + Mutex(const Mutex&); + Mutex& operator=(const Mutex&); + public: + Mutex() { + if (0 != pthread_mutex_init(&_mtx, NULL)) + throw mt_exception("Mutex: Mutex()"); + } + + void lock(void) { + if (0 != pthread_mutex_lock(&_mtx)) + throw mt_exception("Mutex: lock"); + } + + void unlock(void) { + if (0 != pthread_mutex_unlock(&_mtx)) + throw mt_exception("Mutex: unlock"); + } + + ~Mutex() { + pthread_mutex_destroy(&_mtx); + } + }; + + class ScopedLock { + Mutex& _mtx; + + ScopedLock(const ScopedLock&); + ScopedLock& operator=(const ScopedLock&); + public: + ScopedLock(Mutex& mtx) : _mtx(mtx) { + _mtx.lock(); + } + + ~ScopedLock() { + _mtx.unlock(); + } + }; + + class Thread { + pthread_t _th; + + static void* exec(void* par) throw() { + try { + Thread *p = (Thread*)par; + p->run(); + } catch (...) { + } + return 0; + } + + Thread(const Thread&); + Thread& operator=(const Thread&); + public: + Thread() : _th(0) { + } + + virtual void run(void) = 0; + + void start(void) { + if (0 != pthread_create(&_th, NULL, &exec, this)) + throw mt_exception("Thread: start"); + } + + void join(void) { + if (0!=_th) pthread_join(_th, NULL); + } + + virtual ~Thread() { + } + }; + + class ThreadPool { + public: + class Job { + friend class ThreadPool; + Semaphore _sem; + volatile bool _on_hold; + const bool _to_be_deleted; + public: + Job(const bool& to_be_deleted = false) : _on_hold(true), _to_be_deleted(to_be_deleted) { + _sem.push(); + } + + virtual void run(void) = 0; + + /* Just rememeber that + - void wait(void) + - bool is_running(void) + can only be used when the ownership of the + ThreadPool::Job instance is external (eg. + to_be_deleted = false). + Otherwise this will lead to a crash! + */ + + // We pop the semaphore again in case someone else + // will ask to wait again... + void wait(void) { + _sem.push(); + _sem.pop(); + }; + + bool is_running(void) { + if (_on_hold) return false; + if (false == _sem.trypush()) + return true; + _sem.pop(); + return false; + } + + virtual ~Job() { + } + }; + private: + Mutex _list_mtx; + Semaphore _list_sem; + std::list _list_jobs; + + // the following variable is not mutex + // protected because is sort of write-only + // by main ThreadPool thread, see destructor + volatile bool _tp_quit; + const unsigned int _n_execs; + std::vector _th_ids; + + bool get_job(Job** _job) { + ScopedLock _sl(_list_mtx); + if (!_list_jobs.empty()) { + *_job = _list_jobs.front(); + _list_jobs.pop_front(); + return true; + } + return false; + } + + // Technically we should declare it as extern "C" but we + // don't care as seen as the function pointer will correspond + // to a proper C-like function even if the name will be C++ like + static void* job_exec(void* par) throw() { + try { + ThreadPool *p = (ThreadPool*)par; + while(true) { + // first push on the semaphore + p->_list_sem.push(); + // check if we have to quit + if (p->_tp_quit) return 0; + // get an element to process + Job *curJob = 0; + if (p->get_job(&curJob)) { + // we need to save the delete_job varibale because + // after the semaphore has been popped the object + // could not exist anymore + const bool delete_job = curJob->_to_be_deleted; + try { + curJob->_on_hold = false; + curJob->run(); + } catch(...) { + } + // if the following instruction throws is better + // to let the user know because this means that + // the semaphore is not valid anymore...this should + // never happen... + curJob->_sem.pop(); + // if it has to be deleted do it! + if (delete_job) delete curJob; + } + // check if we have to quit + if (p->_tp_quit) return 0; + } + } catch(...) { + } + return 0; + } + + ThreadPool(const ThreadPool&); + ThreadPool& operator=(const ThreadPool&); + public: + // Just take into account that semaphores are not syscall immune + // so when you run in debug mode you can have exceptions thrown on + // push because of system interrruption! Don't get scared! + ThreadPool(const unsigned int& n_execs) : _n_execs(n_execs), _th_ids(n_execs), _tp_quit(false) { + if (_n_execs == 0 || _n_execs > 256) + throw mt_exception("ThreadPool: invalid number of n_execs"); + // create the job_exec threads + for (unsigned int i = 0; i < _n_execs; ++i) + if (0 != pthread_create(&_th_ids[i], NULL, &job_exec, this)) { + for (unsigned int j=0; j < i; ++j) + pthread_cancel(_th_ids[j]); + throw mt_exception("ThreadPool: could not start all specified n_execs"); + } + } + + void add(Job* job) { + ScopedLock _sl(_list_mtx); + _list_jobs.push_back(job); + _list_sem.pop(); + } + + ~ThreadPool() { + _tp_quit = true; + for (unsigned int i = 0; i < _n_execs; ++i) + _list_sem.pop(); + for (unsigned int i = 0; i < _n_execs; ++i) + pthread_join(_th_ids[i], NULL); + // potentialy unsafe...this could throw...but should never... + ScopedLock _sl(_list_mtx); + for (std::list::iterator it = _list_jobs.begin(); it != _list_jobs.end(); ++it) + if ((*it)->_to_be_deleted) delete *it; + } + }; +} + +#endif //_MT_H_ diff --git a/src/qav.cpp b/src/qav.cpp new file mode 100644 index 0000000..8083916 --- /dev/null +++ b/src/qav.cpp @@ -0,0 +1,190 @@ +/* +* qpsnr (C) 2010 E. Oriani, ema fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + +#include "qav.h" +#include "settings.h" +#include +#include + +qav::qvideo::qvideo(const char* file, int _out_width, int _out_height) : frnum(0), videoStream(-1), out_width(_out_width), out_height(_out_height) { + const char* pslash = strrchr(file, '/'); + if (pslash) fname = pslash+1; + else fname = file; + pFormatCtx=NULL; + if (avformat_open_input(&pFormatCtx, file, NULL, NULL)!=0) + throw std::runtime_error("Can't open file"); + if (av_find_stream_info(pFormatCtx)<0) { + av_close_input_file(pFormatCtx); + throw std::runtime_error("Multimedia type not supported"); + } + LOG_INFO << "File info for (" << file << ")..." << std::endl; + av_dump_format(pFormatCtx, 0, file, false); + // find video stream (first) + for (int i=0; inb_streams; i++) + if (AVMEDIA_TYPE_VIDEO == pFormatCtx->streams[i]->codec->codec_type) { + videoStream=i; + break; + } + if (-1==videoStream) { + av_close_input_file(pFormatCtx); + throw std::runtime_error("Can't find video stream"); + } + // Get a pointer to the codec context for the video stream + pCodecCtx=pFormatCtx->streams[videoStream]->codec; + pCodec=avcodec_find_decoder(pCodecCtx->codec_id); + if(!pCodec) { + av_close_input_file(pFormatCtx); + throw std::runtime_error("Can't find codec for video stream"); + } + if(avcodec_open(pCodecCtx, pCodec)<0) { + av_close_input_file(pFormatCtx); + throw std::runtime_error("Can't open codec for video stream"); + } + // alloacate data to extract frames + pFrame = avcodec_alloc_frame(); + if (!pFrame) { + avcodec_close(pCodecCtx); + av_close_input_file(pFormatCtx); + throw std::runtime_error("Can't allocated frame for video stream"); + } + // populate the out_width/out_height members + if (out_width > 0 && out_height > 0) { + LOG_INFO << "Output frame size for (" << file << ") is: " << out_width << 'x' << out_height << std::endl; + } else if (-1 == out_width && -1 == out_height) { + out_width = pCodecCtx->width; + out_height = pCodecCtx->height; + LOG_INFO << "Output frame size for (" << file << ") (default) is: " << out_width << 'x' << out_height << std::endl; + } else { + avcodec_close(pCodecCtx); + av_close_input_file(pFormatCtx); + throw std::runtime_error("Invalid output frame size for video stream"); + } + // just report if we're using a different video size + if (out_width!=pCodecCtx->width || out_height!=pCodecCtx->height) + LOG_WARNING << "Video (" << file <<") will get scaled: " << pCodecCtx->width << 'x' << pCodecCtx->height << " (in), " << out_width << 'x' << out_height << " (out)" << std::endl; + // sw context + img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, + out_width, out_height, PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL); + if (!img_convert_ctx) { + av_free(pFrame); + avcodec_close(pCodecCtx); + av_close_input_file(pFormatCtx); + throw std::runtime_error("Can't allocated sw_scale context"); + } +} + +qav::scr_size qav::qvideo::get_size(void) const { + return scr_size(out_width, out_height); +} + +int qav::qvideo::get_fps_k(void) const { + if (pFormatCtx->streams[videoStream]->r_frame_rate.den) + return 1000*pFormatCtx->streams[videoStream]->r_frame_rate.num/pFormatCtx->streams[videoStream]->r_frame_rate.den; + return 0; +} + +bool qav::qvideo::get_frame(std::vector& out, int *_frnum) { + out.resize(avpicture_get_size(PIX_FMT_RGB24, out_width, out_height)); + AVPacket packet; + bool is_read = false; + av_init_packet(&packet); + while (av_read_frame(pFormatCtx, &packet)>=0) { + if (packet.stream_index==videoStream) { + int frameFinished = 0; + // Decode video frame + //avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size); + avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); // packet.data, packet.size); + if(frameFinished) { + AVPicture picRGB; + // Assign appropriate parts of buffer to image planes in pFrameRGB + avpicture_fill((AVPicture*)&picRGB, (unsigned char*)&out[0], PIX_FMT_RGB24, out_width, out_height); + // Convert the image from its native format to RGB + //img_convert((AVPicture*)&picRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height); + sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picRGB.data, picRGB.linesize); + ++frnum; + if (_frnum) *_frnum = frnum; + if (settings::SAVE_IMAGES) { + if ((settings::SKIP_FRAMES < frnum) && (-1 == settings::MAX_FRAMES || frnum <= settings::MAX_FRAMES)) + save_frame(&out[0]); + } + is_read=true; + } + } + av_free_packet(&packet); + if (is_read) return true; + } + return false; +} + +/*bool SaveTGA(char *name, const unsigned char *data, int sizeX, int sizeY) { + BYTE TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0}; + BYTE header[6]; + header[1] = (BYTE)(sizeX/256); + header[0] = (BYTE) sizeX%256; + header[3] = (BYTE)(sizeY/256); + header[2] = (BYTE) sizeY%256; + header[4] = (BYTE) 24; + header[5] = (BYTE) 0x00; + int fh = open(name, _O_CREAT|_O_TRUNC|_O_WRONLY); + if (-1 == fh) return false; + if (12*sizeof(BYTE) != write(fh, TGAheader, 12*sizeof(BYTE))) return false; + if (6*sizeof(BYTE) != write(fh, header, 6*sizeof(BYTE))) return false; + for (int i = 0; i < sizeY; i++) { + if (3*sizeX != write(fh, data + sizeX*i*3, sizeX*3)) { + close(fh); + return false; + } + } + close(fh); + return true; +}*/ + +void qav::qvideo::save_frame(const unsigned char *buf, const char* __fname) { + FILE *pFile; + std::string s_fname; + char num_buf[32]; + + // + sprintf(num_buf, ".%08d.ppm", frnum); + num_buf[31] = '\0'; + std::ostringstream oss; + oss << ((__fname) ? __fname : fname.c_str()) << num_buf; + // Open file + pFile=fopen(oss.str().c_str(), "wb"); + if(pFile==NULL) + return; + + // Write header + fprintf(pFile, "P6\n%d %d\n255\n", out_width, out_height); + + // Write pixel data + for(int y=0; y fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + +#ifndef _QAV_H_ +#define _QAV_H_ + +// libavcodec is a C library without C++ guards... +extern "C" { +#include +#include +#include +} + +#include +#include + +namespace qav { + struct scr_size { + int x, + y; + scr_size(const int& _x = 0, const int& _y = 0) : x(_x), y(_y) { + } + + inline friend bool operator==(const scr_size& lhs, const scr_size& rhs) { + return lhs.x==rhs.x && lhs.y==rhs.y; + } + }; + + class qvideo { + int frnum, + videoStream, + out_width, + out_height; + AVFormatContext *pFormatCtx; + AVCodecContext *pCodecCtx; + AVCodec *pCodec; + AVFrame *pFrame; + struct SwsContext *img_convert_ctx; + std::string fname; + public: + qvideo(const char* file, int _out_width = -1, int _out_height = -1); + scr_size get_size(void) const; + int get_fps_k(void) const; + bool get_frame(std::vector& out, int *_frnum = 0); + void save_frame(const unsigned char *buf, const char* __fname = 0); + ~qvideo(); + }; +} + + +#endif /*_QAV_H_*/ + diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 0000000..2ca28c6 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,32 @@ +/* +* qpsnr (C) 2010 E. Oriani, ema fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + +#include "settings.h" + +namespace settings { + char LOG = 0x07; + std::string REF_VIDEO = ""; + int MAX_FRAMES = -1; + int SKIP_FRAMES = -1; + bool SAVE_IMAGES = false; + std::string ANALYZER = "psnr"; + bool IGNORE_FPS = false; + int VIDEO_SIZE_W = -1; + int VIDEO_SIZE_H = -1; +} diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..2998ea4 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,52 @@ +/* +* qpsnr (C) 2010 E. Oriani, ema fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ + +#include +#include + +#define LEVEL_LOG_ERROR (0x01) +#define LEVEL_LOG_WARNING (0x02) +#define LEVEL_LOG_INFO (0x04) +#define LEVEL_LOG_DEBUG (0x08) + +#define __LOG(x) if ( x & settings::LOG) std::cerr + +#define LOG_ERROR __LOG(LEVEL_LOG_ERROR) << "[ERROR] " +#define LOG_WARNING __LOG(LEVEL_LOG_WARNING) << "[WARNING] " +#define LOG_INFO __LOG(LEVEL_LOG_INFO) << "[INFO] " +#define LOG_DEBUG __LOG(LEVEL_LOG_DEBUG) << "[DEBUG] " + +namespace settings { + extern char LOG; + extern std::string REF_VIDEO; + extern int MAX_FRAMES; + extern int SKIP_FRAMES; + extern bool SAVE_IMAGES; + extern std::string ANALYZER; + extern bool IGNORE_FPS; + extern int VIDEO_SIZE_W; + extern int VIDEO_SIZE_H; +} + + +#endif /*_SETTINGS_H_*/ + diff --git a/src/shared_ptr.h b/src/shared_ptr.h new file mode 100644 index 0000000..78b2947 --- /dev/null +++ b/src/shared_ptr.h @@ -0,0 +1,149 @@ +/* +* qpsnr (C) 2010 E. Oriani, ema fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + +/* +* myNZB (C) 2009 E. Oriani, ema fastwebnet it +* +* This file is part of myNZB. +* +* myNZB is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* myNZB is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with myNZB. If not, see . +*/ + +#ifndef _SHARED_PTR_H_ +#define _SHARED_PTR_H_ + +template +class shared_ptr +{ +public: + typedef T type; + typedef T* ptr_type; + typedef unsigned int count_type; + + shared_ptr() : + _ptr(0), + _count(0) + { + } + + shared_ptr(const ptr_type& in) : + _ptr(in) + { + _count = new (std::nothrow) count_type; + if (!_count) + { + delete in; + throw std::bad_alloc(); + } + *_count = 1; + } + + shared_ptr(const shared_ptr& in) + { + _ptr = in._ptr; + _count = in._count; + if (_count) *_count += 1; + } + + shared_ptr& operator=(const shared_ptr& in) + { + if (_ptr != in._ptr) + { + if (_ptr) + { + if (!--*_count) + { + delete _ptr; + delete _count; + } + } + _ptr = in._ptr; + _count = in._count; + if (_count) *_count += 1; + } + return *this; + } + + ~shared_ptr() + { + if (_count) + { + if (!--*_count) + { + delete _ptr; + delete _count; + } + } + } + + ptr_type get(void) const + { + return _ptr; + } + + count_type get_count(void) const + { + if (_count) return *_count; + return 0; + } + + ptr_type operator->() const + { + return _ptr; + } + + type& operator*() const + { + return *_ptr; + } + + template + bool operator==(shared_ptr& in) + { + return _ptr == in._ptr; + } + + template + bool operator!=(shared_ptr& in) + { + return _ptr != in._ptr; + } + + template + bool operator<(shared_ptr& in) + { + return _ptr < in._ptr; + } +private: + ptr_type _ptr; + count_type *_count; +}; + +#endif //_SHARED_PTR_H_ diff --git a/src/stats.cpp b/src/stats.cpp new file mode 100644 index 0000000..c537caf --- /dev/null +++ b/src/stats.cpp @@ -0,0 +1,539 @@ +/* +* qpsnr (C) 2010 E. Oriani, ema fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + +#include "stats.h" +#include "mt.h" +#include "shared_ptr.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include +#include + +// define these classes just locally +namespace stats { + static double compute_psnr(const unsigned char *ref, const unsigned char *cmp, const unsigned int& sz) { + double mse = 0.0; + for(unsigned int i = 0; i < sz; ++i) { + const int diff = ref[i]-cmp[i]; + mse += (diff*diff); + } + mse /= (double)sz; + if (0.0 == mse) mse = 1e-10; + return 10.0*log10(65025.0/mse); + } + + static double compute_ssim(const unsigned char *ref, const unsigned char *cmp, const unsigned int& x, const unsigned int& y, const unsigned int& b_sz) { + // we return the average of all the blocks + const unsigned int x_bl_num = x/b_sz, + y_bl_num = y/b_sz; + if (!x_bl_num || !y_bl_num) return 0.0; + std::vector ssim_accum; + // for each block do it + for(int yB = 0; yB < y_bl_num; ++yB) + for(int xB = 0; xB < x_bl_num; ++xB) { + const unsigned int base_offset = xB*b_sz + yB*b_sz*x; + double ref_acc = 0.0, + ref_acc_2 = 0.0, + cmp_acc = 0.0, + cmp_acc_2 = 0.0, + ref_cmp_acc = 0.0; + for(int j = 0; j < b_sz; ++j) + for(int i = 0; i < b_sz; ++i) { + // we have to multiply by 3, colorplanes are Y Cb Cr, we need + // only Y component + const unsigned char c_ref = ref[3*(base_offset + j*x + i)], + c_cmp = cmp[3*(base_offset + j*x + i)]; + ref_acc += c_ref; + ref_acc_2 += (c_ref*c_ref); + cmp_acc += c_cmp; + cmp_acc_2 += (c_cmp*c_cmp); + ref_cmp_acc += (c_ref*c_cmp); + } + // now finally get the ssim for this block + // http://en.wikipedia.org/wiki/SSIM + // http://en.wikipedia.org/wiki/Variance + // http://en.wikipedia.org/wiki/Covariance + const double n_samples = (b_sz*b_sz), + ref_avg = ref_acc/n_samples, + ref_var = ref_acc_2/n_samples - (ref_avg*ref_avg), + cmp_avg = cmp_acc/n_samples, + cmp_var = cmp_acc_2/n_samples - (cmp_avg*cmp_avg), + ref_cmp_cov = ref_cmp_acc/n_samples - (ref_avg*cmp_avg), + c1 = 6.5025, // (0.01*255.0)^2 + c2 = 58.5225, // (0.03*255)^2 + ssim_num = (2.0*ref_avg*cmp_avg + c1)*(2.0*ref_cmp_cov + c2), + ssim_den = (ref_avg*ref_avg + cmp_avg*cmp_avg + c1)*(ref_var + cmp_var + c2), + ssim = ssim_num/ssim_den; + ssim_accum.push_back(ssim); + } + + double avg = 0.0; + for(std::vector::const_iterator it = ssim_accum.begin(); it != ssim_accum.end(); ++it) + avg += *it; + return avg/ssim_accum.size(); + } + + static inline double r_0_1(const double& d) { + return std::max(0.0, std::min(1.0, d)); + } + + static void rgb_2_hsi(unsigned char *p, const int& sz) { + /* + I = (1/3) *(R+G+B) + S = 1 - ( (3/(R+G+B)) * min(R,G,B)) + H = cos^-1( ((1/2) * ((R-G) + (R - B))) / ((R-G)^2 + (R-B)*(G-B)) ^(1/2) ) + H = cos^-1 ( (((R-G)+(R-B))/2)/ (sqrt((R-G)^2 + (R-B)*(G-B) ))) + */ + const static double PI = 3.14159265; + for(int j =0; j < sz; j += 3) { + const double r = p[j+0]/255.0, + g = p[j+1]/255.0, + b = p[j+2]/255.0, + i = r_0_1((r+g+b)/3), + s = r_0_1(1.0 - (3.0/(r+g+b) * std::min(r, std::min(g, b)))), + h = r_0_1(acos(0.5*(r-g + r-b) / sqrt((r-g)*(r-g) + (r-b)*(g-b)))/PI); + p[j+0] = 255.0*h + 0.5; + p[j+1] = 255.0*s + 0.5; + p[j+2] = 255.0*i + 0.5; + } + } + + static void rgb_2_YCbCr(unsigned char *p, const int& sz) { + // http://en.wikipedia.org/wiki/YCbCr + for(int j =0; j < sz; j += 3) { + const double r = p[j+0]/255.0, + g = p[j+1]/255.0, + b = p[j+2]/255.0; + p[j+0] = 16 + (65.481*r + 128.553*g + 24.966*b); + p[j+1] = 128 + (-37.797*r - 74.203*g + 112.0*b); + p[j+2] = 128 + (112.0*r - 93.786*g - 12.214*b); + } + } + + static void rgb_2_Y(unsigned char *p, const int& sz) { + // http://en.wikipedia.org/wiki/YCbCr + for(int j =0; j < sz; j += 3) { + const double r = p[j+0]/255.0, + g = p[j+1]/255.0, + b = p[j+2]/255.0; + p[j+0] = 16 + (65.481*r + 128.553*g + 24.966*b); + p[j+1] = 0.0; + p[j+2] = 0.0; + } + } + + static int getCPUcount(void) { + std::ifstream cpuinfo("/proc/cpuinfo"); + std::string line; + std::set IDs; + while (cpuinfo){ + std::getline(cpuinfo,line); + if (line.empty()) + continue; + if (line.find("processor") != 0) + continue; + IDs.insert(line); + } + if (IDs.empty()) return 1; + return IDs.size(); + } + + static mt::ThreadPool __stats_tp(getCPUcount()); + + class psnr_job : public mt::ThreadPool::Job { + const unsigned char *_ref, + *_cmp; + const unsigned int _sz; + double &_res; + public: + psnr_job(const unsigned char *ref, const unsigned char *cmp, const unsigned int& sz, double& res) : + _ref(ref), _cmp(cmp), _sz(sz), _res(res) { + } + + virtual void run(void) { + _res = compute_psnr(_ref, _cmp, _sz); + } + }; + + static void get_psnr_tp(const VUCHAR& ref, const std::vector& v_ok, const std::vector& streams, std::vector& res) { + const unsigned int sz = v_ok.size(); + std::vector > v_jobs; + for(unsigned int i =0; i < sz; ++i) { + if (v_ok[i]) { + v_jobs.push_back(new psnr_job(&ref[0], &(streams[i][0]), ref.size(), res[i])); + __stats_tp.add(v_jobs.rbegin()->get()); + } else res[i] = 0.0; + } + //wait for all + for(std::vector >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) { + (*it)->wait(); + (*it) = 0; + } + } + + class ssim_job : public mt::ThreadPool::Job { + const unsigned char *_ref, + *_cmp; + const unsigned int _x, + _y, + _b_sz; + double &_res; + public: + ssim_job(const unsigned char *ref, const unsigned char *cmp, const unsigned int& x, const unsigned int& y, const unsigned int b_sz, double& res) : + _ref(ref), _cmp(cmp), _x(x), _y(y), _b_sz(b_sz), _res(res) { + } + + virtual void run(void) { + _res = compute_ssim(_ref, _cmp, _x, _y, _b_sz); + } + }; + + static void get_ssim_tp(const VUCHAR& ref, const std::vector& v_ok, const std::vector& streams, std::vector& res, const unsigned int& x, const unsigned int& y, const unsigned int& b_sz) { + const unsigned int sz = v_ok.size(); + std::vector > v_jobs; + for(unsigned int i =0; i < sz; ++i) { + if (v_ok[i]) { + v_jobs.push_back(new ssim_job(&ref[0], &(streams[i][0]), x, y, b_sz, res[i])); + __stats_tp.add(v_jobs.rbegin()->get()); + } else res[i] = 0.0; + } + //wait for all + for(std::vector >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) { + (*it)->wait(); + (*it) = 0; + } + } + + class hsi_job : public mt::ThreadPool::Job { + unsigned char *_buf; + const unsigned int _sz; + public: + hsi_job(unsigned char *buf, const unsigned int& sz) : + _buf(buf), _sz(sz) { + } + + virtual void run(void) { + rgb_2_hsi(_buf, _sz); + } + }; + + class YCbCr_job : public mt::ThreadPool::Job { + unsigned char *_buf; + const unsigned int _sz; + public: + YCbCr_job(unsigned char *buf, const unsigned int& sz) : + _buf(buf), _sz(sz) { + } + + virtual void run(void) { + rgb_2_YCbCr(_buf, _sz); + } + }; + + class Y_job : public mt::ThreadPool::Job { + unsigned char *_buf; + const unsigned int _sz; + public: + Y_job(unsigned char *buf, const unsigned int& sz) : + _buf(buf), _sz(sz) { + } + + virtual void run(void) { + rgb_2_Y(_buf, _sz); + } + }; + + static void rgb_2_hsi_tp(VUCHAR& ref, const std::vector& v_ok, std::vector& streams) { + const unsigned int sz = v_ok.size(); + std::vector > v_jobs; + v_jobs.push_back(new hsi_job(&ref[0], ref.size())); + __stats_tp.add(v_jobs.rbegin()->get()); + for(unsigned int i =0; i < sz; ++i) { + if (v_ok[i]) { + v_jobs.push_back(new hsi_job(&(streams[i][0]), streams[i].size())); + __stats_tp.add(v_jobs.rbegin()->get()); + } + } + //wait for all + for(std::vector >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) { + (*it)->wait(); + (*it) = 0; + } + } + + static void rgb_2_YCbCr_tp(VUCHAR& ref, const std::vector& v_ok, std::vector& streams) { + const unsigned int sz = v_ok.size(); + std::vector > v_jobs; + v_jobs.push_back(new YCbCr_job(&ref[0], ref.size())); + __stats_tp.add(v_jobs.rbegin()->get()); + for(unsigned int i =0; i < sz; ++i) { + if (v_ok[i]) { + v_jobs.push_back(new YCbCr_job(&(streams[i][0]), streams[i].size())); + __stats_tp.add(v_jobs.rbegin()->get()); + } + } + //wait for all + for(std::vector >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) { + (*it)->wait(); + (*it) = 0; + } + } + + static void rgb_2_Y_tp(VUCHAR& ref, const std::vector& v_ok, std::vector& streams) { + const unsigned int sz = v_ok.size(); + std::vector > v_jobs; + v_jobs.push_back(new Y_job(&ref[0], ref.size())); + __stats_tp.add(v_jobs.rbegin()->get()); + for(unsigned int i =0; i < sz; ++i) { + if (v_ok[i]) { + v_jobs.push_back(new Y_job(&(streams[i][0]), streams[i].size())); + __stats_tp.add(v_jobs.rbegin()->get()); + } + } + //wait for all + for(std::vector >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) { + (*it)->wait(); + (*it) = 0; + } + } + + class psnr : public s_base { + std::string _colorspace; + protected: + void print(const int& ref_frame, const std::vector& v_res) { + _ostr << ref_frame << ','; + for(int i = 0; i < _n_streams; ++i) + _ostr << v_res[i] << ','; + _ostr << std::endl; + } + + void process_colorspace(VUCHAR& ref, const std::vector& v_ok, std::vector& streams) { + if (_colorspace == "hsi") { + rgb_2_hsi_tp(ref, v_ok, streams); + } else if (_colorspace == "ycbcr") { + rgb_2_YCbCr_tp(ref, v_ok, streams); + } else if (_colorspace == "y") { + rgb_2_Y_tp(ref, v_ok, streams); + } + } + public: + psnr(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) : + s_base(n_streams, i_width, i_height, ostr), _colorspace("rgb") { + } + + virtual void set_parameter(const char* p_name, const char *p_value) { + const std::string s_name(p_name); + if (s_name == "colorspace") { + _colorspace = p_value; + if (_colorspace != "rgb" && _colorspace != "hsi" + && _colorspace != "ycbcr" && _colorspace != "y") + throw std::runtime_error("Invalid colorspace passed to analyzer"); + } + } + + virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector& v_ok, std::vector& streams) { + if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer"); + // process colorspace + process_colorspace(ref, v_ok, streams); + // + std::vector v_res(_n_streams); + get_psnr_tp(ref, v_ok, streams, v_res); + // + print(ref_frame, v_res); + } + }; + + class avg_psnr : public psnr { + int _fpa, + _accum_f, + _last_frame; + std::vector _accum_v; + protected: + void print(const int& ref_frame) { + // check if we have to print infor or not + if (_accum_f < _fpa) return; + // else print data + for(int i=0; i < _n_streams; ++i) + _accum_v[i] /= _accum_f; + psnr::print(ref_frame, _accum_v); + // clear the vector and set _accum_f to 0 + _accum_v.clear(); + _accum_v.resize(_n_streams); + _accum_f = 0; + } + public: + avg_psnr(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) : + psnr(n_streams, i_width, i_height, ostr), _fpa(1), _accum_f(0), _last_frame(-1), _accum_v(n_streams) { + } + + virtual void set_parameter(const char* p_name, const char *p_value) { + psnr::set_parameter(p_name, p_value); + // + const std::string s_name(p_name); + if (s_name == "fpa") { + const int fpa = atoi(p_value); + if (fpa > 0) _fpa = fpa; + } + } + + virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector& v_ok, std::vector& streams) { + if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer"); + // set last frame + _last_frame = ref_frame; + // process colorspace + process_colorspace(ref, v_ok, streams); + // compute the psnr + std::vector v_res(_n_streams); + get_psnr_tp(ref, v_ok, streams, v_res); + // accumulate for each + for(int i = 0; i < _n_streams; ++i) { + if (v_ok[i]) { + _accum_v[i] += v_res[i]; + } else _accum_v[i] += 0.0; + } + ++_accum_f; + // try to print data + print(ref_frame); + } + + virtual ~avg_psnr() { + // on exit check if we have some data + if (_accum_f > 0) { + _ostr << _last_frame << ','; + for(int i = 0; i < _n_streams; ++i) { + _ostr << _accum_v[i]/_accum_f << ','; + } + _ostr << std::endl; + } + } + }; + + class ssim : public s_base { + protected: + int _blocksize; + + void print(const int& ref_frame, const std::vector& v_res) { + _ostr << ref_frame << ','; + for(int i = 0; i < _n_streams; ++i) + _ostr << v_res[i] << ','; + _ostr << std::endl; + } + public: + ssim(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) : + s_base(n_streams, i_width, i_height, ostr), _blocksize(8) { + } + + virtual void set_parameter(const char* p_name, const char *p_value) { + const std::string s_name(p_name); + if (s_name == "blocksize") { + const int blocksize = atoi(p_value); + if (blocksize > 0) _blocksize = blocksize; + } + } + + virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector& v_ok, std::vector& streams) { + if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer"); + // convert to Y colorspace + rgb_2_Y_tp(ref, v_ok, streams); + // + std::vector v_res(_n_streams); + get_ssim_tp(ref, v_ok, streams, v_res, _i_width, _i_height, _blocksize); + // + print(ref_frame, v_res); + } + }; + + class avg_ssim : public ssim { + int _fpa, + _accum_f, + _last_frame; + std::vector _accum_v; + protected: + void print(const int& ref_frame) { + // check if we have to print infor or not + if (_accum_f < _fpa) return; + // else print data + for(int i=0; i < _n_streams; ++i) + _accum_v[i] /= _accum_f; + ssim::print(ref_frame, _accum_v); + // clear the vector and set _accum_f to 0 + _accum_v.clear(); + _accum_v.resize(_n_streams); + _accum_f = 0; + } + public: + avg_ssim(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) : + ssim(n_streams, i_width, i_height, ostr), _fpa(1), _accum_f(0), _last_frame(-1), _accum_v(n_streams) { + } + + virtual void set_parameter(const char* p_name, const char *p_value) { + ssim::set_parameter(p_name, p_value); + // + const std::string s_name(p_name); + if (s_name == "fpa") { + const int fpa = atoi(p_value); + if (fpa > 0) _fpa = fpa; + } + } + + virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector& v_ok, std::vector& streams) { + if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer"); + // set last frame + _last_frame = ref_frame; + // convert to Y colorspace + rgb_2_Y_tp(ref, v_ok, streams); + // + std::vector v_res(_n_streams); + get_ssim_tp(ref, v_ok, streams, v_res, _i_width, _i_height, _blocksize); + // accumulate for each + for(int i = 0; i < _n_streams; ++i) { + if (v_ok[i]) { + _accum_v[i] += v_res[i]; + } else _accum_v[i] += 0.0; + } + ++_accum_f; + // try to print data + print(ref_frame); + } + + virtual ~avg_ssim() { + // on exit check if we have some data + if (_accum_f > 0) { + _ostr << _last_frame << ','; + for(int i = 0; i < _n_streams; ++i) { + _ostr << _accum_v[i]/_accum_f << ','; + } + _ostr << std::endl; + } + } + }; +} + +stats::s_base* stats::get_analyzer(const char* id, const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) { + const std::string s_id(id); + if (s_id == "psnr") return new psnr(n_streams, i_width, i_height, ostr); + else if (s_id == "avg_psnr") return new avg_psnr(n_streams, i_width, i_height, ostr); + else if (s_id == "ssim") return new ssim(n_streams, i_width, i_height, ostr); + else if (s_id == "avg_ssim") return new avg_ssim(n_streams, i_width, i_height, ostr); + throw std::runtime_error("Invalid analyzer id"); +} diff --git a/src/stats.h b/src/stats.h new file mode 100644 index 0000000..bec8854 --- /dev/null +++ b/src/stats.h @@ -0,0 +1,53 @@ +/* +* qpsnr (C) 2010 E. Oriani, ema fastwebnet it +* +* This file is part of qpsnr. +* +* qpsnr is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* qpsnr is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with qpsnr. If not, see . +*/ + +#ifndef _STATS_H_ +#define _STATS_H_ + +#include +#include + +namespace stats { + typedef std::vector VUCHAR; + + class s_base { + protected: + const int _n_streams, + _i_width, + _i_height; + std::ostream &_ostr; + public: + s_base(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) : + _n_streams(n_streams), _i_width(i_width), _i_height(i_height), _ostr(ostr) { + } + + virtual void set_parameter(const char* p_name, const char *p_value) { + } + + virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector& v_ok, std::vector& streams) = 0; + + virtual ~s_base() { + } + }; + + extern s_base* get_analyzer(const char* id, const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr); +} + +#endif /*_STATS_H_*/ + -- 2.39.2