1 /* 2 * Copyright (c) 2016, Alliance for Open Media. All rights reserved. 3 * 4 * This source code is subject to the terms of the BSD 2 Clause License and 5 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License 6 * was not distributed with this source code in the LICENSE file, you can 7 * obtain it at www.aomedia.org/license/software. If the Alliance for Open 8 * Media Patent License 1.0 was not distributed with this source code in the 9 * PATENTS file, you can obtain it at www.aomedia.org/license/patent. 10 */ 11 #ifndef AOM_TEST_ENCODE_TEST_DRIVER_H_ 12 #define AOM_TEST_ENCODE_TEST_DRIVER_H_ 13 14 #include <string> 15 #include <vector> 16 17 #include "gtest/gtest.h" 18 19 #include "config/aom_config.h" 20 21 #if CONFIG_AV1_ENCODER 22 #include "aom/aomcx.h" 23 #endif 24 #include "aom/aom_encoder.h" 25 26 namespace libaom_test { 27 28 class CodecFactory; 29 class VideoSource; 30 31 enum TestMode { kRealTime, kOnePassGood, kTwoPassGood, kAllIntra }; 32 #define ALL_TEST_MODES \ 33 ::testing::Values(::libaom_test::kRealTime, ::libaom_test::kOnePassGood, \ 34 ::libaom_test::kTwoPassGood) 35 36 #define ONE_PASS_TEST_MODES \ 37 ::testing::Values(::libaom_test::kRealTime, ::libaom_test::kOnePassGood) 38 39 #define TWO_PASS_TEST_MODES ::testing::Values(::libaom_test::kTwoPassGood) 40 41 #define NONREALTIME_TEST_MODES \ 42 ::testing::Values(::libaom_test::kOnePassGood, ::libaom_test::kTwoPassGood) 43 44 // Provides an object to handle the libaom get_cx_data() iteration pattern 45 class CxDataIterator { 46 public: CxDataIterator(aom_codec_ctx_t * encoder)47 explicit CxDataIterator(aom_codec_ctx_t *encoder) 48 : encoder_(encoder), iter_(nullptr) {} 49 Next()50 const aom_codec_cx_pkt_t *Next() { 51 return aom_codec_get_cx_data(encoder_, &iter_); 52 } 53 54 private: 55 aom_codec_ctx_t *encoder_; 56 aom_codec_iter_t iter_; 57 }; 58 59 // Implements an in-memory store for libaom twopass statistics 60 class TwopassStatsStore { 61 public: Append(const aom_codec_cx_pkt_t & pkt)62 void Append(const aom_codec_cx_pkt_t &pkt) { 63 buffer_.append(reinterpret_cast<char *>(pkt.data.twopass_stats.buf), 64 pkt.data.twopass_stats.sz); 65 } 66 buf()67 aom_fixed_buf_t buf() { 68 const aom_fixed_buf_t buf = { &buffer_[0], buffer_.size() }; 69 return buf; 70 } 71 Reset()72 void Reset() { buffer_.clear(); } 73 74 protected: 75 std::string buffer_; 76 }; 77 78 // Provides a simplified interface to manage one video encoding pass, given 79 // a configuration and video source. 80 // 81 // TODO(jkoleszar): The exact services it provides and the appropriate 82 // level of abstraction will be fleshed out as more tests are written. 83 class Encoder { 84 public: Encoder(aom_codec_enc_cfg_t cfg,const aom_codec_flags_t init_flags,TwopassStatsStore * stats)85 Encoder(aom_codec_enc_cfg_t cfg, const aom_codec_flags_t init_flags, 86 TwopassStatsStore *stats) 87 : cfg_(cfg), init_flags_(init_flags), stats_(stats) { 88 memset(&encoder_, 0, sizeof(encoder_)); 89 } 90 ~Encoder()91 virtual ~Encoder() { aom_codec_destroy(&encoder_); } 92 GetCxData()93 CxDataIterator GetCxData() { return CxDataIterator(&encoder_); } 94 95 void InitEncoder(VideoSource *video); 96 GetPreviewFrame()97 const aom_image_t *GetPreviewFrame() { 98 return aom_codec_get_preview_frame(&encoder_); 99 } 100 // This is a thin wrapper around aom_codec_encode(), so refer to 101 // aom_encoder.h for its semantics. 102 void EncodeFrame(VideoSource *video, aom_enc_frame_flags_t frame_flags); 103 104 // Convenience wrapper for EncodeFrame() EncodeFrame(VideoSource * video)105 void EncodeFrame(VideoSource *video) { EncodeFrame(video, 0); } 106 Control(int ctrl_id,int arg)107 void Control(int ctrl_id, int arg) { 108 const aom_codec_err_t res = aom_codec_control(&encoder_, ctrl_id, arg); 109 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 110 } 111 Control(int ctrl_id,int * arg)112 void Control(int ctrl_id, int *arg) { 113 const aom_codec_err_t res = aom_codec_control(&encoder_, ctrl_id, arg); 114 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 115 } 116 Control(int ctrl_id,struct aom_scaling_mode * arg)117 void Control(int ctrl_id, struct aom_scaling_mode *arg) { 118 const aom_codec_err_t res = aom_codec_control(&encoder_, ctrl_id, arg); 119 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 120 } 121 Control(int ctrl_id,struct aom_svc_layer_id * arg)122 void Control(int ctrl_id, struct aom_svc_layer_id *arg) { 123 const aom_codec_err_t res = aom_codec_control(&encoder_, ctrl_id, arg); 124 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 125 } 126 Control(int ctrl_id,struct aom_svc_ref_frame_config * arg)127 void Control(int ctrl_id, struct aom_svc_ref_frame_config *arg) { 128 const aom_codec_err_t res = aom_codec_control(&encoder_, ctrl_id, arg); 129 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 130 } 131 Control(int ctrl_id,struct aom_svc_ref_frame_comp_pred * arg)132 void Control(int ctrl_id, struct aom_svc_ref_frame_comp_pred *arg) { 133 const aom_codec_err_t res = aom_codec_control(&encoder_, ctrl_id, arg); 134 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 135 } 136 Control(int ctrl_id,struct aom_svc_params * arg)137 void Control(int ctrl_id, struct aom_svc_params *arg) { 138 const aom_codec_err_t res = aom_codec_control(&encoder_, ctrl_id, arg); 139 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 140 } 141 Control(int ctrl_id,struct aom_ext_part_funcs * arg)142 void Control(int ctrl_id, struct aom_ext_part_funcs *arg) { 143 const aom_codec_err_t res = aom_codec_control(&encoder_, ctrl_id, arg); 144 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 145 } 146 147 #if CONFIG_AV1_ENCODER Control(int ctrl_id,aom_active_map_t * arg)148 void Control(int ctrl_id, aom_active_map_t *arg) { 149 const aom_codec_err_t res = aom_codec_control(&encoder_, ctrl_id, arg); 150 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 151 } 152 #endif 153 SetOption(const char * name,const char * value)154 void SetOption(const char *name, const char *value) { 155 const aom_codec_err_t res = aom_codec_set_option(&encoder_, name, value); 156 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 157 } 158 Config(const aom_codec_enc_cfg_t * cfg)159 void Config(const aom_codec_enc_cfg_t *cfg) { 160 const aom_codec_err_t res = aom_codec_enc_config_set(&encoder_, cfg); 161 ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError(); 162 cfg_ = *cfg; 163 } 164 165 protected: 166 virtual aom_codec_iface_t *CodecInterface() const = 0; 167 EncoderError()168 const char *EncoderError() { 169 const char *detail = aom_codec_error_detail(&encoder_); 170 return detail ? detail : aom_codec_error(&encoder_); 171 } 172 173 // Encode an image 174 void EncodeFrameInternal(const VideoSource &video, 175 aom_enc_frame_flags_t frame_flags); 176 177 // Flush the encoder on EOS 178 void Flush(); 179 180 aom_codec_ctx_t encoder_; 181 aom_codec_enc_cfg_t cfg_; 182 aom_codec_flags_t init_flags_; 183 TwopassStatsStore *stats_; 184 }; 185 186 // Common test functionality for all Encoder tests. 187 // 188 // This class is a mixin which provides the main loop common to all 189 // encoder tests. It provides hooks which can be overridden by subclasses 190 // to implement each test's specific behavior, while centralizing the bulk 191 // of the boilerplate. Note that it doesn't inherit the gtest testing 192 // classes directly, so that tests can be parameterized differently. 193 class EncoderTest { 194 protected: EncoderTest(const CodecFactory * codec)195 explicit EncoderTest(const CodecFactory *codec) 196 : codec_(codec), abort_(false), init_flags_(0), frame_flags_(0), 197 mode_(kRealTime) { 198 // Default to 1 thread. 199 cfg_.g_threads = 1; 200 } 201 202 virtual ~EncoderTest() = default; 203 204 // Initialize the cfg_ member with the default configuration for the 205 // TestMode enum and maps the TestMode enum to the passes_ variable. 206 void InitializeConfig(TestMode mode); 207 208 // Set encoder flag. set_init_flags(aom_codec_flags_t flag)209 void set_init_flags(aom_codec_flags_t flag) { init_flags_ = flag; } 210 211 // Main loop 212 virtual void RunLoop(VideoSource *video); 213 214 // Hook to be called at the beginning of a pass. BeginPassHook(unsigned int)215 virtual void BeginPassHook(unsigned int /*pass*/) {} 216 217 // Hook to be called at the end of a pass. EndPassHook()218 virtual void EndPassHook() {} 219 220 // Hook to be called before encoding a frame. PreEncodeFrameHook(VideoSource *,Encoder *)221 virtual void PreEncodeFrameHook(VideoSource * /*video*/, 222 Encoder * /*encoder*/) {} 223 PostEncodeFrameHook(Encoder *)224 virtual void PostEncodeFrameHook(Encoder * /*encoder*/) {} 225 226 // Hook to be called on every compressed data packet. FramePktHook(const aom_codec_cx_pkt_t *)227 virtual void FramePktHook(const aom_codec_cx_pkt_t * /*pkt*/) {} 228 229 // Hook to be called on every PSNR packet. PSNRPktHook(const aom_codec_cx_pkt_t *)230 virtual void PSNRPktHook(const aom_codec_cx_pkt_t * /*pkt*/) {} 231 232 // Hook to be called on every first pass stats packet. StatsPktHook(const aom_codec_cx_pkt_t *)233 virtual void StatsPktHook(const aom_codec_cx_pkt_t * /*pkt*/) {} 234 235 // Calculates SSIM at frame level. CalculateFrameLevelSSIM(const aom_image_t *,const aom_image_t *,aom_bit_depth_t,unsigned int)236 virtual void CalculateFrameLevelSSIM(const aom_image_t * /*img_src*/, 237 const aom_image_t * /*img_enc*/, 238 aom_bit_depth_t /*bit_depth*/, 239 unsigned int /*input_bit_depth*/) {} 240 241 // Hook to determine whether the encode loop should continue. Continue()242 virtual bool Continue() const { 243 return !(::testing::Test::HasFatalFailure() || abort_); 244 } 245 246 // Hook to determine whether to decode frame after encoding DoDecode()247 virtual bool DoDecode() const { return true; } 248 249 // Hook to determine whether to decode invisible frames after encoding DoDecodeInvisible()250 virtual bool DoDecodeInvisible() const { return true; } 251 252 // Hook to handle encode/decode mismatch 253 virtual void MismatchHook(const aom_image_t *img1, const aom_image_t *img2); 254 255 // Hook to be called on every decompressed frame. DecompressedFrameHook(const aom_image_t &,aom_codec_pts_t)256 virtual void DecompressedFrameHook(const aom_image_t & /*img*/, 257 aom_codec_pts_t /*pts*/) {} 258 259 // Hook to be called to handle decode result. Return true to continue. HandleDecodeResult(const aom_codec_err_t res_dec,Decoder * decoder)260 virtual bool HandleDecodeResult(const aom_codec_err_t res_dec, 261 Decoder *decoder) { 262 EXPECT_EQ(AOM_CODEC_OK, res_dec) << decoder->DecodeError(); 263 return AOM_CODEC_OK == res_dec; 264 } 265 GetNumSpatialLayers()266 virtual int GetNumSpatialLayers() { return 1; } 267 268 // Hook that can modify the encoder's output data MutateEncoderOutputHook(const aom_codec_cx_pkt_t * pkt)269 virtual const aom_codec_cx_pkt_t *MutateEncoderOutputHook( 270 const aom_codec_cx_pkt_t *pkt) { 271 return pkt; 272 } 273 274 const CodecFactory *codec_; 275 bool abort_; 276 aom_codec_enc_cfg_t cfg_; 277 unsigned int passes_; 278 TwopassStatsStore stats_; 279 aom_codec_flags_t init_flags_; 280 aom_enc_frame_flags_t frame_flags_; 281 TestMode mode_; 282 }; 283 284 } // namespace libaom_test 285 286 #endif // AOM_TEST_ENCODE_TEST_DRIVER_H_ 287