]> sjero.net Git - qpsnr/commitdiff
Initial Commit of QPSNR (version 0.2.1) master
authorSamuel Jero <sj323707@ohio.edu>
Tue, 12 Mar 2013 01:47:31 +0000 (21:47 -0400)
committerSamuel Jero <sj323707@ohio.edu>
Tue, 12 Mar 2013 01:47:31 +0000 (21:47 -0400)
13 files changed:
.cproject [new file with mode: 0644]
.project [new file with mode: 0644]
Makefile [new file with mode: 0644]
src/gpl_v3 [new file with mode: 0644]
src/main.cpp [new file with mode: 0644]
src/mt.h [new file with mode: 0644]
src/qav.cpp [new file with mode: 0644]
src/qav.h [new file with mode: 0644]
src/settings.cpp [new file with mode: 0644]
src/settings.h [new file with mode: 0644]
src/shared_ptr.h [new file with mode: 0644]
src/stats.cpp [new file with mode: 0644]
src/stats.h [new file with mode: 0644]

diff --git a/.cproject b/.cproject
new file mode 100644 (file)
index 0000000..c0e81ea
--- /dev/null
+++ b/.cproject
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?fileVersion 4.0.0?>
+
+<cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
+       <storageModule moduleId="org.eclipse.cdt.core.settings">
+               <cconfiguration id="cdt.managedbuild.toolchain.gnu.base.1038513072">
+                       <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.base.1038513072" moduleId="org.eclipse.cdt.core.settings" name="Default">
+                               <externalSettings/>
+                               <extensions>
+                                       <extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
+                                       <extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                               </extensions>
+                       </storageModule>
+                       <storageModule moduleId="cdtBuildSystem" version="4.0.0">
+                               <configuration buildProperties="" id="cdt.managedbuild.toolchain.gnu.base.1038513072" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
+                                       <folderInfo id="cdt.managedbuild.toolchain.gnu.base.1038513072.71015752" name="/" resourcePath="">
+                                               <toolChain id="cdt.managedbuild.toolchain.gnu.base.1873147613" name="cdt.managedbuild.toolchain.gnu.base" superClass="cdt.managedbuild.toolchain.gnu.base">
+                                                       <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.target.gnu.platform.base.592077177" name="Debug Platform" osList="linux,hpux,aix,qnx" superClass="cdt.managedbuild.target.gnu.platform.base"/>
+                                                       <builder id="cdt.managedbuild.target.gnu.builder.base.1210866582" managedBuildOn="false" name="Gnu Make Builder.Default" superClass="cdt.managedbuild.target.gnu.builder.base"/>
+                                                       <tool id="cdt.managedbuild.tool.gnu.archiver.base.1523043784" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
+                                                       <tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.1661515438" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base"/>
+                                                       <tool id="cdt.managedbuild.tool.gnu.c.compiler.base.1380832673" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.base"/>
+                                                       <tool id="cdt.managedbuild.tool.gnu.c.linker.base.2027547661" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base"/>
+                                                       <tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.1137861790" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base"/>
+                                                       <tool id="cdt.managedbuild.tool.gnu.assembler.base.1410144416" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.base"/>
+                                               </toolChain>
+                                       </folderInfo>
+                               </configuration>
+                       </storageModule>
+                       <storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
+               </cconfiguration>
+       </storageModule>
+       <storageModule moduleId="cdtBuildSystem" version="4.0.0">
+               <project id="QPSNR.null.1329330942" name="QPSNR"/>
+       </storageModule>
+       <storageModule moduleId="scannerConfiguration">
+               <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+       </storageModule>
+       <storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
+</cproject>
diff --git a/.project b/.project
new file mode 100644 (file)
index 0000000..f67097f
--- /dev/null
+++ b/.project
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>QPSNR</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
+                       <triggers>clean,full,incremental,</triggers>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
+                       <triggers>full,incremental,</triggers>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.cdt.core.cnature</nature>
+               <nature>org.eclipse.cdt.core.ccnature</nature>
+               <nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
+               <nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
+       </natures>
+</projectDescription>
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..a5d093e
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644 (file)
index 0000000..b5fa48d
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <iostream>
+#include <stdexcept>
+#include <vector>
+#include <memory>
+#include <sstream>
+#include <unistd.h>
+#include <getopt.h>
+#include <map>
+#include "mt.h"
+#include "shared_ptr.h"
+#include "qav.h"
+#include "settings.h"
+#include "stats.h"
+
+template<typename T>
+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<unsigned char>      &_buf;
+       qav::qvideo                     &_video;
+       bool                            &_exit;
+public:
+       video_producer(int& frame, mt::Semaphore& s_prod, mt::Semaphore& s_cons, std::vector<unsigned char>& 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<unsigned char>     VUCHAR;
+typedef shared_ptr<qav::qvideo>                SP_QVIDEO;
+struct vp_data {
+       mt::Semaphore   prod;
+       VUCHAR          buf;
+       int             frame;
+       SP_QVIDEO       video;
+       std::string     name;
+};
+typedef std::vector<shared_ptr<vp_data> >              V_VPDATA;
+typedef std::vector<shared_ptr<video_producer> >       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<std::string, std::string>& 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<std::string, std::string>      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<vp_data>     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<stats::s_base>    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<std::string, std::string>::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<VUCHAR>     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<bool>       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 (file)
index 0000000..cb2eb7a
--- /dev/null
+++ b/src/mt.h
@@ -0,0 +1,315 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+/*
+*      myNZB (C) 2009 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MT_H_
+#define _MT_H_
+
+#include <pthread.h>
+#include <semaphore.h>
+#include <list>
+#include <vector>
+#include <errno.h>
+#include <exception>
+#include <string>
+
+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<Job*> _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<pthread_t>  _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<Job*>::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 (file)
index 0000000..8083916
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "qav.h"
+#include "settings.h"
+#include <stdexcept>
+#include <sstream>
+
+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; i<pFormatCtx->nb_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<unsigned char>& 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<out_height; y++)
+               fwrite(buf+y*out_width*3, 1, out_width*3, pFile);
+
+       // Close file
+       fclose(pFile);
+}
+
+qav::qvideo::~qvideo() {
+       sws_freeContext(img_convert_ctx);
+       av_free(pFrame);
+       avcodec_close(pCodecCtx);
+       av_close_input_file(pFormatCtx);
+}
+
diff --git a/src/qav.h b/src/qav.h
new file mode 100644 (file)
index 0000000..d89de3f
--- /dev/null
+++ b/src/qav.h
@@ -0,0 +1,68 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _QAV_H_
+#define _QAV_H_
+
+// libavcodec is a C library without C++ guards...
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+}
+
+#include <string>
+#include <vector>
+
+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<unsigned char>& 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 (file)
index 0000000..2ca28c6
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 (file)
index 0000000..2998ea4
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SETTINGS_H_
+#define _SETTINGS_H_
+
+#include <string>
+#include <iostream>
+
+#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 (file)
index 0000000..78b2947
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+/*
+*      myNZB (C) 2009 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SHARED_PTR_H_
+#define _SHARED_PTR_H_
+
+template<typename T>
+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<typename U>
+       bool operator==(shared_ptr<U>& in)
+       {
+               return _ptr == in._ptr;
+       }
+       
+       template<typename U>
+       bool operator!=(shared_ptr<U>& in)
+       {
+               return _ptr != in._ptr;
+       }
+       
+       template<typename U>
+       bool operator<(shared_ptr<U>& 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 (file)
index 0000000..c537caf
--- /dev/null
@@ -0,0 +1,539 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "stats.h"
+#include "mt.h"
+#include "shared_ptr.h"
+#include "settings.h"
+#include <cmath>
+#include <string>
+#include <stdexcept>
+#include <set>
+#include <fstream>
+#include <algorithm>
+#include <cstdlib>
+
+// 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<double>     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<double>::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<std::string>   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<bool>& v_ok, const std::vector<VUCHAR>& streams, std::vector<double>& res) {
+               const unsigned int                      sz = v_ok.size();
+               std::vector<shared_ptr<psnr_job> >      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<shared_ptr<psnr_job> >::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<bool>& v_ok, const std::vector<VUCHAR>& streams, std::vector<double>& res, const unsigned int& x, const unsigned int& y, const unsigned int& b_sz) {
+               const unsigned int                      sz = v_ok.size();
+               std::vector<shared_ptr<ssim_job> >      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<shared_ptr<ssim_job> >::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<bool>& v_ok, std::vector<VUCHAR>& streams) {
+               const unsigned int                      sz = v_ok.size();
+               std::vector<shared_ptr<hsi_job> >       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<shared_ptr<hsi_job> >::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<bool>& v_ok, std::vector<VUCHAR>& streams) {
+               const unsigned int                      sz = v_ok.size();
+               std::vector<shared_ptr<YCbCr_job> >     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<shared_ptr<YCbCr_job> >::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<bool>& v_ok, std::vector<VUCHAR>& streams) {
+               const unsigned int                      sz = v_ok.size();
+               std::vector<shared_ptr<Y_job> >         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<shared_ptr<Y_job> >::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<double>& 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<bool>& v_ok, std::vector<VUCHAR>& 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<bool>& v_ok, std::vector<VUCHAR>& 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<double>     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<double>     _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<bool>& v_ok, std::vector<VUCHAR>& 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<double>     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<double>& 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<bool>& v_ok, std::vector<VUCHAR>& 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<double>     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<double>     _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<bool>& v_ok, std::vector<VUCHAR>& 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<double>     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 (file)
index 0000000..bec8854
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+*      qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _STATS_H_
+#define _STATS_H_
+
+#include <vector>
+#include <ostream>
+
+namespace stats {
+       typedef std::vector<unsigned char>      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<bool>& v_ok, std::vector<VUCHAR>& 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_*/
+