]> sjero.net Git - qpsnr/blob - src/main.cpp
Initial Commit of QPSNR (version 0.2.1)
[qpsnr] / src / main.cpp
1 /*
2 *       qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> it
3 *
4 *       This file is part of qpsnr.
5 *
6 *       qpsnr is free software: you can redistribute it and/or modify
7 *       it under the terms of the GNU General Public License as published by
8 *       the Free Software Foundation, either version 3 of the License, or
9 *       (at your option) any later version.
10 *
11 *       qpsnr is distributed in the hope that it will be useful,
12 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *       GNU General Public License for more details.
15 *
16 *       You should have received a copy of the GNU General Public License
17 *       along with qpsnr.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <iostream>
21 #include <stdexcept>
22 #include <vector>
23 #include <memory>
24 #include <sstream>
25 #include <unistd.h>
26 #include <getopt.h>
27 #include <map>
28 #include "mt.h"
29 #include "shared_ptr.h"
30 #include "qav.h"
31 #include "settings.h"
32 #include "stats.h"
33
34 template<typename T>
35 std::string XtoS(const T& in) {
36         std::ostringstream      oss;
37         oss << in;
38         return in.str();
39 }
40
41 std::string get_filename(const std::string& in) {
42         const char *pslash = strrchr(in.c_str(), '/');
43         if (pslash) return std::string(pslash+1);
44         return in;
45 }
46
47 class video_producer : public mt::Thread {
48         int                             &_frame;
49         mt::Semaphore                   &_s_prod,
50                                         &_s_cons;
51         std::vector<unsigned char>      &_buf;
52         qav::qvideo                     &_video;
53         bool                            &_exit;
54 public:
55         video_producer(int& frame, mt::Semaphore& s_prod, mt::Semaphore& s_cons, std::vector<unsigned char>& buf, qav::qvideo& video, bool& __exit) : 
56         _frame(frame), _s_prod(s_prod), _s_cons(s_cons), _buf(buf), _video(video), _exit(__exit) {
57         }
58
59         virtual void run(void) {
60                 while(!_exit) {
61                         if (!_video.get_frame(_buf, &_frame)) _frame = -1;
62                         // signal cons we're done
63                         _s_cons.pop();
64                         // wait for cons to tell us to go
65                         _s_prod.push();
66                 }
67         }
68 };
69
70 typedef std::vector<unsigned char>      VUCHAR;
71 typedef shared_ptr<qav::qvideo>         SP_QVIDEO;
72 struct vp_data {
73         mt::Semaphore   prod;
74         VUCHAR          buf;
75         int             frame;
76         SP_QVIDEO       video;
77         std::string     name;
78 };
79 typedef std::vector<shared_ptr<vp_data> >               V_VPDATA;
80 typedef std::vector<shared_ptr<video_producer> >        V_VPTH;
81
82 const std::string       __qpsnr__ = "qpsnr",
83                         __version__ = "0.2.1";
84
85 void print_help(void) {
86         std::cerr <<    __qpsnr__ << " v" << __version__ << " - (C) 2010 E. Oriani\n"
87                         "Usage: " << __qpsnr__ << " [options] -r ref.video compare.video1 compare.video2 ...\n\n"
88                         "-r,--reference:\n\tset reference video (mandatory)\n"
89                         "\n-v,--video-size:\n\tset analysis video size WIDTHxHEIGHT (ie. 1280x720), default is reference video size\n"
90                         "\n-s,--skip-frames:\n\tskip n initial frames\n"
91                         "\n-m,--max-frames:\n\tset max frames to process before quit\n"
92                         "\n-I,--save-frames:\n\tsave frames (ppm format)\n"
93                         "\n-G,--ignore-fps:\n\tanalyze videos even if the expected fps are different\n"
94                         "\n-a,--analyzer:\n"
95                         "\tpsnr : execute the psnr for each frame\n"
96                         "\tavg_psnr : take the average of the psnr every n frames (use option \"fpa\" to set it)\n"
97                         "\tssim : execute the ssim (Y colorspace) on the frames divided in blocks (use option \"blocksize\" to set the size)\n"
98                         "\tavg_ssim : take the average of the ssim (Y colorspace) every n frames (use option \"fpa\" to set it)\n"
99                         "\n-o,--aopts: (specify option1=value1:option2=value2:...)\n"
100                         "\tfpa : set the frames per average, default 25\n"
101                         "\tcolorspace : set the colorspace (\"rgb\", \"hsi\", \"ycbcr\" or \"y\"), default \"rgb\"\n"
102                         "\tblocksize : set blocksize for ssim analysis, default 8\n"
103                         "\n-l,--log-level:\n"
104                         "\t0 : No log\n"
105                         "\t1 : Errors only\n"
106                         "\t2 : Warnings and errors\n"
107                         "\t3 : Info, warnings and errors (default)\n"
108                         "\t4 : Debug, info, warnings and errors\n"
109                         "\n-h,--help:\n\tprint this help and exit\n"
110                  <<     std::flush;
111 }
112
113 int parse_options(int argc, char *argv[], std::map<std::string, std::string>& aopt) {
114         aopt.clear();
115         opterr = 0;
116         int     c = 0,
117                 option_index = 0;
118
119         static struct option long_options[] =
120         {
121                 {"analyzer", required_argument, 0, 'a'},
122                 {"max-frames", required_argument, 0, 'm'},
123                 {"skip-frames", required_argument, 0, 's'},
124                 {"reference", required_argument, 0, 'r'},
125                 {"log-level", required_argument, 0, 'l'},
126                 {"save-frames", no_argument, 0, 'I'},
127                 {"video-size", required_argument, 0, 'v'},
128                 {"ignore-fps", no_argument, 0, 'G'},
129                 {"help", no_argument, 0, 'h'},
130                 {"aopts", required_argument, 0, 'o'},
131                 {0, 0, 0, 0}
132         };
133
134         while ((c = getopt_long (argc, argv, "a:l:m:o:r:s:v:hIG", long_options, &option_index)) != -1) {
135                 switch (c) {
136                         case 'a':
137                                 settings::ANALYZER = optarg;
138                                 break;
139                         case 'v':
140                                 {
141                                         const char *p_x = strchr(optarg, 'x');
142                                         if (!p_x) p_x = strchr(optarg, 'X');
143                                         if (!p_x) throw std::runtime_error("Invalid video size specified (use WIDTHxHEIGHT format, ie. 1280x720)");
144                                         const std::string       s_x(optarg, p_x-optarg),
145                                                                 s_y(p_x+1);
146                                         if (s_x.empty() || s_y.empty())
147                                                 throw std::runtime_error("Invalid video size specified (use WIDTHxHEIGHT format, ie. 1280x720)");
148                                         const int               i_x = atoi(s_x.c_str()),
149                                                                 i_y = atoi(s_y.c_str());
150                                         if (i_x <=0 || i_y <=0)
151                                                 throw std::runtime_error("Invalid video size specified, negative or 0 width/height");
152                                         settings::VIDEO_SIZE_W = i_x;
153                                         settings::VIDEO_SIZE_H = i_y;
154                                 }
155                                 break;
156                         case 's':
157                                 {
158                                         const int skip_frames = atoi(optarg);
159                                         if (skip_frames > 0 ) settings::SKIP_FRAMES = skip_frames;
160                                 }
161                                 break;
162                         case 'r':
163                                 settings::REF_VIDEO = optarg;
164                                 break;
165                         case 'm':
166                                 {
167                                         const int max_frames = atoi(optarg);
168                                         if (max_frames > 0 ) settings::MAX_FRAMES = max_frames;
169                                 }
170                                 break;
171                         case 'l':
172                                 if (isdigit(optarg[0])) {
173                                         char log_level[2];
174                                         log_level[0] = optarg[0];
175                                         log_level[1] = '\0';
176                                         const int log_ilev = atoi(optarg);
177                                         switch (log_ilev) {
178                                                 case 0:
179                                                         settings::LOG = 0x00;
180                                                         break;
181                                                 case 1:
182                                                         settings::LOG = 0x01;
183                                                         break;
184                                                 case 2:
185                                                         settings::LOG = 0x03;
186                                                         break;
187                                                 case 3:
188                                                         settings::LOG = 0x07;
189                                                         break;
190                                                 case 4:
191                                                         settings::LOG = 0x0F;
192                                                         break;
193                                                 default:
194                                                         break;
195                                         }
196                                 }
197                                 break;
198                         case 'h':
199                                 print_help();
200                                 exit(0);
201                                 break;
202                         case 'I':
203                                 settings::SAVE_IMAGES = true;
204                                 break;
205                         case 'G':
206                                 settings::IGNORE_FPS = true;
207                                 break;
208                         case 'o':
209                                 {
210                                         const char      *p_opts = optarg,
211                                                         *p_colon = 0;
212                                         while(p_colon = strchr(p_opts, ':')) {
213                                                 const std::string       c_opt(p_opts, (p_colon-p_opts));
214                                                 const size_t            p_equal = c_opt.find('=');
215                                                 if (std::string::npos != p_equal)
216                                                         aopt[c_opt.substr(0, p_equal)] = c_opt.substr(p_equal+1);
217                                                 p_opts = p_colon+1;
218                                         }
219                                         const std::string       c_opt(p_opts);
220                                         const size_t            p_equal = c_opt.find('=');
221                                         if (std::string::npos != p_equal)
222                                                 aopt[c_opt.substr(0, p_equal)] = c_opt.substr(p_equal+1);
223                                 }
224                                 break;
225                         case '?':
226                                 if (strchr("almorsv", optopt)) {
227                                         std::cerr << "Option -" << (char)optopt << " requires an argument" << std::endl;
228                                         print_help();
229                                         exit(1);
230                                 } else if (isprint (optopt)) {
231                                         std::cerr << "Option -" << (char)optopt << " is unknown" << std::endl;
232                                         print_help();
233                                         exit(1);
234                                 }
235                                 break;
236                         default:
237                                 std::cerr << "Invalid option: " << c << std::endl;
238                                 print_help();
239                                 exit(1);
240                                 break;
241                 }
242         }
243         // fix here the frame limit
244         if (settings::SKIP_FRAMES > 0 && settings::MAX_FRAMES>0) settings::MAX_FRAMES += settings::SKIP_FRAMES;
245         return optind;
246 }
247
248 namespace producers_utils {
249         void start(video_producer& ref_vpth, V_VPTH& v_th) {
250                 ref_vpth.start();
251                 for(V_VPTH::iterator it = v_th.begin(); it != v_th.end(); ++it)
252                         (*it)->start();
253         }
254
255         void stop(video_producer& ref_vpth, V_VPTH& v_th) {
256                 ref_vpth.join();
257                 for(V_VPTH::iterator it = v_th.begin(); it != v_th.end(); ++it)
258                         (*it)->join();
259         }
260
261         void sync(mt::Semaphore& sem_cons, const int& n_v_th) {
262                 for(int i = 0; i < n_v_th; ++i)
263                         sem_cons.push();
264         }
265         
266         void lock(mt::Semaphore& sem_cons, mt::Semaphore& ref_prod, V_VPDATA& v_data) {
267                 // init all the semaphores
268                 sem_cons.push();
269                 ref_prod.push();
270                 for(V_VPDATA::iterator it = v_data.begin(); it != v_data.end(); ++it)
271                         (*it)->prod.push();
272         }
273
274         void unlock(mt::Semaphore& ref_prod, V_VPDATA& v_data) {
275                 ref_prod.pop();
276                 for(V_VPDATA::iterator it = v_data.begin(); it != v_data.end(); ++it)
277                         (*it)->prod.pop();
278         }
279 }
280
281 int main(int argc, char *argv[]) {
282         try {
283                 std::map<std::string, std::string>      aopt;
284                 const int param = parse_options(argc, argv, aopt);
285                 // Register all formats and codecs
286                 av_register_all();
287                 if (settings::REF_VIDEO == "")
288                         throw std::runtime_error("Reference video not specified");
289                 bool            glb_exit = false;
290                 mt::Semaphore   sem_cons;
291                 // create data for reference video
292                 mt::Semaphore   ref_prod;
293                 VUCHAR          ref_buf;
294                 int             ref_frame;
295                 qav::qvideo     ref_video(settings::REF_VIDEO.c_str(), settings::VIDEO_SIZE_W, settings::VIDEO_SIZE_H);
296                 // get const values
297                 const qav::scr_size     ref_sz = ref_video.get_size();
298                 const int               ref_fps_k = ref_video.get_fps_k();
299                 //
300                 //ref_video.get_frame(ref_buf);
301                 //return 0;
302                 // 
303                 V_VPDATA        v_data;
304                 for(int i = param; i < argc; ++i) {
305                         try {
306                                 shared_ptr<vp_data>     vpd(new vp_data);
307                                 vpd->name = get_filename(argv[i]);
308                                 vpd->video = new qav::qvideo(argv[i], ref_sz.x, ref_sz.y);
309                                 if (vpd->video->get_fps_k() != ref_fps_k) {
310                                         if (settings::IGNORE_FPS) {
311                                                 LOG_WARNING << '[' << argv[i] << "] has different FPS (" << vpd->video->get_fps_k()/1000 << ')' << std::endl;
312                                                 v_data.push_back(vpd);
313                                         } else LOG_ERROR << '[' << argv[i] << "] skipped different FPS" << std::endl;
314                                 } else v_data.push_back(vpd);
315                         } catch(std::exception& e) {
316                                 LOG_ERROR << '[' << argv[i] << "] skipped " << e.what() << std::endl;
317                         }
318                 }
319                 if (v_data.empty()) return 0;
320                 // print some infos
321                 LOG_INFO << "Skip frames: " << ((settings::SKIP_FRAMES > 0) ? settings::SKIP_FRAMES : 0) << std::endl;
322                 LOG_INFO << "Max frames: " << ((settings::MAX_FRAMES > 0) ? settings::MAX_FRAMES : 0) << std::endl;
323                 // create the stats analyzer (like the psnr)
324                 LOG_INFO << "Analyzer set: " << settings::ANALYZER << std::endl;
325                 std::auto_ptr<stats::s_base>    s_analyzer(stats::get_analyzer(settings::ANALYZER.c_str(), v_data.size(), ref_sz.x, ref_sz.y, std::cout));
326                 // set the default values, in case will get overwritten
327                 s_analyzer->set_parameter("fpa", "25");
328                 s_analyzer->set_parameter("blocksize", "8");
329                 // load the passed parameters
330                 for(std::map<std::string, std::string>::const_iterator it = aopt.begin(); it != aopt.end(); ++it) {
331                         LOG_INFO << "Analyzer parameter: " << it->first << " = " << it->second << std::endl;
332                         s_analyzer->set_parameter(it->first.c_str(), it->second.c_str());
333                 }
334                 // create all the threads
335                 video_producer  ref_vpth(ref_frame, ref_prod, sem_cons, ref_buf, ref_video, glb_exit);
336                 V_VPTH          v_th;
337                 for(V_VPDATA::iterator it = v_data.begin(); it != v_data.end(); ++it)
338                         v_th.push_back(new video_producer((*it)->frame, (*it)->prod, sem_cons, (*it)->buf, *((*it)->video), glb_exit));
339                 // we'll need some tmp buffers
340                 VUCHAR                  t_ref_buf;
341                 std::vector<VUCHAR>     t_bufs(v_data.size());
342                 // and now the core algorithm
343                 // init all the semaphores
344                 producers_utils::lock(sem_cons, ref_prod, v_data);
345                 // start the threads
346                 producers_utils::start(ref_vpth, v_th);
347                 // print header
348                 std::cout << "Sample,";
349                 for(V_VPDATA::const_iterator it = v_data.begin(); it != v_data.end(); ++it)
350                         std::cout << (*it)->name << ',';
351                 std::cout << std::endl;
352                 while(!glb_exit) {
353                         // wait for the consumer to be signalled 1 + n times
354                         const static int CONS_SIG_NUM = 1 + v_th.size();
355                         producers_utils::sync(sem_cons, CONS_SIG_NUM);
356                         // now check everything is ok
357                         const int       cur_ref_frame = ref_frame;
358                         if (-1 == cur_ref_frame) {
359                                 glb_exit = true;
360                                 // allow the producers to run
361                                 producers_utils::unlock(ref_prod, v_data);
362                                 continue;
363                         }
364                         // in case we have to skip frames...
365                         if (settings::SKIP_FRAMES > 0 && settings::SKIP_FRAMES >= cur_ref_frame) {
366                                 // allow the producers to run
367                                 producers_utils::unlock(ref_prod, v_data);
368                                 continue;
369                         }
370                         // vector of bool telling if everything is ok
371                         std::vector<bool>       v_ok;
372                         for(V_VPDATA::const_iterator it = v_data.begin(); it != v_data.end(); ++it)
373                                 if ((*it)->frame == cur_ref_frame) v_ok.push_back(true);
374                                 else v_ok.push_back(false);
375                         // then swap the vectors
376                         t_ref_buf.swap(ref_buf);
377                         for(int i = 0; i < v_data.size(); ++i)
378                                 t_bufs[i].swap(v_data[i]->buf);
379                         // allow the producers to run
380                         producers_utils::unlock(ref_prod, v_data);
381                         // finally process data
382                         s_analyzer->process(cur_ref_frame, t_ref_buf, v_ok, t_bufs);
383                         // check if we have to exit
384                         if (settings::MAX_FRAMES > 0 && cur_ref_frame >= settings::MAX_FRAMES) {
385                                 glb_exit = true;
386                                 // allow the producers to run
387                                 producers_utils::unlock(ref_prod, v_data);
388                                 break;
389                         }
390                 }
391                 // wait for all threads
392                 producers_utils::stop(ref_vpth, v_th);
393         } catch(std::exception& e) {
394                 LOG_ERROR << e.what() << std::endl;
395         } catch(...) {
396                 LOG_ERROR << "Unknown exception" << std::endl;
397         }
398 }