/// /// @file video.c @brief Video module /// /// Copyright (c) 2009 - 2015 by Johns. All Rights Reserved. /// /// Contributor(s): /// /// License: AGPLv3 /// /// This program is free software: you can redistribute it and/or modify /// it under the terms of the GNU Affero General Public License as /// published by the Free Software Foundation, either version 3 of the /// License. /// /// This program is distributed in the hope that it will be useful, /// but WITHOUT ANY WARRANTY; without even the implied warranty of /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the /// GNU Affero General Public License for more details. /// /// $Id: bacf89f24503be74d113a83139a277ff2290014a $ ////////////////////////////////////////////////////////////////////////////// /// /// @defgroup Video The video module. /// /// This module contains all video rendering functions. /// /// @todo disable screen saver support /// /// Uses Xlib where it is needed for VA-API or cuvid. XCB is used for /// everything else. /// /// - X11 /// - OpenGL rendering /// - OpenGL rendering with GLX texture-from-pixmap /// - Xrender rendering /// /// @todo FIXME: use vaErrorStr for all VA-API errors. /// //#define PLACEBO #define USE_XLIB_XCB ///< use xlib/xcb backend #define noUSE_SCREENSAVER ///< support disable screensaver //#define USE_AUTOCROP ///< compile auto-crop support #define USE_GRAB ///< experimental grab code //#define USE_GLX ///< outdated GLX code #define USE_DOUBLEBUFFER ///< use GLX double buffers #define USE_CUVID ///< enable cuvid support //#define USE_BITMAP ///< use cuvid bitmap surface //#define AV_INFO ///< log a/v sync informations #ifndef AV_INFO_TIME #define AV_INFO_TIME (50 * 60) ///< a/v info every minute #endif #define USE_VIDEO_THREAD ///< run decoder in an own thread //#define USE_VIDEO_THREAD2 ///< run decoder+display in own threads #include #include #include #include #include #include #include #include #include #include #include #include #define _(str) gettext(str) ///< gettext shortcut #define _N(str) str ///< gettext_noop shortcut #ifdef USE_VIDEO_THREAD #ifndef __USE_GNU #define __USE_GNU #endif #include #include #include #ifndef HAVE_PTHREAD_NAME /// only available with newer glibc #define pthread_setname_np(thread, name) #endif #endif #ifdef USE_XLIB_XCB #include #include #include #include #include //#include #ifdef xcb_USE_GLX #include #endif //#include #ifdef USE_SCREENSAVER #include #include #endif //#include //#include //#include //#include //#include #include #ifdef XCB_ICCCM_NUM_WM_SIZE_HINTS_ELEMENTS #include #else // compatibility hack for old xcb-util /** * @brief Action on the _NET_WM_STATE property */ typedef enum { /* Remove/unset property */ XCB_EWMH_WM_STATE_REMOVE = 0, /* Add/set property */ XCB_EWMH_WM_STATE_ADD = 1, /* Toggle property */ XCB_EWMH_WM_STATE_TOGGLE = 2 } xcb_ewmh_wm_state_action_t; #endif #endif #ifdef USE_GLX #include #include // For GL_COLOR_BUFFER_BIT //#include // For GL_COLOR_BUFFER_BIT //#include #include // only for gluErrorString #include #include #include #endif #ifdef CUVID //#define CUDA_API_PER_THREAD_DEFAULT_STREAM #include // For GL_COLOR_BUFFER_BIT //#include // For GL_COLOR_BUFFER_BIT #include #include //#include #include //#include #include #include #include "drvapi_error_string.h" // CUDA includes #define __DEVICE_TYPES_H__ #endif #ifdef PLACEBO #define VK_USE_PLATFORM_XCB_KHR #include #include #include #include #endif #include #include // support old ffmpeg versions <1.0 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,18,102) #define AVCodecID CodecID #define AV_CODEC_ID_H263 CODEC_ID_H263 #define AV_CODEC_ID_H264 CODEC_ID_H264 #define AV_CODEC_ID_MPEG1VIDEO CODEC_ID_MPEG1VIDEO #define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO #define AV_CODEC_ID_MPEG4 CODEC_ID_MPEG4 #define AV_CODEC_ID_VC1 CODEC_ID_VC1 #define AV_CODEC_ID_WMV3 CODEC_ID_WMV3 #endif #include #include #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54,86,100) /// /// ffmpeg version 1.1.1 calls get_format with zero width and height /// for H264 codecs. /// since version 1.1.3 get_format is called twice. /// ffmpeg 1.2 still buggy /// #define FFMPEG_BUG1_WORKAROUND ///< get_format bug workaround #endif #include "iatomic.h" // portable atomic_t #include "misc.h" #include "video.h" #include "audio.h" #include "codec.h" //---------------------------------------------------------------------------- // Declarations //---------------------------------------------------------------------------- /// /// Video resolutions selector. /// typedef enum _video_resolutions_ { VideoResolution576i, ///< ...x576 interlaced VideoResolution720p, ///< ...x720 progressive VideoResolutionFake1080i, ///< 1280x1080 1440x1080 interlaced VideoResolution1080i, ///< 1920x1080 interlaced VideoResolutionUHD, /// UHD progressive VideoResolutionMax ///< number of resolution indexs } VideoResolutions; /// /// Video deinterlace modes. /// typedef enum _video_deinterlace_modes_ { VideoDeinterlaceBob, ///< bob deinterlace VideoDeinterlaceWeave, ///< weave deinterlace VideoDeinterlaceTemporal, ///< temporal deinterlace VideoDeinterlaceTemporalSpatial, ///< temporal spatial deinterlace VideoDeinterlaceSoftBob, ///< software bob deinterlace VideoDeinterlaceSoftSpatial, ///< software spatial deinterlace } VideoDeinterlaceModes; /// /// Video scaleing modes. /// typedef enum _video_scaling_modes_ { VideoScalingNormal, ///< normal scaling VideoScalingFast, ///< fastest scaling VideoScalingHQ, ///< high quality scaling VideoScalingAnamorphic, ///< anamorphic scaling } VideoScalingModes; /// /// Video zoom modes. /// typedef enum _video_zoom_modes_ { VideoNormal, ///< normal VideoStretch, ///< stretch to all edges VideoCenterCutOut, ///< center and cut out VideoAnamorphic, ///< anamorphic scaled (unsupported) } VideoZoomModes; /// /// Video color space conversions. /// typedef enum _video_color_space_ { VideoColorSpaceNone, ///< no conversion VideoColorSpaceBt601, ///< ITU.BT-601 Y'CbCr VideoColorSpaceBt709, ///< ITU.BT-709 HDTV Y'CbCr VideoColorSpaceSmpte240 ///< SMPTE-240M Y'PbPr } VideoColorSpace; /// /// Video output module structure and typedef. /// typedef struct _video_module_ { const char *Name; ///< video output module name char Enabled; ///< flag output module enabled /// allocate new video hw decoder VideoHwDecoder *(*const NewHwDecoder)(VideoStream *); void (*const DelHwDecoder) (VideoHwDecoder *); unsigned (*const GetSurface) (VideoHwDecoder *, const AVCodecContext *); void (*const ReleaseSurface) (VideoHwDecoder *, unsigned); enum AVPixelFormat (*const get_format) (VideoHwDecoder *, AVCodecContext *, const enum AVPixelFormat *); void (*const RenderFrame) (VideoHwDecoder *, const AVCodecContext *, const AVFrame *); void *(*const GetHwAccelContext)(VideoHwDecoder *); void (*const SetClock) (VideoHwDecoder *, int64_t); int64_t(*const GetClock) (const VideoHwDecoder *); void (*const SetClosing) (const VideoHwDecoder *); void (*const ResetStart) (const VideoHwDecoder *); void (*const SetTrickSpeed) (const VideoHwDecoder *, int); uint8_t *(*const GrabOutput)(int *, int *, int *, int); void (*const GetStats) (VideoHwDecoder *, int *, int *, int *, int *, float *); void (*const SetBackground) (uint32_t); void (*const SetVideoMode) (void); void (*const ResetAutoCrop) (void); /// module display handler thread void (*const DisplayHandlerThread) (void); void (*const OsdClear) (void); ///< clear OSD /// draw OSD ARGB area void (*const OsdDrawARGB) (int, int, int, int, int, const uint8_t *, int, int); void (*const OsdInit) (int, int); ///< initialize OSD void (*const OsdExit) (void); ///< cleanup OSD int (*const Init) (const char *); ///< initialize video output module void (*const Exit) (void); ///< cleanup video output module } VideoModule; typedef struct { /** Left X co-ordinate. Inclusive. */ uint32_t x0; /** Top Y co-ordinate. Inclusive. */ uint32_t y0; /** Right X co-ordinate. Exclusive. */ uint32_t x1; /** Bottom Y co-ordinate. Exclusive. */ uint32_t y1; } VdpRect; //---------------------------------------------------------------------------- // Defines //---------------------------------------------------------------------------- #define CODEC_SURFACES_MAX 16 ///< maximal of surfaces #define VIDEO_SURFACES_MAX 8 ///< video output surfaces for queue #define OUTPUT_SURFACES_MAX 4 ///< output surfaces for flip page //---------------------------------------------------------------------------- // Variables //---------------------------------------------------------------------------- AVBufferRef *HwDeviceContext; ///< ffmpeg HW device context char VideoIgnoreRepeatPict; ///< disable repeat pict warning unsigned char *posd; static const char *VideoDriverName="cuvid"; ///< video output device static Display *XlibDisplay; ///< Xlib X11 display static xcb_connection_t *Connection; ///< xcb connection static xcb_colormap_t VideoColormap; ///< video colormap static xcb_window_t VideoWindow; ///< video window static xcb_screen_t const *VideoScreen; ///< video screen static uint32_t VideoBlankTick; ///< blank cursor timer static xcb_pixmap_t VideoCursorPixmap; ///< blank curosr pixmap static xcb_cursor_t VideoBlankCursor; ///< empty invisible cursor static int VideoWindowX; ///< video output window x coordinate static int VideoWindowY; ///< video outout window y coordinate static unsigned VideoWindowWidth; ///< video output window width static unsigned VideoWindowHeight; ///< video output window height static const VideoModule NoopModule; ///< forward definition of noop module /// selected video module static const VideoModule *VideoUsedModule = &NoopModule; signed char VideoHardwareDecoder = -1; ///< flag use hardware decoder static char VideoSurfaceModesChanged; ///< flag surface modes changed /// flag use transparent OSD. static const char VideoTransparentOsd = 1; static uint32_t VideoBackground; ///< video background color static char VideoStudioLevels; ///< flag use studio levels /// Default deinterlace mode. static VideoDeinterlaceModes VideoDeinterlace[VideoResolutionMax]; /// Default number of deinterlace surfaces static const int VideoDeinterlaceSurfaces = 4; /// Default skip chroma deinterlace flag (CUVID only). static char VideoSkipChromaDeinterlace[VideoResolutionMax]; /// Default inverse telecine flag (CUVID only). static char VideoInverseTelecine[VideoResolutionMax]; /// Default amount of noise reduction algorithm to apply (0 .. 1000). static int VideoDenoise[VideoResolutionMax]; /// Default amount of sharpening, or blurring, to apply (-1000 .. 1000). static int VideoSharpen[VideoResolutionMax]; /// Default cut top and bottom in pixels static int VideoCutTopBottom[VideoResolutionMax]; /// Default cut left and right in pixels static int VideoCutLeftRight[VideoResolutionMax]; /// Color space ITU-R BT.601, ITU-R BT.709, ... static const VideoColorSpace VideoColorSpaces[VideoResolutionMax] = { VideoColorSpaceBt601, VideoColorSpaceBt709, VideoColorSpaceBt709, VideoColorSpaceBt709,VideoColorSpaceBt709 }; /// Default scaling mode static VideoScalingModes VideoScaling[VideoResolutionMax]; /// Default audio/video delay int VideoAudioDelay; /// Default zoom mode for 4:3 static VideoZoomModes Video4to3ZoomMode; /// Default zoom mode for 16:9 and others static VideoZoomModes VideoOtherZoomMode; static char Video60HzMode; ///< handle 60hz displays static char VideoSoftStartSync; ///< soft start sync audio/video static const int VideoSoftStartFrames = 100; ///< soft start frames static char VideoShowBlackPicture; ///< flag show black picture static float VideoBrightness = 0.0f; static float VideoContrast = 1.0f; static float VideoSaturation = 1.0f; static float VideoHue = 0.0f; static float VideoGamma = 1.0f; static int VulkanTargetColorSpace = 0; static int VideoScalerTest = 0; static int VideoColorBlindness = 0; static float VideoColorBlindnessFaktor = 1.0f; static xcb_atom_t WmDeleteWindowAtom; ///< WM delete message atom static xcb_atom_t NetWmState; ///< wm-state message atom static xcb_atom_t NetWmStateFullscreen; ///< fullscreen wm-state message atom static xcb_atom_t NetWmStateAbove; #ifdef DEBUG extern uint32_t VideoSwitch; ///< ticks for channel switch #endif extern void AudioVideoReady(int64_t); ///< tell audio video is ready #ifdef USE_VIDEO_THREAD static pthread_t VideoThread; ///< video decode thread static pthread_cond_t VideoWakeupCond; ///< wakeup condition variable static pthread_mutex_t VideoMutex; ///< video condition mutex static pthread_mutex_t VideoLockMutex; ///< video lock mutex pthread_mutex_t OSDMutex; ///< OSD update mutex #endif #ifdef USE_VIDEO_THREAD2 static pthread_t VideoDisplayThread; ///< video decode thread static pthread_cond_t VideoWakeupCond; ///< wakeup condition variable static pthread_mutex_t VideoDisplayMutex; ///< video condition mutex static pthread_mutex_t VideoDisplayLockMutex; ///< video lock mutex #endif static int OsdConfigWidth; ///< osd configured width static int OsdConfigHeight; ///< osd configured height static char OsdShown; ///< flag show osd static char Osd3DMode; ///< 3D OSD mode static int OsdWidth; ///< osd width static int OsdHeight; ///< osd height static int OsdDirtyX; ///< osd dirty area x static int OsdDirtyY; ///< osd dirty area y static int OsdDirtyWidth; ///< osd dirty area width static int OsdDirtyHeight; ///< osd dirty area height #ifdef USE_OPENGLOSD static void (*VideoEventCallback)(void) = NULL; /// callback function to notify VDR about Video Events #endif static int64_t VideoDeltaPTS; ///< FIXME: fix pts #ifdef USE_SCREENSAVER static char DPMSDisabled; ///< flag we have disabled dpms static char EnableDPMSatBlackScreen; ///< flag we should enable dpms at black screen #endif static int GlxEnabled; ///< use GLX static int GlxVSyncEnabled = 1; ///< enable/disable v-sync static GLXContext GlxSharedContext; ///< shared gl context static GLXContext GlxContext; ///< our gl context #ifdef USE_VIDEO_THREAD static GLXContext GlxThreadContext; ///< our gl context for the thread #endif static XVisualInfo *GlxVisualInfo; ///< our gl visual static GLuint OsdGlTextures[2]; ///< gl texture for OSD static int OsdIndex=0; ///< index into OsdGlTextures static void GlxSetupWindow(xcb_window_t window, int width, int height, GLXContext context); GLXContext OSDcontext; //---------------------------------------------------------------------------- // Common Functions //---------------------------------------------------------------------------- static void VideoThreadLock(void); ///< lock video thread static void VideoThreadUnlock(void); ///< unlock video thread static void VideoThreadExit(void); ///< exit/kill video thread #ifdef USE_SCREENSAVER static void X11SuspendScreenSaver(xcb_connection_t *, int); static int X11HaveDPMS(xcb_connection_t *); static void X11DPMSReenable(xcb_connection_t *); static void X11DPMSDisable(xcb_connection_t *); #endif uint64_t gettid() { return pthread_self(); } /// /// Update video pts. /// /// @param pts_p pointer to pts /// @param interlaced interlaced flag (frame isn't right) /// @param frame frame to display /// /// @note frame->interlaced_frame can't be used for interlace detection /// static void VideoSetPts(int64_t * pts_p, int interlaced, const AVCodecContext * video_ctx, const AVFrame * frame) { int64_t pts; int duration; // // Get duration for this frame. // FIXME: using framerate as workaround for av_frame_get_pkt_duration // if (video_ctx->framerate.num && video_ctx->framerate.den) { duration = 1000 * video_ctx->framerate.den / video_ctx->framerate.num; } else { duration = interlaced ? 40 : 20; // 50Hz -> 20ms default } // Debug(4, "video: %d/%d %" PRIx64 " -> %d\n", video_ctx->framerate.den, video_ctx->framerate.num, av_frame_get_pkt_duration(frame), duration); // update video clock if (*pts_p != (int64_t) AV_NOPTS_VALUE) { *pts_p += duration * 90; //Info("video: %s +pts\n", Timestamp2String(*pts_p)); } //av_opt_ptr(avcodec_get_frame_class(), frame, "best_effort_timestamp"); //pts = frame->best_effort_timestamp; // pts = frame->pkt_pts; pts = frame->pts; if (pts == (int64_t) AV_NOPTS_VALUE || !pts) { // libav: 0.8pre didn't set pts pts = frame->pkt_dts; } // libav: sets only pkt_dts which can be 0 if (pts && pts != (int64_t) AV_NOPTS_VALUE) { // build a monotonic pts if (*pts_p != (int64_t) AV_NOPTS_VALUE) { int64_t delta; delta = pts - *pts_p; // ignore negative jumps if (delta > -600 * 90 && delta <= -40 * 90) { if (-delta > VideoDeltaPTS) { VideoDeltaPTS = -delta; Debug(4, "video: %#012" PRIx64 "->%#012" PRIx64 " delta%+4" PRId64 " pts\n", *pts_p, pts, pts - *pts_p); } return; } } else { // first new clock value AudioVideoReady(pts); } if (*pts_p != pts) { Debug(4, "video: %#012" PRIx64 "->%#012" PRIx64 " delta=%4" PRId64 " pts\n", *pts_p, pts, pts - *pts_p); *pts_p = pts; } } } int CuvidMessage(int level, const char *format, ...); /// /// Update output for new size or aspect ratio. /// /// @param input_aspect_ratio video stream aspect /// static void VideoUpdateOutput(AVRational input_aspect_ratio, int input_width, int input_height, VideoResolutions resolution, int video_x, int video_y, int video_width, int video_height, int *output_x, int *output_y, int *output_width, int *output_height, int *crop_x, int *crop_y, int *crop_width, int *crop_height) { AVRational display_aspect_ratio; AVRational tmp_ratio; if (!input_aspect_ratio.num || !input_aspect_ratio.den) { input_aspect_ratio.num = 1; input_aspect_ratio.den = 1; Debug(3, "video: aspect defaults to %d:%d\n", input_aspect_ratio.num, input_aspect_ratio.den); } av_reduce(&input_aspect_ratio.num, &input_aspect_ratio.den, input_width * input_aspect_ratio.num, input_height * input_aspect_ratio.den, 1024 * 1024); // InputWidth/Height can be zero = uninitialized if (!input_aspect_ratio.num || !input_aspect_ratio.den) { input_aspect_ratio.num = 1; input_aspect_ratio.den = 1; } display_aspect_ratio.num = VideoScreen->width_in_pixels * VideoScreen->height_in_millimeters; display_aspect_ratio.den = VideoScreen->height_in_pixels * VideoScreen->width_in_millimeters; display_aspect_ratio = av_mul_q(input_aspect_ratio, display_aspect_ratio); Debug(3, "video: aspect %d:%d Resolution %d\n", display_aspect_ratio.num, display_aspect_ratio.den, resolution); *crop_x = VideoCutLeftRight[resolution]; *crop_y = VideoCutTopBottom[resolution]; *crop_width = input_width - VideoCutLeftRight[resolution] * 2; *crop_height = input_height - VideoCutTopBottom[resolution] * 2; // FIXME: store different positions for the ratios tmp_ratio.num = 4; tmp_ratio.den = 3; #ifdef DEBUG Debug(4, "ratio: %d:%d %d:%d\n", input_aspect_ratio.num, input_aspect_ratio.den, display_aspect_ratio.num, display_aspect_ratio.den); #endif if (!av_cmp_q(input_aspect_ratio, tmp_ratio)) { switch (Video4to3ZoomMode) { case VideoNormal: goto normal; case VideoStretch: goto stretch; case VideoCenterCutOut: goto center_cut_out; case VideoAnamorphic: // FIXME: rest should be done by hardware goto stretch; } } switch (VideoOtherZoomMode) { case VideoNormal: goto normal; case VideoStretch: goto stretch; case VideoCenterCutOut: goto center_cut_out; case VideoAnamorphic: // FIXME: rest should be done by hardware goto stretch; } normal: *output_x = video_x; *output_y = video_y; *output_width = (video_height * display_aspect_ratio.num + display_aspect_ratio.den -1 ) / display_aspect_ratio.den; *output_height = (video_width * display_aspect_ratio.den + display_aspect_ratio.num -1 ) / display_aspect_ratio.num; // JOJO if (*output_width > video_width) { *output_width = video_width; *output_y += (video_height - *output_height) / 2; } else if (*output_height > video_height) { *output_height = video_height; *output_x += (video_width - *output_width) / 2; } CuvidMessage(2, "video: normal aspect output %dx%d%+d%+d Video %dx%d\n", *output_width, *output_height, *output_x, *output_y,video_width,video_height); return; stretch: *output_x = video_x; *output_y = video_y; *output_width = video_width; *output_height = video_height; Debug(3, "video: stretch output %dx%d%+d%+d\n", *output_width, *output_height, *output_x, *output_y); return; center_cut_out: *output_x = video_x; *output_y = video_y; *output_height = video_height; *output_width = video_width; *crop_width = (video_height * display_aspect_ratio.num + display_aspect_ratio.den - 1) / display_aspect_ratio.den; *crop_height = (video_width * display_aspect_ratio.den + display_aspect_ratio.num - 1) / display_aspect_ratio.num; // look which side must be cut if (*crop_width > video_width) { int tmp; *crop_height = input_height - VideoCutTopBottom[resolution] * 2; // adjust scaling tmp = ((*crop_width - video_width) * input_width) / (2 * video_width); // FIXME: round failure? if (tmp > *crop_x) { *crop_x = tmp; } *crop_width = input_width - *crop_x * 2; } else if (*crop_height > video_height) { int tmp; *crop_width = input_width - VideoCutLeftRight[resolution] * 2; // adjust scaling tmp = ((*crop_height - video_height) * input_height) / (2 * video_height); // FIXME: round failure? if (tmp > *crop_y) { *crop_y = tmp; } *crop_height = input_height - *crop_y * 2; } else { *crop_width = input_width - VideoCutLeftRight[resolution] * 2; *crop_height = input_height - VideoCutTopBottom[resolution] * 2; } Debug(3, "video: aspect crop %dx%d%+d%+d\n", *crop_width, *crop_height, *crop_x, *crop_y); return; } //---------------------------------------------------------------------------- // GLX //---------------------------------------------------------------------------- #ifdef USE_GLX /// /// GLX extension functions ///@{ #ifdef GLX_MESA_swap_control static PFNGLXSWAPINTERVALMESAPROC GlxSwapIntervalMESA; #endif #ifdef GLX_SGI_video_sync static PFNGLXGETVIDEOSYNCSGIPROC GlxGetVideoSyncSGI; #endif #ifdef GLX_SGI_swap_control static PFNGLXSWAPINTERVALSGIPROC GlxSwapIntervalSGI; #endif ///@} /// /// GLX check error. /// static void GlxCheck(void) { GLenum err; if ((err = glGetError()) != GL_NO_ERROR) { Debug(3, "video/glx: error %d '%s'\n", err, gluErrorString(err)); } } /// /// GLX check if a GLX extension is supported. /// /// @param ext extension to query /// @returns true if supported, false otherwise /// static int GlxIsExtensionSupported(const char *ext) { const char *extensions; if ((extensions = glXQueryExtensionsString(XlibDisplay, DefaultScreen(XlibDisplay)))) { const char *s; int l; s = strstr(extensions, ext); l = strlen(ext); return s && (s[l] == ' ' || s[l] == '\0'); } return 0; } /// /// Setup GLX decoder /// /// @param width input video textures width /// @param height input video textures height /// @param[OUT] textures created and prepared textures /// static void GlxSetupDecoder(int width, int height, GLuint * textures) { int i; glEnable(GL_TEXTURE_2D); // create 2d texture glGenTextures(2, textures); GlxCheck(); for (i = 0; i < 2; ++i) { glBindTexture(GL_TEXTURE_2D, textures[i]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); } glDisable(GL_TEXTURE_2D); GlxCheck(); } /// /// Render texture. /// /// @param texture 2d texture /// @param x window x /// @param y window y /// @param width window width /// @param height window height /// static inline void GlxRenderTexture(GLuint texture, int x, int y, int width, int height, int flip) { glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture); // glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // no color glBegin(GL_QUADS); { if (!flip) { glTexCoord2f(1.0f, 1.0f); glVertex2i(x + width, y + height); glTexCoord2f(0.0f, 1.0f); glVertex2i(x, y + height); glTexCoord2f(0.0f, 0.0f); glVertex2i(x, y); glTexCoord2f(1.0f, 0.0f); glVertex2i(x + width, y); } else { glTexCoord2f(1.0f, 1.0f); glVertex2i(x + width, y); glTexCoord2f(0.0f, 1.0f); glVertex2i(x, y); glTexCoord2f(0.0f, 0.0f); glVertex2i(x, y+height); glTexCoord2f(1.0f, 0.0f); glVertex2i(x + width, y+height); } } glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); } /// /// Upload OSD texture. /// /// @param x x coordinate texture /// @param y y coordinate texture /// @param width argb image width /// @param height argb image height /// @param argb argb image /// static void GlxUploadOsdTexture(int x, int y, int width, int height, const uint8_t * argb) { // FIXME: use other / faster uploads // ARB_pixelbuffer_object GL_PIXEL_UNPACK_BUFFER glBindBufferARB() // glMapBuffer() glUnmapBuffer() glEnable(GL_TEXTURE_2D); // upload 2d texture glBindTexture(GL_TEXTURE_2D, OsdGlTextures[OsdIndex]); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_BGRA, GL_UNSIGNED_BYTE, argb); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); } /// /// GLX initialize OSD. /// /// @param width osd width /// @param height osd height /// static void GlxOsdInit(int width, int height) { int i; #ifdef DEBUG if (!GlxEnabled) { Debug(3, "video/glx: %s called without glx enabled\n", __FUNCTION__); OsdGlTextures[0] = 0; return; } #endif Debug(3, "video/glx: osd init context %p <-> %p\n", glXGetCurrentContext(), GlxContext); #ifndef USE_OPENGLOSD // // create a RGBA texture. // glEnable(GL_TEXTURE_2D); // create 2d texture(s) glGenTextures(2, OsdGlTextures); for (i = 0; i < 2; ++i) { glBindTexture(GL_TEXTURE_2D, OsdGlTextures[i]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); } glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); #else OsdGlTextures[0] = 0; #endif } /// /// GLX cleanup osd. /// static void GlxOsdExit(void) { if (OsdGlTextures[0]) { glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext ); glDeleteTextures(2, OsdGlTextures); OsdGlTextures[0] = 0; OsdGlTextures[1] = 0; } } /// /// Upload ARGB image to texture. /// /// @param xi x-coordinate in argb image /// @param yi y-coordinate in argb image /// @paran height height in pixel in argb image /// @paran width width in pixel in argb image /// @param pitch pitch of argb image /// @param argb 32bit ARGB image data /// @param x x-coordinate on screen of argb image /// @param y y-coordinate on screen of argb image /// /// @note looked by caller /// static void GlxOsdDrawARGB(int xi, int yi, int width, int height, int pitch, const uint8_t * argb, int x, int y) { uint8_t *tmp; #ifdef DEBUG uint32_t start; uint32_t end; #endif #ifdef DEBUG if (!GlxEnabled) { Debug(3, "video/glx: %s called without glx enabled\n", __FUNCTION__); return; } start = GetMsTicks(); #endif // set glx context if (!glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext)) { Error(_("video/glx: can't make glx context current\n")); return; } // FIXME: faster way tmp = malloc(width * height * 4); if (tmp) { int i; for (i = 0; i < height; ++i) { memcpy(tmp + i * width * 4, argb + xi * 4 + (i + yi) * pitch, width * 4); } GlxUploadOsdTexture(x, y, width, height, tmp); glXMakeCurrent(XlibDisplay, None, NULL); free(tmp); } #ifdef DEBUG end = GetMsTicks(); Debug(4, "video/glx: osd upload %dx%d%+d%+d %dms %d\n", width, height, x, y, end - start, width * height * 4); #endif } /// /// Clear OSD texture. /// /// @note looked by caller /// static void GlxOsdClear(void) { void *texbuf; #ifdef USE_OPENGLOSD return; #endif #ifdef DEBUG if (!GlxEnabled) { Debug(3, "video/glx: %s called without glx enabled\n", __FUNCTION__); return; } Debug(3, "video/glx: osd context %p <-> %p\n", glXGetCurrentContext(), GlxContext); #endif // FIXME: any opengl function to clear an area? // FIXME: if not; use zero buffer // FIXME: if not; use dirty area // set glx context if (!glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext)) { Error(_("video/glx: can't make glx context current\n")); return; } texbuf = calloc(OsdWidth * OsdHeight, 4); GlxUploadOsdTexture(0, 0, OsdWidth, OsdHeight, texbuf); glXMakeCurrent(XlibDisplay, None, NULL); free(texbuf); } /// /// Setup GLX window. /// /// @param window xcb window id /// @param width window width /// @param height window height /// @param context GLX context /// static void GlxSetupWindow(xcb_window_t window, int width, int height, GLXContext context) { #ifdef DEBUG uint32_t start; uint32_t end; int i; unsigned count; #endif #ifdef PLACEBO return; #endif Debug(3, "video/glx: %s %x %dx%d context:%p", __FUNCTION__, window, width, height, context); // set glx context if (!glXMakeCurrent(XlibDisplay, window, context)) { Fatal(_("video/glx: can't make glx context current\n")); GlxEnabled = 0; return; } Debug(3, "video/glx: ok\n"); #ifdef DEBUG // check if v-sync is working correct end = GetMsTicks(); for (i = 0; i < 10; ++i) { start = end; glClear(GL_COLOR_BUFFER_BIT); glXSwapBuffers(XlibDisplay, window); end = GetMsTicks(); GlxGetVideoSyncSGI(&count); Debug(4, "video/glx: %5d frame rate %dms\n", count, end - start); // nvidia can queue 5 swaps if (i > 5 && (end - start) < 15) { Warning(_("video/glx: no v-sync\n")); } } #endif // viewpoint GlxCheck(); glViewport(0, 0, width, height); glDepthRange(-1.0, 1.0); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glColor3f(1.0f, 1.0f, 1.0f); glClearDepth(1.0); GlxCheck(); if (glewInit()) Fatal(_("glewinit failed\n")); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, width, height, 0.0, -1.0, 1.0); GlxCheck(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glDisable(GL_DEPTH_TEST); // setup 2d drawing glDepthMask(GL_FALSE); glDisable(GL_CULL_FACE); #ifdef USE_DOUBLEBUFFER glDrawBuffer(GL_BACK); #else glDrawBuffer(GL_FRONT); #endif glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); #ifdef DEBUG #ifdef USE_DOUBLEBUFFER glDrawBuffer(GL_FRONT); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glDrawBuffer(GL_BACK); #endif #endif // clear glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // intial background color glClear(GL_COLOR_BUFFER_BIT); #ifdef DEBUG glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // background color #endif GlxCheck(); } /// /// Initialize GLX. /// static void GlxInit(void) { XVisualInfo *vi=NULL; #ifdef PLACEBO return; #endif //The desired 30-bit color visual int attributeList10[] = { GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DOUBLEBUFFER, True, GLX_RED_SIZE, 10, /*10bits for R */ GLX_GREEN_SIZE, 10, /*10bits for G */ GLX_BLUE_SIZE, 10, /*10bits for B */ None }; int attributeList[] = { GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DOUBLEBUFFER, True, GLX_RED_SIZE, 8, /*8 bits for R */ GLX_GREEN_SIZE, 8, /*8 bits for G */ GLX_BLUE_SIZE, 8, /*8 bits for B */ None }; int fbcount; GLXContext context; int major; int minor; int glx_GLX_EXT_swap_control; int glx_GLX_MESA_swap_control; int glx_GLX_SGI_swap_control; int glx_GLX_SGI_video_sync; GLXFBConfig *fbc; int redSize, greenSize, blueSize; if (!glXQueryVersion(XlibDisplay, &major, &minor)) { Error(_("video/glx: no GLX support\n")); GlxEnabled = 0; return; } Info(_("video/glx: glx version %d.%d\n"), major, minor); // // check which extension are supported // glx_GLX_EXT_swap_control = GlxIsExtensionSupported("GLX_EXT_swap_control"); glx_GLX_MESA_swap_control = GlxIsExtensionSupported("GLX_MESA_swap_control"); glx_GLX_SGI_swap_control = GlxIsExtensionSupported("GLX_SGI_swap_control"); glx_GLX_SGI_video_sync = GlxIsExtensionSupported("GLX_SGI_video_sync"); #ifdef GLX_MESA_swap_control if (glx_GLX_MESA_swap_control) { GlxSwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) glXGetProcAddress((const GLubyte *)"glXSwapIntervalMESA"); } Debug(3, "video/glx: GlxSwapIntervalMESA=%p\n", GlxSwapIntervalMESA); #endif #ifdef GLX_SGI_swap_control if (glx_GLX_SGI_swap_control) { GlxSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC) glXGetProcAddress((const GLubyte *)"wglSwapIntervalEXT"); } Debug(3, "video/glx: GlxSwapIntervalSGI=%p\n", GlxSwapIntervalSGI); #endif #ifdef GLX_SGI_video_sync if (glx_GLX_SGI_video_sync) { GlxGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC) glXGetProcAddress((const GLubyte *)"glXGetVideoSyncSGI"); } Debug(3, "video/glx: GlxGetVideoSyncSGI=%p\n", GlxGetVideoSyncSGI); #endif // glXGetVideoSyncSGI glXWaitVideoSyncSGI // create glx context glXMakeCurrent(XlibDisplay, None, NULL); fbc = glXChooseFBConfig(XlibDisplay, DefaultScreen(XlibDisplay),attributeList10,&fbcount); // try 10 Bit if (fbc==NULL) { fbc = glXChooseFBConfig(XlibDisplay, DefaultScreen(XlibDisplay),attributeList,&fbcount); // fall back to 8 Bit if (fbc==NULL) Fatal(_("did not get FBconfig")); } vi = glXGetVisualFromFBConfig(XlibDisplay, fbc[0]); glXGetFBConfigAttrib(XlibDisplay, fbc[0], GLX_RED_SIZE, &redSize); glXGetFBConfigAttrib(XlibDisplay, fbc[0], GLX_GREEN_SIZE, &greenSize); glXGetFBConfigAttrib(XlibDisplay, fbc[0], GLX_BLUE_SIZE, &blueSize); Debug(3,"RGB size %d:%d:%d\n",redSize, greenSize, blueSize); if (!vi) { Fatal(_("video/glx: can't get a RGB visual\n")); GlxEnabled = 0; return; } if (!vi->visual) { Fatal(_("video/glx: no valid visual found\n")); GlxEnabled = 0; return; } if (vi->bits_per_rgb < 8) { Fatal(_("video/glx: need atleast 8-bits per RGB\n")); GlxEnabled = 0; return; } Debug(3, "Chosen visual ID = 0x%x\n", vi->visualid ); context = glXCreateContext(XlibDisplay, vi, NULL, GL_TRUE); if (!context) { Fatal(_("video/glx: can't create glx context\n")); GlxEnabled = 0; return; } GlxSharedContext = context; context = glXCreateContext(XlibDisplay, vi, GlxSharedContext, GL_TRUE); if (!context) { Fatal(_("video/glx: can't create glx context\n")); GlxEnabled = 0; glXDestroyContext(XlibDisplay, GlxSharedContext); GlxSharedContext = 0; return; } GlxContext = context; GlxVisualInfo = vi; Debug(3, "video/glx: visual %#02x depth %u\n", (unsigned)vi->visualid, vi->depth); // // query default v-sync state // if (glx_GLX_EXT_swap_control) { unsigned tmp; tmp = -1; glXQueryDrawable(XlibDisplay, DefaultRootWindow(XlibDisplay), GLX_SWAP_INTERVAL_EXT, &tmp); GlxCheck(); Debug(3, "video/glx: default v-sync is %d\n", tmp); } else { Debug(3, "video/glx: default v-sync is unknown\n"); } // // disable wait on v-sync // // FIXME: sleep before swap / busy waiting hardware // FIXME: 60hz lcd panel // FIXME: config: default, on, off #ifdef GLX_SGI_swap_control if (GlxVSyncEnabled < 0 && GlxSwapIntervalSGI) { if (GlxSwapIntervalSGI(0)) { GlxCheck(); Warning(_("video/glx: can't disable v-sync\n")); } else { Info(_("video/glx: v-sync disabled\n")); } } else #endif #ifdef GLX_MESA_swap_control if (GlxVSyncEnabled < 0 && GlxSwapIntervalMESA) { if (GlxSwapIntervalMESA(0)) { GlxCheck(); Warning(_("video/glx: can't disable v-sync\n")); } else { Info(_("video/glx: v-sync disabled\n")); } } #endif // // enable wait on v-sync // #ifdef GLX_SGI_swap_control if (GlxVSyncEnabled > 0 && GlxSwapIntervalMESA) { if (GlxSwapIntervalMESA(1)) { GlxCheck(); Warning(_("video/glx: can't enable v-sync\n")); } else { Info(_("video/glx: v-sync enabled\n")); } } else #endif #ifdef GLX_MESA_swap_control if (GlxVSyncEnabled > 0 && GlxSwapIntervalSGI) { if (GlxSwapIntervalSGI(1)) { GlxCheck(); Warning(_("video/glx: SGI can't enable v-sync\n")); } else { Info(_("video/glx: SGI v-sync enabled\n")); } } #endif } /// /// Cleanup GLX. /// static void GlxExit(void) { Debug(3, "video/glx: %s\n", __FUNCTION__); #ifdef PLACEBO return; #endif glFinish(); // must destroy glx if (glXGetCurrentContext() == GlxContext) { // if currently used, set to none glXMakeCurrent(XlibDisplay, None, NULL); } if (GlxSharedContext) { glXDestroyContext(XlibDisplay, GlxSharedContext); GlxCheck(); } if (GlxContext) { glXDestroyContext(XlibDisplay, GlxContext); GlxCheck(); } if (GlxThreadContext) { glXDestroyContext(XlibDisplay, GlxThreadContext); GlxCheck(); } // FIXME: must free GlxVisualInfo } #endif //---------------------------------------------------------------------------- // common functions //---------------------------------------------------------------------------- /// /// Calculate resolution group. /// /// @param width video picture raw width /// @param height video picture raw height /// @param interlace flag interlaced video picture /// /// @note interlace isn't used yet and probably wrong set by caller. /// static VideoResolutions VideoResolutionGroup(int width, int height, __attribute__ ((unused)) int interlace) { if (height == 2160) { return VideoResolutionUHD; } if (height <= 576) { return VideoResolution576i; } if (height <= 720) { return VideoResolution720p; } if (height < 1080) { return VideoResolutionFake1080i; } if (width < 1920) { return VideoResolutionFake1080i; } return VideoResolution1080i; } //---------------------------------------------------------------------------- // auto-crop //---------------------------------------------------------------------------- /// /// auto-crop context structure and typedef. /// typedef struct _auto_crop_ctx_ { int X1; ///< detected left border int X2; ///< detected right border int Y1; ///< detected top border int Y2; ///< detected bottom border int Count; ///< counter to delay switch int State; ///< auto-crop state (0, 14, 16) } AutoCropCtx; #ifdef USE_AUTOCROP #define YBLACK 0x20 ///< below is black #define UVBLACK 0x80 ///< around is black #define M64 UINT64_C(0x0101010101010101) ///< 64bit multiplicator /// auto-crop percent of video width to ignore logos static const int AutoCropLogoIgnore = 24; static int AutoCropInterval; ///< auto-crop check interval static int AutoCropDelay; ///< auto-crop switch delay static int AutoCropTolerance; ///< auto-crop tolerance /// /// Detect black line Y. /// /// @param data Y plane pixel data /// @param length number of pixel to check /// @param pitch offset of pixels /// /// @note 8 pixel are checked at once, all values must be 8 aligned /// static int AutoCropIsBlackLineY(const uint8_t * data, int length, int pitch) { int n; int o; uint64_t r; const uint64_t *p; #ifdef DEBUG if ((size_t) data & 0x7 || pitch & 0x7) { abort(); } #endif p = (const uint64_t *)data; n = length; // FIXME: can remove n o = pitch / 8; r = 0UL; while (--n >= 0) { r |= *p; p += o; } // below YBLACK(0x20) is black return !(r & ~((YBLACK - 1) * M64)); } /// /// Auto detect black borders and crop them. /// /// @param autocrop auto-crop variables /// @param width frame width in pixel /// @param height frame height in pixel /// @param data frame planes data (Y, U, V) /// @param pitches frame planes pitches (Y, U, V) /// /// @note FIXME: can reduce the checked range, left, right crop isn't /// used yet. /// /// @note FIXME: only Y is checked, for black. /// static void AutoCropDetect(AutoCropCtx * autocrop, int width, int height, void *data[3], uint32_t pitches[3]) { const void *data_y; unsigned length_y; int x; int y; int x1; int x2; int y1; int y2; int logo_skip; // // ignore top+bottom 6 lines and left+right 8 pixels // #define SKIP_X 8 #define SKIP_Y 6 x1 = width - 1; x2 = 0; y1 = height - 1; y2 = 0; logo_skip = SKIP_X + (((width * AutoCropLogoIgnore) / 100 + 8) / 8) * 8; data_y = data[0]; length_y = pitches[0]; // // search top // for (y = SKIP_Y; y < y1; ++y) { if (!AutoCropIsBlackLineY(data_y + logo_skip + y * length_y, (width - 2 * logo_skip) / 8, 8)) { if (y == SKIP_Y) { y = 0; } y1 = y; break; } } // // search bottom // for (y = height - SKIP_Y - 1; y > y2; --y) { if (!AutoCropIsBlackLineY(data_y + logo_skip + y * length_y, (width - 2 * logo_skip) / 8, 8)) { if (y == height - SKIP_Y - 1) { y = height - 1; } y2 = y; break; } } // // search left // for (x = SKIP_X; x < x1; x += 8) { if (!AutoCropIsBlackLineY(data_y + x + SKIP_Y * length_y, height - 2 * SKIP_Y, length_y)) { if (x == SKIP_X) { x = 0; } x1 = x; break; } } // // search right // for (x = width - SKIP_X - 8; x > x2; x -= 8) { if (!AutoCropIsBlackLineY(data_y + x + SKIP_Y * length_y, height - 2 * SKIP_Y * 8, length_y)) { if (x == width - SKIP_X - 8) { x = width - 1; } x2 = x; break; } } if (0 && (y1 > SKIP_Y || x1 > SKIP_X)) { Debug(3, "video/autocrop: top=%d bottom=%d left=%d right=%d\n", y1, y2, x1, x2); } autocrop->X1 = x1; autocrop->X2 = x2; autocrop->Y1 = y1; autocrop->Y2 = y2; } #endif //---------------------------------------------------------------------------- // CUVID //---------------------------------------------------------------------------- #ifdef USE_CUVID #ifdef PLACEBO struct ext_buf { int fd; CUexternalMemory mem; CUdeviceptr buf; }; #endif /// /// CUVID decoder /// typedef struct _cuvid_decoder_ { xcb_window_t Window; ///< output window int VideoX; ///< video base x coordinate int VideoY; ///< video base y coordinate int VideoWidth; ///< video base width int VideoHeight; ///< video base height int OutputX; ///< real video output x coordinate int OutputY; ///< real video output y coordinate int OutputWidth; ///< real video output width int OutputHeight; ///< real video output height enum AVPixelFormat PixFmt; ///< ffmpeg frame pixfmt enum AVColorSpace ColorSpace; /// ffmpeg ColorSpace enum AVColorTransferCharacteristic trc; // enum AVColorPrimaries color_primaries; int WrongInterlacedWarned; ///< warning about interlace flag issued int Interlaced; ///< ffmpeg interlaced flag int TopFieldFirst; ///< ffmpeg top field displayed first int InputWidth; ///< video input width int InputHeight; ///< video input height AVRational InputAspect; ///< video input aspect ratio VideoResolutions Resolution; ///< resolution group int CropX; ///< video crop x int CropY; ///< video crop y int CropWidth; ///< video crop width int CropHeight; ///< video crop height #ifdef USE_AUTOCROP void *AutoCropBuffer; ///< auto-crop buffer cache unsigned AutoCropBufferSize; ///< auto-crop buffer size AutoCropCtx AutoCrop[1]; ///< auto-crop variables #endif int grabwidth,grabheight,grab; // Grab Data void *grabbase; int SurfacesNeeded; ///< number of surface to request int SurfaceUsedN; ///< number of used video surfaces /// used video surface ids int SurfacesUsed[CODEC_SURFACES_MAX]; int SurfaceFreeN; ///< number of free video surfaces /// free video surface ids int SurfacesFree[CODEC_SURFACES_MAX]; /// video surface ring buffer int SurfacesRb[VIDEO_SURFACES_MAX]; // CUcontext cuda_ctx; // cudaStream_t stream; // make my own cuda stream // CUgraphicsResource cuResource; int SurfaceWrite; ///< write pointer int SurfaceRead; ///< read pointer atomic_t SurfacesFilled; ///< how many of the buffer is used CUarray cu_array[CODEC_SURFACES_MAX+1][2]; CUgraphicsResource cu_res[CODEC_SURFACES_MAX+1][2]; GLuint gl_textures[(CODEC_SURFACES_MAX+1)*2]; // where we will copy the CUDA result CUcontext cuda_ctx; #ifdef PLACEBO struct pl_image pl_images[CODEC_SURFACES_MAX+1]; // images für Placebo chain const struct pl_tex *pl_tex_in[CODEC_SURFACES_MAX+1][2]; // Textures in image const struct pl_buf *pl_buf_Y,*pl_buf_UV; // buffer for Texture upload struct ext_buf ebuf[2]; // for managing vk buffer #endif int SurfaceField; ///< current displayed field int TrickSpeed; ///< current trick speed int TrickCounter; ///< current trick speed counter struct timespec FrameTime; ///< time of last display VideoStream *Stream; ///< video stream int Closing; ///< flag about closing current stream int SyncOnAudio; ///< flag sync to audio int64_t PTS; ///< video PTS clock int LastAVDiff; ///< last audio - video difference int SyncCounter; ///< counter to sync frames int StartCounter; ///< counter for video start int FramesDuped; ///< number of frames duplicated int FramesMissed; ///< number of frames missed int FramesDropped; ///< number of frames dropped int FrameCounter; ///< number of frames decoded int FramesDisplayed; ///< number of frames displayed float Frameproc; /// Time to process frame int newchannel; } CuvidDecoder; static CuvidDecoder *CuvidDecoders[2]; ///< open decoder streams static int CuvidDecoderN; ///< number of decoder streams #ifdef PLACEBO typedef struct priv { const struct pl_gpu *gpu; const struct pl_vulkan *vk; const struct pl_vk_inst *vk_inst; struct pl_context *ctx; struct pl_renderer *renderer; struct pl_renderer *renderertest; const struct pl_swapchain *swapchain; struct pl_context_params context; struct pl_render_target r_target; struct pl_render_params r_params; struct pl_tex final_fbo; VkSurfaceKHR pSurface; }priv; static priv *p; static struct pl_overlay osdoverlay; #endif GLuint vao_buffer; // //GLuint vao_vao[4]; // GLuint gl_shader=0,gl_prog = 0,gl_fbo=0; // shader programm GLint gl_colormatrix,gl_colormatrix_c; GLuint OSDfb=0; GLuint OSDtexture; int OSDx,OSDy,OSDxsize,OSDysize; static struct timespec CuvidFrameTime; ///< time of last display #ifdef USE_BITMAP /// bitmap surfaces for osd static VdpBitmapSurface CuvidOsdBitmapSurface[2] = { VDP_INVALID_HANDLE, VDP_INVALID_HANDLE }; #else #if 0 /// output surfaces for osd static VdpOutputSurface CuvidOsdOutputSurface[2] = { VDP_INVALID_HANDLE, VDP_INVALID_HANDLE }; #endif #endif static int CuvidOsdSurfaceIndex; ///< index into double buffered osd /// grab render output surface //static VdpOutputSurface CuvidGrabRenderSurface = VDP_INVALID_HANDLE; static pthread_mutex_t CuvidGrabMutex; unsigned int size_tex_data; unsigned int num_texels; unsigned int num_values; int window_width,window_height; #include "shaders.h" //---------------------------------------------------------------------------- /// /// Output video messages. /// /// Reduce output. /// /// @param level message level (Error, Warning, Info, Debug, ...) /// @param format printf format string (NULL to flush messages) /// @param ... printf arguments /// /// @returns true, if message shown /// int CuvidMessage(int level, const char *format, ...) { if (SysLogLevel > level || DebugLevel > level) { static const char *last_format; static char buf[256]; va_list ap; va_start(ap, format); if (format != last_format) { // don't repeat same message if (buf[0]) { // print last repeated message syslog(LOG_ERR, "%s", buf); buf[0] = '\0'; } if (format) { last_format = format; vsyslog(LOG_ERR, format, ap); } va_end(ap); return 1; } vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); } return 0; } //////////////////////////////////////////////////////////////////////////////// // These are CUDA Helper functions // This will output the proper CUDA error strings in the event that a CUDA host call returns an error #define checkCudaErrors(err) __checkCudaErrors (err, __FILE__, __LINE__) // These are the inline versions for all of the SDK helper functions inline void __checkCudaErrors(CUresult err, const char *file, const int line) { if (CUDA_SUCCESS != err) { CuvidMessage( 2,"checkCudaErrors() Driver API error = %04d \"%s\" from file <%s>, line %i.\n", err, getCudaDrvErrorString(err), file, line); exit(EXIT_FAILURE); } } // Surfaces ------------------------------------------------------------- void createTextureDst(CuvidDecoder * decoder,int anz, unsigned int size_x, unsigned int size_y, enum AVPixelFormat PixFmt); /// /// Create surfaces for CUVID decoder. /// /// @param decoder CUVID hw decoder /// @param width surface source/video width /// @param height surface source/video height /// static void CuvidCreateSurfaces(CuvidDecoder * decoder, int width, int height,enum AVPixelFormat PixFmt ) { int i; #ifdef DEBUG if (!decoder->SurfacesNeeded) { Error(_("video/cuvid: surface needed not set\n")); decoder->SurfacesNeeded = VIDEO_SURFACES_MAX; } #endif Debug(3, "video/cuvid: %s: %dx%d * %d \n", __FUNCTION__, width, height, decoder->SurfacesNeeded); // allocate only the number of needed surfaces decoder->SurfaceFreeN = decoder->SurfacesNeeded; createTextureDst(decoder,decoder->SurfacesNeeded,width,height,PixFmt); for (i = 0; i < decoder->SurfaceFreeN; ++i) { decoder->SurfacesFree[i] = i; } Debug(4, "video/cuvid: created video surface %dx%d with id %d\n",width, height, decoder->SurfacesFree[i]); } /// /// Destroy surfaces of CUVID decoder. /// /// @param decoder CUVID hw decoder /// static void CuvidDestroySurfaces(CuvidDecoder * decoder) { int i,j; CUcontext dummy; CUdeviceptr d; Debug(3, "video/cuvid: %s\n", __FUNCTION__); #ifndef PLACEBO glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext); GlxCheck(); #endif for (i=0;iSurfacesNeeded;i++) { for (j=0;j<2;j++) { #ifdef PLACEBO pl_tex_destroy(p->gpu,&decoder->pl_tex_in[i][j]); #else checkCudaErrors(cuGraphicsUnregisterResource(decoder->cu_res[i][j])); #endif } } #ifdef PLACEBO // Never ever close the FD this will corrupt cuda // if (decoder->pl_buf_Y->handles.fd > 0) // close(decoder->pl_buf_Y->handles.fd); // if (decoder->pl_buf_UV->handles.fd > 0) // close(decoder->pl_buf_UV->handles.fd); pl_buf_destroy(p->gpu,&decoder->pl_buf_Y); pl_buf_destroy(p->gpu,&decoder->pl_buf_UV); pl_renderer_destroy(&p->renderer); p->renderer = pl_renderer_create(p->ctx, p->gpu); #else glDeleteTextures(CODEC_SURFACES_MAX*2,(GLuint*)&decoder->gl_textures); GlxCheck(); if (decoder == CuvidDecoders[0]) { // only wenn last decoder closes Debug(3,"Last decoder closes\n"); glDeleteBuffers(1,(GLuint *)&vao_buffer); if (gl_prog) glDeleteProgram(gl_prog); gl_prog = 0; } #endif for (i = 0; i < decoder->SurfaceFreeN; ++i) { decoder->SurfacesFree[i] = -1; } for (i = 0; i < decoder->SurfaceUsedN; ++i) { decoder->SurfacesUsed[i] = -1; } decoder->SurfaceFreeN = 0; decoder->SurfaceUsedN = 0; } /// /// Get a free surface. /// /// @param decoder CUVID hw decoder /// /// @returns the oldest free surface /// static int CuvidGetVideoSurface0(CuvidDecoder * decoder) { int surface; int i; if (!decoder->SurfaceFreeN) { // Error(_("video/cuvid: out of surfaces\n")); return -1; } // use oldest surface surface = decoder->SurfacesFree[0]; decoder->SurfaceFreeN--; for (i = 0; i < decoder->SurfaceFreeN; ++i) { decoder->SurfacesFree[i] = decoder->SurfacesFree[i + 1]; } decoder->SurfacesFree[i] = -1; // save as used decoder->SurfacesUsed[decoder->SurfaceUsedN++] = surface; return surface; } /// /// Release a surface. /// /// @param decoder CUVID hw decoder /// @param surface surface no longer used /// static void CuvidReleaseSurface(CuvidDecoder * decoder, int surface) { int i; for (i = 0; i < decoder->SurfaceUsedN; ++i) { if (decoder->SurfacesUsed[i] == surface) { // no problem, with last used decoder->SurfacesUsed[i] = decoder->SurfacesUsed[--decoder->SurfaceUsedN]; decoder->SurfacesFree[decoder->SurfaceFreeN++] = surface; return; } } Fatal(_("video/cuvid: release surface %#08x, which is not in use\n"), surface); } /// /// Debug CUVID decoder frames drop... /// /// @param decoder CUVID hw decoder /// static void CuvidPrintFrames(const CuvidDecoder * decoder) { Debug(3, "video/cuvid: %d missed, %d duped, %d dropped frames of %d,%d\n", decoder->FramesMissed, decoder->FramesDuped, decoder->FramesDropped, decoder->FrameCounter, decoder->FramesDisplayed); #ifndef DEBUG (void)decoder; #endif } int CuvidTestSurfaces() { if (CuvidDecoders[0] != NULL) { if (atomic_read(&CuvidDecoders[0]->SurfacesFilled) < VIDEO_SURFACES_MAX) return 1; return 0; } else return 0; } /// /// Allocate new CUVID decoder. /// /// @param stream video stream /// /// @returns a new prepared cuvid hardware decoder. /// static CuvidDecoder *CuvidNewHwDecoder(VideoStream * stream) { CuvidDecoder *decoder; int i=0; setenv ("DISPLAY", ":0", 0); Debug(3,"Cuvid New HW Decoder\n"); if ((unsigned)CuvidDecoderN >= sizeof(CuvidDecoders) / sizeof(*CuvidDecoders)) { Error(_("video/cuvid: out of decoders\n")); return NULL; } if (i = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, X11DisplayName, NULL, 0)) { Fatal("codec: can't allocate HW video codec context err %04x",i); } HwDeviceContext = av_buffer_ref(hw_device_ctx); if (!(decoder = calloc(1, sizeof(*decoder)))) { Error(_("video/cuvid: out of memory\n")); return NULL; } decoder->Window = VideoWindow; //decoder->VideoX = 0; // done by calloc //decoder->VideoY = 0; decoder->VideoWidth = VideoWindowWidth; decoder->VideoHeight = VideoWindowHeight; for (i = 0; i < CODEC_SURFACES_MAX; ++i) { decoder->SurfacesUsed[i] = -1; decoder->SurfacesFree[i] = -1; } // // setup video surface ring buffer // atomic_set(&decoder->SurfacesFilled, 0); for (i = 0; i < VIDEO_SURFACES_MAX; ++i) { decoder->SurfacesRb[i] = -1; } decoder->OutputWidth = VideoWindowWidth; decoder->OutputHeight = VideoWindowHeight; decoder->PixFmt = AV_PIX_FMT_NONE; #ifdef USE_AUTOCROP //decoder->AutoCropBuffer = NULL; // done by calloc //decoder->AutoCropBufferSize = 0; #endif decoder->Stream = stream; if (!CuvidDecoderN) { // FIXME: hack sync on audio decoder->SyncOnAudio = 1; } decoder->Closing = -300 - 1; decoder->PTS = AV_NOPTS_VALUE; CuvidDecoders[CuvidDecoderN++] = decoder; return decoder; } /// /// Cleanup CUVID. /// /// @param decoder CUVID hw decoder /// static void CuvidCleanup(CuvidDecoder * decoder) { int i,n=0; Debug(3,"Cuvid Clean up\n"); if (decoder->SurfaceFreeN || decoder->SurfaceUsedN) { CuvidDestroySurfaces(decoder); } // // reset video surface ring buffer // atomic_set(&decoder->SurfacesFilled, 0); for (i = 0; i < VIDEO_SURFACES_MAX; ++i) { decoder->SurfacesRb[i] = -1; } decoder->SurfaceRead = 0; decoder->SurfaceWrite = 0; decoder->SurfaceField = 0; decoder->SyncCounter = 0; decoder->FrameCounter = 0; decoder->FramesDisplayed = 0; decoder->StartCounter = 0; decoder->Closing = 0; decoder->PTS = AV_NOPTS_VALUE; VideoDeltaPTS = 0; } /// /// Destroy a CUVID decoder. /// /// @param decoder CUVID hw decoder /// static void CuvidDelHwDecoder(CuvidDecoder * decoder) { int i,n; Debug(3,"cuvid del hw decoder \n"); if (decoder == CuvidDecoders[0]) pthread_mutex_lock(&VideoLockMutex); #ifndef PLACEBO glXMakeCurrent(XlibDisplay, VideoWindow, GlxSharedContext); GlxCheck(); #endif if (decoder->SurfaceFreeN || decoder->SurfaceUsedN) { CuvidDestroySurfaces(decoder); } if (decoder == CuvidDecoders[0]) pthread_mutex_unlock(&VideoLockMutex); // glXMakeCurrent(XlibDisplay, None, NULL); for (i = 0; i < CuvidDecoderN; ++i) { if (CuvidDecoders[i] == decoder) { CuvidDecoders[i] = NULL; // copy last slot into empty slot if (i < --CuvidDecoderN) { CuvidDecoders[i] = CuvidDecoders[CuvidDecoderN]; } // CuvidCleanup(decoder); CuvidPrintFrames(decoder); #ifdef USE_AUTOCROP free(decoder->AutoCropBuffer); #endif free(decoder); return; } } Error(_("video/cuvid: decoder not in decoder list.\n")); } static int CuvidGlxInit(const char *display_name) { #ifndef PLACEBO GlxEnabled = 1; GlxInit(); if (GlxEnabled) { GlxSetupWindow(VideoWindow, VideoWindowWidth, VideoWindowHeight, GlxContext); } if (!GlxEnabled) { Error(_("video/glx: glx error\n")); } #else GlxEnabled = 0; #endif return 1; } /// /// CUVID cleanup. /// static void CuvidExit(void) { int i; for (i = 0; i < CuvidDecoderN; ++i) { if (CuvidDecoders[i]) { CuvidDelHwDecoder(CuvidDecoders[i]); CuvidDecoders[i] = NULL; } } CuvidDecoderN = 0; Debug(3,"CuvidExit\n"); pthread_mutex_destroy(&CuvidGrabMutex); } /// /// Update output for new size or aspect ratio. /// /// @param decoder CUVID hw decoder /// static void CuvidUpdateOutput(CuvidDecoder * decoder) { VideoUpdateOutput(decoder->InputAspect, decoder->InputWidth, decoder->InputHeight, decoder->Resolution, decoder->VideoX, decoder->VideoY, decoder->VideoWidth, decoder->VideoHeight, &decoder->OutputX, &decoder->OutputY, &decoder->OutputWidth, &decoder->OutputHeight, &decoder->CropX, &decoder->CropY, &decoder->CropWidth, &decoder->CropHeight); #ifdef USE_AUTOCROP decoder->AutoCrop->State = 0; decoder->AutoCrop->Count = AutoCropDelay; #endif } void SDK_CHECK_ERROR_GL() { GLenum gl_error = glGetError(); if (gl_error != GL_NO_ERROR) { Fatal(_("video/cuvid: SDL error %d: %d\n"),gl_error); } } #ifdef PLACEBO void createTextureDst(CuvidDecoder * decoder,int anz, unsigned int size_x, unsigned int size_y, enum AVPixelFormat PixFmt) { int n,i,size=1; const struct pl_fmt *fmt; struct pl_tex *tex; struct pl_image *img; struct pl_plane *pl; //printf("Create textures and planes %d %d\n",size_x,size_y); Debug(3,"video/vulkan: create %d Textures Format %s w %d h %d \n",anz,PixFmt==AV_PIX_FMT_NV12?"NV12":"P010",size_x,size_y); for (i=0;igpu, n==0?"r8":"rg8"); // 8 Bit YUV size = 1; } else { fmt = pl_find_named_fmt(p->gpu, n==0?"r16":"rg16"); // 10 Bit YUV size = 2; } decoder->pl_tex_in[i][n] = pl_tex_create(p->gpu, &(struct pl_tex_params) { .w = n==0?size_x:size_x/2, .h = n==0?size_y:size_y/2, .d = 0, .format = fmt, .sampleable = true, .host_writable = true, .sample_mode = PL_TEX_SAMPLE_LINEAR, .address_mode = PL_TEX_ADDRESS_CLAMP, }); // make planes for image pl = &decoder->pl_images[i].planes[n]; pl->texture = decoder->pl_tex_in[i][n]; pl->components = n==0?1:2; pl->shift_x = 0.0f; pl->shift_y = 0.0f; if (n==0) { pl->component_mapping[0] = PL_CHANNEL_Y; pl->component_mapping[1] = -1; pl->component_mapping[2] = -1; pl->component_mapping[3] = -1; } else { pl->component_mapping[0] = PL_CHANNEL_U; pl->component_mapping[1] = PL_CHANNEL_V; pl->component_mapping[2] = -1; pl->component_mapping[3] = -1; } if (!ok) { Fatal(_("Unable to create placebo textures")); } } // make image img = &decoder->pl_images[i]; img->signature = i; img->num_planes = 2; img->repr.sys = PL_COLOR_SYSTEM_BT_709; // overwritten later img->repr.levels = PL_COLOR_LEVELS_TV; img->repr.alpha = PL_ALPHA_UNKNOWN; img->color.primaries = pl_color_primaries_guess(size_x,size_y); // Gammut overwritten later img->color.transfer = PL_COLOR_TRC_BT_1886; // overwritten later img->color.light = PL_COLOR_LIGHT_SCENE_709_1886; // needs config ??? img->color.sig_peak = 0.0f; // needs config ???? img->color.sig_avg = 0.0f; img->width = size_x; img->height = size_y; img->num_overlays = 0; } decoder->pl_buf_Y = pl_buf_create(p->gpu, &(struct pl_buf_params) { // buffer für Y texture upload .type = PL_BUF_TEX_TRANSFER, .size = size_x * size_y * size, .host_mapped = false, .host_writable = false, .memory_type = PL_BUF_MEM_DEVICE, .handle_type = PL_HANDLE_FD, }); decoder->ebuf[0].fd = dup(decoder->pl_buf_Y->shared_mem.handle.fd); // dup fd // printf("Y Offset %d Size %d FD %d\n",decoder->pl_buf_Y->handle_offset,decoder->pl_buf_Y->handles.size,decoder->pl_buf_Y->handles.fd); decoder->pl_buf_UV = pl_buf_create(p->gpu, &(struct pl_buf_params) { // buffer für UV texture upload .type = PL_BUF_TEX_TRANSFER, .size = size_x * size_y * size / 2, .host_mapped = false, .host_writable = false, .memory_type = PL_BUF_MEM_DEVICE, .handle_type = PL_HANDLE_FD, }); decoder->ebuf[1].fd = dup(decoder->pl_buf_UV->shared_mem.handle.fd); // dup fd // printf("UV Offset %d Size %d FD %d\n",decoder->pl_buf_UV->handle_offset,decoder->pl_buf_UV->handles.size,decoder->pl_buf_UV->handles.fd); CUDA_EXTERNAL_MEMORY_HANDLE_DESC ext_desc = { .type = CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD, .handle.fd = decoder->ebuf[0].fd, .size = decoder->pl_buf_Y->shared_mem.size, // image_width * image_height * bytes, .flags = 0, }; checkCudaErrors(cuImportExternalMemory(&decoder->ebuf[0].mem, &ext_desc)); // Import Memory segment CUDA_EXTERNAL_MEMORY_BUFFER_DESC buf_desc = { .offset = decoder->pl_buf_Y->shared_mem.offset, .size = size_x * size_y * size, .flags = 0, }; checkCudaErrors(cuExternalMemoryGetMappedBuffer(&decoder->ebuf[0].buf, decoder->ebuf[0].mem, &buf_desc)); // get Pointer CUDA_EXTERNAL_MEMORY_HANDLE_DESC ext_desc1 = { .type = CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD, .handle.fd = decoder->ebuf[1].fd, .size = decoder->pl_buf_UV->shared_mem.size, // image_width * image_height * bytes / 2, .flags = 0, }; checkCudaErrors(cuImportExternalMemory(&decoder->ebuf[1].mem, &ext_desc1)); // Import Memory Segment CUDA_EXTERNAL_MEMORY_BUFFER_DESC buf_desc1 = { .offset = decoder->pl_buf_UV->shared_mem.offset, .size = size_x * size_y * size / 2, .flags = 0, }; checkCudaErrors(cuExternalMemoryGetMappedBuffer(&decoder->ebuf[1].buf, decoder->ebuf[1].mem, &buf_desc1)); // get pointer //printf("generate textures %d %d\n",size_x,size_y); } // copy image and process using CUDA void generateCUDAImage(CuvidDecoder * decoder,int index, const AVFrame *frame,int image_width , int image_height, int bytes) { int n; struct ext_buf ebuf[2]; //printf("Upload buf to texture for frame %d in size %d-%d\n",index,image_width,image_height); if (decoder->pl_buf_Y) while (pl_buf_poll(p->gpu,decoder->pl_buf_Y, 5000000)); // 5 ms else return; if (decoder->pl_buf_UV) while (pl_buf_poll(p->gpu,decoder->pl_buf_UV, 5000000)); else return; for (n = 0; n < 2; n++) { // Copy 2 Planes from Cuda decoder to upload Buffer // widthInBytes must account for the chroma plane // elements being two samples wide. CUDA_MEMCPY2D cpy = { .srcMemoryType = CU_MEMORYTYPE_DEVICE, .srcDevice = (CUdeviceptr)frame->data[n], .srcPitch = frame->linesize[n], .srcY = 0, .WidthInBytes = image_width * bytes, .Height = n==0?image_height:image_height/2 , .dstMemoryType = CU_MEMORYTYPE_DEVICE, .dstDevice = decoder->ebuf[n].buf, .dstPitch = image_width * bytes, }; checkCudaErrors(cuMemcpy2D(&cpy)); } pl_tex_upload(p->gpu,&(struct pl_tex_transfer_params) { // upload Y .tex = decoder->pl_tex_in[index][0], .buf = decoder->pl_buf_Y, }); pl_tex_upload(p->gpu,&(struct pl_tex_transfer_params) { // upload UV .tex = decoder->pl_tex_in[index][1], .buf = decoder->pl_buf_UV, }); pl_buf_export(p->gpu,decoder->pl_buf_Y); pl_buf_export(p->gpu,decoder->pl_buf_UV); // pl_gpu_finish(p->gpu); } #else void createTextureDst(CuvidDecoder * decoder,int anz, unsigned int size_x, unsigned int size_y, enum AVPixelFormat PixFmt) { int n,i,size; CUcontext dummy; glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext); GlxCheck(); glGenBuffers(1,&vao_buffer); GlxCheck(); // create texture planes glGenTextures(CODEC_SURFACES_MAX*2, decoder->gl_textures); GlxCheck(); Debug(3,"video/vdpau: create %d Textures Format %s w %d h %d \n",anz,PixFmt==AV_PIX_FMT_NV12?"NV12":"P010",size_x,size_y); for (i=0;igl_textures[i*2+n]); GlxCheck(); // set basic parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); if (PixFmt == AV_PIX_FMT_NV12) glTexImage2D(GL_TEXTURE_2D, 0,n==0?GL_R8 :GL_RG8 ,n==0?size_x:size_x/2, n==0?size_y:size_y/2, 0, n==0?GL_RED:GL_RG , GL_UNSIGNED_BYTE , NULL); else glTexImage2D(GL_TEXTURE_2D, 0,n==0?GL_R16:GL_RG16 ,n==0?size_x:size_x/2, n==0?size_y:size_y/2, 0, n==0?GL_RED:GL_RG , GL_UNSIGNED_SHORT, NULL); SDK_CHECK_ERROR_GL(); // register this texture with CUDA checkCudaErrors(cuGraphicsGLRegisterImage(&decoder->cu_res[i][n], decoder->gl_textures[i*2+n],GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD)); checkCudaErrors(cuGraphicsMapResources(1, &decoder->cu_res[i][n], 0)); checkCudaErrors(cuGraphicsSubResourceGetMappedArray(&decoder->cu_array[i][n], decoder->cu_res[i][n],0, 0)); checkCudaErrors(cuGraphicsUnmapResources(1, &decoder->cu_res[i][n], 0)); } } glBindTexture(GL_TEXTURE_2D, 0); } // copy image and process using CUDA void generateCUDAImage(CuvidDecoder * decoder,int index, const AVFrame *frame,int image_width , int image_height, int bytes) { int n,version; CUcontext dummy=NULL; for (n = 0; n < 2; n++) { // // widthInBytes must account for the chroma plane // elements being two samples wide. CUDA_MEMCPY2D cpy = { .srcMemoryType = CU_MEMORYTYPE_DEVICE, .dstMemoryType = CU_MEMORYTYPE_ARRAY, .srcDevice = (CUdeviceptr)frame->data[n], .srcPitch = frame->linesize[n], .srcY = 0, .dstArray = decoder->cu_array[index][n], .WidthInBytes = image_width * bytes, .Height = n==0?image_height:image_height/2 , }; checkCudaErrors(cuMemcpy2D(&cpy)); } } #endif /// /// Configure CUVID for new video format. /// /// @param decoder CUVID hw decoder /// static void CuvidSetupOutput(CuvidDecoder * decoder) { uint32_t width; uint32_t height; // FIXME: need only to create and destroy surfaces for size changes // or when number of needed surfaces changed! decoder->Resolution = VideoResolutionGroup(decoder->InputWidth, decoder->InputHeight, decoder->Interlaced); CuvidCreateSurfaces(decoder, decoder->InputWidth, decoder->InputHeight,decoder->PixFmt); CuvidUpdateOutput(decoder); // update aspect/scaling window_width = decoder->OutputWidth; window_height = decoder->OutputHeight; } /// /// Get a free surface. Called from ffmpeg. /// /// @param decoder CUVID hw decoder /// @param video_ctx ffmpeg video codec context /// /// @returns the oldest free surface /// static unsigned CuvidGetVideoSurface(CuvidDecoder * decoder, const AVCodecContext * video_ctx) { (void)video_ctx; return CuvidGetVideoSurface0(decoder); } typedef struct CUVIDContext { AVBufferRef *hw_frames_ctx; AVFrame *tmp_frame; } CUVIDContext; static int cuvid_get_buffer(AVCodecContext *s, AVFrame *frame, int flags) { VideoDecoder *ist = s->opaque; CUVIDContext *ctx = ist->hwaccel_ctx; int ret; if (!ctx->hw_frames_ctx) { Debug(3,"CUDA fail get buffer\n"); exit(0); } ret = av_hwframe_get_buffer(ctx->hw_frames_ctx, frame, 0); //ret = avcodec_default_get_buffer2(s, frame, flags); Debug(3,"CUDA hwframe got buffer %d\n",ret); return ret; } /// /// Callback to negotiate the PixelFormat. /// /// @param fmt is the list of formats which are supported by the codec, /// it is terminated by -1 as 0 is a valid format, the /// formats are ordered by quality. /// static enum AVPixelFormat Cuvid_get_format(CuvidDecoder * decoder, AVCodecContext * video_ctx, const enum AVPixelFormat *fmt) { const enum AVPixelFormat *fmt_idx; int bitformat16 = 0; VideoDecoder *ist = video_ctx->opaque; // // look through formats // Debug(3, "%s: codec %d fmts:\n", __FUNCTION__, video_ctx->codec_id); for (fmt_idx = fmt; *fmt_idx != AV_PIX_FMT_NONE; fmt_idx++) { Debug(3, "\t%#010x %s\n", *fmt_idx, av_get_pix_fmt_name(*fmt_idx)); if (*fmt_idx == AV_PIX_FMT_P010LE) bitformat16 = 1; } Debug(3, "%s: codec %d fmts:\n", __FUNCTION__, video_ctx->codec_id); for (fmt_idx = fmt; *fmt_idx != AV_PIX_FMT_NONE; fmt_idx++) { Debug(3, "\t%#010x %s\n", *fmt_idx, av_get_pix_fmt_name(*fmt_idx)); // check supported pixel format with entry point switch (*fmt_idx) { case AV_PIX_FMT_CUDA: break; default: continue; } break; } Debug(3,"video profile %d codec id %d\n",video_ctx->profile,video_ctx->codec_id); if (*fmt_idx == AV_PIX_FMT_NONE) { Error(_("video: no valid pixfmt found\n")); } if (*fmt_idx != AV_PIX_FMT_CUDA) { Fatal(_("video: no valid profile found\n")); } # Debug(3, "video: create decoder 16bit?=%d %dx%d old %d %d\n",bitformat16, video_ctx->width, video_ctx->height,decoder->InputWidth,decoder->InputHeight); if (*fmt_idx == AV_PIX_FMT_CUDA ) { // HWACCEL used // Check image, format, size // if (bitformat16) { decoder->PixFmt = AV_PIX_FMT_YUV420P; // 10 Bit Planar ist->hwaccel_output_format = AV_PIX_FMT_YUV420P; } else { decoder->PixFmt = AV_PIX_FMT_NV12; // 8 Bit Planar ist->hwaccel_output_format = AV_PIX_FMT_NV12; } if ( video_ctx->width != decoder->InputWidth || video_ctx->height != decoder->InputHeight) { CuvidCleanup(decoder); decoder->InputAspect = video_ctx->sample_aspect_ratio; decoder->InputWidth = video_ctx->width; decoder->InputHeight = video_ctx->height; decoder->Interlaced = 0; decoder->SurfacesNeeded = VIDEO_SURFACES_MAX + 1; CuvidSetupOutput(decoder); #ifdef PLACEBO // dont show first frame decoder->newchannel = 1; #endif } CuvidMessage(2,"CUVID Init ok %dx%d\n",video_ctx->width,video_ctx->height); ist->active_hwaccel_id = HWACCEL_CUVID; ist->hwaccel_pix_fmt = AV_PIX_FMT_CUDA; decoder->InputAspect = video_ctx->sample_aspect_ratio; return AV_PIX_FMT_CUDA; } Fatal(_("NO Format valid")); return *fmt_idx; } #ifdef USE_GRAB #ifdef PLACEBO int get_RGB(CuvidDecoder *decoder,struct pl_overlay *ovl) { #else int get_RGB(CuvidDecoder *decoder) { #endif #ifdef PLACEBO struct pl_render_params render_params = pl_render_default_params; struct pl_render_target target = {0}; const struct pl_fmt *fmt; VkImage Image; #endif uint8_t *base; int width; int height; GLuint fb,texture; int current,i; GLint texLoc; base = decoder->grabbase; width = decoder->grabwidth; height = decoder->grabheight; current = decoder->SurfacesRb[decoder->SurfaceRead]; #ifndef PLACEBO glGenTextures(1, &texture); GlxCheck(); glBindTexture(GL_TEXTURE_2D, texture); GlxCheck(); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); GlxCheck(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); GlxCheck(); glGenFramebuffers(1, &fb); glBindFramebuffer(GL_FRAMEBUFFER, fb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { Debug(3,"video/cuvid: grab Framebuffer is not complete!"); return 0; } glViewport(0,0,width, height); if (gl_prog == 0) gl_prog = sc_generate(gl_prog, decoder->ColorSpace); // generate shader programm glUseProgram(gl_prog); texLoc = glGetUniformLocation(gl_prog, "texture0"); glUniform1i(texLoc, 0); texLoc = glGetUniformLocation(gl_prog, "texture1"); glUniform1i(texLoc, 1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,decoder->gl_textures[current*2+0]); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D,decoder->gl_textures[current*2+1]); glBindFramebuffer(GL_FRAMEBUFFER, fb); render_pass_quad(1,0.0,0.0); glUseProgram(0); glActiveTexture(GL_TEXTURE0); if (OsdShown && decoder->grab == 2) { #ifndef USE_OPENGLOSD glXMakeCurrent(XlibDisplay, VideoWindow, GlxThreadContext); GlxRenderTexture(OsdGlTextures[OsdIndex], 0,0, width, height,1); #else pthread_mutex_lock(&OSDMutex); glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext ); glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, width, height, 0.0, -1.0, 1.0); GlxCheck(); GlxRenderTexture(OSDtexture, 0,0, width, height,1); pthread_mutex_unlock(&OSDMutex); #endif // glXMakeCurrent(XlibDisplay, VideoWindow, GlxSharedContext); glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext); } Debug(3,"Read pixels %d %d\n",width,height); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,base); GlxCheck(); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1,&fb); glDeleteTextures(1,&texture); #else fmt = pl_find_named_fmt(p->gpu,"bgra8"); target.fbo = pl_tex_create(p->gpu, &(struct pl_tex_params) { .w = width, .h = height, .d = 0, .format = fmt, .sampleable = true, .renderable = true, .host_readable = true, .sample_mode = PL_TEX_SAMPLE_LINEAR, .address_mode = PL_TEX_ADDRESS_CLAMP, }); target.dst_rect.x0 = 0; target.dst_rect.y0 = 0; target.dst_rect.x1= width; target.dst_rect.y1= height; target.repr.sys = PL_COLOR_SYSTEM_RGB; target.repr.levels = PL_COLOR_LEVELS_PC; target.repr.alpha = PL_ALPHA_UNKNOWN; target.repr.bits.sample_depth = 8; target.repr.bits.color_depth = 8; target.repr.bits.bit_shift =0; target.color.primaries = PL_COLOR_PRIM_BT_709; target.color.transfer = PL_COLOR_TRC_BT_1886; target.color.light = PL_COLOR_LIGHT_DISPLAY; target.color.sig_peak = 0; target.color.sig_avg = 0; if (ovl) { target.overlays = ovl; target.num_overlays = 1; } else { target.overlays = 0; target.num_overlays = 0; } if (!pl_render_image(p->renderer, &decoder->pl_images[current], &target, &render_params)) { Fatal(_("Failed rendering frame!\n")); } pl_gpu_finish(p->gpu); pl_tex_download(p->gpu,&(struct pl_tex_transfer_params) { // download Data .tex = target.fbo, .ptr = base, }); pl_tex_destroy(p->gpu,&target.fbo); #endif } /// /// Grab output surface already locked. /// /// @param ret_size[out] size of allocated surface copy /// @param ret_width[in,out] width of output /// @param ret_height[in,out] height of output /// static uint8_t *CuvidGrabOutputSurfaceLocked(int *ret_size, int *ret_width, int *ret_height, int mitosd) { int surface,i; int rgba_format; uint32_t size; uint32_t width; uint32_t height; uint8_t *base; void *data[1]; uint32_t pitches[1]; VdpRect source_rect; VdpRect output_rect; CuvidDecoder *decoder; decoder = CuvidDecoders[0]; if (decoder == NULL) // no video aktiv return NULL; // surface = CuvidSurfacesRb[CuvidOutputSurfaceIndex]; // get real surface size #ifdef PLACEBO width = decoder->OutputWidth; height = decoder->OutputHeight; #else width = decoder->InputWidth; height = decoder->InputHeight; #endif Debug(3, "video/cuvid: grab %dx%d\n", width, height); source_rect.x0 = 0; source_rect.y0 = 0; source_rect.x1 = width; source_rect.y1 = height; if (ret_width && ret_height) { if (*ret_width <= -64) { // this is an Atmo grab service request int overscan; // calculate aspect correct size of analyze image width = *ret_width * -1; height = (width * source_rect.y1) / source_rect.x1; // calculate size of grab (sub) window overscan = *ret_height; if (overscan > 0 && overscan <= 200) { source_rect.x0 = source_rect.x1 * overscan / 1000; source_rect.x1 -= source_rect.x0; source_rect.y0 = source_rect.y1 * overscan / 1000; source_rect.y1 -= source_rect.y0; } } else { if (*ret_width > 0 && (unsigned)*ret_width < width) { width = *ret_width; } if (*ret_height > 0 && (unsigned)*ret_height < height) { height = *ret_height; } } Debug(3, "video/cuvid: grab source rect %d,%d:%d,%d dest dim %dx%d\n", source_rect.x0, source_rect.y0, source_rect.x1, source_rect.y1, width, height); output_rect.x0 = 0; output_rect.y0 = 0; output_rect.x1 = width; output_rect.y1 = height; size = width * height * sizeof(uint32_t); base = malloc(size); if (!base) { Error(_("video/cuvid: out of memory\n")); return NULL; } decoder->grabbase = base; decoder->grabwidth = width; decoder->grabheight = height; if (mitosd) decoder->grab = 2; else decoder->grab = 1; while(decoder->grab) { usleep(1000); // wait for data } Debug(3,"got grab data\n"); if (ret_size) { *ret_size = size; } if (ret_width) { *ret_width = width; } if (ret_height) { *ret_height = height; } return base; } return NULL; } /// /// Grab output surface. /// /// @param ret_size[out] size of allocated surface copy /// @param ret_width[in,out] width of output /// @param ret_height[in,out] height of output /// static uint8_t *CuvidGrabOutputSurface(int *ret_size, int *ret_width, int *ret_height, int mitosd) { uint8_t *img; // pthread_mutex_lock(&CuvidGrabMutex); // pthread_mutex_lock(&VideoLockMutex); img = CuvidGrabOutputSurfaceLocked(ret_size, ret_width, ret_height, mitosd); // pthread_mutex_unlock(&VideoLockMutex); // pthread_mutex_unlock(&CuvidGrabMutex); return img; } #endif #ifdef USE_AUTOCROP /// /// CUVID auto-crop support. /// /// @param decoder CUVID hw decoder /// static void CuvidAutoCrop(CuvidDecoder * decoder) { int surface; uint32_t size; uint32_t width; uint32_t height; void *base; void *data[3]; uint32_t pitches[3]; int crop14; int crop16; int next_state; int format; surface = decoder->SurfacesRb[(decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX]; // get real surface size (can be different) status = CuvidVideoSurfaceGetParameters(surface, &chroma_type, &width, &height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't get video surface parameters: %s\n"), CuvidGetErrorString(status)); return; } switch (chroma_type) { case VDP_CHROMA_TYPE_420: case VDP_CHROMA_TYPE_422: case VDP_CHROMA_TYPE_444: size = width * height + ((width + 1) / 2) * ((height + 1) / 2) + ((width + 1) / 2) * ((height + 1) / 2); // cache buffer for reuse base = decoder->AutoCropBuffer; if (size > decoder->AutoCropBufferSize) { free(base); decoder->AutoCropBuffer = malloc(size); base = decoder->AutoCropBuffer; } if (!base) { Error(_("video/vdpau: out of memory\n")); return; } pitches[0] = width; pitches[1] = width / 2; pitches[2] = width / 2; data[0] = base; data[1] = base + width * height; data[2] = base + width * height + width * height / 4; format = VDP_YCBCR_FORMAT_YV12; break; default: Error(_("video/vdpau: unsupported chroma type %d\n"), chroma_type); return; } status = CuvidVideoSurfaceGetBitsYCbCr(surface, format, data, pitches); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't get video surface bits: %s\n"), CuvidGetErrorString(status)); return; } AutoCropDetect(decoder->AutoCrop, width, height, data, pitches); // ignore black frames if (decoder->AutoCrop->Y1 >= decoder->AutoCrop->Y2) { return; } crop14 = (decoder->InputWidth * decoder->InputAspect.num * 9) / (decoder->InputAspect.den * 14); crop14 = (decoder->InputHeight - crop14) / 2; crop16 = (decoder->InputWidth * decoder->InputAspect.num * 9) / (decoder->InputAspect.den * 16); crop16 = (decoder->InputHeight - crop16) / 2; if (decoder->AutoCrop->Y1 >= crop16 - AutoCropTolerance && decoder->InputHeight - decoder->AutoCrop->Y2 >= crop16 - AutoCropTolerance) { next_state = 16; } else if (decoder->AutoCrop->Y1 >= crop14 - AutoCropTolerance && decoder->InputHeight - decoder->AutoCrop->Y2 >= crop14 - AutoCropTolerance) { next_state = 14; } else { next_state = 0; } if (decoder->AutoCrop->State == next_state) { return; } Debug(3, "video: crop aspect %d:%d %d/%d %d%+d\n", decoder->InputAspect.num, decoder->InputAspect.den, crop14, crop16, decoder->AutoCrop->Y1, decoder->InputHeight - decoder->AutoCrop->Y2); Debug(3, "video: crop aspect %d -> %d\n", decoder->AutoCrop->State, next_state); switch (decoder->AutoCrop->State) { case 16: case 14: if (decoder->AutoCrop->Count++ < AutoCropDelay / 2) { return; } break; case 0: if (decoder->AutoCrop->Count++ < AutoCropDelay) { return; } break; } decoder->AutoCrop->State = next_state; if (next_state) { decoder->CropX = VideoCutLeftRight[decoder->Resolution]; decoder->CropY = (next_state == 16 ? crop16 : crop14) + VideoCutTopBottom[decoder->Resolution]; decoder->CropWidth = decoder->InputWidth - decoder->CropX * 2; decoder->CropHeight = decoder->InputHeight - decoder->CropY * 2; // FIXME: this overwrites user choosen output position // FIXME: resize kills the auto crop values // FIXME: support other 4:3 zoom modes decoder->OutputX = decoder->VideoX; decoder->OutputY = decoder->VideoY; decoder->OutputWidth = (decoder->VideoHeight * next_state) / 9; decoder->OutputHeight = (decoder->VideoWidth * 9) / next_state; if (decoder->OutputWidth > decoder->VideoWidth) { decoder->OutputWidth = decoder->VideoWidth; decoder->OutputY = (decoder->VideoHeight - decoder->OutputHeight) / 2; } else if (decoder->OutputHeight > decoder->VideoHeight) { decoder->OutputHeight = decoder->VideoHeight; decoder->OutputX = (decoder->VideoWidth - decoder->OutputWidth) / 2; } Debug(3, "video: aspect output %dx%d %dx%d%+d%+d\n", decoder->InputWidth, decoder->InputHeight, decoder->OutputWidth, decoder->OutputHeight, decoder->OutputX, decoder->OutputY); } else { // sets AutoCrop->Count CuvidUpdateOutput(decoder); } decoder->AutoCrop->Count = 0; } /// /// CUVID check if auto-crop todo. /// /// @param decoder CUVID hw decoder /// /// @note a copy of VaapiCheckAutoCrop /// @note auto-crop only supported with normal 4:3 display mode /// static void CuvidCheckAutoCrop(CuvidDecoder * decoder) { // reduce load, check only n frames if (Video4to3ZoomMode == VideoNormal && AutoCropInterval && !(decoder->FrameCounter % AutoCropInterval)) { AVRational input_aspect_ratio; AVRational tmp_ratio; av_reduce(&input_aspect_ratio.num, &input_aspect_ratio.den, decoder->InputWidth * decoder->InputAspect.num, decoder->InputHeight * decoder->InputAspect.den, 1024 * 1024); tmp_ratio.num = 4; tmp_ratio.den = 3; // only 4:3 with 16:9/14:9 inside supported if (!av_cmp_q(input_aspect_ratio, tmp_ratio)) { CuvidAutoCrop(decoder); } else { decoder->AutoCrop->Count = 0; decoder->AutoCrop->State = 0; } } } /// /// CUVID reset auto-crop. /// static void CuvidResetAutoCrop(void) { int i; for (i = 0; i < CuvidDecoderN; ++i) { CuvidDecoders[i]->AutoCrop->State = 0; CuvidDecoders[i]->AutoCrop->Count = 0; } } #endif /// /// Queue output surface. /// /// @param decoder CUVID hw decoder /// @param surface output surface /// @param softdec software decoder /// /// @note we can't mix software and hardware decoder surfaces /// static void CuvidQueueVideoSurface(CuvidDecoder * decoder, int surface, int softdec) { int old; ++decoder->FrameCounter; // can't wait for output queue empty if (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) { Warning(_("video/vdpau: output buffer full, dropping frame (%d/%d)\n"), ++decoder->FramesDropped, decoder->FrameCounter); if (!(decoder->FramesDisplayed % 300)) { CuvidPrintFrames(decoder); } // software surfaces only if (softdec) { CuvidReleaseSurface(decoder, surface); } return; } // // Check and release, old surface // if ((old = decoder->SurfacesRb[decoder->SurfaceWrite])!= -1) { // now we can release the surface, software surfaces only if (softdec) { CuvidReleaseSurface(decoder, old); } } Debug(4, "video/vdpau: yy video surface %#08x@%d ready\n", surface, decoder->SurfaceWrite); decoder->SurfacesRb[decoder->SurfaceWrite] = surface; decoder->SurfaceWrite = (decoder->SurfaceWrite + 1) % VIDEO_SURFACES_MAX; atomic_inc(&decoder->SurfacesFilled); } extern void Nv12ToBgra32(uint8_t *dpNv12, int nNv12Pitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix,cudaStream_t stream); extern void P016ToBgra32(uint8_t *dpNv12, int nNv12Pitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix,cudaStream_t stream); extern void ResizeNv12(unsigned char *dpDstNv12, int nDstPitch, int nDstWidth, int nDstHeight, unsigned char *dpSrcNv12, int nSrcPitch, int nSrcWidth, int nSrcHeight, unsigned char* dpDstNv12UV); extern void ResizeP016(unsigned char *dpDstP016, int nDstPitch, int nDstWidth, int nDstHeight, unsigned char *dpSrcP016, int nSrcPitch, int nSrcWidth, int nSrcHeight, unsigned char* dpDstP016UV); extern void cudaLaunchNV12toARGBDrv(uint32_t *d_srcNV12, size_t nSourcePitch,uint32_t *d_dstARGB, size_t nDestPitch,uint32_t width, uint32_t height,CUstream streamID); void VideoSetAbove(); /// /// Render a ffmpeg frame. /// /// @param decoder CUVID hw decoder /// @param video_ctx ffmpeg video codec context /// @param frame frame to display /// static void CuvidRenderFrame(CuvidDecoder * decoder, const AVCodecContext * video_ctx, const AVFrame * frame) { int surface; VideoDecoder *ist = video_ctx->opaque; enum AVColorSpace color; // update aspect ratio changes #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,60,100) if (decoder->InputWidth && decoder->InputHeight && av_cmp_q(decoder->InputAspect, frame->sample_aspect_ratio)) { Debug(3, "video/vdpau: aspect ratio changed\n"); decoder->InputAspect = frame->sample_aspect_ratio; //printf("new aspect %d:%d\n",frame->sample_aspect_ratio.num,frame->sample_aspect_ratio.den); CuvidUpdateOutput(decoder); } #else if (decoder->InputWidth && decoder->InputHeight && av_cmp_q(decoder->InputAspect, video_ctx->sample_aspect_ratio)) { Debug(3, "video/vdpau: aspect ratio changed\n"); decoder->InputAspect = video_ctx->sample_aspect_ratio; CuvidUpdateOutput(decoder); } #endif decoder->Closing = 0; color = frame->colorspace; if (color == AVCOL_SPC_UNSPECIFIED) // if unknown color = AVCOL_SPC_BT709; #if 0 // // Check image, format, size // if ( // decoder->PixFmt != video_ctx->pix_fmt video_ctx->width != decoder->InputWidth // || decoder->ColorSpace != color || video_ctx->height != decoder->InputHeight) { Debug(3,"fmt %02d:%02d width %d:%d hight %d:%d\n",decoder->ColorSpace,frame->colorspace ,video_ctx->width, decoder->InputWidth,video_ctx->height, decoder->InputHeight); decoder->InputWidth = video_ctx->width; decoder->InputHeight = video_ctx->height; CuvidCleanup(decoder); decoder->SurfacesNeeded = VIDEO_SURFACES_MAX + 1; CuvidSetupOutput(decoder); #ifdef PLACEBO // dont show first frame decoder->newchannel = 1; #endif } #endif // // Copy data from frame to image // if (video_ctx->pix_fmt == AV_PIX_FMT_CUDA) { int w = decoder->InputWidth; int h = decoder->InputHeight; decoder->ColorSpace = color; // save colorspace decoder->trc = frame->color_trc; decoder->color_primaries = frame->color_primaries; surface = CuvidGetVideoSurface0(decoder); if (surface == -1) // no free surfaces return; #if 0 // old copy via host ram { AVFrame *output; int t = decoder->PixFmt==AV_PIX_FMT_NV12?1:2; struct pl_rect3d rc1 = {0,0,0,w,h,0}; output = av_frame_alloc(); av_hwframe_transfer_data(output,frame,0); av_frame_copy_props(output,frame); bool ok = pl_tex_upload(p->gpu,&(struct pl_tex_transfer_params) { .tex = decoder->pl_tex_in[surface][0], .stride_w = output->linesize[0] / t, .ptr = output->data[0], .rc.x1 = w, .rc.y1 = h, .rc.z1 = 0, }); ok &= pl_tex_upload(p->gpu,&(struct pl_tex_transfer_params) { .tex = decoder->pl_tex_in[surface][1], .stride_w = (output->linesize[1] / 2) / t, .ptr = output->data[1], .rc.x1 = w/2, .rc.y1 = h/2, .rc.z1 = 0, }); av_frame_free(&output); } #endif // copy to texture generateCUDAImage(decoder,surface,frame,w,h,decoder->PixFmt==AV_PIX_FMT_NV12?1:2); CuvidQueueVideoSurface(decoder, surface, 1); return; } Fatal(_("video/vdpau: pixel format %d not supported\n"),video_ctx->pix_fmt); } /// /// Get hwaccel context for ffmpeg. /// /// @param decoder CUVID hw decoder /// static void *CuvidGetHwAccelContext(CuvidDecoder * decoder) { int ret,n; unsigned int device_count,version; CUdevice device; Debug(3, "Initializing cuvid hwaccel thread ID:%ld\n",(long int)syscall(186)); //turn NULL; if (decoder->cuda_ctx) { Debug(3,"schon passiert\n"); return NULL; } checkCudaErrors(cuInit(0)); // checkCudaErrors(cuGLGetDevices(&device_count, &device, 1, CU_GL_DEVICE_LIST_ALL)); if (decoder->cuda_ctx) { cuCtxDestroy (decoder->cuda_ctx); decoder->cuda_ctx = NULL; } checkCudaErrors(cuCtxCreate(&decoder->cuda_ctx, (unsigned int) CU_CTX_SCHED_BLOCKING_SYNC, (CUdevice) 0)); if (decoder->cuda_ctx == NULL) Fatal(_("Kein Cuda device gefunden")); cuCtxGetApiVersion(decoder->cuda_ctx,&version); Debug(3, "***********CUDA API Version %d\n",version); return NULL; } /// /// Create and display a black empty surface. /// /// @param decoder CUVID hw decoder /// /// @FIXME: render only video area, not fullscreen! /// decoder->Output.. isn't correct setup for radio stations /// static void CuvidBlackSurface(CuvidDecoder * decoder) { #ifndef PLACEBO VdpRect source_rect; VdpRect output_rect; glClear(GL_COLOR_BUFFER_BIT); #endif return; } /// /// Advance displayed frame of decoder. /// /// @param decoder CUVID hw decoder /// static void CuvidAdvanceDecoderFrame(CuvidDecoder * decoder) { // next surface, if complete frame is displayed (1 -> 0) if (decoder->SurfaceField) { int filled; // FIXME: this should check the caller // check decoder, if new surface is available // need 2 frames for progressive // need 4 frames for interlaced filled = atomic_read(&decoder->SurfacesFilled); if (filled <= 1 + 2 * decoder->Interlaced) { // keep use of last surface ++decoder->FramesDuped; // FIXME: don't warn after stream start, don't warn during pause // printf("video: display buffer empty, duping frame (%d/%d) %d\n", // decoder->FramesDuped, decoder->FrameCounter, // VideoGetBuffers(decoder->Stream)); return; } decoder->SurfaceRead = (decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX; atomic_dec(&decoder->SurfacesFilled); decoder->SurfaceField = !decoder->Interlaced; return; } // next field decoder->SurfaceField = 1; } /// /// Render video surface to output surface. /// /// @param decoder CUVID hw decoder /// @param level video surface level 0 = bottom /// #ifdef PLACEBO static void CuvidMixVideo(CuvidDecoder * decoder, int level,struct pl_render_target *target, struct pl_overlay *ovl ) #else static void CuvidMixVideo(CuvidDecoder * decoder, int level) #endif { #ifdef PLACEBO struct pl_render_params render_params; struct pl_deband_params deband; struct pl_color_adjustment colors; struct pl_cone_params cone; struct pl_tex_vk *vkp; const struct pl_fmt *fmt; VkImage Image; struct pl_image *img; bool ok; #endif static int last; int current; VdpRect video_src_rect; VdpRect dst_rect; VdpRect dst_video_rect; int w = decoder->InputWidth; int h = decoder->InputHeight; int y; float xcropf, ycropf; GLint texLoc; size_t nSize = 0; static uint32_t lasttime = 0; #ifdef USE_AUTOCROP // FIXME: can move to render frame CuvidCheckAutoCrop(decoder); #endif if (level) { dst_rect.x0 = decoder->VideoX; // video window output (clip) dst_rect.y0 = decoder->VideoY; dst_rect.x1 = decoder->VideoX + decoder->VideoWidth; dst_rect.y1 = decoder->VideoY + decoder->VideoHeight; } else { dst_rect.x0 = 0; // complete window (clip) dst_rect.y0 = 0; dst_rect.x1 = VideoWindowWidth; dst_rect.y1 = VideoWindowHeight; } video_src_rect.x0 = decoder->CropX; // video source (crop) video_src_rect.y0 = decoder->CropY; video_src_rect.x1 = decoder->CropX + decoder->CropWidth; video_src_rect.y1 = decoder->CropY + decoder->CropHeight; xcropf = (float) decoder->CropX / (float) decoder->InputWidth; ycropf = (float) decoder->CropY / (float) decoder->InputHeight; dst_video_rect.x0 = decoder->OutputX; // video output (scale) dst_video_rect.y0 = decoder->OutputY; dst_video_rect.x1 = decoder->OutputX + decoder->OutputWidth; dst_video_rect.y1 = decoder->OutputY + decoder->OutputHeight; current = decoder->SurfacesRb[decoder->SurfaceRead]; // Render Progressive frame and simple interlaced #ifndef PLACEBO y = VideoWindowHeight - decoder->OutputY - decoder->OutputHeight; if (y <0 ) y = 0; glViewport(decoder->OutputX, y, decoder->OutputWidth, decoder->OutputHeight); if (gl_prog == 0) gl_prog = sc_generate(gl_prog, decoder->ColorSpace); // generate shader programm glUseProgram(gl_prog); texLoc = glGetUniformLocation(gl_prog, "texture0"); glUniform1i(texLoc, 0); texLoc = glGetUniformLocation(gl_prog, "texture1"); glUniform1i(texLoc, 1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,decoder->gl_textures[current*2+0]); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D,decoder->gl_textures[current*2+1]); render_pass_quad(0, xcropf, ycropf); glUseProgram(0); glActiveTexture(GL_TEXTURE0); #else img = &decoder->pl_images[current]; memcpy(&deband,&pl_deband_default_params,sizeof(deband)); memcpy(&render_params,&pl_render_default_params,sizeof(render_params)); switch (decoder->ColorSpace) { case AVCOL_SPC_RGB: img->repr.sys = PL_COLOR_SYSTEM_BT_601; img->color.primaries = PL_COLOR_PRIM_BT_601_625; img->color.transfer = PL_COLOR_TRC_BT_1886; img->color.light = PL_COLOR_LIGHT_DISPLAY; break; case AVCOL_SPC_BT709: case AVCOL_SPC_UNSPECIFIED: // comes with UHD img->repr.sys = PL_COLOR_SYSTEM_BT_709; memcpy(&img->color,&pl_color_space_bt709,sizeof(struct pl_color_space)); // img->color.primaries = PL_COLOR_PRIM_BT_709; // img->color.transfer = PL_COLOR_TRC_BT_1886; // img->color.light = PL_COLOR_LIGHT_SCENE_709_1886; // img->color.light = PL_COLOR_LIGHT_DISPLAY; break; case AVCOL_SPC_BT2020_NCL: img->repr.sys = PL_COLOR_SYSTEM_BT_2020_NC; memcpy(&img->color,&pl_color_space_bt2020_hlg,sizeof(struct pl_color_space)); deband.grain = 0.0f; // no grain in HDR // img->color.primaries = PL_COLOR_PRIM_BT_2020; // img->color.transfer = PL_COLOR_TRC_HLG; // img->color.light = PL_COLOR_LIGHT_SCENE_HLG; break; default: // fallback img->repr.sys = PL_COLOR_SYSTEM_BT_709; memcpy(&img->color,&pl_color_space_bt709,sizeof(struct pl_color_space)); // img->color.primaries = PL_COLOR_PRIM_BT_709; // img->color.transfer = PL_COLOR_TRC_BT_1886; // img->color.light = PL_COLOR_LIGHT_DISPLAY; break; } // Source crop if (VideoScalerTest) { // right side defnied scaler pl_tex_clear(p->gpu,target->fbo,(float[4]){0}); // clear frame img->src_rect.x0 = video_src_rect.x1/2+1; img->src_rect.y0 = video_src_rect.y0; img->src_rect.x1 = video_src_rect.x1; img->src_rect.y1 = video_src_rect.y1; // Video aspect ratio target->dst_rect.x0 = dst_video_rect.x1/2+dst_video_rect.x0/2+1; target->dst_rect.y0 = dst_video_rect.y0; target->dst_rect.x1 = dst_video_rect.x1; target->dst_rect.y1 = dst_video_rect.y1; } else { img->src_rect.x0 = video_src_rect.x0; img->src_rect.y0 = video_src_rect.y0; img->src_rect.x1 = video_src_rect.x1; img->src_rect.y1 = video_src_rect.y1; // Video aspect ratio target->dst_rect.x0 = dst_video_rect.x0; target->dst_rect.y0 = dst_video_rect.y0; target->dst_rect.x1 = dst_video_rect.x1; target->dst_rect.y1 = dst_video_rect.y1; } if (VideoColorBlindness) { switch(VideoColorBlindness) { case 1: memcpy(&cone,&pl_vision_protanomaly,sizeof(cone)); break; case 2: memcpy(&cone,&pl_vision_deuteranomaly,sizeof(cone)); break; case 3: memcpy(&cone,&pl_vision_tritanomaly,sizeof(cone)); break; case 4: memcpy(&cone,&pl_vision_monochromacy,sizeof(cone)); break; default: memcpy(&cone,&pl_vision_normal,sizeof(cone)); break; } cone.strength = VideoColorBlindnessFaktor; render_params.cone_params = &cone; } else { render_params.cone_params = NULL; } // render_params.upscaler = &pl_filter_ewa_lanczos; render_params.upscaler = pl_named_filters[VideoScaling[decoder->Resolution]].filter; render_params.downscaler = pl_named_filters[VideoScaling[decoder->Resolution]].filter; render_params.color_adjustment = &colors; render_params.deband_params = &deband; colors.brightness = VideoBrightness; colors.contrast = VideoContrast; colors.saturation = VideoSaturation; colors.hue = VideoHue; colors.gamma = VideoGamma; if (ovl) { pl_tex_clear(p->gpu,target->fbo,(float[4]){0}); // clear frame target->overlays = ovl; target->num_overlays = 1; } else { target->overlays = 0; target->num_overlays = 0; } if (decoder->newchannel && current == 0 ) return; decoder->newchannel = 0; if (!pl_render_image(p->renderer, &decoder->pl_images[current], target, &render_params)) { Fatal(_("Failed rendering frame!\n")); } if (VideoScalerTest) { // left side internal scaler (bicubic) // Source crop img->src_rect.x0 = video_src_rect.x0; img->src_rect.y0 = video_src_rect.y0; img->src_rect.x1 = video_src_rect.x1/2; img->src_rect.y1 = video_src_rect.y1; // Video aspect ratio target->dst_rect.x0 = dst_video_rect.x0; target->dst_rect.y0 = dst_video_rect.y0; target->dst_rect.x1 = dst_video_rect.x1/2+dst_video_rect.x0/2; target->dst_rect.y1 = dst_video_rect.y1; render_params.upscaler = pl_named_filters[VideoScalerTest-1].filter; render_params.downscaler = pl_named_filters[VideoScalerTest-1].filter; if (!p->renderertest) p->renderertest = pl_renderer_create(p->ctx, p->gpu); if (!pl_render_image(p->renderertest, &decoder->pl_images[current], target, &render_params)) { Fatal(_("Failed rendering frame!\n")); } } else if (p->renderertest) { pl_renderer_destroy(&p->renderertest); p->renderertest = NULL; } #endif Debug(4, "video/vdpau: yy video surface %p displayed\n", current, decoder->SurfaceRead); } #ifdef PLACEBO void make_osd_overlay(int x, int y, int width, int height) { const struct pl_fmt *fmt; struct pl_overlay *pl; const float black[4] = { 0.0f,0.0f,0.0f,1.0f}; int offset = VideoWindowHeight - (VideoWindowHeight-height-y) - (VideoWindowHeight - y); fmt = pl_find_named_fmt(p->gpu, "rgba8"); // 8 Bit RGB pl = &osdoverlay; if (pl->plane.texture) { pl_tex_clear(p->gpu,pl->plane.texture,(float[4]){0}); pl_tex_destroy(p->gpu,&pl->plane.texture); } // make texture for OSD pl->plane.texture = pl_tex_create(p->gpu, &(struct pl_tex_params) { .w = width, .h = height, .d = 0, .format = fmt, .sampleable = true, .host_writable = true, .blit_dst = true, .sample_mode = PL_TEX_SAMPLE_LINEAR, .address_mode = PL_TEX_ADDRESS_CLAMP, }); // make overlay pl_tex_clear(p->gpu,pl->plane.texture,(float[4]){0}); pl->plane.components = 4; pl->plane.shift_x = 0.0f; pl->plane.shift_y = 0.0f; pl->plane.component_mapping[0] = PL_CHANNEL_B; pl->plane.component_mapping[1] = PL_CHANNEL_G; pl->plane.component_mapping[2] = PL_CHANNEL_R; pl->plane.component_mapping[3] = PL_CHANNEL_A; pl->mode = PL_OVERLAY_NORMAL; pl->repr.sys = PL_COLOR_SYSTEM_RGB; pl->repr.levels = PL_COLOR_LEVELS_PC; pl->repr.alpha = PL_ALPHA_INDEPENDENT; memcpy(&osdoverlay.color,&pl_color_space_srgb,sizeof(struct pl_color_space)); pl->rect.x0 = x; pl->rect.y0 = VideoWindowHeight - y + offset; // Boden von oben pl->rect.x1 = x+width; pl->rect.y1 = VideoWindowHeight- height - y + offset; } #endif /// /// Display a video frame. /// static void CuvidDisplayFrame(void) { uint64_t first_time, diff, akt_time; static uint64_t last_time = 0; int i; static unsigned int Count; int filled; CuvidDecoder *decoder; #ifdef PLACEBO struct pl_swapchain_frame frame; struct pl_render_target target; bool ok; const float black[4] = { 0.0f,0.0f,0.0f,1.0f}; #endif #ifndef PLACEBO glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext); if (CuvidDecoderN) CuvidDecoders[0]->Frameproc = (float)(GetusTicks()-last_time)/1000000.0; // printf("Time used %2.2f\n",CuvidDecoders[0]->Frameproc); #endif #ifndef PLACEBO glXWaitVideoSyncSGI (2, (Count + 1) % 2, &Count); // wait for previous frame to swap last_time = GetusTicks(); glClear(GL_COLOR_BUFFER_BIT); #else pl_swapchain_swap_buffers(p->swapchain); // last_time = GetusTicks(); // printf(" Latency %d Time wait %2.2f\n",pl_swapchain_latency(p->swapchain),(float)(GetusTicks()-last_time)/1000000.0); last_time = GetusTicks(); if (!p->swapchain) return; usleep(1000); if (!pl_swapchain_start_frame(p->swapchain, &frame)) { // get new frame usleep(10); } pl_render_target_from_swapchain(&target, &frame); // make target frame if (VideoSurfaceModesChanged){ pl_renderer_destroy(&p->renderer); p->renderer = pl_renderer_create(p->ctx, p->gpu); if (p->renderertest) { pl_renderer_destroy(&p->renderertest); p->renderertest = NULL; } VideoSurfaceModesChanged = 0; } target.repr.sys = PL_COLOR_SYSTEM_RGB; if (VideoStudioLevels) target.repr.levels = PL_COLOR_LEVELS_PC; else target.repr.levels = PL_COLOR_LEVELS_TV; target.repr.alpha = PL_ALPHA_UNKNOWN; // target.repr.bits.sample_depth = 16; // target.repr.bits.color_depth = 16; // target.repr.bits.bit_shift =0; switch (VulkanTargetColorSpace) { case 0: memcpy(&target.color,&pl_color_space_monitor,sizeof(struct pl_color_space)); break; case 1: memcpy(&target.color,&pl_color_space_srgb,sizeof(struct pl_color_space)); break; case 2: memcpy(&target.color,&pl_color_space_bt709,sizeof(struct pl_color_space)); break; case 3: memcpy(&target.color,&pl_color_space_bt2020_hlg,sizeof(struct pl_color_space)); break; case 4: memcpy(&target.color,&pl_color_space_hdr10,sizeof(struct pl_color_space)); break; default: memcpy(&target.color,&pl_color_space_monitor,sizeof(struct pl_color_space)); break; } #endif // // Render videos into output // /// for (i = 0; i < CuvidDecoderN; ++i) { decoder = CuvidDecoders[i]; decoder->FramesDisplayed++; decoder->StartCounter++; filled = atomic_read(&decoder->SurfacesFilled); // need 1 frame for progressive, 3 frames for interlaced if (filled < 1 + 2 * decoder->Interlaced) { // FIXME: rewrite MixVideo to support less surfaces if ((VideoShowBlackPicture && !decoder->TrickSpeed) || (VideoShowBlackPicture && decoder->Closing < -300)) { CuvidBlackSurface(decoder); CuvidMessage(3, "video/cuvid: black surface displayed\n"); } continue; } #ifdef PLACEBO if (OsdShown == 1) { // New OSD opened // VideoSetAbove(); pthread_mutex_lock(&OSDMutex); make_osd_overlay(OSDx,OSDy,OSDxsize,OSDysize); if (posd) { pl_tex_upload(p->gpu,&(struct pl_tex_transfer_params) { // upload OSD .tex = osdoverlay.plane.texture, .ptr = posd, }); } OsdShown = 2; pthread_mutex_unlock(&OSDMutex); } if (OsdShown == 2) { // VideoSetAbove(); CuvidMixVideo(decoder, i, &target, &osdoverlay); } else { CuvidMixVideo(decoder, i, &target, NULL); } #else CuvidMixVideo(decoder, i); #endif if (i==0 && decoder->grab) { // Grab frame #ifdef PLACEBO if (decoder->grab == 2 && OsdShown == 2) { get_RGB(decoder,&osdoverlay); } else { get_RGB(decoder,NULL); } #else get_RGB(decoder); #endif decoder->grab = 0; } } // #ifndef PLACEBO // add osd to surface // if (OsdShown) { #ifndef USE_OPENGLOSD glXMakeCurrent(XlibDisplay, VideoWindow, GlxThreadContext); GlxRenderTexture(OsdGlTextures[OsdIndex], 0,0, VideoWindowWidth, VideoWindowHeight,0); #else pthread_mutex_lock(&OSDMutex); glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext ); glViewport(0, 0, VideoWindowWidth, VideoWindowHeight); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, VideoWindowWidth, VideoWindowHeight, 0.0, -1.0, 1.0); GlxCheck(); GlxRenderTexture(OSDtexture, 0,0, VideoWindowWidth, VideoWindowHeight,0); pthread_mutex_unlock(&OSDMutex); #endif // glXMakeCurrent(XlibDisplay, VideoWindow, GlxSharedContext); glXMakeCurrent(XlibDisplay, VideoWindow, GlxContext); } #endif #ifdef PLACEBO if (!pl_swapchain_submit_frame(p->swapchain)) Fatal(_("Failed to submit swapchain buffer\n")); if (CuvidDecoderN) CuvidDecoders[0]->Frameproc = (float)(GetusTicks()-last_time)/1000000.0; #else glXGetVideoSyncSGI (&Count); // get current frame glXSwapBuffers(XlibDisplay, VideoWindow); #endif // FIXME: CLOCK_MONOTONIC_RAW clock_gettime(CLOCK_MONOTONIC, &CuvidFrameTime); for (i = 0; i < CuvidDecoderN; ++i) { // remember time of last shown surface CuvidDecoders[i]->FrameTime = CuvidFrameTime; } // xcb_flush(Connection); } /// /// Set CUVID decoder video clock. /// /// @param decoder CUVID hardware decoder /// @param pts audio presentation timestamp /// void CuvidSetClock(CuvidDecoder * decoder, int64_t pts) { decoder->PTS = pts; } /// /// Get CUVID decoder video clock. /// /// @param decoder CUVID hw decoder /// /// FIXME: 20 wrong for 60hz dvb streams /// static int64_t CuvidGetClock(const CuvidDecoder * decoder) { // pts is the timestamp of the latest decoded frame if (decoder->PTS == (int64_t) AV_NOPTS_VALUE) { return AV_NOPTS_VALUE; } // subtract buffered decoded frames if (decoder->Interlaced) { /* Info("video: %s =pts field%d #%d\n", Timestamp2String(decoder->PTS), decoder->SurfaceField, atomic_read(&decoder->SurfacesFilled)); */ // 1 field is future, 2 fields are past, + 2 in driver queue return decoder->PTS - 20 * 90 * (2 * atomic_read(&decoder->SurfacesFilled) - decoder->SurfaceField - 2 + 2); } // + 2 in driver queue return decoder->PTS - 20 * 90 * (atomic_read(&decoder->SurfacesFilled)); // +2 } /// /// Set CUVID decoder closing stream flag. /// /// @param decoder CUVID decoder /// static void CuvidSetClosing(CuvidDecoder * decoder) { decoder->Closing = 1; OsdShown = 0; } /// /// Reset start of frame counter. /// /// @param decoder CUVID decoder /// static void CuvidResetStart(CuvidDecoder * decoder) { decoder->StartCounter = 0; } /// /// Set trick play speed. /// /// @param decoder CUVID decoder /// @param speed trick speed (0 = normal) /// static void CuvidSetTrickSpeed(CuvidDecoder * decoder, int speed) { decoder->TrickSpeed = speed; decoder->TrickCounter = speed; if (speed) { decoder->Closing = 0; } } /// /// Get CUVID decoder statistics. /// /// @param decoder CUVID decoder /// @param[out] missed missed frames /// @param[out] duped duped frames /// @param[out] dropped dropped frames /// @param[out] count number of decoded frames /// void CuvidGetStats(CuvidDecoder * decoder, int *missed, int *duped, int *dropped, int *counter, float *frametime) { *missed = decoder->FramesMissed; *duped = decoder->FramesDuped; *dropped = decoder->FramesDropped; *counter = decoder->FrameCounter; *frametime = decoder->Frameproc; } /// /// Sync decoder output to audio. /// /// trick-speed show frame times /// still-picture show frame until new frame arrives /// 60hz-mode repeat every 5th picture /// video>audio slow down video by duplicating frames /// video