Add libvmaf_rc support

libvmaf_rc is the new release candidate API that support VMAF_NEG
calculations. This CL enables VMAF_NEG calculations in libaom.

Change-Id: I430eaa390858f8f6b6d91da8a09bbcab5a898361
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6f7bf29..7e1f730 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -432,6 +432,10 @@
   list(APPEND AOM_APP_TARGETS ${AOM_ENCODER_EXAMPLE_TARGETS}
               ${AOM_ENCODER_TOOL_TARGETS})
 
+  if(CONFIG_USE_VMAF_RC AND NOT CONFIG_TUNE_VMAF)
+    message(FATAL_ERROR "Turn on CONFIG_TUNE_VMAF to use CONFIG_USE_VMAF_RC.")
+  endif()
+
   if(CONFIG_TUNE_VMAF)
     find_package(PkgConfig)
     if(PKG_CONFIG_FOUND)
@@ -446,6 +450,21 @@
         message(FATAL_ERROR "VMAF library not found.")
       endif()
     endif()
+
+    if(CONFIG_USE_VMAF_RC)
+      if(PKG_CONFIG_FOUND)
+        pkg_check_modules(VMAF_RC REQUIRED libvmaf_rc)
+        target_link_libraries(aom
+                              PRIVATE ${VMAF_RC_LDFLAGS} ${VMAF_RC_LIBRARIES})
+        target_include_directories(aom PRIVATE ${VMAF_RC_INCLUDE_DIRS})
+        if(VMAF_RC_CFLAGS)
+          append_compiler_flag("${VMAF_RC_CFLAGS}")
+        endif()
+      else()
+        message(FATAL_ERROR "CONFIG_USE_VMAF_RC error: pkg-config not found.")
+      endif()
+    endif()
+
     set_target_properties(aom PROPERTIES LINKER_LANGUAGE CXX)
     if(BUILD_SHARED_LIBS)
       set_target_properties(aom_static PROPERTIES LINKER_LANGUAGE CXX)
diff --git a/aom_dsp/vmaf.c b/aom_dsp/vmaf.c
index 4241f93..b0be482 100644
--- a/aom_dsp/vmaf.c
+++ b/aom_dsp/vmaf.c
@@ -9,6 +9,8 @@
  * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
  */
 
+#include "aom_dsp/vmaf.h"
+
 #include <assert.h>
 #include <libvmaf.h>
 #include <stdio.h>
@@ -20,8 +22,11 @@
 #include <unistd.h>
 #endif
 
+#if CONFIG_USE_VMAF_RC
+#include <libvmaf.rc.h>
+#endif
+
 #include "aom_dsp/blend.h"
-#include "aom_dsp/vmaf.h"
 #include "aom_ports/system_state.h"
 
 typedef struct FrameData {
@@ -168,3 +173,103 @@
 
   aom_clear_system_state();
 }
+
+#if CONFIG_USE_VMAF_RC
+void aom_init_vmaf_rc(VmafModel **vmaf_model, const char *model_path) {
+  if (*vmaf_model != NULL) return;
+  VmafModelConfig model_cfg;
+  model_cfg.flags = VMAF_MODEL_FLAG_DISABLE_CLIP;
+  model_cfg.name = "vmaf";
+  model_cfg.path = (char *)model_path;
+
+  if (vmaf_model_load_from_path(vmaf_model, &model_cfg)) {
+    vmaf_fatal_error("Failed to load VMAF model.");
+  }
+}
+
+void aom_close_vmaf_rc(VmafModel *vmaf_model) {
+  vmaf_model_destroy(vmaf_model);
+}
+
+static void copy_picture(const int bit_depth, const YV12_BUFFER_CONFIG *src,
+                         VmafPicture *dst) {
+  const int width = src->y_width;
+  const int height = src->y_height;
+
+  if (bit_depth > 8) {
+    uint16_t *src_ptr = CONVERT_TO_SHORTPTR(src->y_buffer);
+    uint16_t *dst_ptr = dst->data[0];
+
+    for (int row = 0; row < height; ++row) {
+      memcpy(dst_ptr, src_ptr, width * sizeof(dst_ptr[0]));
+      src_ptr += src->y_stride;
+      dst_ptr += dst->stride[0] / 2;
+    }
+  } else {
+    uint8_t *src_ptr = src->y_buffer;
+    uint8_t *dst_ptr = (uint8_t *)dst->data[0];
+
+    for (int row = 0; row < height; ++row) {
+      memcpy(dst_ptr, src_ptr, width * sizeof(dst_ptr[0]));
+      src_ptr += src->y_stride;
+      dst_ptr += dst->stride[0];
+    }
+  }
+}
+
+void aom_calc_vmaf_rc(VmafModel *vmaf_model, const YV12_BUFFER_CONFIG *source,
+                      const YV12_BUFFER_CONFIG *distorted, int bit_depth,
+                      int cal_vmaf_neg, double *vmaf) {
+  VmafConfiguration cfg;
+  cfg.log_level = VMAF_LOG_LEVEL_NONE;
+  cfg.n_threads = 0;
+  cfg.n_subsample = 0;
+  cfg.cpumask = 0;
+
+  VmafContext *vmaf_context;
+  if (vmaf_init(&vmaf_context, cfg)) {
+    vmaf_fatal_error("Failed to init VMAF context.");
+  }
+
+  if (vmaf_use_features_from_model(vmaf_context, vmaf_model)) {
+    vmaf_fatal_error("Failed to load feature extractors from VMAF model.");
+  }
+
+  if (cal_vmaf_neg) {
+    VmafFeatureDictionary *vif_feature = NULL;
+    vmaf_feature_dictionary_set(&vif_feature, "vif_enhn_gain_limit", "1.0");
+    if (vmaf_use_feature(vmaf_context, "float_vif", vif_feature)) {
+      vmaf_fatal_error("Failed to use feature float_vif.");
+    }
+
+    VmafFeatureDictionary *adm_feature = NULL;
+    vmaf_feature_dictionary_set(&adm_feature, "adm_enhn_gain_limit", "1.0");
+    if (vmaf_use_feature(vmaf_context, "float_adm", adm_feature)) {
+      vmaf_fatal_error("Failed to use feature float_adm.");
+    }
+  }
+
+  VmafPicture ref, dist;
+  if (vmaf_picture_alloc(&ref, VMAF_PIX_FMT_YUV420P, bit_depth, source->y_width,
+                         source->y_height) ||
+      vmaf_picture_alloc(&dist, VMAF_PIX_FMT_YUV420P, bit_depth,
+                         source->y_width, source->y_height)) {
+    vmaf_fatal_error("Failed to alloc VMAF pictures.");
+  }
+  copy_picture(bit_depth, source, &ref);
+  copy_picture(bit_depth, distorted, &dist);
+  if (vmaf_read_pictures(vmaf_context, &ref, &dist, /*picture index=*/0)) {
+    vmaf_fatal_error("Failed to read VMAF pictures.");
+  }
+
+  vmaf_picture_unref(&ref);
+  vmaf_picture_unref(&dist);
+
+  vmaf_score_at_index(vmaf_context, vmaf_model, vmaf, 0);
+
+  if (vmaf_close(vmaf_context)) {
+    vmaf_fatal_error("Failed to close VMAF context.");
+  }
+}
+
+#endif  // CONFIG_USE_VMAF_RC
diff --git a/aom_dsp/vmaf.h b/aom_dsp/vmaf.h
index 6079944..02e59ed 100644
--- a/aom_dsp/vmaf.h
+++ b/aom_dsp/vmaf.h
@@ -14,6 +14,11 @@
 
 #include "aom_scale/yv12config.h"
 
+#if CONFIG_USE_VMAF_RC
+typedef struct VmafContext VmafContext;
+typedef struct VmafModel VmafModel;
+#endif
+
 typedef struct {
   // Stores the scaling factors for rdmult when tuning for VMAF.
   // rdmult_scaling_factors[row * num_cols + col] stores the scaling factors for
@@ -28,6 +33,11 @@
 
   // Stores the filter strength of the last frame.
   double last_frame_unsharp_amount;
+
+#if CONFIG_USE_VMAF_RC
+  // VMAF model used in VMAF caculations.
+  VmafModel *vmaf_model;
+#endif
 } TuneVMAFInfo;
 
 void aom_calc_vmaf(const char *model_path, const YV12_BUFFER_CONFIG *source,
@@ -40,4 +50,14 @@
                       int stride_byte, void *user_data),
     int frame_width, int frame_height, int bit_depth, double *vmaf);
 
+#if CONFIG_USE_VMAF_RC
+void aom_init_vmaf_rc(VmafModel **vmaf_model, const char *model_path);
+
+void aom_calc_vmaf_rc(VmafModel *vmaf_model, const YV12_BUFFER_CONFIG *source,
+                      const YV12_BUFFER_CONFIG *distorted, int bit_depth,
+                      int cal_vmaf_neg, double *vmaf);
+
+void aom_close_vmaf_rc(VmafModel *vmaf_model);
+#endif  // CONFIG_USE_VMAF_RC
+
 #endif  // AOM_AOM_DSP_VMAF_H_
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c
index 5f204a7..6463ad6 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -2162,6 +2162,11 @@
     av1_apply_encoding_flags(cpi_lap, flags);
   }
 
+#if CONFIG_USE_VMAF_RC
+  aom_init_vmaf_rc(&cpi->vmaf_info.vmaf_model,
+                   cpi->oxcf.tune_cfg.vmaf_model_path);
+#endif
+
   // Handle fixed keyframe intervals
   if (is_stat_generation_stage(cpi)) {
     if (ctx->cfg.kf_mode == AOM_KF_AUTO &&
diff --git a/av1/encoder/encoder_alloc.h b/av1/encoder/encoder_alloc.h
index 176dbae..0c3d210 100644
--- a/av1/encoder/encoder_alloc.h
+++ b/av1/encoder/encoder_alloc.h
@@ -241,6 +241,10 @@
 #if CONFIG_TUNE_VMAF
   aom_free(cpi->vmaf_info.rdmult_scaling_factors);
   cpi->vmaf_info.rdmult_scaling_factors = NULL;
+
+#if CONFIG_USE_VMAF_RC
+  aom_close_vmaf_rc(cpi->vmaf_info.vmaf_model);
+#endif
 #endif
 
   release_obmc_buffers(&cpi->td.mb.obmc_buffer);
diff --git a/build/cmake/aom_config_defaults.cmake b/build/cmake/aom_config_defaults.cmake
index 298ed28..ccb720c 100644
--- a/build/cmake/aom_config_defaults.cmake
+++ b/build/cmake/aom_config_defaults.cmake
@@ -107,6 +107,7 @@
 set_aom_config_var(DECODE_HEIGHT_LIMIT 0 "Set limit for decode height.")
 set_aom_config_var(DECODE_WIDTH_LIMIT 0 "Set limit for decode width.")
 set_aom_config_var(CONFIG_TUNE_VMAF 0 "Enable encoding tuning for VMAF.")
+set_aom_config_var(CONFIG_USE_VMAF_RC 0 "Use libvmaf_rc tune for VMAF_NEG.")
 
 # AV1 experiment flags.
 set_aom_config_var(CONFIG_SPEED_STATS 0 "AV1 experiment flag.")