--- tests/Makefile | 5 +- tests/tiny_ssim.c | 153 +------------------------------------ tests/tiny_ssim_avf.c | 159 +++++++++++++++++++++++++++++++++++++++ tests/tiny_ssim_template.c | 183 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 350 insertions(+), 150 deletions(-) create mode 100644 tests/tiny_ssim_avf.c create mode 100644 tests/tiny_ssim_template.c
diff --git a/tests/Makefile b/tests/Makefile index cffa541..642e287 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -188,7 +188,10 @@ $(FATE_EXTERN): @echo "$@ requires external samples and SAMPLES not specified"; false endif -FATE_UTILS = base64 tiny_psnr tiny_ssim +FATE_UTILS = base64 tiny_psnr tiny_ssim tiny_ssim_avf + +tests/tiny_ssim_avf: tests/tiny_ssim_avf.o + $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(FFLIBS) $(FF_EXTRALIBS) $(FF_DEP_LIBS) TOOL = ffmpeg diff --git a/tests/tiny_ssim.c b/tests/tiny_ssim.c index 9f355a3..678c4dd 100644 --- a/tests/tiny_ssim.c +++ b/tests/tiny_ssim.c @@ -27,154 +27,7 @@ * overlapped 8x8 block sums, rather than the original gaussian weights. */ -#include "config.h" -#include <inttypes.h> -#include <limits.h> -#include <math.h> -#include <stdio.h> -#include <stdlib.h> - -#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0) -#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) - -#define BIT_DEPTH 8 -#define PIXEL_MAX ((1 << BIT_DEPTH)-1) -typedef uint8_t pixel; - -/**************************************************************************** - * structural similarity metric - ****************************************************************************/ -static void ssim_4x4x2_core( const pixel *pix1, intptr_t stride1, - const pixel *pix2, intptr_t stride2, - int sums[2][4] ) -{ - int x,y,z; - - for( z = 0; z < 2; z++ ) - { - uint32_t s1 = 0, s2 = 0, ss = 0, s12 = 0; - for( y = 0; y < 4; y++ ) - for( x = 0; x < 4; x++ ) - { - int a = pix1[x+y*stride1]; - int b = pix2[x+y*stride2]; - s1 += a; - s2 += b; - ss += a*a; - ss += b*b; - s12 += a*b; - } - sums[z][0] = s1; - sums[z][1] = s2; - sums[z][2] = ss; - sums[z][3] = s12; - pix1 += 4; - pix2 += 4; - } -} - -static float ssim_end1( int s1, int s2, int ss, int s12 ) -{ -/* Maximum value for 10-bit is: ss*64 = (2^10-1)^2*16*4*64 = 4286582784, which will overflow in some cases. - * s1*s1, s2*s2, and s1*s2 also obtain this value for edge cases: ((2^10-1)*16*4)^2 = 4286582784. - * Maximum value for 9-bit is: ss*64 = (2^9-1)^2*16*4*64 = 1069551616, which will not overflow. */ -#if BIT_DEPTH > 9 -#define type float - static const float ssim_c1 = .01*.01*PIXEL_MAX*PIXEL_MAX*64; - static const float ssim_c2 = .03*.03*PIXEL_MAX*PIXEL_MAX*64*63; -#else -#define type int - static const int ssim_c1 = (int)(.01*.01*PIXEL_MAX*PIXEL_MAX*64 + .5); - static const int ssim_c2 = (int)(.03*.03*PIXEL_MAX*PIXEL_MAX*64*63 + .5); -#endif - type fs1 = s1; - type fs2 = s2; - type fss = ss; - type fs12 = s12; - type vars = fss*64 - fs1*fs1 - fs2*fs2; - type covar = fs12*64 - fs1*fs2; - return (float)(2*fs1*fs2 + ssim_c1) * (float)(2*covar + ssim_c2) - / ((float)(fs1*fs1 + fs2*fs2 + ssim_c1) * (float)(vars + ssim_c2)); -#undef type -} - -static float ssim_end4( int sum0[5][4], int sum1[5][4], int width ) -{ - float ssim = 0.0; - int i; - - for( i = 0; i < width; i++ ) - ssim += ssim_end1( sum0[i][0] + sum0[i+1][0] + sum1[i][0] + sum1[i+1][0], - sum0[i][1] + sum0[i+1][1] + sum1[i][1] + sum1[i+1][1], - sum0[i][2] + sum0[i+1][2] + sum1[i][2] + sum1[i+1][2], - sum0[i][3] + sum0[i+1][3] + sum1[i][3] + sum1[i+1][3] ); - return ssim; -} - -float ssim_plane( - pixel *pix1, intptr_t stride1, - pixel *pix2, intptr_t stride2, - int width, int height, void *buf, int *cnt ) -{ - int z = 0; - int x, y; - float ssim = 0.0; - int (*sum0)[4] = buf; - int (*sum1)[4] = sum0 + (width >> 2) + 3; - width >>= 2; - height >>= 2; - for( y = 1; y < height; y++ ) - { - for( ; z <= y; z++ ) - { - FFSWAP( void*, sum0, sum1 ); - for( x = 0; x < width; x+=2 ) - ssim_4x4x2_core( &pix1[4*(x+z*stride1)], stride1, &pix2[4*(x+z*stride2)], stride2, &sum0[x] ); - } - for( x = 0; x < width-1; x += 4 ) - ssim += ssim_end4( sum0+x, sum1+x, FFMIN(4,width-x-1) ); - } -// *cnt = (height-1) * (width-1); - return ssim / ((height-1) * (width-1)); -} - - -uint64_t ssd_plane( const uint8_t *pix1, const uint8_t *pix2, int size ) -{ - uint64_t ssd = 0; - int i; - for( i=0; i<size; i++ ) - { - int d = pix1[i] - pix2[i]; - ssd += d*d; - } - return ssd; -} - -static double ssd_to_psnr( uint64_t ssd, uint64_t denom ) -{ - return -10*log((double)ssd/(denom*255*255))/log(10); -} - -static double ssim_db( double ssim, double weight ) -{ - return 10*(log(weight)/log(10)-log(weight-ssim)/log(10)); -} - -static void print_results(uint64_t ssd[3], double ssim[3], int frames, int w, int h) -{ - printf( "PSNR Y:%.3f U:%.3f V:%.3f All:%.3f | ", - ssd_to_psnr( ssd[0], (uint64_t)frames*w*h ), - ssd_to_psnr( ssd[1], (uint64_t)frames*w*h/4 ), - ssd_to_psnr( ssd[2], (uint64_t)frames*w*h/4 ), - ssd_to_psnr( ssd[0] + ssd[1] + ssd[2], (uint64_t)frames*w*h*3/2 ) ); - printf( "SSIM Y:%.5f U:%.5f V:%.5f All:%.5f (%.5f)", - ssim[0] / frames, - ssim[1] / frames, - ssim[2] / frames, - (ssim[0]*4 + ssim[1] + ssim[2]) / (frames*6), - ssim_db(ssim[0] * 4 + ssim[1] + ssim[2], frames*6)); -} +#include "tests/tiny_ssim_template.c" int main(int argc, char* argv[]) { @@ -222,7 +75,9 @@ int main(int argc, char* argv[]) if( fread(buf[1], frame_size, 1, f[1]) != 1) break; for( i=0; i<3; i++ ) { - ssd_one[i] = ssd_plane ( plane[0][i], plane[1][i], w*h>>2*!!i ); + ssd_one[i] = ssd_plane ( plane[0][i], w>>!!i, + plane[1][i], w>>!!i, + w>>!!i, h>>!!i ); ssim_one[i] = ssim_plane( plane[0][i], w>>!!i, plane[1][i], w>>!!i, w>>!!i, h>>!!i, temp, NULL ); diff --git a/tests/tiny_ssim_avf.c b/tests/tiny_ssim_avf.c new file mode 100644 index 0000000..f650178 --- /dev/null +++ b/tests/tiny_ssim_avf.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2003-2013 Loren Merritt + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 USA + */ +/* + * tiny_ssim.c + * Computes the Structural Similarity Metric between two rawYV12 video files. + * original algorithm: + * Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli, + * "Image quality assessment: From error visibility to structural similarity," + * IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004. + * + * To improve speed, this implementation uses the standard approximation of + * overlapped 8x8 block sums, rather than the original gaussian weights. + */ + +#include "tests/tiny_ssim_template.c" + +#include "libavformat/avformat.h" +#include "libavcodec/avcodec.h" + +struct inputStuff { + AVFormatContext *ctx; + AVCodecContext *avctx; + int videoStreamIndex; + AVFrame *pic; +}; + +static int open_file(struct inputStuff *i, const char *file) { + int res; + + i->ctx = NULL; + if ((res = avformat_open_input(&i->ctx, file, NULL, NULL))) + return res; + + if ((res = avformat_find_stream_info(i->ctx, NULL)) < 0) + return res; + + AVCodec *codec; + if ((res = av_find_best_stream(i->ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0)) < 0) + return res; + + i->videoStreamIndex = res; + i->avctx = i->ctx->streams[i->videoStreamIndex]->codec; + + if ((res = avcodec_open2(i->avctx, codec, NULL)) < 0) + return res; + + i->pic = av_frame_alloc(); + + return 0; +} + +static int read_frame(struct inputStuff *i) { + AVPacket pkt; + while (!av_read_frame(i->ctx, &pkt)) { + int got; + if (pkt.stream_index != i->videoStreamIndex) + continue; + av_dup_packet(&pkt); + int res = avcodec_decode_video2(i->avctx, i->pic, &got, &pkt); + av_free_packet(&pkt); + if (res < 0) { + return res; + } else if (got > 0) { + return 0; + } + } + + if (i->avctx->codec->capabilities & CODEC_CAP_DELAY || + i->avctx->active_thread_type & FF_THREAD_FRAME) + { + do { + pkt.data = NULL; + pkt.size = 0; + int got, res = avcodec_decode_video2(i->avctx, i->pic, &got, &pkt); + if (res < 0) { + return res; + } else if (got > 0) { + return 0; + } else + break; + } while (1); + } + + return -1; +} + +int main(int argc, char* argv[]) +{ + struct inputStuff f[2]; + int *temp; + uint64_t ssd[3] = {0,0,0}; + double ssim[3] = {0,0,0}; + int frames; + int i; + + if( argc<3) + { + printf("tiny_ssim <file1> <file2>\n"); + return -1; + } + + av_register_all(); + if (open_file(&f[0], argv[1]) < 0 || + open_file(&f[1], argv[2]) < 0) { + fprintf(stderr, "Failed to open one of the files\n"); + return -2; + } + + temp = malloc((2*f[0].avctx->width+12)*sizeof(*temp)); + + for( frames=0;; frames++ ) + { + uint64_t ssd_one[3]; + double ssim_one[3]; + if (read_frame(&f[0]) < 0 || + read_frame(&f[1]) < 0) + break; + for( i=0; i<3; i++ ) + { + ssd_one[i] = ssd_plane ( f[0].pic->data[i], f[0].pic->linesize[i], + f[1].pic->data[i], f[1].pic->linesize[i], + f[0].pic->width>>!!i, f[0].pic->height>>!!i); + ssim_one[i] = ssim_plane( f[0].pic->data[i], f[0].pic->linesize[i], + f[1].pic->data[i], f[1].pic->linesize[i], + f[0].pic->width>>!!i, f[0].pic->height>>!!i, + temp, NULL); + ssd[i] += ssd_one[i]; + ssim[i] += ssim_one[i]; + } + + printf("Frame %d | ", frames); + print_results(ssd_one, ssim_one, 1, f[0].pic->width, f[0].pic->height); + printf(" \r"); + fflush(stdout); + } + + if( !frames ) return 0; + + printf("Total %d frames | ", frames); + print_results(ssd, ssim, frames, f[0].avctx->width, f[0].avctx->height); + printf("\n"); + + return 0; +} diff --git a/tests/tiny_ssim_template.c b/tests/tiny_ssim_template.c new file mode 100644 index 0000000..2588a76 --- /dev/null +++ b/tests/tiny_ssim_template.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2003-2013 Loren Merritt + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 USA + */ +/* + * tiny_ssim.c + * Computes the Structural Similarity Metric between two rawYV12 video files. + * original algorithm: + * Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli, + * "Image quality assessment: From error visibility to structural similarity," + * IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004. + * + * To improve speed, this implementation uses the standard approximation of + * overlapped 8x8 block sums, rather than the original gaussian weights. + */ + +#include "config.h" +#include <inttypes.h> +#include <limits.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> + +#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0) +#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) + +#define BIT_DEPTH 8 +#define PIXEL_MAX ((1 << BIT_DEPTH)-1) +typedef uint8_t pixel; + +/**************************************************************************** + * structural similarity metric + ****************************************************************************/ +static void ssim_4x4x2_core( const pixel *pix1, intptr_t stride1, + const pixel *pix2, intptr_t stride2, + int sums[2][4] ) +{ + int x,y,z; + + for( z = 0; z < 2; z++ ) + { + uint32_t s1 = 0, s2 = 0, ss = 0, s12 = 0; + for( y = 0; y < 4; y++ ) + for( x = 0; x < 4; x++ ) + { + int a = pix1[x+y*stride1]; + int b = pix2[x+y*stride2]; + s1 += a; + s2 += b; + ss += a*a; + ss += b*b; + s12 += a*b; + } + sums[z][0] = s1; + sums[z][1] = s2; + sums[z][2] = ss; + sums[z][3] = s12; + pix1 += 4; + pix2 += 4; + } +} + +static float ssim_end1( int s1, int s2, int ss, int s12 ) +{ +/* Maximum value for 10-bit is: ss*64 = (2^10-1)^2*16*4*64 = 4286582784, which will overflow in some cases. + * s1*s1, s2*s2, and s1*s2 also obtain this value for edge cases: ((2^10-1)*16*4)^2 = 4286582784. + * Maximum value for 9-bit is: ss*64 = (2^9-1)^2*16*4*64 = 1069551616, which will not overflow. */ +#if BIT_DEPTH > 9 +#define type float + static const float ssim_c1 = .01*.01*PIXEL_MAX*PIXEL_MAX*64; + static const float ssim_c2 = .03*.03*PIXEL_MAX*PIXEL_MAX*64*63; +#else +#define type int + static const int ssim_c1 = (int)(.01*.01*PIXEL_MAX*PIXEL_MAX*64 + .5); + static const int ssim_c2 = (int)(.03*.03*PIXEL_MAX*PIXEL_MAX*64*63 + .5); +#endif + type fs1 = s1; + type fs2 = s2; + type fss = ss; + type fs12 = s12; + type vars = fss*64 - fs1*fs1 - fs2*fs2; + type covar = fs12*64 - fs1*fs2; + return (float)(2*fs1*fs2 + ssim_c1) * (float)(2*covar + ssim_c2) + / ((float)(fs1*fs1 + fs2*fs2 + ssim_c1) * (float)(vars + ssim_c2)); +#undef type +} + +static float ssim_end4( int sum0[5][4], int sum1[5][4], int width ) +{ + float ssim = 0.0; + int i; + + for( i = 0; i < width; i++ ) + ssim += ssim_end1( sum0[i][0] + sum0[i+1][0] + sum1[i][0] + sum1[i+1][0], + sum0[i][1] + sum0[i+1][1] + sum1[i][1] + sum1[i+1][1], + sum0[i][2] + sum0[i+1][2] + sum1[i][2] + sum1[i+1][2], + sum0[i][3] + sum0[i+1][3] + sum1[i][3] + sum1[i+1][3] ); + return ssim; +} + +static float ssim_plane( + pixel *pix1, intptr_t stride1, + pixel *pix2, intptr_t stride2, + int width, int height, void *buf, int *cnt ) +{ + int z = 0; + int x, y; + float ssim = 0.0; + int (*sum0)[4] = buf; + int (*sum1)[4] = sum0 + (width >> 2) + 3; + width >>= 2; + height >>= 2; + for( y = 1; y < height; y++ ) + { + for( ; z <= y; z++ ) + { + FFSWAP( void*, sum0, sum1 ); + for( x = 0; x < width; x+=2 ) + ssim_4x4x2_core( &pix1[4*(x+z*stride1)], stride1, &pix2[4*(x+z*stride2)], stride2, &sum0[x] ); + } + for( x = 0; x < width-1; x += 4 ) + ssim += ssim_end4( sum0+x, sum1+x, FFMIN(4,width-x-1) ); + } +// *cnt = (height-1) * (width-1); + return ssim / ((height-1) * (width-1)); +} + + +static uint64_t ssd_plane( const uint8_t *pix1, int stridea, const uint8_t *pix2, int strideb, int w, int h ) +{ + uint64_t ssd = 0; + int x, y; + + for (y=0;y<h;y++) + { + for (x=0;x<w;x++) + { + int d = pix1[x] - pix2[x]; + ssd += d * d; + } + pix1 += stridea; + pix2 += strideb; + } + return ssd; +} + +static double ssd_to_psnr( uint64_t ssd, uint64_t denom ) +{ + return -10*log((double)ssd/(denom*255*255))/log(10); +} + +static double ssim_db( double ssim, double weight ) +{ + return 10*(log(weight)/log(10)-log(weight-ssim)/log(10)); +} + +static void print_results(uint64_t ssd[3], double ssim[3], int frames, int w, int h) +{ + printf( "PSNR Y:%.3f U:%.3f V:%.3f All:%.3f | ", + ssd_to_psnr( ssd[0], (uint64_t)frames*w*h ), + ssd_to_psnr( ssd[1], (uint64_t)frames*w*h/4 ), + ssd_to_psnr( ssd[2], (uint64_t)frames*w*h/4 ), + ssd_to_psnr( ssd[0] + ssd[1] + ssd[2], (uint64_t)frames*w*h*3/2 ) ); + printf( "SSIM Y:%.5f U:%.5f V:%.5f All:%.5f (%.5f)", + ssim[0] / frames, + ssim[1] / frames, + ssim[2] / frames, + (ssim[0]*4 + ssim[1] + ssim[2]) / (frames*6), + ssim_db(ssim[0] * 4 + ssim[1] + ssim[2], frames*6)); +} -- 2.1.2 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel