You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
vdr-plugin-softhdcuvid/video.c

7282 lines
227 KiB

///
/// @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 USE_XLIB_XCB ///< use xlib/xcb backend
#define noUSE_SCREENSAVER ///< support disable screensaver
#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 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
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include <sys/time.h>
#include <errno.h> /* ERROR Number Definitions */
#include <fcntl.h> /* File Control Definitions */
#include <sys/ioctl.h> /* ioctl() */
#include <termios.h> /* POSIX Terminal Control Definitions */
#include <unistd.h> /* UNIX Standard Definitions */
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libintl.h>
#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 <pthread.h>
#include <signal.h>
#include <time.h>
#ifndef HAVE_PTHREAD_NAME
/// only available with newer glibc
#define pthread_setname_np(thread, name)
#endif
#endif
#ifdef USE_XLIB_XCB
#include <X11/Xlib-xcb.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <xcb/xcb.h>
#ifdef USE_SCREENSAVER
#include <xcb/dpms.h>
#include <xcb/screensaver.h>
#endif
// #include <xcb/shm.h>
// #include <xcb/xv.h>
// #include <xcb/xcb_image.h>
// #include <xcb/xcb_event.h>
// #include <xcb/xcb_atom.h>
#include <xcb/xcb_icccm.h>
#ifdef XCB_ICCCM_NUM_WM_SIZE_HINTS_ELEMENTS
#include <xcb/xcb_ewmh.h>
#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
#ifndef PLACEBO_GL
#include <GL/glew.h>
#else
#include <epoxy/egl.h>
#endif
// clang-format off
#include <GL/glu.h>
#include <GL/glut.h>
#include <GL/freeglut_ext.h>
// clang-format on
#endif
#include <libavcodec/avcodec.h>
#include <libavutil/hwcontext.h>
#include <libavutil/mastering_display_metadata.h>
#include <libavutil/pixdesc.h>
#ifdef CUVID
// clang-format off
#include <ffnvcodec/dynlink_cuda.h>
#include <ffnvcodec/dynlink_loader.h>
#include <libavutil/hwcontext_cuda.h>
#include "drvapi_error_string.h"
// clang-format on
#define __DEVICE_TYPES_H__
#endif
#ifdef VAAPI
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,74,100)
#include <libavcodec/vaapi.h>
#endif
#include <libdrm/drm_fourcc.h>
#include <va/va_drmcommon.h>
#ifdef RASPI
#include <libavutil/hwcontext_drm.h>
#endif
#include <libavutil/hwcontext_vaapi.h>
#define TO_AVHW_DEVICE_CTX(x) ((AVHWDeviceContext *)x->data)
#define TO_AVHW_FRAMES_CTX(x) ((AVHWFramesContext *)x->data)
#define TO_VAAPI_DEVICE_CTX(x) ((AVVAAPIDeviceContext *)TO_AVHW_DEVICE_CTX(x)->hwctx)
#define TO_VAAPI_FRAMES_CTX(x) ((AVVAAPIFramesContext *)TO_AVHW_FRAMES_CTX(x)->hwctx)
#endif
#include <assert.h>
// #define EGL_EGLEXT_PROTOTYPES
#if !defined PLACEBO_GL
#include <EGL/egl.h>
#include <EGL/eglext.h>
#endif
#ifndef GL_OES_EGL_image
typedef void *GLeglImageOES;
#endif
#ifndef EGL_KHR_image
typedef void *EGLImageKHR;
#endif
#ifdef PLACEBO
#ifdef PLACEBO_GL
#include <libplacebo/opengl.h>
#else
#define VK_USE_PLATFORM_XCB_KHR
#include <libplacebo/vulkan.h>
#endif
#if PL_API_VER >= 113
#include <libplacebo/shaders/lut.h>
#endif
#include <libplacebo/renderer.h>
#endif
#include <libswscale/swscale.h>
#if defined(YADIF) || defined(VAAPI)
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/opt.h>
#endif
// clang-format off
#include "iatomic.h" // portable atomic_t
#include "misc.h"
#include "video.h"
#include "audio.h"
#include "codec.h"
// clang-format on
#if defined(APIVERSNUM) && APIVERSNUM < 20400
#error "VDR 2.4.0 or greater is required!"
#endif
#define HAS_FFMPEG_3_4_API (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 107, 100))
#define HAS_FFMPEG_4_API (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 18, 100))
#if !HAS_FFMPEG_3_4_API
#error "FFmpeg 3.4 or greater is required!"
#endif
//----------------------------------------------------------------------------
// 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_ {
VideoDeinterlaceCuda, ///< Cuda build in deinterlace
VideoDeinterlaceYadif, ///< Yadif 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
VideoNone, ///< no scaling
} 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 *, int *, int *, int *, int *);
void (*const SetBackground)(uint32_t);
void (*const SetVideoMode)(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 12 //
#define VIDEO_SURFACES_MAX 6 ///< video output surfaces for queue
#define NUM_SHADERS 5 // Number of supported user shaders with placebo
#if defined VAAPI && !defined RASPI
#define PIXEL_FORMAT AV_PIX_FMT_VAAPI
#define SWAP_BUFFER_SIZE 3
#endif
#ifdef CUVID
#define PIXEL_FORMAT AV_PIX_FMT_CUDA
#define SWAP_BUFFER_SIZE 3
#endif
#if defined RASPI
#define PIXEL_FORMAT AV_PIX_FMT_MMAL
#define SWAP_BUFFER_SIZE 3
#endif
//----------------------------------------------------------------------------
// Variables
//----------------------------------------------------------------------------
AVBufferRef *HwDeviceContext; ///< ffmpeg HW device context
char VideoIgnoreRepeatPict; ///< disable repeat pict warning
#ifdef RASPI
int Planes = 3;
#else
int Planes = 2;
#endif
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
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];
/// 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;
/// Default Value for DRM Connector
static char *DRMConnector = NULL;
/// Default Value for DRM Refreshrate
static int DRMRefresh = 50;
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 float VideoTemperature = 0.0f;
static int VulkanTargetColorSpace = 0;
static int VideoScalerTest = 0;
static int VideoColorBlindness = 0;
static float VideoColorBlindnessFaktor = 1.0f;
static char *shadersp[NUM_SHADERS];
char MyConfigDir[200];
static int num_shaders = 0;
static int LUTon = -1;
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
static pthread_t VideoDisplayThread; ///< video display thread
// static pthread_cond_t VideoDisplayWakeupCond; ///< wakeup condition variable
// static pthread_mutex_t VideoDisplayMutex; ///< video condition mutex
// static pthread_mutex_t VideoDisplayLockMutex; ///< video lock mutex
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
static void (*VideoEventCallback)(void) = NULL; /// callback function to notify VDR about Video Events
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 unsigned int Count;
static int EglEnabled; ///< use EGL
static int GlxVSyncEnabled = 1; ///< enable/disable v-sync
#ifdef CUVID
static GLXContext glxSharedContext; ///< shared gl context
static GLXContext glxContext; ///< our gl context
static GLXContext glxThreadContext; ///< our gl context for the thread
static XVisualInfo *GlxVisualInfo; ///< our gl visual
static void GlxSetupWindow(xcb_window_t window, int width, int height, GLXContext context);
GLXContext OSDcontext;
#else
static EGLContext eglSharedContext; ///< shared gl context
static EGLContext eglOSDContext = NULL; ///< our gl context for the thread
static EGLContext eglContext; ///< our gl context
static EGLConfig eglConfig;
static EGLDisplay eglDisplay;
static EGLSurface eglSurface, eglOSDSurface;
static EGLint eglAttrs[10];
static int eglVersion = 2;
static EGLImageKHR(EGLAPIENTRY *CreateImageKHR)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *);
static EGLBoolean(EGLAPIENTRY *DestroyImageKHR)(EGLDisplay, EGLImageKHR);
static void(EGLAPIENTRY *EGLImageTargetTexture2DOES)(GLenum, GLeglImageOES);
PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;
PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;
PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR;
PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;
PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID;
static EGLContext eglThreadContext; ///< our gl context for the thread
static void GlxSetupWindow(xcb_window_t window, int width, int height, EGLContext context);
EGLContext OSDcontext;
#endif
static GLuint OsdGlTextures[2]; ///< gl texture for OSD
static int OsdIndex = 0; ///< index into OsdGlTextures
//----------------------------------------------------------------------------
// Common Functions
//----------------------------------------------------------------------------
void VideoThreadLock(void); ///< lock video thread
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
char *eglErrorString(EGLint error) {
switch (error) {
case EGL_SUCCESS:
return "No error";
case EGL_NOT_INITIALIZED:
return "EGL not initialized or failed to initialize";
case EGL_BAD_ACCESS:
return "Resource inaccessible";
case EGL_BAD_ALLOC:
return "Cannot allocate resources";
case EGL_BAD_ATTRIBUTE:
return "Unrecognized attribute or attribute value";
case EGL_BAD_CONTEXT:
return "Invalid EGL context";
case EGL_BAD_CONFIG:
return "Invalid EGL frame buffer configuration";
case EGL_BAD_CURRENT_SURFACE:
return "Current surface is no longer valid";
case EGL_BAD_DISPLAY:
return "Invalid EGL display";
case EGL_BAD_SURFACE:
return "Invalid surface";
case EGL_BAD_MATCH:
return "Inconsistent arguments";
case EGL_BAD_PARAMETER:
return "Invalid argument";
case EGL_BAD_NATIVE_PIXMAP:
return "Invalid native pixmap";
case EGL_BAD_NATIVE_WINDOW:
return "Invalid native window";
case EGL_CONTEXT_LOST:
return "Context lost";
}
return "Unknown error ";
}
///
/// egl check error.
///
#define EglCheck(void) \
{ \
EGLint err; \
\
if ((err = eglGetError()) != EGL_SUCCESS) { \
Debug(3, "video/egl: %s:%d error %d %s\n", __FILE__, __LINE__, err, eglErrorString(err)); \
} \
}
//----------------------------------------------------------------------------
// DRM Helper Functions
//----------------------------------------------------------------------------
#ifdef USE_DRM
#include "drm.c"
#include "hdr.c"
#endif
///
/// 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
Debug(3, "++++++++++++++++++++++++++++++++++++starte audio\n");
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;
// input not initialized yet, return immediately
if (!input_aspect_ratio.num || !input_aspect_ratio.den) {
*output_width = video_width;
*output_height = video_height;
return;
}
#ifdef USE_DRM
get_drm_aspect(&display_aspect_ratio.num, &display_aspect_ratio.den);
#else
display_aspect_ratio.num = VideoScreen->width_in_pixels;
display_aspect_ratio.den = VideoScreen->height_in_pixels;
#endif
av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, display_aspect_ratio.num, display_aspect_ratio.den,
1024 * 1024);
Debug(3, "video: input %dx%d (%d:%d)\n", input_width, input_height, input_aspect_ratio.num,
input_aspect_ratio.den);
Debug(3, "video: display aspect %d:%d Resolution %d\n", display_aspect_ratio.num, display_aspect_ratio.den,
resolution);
Debug(3, "video: video %+d%+d %dx%d\n", video_x, video_y, video_width, video_height);
*crop_x = VideoCutLeftRight[resolution];
*crop_y = VideoCutTopBottom[resolution];
*crop_width = input_width - VideoCutLeftRight[resolution] * 2;
*crop_height = input_height - VideoCutTopBottom[resolution] * 2;
CuvidMessage(2, "video: crop to %+d%+d %dx%d\n", *crop_x, *crop_y, *crop_width, *crop_height);
tmp_ratio.num = 4;
tmp_ratio.den = 3;
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 VideoNone:
goto video_none;
}
}
switch (VideoOtherZoomMode) {
case VideoNormal:
goto normal;
case VideoStretch:
goto stretch;
case VideoCenterCutOut:
goto center_cut_out;
case VideoNone:
goto video_none;
}
normal:
*output_x = video_x;
*output_y = video_y;
*output_height = video_height;
*output_width = (*crop_width * *output_height * input_aspect_ratio.num) / (input_aspect_ratio.den * *crop_height);
if (*output_width > video_width) {
*output_width = video_width;
*output_height =
(*crop_height * *output_width * input_aspect_ratio.den) / (input_aspect_ratio.num * *crop_width);
*output_y += (video_height - *output_height) / 2;
} else if (*output_width < video_width) {
*output_x += (video_width - *output_width) / 2;
}
CuvidMessage(2, "video: normal aspect output %dx%d%+d%+d\n", *output_width, *output_height, *output_x, *output_y);
return;
stretch:
*output_x = video_x;
*output_y = video_y;
*output_width = video_width;
*output_height = video_height;
CuvidMessage(2, "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 = (*crop_width * *output_height * input_aspect_ratio.num) / (input_aspect_ratio.den * *crop_height);
if (*output_width > video_width) {
// fix height cropping
*crop_width = (int)((*crop_width * video_width) / (*output_width * 2.0) + 0.5) * 2;
*crop_x = (input_width - *crop_width) / 2;
*output_width = video_width;
} else if (*output_width < video_width) {
// fix width cropping
*crop_height = (int)((*crop_height * *output_width) / (video_width * 2.0) + 0.5) * 2;
*crop_y = (input_height - *crop_height) / 2;
*output_width = video_width;
}
CuvidMessage(2, "video: aspect crop %dx%d%+d%+d\n", *crop_width, *crop_height, *crop_x, *crop_y);
return;
video_none:
*output_height = *crop_height;
*output_width = (*crop_width * input_aspect_ratio.num) / input_aspect_ratio.den; // normalize pixel aspect ratio
*output_x = video_x + (video_width - *output_width) / 2;
*output_y = video_y + (video_height - *output_height) / 2;
CuvidMessage(2, "video: original aspect output %dx%d%+d%+d\n", *output_width, *output_height, *output_x,
*output_y);
return;
}
static uint64_t test_time = 0;
///
/// Lock video thread.
///
#define VideoThreadLock(void) \
{ \
if (VideoThread) { \
if (pthread_mutex_lock(&VideoLockMutex)) { \
Error(_("video: can't lock thread\n")); \
} \
} \
}
// test_time = GetusTicks();
// printf("Lock start....");
///
/// Unlock video thread.
///
#define VideoThreadUnlock(void) \
{ \
if (VideoThread) { \
if (pthread_mutex_unlock(&VideoLockMutex)) { \
Error(_("video: can't unlock thread\n")); \
} \
} \
}
// printf("Video Locked for %d\n",(GetusTicks()-test_time)/1000);
#ifdef PLACEBO_GL
#define Lock_and_SharedContext \
{ \
VideoThreadLock(); \
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglSharedContext); \
EglCheck(); \
}
#define Unlock_and_NoContext \
{ \
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); \
EglCheck(); \
VideoThreadUnlock(); \
}
#define SharedContext \
{ \
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglSharedContext); \
EglCheck(); \
}
#define NoContext \
{ \
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); \
EglCheck(); \
}
#else
#ifdef PLACEBO
#define Lock_and_SharedContext \
{ VideoThreadLock(); }
#define Unlock_and_NoContext \
{ VideoThreadUnlock(); }
#define SharedContext \
{}
#define NoContext \
{}
#endif
#endif
//----------------------------------------------------------------------------
// 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.
///
#define GlxCheck(void) \
{ \
GLenum err; \
\
if ((err = glGetError()) != GL_NO_ERROR) { \
Debug(3, "video/glx: error %s:%d %d '%s'\n", __FILE__, __LINE__, 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 window.
///
/// @param window xcb window id
/// @param width window width
/// @param height window height
/// @param context GLX context
///
#ifdef CUVID
static void GlxSetupWindow(xcb_window_t window, int width, int height, GLXContext context)
#else
static void GlxSetupWindow(xcb_window_t window, int width, int height, EGLContext context)
#endif
{
uint32_t start;
uint32_t end;
int i;
unsigned count;
#ifdef PLACEBO_
return;
#endif
Debug(3, "video/egl: %s %x %dx%d context: %p", __FUNCTION__, window, width, height, context);
// set gl context
#ifdef CUVID
if (!glXMakeCurrent(XlibDisplay, window, context)) {
Fatal(_("video/egl: GlxSetupWindow can't make egl/glx context current\n"));
EglEnabled = 0;
return;
}
#endif
Debug(3, "video/egl: ok\n");
#ifdef CUVID
// 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"));
}
}
GLenum err = glewInit();
if (err != GLEW_OK) {
Debug(3, "Error: %s\n", glewGetErrorString(err));
}
GlxCheck();
#endif
// viewpoint
glViewport(0, 0, width, height);
GlxCheck();
#ifdef VAAPI
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
#endif
}
///
/// Initialize GLX.
///
#ifdef CUVID
static void EglInit(void) {
XVisualInfo *vi = NULL;
#if defined PLACEBO && !defined PLACEBO_GL
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)) {
Fatal(_("video/glx: no GLX support\n"));
}
Debug(3, "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
// 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);
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"));
}
glxSharedContext = context;
context = glXCreateContext(XlibDisplay, vi, glxSharedContext, GL_TRUE);
if (!context) {
Fatal(_("video/glx: can't create glx context\n"));
}
glxContext = context;
EglEnabled = 1;
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
}
#else // VAAPI
static void EglInit(void) {
int redSize, greenSize, blueSize, alphaSize;
static int glewdone = 0;
#if defined PLACEBO && !defined PLACEBO_GL
return;
#endif
EGLContext context;
// create egl context
// setenv("MESA_GL_VERSION_OVERRIDE", "3.3", 0);
// setenv("V3D_DOUBLE_BUFFER", "1", 0);
make_egl();
if (!glewdone) {
GLenum err = glewInit();
glewdone = 1;
// if (err != GLEW_OK) {
// Debug(3, "Error: %s\n", glewGetErrorString(err));
// }
}
eglGetConfigAttrib(eglDisplay, eglConfig, EGL_BLUE_SIZE, &blueSize);
eglGetConfigAttrib(eglDisplay, eglConfig, EGL_RED_SIZE, &redSize);
eglGetConfigAttrib(eglDisplay, eglConfig, EGL_GREEN_SIZE, &greenSize);
eglGetConfigAttrib(eglDisplay, eglConfig, EGL_ALPHA_SIZE, &alphaSize);
Debug(3, "RGB size %d:%d:%d Alpha %d\n", redSize, greenSize, blueSize, alphaSize);
eglSharedContext = eglContext;
context = eglCreateContext(eglDisplay, eglConfig, eglSharedContext, eglAttrs);
EglCheck();
if (!context) {
Fatal(_("video/egl: can't create egl context\n"));
}
eglContext = context;
}
#endif
///
/// Cleanup GLX.
///
static void EglExit(void) {
Debug(3, "video/egl: %s\n", __FUNCTION__);
#if defined PLACEBO && !defined PLACEBO_GL
return;
#endif
glFinish();
// must destroy contet
#ifdef CUVID
// must destroy glx
// if (glXGetCurrentContext() == glxContext) {
// if currently used, set to none
// glXMakeCurrent(XlibDisplay, None, NULL);
// }
if (OSDcontext) {
glXDestroyContext(XlibDisplay, OSDcontext);
GlxCheck();
OSDcontext = NULL;
}
if (glxContext) {
glXDestroyContext(XlibDisplay, glxContext);
GlxCheck();
glxContext = NULL;
}
if (glxSharedContext) {
glXDestroyContext(XlibDisplay, glxSharedContext);
GlxCheck();
glxSharedContext = NULL;
}
#else
if (eglGetCurrentContext() == eglContext) {
// if currently used, set to none
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
#ifndef USE_DRM
if (eglSharedContext) {
eglDestroyContext(eglDisplay, eglSharedContext);
EglCheck();
}
if (eglContext) {
eglDestroyContext(eglDisplay, eglContext);
EglCheck();
eglContext = NULL;
}
eglTerminate(eglDisplay);
#endif
#ifdef USE_DRM
drm_clean_up();
#endif
#endif
}
#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;
}
//----------------------------------------------------------------------------
// CUVID
//----------------------------------------------------------------------------
#ifdef USE_CUVID
#ifdef PLACEBO
struct ext_buf {
int fd;
#ifdef CUVID
CUexternalMemory mem;
CUmipmappedArray mma;
CUexternalSemaphore ss;
CUexternalSemaphore ws;
const struct pl_sysnc *sysnc;
#endif
};
#endif
#ifdef VAAPI
static VADisplay *VaDisplay; ///< VA-API display
#endif
///
/// CUVID decoder
///
typedef struct _cuvid_decoder_ {
#ifdef VAAPI
VADisplay *VaDisplay; ///< VA-API display
#endif
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
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
AVFrame *frames[CODEC_SURFACES_MAX + 1];
#ifdef CUVID
CUarray cu_array[CODEC_SURFACES_MAX + 1][2];
CUgraphicsResource cu_res[CODEC_SURFACES_MAX + 1][2];
CUcontext cuda_ctx;
#endif
GLuint gl_textures[(CODEC_SURFACES_MAX + 1) * 2]; // where we will copy the CUDA result
#ifdef VAAPI
EGLImageKHR images[(CODEC_SURFACES_MAX + 1) * 2];
int fds[(CODEC_SURFACES_MAX + 1) * 2];
#endif
#ifdef PLACEBO
struct pl_frame pl_frames[CODEC_SURFACES_MAX + 1]; // images for Placebo chain
struct ext_buf ebuf[CODEC_SURFACES_MAX + 1]; // 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
#if defined(YADIF) || defined(VAAPI)
AVFilterContext *buffersink_ctx;
AVFilterContext *buffersrc_ctx;
AVFilterGraph *filter_graph;
#endif
AVBufferRef *cached_hw_frames_ctx;
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 CUVID
static CudaFunctions *cu;
#endif
#ifdef PLACEBO
struct file {
void *data;
size_t size;
};
typedef struct priv {
const struct pl_gpu *gpu;
const struct pl_vulkan *vk;
const struct pl_vk_inst *vk_inst;
struct pl_context *ctx;
#if PL_API_VER >= 113
struct pl_custom_lut *lut;
#endif
struct pl_renderer *renderer;
struct pl_renderer *renderertest;
const struct pl_swapchain *swapchain;
struct pl_context_params context;
// struct pl_frame r_target;
// struct pl_render_params r_params;
// struct pl_tex final_fbo;
#ifndef PLACEBO_GL
VkSurfaceKHR pSurface;
#endif
// VkSemaphore sig_in;
int has_dma_buf;
#ifdef PLACEBO_GL
struct pl_opengl *gl;
#endif
const struct pl_hook *hook[NUM_SHADERS];
int num_shaders;
} priv;
static priv *p;
static struct pl_overlay osdoverlay;
static int semid;
struct itimerval itimer;
#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, gl_prog_osd = 0;
int OSDx, OSDy, OSDxsize, OSDysize;
static struct timespec CuvidFrameTime; ///< time of last display
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
#ifdef CUVID
// 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
static 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);
}
}
#endif
// 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;
Debug(3, "video/cuvid: %s\n", __FUNCTION__);
#ifndef PLACEBO
#ifdef CUVID
glXMakeCurrent(XlibDisplay, VideoWindow, glxSharedContext);
GlxCheck();
#else
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglContext);
EglCheck();
#endif
#endif
#ifdef PLACEBO
pl_gpu_finish(p->gpu);
#if API_VER >= 58
p->num_shaders = 0;
#endif
#endif
for (i = 0; i < decoder->SurfacesNeeded; i++) {
if (decoder->frames[i]) {
av_frame_free(&decoder->frames[i]);
}
for (j = 0; j < Planes; j++) {
#ifdef PLACEBO
if (decoder->pl_frames[i].planes[j].texture) {
#ifdef VAAPI
if (p->has_dma_buf && decoder->pl_frames[i].planes[j].texture->params.shared_mem.handle.fd) {
close(decoder->pl_frames[i].planes[j].texture->params.shared_mem.handle.fd);
}
#endif
SharedContext;
pl_tex_destroy(p->gpu, &decoder->pl_frames[i].planes[j].texture);
NoContext;
}
#else
#ifdef CUVID
checkCudaErrors(cu->cuGraphicsUnregisterResource(decoder->cu_res[i][j]));
#endif
#ifdef PLACEBO
if (p->hasdma_buf) {
#endif
#ifdef VAAPI
if (decoder->images[i * Planes + j]) {
DestroyImageKHR(eglGetCurrentDisplay(), decoder->images[i * Planes + j]);
if (decoder->fds[i * Planes + j])
close(decoder->fds[i * Planes + j]);
}
decoder->fds[i * Planes + j] = 0;
decoder->images[i * Planes + j] = 0;
#endif
#ifdef PLACEBO
}
#endif
#endif
}
}
#ifdef PLACEBO
// 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 (CuvidDecoderN == 1) { // 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;
if (decoder->frames[surface]) {
av_frame_free(&decoder->frames[surface]);
}
#ifdef PLACEBO
SharedContext;
if (p->has_dma_buf) {
if (decoder->pl_frames[surface].planes[0].texture) {
if (decoder->pl_frames[surface].planes[0].texture->params.shared_mem.handle.fd) {
close(decoder->pl_frames[surface].planes[0].texture->params.shared_mem.handle.fd);
}
pl_tex_destroy(p->gpu, &decoder->pl_frames[surface].planes[0].texture);
}
if (decoder->pl_frames[surface].planes[1].texture) {
if (decoder->pl_frames[surface].planes[1].texture->params.shared_mem.handle.fd) {
close(decoder->pl_frames[surface].planes[1].texture->params.shared_mem.handle.fd);
}
pl_tex_destroy(p->gpu, &decoder->pl_frames[surface].planes[1].texture);
}
}
NoContext;
#else
#ifdef VAAPI
if (decoder->images[surface * Planes]) {
DestroyImageKHR(eglGetCurrentDisplay(), decoder->images[surface * Planes]);
DestroyImageKHR(eglGetCurrentDisplay(), decoder->images[surface * Planes + 1]);
if (decoder->fds[surface * Planes]) {
close(decoder->fds[surface * Planes]);
}
if (decoder->fds[surface * Planes + 1]) {
close(decoder->fds[surface * Planes + 1]);
}
}
decoder->fds[surface * Planes] = 0;
decoder->fds[surface * Planes + 1] = 0;
decoder->images[surface * Planes] = 0;
decoder->images[surface * Planes + 1] = 0;
#endif
#endif
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() {
int i = 0;
if (CuvidDecoders[0] != NULL) {
if (i = atomic_read(&CuvidDecoders[0]->SurfacesFilled) < VIDEO_SURFACES_MAX - 1)
return i;
return 0;
} else
return 0;
}
#ifdef VAAPI
struct mp_egl_config_attr {
int attrib;
const char *name;
};
#define MPGL_VER(major, minor) (((major)*100) + (minor)*10)
#define MPGL_VER_GET_MAJOR(ver) ((unsigned)(ver) / 100)
#define MPGL_VER_GET_MINOR(ver) ((unsigned)(ver) % 100 / 10)
#define MP_EGL_ATTRIB(id) \
{ id, #id }
static const struct mp_egl_config_attr mp_egl_attribs[] = {
MP_EGL_ATTRIB(EGL_CONFIG_ID), MP_EGL_ATTRIB(EGL_RED_SIZE), MP_EGL_ATTRIB(EGL_GREEN_SIZE),
MP_EGL_ATTRIB(EGL_BLUE_SIZE), MP_EGL_ATTRIB(EGL_ALPHA_SIZE), MP_EGL_ATTRIB(EGL_COLOR_BUFFER_TYPE),
MP_EGL_ATTRIB(EGL_CONFIG_CAVEAT), MP_EGL_ATTRIB(EGL_CONFORMANT),
};
const int mpgl_preferred_gl_versions[] = {460, 440, 430, 400, 330, 320, 310, 300, 210, 0};
static bool create_context_cb(EGLDisplay display, int es_version, EGLContext *out_context, EGLConfig *out_config,
int *bpp) {
EGLenum api;
EGLint rend, *attribs;
const char *name;
switch (es_version) {
case 0:
api = EGL_OPENGL_API;
rend = EGL_OPENGL_BIT;
name = "Desktop OpenGL";
break;
case 2:
api = EGL_OPENGL_ES_API;
rend = EGL_OPENGL_ES2_BIT;
name = "GLES 2.x";
break;
case 3:
api = EGL_OPENGL_ES_API;
rend = EGL_OPENGL_ES3_BIT;
name = "GLES 3.x";
break;
default:
Fatal(_("Wrong ES version \n"));
;
}
if (!eglBindAPI(api)) {
Fatal(_(" Could not bind API!\n"));
}
Debug(3, "Trying to create %s context \n", name);
EGLint attributes8[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
EGL_RENDERABLE_TYPE, rend, EGL_NONE};
EGLint attributes10[] = {EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RED_SIZE,
10,
EGL_GREEN_SIZE,
10,
EGL_BLUE_SIZE,
10,
EGL_ALPHA_SIZE,
2,
EGL_RENDERABLE_TYPE,
rend,
EGL_NONE};
EGLint num_configs = 0;
#ifndef RASPI
attribs = attributes10;
*bpp = 10;
if (!eglChooseConfig(display, attributes10, NULL, 0,
&num_configs)) { // try 10 Bit
EglCheck();
Debug(3, " 10 Bit egl Failed\n");
attribs = attributes8;
*bpp = 8;
if (!eglChooseConfig(display, attributes8, NULL, 0,
&num_configs)) { // try 8 Bit
num_configs = 0;
}
} else
#endif
if (num_configs == 0) {
EglCheck();
Debug(3, " 10 Bit egl Failed\n");
attribs = attributes8;
*bpp = 8;
if (!eglChooseConfig(display, attributes8, NULL, 0,
&num_configs)) { // try 8 Bit
num_configs = 0;
}
}
EGLConfig *configs = malloc(sizeof(EGLConfig) * num_configs);
if (!eglChooseConfig(display, attribs, configs, num_configs, &num_configs))
num_configs = 0;
if (!num_configs) {
free(configs);
Debug(3, "Could not choose EGLConfig for %s!\n", name);
return false;
}
EGLConfig config = configs[0];
free(configs);
EGLContext *egl_ctx = NULL;
if (es_version) {
eglAttrs[0] = EGL_CONTEXT_CLIENT_VERSION;
eglAttrs[1] = es_version;
eglAttrs[2] = EGL_NONE;
egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, eglAttrs);
} else {
for (int n = 0; mpgl_preferred_gl_versions[n]; n++) {
int ver = mpgl_preferred_gl_versions[n];
eglAttrs[0] = EGL_CONTEXT_MAJOR_VERSION;
eglAttrs[1] = MPGL_VER_GET_MAJOR(ver);
eglAttrs[2] = EGL_CONTEXT_MINOR_VERSION;
eglAttrs[3] = MPGL_VER_GET_MINOR(ver);
eglAttrs[4] = EGL_CONTEXT_OPENGL_PROFILE_MASK;
eglAttrs[5] = ver >= 320 ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT : 0;
eglAttrs[6] = EGL_NONE;
egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, eglAttrs);
EglCheck();
if (egl_ctx) {
Debug(3, "Use %d GLVersion\n", ver);
break;
}
}
}
if (!egl_ctx) {
Debug(3, "Could not create EGL context for %s!\n", name);
return false;
}
*out_context = egl_ctx;
*out_config = config;
eglVersion = es_version;
return true;
}
make_egl() {
int bpp;
CreateImageKHR = (void *)eglGetProcAddress("eglCreateImageKHR");
DestroyImageKHR = (void *)eglGetProcAddress("eglDestroyImageKHR");
EGLImageTargetTexture2DOES = (void *)eglGetProcAddress("glEGLImageTargetTexture2DOES");
eglCreateSyncKHR = (void *)eglGetProcAddress("eglCreateSyncKHR");
eglDestroySyncKHR = (void *)eglGetProcAddress("eglDestroySyncKHR");
eglWaitSyncKHR = (void *)eglGetProcAddress("eglWaitSyncKHR");
eglClientWaitSyncKHR = (void *)eglGetProcAddress("eglClientWaitSyncKHR");
eglDupNativeFenceFDANDROID = (void *)eglGetProcAddress("eglDupNativeFenceFDANDROID");
if (!CreateImageKHR || !DestroyImageKHR || !EGLImageTargetTexture2DOES || !eglCreateSyncKHR)
Fatal(_("Can't get EGL Extentions\n"));
#ifndef USE_DRM
eglDisplay = eglGetDisplay(XlibDisplay);
#endif
if (!eglInitialize(eglDisplay, NULL, NULL)) {
Fatal(_("Could not initialize EGL.\n"));
}
if (!create_context_cb(eglDisplay, 0, &eglContext, &eglConfig, &bpp)) {
Fatal(_("Could not create EGL Context\n"));
}
int vID, n;
eglGetConfigAttrib(eglDisplay, eglConfig, EGL_NATIVE_VISUAL_ID, &vID);
Debug(3, "chose visual 0x%x bpp %d\n", vID, bpp);
#ifdef USE_DRM
InitBo(bpp);
#else
eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, (EGLNativeWindowType)VideoWindow, NULL);
if (eglSurface == EGL_NO_SURFACE) {
Fatal(_("Could not create EGL surface!\n"));
}
#endif
if (!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
Fatal(_("Could not make context current!\n"));
}
EglEnabled = 1;
}
#endif
///
/// 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;
}
#ifdef CUVID
if ((i = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, X11DisplayName, NULL, 0)) != 0) {
Fatal("codec: can't allocate HW video codec context err %04x", i);
}
#endif
#if defined(VAAPI) && !defined(RASPI)
// if ((i = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI,
// ":0.0" , NULL, 0)) != 0 ) {
if ((i = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/renderD128", NULL, 0)) != 0) {
Fatal("codec: can't allocate HW video codec context err %04x", i);
}
#endif
#ifndef RASPI
HwDeviceContext = av_buffer_ref(hw_device_ctx);
#endif
if (!(decoder = calloc(1, sizeof(*decoder)))) {
Error(_("video/cuvid: out of memory\n"));
return NULL;
}
#if defined(VAAPI) && !defined(RASPI)
VaDisplay = TO_VAAPI_DEVICE_CTX(HwDeviceContext)->display;
decoder->VaDisplay = VaDisplay;
#endif
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;
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;
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;
Debug(3, "cuvid del hw decoder \n");
if (decoder == CuvidDecoders[0])
VideoThreadLock();
#ifndef PLACEBO
#ifdef CUVID
glXMakeCurrent(XlibDisplay, VideoWindow, glxSharedContext);
GlxCheck();
#else
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglContext);
EglCheck();
#endif
#endif
#if defined PLACEBO || defined VAAPI
if (decoder->SurfaceFreeN || decoder->SurfaceUsedN) {
CuvidDestroySurfaces(decoder);
}
#endif
if (decoder == CuvidDecoders[0])
VideoThreadUnlock();
// 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 CUVID
if (decoder->cuda_ctx && CuvidDecoderN == 1) {
cuCtxDestroy(decoder->cuda_ctx);
}
#endif
free(decoder);
return;
}
}
Error(_("video/cuvid: decoder not in decoder list.\n"));
}
static int CuvidGlxInit(__attribute__((unused)) const char *display_name) {
#if !defined PLACEBO || defined PLACEBO_GL
EglInit();
if (EglEnabled) {
#ifdef CUVID
GlxSetupWindow(VideoWindow, VideoWindowWidth, VideoWindowHeight, glxContext);
#else
GlxSetupWindow(VideoWindow, VideoWindowWidth, VideoWindowHeight, eglContext);
#endif
}
if (!EglEnabled) {
Fatal(_("video/egl: egl init error\n"));
}
#else
EglEnabled = 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");
}
///
/// 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);
}
void SDK_CHECK_ERROR_GL() {
GLenum gl_error = glGetError();
if (gl_error != GL_NO_ERROR) {
Fatal(_("video/cuvid: SDL error %d\n"), gl_error);
}
}
#ifdef CUVID
// 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;
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(cu->cuMemcpy2D(&cpy));
}
}
#endif
#ifdef PLACEBO
void createTextureDst(CuvidDecoder *decoder, int anz, unsigned int size_x, unsigned int size_y,
enum AVPixelFormat PixFmt) {
int n, i, size = 1, fd;
const struct pl_fmt *fmt;
struct pl_tex *tex;
struct pl_frame *img;
struct pl_plane *pl;
SharedContext;
// 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; i < anz; i++) { // number of texture
if (decoder->frames[i]) {
av_frame_free(&decoder->frames[i]);
decoder->frames[i] = NULL;
}
for (n = 0; n < 2; n++) { // number of planes
bool ok = true;
if (PixFmt == AV_PIX_FMT_NV12) {
fmt = pl_find_named_fmt(p->gpu, 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;
}
if (decoder->pl_frames[i].planes[n].texture) {
// #ifdef VAAPI
if (decoder->pl_frames[i].planes[n].texture->params.shared_mem.handle.fd) {
close(decoder->pl_frames[i].planes[n].texture->params.shared_mem.handle.fd);
}
// #endif
pl_tex_destroy(p->gpu,
&decoder->pl_frames[i].planes[n].texture); // delete old texture
}
if (p->has_dma_buf == 0) {
decoder->pl_frames[i].planes[n].texture = 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, .blit_dst = true,
#if PL_API_VER < 159
.sample_mode = PL_TEX_SAMPLE_LINEAR, .address_mode = PL_TEX_ADDRESS_CLAMP,
#endif
#if !defined PLACEBO_GL
.export_handle = PL_HANDLE_FD,
#endif
});
}
// make planes for image
pl = &decoder->pl_frames[i].planes[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->shift_x = -0.5f; // PL_CHROMA_LEFT
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"));
}
#ifdef CUVID
fd = dup(decoder->pl_frames[i].planes[n].texture->shared_mem.handle.fd);
CUDA_EXTERNAL_MEMORY_HANDLE_DESC ext_desc = {
.type = CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD,
.handle.fd = fd,
.size =
decoder->pl_frames[i].planes[n].texture->shared_mem.size, // image_width * image_height * bytes,
.flags = 0,
};
checkCudaErrors(
cu->cuImportExternalMemory(&decoder->ebuf[i * 2 + n].mem, &ext_desc)); // Import Memory segment
CUDA_EXTERNAL_MEMORY_MIPMAPPED_ARRAY_DESC tex_desc = {
.offset = decoder->pl_frames[i].planes[n].texture->shared_mem.offset,
.arrayDesc =
{
.Width = n == 0 ? size_x : size_x / 2,
.Height = n == 0 ? size_y : size_y / 2,
.Depth = 0,
.Format = PixFmt == AV_PIX_FMT_NV12 ? CU_AD_FORMAT_UNSIGNED_INT8 : CU_AD_FORMAT_UNSIGNED_INT16,
.NumChannels = n == 0 ? 1 : 2,
.Flags = 0,
},
.numLevels = 1,
};
checkCudaErrors(cu->cuExternalMemoryGetMappedMipmappedArray(&decoder->ebuf[i * 2 + n].mma,
decoder->ebuf[i * 2 + n].mem, &tex_desc));
checkCudaErrors(cu->cuMipmappedArrayGetLevel(&decoder->cu_array[i][n], decoder->ebuf[i * 2 + n].mma, 0));
#endif
}
// make image
img = &decoder->pl_frames[i];
#if PL_API_VER < 159
img->signature = i;
img->width = size_x;
img->height = size_y;
#endif
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->num_overlays = 0;
}
NoContext;
}
#ifdef VAAPI
// copy image and process using CUDA
void generateVAAPIImage(CuvidDecoder *decoder, int index, const AVFrame *frame, int image_width, int image_height) {
int n;
VAStatus status;
int toggle = 0;
uint64_t first_time;
VADRMPRIMESurfaceDescriptor desc;
vaSyncSurface(decoder->VaDisplay, (unsigned int)frame->data[3]);
status =
vaExportSurfaceHandle(decoder->VaDisplay, (unsigned int)frame->data[3], VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &desc);
if (status != VA_STATUS_SUCCESS) {
printf("Fehler beim export VAAPI Handle\n");
return;
}
// vaSyncSurface(decoder->VaDisplay, (unsigned int)frame->data[3]);
Lock_and_SharedContext;
for (n = 0; n < 2; n++) { // Set DMA_BUF from VAAPI decoder to Textures
int id = desc.layers[n].object_index[0];
int fd = desc.objects[id].fd;
uint32_t size = desc.objects[id].size;
uint32_t offset = desc.layers[n].offset[0];
struct pl_fmt *fmt;
if (fd == -1) {
printf("Fehler beim Import von Surface %d\n", index);
return;
}
if (!size) {
size = n == 0 ? desc.width * desc.height : desc.width * desc.height / 2;
}
// fmt = pl_find_fourcc(p->gpu,desc.layers[n].drm_format);
#if 1
if (decoder->PixFmt == AV_PIX_FMT_NV12) {
fmt = pl_find_named_fmt(p->gpu, n == 0 ? "r8" : "rg8"); // 8 Bit YUV
} else {
fmt = pl_find_fourcc(p->gpu,
n == 0 ? 0x20363152 : 0x32335247); // 10 Bit YUV
}
#endif
assert(fmt != NULL);
#ifdef PLACEBO_GL
fmt->fourcc = desc.layers[n].drm_format;
#endif
struct pl_tex_params tex_params = {
.w = n == 0 ? image_width : image_width / 2,
.h = n == 0 ? image_height : image_height / 2,
.d = 0,
.format = fmt,
.sampleable = true,
.host_writable = false,
.blit_dst = true,
.renderable = true,
#if PL_API_VER < 159
.address_mode = PL_TEX_ADDRESS_CLAMP,
.sample_mode = PL_TEX_SAMPLE_LINEAR,
#endif
.import_handle = PL_HANDLE_DMA_BUF,
.shared_mem =
(struct pl_shared_mem){
.handle =
{
.fd = fd,
},
.size = size,
.offset = offset,
.stride_h = n == 0 ? image_height : image_height / 2,
.stride_w = desc.layers[n].pitch[0],
.drm_format_mod = desc.objects[id].drm_format_modifier,
},
};
// printf("vor create Object %d with fd %d import size %u offset %d
// %dx%d\n",id,fd,size,offset, tex_params.w,tex_params.h);
if (decoder->pl_frames[index].planes[n].texture) {
pl_tex_destroy(p->gpu, &decoder->pl_frames[index].planes[n].texture);
}
decoder->pl_frames[index].planes[n].texture = pl_tex_create(p->gpu, &tex_params);
}
Unlock_and_NoContext;
}
#endif
#else // no PLACEBO
void createTextureDst(CuvidDecoder *decoder, int anz, unsigned int size_x, unsigned int size_y,
enum AVPixelFormat PixFmt) {
int n, i;
Debug(3, "video: create %d Textures Format %s w %d h %d \n", anz, PixFmt == AV_PIX_FMT_NV12 ? "NV12" : "P010",
size_x, size_y);
#ifdef USE_DRM
// set_video_mode(size_x,size_y); // switch Mode here (highly
// experimental)
#endif
#ifdef CUVID
glXMakeCurrent(XlibDisplay, VideoWindow, glxSharedContext);
GlxCheck();
#else
#ifdef USE_DRM
pthread_mutex_lock(&OSDMutex);
#endif
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglSharedContext);
#endif
glGenBuffers(1, &vao_buffer);
GlxCheck();
// create texture planes
glGenTextures(CODEC_SURFACES_MAX * Planes, decoder->gl_textures);
GlxCheck();
for (i = 0; i < anz; i++) {
for (n = 0; n < Planes; n++) { // number of planes
glBindTexture(GL_TEXTURE_2D, decoder->gl_textures[i * Planes + 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);
#ifdef RASPI
if (PixFmt == AV_PIX_FMT_NV12)
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, n == 0 ? size_x : size_x / 2, n == 0 ? size_y : size_y / 2, 0,
GL_RED, GL_UNSIGNED_BYTE, NULL);
else
glTexImage2D(GL_TEXTURE_2D, 0, GL_R16, n == 0 ? size_x : size_x / 2, n == 0 ? size_y : size_y / 2, 0,
GL_RED, GL_UNSIGNED_SHORT, NULL);
#else
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);
#endif
SDK_CHECK_ERROR_GL();
// register this texture with CUDA
#ifdef CUVID
checkCudaErrors(cu->cuGraphicsGLRegisterImage(&decoder->cu_res[i][n], decoder->gl_textures[i * Planes + n],
GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD));
checkCudaErrors(cu->cuGraphicsMapResources(1, &decoder->cu_res[i][n], 0));
checkCudaErrors(
cu->cuGraphicsSubResourceGetMappedArray(&decoder->cu_array[i][n], decoder->cu_res[i][n], 0, 0));
checkCudaErrors(cu->cuGraphicsUnmapResources(1, &decoder->cu_res[i][n], 0));
#endif
}
}
glBindTexture(GL_TEXTURE_2D, 0);
GlxCheck();
#ifdef VAAPI
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
#ifdef USE_DRM
pthread_mutex_unlock(&OSDMutex);
#endif
#endif
}
#ifdef VAAPI
#define MP_ARRAY_SIZE(s) (sizeof(s) / sizeof((s)[0]))
#define ADD_ATTRIB(name, value) \
do { \
assert(num_attribs + 3 < MP_ARRAY_SIZE(attribs)); \
attribs[num_attribs++] = (name); \
attribs[num_attribs++] = (value); \
attribs[num_attribs] = EGL_NONE; \
} while (0)
#define ADD_PLANE_ATTRIBS(plane) \
do { \
ADD_ATTRIB(EGL_DMA_BUF_PLANE##plane##_FD_EXT, desc.objects[desc.layers[n].object_index[plane]].fd); \
ADD_ATTRIB(EGL_DMA_BUF_PLANE##plane##_OFFSET_EXT, desc.layers[n].offset[plane]); \
ADD_ATTRIB(EGL_DMA_BUF_PLANE##plane##_PITCH_EXT, desc.layers[n].pitch[plane]); \
} while (0)
void generateVAAPIImage(CuvidDecoder *decoder, VASurfaceID index, const AVFrame *frame, int image_width,
int image_height) {
VAStatus status;
uint64_t first_time;
#if defined(VAAPI) && !defined(RASPI)
VADRMPRIMESurfaceDescriptor desc;
vaSyncSurface(decoder->VaDisplay, (VASurfaceID)(uintptr_t)frame->data[3]);
status = vaExportSurfaceHandle(decoder->VaDisplay, (VASurfaceID)(uintptr_t)frame->data[3],
VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &desc);
if (status != VA_STATUS_SUCCESS) {
printf("Fehler beim export VAAPI Handle\n");
return;
}
// vaSyncSurface(decoder->VaDisplay, (VASurfaceID) (uintptr_t)
// frame->data[3]);
#endif
#ifdef RASPI
AVDRMFrameDescriptor desc;
memcpy(&desc, frame->data[0], sizeof(desc));
#endif
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglSharedContext);
EglCheck();
for (int n = 0; n < Planes; n++) {
int attribs[20] = {EGL_NONE};
uint num_attribs = 0;
int fd;
#if defined(VAAPI) && !defined(RASPI)
ADD_ATTRIB(EGL_LINUX_DRM_FOURCC_EXT, desc.layers[n].drm_format);
ADD_ATTRIB(EGL_WIDTH, n == 0 ? image_width : image_width / 2);
ADD_ATTRIB(EGL_HEIGHT, n == 0 ? image_height : image_height / 2);
ADD_PLANE_ATTRIBS(0);
#endif
decoder->images[index * Planes + n] =
CreateImageKHR(eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
if (!decoder->images[index * Planes + n])
goto esh_failed;
glBindTexture(GL_TEXTURE_2D, decoder->gl_textures[index * Planes + n]);
EGLImageTargetTexture2DOES(GL_TEXTURE_2D, decoder->images[index * Planes + n]);
decoder->fds[index * Planes + n] = desc.objects[n].fd;
}
glBindTexture(GL_TEXTURE_2D, 0);
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
EglCheck();
return;
esh_failed:
Debug(3, "Failure in generateVAAPIImage\n");
for (int n = 0; n < Planes; n++)
close(desc.objects[n].fd);
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
EglCheck();
}
#endif
#endif
///
/// Configure CUVID for new video format.
///
/// @param decoder CUVID hw decoder
///
static void CuvidSetupOutput(CuvidDecoder *decoder) {
// 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);
}
#if defined(VAAPI) || defined(YADIF)
static void CuvidSyncRenderFrame(CuvidDecoder *decoder, const AVCodecContext *video_ctx, AVFrame *frame);
int push_filters(AVCodecContext *dec_ctx, CuvidDecoder *decoder, AVFrame *frame) {
int ret;
AVFrame *filt_frame = av_frame_alloc();
/* push the decoded frame into the filtergraph */
if (av_buffersrc_add_frame_flags(decoder->buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) {
av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
}
// printf("Interlaced %d tff
// %d\n",frame->interlaced_frame,frame->top_field_first);
/* pull filtered frames from the filtergraph */
while ((ret = av_buffersink_get_frame(decoder->buffersink_ctx, filt_frame)) >= 0) {
filt_frame->pts /= 2;
decoder->Interlaced = 0;
// printf("vaapideint video:new %#012" PRIx64 " old %#012" PRIx64