/* * 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 #include #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__) namespace { constexpr int kReleaseThreadPollMs = 16; } VtInstance::VtInstance(int tunnelId) : mTunnelId(tunnelId), mVideoTunnelDev(VideoTunnelDev::getInstance()), mSolidBufferAllocator(VtAllocSolidColorBuffer::getInstance()), mOwnsDefaultDependencies(false), mReleaseThreadExit(false), mReleaseThreadCreated(false), mReleaseThread(0) { 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() { stopReleaseThread(); 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) { bool needStopReleaseThread = false; std::unique_lock 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); needStopReleaseThread = queueReleaseBufferLocked(ptrBufferItem); } lock.unlock(); if (needStopReleaseThread) stopReleaseThread(); 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; struct vt_meta_data meta_data; 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); memset(&meta_data, 0, sizeof(meta_data)); std::shared_ptr item = std::make_shared(info.fd, info.timestamp, info, meta_data, 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 || !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() { stopReleaseThread(); { std::lock_guard lock(mMutex); releaseDelayedBuffersLocked(true); } 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; 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) { std::shared_ptr item = std::make_shared(); ret = mVideoTunnelDev.acquireBuffer(mTunnelId, item->mBufferFd, item->mTimeStamp, item->mMetaData); if (ret == 0) { if (isNeedDisplay) { memset(&item->mInfo, 0, sizeof(item->mInfo)); item->mInfo.fd = item->mBufferFd; UvmDev::getInstance().getVideoInfo(item->mInfo); #ifndef LINUX_COMPILE if (HwcConfig::enableDrmVideo()) { item->mInfo.crop_left += mCrop.left; item->mInfo.crop_top += mCrop.top; item->mInfo.crop_right -= mCrop.right; item->mInfo.crop_bottom -= mCrop.bottom; } else { item->mInfo.crop_left = mCrop.left; item->mInfo.crop_top = mCrop.top; item->mInfo.crop_right = mCrop.right; item->mInfo.crop_bottom = mCrop.bottom; } #else item->mInfo.crop_left += mCrop.left; item->mInfo.crop_top += mCrop.top; item->mInfo.crop_right -= mCrop.right; item->mInfo.crop_bottom -= mCrop.bottom; #endif item->mInfo.buf_stride = item->mInfo.buf_width; item->mInfo.buf_format = DRM_FORMAT_NV21; VTINST_LOGV("acquire buffer %d timeStamp %" PRId64 ", stride %d, w/h(%d %d), crop(%d %d %d %d)", item->mBufferFd, item->mTimeStamp, item->mInfo.buf_stride, item->mInfo.buf_width, item->mInfo.buf_height, item->mInfo.crop_left, item->mInfo.crop_top, item->mInfo.crop_right, item->mInfo.crop_bottom); VTINST_LOGV("hdr types: %d hdr data: smpte2086[%f %f %f %f %f %f %f %f %f %f] cta8613[%f %f]", item->mMetaData.hdr.validTypes, item->mMetaData.hdr.smpte2086.displayPrimaryRed.x, item->mMetaData.hdr.smpte2086.displayPrimaryRed.y, item->mMetaData.hdr.smpte2086.displayPrimaryGreen.x, item->mMetaData.hdr.smpte2086.displayPrimaryGreen.y, item->mMetaData.hdr.smpte2086.displayPrimaryBlue.x, item->mMetaData.hdr.smpte2086.displayPrimaryBlue.y, item->mMetaData.hdr.smpte2086.whitePoint.x, item->mMetaData.hdr.smpte2086.whitePoint.y, item->mMetaData.hdr.smpte2086.maxLuminance, item->mMetaData.hdr.smpte2086.minLuminance, item->mMetaData.hdr.cta8613.maxContentLightLevel, item->mMetaData.hdr.cta8613.maxFrameAverageLightLevel); VTINST_LOGV("buffer info[%d %d %d %d %d %d %d %d %d]",item->mMetaData.stride_in_pixel, item->mMetaData.aligned_height, item->mMetaData.bufferFormat, item->mMetaData.buffer_w, item->mMetaData.buffer_h, item->mMetaData.crop_x, item->mMetaData.crop_y, item->mMetaData.crop_w, item->mMetaData.crop_h); items.push_back(item); mBufferQueue.push_back(std::move(item)); } else { VTINST_LOGV("acquire buffer %d timeStamp %" PRId64 " will drop for status %s", item->mBufferFd, item->mTimeStamp, vtVideoStatusToString(mLastData.videoStatus)); mVideoTunnelDev.releaseBuffer(mTunnelId, item->mBufferFd, -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; } bool VtInstance::shouldDelayReleaseLocked() { return DebugHelper::getInstance().discardOutFence() && DebugHelper::getInstance().videoOutFenceDepth() > 0; } bool VtInstance::isReleaseFenceReadyLocked(std::shared_ptr & item) { if (!item || item->mIsSolidBuffer) return true; return item->getReleaseFenceStatus() != DrmFence::Status::Unsignaled; } bool VtInstance::queueReleaseBufferLocked(std::shared_ptr & item) { if (!item) return false; if (item->mIsSolidBuffer) { releaseBufferLocked(item); return false; } if (!shouldDelayReleaseLocked()) { releaseDelayedBuffersLocked(true); releaseBufferLocked(item); return mReleaseThreadCreated; } if (!startReleaseThreadLocked()) { releaseBufferLocked(item); return false; } mDelayedReleaseQueue.push_back(item); if (mDelayedReleaseQueue.size() >= DebugHelper::getInstance().videoOutFenceDepth()) { releaseDelayedBuffersLocked(false); } mReleaseCondition.notify_all(); return false; } void VtInstance::releaseDelayedBuffersLocked(bool force) { if (mDelayedReleaseQueue.empty()) return; if (force || !shouldDelayReleaseLocked()) { while (!mDelayedReleaseQueue.empty()) { std::shared_ptr item = mDelayedReleaseQueue.front(); mDelayedReleaseQueue.pop_front(); releaseBufferLocked(item); } return; } while (!mDelayedReleaseQueue.empty()) { std::shared_ptr item = mDelayedReleaseQueue.front(); if (!isReleaseFenceReadyLocked(item)) break; mDelayedReleaseQueue.pop_front(); releaseBufferLocked(item); } } bool VtInstance::startReleaseThreadLocked() { if (mReleaseThreadCreated) return true; mReleaseThreadExit = false; int ret = pthread_create(&mReleaseThread, NULL, releaseThreadMain, this); if (ret) { VTINST_LOGE("failed to create release thread: %s", strerror(ret)); mReleaseThreadExit = false; mReleaseThread = 0; return false; } mReleaseThreadCreated = true; pthread_setname_np(mReleaseThread, "VtReleaseThread"); return true; } void VtInstance::stopReleaseThread() { pthread_t releaseThread; { std::unique_lock lock(mMutex); if (!mReleaseThreadCreated) return; mReleaseThreadExit = true; releaseThread = mReleaseThread; mReleaseCondition.notify_all(); } pthread_join(releaseThread, NULL); std::lock_guard lock(mMutex); mReleaseThreadCreated = false; mReleaseThreadExit = false; } void *VtInstance::releaseThreadMain(void * data) { set_thread_debug_info("VtReleaseThread"); VtInstance* pThis = static_cast(data); pThis->releaseThreadLoop(); return NULL; } void VtInstance::releaseThreadLoop() { while (true) { std::unique_lock lock(mMutex); mReleaseCondition.wait_for(lock, std::chrono::milliseconds(kReleaseThreadPollMs), [this] { return mReleaseThreadExit; }); if (mReleaseThreadExit) return; releaseDelayedBuffersLocked(false); } } 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); releaseDelayedBuffersLocked(true); 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; struct vt_meta_data meta_data; 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); memset(&meta_data, 0, sizeof(meta_data)); std::shared_ptr item = std::make_shared (info.fd, info.timestamp, info, meta_data, true); items.push_back(item); mBufferQueue.push_back(std::move(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)); } }