// Copyright 2019 Fuzhou Rockchip Electronics Co., Ltd. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "encoder.h" #include "flow.h" #include "buffer.h" #include "media_type.h" #ifdef RK_MOVE_DETECTION #include "move_detection_flow.h" #endif // When the resolution is 2688x1520, // the average encoding takes 12ms. // // TO DO: Dynamic calculate time cost. #define ENC_CONST_MAX_TIME 12000 // us namespace easymedia { static bool encode(Flow* f, MediaBufferVector& input_vector); class VideoEncoderFlow : public Flow { public: VideoEncoderFlow(const char* param); virtual ~VideoEncoderFlow() { AutoPrintLine apl(__func__); StopAllThread(); } static const char* GetFlowName() { return "video_enc"; } int Control(unsigned long int request, ...); void Dump(std::string& dump_info) override; MediaConfig GetConfig() { MediaConfig cfg; memset(&cfg, 0, sizeof(cfg)); if (enc) { cfg = enc->GetConfig(); } return cfg; } private: std::shared_ptr enc; bool extra_output; bool extra_merge; std::list> extra_buffer_list; #ifdef RK_MOVE_DETECTION MoveDetectionFlow* md_flow; #endif // RK_MOVE_DETECTION friend bool encode(Flow* f, MediaBufferVector& input_vector); }; bool encode(Flow* f, MediaBufferVector& input_vector) { VideoEncoderFlow* vf = (VideoEncoderFlow*)f; std::shared_ptr enc = vf->enc; std::shared_ptr& src = input_vector[0]; std::shared_ptr dst, extra_dst; if (!src) { return false; } dst = std::make_shared(); // TODO: buffer pool if (!dst) { LOG_NO_MEMORY(); return false; } if (vf->extra_output) { extra_dst = std::make_shared(); if (!extra_dst) { LOG_NO_MEMORY(); return false; } } #ifdef RK_MOVE_DETECTION std::shared_ptr md_info; if (vf->md_flow) { int smartp_enable = 0; enc->QueryChange(VideoEncoder::kMoveDetectionFlow, &smartp_enable, sizeof(int)); if (!smartp_enable) { LOG("INFO: VEnc Flow: Wait for smartp configuration to take effect\n"); } else { LOGD("VEnc Flow: LookForMdResult start!\n"); MediaConfig mcfg = vf->GetConfig(); int fps = mcfg.vid_cfg.frame_rate; if (fps <= 0) { LOG("ERROR: VEnc Flow: smartp must config fps correctly!"); fps = 30; } else if (fps > 60) { LOG("WARN: VEnc Flow: smartp may error in high fps:%d!", fps); } int maximum_timeout = 1000000 / fps - ENC_CONST_MAX_TIME; md_info = vf->md_flow->LookForMdResult(src->GetAtomicClock(), maximum_timeout); if (md_info) { #ifndef NDEBUG LOGD("VEnc Flow: get md info(cnt=%d): %p, %zuBytes\n", md_info->GetValidSize() / sizeof(INFO_LIST), md_info.get(), md_info->GetValidSize()); #endif if (md_info->GetSize() >= sizeof(INFO_LIST)) { #ifndef NDEBUG INFO_LIST* info = (INFO_LIST*)md_info->GetPtr(); while (info->flag) { LOGD("VEnc Flow: mdinfo: flag:%d, upleft:<%d, %d>, downright:<%d, " "%d>\n", info->flag, info->up_left[0], info->up_left[1], info->down_right[0], info->down_right[1]); info += 1; } #endif src->SetRelatedSPtr(md_info); } } else { LOG("ERROR: VEnc Flow: fate error get null md result\n"); } LOGD("VEnc Flow: LookForMdResult end!\n\n"); } } #endif // RK_MOVE_DETECTION if (0 != enc->Process(src, dst, extra_dst)) { LOG("encoder failed\n"); return false; } bool ret = true; // when output fps less len input fps, enc->Proccess() may // return a empty mediabuff. if (dst->GetValidSize() > 0) { ret = vf->SetOutput(dst, 0); if (vf->extra_output) { ret &= vf->SetOutput(extra_dst, 1); } } return ret; } VideoEncoderFlow::VideoEncoderFlow(const char* param) : extra_output(false), extra_merge(false) #ifdef RK_MOVE_DETECTION , md_flow(nullptr) #endif { std::list separate_list; std::map params; LOG("VEnc Flow: dump param:%s\n", param); if (!ParseWrapFlowParams(param, params, separate_list)) { SetError(-EINVAL); return; } std::string& codec_name = params[KEY_NAME]; if (codec_name.empty()) { LOG("missing codec name\n"); SetError(-EINVAL); return; } std::string& extra_merge_value = params[KEY_NEED_EXTRA_MERGE]; if (!extra_merge_value.empty()) { extra_merge = !!std::stoi(extra_merge_value); } const char* ccodec_name = codec_name.c_str(); // check input/output type std::string&& rule = gen_datatype_rule(params); if (rule.empty()) { SetError(-EINVAL); return; } if (!REFLECTOR(Encoder)::IsMatch(ccodec_name, rule.c_str())) { LOG("Unsupport for video encoder %s : [%s]\n", ccodec_name, rule.c_str()); SetError(-EINVAL); return; } std::string& enc_param_str = separate_list.back(); std::map enc_params; if (!parse_media_param_map(enc_param_str.c_str(), enc_params)) { SetError(-EINVAL); return; } // copy data type to enc params. std::string::size_type idx; idx = enc_param_str.find(KEY_OUTPUTDATATYPE); if (idx == enc_param_str.npos) PARAM_STRING_APPEND(enc_param_str, KEY_OUTPUTDATATYPE, params[KEY_OUTPUTDATATYPE]); if (enc_params[KEY_INPUTDATATYPE].empty()) { enc_params[KEY_INPUTDATATYPE] = params[KEY_INPUTDATATYPE]; } if (enc_params[KEY_OUTPUTDATATYPE].empty()) { enc_params[KEY_OUTPUTDATATYPE] = params[KEY_OUTPUTDATATYPE]; } MediaConfig mc; if (!ParseMediaConfigFromMap(enc_params, mc)) { SetError(-EINVAL); return; } auto encoder = REFLECTOR(Encoder)::Create(ccodec_name, enc_param_str.c_str()); if (!encoder) { LOG("Fail to create video encoder %s<%s>\n", ccodec_name, enc_param_str.c_str()); SetError(-EINVAL); return; } if (!encoder->InitConfig(mc)) { LOG("Fail to init config, %s\n", ccodec_name); SetError(-EINVAL); return; } std::string roi_region_str = enc_params[KEY_ROI_REGIONS]; if (!roi_region_str.empty()) { int roi_regions_cnt = 0; std::vector roi_regions; roi_regions = StringToRoiRegions(roi_region_str); roi_regions_cnt = roi_regions.size(); if (roi_regions_cnt) { EncROIRegion* regions = (EncROIRegion*)malloc(roi_regions_cnt * sizeof(EncROIRegion)); for (int i = 0; i < roi_regions_cnt; i++) { (regions + i)->x = roi_regions[i].x; (regions + i)->y = roi_regions[i].y; (regions + i)->w = roi_regions[i].w; (regions + i)->h = roi_regions[i].h; (regions + i)->intra = roi_regions[i].intra; (regions + i)->quality = roi_regions[i].quality; (regions + i)->qp_area_idx = roi_regions[i].qp_area_idx; (regions + i)->area_map_en = roi_regions[i].area_map_en; (regions + i)->abs_qp_en = roi_regions[i].abs_qp_en; LOG("VEnc Flow: Roi Regions[%d]: (%d,%d,%d,%d,%d,%d,%d,%d,%d)\n", i, roi_regions[i].x, roi_regions[i].y, roi_regions[i].w, roi_regions[i].h, roi_regions[i].intra, roi_regions[i].quality, roi_regions[i].qp_area_idx, roi_regions[i].area_map_en, roi_regions[i].abs_qp_en); } auto pbuff = std::make_shared(0); pbuff->SetPtr(regions, roi_regions_cnt * sizeof(EncROIRegion)); encoder->RequestChange(VideoEncoder::kROICfgChange, pbuff); } } void* extra_data = nullptr; size_t extra_data_size = 0; encoder->GetExtraData(&extra_data, &extra_data_size); // TODO: if not h264 const std::string& output_dt = enc_params[KEY_OUTPUTDATATYPE]; enc = encoder; SlotMap sm; sm.input_slots.push_back(0); sm.output_slots.push_back(0); if (params[KEY_NEED_EXTRA_OUTPUT] == "y") { extra_output = true; sm.output_slots.push_back(1); } sm.process = encode; sm.thread_model = Model::ASYNCCOMMON; sm.mode_when_full = InputMode::DROPFRONT; sm.input_maxcachenum.push_back(3); if (!InstallSlotMap(sm, "VideoEncoderFlow", 40)) { LOG("Fail to InstallSlotMap, %s\n", ccodec_name); SetError(-EINVAL); return; } SetFlowTag("VideoEncoderFlow"); if (extra_data && extra_data_size > 0 && (output_dt == VIDEO_H264 || output_dt == VIDEO_H265)) { if (extra_merge) { std::shared_ptr extra_buf = std::make_shared(); extra_buf->SetPtr(extra_data); extra_buf->SetValidSize(extra_data_size); extra_buf->SetUserFlag(MediaBuffer::kExtraIntra); SetOutput(extra_buf, 0); } else { if (output_dt == VIDEO_H264) extra_buffer_list = split_h264_separate((const uint8_t*)extra_data, extra_data_size, gettimeofday()); else extra_buffer_list = split_h265_separate((const uint8_t*)extra_data, extra_data_size, gettimeofday()); for (auto& extra_buffer : extra_buffer_list) { assert(extra_buffer->GetUserFlag() & MediaBuffer::kExtraIntra); SetOutput(extra_buffer, 0); } } if (extra_output) { std::shared_ptr nullbuffer; SetOutput(nullbuffer, 1); } } } int VideoEncoderFlow::Control(unsigned long int request, ...) { va_list ap; va_start(ap, request); auto value = va_arg(ap, std::shared_ptr); va_end(ap); assert(value); #ifdef RK_MOVE_DETECTION if (request == VideoEncoder::kMoveDetectionFlow) { if (value->GetSize() != sizeof(void**)) { LOG("ERROR: VEnc Flow: move detect config falied!\n"); return -1; } md_flow = *((MoveDetectionFlow**)value->GetPtr()); LOGD("VEnc Flow: md_flow:%p\n", md_flow); } #endif // RK_MOVE_DETECTION enc->RequestChange(request, value); return 0; } void VideoEncoderFlow::Dump(std::string& dump_info) { const MediaConfig mc = GetConfig(); char str_line[1024] = {0}; DumpBase(dump_info); sprintf(str_line, "#Dump Flow(%s) advanced info:\r\n", GetFlowTag()); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " Name:%s\r\n", GetFlowName()); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); if (mc.type == Type::Image) { dump_info.append(" CodecType: JPEG\r\n"); sprintf(str_line, " Input: %d(%d)x%d(%d) fmt:%s\r\n", mc.img_cfg.image_info.width, mc.img_cfg.image_info.vir_width, mc.img_cfg.image_info.height, mc.img_cfg.image_info.vir_height, PixFmtToString(mc.img_cfg.image_info.pix_fmt)); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " Qfactor:%d\n", mc.img_cfg.qfactor); dump_info.append(str_line); } else if (mc.type == Type::Video) { const VideoConfig& vcfg = mc.vid_cfg; const ImageConfig& imgcfg = vcfg.image_cfg; if (imgcfg.codec_type == CODEC_TYPE_H264) { dump_info.append(" CodecType: H264\r\n"); } else if (imgcfg.codec_type == CODEC_TYPE_H265) { dump_info.append(" CodecType: H265\r\n"); } else { LOG("ERROR: VEnc Flow: config fatal error!\n"); return; } sprintf(str_line, " Input: %d(%d)x%d(%d) fmt:%s\r\n", imgcfg.image_info.width, imgcfg.image_info.vir_width, imgcfg.image_info.height, imgcfg.image_info.vir_height, PixFmtToString(imgcfg.image_info.pix_fmt)); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " QpArray: init:%d min:%d, max:%d, step:%d, min_i:%d, max_i:%d\r\n", vcfg.qp_init, vcfg.qp_min, vcfg.qp_max, vcfg.qp_step, vcfg.qp_min_i, vcfg.qp_max_i); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " BitRate: target:%d, min:%d, max:%d\r\n", vcfg.bit_rate, vcfg.bit_rate_min, vcfg.bit_rate_max); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " FrameRate: in:%d/%d, out:%d/%d\r\n", vcfg.frame_in_rate, vcfg.frame_in_rate_den, vcfg.frame_rate, vcfg.frame_rate_den); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " GopSize: %d\r\n", vcfg.gop_size); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " RcQuality: %s\r\n", vcfg.rc_quality); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " RcMode: %s\r\n", vcfg.rc_mode); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " FullRange: %s\r\n", vcfg.full_range ? "Enable" : "Disable"); dump_info.append(str_line); if (imgcfg.codec_type == CODEC_TYPE_H264) { memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " Trans8x8: %s\r\n", vcfg.trans_8x8 ? "Enable" : "Disable"); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " H264Level: %d\r\n", vcfg.level); dump_info.append(str_line); memset(str_line, 0, sizeof(str_line)); sprintf(str_line, " H264Profile: %d\r\n", vcfg.profile); dump_info.append(str_line); } } else { LOG("ERROR: VEnc Flow: Dump: to do...!\n"); return; } return; } DEFINE_FLOW_FACTORY(VideoEncoderFlow, Flow) // type depends on encoder const char* FACTORY(VideoEncoderFlow)::ExpectedInputDataType() { return ""; } const char* FACTORY(VideoEncoderFlow)::OutPutDataType() { return ""; } } // namespace easymedia