/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include "diff.h" #include "util.h" /* * Compute 64-bit average perceptual hash. * * 1. Scale frame to 8x8 grayscale. * 2. Compute mean pixel value. * 3. Each bit = 1 if pixel > mean, 0 otherwise. * * Fast and effective for detecting duplicate/near-duplicate frames. */ uint64_t diff_phash(AVFrame *frame, struct SwsContext *sws, int src_w, int src_h) { uint8_t gray[64]; uint8_t *dst_data[1]; int dst_linesize[1]; uint64_t hash; unsigned sum; uint8_t mean; int i; dst_data[0] = gray; dst_linesize[0] = 8; sws_scale(sws, (const uint8_t *const *)frame->data, frame->linesize, 0, src_h, dst_data, dst_linesize); /* compute mean */ sum = 0; for (i = 0; i < 64; i++) sum += gray[i]; mean = (uint8_t)(sum / 64); /* build hash */ hash = 0; for (i = 0; i < 64; i++) { if (gray[i] > mean) hash |= ((uint64_t)1 << i); } return hash; } int diff_hamming(uint64_t a, uint64_t b) { uint64_t x; int count; x = a ^ b; count = 0; while (x) { count++; x &= x - 1; } return count; } static int save_png(const uint8_t *rgb, int w, int h, int stride, const char *path) { FILE *fp; png_structp png; png_infop info; int y; fp = fopen(path, "wb"); if (!fp) { warn("cannot open '%s' for writing:", path); return -1; } png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { fclose(fp); return -1; } info = png_create_info_struct(png); if (!info) { png_destroy_write_struct(&png, NULL); fclose(fp); return -1; } if (setjmp(png_jmpbuf(png))) { png_destroy_write_struct(&png, &info); fclose(fp); return -1; } png_init_io(png, fp); png_set_IHDR(png, info, w, h, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png, info); for (y = 0; y < h; y++) png_write_row(png, rgb + y * stride); png_write_end(png, NULL); png_destroy_write_struct(&png, &info); fclose(fp); return 0; } static int save_jpg(const uint8_t *rgb, int w, int h, int stride, const char *path) { FILE *fp; struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; JSAMPROW row; int y; fp = fopen(path, "wb"); if (!fp) { warn("cannot open '%s' for writing:", path); return -1; } cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, fp); cinfo.image_width = w; cinfo.image_height = h; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, 95, 1); jpeg_start_compress(&cinfo, 1); for (y = 0; y < h; y++) { row = (JSAMPROW)(rgb + y * stride); jpeg_write_scanlines(&cinfo, &row, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); fclose(fp); return 0; } int diff_save_frame(AVFrame *frame, struct SwsContext *sws_rgb, int src_w, int src_h, const char *path, const char *fmt) { AVFrame *rgb_frame; int ret; rgb_frame = av_frame_alloc(); if (!rgb_frame) return -1; rgb_frame->format = AV_PIX_FMT_RGB24; rgb_frame->width = src_w; rgb_frame->height = src_h; ret = av_image_alloc(rgb_frame->data, rgb_frame->linesize, src_w, src_h, AV_PIX_FMT_RGB24, 32); if (ret < 0) { av_frame_free(&rgb_frame); return -1; } sws_scale(sws_rgb, (const uint8_t *const *)frame->data, frame->linesize, 0, src_h, rgb_frame->data, rgb_frame->linesize); if (strcmp(fmt, "jpg") == 0 || strcmp(fmt, "jpeg") == 0) { ret = save_jpg(rgb_frame->data[0], src_w, src_h, rgb_frame->linesize[0], path); } else { ret = save_png(rgb_frame->data[0], src_w, src_h, rgb_frame->linesize[0], path); } av_freep(&rgb_frame->data[0]); av_frame_free(&rgb_frame); return ret; }