/* Copyright (C) 2003 Daniel Moreno <comac@comac.darktech.org> 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "hb.h" #include "hbffmpeg.h" #include "mpeg2dec/mpeg2.h" #define HQDN3D_SPATIAL_LUMA_DEFAULT 4.0f #define HQDN3D_SPATIAL_CHROMA_DEFAULT 3.0f #define HQDN3D_TEMPORAL_LUMA_DEFAULT 6.0f #define ABS(A) ( (A) > 0 ? (A) : -(A) ) struct hb_filter_private_s { int pix_fmt; int width[3]; int height[3]; int hqdn3d_coef[4][512*16]; unsigned int * hqdn3d_line; unsigned short * hqdn3d_frame[3]; AVPicture pic_in; AVPicture pic_out; hb_buffer_t * buf_out; }; hb_filter_private_t * hb_denoise_init( int pix_fmt, int width, int height, char * settings ); int hb_denoise_work( const hb_buffer_t * buf_in, hb_buffer_t ** buf_out, int pix_fmt, int width, int height, hb_filter_private_t * pv ); void hb_denoise_close( hb_filter_private_t * pv ); hb_filter_object_t hb_filter_denoise = { FILTER_DENOISE, "Denoise (hqdn3d)", NULL, hb_denoise_init, hb_denoise_work, hb_denoise_close, }; static void hqdn3d_precalc_coef( int * ct, double dist25 ) { int i; double gamma, simil, c; gamma = log( 0.25 ) / log( 1.0 - dist25/255.0 - 0.00001 ); for( i = -255*16; i <= 255*16; i++ ) { simil = 1.0 - ABS(i) / (16*255.0); c = pow( simil, gamma ) * 65536.0 * (double)i / 16.0; ct[16*256+i] = (c<0) ? (c-0.5) : (c+0.5); } ct[0] = (dist25 != 0); } static inline unsigned int hqdn3d_lowpass_mul( unsigned int prev_mul, unsigned int curr_mul, int * coef ) { int diff_mul = prev_mul - curr_mul; int d = ((diff_mul+0x10007FF)>>12); return curr_mul + coef[d]; } static void hqdn3d_denoise_temporal( unsigned char * frame_src, unsigned char * frame_dst, unsigned short * frame_ant, int w, int h, int * temporal) { int x, y; unsigned int pixel_dst; for( y = 0; y < h; y++ ) { for( x = 0; x < w; x++ ) { pixel_dst = hqdn3d_lowpass_mul( frame_ant[x]<<8, frame_src[x]<<16, temporal ); frame_ant[x] = ((pixel_dst+0x1000007F)>>8); frame_dst[x] = ((pixel_dst+0x10007FFF)>>16); } frame_src += w; frame_dst += w; frame_ant += w; } } static void hqdn3d_denoise_spatial( unsigned char * frame_src, unsigned char * frame_dst, unsigned int * line_ant, int w, int h, int * horizontal, int * vertical ) { int x, y; int line_offset_src = 0, line_offset_dst = 0; unsigned int pixel_ant; unsigned int pixel_dst; /* First pixel has no left nor top neighbor. */ pixel_dst = line_ant[0] = pixel_ant = frame_src[0]<<16; frame_dst[0] = ((pixel_dst+0x10007FFF)>>16); /* First line has no top neighbor, only left. */ for( x = 1; x < w; x++ ) { pixel_dst = line_ant[x] = hqdn3d_lowpass_mul(pixel_ant, frame_src[x]<<16, horizontal); frame_dst[x] = ((pixel_dst+0x10007FFF)>>16); } for( y = 1; y < h; y++ ) { unsigned int pixel_ant; line_offset_src += w, line_offset_dst += w; /* First pixel on each line doesn't have previous pixel */ pixel_ant = frame_src[line_offset_src]<<16; pixel_dst = line_ant[0] = hqdn3d_lowpass_mul( line_ant[0], pixel_ant, vertical); frame_dst[line_offset_dst] = ((pixel_dst+0x10007FFF)>>16); /* The rest of the pixels in the line are normal */ for( x = 1; x < w; x++ ) { unsigned int pixel_dst; pixel_ant = hqdn3d_lowpass_mul( pixel_ant, frame_src[line_offset_src+x]<<16, horizontal ); pixel_dst = line_ant[x] = hqdn3d_lowpass_mul( line_ant[x], pixel_ant, vertical ); frame_dst[line_offset_dst+x]= ((pixel_dst+0x10007FFF)>>16); } } } static void hqdn3d_denoise( unsigned char * frame_src, unsigned char * frame_dst, unsigned int * line_ant, unsigned short ** frame_ant_ptr, int w, int h, int * horizontal, int * vertical, int * temporal) { int x, y; int line_offset_src = 0, line_offset_dst = 0; unsigned int pixel_ant; unsigned int pixel_dst; unsigned short* frame_ant = (*frame_ant_ptr); if( !frame_ant) { (*frame_ant_ptr) = frame_ant = malloc( w*h*sizeof(unsigned short) ); for( y = 0; y < h; y++ ) { unsigned short* dst = &frame_ant[y*w]; unsigned char* src = frame_src + y*w; for( x = 0; x < w; x++ ) { dst[x] = src[x] << 8; } } } /* If no spatial coefficients, do temporal denoise only */ if( !horizontal[0] && !vertical[0] ) { hqdn3d_denoise_temporal( frame_src, frame_dst, frame_ant, w, h, temporal); return; } /* If no temporal coefficients, do spatial denoise only */ if( !temporal[0] ) { hqdn3d_denoise_spatial( frame_src, frame_dst, line_ant, w, h, horizontal, vertical); return; } /* First pixel has no left nor top neighbor. Only previous frame */ line_ant[0] = pixel_ant = frame_src[0] << 16; pixel_dst = hqdn3d_lowpass_mul( frame_ant[0]<<8, pixel_ant, temporal ); frame_ant[0] = ((pixel_dst+0x1000007F)>>8); frame_dst[0] = ((pixel_dst+0x10007FFF)>>16); /* First line has no top neighbor. Only left one for each pixel and last frame */ for( x = 1; x < w; x++ ) { line_ant[x] = pixel_ant = hqdn3d_lowpass_mul( pixel_ant, frame_src[x]<<16, horizontal); pixel_dst = hqdn3d_lowpass_mul( frame_ant[x]<<8, pixel_ant, temporal); frame_ant[x] = ((pixel_dst+0x1000007F)>>8); frame_dst[x] = ((pixel_dst+0x10007FFF)>>16); } /* The rest of the lines in the frame are normal */ for( y = 1; y < h; y++ ) { unsigned int pixel_ant; unsigned short * line_prev = &frame_ant[y*w]; line_offset_src += w, line_offset_dst += w; /* First pixel on each line doesn't have previous pixel */ pixel_ant = frame_src[line_offset_src]<<16; line_ant[0] = hqdn3d_lowpass_mul( line_ant[0], pixel_ant, vertical); pixel_dst = hqdn3d_lowpass_mul( line_prev[0]<<8, line_ant[0], temporal); line_prev[0] = ((pixel_dst+0x1000007F)>>8); frame_dst[line_offset_dst] = ((pixel_dst+0x10007FFF)>>16); /* The rest of the pixels in the line are normal */ for( x = 1; x < w; x++ ) { unsigned int pixel_dst; pixel_ant = hqdn3d_lowpass_mul( pixel_ant, frame_src[line_offset_src+x]<<16, horizontal ); line_ant[x] = hqdn3d_lowpass_mul( line_ant[x], pixel_ant, vertical); pixel_dst = hqdn3d_lowpass_mul( line_prev[x]<<8, line_ant[x], temporal ); line_prev[x] = ((pixel_dst+0x1000007F)>>8); frame_dst[line_offset_dst+x] = ((pixel_dst+0x10007FFF)>>16); } } } hb_filter_private_t * hb_denoise_init( int pix_fmt, int width, int height, char * settings ) { if( pix_fmt != PIX_FMT_YUV420P ) { return 0; } hb_filter_private_t * pv = malloc( sizeof(struct hb_filter_private_s) ); /* * Clear the memory to avoid freeing uninitialised memory later. */ memset( pv, 0, sizeof( struct hb_filter_private_s ) ); pv->pix_fmt = pix_fmt; pv->width[0] = width; pv->height[0] = height; pv->width[1] = pv->width[2] = width >> 1; pv->height[1] = pv->height[2] = height >> 1; double spatial_luma, temporal_luma, spatial_chroma, temporal_chroma; if( settings ) { switch( sscanf( settings, "%lf:%lf:%lf:%lf", &spatial_luma, &spatial_chroma, &temporal_luma, &temporal_chroma ) ) { case 0: spatial_luma = HQDN3D_SPATIAL_LUMA_DEFAULT; spatial_chroma = HQDN3D_SPATIAL_CHROMA_DEFAULT; temporal_luma = HQDN3D_TEMPORAL_LUMA_DEFAULT; temporal_chroma = temporal_luma * spatial_chroma / spatial_luma; break; case 1: spatial_chroma = HQDN3D_SPATIAL_CHROMA_DEFAULT * spatial_luma / HQDN3D_SPATIAL_LUMA_DEFAULT; temporal_luma = HQDN3D_TEMPORAL_LUMA_DEFAULT * spatial_luma / HQDN3D_SPATIAL_LUMA_DEFAULT; temporal_chroma = temporal_luma * spatial_chroma / spatial_luma; break; case 2: temporal_luma = HQDN3D_TEMPORAL_LUMA_DEFAULT * spatial_luma / HQDN3D_SPATIAL_LUMA_DEFAULT; temporal_chroma = temporal_luma * spatial_chroma / spatial_luma; break; case 3: temporal_chroma = temporal_luma * spatial_chroma / spatial_luma; break; } } pv->hqdn3d_line = malloc( width * sizeof(int) ); hqdn3d_precalc_coef( pv->hqdn3d_coef[0], spatial_luma ); hqdn3d_precalc_coef( pv->hqdn3d_coef[1], temporal_luma ); hqdn3d_precalc_coef( pv->hqdn3d_coef[2], spatial_chroma ); hqdn3d_precalc_coef( pv->hqdn3d_coef[3], temporal_chroma ); pv->buf_out = hb_video_buffer_init( width, height ); return pv; } void hb_denoise_close( hb_filter_private_t * pv ) { if( !pv ) { return; } if( pv->hqdn3d_line ) { free( pv->hqdn3d_line ); pv->hqdn3d_line = NULL; } if( pv->hqdn3d_frame[0] ) { free( pv->hqdn3d_frame[0] ); pv->hqdn3d_frame[0] = NULL; } if( pv->hqdn3d_frame[1] ) { free( pv->hqdn3d_frame[1] ); pv->hqdn3d_frame[1] = NULL; } if( pv->hqdn3d_frame[2] ) { free( pv->hqdn3d_frame[2] ); pv->hqdn3d_frame[2] = NULL; } if( pv->buf_out ) { hb_buffer_close( &pv->buf_out ); } free( pv ); } int hb_denoise_work( const hb_buffer_t * buf_in, hb_buffer_t ** buf_out, int pix_fmt, int width, int height, hb_filter_private_t * pv ) { if( !pv || pix_fmt != pv->pix_fmt || width != pv->width[0] || height != pv->height[0] ) { return FILTER_FAILED; } avpicture_fill( &pv->pic_in, buf_in->data, pix_fmt, width, height ); avpicture_fill( &pv->pic_out, pv->buf_out->data, pix_fmt, width, height ); hqdn3d_denoise( pv->pic_in.data[0], pv->pic_out.data[0], pv->hqdn3d_line, &pv->hqdn3d_frame[0], pv->width[0], pv->height[0], pv->hqdn3d_coef[0], pv->hqdn3d_coef[0], pv->hqdn3d_coef[1] ); hqdn3d_denoise( pv->pic_in.data[1], pv->pic_out.data[1], pv->hqdn3d_line, &pv->hqdn3d_frame[1], pv->width[1], pv->height[1], pv->hqdn3d_coef[2], pv->hqdn3d_coef[2], pv->hqdn3d_coef[3] ); hqdn3d_denoise( pv->pic_in.data[2], pv->pic_out.data[2], pv->hqdn3d_line, &pv->hqdn3d_frame[2], pv->width[2], pv->height[2], pv->hqdn3d_coef[2], pv->hqdn3d_coef[2], pv->hqdn3d_coef[3] ); hb_buffer_copy_settings( pv->buf_out, buf_in ); *buf_out = pv->buf_out; return FILTER_OK; }