/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "hcodec.h"
#include <cassert>
#include <vector>
#include <algorithm>
#include <thread>
#include "syspara/parameters.h" // base/startup/init/interfaces/innerkits/include/
#include "qos.h"
// foundation/resourceschedule/resource_schedule_service/ressched/interfaces/innerkits/ressched_client/include
#include "res_sched_client.h"
#include "hdf_base.h"
#include "codec_omx_ext.h"
#include "hcodec_list.h"
#include "hencoder.h"
#include "hdecoder.h"
#include "hitrace_meter.h"
#include "hcodec_log.h"
#include "hcodec_dfx.h"
#include "hcodec_utils.h"
#include <sys/ioctl.h>
#include <linux/dma-buf.h>
#define DMA_BUF_SET_LEAK_TYPE	_IOW(DMA_BUF_BASE, 5, const char *)

namespace OHOS::MediaAVCodec {
using namespace std;
using namespace CodecHDI;
using namespace Media;

void HCodec::SetCallerToBuffer(int fd, uint32_t w, uint32_t h)
{
    const char* type = isEncoder_ ? "hw-video-encoder" : "hw-video-decoder";
    ioctl(fd, DMA_BUF_SET_LEAK_TYPE, type);
 
    string pid = to_string(caller_.playerCaller.pid);
    ioctl(fd, DMA_BUF_SET_NAME_A, pid.c_str());
    
    string bufName = caller_.app.processName + '-' + to_string(w) + 'x' + to_string(h)
                     + '-' + mime_ + '-' + to_string(componentId_);
    ioctl(fd, DMA_BUF_SET_NAME_A, bufName.c_str());
}

std::shared_ptr<HCodec> HCodec::Create(const std::string &name)
{
    vector<CodecCompCapability> capList = GetCapList();
    shared_ptr<HCodec> codec;
    for (const auto& cap : capList) {
        if (cap.compName != name) {
            continue;
        }
        optional<OMX_VIDEO_CODINGTYPE> type = TypeConverter::HdiRoleToOmxCodingType(cap.role);
        if (!type) {
            LOGE("unsupported role %d", cap.role);
            return nullptr;
        }
        if (cap.type == VIDEO_DECODER) {
            codec = make_shared<HDecoder>(cap, type.value());
        } else if (cap.type == VIDEO_ENCODER) {
            codec = make_shared<HEncoder>(cap, type.value());
        }
        break;
    }
    if (codec == nullptr) {
        LOGE("cannot find %s", name.c_str());
        return nullptr;
    }
    return codec;
}

int32_t HCodec::Init(Media::Meta &callerInfo)
{
    if (callerInfo.GetData(Tag::AV_CODEC_FORWARD_CALLER_PID, caller_.playerCaller.pid) &&
        callerInfo.GetData(Tag::AV_CODEC_FORWARD_CALLER_PROCESS_NAME, caller_.playerCaller.processName)) {
        caller_.calledByAvcodec = false;
        caller_.app = caller_.playerCaller;
    } else if (callerInfo.GetData(Tag::AV_CODEC_CALLER_PID, caller_.avcodecCaller.pid) &&
               callerInfo.GetData(Tag::AV_CODEC_CALLER_PROCESS_NAME, caller_.avcodecCaller.processName)) {
        caller_.calledByAvcodec = true;
        caller_.app = caller_.avcodecCaller;
    }
    callerInfo.GetData("av_codec_event_info_instance_id", instanceId_);
    return DoSyncCall(MsgWhat::INIT, nullptr);
}

std::shared_mutex HCodec::g_mtx;
std::unordered_map<HCodec::CallerInfo, std::vector<HCodec::InstInfo>,
    HCodec::CallerInfo, HCodec::CallerInfo> HCodec::g_callers[2];

void HCodec::PrintCaller()
{
    if (caller_.calledByAvcodec) {
        HLOGI("[pid %d][%s] -> avcodec", caller_.avcodecCaller.pid, caller_.avcodecCaller.processName.c_str());
    } else {
        HLOGI("[pid %d][%s] -> player -> avcodec", caller_.playerCaller.pid, caller_.playerCaller.processName.c_str());
    }
    std::unique_lock<std::shared_mutex> lk(g_mtx);
    g_callers[isEncoder_][caller_.app].emplace_back(InstInfo{GetNowUs(), compUniqueStr_});
    
    size_t totalInstCntNow = CalculateTotalInstCnt();
    size_t singleAppInstCntNow = g_callers[isEncoder_][caller_.app].size();
    if ((totalInstCntNow >= totalWarnInstCnt_) || (singleAppInstCntNow >= singleAppWarnInstCnt_)) {
        ReportToRss();
    }
}

size_t HCodec::CalculateTotalInstCnt()
{
    size_t ret = 0;
    const auto& callers = g_callers[isEncoder_];
    for (const auto& [_, vec] : callers) {
        ret += vec.size();
    }
    return ret;
}

void HCodec::PrintAllCaller()
{
    std::shared_lock<std::shared_mutex> lk(g_mtx);
    for (size_t i = 0; i < 2; i++) {
        for (const auto& [app, vec] : g_callers[i]) {
            LOGI("[pid %d][%s] hold %zu %s", app.pid, app.processName.c_str(), vec.size(), coderNames[i]);
            for (const InstInfo& inst : vec) {
                LOGI("createTime: %" PRId64 ", %s", inst.createTimeUs, inst.compUniqueStr.c_str());
            }
        }
    }
}

void HCodec::RemoveCaller()
{
    std::unique_lock<std::shared_mutex> lk(g_mtx);
    auto& callers = g_callers[isEncoder_];
    for (auto mapIter = callers.begin(); mapIter != callers.end(); mapIter++) {
        std::vector<InstInfo>& vec = mapIter->second;
        auto iter = find_if(vec.begin(), vec.end(), [this](const InstInfo& inst) {
            return inst.compUniqueStr == compUniqueStr_;
        });
        if (iter == vec.end()) {
            continue;
        }
        size_t totalInstCntBefore = CalculateTotalInstCnt();
        size_t singleAppInstCntBefore = vec.size();
        vec.erase(iter);
        if (vec.empty()) {
            callers.erase(mapIter);
        }
        if ((totalInstCntBefore >= totalWarnInstCnt_) || (singleAppInstCntBefore >= singleAppWarnInstCnt_)) {
            ReportToRss();
        }
        return;
    }
}

void HCodec::ReportToRss()
{
    std::unordered_map<std::string, std::string> mapPayload;
    for (const auto& [app, vec] : g_callers[isEncoder_]) {
        nlohmann::json js = {
            {"AppProcessName", app.processName},
            {"AppHold" + std::string(coderNames[isEncoder_]) + "Cnt", vec.size()},
            {"MaxAllow" + std::string(coderNames[isEncoder_]) + "Cnt", maxCodecInst_},
        };
        string pidStr = to_string(app.pid);
        string jsStr = js.dump();
        mapPayload[pidStr] = jsStr;
    }
    ResourceSchedule::ResSchedClient::GetInstance().ReportData(
        ResourceSchedule::ResType::RES_TYPE_AVCODE_HARDWARE_RESOURCES, isEncoder_ ? 1 : 0, mapPayload);
}

int32_t HCodec::SetCallback(const std::shared_ptr<MediaCodecCallback> &callback)
{
    HLOGD(">>");
    std::function<void(ParamSP)> proc = [&](ParamSP msg) {
        msg->SetValue("callback", callback);
    };
    return DoSyncCall(MsgWhat::SET_CALLBACK, proc);
}

int32_t HCodec::Configure(const Format &format)
{
    SCOPED_TRACE();
    HLOGI("%s", format.Stringify().c_str());
    std::function<void(ParamSP)> proc = [&](ParamSP msg) {
        msg->SetValue("format", format);
    };
    return DoSyncCall(MsgWhat::CONFIGURE, proc);
}

int32_t HCodec::SetCustomBuffer(std::shared_ptr<AVBuffer> buffer)
{
    std::function<void(ParamSP)> proc = [&](ParamSP msg) {
        msg->SetValue("buffer", buffer);
    };
    return DoSyncCall(MsgWhat::CONFIGURE_BUFFER, proc);
}

int32_t HCodec::SetOutputSurface(sptr<Surface> surface)
{
    HLOGI(">>");
    std::function<void(ParamSP)> proc = [&](ParamSP msg) {
        msg->SetValue("surface", surface);
    };
    return DoSyncCall(MsgWhat::SET_OUTPUT_SURFACE, proc);
}

int32_t HCodec::Start()
{
    SCOPED_TRACE();
    FUNC_TRACKER();
    return DoSyncCall(MsgWhat::START, nullptr,
        (isSecure_ ? FIVE_SECONDS_IN_MS * 2 : FIVE_SECONDS_IN_MS)); // 2: Secure mode
}

int32_t HCodec::Stop()
{
    SCOPED_TRACE();
    FUNC_TRACKER();
    return DoSyncCall(MsgWhat::STOP, nullptr);
}

int32_t HCodec::Flush()
{
    SCOPED_TRACE();
    FUNC_TRACKER();
    return DoSyncCall(MsgWhat::FLUSH, nullptr);
}

int32_t HCodec::Reset()
{
    SCOPED_TRACE();
    FUNC_TRACKER();
    int32_t ret = Release();
    if (ret == AVCS_ERR_OK) {
        ret = DoSyncCall(MsgWhat::INIT, nullptr);
    }
    return ret;
}

int32_t HCodec::Release()
{
    SCOPED_TRACE();
    FUNC_TRACKER();
    return DoSyncCall(MsgWhat::RELEASE, nullptr);
}

int32_t HCodec::NotifyEos()
{
    HLOGI(">>");
    return DoSyncCall(MsgWhat::NOTIFY_EOS, nullptr);
}

int32_t HCodec::SetParameter(const Format &format)
{
    HLOGI("%s", format.Stringify().c_str());
    std::function<void(ParamSP)> proc = [&](ParamSP msg) {
        msg->SetValue("params", format);
    };
    return DoSyncCall(MsgWhat::SET_PARAMETERS, proc);
}

int32_t HCodec::GetInputFormat(Format& format)
{
    HLOGI(">>");
    ParamSP reply;
    int32_t ret = DoSyncCallAndGetReply(MsgWhat::GET_INPUT_FORMAT, nullptr, reply);
    if (ret != AVCS_ERR_OK) {
        HLOGE("failed to get input format");
        return ret;
    }
    IF_TRUE_RETURN_VAL_WITH_MSG(!reply->GetValue("format", format),
        AVCS_ERR_UNKNOWN, "input format not replied");
    OnGetInputFormat(format);
    return AVCS_ERR_OK;
}

int32_t HCodec::GetOutputFormat(Format &format)
{
    ParamSP reply;
    int32_t ret = DoSyncCallAndGetReply(MsgWhat::GET_OUTPUT_FORMAT, nullptr, reply);
    if (ret != AVCS_ERR_OK) {
        HLOGE("failed to get output format");
        return ret;
    }
    IF_TRUE_RETURN_VAL_WITH_MSG(!reply->GetValue("format", format),
        AVCS_ERR_UNKNOWN, "output format not replied");
    format.PutStringValue(MediaDescriptionKey::MD_KEY_CODEC_NAME, caps_.compName);
    format.PutIntValue("IS_VENDOR", 1);
    return AVCS_ERR_OK;
}

// LCOV_EXCL_START
int32_t HCodec::GetChannelId(int32_t &channelId)
{
    GetChannelIdParam param {};
    InitOMXParamExt(param);
    if (!GetParameter(OMX_IndexParamChannelId, param)) {
        HLOGI("LowPowerPlayer  GetChannelId failed");
        return AVCS_ERR_UNKNOWN;
    }
    HLOGI("LowPowerPlayer  GetChannelId Success");
    channelId = param.channelId;
    return AVCS_ERR_OK;
}

int32_t HCodec::SetLowPowerPlayerMode(bool isLpp)
{
    bool originalIsLpp = isLpp_;
    isLpp_ = isLpp;
    GetLppModeParam param {};
    InitOMXParamExt(param);
    param.enable = isLpp;
    if (!SetParameter(OMX_IndexParamLppMode, param)) {
        HLOGE("set LowPowerPlayer Mode failed");
        isLpp_ = originalIsLpp;
        return AVCS_ERR_UNKNOWN;
    }
    return AVCS_ERR_OK;
}

std::string HCodec::GetHidumperInfo()
{
    HLOGI(">>");
    ParamSP reply;
    int32_t ret = DoSyncCallAndGetReply(MsgWhat::GET_HIDUMPER_INFO, nullptr, reply);
    if (ret != AVCS_ERR_OK) {
        HLOGW("failed to get hidumper info");
        return "";
    }
    string info;
    IF_TRUE_RETURN_VAL_WITH_MSG(!reply->GetValue("hidumper-info", info),
        "", "hidumper info not replied");
    return info;
}
// LCOV_EXCL_STOP

sptr<Surface> HCodec::CreateInputSurface()
{
    HLOGI(">>");
    ParamSP reply;
    int32_t ret = DoSyncCallAndGetReply(MsgWhat::CREATE_INPUT_SURFACE, nullptr, reply);
    if (ret != AVCS_ERR_OK) {
        HLOGE("failed to create input surface");
        return nullptr;
    }
    sptr<Surface> inputSurface;
    IF_TRUE_RETURN_VAL_WITH_MSG(!reply->GetValue("surface", inputSurface), nullptr, "input surface not replied");
    return inputSurface;
}

int32_t HCodec::SetInputSurface(sptr<Surface> surface)
{
    HLOGI(">>");
    std::function<void(ParamSP)> proc = [&](ParamSP msg) {
        msg->SetValue("surface", surface);
    };
    return DoSyncCall(MsgWhat::SET_INPUT_SURFACE, proc);
}

int32_t HCodec::SignalRequestIDRFrame()
{
    HLOGI(">>");
    return DoSyncCall(MsgWhat::REQUEST_IDR_FRAME, nullptr);
}

int32_t HCodec::QueueInputBuffer(uint32_t index)
{
    std::function<void(ParamSP)> proc = [&](ParamSP msg) {
        msg->SetValue(BUFFER_ID, index);
    };
    return DoSyncCall(MsgWhat::QUEUE_INPUT_BUFFER, proc);
}

int32_t HCodec::RenderOutputBuffer(uint32_t index)
{
    std::function<void(ParamSP)> proc = [&](ParamSP msg) {
        msg->SetValue(BUFFER_ID, index);
    };
    return DoSyncCall(MsgWhat::RENDER_OUTPUT_BUFFER, proc);
}

int32_t HCodec::ReleaseOutputBuffer(uint32_t index)
{
    std::function<void(ParamSP)> proc = [&](ParamSP msg) {
        msg->SetValue(BUFFER_ID, index);
    };
    return DoSyncCall(MsgWhat::RELEASE_OUTPUT_BUFFER, proc);
}
/**************************** public functions end ****************************/


HCodec::HCodec(CodecCompCapability caps, OMX_VIDEO_CODINGTYPE codingType, bool isEncoder)
    : caps_(caps), codingType_(codingType), isEncoder_(isEncoder)
{
    debugMode_ = HiLogIsLoggable(HCODEC_DOMAIN, HCODEC_TAG, LOG_DEBUG);
    string dumpModeStr = OHOS::system::GetParameter("hcodec.dump", "0");
    dumpMode_ = static_cast<DumpMode>(strtoul(dumpModeStr.c_str(), nullptr, 2)); // 2 is binary
    disableDmaSwap_ = OHOS::system::GetBoolParameter("hcodec.dmaswap.disable", false);
    pid_ = getpid();

    switch (static_cast<int>(codingType_)) {
        case OMX_VIDEO_CodingAVC:
            mime_ = "avc";
            break;
        case CODEC_OMX_VIDEO_CodingHEVC:
            mime_ = "hevc";
            break;
        case CODEC_OMX_VIDEO_CodingVVC:
            mime_ = "vvc";
            break;
        default:
            break;
    };
    isSecure_ = IsSecureMode(caps_.compName);

    uninitializedState_ = make_shared<UninitializedState>(this);
    initializedState_ = make_shared<InitializedState>(this);
    startingState_ = make_shared<StartingState>(this);
    runningState_ = make_shared<RunningState>(this);
    outputPortChangedState_ = make_shared<OutputPortChangedState>(this);
    stoppingState_ = make_shared<StoppingState>(this);
    flushingState_ = make_shared<FlushingState>(this);
    frozenState_ = make_shared<FrozenState>(this);
    StateMachine::ChangeStateTo(uninitializedState_);
}

HCodec::~HCodec()
{
    HLOGI(">>");
    MsgHandleLoop::Stop();
    ReleaseComponent();
}

int32_t HCodec::HdiCallback::EventHandler(CodecEventType event, const EventInfo &info)
{
    LOGD("event = %d, data1 = %u, data2 = %u", event, info.data1, info.data2);
    ParamSP msg = make_shared<ParamBundle>();
    msg->SetValue("event", event);
    msg->SetValue("data1", info.data1);
    msg->SetValue("data2", info.data2);
    std::shared_ptr<MsgToken> codec = codec_.lock();
    if (codec == nullptr) {
        LOGI("HCodec is gone");
        return HDF_SUCCESS;
    }
    codec->SendAsyncMsg(MsgWhat::CODEC_EVENT, msg);
    return HDF_SUCCESS;
}

int32_t HCodec::HdiCallback::EmptyBufferDone(int64_t appData, const OmxCodecBuffer& buffer)
{
    ParamSP msg = make_shared<ParamBundle>();
    msg->SetValue(BUFFER_ID, buffer.bufferId);
    std::shared_ptr<MsgToken> codec = codec_.lock();
    if (codec == nullptr) {
        LOGI("HCodec is gone");
        return HDF_SUCCESS;
    }
    codec->SendAsyncMsg(MsgWhat::OMX_EMPTY_BUFFER_DONE, msg);
    return HDF_SUCCESS;
}

int32_t HCodec::HdiCallback::FillBufferDone(int64_t appData, const OmxCodecBuffer& buffer)
{
    ParamSP msg = make_shared<ParamBundle>();
    msg->SetValue("omxBuffer", buffer);
    std::shared_ptr<MsgToken> codec = codec_.lock();
    if (codec == nullptr) {
        LOGI("HCodec is gone");
        return HDF_SUCCESS;
    }
    codec->SendAsyncMsg(MsgWhat::OMX_FILL_BUFFER_DONE, msg);
    return HDF_SUCCESS;
}

int32_t HCodec::SetFrameRateAdaptiveMode(const Format &format)
{
    if (!format.ContainKey(OHOS::Media::Tag::VIDEO_FRAME_RATE_ADAPTIVE_MODE)) {
        return AVCS_ERR_UNKNOWN;
    }

    WorkingFrequencyParam param {};
    InitOMXParamExt(param);
    if (!GetParameter(OMX_IndexParamWorkingFrequency, param)) {
        HLOGW("get working freq param failed");
        return AVCS_ERR_UNKNOWN;
    }
    if (param.level == 0) {
        return AVCS_ERR_UNKNOWN;
    }
    HLOGI("level cnt is %d, set level to %d", param.level, param.level - 1);
    param.level = param.level - 1;

    if (!SetParameter(OMX_IndexParamWorkingFrequency, param)) {
        HLOGW("set working freq param failed");
        return AVCS_ERR_UNKNOWN;
    }
    return AVCS_ERR_OK;
}

int32_t HCodec::SetProcessName()
{
    const std::string& processName = caller_.app.processName;
    HLOGI("processName is %s", processName.c_str());

    ProcessNameParam param {};
    InitOMXParamExt(param);
    if (strcpy_s(param.processName, sizeof(param.processName), processName.c_str()) != EOK) {
        HLOGW("strcpy failed");
        return AVCS_ERR_UNKNOWN;
    }
    if (!SetParameter(OMX_IndexParamProcessName, param)) {
        HLOGW("set process name failed");
        return AVCS_ERR_UNKNOWN;
    }
    return AVCS_ERR_OK;
}

int32_t HCodec::SetLowLatency(const Format &format)
{
    int32_t enableLowLatency;
    if (!format.GetIntValue(OHOS::Media::Tag::VIDEO_ENABLE_LOW_LATENCY, enableLowLatency)) {
        return AVCS_ERR_OK;
    }
    if (!HCodecList::FindFeature(caps_.port.video.features, VIDEO_FEATURE_LOW_LATENCY).support) {
        HLOGW("platform not support LowLatency");
        return AVCS_ERR_OK;
    }

    OMX_CONFIG_BOOLEANTYPE param {};
    InitOMXParam(param);
    param.bEnabled = enableLowLatency ? OMX_TRUE : OMX_FALSE;
    if (!SetParameter(OMX_IndexParamLowLatency, param)) {
        HLOGW("set low latency failed");
        return AVCS_ERR_UNKNOWN;
    }
    HLOGI("set low latency %s", enableLowLatency ? "enable" : "disable");
    return AVCS_ERR_OK;
}

bool HCodec::GetPixelFmtFromUser(const Format &format)
{
    optional<PixelFmt> fmt;
    VideoPixelFormat innerFmt;
    if (format.GetIntValue(MediaDescriptionKey::MD_KEY_PIXEL_FORMAT, *(int *)&innerFmt) &&
        innerFmt != VideoPixelFormat::SURFACE_FORMAT) {
        fmt = TypeConverter::InnerFmtToFmt(innerFmt);
    } else {
        HLOGI("user don't set VideoPixelFormat, use default");
        for (int32_t f : caps_.port.video.supportPixFmts) {
            fmt = TypeConverter::GraphicFmtToFmt(static_cast<GraphicPixelFormat>(f));
            if (fmt.has_value()) {
                break;
            }
        }
    }
    if (!fmt) {
        HLOGE("pixel format unspecified");
        return false;
    }
    configuredFmt_ = fmt.value();
    HLOGI("configured pixel format is %s", configuredFmt_.strFmt.c_str());
    return true;
}

std::optional<double> HCodec::GetFrameRateFromUser(const Format &format)
{
    double frameRateDouble;
    if (format.GetDoubleValue(MediaDescriptionKey::MD_KEY_FRAME_RATE, frameRateDouble) && frameRateDouble > 0) {
        LOGD("user set frame rate %.2f", frameRateDouble);
        return frameRateDouble;
    }
    int frameRateInt;
    if (format.GetIntValue(MediaDescriptionKey::MD_KEY_FRAME_RATE, frameRateInt) && frameRateInt > 0) {
        LOGD("user set frame rate %d", frameRateInt);
        return static_cast<double>(frameRateInt);
    }
    return nullopt;
}

std::optional<double> HCodec::GetOperatingRateFromUser(const Format &format)
{
    double operatingRate = 0.0;
    if (format.GetDoubleValue(OHOS::Media::Tag::VIDEO_OPERATING_RATE, operatingRate) && operatingRate > 0.0) {
        return operatingRate;
    }
    return nullopt;
}

bool HCodec::CheckBufPixFmt(int32_t dispFmt)
{
    const std::vector<int32_t>& supportFmts = caps_.port.video.supportPixFmts;
    if (std::find(supportFmts.begin(), supportFmts.end(), dispFmt) == supportFmts.end()) {
        LOGE("unsupported buffer pixel format %d", dispFmt);
        callback_->OnError(AVCODEC_ERROR_INTERNAL, AVCS_ERR_INPUT_DATA_ERROR);
        return false;
    }
    return true;
}

int32_t HCodec::SetVideoPortInfo(OMX_DIRTYPE portIndex, const PortInfo& info)
{
    if (info.pixelFmt.has_value()) {
        CodecVideoPortFormatParam param;
        InitOMXParamExt(param);
        param.portIndex = portIndex;
        param.codecCompressFormat = info.codingType;
        param.codecColorFormat = info.pixelFmt->graphicFmt;
        param.framerate = info.frameRate * FRAME_RATE_COEFFICIENT;
        if (!SetParameter(OMX_IndexCodecVideoPortFormat, param)) {
            HLOGE("set port format failed");
            return AVCS_ERR_UNKNOWN;
        }
    }
    {
        OMX_PARAM_PORTDEFINITIONTYPE def;
        InitOMXParam(def);
        def.nPortIndex = portIndex;
        if (!GetParameter(OMX_IndexParamPortDefinition, def)) {
            HLOGE("get port definition failed");
            return AVCS_ERR_UNKNOWN;
        }
        def.format.video.nFrameWidth = info.width;
        def.format.video.nFrameHeight = info.height;
        def.format.video.eCompressionFormat = info.codingType;
        // we dont set eColorFormat here because it has been set by CodecVideoPortFormatParam
        def.format.video.xFramerate = info.frameRate * FRAME_RATE_COEFFICIENT;
        if (portIndex == OMX_DirInput && info.inputBufSize.has_value()) {
            def.nBufferSize = info.inputBufSize.value();
        }
        if (portIndex == OMX_DirInput && info.inputBufferCount.has_value()) {
            def.nBufferCountActual = info.inputBufferCount.value();
            HLOGI("set input buffer count: %d", def.nBufferCountActual);
        }
        if (!SetParameter(OMX_IndexParamPortDefinition, def)) {
            HLOGE("set port definition failed");
            return AVCS_ERR_UNKNOWN;
        }
        if (portIndex == OMX_DirOutput) {
            if (outputFormat_ == nullptr) {
                outputFormat_ = make_shared<Format>();
            }
            outputFormat_->PutDoubleValue(MediaDescriptionKey::MD_KEY_FRAME_RATE, info.frameRate);
        }
    }
    
    return (portIndex == OMX_DirInput) ? UpdateInPortFormat() : UpdateOutPortFormat();
}

void HCodec::PrintPortDefinition(const OMX_PARAM_PORTDEFINITIONTYPE& def)
{
    const OMX_VIDEO_PORTDEFINITIONTYPE& video = def.format.video;
    HLOGI("%s: bufCntAct %u, bufCntMin %u bufSize %u, %u x %u @ %u(%.2f)",
        (def.nPortIndex == OMX_DirInput) ? "INPUT" : "OUTPUT",
        def.nBufferCountActual, def.nBufferCountMin, def.nBufferSize, video.nFrameWidth, video.nFrameHeight,
        video.xFramerate, video.xFramerate / FRAME_RATE_COEFFICIENT);
}

int32_t HCodec::GetPortDefinition(OMX_DIRTYPE portIndex, OMX_PARAM_PORTDEFINITIONTYPE& def)
{
    InitOMXParam(def);
    def.nPortIndex = portIndex;
    if (!GetParameter(OMX_IndexParamPortDefinition, def)) {
        HLOGE("get %s port definition failed", (portIndex == OMX_DirInput ? "input" : "output"));
        return AVCS_ERR_INVALID_VAL;
    }
    if (def.nBufferSize == 0 || def.nBufferSize > MAX_HCODEC_BUFFER_SIZE) {
        HLOGE("invalid nBufferSize %u", def.nBufferSize);
        return AVCS_ERR_INVALID_VAL;
    }
    return AVCS_ERR_OK;
}

int32_t HCodec::AllocateAvLinearBuffers(OMX_DIRTYPE portIndex)
{
    SCOPED_TRACE();
    OMX_PARAM_PORTDEFINITIONTYPE def;
    int32_t ret = GetPortDefinition(portIndex, def);
    if (ret != AVCS_ERR_OK) {
        return ret;
    }

    SupportBufferType type;
    InitOMXParamExt(type);
    type.portIndex = portIndex;
    if (GetParameter(OMX_IndexParamSupportBufferType, type) && (type.bufferTypes & CODEC_BUFFER_TYPE_DMA_MEM_FD)) {
        HLOGI("allocate hardware buffer");
        return AllocateAvHardwareBuffers(portIndex, def);
    } else {
        HLOGI("allocate shared buffer");
        return AllocateAvSharedBuffers(portIndex, def);
    }
}

int32_t HCodec::AllocateAvHardwareBuffers(OMX_DIRTYPE portIndex, const OMX_PARAM_PORTDEFINITIONTYPE& def)
{
    SCOPED_TRACE();
    vector<BufferInfo>& pool = (portIndex == OMX_DirInput) ? inputBufferPool_ : outputBufferPool_;
    pool.clear();
    for (uint32_t i = 0; i < def.nBufferCountActual; ++i) {
        std::shared_ptr<OmxCodecBuffer> omxBuffer = std::make_shared<OmxCodecBuffer>();
        omxBuffer->size = sizeof(OmxCodecBuffer);
        omxBuffer->version.version.majorVersion = 1;
        omxBuffer->bufferType = CODEC_BUFFER_TYPE_DMA_MEM_FD;
        omxBuffer->fd = -1;
        omxBuffer->allocLen = def.nBufferSize;
        omxBuffer->fenceFd = -1;
        shared_ptr<OmxCodecBuffer> outBuffer = make_shared<OmxCodecBuffer>();
        int32_t ret = compNode_->AllocateBuffer(portIndex, *omxBuffer, *outBuffer);
        if (ret != HDF_SUCCESS) {
            HLOGE("Failed to AllocateBuffer on %s port", (portIndex == OMX_DirInput ? "input" : "output"));
            return AVCS_ERR_INVALID_VAL;
        }
        MemoryFlag memFlag = MEMORY_READ_WRITE;
        std::shared_ptr<AVAllocator> avAllocator = AVAllocatorFactory::CreateHardwareAllocator(
            outBuffer->fd, static_cast<int32_t>(def.nBufferSize), memFlag, isSecure_);
        IF_TRUE_RETURN_VAL_WITH_MSG(avAllocator == nullptr, AVCS_ERR_INVALID_VAL, "CreateHardwareAllocator failed");

        std::shared_ptr<AVBuffer> avBuffer = AVBuffer::CreateAVBuffer(
            avAllocator, static_cast<int32_t>(def.nBufferSize));
        if (avBuffer == nullptr || avBuffer->memory_ == nullptr ||
            avBuffer->memory_->GetCapacity() != static_cast<int32_t>(def.nBufferSize)) {
            HLOGE("CreateAVBuffer failed");
            return AVCS_ERR_NO_MEMORY;
        }
        SetCallerToBuffer(outBuffer->fd, def.format.video.nFrameWidth, def.format.video.nFrameHeight);
        BufferInfo bufInfo((portIndex == OMX_DirInput), BufferOwner::OWNED_BY_US, record_);
        bufInfo.surfaceBuffer  = nullptr;
        bufInfo.avBuffer       = avBuffer;
        bufInfo.omxBuffer      = outBuffer;
        bufInfo.bufferId       = outBuffer->bufferId;
        bufInfo.CleanUpUnusedInfo();
        pool.push_back(bufInfo);
    }
    return AVCS_ERR_OK;
}

// LCOV_EXCL_START
int32_t HCodec::AllocateAvSharedBuffers(OMX_DIRTYPE portIndex, const OMX_PARAM_PORTDEFINITIONTYPE& def)
{
    SCOPED_TRACE();
    MemoryFlag memFlag = MEMORY_READ_WRITE;
    std::shared_ptr<AVAllocator> avAllocator = AVAllocatorFactory::CreateSharedAllocator(memFlag);
    IF_TRUE_RETURN_VAL_WITH_MSG(avAllocator == nullptr, AVCS_ERR_INVALID_VAL, "CreateSharedAllocator failed");

    vector<BufferInfo>& pool = (portIndex == OMX_DirInput) ? inputBufferPool_ : outputBufferPool_;
    pool.clear();
    for (uint32_t i = 0; i < def.nBufferCountActual; ++i) {
        std::shared_ptr<AVBuffer> avBuffer = AVBuffer::CreateAVBuffer(avAllocator,
                                                                      static_cast<int32_t>(def.nBufferSize));
        if (avBuffer == nullptr || avBuffer->memory_ == nullptr ||
            avBuffer->memory_->GetCapacity() != static_cast<int32_t>(def.nBufferSize)) {
            HLOGE("CreateAVBuffer failed");
            return AVCS_ERR_NO_MEMORY;
        }
        std::shared_ptr<OmxCodecBuffer> omxBuffer = std::make_shared<OmxCodecBuffer>();
        omxBuffer->size = sizeof(OmxCodecBuffer);
        omxBuffer->version.version.majorVersion = 1;
        omxBuffer->bufferType = CODEC_BUFFER_TYPE_AVSHARE_MEM_FD;
        omxBuffer->fd = avBuffer->memory_->GetFileDescriptor();
        omxBuffer->allocLen = def.nBufferSize;
        omxBuffer->fenceFd = -1;
        omxBuffer->type = (portIndex == OMX_DirInput) ? READ_ONLY_TYPE : READ_WRITE_TYPE;
        shared_ptr<OmxCodecBuffer> outBuffer = make_shared<OmxCodecBuffer>();
        int32_t ret = compNode_->UseBuffer(portIndex, *omxBuffer, *outBuffer);
        if (ret != HDF_SUCCESS) {
            HLOGE("Failed to UseBuffer on %s port", (portIndex == OMX_DirInput ? "input" : "output"));
            return AVCS_ERR_INVALID_VAL;
        }
        BufferInfo bufInfo((portIndex == OMX_DirInput), BufferOwner::OWNED_BY_US, record_);
        bufInfo.surfaceBuffer  = nullptr;
        bufInfo.avBuffer       = avBuffer;
        bufInfo.omxBuffer      = outBuffer;
        bufInfo.bufferId       = outBuffer->bufferId;
        pool.push_back(bufInfo);
    }
    return AVCS_ERR_OK;
}
// LCOV_EXCL_STOP

int32_t HCodec::AllocateAvSurfaceBuffers(OMX_DIRTYPE portIndex)
{
    SCOPED_TRACE();
    OMX_PARAM_PORTDEFINITIONTYPE def;
    int32_t ret = GetPortDefinition(portIndex, def);
    if (ret != AVCS_ERR_OK) {
        return ret;
    }
    std::shared_ptr<AVAllocator> avAllocator = AVAllocatorFactory::CreateSurfaceAllocator(requestCfg_);
    IF_TRUE_RETURN_VAL_WITH_MSG(avAllocator == nullptr, AVCS_ERR_INVALID_VAL, "CreateSurfaceAllocator failed");

    vector<BufferInfo>& pool = (portIndex == OMX_DirInput) ? inputBufferPool_ : outputBufferPool_;
    pool.clear();
    for (uint32_t i = 0; i < def.nBufferCountActual; ++i) {
        std::shared_ptr<AVBuffer> avBuffer = AVBuffer::CreateAVBuffer(avAllocator,
                                                                      static_cast<int32_t>(def.nBufferSize));
        if (avBuffer == nullptr || avBuffer->memory_ == nullptr) {
            HLOGE("CreateAVBuffer failed");
            return AVCS_ERR_NO_MEMORY;
        }
        sptr<SurfaceBuffer> surfaceBuffer = avBuffer->memory_->GetSurfaceBuffer();
        IF_TRUE_RETURN_VAL_WITH_MSG(surfaceBuffer == nullptr, AVCS_ERR_INVALID_VAL, "avbuffer has null surfacebuffer");
        shared_ptr<OmxCodecBuffer> omxBuffer = isEncoder_ ?
            DynamicSurfaceBufferToOmxBuffer() : SurfaceBufferToOmxBuffer(surfaceBuffer);
        IF_TRUE_RETURN_VAL(omxBuffer == nullptr, AVCS_ERR_INVALID_VAL);
        shared_ptr<OmxCodecBuffer> outBuffer = make_shared<OmxCodecBuffer>();
        int32_t hdiRet = compNode_->UseBuffer(portIndex, *omxBuffer, *outBuffer);
        if (hdiRet != HDF_SUCCESS) {
            HLOGE("Failed to UseBuffer on %s port", (portIndex == OMX_DirInput ? "input" : "output"));
            return AVCS_ERR_INVALID_VAL;
        }
        SetCallerToBuffer(surfaceBuffer->GetFileDescriptor(),
                          static_cast<uint32_t>(surfaceBuffer->GetWidth()),
                          static_cast<uint32_t>(surfaceBuffer->GetHeight()));
        BufferInfo bufInfo((portIndex == OMX_DirInput), BufferOwner::OWNED_BY_US, record_);
        bufInfo.surfaceBuffer  = surfaceBuffer;
        bufInfo.avBuffer       = avBuffer;
        bufInfo.omxBuffer      = outBuffer;
        bufInfo.bufferId       = outBuffer->bufferId;
        bufInfo.needDealWithCache = true;
        pool.push_back(bufInfo);
    }

    return AVCS_ERR_OK;
}

shared_ptr<OmxCodecBuffer> HCodec::SurfaceBufferToOmxBuffer(const sptr<SurfaceBuffer>& surfaceBuffer)
{
    BufferHandle* bufferHandle = surfaceBuffer->GetBufferHandle();
    IF_TRUE_RETURN_VAL_WITH_MSG(bufferHandle == nullptr, nullptr, "surfacebuffer has null bufferhandle");
    auto omxBuffer = std::make_shared<OmxCodecBuffer>();
    omxBuffer->size = sizeof(OmxCodecBuffer);
    omxBuffer->version.version.majorVersion = 1;
    omxBuffer->bufferType = CODEC_BUFFER_TYPE_HANDLE;
    omxBuffer->bufferhandle = new NativeBuffer(bufferHandle);
    omxBuffer->fd = -1;
    omxBuffer->allocLen = surfaceBuffer->GetSize();
    omxBuffer->fenceFd = -1;
    return omxBuffer;
}

shared_ptr<OmxCodecBuffer> HCodec::DynamicSurfaceBufferToOmxBuffer()
{
    auto omxBuffer = make_shared<OmxCodecBuffer>();
    omxBuffer->size = sizeof(OmxCodecBuffer);
    omxBuffer->version.version.majorVersion = 1;
    omxBuffer->bufferType = CODEC_BUFFER_TYPE_DYNAMIC_HANDLE;
    omxBuffer->fd = -1;
    omxBuffer->allocLen = 0;
    omxBuffer->fenceFd = -1;
    return omxBuffer;
}

const char* HCodec::ToString(BufferOwner owner)
{
    switch (owner) {
        case BufferOwner::OWNED_BY_US:
            return "us";
        case BufferOwner::OWNED_BY_USER:
            return "user";
        case BufferOwner::OWNED_BY_OMX:
            return "omx";
        case BufferOwner::OWNED_BY_SURFACE:
            return "surface";
        default:
            return "";
    }
}

void HCodec::BufferInfo::CleanUpUnusedInfo()
{
    if (omxBuffer == nullptr || omxBuffer->fd < 0) {
        return;
    }
    if (omxBuffer->fd == 0) {
        LOGW("fd of omxbuffer should never be 0");
    }
    close(omxBuffer->fd);
    omxBuffer->fd = -1;
}

void HCodec::BufferInfo::BeginCpuAccess()
{
    if (surfaceBuffer && needDealWithCache) {
        GSError err = surfaceBuffer->InvalidateCache();
        if (err != GSERROR_OK) {
            LOGW("InvalidateCache failed, GSError=%d", err);
        }
    }
}

void HCodec::BufferInfo::EndCpuAccess()
{
    if (surfaceBuffer && needDealWithCache) {
        GSError err = surfaceBuffer->Map();
        if (err != GSERROR_OK) {
            LOGW("Map failed, GSError=%d", err);
            return;
        }
        err = surfaceBuffer->FlushCache();
        if (err != GSERROR_OK) {
            LOGW("FlushCache failed, GSError=%d", err);
        }
    }
}

HCodec::BufferInfo* HCodec::FindBufferInfoByID(OMX_DIRTYPE portIndex, uint32_t bufferId)
{
    vector<BufferInfo>& pool = (portIndex == OMX_DirInput) ? inputBufferPool_ : outputBufferPool_;
    for (BufferInfo &info : pool) {
        if (info.bufferId == bufferId) {
            return &info;
        }
    }
    HLOGE("unknown buffer id %u", bufferId);
    return nullptr;
}

optional<size_t> HCodec::FindBufferIndexByID(OMX_DIRTYPE portIndex, uint32_t bufferId)
{
    const vector<BufferInfo>& pool = (portIndex == OMX_DirInput) ? inputBufferPool_ : outputBufferPool_;
    for (size_t i = 0; i < pool.size(); i++) {
        if (pool[i].bufferId == bufferId) {
            return i;
        }
    }
    HLOGE("unknown buffer id %u", bufferId);
    return nullopt;
}

uint32_t HCodec::UserFlagToOmxFlag(AVCodecBufferFlag userFlag)
{
    uint32_t flags = 0;
    if (userFlag & AVCODEC_BUFFER_FLAG_EOS) {
        flags |= OMX_BUFFERFLAG_EOS;
        HLOGI("got input eos");
    }
    if (userFlag & AVCODEC_BUFFER_FLAG_SYNC_FRAME) {
        flags |= OMX_BUFFERFLAG_SYNCFRAME;
    }
    if (userFlag & AVCODEC_BUFFER_FLAG_CODEC_DATA) {
        flags |= OMX_BUFFERFLAG_CODECCONFIG;
    }
    if (isLpp_ && (userFlag & AVCODEC_BUFFER_FLAG_MUL_FRAME)) {
        flags |= OMX_BUFFERFLAG_MULTI_FRAME;
    }
    return flags;
}

AVCodecBufferFlag HCodec::OmxFlagToUserFlag(uint32_t omxFlag)
{
    uint32_t flags = 0;
    if (omxFlag & OMX_BUFFERFLAG_EOS) {
        flags |= AVCODEC_BUFFER_FLAG_EOS;
        HLOGI("got output eos");
    }
    if (omxFlag & OMX_BUFFERFLAG_SYNCFRAME) {
        flags |= AVCODEC_BUFFER_FLAG_SYNC_FRAME;
    }
    if (omxFlag & OMX_BUFFERFLAG_CODECCONFIG) {
        flags |= AVCODEC_BUFFER_FLAG_CODEC_DATA;
    }
    return static_cast<AVCodecBufferFlag>(flags);
}

bool HCodec::WaitFence(const sptr<SyncFence>& fence, uint32_t seq, const std::string& prefix, uint64_t& costUs)
{
    if (fence == nullptr || !fence->IsValid()) {
        return true;
    }
    HITRACE_METER_FMT(HITRACE_TAG_ZMEDIA, "[hcodec][%s]WaitFence for seq=%u", prefix.c_str(), seq);
    auto before = chrono::steady_clock::now();
    int waitRes = fence->Wait(WAIT_FENCE_MS);
    if (waitRes == 0) {
        costUs = static_cast<uint64_t>(
        chrono::duration_cast<chrono::microseconds>(chrono::steady_clock::now() - before).count());
        return true;
    } else {
        LOGW("wait fence time out, cost more than %u ms", WAIT_FENCE_MS);
        costUs = WAIT_FENCE_MS * TIME_RATIO_S_TO_MS;
        return false;
    }
}

bool HCodec::WaitFence(const sptr<SyncFence>& fence, uint32_t seq)
{
    uint64_t costUs = 0;
    bool ret = WaitFence(fence, seq, compUniqueStr_, costUs);
    uint64_t& waitFenceCostUs = isEncoder_ ?
        record_[OMX_DirInput].waitFenceCostUsInterval_ : record_[OMX_DirOutput].waitFenceCostUsInterval_;
    waitFenceCostUs += costUs;
    return ret;
}
 
void HCodec::NotifyUserToFillThisInBuffer(BufferInfo &info)
{
    SCOPED_TRACE_FMT("id: %u", info.bufferId);
    callback_->OnInputBufferAvailable(info.bufferId, info.avBuffer);
    ChangeOwner(info, BufferOwner::OWNED_BY_USER);
}

void HCodec::OnQueueInputBuffer(const MsgInfo &msg, BufferOperationMode mode)
{
    uint32_t bufferId = 0;
    (void)msg.param->GetValue(BUFFER_ID, bufferId);
    SCOPED_TRACE_FMT("id: %u", bufferId);
    BufferInfo* bufferInfo = FindBufferInfoByID(OMX_DirInput, bufferId);
    if (bufferInfo == nullptr) {
        ReplyErrorCode(msg.id, AVCS_ERR_INVALID_VAL);
        return;
    }
    if (bufferInfo->owner != BufferOwner::OWNED_BY_USER) {
        HLOGE("wrong ownership: buffer id=%d, owner=%s", bufferId, ToString(bufferInfo->owner));
        ReplyErrorCode(msg.id, AVCS_ERR_INVALID_VAL);
        return;
    }
    bufferInfo->omxBuffer->filledLen = static_cast<uint32_t>
        (bufferInfo->avBuffer->memory_->GetSize());
    bufferInfo->omxBuffer->offset = static_cast<uint32_t>(bufferInfo->avBuffer->memory_->GetOffset());
    bufferInfo->omxBuffer->pts = bufferInfo->avBuffer->pts_;
    bufferInfo->omxBuffer->flag = UserFlagToOmxFlag(static_cast<AVCodecBufferFlag>(bufferInfo->avBuffer->flag_));
    RecordProcessTimeOfUpstream(bufferInfo->avBuffer);
    ChangeOwner(*bufferInfo, BufferOwner::OWNED_BY_US);
    ReplyErrorCode(msg.id, AVCS_ERR_OK);
    int32_t ret = OnQueueInputBuffer(mode, bufferInfo);
    if (ret != AVCS_ERR_OK) {
        SignalError(AVCODEC_ERROR_INTERNAL, AVCS_ERR_UNKNOWN);
    }
}

int32_t HCodec::OnQueueInputBuffer(BufferOperationMode mode, BufferInfo* info)
{
    switch (mode) {
        case KEEP_BUFFER: {
            return AVCS_ERR_OK;
        }
        case RESUBMIT_BUFFER: {
            if (inputPortEos_) {
                HLOGI("input already eos, keep this buffer");
                return AVCS_ERR_OK;
            }
            bool eos = (info->omxBuffer->flag & OMX_BUFFERFLAG_EOS);
            if (!eos && info->omxBuffer->filledLen == 0) {
                HLOGI("this is not a eos buffer but not filled, ask user to re-fill it");
                NotifyUserToFillThisInBuffer(*info);
                return AVCS_ERR_OK;
            }
            if (eos) {
                inputPortEos_ = true;
            }
            return InBufUsToOmx(*info);
        }
        default: {
            HLOGE("SHOULD NEVER BE HERE");
            return AVCS_ERR_UNKNOWN;
        }
    }
}

void HCodec::WrapSurfaceBufferToSlot(BufferInfo &info,
    const sptr<SurfaceBuffer>& surfaceBuffer, int64_t pts, uint32_t flag)
{
    info.surfaceBuffer = surfaceBuffer;
    info.omxBuffer->bufferhandle = new NativeBuffer(surfaceBuffer->GetBufferHandle());
    info.omxBuffer->filledLen = surfaceBuffer->GetSize();
    info.omxBuffer->fd = -1;
    info.omxBuffer->fenceFd = -1;
    info.omxBuffer->pts = pts;
    info.omxBuffer->flag = flag;
}

void HCodec::OnSignalEndOfInputStream(const MsgInfo &msg)
{
    ReplyErrorCode(msg.id, AVCS_ERR_UNSUPPORT);
}

int32_t HCodec::InBufUsToOmx(BufferInfo& info)
{
    SCOPED_TRACE_FMT("id: %u, pts: %" PRId64, info.bufferId, info.omxBuffer->pts);
    OnFrameSubmitted(info.omxBuffer->pts);
    if (!gotFirstInput_) {
        HLOGI("got first input, pts = %" PRId64 ", len = %u, flags = 0x%x",
            info.omxBuffer->pts, info.omxBuffer->filledLen, info.omxBuffer->flag);
        gotFirstInput_ = true;
    }
#ifdef BUILD_ENG_VERSION
    info.Dump(compUniqueStrWithMime_, record_[OMX_DirInput].frameCntTotal_, dumpMode_, isEncoder_);
#endif
    info.EndCpuAccess();
    int32_t ret = compNode_->EmptyThisBuffer(*(info.omxBuffer));
    if (ret != HDF_SUCCESS) {
        HLOGE("EmptyThisBuffer failed");
        return AVCS_ERR_UNKNOWN;
    }
    ChangeOwner(info, BufferOwner::OWNED_BY_OMX);
    return AVCS_ERR_OK;
}

int32_t HCodec::NotifyOmxToFillThisOutBuffer(BufferInfo& info)
{
    SCOPED_TRACE_FMT("id: %u", info.bufferId);
    info.omxBuffer->flag = 0;
    int32_t ret = compNode_->FillThisBuffer(*(info.omxBuffer));
    if (ret != HDF_SUCCESS) {
        HLOGE("outBufId = %u failed", info.bufferId);
        return AVCS_ERR_UNKNOWN;
    }
    ChangeOwner(info, BufferOwner::OWNED_BY_OMX);
    return AVCS_ERR_OK;
}

void HCodec::OnOMXFillBufferDone(const OmxCodecBuffer& omxBuffer, BufferOperationMode mode)
{
    SCOPED_TRACE_FMT("id: %u", omxBuffer.bufferId);
    optional<size_t> idx = FindBufferIndexByID(OMX_DirOutput, omxBuffer.bufferId);
    if (!idx.has_value()) {
        return;
    }
    BufferInfo& info = outputBufferPool_[idx.value()];
    if (info.owner != BufferOwner::OWNED_BY_OMX) {
        HLOGE("wrong ownership: buffer id=%d, owner=%s", info.bufferId, ToString(info.owner));
        return;
    }
    info.omxBuffer->offset = omxBuffer.offset;
    info.omxBuffer->filledLen = omxBuffer.filledLen;
    info.omxBuffer->pts = omxBuffer.pts;
    info.omxBuffer->flag = omxBuffer.flag;
    info.omxBuffer->alongParam = std::move(omxBuffer.alongParam);
    ChangeOwner(info, BufferOwner::OWNED_BY_US);
    OnOMXFillBufferDone(mode, info, idx.value());
}

void HCodec::OnOMXFillBufferDone(BufferOperationMode mode, BufferInfo& info, size_t bufferIdx)
{
    switch (mode) {
        case KEEP_BUFFER:
            return;
        case RESUBMIT_BUFFER: {
            if (outputPortEos_) {
                HLOGD("output eos, keep this buffer");
                return;
            }
            bool eos = (info.omxBuffer->flag & OMX_BUFFERFLAG_EOS);
            if (!eos && info.omxBuffer->filledLen == 0) {
                HLOGW("it's not a eos buffer but not filled, ask omx to re-fill it");
                NotifyOmxToFillThisOutBuffer(info);
                return;
            }
#ifdef USE_VIDEO_PROCESSING_ENGINE
            if (!isEncoder_ && isVrrInitialized_) {
                (void)VrrPrediction(info);
            }
#endif
            OutBufUsToUser(info);
            if (eos) {
                outputPortEos_ = true;
            }
            return;
        }
        case FREE_BUFFER:
            EraseBufferFromPool(OMX_DirOutput, bufferIdx);
            return;
        default:
            HLOGE("SHOULD NEVER BE HERE");
            return;
    }
}

void HCodec::OutBufUsToUser(BufferInfo &info)
{
    SCOPED_TRACE_FMT("id: %u, pts: %" PRId64, info.bufferId, info.omxBuffer->pts);
    if (!gotFirstOutput_) {
        HLOGI("got first output, pts = %" PRId64 ", len = %u, flags = 0x%x",
            info.omxBuffer->pts, info.omxBuffer->filledLen, info.omxBuffer->flag);
        gotFirstOutput_ = true;
    }
#ifndef AV_CODEC_HCODEC_ENABLE_QOS_THE_WHOLE_TIME
    if (!isEncoder_ || codecRate_ < HIGH_FPS) {
        SetThreadInteractiveQos(false);
    }
#endif
    info.BeginCpuAccess();
#ifdef BUILD_ENG_VERSION
    info.Dump(compUniqueStrWithMime_, record_[OMX_DirOutput].frameCntTotal_, dumpMode_, isEncoder_);
#endif
    shared_ptr<OmxCodecBuffer> omxBuffer = info.omxBuffer;
    info.avBuffer->pts_ = omxBuffer->pts;
    info.avBuffer->flag_ = OmxFlagToUserFlag(omxBuffer->flag);
    if (info.avBuffer->memory_) {
        info.avBuffer->memory_->SetSize(static_cast<int32_t>(omxBuffer->filledLen));
        info.avBuffer->memory_->SetOffset(static_cast<int32_t>(omxBuffer->offset));
    }
    BeforeCbOutToUser(info);
    TimePoint now = ChangeOwner(info, BufferOwner::OWNED_BY_USER);
    AppendProcessTimeOfUs(info.avBuffer, omxBuffer->pts, now);
    callback_->OnOutputBufferAvailable(info.bufferId, info.avBuffer);
}

void HCodec::OnReleaseOutputBuffer(const MsgInfo &msg, BufferOperationMode mode)
{
    uint32_t bufferId = 0;
    (void)msg.param->GetValue(BUFFER_ID, bufferId);
    SCOPED_TRACE_FMT("id: %u", bufferId);
    optional<size_t> idx = FindBufferIndexByID(OMX_DirOutput, bufferId);
    if (!idx.has_value()) {
        ReplyErrorCode(msg.id, AVCS_ERR_INVALID_VAL);
        return;
    }
    BufferInfo& info = outputBufferPool_[idx.value()];
    if (info.owner != BufferOwner::OWNED_BY_USER) {
        HLOGE("wrong ownership: buffer id=%d, owner=%s", bufferId, ToString(info.owner));
        ReplyErrorCode(msg.id, AVCS_ERR_INVALID_VAL);
        return;
    }
    OnReleaseOutputBuffer(info);
    ChangeOwner(info, BufferOwner::OWNED_BY_US);
    ReplyErrorCode(msg.id, AVCS_ERR_OK);
    OnReleaseOutputBuffer(bufferId, mode);
}

void HCodec::OnReleaseOutputBuffer(uint32_t bufferId, BufferOperationMode mode)
{
    optional<size_t> idx = FindBufferIndexByID(OMX_DirOutput, bufferId);
    if (!idx.has_value()) {
        return;
    }
    BufferInfo& info = outputBufferPool_[idx.value()];
    switch (mode) {
        case KEEP_BUFFER: {
            return;
        }
        case RESUBMIT_BUFFER: {
            if (outputPortEos_) {
                HLOGD("output eos, keep this buffer");
                return;
            }
            int32_t ret = NotifyOmxToFillThisOutBuffer(info);
            if (ret != AVCS_ERR_OK) {
                SignalError(AVCODEC_ERROR_INTERNAL, AVCS_ERR_UNKNOWN);
            }
            return;
        }
        case FREE_BUFFER: {
            EraseBufferFromPool(OMX_DirOutput, idx.value());
            return;
        }
        default: {
            HLOGE("SHOULD NEVER BE HERE");
            return;
        }
    }
}

void HCodec::OnRenderOutputBuffer(const MsgInfo &msg, BufferOperationMode mode)
{
    ReplyErrorCode(msg.id, AVCS_ERR_UNSUPPORT);
}

void HCodec::ReclaimBuffer(OMX_DIRTYPE portIndex, BufferOwner owner, bool erase)
{
    vector<BufferInfo>& pool = (portIndex == OMX_DirInput) ? inputBufferPool_ : outputBufferPool_;
    for (size_t i = pool.size(); i > 0;) {
        i--;
        BufferInfo& info = pool[i];
        if (info.owner == owner) {
            ChangeOwner(info, BufferOwner::OWNED_BY_US);
            if (erase) {
                EraseBufferFromPool(portIndex, i);
            }
        }
    }
}

bool HCodec::ThereAreBufsStillOwnedByOmx(OMX_DIRTYPE portIndex)
{
    vector<BufferInfo>& pool = (portIndex == OMX_DirInput) ? inputBufferPool_ : outputBufferPool_;
    return std::any_of(pool.begin(), pool.end(), [](const BufferInfo& info) {
        return info.owner == BufferOwner::OWNED_BY_OMX;
    });
}

bool HCodec::IsAllBufferOwnedByUsOrSurface(OMX_DIRTYPE portIndex)
{
    const vector<BufferInfo>& pool = (portIndex == OMX_DirInput) ? inputBufferPool_ : outputBufferPool_;
    for (const BufferInfo& info : pool) {
        if (info.owner != BufferOwner::OWNED_BY_US &&
            info.owner != BufferOwner::OWNED_BY_SURFACE) {
            return false;
        }
    }
    return true;
}

bool HCodec::IsAllBufferOwnedByUsOrSurface()
{
    return IsAllBufferOwnedByUsOrSurface(OMX_DirInput) &&
           IsAllBufferOwnedByUsOrSurface(OMX_DirOutput);
}

void HCodec::ClearBufferPool(OMX_DIRTYPE portIndex)
{
    const vector<BufferInfo>& pool = (portIndex == OMX_DirInput) ? inputBufferPool_ : outputBufferPool_;
    for (size_t i = pool.size(); i > 0;) {
        i--;
        EraseBufferFromPool(portIndex, i);
    }
    OnClearBufferPool(portIndex);
}

void HCodec::FreeOmxBuffer(OMX_DIRTYPE portIndex, const BufferInfo& info)
{
    if (compNode_ && info.omxBuffer) {
        int32_t omxRet = compNode_->FreeBuffer(portIndex, *(info.omxBuffer));
        if (omxRet != HDF_SUCCESS) {
            HLOGW("notify omx to free buffer failed");
        }
    }
}

void HCodec::EraseOutBuffersOwnedByUsOrSurface()
{
    // traverse index in reverse order because we need to erase index from vector
    for (size_t i = outputBufferPool_.size(); i > 0;) {
        i--;
        const BufferInfo& info = outputBufferPool_[i];
        if (info.owner == BufferOwner::OWNED_BY_US || info.owner == BufferOwner::OWNED_BY_SURFACE) {
            EraseBufferFromPool(OMX_DirOutput, i);
        }
    }
}

void HCodec::OnSetOutputSurface(const MsgInfo &msg, BufferOperationMode mode)
{
    (void)msg;
    (void)mode;
    ReplyErrorCode(msg.id, AVCS_ERR_UNSUPPORT);
}

int32_t HCodec::ForceShutdown(int32_t generation, bool isNeedNotifyCaller)
{
    if (generation != stateGeneration_) {
        HLOGE("ignoring stale force shutdown message: #%d (now #%d)",
            generation, stateGeneration_);
        return AVCS_ERR_OK;
    }
    HLOGI("force to shutdown");
    isShutDownFromRunning_ = true;
    notifyCallerAfterShutdownComplete_ = isNeedNotifyCaller;
    keepComponentAllocated_ = false;
    auto err = compNode_->SendCommand(CODEC_COMMAND_STATE_SET, CODEC_STATE_IDLE, {});
    if (err == HDF_SUCCESS) {
        ChangeStateTo(stoppingState_);
    }
    return AVCS_ERR_OK;
}

void HCodec::SignalError(AVCodecErrorType errorType, int32_t errorCode)
{
    HLOGE("fatal error happened: errType=%d, errCode=%d", errorType, errorCode);
    hasFatalError_ = true;
    callback_->OnError(errorType, errorCode);
}

int32_t HCodec::DoSyncCall(MsgWhat msgType, std::function<void(ParamSP)> oper, uint32_t waitMs)
{
    ParamSP reply;
    return DoSyncCallAndGetReply(msgType, oper, reply, waitMs);
}

int32_t HCodec::DoSyncCallAndGetReply(MsgWhat msgType, std::function<void(ParamSP)> oper,
                                      ParamSP &reply, uint32_t waitMs)
{
    ParamSP msg = make_shared<ParamBundle>();
    IF_TRUE_RETURN_VAL_WITH_MSG(msg == nullptr, AVCS_ERR_NO_MEMORY, "out of memory");
    if (oper) {
        oper(msg);
    }
    bool ret = MsgHandleLoop::SendSyncMsg(msgType, msg, reply, waitMs);
    if (!ret) {
        HLOGE("wait msg %d(%s) time out", msgType, ToString(msgType));
        return AVCS_ERR_UNKNOWN;
    }
    int32_t err;
    IF_TRUE_RETURN_VAL_WITH_MSG(reply == nullptr || !reply->GetValue("err", err),
        AVCS_ERR_UNKNOWN, "error code of msg %d not replied", msgType);
    return err;
}

void HCodec::DeferMessage(const MsgInfo &info)
{
    deferredQueue_.push_back(info);
}

void HCodec::ProcessDeferredMessages()
{
    std::list<MsgInfo> queue = std::exchange(deferredQueue_, {});
    for (const MsgInfo &info : queue) {
        StateMachine::OnMsgReceived(info);
    }
    queue.clear();
}

void HCodec::ReplyToSyncMsgLater(const MsgInfo& msg)
{
    syncMsgToReply_[msg.type].push(std::make_pair(msg.id, msg.param));
}

bool HCodec::GetFirstSyncMsgToReply(MsgInfo& msg)
{
    auto iter = syncMsgToReply_.find(msg.type);
    if (iter == syncMsgToReply_.end()) {
        return false;
    }
    if (iter->second.empty()) {
        return false;
    }
    std::tie(msg.id, msg.param) = iter->second.front();
    iter->second.pop();
    if (iter->second.empty()) {
        syncMsgToReply_.erase(iter);
    }
    return true;
}

void HCodec::ReplyErrorCode(MsgId id, int32_t err)
{
    if (id == ASYNC_MSG_ID) {
        return;
    }
    ParamSP reply = make_shared<ParamBundle>();
    reply->SetValue("err", err);
    PostReply(id, reply);
}

void HCodec::ChangeOmxToTargetState(CodecStateType &state, CodecStateType targetState)
{
    int32_t ret = compNode_->SendCommand(CODEC_COMMAND_STATE_SET, targetState, {});
    if (ret != HDF_SUCCESS) {
        HLOGE("failed to change omx state, ret=%d", ret);
        return;
    }
    if (targetState == CODEC_STATE_LOADED) {
        for (const BufferInfo& info : inputBufferPool_) {
            FreeOmxBuffer(OMX_DirInput, info);
        }
        for (const BufferInfo& info : outputBufferPool_) {
            FreeOmxBuffer(OMX_DirOutput, info);
        }
    }
    int tryCnt = 0;
    do {
        if (tryCnt++ > 10) { // try up to 10 times
            HLOGE("failed to change to state(%d), abort", targetState);
            state = CODEC_STATE_INVALID;
            break;
        }
        this_thread::sleep_for(10ms); // wait 10ms
        ret = compNode_->GetState(state);
        if (ret != HDF_SUCCESS) {
            HLOGE("failed to get omx state, ret=%d", ret);
        }
    } while (ret == HDF_SUCCESS && state != targetState && state != CODEC_STATE_INVALID);
}

bool HCodec::RollOmxBackToLoaded()
{
    CodecStateType state;
    int32_t ret = compNode_->GetState(state);
    if (ret != HDF_SUCCESS) {
        HLOGE("failed to get omx node status(ret=%d), can not perform state rollback", ret);
        return false;
    }
    HLOGI("current omx state (%d)", state);
    switch (state) {
        case CODEC_STATE_EXECUTING: {
            ChangeOmxToTargetState(state, CODEC_STATE_IDLE);
            [[fallthrough]];
        }
        case CODEC_STATE_IDLE: {
            ChangeOmxToTargetState(state, CODEC_STATE_LOADED);
            [[fallthrough]];
        }
        case CODEC_STATE_LOADED:
        case CODEC_STATE_INVALID: {
            return true;
        }
        default: {
            HLOGE("invalid omx state: %d", state);
            return false;
        }
    }
}

void HCodec::CleanUpOmxNode()
{
    if (compNode_ == nullptr) {
        return;
    }
    RollOmxBackToLoaded();
}

int32_t HCodec::OnAllocateComponent()
{
    HitraceMeterFmtScoped trace(HITRACE_TAG_ZMEDIA, "hcodec %s %s", __func__, caps_.compName.c_str());
    compMgr_ = GetManager(false,
                          HCodecList::FindFeature(caps_.port.video.features, VIDEO_FEATURE_PASS_THROUGH).support,
                          isSecure_);
    if (compMgr_ == nullptr) {
        HLOGE("GetCodecComponentManager failed");
        return AVCS_ERR_UNKNOWN;
    }
    maxCodecInst_ = isEncoder_ ? GetMaxEncInstCnt() : GetMaxDecInstCnt();
    compCb_ = new HdiCallback(m_token);
    int32_t ret = compMgr_->CreateComponent(compNode_, componentId_, caps_.compName, 0, compCb_);
    if (ret != HDF_SUCCESS || compNode_ == nullptr) {
        compCb_ = nullptr;
        compMgr_ = nullptr;
        HLOGE("CreateComponent failed, ret=%d", ret);
        PrintAllCaller();
        std::unique_lock<std::shared_mutex> lk(g_mtx);
        auto& callers = g_callers[isEncoder_];
        size_t totalInstCntNow = CalculateTotalInstCnt();
        size_t singleAppInstCntNow = callers.count(caller_.app) > 0 ? callers[caller_.app].size() : 0;
        if ((totalInstCntNow >= totalWarnInstCnt_) || (singleAppInstCntNow >= singleAppWarnInstCnt_)) {
            ReportToRss();
        }
        return ret == OMX_ErrorInsufficientResources ? AVCS_ERR_INSUFFICIENT_HARDWARE_RESOURCES : AVCS_ERR_UNKNOWN;
    }
    totalWarnInstCnt_ = maxCodecInst_ * 0.6;
    singleAppWarnInstCnt_ = maxCodecInst_ * 0.5;
    compUniqueStr_ = to_string(componentId_) + (isEncoder_ ? "_e" : "_d");
    compUniqueStrWithMime_ = compUniqueStr_ + "_" + mime_;
    record_[OMX_DirInput].ownerTraceTag_ = { "[" + compUniqueStr_ + "]in_us", "[" + compUniqueStr_ + "]in_user",
        "[" + compUniqueStr_ + "]in_omx", "[" + compUniqueStr_ + "]in_surface"};
    record_[OMX_DirOutput].ownerTraceTag_ = { "[" + compUniqueStr_ + "]out_us", "[" + compUniqueStr_ + "]out_user",
        "[" + compUniqueStr_ + "]out_omx", "[" + compUniqueStr_ + "]out_surface"};
    HLOGI("create omx node %s succ", caps_.compName.c_str());
    PrintCaller();
    return AVCS_ERR_OK;
}

void HCodec::ReleaseComponent()
{
    CleanUpOmxNode();
    if (compMgr_ != nullptr) {
        RemoveCaller();
        compMgr_->DestroyComponent(componentId_);
    }
    compNode_ = nullptr;
    compCb_ = nullptr;
    compMgr_ = nullptr;
    componentId_ = 0;
}

int32_t HCodec::SetColorAspects(const Format &format)
{
    int range = 0;
    int primary = static_cast<int>(COLOR_PRIMARY_UNSPECIFIED);
    int transfer = static_cast<int>(TRANSFER_CHARACTERISTIC_UNSPECIFIED);
    int matrix = static_cast<int>(MATRIX_COEFFICIENT_UNSPECIFIED);

    if (format.GetIntValue(MediaDescriptionKey::MD_KEY_RANGE_FLAG, range)) {
        HLOGI("user set range flag %d", range);
    }
    if (format.GetIntValue(MediaDescriptionKey::MD_KEY_COLOR_PRIMARIES, primary)) {
        HLOGI("user set primary %d", primary);
    }
    if (format.GetIntValue(MediaDescriptionKey::MD_KEY_TRANSFER_CHARACTERISTICS, transfer)) {
        HLOGI("user set transfer %d", transfer);
    }
    if (format.GetIntValue(MediaDescriptionKey::MD_KEY_MATRIX_COEFFICIENTS, matrix)) {
        HLOGI("user set matrix %d", matrix);
    }
    if (primary < 0 || primary > UINT8_MAX ||
        transfer < 0 || transfer > UINT8_MAX ||
        matrix < 0 || matrix > UINT8_MAX) {
        HLOGW("invalid color");
        return AVCS_ERR_INVALID_VAL;
    }

    CodecVideoColorspace param;
    InitOMXParamExt(param);
    param.portIndex = isEncoder_ ? OMX_DirInput : OMX_DirOutput;
    param.aspects.range = static_cast<bool>(range);
    param.aspects.primaries = static_cast<uint8_t>(primary);
    param.aspects.transfer = static_cast<uint8_t>(transfer);
    param.aspects.matrixCoeffs = static_cast<uint8_t>(matrix);

    if (!SetParameter(OMX_IndexColorAspects, param)) {
        HLOGE("failed to set CodecVideoColorSpace");
        return AVCS_ERR_UNKNOWN;
    }
    HLOGI("set color aspects (isFullRange %d, primary %u, transfer %u, matrix %u) succ",
          param.aspects.range, param.aspects.primaries,
          param.aspects.transfer, param.aspects.matrixCoeffs);
    return AVCS_ERR_OK;
}
} // namespace OHOS::MediaAVCodec