| /* | 
 |  * Copyright (c) 2021, Alliance for Open Media. All rights reserved | 
 |  * | 
 |  * This source code is subject to the terms of the BSD 3-Clause Clear License | 
 |  * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear | 
 |  * License was not distributed with this source code in the LICENSE file, you | 
 |  * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/.  If the | 
 |  * Alliance for Open Media Patent License 1.0 was not distributed with this | 
 |  * source code in the PATENTS file, you can obtain it at | 
 |  * aomedia.org/license/patent-license/. | 
 |  */ | 
 |  | 
 | #include "third_party/googletest/src/googletest/include/gtest/gtest.h" | 
 | #include "test/codec_factory.h" | 
 | #include "test/encode_test_driver.h" | 
 | #include "test/y4m_video_source.h" | 
 | #include "test/i420_video_source.h" | 
 | #include "test/util.h" | 
 | #include "aom/aom_codec.h" | 
 |  | 
 | #include "av1/encoder/encoder.h" | 
 | #include "av1/encoder/subgop.h" | 
 |  | 
 | // Silence compiler warning for unused static functions | 
 | static void yuvconfig2image(aom_image_t *img, const YV12_BUFFER_CONFIG *yv12, | 
 |                             void *user_priv) AOM_UNUSED; | 
 | static aom_codec_err_t image2yuvconfig(const aom_image_t *img, | 
 |                                        YV12_BUFFER_CONFIG *yv12) AOM_UNUSED; | 
 | static void image2yuvconfig_upshift(aom_image_t *hbd_img, | 
 |                                     const aom_image_t *img, | 
 |                                     YV12_BUFFER_CONFIG *yv12) AOM_UNUSED; | 
 | #include "av1/av1_iface_common.h" | 
 |  | 
 | #define MAX_SUBGOP_CODES 3 | 
 |  | 
 | static const char *subgop_config_str_nondef[] = { | 
 |   // enh, subgop size = 4 | 
 |   "4:0:4U1/2U2/1V3/2S/3V3/4S," | 
 |   "4:1:3U1/2U2/1V3/2S/3S/4V3," | 
 |   // enh, subgop size = 5 | 
 |   "5:0:5U1/3U2/1V3/2V3/3S/4V3/5S," | 
 |   "5:1:4U1/2U2/1V3/2S/3V3/4S/5V3," | 
 |   // enh, subgop size = 7 | 
 |   "7:0:7U1/3U2/1V4/2V4/3S/5U3/4V4/5S/6V4/7S," | 
 |   "7:1:6U1/3U2/2U3/1V4/2S/3S/5U3/4V4/5S/6S/7V4," | 
 |   // enh, subgop size = 9 | 
 |   "9:0:9F1/4U2/2U3/1V4/2S/3V4/4S/7U3/5V4/6V5/7S/8V5/9R1," | 
 |   "9:1:7F1/3U2/1V4/2V4/3S/5U3/4V4/5S/6V4/7R1/9U3/8V4/9S,", | 
 | }; | 
 |  | 
 | namespace { | 
 | // Default config | 
 | extern "C" const char subgop_config_str_def[]; | 
 | // An enhanced config where the last subgop uses a shorter dist to arf | 
 | extern "C" const char subgop_config_str_enh[]; | 
 | // A config that honors temporally scalable prediction structure, i.e. | 
 | // no frame is coded with references at higher pyramid depths. | 
 | extern "C" const char subgop_config_str_ts[]; | 
 | // An asymmetrical config where the hierarchical frames are not exactly | 
 | // dyadic, but slightly skewed. | 
 | extern "C" const char subgop_config_str_asym[]; | 
 | // low delay config without references | 
 | extern "C" const char subgop_config_str_ld[]; | 
 |  | 
 | const int kCpuUsed = 5; | 
 | const unsigned int kFrames = 70; | 
 |  | 
 | typedef enum { | 
 |   DEFAULT, | 
 |   ENHANCE, | 
 |   ASYMMETRIC, | 
 |   TEMPORAL_SCALABLE, | 
 |   LOW_DELAY, | 
 | } subgop_config_tag; | 
 |  | 
 | typedef struct { | 
 |   const char *preset_tag; | 
 |   const char *preset_str; | 
 | } subgop_config_str_preset_map_type; | 
 |  | 
 | const subgop_config_str_preset_map_type subgop_config_str_preset_map[] = { | 
 |   { "def", subgop_config_str_def },   { "enh", subgop_config_str_enh }, | 
 |   { "asym", subgop_config_str_asym }, { "ts", subgop_config_str_ts }, | 
 |   { "ld", subgop_config_str_ld }, | 
 | }; | 
 |  | 
 | typedef struct { | 
 |   const char *subgop_str; | 
 |   const char *input_file; | 
 |   int min_gf_interval; | 
 |   int max_gf_interval; | 
 |   int frame_w; | 
 |   int frame_h; | 
 |   int lag_in_frames; | 
 |   int use_fixed_qp_offsets; | 
 | } SubgopTestParams; | 
 |  | 
 | int is_extension_y4m(const char *filename) { | 
 |   const char *dot = strrchr(filename, '.'); | 
 |   if (!dot || dot == filename) | 
 |     return 0; | 
 |   else | 
 |     return !strcmp(dot, ".y4m"); | 
 | } | 
 |  | 
 | static const SubgopTestParams SubGopTestVectors[] = { | 
 |   // Default subgop config | 
 |   { subgop_config_str_preset_map[DEFAULT].preset_tag, | 
 |     "hantro_collage_w352h288.yuv", 0, 16, 352, 288, 35, 0 }, | 
 |   { subgop_config_str_preset_map[DEFAULT].preset_tag, "desktop1.320_180.yuv", 0, | 
 |     16, 320, 180, 35, 0 }, | 
 |   { subgop_config_str_preset_map[DEFAULT].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 16, 16, 320, 240, 35, 1 }, | 
 |   { subgop_config_str_preset_map[DEFAULT].preset_tag, | 
 |     "hantro_collage_w352h288.yuv", 0, 32, 352, 288, 35, 0 }, | 
 |   { subgop_config_str_preset_map[DEFAULT].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 32, 32, 320, 240, 35, 1 }, | 
 |  | 
 |   // Enhanced subgop config | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, "niklas_640_480_30.yuv", | 
 |     0, 15, 640, 480, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, "paris_352_288_30.y4m", 0, | 
 |     6, 352, 288, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, | 
 |     "hantro_collage_w352h288.yuv", 0, 16, 352, 288, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 0, 12, 320, 240, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, "niklas_640_480_30.yuv", | 
 |     0, 11, 640, 480, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, "screendata.y4m", 0, 16, | 
 |     640, 480, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 0, 14, 320, 240, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, "desktop1.320_180.yuv", 0, | 
 |     10, 320, 180, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, "paris_352_288_30.y4m", 0, | 
 |     13, 352, 288, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 0, 8, 320, 240, 35, 0 }, | 
 |  | 
 |   // Asymmetric subgop config | 
 |   { subgop_config_str_preset_map[ASYMMETRIC].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 0, 16, 320, 240, 35, 0 }, | 
 |   { subgop_config_str_preset_map[ASYMMETRIC].preset_tag, "desktop1.320_180.yuv", | 
 |     0, 16, 320, 180, 35, 0 }, | 
 |  | 
 |   // Temporal scalable subgop config | 
 |   { subgop_config_str_preset_map[TEMPORAL_SCALABLE].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 0, 16, 320, 240, 35, 0 }, | 
 |   { subgop_config_str_preset_map[TEMPORAL_SCALABLE].preset_tag, | 
 |     "hantro_collage_w352h288.yuv", 0, 16, 352, 288, 35, 0 }, | 
 |  | 
 |   // Low delay subgop config | 
 |   { subgop_config_str_preset_map[LOW_DELAY].preset_tag, "paris_352_288_30.y4m", | 
 |     0, 16, 352, 288, 0, 0 }, | 
 |   { subgop_config_str_preset_map[LOW_DELAY].preset_tag, "desktop1.320_180.yuv", | 
 |     16, 16, 320, 180, 0, 1 }, | 
 |   { subgop_config_str_preset_map[LOW_DELAY].preset_tag, "paris_352_288_30.y4m", | 
 |     0, 32, 352, 288, 0, 0 }, | 
 |   { subgop_config_str_preset_map[LOW_DELAY].preset_tag, "desktop1.320_180.yuv", | 
 |     32, 32, 320, 180, 0, 1 }, | 
 |  | 
 |   // Non-default subgop config | 
 |   { subgop_config_str_nondef[0], "pixel_capture_w320h240.yuv", 0, 4, 320, 240, | 
 |     35, 0 }, | 
 |   { subgop_config_str_nondef[0], "desktop1.320_180.yuv", 0, 5, 320, 180, 35, | 
 |     0 }, | 
 |   { subgop_config_str_nondef[0], "pixel_capture_w320h240.yuv", 0, 7, 320, 240, | 
 |     35, 0 }, | 
 |   { subgop_config_str_nondef[0], "hantro_collage_w352h288.yuv", 0, 9, 352, 288, | 
 |     35, 0 }, | 
 | }; | 
 |  | 
 | std::ostream &operator<<(std::ostream &os, const SubgopTestParams &test_arg) { | 
 |   return os << "SubgopTestParams { sub_gop_config:" << test_arg.subgop_str | 
 |             << " source_file:" << test_arg.input_file | 
 |             << " min_gf_interval:" << test_arg.min_gf_interval | 
 |             << " max_gf_interval:" << test_arg.max_gf_interval | 
 |             << " frame_width:" << test_arg.frame_w | 
 |             << " frame_height:" << test_arg.frame_h << " cpu_used:" << kCpuUsed | 
 |             << " lag_in_frames:" << test_arg.lag_in_frames | 
 |             << " use_fixed_qp_offsets:" << test_arg.use_fixed_qp_offsets | 
 |             << " }"; | 
 | } | 
 | // This class is used to validate the subgop config in a gop. | 
 | class SubGopTestLarge | 
 |     : public ::libaom_test::CodecTestWith2Params<SubgopTestParams, aom_rc_mode>, | 
 |       public ::libaom_test::EncoderTest { | 
 |  protected: | 
 |   SubGopTestLarge() | 
 |       : EncoderTest(GET_PARAM(0)), subgop_test_params_(GET_PARAM(1)), | 
 |         rc_end_usage_(GET_PARAM(2)) { | 
 |     InitSubgop(); | 
 |   } | 
 |   virtual ~SubGopTestLarge() {} | 
 |  | 
 |   virtual void SetUp() { | 
 |     InitializeConfig(); | 
 |     SetMode(::libaom_test::kOnePassGood); | 
 |     const aom_rational timebase = { 1, 30 }; | 
 |     cfg_.g_timebase = timebase; | 
 |     cfg_.g_threads = 1; | 
 |     cfg_.rc_end_usage = rc_end_usage_; | 
 |     cfg_.rc_undershoot_pct = 100; | 
 |     cfg_.rc_overshoot_pct = 100; | 
 |     if (rc_end_usage_ == AOM_Q) { | 
 |       cfg_.use_fixed_qp_offsets = subgop_test_params_.use_fixed_qp_offsets; | 
 |     } | 
 |     // Note: kf_min_dist, kf_max_dist, g_lag_in_frames are configurable | 
 |     // parameters | 
 |     cfg_.kf_min_dist = 65; | 
 |     cfg_.kf_max_dist = 65; | 
 |     cfg_.g_lag_in_frames = subgop_test_params_.lag_in_frames; | 
 |     // Note: Uncomment the following line for verbose output, to aid debugging. | 
 |     // init_flags_ = AOM_CODEC_USE_PER_FRAME_STATS; | 
 |   } | 
 |  | 
 |   // check if subgop_config_str is a preset tag | 
 |   void GetSubGOPConfigStr() { | 
 |     int num_preset_configs = sizeof(subgop_config_str_preset_map) / | 
 |                              sizeof(*subgop_config_str_preset_map); | 
 |     for (int p = 0; p < num_preset_configs; ++p) { | 
 |       if (!strcmp(subgop_test_params_.subgop_str, | 
 |                   subgop_config_str_preset_map[p].preset_tag)) { | 
 |         subgop_test_params_.subgop_str = | 
 |             subgop_config_str_preset_map[p].preset_str; | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   virtual void PreEncodeFrameHook(::libaom_test::VideoSource *video, | 
 |                                   ::libaom_test::Encoder *encoder) { | 
 |     if (video->frame() == 0) { | 
 |       encoder->Control(AOME_SET_CPUUSED, kCpuUsed); | 
 |       if (rc_end_usage_ == AOM_Q || rc_end_usage_ == AOM_CQ) { | 
 |         encoder->Control(AOME_SET_QP, 210); | 
 |       } | 
 |       encoder->Control(AV1E_ENABLE_SUBGOP_STATS, enable_subgop_stats_); | 
 |       GetSubGOPConfigStr(); | 
 |       encoder->Control(AV1E_SET_SUBGOP_CONFIG_STR, | 
 |                        subgop_test_params_.subgop_str); | 
 |       av1_process_subgop_config_set(subgop_test_params_.subgop_str, | 
 |                                     &user_cfg_set_); | 
 |       encoder->Control(AV1E_SET_MIN_GF_INTERVAL, | 
 |                        subgop_test_params_.min_gf_interval); | 
 |       encoder->Control(AV1E_SET_MAX_GF_INTERVAL, | 
 |                        subgop_test_params_.max_gf_interval); | 
 |     } | 
 |   } | 
 |  | 
 |   virtual bool DoDecode() const { return 1; } | 
 |  | 
 |   virtual void PreDecodeFrameHook(::libaom_test::VideoSource *video, | 
 |                                   ::libaom_test::Decoder *decoder) { | 
 |     aom_codec_ctx_t *ctx_dec = decoder->GetDecoder(); | 
 |     if (video->frame() == 0) | 
 |       AOM_CODEC_CONTROL_TYPECHECKED(ctx_dec, AV1D_ENABLE_SUBGOP_STATS, | 
 |                                     enable_subgop_stats_); | 
 |   } | 
 |  | 
 |   void InitSubgop() { | 
 |     memset(&user_cfg_set_, 0, sizeof(user_cfg_set_)); | 
 |     subgop_data_.num_steps = MAX_SUBGOP_STATS_SIZE; | 
 |     ResetSubgop(); | 
 |     is_first_frame_in_subgop_key_ = 0; | 
 |     frames_from_key_ = 0; | 
 |     enable_subgop_stats_ = 1; | 
 |     memset(&subgop_last_step_, 0, sizeof(subgop_last_step_)); | 
 |   } | 
 |  | 
 |   void ResetSubgop() { | 
 |     subgop_info_.is_user_specified = 0; | 
 |     subgop_info_.frames_to_key = 0; | 
 |     subgop_info_.gf_interval = 0; | 
 |     subgop_info_.size = 0; | 
 |     subgop_info_.pos_code = SUBGOP_IN_GOP_GENERIC; | 
 |  | 
 |     for (int idx = 0; idx < MAX_SUBGOP_STATS_SIZE; idx++) { | 
 |       subgop_data_.step[idx].disp_frame_idx = -1; | 
 |       subgop_data_.step[idx].show_existing_frame = -1; | 
 |       subgop_data_.step[idx].show_frame = -1; | 
 |       subgop_data_.step[idx].is_filtered = -1; | 
 |       subgop_data_.step[idx].pyramid_level = 0; | 
 |       subgop_data_.step[idx].qindex = 0; | 
 |       subgop_data_.step[idx].refresh_frame_flags = 0; | 
 |       subgop_data_.step[idx].num_references = -1; | 
 |       memset(subgop_data_.step[idx].ref_frame_pyr_level, 0, | 
 |              sizeof(subgop_data_.step[idx].ref_frame_pyr_level)); | 
 |       memset(subgop_data_.step[idx].is_valid_ref_frame, 0, | 
 |              sizeof(subgop_data_.step[idx].is_valid_ref_frame)); | 
 |       memset(subgop_data_.step[idx].ref_frame_map, 0, | 
 |              sizeof(subgop_data_.step[idx].ref_frame_map)); | 
 |       for (int ref = 0; ref < INTER_REFS_PER_FRAME; ref++) { | 
 |         subgop_data_.step[idx].ref_frame_disp_order[ref] = -1; | 
 |         display_order_test_[idx][ref] = -1; | 
 |       } | 
 |     } | 
 |     subgop_data_.num_steps = 0; | 
 |     subgop_data_.step_idx_enc = 0; | 
 |     subgop_data_.step_idx_dec = 0; | 
 |  | 
 |     subgop_code_test_ = SUBGOP_IN_GOP_GENERIC; | 
 |     subgop_size_ = 0; | 
 |     frame_num_in_subgop_ = 0; | 
 |   } | 
 |  | 
 |   int is_key_frame() { | 
 | #if CONFIG_KEY_OVERLAY | 
 |     // In the key overlay case, this function is called after the key overlay | 
 |     // frame. Then frame_type_test_ is INTER. Need to add the extra condition | 
 |     // to identify key overlay frame case. | 
 |     return frame_type_test_ == KEY_FRAME || subgop_info_.has_key_overlay; | 
 | #else | 
 |     return frame_type_test_ == KEY_FRAME; | 
 | #endif  // CONFIG_KEY_OVERLAY | 
 |   } | 
 |  | 
 |   void DetermineSubgopCode(libaom_test::Encoder *encoder) { | 
 |     encoder->Control(AV1E_GET_FRAME_TYPE, &frame_type_test_); | 
 |     if (is_key_frame()) { | 
 |       is_first_frame_in_subgop_key_ = 1; | 
 |       return; | 
 |     } | 
 |     const int is_last_subgop = | 
 |         subgop_info_.frames_to_key <= (subgop_info_.gf_interval + 1); | 
 |     const int is_first_subgop = is_first_frame_in_subgop_key_; | 
 |     if (is_last_subgop) | 
 |       subgop_code_test_ = SUBGOP_IN_GOP_LAST; | 
 |     else if (is_first_subgop) | 
 |       subgop_code_test_ = SUBGOP_IN_GOP_FIRST; | 
 |     else | 
 |       subgop_code_test_ = SUBGOP_IN_GOP_GENERIC; | 
 |     subgop_size_ = subgop_info_.gf_interval; | 
 |   } | 
 |  | 
 |   virtual bool HandleEncodeResult(::libaom_test::VideoSource *video, | 
 |                                   libaom_test::Encoder *encoder) { | 
 |     (void)video; | 
 |     // Capturing the subgop info at the start of subgop. | 
 |     if (!frame_num_in_subgop_) { | 
 |       encoder->Control(AV1E_GET_SUB_GOP_CONFIG, &subgop_info_); | 
 |       DetermineSubgopCode(encoder); | 
 |       // Validation of user specified subgop structure adoption in encoder path. | 
 |       ValidateSubgopConfig(); | 
 |       subgop_data_.num_steps = subgop_info_.num_steps; | 
 |     } | 
 |     if (subgop_info_.is_user_specified) | 
 |       encoder->Control(AV1E_GET_FRAME_INFO, &subgop_data_); | 
 |     return 1; | 
 |   } | 
 |  | 
 |   void FillTestSubgopConfig() { | 
 |     int filtered_frames[REF_FRAMES] = { 0 }, buf_idx = 0; | 
 |     if (is_key_frame()) return; | 
 |  | 
 |     subgop_cfg_test_.num_frames = (int8_t)subgop_info_.size; | 
 |     subgop_cfg_test_.num_steps = (int8_t)subgop_data_.num_steps; | 
 |     subgop_cfg_test_.subgop_in_gop_code = subgop_info_.pos_code; | 
 |     // Populating the filter-type of out-of-order frames appropriately for all | 
 |     // steps in sub-gop. | 
 |     for (int idx = 0; idx < subgop_data_.num_steps; idx++) { | 
 |       subgop_cfg_test_.step[idx].disp_frame_idx = | 
 |           (int8_t)(subgop_data_.step[idx].disp_frame_idx - frames_from_key_); | 
 |       subgop_cfg_test_.step[idx].pyr_level = | 
 |           (int8_t)subgop_data_.step[idx].pyramid_level; | 
 |       subgop_cfg_test_.step[idx].num_references = | 
 |           (int8_t)subgop_data_.step[idx].num_references; | 
 |       for (int ref = 0; ref < INTER_REFS_PER_FRAME; ref++) { | 
 |         subgop_cfg_test_.step[idx].references[ref] = | 
 |             (int8_t)subgop_data_.step[idx].ref_frame_pyr_level[ref]; | 
 |         display_order_test_[idx][ref] = | 
 |             subgop_data_.step[idx].ref_frame_disp_order[ref]; | 
 |       } | 
 |       if (subgop_data_.step[idx].is_filtered) { | 
 |         filtered_frames[buf_idx++] = | 
 |             subgop_data_.step[idx].disp_frame_idx - frames_from_key_; | 
 |       } else { | 
 |         for (int ref_frame = 0; ref_frame < buf_idx; ref_frame++) { | 
 |           if (subgop_cfg_test_.step[idx].disp_frame_idx == | 
 |               (int8_t)filtered_frames[ref_frame]) | 
 |             subgop_data_.step[idx].is_filtered = 1; | 
 |         } | 
 |       } | 
 |     } | 
 |     // Calculating frame type code for all the steps in subgop. | 
 |     for (int idx = 0; idx < subgop_data_.num_steps; idx++) { | 
 |       FRAME_TYPE_CODE frame_type_code = FRAME_TYPE_INO_VISIBLE; | 
 |       int show_existing_frame = subgop_data_.step[idx].show_existing_frame; | 
 |       int show_frame = subgop_data_.step[idx].show_frame; | 
 |       int is_filtered = subgop_data_.step[idx].is_filtered; | 
 |  | 
 |       assert(show_existing_frame >= 0); | 
 |       assert(show_frame >= 0); | 
 |       assert(frame_type_code != 0); | 
 |       if (show_existing_frame == 0) { | 
 |         if (show_frame == 0) | 
 |           frame_type_code = (is_filtered == 1) ? FRAME_TYPE_OOO_FILTERED | 
 |                                                : FRAME_TYPE_OOO_UNFILTERED; | 
 |         else if (show_frame == 1) | 
 |           frame_type_code = (is_filtered == 1) ? FRAME_TYPE_INO_REPEAT | 
 |                                                : FRAME_TYPE_INO_VISIBLE; | 
 |       } else if (show_existing_frame == 1) { | 
 |         frame_type_code = (is_filtered == 1) ? FRAME_TYPE_INO_REPEAT | 
 |                                              : FRAME_TYPE_INO_SHOWEXISTING; | 
 |       } | 
 |       subgop_cfg_test_.step[idx].type_code = frame_type_code; | 
 |     } | 
 |   } | 
 |  | 
 |   SubGOPCfg *DetermineSubgopConfig() { | 
 |     SubGOPCfg *subgop_cfg = user_cfg_set_.config; | 
 |     SubGOPCfg *viable_subgop_cfg[MAX_SUBGOP_CODES] = { NULL }; | 
 |     unsigned int cfg_count = 0; | 
 |  | 
 |     for (int idx = 0; idx < user_cfg_set_.num_configs; idx++) { | 
 |       if (subgop_cfg[idx].num_frames == (int8_t)subgop_size_) { | 
 |         if (subgop_cfg[idx].subgop_in_gop_code == subgop_code_test_) | 
 |           return &subgop_cfg[idx]; | 
 |         else | 
 |           viable_subgop_cfg[cfg_count++] = &subgop_cfg[idx]; | 
 |         assert(cfg_count < MAX_SUBGOP_CODES); | 
 |       } | 
 |     } | 
 |     subgop_code_test_ = SUBGOP_IN_GOP_GENERIC; | 
 |     for (unsigned int cfg = 0; cfg < cfg_count; cfg++) { | 
 |       if (viable_subgop_cfg[cfg]->subgop_in_gop_code == subgop_code_test_) | 
 |         return viable_subgop_cfg[cfg]; | 
 |     } | 
 |     return NULL; | 
 |   } | 
 |  | 
 |   // Validates frametype(along with temporal filtering), frame coding order | 
 |   bool ValidateSubgopFrametype() { | 
 |     for (int idx = 0; idx < subgop_cfg_ref_->num_steps; idx++) { | 
 |       if (subgop_cfg_ref_->step[idx].type_code != FRAME_TYPE_INO_SHOWEXISTING) { | 
 |         EXPECT_EQ(subgop_cfg_ref_->step[idx].disp_frame_idx, | 
 |                   subgop_cfg_test_.step[idx].disp_frame_idx) | 
 |             << "Error:display_index doesn't match"; | 
 |         EXPECT_EQ(subgop_cfg_ref_->step[idx].type_code, | 
 |                   subgop_cfg_test_.step[idx].type_code) | 
 |             << "Error:frame type doesn't match"; | 
 |       } | 
 |     } | 
 |     return 1; | 
 |   } | 
 |  | 
 |   // Validates Pyramid level with user config | 
 |   void ValidatePyramidLevel() { | 
 |     int8_t max_pyramid_level = 0; | 
 |     for (int idx = 0; idx < subgop_cfg_ref_->num_steps; idx++) { | 
 |       if (max_pyramid_level < subgop_cfg_ref_->step[idx].pyr_level) | 
 |         max_pyramid_level = subgop_cfg_ref_->step[idx].pyr_level; | 
 |     } | 
 |     for (int idx = 0; idx < subgop_cfg_ref_->num_steps; idx++) { | 
 |       if (subgop_cfg_ref_->step[idx].type_code != FRAME_TYPE_INO_SHOWEXISTING) { | 
 |         int8_t ref_pyramid_level = | 
 |             (subgop_cfg_ref_->step[idx].pyr_level == max_pyramid_level) | 
 |                 ? MAX_ARF_LAYERS | 
 |                 : subgop_cfg_ref_->step[idx].pyr_level; | 
 |         EXPECT_EQ(subgop_cfg_test_.step[idx].pyr_level, ref_pyramid_level) | 
 |             << "Error:pyramid level doesn't match"; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // Validates Pyramid level along with qindex assignment | 
 |   void ValidatePyramidLevelQIndex() { | 
 |     int level_qindex[MAX_ARF_LAYERS + 1]; | 
 |     for (int i = 0; i <= MAX_ARF_LAYERS; i++) level_qindex[i] = -1; | 
 |     int pyramid_level; | 
 |     for (int idx = 0; idx < subgop_cfg_ref_->num_steps; idx++) { | 
 |       pyramid_level = subgop_cfg_test_.step[idx].pyr_level; | 
 |       if (subgop_cfg_ref_->step[idx].type_code != FRAME_TYPE_INO_SHOWEXISTING) { | 
 |         if (level_qindex[pyramid_level] < 0) { | 
 |           level_qindex[pyramid_level] = subgop_data_.step[idx].qindex; | 
 |         } else if (!subgop_data_.step[idx].show_existing_frame && | 
 |                    !subgop_data_.step[idx].is_filtered) { | 
 |           EXPECT_EQ(level_qindex[pyramid_level], subgop_data_.step[idx].qindex) | 
 |               << "Error:qindex in a pyramid level doesn't match"; | 
 |         } | 
 |       } | 
 |     } | 
 |     for (pyramid_level = 1; pyramid_level <= MAX_ARF_LAYERS; pyramid_level++) { | 
 |       if (level_qindex[pyramid_level] >= 0) { | 
 |         EXPECT_LT(level_qindex[pyramid_level - 1], level_qindex[pyramid_level]) | 
 |             << "Error: level " << pyramid_level - 1 << " qindex " | 
 |             << "should be less than level " << pyramid_level << " qindex"; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // Validates reference buffer refresh | 
 |   void ValidateRefBufRefresh() { | 
 |     int start_idx = 0; | 
 |     SubGOPStepData *prev_step_data = &subgop_last_step_; | 
 |     if (is_first_frame_in_subgop_key_) { | 
 |       start_idx = 1; | 
 |       prev_step_data = &subgop_data_.step[0]; | 
 |     } | 
 |  | 
 |     for (int idx = start_idx; idx < subgop_cfg_ref_->num_steps; idx++) { | 
 |       SubGOPStepData *curr_step_data = &subgop_data_.step[idx]; | 
 |       int ref_count = 0; | 
 |       int refresh_frame_flags = curr_step_data->refresh_frame_flags; | 
 |       // Validates user-defined refresh_flag with decoder | 
 |       if (subgop_cfg_ref_->step[idx].refresh != -1 && | 
 |           subgop_cfg_ref_->step[idx].type_code != FRAME_TYPE_INO_SHOWEXISTING) { | 
 |         EXPECT_EQ(subgop_cfg_ref_->step[idx].refresh, | 
 |                   (int8_t)refresh_frame_flags) | 
 |             << "Error: refresh flag mismatch"; | 
 |       } | 
 |       // Validates reference picture management w.r.t refresh_flags | 
 |       if (refresh_frame_flags && | 
 |           subgop_cfg_ref_->step[idx].type_code != FRAME_TYPE_INO_SHOWEXISTING) { | 
 |         for (int mask = refresh_frame_flags; mask; mask >>= 1) { | 
 |           if (mask & 1) | 
 |             EXPECT_EQ(curr_step_data->disp_frame_idx, | 
 |                       (int)curr_step_data->ref_frame_map[ref_count]) | 
 |                 << "Error: reference buffer refresh failed"; | 
 |           else | 
 |             EXPECT_EQ(prev_step_data->ref_frame_map[ref_count], | 
 |                       curr_step_data->ref_frame_map[ref_count]) | 
 |                 << "Error: reference buffer refresh failed"; | 
 |           assert(ref_count < REF_FRAMES); | 
 |           ref_count++; | 
 |         } | 
 |       } | 
 |  | 
 |       for (int ref_idx = ref_count; ref_idx < REF_FRAMES; ref_idx++) | 
 |         EXPECT_EQ(prev_step_data->ref_frame_map[ref_idx], | 
 |                   curr_step_data->ref_frame_map[ref_idx]) | 
 |             << "Error: reference buffer refresh failed"; | 
 |       prev_step_data = curr_step_data; | 
 |     } | 
 |   } | 
 |  | 
 |   void ValidateRefFrames() { | 
 |     int start_idx = is_first_frame_in_subgop_key_; | 
 |     for (int idx = start_idx; idx < subgop_cfg_ref_->num_steps; idx++) { | 
 |       unsigned int *ref_frame_map = | 
 |           (idx > 0) ? subgop_data_.step[idx - 1].ref_frame_map | 
 |                     : subgop_last_step_.ref_frame_map; | 
 |       if (subgop_cfg_ref_->step[idx].type_code != FRAME_TYPE_INO_SHOWEXISTING) { | 
 |         EXPECT_EQ(subgop_cfg_ref_->step[idx].num_references, | 
 |                   subgop_cfg_test_.step[idx].num_references) | 
 |             << "Error:Reference frames count doesn't match"; | 
 |       } | 
 |       // To validate the count of ref_frames and their management with user | 
 |       // config. | 
 |       for (int ref = 0; ref < subgop_cfg_test_.step[idx].num_references; | 
 |            ref++) { | 
 |         if (subgop_cfg_ref_->step[idx].type_code != | 
 |                 FRAME_TYPE_INO_SHOWEXISTING && | 
 |             subgop_data_.step[idx].is_valid_ref_frame[ref]) { | 
 |           EXPECT_EQ(subgop_cfg_ref_->step[idx].references[ref], | 
 |                     subgop_cfg_test_.step[idx].references[ref]) | 
 |               << "Error:Reference frame level doesn't match"; | 
 |           for (int buf = 0; buf < REF_FRAMES; buf++) { | 
 |             if (display_order_test_[idx][ref] == (int)ref_frame_map[buf]) break; | 
 |             EXPECT_NE(buf, REF_FRAMES - 1) | 
 |                 << "Error:Ref frame isn't part of ref_picture_buf"; | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   bool IsInputSubgopCfgUsed() { | 
 |     int num_ooo_frames_ref = 0; | 
 |     int num_ooo_frames_test = 0; | 
 |     // Encoder may choose to opt out input sub-gop config when use_altref is 0 | 
 |     // for the given sub-gop | 
 |     for (int idx = 0; idx < subgop_cfg_ref_->num_steps; idx++) { | 
 |       // Count number out-of-order frames in reference config | 
 |       num_ooo_frames_ref += | 
 |           subgop_cfg_ref_->step[idx].type_code == FRAME_TYPE_OOO_FILTERED; | 
 |       num_ooo_frames_ref += | 
 |           subgop_cfg_ref_->step[idx].type_code == FRAME_TYPE_OOO_UNFILTERED; | 
 |       // Count number out-of-order frames in test config | 
 |       num_ooo_frames_test += | 
 |           subgop_cfg_test_.step[idx].type_code == FRAME_TYPE_OOO_FILTERED; | 
 |       num_ooo_frames_test += | 
 |           subgop_cfg_test_.step[idx].type_code == FRAME_TYPE_OOO_UNFILTERED; | 
 |     } | 
 |     return num_ooo_frames_ref == num_ooo_frames_test; | 
 |   } | 
 |  | 
 |   void ValidateSubgopConfig() { | 
 |     if (is_key_frame()) return; | 
 |     subgop_cfg_ref_ = DetermineSubgopConfig(); | 
 |     if (subgop_cfg_ref_) { | 
 |       EXPECT_EQ((int8_t)subgop_size_, subgop_cfg_ref_->num_frames) | 
 |           << "Error:subgop config selection wrong"; | 
 |       subgop_info_.is_user_specified = 1; | 
 |     } | 
 |   } | 
 |  | 
 |   virtual void FramePktHook(const aom_codec_cx_pkt_t *pkt, | 
 |                             ::libaom_test::DxDataIterator *dec_iter) { | 
 |     (void)dec_iter; | 
 |     (void)pkt; | 
 |     ++frame_num_in_subgop_; | 
 |   } | 
 |  | 
 |   virtual bool HandleDecodeResult(const aom_codec_err_t res_dec, | 
 |                                   libaom_test::Decoder *decoder) { | 
 |     EXPECT_EQ(AOM_CODEC_OK, res_dec) << decoder->DecodeError(); | 
 |     if (AOM_CODEC_OK != res_dec) return 0; | 
 |     aom_codec_ctx_t *ctx_dec = decoder->GetDecoder(); | 
 |  | 
 |     int is_last_frame_in_subgop = (frame_num_in_subgop_ == subgop_info_.size); | 
 |  | 
 |     if (subgop_info_.is_user_specified || | 
 |         is_last_frame_in_subgop)  // To collect last step info of subgop in | 
 |                                   // encoder defined config | 
 |       AOM_CODEC_CONTROL_TYPECHECKED(ctx_dec, AOMD_GET_FRAME_INFO, | 
 |                                     &subgop_data_); | 
 |     if (is_last_frame_in_subgop) { | 
 |       // Validation of sub-gop structure propagation to decoder. | 
 |       if (subgop_info_.is_user_specified) { | 
 |         FillTestSubgopConfig(); | 
 |         if ((subgop_info_.is_user_specified = IsInputSubgopCfgUsed())) { | 
 |           EXPECT_EQ(subgop_code_test_, subgop_info_.pos_code) | 
 |               << "Error:subgop code doesn't match"; | 
 |           ValidateSubgopFrametype(); | 
 |           ValidatePyramidLevel(); | 
 |           if (rc_end_usage_ == AOM_Q) ValidatePyramidLevelQIndex(); | 
 |           ValidateRefBufRefresh(); | 
 |           ValidateRefFrames(); | 
 |         } | 
 |       } | 
 |       frames_from_key_ += subgop_info_.size; | 
 |       if (is_key_frame()) { | 
 |         frames_from_key_ = 0; | 
 |       } else { | 
 |         is_first_frame_in_subgop_key_ = 0; | 
 |         // To collect last step info of subgop. | 
 |         assert(subgop_data_.step_idx_dec >= 0); | 
 |         memcpy(&subgop_last_step_, | 
 |                &subgop_data_.step[subgop_data_.step_idx_dec - 1], | 
 |                sizeof(subgop_last_step_)); | 
 |       } | 
 |       ResetSubgop(); | 
 |     } | 
 |     return AOM_CODEC_OK == res_dec; | 
 |   } | 
 |  | 
 |   SubgopTestParams subgop_test_params_; | 
 |   SubGOPSetCfg user_cfg_set_; | 
 |   SubGOPCfg subgop_cfg_test_; | 
 |   SubGOPCfg *subgop_cfg_ref_; | 
 |   SubGOPInfo subgop_info_; | 
 |   SubGOPData subgop_data_; | 
 |   SubGOPStepData subgop_last_step_; | 
 |   SUBGOP_IN_GOP_CODE subgop_code_test_; | 
 |   FRAME_TYPE frame_type_test_; | 
 |   aom_rc_mode rc_end_usage_; | 
 |   int display_order_test_[MAX_SUBGOP_SIZE][REF_FRAMES]; | 
 |   int subgop_size_; | 
 |   bool is_first_frame_in_subgop_key_; | 
 |   int frames_from_key_; | 
 |   int frame_num_in_subgop_; | 
 |   unsigned int frame_num_; | 
 |   unsigned int enable_subgop_stats_; | 
 | }; | 
 |  | 
 | TEST_P(SubGopTestLarge, SubGopTest) { | 
 |   if (!is_extension_y4m(subgop_test_params_.input_file)) { | 
 |     libaom_test::I420VideoSource video( | 
 |         subgop_test_params_.input_file, subgop_test_params_.frame_w, | 
 |         subgop_test_params_.frame_h, cfg_.g_timebase.den, cfg_.g_timebase.num, | 
 |         0, kFrames); | 
 |     ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); | 
 |   } else { | 
 |     ::libaom_test::Y4mVideoSource video(subgop_test_params_.input_file, 0, | 
 |                                         kFrames); | 
 |     ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); | 
 |   } | 
 | } | 
 |  | 
 | AV1_INSTANTIATE_TEST_SUITE(SubGopTestLarge, | 
 |                            ::testing::ValuesIn(SubGopTestVectors), | 
 |                            ::testing::Values(AOM_Q, AOM_VBR | 
 |                                              // Disabled to reduce combinations. | 
 |                                              //, AOM_CQ, AOM_CBR | 
 |                                              )); | 
 |  | 
 | typedef struct { | 
 |   const char *subgop_str; | 
 |   const char *input_file; | 
 |   int frame_w; | 
 |   int frame_h; | 
 |   int lag_in_frames; | 
 | } SubgopPsnrTestParams; | 
 |  | 
 | static const SubgopPsnrTestParams SubGopPsnrTestVectors[] = { | 
 |   { subgop_config_str_preset_map[DEFAULT].preset_tag, | 
 |     "hantro_collage_w352h288.yuv", 352, 288, 35 }, | 
 |   { subgop_config_str_preset_map[DEFAULT].preset_tag, "desktop1.320_180.yuv", | 
 |     320, 180, 35 }, | 
 |  | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, | 
 |     "hantro_collage_w352h288.yuv", 352, 288, 35 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 320, 240, 35 }, | 
 |   // TODO(any): Enable after fix | 
 |   /* { subgop_config_str_preset_map[ENHANCE].preset_tag, "paris_352_288_30.y4m", | 
 |      352, 288, 35 }, | 
 |      { subgop_config_str_preset_map[ENHANCE].preset_tag, "screendata.y4m", 640, | 
 |      480, 35 }, | 
 |      { subgop_config_str_preset_map[ENHANCE].preset_tag, "paris_352_288_30.y4m", | 
 |      352, 288, 35 }, */ | 
 |  | 
 |   { subgop_config_str_preset_map[ASYMMETRIC].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 320, 240, 35 }, | 
 |   // TODO(any): Enable after fix | 
 |   /* { subgop_config_str_preset_map[ASYMMETRIC].preset_tag, | 
 |     "desktop1.320_180.yuv", 320, 180, 35 }, */ | 
 |  | 
 |   { subgop_config_str_preset_map[TEMPORAL_SCALABLE].preset_tag, | 
 |     "hantro_collage_w352h288.yuv", 352, 288, 35 }, | 
 |  | 
 |   // TODO(any): Enable after fix | 
 |   /* { subgop_config_str_preset_map[LOW_DELAY].preset_tag, | 
 |      "paris_352_288_30.y4m", 352, 288, 0 }, | 
 |      { subgop_config_str_preset_map[LOW_DELAY].preset_tag, | 
 |      "desktop1.320_180.yuv", 320, 180, 0 }, */ | 
 | }; | 
 |  | 
 | std::ostream &operator<<(std::ostream &os, | 
 |                          const SubgopPsnrTestParams &test_arg) { | 
 |   return os << "SubgopPsnrTestParams { sub_gop_config:" << test_arg.subgop_str | 
 |             << " source_file:" << test_arg.input_file | 
 |             << " frame_width:" << test_arg.frame_w | 
 |             << " frame_height:" << test_arg.frame_h << " cpu_used:" << kCpuUsed | 
 |             << " lag_in_frames:" << test_arg.lag_in_frames << " }"; | 
 | } | 
 |  | 
 | class SubGopPSNRCheckTestLarge | 
 |     : public ::libaom_test::CodecTestWith2Params<SubgopPsnrTestParams, | 
 |                                                  aom_rc_mode>, | 
 |       public ::libaom_test::EncoderTest { | 
 |  protected: | 
 |   SubGopPSNRCheckTestLarge() | 
 |       : EncoderTest(GET_PARAM(0)), test_params_(GET_PARAM(1)), | 
 |         rc_end_usage_(GET_PARAM(2)) { | 
 |     Reset(); | 
 |   } | 
 |   virtual ~SubGopPSNRCheckTestLarge() {} | 
 |  | 
 |   void Reset() { | 
 |     frame_num_ = 0; | 
 |     total_psnr_ = 0.0; | 
 |     enable_subgop_ = 0; | 
 |   } | 
 |  | 
 |   virtual void SetUp() { | 
 |     InitializeConfig(); | 
 |     SetMode(::libaom_test::kOnePassGood); | 
 |     cfg_.g_threads = 1; | 
 |     cfg_.g_lag_in_frames = test_params_.lag_in_frames; | 
 |     cfg_.rc_end_usage = rc_end_usage_; | 
 |     cfg_.rc_undershoot_pct = 100; | 
 |     cfg_.rc_overshoot_pct = 100; | 
 |     init_flags_ = AOM_CODEC_USE_PSNR; | 
 |   } | 
 |  | 
 |   virtual void PSNRPktHook(const aom_codec_cx_pkt_t *pkt) { | 
 |     // Accumulate total psnr | 
 |     total_psnr_ += pkt->data.psnr.psnr[0]; | 
 |     frame_num_++; | 
 |   } | 
 |  | 
 |   double GetAveragePsnr() const { | 
 |     if (frame_num_) return total_psnr_ / frame_num_; | 
 |     return 0.0; | 
 |   } | 
 |  | 
 |   virtual void PreEncodeFrameHook(::libaom_test::VideoSource *video, | 
 |                                   ::libaom_test::Encoder *encoder) { | 
 |     if (video->frame() == 0) { | 
 |       encoder->Control(AOME_SET_CPUUSED, kCpuUsed); | 
 |       if (rc_end_usage_ == AOM_Q || rc_end_usage_ == AOM_CQ) { | 
 |         encoder->Control(AOME_SET_QP, 210); | 
 |       } | 
 |       if (enable_subgop_) | 
 |         encoder->Control(AV1E_SET_SUBGOP_CONFIG_STR, test_params_.subgop_str); | 
 |     } | 
 |   } | 
 |   unsigned int enable_subgop_; | 
 |   SubgopPsnrTestParams test_params_; | 
 |  | 
 |  private: | 
 |   aom_rc_mode rc_end_usage_; | 
 |   double total_psnr_; | 
 |   unsigned int frame_num_; | 
 | }; | 
 |  | 
 | TEST_P(SubGopPSNRCheckTestLarge, SubGopPSNRCheck) { | 
 |   std::unique_ptr<libaom_test::VideoSource> video; | 
 |   const double psnr_diff_thresh = 0.5; | 
 |   if (is_extension_y4m(test_params_.input_file)) { | 
 |     video.reset( | 
 |         new libaom_test::Y4mVideoSource(test_params_.input_file, 0, kFrames)); | 
 |   } else { | 
 |     video.reset(new libaom_test::YUVVideoSource( | 
 |         test_params_.input_file, AOM_IMG_FMT_I420, test_params_.frame_w, | 
 |         test_params_.frame_h, 30, 1, 0, kFrames)); | 
 |   } | 
 |  | 
 |   // Encode with no sub-gop configuration | 
 |   ASSERT_NO_FATAL_FAILURE(RunLoop(video.get())); | 
 |   const double psnr_no_subgop_ = GetAveragePsnr(); | 
 |   Reset(); | 
 |  | 
 |   // Encode with default sub-gop configuration | 
 |   enable_subgop_ = 1; | 
 |   ASSERT_NO_FATAL_FAILURE(RunLoop(video.get())); | 
 |   const double psnr_subgop_ = GetAveragePsnr(); | 
 |  | 
 |   const double psnr_diff = psnr_subgop_ - psnr_no_subgop_; | 
 |   EXPECT_LE(fabs(psnr_diff), psnr_diff_thresh); | 
 | } | 
 |  | 
 | // TODO(any) : Enable AOM_CBR after fix | 
 | AV1_INSTANTIATE_TEST_SUITE(SubGopPSNRCheckTestLarge, | 
 |                            ::testing::ValuesIn(SubGopPsnrTestVectors), | 
 |                            ::testing::Values(AOM_Q, AOM_VBR, | 
 |                                              AOM_CQ /*, AOM_CBR*/)); | 
 |  | 
 | typedef struct { | 
 |   const char *subgop_str; | 
 |   const char *input_file; | 
 |   int frame_w; | 
 |   int frame_h; | 
 |   int lag_in_frames; | 
 |   int max_gf_interval; | 
 | } SubGopSwitchTestParams; | 
 |  | 
 | std::ostream &operator<<(std::ostream &os, | 
 |                          const SubGopSwitchTestParams &test_arg) { | 
 |   return os << "SubGopSwitchTestParams { sub_gop_config:" << test_arg.subgop_str | 
 |             << " source_file:" << test_arg.input_file | 
 |             << " frame_width:" << test_arg.frame_w | 
 |             << " frame_height:" << test_arg.frame_h << " cpu_used:" << kCpuUsed | 
 |             << " lag_in_frames:" << test_arg.lag_in_frames | 
 |             << " max_gf_interval:" << test_arg.max_gf_interval << " }"; | 
 | } | 
 |  | 
 | static const SubGopSwitchTestParams SubgopSwitchTestVectors[] = { | 
 |   { subgop_config_str_preset_map[DEFAULT].preset_tag, "niklas_640_480_30.yuv", | 
 |     640, 480, 35, 16 }, | 
 |   /* TODO(sarahparker/debargha): Enable after adding default 32 subgop config. | 
 |    { subgop_config_str_preset_map[DEFAULT].preset_tag, "niklas_640_480_30.yuv", | 
 |     640, 480, 35, 32 },*/ | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, "desktop1.320_180.yuv", | 
 |     320, 180, 35, 16 }, | 
 |   { subgop_config_str_preset_map[ENHANCE].preset_tag, | 
 |     "hantro_collage_w352h288.yuv", 352, 288, 35, 16 }, | 
 |   { subgop_config_str_preset_map[ASYMMETRIC].preset_tag, | 
 |     "pixel_capture_w320h240.yuv", 320, 240, 35, 16 }, | 
 |   { subgop_config_str_preset_map[TEMPORAL_SCALABLE].preset_tag, | 
 |     "paris_352_288_30.y4m", 352, 288, 35, 16 }, | 
 |   { subgop_config_str_preset_map[LOW_DELAY].preset_tag, "screendata.y4m", 640, | 
 |     480, 0, 16 }, | 
 |   { subgop_config_str_preset_map[LOW_DELAY].preset_tag, "screendata.y4m", 640, | 
 |     480, 0, 32 }, | 
 | }; | 
 |  | 
 | using libaom_test::ACMRandom; | 
 | class SubGopSwitchingTestLarge | 
 |     : public ::libaom_test::CodecTestWith2Params<SubGopSwitchTestParams, | 
 |                                                  aom_rc_mode>, | 
 |       public ::libaom_test::EncoderTest { | 
 |  protected: | 
 |   SubGopSwitchingTestLarge() | 
 |       : EncoderTest(GET_PARAM(0)), test_params_(GET_PARAM(1)), | 
 |         rc_end_usage_(GET_PARAM(2)) { | 
 |     last_subgop_str_ = NULL; | 
 |     num_subgop_cfg_used_ = 0; | 
 |     rnd_.Reset(ACMRandom::DeterministicSeed()); | 
 |     ResetSubgop(); | 
 |   } | 
 |   virtual ~SubGopSwitchingTestLarge() {} | 
 |  | 
 |   virtual void SetUp() { | 
 |     InitializeConfig(); | 
 |     SetMode(::libaom_test::kOnePassGood); | 
 |     cfg_.g_threads = 1; | 
 |     cfg_.rc_end_usage = rc_end_usage_; | 
 |     cfg_.rc_target_bitrate = 200; | 
 |     cfg_.rc_undershoot_pct = 100; | 
 |     cfg_.rc_overshoot_pct = 100; | 
 |     // Keep sufficient distance between keyframes to let subgop configs be used. | 
 |     cfg_.kf_min_dist = 65; | 
 |     cfg_.kf_max_dist = 9999; | 
 |     cfg_.g_lag_in_frames = test_params_.lag_in_frames; | 
 |   } | 
 |  | 
 |   void ResetSubgop() { | 
 |     frame_num_in_subgop_ = 0; | 
 |     subgop_size_ = 0; | 
 |     memset(&subgop_info_, 0, sizeof(subgop_info_)); | 
 |   } | 
 |  | 
 |   bool GetRandSwitch() { return !(rnd_.Rand8() & 1); } | 
 |  | 
 |   int GetRandGFIntervalEnh() { | 
 |     const int subgop_size_enh[] = { 6, 8, 10, 11, 12, 13, 14, 15, 16 }; | 
 |     const int length = sizeof(subgop_size_enh) / sizeof(subgop_size_enh[0]); | 
 |     const int idx = rnd_.Rand8() % length; | 
 |  | 
 |     return subgop_size_enh[idx]; | 
 |   } | 
 |  | 
 |   void set_subgop_config(::libaom_test::Encoder *encoder) { | 
 |     const bool switch_subgop_cfg = GetRandSwitch(); | 
 |     if (!switch_subgop_cfg) return; | 
 |  | 
 |     // Switch between input sub-gop and no sub-gop config and configure the | 
 |     // encoder | 
 |     const char *subgop_str = last_subgop_str_ ? NULL : test_params_.subgop_str; | 
 |     int max_gf_interval = test_params_.max_gf_interval; | 
 |     // Get max gf interval for enh config | 
 |     if (subgop_str && !strcmp(subgop_str, "enh")) | 
 |       max_gf_interval = GetRandGFIntervalEnh(); | 
 |  | 
 |     // Set subgop config string | 
 |     encoder->Control(AV1E_SET_SUBGOP_CONFIG_STR, subgop_str); | 
 |  | 
 |     // Set max gf interval | 
 |     if (subgop_str) encoder->Control(AV1E_SET_MAX_GF_INTERVAL, max_gf_interval); | 
 |  | 
 |     // Keep min gf interval same as max gf interval in most cases, to ensure | 
 |     // that user-provided subgop config is used. | 
 |     int min_gf_interval = max_gf_interval; | 
 |     // In case of no subgop config / enhanced subgop config, test arbitrary gf | 
 |     // intervals by setting a lower min gf interval. | 
 |     if (!subgop_str || !strcmp(subgop_str, "enh")) min_gf_interval = 6; | 
 |  | 
 |     // Set min gf interval | 
 |     encoder->Control(AV1E_SET_MIN_GF_INTERVAL, min_gf_interval); | 
 |  | 
 |     last_subgop_str_ = subgop_str; | 
 |   } | 
 |  | 
 |   virtual void PreEncodeFrameHook(::libaom_test::VideoSource *video, | 
 |                                   ::libaom_test::Encoder *encoder) { | 
 |     if (video->frame() == 0) { | 
 |       encoder->Control(AOME_SET_CPUUSED, kCpuUsed); | 
 |       if (rc_end_usage_ == AOM_Q || rc_end_usage_ == AOM_CQ) { | 
 |         encoder->Control(AOME_SET_QP, 210); | 
 |       } | 
 |       set_subgop_config(encoder); | 
 |     } | 
 |  | 
 |     // Configure sub-gop string before sub-gop decision | 
 |     if (frame_num_in_subgop_ == subgop_size_) { | 
 |       ResetSubgop(); | 
 |       set_subgop_config(encoder); | 
 |     } | 
 |   } | 
 |  | 
 |   virtual bool HandleEncodeResult(::libaom_test::VideoSource *video, | 
 |                                   libaom_test::Encoder *encoder) { | 
 |     (void)video; | 
 |     // Get sub-gop info at beginning of the sub-gop | 
 |     if (!frame_num_in_subgop_) { | 
 |       FRAME_TYPE frame_type = FRAME_TYPES; | 
 |  | 
 |       // Get current frame type | 
 |       encoder->Control(AV1E_GET_FRAME_TYPE, &frame_type); | 
 |       assert(frame_type != FRAME_TYPES); | 
 |       // Get subgop config | 
 |       encoder->Control(AV1E_GET_SUB_GOP_CONFIG, &subgop_info_); | 
 |  | 
 |       // Compute sub-gop size | 
 |       subgop_size_ = subgop_info_.gf_interval; | 
 |       // Include KF in sub-gop size | 
 |       if (frame_type == KEY_FRAME | 
 | #if CONFIG_KEY_OVERLAY | 
 |           || subgop_info_.has_key_overlay | 
 | #endif  // CONFIG_KEY_OVERLAY | 
 |       ) | 
 |         subgop_size_++; | 
 |  | 
 |       // Update count of subgop cfg usage by the encoder | 
 |       num_subgop_cfg_used_ += subgop_info_.is_user_specified; | 
 |     } | 
 |     return 1; | 
 |   } | 
 |  | 
 |   virtual void FramePktHook(const aom_codec_cx_pkt_t *pkt, | 
 |                             ::libaom_test::DxDataIterator *dec_iter) { | 
 |     (void)dec_iter; | 
 |     (void)pkt; | 
 |     ++frame_num_in_subgop_; | 
 |   } | 
 |  | 
 |   virtual bool HandleDecodeResult(const aom_codec_err_t res_dec, | 
 |                                   libaom_test::Decoder *decoder) { | 
 |     EXPECT_EQ(AOM_CODEC_OK, res_dec) << decoder->DecodeError(); | 
 |     if (AOM_CODEC_OK != res_dec) return 0; | 
 |  | 
 |     return AOM_CODEC_OK == res_dec; | 
 |   } | 
 |   SubGopSwitchTestParams test_params_; | 
 |   unsigned int num_subgop_cfg_used_; | 
 |  | 
 |  private: | 
 |   ACMRandom rnd_; | 
 |   aom_rc_mode rc_end_usage_; | 
 |   SubGOPInfo subgop_info_; | 
 |   unsigned int frame_num_in_subgop_; | 
 |   unsigned int subgop_size_; | 
 |   const char *last_subgop_str_; | 
 | }; | 
 |  | 
 | TEST_P(SubGopSwitchingTestLarge, SubGopSwitching) { | 
 |   std::unique_ptr<libaom_test::VideoSource> video; | 
 |   if (is_extension_y4m(test_params_.input_file)) { | 
 |     video.reset( | 
 |         new libaom_test::Y4mVideoSource(test_params_.input_file, 0, kFrames)); | 
 |   } else { | 
 |     video.reset(new libaom_test::YUVVideoSource( | 
 |         test_params_.input_file, AOM_IMG_FMT_I420, test_params_.frame_w, | 
 |         test_params_.frame_h, 30, 1, 0, kFrames)); | 
 |   } | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE(RunLoop(video.get())); | 
 |  | 
 |   // Check input config is used by the encoder | 
 |   EXPECT_TRUE(num_subgop_cfg_used_); | 
 | } | 
 |  | 
 | AV1_INSTANTIATE_TEST_SUITE(SubGopSwitchingTestLarge, | 
 |                            ::testing::ValuesIn(SubgopSwitchTestVectors), | 
 |                            ::testing::Values(AOM_Q, AOM_VBR, AOM_CQ, AOM_CBR)); | 
 | }  // namespace |