| /* | 
 |  * Copyright (c) 2022, Alliance for Open Media. All rights reserved | 
 |  * | 
 |  * This source code is subject to the terms of the BSD 2 Clause License and | 
 |  * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License | 
 |  * was not distributed with this source code in the LICENSE file, you can | 
 |  * obtain it at www.aomedia.org/license/software. 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 www.aomedia.org/license/patent. | 
 |  */ | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <algorithm> | 
 | #include <memory> | 
 | #include <numeric> | 
 | #include <vector> | 
 |  | 
 | #include "config/aom_config.h" | 
 |  | 
 | #include "aom/aom_encoder.h" | 
 |  | 
 | #include "av1/av1_cx_iface.h" | 
 | #include "av1/av1_iface_common.h" | 
 | #include "av1/encoder/encoder.h" | 
 | #include "av1/encoder/ethread.h" | 
 | #include "av1/encoder/firstpass.h" | 
 | #include "av1/encoder/temporal_filter.h" | 
 | #include "av1/ducky_encode.h" | 
 |  | 
 | #include "common/tools_common.h" | 
 |  | 
 | namespace aom { | 
 | struct EncoderResource { | 
 |   FILE *in_file; | 
 |   STATS_BUFFER_CTX *stats_buf_ctx; | 
 |   FIRSTPASS_STATS *stats_buffer; | 
 |   aom_image_t img; | 
 |   AV1_PRIMARY *ppi; | 
 |   int lookahead_push_count; | 
 |   int encode_frame_count;  // Use in second pass only | 
 | }; | 
 |  | 
 | class DuckyEncode::EncodeImpl { | 
 |  public: | 
 |   VideoInfo video_info; | 
 |   int g_usage; | 
 |   int max_ref_frames; | 
 |   int speed; | 
 |   enum aom_rc_mode rc_end_usage; | 
 |   aom_rational64_t timestamp_ratio; | 
 |   std::vector<FIRSTPASS_STATS> stats_list; | 
 |   EncoderResource enc_resource; | 
 | }; | 
 |  | 
 | DuckyEncode::DuckyEncode(const VideoInfo &video_info, int max_ref_frames, | 
 |                          int speed) { | 
 |   impl_ptr_ = std::unique_ptr<EncodeImpl>(new EncodeImpl()); | 
 |   impl_ptr_->video_info = video_info; | 
 |   impl_ptr_->g_usage = GOOD; | 
 |   impl_ptr_->max_ref_frames = max_ref_frames; | 
 |   impl_ptr_->speed = speed; | 
 |   impl_ptr_->rc_end_usage = AOM_Q; | 
 |   // TODO(angiebird): Set timestamp_ratio properly | 
 |   // timestamp_ratio.den = cfg->g_timebase.den; | 
 |   // timestamp_ratio.num = (int64_t)cfg->g_timebase.num * TICKS_PER_SEC; | 
 |   impl_ptr_->timestamp_ratio = { 1, 1 }; | 
 |   // TODO(angiebird): How to set ptsvol and duration? | 
 | } | 
 |  | 
 | DuckyEncode::~DuckyEncode() {} | 
 |  | 
 | static AV1EncoderConfig GetEncoderConfig(const VideoInfo &video_info, | 
 |                                          int g_usage, aom_enc_pass pass) { | 
 |   const aom_codec_iface *codec = aom_codec_av1_cx(); | 
 |   aom_codec_enc_cfg_t cfg; | 
 |   aom_codec_enc_config_default(codec, &cfg, g_usage); | 
 |   cfg.g_w = video_info.frame_width; | 
 |   cfg.g_h = video_info.frame_height; | 
 |   cfg.g_pass = pass; | 
 |   // g_timebase is the inverse of frame_rate | 
 |   cfg.g_timebase.num = video_info.frame_rate.den; | 
 |   cfg.g_timebase.den = video_info.frame_rate.num; | 
 |   AV1EncoderConfig oxcf = av1_get_encoder_config(&cfg); | 
 |   // TODO(angiebird): Why didn't we init use_highbitdepth in | 
 |   // av1_get_encoder_config()? | 
 |   oxcf.use_highbitdepth = 0; | 
 |  | 
 |   // TODO(jingning): Change this to 35 when the baseline rate control | 
 |   // logic is in place. | 
 |   // Force maximum look ahead buffer to be 19. This will disable the use | 
 |   // of maximum 32 GOP length. | 
 |   oxcf.gf_cfg.lag_in_frames = 19; | 
 |  | 
 |   return oxcf; | 
 | } | 
 |  | 
 | static STATS_BUFFER_CTX *CreateStatsBufferCtx(int frame_count, | 
 |                                               FIRSTPASS_STATS **stats_buffer) { | 
 |   STATS_BUFFER_CTX *stats_buf_ctx = new STATS_BUFFER_CTX; | 
 |   // +2 is for total_stats and total_left_stats | 
 |   *stats_buffer = new FIRSTPASS_STATS[frame_count + 2]; | 
 |   stats_buf_ctx->stats_in_start = *stats_buffer; | 
 |   stats_buf_ctx->stats_in_end = stats_buf_ctx->stats_in_start; | 
 |   stats_buf_ctx->stats_in_buf_end = stats_buf_ctx->stats_in_start + frame_count; | 
 |   stats_buf_ctx->total_stats = stats_buf_ctx->stats_in_buf_end; | 
 |   stats_buf_ctx->total_left_stats = | 
 |       stats_buf_ctx->stats_in_start + frame_count + 1; | 
 |   av1_twopass_zero_stats(stats_buf_ctx->total_left_stats); | 
 |   av1_twopass_zero_stats(stats_buf_ctx->total_stats); | 
 |   return stats_buf_ctx; | 
 | } | 
 |  | 
 | static void DestroyStatsBufferCtx(STATS_BUFFER_CTX **stats_buf_context, | 
 |                                   FIRSTPASS_STATS **stats_buffer) { | 
 |   (*stats_buf_context)->stats_in_start = nullptr; | 
 |   (*stats_buf_context)->stats_in_end = nullptr; | 
 |   (*stats_buf_context)->stats_in_buf_end = nullptr; | 
 |   (*stats_buf_context)->total_stats = nullptr; | 
 |   (*stats_buf_context)->total_left_stats = nullptr; | 
 |   delete *stats_buf_context; | 
 |   *stats_buf_context = nullptr; | 
 |   delete[](*stats_buffer); | 
 |   *stats_buffer = nullptr; | 
 | } | 
 |  | 
 | static FIRSTPASS_STATS ComputeTotalStats( | 
 |     const std::vector<FIRSTPASS_STATS> &stats_list) { | 
 |   FIRSTPASS_STATS total_stats = {}; | 
 |   for (size_t i = 0; i < stats_list.size(); ++i) { | 
 |     av1_accumulate_stats(&total_stats, &stats_list[i]); | 
 |   } | 
 |   return total_stats; | 
 | } | 
 |  | 
 | static EncoderResource InitEncoder( | 
 |     const VideoInfo &video_info, int g_usage, enum aom_rc_mode rc_end_usage, | 
 |     aom_enc_pass pass, const std::vector<FIRSTPASS_STATS> *stats_list, | 
 |     int max_ref_frames, int speed) { | 
 |   EncoderResource enc_resource = {}; | 
 |   enc_resource.in_file = fopen(video_info.file_path.c_str(), "r"); | 
 |   enc_resource.lookahead_push_count = 0; | 
 |   aom_img_alloc(&enc_resource.img, video_info.img_fmt, video_info.frame_width, | 
 |                 video_info.frame_height, /*align=*/1); | 
 |   AV1EncoderConfig oxcf = GetEncoderConfig(video_info, g_usage, pass); | 
 |   oxcf.dec_model_cfg.decoder_model_info_present_flag = 0; | 
 |   oxcf.dec_model_cfg.display_model_info_present_flag = 0; | 
 |   oxcf.ref_frm_cfg.max_reference_frames = max_ref_frames; | 
 |   oxcf.speed = speed; | 
 |   av1_initialize_enc(g_usage, rc_end_usage); | 
 |   AV1_PRIMARY *ppi = | 
 |       av1_create_primary_compressor(nullptr, | 
 |                                     /*num_lap_buffers=*/0, &oxcf); | 
 |   enc_resource.ppi = ppi; | 
 |  | 
 |   assert(ppi != nullptr); | 
 |   // Turn off ppi->b_calculate_psnr to avoid calling generate_psnr_packet() in | 
 |   // av1_post_encode_updates(). | 
 |   // TODO(angiebird): Modify generate_psnr_packet() to handle the case that | 
 |   // cpi->ppi->output_pkt_list = nullptr. | 
 |   ppi->b_calculate_psnr = 0; | 
 |  | 
 |   aom_codec_err_t res = AOM_CODEC_OK; | 
 |   (void)res; | 
 |   enc_resource.stats_buf_ctx = | 
 |       CreateStatsBufferCtx(video_info.frame_count, &enc_resource.stats_buffer); | 
 |   if (pass == AOM_RC_SECOND_PASS) { | 
 |     assert(stats_list != nullptr); | 
 |     std::copy(stats_list->begin(), stats_list->end(), | 
 |               enc_resource.stats_buffer); | 
 |     *enc_resource.stats_buf_ctx->total_stats = ComputeTotalStats(*stats_list); | 
 |     oxcf.twopass_stats_in.buf = enc_resource.stats_buffer; | 
 |     // We need +1 here because av1 encoder assumes | 
 |     // oxcf.twopass_stats_in.buf[video_info.frame_count] has the total_stats | 
 |     oxcf.twopass_stats_in.sz = | 
 |         (video_info.frame_count + 1) * sizeof(enc_resource.stats_buffer[0]); | 
 |   } else { | 
 |     assert(pass == AOM_RC_FIRST_PASS); | 
 |     // We don't use stats_list for AOM_RC_FIRST_PASS. | 
 |     assert(stats_list == nullptr); | 
 |   } | 
 |   ppi->twopass.stats_buf_ctx = enc_resource.stats_buf_ctx; | 
 |   BufferPool *buffer_pool = nullptr; | 
 |   res = av1_create_context_and_bufferpool(ppi, &ppi->cpi, &buffer_pool, &oxcf, | 
 |                                           ENCODE_STAGE, -1); | 
 |   // TODO(angiebird): Why didn't we set initial_dimensions in | 
 |   // av1_create_compressor()? | 
 |   ppi->cpi->initial_dimensions.width = oxcf.frm_dim_cfg.width; | 
 |   ppi->cpi->initial_dimensions.height = oxcf.frm_dim_cfg.height; | 
 |   // use_ducky_encode is the flag we use to change AV1 behavior | 
 |   // slightly based on DuckyEncode's need. We should minimize this kind of | 
 |   // change unless it's necessary. | 
 |   ppi->cpi->use_ducky_encode = 1; | 
 |   assert(res == AOM_CODEC_OK); | 
 |   assert(ppi->cpi != nullptr); | 
 |   assert(buffer_pool != nullptr); | 
 |   const AV1_COMP *cpi = ppi->cpi; | 
 |   SequenceHeader *seq_params = ppi->cpi->common.seq_params; | 
 |  | 
 |   ppi->seq_params_locked = 1; | 
 |   assert(ppi->lookahead == nullptr); | 
 |  | 
 |   int lag_in_frames = cpi->oxcf.gf_cfg.lag_in_frames; | 
 |   ppi->lookahead = av1_lookahead_init( | 
 |       cpi->oxcf.frm_dim_cfg.width, cpi->oxcf.frm_dim_cfg.height, | 
 |       seq_params->subsampling_x, seq_params->subsampling_y, | 
 |       seq_params->use_highbitdepth, lag_in_frames, cpi->oxcf.border_in_pixels, | 
 |       cpi->common.features.byte_alignment, | 
 |       /*num_lap_buffers=*/0, /*is_all_intra=*/0, | 
 |       cpi->oxcf.tool_cfg.enable_global_motion); | 
 |  | 
 |   av1_tf_info_alloc(&cpi->ppi->tf_info, cpi); | 
 |   assert(ppi->lookahead != nullptr); | 
 |   return enc_resource; | 
 | } | 
 |  | 
 | static void FreeEncoder(EncoderResource *enc_resource) { | 
 |   fclose(enc_resource->in_file); | 
 |   enc_resource->in_file = nullptr; | 
 |   aom_img_free(&enc_resource->img); | 
 |   DestroyStatsBufferCtx(&enc_resource->stats_buf_ctx, | 
 |                         &enc_resource->stats_buffer); | 
 |   BufferPool *buffer_pool = enc_resource->ppi->cpi->common.buffer_pool; | 
 |   av1_destroy_context_and_bufferpool(enc_resource->ppi->cpi, &buffer_pool); | 
 |   av1_remove_primary_compressor(enc_resource->ppi); | 
 |   enc_resource->ppi = nullptr; | 
 | } | 
 |  | 
 | std::vector<FIRSTPASS_STATS> DuckyEncode::ComputeFirstPassStats() { | 
 |   aom_enc_pass pass = AOM_RC_FIRST_PASS; | 
 |   EncoderResource enc_resource = InitEncoder( | 
 |       impl_ptr_->video_info, impl_ptr_->g_usage, impl_ptr_->rc_end_usage, pass, | 
 |       nullptr, impl_ptr_->max_ref_frames, impl_ptr_->speed); | 
 |   AV1_PRIMARY *ppi = enc_resource.ppi; | 
 |   struct lookahead_ctx *lookahead = ppi->lookahead; | 
 |   int frame_count = impl_ptr_->video_info.frame_count; | 
 |   FILE *in_file = enc_resource.in_file; | 
 |   aom_rational64_t timestamp_ratio = impl_ptr_->timestamp_ratio; | 
 |   // TODO(angiebird): Ideally, ComputeFirstPassStats() doesn't output | 
 |   // bitstream. Do we need bitstream buffer here? | 
 |   std::vector<uint8_t> buf(1000); | 
 |   std::vector<FIRSTPASS_STATS> stats_list; | 
 |   for (int i = 0; i < frame_count; ++i) { | 
 |     if (aom_img_read(&enc_resource.img, in_file)) { | 
 |       // TODO(angiebird): Set ts_start/ts_end properly | 
 |       int64_t ts_start = enc_resource.lookahead_push_count; | 
 |       int64_t ts_end = ts_start + 1; | 
 |       YV12_BUFFER_CONFIG sd; | 
 |       image2yuvconfig(&enc_resource.img, &sd); | 
 |       av1_lookahead_push(lookahead, &sd, ts_start, ts_end, | 
 |                          /*use_highbitdepth=*/0, /*flags=*/0); | 
 |       ++enc_resource.lookahead_push_count; | 
 |       AV1_COMP_DATA cpi_data = {}; | 
 |       cpi_data.cx_data = buf.data(); | 
 |       cpi_data.cx_data_sz = buf.size(); | 
 |       cpi_data.frame_size = 0; | 
 |       cpi_data.flush = 1;  // Makes av1_get_compressed_data process a frame | 
 |       cpi_data.ts_frame_start = ts_start; | 
 |       cpi_data.ts_frame_end = ts_end; | 
 |       cpi_data.pop_lookahead = 1; | 
 |       cpi_data.timestamp_ratio = ×tamp_ratio; | 
 |       // av1_get_compressed_data only generates first pass stats not | 
 |       // compresses data | 
 |       int res = av1_get_compressed_data(ppi->cpi, &cpi_data); | 
 |       (void)res; | 
 |       assert(res == static_cast<int>(AOM_CODEC_OK)); | 
 |       stats_list.push_back(*(ppi->twopass.stats_buf_ctx->stats_in_end - 1)); | 
 |       av1_post_encode_updates(ppi->cpi, &cpi_data); | 
 |     } | 
 |   } | 
 |   av1_end_first_pass(ppi->cpi); | 
 |  | 
 |   FreeEncoder(&enc_resource); | 
 |   return stats_list; | 
 | } | 
 |  | 
 | void DuckyEncode::StartEncode(const std::vector<FIRSTPASS_STATS> &stats_list) { | 
 |   aom_enc_pass pass = AOM_RC_SECOND_PASS; | 
 |   impl_ptr_->stats_list = stats_list; | 
 |   impl_ptr_->enc_resource = InitEncoder( | 
 |       impl_ptr_->video_info, impl_ptr_->g_usage, impl_ptr_->rc_end_usage, pass, | 
 |       &stats_list, impl_ptr_->max_ref_frames, impl_ptr_->speed); | 
 |   write_temp_delimiter_ = true; | 
 | } | 
 |  | 
 | static void DuckyEncodeInfoSetGopStruct(AV1_PRIMARY *ppi, | 
 |                                         GopStruct gop_struct) { | 
 |   GF_GROUP *gf_group = &ppi->gf_group; | 
 |   ppi->p_rc.baseline_gf_interval = gop_struct.show_frame_count; | 
 |   ppi->internal_altref_allowed = 1; | 
 |  | 
 |   gf_group->size = static_cast<int>(gop_struct.gop_frame_list.size()); | 
 |   gf_group->max_layer_depth = 0; | 
 |  | 
 |   int i = 0; | 
 |   for (auto &frame : gop_struct.gop_frame_list) { | 
 |     gf_group->update_type[i] = (int)frame.update_type; | 
 |     if (frame.update_type == GopFrameType::kRegularArf) gf_group->arf_index = i; | 
 |  | 
 |     gf_group->frame_type[i] = !frame.is_key_frame; | 
 |  | 
 |     gf_group->cur_frame_idx[i] = 0; | 
 |     gf_group->arf_src_offset[i] = frame.order_idx - frame.display_idx; | 
 |     gf_group->cur_frame_idx[i] = frame.display_idx; | 
 |     gf_group->src_offset[i] = 0; | 
 |  | 
 |     // TODO(jingning): Placeholder - update the arf boost. | 
 |     gf_group->arf_boost[i] = 500; | 
 |     gf_group->layer_depth[i] = frame.layer_depth; | 
 |     gf_group->max_layer_depth = | 
 |         AOMMAX(frame.layer_depth, gf_group->max_layer_depth); | 
 |     gf_group->refbuf_state[i] = | 
 |         frame.is_key_frame ? REFBUF_RESET : REFBUF_UPDATE; | 
 |     ++i; | 
 |   } | 
 |   ppi->cpi->gf_frame_index = 0; | 
 | } | 
 |  | 
 | static void DuckyEncodeInfoSetEncodeFrameDecision( | 
 |     DuckyEncodeInfo *ducky_encode_info, const EncodeFrameDecision &decision) { | 
 |   DuckyEncodeFrameInfo *frame_info = &ducky_encode_info->frame_info; | 
 |   frame_info->qp_mode = static_cast<DUCKY_ENCODE_FRAME_MODE>(decision.qp_mode); | 
 |   frame_info->gop_mode = static_cast<DUCKY_ENCODE_GOP_MODE>(decision.gop_mode); | 
 |   frame_info->q_index = decision.parameters.q_index; | 
 |   frame_info->rdmult = decision.parameters.rdmult; | 
 | } | 
 |  | 
 | static void DuckyEncodeInfoGetEncodeFrameResult( | 
 |     const DuckyEncodeInfo *ducky_encode_info, EncodeFrameResult *result) { | 
 |   const DuckyEncodeFrameResult &frame_result = ducky_encode_info->frame_result; | 
 |   result->global_order_idx = frame_result.global_order_idx; | 
 |   result->q_index = frame_result.q_index; | 
 |   result->rdmult = frame_result.rdmult; | 
 |   result->rate = frame_result.rate; | 
 |   result->dist = frame_result.dist; | 
 |   result->psnr = frame_result.psnr; | 
 | } | 
 |  | 
 | static void WriteObu(AV1_PRIMARY *ppi, AV1_COMP_DATA *cpi_data) { | 
 |   AV1_COMP *const cpi = ppi->cpi; | 
 |   uint32_t obu_header_size = 1; | 
 |   const uint32_t obu_payload_size = 0; | 
 |   const size_t length_field_size = aom_uleb_size_in_bytes(obu_payload_size); | 
 |  | 
 |   const size_t move_offset = obu_header_size + length_field_size; | 
 |   memmove(cpi_data->cx_data + move_offset, cpi_data->cx_data, | 
 |           cpi_data->frame_size); | 
 |   obu_header_size = | 
 |       av1_write_obu_header(&ppi->level_params, &cpi->frame_header_count, | 
 |                            OBU_TEMPORAL_DELIMITER, 0, cpi_data->cx_data); | 
 |  | 
 |   // OBUs are preceded/succeeded by an unsigned leb128 coded integer. | 
 |   if (av1_write_uleb_obu_size(obu_header_size, obu_payload_size, | 
 |                               cpi_data->cx_data) != AOM_CODEC_OK) { | 
 |     aom_internal_error(&ppi->error, AOM_CODEC_ERROR, NULL); | 
 |   } | 
 |  | 
 |   cpi_data->frame_size += | 
 |       obu_header_size + obu_payload_size + length_field_size; | 
 | } | 
 |  | 
 | TplGopStats DuckyEncode::ObtainTplStats(const GopStruct gop_struct) { | 
 |   TplGopStats tpl_gop_stats; | 
 |  | 
 |   AV1_PRIMARY *ppi = impl_ptr_->enc_resource.ppi; | 
 |   const uint8_t block_mis_log2 = ppi->tpl_data.tpl_stats_block_mis_log2; | 
 |  | 
 |   for (size_t idx = 0; idx < gop_struct.gop_frame_list.size(); ++idx) { | 
 |     TplFrameStats tpl_frame_stats = {}; | 
 |     TplDepFrame *tpl_frame = &ppi->tpl_data.tpl_frame[idx]; | 
 |     if (gop_struct.gop_frame_list[idx].update_type == GopFrameType::kOverlay || | 
 |         gop_struct.gop_frame_list[idx].update_type == | 
 |             GopFrameType::kIntermediateOverlay) { | 
 |       tpl_gop_stats.frame_stats_list.push_back(tpl_frame_stats); | 
 |       continue; | 
 |     } | 
 |  | 
 |     int ref_frame_index_mapping[REF_FRAMES] = { 0 }; | 
 |     const GopFrame &gop_frame = gop_struct.gop_frame_list[idx]; | 
 |  | 
 |     for (auto &rf : gop_frame.ref_frame_list) { | 
 |       ref_frame_index_mapping[static_cast<int>(rf.name)] = rf.index; | 
 |     } | 
 |  | 
 |     const int mi_rows = tpl_frame->mi_rows; | 
 |     const int mi_cols = tpl_frame->mi_cols; | 
 |     const int tpl_frame_stride = tpl_frame->stride; | 
 |     tpl_frame_stats.frame_height = mi_rows * MI_SIZE; | 
 |     tpl_frame_stats.frame_width = mi_cols * MI_SIZE; | 
 |     tpl_frame_stats.min_block_size = (1 << block_mis_log2) * MI_SIZE; | 
 |  | 
 |     const int mi_step = 1 << block_mis_log2; | 
 |     for (int mi_row = 0; mi_row < mi_rows; mi_row += mi_step) { | 
 |       for (int mi_col = 0; mi_col < mi_cols; mi_col += mi_step) { | 
 |         int tpl_blk_pos = (mi_row >> block_mis_log2) * tpl_frame_stride + | 
 |                           (mi_col >> block_mis_log2); | 
 |         TplDepStats *tpl_stats_ptr = &tpl_frame->tpl_stats_ptr[tpl_blk_pos]; | 
 |  | 
 |         TplBlockStats block_stats; | 
 |         block_stats.row = mi_row * MI_SIZE; | 
 |         block_stats.col = mi_col * MI_SIZE; | 
 |         block_stats.height = (1 << block_mis_log2) * MI_SIZE; | 
 |         block_stats.width = (1 << block_mis_log2) * MI_SIZE; | 
 |         block_stats.inter_cost = tpl_stats_ptr->inter_cost; | 
 |         block_stats.intra_cost = tpl_stats_ptr->intra_cost; | 
 |         block_stats.ref_frame_index = { -1, -1 }; | 
 |  | 
 |         for (int i = 0; i < kBlockRefCount; ++i) { | 
 |           if (tpl_stats_ptr->ref_frame_index[i] >= 0) { | 
 |             block_stats.ref_frame_index[i] = | 
 |                 ref_frame_index_mapping[tpl_stats_ptr->ref_frame_index[i] + 1]; | 
 |             block_stats.mv[i] = { | 
 |               tpl_stats_ptr->mv[tpl_stats_ptr->ref_frame_index[i]].as_mv.row, | 
 |               tpl_stats_ptr->mv[tpl_stats_ptr->ref_frame_index[i]].as_mv.col, 3 | 
 |             }; | 
 |           } | 
 |         } | 
 |         tpl_frame_stats.block_stats_list.push_back(block_stats); | 
 |       } | 
 |     } | 
 |  | 
 |     tpl_gop_stats.frame_stats_list.push_back(tpl_frame_stats); | 
 |   } | 
 |  | 
 |   return tpl_gop_stats; | 
 | } | 
 |  | 
 | // Obtain TPL stats through ducky_encode. | 
 | std::vector<TplGopStats> DuckyEncode::ComputeTplStats( | 
 |     const GopStructList &gop_list) { | 
 |   std::vector<TplGopStats> tpl_gop_stats_list; | 
 |   AV1_PRIMARY *ppi = impl_ptr_->enc_resource.ppi; | 
 |   const VideoInfo &video_info = impl_ptr_->video_info; | 
 |   write_temp_delimiter_ = true; | 
 |   AllocateBitstreamBuffer(video_info); | 
 |  | 
 |   // Go through each gop and encode each frame in the gop | 
 |   for (size_t i = 0; i < gop_list.size(); ++i) { | 
 |     const aom::GopStruct &gop_struct = gop_list[i]; | 
 |     DuckyEncodeInfoSetGopStruct(ppi, gop_struct); | 
 |  | 
 |     aom::TplGopStats tpl_gop_stats; | 
 |     for (auto &frame : gop_struct.gop_frame_list) { | 
 |       // encoding frame frame_number | 
 |       aom::EncodeFrameDecision frame_decision = { aom::EncodeFrameMode::kQindex, | 
 |                                                   aom::EncodeGopMode::kGopRcl, | 
 |                                                   { 128, -1 } }; | 
 |       (void)frame; | 
 |       EncodeFrame(frame_decision); | 
 |       if (ppi->cpi->common.show_frame) pending_ctx_size_ = 0; | 
 |       write_temp_delimiter_ = ppi->cpi->common.show_frame; | 
 |     } | 
 |     tpl_gop_stats = ObtainTplStats(gop_struct); | 
 |     // TODO(jingning): Set the tpl stats file format and populate the stats. | 
 |     tpl_gop_stats_list.push_back(tpl_gop_stats); | 
 |   } | 
 |  | 
 |   return tpl_gop_stats_list; | 
 | } | 
 |  | 
 | // Obtain TPL stats through ducky_encode. | 
 | std::vector<EncodeFrameResult> DuckyEncode::EncodeVideo( | 
 |     const GopStructList &gop_list, | 
 |     const GopEncodeInfoList &gop_encode_info_list) { | 
 |   AV1_PRIMARY *ppi = impl_ptr_->enc_resource.ppi; | 
 |   std::vector<EncodeFrameResult> encoded_frame_list; | 
 |   const VideoInfo &video_info = impl_ptr_->video_info; | 
 |  | 
 |   write_temp_delimiter_ = true; | 
 |   AllocateBitstreamBuffer(video_info); | 
 |  | 
 |   // Go through each gop and encode each frame in the gop | 
 |   for (size_t i = 0; i < gop_list.size(); ++i) { | 
 |     const aom::GopStruct &gop_struct = gop_list[i]; | 
 |     DuckyEncodeInfoSetGopStruct(ppi, gop_struct); | 
 |     aom::GopEncodeInfo gop_encode_info = gop_encode_info_list[i]; | 
 |  | 
 |     for (auto &frame_param : gop_encode_info.param_list) { | 
 |       aom::EncodeFrameDecision frame_decision = { aom::EncodeFrameMode::kQindex, | 
 |                                                   aom::EncodeGopMode::kGopRcl, | 
 |                                                   frame_param }; | 
 |       EncodeFrame(frame_decision); | 
 |       if (ppi->cpi->common.show_frame) { | 
 |         bitstream_buf_.resize(pending_ctx_size_); | 
 |         EncodeFrameResult encode_frame_result = {}; | 
 |         encode_frame_result.bitstream_buf = bitstream_buf_; | 
 |         encoded_frame_list.push_back(encode_frame_result); | 
 |  | 
 |         AllocateBitstreamBuffer(video_info); | 
 |       } | 
 |       write_temp_delimiter_ = ppi->cpi->common.show_frame; | 
 |     } | 
 |   } | 
 |  | 
 |   return encoded_frame_list; | 
 | } | 
 |  | 
 | EncodeFrameResult DuckyEncode::EncodeFrame( | 
 |     const EncodeFrameDecision &decision) { | 
 |   EncodeFrameResult encode_frame_result = {}; | 
 |   encode_frame_result.bitstream_buf = bitstream_buf_; | 
 |   AV1_PRIMARY *ppi = impl_ptr_->enc_resource.ppi; | 
 |   aom_image_t *img = &impl_ptr_->enc_resource.img; | 
 |   AV1_COMP *const cpi = ppi->cpi; | 
 |   FILE *in_file = impl_ptr_->enc_resource.in_file; | 
 |   struct lookahead_ctx *lookahead = ppi->lookahead; | 
 |  | 
 |   while (!av1_lookahead_full(lookahead)) { | 
 |     if (aom_img_read(img, in_file)) { | 
 |       YV12_BUFFER_CONFIG sd; | 
 |       image2yuvconfig(img, &sd); | 
 |       int64_t ts_start = impl_ptr_->enc_resource.lookahead_push_count; | 
 |       int64_t ts_end = ts_start + 1; | 
 |       av1_lookahead_push(lookahead, &sd, ts_start, ts_end, | 
 |                          /*use_highbitdepth=*/0, /*flags=*/0); | 
 |       ++impl_ptr_->enc_resource.lookahead_push_count; | 
 |     } else { | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   AV1_COMP_DATA cpi_data = {}; | 
 |   cpi_data.cx_data = bitstream_buf_.data() + pending_ctx_size_; | 
 |   cpi_data.cx_data_sz = bitstream_buf_.size() - pending_ctx_size_; | 
 |   cpi_data.frame_size = 0; | 
 |   cpi_data.flush = 1; | 
 |   // ts_frame_start and ts_frame_end are not as important since we are focusing | 
 |   // on q mode | 
 |   cpi_data.ts_frame_start = impl_ptr_->enc_resource.encode_frame_count; | 
 |   cpi_data.ts_frame_end = cpi_data.ts_frame_start + 1; | 
 |   cpi_data.pop_lookahead = 1; | 
 |   cpi_data.timestamp_ratio = &impl_ptr_->timestamp_ratio; | 
 |   ++impl_ptr_->enc_resource.encode_frame_count; | 
 |  | 
 |   av1_compute_num_workers_for_mt(cpi); | 
 |   av1_init_frame_mt(ppi, cpi); | 
 |  | 
 |   DuckyEncodeInfoSetEncodeFrameDecision(&cpi->ducky_encode_info, decision); | 
 |   const int status = av1_get_compressed_data(cpi, &cpi_data); | 
 |  | 
 |   if (write_temp_delimiter_) WriteObu(ppi, &cpi_data); | 
 |   (void)status; | 
 |   assert(status == static_cast<int>(AOM_CODEC_OK)); | 
 |   DuckyEncodeInfoGetEncodeFrameResult(&cpi->ducky_encode_info, | 
 |                                       &encode_frame_result); | 
 |   av1_post_encode_updates(cpi, &cpi_data); | 
 |   if (cpi->common.show_frame) { | 
 |     // decrement frames_left counter | 
 |     ppi->frames_left = AOMMAX(0, ppi->frames_left - 1); | 
 |   } | 
 |  | 
 |   pending_ctx_size_ += cpi_data.frame_size; | 
 |  | 
 |   fprintf(stderr, "frame %d, qp = %d, size %d, PSNR %f\n", | 
 |           encode_frame_result.global_order_idx, encode_frame_result.q_index, | 
 |           encode_frame_result.rate, encode_frame_result.psnr); | 
 |   return encode_frame_result; | 
 | } | 
 |  | 
 | void DuckyEncode::EndEncode() { FreeEncoder(&impl_ptr_->enc_resource); } | 
 |  | 
 | void DuckyEncode::AllocateBitstreamBuffer(const VideoInfo &video_info) { | 
 |   pending_ctx_size_ = 0; | 
 |   // TODO(angiebird): Set bitstream_buf size to a conservatve upperbound. | 
 |   bitstream_buf_.assign( | 
 |       video_info.frame_width * video_info.frame_height * 3 * 8, 0); | 
 | } | 
 | }  // namespace aom |