/* * Copyright (c) 2021 Amlogic, Inc. All rights reserved. * * This source code is subject to the terms and conditions defined in the * file 'LICENSE' which is part of this source code package. * * Description: */ #define LOG_NDEBUG 1 #include #include "VtInstance.h" #include "VtAllocSolidColorBuffer.h" #ifndef LINUX_COMPILE #include #endif #define VTINST_LOGV(fmt,...) MESON_LOGV("[%s] [%s] " fmt, mName, __func__, ##__VA_ARGS__) #define VTINST_LOGD(fmt,...) MESON_LOGD("[%s] [%s] " fmt, mName, __func__, ##__VA_ARGS__) #define VTINST_LOGW(fmt,...) MESON_LOGW("[%s] [%s] " fmt, mName, __func__, ##__VA_ARGS__) #define VTINST_LOGE(fmt,...) MESON_LOGE("[%s] [%s] " fmt, mName, __func__, ##__VA_ARGS__) VtInstance::VtInstance(int tunnelId) : mTunnelId(tunnelId), mVideoTunnelDev(VideoTunnelDev::getInstance()), mSolidBufferAllocator(VtAllocSolidColorBuffer::getInstance()), mOwnsDefaultDependencies(false) { mConsumers.clear(); mRefreshConsumers.clear(); mBufferQueue.clear(); memset(&mCrop, 0, sizeof(vt_rect_t)); snprintf(mName, 64, "VtInstance-%d", tunnelId); memset(&mLastData, 0, sizeof(last_data_t)); mLastData.videoStatus = VT_VIDEO_STATUS_SHOW; mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID; } // New constructor with dependency injection (for testing) VtInstance::VtInstance(int tunnelId, VideoTunnelDev& videoTunnelDev, ISolidColorBufferAllocator& solidBufferAllocator) : mTunnelId(tunnelId), mVideoTunnelDev(videoTunnelDev), mSolidBufferAllocator(solidBufferAllocator), mOwnsDefaultDependencies(false) { mConsumers.clear(); mBufferQueue.clear(); memset(&mCrop, 0, sizeof(vt_rect_t)); snprintf(mName, 64, "VtInstance-%d", tunnelId); memset(&mLastData, 0, sizeof(last_data_t)); mLastData.videoStatus = VT_VIDEO_STATUS_SHOW; mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID; } VtInstance::~VtInstance() { auto it = mConsumers.begin(); for (; it != mConsumers.end(); it++) { if ((*it).get()) (*it)->setReleaseListener(nullptr); } mConsumers.clear(); mRefreshConsumers.clear(); releaseInstanceBuffers(); disconnect(); } int32_t VtInstance::onFrameDisplayed(std::shared_ptr & item) { std::lock_guard lock(mMutex); std::shared_ptr ptrBufferItem; if (mBufferQueue.empty()) { VTINST_LOGV("buffer queue is empty (%s)", item->toString().c_str()); item->closeFd(); return -1; } for (auto it = mBufferQueue.begin(); it != mBufferQueue.end();) { ptrBufferItem = *it; if (ptrBufferItem.get() != item.get()) { ++it; continue; } if (ptrBufferItem->unrefHandle()) { it = mBufferQueue.erase(it); releaseBufferLocked(ptrBufferItem); } return 0; } if (!item->mIsSolidBuffer) VTINST_LOGE("cannot find (%s) in buffer queue", item->toString().c_str()); item->closeFd(); return -1; } int32_t VtInstance::handleRefresh() { int32_t ret = 0; enum vt_cmd cmd; struct vt_cmd_data cmdData; if (mRefreshConsumers.empty()) return ret; for (auto it = mRefreshConsumers.begin(); it != mRefreshConsumers.end(); it++) { if (mLastData.gameMode == 1) { memset(&cmdData, 0, sizeof(cmdData)); cmd = VT_CMD_SET_GAME_MODE; cmdData.data = mLastData.gameMode; ret = (*it)->onVtCmds(cmd, cmdData); } if (mLastData.transform != 0) { memset(&cmdData, 0, sizeof(cmdData)); cmd = VT_CMD_SET_VIDEO_TRANSFORM; cmdData.transform = mLastData.transform; ret = (*it)->onVtCmds(cmd, cmdData); } if (vt_rect_is_valid(&mLastData.displayFrame)) { memset(&cmdData, 0, sizeof(cmdData)); cmd = VT_CMD_SET_DISPLAY_FRAME; cmdData.rect = mLastData.displayFrame; ret = (*it)->onVtCmds(cmd, cmdData); } if (mLastData.solidBufferType != SOLID_COLOR_FOR_INVALID && (mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ONCE || mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS || mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS_FOR_VRR)) { VTINST_LOGD("status:%s, solidBufferType:%s", vtVideoStatusToString(mLastData.videoStatus), vtVideoColorTypeToString(mLastData.solidBufferType)); struct uvm_fd_info info; memset(&info, 0, sizeof(info)); mSolidBufferAllocator.getBufferInfo(mLastData.solidBufferType, info); if (info.fd >= 0) { std::vector> items; info.fd = dup(info.fd); std::shared_ptr item = std::make_shared(info.fd, info.timestamp, info, true); items.push_back(item); ret = sendBufferToConsumer(mRefreshConsumers, items); } } } /* refresh buffers*/ if (!mBufferQueue.empty() && mLastData.videoStatus == VT_VIDEO_STATUS_SHOW && mLastData.solidBufferType == SOLID_COLOR_FOR_INVALID) { std::vector> items; std::shared_ptr lastItem = mBufferQueue.back(); if (lastItem.get()) { items.push_back(lastItem); ret = sendBufferToConsumer(mRefreshConsumers, items); } } mRefreshConsumers.clear(); return ret; } int32_t VtInstance::registerVtConsumer( std::shared_ptr& consumer, bool refresh) { /* only Hwc2Layer will call this api when have * an videotunnel layer create */ if (!consumer || !consumer.get()) { VTINST_LOGE("consumer is null"); return -1; } consumer->setReleaseListener(this); mConsumers.push_back(consumer); if (refresh) mRefreshConsumers.push_back(consumer); return 0; } int32_t VtInstance::unregisterVtConsumer( std::shared_ptr& consumer) { /* only Hwc2Layer will call this api when videotunnel layer destroy. * and Hwc2Layer will release all buffer Fd before call this api */ int32_t ret = -1; if (!consumer.get()) { VTINST_LOGE("consumer is null"); return ret; } for (auto it = mRefreshConsumers.begin(); it != mRefreshConsumers.end(); ) { std::shared_ptr item = (*it); if (item.get() == consumer.get()) { it = mRefreshConsumers.erase(it); ret = 0; break; } else { it++; } } for (auto it = mConsumers.begin(); it != mConsumers.end(); ) { std::shared_ptr item = (*it); if (item.get() == consumer.get()) { VTINST_LOGD("remove consumer"); it = mConsumers.erase(it); ret = 0; break; } else { it++; } } if (ret) VTINST_LOGW("cannot find VT consumer %p",consumer.get()); return ret; } int32_t VtInstance::connect() { int32_t ret = -1; if (mTunnelId >= 0) { ret = mVideoTunnelDev.connect(mTunnelId); if (ret >= 0) { VTINST_LOGD("connect to videotunnel succeeded"); } else { VTINST_LOGE("connect to videotunnel filed"); } } else { VTINST_LOGE("tunnel id is invalid"); } return ret; } void VtInstance::disconnect() { VTINST_LOGD("disconnect videotunnel"); if (mTunnelId >= 0) mVideoTunnelDev.disconnect(mTunnelId); mTunnelId = -1; memset(&mLastData, 0, sizeof(last_data_t)); mLastData.videoStatus = VT_VIDEO_STATUS_SHOW; mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID; } int32_t VtInstance::sendBufferToConsumer( std::vector> & consumers, std::vector> & items) { int32_t ret = 0; if (!items.empty() && !consumers.empty()) { for (auto it = consumers.begin(); it != consumers.end(); ) { ret = (*it)->onFrameAvailable(items); if (ret < 0 && ret != -EAGAIN) { VTINST_LOGE("call consumer(%p) onFrameAvailable failed", (*it).get()); it = consumers.erase(it); ret = -EAGAIN; } else { it++; if (ret != -EAGAIN) for (auto item = items.begin(); item != items.end(); item++) (*item)->refHandle(); } } } return ret; } int32_t VtInstance::acquireBuffer() { bool isNeedDisplay = true; int32_t ret = 0; int bufFd = -1; int64_t timeStamp = -1; std::vector> items; if (mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS_FOR_VRR || mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS || mLastData.videoStatus == VT_VIDEO_STATUS_HIDE || mLastData.videoStatus == VT_VIDEO_STATUS_HOLD_FRAME) { isNeedDisplay = false; } { std::lock_guard lock(mMutex); while (true) { ret = mVideoTunnelDev.acquireBuffer(mTunnelId, bufFd, timeStamp); if (ret == 0) { if (isNeedDisplay) { struct uvm_fd_info info; memset(&info, 0, sizeof(info)); info.fd = bufFd; UvmDev::getInstance().getVideoInfo(info); #ifndef LINUX_COMPILE if (HwcConfig::enableDrmVideo()) { info.crop_left += mCrop.left; info.crop_top += mCrop.top; info.crop_right -= mCrop.right; info.crop_bottom -= mCrop.bottom; } else { info.crop_left = mCrop.left; info.crop_top = mCrop.top; info.crop_right = mCrop.right; info.crop_bottom = mCrop.bottom; } #else info.crop_left += mCrop.left; info.crop_top += mCrop.top; info.crop_right -= mCrop.right; info.crop_bottom -= mCrop.bottom; #endif info.buf_stride = info.buf_width; info.buf_format = DRM_FORMAT_NV21; VTINST_LOGV("acquire buffer %d timeStamp %" PRId64 ", stride %d, w/h(%d %d), crop(%d %d %d %d)", bufFd, timeStamp, info.buf_stride, info.buf_width, info.buf_height, info.crop_left, info.crop_top, info.crop_right, info.crop_bottom); std::shared_ptr item = std::make_shared (bufFd, timeStamp, info); items.push_back(item); mBufferQueue.push_back(item); } else { VTINST_LOGV("acquire buffer %d timeStamp %" PRId64 " will drop for status %s", bufFd, timeStamp, vtVideoStatusToString(mLastData.videoStatus)); mVideoTunnelDev.releaseBuffer(mTunnelId, bufFd, -1); } } else if (ret == -EAGAIN) { /* no buffer available */ break; } else { VTINST_LOGE("acquire buffer error %d", ret); return ret; } } } if (isNeedDisplay) { mLastData.videoStatus = VT_VIDEO_STATUS_SHOW; mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID; ret = sendBufferToConsumer(mConsumers, items); } return ret; } void VtInstance::releaseBufferLocked(std::shared_ptr & item) { if (item.get()) { if (!item->mIsSolidBuffer) { VTINST_LOGV("release buffer (%s)", item->toString().c_str()); mVideoTunnelDev.releaseBuffer(mTunnelId, item->mBufferFd, item->getReleaseFenceFd()); } else { VTINST_LOGV("release solid buffer %d", item->mBufferFd); item->closeFd(); } } } void VtInstance::releaseInstanceBuffers() { std::lock_guard lock(mMutex); if (mBufferQueue.empty()) { VTINST_LOGV("buffer queue is empty"); return; } for (auto it = mBufferQueue.begin(); it != mBufferQueue.end(); ++it) { std::shared_ptr item = *it; if (!item->mIsSolidBuffer) { VTINST_LOGV("release buffer(%s)", item->toString().c_str()); mVideoTunnelDev.releaseBuffer(mTunnelId, item->mBufferFd, -1); } else { VTINST_LOGV("release solid buffer %d", item->mBufferFd); item->closeFd(); } } mBufferQueue.clear(); } void VtInstance::onVtCmds(vt_cmd_t cmd, vt_cmd_data_t cmdData, bool & needSendToConsumer, struct uvm_fd_info & info) { bool debuglog = true; vt_video_color_t prevType = mLastData.solidBufferType; needSendToConsumer = false; switch (cmd) { case VT_CMD_SET_SHOW_SOLID_COLOR: mLastData.videoStatus = VT_VIDEO_STATUS_COLOR_ONCE; mLastData.solidBufferType = (vt_video_color_t)cmdData.data; break; case VT_CMD_SET_COLOR_BLACK: mLastData.videoStatus = (vt_video_status_t)cmdData.data; mLastData.solidBufferType = SOLID_COLOR_FOR_BLACK; break; case VT_CMD_SET_COLOR_BLUE: mLastData.videoStatus = (vt_video_status_t)cmdData.data; mLastData.solidBufferType = SOLID_COLOR_FOR_BLUE; break; case VT_CMD_SET_COLOR_GREEN: /* cannot support green buffer */ //mLastData.videoStatus = (vt_video_status_t)cmdData.data; //mLastData.solidBufferType = SOLID_COLOR_FOR_GREEN; mLastData.videoStatus = VT_VIDEO_STATUS_SHOW; mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID; break; case VT_CMD_SET_VIDEO_STATUS: mLastData.videoStatus = (vt_video_status_t)cmdData.data; if (mLastData.videoStatus == VT_VIDEO_STATUS_BLANK || mLastData.videoStatus == VT_VIDEO_STATUS_HIDE) { mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID; needSendToConsumer = true; } break; case VT_CMD_SET_SOURCE_CROP: mCrop = cmdData.rect; debuglog = false; VTINST_LOGD("cmd VT_CMD_SET_SOURCE_CROP(%d %d %d %d)", mCrop.left, mCrop.top, mCrop.right, mCrop.bottom); break; case VT_CMD_SET_VIDEO_TRANSFORM: mLastData.transform = cmdData.transform; needSendToConsumer = true; break; case VT_CMD_SET_GAME_MODE: mLastData.gameMode = cmdData.data; needSendToConsumer = true; break; case VT_CMD_SET_DISPLAY_FRAME: mLastData.displayFrame = cmdData.rect; needSendToConsumer = true; break; default: debuglog = false; needSendToConsumer = true; break; } if (mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_DISABLE || mLastData.videoStatus == VT_VIDEO_STATUS_SHOW) mLastData.solidBufferType = SOLID_COLOR_FOR_INVALID; if (debuglog) VTINST_LOGD("cmd:%s, status:%s, solidBufferType:%s", vtCmdToString(cmd), vtVideoStatusToString(mLastData.videoStatus), vtVideoColorTypeToString(mLastData.solidBufferType)); if (prevType != mLastData.solidBufferType && mLastData.solidBufferType != SOLID_COLOR_FOR_INVALID) mSolidBufferAllocator.getBufferInfo(mLastData.solidBufferType, info); } int32_t VtInstance::receiveCmds() { bool needSendToConsumer; int32_t ret = 0; enum vt_cmd cmd; struct vt_cmd_data cmdData; struct uvm_fd_info info; std::vector> items; ret = mVideoTunnelDev.receiveCmd(mTunnelId, cmd, cmdData); if (ret < 0) return ret; memset(&info, 0, sizeof(uvm_fd_info)); info.fd = -1; info.fps = -1; onVtCmds(cmd, cmdData, needSendToConsumer, info); if (info.fd >= 0) { VTINST_LOGD("alloc solid buffer %d, stride %d, w/h(%d %d), crop(%d %d %d %d)", info.fd, info.buf_stride, info.buf_width, info.buf_height, info.crop_left, info.crop_top, info.crop_right, info.crop_bottom); std::lock_guard lock(mMutex); std::shared_ptr item = std::make_shared (info.fd, info.timestamp, info, true); items.push_back(item); mBufferQueue.push_back(item); } if (!items.empty()) return sendBufferToConsumer(mConsumers,items); if (needSendToConsumer) { for (auto it = mConsumers.begin(); it != mConsumers.end(); it++) { ret = (*it)->onVtCmds(cmd, cmdData); } } return ret; } bool VtInstance::needDestroyThisInstance() { return mConsumers.empty() ? true : false; } void VtInstance::onClearAlwaysVRRMode() { std::lock_guard lock(mMutex); if (mLastData.videoStatus == VT_VIDEO_STATUS_COLOR_ALWAYS_FOR_VRR) { mLastData.videoStatus = VT_VIDEO_STATUS_COLOR_ONCE; VTINST_LOGV("change video statu %s -> %s", vtVideoStatusToString(VT_VIDEO_STATUS_COLOR_ALWAYS_FOR_VRR), vtVideoStatusToString(mLastData.videoStatus)); } }