/* * 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_display.h" #include "ErrorCode.h" #include "Logger.h" #include "wayland_plugin.h" #include "wayland_videoformat.h" #include "wayland_shm.h" #include "wayland_dma.h" #include "wayland_buffer.h" #include "Times.h" #ifndef MAX # define MAX(a,b) ((a) > (b)? (a) : (b)) # define MIN(a,b) ((a) < (b)? (a) : (b)) #endif #define UNUSED_PARAM(x) ((void)(x)) #define INVALID_OUTPUT_INDEX (-1) #define TAG "rlib:wayland_display" #define FRAME_FREEZE_THRESHOLD(x) (x*1.67) #define DETECT_FRAMERATE_CNT (5) /*do not use -1 to indicate invalid pts. player maybe display -1 pts frame*/ #define INVALID_PTS (INT64_MIN) #define DUMP_WAYLAND_BUFFER_LOG (0) /** * @brief this log function registered to wayland protocol * to print wayland server or wayland log * @param fmt log format */ static void wl_log_handle(const char *format, va_list args) { wl_log(format, args); } void WaylandDisplay::dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { WaylandDisplay *self = static_cast(data); ATools::Mutex::Autolock _l(self->mMutex); if (wl_dmabuf_format_to_video_format (format) != VIDEO_FORMAT_UNKNOWN) { TRACE(self->mLogCategory,"regist dmabuffer format:%d (%s) hi:%x,lo:%x",format,print_dmabuf_format_name(format),modifier_hi,modifier_lo); uint64_t modifier = ((uint64_t)modifier_hi << 32) | modifier_lo; auto item = self->mDmaBufferFormats.find(format); if (item == self->mDmaBufferFormats.end()) { std::pair item(format, modifier); self->mDmaBufferFormats.insert(item); } else { //found format item->second = modifier; } } } void WaylandDisplay::dmaBufferFormat (void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) { #if 0 WaylandDisplay *self = static_cast(data); if (wl_dmabuf_format_to_video_format (format) != VIDEO_FORMAT_UNKNOWN) { TRACE(mLogCategory,"regist dmabuffer format:%d : %s",format); //self->mDmaBufferFormats.push_back(format); } #endif /* XXX: deprecated */ } static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { WaylandDisplay::dmaBufferFormat, WaylandDisplay::dmabuf_modifiers }; static void handle_xdg_wm_base_ping (void *user_data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { xdg_wm_base_pong (xdg_wm_base, serial); } static const struct xdg_wm_base_listener xdg_wm_base_listener = { handle_xdg_wm_base_ping }; void WaylandDisplay::shmFormat(void *data, struct wl_shm *wl_shm, uint32_t format) { WaylandDisplay *self = static_cast(data); self->mShmFormats.push_back(format); } static const struct wl_shm_listener shm_listener = { WaylandDisplay::shmFormat }; void WaylandDisplay::outputHandleGeometry( void *data, struct wl_output *output, int x, int y, int physicalWidth, int physicalHeight, int subPixel, const char *make, const char *model, int transform ) { UNUSED_PARAM(make); UNUSED_PARAM(model); WaylandDisplay *self = static_cast(data); DEBUG(self->mLogCategory,"wl_output %p x:%d,y:%d,physicalWidth:%d,physicalHeight:%d,subPixel:%d,trans:%d", output,x, y,physicalWidth, physicalHeight,subPixel,transform); ATools::Mutex::Autolock _l(self->mMutex); for (int i = 0; i < DEFAULT_DISPLAY_OUTPUT_NUM; i++) { if (output == self->mOutput[i].wlOutput) { self->mOutput[i].offsetX = x; self->mOutput[i].offsetY = y; } } self->mDefaultTransform = transform; } void WaylandDisplay::outputHandleMode( void *data, struct wl_output *output, uint32_t flags, int width, int height, int refreshRate ) { WaylandDisplay *self = static_cast(data); if ( flags & WL_OUTPUT_MODE_CURRENT ) { ATools::Mutex::Autolock _l(self->mMutex); for (int i = 0; i < DEFAULT_DISPLAY_OUTPUT_NUM; i++) { if (output == self->mOutput[i].wlOutput) { self->mOutput[i].width = width; self->mOutput[i].height = height; self->mOutput[i].refreshRate = refreshRate; } } DEBUG(self->mLogCategory,"wl_output: %p (%dx%d) refreshrate:%d,select output index %d",output, width, height,refreshRate,self->mSelectOutputIndex); if (self->mCurrentDisplayOutput->width > 0 && self->mCurrentDisplayOutput->height > 0) { self->updateDisplayOutput(); } } } void WaylandDisplay::outputHandleDone( void *data, struct wl_output *output ) { WaylandDisplay *self = static_cast(data); DEBUG(self->mLogCategory,"wl_output: %p",output); UNUSED_PARAM(data); UNUSED_PARAM(output); } void WaylandDisplay::outputHandleScale( void *data, struct wl_output *output, int32_t scale ) { WaylandDisplay *self = static_cast(data); DEBUG(self->mLogCategory,"wl_output: %p scale %d",output, scale); UNUSED_PARAM(data); UNUSED_PARAM(output); UNUSED_PARAM(scale); } void WaylandDisplay::outputHandleCrtcIndex( void *data, struct wl_output *output, int32_t index ) { WaylandDisplay *self = static_cast(data); DEBUG(self->mLogCategory,"wl_output: %p crtc index %d",output, index); ATools::Mutex::Autolock _l(self->mMutex); for (int i = 0; i < DEFAULT_DISPLAY_OUTPUT_NUM; i++) { if (output == self->mOutput[i].wlOutput) { self->mOutput[i].crtcIndex = index; } } } //aml weston always add crtcIndex in callbacks. static const struct wl_output_listener outputListener = { WaylandDisplay::outputHandleGeometry, WaylandDisplay::outputHandleMode, WaylandDisplay::outputHandleDone, WaylandDisplay::outputHandleScale, WaylandDisplay::outputHandleCrtcIndex, }; void WaylandDisplay::pointerHandleEnter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) { WaylandDisplay *self = static_cast(data); if (self->mFullScreen) { wl_pointer_set_cursor(pointer, serial, NULL, 0, 0); } } void WaylandDisplay::pointerHandleLeave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { UNUSED_PARAM(data); UNUSED_PARAM(pointer); UNUSED_PARAM(serial); UNUSED_PARAM(surface); } void WaylandDisplay::pointerHandleMotion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { WaylandDisplay *self = static_cast(data); int x = wl_fixed_to_int(sx); int y = wl_fixed_to_int(sy); DEBUG(self->mLogCategory,"pointer motion fixed[%d, %d] to-int: x[%d] y[%d]\n", sx, sy, x, y); } void WaylandDisplay::pointerHandleButton(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { UNUSED_PARAM(data); UNUSED_PARAM(wl_pointer); UNUSED_PARAM(time); UNUSED_PARAM(serial); UNUSED_PARAM(button); UNUSED_PARAM(state); } void WaylandDisplay::pointerHandleAxis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { UNUSED_PARAM(data); UNUSED_PARAM(wl_pointer); UNUSED_PARAM(time); UNUSED_PARAM(axis); UNUSED_PARAM(value); } static const struct wl_pointer_listener pointer_listener = { WaylandDisplay::pointerHandleEnter, WaylandDisplay::pointerHandleLeave, WaylandDisplay::pointerHandleMotion, WaylandDisplay::pointerHandleButton, WaylandDisplay::pointerHandleAxis, }; void WaylandDisplay::touchHandleDown(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { WaylandDisplay *self = static_cast(data); } void WaylandDisplay::touchHandleUp(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { UNUSED_PARAM(data); UNUSED_PARAM(wl_touch); UNUSED_PARAM(serial); UNUSED_PARAM(time); UNUSED_PARAM(id); } void WaylandDisplay::touchHandleMotion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { UNUSED_PARAM(data); UNUSED_PARAM(wl_touch); UNUSED_PARAM(time); UNUSED_PARAM(id); UNUSED_PARAM(x_w); UNUSED_PARAM(y_w); } void WaylandDisplay::touchHandleFrame(void *data, struct wl_touch *wl_touch) { UNUSED_PARAM(data); UNUSED_PARAM(wl_touch); } void WaylandDisplay::touchHandleCancel(void *data, struct wl_touch *wl_touch) { UNUSED_PARAM(data); UNUSED_PARAM(wl_touch); } static const struct wl_touch_listener touch_listener = { WaylandDisplay::touchHandleDown, WaylandDisplay::touchHandleUp, WaylandDisplay::touchHandleMotion, WaylandDisplay::touchHandleFrame, WaylandDisplay::touchHandleCancel, }; void WaylandDisplay::keyboardHandleKeymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { UNUSED_PARAM(data); UNUSED_PARAM(keyboard); UNUSED_PARAM(format); UNUSED_PARAM(fd); UNUSED_PARAM(size); } void WaylandDisplay::keyboardHandleEnter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { UNUSED_PARAM(data); UNUSED_PARAM(keyboard); UNUSED_PARAM(serial); UNUSED_PARAM(surface); UNUSED_PARAM(keys); } void WaylandDisplay::keyboardHandleLeave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { UNUSED_PARAM(data); UNUSED_PARAM(keyboard); UNUSED_PARAM(serial); UNUSED_PARAM(surface); } void WaylandDisplay::keyboardHandleKey(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { UNUSED_PARAM(data); UNUSED_PARAM(keyboard); UNUSED_PARAM(serial); UNUSED_PARAM(time); UNUSED_PARAM(key); UNUSED_PARAM(state); } void WaylandDisplay::keyboardHandleModifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { UNUSED_PARAM(data); UNUSED_PARAM(keyboard); UNUSED_PARAM(serial); UNUSED_PARAM(mods_depressed); UNUSED_PARAM(mods_latched); UNUSED_PARAM(mods_locked); UNUSED_PARAM(group); } static const struct wl_keyboard_listener keyboard_listener = { WaylandDisplay::keyboardHandleKeymap, WaylandDisplay::keyboardHandleEnter, WaylandDisplay::keyboardHandleLeave, WaylandDisplay::keyboardHandleKey, WaylandDisplay::keyboardHandleModifiers, }; void WaylandDisplay::seatHandleCapabilities(void *data, struct wl_seat *seat, uint32_t caps) { WaylandDisplay *self = static_cast(data); if ((caps & WL_SEAT_CAPABILITY_POINTER) && !self->mPointer) { self->mPointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(self->mPointer, &pointer_listener, data); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && self->mPointer) { wl_pointer_destroy(self->mPointer); self->mPointer = NULL; } if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !self->mKeyboard) { self->mKeyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(self->mKeyboard, &keyboard_listener, data); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && self->mKeyboard) { wl_keyboard_destroy(self->mKeyboard); self->mKeyboard = NULL; } if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !self->mTouch) { self->mTouch = wl_seat_get_touch(seat); wl_touch_set_user_data(self->mTouch, data); wl_touch_add_listener(self->mTouch, &touch_listener, data); } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && self->mTouch) { wl_touch_destroy(self->mTouch); self->mTouch = NULL; } } static const struct wl_seat_listener seat_listener = { WaylandDisplay::seatHandleCapabilities, }; void WaylandDisplay::handleXdgToplevelClose (void *data, struct xdg_toplevel *xdg_toplevel) { WaylandDisplay *self = static_cast(data); INFO(self->mLogCategory,"XDG toplevel got a close event."); } void WaylandDisplay::handleXdgToplevelConfigure (void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { WaylandDisplay *self = static_cast(data); uint32_t *state; /* wl_array_for_each (state, states) { switch (*state) { case XDG_TOPLEVEL_STATE_FULLSCREEN: case XDG_TOPLEVEL_STATE_MAXIMIZED: case XDG_TOPLEVEL_STATE_RESIZING: case XDG_TOPLEVEL_STATE_ACTIVATED: break; } } */ if (width <= 0 || height <= 0) return; INFO(self->mLogCategory, "XDG toplevel got a configure event, width:height [ %d, %d ].", width, height); if (width == self->mCurrentDisplayOutput->width && height == self->mCurrentDisplayOutput->height && self->mUpdateRenderRectangle) { self->mUpdateRenderRectangle = false; self->setRenderRectangle(self->mCurrentDisplayOutput->offsetX, self->mCurrentDisplayOutput->offsetY, self->mCurrentDisplayOutput->width, self->mCurrentDisplayOutput->height); } else{ self->setRenderRectangle(self->mRenderRect.x, self->mRenderRect.y, width, height); } } static const struct xdg_toplevel_listener xdg_toplevel_listener = { WaylandDisplay::handleXdgToplevelConfigure, WaylandDisplay::handleXdgToplevelClose, }; void WaylandDisplay::handleXdgSurfaceConfigure (void *data, struct xdg_surface *xdg_surface, uint32_t serial) { WaylandDisplay *self = static_cast(data); xdg_surface_ack_configure (xdg_surface, serial); TRACE(self->mLogCategory,"handleXdgSurfaceConfigure"); ATools::Mutex::Autolock _l(self->mConfigureMutex); self->mXdgSurfaceConfigured = true; self->updateDisplayOutput(); } static const struct xdg_surface_listener xdg_surface_listener = { WaylandDisplay::handleXdgSurfaceConfigure, }; void WaylandDisplay::handleSurfaceDestroy(void *data, struct wl_callback *callback, uint32_t time) { WaylandDisplay *self = static_cast(data); INFO(self->mLogCategory,"handle video surface destroy"); if (self->mMutex.lock() == 0) { self->mCondition.signal(); self->mMutex.unlock(); } wl_callback_destroy (callback); } static const struct wl_callback_listener surface_destroy_listener = { WaylandDisplay::handleSurfaceDestroy, }; void WaylandDisplay::surfaceEnter(void *data, struct wl_surface *wl_surface, struct wl_output *output) { UNUSED_PARAM(data); UNUSED_PARAM(wl_surface); UNUSED_PARAM(output); } void WaylandDisplay::surfaceLeave(void *data, struct wl_surface *wl_surface, struct wl_output *output) { UNUSED_PARAM(data); UNUSED_PARAM(wl_surface); UNUSED_PARAM(output); } void WaylandDisplay::surfaceUnderFlow(void *data, struct wl_surface *wl_surface, uint32_t pts_hi, uint32_t pts_lo) { WaylandDisplay *self = static_cast(data); int64_t displayTime = ((int64_t)pts_hi << 32) | pts_lo; INFO(self->mLogCategory,"underflow displaytime:%lld", displayTime); self->mWaylandPlugin->reportUnderFlow(); } static const struct wl_surface_listener surface_listener = { WaylandDisplay::surfaceEnter, WaylandDisplay::surfaceLeave, WaylandDisplay::surfaceUnderFlow, }; void WaylandDisplay::amlConfigure(void *data, struct aml_config *config, const char *list) { WaylandDisplay *self = static_cast(data); TRACE(self->mLogCategory,"aml_config:%s",list); if (list && strlen(list) > 0) { if (strstr(list, "set_video_plane")) { TRACE(self->mLogCategory,"weston enable set_video_plane"); self->mAmlConfigAPIList.enableSetVideoPlane = true; } if (strstr(list, "set_pts")) { TRACE(self->mLogCategory,"weston enable set_pts"); self->mAmlConfigAPIList.enableSetPts = true; } if (strstr(list, "keep_last_frame")) { TRACE(self->mLogCategory,"weston enable keep_last_frame"); self->mAmlConfigAPIList.enableKeepLastFrame = true; } if (strstr(list, "surface_destroy_cb")) { TRACE(self->mLogCategory,"weston enable surface_destroy_cb"); self->mAmlConfigAPIList.enableSurfaceDestroyCallback = true; } if (strstr(list, "set_display_rate")) { TRACE(self->mLogCategory,"weston enable set_display_rate"); self->mAmlConfigAPIList.enableSetDisplayRate = true; } if (strstr(list, "set_surface_invisible")) { TRACE(self->mLogCategory,"weston enable set_surface_invisible"); self->mAmlConfigAPIList.enableSetSurfaceInvisible = true; } if (strstr(list, "display_time")) { TRACE(self->mLogCategory,"weston enable display_time"); self->mAmlConfigAPIList.enableDisplayTime = true; } if (strstr(list, "force_update_rect")) { TRACE(self->mLogCategory,"weston enable force_update_rect"); self->mAmlConfigAPIList.enableForceUpdateRect = true; } if (strstr(list, "render_to_osd")) { TRACE(self->mLogCategory,"weston enable render_to_osd"); self->mAmlConfigAPIList.enableVideoOnOSD = true; } if (strstr(list, "need_original_size")) { TRACE(self->mLogCategory,"weston enable need_original_size"); self->mAmlConfigAPIList.enableNeedOriginalSize = true; } if (strstr(list, "dpss_enabled")) { TRACE(self->mLogCategory,"weston enable dpss_enabled"); self->mAmlConfigAPIList.enableDpss = true; } if (strstr(list, "vsync_callback")) { TRACE(self->mLogCategory,"weston enable vsync_callback"); self->mAmlConfigAPIList.enableVsyncCallback = true; } } } void WaylandDisplay::dpssLatency(void *data, struct aml_config *config, uint32_t latency) { WaylandDisplay *self = static_cast(data); if (self) { self->mHWCLatency = (uint32_t)latency; TRACE(self->mLogCategory,"compositor latency:%u us",latency); } } void WaylandDisplay::vsyncCallback(void *data, struct aml_config *config, uint32_t vsync_hi, uint32_t vsync_lo, uint32_t interval) { WaylandDisplay *self = static_cast(data); if (self) { int64_t lastVsyncUs = self->mVsyncTimeUs; self->mVsyncTimeUs = ((int64_t)vsync_hi << 32) | vsync_lo; self->mVsyncPeriodUs = (int64_t)interval; TRACE(self->mLogCategory,"vsync_hi:%d, vsync_lo:%d, (%lld, %lld->%lld)", vsync_hi, vsync_lo, self->mVsyncTimeUs, self->mVsyncPeriodUs,self->mVsyncTimeUs - lastVsyncUs); } } static const struct aml_config_listener aml_config_listener = { WaylandDisplay::amlConfigure, WaylandDisplay::vsyncCallback, WaylandDisplay::dpssLatency, }; void WaylandDisplay::registryHandleGlobal (void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { WaylandDisplay *self = static_cast(data); TRACE(self->mLogCategory,"registryHandleGlobal,name:%u,interface:%s,version:%d",name,interface,version); if (strcmp (interface, "wl_compositor") == 0) { self->mCompositor = (struct wl_compositor *)wl_registry_bind (registry, name, &wl_compositor_interface, version); } else if (strcmp (interface, "wl_subcompositor") == 0) { self->mSubCompositor = (struct wl_subcompositor *)wl_registry_bind (registry, name, &wl_subcompositor_interface, 1); } else if (strcmp (interface, "xdg_wm_base") == 0) { self->mXdgWmBase = (struct xdg_wm_base *)wl_registry_bind (registry, name, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener (self->mXdgWmBase, &xdg_wm_base_listener, (void *)self); } else if (strcmp (interface, "wl_shm") == 0) { self->mShm = (struct wl_shm *)wl_registry_bind (registry, name, &wl_shm_interface, 1); wl_shm_add_listener (self->mShm, &shm_listener, self); } else if (strcmp (interface, "zwp_fullscreen_shell_v1") == 0) { //self->mFullscreenShell = (struct zwp_fullscreen_shell_v1 *)wl_registry_bind (registry, name, // &zwp_fullscreen_shell_v1_interface, 1); } else if (strcmp (interface, "wp_viewporter") == 0) { self->mViewporter = (struct wp_viewporter *)wl_registry_bind (registry, name, &wp_viewporter_interface, 1); } else if (strcmp (interface, "zwp_linux_dmabuf_v1") == 0) { if (version < 3) return; self->mDmabuf = (struct zwp_linux_dmabuf_v1 *)wl_registry_bind (registry, name, &zwp_linux_dmabuf_v1_interface, 3); zwp_linux_dmabuf_v1_add_listener (self->mDmabuf, &dmabuf_listener, (void *)self); } else if (strcmp (interface, "wl_output") == 0) { int i = 0; uint32_t oriName = self->mCurrentDisplayOutput->name; for (int i = 0; i < DEFAULT_DISPLAY_OUTPUT_NUM; i++) { if (self->mOutput[i].wlOutput == NULL) { self->mOutput[i].name = name; self->mOutput[i].wlOutput = (struct wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version); TRACE(self->mLogCategory,"name:%u, wl_output:%p, select:%d",self->mOutput[i].name,self->mOutput[i].wlOutput,self->mSelectOutputIndex); wl_output_add_listener(self->mOutput[i].wlOutput, &outputListener, (void *)self); if (i == 0) { //primary wl_output self->mOutput[i].isPrimary = true; } break; } } if (i == DEFAULT_DISPLAY_OUTPUT_NUM) { WARNING(self->mLogCategory,"Not enough free output"); } //select current wl_output if (self->mSelectOutputIndex != INVALID_OUTPUT_INDEX && !self->isRunning()) { TRACE(self->mLogCategory,"select %d output",self->mSelectOutputIndex); self->mCurrentDisplayOutput = &self->mOutput[self->mSelectOutputIndex]; } //if user select a wrong output index, we using a suiteble wl_output if (self->mCurrentDisplayOutput->wlOutput == NULL) { WARNING(self->mLogCategory,"wl_output is null,we should find a suiteble output"); for (i = 0; i < DEFAULT_DISPLAY_OUTPUT_NUM; i++) { if (self->mOutput[i].wlOutput) { self->mCurrentDisplayOutput = &self->mOutput[i]; break; } } } //if current wl_output update, we should update render rectangle if (self->mCurrentDisplayOutput->name != oriName) { self->mUpdateRenderRectangle = true; } //if wl_output plugin,active sending frame self->setRedrawingPending(false); } else if (strcmp(interface, "wl_seat") == 0) { //self->mSeat = (struct wl_seat *)wl_registry_bind(registry, name, &wl_seat_interface, 1); //wl_seat_add_listener(self->mSeat, &seat_listener, (void *)self); } else if (strcmp(interface, "weston_direct_display_v1") == 0) { self->mDirect_display = (struct weston_direct_display_v1 *)wl_registry_bind(registry,name, &weston_direct_display_v1_interface, 1); } else if (strcmp(interface, "aml_config") == 0) { self->mAmlConfig = (struct aml_config*)wl_registry_bind(registry, name, &aml_config_interface, 1); aml_config_add_listener(self->mAmlConfig, &aml_config_listener, (void *)self); } } void WaylandDisplay::registryHandleGlobalRemove (void *data, struct wl_registry *registry, uint32_t name) { int i = 0; WaylandDisplay *self = static_cast(data); /* check wl_output changed */ DEBUG(self->mLogCategory,"wayland display remove registry handle global,name:%u",name); //if user selected wl_output removed, reset selected output index if (self->mSelectOutputIndex != INVALID_OUTPUT_INDEX && self->mOutput[self->mSelectOutputIndex].wlOutput && self->mOutput[self->mSelectOutputIndex].name == name) { self->mSelectOutputIndex = INVALID_OUTPUT_INDEX; } for (i = 0; i < DEFAULT_DISPLAY_OUTPUT_NUM; i++) { if (self->mOutput[i].name == name) { DEBUG(self->mLogCategory,"remove wl_output name:%u,wl_output:%p",name,self->mOutput[i].wlOutput); self->mOutput[i].name = 0; if (self->mOutput[i].wlOutput) { wl_output_destroy(self->mOutput[i].wlOutput); } self->mOutput[i].wlOutput = NULL; } } //if current output removed, select a suiteble output if (self->mCurrentDisplayOutput->wlOutput == NULL) { for (i = 0; i < DEFAULT_DISPLAY_OUTPUT_NUM; i++) { if (self->mOutput[i].wlOutput) { self->mCurrentDisplayOutput = &self->mOutput[i]; self->mUpdateRenderRectangle = true; } } //set new output rectangle if (self->mUpdateRenderRectangle) { self->mUpdateRenderRectangle = false; self->setRenderRectangle(self->mCurrentDisplayOutput->offsetX, self->mCurrentDisplayOutput->offsetY, self->mCurrentDisplayOutput->width, self->mCurrentDisplayOutput->height); } } } static const struct wl_registry_listener registry_listener = { WaylandDisplay::registryHandleGlobal, WaylandDisplay::registryHandleGlobalRemove }; WaylandDisplay::WaylandDisplay(WaylandPlugin *plugin, int logCategory) :mBufferMutex("bufferMutex"), mWaylandPlugin(plugin), mLogCategory(logCategory) { TRACE(mLogCategory,"construct WaylandDisplay"); mWlDisplay = NULL; mWlDisplayWrapper = NULL; mWlQueue = NULL; mRegistry = NULL; mCompositor = NULL; mXdgWmBase = NULL; mViewporter = NULL; mDmabuf = NULL; mShm = NULL; mSeat = NULL; mPointer = NULL; mTouch = NULL; mKeyboard = NULL; mDirect_display = NULL; mSelectOutputIndex = INVALID_OUTPUT_INDEX; mPoll = NULL; //window mVideoWidth = 0; mVideoHeight = 0; mVideoSurface = NULL; mXdgSurface = NULL; mXdgToplevel = NULL; mAreaViewport = NULL; mVideoViewport = NULL; mAreaShmBuffer = NULL; mCommitCnt = 0; mAreaSurface = NULL; mAreaSurfaceWrapper = NULL; mVideoSurfaceWrapper = NULL; mVideoSubSurface = NULL; mPip = 0; mKeepLastFrame = 0; //default is off keep last frame mKeepFrameOnFlush = 1; mFirstPtsAfterFlush = INVALID_PTS; mSignalFirstFramePts = false; mFirstFramePts = INVALID_PTS; mToSendKeepLastFrame = false; mVideoPlaneZorder = -1; mVideoPlaneZorderChanged = false; mVideoRotateDegree = -1; mEnableWaylandClientLog = false; mMuteVideoOnFlush = false; mPaused = false; mKeepVideoAsptectRatio = true; mHideVideo = 0; mHideVideoChanged = false; mDisplayConnected = false; mVideoOnOSD = false; mDefaultTransform = 0; mHWCLatency = 0; mVsyncTimeUs = 0; mVsyncPeriodUs = 0; memset(&mWindowRect, 0, sizeof(struct Rectangle)); memset(&mCropRect, 0, sizeof(struct Rectangle)); mCommittedBufferMap.clear(); mWaylandBuffersMap.clear(); mDmaBufferFormats.clear(); mDeferredReleaseBuffers.clear(); } WaylandDisplay::~WaylandDisplay() { TRACE(mLogCategory,"desconstruct WaylandDisplay"); } char *WaylandDisplay::require_xdg_runtime_dir() { char *val = getenv("XDG_RUNTIME_DIR"); INFO(mLogCategory,"XDG_RUNTIME_DIR=%s",val); //if not set XDG_RUNTIME_DIR,set default value // if (!val) { // val = const_cast("/run/user/0"); // setenv("XDG_RUNTIME_DIR",val,0); // WARNING(mLogCategory,"XDG_RUNTIME_DIR is not set,set default %s",val); // } return val; } int WaylandDisplay::openDisplay() { mPoll = new ATools::Poll(true); mPrepareFrameWidth = 0; mPrepareFrameHeight = 0; mWaylandBufferCookieId = 0; mDisplayedFrameWidth = 0; mDisplayedFrameHeight = 0; mUpdateWaylandBuffersMap = false; frameCallbackTimeUs = 0; mLastDisplayFramePts = -1; mLastDisplayFrameTimeUs = -1; mFrameDurationUs = 0; mPreFramePts = -1; mFrameRateDetectCnt = 0; mFrameRateDetectPeriod = 0; mPixelAspectRatio = 1.0; mUpdateVideoSurface = false; mUpdateCropSize = false; mNoBorderUpdate = false; mReCommitAreaSurface = false; mXdgSurfaceConfigured = false; mUpdateRenderRectangle = false; mFrameRateFractionNum = 0; mFrameRateFractionDenom = 1; mFrameRateChanged = false; mVideoPlaneZorder = -1; mVideoPlaneZorderChanged = false; mVideoIsUnderflow = false; mSignalFirstFramePts = false; mFirstFramePts = INVALID_PTS; mPaused = false; mMuteVideoOnFlush = false; mDisplayConnected = false; memset(&mRenderRect, 0, sizeof(struct Rectangle)); memset(&mVideoRect, 0, sizeof(struct Rectangle)); mDeferredReleaseBuffers.clear(); //don't full screen when osd displayed if (!mVideoOnOSD) { mFullScreen = true; //default is full screen } mAmlConfig = NULL; //weston config private api mAmlConfigAPIList.enableKeepLastFrame = false; mAmlConfigAPIList.enableSetPts = false; mAmlConfigAPIList.enableSetVideoPlane = false; mAmlConfigAPIList.enableSurfaceDestroyCallback = false; mAmlConfigAPIList.enableSetDisplayRate = false; mAmlConfigAPIList.enableSetSurfaceInvisible = false; mAmlConfigAPIList.enableDisplayTime = false; mAmlConfigAPIList.enableForceUpdateRect = false; mAmlConfigAPIList.enableVideoOnOSD = false; mAmlConfigAPIList.enableNeedOriginalSize = false; mAmlConfigAPIList.enableDpss = false; mAmlConfigAPIList.enableVsyncCallback = false; mCurrentDisplayOutput = &mOutput[0]; for (int i = 0; i < DEFAULT_DISPLAY_OUTPUT_NUM; i++) { mOutput[i].wlOutput = NULL; mOutput[i].offsetX = 0; mOutput[i].offsetY = 0; mOutput[i].width = 0; mOutput[i].height = 0; mOutput[i].refreshRate = 0; mOutput[i].isPrimary = false; mOutput[i].name = 0; mOutput[i].crtcIndex = 0; } /*if mKeepLastFrame had set but mToSendKeepLastFrame is false,maybe playback is doing FF/FW action,so we keep set it on new connection*/ if (!mToSendKeepLastFrame && mKeepLastFrame) { mToSendKeepLastFrame = true; } char *name = require_xdg_runtime_dir(); //DEBUG(mLogCategory,"name:%s",name); char* debug = getenv("WAYLAND_DEBUG"); if (debug && strstr(debug, "1")) { mEnableWaylandClientLog = true; wl_log_set_handler_client(wl_log_handle); } debug = getenv("VIDEO_RENDER_KEEP_VIDEO_ASPECT_RATIO"); if (debug && strstr(debug, "0")) { mKeepVideoAsptectRatio = false; } DEBUG(mLogCategory,"openDisplay in"); mWlDisplay = wl_display_connect(NULL); if (!mWlDisplay) { ERROR(mLogCategory,"Failed to connect to the wayland display, XDG_RUNTIME_DIR='%s'", name ? name : "NULL"); return ERROR_OPEN_FAIL; } mWlDisplayWrapper = (struct wl_display *)wl_proxy_create_wrapper ((void *)mWlDisplay); mWlQueue = wl_display_create_queue (mWlDisplay); wl_proxy_set_queue ((struct wl_proxy *)mWlDisplayWrapper, mWlQueue); mRegistry = wl_display_get_registry (mWlDisplayWrapper); wl_registry_add_listener (mRegistry, ®istry_listener, (void *)this); /* we need exactly 2 roundtrips to discover global objects and their state */ for (int i = 0; i < 2; i++) { if (wl_display_roundtrip_queue (mWlDisplay, mWlQueue) < 0) { ERROR(mLogCategory,"Error communicating with the wayland display"); return ERROR_OPEN_FAIL; } } if (!mCompositor) { ERROR(mLogCategory,"Could not bind to wl_compositor. Either it is not implemented in " \ "the compositor, or the implemented version doesn't match"); return ERROR_OPEN_FAIL; } if (!mDmabuf) { ERROR(mLogCategory,"Could not bind to zwp_linux_dmabuf_v1"); return ERROR_OPEN_FAIL; } if (!mXdgWmBase) { /* If wl_surface and wl_display are passed via GstContext * wl_shell, xdg_shell and zwp_fullscreen_shell are not used. * In this case is correct to continue. */ ERROR(mLogCategory,"Could not bind to either wl_shell, xdg_wm_base or " "zwp_fullscreen_shell, video display may not work properly."); return ERROR_OPEN_FAIL; } //create window surface createCommonWindowSurface(); createXdgShellWindowSurface(); //config weston video plane if (mAmlConfigAPIList.enableSetVideoPlane) { INFO(mLogCategory,"set weston video plane:%d",mPip); wl_surface_set_video_plane(mVideoSurfaceWrapper, mPip); } //enable weston vsync callback if (mAmlConfigAPIList.enableVsyncCallback) { INFO(mLogCategory,"set vsync output id:%d",mSelectOutputIndex); aml_config_need_vsync_callback(mAmlConfig, mCompositor, mSelectOutputIndex); } if (mHideVideoChanged) { setHideVideo(mHideVideo); } //run wl display queue dispatch DEBUG(mLogCategory,"To run wl display dispatch queue"); setThreadPriority(69); run("waylandProtocol"); mRedrawingPending = false; mDisplayConnected = true; DEBUG(mLogCategory,"openDisplay out"); return NO_ERROR; } void WaylandDisplay::closeDisplay() { DEBUG(mLogCategory,"closeDisplay in"); //first destroy window surface destroyWindowSurfaces(); //flush pending event to weston if (mWlDisplay) { wl_display_flush (mWlDisplay); } //wait video surface destroyed or 50ms timeout if (mAmlConfigAPIList.enableSurfaceDestroyCallback) { INFO(mLogCategory,"waiting surface_destroy_cb from weston"); if (mMutex.lock() == 0) { //success if (ERROR_TIMED_OUT == mCondition.waitRelative(mMutex, 50/*microsecond*/)) { WARNING(mLogCategory,"waited surface_destroy_cb timeout"); } mMutex.unlock(); } } //we should receive all event from weston before stopped dispatch queue if (isRunning()) { TRACE(mLogCategory,"try stop dispatch thread"); if (mPoll) { mPoll->setFlushing(true); } requestExitAndWait(); } //after destroyed surface,then destroy buffers,otherwise maybe crash if (mAreaShmBuffer) { delete mAreaShmBuffer; mAreaShmBuffer = NULL; } //clean all wayland buffers cleanAllWaylandBuffer(); if (mViewporter) { wp_viewporter_destroy (mViewporter); mViewporter = NULL; } if (mShm) { wl_shm_destroy(mShm); mShm = NULL; } if (mDmabuf) { zwp_linux_dmabuf_v1_destroy (mDmabuf); mDmabuf = NULL; } if (mXdgWmBase) { xdg_wm_base_destroy (mXdgWmBase); mXdgWmBase = NULL; } if (mCompositor) { wl_compositor_destroy (mCompositor); mCompositor = NULL; } if (mSubCompositor) { wl_subcompositor_destroy (mSubCompositor); mSubCompositor = NULL; } if (mDirect_display) { weston_direct_display_v1_destroy(mDirect_display); mDirect_display = NULL; } for (int i = 0; i < DEFAULT_DISPLAY_OUTPUT_NUM; i++) { if (mOutput[i].wlOutput) { wl_output_destroy(mOutput[i].wlOutput); } mOutput[i].wlOutput = NULL; } if (mRegistry) { wl_registry_destroy (mRegistry); mRegistry= NULL; } if (mWlDisplayWrapper) { wl_proxy_wrapper_destroy (mWlDisplayWrapper); mWlDisplayWrapper = NULL; } if (mAmlConfig) { aml_config_destroy(mAmlConfig); mAmlConfig = NULL; } if (mWlQueue) { wl_event_queue_destroy (mWlQueue); mWlQueue = NULL; } if (mWlDisplay) { wl_display_flush (mWlDisplay); wl_display_disconnect (mWlDisplay); mWlDisplay = NULL; } /* Release deferred buffers after disconnecting from weston. * These buffers were in DISPLAYING/DISPLAYED state during cleanup; * weston no longer references them now that we've disconnected. */ releaseDeferredBuffers(); mDisplayConnected = false; //do not handle wayland client logs after disconnect if (mEnableWaylandClientLog) { wl_log_set_handler_client(NULL); mEnableWaylandClientLog = false; } mHideVideoChanged = false; mHideVideo = 0; //free all obtain buff TRACE(mLogCategory,"mCommittedBufferMap size:%d",mCommittedBufferMap.size()); for (auto item = mCommittedBufferMap.begin(); item != mCommittedBufferMap.end(); ) { mCommittedBufferMap.erase(item++); } mCommittedBufferMap.clear(); mDmaBufferFormats.clear(); if (mPoll) { delete mPoll; mPoll = NULL; } DEBUG(mLogCategory,"closeDisplay out"); } int WaylandDisplay::toDmaBufferFormat(RenderVideoFormat format, uint32_t *outDmaformat /*out param*/, uint64_t *outDmaformatModifiers /*out param*/) { if (!outDmaformat || !outDmaformatModifiers) { WARNING(mLogCategory,"NULL params"); return ERROR_PARAM_NULL; } *outDmaformat = 0; *outDmaformatModifiers = 0; uint32_t dmaformat = video_format_to_wl_dmabuf_format (format); if (dmaformat == -1) { ERROR(mLogCategory,"Error not found render video format:%d to wl dmabuf format",format); return ERROR_NOT_FOUND; } //TRACE(mLogCategory,"render video format:%d -> dmabuf format:%d",format,dmaformat); *outDmaformat = (uint32_t)dmaformat; /*get dmaformat and modifiers*/ auto item = mDmaBufferFormats.find(dmaformat); if (item == mDmaBufferFormats.end()) { //not found WARNING(mLogCategory,"Not found dmabuf for render video format :%d",format); *outDmaformatModifiers = 0; return NO_ERROR; } *outDmaformatModifiers = (uint64_t)item->second; return NO_ERROR; } int WaylandDisplay::toShmBufferFormat(RenderVideoFormat format, uint32_t *outformat) { if (!outformat) { WARNING(mLogCategory,"NULL params"); return ERROR_PARAM_NULL; } *outformat = 0; int shmformat = (int)video_format_to_wl_shm_format(format); if (shmformat < 0) { ERROR(mLogCategory,"Error not found render video format:%d to wl shmbuf format",format); return ERROR_NOT_FOUND; } for (auto item = mShmFormats.begin(); item != mShmFormats.end(); ++item) { uint32_t registFormat = (uint32_t)*item; if (registFormat == (uint32_t)shmformat) { *outformat = registFormat; return NO_ERROR; } } return ERROR_NOT_FOUND; } void WaylandDisplay::setVideoBufferFormat(RenderVideoFormat format) { TRACE(mLogCategory,"set video buffer format: %d",format); mBufferFormat = format; }; void WaylandDisplay::setDisplayOutput(int output) { TRACE(mLogCategory,"select display output: %d",output); if (output < 0 || output >= DEFAULT_DISPLAY_OUTPUT_NUM) { ERROR(mLogCategory, "display output index error,please set 0:primary or 1:extend,now:%d",output); return; } //only do select output before video playing if (mSelectOutputIndex != output) { mSelectOutputIndex = output; } } int WaylandDisplay::getDisplayOutput() { return mSelectOutputIndex == INVALID_OUTPUT_INDEX? 0: mSelectOutputIndex; } void WaylandDisplay::setPip(int pip) { INFO(mLogCategory,"set pip:%d",pip); mPip = pip; } void WaylandDisplay::updateDisplayOutput() { if (!mCurrentDisplayOutput->wlOutput || !mXdgToplevel || !mXdgSurface) { return; } if (mUpdateRenderRectangle) { if (mFullScreen) { DEBUG(mLogCategory,"unset full screen"); xdg_toplevel_unset_fullscreen (mXdgToplevel); } if (mXdgSurface) { DEBUG(mLogCategory,"set geometry"); xdg_surface_set_window_geometry(mXdgSurface, mCurrentDisplayOutput->offsetX, mCurrentDisplayOutput->offsetY, mCurrentDisplayOutput->width, mCurrentDisplayOutput->height); } if (mFullScreen && mXdgToplevel) { DEBUG(mLogCategory,"set full screen"); xdg_toplevel_set_fullscreen (mXdgToplevel, mCurrentDisplayOutput->wlOutput); } setRenderRectangle(mCurrentDisplayOutput->offsetX, mCurrentDisplayOutput->offsetY, mCurrentDisplayOutput->width, mCurrentDisplayOutput->height); mUpdateRenderRectangle = false; } } void WaylandDisplay::createCommonWindowSurface() { struct wl_region *region; mAreaSurface = wl_compositor_create_surface (mCompositor); mVideoSurface = wl_compositor_create_surface (mCompositor); mAreaSurfaceWrapper = (struct wl_surface *)wl_proxy_create_wrapper (mAreaSurface); mVideoSurfaceWrapper = (struct wl_surface *)wl_proxy_create_wrapper (mVideoSurface); wl_proxy_set_queue ((struct wl_proxy *) mAreaSurfaceWrapper, mWlQueue); wl_proxy_set_queue ((struct wl_proxy *) mVideoSurfaceWrapper, mWlQueue); /* embed video_surface in area_surface */ mVideoSubSurface = wl_subcompositor_get_subsurface (mSubCompositor, mVideoSurface, mAreaSurface); wl_subsurface_set_desync (mVideoSubSurface); //add video surface callback to weston if weston enable this feature if (mVideoSurface && mAmlConfigAPIList.enableSurfaceDestroyCallback) { struct wl_callback *surfaceDestroyCb = wl_surface_destroy_callback(mVideoSurface); wl_callback_add_listener(surfaceDestroyCb, &surface_destroy_listener, this); } if (mViewporter) { mAreaViewport = wp_viewporter_get_viewport (mViewporter, mAreaSurface); mVideoViewport = wp_viewporter_get_viewport (mViewporter, mVideoSurface); } /*set area surface to invisible. prevent frame droped at start playback, wl_surface_set_invisible must called before mAreaSurface commit called*/ if (mAmlConfigAPIList.enableSetSurfaceInvisible) { int invisible = 1; INFO(mLogCategory,"set surface invisible:%d",invisible); wl_surface_set_invisible(mAreaSurfaceWrapper, invisible); } if (mAmlConfigAPIList.enableDisplayTime) { INFO(mLogCategory,"set enable display time"); wl_surface_enable_display_time(mVideoSurface, 1); } if (mAmlConfigAPIList.enableSetPts) { INFO(mLogCategory,"add surface underflow listener"); wl_surface_add_listener(mVideoSurface, &surface_listener, this); } /* do not accept input */ region = wl_compositor_create_region (mCompositor); wl_surface_set_input_region (mAreaSurface, region); wl_region_destroy (region); region = wl_compositor_create_region (mCompositor); wl_surface_set_input_region (mVideoSurface, region); wl_region_destroy (region); } void WaylandDisplay::createXdgShellWindowSurface() { /* Check which protocol we will use (in order of preference) */ if (mXdgWmBase) { /* First create the XDG surface */ mXdgSurface= xdg_wm_base_get_xdg_surface (mXdgWmBase, mAreaSurface); if (!mXdgSurface) { ERROR(mLogCategory,"Unable to get xdg_surface"); return; } xdg_surface_add_listener (mXdgSurface, &xdg_surface_listener,(void *)this); /* Then the toplevel */ mXdgToplevel= xdg_surface_get_toplevel (mXdgSurface); if (!mXdgSurface) { ERROR(mLogCategory,"Unable to get xdg_toplevel"); return; } xdg_toplevel_add_listener (mXdgToplevel, &xdg_toplevel_listener, this); /* Finally, commit the xdg_surface state as toplevel */ mXdgSurfaceConfigured = false; wl_surface_commit (mAreaSurface); wl_display_flush (mWlDisplay); /* we need exactly 3 roundtrips to discover global objects and their state */ for (int i = 0; i < 3; i++) { if (wl_display_roundtrip_queue(mWlDisplay, mWlQueue) < 0) { ERROR(mLogCategory,"Error communicating with the wayland display"); } } if (mXdgSurfaceConfigured) { INFO(mLogCategory,"xdg surface had configured"); } else { WARNING(mLogCategory,"xdg surface not configured"); } } else { ERROR(mLogCategory,"Unable to use xdg_wm_base "); return; } } void WaylandDisplay::destroyWindowSurfaces() { if (mXdgToplevel) { xdg_toplevel_destroy (mXdgToplevel); mXdgToplevel = NULL; } if (mXdgSurface) { xdg_surface_destroy (mXdgSurface); mXdgSurface = NULL; } if (mVideoSurfaceWrapper) { wl_proxy_wrapper_destroy (mVideoSurfaceWrapper); mVideoSurfaceWrapper = NULL; } if (mVideoSubSurface) { wl_subsurface_destroy (mVideoSubSurface); mVideoSubSurface = NULL; } if (mVideoSurface) { wl_surface_destroy (mVideoSurface); mVideoSurface = NULL; } if (mAreaSurfaceWrapper) { wl_proxy_wrapper_destroy (mAreaSurfaceWrapper); mAreaSurfaceWrapper = NULL; } if (mAreaSurface) { wl_surface_destroy (mAreaSurface); mAreaSurface = NULL; mReCommitAreaSurface = false; } if (mAreaViewport) { wp_viewport_destroy(mAreaViewport); mAreaViewport = NULL; } if (mVideoViewport) { wp_viewport_destroy(mVideoViewport); mVideoViewport = NULL; } } void WaylandDisplay::ensureFullscreen(bool fullscreen) { if (mXdgWmBase) { DEBUG(mLogCategory,"full screen : %d",fullscreen); if (fullscreen) { xdg_toplevel_set_fullscreen (mXdgToplevel, mCurrentDisplayOutput->wlOutput); } else { xdg_toplevel_unset_fullscreen (mXdgToplevel); } } } void WaylandDisplay::setRenderRectangle(int x, int y, int w, int h) { DEBUG(mLogCategory,"set render rect:x:%d,y:%d,w:%d,h:%d",x,y,w,h); if (w <= 0 || h <= 0) { WARNING(mLogCategory, "wrong render width or height %dx%d",w,h); return; } mRenderRect.x = x; mRenderRect.y = y; mRenderRect.w = w; mRenderRect.h = h; /*if default transform is 1(90 degree) or 3(270 degree) or 5(flip and 90 degree) or 7(flip and 270 degree),transform w/h*/ if (mVideoOnOSD && (mDefaultTransform == 1 || mDefaultTransform == 3 || mDefaultTransform == 5 || mDefaultTransform == 7)) { mRenderRect.w = h; mRenderRect.h = w; DEBUG(mLogCategory,"transform %d,reset w:%d,h:%d",mDefaultTransform, mRenderRect.w, mRenderRect.h); } if (!mXdgSurfaceConfigured) { WARNING(mLogCategory,"Not configured xdg"); return; } if (mAreaViewport) { wp_viewport_set_destination (mAreaViewport, mRenderRect.w, mRenderRect.h); } updateBorders(); if (mVideoWidth != 0 && mVideoSurface) { wl_subsurface_set_sync (mVideoSubSurface); resizeVideoSurface(true); } wl_surface_damage (mAreaSurfaceWrapper, 0, 0, mRenderRect.w, mRenderRect.h); wl_surface_commit (mAreaSurfaceWrapper); if (mVideoWidth != 0) { wl_subsurface_set_desync (mVideoSubSurface); } } void WaylandDisplay::setFrameSize(int w, int h) { mVideoWidth = w; mVideoHeight = h; mUpdateCropSize = true; INFO(mLogCategory,"frame w:%d,h:%d",mVideoWidth,mVideoHeight); } void WaylandDisplay::setWindowSize(int x, int y, int w, int h) { mWindowRect.x = x; mWindowRect.y = y; mWindowRect.w = w; mWindowRect.h = h; INFO(mLogCategory,"window size:x:%d,y:%d,w:%d,h:%d",mWindowRect.x,mWindowRect.y,mWindowRect.w,mWindowRect.h); //if user set window size,force update window size if (mAmlConfigAPIList.enableForceUpdateRect && mVideoSurfaceWrapper && mVideoWidth != 0) { if (mFullScreen) { mFullScreen = false; ensureFullscreen(mFullScreen); } resizeVideoSurface(false); //force weston update rectangle wl_surface_force_update_rect(mVideoSurfaceWrapper); wl_display_flush (mWlDisplay); } else { mUpdateVideoSurface = true; } } void WaylandDisplay::setCropSize(int x, int y, int w, int h) { if (w <= 0 || h <= 0) { WARNING(mLogCategory, "wrong crop width or height %dx%d",w,h); return; } mCropRect.x = x; mCropRect.y = y; mCropRect.w = w; mCropRect.h = h; INFO(mLogCategory,"set crop window size:x:%d,y:%d,w:%d,h:%d",mCropRect.x,mCropRect.y,mCropRect.w,mCropRect.h); mUpdateCropSize = true; } void WaylandDisplay::setPixelAspectRatio(double ratio) { //TRACE(mLogCategory,"pixel aspect ratio:%f",ratio); if (fabs(mPixelAspectRatio - ratio) > 0.001) { mUpdateVideoSurface = true; } mPixelAspectRatio = ratio; } void WaylandDisplay::configCropSize(int dma_width, int dma_height) { if (mVideoViewport && mVideoHeight > 0 && mVideoWidth > 0 && dma_width > 0 && dma_height > 0) { int cx = mCropRect.x*dma_width/mVideoWidth; int cy = mCropRect.y*dma_height/mVideoHeight; int cw = mCropRect.w*dma_width/mVideoWidth; int ch = mCropRect.h*dma_height/mVideoHeight; if (cw > 0 && ch > 0) { INFO(mLogCategory,"set crop size:x:%d,y:%d,w:%d,h:%d to weston",cx,cy,cw,ch); wl_fixed_t fixed_t_x = wl_fixed_from_int(cx); wl_fixed_t fixed_t_y = wl_fixed_from_int(cy); wl_fixed_t fixed_t_w = wl_fixed_from_int(cw); wl_fixed_t fixed_t_h = wl_fixed_from_int(ch); wp_viewport_set_source(mVideoViewport, fixed_t_x,fixed_t_y,fixed_t_w,fixed_t_h); wl_display_flush (mWlDisplay); } else { INFO(mLogCategory,"warning crop size ,crop:(%d,%d,%d,%d) w:%d/%d,h:%d/%d", mCropRect.x, mCropRect.y, mCropRect.w, mCropRect.h,dma_width, mVideoWidth,dma_height, mVideoHeight); } } else { mUpdateCropSize = true; } } void WaylandDisplay::resizeVideoSurface(bool commit) { Rectangle src = {0,}; Rectangle dst = {0,}; Rectangle res = {0,}; /* center the video_subsurface inside area_subsurface */ src.w = mVideoWidth; src.h = mVideoHeight; /*if had set the window size, we will scall video surface to this window size,do not limit window size*/ if (mWindowRect.w > 0 && mWindowRect.h > 0) { dst.x = mWindowRect.x; dst.y = mWindowRect.y; dst.w = mWindowRect.w; dst.h = mWindowRect.h; } else { //scal video to full screen dst.w = mRenderRect.w; dst.h = mRenderRect.h; } /* 90/270 rotation window position check, dst window position updated * only hardware rotation is available */ if (mVideoRotateDegree == 90 || mVideoRotateDegree == 270) { if (checkHWRotationAvailable()) { src.w = mVideoHeight; src.h = mVideoWidth; INFO(mLogCategory,"rotation %d degree src (%d,%d) -> (%d,%d)",mVideoRotateDegree,mVideoWidth, mVideoHeight, src.w, src.h); } } if (mKeepVideoAsptectRatio) { if (mViewporter) { videoCenterRect(src, dst, &res, true); } else { videoCenterRect(src, dst, &res, false); } } else { res.x = dst.x; res.y = dst.y; res.w = dst.w; res.h = dst.h; } if (mVideoSubSurface) { wl_subsurface_set_position (mVideoSubSurface, res.x, res.y); } if (mVideoSurfaceWrapper && commit) { wl_surface_damage (mVideoSurfaceWrapper, 0, 0, res.w, res.h); wl_surface_commit (mVideoSurfaceWrapper); } //top level setting if (mXdgToplevel) { struct wl_region *region; region = wl_compositor_create_region (mCompositor); wl_region_add (region, 0, 0, mRenderRect.w, mRenderRect.h); wl_surface_set_input_region (mAreaSurface, region); wl_region_destroy (region); } /* this is saved for use in wl_surface_damage */ mVideoRect.x = res.x; mVideoRect.y = res.y; mVideoRect.w = res.w; mVideoRect.h = res.h; //to scale video surface if (mVideoViewport) { wp_viewport_set_destination(mVideoViewport, res.w, res.h); wl_display_flush (mWlDisplay); } TRACE(mLogCategory,"video rectangle,x:%d,y:%d,w:%d,h:%d",mVideoRect.x, mVideoRect.y, mVideoRect.w, mVideoRect.h); } void WaylandDisplay::setOpaque() { struct wl_region *region; /* Set area opaque */ if (mCompositor) { region = wl_compositor_create_region (mCompositor); wl_region_add (region, 0, 0, mRenderRect.w, mRenderRect.h); wl_surface_set_opaque_region (mAreaSurface, region); wl_region_destroy (region); } } int WaylandDisplay::prepareFrameBuffer(RenderBuffer * buf) { WaylandBuffer *waylandBuf = NULL; int ret = NO_ERROR; bool isNew = false; if (!mWlDisplay || !mDisplayConnected) { ERROR(mLogCategory,"Error disconnected from weston %s", mWlDisplay == NULL?"mWlDisplay is NULL":" "); return ERROR_UNKNOWN; } if (buf->flag & BUFFER_FLAG_LAST_EMPTY_BUFFER) { mWaylandBufferCookieId += 1; DEBUG(mLogCategory,"drs, get last buffer, update cookie:%d",mWaylandBufferCookieId); if (DUMP_WAYLAND_BUFFER_LOG) { dumpAllWaylandBuffer(); } cleanWaylandBufferBeforeResChanged(); return NO_ERROR; } if (DUMP_WAYLAND_BUFFER_LOG) { DEBUG(mLogCategory,"pts:%lld us w:%d,h:%d",buf->pts/1000, buf->dma.width,buf->dma.height); } //detect frame rate and frame duration if (mFrameRateFractionNum == 0) { if (mPreFramePts == -1) { mPreFramePts = buf->pts; //ns } else { if (mFrameRateDetectCnt < DETECT_FRAMERATE_CNT) { mFrameRateDetectPeriod += (buf->pts - mPreFramePts); ++mFrameRateDetectCnt; } if (mFrameRateDetectCnt == DETECT_FRAMERATE_CNT) { int64_t mFrameDurationUs = (mFrameRateDetectPeriod/mFrameRateDetectCnt)/1000; double rate = 1000000.0/mFrameDurationUs; mFrameRateFractionNum = rate * 100; mFrameRateFractionDenom = 100; mFrameRateChanged = true; INFO(mLogCategory,"detect frame num:%d,denom:%d,dur:%lld us", mFrameRateFractionNum,mFrameRateFractionDenom,mFrameDurationUs); } } } mPreFramePts = buf->pts; if (!mDmabuf) { ERROR(mLogCategory,"Error zwp_linux_dmabuf_v1"); return ERROR_UNKNOWN; } //prepare frame with and height if ((mPrepareFrameWidth == 0 || mPrepareFrameHeight == 0) && buf->dma.width > 0) { mPrepareFrameWidth = buf->dma.width; mPrepareFrameHeight = buf->dma.height; } //if resolution changed,update waylandbuffer cookie id if (mPrepareFrameWidth != buf->dma.width || mPrepareFrameHeight != buf->dma.height) { mWaylandBufferCookieId += 1; DEBUG(mLogCategory,"drs,pts:%lld us,w:%d,h:%d,update cookie:%d",buf->pts/1000,buf->dma.width,buf->dma.height,mWaylandBufferCookieId); } //TRACE(mLogCategory,"renderbuf:%p,pts:%lld us,w:%d,h:%d,cookie:%d",buf,buf->pts/1000,buf->dma.width,buf->dma.height,mWaylandBufferCookieId); waylandBuf = findWaylandBuffer(buf); if (waylandBuf == NULL || waylandBuf->getCookieId() != mWaylandBufferCookieId) { /*When the fps changes, sometimes the hash value of the dmabuffer is the same as that of the previous buffer. we should free previous wayland buffer*/ if (waylandBuf && waylandBuf->isFree()) { removeWaylandBuffer(buf); delete waylandBuf; waylandBuf = NULL; } waylandBuf = new WaylandBuffer(this, mLogCategory, mWaylandBufferCookieId); isNew = true; } waylandBuf->setBufferFormat(mBufferFormat); ret = waylandBuf->constructWlBuffer(buf); if (ret != NO_ERROR) { WARNING(mLogCategory,"dmabufConstructWlBuffer fail"); } mPrepareFrameWidth = buf->dma.width; mPrepareFrameHeight = buf->dma.height; if (isNew) { addWaylandBuffer(buf, waylandBuf); } return NO_ERROR; } void WaylandDisplay::displayFrameBuffer(RenderBuffer * buf, int64_t realDisplayTime) { WaylandBuffer *waylandBuf = NULL; struct wl_buffer * wlbuffer = NULL; int ret = NO_ERROR; if (!buf) { ERROR(mLogCategory,"Error input params, RenderBuffer is null"); return; } if (!mWlDisplay || !mDisplayConnected) { ERROR(mLogCategory,"Error disconnect from weston %s", mWlDisplay == NULL?"mWlDisplay is NULL":" "); goto waylandbuf_fail; } if (buf->flag & BUFFER_FLAG_LAST_EMPTY_BUFFER) { int num = 0; DEBUG(mLogCategory,"drs, last buffer, cookie:%d",mWaylandBufferCookieId); mWaylandPlugin->handleBufferRelease(buf); /*clean prepared waylandbuffers but dropped, we should force buffer state to idle and clean it*/ { ATools::Mutex::Autolock _l(mBufferMutex); for (auto item = mWaylandBuffersMap.begin(); item != mWaylandBuffersMap.end(); ) { WaylandBuffer *wlbuf = (WaylandBuffer*)item->second; if (wlbuf->getCookieId() < mWaylandBufferCookieId && wlbuf->getBufferState() == BUFFER_PREPARED) { wlbuf->forceBufferStateIdle(); } item++; } } num = cleanWaylandBufferBeforeResChanged(); if (num > 0) { mUpdateWaylandBuffersMap = true; } else { mUpdateWaylandBuffersMap = false; } if (DUMP_WAYLAND_BUFFER_LOG) { dumpAllWaylandBuffer(); } return; } if ((mDisplayedFrameWidth == 0 || mDisplayedFrameHeight == 0) && buf->dma.width > 0) { mDisplayedFrameWidth = buf->dma.width; mDisplayedFrameHeight = buf->dma.height; mUpdateVideoSurface = true; } if (buf->flag & BUFFER_FLAG_DMA_BUFFER) { if (buf->dma.width <=0 || buf->dma.height <=0) { buf->dma.width = mVideoWidth; buf->dma.height = mVideoHeight; } waylandBuf = findWaylandBuffer(buf); if (waylandBuf) { waylandBuf->setRenderRealTime(realDisplayTime); } else { ERROR(mLogCategory,"NOT found wayland buffer,please prepare buffer first"); goto waylandbuf_fail; } } //if no wl_output, drop this buffer if (mCurrentDisplayOutput->wlOutput == NULL) { TRACE(mLogCategory,"No wl_output"); mRenderMutex.lock(); //insert this buffer to committed weston buffer manager std::pair item(realDisplayTime, waylandBuf); mCommittedBufferMap.insert(item); mRenderMutex.unlock(); mWaylandPlugin->handleFrameDropped(buf); WaylandBuffer::bufferRelease(waylandBuf,NULL); return; } //must commit areasurface first, because weston xdg surface maybe timeout //this cause video is not display,commit can resume xdg surface if (!mReCommitAreaSurface) { mReCommitAreaSurface = true; wl_surface_commit (mAreaSurface); } //set frame rate to weston,it lets weston to select suitable mode if (mFrameRateChanged && mAmlConfigAPIList.enableSetDisplayRate) { mFrameRateChanged = false; TRACE(mLogCategory,"set frame rate %d/%d to weston", mFrameRateFractionNum, mFrameRateFractionDenom); wl_surface_set_display_rate(mVideoSurfaceWrapper, mFrameRateFractionNum, mFrameRateFractionDenom); //get dpss latency from weston if (mAmlConfigAPIList.enableDpss) { int fr = mFrameRateFractionNum * 1000/mFrameRateFractionDenom; if (fr <= 0) { fr = 60000; } TRACE(mLogCategory,"get dpss latency from weston"); aml_config_get_dpss_latency(mAmlConfig, mCompositor, fr, mSelectOutputIndex); } } //resolution changed,update video surface size if (buf->dma.width != mDisplayedFrameWidth || buf->dma.height != mDisplayedFrameHeight) { mUpdateVideoSurface = true; mUpdateCropSize = true; mUpdateWaylandBuffersMap = true; } //update video crop size if (mUpdateCropSize && mVideoViewport) { mUpdateCropSize = false; if (!mAmlConfigAPIList.enableNeedOriginalSize) { configCropSize(buf->dma.width, buf->dma.height); } else { configCropSize(mVideoWidth, mVideoHeight); } } //update video surface size if (mUpdateVideoSurface && mVideoSurface) { mUpdateVideoSurface = false; //if had full screen, unset it and set window size if (mFullScreen) { mFullScreen = false; ensureFullscreen(mFullScreen); } /*don't wl_surface_commit when we ready to post buffer to weston. wl_surface_commit before post buffer makes weston mistake it for a new frame submission */ resizeVideoSurface(false); } if (mVideoPlaneZorderChanged && mVideoSurfaceWrapper) { mVideoPlaneZorderChanged = false; //only set zorder to area surface when doing osd displayed if (mVideoOnOSD) { wl_surface_set_zorder(mAreaSurfaceWrapper, mVideoPlaneZorder); } wl_surface_set_zorder(mVideoSurfaceWrapper, mVideoPlaneZorder); } //TRACE(mLogCategory,"display renderBuffer:%p,PTS:%lld us,realtime:%lld",buf, buf->pts/1000, realDisplayTime); if (waylandBuf) { wlbuffer = waylandBuf->getWlBuffer(); } if (wlbuffer) { ATools::Mutex::Autolock _l(mRenderMutex); auto cItem = mCommittedBufferMap.find(realDisplayTime); if (cItem != mCommittedBufferMap.end()) { TRACE(mLogCategory,"Error.release same display time buffer,pts:%lld us",buf->pts/1000); goto waylandbuf_fail; } //if renderbuffer in WaylandBuffer is null, construct it if (!waylandBuf->getRenderBuffer()) { ret = waylandBuf->constructWlBuffer(buf); if (ret != NO_ERROR) { WARNING(mLogCategory,"waylandBuf construct failed"); goto waylandbuf_fail; } } // some DTV case will wait for first frame signal to unmute video // we need report first frame signal in advance before attach the frame // otherwise, it may cause weston mute video until several frames attached if (!mSignalFirstFramePts && mFirstFramePts == INVALID_PTS && mCommitCnt == 0) { mSignalFirstFramePts = true; INFO(mLogCategory,"signalFirstFrame:%lld ns",buf->pts); mWaylandPlugin->handleMsgNotify(MSG_FIRST_FRAME, (void*)&buf->pts); } ++mCommitCnt; uint32_t hiPts = realDisplayTime >> 32; uint32_t lowPts = realDisplayTime & 0xFFFFFFFF; //attach this wl_buffer to weston DEBUG(mLogCategory,"++attach,renderbuf:%p(%p),fd(%d,%d),wl_buffer:%p(%d,%d,%d,%d),pts:%lld us(%lld),commitCnt:%d", buf,waylandBuf,buf->dma.fd[0],buf->dma.fd[1],wlbuffer,mVideoRect.x,mVideoRect.y,mVideoRect.w,mVideoRect.h,buf->pts/1000,realDisplayTime,mCommitCnt); waylandBuf->attach(mVideoSurfaceWrapper); if (mAmlConfigAPIList.enableSetPts) { //TRACE(mLogCategory,"display time:%lld,hiPts:%u,lowPts:%u",realDisplayTime, hiPts, lowPts); wl_surface_set_pts(mVideoSurfaceWrapper, hiPts, lowPts); } //record first pts after mute video when flush if (!mKeepFrameOnFlush && mMuteVideoOnFlush && mFirstPtsAfterFlush == INVALID_PTS) { mFirstPtsAfterFlush = buf->pts; DEBUG(mLogCategory,"mFirstPtsAfterFlush,pts:%lld us",mFirstPtsAfterFlush); } if (mFirstFramePts == INVALID_PTS) { mFirstFramePts = buf->pts; DEBUG(mLogCategory,"mFirstFramePts,pts:%lld ns",mFirstFramePts); } //doing video rotate if needed if (mVideoRotateDegree >= 0) { /*weston accept degree map: transfer value->degree, 0->0, 1->90,2->180,3->270 detail see: enum wl_output_transform or search WL_OUTPUT_TRANSFORM_xxx*/ int transform = 0; switch (mVideoRotateDegree) { case 90: { transform = 1; } break; case 180: { transform = 2; } break; case 270: { transform = 3; } break; default: transform = 0; } DEBUG(mLogCategory,"set transform to weston:%d",transform); wl_surface_set_buffer_transform(mVideoSurfaceWrapper, transform); } wl_surface_damage (mVideoSurfaceWrapper, 0, 0, mVideoRect.w, mVideoRect.h); wl_surface_commit (mVideoSurfaceWrapper); mDisplayedFrameWidth = buf->dma.width; mDisplayedFrameHeight = buf->dma.height; //insert this buffer to committed weston buffer manager std::pair item(realDisplayTime, waylandBuf); mCommittedBufferMap.insert(item); } else { WARNING(mLogCategory,"wlbuffer is NULL,pts:%lld us",buf->pts/1000); goto waylandbuf_fail; } wl_display_flush (mWlDisplay); //set keep last frame or not when after send first buffer to weston,1 keep last frame, 0 not if (mToSendKeepLastFrame) { setKeepLastFrame(mKeepLastFrame); } /** * 1.clean released buffers by weston,these buffers had displayed and released * 2.some buffer dropped before committed to weston, force these buffer to idle * state and clean them */ if (mUpdateWaylandBuffersMap) { int num = 0; /*if waylandBuf's cookie reached mWaylandBufferCookieId, those buffers allocated before waylandBuf had displayed, if buffer state is prepared,maybe this buffer dropped. we should force buffer state to idle and clean it*/ if (waylandBuf->getCookieId() == mWaylandBufferCookieId) { ATools::Mutex::Autolock _l(mBufferMutex); for (auto item = mWaylandBuffersMap.begin(); item != mWaylandBuffersMap.end(); ) { WaylandBuffer *wlbuf = (WaylandBuffer*)item->second; if (wlbuf->getCookieId() < mWaylandBufferCookieId && wlbuf->getBufferState() == BUFFER_PREPARED) { wlbuf->forceBufferStateIdle(); } item++; } } num = cleanWaylandBufferBeforeResChanged(); if (num > 0) { mUpdateWaylandBuffersMap = true; } else { mUpdateWaylandBuffersMap = false; } if (DUMP_WAYLAND_BUFFER_LOG) { dumpAllWaylandBuffer(); } } return; waylandbuf_fail: //notify dropped mWaylandPlugin->handleFrameDropped(buf); //notify app release this buf mWaylandPlugin->handleBufferRelease(buf); if (waylandBuf) { if (waylandBuf->getCookieId() < mWaylandBufferCookieId && waylandBuf->getBufferState() == BUFFER_PREPARED) { waylandBuf->forceBufferStateIdle(); cleanWaylandBufferBeforeResChanged(); if (DUMP_WAYLAND_BUFFER_LOG) { dumpAllWaylandBuffer(); } } } return; } void WaylandDisplay::handleBufferReleaseCallback(WaylandBuffer *buf) { RenderBuffer *renderBuffer = buf->getRenderBuffer(); { ATools::Mutex::Autolock _l(mRenderMutex); //remove buffer if this buffer is ready to release auto item = mCommittedBufferMap.find(buf->getRenderRealTime()); if (item != mCommittedBufferMap.end()) { --mCommitCnt; mCommittedBufferMap.erase(buf->getRenderRealTime()); } else { if (renderBuffer) { TRACE(mLogCategory,"Error,Can't find WaylandBuffer pts:%lld us (%lld) in buffer map %p", renderBuffer->pts/1000,buf->getRenderRealTime(),renderBuffer); } return; } } DEBUG(mLogCategory,"renderBuffer :%p,fd(%d,%d)priv:%p,PTS:%lld us,realtime:%lld us,commitCnt:%d", renderBuffer,renderBuffer->dma.fd[0],renderBuffer->dma.fd[1],renderBuffer->priv,renderBuffer->pts/1000,buf->getRenderRealTime(),mCommitCnt); mWaylandPlugin->handleBufferRelease(renderBuffer); } void WaylandDisplay::handleFrameDisplayedCallback(WaylandBuffer *buf) { RenderBuffer *renderBuffer = buf->getRenderBuffer(); DEBUG(mLogCategory,"renderBuffer:%p,PTS:%lld us,realtime:%lld us",renderBuffer,renderBuffer->pts/1000,buf->getRenderRealTime()); if (!mSignalFirstFramePts && mFirstFramePts != INVALID_PTS && renderBuffer->pts >= mFirstFramePts) { mSignalFirstFramePts = true; INFO(mLogCategory,"signalFirstFrame:%lld ns",renderBuffer->pts); mWaylandPlugin->handleMsgNotify(MSG_FIRST_FRAME, (void*)&renderBuffer->pts); } if (!mAmlConfigAPIList.enableSetPts) { mWaylandPlugin->signalWaitTimeout(); } mWaylandPlugin->handleFrameDisplayed(renderBuffer); if (!mKeepFrameOnFlush && mMuteVideoOnFlush && mFirstPtsAfterFlush != INVALID_PTS && mFirstPtsAfterFlush <= renderBuffer->pts) { mFirstPtsAfterFlush = INVALID_PTS; mMuteVideoOnFlush = false; wl_surface_set_video_plane_mute(mVideoSurfaceWrapper, 0); INFO(mLogCategory,"unmute video"); } } void WaylandDisplay::handleFrameDropedCallback(WaylandBuffer *buf) { RenderBuffer *renderBuffer = buf->getRenderBuffer(); DEBUG(mLogCategory,"renderBuffer :%p,PTS:%lld us,realtime:%lld us",renderBuffer,renderBuffer->pts/1000,buf->getRenderRealTime()); mWaylandPlugin->handleFrameDropped(renderBuffer); } void WaylandDisplay::handleFrameTime(WaylandBuffer *buf,uint32_t sec, uint32_t usec) { RenderBuffer *renderBuffer = buf->getRenderBuffer(); handleFrameDisplayedCallback(buf); int64_t frameDisplayTimeUs = int64_t(sec)*1000000LL + usec; if (mLastDisplayFramePts == -1) { mLastDisplayFramePts = renderBuffer->pts; mLastDisplayFrameTimeUs = frameDisplayTimeUs; } else { //calculate last frame displayed duration int64_t duration = frameDisplayTimeUs - mLastDisplayFrameTimeUs; TRACE(mLogCategory,"now pts:%lld us,last dur:%lld us,time:%u us", renderBuffer->pts/1000,duration,frameDisplayTimeUs); if (mFrameDurationUs > 0 && duration > FRAME_FREEZE_THRESHOLD(mFrameDurationUs)) { FrameDisplayInfo frameDisplayInfo = {-1, -1}; frameDisplayInfo.pts = mLastDisplayFramePts; frameDisplayInfo.duration = duration; INFO(mLogCategory,"reportFreezeFrame,pts:%lld us,dur:%lld us",frameDisplayInfo.pts/1000,frameDisplayInfo.duration); mWaylandPlugin->handleMsgNotify(MSG_FRAME_FREEZE, &frameDisplayInfo); } mLastDisplayFramePts = renderBuffer->pts; mLastDisplayFrameTimeUs = frameDisplayTimeUs; } } void WaylandDisplay::readyToRun() { mFd = wl_display_get_fd (mWlDisplay); if (mPoll) { mPoll->addFd(mFd); mPoll->setFdReadable(mFd, true); } } void WaylandDisplay::readyToExit() { if (mPoll && mFd >= 0) { mPoll->removeFd(mFd); } } bool WaylandDisplay::threadLoop() { int ret; while (wl_display_prepare_read_queue (mWlDisplay, mWlQueue) != 0) { wl_display_dispatch_queue_pending (mWlDisplay, mWlQueue); } wl_display_flush (mWlDisplay); /*poll timeout value must > 300 ms,otherwise zwp_linux_dmabuf will create failed, so do use -1 to wait for ever*/ ret = mPoll->wait(-1); //wait for ever if (ret < 0) { //poll error WARNING(mLogCategory,"poll error"); wl_display_cancel_read(mWlDisplay); return false; } else if (ret == 0) { //poll time out return true; //run loop } if (wl_display_read_events (mWlDisplay) == -1) { ERROR(mLogCategory,"Error communicating with the wayland server"); mDisplayConnected = false; return false; } wl_display_dispatch_queue_pending (mWlDisplay, mWlQueue); return true; } void WaylandDisplay::videoCenterRect(Rectangle src, Rectangle dst, Rectangle *result, bool scaling) { //if dst is a small window, we scale video to map window size,don't doing center // if (mRenderRect.w != dst.w && mRenderRect.h != dst.h) { // result->x = dst.x; // result->y = dst.y; // result->w = dst.w; // result->h = dst.h; // TRACE(mLogCategory,"small window source is %dx%d dest is %dx%d, result is %d,%d,%d,%d", // src.w, src.h, dst.w, dst.h, result->x, result->y, result->w, result->h); // return; // } if (!scaling) { result->w = MIN (src.w, dst.w); result->h = MIN (src.h, dst.h); result->x = dst.x + (dst.w - result->w) / 2; result->y = dst.y + (dst.h - result->h) / 2; } else { double src_ratio, dst_ratio; src_ratio = (double) (src.w * mPixelAspectRatio) / src.h; dst_ratio = (double) dst.w / dst.h; if (src_ratio > dst_ratio) { result->w = dst.w; result->h = dst.w / src_ratio; result->x = dst.x; result->y = dst.y + (dst.h - result->h) / 2; } else if (src_ratio < dst_ratio) { result->w = dst.h * src_ratio; result->h = dst.h; result->x = dst.x + (dst.w - result->w) / 2; result->y = dst.y; } else { result->x = dst.x; result->y = dst.y; result->w = dst.w; result->h = dst.h; } } DEBUG(mLogCategory,"source is %dx%d dest is %dx%d, result is %d,%d,%d,%d", src.w, src.h, dst.w, dst.h, result->x, result->y,result->w, result->h); } void WaylandDisplay::updateBorders() { int width = 0; int height = 0; if (mNoBorderUpdate) return; if (mViewporter) { width = height = 1; mNoBorderUpdate = true; } else { width = mRenderRect.w; height = mRenderRect.h; } RenderVideoFormat format = VIDEO_FORMAT_BGRA; mAreaShmBuffer = new WaylandShmBuffer(this, mLogCategory); struct wl_buffer *wlbuf = mAreaShmBuffer->constructAreaShmWlBuffer(width, height, format); if (wlbuf == NULL) { delete mAreaShmBuffer; mAreaShmBuffer = NULL; } if (mAreaSurfaceWrapper) { wl_surface_attach(mAreaSurfaceWrapper, wlbuf, 0, 0); } } std::size_t WaylandDisplay::calculateDmaBufferHash(RenderDmaBuffer &dmabuf) { std::string hashString(""); for (int i = 0; i < dmabuf.planeCnt; i++) { char hashtmp[1024]; snprintf (hashtmp, 1024, "%d%d%d%d%d%d%d%d",i,dmabuf.width,dmabuf.height,dmabuf.planeCnt, dmabuf.stride[i],dmabuf.offset[i],dmabuf.fd[i],dmabuf.handle[i]); std::string tmp(hashtmp); hashString += tmp; } std::size_t hashval = std::hash()(hashString); if (DUMP_WAYLAND_BUFFER_LOG) { TRACE(mLogCategory,"hashstr:%s,val:%zu",hashString.c_str(),hashval); } return hashval; } void WaylandDisplay::addWaylandBuffer(RenderBuffer * buf, WaylandBuffer *waylandbuf) { ATools::Mutex::Autolock _l(mBufferMutex); if (buf->flag & BUFFER_FLAG_DMA_BUFFER) { std::size_t hashval = calculateDmaBufferHash(buf->dma); std::pair item(hashval, waylandbuf); if (DUMP_WAYLAND_BUFFER_LOG) { TRACE(mLogCategory,"fd[0]:%d,fd[1]:%d,w:%d,h:%d,%p,hash:%zu",buf->dma.fd[0],buf->dma.fd[1],buf->dma.width,buf->dma.height,waylandbuf,hashval); } mWaylandBuffersMap.insert(item); } TRACE(mLogCategory,"mWaylandBuffersMap size:%d",mWaylandBuffersMap.size()); if (DUMP_WAYLAND_BUFFER_LOG) { dumpAllWaylandBuffer(); } } WaylandBuffer* WaylandDisplay::findWaylandBuffer(RenderBuffer * buf) { ATools::Mutex::Autolock _l(mBufferMutex); std::size_t hashval = calculateDmaBufferHash(buf->dma); auto item = mWaylandBuffersMap.find(hashval); if (item == mWaylandBuffersMap.end()) { return NULL; } return (WaylandBuffer*) item->second; } void WaylandDisplay::removeWaylandBuffer(RenderBuffer * buf) { ATools::Mutex::Autolock _l(mBufferMutex); std::size_t hashval = calculateDmaBufferHash(buf->dma); auto item = mWaylandBuffersMap.find(hashval); if (item == mWaylandBuffersMap.end()) { return ; } mWaylandBuffersMap.erase(item); } void WaylandDisplay::cleanAllWaylandBuffer() { ATools::Mutex::Autolock _l(mBufferMutex); //free all obtain buff for (auto item = mWaylandBuffersMap.begin(); item != mWaylandBuffersMap.end(); ) { WaylandBuffer *waylandbuf = (WaylandBuffer*)item->second; /* If buffer is still held by weston (DISPLAYING or DISPLAYED state), * defer the buffer release callback until after wl_display_disconnect. * weston may still be using these buffers; releasing them prematurely * could cause the upstream plugin to reuse a buffer weston still reads. */ if ((waylandbuf->getBufferState() == BUFFER_DISPLAYING || waylandbuf->getBufferState() == BUFFER_DISPLAYED) && waylandbuf->getRenderBuffer()) { mDeferredReleaseBuffers.push_back(waylandbuf->getRenderBuffer()); waylandbuf->forceBufferStateIdle(); // sets state to IDLE, clears mRenderBuffer } mWaylandBuffersMap.erase(item++); delete waylandbuf; } mWaylandBuffersMap.clear(); } void WaylandDisplay::releaseDeferredBuffers() { for (auto buf : mDeferredReleaseBuffers) { INFO(mLogCategory,"release deferred buffer:%p,pts:%lld us", buf, buf->pts/1000); mWaylandPlugin->handleBufferRelease(buf); } mDeferredReleaseBuffers.clear(); } void WaylandDisplay::dumpAllWaylandBuffer() { INFO(mLogCategory,"mWaylandBuffersMap size:%d",mWaylandBuffersMap.size()); for (auto item = mWaylandBuffersMap.begin(); item != mWaylandBuffersMap.end(); ) { WaylandBuffer *wlbuf = (WaylandBuffer*)item->second; std::size_t hashVal = item->first; item++; INFO(mLogCategory,"WaylandBuffer:%p, hash:%zu, state:%d, cookie:%d, %dx%d", wlbuf,hashVal,wlbuf->getBufferState(),wlbuf->getCookieId(),wlbuf->getFrameWidth(),wlbuf->getFrameHeight()); } } /** * @brief clean wayland buffers those malloc before resolution changed * */ int WaylandDisplay::cleanWaylandBufferBeforeResChanged() { int num = 0; ATools::Mutex::Autolock _l(mBufferMutex); for (auto item = mWaylandBuffersMap.begin(); item != mWaylandBuffersMap.end(); ) { WaylandBuffer *wlbuf = (WaylandBuffer*)item->second; std::size_t hashVal = item->first; if (wlbuf->isFree()) { mWaylandBuffersMap.erase(item++); delete wlbuf; } else { item++; if (wlbuf->getCookieId() < mWaylandBufferCookieId) { num++; TRACE(mLogCategory,"left WaylandBuffer:%p, hash:%zu, state:%d, cookie:%d, %dx%d", wlbuf,hashVal,wlbuf->getBufferState(),wlbuf->getCookieId(),wlbuf->getFrameWidth(),wlbuf->getFrameHeight()); } } } return num; } void WaylandDisplay::flushBuffers() { INFO(mLogCategory,"flushBuffers"); if (mVideoSurfaceWrapper && !mKeepFrameOnFlush) { INFO(mLogCategory,"mute video"); mMuteVideoOnFlush = true; mFirstPtsAfterFlush = INVALID_PTS; wl_surface_set_video_plane_mute(mVideoSurfaceWrapper, 1); } /*buffers obtained by weston will displayed,so we don't force to do display callback*/ // ATools::Mutex::Autolock _l(mRenderMutex); // for (auto item = mCommittedBufferMap.begin(); item != mCommittedBufferMap.end(); item++) { // WaylandBuffer *waylandbuf = (WaylandBuffer*)item->second; // waylandbuf->forceRedrawing(); // handleFrameDisplayedCallback(waylandbuf); // } mSignalFirstFramePts = false; mFirstFramePts = INVALID_PTS; } void WaylandDisplay::cleanSurface() { /* clear both video and parent surfaces */ wl_surface_attach (mVideoSurfaceWrapper, NULL, 0, 0); wl_surface_commit (mVideoSurfaceWrapper); wl_surface_attach (mAreaSurfaceWrapper, NULL, 0, 0); wl_surface_commit (mAreaSurfaceWrapper); } void WaylandDisplay::setKeepLastFrame(int keep) { mKeepLastFrame = keep; if (mVideoSurfaceWrapper && mAmlConfigAPIList.enableKeepLastFrame) { INFO(mLogCategory,"keep last frame:%d",keep); wl_surface_keep_last_frame(mVideoSurfaceWrapper, keep); mToSendKeepLastFrame = false; return; } mToSendKeepLastFrame = true; } void WaylandDisplay::setKeepLastFrameOnFlush(int keepOnFlush) { mKeepFrameOnFlush = keepOnFlush; //INFO(mLogCategory,"keep last frame:%d",mKeepFrameOnFlush); } void WaylandDisplay::setImmediatelyOutput(int enable) { if (mVideoSurfaceWrapper) { wl_surface_enable_ll_mode(mVideoSurfaceWrapper, enable); } //INFO(mLogCategory,"set immediately output:%d", enable); } void WaylandDisplay::setFrameRate(int frameRateNum, int frameRateDenom) { mFrameRateFractionNum = frameRateNum; mFrameRateFractionDenom = frameRateDenom; if (mFrameRateFractionDenom == 0) { mFrameRateFractionDenom = 1; } if (mFrameRateFractionNum > 0) { mFrameDurationUs = 1000000 * (int64_t)mFrameRateFractionDenom/(int64_t)mFrameRateFractionNum; mFrameRateChanged = true; } if (mVideoSurfaceWrapper && mFrameRateChanged && mAmlConfigAPIList.enableSetDisplayRate) { mFrameRateChanged = false; INFO(mLogCategory,"set frame rate %d/%d to weston", mFrameRateFractionNum, mFrameRateFractionDenom); wl_surface_set_display_rate(mVideoSurfaceWrapper, mFrameRateFractionNum, mFrameRateFractionDenom); //get dpss latency from weston if (mAmlConfigAPIList.enableDpss) { int fr = mFrameRateFractionNum * 1000/mFrameRateFractionDenom; if (fr <= 0) { fr = 60000; } TRACE(mLogCategory,"get dpss latency from weston"); aml_config_get_dpss_latency(mAmlConfig, mCompositor, fr, mSelectOutputIndex); } } INFO(mLogCategory,"num:%d,denom:%d,fps:%d/100,frame dur:%lld us", mFrameRateFractionNum, mFrameRateFractionDenom,mFrameRateFractionNum*100/mFrameRateFractionDenom,mFrameDurationUs); } void WaylandDisplay::setVideoPlaneZorder(int zorder) { mVideoPlaneZorder = zorder; mVideoPlaneZorderChanged = true; if (mVideoSurfaceWrapper) { mVideoPlaneZorderChanged = false; //only set zorder to area surface when doing osd displayed if (mVideoOnOSD) { wl_surface_set_zorder(mAreaSurfaceWrapper, mVideoPlaneZorder); } wl_surface_set_zorder(mVideoSurfaceWrapper, mVideoPlaneZorder); } } void WaylandDisplay::pause() { mPaused = true; if (!mVideoIsUnderflow) { mLastDisplayFramePts = -1; mLastDisplayFrameTimeUs = -1; } } void WaylandDisplay::resume() { mPaused = false; if (!mVideoIsUnderflow) { mLastDisplayFramePts = -1; mLastDisplayFrameTimeUs = -1; } mVideoIsUnderflow = false; } void WaylandDisplay::underflow() { mVideoIsUnderflow = true; } void WaylandDisplay::setHideVideo(int hide) { mHideVideo = hide; if (mVideoSurfaceWrapper) { int mute = hide > 0 ? 1: 0; mHideVideoChanged = false; INFO(mLogCategory,"mute:%d",hide); wl_surface_set_video_plane_mute(mVideoSurfaceWrapper, mute); } else { INFO(mLogCategory,"pending mute:%d",hide); mHideVideoChanged = true; } } int WaylandDisplay::unexpectReleaseFrame(RenderBuffer *buffer) { WaylandBuffer *waylandBuf = NULL; if (buffer->flag & BUFFER_FLAG_DMA_BUFFER) { waylandBuf = findWaylandBuffer(buffer); if (waylandBuf && waylandBuf->getBufferState() == BUFFER_PREPARED) { DEBUG(mLogCategory," unexpected release pts:%lld us,cookie:%d",buffer->pts/1000,waylandBuf->getCookieId()); waylandBuf->forceBufferStateIdle(); } } return NO_ERROR; } void WaylandDisplay::setVideoOnOSD(int videoOnOSD) { mVideoOnOSD = videoOnOSD > 0? true: false; INFO(mLogCategory,"video on osd:%d",mVideoOnOSD); } /* * if v2d or dpss does 90/270 degree ratotion, we should change * window position mapping 90/270 degree ratotion,this feature * is available after 6.12 kernel version. */ bool WaylandDisplay::checkHWRotationAvailable() { bool ret = false; struct utsname info = {0}; int major = 0, minor = 0; if (uname(&info) || sscanf(info.release, "%d.%d", &major, &minor) <= 0) { WARNING(mLogCategory,"get linux version failed"); } if (major >=6 && minor >= 12) { ret = true; } INFO(mLogCategory,"linux major version %d.%d,HWRotation:%d", major,minor,ret); return ret; } int WaylandDisplay::getHWCLatency() { return mHWCLatency; } int WaylandDisplay::getDisplayVsyncAndPeriod(uint64_t *vsyncTimestamp, uint64_t *vsyncPeriod) { if (vsyncTimestamp && vsyncPeriod) { *vsyncTimestamp = mVsyncTimeUs; *vsyncPeriod = mVsyncPeriodUs; return NO_ERROR; } return ERROR_PARAM_NULL; }