[FFmpeg-devel] [PATCH] Add a tiny_ssim that is avified.

wm4 nfxjfg at googlemail.com
Sat Jun 20 11:55:41 CEST 2015


On Fri, 19 Jun 2015 08:44:51 -0400
"Ronald S. Bultje" <rsbultje at gmail.com> wrote:

> ---
>  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;

Using the same AVCodecContext for demuxing and decoding is not sane,
and as far as the API is concerned, is deprecated.

> +    i->pic = av_frame_alloc();

Missing check. (Well, if anyone even cares about this in a tool like
this.)

> +    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);

Why the dup call? It doesn't seem to ave any purpose. Also, its return
value is not checked.

> +        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)
> +    {

The check is unnecessary. Flushing always works; for codecs which don't
support it there's an internal check.

> +        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));
> +}



More information about the ffmpeg-devel mailing list