dump_ssim.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. #include "vidinput.h"
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <math.h>
  5. #if !defined(M_PI)
  6. # define M_PI (3.141592653589793238462643)
  7. #endif
  8. #include <string.h>
  9. /*Yes, yes, we're going to hell.*/
  10. #if defined(_WIN32)
  11. #include <io.h>
  12. #include <fcntl.h>
  13. #endif
  14. #include <ogg/os_types.h>
  15. #include "getopt.h"
  16. const char *optstring = "frsy";
  17. const struct option options[]={
  18. {"frame-type",no_argument,NULL,'f'},
  19. {"raw",no_argument,NULL,'r'},
  20. {"summary",no_argument,NULL,'s'},
  21. {"luma-only",no_argument,NULL,'y'},
  22. {NULL,0,NULL,0}
  23. };
  24. static int show_frame_type;
  25. static int summary_only;
  26. static int luma_only;
  27. #define KERNEL_SHIFT (8)
  28. #define KERNEL_WEIGHT (1<<KERNEL_SHIFT)
  29. #define KERNEL_ROUND ((1<<KERNEL_SHIFT)>>1)
  30. static int gaussian_filter_init(unsigned **_kernel,double _sigma,int _max_len){
  31. unsigned *kernel;
  32. double scale;
  33. double nhisigma2;
  34. double s;
  35. double len;
  36. unsigned sum;
  37. int kernel_len;
  38. int kernel_sz;
  39. int ci;
  40. scale=1/(sqrt(2*M_PI)*_sigma);
  41. nhisigma2=-0.5/(_sigma*_sigma);
  42. /*Compute the kernel size so that the error in the first truncated
  43. coefficient is no larger than 0.5*KERNEL_WEIGHT.
  44. There is no point in going beyond this given our working precision.*/
  45. s=sqrt(0.5*M_PI)*_sigma*(1.0/KERNEL_WEIGHT);
  46. if(s>=1)len=0;
  47. else len=floor(_sigma*sqrt(-2*log(s)));
  48. kernel_len=len>=_max_len?_max_len-1:(int)len;
  49. kernel_sz=kernel_len<<1|1;
  50. kernel=(unsigned *)_ogg_malloc(kernel_sz*sizeof(*kernel));
  51. sum=0;
  52. for(ci=kernel_len;ci>0;ci--){
  53. kernel[kernel_len-ci]=kernel[kernel_len+ci]=
  54. (unsigned)(KERNEL_WEIGHT*scale*exp(nhisigma2*ci*ci)+0.5);
  55. sum+=kernel[kernel_len-ci];
  56. }
  57. kernel[kernel_len]=KERNEL_WEIGHT-(sum<<1);
  58. *_kernel=kernel;
  59. return kernel_sz;
  60. }
  61. typedef struct ssim_moments ssim_moments;
  62. struct ssim_moments{
  63. unsigned mux;
  64. unsigned muy;
  65. unsigned x2;
  66. unsigned xy;
  67. unsigned y2;
  68. unsigned w;
  69. };
  70. #define SSIM_C1 (255*255*0.01*0.01)
  71. #define SSIM_C2 (255*255*0.03*0.03)
  72. static double calc_ssim(const unsigned char *_src,int _systride,
  73. const unsigned char *_dst,int _dystride,double _par,int _w,int _h){
  74. ssim_moments *line_buf;
  75. ssim_moments **lines;
  76. double ssim;
  77. double ssimw;
  78. unsigned *hkernel;
  79. int hkernel_sz;
  80. int hkernel_offs;
  81. unsigned *vkernel;
  82. int vkernel_sz;
  83. int vkernel_offs;
  84. int log_line_sz;
  85. int line_sz;
  86. int line_mask;
  87. int x;
  88. int y;
  89. vkernel_sz=gaussian_filter_init(&vkernel,_h*(1.5/256),_w<_h?_w:_h);
  90. vkernel_offs=vkernel_sz>>1;
  91. for(line_sz=1,log_line_sz=0;line_sz<vkernel_sz;line_sz<<=1,log_line_sz++);
  92. line_mask=line_sz-1;
  93. lines=(ssim_moments **)_ogg_malloc(line_sz*sizeof(*lines));
  94. lines[0]=line_buf=(ssim_moments *)_ogg_malloc(line_sz*_w*sizeof(*line_buf));
  95. for(y=1;y<line_sz;y++)lines[y]=lines[y-1]+_w;
  96. hkernel_sz=gaussian_filter_init(&hkernel,_h*(1.5/256)/_par,_w<_h?_w:_h);
  97. hkernel_offs=hkernel_sz>>1;
  98. ssim=0;
  99. ssimw=0;
  100. for(y=0;y<_h+vkernel_offs;y++){
  101. ssim_moments *buf;
  102. int k;
  103. int k_min;
  104. int k_max;
  105. if(y<_h){
  106. buf=lines[y&line_mask];
  107. for(x=0;x<_w;x++){
  108. ssim_moments m;
  109. memset(&m,0,sizeof(m));
  110. k_min=hkernel_offs-x<=0?0:hkernel_offs-x;
  111. k_max=x+hkernel_offs-_w+1<=0?
  112. hkernel_sz:hkernel_sz-(x+hkernel_offs-_w+1);
  113. for(k=k_min;k<k_max;k++){
  114. unsigned s;
  115. unsigned d;
  116. unsigned window;
  117. s=_src[x-hkernel_offs+k];
  118. d=_dst[x-hkernel_offs+k];
  119. window=hkernel[k];
  120. m.mux+=window*s;
  121. m.muy+=window*d;
  122. m.x2+=window*s*s;
  123. m.xy+=window*s*d;
  124. m.y2+=window*d*d;
  125. m.w+=window;
  126. }
  127. *(buf+x)=*&m;
  128. }
  129. _src+=_systride;
  130. _dst+=_dystride;
  131. }
  132. if(y>=vkernel_offs){
  133. k_min=vkernel_sz-y-1<=0?0:vkernel_sz-y-1;
  134. k_max=y+1-_h<=0?vkernel_sz:vkernel_sz-(y+1-_h);
  135. for(x=0;x<_w;x++){
  136. ssim_moments m;
  137. double c1;
  138. double c2;
  139. double mx2;
  140. double mxy;
  141. double my2;
  142. double w;
  143. memset(&m,0,sizeof(m));
  144. for(k=k_min;k<k_max;k++){
  145. unsigned window;
  146. buf=lines[y+1-vkernel_sz+k&line_mask]+x;
  147. window=vkernel[k];
  148. m.mux+=window*buf->mux;
  149. m.muy+=window*buf->muy;
  150. m.x2+=window*buf->x2;
  151. m.xy+=window*buf->xy;
  152. m.y2+=window*buf->y2;
  153. m.w+=window*buf->w;
  154. }
  155. w=m.w;
  156. c1=SSIM_C1*w*w;
  157. c2=SSIM_C2*w*w;
  158. mx2=m.mux*(double)m.mux;
  159. mxy=m.mux*(double)m.muy;
  160. my2=m.muy*(double)m.muy;
  161. ssim+=m.w*(2*mxy+c1)*(c2+2*(m.xy*w-mxy))/
  162. ((mx2+my2+c1)*(m.x2*w-mx2+m.y2*w-my2+c2));
  163. ssimw+=m.w;
  164. }
  165. }
  166. }
  167. _ogg_free(line_buf);
  168. _ogg_free(lines);
  169. return ssim/ssimw;
  170. }
  171. static void usage(char *_argv[]){
  172. fprintf(stderr,"Usage: %s [options] <video1> <video2>\n"
  173. " <video1> and <video2> may be either YUV4MPEG or Ogg Theora files.\n\n"
  174. " Options:\n\n"
  175. " -f --frame-type Show frame type and QI value for each Theora frame.\n"
  176. " -r --raw Show raw SSIM scores, instead of"
  177. " 10*log10(1/(1-ssim)).\n"
  178. " -s --summary Only output the summary line.\n"
  179. " -y --luma-only Only output values for the luma channel.\n",_argv[0]);
  180. }
  181. typedef double (*convert_ssim_func)(double _ssim,double _weight);
  182. static double convert_ssim_raw(double _ssim,double _weight){
  183. return _ssim/_weight;
  184. }
  185. static double convert_ssim_db(double _ssim,double _weight){
  186. return 10*(log10(_weight)-log10(_weight-_ssim));
  187. }
  188. int main(int _argc,char *_argv[]){
  189. video_input vid1;
  190. video_input_info info1;
  191. video_input vid2;
  192. video_input_info info2;
  193. convert_ssim_func convert;
  194. double gssim[3];
  195. double cweight;
  196. double par;
  197. int frameno;
  198. FILE *fin;
  199. int long_option_index;
  200. int c;
  201. #ifdef _WIN32
  202. /*We need to set stdin/stdout to binary mode on windows.
  203. Beware the evil ifdef.
  204. We avoid these where we can, but this one we cannot.
  205. Don't add any more, you'll probably go to hell if you do.*/
  206. _setmode(_fileno(stdin),_O_BINARY);
  207. #endif
  208. /*Process option arguments.*/
  209. convert=convert_ssim_db;
  210. while((c=getopt_long(_argc,_argv,optstring,options,&long_option_index))!=EOF){
  211. switch(c){
  212. case 'f':show_frame_type=1;break;
  213. case 'r':convert=convert_ssim_raw;break;
  214. case 's':summary_only=1;break;
  215. case 'y':luma_only=1;break;
  216. default:{
  217. usage(_argv);
  218. exit(EXIT_FAILURE);
  219. }break;
  220. }
  221. }
  222. if(optind+2!=_argc){
  223. usage(_argv);
  224. exit(EXIT_FAILURE);
  225. }
  226. fin=strcmp(_argv[optind],"-")==0?stdin:fopen(_argv[optind],"rb");
  227. if(fin==NULL){
  228. fprintf(stderr,"Unable to open '%s' for extraction.\n",_argv[optind]);
  229. exit(EXIT_FAILURE);
  230. }
  231. fprintf(stderr,"Opening %s...\n",_argv[optind]);
  232. if(video_input_open(&vid1,fin)<0)exit(EXIT_FAILURE);
  233. video_input_get_info(&vid1,&info1);
  234. fin=strcmp(_argv[optind+1],"-")==0?stdin:fopen(_argv[optind+1],"rb");
  235. if(fin==NULL){
  236. fprintf(stderr,"Unable to open '%s' for extraction.\n",_argv[optind+1]);
  237. exit(EXIT_FAILURE);
  238. }
  239. fprintf(stderr,"Opening %s...\n",_argv[optind+1]);
  240. if(video_input_open(&vid2,fin)<0)exit(EXIT_FAILURE);
  241. video_input_get_info(&vid2,&info2);
  242. /*Check to make sure these videos are compatible.*/
  243. if(info1.pic_w!=info2.pic_w||info1.pic_h!=info2.pic_h){
  244. fprintf(stderr,"Video resolution does not match.\n");
  245. exit(EXIT_FAILURE);
  246. }
  247. if(info1.pixel_fmt!=info2.pixel_fmt){
  248. fprintf(stderr,"Pixel formats do not match.\n");
  249. exit(EXIT_FAILURE);
  250. }
  251. if((info1.pic_x&!(info1.pixel_fmt&1))!=(info2.pic_x&!(info2.pixel_fmt&1))||
  252. (info1.pic_y&!(info1.pixel_fmt&2))!=(info2.pic_y&!(info2.pixel_fmt&2))){
  253. fprintf(stderr,"Chroma subsampling offsets do not match.\n");
  254. exit(EXIT_FAILURE);
  255. }
  256. if(info1.fps_n*(ogg_int64_t)info2.fps_d!=
  257. info2.fps_n*(ogg_int64_t)info1.fps_d){
  258. fprintf(stderr,"Warning: framerates do not match.\n");
  259. fprintf(stderr,"info1.fps_n=%i info1.fps_d=%i info2.fps_n=%i info2.fps_d=%i\n",info1.fps_n,info1.fps_d,info2.fps_n,info2.fps_d);
  260. }
  261. if(info1.par_n*(ogg_int64_t)info2.par_d!=
  262. info2.par_n*(ogg_int64_t)info1.par_d){
  263. fprintf(stderr,"Warning: aspect ratios do not match.\n");
  264. }
  265. par=info1.par_n>0&&info2.par_d>0?
  266. info1.par_n/(double)info2.par_d:1;
  267. gssim[0]=gssim[1]=gssim[2]=0;
  268. /*We just use a simple weighting to get a single full-color score.
  269. In reality the CSF for chroma is not the same as luma.*/
  270. cweight=0.25*(4>>(!(info1.pixel_fmt&1)+!(info1.pixel_fmt&2)));
  271. for(frameno=0;;frameno++){
  272. video_input_ycbcr f1;
  273. video_input_ycbcr f2;
  274. double ssim[3];
  275. char tag1[5];
  276. char tag2[5];
  277. int ret1;
  278. int ret2;
  279. int pli;
  280. ret1=video_input_fetch_frame(&vid1,f1,tag1);
  281. ret2=video_input_fetch_frame(&vid2,f2,tag2);
  282. if(ret1==0&&ret2==0)break;
  283. else if(ret1<0||ret2<0)break;
  284. else if(ret1==0){
  285. fprintf(stderr,"%s ended before %s.\n",
  286. _argv[optind],_argv[optind+1]);
  287. break;
  288. }
  289. else if(ret2==0){
  290. fprintf(stderr,"%s ended before %s.\n",
  291. _argv[optind+1],_argv[optind]);
  292. break;
  293. }
  294. /*Okay, we got one frame from each.*/
  295. for(pli=0;pli<3;pli++){
  296. int xdec;
  297. int ydec;
  298. xdec=pli&&!(info1.pixel_fmt&1);
  299. ydec=pli&&!(info1.pixel_fmt&2);
  300. ssim[pli]=calc_ssim(
  301. f1[pli].data+(info1.pic_y>>ydec)*f1[pli].stride+(info1.pic_x>>xdec),
  302. f1[pli].stride,
  303. f2[pli].data+(info2.pic_y>>ydec)*f2[pli].stride+(info2.pic_x>>xdec),
  304. f2[pli].stride,
  305. par,((info1.pic_x+info1.pic_w+xdec)>>xdec)-(info1.pic_x>>xdec),
  306. ((info1.pic_y+info1.pic_h+ydec)>>ydec)-(info1.pic_y>>ydec));
  307. gssim[pli]+=ssim[pli];
  308. }
  309. if(!summary_only){
  310. if(show_frame_type)printf("%s%s",tag1,tag2);
  311. if(!luma_only){
  312. printf("%08i: %-8G (Y': %-8G Cb: %-8G Cr: %-8G)\n",frameno,
  313. convert(ssim[0]+cweight*(ssim[1]+ssim[2]),1+2*cweight),
  314. convert(ssim[0],1),convert(ssim[1],1),convert(ssim[2],1));
  315. }
  316. else printf("%08i: %-8G\n",frameno,convert(ssim[0],1));
  317. }
  318. }
  319. if(!luma_only){
  320. printf("Total: %-8G (Y': %-8G Cb: %-8G Cr: %-8G)\n",
  321. convert(gssim[0]+cweight*(gssim[1]+gssim[2]),(1+2*cweight)*frameno),
  322. convert(gssim[0],frameno),convert(gssim[1],frameno),
  323. convert(gssim[2],frameno));
  324. }
  325. else printf("Total: %-8G\n",convert(gssim[0],frameno));
  326. video_input_close(&vid1);
  327. video_input_close(&vid2);
  328. return EXIT_SUCCESS;
  329. }