1
0
mirror of https://github.com/jojo61/vdr-plugin-softhdcuvid.git synced 2023-10-10 13:37:41 +02:00
vdr-plugin-softhdcuvid/video.c
Dirk Nehring 23651104f2 Reworked the aspect function. Now we calculate the display aspect from the pixel width and
pixel height (and not from the physical size, which was the root cause for rounding errors).
Cropping calculation is reworking, now the rounding is correct. I introduce a new aspect mode
"original" which displays the output with the original size (but after correction the
pixel aspect ratio). Tested with vaapi(X11) and vaapi(DRM) with and without libplacebo.
2020-04-13 18:04:57 +02:00

6983 lines
208 KiB
C

///
/// @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_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 <sys/time.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/prctl.h>
#include <stdio.h>
#include <fcntl.h> /* File Control Definitions */
#include <termios.h> /* POSIX Terminal Control Definitions */
#include <unistd.h> /* UNIX Standard Definitions */
#include <errno.h> /* ERROR Number Definitions */
#include <sys/ioctl.h> /* ioctl() */
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <math.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 <time.h>
#include <signal.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/screensaver.h>
#include <xcb/dpms.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
#include <GL/glew.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <GL/freeglut_ext.h>
#endif
#include <libavutil/hwcontext.h>
#include <libavutil/pixdesc.h>
#ifdef CUVID
#include <ffnvcodec/dynlink_cuda.h>
#include <ffnvcodec/dynlink_loader.h>
#include <libavutil/hwcontext_cuda.h>
#include "drvapi_error_string.h"
#define __DEVICE_TYPES_H__
#endif
#ifdef VAAPI
#include <va/va_drmcommon.h>
#include <libavcodec/vaapi.h>
#ifdef RASPI
#include <libavutil/hwcontext_drm.h>
#include <libdrm/drm_fourcc.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
#include <EGL/egl.h>
#include <EGL/eglext.h>
#ifndef GL_OES_EGL_image
typedef void *GLeglImageOES;
#endif
#ifndef EGL_KHR_image
typedef void *EGLImageKHR;
#endif
#ifdef PLACEBO
#define VK_USE_PLATFORM_XCB_KHR
#include <vulkan/vulkan.h>
#include <libplacebo/context.h>
#include <libplacebo/vulkan.h>
#include <libplacebo/renderer.h>
#endif
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#if defined(YADIF) || defined (VAAPI)
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/opt.h>
#endif
#include "iatomic.h" // portable atomic_t
#include "misc.h"
#include "video.h"
#include "audio.h"
#include "codec.h"
#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
#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 1
#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 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
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 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);
//----------------------------------------------------------------------------
// 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;
#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)) {
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;
#ifdef PLACEBO
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
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_image pl_images[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
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;
// VkSemaphore sig_in;
int has_dma_buf;
} 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
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_images[i].planes[j].texture) {
#ifdef VAAPI
if (p->has_dma_buf && decoder->pl_images[i].planes[j].texture->params.shared_mem.handle.fd) {
close(decoder->pl_images[i].planes[j].texture->params.shared_mem.handle.fd);
}
#endif
pl_tex_destroy(p->gpu, &decoder->pl_images[i].planes[j].texture);
}
#else
#ifdef CUVID
checkCudaErrors(cu->cuGraphicsUnregisterResource(decoder->cu_res[i][j]));
#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
#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
if (p->has_dma_buf) {
if (decoder->pl_images[surface].planes[0].texture) {
if (decoder->pl_images[surface].planes[0].texture->params.shared_mem.handle.fd) {
close(decoder->pl_images[surface].planes[0].texture->params.shared_mem.handle.fd);
}
pl_tex_destroy(p->gpu, &decoder->pl_images[surface].planes[0].texture);
}
if (decoder->pl_images[surface].planes[1].texture) {
if (decoder->pl_images[surface].planes[1].texture->params.shared_mem.handle.fd) {
close(decoder->pl_images[surface].planes[1].texture->params.shared_mem.handle.fd);
}
pl_tex_destroy(p->gpu, &decoder->pl_images[surface].planes[1].texture);
}
}
#else
#ifdef VAAPI
if (decoder->images[surface * Planes]) {
DestroyImageKHR(eglGetCurrentDisplay(), decoder->images[surface * Planes]);
DestroyImageKHR(eglGetCurrentDisplay(), decoder->images[surface * Planes + 1]);
#ifdef RASPI
DestroyImageKHR(eglGetCurrentDisplay(), decoder->images[surface * Planes + 2]);
#endif
if (decoder->fds[surface * Planes]) {
close(decoder->fds[surface * Planes]);
// close(decoder->fds[surface*Planes+1]);
#ifdef RASPI
close(decoder->fds[surface * Planes + 2]);
#endif
}
}
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[] = {
// 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"));;
}
Debug(3, "Trying to create %s context.\n", name);
if (!eglBindAPI(api)) {
Fatal(_(" Could not bind API!\n"));
}
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
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)
{
#ifndef PLACEBO
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_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; 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_images[i].planes[n].texture) {
// #ifdef VAAPI
if (decoder->pl_images[i].planes[n].texture->params.shared_mem.handle.fd) {
close(decoder->pl_images[i].planes[n].texture->params.shared_mem.handle.fd);
}
// #endif
pl_tex_destroy(p->gpu, &decoder->pl_images[i].planes[n].texture); // delete old texture
}
if (p->has_dma_buf == 0) {
decoder->pl_images[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,
.sample_mode = PL_TEX_SAMPLE_LINEAR,
.address_mode = PL_TEX_ADDRESS_CLAMP,
.export_handle = PL_HANDLE_FD,
});
}
// make planes for image
pl = &decoder->pl_images[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_images[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_images[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_images[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_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;
}
}
#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;
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]);
VideoThreadLock();
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];
const struct pl_fmt *fmt;
if (fd == -1) {
printf("Fehler beim Import von Surface %d\n", index);
return;
}
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_named_fmt(p->gpu, n == 0 ? "r16" : "rg16"); // 10 Bit YUV
}
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,
.address_mode = PL_TEX_ADDRESS_CLAMP,
.sample_mode = PL_TEX_SAMPLE_LINEAR,
.import_handle = PL_HANDLE_DMA_BUF,
.shared_mem = (struct pl_shared_mem) {
.handle = {
.fd = fd,
},
.size = size,
.offset = offset,
},
};
// 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_images[index].planes[n].texture) {
pl_tex_destroy(p->gpu, &decoder->pl_images[index].planes[n].texture);
}
decoder->pl_images[index].planes[n].texture = pl_tex_create(p->gpu, &tex_params);
}
VideoThreadUnlock();
}
#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;
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
#ifdef RASPI
ADD_ATTRIB(EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_R8);
ADD_ATTRIB(EGL_WIDTH, n == 0 ? image_width : image_width / 2);
ADD_ATTRIB(EGL_HEIGHT, n == 0 ? image_height : image_height / 2);
if (n == 0) {
fd = dup(desc.objects[0].fd);
ADD_ATTRIB(EGL_DMA_BUF_PLANE0_FD_EXT, fd);
ADD_ATTRIB(EGL_DMA_BUF_PLANE0_OFFSET_EXT, desc.layers[0].planes[n].offset);
ADD_ATTRIB(EGL_DMA_BUF_PLANE0_PITCH_EXT, desc.layers[0].planes[n].pitch);
} else {
fd = dup(desc.objects[0].fd);
ADD_ATTRIB(EGL_DMA_BUF_PLANE0_FD_EXT, fd);
ADD_ATTRIB(EGL_DMA_BUF_PLANE0_OFFSET_EXT, desc.layers[0].planes[n].offset);
ADD_ATTRIB(EGL_DMA_BUF_PLANE0_PITCH_EXT, desc.layers[0].planes[n].pitch);
}
// Debug(3,"n %d fd %d nb_planes %d nb_layers %d plane %d offeset %d offset2 %d pitch %d \n",n, fd,
// desc.layers[0].nb_planes,desc.nb_layers,n,desc.layers[0].planes[n].offset,desc.layers[0].planes[n+1].offset,desc.layers[0].planes[n].pitch);
#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]);
#ifdef RASPI
decoder->fds[index * Planes + n] = fd;
#endif
}
decoder->fds[index * Planes] = desc.objects[0].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 "\n",filt_frame->pts,frame->pts);
CuvidSyncRenderFrame(decoder, dec_ctx, filt_frame);
filt_frame = av_frame_alloc(); // get new frame
}
av_frame_free(&filt_frame);
av_frame_free(&frame);
return ret;
}
int init_filters(AVCodecContext * dec_ctx, CuvidDecoder * decoder, AVFrame * frame)
{
enum AVPixelFormat format = PIXEL_FORMAT;
#ifdef VAAPI
const char *filters_descr = "deinterlace_vaapi=rate=field:auto=1";
#endif
#ifdef YADIF
const char *filters_descr = "yadif_cuda=1:0:1"; // mode=send_field,parity=tff,deint=interlaced";
enum AVPixelFormat pix_fmts[] = { format, AV_PIX_FMT_NONE };
#endif
char args[512];
int ret = 0;
const AVFilter *buffersrc = avfilter_get_by_name("buffer");
const AVFilter *buffersink = avfilter_get_by_name("buffersink");
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
AVBufferSrcParameters *src_params;
if (decoder->filter_graph)
avfilter_graph_free(&decoder->filter_graph);
decoder->filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !decoder->filter_graph) {
ret = AVERROR(ENOMEM);
goto end;
}
/* buffer video source: the decoded frames from the decoder will be inserted here. */
snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", dec_ctx->width,
dec_ctx->height, format, 1, 90000, dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
ret = avfilter_graph_create_filter(&decoder->buffersrc_ctx, buffersrc, "in", args, NULL, decoder->filter_graph);
if (ret < 0) {
Debug(3, "Cannot create buffer source\n");
goto end;
}
src_params = av_buffersrc_parameters_alloc();
src_params->hw_frames_ctx = frame->hw_frames_ctx;
src_params->format = format;
src_params->time_base.num = 1;
src_params->time_base.den = 90000;
src_params->width = dec_ctx->width;
src_params->height = dec_ctx->height;
src_params->frame_rate.num = 50;
src_params->frame_rate.den = 1;
src_params->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
// printf("width %d height %d hw_frames_ctx %p\n",dec_ctx->width,dec_ctx->height ,frame->hw_frames_ctx);
ret = av_buffersrc_parameters_set(decoder->buffersrc_ctx, src_params);
if (ret < 0) {
Debug(3, "Cannot set hw_frames_ctx to src\n");
goto end;
}
/* buffer video sink: to terminate the filter chain. */
ret = avfilter_graph_create_filter(&decoder->buffersink_ctx, buffersink, "out", NULL, NULL, decoder->filter_graph);
if (ret < 0) {
Debug(3, "Cannot create buffer sink\n");
goto end;
}
#ifdef YADIF
ret = av_opt_set_int_list(decoder->buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
Debug(3, "Cannot set output pixel format\n");
goto end;
}
#endif
/*
* Set the endpoints for the filter graph. The filter_graph will
* be linked to the graph described by filters_descr.
*/
/*
* The buffer source output must be connected to the input pad of
* the first filter described by filters_descr; since the first
* filter input label is not specified, it is set to "in" by
* default.
*/
outputs->name = av_strdup("in");
outputs->filter_ctx = decoder->buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
/*
* The buffer sink input must be connected to the output pad of
* the last filter described by filters_descr; since the last
* filter output label is not specified, it is set to "out" by
* default.
*/
inputs->name = av_strdup("out");
inputs->filter_ctx = decoder->buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if ((ret = avfilter_graph_parse_ptr(decoder->filter_graph, filters_descr, &inputs, &outputs, NULL)) < 0) {
Debug(3, "Cannot set graph parse %d\n", ret);
goto end;
}
if ((ret = avfilter_graph_config(decoder->filter_graph, NULL)) < 0) {
Debug(3, "Cannot set graph config %d\n", ret);
goto end;
}
end:
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
#endif
#ifdef VAAPI
static int init_generic_hwaccel(CuvidDecoder * decoder, enum AVPixelFormat hw_fmt, AVCodecContext * video_ctx)
{
AVBufferRef *new_frames_ctx = NULL;
if (!hw_device_ctx) {
Debug(3, "Missing device context.\n");
goto error;
}
if (avcodec_get_hw_frames_parameters(video_ctx, hw_device_ctx, hw_fmt, &new_frames_ctx) < 0) {
Debug(3, "Hardware decoding of this stream is unsupported?\n");
goto error;
}
AVHWFramesContext *new_fctx = (void *)new_frames_ctx->data;
// We might be able to reuse a previously allocated frame pool.
if (decoder->cached_hw_frames_ctx) {
AVHWFramesContext *old_fctx = (void *)decoder->cached_hw_frames_ctx->data;
Debug(3, "CMP %d:%d %d:%d %d:%d %d:%d %d:%d\,", new_fctx->format, old_fctx->format, new_fctx->sw_format,
old_fctx->sw_format, new_fctx->width, old_fctx->width, new_fctx->height, old_fctx->height,
new_fctx->initial_pool_size, old_fctx->initial_pool_size);
if (new_fctx->format != old_fctx->format || new_fctx->sw_format != old_fctx->sw_format
|| new_fctx->width != old_fctx->width || new_fctx->height != old_fctx->height
|| new_fctx->initial_pool_size != old_fctx->initial_pool_size) {
Debug(3, "delete old cache");
if (decoder->filter_graph)
avfilter_graph_free(&decoder->filter_graph);
av_buffer_unref(&decoder->cached_hw_frames_ctx);
}
}
if (!decoder->cached_hw_frames_ctx) {
new_fctx->initial_pool_size = 17;
if (av_hwframe_ctx_init(new_frames_ctx) < 0) {
Debug(3, "Failed to allocate hw frames.\n");
goto error;
}
decoder->cached_hw_frames_ctx = new_frames_ctx;
new_frames_ctx = NULL;
}
video_ctx->hw_frames_ctx = av_buffer_ref(decoder->cached_hw_frames_ctx);
if (!video_ctx->hw_frames_ctx)
goto error;
av_buffer_unref(&new_frames_ctx);
return 0;
error:
Debug(3, "Error with hwframes\n");
av_buffer_unref(&new_frames_ctx);
av_buffer_unref(&decoder->cached_hw_frames_ctx);
return -1;
}
#endif
///
/// 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, deint = 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;
}
#ifdef VAAPI
if (video_ctx->profile == FF_PROFILE_HEVC_MAIN_10)
bitformat16 = 1;
#endif
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 PIXEL_FORMAT:
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) {
Fatal(_("video: no valid pixfmt found\n"));
}
if (*fmt_idx != PIXEL_FORMAT) {
Fatal(_("video: no valid profile found\n"));
}
// decoder->newchannel = 1;
#ifdef VAAPI
init_generic_hwaccel(decoder, PIXEL_FORMAT, video_ctx);
#endif
if (ist->GetFormatDone) {
return PIXEL_FORMAT;
}
ist->GetFormatDone = 1;
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 == PIXEL_FORMAT) { // 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)
&& decoder->TrickSpeed == 0) {
// if (decoder->TrickSpeed == 0) {
#ifdef PLACEBO
VideoThreadLock();
#endif
decoder->newchannel = 1;
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
VideoThreadUnlock();
// dont show first frame
#endif
#ifdef YADIF
if (VideoDeinterlace[decoder->Resolution] == VideoDeinterlaceYadif) {
deint = 0;
ist->filter = 1; // init yadif_cuda
} else {
deint = 2;
ist->filter = 0;
}
CuvidMessage(2, "deint = %s\n", deint == 0 ? "Yadif" : "Cuda");
if (av_opt_set_int(video_ctx->priv_data, "deint", deint, 0) < 0) { // adaptive
Fatal(_("codec: can't set option deint to video codec!\n"));
}
#endif
} else {
decoder->SyncCounter = 0;
decoder->FrameCounter = 0;
decoder->FramesDisplayed = 0;
decoder->StartCounter = 0;
decoder->Closing = 0;
decoder->PTS = AV_NOPTS_VALUE;
VideoDeltaPTS = 0;
}
CuvidMessage(2, "GetFormat Init ok %dx%d\n", video_ctx->width, video_ctx->height);
decoder->InputAspect = video_ctx->sample_aspect_ratio;
#ifdef CUVID
ist->active_hwaccel_id = HWACCEL_CUVID;
#else
if (VideoDeinterlace[decoder->Resolution]) // need deinterlace
ist->filter = 1; // init deint vaapi
else
ist->filter = 0;
ist->active_hwaccel_id = HWACCEL_VAAPI;
#endif
ist->hwaccel_pix_fmt = PIXEL_FORMAT;
return PIXEL_FORMAT;
}
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;
int offset, x1, y1, x0, y0;
float faktorx, faktory;
#endif
uint8_t *base;
int width;
int height;
GLuint fb, texture;
int current;
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);
GlxCheck();
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);
#ifdef RASPI
texLoc = glGetUniformLocation(gl_prog, "texture2");
glUniform1i(texLoc, 2);
#endif
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, decoder->gl_textures[current * Planes + 0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, decoder->gl_textures[current * Planes + 1]);
#ifdef RASPI
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, decoder->gl_textures[current * Planes + 2]);
#endif
glBindFramebuffer(GL_FRAMEBUFFER, fb);
render_pass_quad(1, 0.0, 0.0);
glUseProgram(0);
glActiveTexture(GL_TEXTURE0);
if (OsdShown && decoder->grab == 2) {
int x, y, h, w;
GLint texLoc;
if (OsdShown == 1) {
if (OSDtexture)
glDeleteTextures(1, &OSDtexture);
// pthread_mutex_lock(&OSDMutex);
glGenTextures(1, &OSDtexture);
glBindTexture(GL_TEXTURE_2D, OSDtexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, OSDxsize, OSDysize, 0, GL_RGBA, GL_UNSIGNED_BYTE, posd);
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);
// pthread_mutex_unlock(&OSDMutex);
OsdShown = 2;
}
y = OSDy * height / VideoWindowHeight;
x = OSDx * width / VideoWindowWidth;
h = OSDysize * height / VideoWindowHeight;
w = OSDxsize * width / VideoWindowWidth;
glViewport(x, (height - h - y), w, h);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (gl_prog_osd == 0)
gl_prog_osd = sc_generate_osd(gl_prog_osd); // generate shader programm
glUseProgram(gl_prog_osd);
texLoc = glGetUniformLocation(gl_prog_osd, "texture0");
glUniform1i(texLoc, 0);
glActiveTexture(GL_TEXTURE0);
// pthread_mutex_lock(&OSDMutex);
glBindTexture(GL_TEXTURE_2D, OSDtexture);
glBindFramebuffer(GL_FRAMEBUFFER, fb);
render_pass_quad(0, 0.0, 0.0);
// pthread_mutex_unlock(&OSDMutex);
glUseProgram(0);
glActiveTexture(GL_TEXTURE0);
}
glFlush();
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 // Placebo
faktorx = (float)width / (float)VideoWindowWidth;
faktory = (float)height / (float)VideoWindowHeight;
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 = (float)decoder->OutputX * faktorx;
target.dst_rect.y0 = (float)decoder->OutputY * faktory;
target.dst_rect.x1 = (float)(decoder->OutputX + decoder->OutputWidth) * faktorx;
target.dst_rect.y1 = (float)(decoder->OutputY + decoder->OutputHeight) * faktory;
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;
x0 = ovl->rect.x0;
y0 = ovl->rect.y0;
x1 = ovl->rect.x1;
y1 = ovl->rect.y1;
ovl->rect.x0 = (float)x0 *faktorx;
ovl->rect.y0 = (float)y0 *faktory;
ovl->rect.x1 = (float)x1 *faktorx;
ovl->rect.y1 = (float)y1 *faktory;
} 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);
if (ovl) {
ovl->rect.x0 = x0;
ovl->rect.y0 = y0;
ovl->rect.x1 = x1;
ovl->rect.y1 = y1;
}
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
return 0;
}
///
/// 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)
{
uint32_t size;
uint32_t width;
uint32_t height;
uint8_t *base;
VdpRect source_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->VideoWidth;
height = decoder->VideoHeight;
#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;
}
}
// printf("video/cuvid: grab source dim %dx%d\n", width, 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;
img = CuvidGrabOutputSurfaceLocked(ret_size, ret_width, ret_height, mitosd);
return img;
}
#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/cuvid: 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/cuvid: 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);
}
#if 0
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);
#endif
///
/// 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, AVFrame * frame)
{
uint64_t first_time;
int surface;
enum AVColorSpace color;
if (decoder->Closing == 1) {
av_frame_free(&frame);
return;
}
if (!decoder->Closing) {
VideoSetPts(&decoder->PTS, decoder->Interlaced, video_ctx, frame);
}
// update aspect ratio changes
if (decoder->InputWidth && decoder->InputHeight && av_cmp_q(decoder->InputAspect, frame->sample_aspect_ratio)) {
Debug(3, "video/cuvid: 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);
}
color = frame->colorspace;
if (color == AVCOL_SPC_UNSPECIFIED) // if unknown
color = AVCOL_SPC_BT709;
#ifdef RASPI
//
// 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->PixFmt = AV_PIX_FMT_NV12;
decoder->InputWidth = video_ctx->width;
decoder->InputHeight = video_ctx->height;
CuvidCleanup(decoder);
decoder->SurfacesNeeded = VIDEO_SURFACES_MAX + 1;
CuvidSetupOutput(decoder);
}
#endif
//
// Copy data from frame to image
//
#ifdef RASPI
if (video_ctx->pix_fmt == 0) {
#else
if (video_ctx->pix_fmt == PIXEL_FORMAT) {
#endif
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
Debug(3, "no more surfaces\n");
av_frame_free(&frame);
return;
}
#if defined (VAAPI) && defined (PLACEBO)
if (p->has_dma_buf) { // Vulkan supports DMA_BUF no copy required
generateVAAPIImage(decoder, surface, frame, w, h);
} else { // we need to Copy the frame via RAM
AVFrame *output;
VideoThreadLock();
vaSyncSurface(decoder->VaDisplay, (unsigned int)frame->data[3]);
output = av_frame_alloc();
av_hwframe_transfer_data(output, frame, 0);
av_frame_copy_props(output, frame);
// printf("Save Surface ID %d %p %p\n",surface,decoder->pl_images[surface].planes[0].texture,decoder->pl_images[surface].planes[1].texture);
bool ok = pl_tex_upload(p->gpu, &(struct pl_tex_transfer_params) {
.tex = decoder->pl_images[surface].planes[0].texture,
.stride_w = output->linesize[0],
.stride_h = h,
.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_images[surface].planes[1].texture,
.stride_w = output->linesize[0] / 2,
.stride_h = h / 2,
.ptr = output->data[1],
.rc.x1 = w / 2,
.rc.y1 = h / 2,
.rc.z1 = 0,
});
av_frame_free(&output);
VideoThreadUnlock();
}
#else
#ifdef CUVID
// copy to texture
generateCUDAImage(decoder, surface, frame, w, h, decoder->PixFmt == AV_PIX_FMT_NV12 ? 1 : 2);
#else
// copy to texture
generateVAAPIImage(decoder, surface, frame, w, h);
#endif
#endif
CuvidQueueVideoSurface(decoder, surface, 1);
decoder->frames[surface] = frame;
return;
}
// Debug(3,"video/cuvid: pixel format %d not supported\n", video_ctx->pix_fmt);
av_frame_free(&frame);
return;
}
///
/// Get hwaccel context for ffmpeg.
///
/// @param decoder CUVID hw decoder
///
static void *CuvidGetHwAccelContext(CuvidDecoder * decoder)
{
unsigned int version, ret;
Debug(3, "Initializing cuvid hwaccel thread ID:%ld\n", (long int)syscall(186));
// turn NULL;
#ifdef CUVID
if (decoder->cuda_ctx) {
Debug(3, "schon passiert\n");
return NULL;
}
if (!cu) {
ret = cuda_load_functions(&cu, NULL);
if (ret < 0) {
Error(_("Could not dynamically load CUDA\n"));
return 0;
}
}
checkCudaErrors(cu->cuInit(0));
checkCudaErrors(cu->cuCtxCreate(&decoder->cuda_ctx, (unsigned int)CU_CTX_SCHED_BLOCKING_SYNC, (CUdevice) 0));
if (decoder->cuda_ctx == NULL)
Fatal(_("Kein Cuda device gefunden"));
// cu->cuCtxGetApiVersion(decoder->cuda_ctx, &version);
// Debug(3, "***********CUDA API Version %d\n", version);
#endif
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( __attribute__((unused)) CuvidDecoder * decoder)
{
#ifndef PLACEBO
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, __attribute__((unused))
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;
struct pl_plane *pl;
const struct pl_fmt *fmt;
VkImage Image;
struct pl_image *img;
bool ok;
VdpRect video_src_rect;
VdpRect dst_rect;
VdpRect dst_video_rect;
#endif
int current;
int y;
float xcropf, ycropf;
GLint texLoc;
AVFrame *frame;
AVFrameSideData *FrameSideData = NULL;
#ifdef PLACEBO
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;
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;
#endif
xcropf = (float)decoder->CropX / (float)decoder->InputWidth;
ycropf = (float)decoder->CropY / (float)decoder->InputHeight;
current = decoder->SurfacesRb[decoder->SurfaceRead];
#ifdef USE_DRM
if (!decoder->Closing) {
frame = decoder->frames[current];
AVFrameSideData *sd1 = av_frame_get_side_data(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
AVFrameSideData *sd2 = av_frame_get_side_data(frame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
set_hdr_metadata(frame->color_primaries, frame->color_trc, sd1, sd2);
}
#endif
// Render Progressive frame
#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);
#ifdef RASPI
texLoc = glGetUniformLocation(gl_prog, "texture2");
glUniform1i(texLoc, 2);
#endif
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, decoder->gl_textures[current * Planes + 0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, decoder->gl_textures[current * Planes + 1]);
#ifdef RASPI
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, decoder->gl_textures[current * Planes + 2]);
#endif
render_pass_quad(0, xcropf, ycropf);
glUseProgram(0);
glActiveTexture(GL_TEXTURE0);
#else
img = &decoder->pl_images[current];
pl = &decoder->pl_images[current].planes[1];
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: // BT 601 is reportet as RGB
img->repr.sys = PL_COLOR_SYSTEM_BT_601;
img->repr.levels = PL_COLOR_LEVELS_TV;
img->color.primaries = PL_COLOR_PRIM_BT_601_625;
img->color.transfer = PL_COLOR_TRC_BT_1886;
img->color.light = PL_COLOR_LIGHT_DISPLAY;
pl->shift_x = 0.0f;
break;
case AVCOL_SPC_BT709:
case AVCOL_SPC_UNSPECIFIED: // comes with UHD
img->repr.sys = PL_COLOR_SYSTEM_BT_709;
img->repr.levels = PL_COLOR_LEVELS_TV;
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;
pl->shift_x = -0.5f;
break;
case AVCOL_SPC_BT2020_NCL:
img->repr.sys = PL_COLOR_SYSTEM_BT_2020_NC;
img->repr.levels = PL_COLOR_LEVELS_TV;
memcpy(&img->repr, &pl_color_repr_uhdtv, sizeof(struct pl_color_repr));
memcpy(&img->color, &pl_color_space_bt2020_hlg, sizeof(struct pl_color_space));
deband.grain = 0.0f; // no grain in HDR
img->color.sig_scale = 1.0f;
pl->shift_x = -0.5f;
#if defined VAAPI || defined USE_DRM
render_params.peak_detect_params = NULL;
#endif
// 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;
img->repr.levels = PL_COLOR_LEVELS_TV;
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;
pl->shift_x = -0.5f;
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 (level == 0)
pl_tex_clear(p->gpu, target->fbo, (float[4]) { 0 }
);
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) {
target->overlays = ovl;
target->num_overlays = 1;
} else {
target->overlays = 0;
target->num_overlays = 0;
}
if (decoder->newchannel && current == 0) {
colors.brightness = -1.0f;
colors.contrast = 0.0f;
if (!pl_render_image(p->renderer, &decoder->pl_images[current], target, &render_params)) {
Debug(3, "Failed rendering frame!\n");
}
return;
}
decoder->newchannel = 0;
if (!pl_render_image(p->renderer, &decoder->pl_images[current], target, &render_params)) {
Debug(3, "Failed rendering frame!\n");
}
if (VideoScalerTest) { // left side test scaler
// 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)) {
Debug(3, "Failed rendering frame!\n");
}
} else if (p->renderertest) {
pl_renderer_destroy(&p->renderertest);
p->renderertest = NULL;
}
#endif
Debug(4, "video/cuvid: 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;
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->plane.texture->params.w != width || pl->plane.texture->params.h != height)) {
// pl_tex_clear(p->gpu, pl->plane.texture, (float[4]) { 0 });
pl_tex_destroy(p->gpu, &pl->plane.texture);
}
// make texture for OSD
if (pl->plane.texture == NULL) {
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_R;
pl->plane.component_mapping[1] = PL_CHANNEL_G;
pl->plane.component_mapping[2] = PL_CHANNEL_B;
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)
{
static uint64_t first_time = 0, round_time = 0;
static uint64_t last_time = 0;
int i;
static unsigned int Count;
int filled;
CuvidDecoder *decoder;
int RTS_flag;
int valid_frame = 0;
float ldiff;
static int first = 1;
float turnaround;
#ifdef PLACEBO
uint64_t diff;
static float fdiff = 23000.0;
struct pl_swapchain_frame frame;
struct pl_render_target target;
bool ok;
VkImage Image;
const struct pl_fmt *fmt;
const float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
#endif
#ifndef PLACEBO
if (CuvidDecoderN)
CuvidDecoders[0]->Frameproc = (float)(GetusTicks() - last_time) / 1000000.0;
#ifdef CUVID
glXMakeCurrent(XlibDisplay, VideoWindow, glxThreadContext);
glXWaitVideoSyncSGI(2, (Count + 1) % 2, &Count); // wait for previous frame to swap
last_time = GetusTicks();
#else
eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglThreadContext);
EglCheck();
#endif
glClear(GL_COLOR_BUFFER_BIT);
#else // PLACEBO
if (CuvidDecoderN) {
ldiff = (float)(GetusTicks() - round_time) / 1000000.0;
if (ldiff < 100.0 && ldiff > 0.0)
CuvidDecoders[0]->Frameproc = (CuvidDecoders[0]->Frameproc + ldiff + ldiff) / 3.0;
}
round_time = GetusTicks();
#if 1
diff = (GetusTicks() - last_time) / 1000;
// last_time = GetusTicks();
// printf("Roundtrip Displayframe %d\n",diff);
if (diff < 5000 && diff > 0) {
// printf("Sleep %d\n",15000-diff);
usleep((5000 - diff)); // * 1000);
}
#endif
if (!p->swapchain)
return;
#ifdef CUVID
VideoThreadLock();
if (!first) {
if (!pl_swapchain_submit_frame(p->swapchain))
Error(_("Failed to submit swapchain buffer\n"));
pl_swapchain_swap_buffers(p->swapchain); // swap buffers
}
#endif
first = 0;
last_time = GetusTicks();
while (!pl_swapchain_start_frame(p->swapchain, &frame)) { // get new frame wait for previous to swap
usleep(5);
}
if (!frame.fbo) {
#ifdef CUVID
VideoThreadUnlock();
#endif
return;
}
#ifdef VAAPI
VideoThreadLock();
#endif
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;
}
#ifdef GAMMA
// target.color.transfer = PL_COLOR_TRC_LINEAR;
#endif
#endif
//
// Render videos into output
//
///
for (i = 0; i < CuvidDecoderN; ++i) {
decoder = CuvidDecoders[i];
decoder->FramesDisplayed++;
decoder->StartCounter++;
filled = atomic_read(&decoder->SurfacesFilled);
//printf("Filled %d\n",filled);
// 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(4, "video/cuvid: black surface displayed\n");
}
continue;
}
valid_frame = 1;
#ifdef PLACEBO
if (OsdShown == 1) { // New OSD opened
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) {
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 && valid_frame) {
GLint texLoc;
int x, y, w, h;
glBindTexture(GL_TEXTURE_2D, 0);
GlxCheck();
if (OsdShown == 1) {
if (OSDtexture)
glDeleteTextures(1, &OSDtexture);
pthread_mutex_lock(&OSDMutex);
glGenTextures(1, &OSDtexture);
glBindTexture(GL_TEXTURE_2D, OSDtexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, OSDxsize, OSDysize, 0, GL_RGBA, GL_UNSIGNED_BYTE, posd);
GlxCheck();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GlxCheck();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glFlush();
pthread_mutex_unlock(&OSDMutex);
OsdShown = 2;
}
GlxCheck();
glBindTexture(GL_TEXTURE_2D, 0);
GlxCheck();
glEnable(GL_BLEND);
GlxCheck();
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
GlxCheck();
y = OSDy * VideoWindowHeight / OsdHeight;
x = OSDx * VideoWindowWidth / OsdWidth;
h = OSDysize * VideoWindowHeight / OsdHeight;
w = OSDxsize * VideoWindowWidth / OsdWidth;
glViewport(x, (VideoWindowHeight - h - y), w, h);
if (gl_prog_osd == 0)
gl_prog_osd = sc_generate_osd(gl_prog_osd); // generate shader programm
glUseProgram(gl_prog_osd);
texLoc = glGetUniformLocation(gl_prog_osd, "texture0");
glUniform1i(texLoc, 0);
glActiveTexture(GL_TEXTURE0);
// pthread_mutex_lock(&OSDMutex);
glBindTexture(GL_TEXTURE_2D, OSDtexture);
render_pass_quad(1, 0, 0);
// pthread_mutex_unlock(&OSDMutex);
glUseProgram(0);
glActiveTexture(GL_TEXTURE0);
// eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglThreadContext);
}
#endif
#ifdef PLACEBO
#ifdef VAAPI
// first_time = GetusTicks();
if (!pl_swapchain_submit_frame(p->swapchain))
Fatal(_("Failed to submit swapchain buffer\n"));
pl_swapchain_swap_buffers(p->swapchain); // swap buffers
#endif
VideoThreadUnlock();
#else // not PLACEBO
#ifdef CUVID
glXGetVideoSyncSGI(&Count); // get current frame
glXSwapBuffers(XlibDisplay, VideoWindow);
glXMakeCurrent(XlibDisplay, None, NULL);
#else
#ifndef USE_DRM
eglSwapBuffers(eglDisplay, eglSurface);
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
#else
drm_swap_buffers();
#endif
#endif
#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;
}
}
///
/// 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) + SWAP_BUFFER_SIZE + 1); // +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,
int *width, int *height, int *color, int *eotf)
{
*missed = decoder->FramesMissed;
*duped = decoder->FramesDuped;
*dropped = decoder->FramesDropped;
*counter = decoder->FrameCounter;
*frametime = decoder->Frameproc;
*width = decoder->InputWidth;
*height = decoder->InputHeight;
*color = decoder->ColorSpace;
*eotf = 0;
}
///
/// Sync decoder output to audio.
///
/// trick-speed show frame <n> times
/// still-picture show frame until new frame arrives
/// 60hz-mode repeat every 5th picture
/// video>audio slow down video by duplicating frames
/// video<audio speed up video by skipping frames
/// soft-start show every second frame
///
/// @param decoder CUVID hw decoder
///
void AudioDelayms(int);
static void CuvidSyncDecoder(CuvidDecoder * decoder)
{
int filled;
int64_t audio_clock;
int64_t video_clock;
int err = 0;
static int speedup = 3;
#ifdef GAMMA
Get_Gamma();
#endif
video_clock = CuvidGetClock(decoder);
filled = atomic_read(&decoder->SurfacesFilled);
if (!decoder->SyncOnAudio) {
audio_clock = AV_NOPTS_VALUE;
// FIXME: 60Hz Mode
goto skip_sync;
}
audio_clock = AudioGetClock();
// printf("Diff %d %#012" PRIx64 " %#012" PRIx64" filled %d \n",(video_clock - audio_clock - VideoAudioDelay)/90,video_clock,audio_clock,filled);
// 60Hz: repeat every 5th field
if (Video60HzMode && !(decoder->FramesDisplayed % 6)) {
if (audio_clock == (int64_t) AV_NOPTS_VALUE || video_clock == (int64_t) AV_NOPTS_VALUE) {
goto out;
}
// both clocks are known
if (audio_clock + VideoAudioDelay <= video_clock + 25 * 90) {
goto out;
}
// out of sync: audio before video
if (!decoder->TrickSpeed) {
goto skip_sync;
}
}
// TrickSpeed
if (decoder->TrickSpeed) {
if (decoder->TrickCounter--) {
goto out;
}
decoder->TrickCounter = decoder->TrickSpeed;
goto skip_sync;
}
#if 0
// at start of new video stream, soft or hard sync video to audio
if (!VideoSoftStartSync && decoder->StartCounter < VideoSoftStartFrames && video_clock != (int64_t) AV_NOPTS_VALUE
&& (audio_clock == (int64_t) AV_NOPTS_VALUE || video_clock > audio_clock + VideoAudioDelay + 120 * 90)) {
Debug(4, "video: initial slow down video, frame %d\n", decoder->StartCounter);
goto skip_sync;
}
#endif
if (decoder->SyncCounter && decoder->SyncCounter--) {
goto skip_sync;
}
if (audio_clock != (int64_t) AV_NOPTS_VALUE && video_clock != (int64_t) AV_NOPTS_VALUE) {
// both clocks are known
int diff;
diff = video_clock - audio_clock - VideoAudioDelay;
// diff = (decoder->LastAVDiff + diff) / 2;
decoder->LastAVDiff = diff;
#if 0
if (abs(diff / 90) > 0) {
printf(" Diff %d filled %d \n", diff / 90, filled);
}
#endif
if (abs(diff) > 5000 * 90) { // more than 5s
err = CuvidMessage(2, "video: audio/video difference too big %d\n", diff / 90);
// decoder->SyncCounter = 1;
// usleep(10);
goto skip_sync;
} else if (diff > 100 * 90) {
err = CuvidMessage(4, "video: slow down video, duping frame %d\n", diff / 90);
++decoder->FramesDuped;
if ((speedup && --speedup) || VideoSoftStartSync)
decoder->SyncCounter = 1;
else
decoder->SyncCounter = 0;
goto out;
} else if (diff > 25 * 90) {
err = CuvidMessage(3, "video: slow down video, duping frame %d \n", diff / 90);
++decoder->FramesDuped;
decoder->SyncCounter = 1;
goto out;
} else if ((diff < -100 * 90)) {
if (filled > 2) {
err = CuvidMessage(3, "video: speed up video, droping frame %d\n", diff / 90);
++decoder->FramesDropped;
CuvidAdvanceDecoderFrame(decoder);
} else if ((diff < -100 * 90)) { // give it some time to get frames to drop
Debug(3, "Delay Audio %d ms\n", abs(diff / 90));
AudioDelayms(abs(diff / 90));
}
decoder->SyncCounter = 1;
} else {
speedup = 2;
}
#if defined(DEBUG) || defined(AV_INFO)
if (!decoder->SyncCounter && decoder->StartCounter < 1000) {
#ifdef DEBUG
Debug(3, "video/cuvid: synced after %d frames %dms\n", decoder->StartCounter, GetMsTicks() - VideoSwitch);
#else
Info("video/cuvid: synced after %d frames\n", decoder->StartCounter);
#endif
decoder->StartCounter += 1000;
}
#endif
}
skip_sync:
// check if next field is available
if (decoder->SurfaceField && filled <= 1 + 2 * decoder->Interlaced) {
if (filled < 1 + 2 * decoder->Interlaced) {
++decoder->FramesDuped;
#if 0
// FIXME: don't warn after stream start, don't warn during pause
err =
CuvidMessage(1, _("video: decoder buffer empty, duping frame (%d/%d) %d v-buf\n"),
decoder->FramesDuped, decoder->FrameCounter, VideoGetBuffers(decoder->Stream));
// some time no new picture or black video configured
if (decoder->Closing < -300 || (VideoShowBlackPicture && decoder->Closing)) {
// clear ring buffer to trigger black picture
atomic_set(&decoder->SurfacesFilled, 0);
}
#endif
}
// Debug(3,"filled zu klein %d Field %d Interlaced %d\n",filled,decoder->SurfaceField,decoder->Interlaced);
// goto out;
}
CuvidAdvanceDecoderFrame(decoder);
out:
#if 0
// defined(DEBUG) || defined(AV_INFO)
// debug audio/video sync
if (err || !(decoder->FramesDisplayed % AV_INFO_TIME)) {
if (!err) {
CuvidMessage(0, NULL);
}
Info("video: %s%+5" PRId64 " %4" PRId64 " %3d/\\ms %3d%+d%+d v-buf\n", Timestamp2String(video_clock),
abs((video_clock - audio_clock) / 90) < 8888 ? ((video_clock - audio_clock) / 90) : 8888,
AudioGetDelay() / 90, (int)VideoDeltaPTS / 90, VideoGetBuffers(decoder->Stream),
decoder->Interlaced ? 2 * atomic_read(&decoder->SurfacesFilled)
- decoder->SurfaceField : atomic_read(&decoder->SurfacesFilled), CuvidOutputSurfaceQueued);
if (!(decoder->FramesDisplayed % (5 * 60 * 60))) {
CuvidPrintFrames(decoder);
}
}
#endif
return; // fix gcc bug!
}
///
/// Sync a video frame.
///
static void CuvidSyncFrame(void)
{
int i;
//
// Sync video decoder to audio
//
for (i = 0; i < CuvidDecoderN; ++i) {
CuvidSyncDecoder(CuvidDecoders[i]);
}
}
///
/// Sync and display surface.
///
static void CuvidSyncDisplayFrame(void)
{
CuvidDisplayFrame();
CuvidSyncFrame();
}
///
/// Sync and render a ffmpeg frame
///
/// @param decoder CUVID hw decoder
/// @param video_ctx ffmpeg video codec context
/// @param frame frame to display
///
static void CuvidSyncRenderFrame(CuvidDecoder * decoder, const AVCodecContext * video_ctx, AVFrame * frame)
{
#if 0
// FIXME: temp debug
if (0 && frame->pkt_pts != (int64_t) AV_NOPTS_VALUE) {
Debug(3, "video: render frame pts %s\n", Timestamp2String(frame->pkt_pts));
}
#endif
#ifdef DEBUG
if (!atomic_read(&decoder->SurfacesFilled)) {
Debug(4, "video: new stream frame %dms\n", GetMsTicks() - VideoSwitch);
}
#endif
// if video output buffer is full, wait and display surface.
// loop for interlace
if (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) {
Fatal("video/cuvid: this code part shouldn't be used\n");
return;
}
// if (!decoder->Closing) {
// VideoSetPts(&decoder->PTS, decoder->Interlaced, video_ctx, frame);
// }
CuvidRenderFrame(decoder, video_ctx, frame);
}
///
/// Set CUVID background color.
///
/// @param rgba 32 bit RGBA color.
///
static void CuvidSetBackground( __attribute__((unused)) uint32_t rgba)
{
}
///
/// Set CUVID video mode.
///
static void CuvidSetVideoMode(void)
{
int i;
Debug(3, "Set video mode %dx%d\n", VideoWindowWidth, VideoWindowHeight);
if (EglEnabled) {
#ifdef CUVID
GlxSetupWindow(VideoWindow, VideoWindowWidth, VideoWindowHeight, glxThreadContext);
#else
GlxSetupWindow(VideoWindow, VideoWindowWidth, VideoWindowHeight, eglContext);
#endif
}
for (i = 0; i < CuvidDecoderN; ++i) {
// reset video window, upper level needs to fix the positions
CuvidDecoders[i]->VideoX = 0;
CuvidDecoders[i]->VideoY = 0;
CuvidDecoders[i]->VideoWidth = VideoWindowWidth;
CuvidDecoders[i]->VideoHeight = VideoWindowHeight;
CuvidUpdateOutput(CuvidDecoders[i]);
}
}
///
/// Handle a CUVID display.
///
static void CuvidDisplayHandlerThread(void)
{
int i;
int err = 0;
int allfull;
int decoded;
int filled;
struct timespec nowtime;
CuvidDecoder *decoder;
allfull = 1;
decoded = 0;
for (i = 0; i < CuvidDecoderN; ++i) {
decoder = CuvidDecoders[i];
//
// fill frame output ring buffer
//
filled = atomic_read(&decoder->SurfacesFilled);
//if (filled <= 1 + 2 * decoder->Interlaced) {
if (filled < 5) {
// FIXME: hot polling
// fetch+decode or reopen
allfull = 0;
err = VideoDecodeInput(decoder->Stream);
} else {
err = VideoPollInput(decoder->Stream);
}
// decoder can be invalid here
if (err) {
// nothing buffered?
if (err == -1 && decoder->Closing) {
decoder->Closing--;
if (!decoder->Closing) {
Debug(3, "video/cuvid: closing eof\n");
decoder->Closing = -1;
}
}
usleep(10 * 1000);
continue;
}
decoded = 1;
}
if (!decoded) { // nothing decoded, sleep
// FIXME: sleep on wakeup
usleep(1 * 1000);
}
usleep(1000);
// all decoder buffers are full
// and display is not preempted
// speed up filling display queue, wait on display queue empty
if (!allfull && !decoder->TrickSpeed) {
clock_gettime(CLOCK_MONOTONIC, &nowtime);
// time for one frame over?
if ((nowtime.tv_sec - CuvidFrameTime.tv_sec) * 1000 * 1000 * 1000 + (nowtime.tv_nsec -
CuvidFrameTime.tv_nsec) < 15 * 1000 * 1000) {
return;
}
}
return;
}
///
/// Set video output position.
///
/// @param decoder CUVID hw decoder
/// @param x video output x coordinate inside the window
/// @param y video output y coordinate inside the window
/// @param width video output width
/// @param height video output height
///
/// @note FIXME: need to know which stream.
///
static void CuvidSetOutputPosition(CuvidDecoder * decoder, int x, int y, int width, int height)
{
Debug(3, "video/cuvid: output %dx%d%+d%+d\n", width, height, x, y);
decoder->VideoX = x;
decoder->VideoY = y;
decoder->VideoWidth = width;
decoder->VideoHeight = height;
// next video pictures are automatic rendered to correct position
}
//----------------------------------------------------------------------------
// CUVID OSD
//----------------------------------------------------------------------------
///
/// CUVID module.
///
static const VideoModule CuvidModule = {
.Name = "cuvid",
.Enabled = 1,
.NewHwDecoder = (VideoHwDecoder * (*const)(VideoStream *)) CuvidNewHwDecoder,
.DelHwDecoder = (void (*const) (VideoHwDecoder *))CuvidDelHwDecoder,
.GetSurface = (unsigned (*const) (VideoHwDecoder *, const AVCodecContext *))CuvidGetVideoSurface,
.ReleaseSurface = (void (*const) (VideoHwDecoder *, unsigned))CuvidReleaseSurface,
.get_format = (enum AVPixelFormat(*const) (VideoHwDecoder *,
AVCodecContext *, const enum AVPixelFormat *))Cuvid_get_format,
.RenderFrame = (void (*const) (VideoHwDecoder *,
const AVCodecContext *, const AVFrame *))CuvidSyncRenderFrame,
.GetHwAccelContext = (void *(*const)(VideoHwDecoder *))CuvidGetHwAccelContext,
.SetClock = (void(*const)(VideoHwDecoder *, int64_t))CuvidSetClock,
.GetClock = (int64_t(*const)(const VideoHwDecoder *))CuvidGetClock,
.SetClosing = (void(*const)(const VideoHwDecoder *))CuvidSetClosing,
.ResetStart = (void(*const)(const VideoHwDecoder *))CuvidResetStart,
.SetTrickSpeed = (void(*const)(const VideoHwDecoder *, int))CuvidSetTrickSpeed,
.GrabOutput = CuvidGrabOutputSurface,
.GetStats = (void(*const)(VideoHwDecoder *, int *, int *, int *,
int *, float *, int *, int *, int *, int *))CuvidGetStats,
.SetBackground = CuvidSetBackground,
.SetVideoMode = CuvidSetVideoMode,
.DisplayHandlerThread = CuvidDisplayHandlerThread,
// .OsdClear = GlxOsdClear,
// .OsdDrawARGB = GlxOsdDrawARGB,
// .OsdInit = GlxOsdInit,
// .OsdExit = GlxOsdExit,
// .OsdClear = CuvidOsdClear,
// .OsdDrawARGB = CuvidOsdDrawARGB,
// .OsdInit = CuvidOsdInit,
// .OsdExit = CuvidOsdExit,
.Exit = CuvidExit,
.Init = CuvidGlxInit,
};
#endif
//----------------------------------------------------------------------------
// NOOP
//----------------------------------------------------------------------------
///
/// Allocate new noop decoder.
///
/// @param stream video stream
///
/// @returns always NULL.
///
static VideoHwDecoder *NoopNewHwDecoder( __attribute__((unused)) VideoStream * stream)
{
return NULL;
}
///
/// Release a surface.
///
/// Can be called while exit.
///
/// @param decoder noop hw decoder
/// @param surface surface no longer used
///
static void NoopReleaseSurface( __attribute__((unused)) VideoHwDecoder * decoder, __attribute__((unused))
unsigned surface)
{
}
///
/// Set noop background color.
///
/// @param rgba 32 bit RGBA color.
///
static void NoopSetBackground( __attribute__((unused)) uint32_t rgba)
{
}
///
/// Noop initialize OSD.
///
/// @param width osd width
/// @param height osd height
///
static void NoopOsdInit( __attribute__((unused))
int width, __attribute__((unused))
int height)
{
}
///
/// Draw OSD ARGB image.
///
/// @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 NoopOsdDrawARGB( __attribute__((unused))
int xi, __attribute__((unused))
int yi, __attribute__((unused))
int width, __attribute__((unused))
int height, __attribute__((unused))
int pitch, __attribute__((unused))
const uint8_t * argb, __attribute__((unused))
int x, __attribute__((unused))
int y)
{
}
///
/// Noop setup.
///
/// @param display_name x11/xcb display name
///
/// @returns always true.
///
static int NoopInit(const char *display_name)
{
Info("video/noop: noop driver running on display '%s'\n", display_name);
return 1;
}
#ifdef USE_VIDEO_THREAD
///
/// Handle a noop display.
///
static void NoopDisplayHandlerThread(void)
{
// avoid 100% cpu use
usleep(20 * 1000);
#if 0
// this can't be canceled
if (XlibDisplay) {
XEvent event;
XPeekEvent(XlibDisplay, &event);
}
#endif
}
#else
#define NoopDisplayHandlerThread NULL
#endif
///
/// Noop void function.
///
static void NoopVoid(void)
{
}
///
/// Noop video module.
///
static const VideoModule NoopModule = {
.Name = "noop",
.Enabled = 1,
.NewHwDecoder = NoopNewHwDecoder,
#if 0
// can't be called:
.DelHwDecoder = NoopDelHwDecoder,
.GetSurface = (unsigned (*const) (VideoHwDecoder *,
const AVCodecContext *))NoopGetSurface,
#endif
.ReleaseSurface = NoopReleaseSurface,
#if 0
.get_format = (enum AVPixelFormat(*const) (VideoHwDecoder *,
AVCodecContext *, const enum AVPixelFormat *))Noop_get_format,
.RenderFrame = (void (*const) (VideoHwDecoder *,
const AVCodecContext *, const AVFrame *))NoopSyncRenderFrame,
.GetHwAccelContext = (void *(*const)(VideoHwDecoder *))
DummyGetHwAccelContext,
.SetClock = (void(*const)(VideoHwDecoder *, int64_t))NoopSetClock,
.GetClock = (int64_t(*const)(const VideoHwDecoder *))NoopGetClock,
.SetClosing = (void(*const)(const VideoHwDecoder *))NoopSetClosing,
.ResetStart = (void(*const)(const VideoHwDecoder *))NoopResetStart,
.SetTrickSpeed = (void(*const)(const VideoHwDecoder *, int))NoopSetTrickSpeed,
.GrabOutput = NoopGrabOutputSurface,
.GetStats = (void(*const)(VideoHwDecoder *, int *, int *, int *,
int *, float *, int *, int *, int *, int *))NoopGetStats,
#endif
.SetBackground = NoopSetBackground,
.SetVideoMode = NoopVoid,
.DisplayHandlerThread = NoopDisplayHandlerThread,
.OsdClear = NoopVoid,
.OsdDrawARGB = NoopOsdDrawARGB,
.OsdInit = NoopOsdInit,
.OsdExit = NoopVoid,
.Init = NoopInit,
.Exit = NoopVoid,
};
//----------------------------------------------------------------------------
// OSD
//----------------------------------------------------------------------------
///
/// Clear the OSD.
///
/// @todo I use glTexImage2D to clear the texture, are there faster and
/// better ways to clear a texture?
///
void VideoOsdClear(void)
{
#ifdef PLACEBO
OsdShown = 0;
#else
VideoThreadLock();
// VideoUsedModule->OsdClear();
OsdDirtyX = OsdWidth; // reset dirty area
OsdDirtyY = OsdHeight;
OsdDirtyWidth = 0;
OsdDirtyHeight = 0;
OsdShown = 0;
VideoThreadUnlock();
#endif
}
///
/// Draw an OSD ARGB image.
///
/// @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
///
void VideoOsdDrawARGB(int xi, int yi, int width, int height, int pitch, const uint8_t * argb, int x, int y)
{
VideoThreadLock();
// update dirty area
if (x < OsdDirtyX) {
if (OsdDirtyWidth) {
OsdDirtyWidth += OsdDirtyX - x;
}
OsdDirtyX = x;
}
if (y < OsdDirtyY) {
if (OsdDirtyHeight) {
OsdDirtyHeight += OsdDirtyY - y;
}
OsdDirtyY = y;
}
if (x + width > OsdDirtyX + OsdDirtyWidth) {
OsdDirtyWidth = x + width - OsdDirtyX;
}
if (y + height > OsdDirtyY + OsdDirtyHeight) {
OsdDirtyHeight = y + height - OsdDirtyY;
}
Debug(3, "video: osd dirty %dx%d%+d%+d -> %dx%d%+d%+d\n", width, height, x, y, OsdDirtyWidth, OsdDirtyHeight,
OsdDirtyX, OsdDirtyY);
VideoThreadUnlock();
}
void ActivateOsd(GLuint texture, int x, int y, int xsize, int ysize)
{
//printf("OSD open %d %d %d %d\n",x,y,xsize,ysize);
OSDfb = texture;
// OSDtexture = texture;
OSDx = x;
OSDy = y;
OSDxsize = xsize;
OSDysize = ysize;
OsdShown = 1;
}
///
/// Get OSD size.
///
/// @param[out] width OSD width
/// @param[out] height OSD height
///
void VideoGetOsdSize(int *width, int *height)
{
*width = 1920;
*height = 1080; // unknown default
if (OsdWidth && OsdHeight) {
*width = OsdWidth;
*height = OsdHeight;
}
}
/// Set OSD Size.
///
/// @param width OSD width
/// @param height OSD height
///
void VideoSetOsdSize(int width, int height)
{
if (OsdConfigWidth != width || OsdConfigHeight != height) {
VideoOsdExit();
OsdConfigWidth = width;
OsdConfigHeight = height;
VideoOsdInit();
}
}
///
/// Set the 3d OSD mode.
///
/// @param mode OSD mode (0=off, 1=SBS, 2=Top Bottom)
///
void VideoSetOsd3DMode(int mode)
{
Osd3DMode = mode;
}
///
/// Setup osd.
///
/// FIXME: looking for BGRA, but this fourcc isn't supported by the
/// drawing functions yet.
///
void VideoOsdInit(void)
{
if (OsdConfigWidth && OsdConfigHeight) {
OsdWidth = OsdConfigWidth;
OsdHeight = OsdConfigHeight;
} else {
OsdWidth = VideoWindowWidth;
OsdHeight = VideoWindowHeight;
}
if (posd)
free(posd);
posd = (unsigned char *)calloc((OsdWidth + 1) * (OsdHeight + 1) * 4, 1);
VideoOsdClear();
}
///
/// Cleanup OSD.
///
void VideoOsdExit(void)
{
OsdDirtyWidth = 0;
OsdDirtyHeight = 0;
VideoOsdClear();
}
//----------------------------------------------------------------------------
// Events
//----------------------------------------------------------------------------
/// C callback feed key press
extern void FeedKeyPress(const char *, const char *, int, int, const char *);
///
/// Handle XLib I/O Errors.
///
/// @param display display with i/o error
///
static int VideoIOErrorHandler( __attribute__((unused)) Display * display)
{
Error(_("video: fatal i/o error\n"));
// should be called from VideoThread
if (VideoThread && VideoThread == pthread_self()) {
Debug(3, "video: called from video thread\n");
VideoUsedModule = &NoopModule;
XlibDisplay = NULL;
VideoWindow = XCB_NONE;
#ifdef USE_VIDEO_THREAD
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_cond_destroy(&VideoWakeupCond);
pthread_mutex_destroy(&VideoLockMutex);
pthread_mutex_destroy(&VideoMutex);
VideoThread = 0;
pthread_exit("video thread exit");
#endif
}
do {
sleep(1000);
} while (1); // let other threads running
return -1;
}
///
/// Handle X11 events.
///
/// @todo Signal WmDeleteMessage to application.
///
static void VideoEvent(void)
{
XEvent event;
KeySym keysym;
const char *keynam;
char buf[64];
char letter[64];
int letter_len;
uint32_t values[1];
VideoThreadLock();
XNextEvent(XlibDisplay, &event);
VideoThreadUnlock();
switch (event.type) {
case ClientMessage:
Debug(3, "video/event: ClientMessage\n");
if (event.xclient.data.l[0] == (long)WmDeleteWindowAtom) {
Debug(3, "video/event: wm-delete-message\n");
FeedKeyPress("XKeySym", "Close", 0, 0, NULL);
}
break;
case MapNotify:
Debug(3, "video/event: MapNotify\n");
// wm workaround
VideoThreadLock();
xcb_change_window_attributes(Connection, VideoWindow, XCB_CW_CURSOR, &VideoBlankCursor);
VideoThreadUnlock();
VideoBlankTick = 0;
break;
case Expose:
// Debug(3, "video/event: Expose\n");
break;
case ReparentNotify:
Debug(3, "video/event: ReparentNotify\n");
break;
case ConfigureNotify:
// Debug(3, "video/event: ConfigureNotify\n");
VideoSetVideoMode(event.xconfigure.x, event.xconfigure.y, event.xconfigure.width, event.xconfigure.height);
break;
case ButtonPress:
VideoSetFullscreen(-1);
break;
case KeyPress:
VideoThreadLock();
letter_len = XLookupString(&event.xkey, letter, sizeof(letter) - 1, &keysym, NULL);
VideoThreadUnlock();
if (letter_len < 0) {
letter_len = 0;
}
letter[letter_len] = '\0';
if (keysym == NoSymbol) {
Warning(_("video/event: No symbol for %d\n"), event.xkey.keycode);
break;
}
VideoThreadLock();
keynam = XKeysymToString(keysym);
VideoThreadUnlock();
// check for key modifiers (Alt/Ctrl)
if (event.xkey.state & (Mod1Mask | ControlMask)) {
if (event.xkey.state & Mod1Mask) {
strcpy(buf, "Alt+");
} else {
buf[0] = '\0';
}
if (event.xkey.state & ControlMask) {
strcat(buf, "Ctrl+");
}
strncat(buf, keynam, sizeof(buf) - 10);
keynam = buf;
}
FeedKeyPress("XKeySym", keynam, 0, 0, letter);
break;
case KeyRelease:
break;
case MotionNotify:
values[0] = XCB_NONE;
VideoThreadLock();
xcb_change_window_attributes(Connection, VideoWindow, XCB_CW_CURSOR, values);
VideoThreadUnlock();
VideoBlankTick = GetMsTicks();
break;
default:
#if 0
if (XShmGetEventBase(XlibDisplay) + ShmCompletion == event.type) {
// printf("ShmCompletion\n");
}
#endif
Debug(3, "Unsupported event type %d\n", event.type);
break;
}
}
///
/// Poll all x11 events.
///
void VideoPollEvent(void)
{
// hide cursor, after xx ms
if (VideoBlankTick && VideoWindow != XCB_NONE && VideoBlankTick + 200 < GetMsTicks()) {
VideoThreadLock();
xcb_change_window_attributes(Connection, VideoWindow, XCB_CW_CURSOR, &VideoBlankCursor);
VideoThreadUnlock();
VideoBlankTick = 0;
}
while (XlibDisplay) {
VideoThreadLock();
if (!XPending(XlibDisplay)) {
VideoThreadUnlock();
break;
}
VideoThreadUnlock();
VideoEvent();
}
}
void VideoSetVideoEventCallback(void (*videoEventCallback)(void))
{
VideoEventCallback = videoEventCallback;
}
//----------------------------------------------------------------------------
// Thread
//----------------------------------------------------------------------------
#ifdef USE_VIDEO_THREAD
#ifdef PLACEBO
void pl_log_intern(void *stream, enum pl_log_level level, const char *msg)
{
static const char *prefix[] = {
[PL_LOG_FATAL] = "fatal",
[PL_LOG_ERR] = "error",
[PL_LOG_WARN] = "warn",
[PL_LOG_INFO] = "info",
[PL_LOG_DEBUG] = "debug",
[PL_LOG_TRACE] = "trace",
};
printf("%5s: %s\n", prefix[level], msg);
}
void InitPlacebo()
{
struct pl_vulkan_params params;
struct pl_vk_inst_params iparams = pl_vk_inst_default_params;
VkXcbSurfaceCreateInfoKHR xcbinfo;
char xcbext[] = { "VK_KHR_xcb_surface" };
char surfext[] = { "VK_KHR_surface" };
Debug(3, "Init Placebo mit API %d\n", PL_API_VER);
p = calloc(1, sizeof(struct priv));
if (!p)
Fatal(_("Cant get memory for PLACEBO struct"));
// Create context
p->context.log_cb = &pl_log_intern;
p->context.log_level = PL_LOG_WARN;
p->ctx = pl_context_create(PL_API_VER, &p->context);
if (!p->ctx) {
Fatal(_("Failed initializing libplacebo\n"));
}
// create Vulkan instance
memcpy(&iparams, &pl_vk_inst_default_params, sizeof(iparams));
// iparams.debug = true;
iparams.num_extensions = 2;
iparams.extensions = malloc(2 * sizeof(const char *));
*iparams.extensions = surfext;
iparams.debug = false;
*(iparams.extensions + 1) = xcbext;
p->vk_inst = pl_vk_inst_create(p->ctx, &iparams);
free(iparams.extensions);
// create XCB surface for swapchain
xcbinfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
xcbinfo.pNext = NULL;
xcbinfo.flags = 0;
xcbinfo.connection = Connection;
xcbinfo.window = VideoWindow;
if (vkCreateXcbSurfaceKHR(p->vk_inst->instance, &xcbinfo, NULL, &p->pSurface) != VK_SUCCESS) {
Fatal(_("Failed to create XCB Surface\n"));
}
// create Vulkan device
memcpy(&params, &pl_vulkan_default_params, sizeof(params));
params.instance = p->vk_inst->instance;
params.async_transfer = true;
params.async_compute = true;
params.queue_count = 16;
params.surface = p->pSurface;
params.allow_software = false;
p->vk = pl_vulkan_create(p->ctx, &params);
if (!p->vk)
Fatal(_("Failed to create Vulkan Device"));
p->gpu = p->vk->gpu;
if (!(p->gpu->import_caps.tex & PL_HANDLE_DMA_BUF)) {
p->has_dma_buf = 0;
Debug(3, "No support for dma_buf import in Vulkan\n");
} else {
p->has_dma_buf = 1;
Debug(3, "dma_buf support in Vulkan available\n");
}
#if 1
// Create initial swapchain
p->swapchain = pl_vulkan_create_swapchain(p->vk, &(struct pl_vulkan_swapchain_params) {
.surface = p->pSurface,
.present_mode = VK_PRESENT_MODE_FIFO_KHR,
.swapchain_depth = SWAP_BUFFER_SIZE,
});
if (!p->swapchain) {
Fatal(_("Failed creating vulkan swapchain!"));
}
// create renderer
p->renderer = pl_renderer_create(p->ctx, p->gpu);
if (!p->renderer) {
Fatal(_("Failed initializing libplacebo renderer\n"));
}
#endif
Debug(3, "Placebo: init ok");
}
#endif
///
/// Video render thread.
///
void delete_decode()
{
Debug(3, "decoder thread exit\n");
}
static void *VideoDisplayHandlerThread(void *dummy)
{
prctl(PR_SET_NAME, "video decoder", 0, 0, 0);
sleep(2);
pthread_cleanup_push(delete_decode, NULL);
for (;;) {
// fix dead-lock with CuvidExit
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_testcancel();
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
VideoUsedModule->DisplayHandlerThread();
}
pthread_cleanup_pop(NULL);
return dummy;
}
void exit_display()
{
#ifdef GAMMA
Exit_Gamma();
#endif
#ifdef PLACEBO
Debug(3, "delete placebo\n");
if (p == NULL)
return;
if (osdoverlay.plane.texture)
pl_tex_destroy(p->gpu, &osdoverlay.plane.texture);
pl_renderer_destroy(&p->renderer);
if (p->renderertest) {
pl_renderer_destroy(&p->renderertest);
p->renderertest = NULL;
}
pl_swapchain_destroy(&p->swapchain);
vkDestroySurfaceKHR(p->vk_inst->instance, p->pSurface, NULL);
pl_vk_inst_destroy(&p->vk_inst);
// pl_vulkan_destroy(&p->vk);
pl_context_destroy(&p->ctx);
free(p);
p = NULL;
#endif
#ifdef CUVID
if (glxThreadContext) {
glXDestroyContext(XlibDisplay, glxThreadContext);
GlxCheck();
glxThreadContext = NULL;
}
#else
if (eglThreadContext) {
eglDestroyContext(eglDisplay, eglThreadContext);
EglCheck();
eglThreadContext = NULL;
}
#endif
Debug(3, "display thread exit\n");
}
static void *VideoHandlerThread(void *dummy)
{
EGLint contextAttrs[] = {
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};
prctl(PR_SET_NAME, "video display", 0, 0, 0);
#ifdef GAMMA
Init_Gamma();
Set_Gamma(0.0, 6500);
#endif
#ifdef PLACEBO
InitPlacebo();
#else
#ifdef CUVID
if (EglEnabled) {
glxThreadContext = glXCreateContext(XlibDisplay, GlxVisualInfo, glxSharedContext, GL_TRUE);
GlxSetupWindow(VideoWindow, VideoWindowWidth, VideoWindowHeight, glxThreadContext);
}
#endif
#ifdef VAAPI
eglThreadContext = eglCreateContext(eglDisplay, eglConfig, eglSharedContext, contextAttrs);
if (!eglThreadContext) {
EglCheck();
Fatal(_("video/egl: can't create thread egl context\n"));
return NULL;
}
eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglThreadContext);
#endif
#endif
pthread_cleanup_push(exit_display, NULL);
for (;;) {
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_testcancel();
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
#ifndef USE_DRM
VideoPollEvent();
#endif
// first_time = GetusTicks();
CuvidSyncDisplayFrame();
// printf("syncdisplayframe exec %d\n",(GetusTicks()-first_time)/1000);
}
pthread_cleanup_pop(NULL);
return dummy;
}
///
/// Initialize video threads.
///
static void VideoThreadInit(void)
{
#ifndef PLACEBO
#ifdef CUVID
glXMakeCurrent(XlibDisplay, None, NULL);
#else
// eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglContext);
#endif
#endif
pthread_mutex_init(&VideoMutex, NULL);
pthread_mutex_init(&VideoLockMutex, NULL);
pthread_mutex_init(&OSDMutex, NULL);
pthread_cond_init(&VideoWakeupCond, NULL);
pthread_create(&VideoThread, NULL, VideoDisplayHandlerThread, NULL);
pthread_create(&VideoDisplayThread, NULL, VideoHandlerThread, NULL);
}
///
/// Exit and cleanup video threads.
///
static void VideoThreadExit(void)
{
if (VideoThread) {
void *retval;
Debug(3, "video: video thread canceled\n");
// FIXME: can't cancel locked
if (pthread_cancel(VideoThread)) {
Debug(3, "video: can't queue cancel video display thread\n");
}
usleep(200000); // 200ms
if (pthread_join(VideoThread, &retval) || retval != PTHREAD_CANCELED) {
Debug(3, "video: can't cancel video decoder thread\n");
}
if (VideoDisplayThread) {
if (pthread_cancel(VideoDisplayThread)) {
Debug(3, "video: can't queue cancel video display thread\n");
}
usleep(200000); // 200ms
if (pthread_join(VideoDisplayThread, &retval) || retval != PTHREAD_CANCELED) {
Debug(3, "video: can't cancel video display thread\n");
}
VideoDisplayThread = 0;
}
VideoThread = 0;
pthread_cond_destroy(&VideoWakeupCond);
pthread_mutex_destroy(&VideoLockMutex);
pthread_mutex_destroy(&VideoMutex);
pthread_mutex_destroy(&OSDMutex);
#ifndef PLACEBO
if (OSDtexture)
glDeleteTextures(1, &OSDtexture);
if (gl_prog_osd) {
glDeleteProgram(gl_prog_osd);
gl_prog_osd = 0;
}
if (gl_prog) {
glDeleteProgram(gl_prog);
gl_prog = 0;
}
#endif
}
}
///
/// Video display wakeup.
///
/// New video arrived, wakeup video thread.
///
void VideoDisplayWakeup(void)
{
#ifndef USE_DRM
if (!XlibDisplay) { // not yet started
return;
}
#endif
if (!VideoThread) { // start video thread, if needed
VideoThreadInit();
}
}
#endif
//----------------------------------------------------------------------------
// Video API
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
///
/// Table of all video modules.
///
static const VideoModule *VideoModules[] = {
&CuvidModule,
&NoopModule
};
///
/// Video hardware decoder
///
struct _video_hw_decoder_
{
union
{
CuvidDecoder Cuvid; ///< cuvid decoder structure
};
};
///
/// Allocate new video hw decoder.
///
/// @param stream video stream
///
/// @returns a new initialized video hardware decoder.
///
VideoHwDecoder *VideoNewHwDecoder(VideoStream * stream)
{
VideoHwDecoder *hw;
VideoThreadLock();
hw = VideoUsedModule->NewHwDecoder(stream);
VideoThreadUnlock();
return hw;
}
///
/// Destroy a video hw decoder.
///
/// @param hw_decoder video hardware decoder
///
void VideoDelHwDecoder(VideoHwDecoder * hw_decoder)
{
if (hw_decoder) {
#ifdef DEBUG
if (!pthread_equal(pthread_self(), VideoThread)) {
Debug(3, "video: should only be called from inside the thread\n");
}
#endif
// only called from inside the thread
// VideoThreadLock();
VideoUsedModule->DelHwDecoder(hw_decoder);
// VideoThreadUnlock();
}
}
///
/// Get a free hardware decoder surface.
///
/// @param hw_decoder video hardware decoder
/// @param video_ctx ffmpeg video codec context
///
/// @returns the oldest free surface or invalid surface
///
unsigned VideoGetSurface(VideoHwDecoder * hw_decoder, const AVCodecContext * video_ctx)
{
return VideoUsedModule->GetSurface(hw_decoder, video_ctx);
}
///
/// Release a hardware decoder surface.
///
/// @param hw_decoder video hardware decoder
/// @param surface surface no longer used
///
void VideoReleaseSurface(VideoHwDecoder * hw_decoder, unsigned surface)
{
// FIXME: must be guarded against calls, after VideoExit
VideoUsedModule->ReleaseSurface(hw_decoder, surface);
}
///
/// Callback to negotiate the PixelFormat.
///
/// @param hw_decoder video hardware decoder
/// @param video_ctx ffmpeg video codec context
/// @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.
///
enum AVPixelFormat Video_get_format(VideoHwDecoder * hw_decoder, AVCodecContext * video_ctx,
const enum AVPixelFormat *fmt)
{
#ifdef DEBUG
int ms_delay;
// FIXME: use frame time
ms_delay = (1000 * video_ctx->time_base.num * video_ctx->ticks_per_frame)
/ video_ctx->time_base.den;
Debug(3, "video: ready %s %2dms/frame %dms\n", Timestamp2String(VideoGetClock(hw_decoder)), ms_delay,
GetMsTicks() - VideoSwitch);
#endif
return VideoUsedModule->get_format(hw_decoder, video_ctx, fmt);
}
///
/// Display a ffmpeg frame
///
/// @param hw_decoder video hardware decoder
/// @param video_ctx ffmpeg video codec context
/// @param frame frame to display
///
void VideoRenderFrame(VideoHwDecoder * hw_decoder, const AVCodecContext * video_ctx, const AVFrame * frame)
{
#if 0
fprintf(stderr, "video: render frame pts %s closing %d\n", Timestamp2String(frame->pkt_pts),
hw_decoder->Cuvid.Closing);
#endif
if (frame->repeat_pict && !VideoIgnoreRepeatPict) {
Warning(_("video: repeated pict %d found, but not handled\n"), frame->repeat_pict);
}
VideoUsedModule->RenderFrame(hw_decoder, video_ctx, frame);
}
///
/// Get hwaccel context for ffmpeg.
///
/// FIXME: new ffmpeg supports cuvid hw context
///
/// @param hw_decoder video hardware decoder (must be VA-API)
///
void *VideoGetHwAccelContext(VideoHwDecoder * hw_decoder)
{
return VideoUsedModule->GetHwAccelContext(hw_decoder);
}
///
/// Set video clock.
///
/// @param hw_decoder video hardware decoder
/// @param pts audio presentation timestamp
///
void VideoSetClock(VideoHwDecoder * hw_decoder, int64_t pts)
{
Debug(3, "video: set clock %s\n", Timestamp2String(pts));
if (hw_decoder) {
VideoUsedModule->SetClock(hw_decoder, pts);
}
}
///
/// Get video clock.
///
/// @param hw_decoder video hardware decoder
///
/// @note this isn't monoton, decoding reorders frames, setter keeps it
/// monotonic
///
int64_t VideoGetClock(const VideoHwDecoder * hw_decoder)
{
if (hw_decoder) {
return VideoUsedModule->GetClock(hw_decoder);
}
return AV_NOPTS_VALUE;
}
///
/// Set closing stream flag.
///
/// @param hw_decoder video hardware decoder
///
void VideoSetClosing(VideoHwDecoder * hw_decoder)
{
Debug(3, "video: set closing\n");
VideoUsedModule->SetClosing(hw_decoder);
// clear clock to avoid further sync
VideoSetClock(hw_decoder, AV_NOPTS_VALUE);
}
///
/// Reset start of frame counter.
///
/// @param hw_decoder video hardware decoder
///
void VideoResetStart(VideoHwDecoder * hw_decoder)
{
Debug(3, "video: reset start\n");
VideoUsedModule->ResetStart(hw_decoder);
// clear clock to trigger new video stream
VideoSetClock(hw_decoder, AV_NOPTS_VALUE);
}
///
/// Set trick play speed.
///
/// @param hw_decoder video hardware decoder
/// @param speed trick speed (0 = normal)
///
void VideoSetTrickSpeed(VideoHwDecoder * hw_decoder, int speed)
{
Debug(3, "video: set trick-speed %d\n", speed);
VideoUsedModule->SetTrickSpeed(hw_decoder, speed);
}
///
/// Grab full screen image.
///
/// @param size[out] size of allocated image
/// @param width[in,out] width of image
/// @param height[in,out] height of image
///
uint8_t *VideoGrab(int *size, int *width, int *height, int write_header)
{
Debug(3, "video: grab\n");
#ifdef USE_GRAB
if (VideoUsedModule->GrabOutput) {
uint8_t *data;
uint8_t *rgb;
char buf[64];
int i;
int n;
int scale_width;
int scale_height;
int x;
int y;
double src_x;
double src_y;
double scale_x;
double scale_y;
scale_width = *width;
scale_height = *height;
n = 0;
data = VideoUsedModule->GrabOutput(size, width, height, 1);
if (data == NULL)
return NULL;
if (scale_width <= 0) {
scale_width = *width;
}
if (scale_height <= 0) {
scale_height = *height;
}
// hardware didn't scale for us, use simple software scaler
if (scale_width != *width && scale_height != *height) {
if (write_header) {
n = snprintf(buf, sizeof(buf), "P6\n%d\n%d\n255\n", scale_width, scale_height);
}
rgb = malloc(scale_width * scale_height * 3 + n);
if (!rgb) {
Error(_("video: out of memory\n"));
free(data);
return NULL;
}
*size = scale_width * scale_height * 3 + n;
memcpy(rgb, buf, n); // header
scale_x = (double)*width / scale_width;
scale_y = (double)*height / scale_height;
src_y = 0.0;
for (y = 0; y < scale_height; y++) {
int o;
src_x = 0.0;
o = (int)src_y **width;
for (x = 0; x < scale_width; x++) {
i = 4 * (o + (int)src_x);
rgb[n + (x + y * scale_width) * 3 + 0] = data[i + 2];
rgb[n + (x + y * scale_width) * 3 + 1] = data[i + 1];
rgb[n + (x + y * scale_width) * 3 + 2] = data[i + 0];
src_x += scale_x;
}
src_y += scale_y;
}
*width = scale_width;
*height = scale_height;
// grabed image of correct size convert BGRA -> RGB
} else {
if (write_header) {
n = snprintf(buf, sizeof(buf), "P6\n%d\n%d\n255\n", *width, *height);
}
rgb = malloc(*width * *height * 3 + n);
if (!rgb) {
Error(_("video: out of memory\n"));
free(data);
return NULL;
}
memcpy(rgb, buf, n); // header
for (i = 0; i < *size / 4; ++i) { // convert bgra -> rgb
rgb[n + i * 3 + 0] = data[i * 4 + 2];
rgb[n + i * 3 + 1] = data[i * 4 + 1];
rgb[n + i * 3 + 2] = data[i * 4 + 0];
}
*size = *width * *height * 3 + n;
}
free(data);
return rgb;
} else
#endif
{
Warning(_("softhddev: grab unsupported\n"));
}
(void)size;
(void)width;
(void)height;
(void)write_header;
return NULL;
}
///
/// Grab image service.
///
/// @param size[out] size of allocated image
/// @param width[in,out] width of image
/// @param height[in,out] height of image
///
uint8_t *VideoGrabService(int *size, int *width, int *height)
{
// Debug(3, "video: grab service\n");
#ifdef USE_GRAB
if (VideoUsedModule->GrabOutput) {
return VideoUsedModule->GrabOutput(size, width, height, 0);
} else
#endif
{
Warning(_("softhddev: grab unsupported\n"));
}
(void)size;
(void)width;
(void)height;
return NULL;
}
///
/// Get decoder statistics.
///
/// @param hw_decoder video hardware decoder
/// @param[out] missed missed frames
/// @param[out] duped duped frames
/// @param[out] dropped dropped frames
/// @param[out] count number of decoded frames
///
void VideoGetStats(VideoHwDecoder * hw_decoder, int *missed, int *duped, int *dropped, int *counter, float *frametime,
int *width, int *height, int *color, int *eotf)
{
VideoUsedModule->GetStats(hw_decoder, missed, duped, dropped, counter, frametime, width, height, color, eotf);
}
///
/// Get decoder video stream size.
///
/// @param hw_decoder video hardware decoder
/// @param[out] width video stream width
/// @param[out] height video stream height
/// @param[out] aspect_num video stream aspect numerator
/// @param[out] aspect_den video stream aspect denominator
///
void VideoGetVideoSize(VideoHwDecoder * hw_decoder, int *width, int *height, int *aspect_num, int *aspect_den)
{
*width = 1920;
*height = 1080;
*aspect_num = 16;
*aspect_den = 9;
// FIXME: test to check if working, than make module function
if (VideoUsedModule == &CuvidModule) {
*width = hw_decoder->Cuvid.InputWidth;
*height = hw_decoder->Cuvid.InputHeight;
av_reduce(aspect_num, aspect_den, hw_decoder->Cuvid.InputWidth * hw_decoder->Cuvid.InputAspect.num,
hw_decoder->Cuvid.InputHeight * hw_decoder->Cuvid.InputAspect.den, 1024 * 1024);
}
}
#ifdef USE_SCREENSAVER
//----------------------------------------------------------------------------
// DPMS / Screensaver
//----------------------------------------------------------------------------
///
/// Suspend X11 screen saver.
///
/// @param connection X11 connection to enable/disable screensaver
/// @param suspend True suspend screensaver,
/// false enable screensaver
///
static void X11SuspendScreenSaver(xcb_connection_t * connection, int suspend)
{
const xcb_query_extension_reply_t *query_extension_reply;
query_extension_reply = xcb_get_extension_data(connection, &xcb_screensaver_id);
if (query_extension_reply && query_extension_reply->present) {
xcb_screensaver_query_version_cookie_t cookie;
xcb_screensaver_query_version_reply_t *reply;
Debug(3, "video: screen saver extension present\n");
cookie =
xcb_screensaver_query_version_unchecked(connection, XCB_SCREENSAVER_MAJOR_VERSION,
XCB_SCREENSAVER_MINOR_VERSION);
reply = xcb_screensaver_query_version_reply(connection, cookie, NULL);
if (reply && (reply->server_major_version >= XCB_SCREENSAVER_MAJOR_VERSION)
&& (reply->server_minor_version >= XCB_SCREENSAVER_MINOR_VERSION)
) {
xcb_screensaver_suspend(connection, suspend);
}
free(reply);
}
}
///
/// DPMS (Display Power Management Signaling) extension available.
///
/// @param connection X11 connection to check for DPMS
///
static int X11HaveDPMS(xcb_connection_t * connection)
{
static int have_dpms = -1;
const xcb_query_extension_reply_t *query_extension_reply;
if (have_dpms != -1) { // already checked
return have_dpms;
}
have_dpms = 0;
query_extension_reply = xcb_get_extension_data(connection, &xcb_dpms_id);
if (query_extension_reply && query_extension_reply->present) {
xcb_dpms_get_version_cookie_t cookie;
xcb_dpms_get_version_reply_t *reply;
int major;
int minor;
Debug(3, "video: dpms extension present\n");
cookie = xcb_dpms_get_version_unchecked(connection, XCB_DPMS_MAJOR_VERSION, XCB_DPMS_MINOR_VERSION);
reply = xcb_dpms_get_version_reply(connection, cookie, NULL);
// use locals to avoid gcc warning
major = XCB_DPMS_MAJOR_VERSION;
minor = XCB_DPMS_MINOR_VERSION;
if (reply && (reply->server_major_version >= major)
&& (reply->server_minor_version >= minor)
) {
have_dpms = 1;
}
free(reply);
}
return have_dpms;
}
///
/// Disable DPMS (Display Power Management Signaling)
///
/// @param connection X11 connection to disable DPMS
///
static void X11DPMSDisable(xcb_connection_t * connection)
{
if (X11HaveDPMS(connection)) {
xcb_dpms_info_cookie_t cookie;
xcb_dpms_info_reply_t *reply;
cookie = xcb_dpms_info_unchecked(connection);
reply = xcb_dpms_info_reply(connection, cookie, NULL);
if (reply) {
if (reply->state) {
Debug(3, "video: dpms was enabled\n");
xcb_dpms_disable(connection); // monitor powersave off
}
free(reply);
}
DPMSDisabled = 1;
}
}
///
/// Reenable DPMS (Display Power Management Signaling)
///
/// @param connection X11 connection to enable DPMS
///
static void X11DPMSReenable(xcb_connection_t * connection)
{
if (DPMSDisabled && X11HaveDPMS(connection)) {
xcb_dpms_enable(connection); // monitor powersave on
xcb_dpms_force_level(connection, XCB_DPMS_DPMS_MODE_ON);
DPMSDisabled = 0;
}
}
#else
/// dummy function: Suspend X11 screen saver.
#define X11SuspendScreenSaver(connection, suspend)
/// dummy function: Disable X11 DPMS.
#define X11DPMSDisable(connection)
/// dummy function: Reenable X11 DPMS.
#define X11DPMSReenable(connection)
#endif
//----------------------------------------------------------------------------
// Setup
//----------------------------------------------------------------------------
///
/// Create main window.
///
/// @param parent parent of new window
/// @param visual visual of parent
/// @param depth depth of parent
///
static void VideoCreateWindow(xcb_window_t parent, xcb_visualid_t visual, uint8_t depth)
{
uint32_t values[4];
xcb_intern_atom_reply_t *reply;
xcb_pixmap_t pixmap;
xcb_cursor_t cursor;
Debug(3, "video: visual %#0x depth %d\n", visual, depth);
// Color map
VideoColormap = xcb_generate_id(Connection);
xcb_create_colormap(Connection, XCB_COLORMAP_ALLOC_NONE, VideoColormap, parent, visual);
values[0] = 0;
values[1] = 0;
values[2] =
XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_STRUCTURE_NOTIFY;
values[3] = VideoColormap;
VideoWindow = xcb_generate_id(Connection);
xcb_create_window(Connection, depth, VideoWindow, parent, VideoWindowX, VideoWindowY, VideoWindowWidth,
VideoWindowHeight, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual,
XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP, values);
Debug(3, "Create Window at %d,%d\n", VideoWindowX, VideoWindowY);
// define only available with xcb-utils-0.3.8
#ifdef XCB_ICCCM_NUM_WM_SIZE_HINTS_ELEMENTS
// FIXME: utf _NET_WM_NAME
xcb_icccm_set_wm_name(Connection, VideoWindow, XCB_ATOM_STRING, 8, sizeof("softhdcuvid") - 1, "softhdcuvid");
xcb_icccm_set_wm_icon_name(Connection, VideoWindow, XCB_ATOM_STRING, 8, sizeof("softhdcuvid") - 1, "softhdcuvid");
#endif
// define only available with xcb-utils-0.3.6
#ifdef XCB_NUM_WM_HINTS_ELEMENTS
// FIXME: utf _NET_WM_NAME
xcb_set_wm_name(Connection, VideoWindow, XCB_ATOM_STRING, sizeof("softhdcuvid") - 1, "softhdcuvid");
xcb_set_wm_icon_name(Connection, VideoWindow, XCB_ATOM_STRING, sizeof("softhdcuvid") - 1, "softhdcuvid");
#endif
// FIXME: size hints
// register interest in the delete window message
if ((reply =
xcb_intern_atom_reply(Connection, xcb_intern_atom(Connection, 0, sizeof("WM_DELETE_WINDOW") - 1,
"WM_DELETE_WINDOW"), NULL))) {
WmDeleteWindowAtom = reply->atom;
free(reply);
if ((reply =
xcb_intern_atom_reply(Connection, xcb_intern_atom(Connection, 0, sizeof("WM_PROTOCOLS") - 1,
"WM_PROTOCOLS"), NULL))) {
#ifdef XCB_ICCCM_NUM_WM_SIZE_HINTS_ELEMENTS
xcb_icccm_set_wm_protocols(Connection, VideoWindow, reply->atom, 1, &WmDeleteWindowAtom);
#endif
#ifdef XCB_NUM_WM_HINTS_ELEMENTS
xcb_set_wm_protocols(Connection, reply->atom, VideoWindow, 1, &WmDeleteWindowAtom);
#endif
free(reply);
}
}
//
// prepare fullscreen.
//
if ((reply =
xcb_intern_atom_reply(Connection, xcb_intern_atom(Connection, 0, sizeof("_NET_WM_STATE") - 1,
"_NET_WM_STATE"), NULL))) {
NetWmState = reply->atom;
free(reply);
}
if ((reply =
xcb_intern_atom_reply(Connection, xcb_intern_atom(Connection, 0, sizeof("_NET_WM_STATE_FULLSCREEN") - 1,
"_NET_WM_STATE_FULLSCREEN"), NULL))) {
NetWmStateFullscreen = reply->atom;
free(reply);
}
if ((reply =
xcb_intern_atom_reply(Connection, xcb_intern_atom(Connection, 0, sizeof("_NET_WM_STATE_ABOVE") - 1,
"_NET_WM_STATE_ABOVE"), NULL))) {
NetWmStateAbove = reply->atom;
free(reply);
}
xcb_map_window(Connection, VideoWindow);
xcb_flush(Connection);
//
// hide cursor
//
pixmap = xcb_generate_id(Connection);
xcb_create_pixmap(Connection, 1, pixmap, parent, 1, 1);
cursor = xcb_generate_id(Connection);
xcb_create_cursor(Connection, cursor, pixmap, pixmap, 0, 0, 0, 0, 0, 0, 1, 1);
values[0] = cursor;
xcb_change_window_attributes(Connection, VideoWindow, XCB_CW_CURSOR, values);
VideoCursorPixmap = pixmap;
VideoBlankCursor = cursor;
VideoBlankTick = 0;
}
///
/// Set video device.
///
/// Currently this only choose the driver.
///
void VideoSetDevice(const char *device)
{
VideoDriverName = device;
}
void VideoSetConnector(char *c)
{
DRMConnector = c;
}
void VideoSetRefresh(char *r)
{
DRMRefresh = atoi(r);
}
///
/// Get video driver name.
///
/// @returns name of current video driver.
///
const char *VideoGetDriverName(void)
{
if (VideoUsedModule) {
return VideoUsedModule->Name;
}
return "";
}
///
/// Set video geometry.
///
/// @param geometry [=][<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>]
///
int VideoSetGeometry(const char *geometry)
{
XParseGeometry(geometry, &VideoWindowX, &VideoWindowY, &VideoWindowWidth, &VideoWindowHeight);
return 0;
}
///
/// Set 60hz display mode.
///
/// Pull up 50 Hz video for 60 Hz display.
///
/// @param onoff enable / disable the 60 Hz mode.
///
void VideoSet60HzMode(int onoff)
{
Video60HzMode = onoff;
}
///
/// Set soft start audio/video sync.
///
/// @param onoff enable / disable the soft start sync.
///
void VideoSetSoftStartSync(int onoff)
{
VideoSoftStartSync = onoff;
}
///
/// Set show black picture during channel switch.
///
/// @param onoff enable / disable black picture.
///
void VideoSetBlackPicture(int onoff)
{
VideoShowBlackPicture = onoff;
}
///
/// Set brightness adjustment.
///
/// @param brightness between -1000and 100.
/// 0 represents no modification
///
void VideoSetBrightness(int brightness)
{
VideoBrightness = (float)brightness / 100.0f;
}
///
/// Set contrast adjustment.
///
/// @param contrast between 0 and 100.
/// 1000 represents no modification
///
void VideoSetContrast(int contrast)
{
VideoContrast = (float)contrast / 100.0f;
}
///
/// Set saturation adjustment.
///
/// @param saturation between 0 and 100.
/// 100 represents no modification
///
void VideoSetSaturation(int saturation)
{
VideoSaturation = (float)saturation / 100.0f;
}
///
/// Set Gamma adjustment.
///
/// @param saturation between 0 and 100.
/// 100 represents no modification
///
void VideoSetGamma(int gamma)
{
VideoGamma = (float)gamma / 100.0f;
}
///
/// Set TargetColorSpace.
///
/// @param TargetColorSpace
///
void VideoSetTargetColor(int color)
{
VulkanTargetColorSpace = color;
}
///
/// Set hue adjustment.
///
/// @param hue between -PI*100 and PI*100.
/// 0 represents no modification
///
void VideoSetHue(int hue)
{
VideoHue = (float)hue / 100.0f;
}
///
/// Set video output position.
///
/// @param hw_decoder video hardware decoder
/// @param x video output x coordinate OSD relative
/// @param y video output y coordinate OSD relative
/// @param width video output width
/// @param height video output height
///
void VideoSetOutputPosition(VideoHwDecoder * hw_decoder, int x, int y, int width, int height)
{
if (!OsdWidth || !OsdHeight) {
return;
}
if (!width || !height) {
// restore full size
width = VideoWindowWidth;
height = VideoWindowHeight;
} else {
// convert OSD coordinates to window coordinates
x = (x * VideoWindowWidth) / OsdWidth;
width = (width * VideoWindowWidth) / OsdWidth;
y = (y * VideoWindowHeight) / OsdHeight;
height = (height * VideoWindowHeight) / OsdHeight;
}
// FIXME: add function to module class
if (VideoUsedModule == &CuvidModule) {
// check values to be able to avoid
// interfering with the video thread if possible
if (x == hw_decoder->Cuvid.VideoX && y == hw_decoder->Cuvid.VideoY && width == hw_decoder->Cuvid.VideoWidth
&& height == hw_decoder->Cuvid.VideoHeight) {
// not necessary...
return;
}
VideoThreadLock();
CuvidSetOutputPosition(&hw_decoder->Cuvid, x, y, width, height);
CuvidUpdateOutput(&hw_decoder->Cuvid);
VideoThreadUnlock();
}
(void)hw_decoder;
}
///
/// Set video window position.
///
/// @param x window x coordinate
/// @param y window y coordinate
/// @param width window width
/// @param height window height
///
/// @note no need to lock, only called from inside the video thread
///
void VideoSetVideoMode( __attribute__((unused))
int x, __attribute__((unused))
int y, int width, int height)
{
Debug(4, "video: %s %dx%d%+d%+d\n", __FUNCTION__, width, height, x, y);
if ((unsigned)width == VideoWindowWidth && (unsigned)height == VideoWindowHeight) {
return; // same size nothing todo
}
if (VideoEventCallback) {
sleep(1);
VideoEventCallback();
Debug(3, "call back set video mode %d %d\n", width, height);
}
VideoThreadLock();
VideoWindowWidth = width;
VideoWindowHeight = height;
#ifdef PLACEBO
VideoSetOsdSize(width, height);
#endif
VideoUsedModule->SetVideoMode();
VideoThreadUnlock();
}
///
/// Set 4:3 video display format.
///
/// @param format video format (stretch, normal, center cut-out)
///
void VideoSet4to3DisplayFormat(int format)
{
// convert api to internal format
switch (format) {
case -1: // rotate settings
format = (Video4to3ZoomMode + 1) % (VideoCenterCutOut + 1);
break;
case 0: // pan&scan (we have no pan&scan)
format = VideoStretch;
break;
case 1: // letter box
format = VideoNormal;
break;
case 2: // center cut-out
format = VideoCenterCutOut;
break;
}
if ((unsigned)format == Video4to3ZoomMode) {
return; // no change, no need to lock
}
VideoOsdExit();
// FIXME: must tell VDR that the OsdSize has been changed!
VideoThreadLock();
Video4to3ZoomMode = format;
// FIXME: need only VideoUsedModule->UpdateOutput();
VideoUsedModule->SetVideoMode();
VideoThreadUnlock();
VideoOsdInit();
}
///
/// Set other video display format.
///
/// @param format video format (stretch, normal, center cut-out)
///
void VideoSetOtherDisplayFormat(int format)
{
// convert api to internal format
switch (format) {
case -1: // rotate settings
format = (VideoOtherZoomMode + 1) % (VideoCenterCutOut + 1);
break;
case 0: // pan&scan (we have no pan&scan)
format = VideoStretch;
break;
case 1: // letter box
format = VideoNormal;
break;
case 2: // center cut-out
format = VideoCenterCutOut;
break;
}
if ((unsigned)format == VideoOtherZoomMode) {
return; // no change, no need to lock
}
VideoOsdExit();
// FIXME: must tell VDR that the OsdSize has been changed!
VideoThreadLock();
VideoOtherZoomMode = format;
// FIXME: need only VideoUsedModule->UpdateOutput();
VideoUsedModule->SetVideoMode();
VideoThreadUnlock();
VideoOsdInit();
}
///
/// Send fullscreen message to window.
///
/// @param onoff -1 toggle, true turn on, false turn off
///
void VideoSetFullscreen(int onoff)
{
if (XlibDisplay) { // needs running connection
xcb_client_message_event_t event;
memset(&event, 0, sizeof(event));
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = VideoWindow;
event.type = NetWmState;
if (onoff < 0) {
event.data.data32[0] = XCB_EWMH_WM_STATE_TOGGLE;
} else if (onoff) {
event.data.data32[0] = XCB_EWMH_WM_STATE_ADD;
} else {
event.data.data32[0] = XCB_EWMH_WM_STATE_REMOVE;
}
event.data.data32[1] = NetWmStateFullscreen;
event.data.data32[2] = NetWmStateAbove;
xcb_send_event(Connection, XCB_SEND_EVENT_DEST_POINTER_WINDOW, DefaultRootWindow(XlibDisplay),
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (void *)&event);
Debug(3, "video/x11: send fullscreen message %x %x\n", event.data.data32[0], event.data.data32[1]);
}
}
void VideoSetAbove()
{
if (XlibDisplay) { // needs running connection
xcb_client_message_event_t event;
memset(&event, 0, sizeof(event));
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = VideoWindow;
event.type = NetWmState;
event.data.data32[0] = XCB_EWMH_WM_STATE_ADD;
event.data.data32[1] = NetWmStateAbove;
xcb_send_event(Connection, XCB_SEND_EVENT_DEST_POINTER_WINDOW, DefaultRootWindow(XlibDisplay),
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (void *)&event);
Debug(3, "video/x11: send fullscreen message %x %x\n", event.data.data32[0], event.data.data32[1]);
}
}
///
/// Set deinterlace mode.
///
void VideoSetDeinterlace(int mode[VideoResolutionMax])
{
#ifdef CUVID
VideoDeinterlace[0] = mode[0]; // 576i
VideoDeinterlace[1] = 1; //mode[1]; // 720p
VideoDeinterlace[2] = mode[2]; // fake 1080
VideoDeinterlace[3] = mode[3]; // 1080
VideoDeinterlace[4] = 1; //mode[4]; 2160p
#else
VideoDeinterlace[0] = 1; // 576i
VideoDeinterlace[1] = 0; //mode[1]; // 720p
VideoDeinterlace[2] = 1; // fake 1080
VideoDeinterlace[3] = 1; // 1080
VideoDeinterlace[4] = 0; //mode[4]; 2160p
#endif
VideoSurfaceModesChanged = 1;
}
///
/// Set skip chroma deinterlace on/off.
///
void VideoSetSkipChromaDeinterlace(int onoff[VideoResolutionMax])
{
VideoSkipChromaDeinterlace[0] = onoff[0];
VideoSkipChromaDeinterlace[1] = onoff[1];
VideoSkipChromaDeinterlace[2] = onoff[2];
VideoSkipChromaDeinterlace[3] = onoff[3];
VideoSkipChromaDeinterlace[4] = onoff[4];
VideoSurfaceModesChanged = 1;
}
///
/// Set inverse telecine on/off.
///
void VideoSetInverseTelecine(int onoff[VideoResolutionMax])
{
VideoInverseTelecine[0] = onoff[0];
VideoInverseTelecine[1] = onoff[1];
VideoInverseTelecine[2] = onoff[2];
VideoInverseTelecine[3] = onoff[3];
VideoInverseTelecine[4] = onoff[4];
VideoSurfaceModesChanged = 1;
}
///
/// Set denoise level (0 .. 1000).
///
void VideoSetDenoise(int level[VideoResolutionMax])
{
VideoDenoise[0] = level[0];
VideoDenoise[1] = level[1];
VideoDenoise[2] = level[2];
VideoDenoise[3] = level[3];
VideoDenoise[4] = level[4];
VideoSurfaceModesChanged = 1;
}
///
/// Set sharpness level (-1000 .. 1000).
///
void VideoSetSharpen(int level[VideoResolutionMax])
{
VideoSharpen[0] = level[0];
VideoSharpen[1] = level[1];
VideoSharpen[2] = level[2];
VideoSharpen[3] = level[3];
VideoSharpen[4] = level[4];
VideoSurfaceModesChanged = 1;
}
///
/// Set scaling mode.
///
/// @param mode table with VideoResolutionMax values
///
void VideoSetScaling(int mode[VideoResolutionMax])
{
VideoScaling[0] = mode[0];
VideoScaling[1] = mode[1];
VideoScaling[2] = mode[2];
VideoScaling[3] = mode[3];
VideoScaling[4] = mode[4];
VideoSurfaceModesChanged = 1;
}
///
/// Set cut top and bottom.
///
/// @param pixels table with VideoResolutionMax values
///
void VideoSetCutTopBottom(int pixels[VideoResolutionMax])
{
VideoCutTopBottom[0] = pixels[0];
VideoCutTopBottom[1] = pixels[1];
VideoCutTopBottom[2] = pixels[2];
VideoCutTopBottom[3] = pixels[3];
VideoCutTopBottom[4] = pixels[4];
// FIXME: update output
}
///
/// Set cut left and right.
///
/// @param pixels table with VideoResolutionMax values
///
void VideoSetCutLeftRight(int pixels[VideoResolutionMax])
{
VideoCutLeftRight[0] = pixels[0];
VideoCutLeftRight[1] = pixels[1];
VideoCutLeftRight[2] = pixels[2];
VideoCutLeftRight[3] = pixels[3];
VideoCutLeftRight[4] = pixels[4];
// FIXME: update output
}
///
/// Set studio levels.
///
/// @param onoff flag on/off
///
void VideoSetStudioLevels(int onoff)
{
VideoStudioLevels = onoff;
#ifdef GAMMA
Set_Gamma(2.4, 6500);
#endif
}
///
/// Set scaler test.
///
/// @param onoff flag on/off
///
void VideoSetScalerTest(int onoff)
{
VideoScalerTest = onoff;
VideoSurfaceModesChanged = 1;
}
///
/// Set Color Blindness.
///
void VideoSetColorBlindness(int value)
{
VideoColorBlindness = value;;
}
///
/// Set Color Blindness Faktor.
///
void VideoSetColorBlindnessFaktor(int value)
{
VideoColorBlindnessFaktor = (float)value / 100.0f + 1.0f;
}
///
/// Set background color.
///
/// @param rgba 32 bit RGBA color.
///
void VideoSetBackground(uint32_t rgba)
{
VideoBackground = rgba; // saved for later start
VideoUsedModule->SetBackground(rgba);
}
///
/// Set audio delay.
///
/// @param ms delay in ms
///
void VideoSetAudioDelay(int ms)
{
VideoAudioDelay = ms * 90;
}
///
/// Set auto-crop parameters.
///
void VideoSetAutoCrop(int interval, int delay, int tolerance)
{
(void)interval;
(void)delay;
(void)tolerance;
}
///
/// Set EnableDPMSatBlackScreen
///
/// Currently this only choose the driver.
///
void SetDPMSatBlackScreen(int enable)
{
#ifdef USE_SCREENSAVER
EnableDPMSatBlackScreen = enable;
#endif
}
///
/// Raise video window.
///
int VideoRaiseWindow(void)
{
static const uint32_t values[] = { XCB_STACK_MODE_ABOVE };
xcb_configure_window(Connection, VideoWindow, XCB_CONFIG_WINDOW_STACK_MODE, values);
return 1;
}
///
/// Initialize video output module.
///
/// @param display_name X11 display name
///
void VideoInit(const char *display_name)
{
int screen_nr;
int i;
xcb_screen_iterator_t screen_iter;
xcb_screen_t const *screen;
#ifdef USE_DRM
VideoInitDrm();
#else
if (XlibDisplay) { // allow multiple calls
Debug(3, "video: x11 already setup\n");
return;
}
#ifdef USE_GLX
if (!XInitThreads()) {
Error(_("video: Can't initialize X11 thread support on '%s'\n"), display_name);
}
#endif
// Open the connection to the X server.
// use the DISPLAY environment variable as the default display name
if (!display_name && !(display_name = getenv("DISPLAY"))) {
// if no environment variable, use :0.0 as default display name
display_name = ":0.0";
}
if (!(XlibDisplay = XOpenDisplay(display_name))) {
Error(_("video: Can't connect to X11 server on '%s'\n"), display_name);
// FIXME: we need to retry connection
return;
}
// Register error handler
XSetIOErrorHandler(VideoIOErrorHandler);
// Convert XLIB display to XCB connection
if (!(Connection = XGetXCBConnection(XlibDisplay))) {
Error(_("video: Can't convert XLIB display to XCB connection\n"));
VideoExit();
return;
}
// prefetch extensions
// xcb_prefetch_extension_data(Connection, &xcb_big_requests_id);
#ifdef xcb_USE_GLX
xcb_prefetch_extension_data(Connection, &xcb_glx_id);
#endif
//xcb_prefetch_extension_data(Connection, &xcb_randr_id);
#ifdef USE_SCREENSAVER
xcb_prefetch_extension_data(Connection, &xcb_screensaver_id);
xcb_prefetch_extension_data(Connection, &xcb_dpms_id);
#endif
//xcb_prefetch_extension_data(Connection, &xcb_shm_id);
//xcb_prefetch_extension_data(Connection, &xcb_xv_id);
// Get the requested screen number
screen_nr = DefaultScreen(XlibDisplay);
screen_iter = xcb_setup_roots_iterator(xcb_get_setup(Connection));
for (i = 0; i < screen_nr; ++i) {
xcb_screen_next(&screen_iter);
}
screen = screen_iter.data;
VideoScreen = screen;
//
// Default window size
//
if (!VideoWindowHeight) {
if (VideoWindowWidth) {
VideoWindowHeight = (VideoWindowWidth * 9) / 16;
} else { // default to fullscreen
VideoWindowHeight = screen->height_in_pixels;
VideoWindowWidth = screen->width_in_pixels;
//***********************************************************************************************
#if DEBUG_no
if (strcmp(":0.0", display_name) == 0) {
VideoWindowHeight = 1080;
VideoWindowWidth = 1920;
}
#endif
}
}
if (!VideoWindowWidth) {
VideoWindowWidth = (VideoWindowHeight * 16) / 9;
}
//
// Create output window
//
VideoCreateWindow(screen->root, screen->root_visual, screen->root_depth);
Debug(3, "video: window prepared\n");
#endif
//
// prepare hardware decoder
//
for (i = 0; i < (int)(sizeof(VideoModules) / sizeof(*VideoModules)); ++i) {
// FIXME: support list of drivers and include display name
// use user device or first working enabled device driver
if ((VideoDriverName && !strcasecmp(VideoDriverName, VideoModules[i]->Name))
|| (!VideoDriverName && VideoModules[i]->Enabled)) {
if (VideoModules[i]->Init(display_name)) {
VideoUsedModule = VideoModules[i];
goto found;
}
}
}
Error(_("video: '%s' output module isn't supported\n"), VideoDriverName);
VideoUsedModule = &NoopModule;
found:
;
#ifndef USE_DRM
// FIXME: make it configurable from gui
if (getenv("NO_MPEG_HW")) {
VideoHardwareDecoder = 1;
}
if (getenv("NO_HW")) {
VideoHardwareDecoder = 0;
}
// disable x11 screensaver
X11SuspendScreenSaver(Connection, 1);
X11DPMSDisable(Connection);
//xcb_prefetch_maximum_request_length(Connection);
xcb_flush(Connection);
#endif
#ifdef PLACEBO_
InitPlacebo();
#endif
}
///
/// Cleanup video output module.
///
void VideoExit(void)
{
Debug(3, "Video Exit\n");
#ifndef USE_DRM
if (!XlibDisplay) { // no init or failed
return;
}
//
// Reenable screensaver / DPMS.
//
X11DPMSReenable(Connection);
X11SuspendScreenSaver(Connection, 0);
#endif
VideoUsedModule->Exit();
VideoUsedModule = &NoopModule;
#ifdef USE_VIDEO_THREAD
VideoThreadExit(); // destroy all mutexes
#endif
#ifdef USE_GLX
if (EglEnabled) {
EglExit(); // delete all contexts
}
#endif
#ifndef USE_DRM
//
// FIXME: cleanup.
//
// RandrExit();
//
// X11/xcb cleanup
//
if (VideoWindow != XCB_NONE) {
xcb_destroy_window(Connection, VideoWindow);
VideoWindow = XCB_NONE;
}
if (VideoColormap != XCB_NONE) {
xcb_free_colormap(Connection, VideoColormap);
VideoColormap = XCB_NONE;
}
if (VideoBlankCursor != XCB_NONE) {
xcb_free_cursor(Connection, VideoBlankCursor);
VideoBlankCursor = XCB_NONE;
}
if (VideoCursorPixmap != XCB_NONE) {
xcb_free_pixmap(Connection, VideoCursorPixmap);
VideoCursorPixmap = XCB_NONE;
}
xcb_flush(Connection);
if (XlibDisplay) {
if (XCloseDisplay(XlibDisplay)) {
Error(_("video: error closing display\n"));
}
XlibDisplay = NULL;
Connection = 0;
}
#endif
}
#ifdef USE_DRM
int GlxInitopengl()
{
EGLint contextAttrs[] = {
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};
while (!eglSharedContext)
sleep(1);
if (!eglOSDContext) {
eglOSDContext = eglCreateContext(eglDisplay, eglConfig, eglSharedContext, contextAttrs);
if (!eglOSDContext) {
EglCheck();
Fatal(_("video/egl: can't create thread egl context\n"));
return NULL;
}
}
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglOSDContext);
return;
}
int GlxDrawopengl()
{
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglSharedContext);
return;
}
void GlxDestroy()
{
eglDestroyContext(eglDisplay, eglOSDContext);
eglOSDContext = NULL;
}
#endif
#if 0 // for debug only
#include <sys/stat.h>
extern uint8_t *CreateJpeg(uint8_t *, int *, int, int, int);
void makejpg(uint8_t * data, int width, int height)
{
static int count = 0;
int i, n = 0, gpu = 0;;
char buf[32], FileName[32];
uint8_t *rgb;
uint8_t *jpg_image;
int size, size1;
if (data == NULL) {
data = malloc(width * height * 4);
gpu = 1;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, data);
}
// n = snprintf(buf, sizeof(buf), "P6\n%d\n%d\n255\n", width, height);
sprintf(FileName, "/tmp/test%d.jpg", count++);
rgb = malloc(width * height * 3 + n);
if (!rgb) {
printf("Unable to get RGB Memory \n");
return;
}
// memcpy(rgb, buf, n); // header
size = width * height * 4;
for (i = 0; i < size / 4; ++i) { // convert bgra -> rgb
rgb[n + i * 3 + 0] = data[i * 4 + 2];
rgb[n + i * 3 + 1] = data[i * 4 + 1];
rgb[n + i * 3 + 2] = data[i * 4 + 0];
}
if (gpu)
free(data);
jpg_image = CreateJpeg(rgb, &size1, 90, width, height);
int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
write(fd, jpg_image, size1);
close(fd);
free(rgb);
}
#endif