/* * Copyright (C) 2021 Amlogic Corporation. * * 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 #include #include "wayland_plugin.h" #include "wayland_display.h" #include "Logger.h" #include "Times.h" #include "ErrorCode.h" #include "aml_version.h" #define TAG "rlib:wayland_plugin" #define UNDER_FLOW_EXPIRED_TIME_MS 83 WaylandPlugin::WaylandPlugin(int logCatgory) : mRenderLock("renderlock"), mLogCategory(logCatgory) { mDisplay = new WaylandDisplay(this, logCatgory); mQueue = new ATools::Queue(); mPaused = false; mImmediatelyOutput = false; mUnderFlowDetect = false; } WaylandPlugin::~WaylandPlugin() { if (mDisplay) { delete mDisplay; mDisplay = NULL; } if (mQueue) { mQueue->flush(); delete mQueue; mQueue = NULL; } TRACE(mLogCategory,"desconstruct"); } void WaylandPlugin::init() { INFO(mLogCategory,"\n--------------------------------\n" "plugin : weston\n" "ARCH : %s\n" "branch name : %s\n" "git version : %s\n" "change id : %s \n" "ID : %s \n" "last changed: %s\n" "build-time : %s\n" "build-name : %s\n" "level : /tmp \n" "--------------------------------\n", #if defined(__aarch64__) "arm64", #else "arm", #endif BRANCH_NAME, GIT_VERSION, COMMIT_CHANGEID, COMMIT_PD, LAST_CHANGED, BUILD_TIME, BUILD_NAME ); } void WaylandPlugin::release() { } void WaylandPlugin::setCallback(void *userData, PluginCallback *callback) { mUserData = userData; mCallback = callback; } int WaylandPlugin::openDisplay() { int ret = NO_ERROR; ATools::Mutex::Autolock _l(mRenderLock); DEBUG(mLogCategory,"openDisplay"); ret = mDisplay->openDisplay(); if (ret != NO_ERROR) { ERROR(mLogCategory,"Error open display"); return ret; } DEBUG(mLogCategory,"openDisplay end"); return ret; } int WaylandPlugin::openWindow() { ATools::Mutex::Autolock _l(mRenderLock); /* if weston can't support pts feature, * we should create a post buffer thread to * send buffer by mono time */ WaylandDisplay::AmlConfigAPIList *amlconfig = mDisplay->getAmlConfigAPIList(); if (!amlconfig->enableSetPts) { DEBUG(mLogCategory,"run frame post thread"); setThreadPriority(60); run("waylandPostBuf"); } return NO_ERROR; } int WaylandPlugin::prepareFrame(RenderBuffer *buffer) { mDisplay->prepareFrameBuffer(buffer); return NO_ERROR; } int WaylandPlugin::displayFrame(RenderBuffer *buffer, int64_t displayTime) { //resume report under flow if (mUnderFlowDetect) { mUnderFlowDetect = false; } /* if weston can't support pts feature, * push buffer to queue, the buffer will send to * weston in post thread */ WaylandDisplay::AmlConfigAPIList *amlconfig = mDisplay->getAmlConfigAPIList(); if (!amlconfig->enableSetPts) { buffer->time = displayTime; mQueue->push(buffer); //let post thread to post buffer asap if (mQueue->getCnt() == 1) { signalWaitTimeout(); } DEBUG(mLogCategory,"queue size:%d,pts:%lld us,time:%lld us",mQueue->getCnt(),buffer->pts/1000,displayTime); } else { buffer->time = displayTime; mDisplay->displayFrameBuffer(buffer, displayTime); } return NO_ERROR; } void WaylandPlugin::queueFlushCallback(void *userdata,void *data) { WaylandPlugin* plugin = static_cast(userdata); plugin->handleFrameDropped((RenderBuffer *)data); plugin->handleBufferRelease((RenderBuffer *)data); } int WaylandPlugin::flush() { WaylandDisplay::AmlConfigAPIList *amlconfig = mDisplay->getAmlConfigAPIList(); mQueue->flushAndCallback(this, WaylandPlugin::queueFlushCallback); mDisplay->flushBuffers(); if (!amlconfig->enableSetPts) { signalWaitTimeout(); } return NO_ERROR; } int WaylandPlugin::pause() { /*pause and resume can cause frame dropped. those frames ready to send to weston in queue will drop when resume*/ mPaused = true; mDisplay->pause(); return NO_ERROR; } int WaylandPlugin::resume() { mPaused = false; mDisplay->resume(); return NO_ERROR; } int WaylandPlugin::closeDisplay() { ATools::Mutex::Autolock _l(mRenderLock); mDisplay->closeDisplay(); return NO_ERROR; } int WaylandPlugin::closeWindow() { ATools::Mutex::Autolock _l(mRenderLock); if (isRunning()) { signalWaitTimeout(); DEBUG(mLogCategory,"stop frame post thread"); requestExitAndWait(); } mQueue->flushAndCallback(this, WaylandPlugin::queueFlushCallback); return NO_ERROR; } int WaylandPlugin::getValue(PluginKey key, void *value) { switch (key) { case PLUGIN_KEY_SELECT_DISPLAY_OUTPUT: { *(int *)(value) = mDisplay->getDisplayOutput(); TRACE(mLogCategory,"get select display output:%d",*(int *)value); } break; case PLUGIN_KEY_CURRENT_OUTPUT: { *(int *)(value) = mDisplay->getCurrentOutputCrtcIndex(); //DEBUG(mLogCategory,"get current crtc output index:%d",*(int *)value); } break; case PLUGIN_KEY_ZORDER: { *(int *)(value) = mDisplay->getVideoPlaneZorder(); } break; case PLUGIN_KEY_VIDEO_HWC_LATENCY: { *(int *)(value) = mDisplay->getHWCLatency(); DEBUG(mLogCategory,"get HWC latency :%d us", *(int *)(value)); } break; case PLUGIN_KEY_PROVIDE_VSYNC: { *(int *)(value) = 0; WaylandDisplay::AmlConfigAPIList *amlConfigAPI = mDisplay->getAmlConfigAPIList(); if (amlConfigAPI->enableVsyncCallback) { *(int *)(value) = 1; } DEBUG(mLogCategory,"provide vsync: %d", *(int *)(value)); } break; } return NO_ERROR; } int WaylandPlugin::setValue(PluginKey key, void *value) { switch (key) { case PLUGIN_KEY_WINDOW_SIZE: { RenderRect* rect = static_cast(value); if (mDisplay) { mDisplay->setWindowSize(rect->x, rect->y, rect->w, rect->h); } } break; case PLUGIN_KEY_FRAME_SIZE: { RenderFrameSize * frameSize = static_cast(value); if (mDisplay) { mDisplay->setFrameSize(frameSize->width, frameSize->height); } } break; case PLUGIN_KEY_VIDEO_FORMAT: { int videoFormat = *(int *)(value); DEBUG(mLogCategory,"Set video format :%d",videoFormat); mDisplay->setVideoBufferFormat((RenderVideoFormat)videoFormat); } break; case PLUGIN_KEY_SELECT_DISPLAY_OUTPUT: { int outputIndex = *(int *)(value); INFO(mLogCategory,"Set select display output :%d",outputIndex); mDisplay->setDisplayOutput(outputIndex); } break; case PLUGIN_KEY_VIDEO_PIP: { int pip = *(int *) (value); pip = pip > 0? 1: 0; mDisplay->setPip(pip); } break; case PLUGIN_KEY_IMMEDIATELY_OUTPUT: { int enableImmediately = (*(int *)(value)); mImmediatelyOutput = enableImmediately > 0? true: false; mDisplay->setImmediatelyOutput(enableImmediately); INFO(mLogCategory, "Set immediately output:%d",mImmediatelyOutput); } break; case PLUGIN_KEY_KEEP_LAST_FRAME: { int keep = *(int *) (value); INFO(mLogCategory, "Set keep last frame:%d",keep); mDisplay->setKeepLastFrame(keep); } break; case PLUGIN_KEY_KEEP_LAST_FRAME_ON_FLUSH: { int keepOnFlush = *(int *) (value); INFO(mLogCategory, "Set keep last frame on flush:%d",keepOnFlush); mDisplay->setKeepLastFrameOnFlush(keepOnFlush); } break; case PLUGIN_KEY_FORCE_ASPECT_RATIO: { int forceAspectRatio = *(int *)(value); INFO(mLogCategory, "force aspect ratio:%d, TODO ",forceAspectRatio); } break; case PLUGIN_KEY_PIXEL_ASPECT_RATIO: { double ratio = *(double *)(value); INFO(mLogCategory,"pixel aspect ratio :%f",ratio); mDisplay->setPixelAspectRatio((double)ratio); } break; case PLUGIN_KEY_VIDEO_FRAME_RATE: { RenderFraction * fraction = static_cast(value); INFO(mLogCategory,"frame rate,num:%d,denom:%d",fraction->num,fraction->denom); mDisplay->setFrameRate(fraction->num, fraction->denom); } break; case PLUGIN_KEY_ZORDER: { int zorder = *(int *)(value); INFO(mLogCategory,"set video plane zorder:%d",zorder); mDisplay->setVideoPlaneZorder(zorder); } break; case PLUGIN_KEY_ROTATE_VIDEO: { int degree = *(int *)(value); INFO(mLogCategory,"set rotate video to:%d",degree); mDisplay->setRotateVideo(degree); } break; case PLUGIN_KEY_VIDEO_STEP_DISPLAY_FRAME: { int stepDisplayFrame = *(int *)(value); INFO(mLogCategory,"set step display frame:%d",stepDisplayFrame); } break; case PLUGIN_KEY_HIDE_VIDEO: { int hide = *(int *)(value); INFO(mLogCategory,"set hide video:%d",hide); mDisplay->setHideVideo(hide); } break; case PLUGIN_KEY_VIDEO_ON_OSD: { int videoOnOSD = *(int *)(value); INFO(mLogCategory,"set video on osd:%d",videoOnOSD); mDisplay->setVideoOnOSD(videoOnOSD); } break; case PLUGIN_KEY_CROP_FRAME_SIZE: { RenderRect * rect = static_cast(value); if (mDisplay) { mDisplay->setCropSize(rect->x, rect->y, rect->w, rect->h); } } break; } return 0; } int WaylandPlugin::unexpectReleaseFrame(RenderBuffer *buffer) { return mDisplay->unexpectReleaseFrame(buffer); } bool WaylandPlugin::getDisplayVsyncAndPeriod (uint64_t *vsyncTimestamp, uint64_t *vsyncPeriod) { int ret = NO_ERROR; ret = mDisplay->getDisplayVsyncAndPeriod(vsyncTimestamp, vsyncPeriod); if (ret == NO_ERROR) { return true; } return false; } void WaylandPlugin::waitTimeoutUs(int64_t timeoutUs) { if (mTimeMutex.lock() == 0) { //success mTimeCond.waitRelativeUs(mTimeMutex, timeoutUs); mTimeMutex.unlock(); } } void WaylandPlugin::signalWaitTimeout() { if (mTimeMutex.lock() == 0) { //success mTimeCond.signal(); mTimeMutex.unlock(); } } /** * @brief report underflow to player * only report one time when detected * under flow in playing state */ void WaylandPlugin::reportUnderFlow() { if (!mPaused && !mUnderFlowDetect) { mUnderFlowDetect = true; INFO(mLogCategory,"report under flow"); handleMsgNotify(MSG_UNDER_FLOW, NULL); mDisplay->underflow(); } } void WaylandPlugin::handleBufferRelease(RenderBuffer *buffer) { if (mCallback) { mCallback->doBufferReleaseCallback(mUserData, (void *)buffer); } } void WaylandPlugin::handleFrameDisplayed(RenderBuffer *buffer) { if (mCallback) { mCallback->doBufferDisplayedCallback(mUserData, (void *)buffer); } } void WaylandPlugin::handleFrameDropped(RenderBuffer *buffer) { if (mCallback) { mCallback->doBufferDroppedCallback(mUserData, (void *)buffer); } } void WaylandPlugin::handleMsgNotify(int type, void *detail) { if (mCallback) { mCallback->doMsgCallback(mUserData, type, detail); } } void WaylandPlugin::readyToRun() { } bool WaylandPlugin::threadLoop() { RenderBuffer *curFrameEntity = NULL; RenderBuffer *expiredFrameEntity = NULL; int64_t nowMonotime = ATools::Times::getSystemTimeUs(); int64_t waitTimeUs = 4*1000; //if queue is empty or paused, loop next if ((mQueue->getCnt() < 1)) { //underflow detected if (!mPaused && ATools::Times::getSystemTimeMs() - mLastDisplayTime > UNDER_FLOW_EXPIRED_TIME_MS) { reportUnderFlow(); } goto tag_next; } //we output video frame asap if (mImmediatelyOutput) { //pop the peeked frame mQueue->pop((void **)&expiredFrameEntity); goto tag_post; } //if weston obtains a buffer rendering,we can't send buffer to weston if (mDisplay->isRedrawingPending()) { goto tag_next; } while (mQueue->peek((void **)&curFrameEntity, 0) == Q_OK) { //pop buffer when display time - now less than 1 ms //no frame expired,loop next if ((curFrameEntity->time - nowMonotime) > 1000 ) { waitTimeUs = curFrameEntity->time - nowMonotime; break; } //pop the peeked frame mQueue->pop((void **)&curFrameEntity); //drop last expired frame,got a new expired frame if (expiredFrameEntity) { WARNING(mLogCategory,"drop,now:%lld,display:%lld(pts:%lld ms),n-d:%lld ms", nowMonotime,expiredFrameEntity->time,expiredFrameEntity->pts/1000000, (nowMonotime - expiredFrameEntity->time)/1000); handleFrameDropped(expiredFrameEntity); handleBufferRelease(expiredFrameEntity); expiredFrameEntity = NULL; } expiredFrameEntity = curFrameEntity; } tag_post: if (!expiredFrameEntity) { //TRACE(mLogCategory,"no frame expire"); goto tag_next; } if (mDisplay) { mLastDisplayTime = ATools::Times::getSystemTimeMs(); TRACE(mLogCategory,"post,now:%lld,display:%lld(pts:%lld us),n-d::%lld ms, size:%d", nowMonotime,expiredFrameEntity->time,expiredFrameEntity->pts/1000, (nowMonotime - expiredFrameEntity->time)/1000,mQueue->getCnt()); mDisplay->displayFrameBuffer(expiredFrameEntity, expiredFrameEntity->time); } tag_next: waitTimeoutUs(waitTimeUs); return true; } void *makePluginInstance(int id) { int fd = -1; const char *levelPath = "/tmp/rlib_plugin_level"; const char *filePath = "/tmp/rlib_plugin_file"; int category = ALogger_init(id); char *env = getenv("VIDEO_RENDER_PLUGIN_LOG_LEVEL"); if (env) { int level = atoi(env); ALogger_set_level(level); INFO(category,"env set VIDEO_RENDER_PLUGIN_LOG_LEVEL=%d",level); } env = getenv("VIDEO_RENDER_PLUGIN_LOG_FILE"); if (env && strlen(env) > 0) { ALogger_set_file(env); INFO(category,"env set VIDEO_RENDER_PLUGIN_LOG_FILE=%s",env); } INFO(category,"levelPath:%s",levelPath); //get log file from /tmp/rlib_plugin_file fd = open(filePath, O_RDONLY|O_CLOEXEC); if ( fd >= 0 ) { char valstr[64]; int ret; memset(valstr, 0, sizeof(valstr)); ret = read(fd, valstr, sizeof(valstr) - 1); if (ret > 0) { valstr[strlen(valstr)] = '\0'; if (strlen(valstr) > 0) { if (valstr[strlen(valstr) -1] == '\n') valstr[strlen(valstr) -1] = '\0'; ALogger_set_file(valstr); INFO(category,"set VIDEO_RENDER_PLUGIN_LOG_FILE=%s",valstr); } } close(fd); fd = -1; } //get log level from /tmp/rlib_plugin_level fd= open(levelPath, O_RDONLY|O_CLOEXEC); if ( fd >= 0 ) { char valstr[64]; uint32_t val= 0; int nRead = 0; memset(valstr, 0, sizeof(valstr)); nRead = read(fd, valstr, sizeof(valstr) - 1); valstr[strlen(valstr)] = '\0'; if (sscanf(valstr, "%u", &val) > 0) { ALogger_set_level(val); INFO(category,"set VIDEO_RENDER_PLUGIN_LOG_LEVEL=%d",val); } close(fd); fd = -1; } WaylandPlugin *pluginInstance = new WaylandPlugin(category); return static_cast(pluginInstance); } void destroyPluginInstance(void * plugin) { int category = 0; WaylandPlugin *pluginInstance = static_cast(plugin); category = pluginInstance->getLogCategory(); delete pluginInstance; ALogger_exit(category); }