/// /// @file video.c @brief Video module /// /// Copyright (c) 2009 - 2012 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$ ////////////////////////////////////////////////////////////////////////////// /// /// @defgroup Video The video module. /// /// This module contains all video rendering functions. /// /// @todo hide mouse cursor support /// /// Uses Xlib where it is needed for VA-API or vdpau. XCB is used for /// everything else. /// /// - X11 /// - OpenGL rendering /// - OpenGL rendering with GLX texture-from-pixmap /// - Xrender rendering /// #define USE_XLIB_XCB #define noUSE_GRAB #define noUSE_GLX #define noUSE_DOUBLEBUFFER //#define USE_VAAPI ///< enable vaapi support //#define USE_VDPAU ///< enable vdpau support #define noUSE_BITMAP ///< use vdpau bitmap surface #define USE_VIDEO_THREAD #include #include #include #include #include #include #include #include #include #define _(str) gettext(str) ///< gettext shortcut #define _N(str) str ///< gettext_noop shortcut #include // portable atomic_t #ifdef USE_VIDEO_THREAD #ifndef __USE_GNU #define __USE_GNU #endif #include #include #ifndef HAVE_PTHREAD_NAME /// only available with newer glibc #define pthread_setname_np(thread, name) #endif #endif #ifdef USE_XLIB_XCB #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #ifdef USE_GLX #include // For GL_COLOR_BUFFER_BIT #include // only for gluErrorString #include #endif #ifdef USE_VAAPI #include #ifdef USE_GLX #include #endif #endif #ifdef USE_VDPAU #include #include #endif #include #include #include #include "misc.h" #include "video.h" #include "audio.h" #ifdef USE_XLIB_XCB //---------------------------------------------------------------------------- // Declarations //---------------------------------------------------------------------------- /// /// Video resolutions selector. /// typedef enum _video_resolutions_ { VideoResolution567i, ///< ...x567 interlaced VideoResolution720p, ///< ...x720 progressive VideoResolutionFake1080i, ///< 1280x1080 1440x1080 interlaced VideoResolution1080i, ///< 1920x1080 interlaced VideoResolutionMax ///< number of resolution indexs } VideoResolutions; /// /// Video deinterlace modes. /// typedef enum _video_deinterlace_modes_ { VideoDeinterlaceBob, ///< bob deinterlace VideoDeinterlaceWeave, ///< weave deinterlace VideoDeinterlaceTemporal, ///< temporal deinterlace VideoDeinterlaceTemporalSpatial, ///< temporal spatial deinterlace VideoDeinterlaceSoftware, ///< software 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 VideoZoom, ///< zoom out VideoAnamorphic, ///< anamorphic scaled (unsupported) } VideoZoomModes; //---------------------------------------------------------------------------- // Defines //---------------------------------------------------------------------------- #define CODEC_SURFACES_MAX 31 ///< maximal of surfaces #define CODEC_SURFACES_DEFAULT (21+4) ///< default of surfaces // FIXME: video-xvba only supports 14 #define xCODEC_SURFACES_DEFAULT 14 ///< default of surfaces #define CODEC_SURFACES_MPEG2 3 ///< 1 decode, up to 2 references #define CODEC_SURFACES_MPEG4 3 ///< 1 decode, up to 2 references #define CODEC_SURFACES_H264 21 ///< 1 decode, up to 20 references #define CODEC_SURFACES_VC1 3 ///< 1 decode, up to 2 references #define VIDEO_SURFACES_MAX 4 ///< video output surfaces for queue #define OUTPUT_SURFACES_MAX 4 ///< output surfaces for flip page //---------------------------------------------------------------------------- // Variables //---------------------------------------------------------------------------- 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_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 char VideoSurfaceModesChanged; ///< flag surface modes changed /// Default deinterlace mode. static VideoDeinterlaceModes VideoDeinterlace[VideoResolutionMax]; /// Default number of deinterlace surfaces static const int VideoDeinterlaceSurfaces = 4; /// Default skip chroma deinterlace flag (VDPAU only) static int VideoSkipChromaDeinterlace[VideoResolutionMax]; /// Default amount of noise reduction algorithm to apply (0 .. 1000). static int VideoDenoise[VideoResolutionMax]; /// Default amount of of sharpening, or blurring, to apply (-1000 .. 1000). static int VideoSharpen[VideoResolutionMax]; // FIXME: color space / studio levels /// Default scaling mode static VideoScalingModes VideoScaling[VideoResolutionMax]; /// Default audio/video delay static int VideoAudioDelay; /// Default zoom mode static VideoZoomModes Video4to3ZoomMode; //static char VideoSoftStartSync; ///< soft start sync audio/video static char Video60HzMode; ///< handle 60hz displays static xcb_atom_t WmDeleteWindowAtom; ///< WM delete message extern uint32_t VideoSwitch; ///< ticks for channel switch #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 #endif //---------------------------------------------------------------------------- // Functions //---------------------------------------------------------------------------- static void VideoThreadLock(void); ///< lock video thread static void VideoThreadUnlock(void); ///< unlock video thread //---------------------------------------------------------------------------- // GLX //---------------------------------------------------------------------------- #ifdef USE_GLX static int GlxEnabled = 1; ///< use GLX static int GlxVSyncEnabled = 0; ///< enable/disable v-sync static GLXContext GlxSharedContext; ///< shared gl context static GLXContext GlxContext; ///< our gl context static XVisualInfo *GlxVisualInfo; ///< our gl visual static GLuint OsdGlTextures[2]; ///< gl texture for OSD static int OsdIndex; ///< index into OsdGlTextures /// /// GLX extension functions ///@{ #ifdef GLX_MESA_swap_control static PFNGLXSWAPINTERVALMESAPROC GlxSwapIntervalMESA; #endif #ifdef GLX_SGI_video_sync static PFNGLXGETVIDEOSYNCSGIPROC GlxGetVideoSyncSGI; #endif #ifdef GLX_SGI_swap_control static PFNGLXSWAPINTERVALSGIPROC GlxSwapIntervalSGI; #endif ///@} /// /// GLX check error. /// static void GlxCheck(void) { GLenum err; if ((err = glGetError()) != GL_NO_ERROR) { Debug(3, "video/glx: error %d '%s'\n", err, gluErrorString(err)); } } /// /// GLX check if a GLX extension is supported. /// /// @param ext extension to query /// @returns true if supported, false otherwise /// static int GlxIsExtensionSupported(const char *ext) { const char *extensions; if ((extensions = glXQueryExtensionsString(XlibDisplay, DefaultScreen(XlibDisplay)))) { const char *s; int l; s = strstr(extensions, ext); l = strlen(ext); return s && (s[l] == ' ' || s[l] == '\0'); } return 0; } #if 0 /// /// Setup GLX decoder /// /// @param decoder VA-API decoder /// void GlxSetupDecoder(VaapiDecoder * decoder) { int width; int height; int i; width = decoder->InputWidth; height = decoder->InputHeight; glEnable(GL_TEXTURE_2D); // create 2d texture glGenTextures(2, decoder->GlTexture); GlxCheck(); for (i = 0; i < 2; ++i) { glBindTexture(GL_TEXTURE_2D, decoder->GlTexture[i]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); } glDisable(GL_TEXTURE_2D); GlxCheck(); } #endif /// /// Render texture. /// /// @param texture 2d texture /// static inline void GlxRenderTexture(GLuint texture, int x, int y, int width, int height) { glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // no color glBegin(GL_QUADS); { glTexCoord2f(1.0f, 1.0f); glVertex2i(x + width, y + height); glTexCoord2f(0.0f, 1.0f); glVertex2i(x, y + height); glTexCoord2f(0.0f, 0.0f); glVertex2i(x, y); glTexCoord2f(1.0f, 0.0f); glVertex2i(x + width, y); #if 0 glTexCoord2f(0.0f, 0.0f); glVertex2i(x, y); glTexCoord2f(0.0f, 1.0f); glVertex2i(x, y + height); glTexCoord2f(1.0f, 1.0f); glVertex2i(x + width, y + height); glTexCoord2f(1.0f, 0.0f); glVertex2i(x + width, y); #endif } glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); } /// /// Upload texture. /// static void GlxUploadTexture(int x, int y, int width, int height, const uint8_t * argb) { // FIXME: use other / faster uploads // ARB_pixelbuffer_object GL_PIXEL_UNPACK_BUFFER glBindBufferARB() // glMapBuffer() glUnmapBuffer() // glTexSubImage2D glEnable(GL_TEXTURE_2D); // upload 2d texture glBindTexture(GL_TEXTURE_2D, OsdGlTextures[OsdIndex]); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_BGRA, GL_UNSIGNED_BYTE, argb); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); } /// /// Render to glx texture. /// static void GlxRender(int osd_width, int osd_height) { static uint8_t *image; static uint8_t cycle; int x; int y; if (!OsdGlTextures[0] || !OsdGlTextures[1]) { return; } // render each frame kills performance // osd 1920 * 1080 * 4 (RGBA) * 50 (HZ) = 396 Mb/s // too big for alloca if (!image) { image = malloc(4 * osd_width * osd_height); memset(image, 0x00, 4 * osd_width * osd_height); } for (y = 0; y < osd_height; ++y) { for (x = 0; x < osd_width; ++x) { ((uint32_t *) image)[x + y * osd_width] = 0x00FFFFFF | (cycle++) << 24; } } cycle++; // FIXME: convert is for GLX texture unneeded // convert internal osd to image //GfxConvert(image, 0, 4 * osd_width); // GlxUploadTexture(0, 0, osd_width, osd_height, image); } /// /// Setup GLX window. /// static void GlxSetupWindow(xcb_window_t window, int width, int height) { uint32_t start; uint32_t end; int i; unsigned count; Debug(3, "video/glx: %s\n %x %dx%d", __FUNCTION__, window, width, height); // set glx context if (!glXMakeCurrent(XlibDisplay, window, GlxContext)) { Fatal(_("video/glx: can't make glx context current\n")); // FIXME: disable glx return; } Debug(3, "video/glx: ok\n"); #ifdef DEBUG // check if v-sync is working correct end = GetMsTicks(); for (i = 0; i < 10; ++i) { start = end; glClear(GL_COLOR_BUFFER_BIT); glXSwapBuffers(XlibDisplay, window); end = GetMsTicks(); GlxGetVideoSyncSGI(&count); Debug(3, "video/glx: %5d frame rate %d ms\n", count, end - start); // nvidia can queue 5 swaps if (i > 5 && (end - start) < 15) { Warning(_("video/glx: no v-sync\n")); } } #endif // viewpoint GlxCheck(); glViewport(0, 0, width, height); glDepthRange(-1.0, 1.0); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glColor3f(1.0f, 1.0f, 1.0f); glClearDepth(1.0); GlxCheck(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, width, height, 0.0, -1.0, 1.0); GlxCheck(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glDisable(GL_DEPTH_TEST); // setup 2d drawing glDepthMask(GL_FALSE); glDisable(GL_CULL_FACE); #ifdef USE_DOUBLEBUFFER glDrawBuffer(GL_BACK); #else glDrawBuffer(GL_FRONT); #endif glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); #ifdef DEBUG #ifdef USE_DOUBLEBUFFER glDrawBuffer(GL_FRONT); glClearColor(1.0f, 0.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glDrawBuffer(GL_BACK); #endif #endif // clear glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // intial background color glClear(GL_COLOR_BUFFER_BIT); #ifdef DEBUG glClearColor(1.0f, 1.0f, 0.0f, 1.0f); // background color #endif GlxCheck(); } /// /// Initialize GLX. /// static void GlxInit(void) { static GLint visual_attr[] = { GLX_RGBA, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, #ifdef USE_DOUBLEBUFFER GLX_DOUBLEBUFFER, #endif None }; XVisualInfo *vi; 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; if (!glXQueryVersion(XlibDisplay, &major, &minor)) { Error(_("video/glx: no GLX support\n")); GlxEnabled = 0; return; } Info(_("video/glx: glx version %d.%d\n"), major, minor); // // check which extension are supported // glx_GLX_EXT_swap_control = GlxIsExtensionSupported("GLX_EXT_swap_control"); glx_GLX_MESA_swap_control = GlxIsExtensionSupported("GLX_MESA_swap_control"); glx_GLX_SGI_swap_control = GlxIsExtensionSupported("GLX_SGI_swap_control"); glx_GLX_SGI_video_sync = GlxIsExtensionSupported("GLX_SGI_video_sync"); #ifdef GLX_MESA_swap_control if (glx_GLX_MESA_swap_control) { GlxSwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) glXGetProcAddress((const GLubyte *)"glXSwapIntervalMESA"); } Debug(3, "video/glx: GlxSwapIntervalMESA=%p\n", GlxSwapIntervalMESA); #endif #ifdef GLX_SGI_swap_control if (glx_GLX_SGI_swap_control) { GlxSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC) glXGetProcAddress((const GLubyte *)"glXSwapIntervalSGI"); } Debug(3, "video/glx: GlxSwapIntervalSGI=%p\n", GlxSwapIntervalSGI); #endif #ifdef GLX_SGI_video_sync if (glx_GLX_SGI_video_sync) { GlxGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC) glXGetProcAddress((const GLubyte *)"glXGetVideoSyncSGI"); } Debug(3, "video/glx: GlxGetVideoSyncSGI=%p\n", GlxGetVideoSyncSGI); #endif // glXGetVideoSyncSGI glXWaitVideoSyncSGI #if 0 // FIXME: use xcb: xcb_glx_create_context #endif // create glx context glXMakeCurrent(XlibDisplay, None, NULL); vi = glXChooseVisual(XlibDisplay, DefaultScreen(XlibDisplay), visual_attr); if (!vi) { Error(_("video/glx: can't get a RGB visual\n")); GlxEnabled = 0; return; } if (!vi->visual) { Error(_("video/glx: no valid visual found\n")); GlxEnabled = 0; return; } if (vi->bits_per_rgb < 8) { Error(_("video/glx: need atleast 8-bits per RGB\n")); GlxEnabled = 0; return; } context = glXCreateContext(XlibDisplay, vi, NULL, GL_TRUE); if (!context) { Error(_("video/glx: can't create glx context\n")); GlxEnabled = 0; return; } GlxSharedContext = context; context = glXCreateContext(XlibDisplay, vi, GlxSharedContext, GL_TRUE); if (!context) { Error(_("video/glx: can't create glx context\n")); GlxEnabled = 0; // FIXME: destroy GlxSharedContext return; } GlxContext = context; GlxVisualInfo = vi; Debug(3, "video/glx: visual %#02x depth %u\n", (unsigned)vi->visualid, vi->depth); // // query default v-sync state // if (glx_GLX_EXT_swap_control) { unsigned tmp; tmp = -1; glXQueryDrawable(XlibDisplay, DefaultRootWindow(XlibDisplay), GLX_SWAP_INTERVAL_EXT, &tmp); GlxCheck(); Debug(3, "video/glx: default v-sync is %d\n", tmp); } else { Debug(3, "video/glx: default v-sync is unknown\n"); } // // disable wait on v-sync // // FIXME: sleep before swap / busy waiting hardware // FIXME: 60hz lcd panel // FIXME: config: default, on, off #ifdef GLX_SGI_swap_control if (GlxVSyncEnabled < 0 && GlxSwapIntervalSGI) { if (GlxSwapIntervalSGI(0)) { GlxCheck(); Warning(_("video/glx: can't disable v-sync\n")); } else { Info(_("video/glx: v-sync disabled\n")); } } else #endif #ifdef GLX_MESA_swap_control if (GlxVSyncEnabled < 0 && GlxSwapIntervalMESA) { if (GlxSwapIntervalMESA(0)) { GlxCheck(); Warning(_("video/glx: can't disable v-sync\n")); } else { Info(_("video/glx: v-sync disabled\n")); } } #endif // // enable wait on v-sync // #ifdef GLX_SGI_swap_control if (GlxVSyncEnabled > 0 && GlxSwapIntervalMESA) { if (GlxSwapIntervalMESA(1)) { GlxCheck(); Warning(_("video/glx: can't enable v-sync\n")); } else { Info(_("video/glx: v-sync enabled\n")); } } else #endif #ifdef GLX_MESA_swap_control if (GlxVSyncEnabled > 0 && GlxSwapIntervalSGI) { if (GlxSwapIntervalSGI(1)) { GlxCheck(); Warning(_("video/glx: can't enable v-sync\n")); } else { Info(_("video/glx: v-sync enabled\n")); } } #endif } /// /// Cleanup GLX. /// static void GlxExit(void) { Debug(3, "video/glx: %s\n", __FUNCTION__); glFinish(); // must destroy glx if (glXGetCurrentContext() == GlxContext) { // if currently used, set to none glXMakeCurrent(XlibDisplay, None, NULL); } if (GlxSharedContext) { glXDestroyContext(XlibDisplay, GlxSharedContext); } if (GlxContext) { glXDestroyContext(XlibDisplay, GlxContext); } #if 0 if (GlxThreadContext) { glXDestroyContext(XlibDisplay, GlxThreadContext); } // FIXME: must free GlxVisualInfo #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 <= 567) { return VideoResolution567i; } if (height <= 720) { return VideoResolution720p; } if (height < 1080) { return VideoResolutionFake1080i; } if (width < 1920) { return VideoResolutionFake1080i; } return VideoResolution1080i; } //---------------------------------------------------------------------------- // auto-crop //---------------------------------------------------------------------------- /// /// avfilter_vf_cropdetect /// typedef struct _video_auto_crop_ctx_ { int x1; ///< detected left border int x2; ///< detected right border int y1; ///< detected top border int y2; ///< detected bottom border } VideoAutoCropCtx; #define YBLACK 0x20 ///< below is black #define UVBLACK 0x80 ///< around is black #define M64 UINT64_C(0x0101010101010101) ///< 64bit multiplicator /// /// Detect black line Y. /// /// @param data Y plane data /// @param length number of pixel to check /// @param stride offset of pixels /// /// @note 8 pixel are checked at once, all values must be 8 aligned /// static int AutoCropIsBlackLineY(const uint8_t * data, int length, int stride) { int n; int o; uint64_t r; const uint64_t *p; #ifdef DEBUG if ((size_t) data & 0x7 || length & 0x7 || stride & 0x7) { abort(); } #endif p = (const uint64_t *)data; n = length / 8; // FIXME: can remove n o = stride / 8; r = 0; while (--n >= 0) { r |= *p; p += o; } // below YBLACK(0x20) is black return r & (~(YBLACK - 1) * M64); } /// /// Auto detect black borders and crop them. /// static void AutoCropDetect(int width, int height, void *data[3], uint32_t pitches[3]) { const void *data_y; unsigned length_y; int x; int y; int x1; int x2; int y1; int y2; // // ignore top+bottom 4 lines and left+right 8 pixels // x1 = 0; y1 = 0; x2 = width; y2 = height; data_y = data[0]; length_y = pitches[0]; // // search top // // // search bottom // // // search left // // // search right // } //---------------------------------------------------------------------------- // software - deinterlace //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // VA-API //---------------------------------------------------------------------------- #ifdef USE_VAAPI static int VideoVaapiEnabled = 1; ///< use VA-API decoder static int VaapiBuggyVdpau; ///< fix libva-driver-vdpau bugs static int VaapiBuggyIntel; ///< fix libva-driver-intel bugs static VADisplay *VaDisplay; ///< VA-API display static VAImage VaOsdImage = { .image_id = VA_INVALID_ID }; ///< osd VA-API image static VASubpictureID VaOsdSubpicture = VA_INVALID_ID; ///< osd VA-API subpicture static char VaapiUnscaledOsd; ///< unscaled osd supported /// VA-API decoder typedef typedef struct _vaapi_decoder_ VaapiDecoder; /// /// VA-API decoder /// struct _vaapi_decoder_ { VADisplay *VaDisplay; ///< VA-API display xcb_window_t Window; ///< output window int OutputX; ///< output window x int OutputY; ///< output window y int OutputWidth; ///< output window width int OutputHeight; ///< output window height /// flags for put surface for different resolutions groups unsigned SurfaceFlagsTable[VideoResolutionMax]; unsigned SurfaceFlags; ///< current flags for put surface enum PixelFormat PixFmt; ///< ffmpeg frame pixfmt int WrongInterlacedWarned; ///< warning about interlace flag issued int Interlaced; ///< ffmpeg interlaced flag int TopFieldFirst; ///< ffmpeg top field displayed first VAImage DeintImages[3]; ///< deinterlace image buffers VAImage Image[1]; ///< image buffer to update surface struct vaapi_context VaapiContext[1]; ///< ffmpeg VA-API context int SurfacesNeeded; ///< number of surface to request int SurfaceUsedN; ///< number of used surfaces /// used surface ids VASurfaceID SurfacesUsed[CODEC_SURFACES_MAX]; int SurfaceFreeN; ///< number of free surfaces /// free surface ids VASurfaceID SurfacesFree[CODEC_SURFACES_MAX]; int InputWidth; ///< video input width int InputHeight; ///< video input height AVRational InputAspect; ///< video input aspect ratio VideoResolutions Resolution; ///< resolution group int CropX; ///< video crop x int CropY; ///< video crop y int CropWidth; ///< video crop width int CropHeight; ///< video crop height #ifdef USE_GLX GLuint GlTexture[2]; ///< gl texture for VA-API void *GlxSurface[2]; ///< VA-API/GLX surface #endif VASurfaceID BlackSurface; ///< empty black surface /// video surface ring buffer VASurfaceID SurfacesRb[VIDEO_SURFACES_MAX]; int SurfaceWrite; ///< write pointer int SurfaceRead; ///< read pointer atomic_t SurfacesFilled; ///< how many of the buffer is used int SurfaceField; ///< current displayed field int DropNextFrame; ///< flag drop next frame int DupNextFrame; ///< flag duplicate next frame struct timespec FrameTime; ///< time of last display int64_t PTS; ///< video PTS clock 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 }; static VaapiDecoder *VaapiDecoders[1]; ///< open decoder streams static int VaapiDecoderN; ///< number of decoder streams /// forward display back surface static void VaapiBlackSurface(VaapiDecoder * decoder); //---------------------------------------------------------------------------- // VA-API Functions //---------------------------------------------------------------------------- // Surfaces ------------------------------------------------------------- /// /// Create surfaces for VA-API decoder. /// /// @param decoder VA-API decoder /// @param width surface source/video width /// @param height surface source/video height /// static void VaapiCreateSurfaces(VaapiDecoder * decoder, int width, int height) { #ifdef DEBUG if (!decoder->SurfacesNeeded) { Error(_("video/vaapi: surface needed not set\n")); decoder->SurfacesNeeded = 3 + VIDEO_SURFACES_MAX; } #endif Debug(3, "video/vaapi: %s: %dx%d * %d\n", __FUNCTION__, width, height, decoder->SurfacesNeeded); decoder->SurfaceFreeN = decoder->SurfacesNeeded; // VA_RT_FORMAT_YUV420 VA_RT_FORMAT_YUV422 VA_RT_FORMAT_YUV444 if (vaCreateSurfaces(decoder->VaDisplay, width, height, VA_RT_FORMAT_YUV420, decoder->SurfaceFreeN, decoder->SurfacesFree) != VA_STATUS_SUCCESS) { Fatal(_("video/vaapi: can't create %d surfaces\n"), decoder->SurfaceFreeN); // FIXME: write error handler / fallback } // // update OSD associate // if (VaOsdSubpicture == VA_INVALID_ID) { Warning(_("video/vaapi: no osd subpicture yet\n")); return; } if (VaapiUnscaledOsd) { if (vaAssociateSubpicture(VaDisplay, VaOsdSubpicture, decoder->SurfacesFree, decoder->SurfaceFreeN, 0, 0, VaOsdImage.width, VaOsdImage.height, 0, 0, VideoWindowWidth, VideoWindowHeight, VA_SUBPICTURE_DESTINATION_IS_SCREEN_COORD) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't associate subpicture\n")); } } else { if (vaAssociateSubpicture(VaDisplay, VaOsdSubpicture, decoder->SurfacesFree, decoder->SurfaceFreeN, 0, 0, VaOsdImage.width, VaOsdImage.height, 0, 0, width, height, 0) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't associate subpicture\n")); } } } /// /// Destroy surfaces of VA-API decoder. /// /// @param decoder VA-API decoder /// static void VaapiDestroySurfaces(VaapiDecoder * decoder) { Debug(3, "video/vaapi: %s:\n", __FUNCTION__); // // update OSD associate // if (VaOsdSubpicture != VA_INVALID_ID) { if (decoder->SurfaceFreeN && vaDeassociateSubpicture(VaDisplay, VaOsdSubpicture, decoder->SurfacesFree, decoder->SurfaceFreeN) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't deassociate %d surfaces\n"), decoder->SurfaceFreeN); } if (decoder->SurfaceUsedN && vaDeassociateSubpicture(VaDisplay, VaOsdSubpicture, decoder->SurfacesUsed, decoder->SurfaceUsedN) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't deassociate %d surfaces\n"), decoder->SurfaceUsedN); } } if (vaDestroySurfaces(decoder->VaDisplay, decoder->SurfacesFree, decoder->SurfaceFreeN) != VA_STATUS_SUCCESS) { Error("video/vaapi: can't destroy %d surfaces\n", decoder->SurfaceFreeN); } decoder->SurfaceFreeN = 0; if (vaDestroySurfaces(decoder->VaDisplay, decoder->SurfacesUsed, decoder->SurfaceUsedN) != VA_STATUS_SUCCESS) { Error("video/vaapi: can't destroy %d surfaces\n", decoder->SurfaceUsedN); } decoder->SurfaceUsedN = 0; // FIXME surfaces used for output } /// /// Get a free surface. /// /// @param decoder VA-API decoder /// /// @returns the oldest free surface /// static VASurfaceID VaapiGetSurface(VaapiDecoder * decoder) { VASurfaceID surface; int i; if (!decoder->SurfaceFreeN) { Error(_("video/vaapi: out of surfaces\n")); return VA_INVALID_ID; } // 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] = VA_INVALID_ID; // save as used decoder->SurfacesUsed[decoder->SurfaceUsedN++] = surface; return surface; } /// /// Release a surface. /// /// @param decoder VA-API decoder /// @param surface surface no longer used /// static void VaapiReleaseSurface(VaapiDecoder * decoder, VASurfaceID surface) { int i; for (i = 0; i < decoder->SurfaceUsedN; ++i) { if (decoder->SurfacesUsed[i] == surface) { // no problem, with last used decoder->SurfacesUsed[i] = decoder->SurfacesUsed[--decoder->SurfaceUsedN]; decoder->SurfacesFree[decoder->SurfaceFreeN++] = surface; return; } } Error(_("video/vaapi: release surface %#x, which is not in use\n"), surface); } // Init/Exit ------------------------------------------------------------ /// /// Debug VA-API decoder frames drop... /// /// @param decoder video hardware decoder /// static void VaapiPrintFrames(const VaapiDecoder * decoder) { Debug(3, "video/vaapi: %d missed, %d duped, %d dropped frames of %d\n", decoder->FramesMissed, decoder->FramesDuped, decoder->FramesDropped, decoder->FrameCounter); #ifndef DEBUG (void)decoder; #endif } /// /// Initialize surface flags. /// /// @param decoder video hardware decoder /// static void VaapiInitSurfaceFlags(VaapiDecoder * decoder) { int i; for (i = 0; i < VideoResolutionMax; ++i) { decoder->SurfaceFlagsTable[i] = VA_CLEAR_DRAWABLE; // FIXME: color space conversion none, ITU-R BT.601, ITU-R BT.709 decoder->SurfaceFlagsTable[i] |= VA_SRC_BT601; // scaling flags FAST, HQ, NL_ANAMORPHIC // FIXME: need to detect the backend to choose the parameter switch (VideoScaling[i]) { case VideoScalingNormal: decoder->SurfaceFlagsTable[i] |= VA_FILTER_SCALING_DEFAULT; break; case VideoScalingFast: decoder->SurfaceFlagsTable[i] |= VA_FILTER_SCALING_FAST; break; case VideoScalingHQ: // vdpau backend supports only VA_FILTER_SCALING_HQ // vdpau backend with advanced deinterlacer and my GT-210 // is too slow decoder->SurfaceFlagsTable[i] |= VA_FILTER_SCALING_HQ; break; case VideoScalingAnamorphic: // intel backend supports only VA_FILTER_SCALING_NL_ANAMORPHIC; // don't use it, its for 4:3 -> 16:9 scaling decoder->SurfaceFlagsTable[i] |= VA_FILTER_SCALING_NL_ANAMORPHIC; break; } // deinterlace flags (not yet supported by libva) switch (VideoDeinterlace[i]) { case VideoDeinterlaceBob: break; case VideoDeinterlaceWeave: break; case VideoDeinterlaceTemporal: //FIXME: private hack //decoder->SurfaceFlagsTable[i] |= 0x00002000; break; case VideoDeinterlaceTemporalSpatial: //FIXME: private hack //decoder->SurfaceFlagsTable[i] |= 0x00006000; break; case VideoDeinterlaceSoftware: break; } } } /// /// Allocate new VA-API decoder. /// /// @returns a new prepared va-api hardware decoder. /// static VaapiDecoder *VaapiNewDecoder(void) { VaapiDecoder *decoder; int i; if (VaapiDecoderN == 1) { Fatal(_("video/vaapi: out of decoders\n")); } if (!(decoder = calloc(1, sizeof(*decoder)))) { Fatal(_("video/vaapi: out of memory\n")); } decoder->VaDisplay = VaDisplay; decoder->Window = VideoWindow; VaapiInitSurfaceFlags(decoder); decoder->SurfaceFlags = decoder->SurfaceFlagsTable[0]; decoder->DeintImages[0].image_id = VA_INVALID_ID; decoder->DeintImages[1].image_id = VA_INVALID_ID; decoder->DeintImages[2].image_id = VA_INVALID_ID; decoder->Image->image_id = VA_INVALID_ID; for (i = 0; i < CODEC_SURFACES_MAX; ++i) { decoder->SurfacesUsed[i] = VA_INVALID_ID; decoder->SurfacesFree[i] = VA_INVALID_ID; } // setup video surface ring buffer atomic_set(&decoder->SurfacesFilled, 0); for (i = 0; i < VIDEO_SURFACES_MAX; ++i) { decoder->SurfacesRb[i] = VA_INVALID_ID; } decoder->BlackSurface = VA_INVALID_ID; // // Setup ffmpeg vaapi context // decoder->VaapiContext->display = VaDisplay; decoder->VaapiContext->config_id = VA_INVALID_ID; decoder->VaapiContext->context_id = VA_INVALID_ID; #ifdef USE_GLX decoder->GlxSurface[0] = VA_INVALID_ID; decoder->GlxSurface[1] = VA_INVALID_ID; if (GlxEnabled) { // FIXME: create GLX context here } #endif decoder->OutputWidth = VideoWindowWidth; decoder->OutputHeight = VideoWindowHeight; VaapiDecoders[VaapiDecoderN++] = decoder; return decoder; } /// /// Cleanup VA-API. /// /// @param decoder va-api hw decoder /// static void VaapiCleanup(VaapiDecoder * decoder) { int filled; VASurfaceID surface; int i; // flush output queue, only 1-2 frames buffered, no big loss while ((filled = atomic_read(&decoder->SurfacesFilled))) { decoder->SurfaceRead = (decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX; atomic_dec(&decoder->SurfacesFilled); surface = decoder->SurfacesRb[decoder->SurfaceRead]; if (surface == VA_INVALID_ID) { printf(_("video/vaapi: invalid surface in ringbuffer\n")); Error(_("video/vaapi: invalid surface in ringbuffer\n")); continue; } // can crash and hang if (0 && vaSyncSurface(decoder->VaDisplay, surface) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaSyncSurface failed\n")); } } if (decoder->SurfaceRead != decoder->SurfaceWrite) { abort(); } // clear ring buffer for (i = 0; i < VIDEO_SURFACES_MAX; ++i) { decoder->SurfacesRb[i] = VA_INVALID_ID; } decoder->WrongInterlacedWarned = 0; // cleanup image if (decoder->Image->image_id != VA_INVALID_ID) { if (vaDestroyImage(VaDisplay, decoder->Image->image_id) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't destroy image!\n")); } decoder->Image->image_id = VA_INVALID_ID; } // cleanup context and config if (decoder->VaapiContext) { if (decoder->VaapiContext->context_id != VA_INVALID_ID) { if (vaDestroyContext(VaDisplay, decoder->VaapiContext->context_id) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't destroy context!\n")); } decoder->VaapiContext->context_id = VA_INVALID_ID; } if (decoder->VaapiContext->config_id != VA_INVALID_ID) { if (vaDestroyConfig(VaDisplay, decoder->VaapiContext->config_id) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't destroy config!\n")); } decoder->VaapiContext->config_id = VA_INVALID_ID; } } // cleanup surfaces if (decoder->SurfaceFreeN || decoder->SurfaceUsedN) { VaapiDestroySurfaces(decoder); } decoder->PTS = AV_NOPTS_VALUE; } /// /// Destroy a VA-API decoder. /// /// @param decoder VA-API decoder /// static void VaapiDelDecoder(VaapiDecoder * decoder) { VaapiCleanup(decoder); if (decoder->BlackSurface != VA_INVALID_ID) { // // update OSD associate // if (VaOsdSubpicture != VA_INVALID_ID) { if (vaDeassociateSubpicture(VaDisplay, VaOsdSubpicture, &decoder->BlackSurface, 1) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't deassociate black surfaces\n")); } } if (vaDestroySurfaces(decoder->VaDisplay, &decoder->BlackSurface, 1) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't destroy a surface\n")); } } // FIXME: decoder->DeintImages #ifdef USE_GLX if (decoder->GlxSurface[0] != VA_INVALID_ID) { if (vaDestroySurfaceGLX(VaDisplay, decoder->GlxSurface[0]) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't destroy glx surface!\n")); } } if (decoder->GlxSurface[1] != VA_INVALID_ID) { if (vaDestroySurfaceGLX(VaDisplay, decoder->GlxSurface[1]) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't destroy glx surface!\n")); } } if (decoder->GlTexture[0]) { glDeleteTextures(2, decoder->GlTexture); } #endif VaapiPrintFrames(decoder); free(decoder); } /// /// VA-API setup. /// /// @param display_name x11/xcb display name /// static void VideoVaapiInit(const char *display_name) { int major; int minor; VADisplayAttribute attr; const char *s; VaOsdImage.image_id = VA_INVALID_ID; VaOsdSubpicture = VA_INVALID_ID; #ifdef USE_GLX if (GlxEnabled) { // support glx VaDisplay = vaGetDisplayGLX(XlibDisplay); } else #endif { VaDisplay = vaGetDisplay(XlibDisplay); } if (!VaDisplay) { Error(_("video/vaapi: Can't connect VA-API to X11 server on '%s'"), display_name); // FIXME: no fatal for plugin return; } if (vaInitialize(VaDisplay, &major, &minor) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: Can't inititialize VA-API on '%s'"), display_name); vaTerminate(VaDisplay); VaDisplay = NULL; return; } s = vaQueryVendorString(VaDisplay); Info(_("video/vaapi: libva %d.%d (%s) initialized\n"), major, minor, s); // // Setup fixes for driver bugs. // if (strstr(s, "VDPAU")) { Info(_("video/vaapi: use vdpau bug workaround\n")); setenv("VDPAU_VIDEO_PUTSURFACE_FAST", "0", 0); VaapiBuggyVdpau = 1; } if (strstr(s, "Intel i965")) { VaapiBuggyIntel = 1; } // // check if driver makes a copy of the VA surface for display. // attr.type = VADisplayAttribDirectSurface; attr.flags = VA_DISPLAY_ATTRIB_GETTABLE; if (vaGetDisplayAttributes(VaDisplay, &attr, 1) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: Can't get direct-surface attribute\n")); attr.value = 1; } Info(_("video/vaapi: VA surface is %s\n"), attr.value ? _("direct mapped") : _("copied")); // FIXME: handle the cases: new liba: Don't use it. #if 0 // // check the chroma format // attr.type = VAConfigAttribRTFormat attr.flags = VA_DISPLAY_ATTRIB_GETTABLE; #endif } /// /// VA-API cleanup /// static void VideoVaapiExit(void) { int i; // FIXME: more VA-API cleanups... // FIXME: can hang with vdpau in pthread_rwlock_wrlock for (i = 0; i < VaapiDecoderN; ++i) { if (VaapiDecoders[i]) { VaapiDelDecoder(VaapiDecoders[i]); VaapiDecoders[i] = NULL; } } VaapiDecoderN = 0; if (VaOsdImage.image_id != VA_INVALID_ID) { if (vaDestroyImage(VaDisplay, VaOsdImage.image_id) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't destroy image!\n")); } VaOsdImage.image_id = VA_INVALID_ID; } if (VaOsdSubpicture != VA_INVALID_ID) { // still has 35 surfaces associated to it if (vaDestroySubpicture(VaDisplay, VaOsdSubpicture) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't destroy subpicture\n")); } VaOsdSubpicture = VA_INVALID_ID; } if (!VaDisplay) { vaTerminate(VaDisplay); VaDisplay = NULL; } } /// /// Update output for new size or aspect ratio. /// /// @param decoder VA-API decoder /// /// @todo combine VaapiUpdateOutput and VdpauUpdateOutput /// static void VaapiUpdateOutput(VaapiDecoder * decoder) { AVRational input_aspect_ratio; AVRational display_aspect_ratio; input_aspect_ratio = decoder->InputAspect; if (!input_aspect_ratio.num || !input_aspect_ratio.den) { input_aspect_ratio.num = 1; input_aspect_ratio.den = 1; Debug(3, "video: aspect defaults to %d:%d\n", input_aspect_ratio.num, input_aspect_ratio.den); } av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, decoder->InputWidth * input_aspect_ratio.num, decoder->InputHeight * input_aspect_ratio.den, 1024 * 1024); Debug(3, "video: aspect %d:%d\n", display_aspect_ratio.num, display_aspect_ratio.den); // FIXME: store different positions for the ratios if (display_aspect_ratio.num == 4 && display_aspect_ratio.den == 3) { switch (Video4to3ZoomMode) { case VideoNormal: case VideoStretch: case VideoZoom: case VideoAnamorphic: break; } } // FIXME: this overwrites user choosen output position decoder->OutputX = 0; decoder->OutputY = 0; decoder->OutputWidth = (VideoWindowHeight * display_aspect_ratio.num) / display_aspect_ratio.den; decoder->OutputHeight = (VideoWindowWidth * display_aspect_ratio.den) / display_aspect_ratio.num; if ((unsigned)decoder->OutputWidth > VideoWindowWidth) { decoder->OutputWidth = VideoWindowWidth; decoder->OutputY = (VideoWindowHeight - decoder->OutputHeight) / 2; } else if ((unsigned)decoder->OutputHeight > VideoWindowHeight) { decoder->OutputHeight = VideoWindowHeight; decoder->OutputX = (VideoWindowWidth - decoder->OutputWidth) / 2; } Debug(3, "video: aspect output %dx%d+%d+%d\n", decoder->OutputWidth, decoder->OutputHeight, decoder->OutputX, decoder->OutputY); } /// /// Find VA-API profile. /// /// Check if the requested profile is supported by VA-API. /// /// @param profiles a table of all supported profiles /// @param n number of supported profiles /// @param profile requested profile /// /// @returns the profile if supported, -1 if unsupported. /// static VAProfile VaapiFindProfile(const VAProfile * profiles, unsigned n, VAProfile profile) { unsigned u; for (u = 0; u < n; ++u) { if (profiles[u] == profile) { return profile; } } return -1; } /// /// Find VA-API entry point. /// /// Check if the requested entry point is supported by VA-API. /// /// @param entrypoints a table of all supported entrypoints /// @param n number of supported entrypoints /// @param entrypoint requested entrypoint /// /// @returns the entry point if supported, -1 if unsupported. /// static VAEntrypoint VaapiFindEntrypoint(const VAEntrypoint * entrypoints, unsigned n, VAEntrypoint entrypoint) { unsigned u; for (u = 0; u < n; ++u) { if (entrypoints[u] == entrypoint) { return entrypoint; } } return -1; } /// /// 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 PixelFormat Vaapi_get_format(VaapiDecoder * decoder, AVCodecContext * video_ctx, const enum PixelFormat *fmt) { const enum PixelFormat *fmt_idx; VAProfile profiles[vaMaxNumProfiles(VaDisplay)]; int profile_n; VAEntrypoint entrypoints[vaMaxNumEntrypoints(VaDisplay)]; int entrypoint_n; int p; int e; VAConfigAttrib attrib; Debug(3, "video: new stream format %d\n", GetMsTicks() - VideoSwitch); // create initial black surface and display VaapiBlackSurface(decoder); VaapiCleanup(decoder); if (getenv("NO_HW")) { // FIXME: make config option Debug(3, "codec: hardware acceleration disabled\n"); goto slow_path; } p = -1; e = -1; // prepare va-api profiles if (vaQueryConfigProfiles(VaDisplay, profiles, &profile_n)) { Error(_("codec: vaQueryConfigProfiles failed")); goto slow_path; } Debug(3, "codec: %d profiles\n", profile_n); // check profile switch (video_ctx->codec_id) { case CODEC_ID_MPEG2VIDEO: decoder->SurfacesNeeded = CODEC_SURFACES_MPEG2 + VIDEO_SURFACES_MAX; p = VaapiFindProfile(profiles, profile_n, VAProfileMPEG2Main); break; case CODEC_ID_MPEG4: case CODEC_ID_H263: decoder->SurfacesNeeded = CODEC_SURFACES_MPEG4 + VIDEO_SURFACES_MAX; p = VaapiFindProfile(profiles, profile_n, VAProfileMPEG4AdvancedSimple); break; case CODEC_ID_H264: decoder->SurfacesNeeded = CODEC_SURFACES_H264 + VIDEO_SURFACES_MAX; // try more simple formats, fallback to better if (video_ctx->profile == FF_PROFILE_H264_BASELINE) { p = VaapiFindProfile(profiles, profile_n, VAProfileH264Baseline); if (p == -1) { p = VaapiFindProfile(profiles, profile_n, VAProfileH264Main); } } else if (video_ctx->profile == FF_PROFILE_H264_MAIN) { p = VaapiFindProfile(profiles, profile_n, VAProfileH264Main); } if (p == -1) { p = VaapiFindProfile(profiles, profile_n, VAProfileH264High); } break; case CODEC_ID_WMV3: decoder->SurfacesNeeded = CODEC_SURFACES_VC1 + VIDEO_SURFACES_MAX; p = VaapiFindProfile(profiles, profile_n, VAProfileVC1Main); break; case CODEC_ID_VC1: decoder->SurfacesNeeded = CODEC_SURFACES_VC1 + VIDEO_SURFACES_MAX; p = VaapiFindProfile(profiles, profile_n, VAProfileVC1Advanced); break; default: goto slow_path; } if (p == -1) { Debug(3, "\tno profile found\n"); goto slow_path; } Debug(3, "\tprofile %d\n", p); // prepare va-api entry points if (vaQueryConfigEntrypoints(VaDisplay, p, entrypoints, &entrypoint_n)) { Error(_("codec: vaQueryConfigEntrypoints failed")); goto slow_path; } Debug(3, "codec: %d entrypoints\n", entrypoint_n); // look through formats for (fmt_idx = fmt; *fmt_idx != 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 PIX_FMT_VAAPI_VLD: e = VaapiFindEntrypoint(entrypoints, entrypoint_n, VAEntrypointVLD); break; case PIX_FMT_VAAPI_MOCO: case PIX_FMT_VAAPI_IDCT: Debug(3, "codec: this VA-API pixel format is not supported\n"); default: continue; } if (e != -1) { Debug(3, "\tentry point %d\n", e); break; } } if (e == -1) { Warning(_("codec: unsupported: slow path\n")); goto slow_path; } // // prepare decoder // memset(&attrib, 0, sizeof(attrib)); attrib.type = VAConfigAttribRTFormat; if (vaGetConfigAttributes(decoder->VaDisplay, p, e, &attrib, 1)) { Error(_("codec: can't get attributes")); goto slow_path; } if (attrib.value & VA_RT_FORMAT_YUV420) { Info(_("codec: YUV 420 supported\n")); } if (attrib.value & VA_RT_FORMAT_YUV422) { Info(_("codec: YUV 422 supported\n")); } if (attrib.value & VA_RT_FORMAT_YUV444) { Info(_("codec: YUV 444 supported\n")); } if (!(attrib.value & VA_RT_FORMAT_YUV420)) { Warning(_("codec: YUV 420 not supported\n")); goto slow_path; } // create a configuration for the decode pipeline if (vaCreateConfig(decoder->VaDisplay, p, e, &attrib, 1, &decoder->VaapiContext->config_id)) { Error(_("codec: can't create config")); goto slow_path; } // FIXME: interlaced not valid here? decoder->Resolution = VideoResolutionGroup(video_ctx->width, video_ctx->height, decoder->Interlaced); decoder->SurfaceFlags = decoder->SurfaceFlagsTable[decoder->Resolution]; // FIXME: need only to create and destroy surfaces for size changes // or when number of needed surfaces changed! VaapiCreateSurfaces(decoder, video_ctx->width, video_ctx->height); // bind surfaces to context if (vaCreateContext(decoder->VaDisplay, decoder->VaapiContext->config_id, video_ctx->width, video_ctx->height, VA_PROGRESSIVE, decoder->SurfacesFree, decoder->SurfaceFreeN, &decoder->VaapiContext->context_id)) { Error(_("codec: can't create context")); goto slow_path; } decoder->CropX = 0; decoder->CropY = 0; decoder->CropWidth = video_ctx->width; decoder->CropHeight = video_ctx->height; decoder->InputWidth = video_ctx->width; decoder->InputHeight = video_ctx->height; decoder->InputAspect = video_ctx->sample_aspect_ratio; VaapiUpdateOutput(decoder); #ifdef USE_GLX if (GlxEnabled) { GlxSetupDecoder(decoder); // FIXME: try two textures, but vdpau-backend supports only 1 surface if (vaCreateSurfaceGLX(decoder->VaDisplay, GL_TEXTURE_2D, decoder->GlTexture[0], &decoder->GlxSurface[0]) != VA_STATUS_SUCCESS) { Fatal(_("video/glx: can't create glx surfaces")); } // FIXME: this isn't usable with vdpau-backend /* if (vaCreateSurfaceGLX(decoder->VaDisplay, GL_TEXTURE_2D, decoder->GlTexture[1], &decoder->GlxSurface[1]) != VA_STATUS_SUCCESS) { Fatal(_("video/glx: can't create glx surfaces")); } */ } #endif Debug(3, "\t%#010x %s\n", fmt_idx[0], av_get_pix_fmt_name(fmt_idx[0])); return *fmt_idx; slow_path: // no accelerated format found decoder->SurfacesNeeded = 1 + VIDEO_SURFACES_MAX; video_ctx->hwaccel_context = NULL; return avcodec_default_get_format(video_ctx, fmt); } /// /// Draw surface of the VA-API decoder with x11. /// /// vaPutSurface with intel backend does sync on v-sync. /// /// @param decoder VA-API decoder /// @param surface VA-API surface id /// @param interlaced flag interlaced source /// @param top_field_first flag top_field_first for interlaced source /// @param field interlaced draw: 0 first field, 1 second field /// static void VaapiPutSurfaceX11(VaapiDecoder * decoder, VASurfaceID surface, int interlaced, int top_field_first, int field) { unsigned type; VAStatus status; // deinterlace if (interlaced && VideoDeinterlace[decoder->Resolution] != VideoDeinterlaceWeave) { if (top_field_first) { if (field) { type = VA_BOTTOM_FIELD; } else { type = VA_TOP_FIELD; } } else { if (field) { type = VA_TOP_FIELD; } else { type = VA_BOTTOM_FIELD; } } } else { type = VA_FRAME_PICTURE; } xcb_flush(Connection); if ((status = vaPutSurface(decoder->VaDisplay, surface, decoder->Window, // decoder src decoder->CropX, decoder->CropY, decoder->CropWidth, decoder->CropHeight, // video dst decoder->OutputX, decoder->OutputY, decoder->OutputWidth, decoder->OutputHeight, NULL, 0, type | decoder->SurfaceFlags)) != VA_STATUS_SUCCESS) { // switching video kills VdpPresentationQueueBlockUntilSurfaceIdle Error(_("video/vaapi: vaPutSurface failed %d\n"), status); } if (0) { // check if surface is really ready // VDPAU backend, says always ready VASurfaceStatus status; if (vaQuerySurfaceStatus(decoder->VaDisplay, surface, &status) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaQuerySurface failed\n")); status = VASurfaceReady; } if (status != VASurfaceReady) { Warning(_ ("video/vaapi: surface %#x not ready: still displayed %d\n"), surface, status); return; } } if (0) { int i; // look how the status changes the next 40ms for (i = 0; i < 40; ++i) { VASurfaceStatus status; if (vaQuerySurfaceStatus(VaDisplay, surface, &status) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaQuerySurface failed\n")); } Debug(3, "video/vaapi: %2d %d\n", i, status); usleep(1 * 1000); } } } #ifdef USE_GLX /// /// Render texture. /// /// @param texture 2d texture /// static inline void VideoRenderTexture(GLuint texture, int x, int y, int width, int height) { glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // no color glBegin(GL_QUADS); { glTexCoord2f(1.0f, 1.0f); glVertex2i(x + width, y + height); glTexCoord2f(0.0f, 1.0f); glVertex2i(x, y + height); glTexCoord2f(0.0f, 0.0f); glVertex2i(x, y); glTexCoord2f(1.0f, 0.0f); glVertex2i(x + width, y); #if 0 glTexCoord2f(0.0f, 0.0f); glVertex2i(x, y); glTexCoord2f(0.0f, 1.0f); glVertex2i(x, y + height); glTexCoord2f(1.0f, 1.0f); glVertex2i(x + width, y + height); glTexCoord2f(1.0f, 0.0f); glVertex2i(x + width, y); #endif } glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); } /// /// Draw surface of the VA-API decoder with glx. /// /// @param decoder VA-API decoder /// @param surface VA-API surface id /// @param interlaced flag interlaced source /// @param top_field_first flag top_field_first for interlaced source /// @param field interlaced draw: 0 first field, 1 second field /// static void VaapiPutSurfaceGLX(VaapiDecoder * decoder, VASurfaceID surface, int interlaced, int top_field_first, int field) { unsigned type; uint32_t start; uint32_t copy; uint32_t end; // deinterlace if (interlaced && VideoDeinterlace[decoder->Resolution] != VideoDeinterlaceWeave) { if (top_field_first) { if (field) { type = VA_BOTTOM_FIELD; } else { type = VA_TOP_FIELD; } } else { if (field) { type = VA_TOP_FIELD; } else { type = VA_BOTTOM_FIELD; } } } else { type = VA_FRAME_PICTURE; } start = GetMsTicks(); if (vaCopySurfaceGLX(decoder->VaDisplay, decoder->GlxSurface[0], surface, type | decoder->SurfaceFlags) != VA_STATUS_SUCCESS) { Error(_("video/glx: vaCopySurfaceGLX failed\n")); return; } copy = GetMsTicks(); // hardware surfaces are always busy VideoRenderTexture(decoder->GlTexture[0], decoder->OutputX, decoder->OutputY, decoder->OutputWidth, decoder->OutputHeight); end = GetMsTicks(); //Debug(3, "video/vaapi/glx: %d copy %d render\n", copy - start, end - copy); } #endif /// /// Find VA-API image format. /// /// @param decoder VA-API decoder /// @param pix_fmt ffmpeg pixel format /// @param[out] format image format /// /// FIXME: can fallback from I420 to YV12, if not supported /// FIXME: must check if put/get with this format is supported (see intel) /// static int VaapiFindImageFormat(VaapiDecoder * decoder, enum PixelFormat pix_fmt, VAImageFormat * format) { VAImageFormat *imgfrmts; int imgfrmt_n; int i; unsigned fourcc; switch (pix_fmt) { // convert ffmpeg to VA-API // NV12, YV12, I420, BGRA // intel: I420 is native format for MPEG-2 decoded surfaces // intel: NV12 is native format for H.264 decoded surfaces case PIX_FMT_YUV420P: fourcc = VA_FOURCC_YV12; // YVU fourcc = VA_FOURCC('I', '4', '2', '0'); // YUV // FIXME: intel deinterlace ... only supported with nv12 break; case PIX_FMT_NV12: fourcc = VA_FOURCC_NV12; break; default: Fatal(_("video/vaapi: unsupported pixel format %d\n"), pix_fmt); } imgfrmt_n = vaMaxNumImageFormats(decoder->VaDisplay); imgfrmts = alloca(imgfrmt_n * sizeof(*imgfrmts)); if (vaQueryImageFormats(decoder->VaDisplay, imgfrmts, &imgfrmt_n) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaQueryImageFormats failed\n")); return 0; } Debug(3, "video/vaapi: search format %c%c%c%c in %d image formats\n", fourcc, fourcc >> 8, fourcc >> 16, fourcc >> 24, imgfrmt_n); Debug(3, "video/vaapi: supported image formats:\n"); for (i = 0; i < imgfrmt_n; ++i) { Debug(3, "video/vaapi:\t%c%c%c%c\t%d\n", imgfrmts[i].fourcc, imgfrmts[i].fourcc >> 8, imgfrmts[i].fourcc >> 16, imgfrmts[i].fourcc >> 24, imgfrmts[i].depth); } // // search image format // for (i = 0; i < imgfrmt_n; ++i) { if (imgfrmts[i].fourcc == fourcc) { *format = imgfrmts[i]; Debug(3, "video/vaapi: use\t%c%c%c%c\t%d\n", imgfrmts[i].fourcc, imgfrmts[i].fourcc >> 8, imgfrmts[i].fourcc >> 16, imgfrmts[i].fourcc >> 24, imgfrmts[i].depth); return 1; } } Fatal("video/vaapi: pixel format %d unsupported by VA-API\n", pix_fmt); return 0; } /// /// Configure VA-API for new video format. /// /// @param decoder VA-API decoder /// /// @note called only for software decoder. /// static void VaapiSetup(VaapiDecoder * decoder, const AVCodecContext * video_ctx) { int width; int height; VAImageFormat format[1]; // create initial black surface and display VaapiBlackSurface(decoder); // cleanup last context VaapiCleanup(decoder); width = video_ctx->width; height = video_ctx->height; if (decoder->Image->image_id != VA_INVALID_ID) { if (vaDestroyImage(VaDisplay, decoder->Image->image_id) != VA_STATUS_SUCCESS) { Error("video/vaapi: can't destroy image!\n"); } } VaapiFindImageFormat(decoder, video_ctx->pix_fmt, format); if (vaCreateImage(VaDisplay, format, width, height, decoder->Image) != VA_STATUS_SUCCESS) { Fatal("video/vaapi: can't create image!\n"); } Debug(3, "video/vaapi: created image %dx%d with id 0x%08x and buffer id 0x%08x\n", width, height, decoder->Image->image_id, decoder->Image->buf); // FIXME: interlaced not valid here? decoder->Resolution = VideoResolutionGroup(width, height, decoder->Interlaced); decoder->SurfaceFlags = decoder->SurfaceFlagsTable[decoder->Resolution]; VaapiCreateSurfaces(decoder, width, height); #ifdef USE_GLX if (GlxEnabled) { // FIXME: destroy old context GlxSetupDecoder(decoder); // FIXME: try two textures if (vaCreateSurfaceGLX(decoder->VaDisplay, GL_TEXTURE_2D, decoder->GlTexture[0], &decoder->GlxSurface[0]) != VA_STATUS_SUCCESS) { Fatal(_("video/glx: can't create glx surfaces")); } /* if (vaCreateSurfaceGLX(decoder->VaDisplay, GL_TEXTURE_2D, decoder->GlTexture[1], &decoder->GlxSurface[1]) != VA_STATUS_SUCCESS) { Fatal(_("video/glx: can't create glx surfaces")); } */ } #endif } /// /// Queue output surface. /// /// @param decoder VA-API decoder /// @param surface output surface /// @param softdec software decoder /// /// @note we can't mix software and hardware decoder surfaces /// static void VaapiQueueSurface(VaapiDecoder * decoder, VASurfaceID surface, int softdec) { VASurfaceID old; ++decoder->FrameCounter; if (1) { // can't wait for output queue empty if (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) { ++decoder->FramesDropped; Warning(_("video: output buffer full, dropping frame (%d/%d)\n"), decoder->FramesDropped, decoder->FrameCounter); if (!(decoder->FramesDisplayed % 300)) { VaapiPrintFrames(decoder); } if (softdec) { // software surfaces only VaapiReleaseSurface(decoder, surface); } return; } #if 0 } else { // wait for output queue empty while (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) { VideoDisplayHandler(); } #endif } // // Check and release, old surface // if ((old = decoder->SurfacesRb[decoder->SurfaceWrite]) != VA_INVALID_ID) { if (vaSyncSurface(decoder->VaDisplay, old) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaSyncSurface failed\n")); } #if 0 VASurfaceStatus status; if (vaQuerySurfaceStatus(decoder->VaDisplay, old, &status) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaQuerySurface failed\n")); status = VASurfaceReady; } if (status != VASurfaceReady) { Warning(_ ("video/vaapi: surface %#x not ready: still displayed %d\n"), old, status); if (0 && vaSyncSurface(decoder->VaDisplay, old) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaSyncSurface failed\n")); } } #endif // now we can release the surface if (softdec) { // software surfaces only VaapiReleaseSurface(decoder, old); } } #if 1 // FIXME: intel seems to forget this, nvidia GT 210 has speed problems here if (VaapiBuggyIntel && VaOsdSubpicture != VA_INVALID_ID) { // // associate the OSD with surface // if (VaapiUnscaledOsd) { if (vaAssociateSubpicture(VaDisplay, VaOsdSubpicture, &surface, 1, 0, 0, VaOsdImage.width, VaOsdImage.height, 0, 0, VideoWindowWidth, VideoWindowHeight, VA_SUBPICTURE_DESTINATION_IS_SCREEN_COORD) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't associate subpicture\n")); } } else { if (vaAssociateSubpicture(VaDisplay, VaOsdSubpicture, &surface, 1, 0, 0, VaOsdImage.width, VaOsdImage.height, 0, 0, decoder->InputWidth, decoder->InputHeight, 0) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't associate subpicture\n")); } } } #endif decoder->SurfacesRb[decoder->SurfaceWrite] = surface; decoder->SurfaceWrite = (decoder->SurfaceWrite + 1) % VIDEO_SURFACES_MAX; atomic_inc(&decoder->SurfacesFilled); Debug(4, "video/vaapi: yy video surface %#010x ready\n", surface); } #if 0 /// Return the absolute value of an integer. #define ABS(i) ((i) >= 0 ? (i) : (-(i))) /// /// ELA Edge-based Line Averaging /// Low-Complexity Interpolation Method /// /// abcdefg abcdefg abcdefg abcdefg abcdefg /// x x x x x /// hijklmn hijklmn hijklmn hijklmn hijklmn /// static void FilterLine(const uint8_t * past, const uint8_t * cur, const uint8_t * future, int width, int above, int below) { int a, b, c, d, e, f, g, h, i, j, k, l, m, n; } #endif /// /// Create and display a black empty surface. /// /// @param decoder VA-API decoder /// static void VaapiBlackSurface(VaapiDecoder * decoder) { VAStatus status; uint32_t start; uint32_t sync; uint32_t put1; // wait until we have osd subpicture if (VaOsdSubpicture == VA_INVALID_ID) { Warning(_("video/vaapi: no osd subpicture yet\n")); return; } if (decoder->BlackSurface == VA_INVALID_ID) { if (vaCreateSurfaces(decoder->VaDisplay, VideoWindowWidth, VideoWindowHeight, VA_RT_FORMAT_YUV420, 1, &decoder->BlackSurface) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't create a surface\n")); return; } // full sized surface, no difference unscaled/scaled osd if (vaAssociateSubpicture(decoder->VaDisplay, VaOsdSubpicture, &decoder->BlackSurface, 1, 0, 0, VaOsdImage.width, VaOsdImage.height, 0, 0, VideoWindowWidth, VideoWindowHeight, 0) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't associate subpicture\n")); } Debug(3, "video/vaapi: associate %08x\n", decoder->BlackSurface); // FIXME: check if intel forgets this also start = GetMsTicks(); if (vaSyncSurface(decoder->VaDisplay, decoder->BlackSurface) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaSyncSurface failed\n")); } } else { start = GetMsTicks(); } Debug(4, "video/vaapi: yy black video surface %#010x displayed\n", decoder->BlackSurface); sync = GetMsTicks(); xcb_flush(Connection); if ((status = vaPutSurface(decoder->VaDisplay, decoder->BlackSurface, decoder->Window, // decoder src decoder->OutputX, decoder->OutputY, decoder->OutputWidth, decoder->OutputHeight, // video dst decoder->OutputX, decoder->OutputY, decoder->OutputWidth, decoder->OutputHeight, NULL, 0, VA_FRAME_PICTURE)) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaPutSurface failed %d\n"), status); } clock_gettime(CLOCK_REALTIME, &decoder->FrameTime); put1 = GetMsTicks(); Debug(4, "video/vaapi: sync %2u put1 %2u\n", sync - start, put1 - sync); if (0 && vaSyncSurface(decoder->VaDisplay, decoder->BlackSurface) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaSyncSurface failed\n")); } usleep(500); } /// /// Vaapi bob deinterlace. /// /// @note FIXME: use common software deinterlace functions. /// static void VaapiBob(VaapiDecoder * decoder, VAImage * src, VAImage * dst1, VAImage * dst2) { void *src_base; void *dst1_base; void *dst2_base; unsigned y; unsigned p; if (vaMapBuffer(decoder->VaDisplay, src->buf, &src_base) != VA_STATUS_SUCCESS) { Fatal("video/vaapi: can't map the image!\n"); } if (vaMapBuffer(decoder->VaDisplay, dst1->buf, &dst1_base) != VA_STATUS_SUCCESS) { Fatal("video/vaapi: can't map the image!\n"); } if (vaMapBuffer(decoder->VaDisplay, dst2->buf, &dst2_base) != VA_STATUS_SUCCESS) { Fatal("video/vaapi: can't map the image!\n"); } if (1) { memset(dst1_base, 0x00, dst1->data_size); memset(dst2_base, 0x00, dst2->data_size); } for (p = 0; p < src->num_planes; ++p) { for (y = 0; y < (unsigned)(src->height >> (p != 0)); y += 2) { memcpy(dst1_base + src->offsets[p] + y * src->pitches[p], src_base + src->offsets[p] + y * src->pitches[p], src->pitches[p]); memcpy(dst1_base + src->offsets[p] + (y + 1) * src->pitches[p], src_base + src->offsets[p] + y * src->pitches[p], src->pitches[p]); memcpy(dst2_base + src->offsets[p] + y * src->pitches[p], src_base + src->offsets[p] + (y + 1) * src->pitches[p], src->pitches[p]); memcpy(dst2_base + src->offsets[p] + (y + 1) * src->pitches[p], src_base + src->offsets[p] + (y + 1) * src->pitches[p], src->pitches[p]); } } if (vaUnmapBuffer(decoder->VaDisplay, dst2->buf) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't unmap image buffer\n")); } if (vaUnmapBuffer(decoder->VaDisplay, dst1->buf) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't unmap image buffer\n")); } if (vaUnmapBuffer(decoder->VaDisplay, src->buf) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't unmap image buffer\n")); } } /// /// Vaapi software deinterlace. /// static void VaapiCpuDeinterlace(VaapiDecoder * decoder, VASurfaceID surface) { #if 0 VAImage image[1]; VAStatus status; VAImageFormat format[1]; void *image_base; int image_derived; // release old frame // get new frame // deinterlace image_derived = 1; if ((status = vaDeriveImage(decoder->VaDisplay, surface, image)) != VA_STATUS_SUCCESS) { image_derived = 0; Warning(_("video/vaapi: vaDeriveImage failed %d\n"), status); // NV12, YV12, I420, BGRA VaapiFindImageFormat(decoder, PIX_FMT_YUV420P, format); if (vaCreateImage(decoder->VaDisplay, format, decoder->InputWidth, decoder->InputHeight, image) != VA_STATUS_SUCCESS) { Fatal(_("video/vaapi: can't create image!\n")); } if (vaGetImage(decoder->VaDisplay, surface, 0, 0, decoder->InputWidth, decoder->InputHeight, image->image_id) != VA_STATUS_SUCCESS) { Fatal(_("video/vaapi: can't get image!\n")); } } Debug(3, "video/vaapi: %c%c%c%c %dx%d*%d\n", image->format.fourcc, image->format.fourcc >> 8, image->format.fourcc >> 16, image->format.fourcc >> 24, image->width, image->height, image->num_planes); if (vaMapBuffer(decoder->VaDisplay, image->buf, &image_base) != VA_STATUS_SUCCESS) { Fatal("video/vaapi: can't map the image!\n"); } memset(image_base, 0xff, image->width * image->height); if (vaUnmapBuffer(decoder->VaDisplay, image->buf) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't unmap image buffer\n")); } if (!image_derived) { if ((status = vaPutImage(decoder->VaDisplay, surface, image->image_id, 0, 0, image->width, image->height, 0, 0, image->width, image->height)) != VA_STATUS_SUCCESS) { Error("video/vaapi: can't put image %d!\n", status); } } vaDestroyImage(decoder->VaDisplay, image->image_id); #endif VAImage *img1; VAImage *img2; VAImage *img3; VASurfaceID out1; VASurfaceID out2; // // Create deinterlace images. // if (decoder->DeintImages[0].image_id == VA_INVALID_ID) { VAImageFormat format[1]; int i; // NV12, YV12, I420, BGRA // VaapiFindImageFormat(decoder, PIX_FMT_YUV420P, format); // Intel needs NV12 VaapiFindImageFormat(decoder, PIX_FMT_NV12, format); for (i = 0; i < 3; ++i) { if (vaCreateImage(decoder->VaDisplay, format, decoder->InputWidth, decoder->InputHeight, decoder->DeintImages + i) != VA_STATUS_SUCCESS) { Fatal(_("video/vaapi: can't create image!\n")); } } img1 = decoder->DeintImages; Debug(3, "video/vaapi: %c%c%c%c %dx%d*%d\n", img1->format.fourcc, img1->format.fourcc >> 8, img1->format.fourcc >> 16, img1->format.fourcc >> 24, img1->width, img1->height, img1->num_planes); } if (0 && vaSyncSurface(decoder->VaDisplay, surface) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaSyncSurface failed\n")); } img1 = decoder->DeintImages; img2 = decoder->DeintImages + 1; img3 = decoder->DeintImages + 2; if (vaGetImage(decoder->VaDisplay, surface, 0, 0, decoder->InputWidth, decoder->InputHeight, img1->image_id) != VA_STATUS_SUCCESS) { Fatal(_("video/vaapi: can't get img1!\n")); } VaapiBob(decoder, img1, img2, img3); // get a free surface and upload the image out1 = VaapiGetSurface(decoder); if (vaPutImage(VaDisplay, out1, img2->image_id, 0, 0, img2->width, img2->height, 0, 0, img2->width, img2->height) != VA_STATUS_SUCCESS) { Error("video/vaapi: can't put image!\n"); } VaapiQueueSurface(decoder, out1, 1); if (0 && vaSyncSurface(decoder->VaDisplay, out1) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaSyncSurface failed\n")); } // get a free surface and upload the image out2 = VaapiGetSurface(decoder); if (vaPutImage(VaDisplay, out2, img3->image_id, 0, 0, img3->width, img3->height, 0, 0, img3->width, img3->height) != VA_STATUS_SUCCESS) { Error("video/vaapi: can't put image!\n"); } VaapiQueueSurface(decoder, out2, 1); if (0 && vaSyncSurface(decoder->VaDisplay, out2) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaSyncSurface failed\n")); } // FIXME: must release software input surface } /// /// Render a ffmpeg frame /// /// @param decoder VA-API decoder /// @param video_ctx ffmpeg video codec context /// @param frame frame to display /// static void VaapiRenderFrame(VaapiDecoder * decoder, const AVCodecContext * video_ctx, const AVFrame * frame) { VASurfaceID surface; if (video_ctx->height != decoder->InputHeight || video_ctx->width != decoder->InputWidth) { Debug(3, "video/vaapi: stream <-> surface size mismatch\n"); } // // Hardware render // if (video_ctx->hwaccel_context) { int interlaced; surface = (unsigned)(size_t) frame->data[3]; Debug(4, "video/vaapi: hw render hw surface %#x\n", surface); // FIXME: some tv-stations toggle interlace on/off // frame->interlaced_frame isn't always correct set interlaced = frame->interlaced_frame; if (video_ctx->height == 720) { if (interlaced && !decoder->WrongInterlacedWarned) { Debug(3, "video/vaapi: wrong interlace flag fixed\n"); decoder->WrongInterlacedWarned = 1; } interlaced = 0; } else { if (!interlaced && !decoder->WrongInterlacedWarned) { Debug(3, "video/vaapi: wrong interlace flag fixed\n"); decoder->WrongInterlacedWarned = 1; } interlaced = 1; } // update aspect ratio changes #ifdef still_to_detect_define if (av_cmp_q(decoder->InputAspect, frame->sample_aspect_ratio)) { Debug(3, "video/vaapi: aspect ratio changed\n"); //decoder->InputWidth = video_ctx->width; //decoder->InputHeight = video_ctx->height; decoder->InputAspect = frame->sample_aspect_ratio; VaapiUpdateOutput(decoder); } #else if (av_cmp_q(decoder->InputAspect, video_ctx->sample_aspect_ratio)) { Debug(3, "video/vaapi: aspect ratio changed\n"); //decoder->InputWidth = video_ctx->width; //decoder->InputHeight = video_ctx->height; decoder->InputAspect = video_ctx->sample_aspect_ratio; VaapiUpdateOutput(decoder); } #endif if (VideoDeinterlace[decoder->Resolution] == VideoDeinterlaceSoftware && interlaced) { // FIXME: software deinterlace avpicture_deinterlace VaapiCpuDeinterlace(decoder, surface); } else { // FIXME: should be done by init if (decoder->Interlaced != interlaced || decoder->TopFieldFirst != frame->top_field_first) { Debug(3, "video/vaapi: interlaced %d top-field-first %d\n", interlaced, frame->top_field_first); decoder->Interlaced = interlaced; decoder->TopFieldFirst = frame->top_field_first; decoder->SurfaceField = 1; } VaapiQueueSurface(decoder, surface, 0); } // // VAImage render // } else { void *va_image_data; int i; AVPicture picture[1]; int width; int height; Debug(4, "video/vaapi: hw render sw surface\n"); width = video_ctx->width; height = video_ctx->height; // // Check image, format, size // if (decoder->Image->image_id == VA_INVALID_ID || decoder->PixFmt != video_ctx->pix_fmt || width != decoder->InputWidth || height != decoder->InputHeight) { decoder->PixFmt = video_ctx->pix_fmt; decoder->CropX = 0; decoder->CropY = 0; decoder->CropWidth = video_ctx->width; decoder->CropHeight = video_ctx->height; decoder->InputWidth = width; decoder->InputHeight = height; VaapiSetup(decoder, video_ctx); // FIXME: bad interlace like hw-part // FIXME: aspect ratio // // detect interlaced input // Debug(3, "video/vaapi: interlaced %d top-field-first %d\n", frame->interlaced_frame, frame->top_field_first); decoder->Interlaced = frame->interlaced_frame; decoder->TopFieldFirst = frame->top_field_first; decoder->SurfaceField = 1; // FIXME: I hope this didn't change in the middle of the stream } // FIXME: Need to insert software deinterlace here // // Copy data from frame to image // if (vaMapBuffer(VaDisplay, decoder->Image->buf, &va_image_data) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't map the image!\n")); } for (i = 0; (unsigned)i < decoder->Image->num_planes; ++i) { picture->data[i] = va_image_data + decoder->Image->offsets[i]; picture->linesize[i] = decoder->Image->pitches[i]; } av_picture_copy(picture, (AVPicture *) frame, video_ctx->pix_fmt, width, height); if (vaUnmapBuffer(VaDisplay, decoder->Image->buf) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't unmap the image!\n")); } // get a free surface and upload the image surface = VaapiGetSurface(decoder); Debug(4, "video/vaapi: video surface %#010x displayed\n", surface); Debug(4, "video/vaapi: buffer %dx%d <- %dx%d\n", decoder->Image->width, decoder->Image->height, width, height); // FIXME: intel didn't support put image. if ((i = vaPutImage(VaDisplay, surface, decoder->Image->image_id, 0, 0, width, height, 0, 0, width, height) ) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't put image err:%d!\n"), i); } VaapiQueueSurface(decoder, surface, 1); } if (decoder->Interlaced) { ++decoder->FrameCounter; } } /// /// Advance displayed frame. /// static void VaapiAdvanceFrame(void) { int i; // show any frame as fast as possible // we keep always the last frame in the ring buffer for (i = 0; i < VaapiDecoderN; ++i) { VaapiDecoder *decoder; VASurfaceID surface; int filled; decoder = VaapiDecoders[i]; filled = atomic_read(&decoder->SurfacesFilled); // 0 -> 1 // 1 -> 0 + advance if (decoder->Interlaced) { // FIXME: first frame is never shown if (decoder->SurfaceField) { if (filled > 1) { decoder->SurfaceField = 0; } } else { decoder->SurfaceField = 1; return; } } if (filled > 1) { decoder->SurfaceRead = (decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX; atomic_dec(&decoder->SurfacesFilled); // wait for rendering finished surface = decoder->SurfacesRb[decoder->SurfaceRead]; if (vaSyncSurface(decoder->VaDisplay, surface) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: vaSyncSurface failed\n")); } } } } /// /// Display a video frame. /// /// @todo FIXME: add detection of missed frames /// static void VaapiDisplayFrame(void) { struct timespec nowtime; uint32_t start; uint32_t put1; uint32_t put2; int i; VaapiDecoder *decoder; if (VideoSurfaceModesChanged) { // handle changed modes for (i = 0; i < VaapiDecoderN; ++i) { VaapiInitSurfaceFlags(VaapiDecoders[i]); } VideoSurfaceModesChanged = 0; } // look if any stream have a new surface available for (i = 0; i < VaapiDecoderN; ++i) { VASurfaceID surface; int filled; decoder = VaapiDecoders[i]; decoder->FramesDisplayed++; filled = atomic_read(&decoder->SurfacesFilled); // no surface availble show black with possible osd if (!filled) { VaapiBlackSurface(decoder); continue; } surface = decoder->SurfacesRb[decoder->SurfaceRead]; #ifdef DEBUG if (surface == VA_INVALID_ID) { printf(_("video/vaapi: invalid surface in ringbuffer\n")); } Debug(4, "video/vaapi: yy video surface %#010x displayed\n", surface); #endif start = GetMsTicks(); // deinterlace and full frame rate // VDPAU driver only display a frame, if a full frame is put // INTEL driver does the same, but only with 1080i if (0 && decoder->Interlaced // FIXME: buggy libva-driver-vdpau, buggy libva-driver-intel && (VaapiBuggyVdpau || (0 && VaapiBuggyIntel && decoder->InputHeight == 1080)) && VideoDeinterlace[decoder->Resolution] != VideoDeinterlaceWeave) { VaapiPutSurfaceX11(decoder, surface, decoder->Interlaced, decoder->TopFieldFirst, 0); put1 = GetMsTicks(); VaapiPutSurfaceX11(decoder, surface, decoder->Interlaced, decoder->TopFieldFirst, 1); put2 = GetMsTicks(); } else { VaapiPutSurfaceX11(decoder, surface, decoder->Interlaced, decoder->TopFieldFirst, decoder->SurfaceField); put1 = GetMsTicks(); put2 = put1; } clock_gettime(CLOCK_REALTIME, &nowtime); #ifdef noDEBUG if ((nowtime.tv_sec - decoder->FrameTime.tv_sec) * 1000 * 1000 * 1000 + (nowtime.tv_nsec - decoder->FrameTime.tv_nsec) > 21 * 1000 * 1000) { Debug(3, "video/vaapi: time/frame too long %ld ms\n", ((nowtime.tv_sec - decoder->FrameTime.tv_sec) * 1000 * 1000 * 1000 + (nowtime.tv_nsec - decoder->FrameTime.tv_nsec)) / (1000 * 1000)); Debug(4, "video/vaapi: put1 %2u put2 %2u\n", put1 - start, put2 - put1); } if (put2 > start + 20) { Debug(3, "video/vaapi: putsurface too long %u ms\n", put2 - start); } Debug(4, "video/vaapi: put1 %2u put2 %2u\n", put1 - start, put2 - put1); #endif decoder->FrameTime = nowtime; // fixes: [drm:i915_hangcheck_elapsed] *ERROR* Hangcheck // timer elapsed... GPU hung usleep(1 * 1000); } } /// /// Sync and display surface. /// /// @param decoder VA-API decoder /// static void VaapiSyncDisplayFrame(VaapiDecoder * decoder) { int filled; int64_t audio_clock; int64_t video_clock; if (!decoder->DupNextFrame && (!Video60HzMode || decoder->FramesDisplayed % 6)) { VaapiAdvanceFrame(); } // debug duplicate frames filled = atomic_read(&decoder->SurfacesFilled); if (filled == 1) { decoder->FramesDuped++; Warning(_("video: display buffer empty, duping frame (%d/%d)\n"), decoder->FramesDuped, decoder->FrameCounter); if (!(decoder->FramesDisplayed % 300)) { VaapiPrintFrames(decoder); } } VaapiDisplayFrame(); // // audio/video sync // audio_clock = AudioGetClock(); video_clock = VideoGetClock(); // FIXME: audio not known assume 333ms delay if (decoder->DupNextFrame) { decoder->DupNextFrame = 0; } else if ((uint64_t) audio_clock != AV_NOPTS_VALUE && (uint64_t) video_clock != AV_NOPTS_VALUE) { // both clocks are known if (abs(video_clock - audio_clock) > 5000 * 90) { Debug(3, "video: pts difference too big\n"); } else if (video_clock > audio_clock + VideoAudioDelay + 30 * 90) { Debug(3, "video: slow down video\n"); decoder->DupNextFrame = 1; } else if (audio_clock + VideoAudioDelay > video_clock + 50 * 90 && filled > 1) { Debug(3, "video: speed up video\n"); decoder->DropNextFrame = 1; } } if (decoder->DupNextFrame || decoder->DropNextFrame || !(decoder->FramesDisplayed % (50 * 10))) { static int64_t last_video_clock; Debug(3, "video: %09" PRIx64 "-%09" PRIx64 " %4" PRId64 " pts %+dms %" PRId64 "\n", audio_clock, video_clock, video_clock - last_video_clock, (int)(audio_clock - video_clock) / 90, AudioGetDelay() / 90); last_video_clock = video_clock; } } /// /// Sync and render a ffmpeg frame /// /// @param decoder VA-API decoder /// @param video_ctx ffmpeg video codec context /// @param frame frame to display /// static void VaapiSyncRenderFrame(VaapiDecoder * decoder, const AVCodecContext * video_ctx, const AVFrame * frame) { if (!atomic_read(&decoder->SurfacesFilled)) { Debug(3, "video: new stream frame %d\n", GetMsTicks() - VideoSwitch); } if (decoder->DropNextFrame) { // drop frame requested ++decoder->FramesDropped; Warning(_("video: dropping frame (%d/%d)\n"), decoder->FramesDropped, decoder->FrameCounter); if (!(decoder->FramesDisplayed % 300)) { VaapiPrintFrames(decoder); } decoder->DropNextFrame = 0; return; } // if video output buffer is full, wait and display surface. // loop for interlace while (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) { struct timespec abstime; abstime = decoder->FrameTime; abstime.tv_nsec += 14 * 1000 * 1000; if (abstime.tv_nsec >= 1000 * 1000 * 1000) { // avoid overflow abstime.tv_sec++; abstime.tv_nsec -= 1000 * 1000 * 1000; } VideoPollEvent(); // give osd some time slot while (pthread_cond_timedwait(&VideoWakeupCond, &VideoLockMutex, &abstime) != ETIMEDOUT) { // SIGUSR1 Debug(3, "video/vaapi: pthread_cond_timedwait error\n"); } VaapiSyncDisplayFrame(decoder); } VaapiRenderFrame(decoder, video_ctx, frame); } #if 0 /// /// Update video pts. /// /// @param decoder VA-API decoder /// @param frame frame to display /// static void VaapiSetPts(VaapiDecoder * decoder, const AVFrame * frame) { int64_t pts; // update video clock if ((uint64_t) decoder->PTS != AV_NOPTS_VALUE) { decoder->PTS += decoder->Interlaced ? 40 * 90 : 20 * 90; } //pts = frame->best_effort_timestamp; pts = frame->pkt_pts; if ((uint64_t) pts == AV_NOPTS_VALUE || !pts) { // libav: 0.8pre didn't set pts pts = frame->pkt_dts; } if (!pts) { pts = AV_NOPTS_VALUE; } // build a monotonic pts if ((uint64_t) decoder->PTS != AV_NOPTS_VALUE) { if (pts - decoder->PTS < -10 * 90) { pts = AV_NOPTS_VALUE; } } // libav: sets only pkt_dts which can be 0 if ((uint64_t) pts != AV_NOPTS_VALUE) { if (decoder->PTS != pts) { Debug(3, "video: %#012" PRIx64 "->%#012" PRIx64 " %4" PRId64 " pts\n", decoder->PTS, pts, pts - decoder->PTS); decoder->PTS = pts; } } } #endif /// /// Get VA-API decoder video clock. /// /// @param decoder VA-API decoder /// static int64_t VaapiGetClock(const VaapiDecoder * decoder) { // pts is the timestamp of the latest decoded frame if ((uint64_t) decoder->PTS == AV_NOPTS_VALUE) { return AV_NOPTS_VALUE; } // subtract buffered decoded frames if (decoder->Interlaced) { return decoder->PTS - 20 * 90 * (2 * atomic_read(&decoder->SurfacesFilled) - decoder->SurfaceField); } return decoder->PTS - 20 * 90 * (atomic_read(&decoder->SurfacesFilled) - 1); } #ifdef USE_VIDEO_THREAD /// /// Handle a va-api display. /// /// @todo FIXME: only a single decoder supported. /// static void VaapiDisplayHandlerThread(void) { int err; int filled; struct timespec nowtime; VaapiDecoder *decoder; decoder = VaapiDecoders[0]; // // fill frame output ring buffer // filled = atomic_read(&decoder->SurfacesFilled); err = 1; if (filled < VIDEO_SURFACES_MAX) { // FIXME: hot polling pthread_mutex_lock(&VideoLockMutex); // fetch+decode or reopen err = VideoDecode(); pthread_mutex_unlock(&VideoLockMutex); } if (err) { // FIXME: sleep on wakeup usleep(5 * 1000); // nothing buffered } clock_gettime(CLOCK_REALTIME, &nowtime); // time for one frame over? if ((nowtime.tv_sec - decoder->FrameTime.tv_sec) * 1000 * 1000 * 1000 + (nowtime.tv_nsec - decoder->FrameTime.tv_nsec) < 15 * 1000 * 1000) { return; } pthread_mutex_lock(&VideoLockMutex); VaapiSyncDisplayFrame(decoder); pthread_mutex_unlock(&VideoLockMutex); } #endif //---------------------------------------------------------------------------- // VA-API OSD //---------------------------------------------------------------------------- /// /// Clear subpicture image. /// /// @note looked by caller /// static void VaapiOsdClear(void) { void *image_buffer; // osd image available? if (VaOsdImage.image_id == VA_INVALID_ID) { return; } Debug(3, "video/vaapi: clear image\n"); // map osd surface/image into memory. if (vaMapBuffer(VaDisplay, VaOsdImage.buf, &image_buffer) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't map osd image buffer\n")); return; } // 100% transparent memset(image_buffer, 0x00, VaOsdImage.data_size); if (vaUnmapBuffer(VaDisplay, VaOsdImage.buf) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't unmap osd image buffer\n")); } } /// /// Upload ARGB to subpicture image. /// /// @param x x position of image in osd /// @param y y position of image in osd /// @param width width of image /// @param height height of image /// @param argb argb image /// /// @note looked by caller /// static void VaapiUploadImage(int x, int y, int width, int height, const uint8_t * argb) { void *image_buffer; int o; // osd image available? if (VaOsdImage.image_id == VA_INVALID_ID) { return; } Debug(3, "video/vaapi: upload image\n"); // map osd surface/image into memory. if (vaMapBuffer(VaDisplay, VaOsdImage.buf, &image_buffer) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't map osd image buffer\n")); return; } // 100% transparent //memset(image_buffer, 0x00, VaOsdImage.data_size); // FIXME: convert image from ARGB to subpicture format, if not argb // copy argb to image for (o = 0; o < height; ++o) { memcpy(image_buffer + (x + (y + o) * VaOsdImage.width) * 4, argb + o * width * 4, width * 4); } if (vaUnmapBuffer(VaDisplay, VaOsdImage.buf) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't unmap osd image buffer\n")); } } /// /// VA-API initialize OSD. /// /// @param width osd width /// @param height osd height /// /// @note subpicture is unusable, it can be scaled with the video image. /// static void VaapiOsdInit(int width, int height) { VAImageFormat *formats; unsigned *flags; unsigned format_n; unsigned u; unsigned v; static uint32_t wanted_formats[] = { VA_FOURCC('B', 'G', 'R', 'A'), VA_FOURCC_RGBA }; if (VaOsdImage.image_id != VA_INVALID_ID) { Debug(3, "video/vaapi: osd already setup\n"); return; } if (!VaDisplay) { Debug(3, "video/vaapi: va-api not setup\n"); return; } // // look through subpicture formats // format_n = vaMaxNumSubpictureFormats(VaDisplay); formats = alloca(format_n * sizeof(*formats)); flags = alloca(format_n * sizeof(*formats)); if (vaQuerySubpictureFormats(VaDisplay, formats, flags, &format_n) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't get subpicture formats")); return; } #ifdef DEBUG Debug(3, "video/vaapi: supported subpicture formats:\n"); for (u = 0; u < format_n; ++u) { Debug(3, "video/vaapi:\t%c%c%c%c flags %#x %s\n", formats[u].fourcc, formats[u].fourcc >> 8, formats[u].fourcc >> 16, formats[u].fourcc >> 24, flags[u], flags[u] & VA_SUBPICTURE_DESTINATION_IS_SCREEN_COORD ? "screen coord" : ""); } #endif for (v = 0; v < sizeof(wanted_formats) / sizeof(*wanted_formats); ++v) { for (u = 0; u < format_n; ++u) { if (formats[u].fourcc == wanted_formats[v]) { goto found; } } } Error(_("video/vaapi: can't find a supported subpicture format")); return; found: Debug(3, "video/vaapi: use %c%c%c%c subpicture format with flags %#x\n", formats[u].fourcc, formats[u].fourcc >> 8, formats[u].fourcc >> 16, formats[u].fourcc >> 24, flags[u]); VaapiUnscaledOsd = 0; if (flags[u] & VA_SUBPICTURE_DESTINATION_IS_SCREEN_COORD) { Info(_("video/vaapi: vaapi supports unscaled osd\n")); VaapiUnscaledOsd = 1; } //VaapiUnscaledOsd = 0; //Info(_("video/vaapi: unscaled osd disabled\n")); // FIXME: lock if (vaCreateImage(VaDisplay, &formats[u], width, height, &VaOsdImage) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't create osd image\n")); return; } if (vaCreateSubpicture(VaDisplay, VaOsdImage.image_id, &VaOsdSubpicture) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't create subpicture\n")); if (vaDestroyImage(VaDisplay, VaOsdImage.image_id) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't destroy image!\n")); } VaOsdImage.image_id = VA_INVALID_ID; return; } // FIXME: must store format, to convert ARGB to it. VaapiOsdClear(); // FIXME: unlock } #endif //---------------------------------------------------------------------------- // VDPAU //---------------------------------------------------------------------------- #ifdef USE_VDPAU /// /// VDPAU decoder /// typedef struct _vdpau_decoder_ { VdpDevice Device; ///< VDPAU device xcb_window_t Window; ///< output window int OutputX; ///< output window x int OutputY; ///< output window y int OutputWidth; ///< output window width int OutputHeight; ///< output window height enum PixelFormat PixFmt; ///< ffmpeg frame pixfmt int WrongInterlacedWarned; ///< warning about interlace flag issued int Interlaced; ///< ffmpeg interlaced flag int TopFieldFirst; ///< ffmpeg top field displayed first int InputWidth; ///< video input width int InputHeight; ///< video input height AVRational InputAspect; ///< video input aspect ratio VideoResolutions Resolution; ///< resolution group int CropX; ///< video crop x int CropY; ///< video crop y int CropWidth; ///< video crop width int CropHeight; ///< video crop height #ifdef noyetUSE_GLX GLuint GlTexture[2]; ///< gl texture for VDPAU void *GlxSurface[2]; ///< VDPAU/GLX surface #endif VdpDecoder VideoDecoder; ///< vdp video decoder VdpVideoMixer VideoMixer; ///< vdp video mixer VdpChromaType ChromaType; ///< vdp video surface chroma format VdpProcamp Procamp; ///< vdp procamp parameterization data int SurfaceUsedN; ///< number of used video surfaces /// used video surface ids VdpVideoSurface SurfacesUsed[CODEC_SURFACES_MAX]; int SurfaceFreeN; ///< number of free video surfaces /// free video surface ids VdpVideoSurface SurfacesFree[CODEC_SURFACES_MAX]; /// video surface ring buffer VdpVideoSurface SurfacesRb[VIDEO_SURFACES_MAX]; int SurfaceWrite; ///< write pointer int SurfaceRead; ///< read pointer atomic_t SurfacesFilled; ///< how many of the buffer is used int SurfaceField; ///< current displayed field int DropNextFrame; ///< flag drop next frame int DupNextFrame; ///< flag duplicate next frame struct timespec FrameTime; ///< time of last display int64_t PTS; ///< video PTS clock 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 } VdpauDecoder; static int VideoVdpauEnabled = 1; ///< use VDPAU decoder static VdpauDecoder *VdpauDecoders[1]; ///< open decoder streams static int VdpauDecoderN; ///< number of decoder streams static VdpDevice VdpauDevice; ///< VDPAU device static VdpGetProcAddress *VdpauGetProcAddress; ///< entry point to use /// presentation queue target static VdpPresentationQueueTarget VdpauQueueTarget; static VdpPresentationQueue VdpauQueue; ///< presentation queue static VdpColor VdpauBackgroundColor[1]; ///< queue background color static int VdpauHqScalingMax; ///< highest supported scaling level static int VdpauTemporal; ///< temporal deinterlacer supported static int VdpauTemporalSpatial; ///< temporal spatial deint. supported static int VdpauInverseTelecine; ///< inverse telecine deint. supported static int VdpauNoiseReduction; ///< noise reduction supported static int VdpauSharpness; ///< sharpness supported static int VdpauSkipChroma; ///< skip chroma deint. supported /// display surface ring buffer static VdpOutputSurface VdpauSurfacesRb[OUTPUT_SURFACES_MAX]; static int VdpauSurfaceIndex; ///< current display surface static int VdpauOsdWidth; ///< width of osd surface static int VdpauOsdHeight; ///< height of osd surface static int VdpauShowOsd; ///< flag show osd #ifdef USE_BITMAP /// bitmap surfaces for osd static VdpBitmapSurface VdpauOsdBitmapSurface[2] = { VDP_INVALID_HANDLE, VDP_INVALID_HANDLE }; #else /// output surfaces for osd static VdpOutputSurface VdpauOsdOutputSurface[2] = { VDP_INVALID_HANDLE, VDP_INVALID_HANDLE }; #endif static int VdpauOsdSurfaceIndex; ///< index into double buffered osd /// /// Function pointer of the VDPAU device. /// ///@{ static VdpGetErrorString *VdpauGetErrorString; static VdpDeviceDestroy *VdpauDeviceDestroy; static VdpGenerateCSCMatrix *VdpauGenerateCSCMatrix; static VdpVideoSurfaceQueryCapabilities *VdpauVideoSurfaceQueryCapabilities; static VdpVideoSurfaceQueryGetPutBitsYCbCrCapabilities * VdpauVideoSurfaceQueryGetPutBitsYCbCrCapabilities; static VdpVideoSurfaceCreate *VdpauVideoSurfaceCreate; static VdpVideoSurfaceDestroy *VdpauVideoSurfaceDestroy; static VdpVideoSurfaceGetParameters *VdpauVideoSurfaceGetParameters; static VdpVideoSurfaceGetBitsYCbCr *VdpauVideoSurfaceGetBitsYCbCr; static VdpVideoSurfacePutBitsYCbCr *VdpauVideoSurfacePutBitsYCbCr; static VdpOutputSurfaceQueryCapabilities *VdpauOutputSurfaceQueryCapabilities; static VdpOutputSurfaceCreate *VdpauOutputSurfaceCreate; static VdpOutputSurfaceDestroy *VdpauOutputSurfaceDestroy; static VdpOutputSurfacePutBitsNative *VdpauOutputSurfacePutBitsNative; static VdpBitmapSurfaceQueryCapabilities *VdpauBitmapSurfaceQueryCapabilities; static VdpBitmapSurfaceCreate *VdpauBitmapSurfaceCreate; static VdpBitmapSurfaceDestroy *VdpauBitmapSurfaceDestroy; static VdpBitmapSurfacePutBitsNative *VdpauBitmapSurfacePutBitsNative; static VdpOutputSurfaceRenderOutputSurface *VdpauOutputSurfaceRenderOutputSurface; static VdpOutputSurfaceRenderBitmapSurface *VdpauOutputSurfaceRenderBitmapSurface; static VdpDecoderQueryCapabilities *VdpauDecoderQueryCapabilities; static VdpDecoderCreate *VdpauDecoderCreate; static VdpDecoderDestroy *VdpauDecoderDestroy; static VdpDecoderRender *VdpauDecoderRender; static VdpVideoMixerQueryFeatureSupport *VdpauVideoMixerQueryFeatureSupport; static VdpVideoMixerQueryAttributeSupport *VdpauVideoMixerQueryAttributeSupport; static VdpVideoMixerCreate *VdpauVideoMixerCreate; static VdpVideoMixerSetFeatureEnables *VdpauVideoMixerSetFeatureEnables; static VdpVideoMixerSetAttributeValues *VdpauVideoMixerSetAttributeValues; static VdpVideoMixerDestroy *VdpauVideoMixerDestroy; static VdpVideoMixerRender *VdpauVideoMixerRender; static VdpPresentationQueueTargetDestroy *VdpauPresentationQueueTargetDestroy; static VdpPresentationQueueCreate *VdpauPresentationQueueCreate; static VdpPresentationQueueDestroy *VdpauPresentationQueueDestroy; static VdpPresentationQueueSetBackgroundColor * VdpauPresentationQueueSetBackgroundColor; static VdpPresentationQueueGetTime *VdpauPresentationQueueGetTime; static VdpPresentationQueueDisplay *VdpauPresentationQueueDisplay; static VdpPresentationQueueBlockUntilSurfaceIdle *VdpauPresentationQueueBlockUntilSurfaceIdle; static VdpPresentationQueueQuerySurfaceStatus *VdpauPresentationQueueQuerySurfaceStatus; static VdpPresentationQueueTargetCreateX11 * VdpauPresentationQueueTargetCreateX11; ///@} /// /// Create surfaces for VDPAU decoder. /// /// @param decoder VDPAU hw decoder /// @param width surface source/video width /// @param height surface source/video height /// static void VdpauCreateSurfaces(VdpauDecoder * decoder, int width, int height) { int i; Debug(3, "video/vdpau: %s: %dx%d * %d\n", __FUNCTION__, width, height, CODEC_SURFACES_DEFAULT); // FIXME: allocate only the number of needed surfaces decoder->SurfaceFreeN = CODEC_SURFACES_DEFAULT; for (i = 0; i < decoder->SurfaceFreeN; ++i) { VdpStatus status; status = VdpauVideoSurfaceCreate(decoder->Device, decoder->ChromaType, width, height, decoder->SurfacesFree + i); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't create video surface: %s\n"), VdpauGetErrorString(status)); decoder->SurfacesFree[i] = VDP_INVALID_HANDLE; // FIXME: better error handling } Debug(4, "video/vdpau: created video surface %dx%d with id 0x%08x\n", width, height, decoder->SurfacesFree[i]); } } /// /// Destroy surfaces of VDPAU decoder. /// /// @param decoder VDPAU hw decoder /// static void VdpauDestroySurfaces(VdpauDecoder * decoder) { int i; VdpStatus status; Debug(3, "video/vdpau: %s\n", __FUNCTION__); for (i = 0; i < decoder->SurfaceFreeN; ++i) { #ifdef DEBUG if (decoder->SurfacesFree[i] == VDP_INVALID_HANDLE) { Debug(3, "video/vdpau: invalid surface\n"); } #endif Debug(4, "video/vdpau: destroy video surface with id 0x%08x\n", decoder->SurfacesFree[i]); status = VdpauVideoSurfaceDestroy(decoder->SurfacesFree[i]); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't destroy video surface: %s\n"), VdpauGetErrorString(status)); } decoder->SurfacesFree[i] = VDP_INVALID_HANDLE; } for (i = 0; i < decoder->SurfaceUsedN; ++i) { #ifdef DEBUG if (decoder->SurfacesUsed[i] == VDP_INVALID_HANDLE) { Debug(3, "video/vdpau: invalid surface\n"); } #endif Debug(4, "video/vdpau: destroy video surface with id 0x%08x\n", decoder->SurfacesUsed[i]); status = VdpauVideoSurfaceDestroy(decoder->SurfacesUsed[i]); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't destroy video surface: %s\n"), VdpauGetErrorString(status)); } decoder->SurfacesUsed[i] = VDP_INVALID_HANDLE; } decoder->SurfaceFreeN = 0; decoder->SurfaceUsedN = 0; } /// /// Get a free surface. /// /// @param decoder VDPAU hw decoder /// /// @returns the oldest free surface /// static unsigned VdpauGetSurface(VdpauDecoder * decoder) { VdpVideoSurface surface; int i; if (!decoder->SurfaceFreeN) { Error(_("video/vdpau: out of surfaces\n")); return VDP_INVALID_HANDLE; } // 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] = VDP_INVALID_HANDLE; // save as used decoder->SurfacesUsed[decoder->SurfaceUsedN++] = surface; return surface; } /// /// Release a surface. /// /// @param decoder VDPAU hw decoder /// @param surface surface no longer used /// static void VdpauReleaseSurface(VdpauDecoder * decoder, unsigned surface) { int i; for (i = 0; i < decoder->SurfaceUsedN; ++i) { if (decoder->SurfacesUsed[i] == surface) { // no problem, with last used decoder->SurfacesUsed[i] = decoder->SurfacesUsed[--decoder->SurfaceUsedN]; decoder->SurfacesFree[decoder->SurfaceFreeN++] = surface; return; } } Error(_("video/vdpau: release surface %#x, which is not in use\n"), surface); } /// /// Debug VDPAU decoder frames drop... /// /// @param decoder VDPAU hw decoder /// static void VdpauPrintFrames(const VdpauDecoder * decoder) { Debug(3, "video/vdpau: %d missed, %d duped, %d dropped frames of %d\n", decoder->FramesMissed, decoder->FramesDuped, decoder->FramesDropped, decoder->FrameCounter); #ifndef DEBUG (void)decoder; #endif } /// /// Create and setup VDPAU mixer. /// /// @param decoder VDPAU hw decoder /// /// @note don't forget to update features, paramaters, attributes table /// size, if more is add. /// static void VdpauMixerSetup(VdpauDecoder * decoder) { VdpStatus status; int i; VdpVideoMixerFeature features[15]; VdpBool enables[15]; int feature_n; VdpVideoMixerParameter paramaters[4]; void const *value_ptrs[4]; int parameter_n; VdpVideoMixerAttribute attributes[4]; void const *attribute_value_ptrs[4]; int attribute_n; uint8_t skip_chroma_value; float noise_reduction_level; float sharpness_level; VdpChromaType chroma_type; int layers; VdpColorStandard color_standard; VdpCSCMatrix csc_matrix[1]; // // Build feature table // feature_n = 0; if (VdpauTemporal) { features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL; } if (VdpauTemporalSpatial) { features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL; } if (VdpauInverseTelecine) { features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE; } if (VdpauNoiseReduction) { features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION; } if (VdpauSharpness) { features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_SHARPNESS; } for (i = VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1; i <= VdpauHqScalingMax; ++i) { features[feature_n++] = i; } decoder->ChromaType = chroma_type = VDP_CHROMA_TYPE_420; // FIXME: use best chroma // // Setup parameter/value tables // paramaters[0] = VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH; value_ptrs[0] = &decoder->InputWidth; paramaters[1] = VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT; value_ptrs[1] = &decoder->InputHeight; paramaters[2] = VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE; value_ptrs[2] = &chroma_type; layers = 0; paramaters[3] = VDP_VIDEO_MIXER_PARAMETER_LAYERS; value_ptrs[3] = &layers; parameter_n = 4; status = VdpauVideoMixerCreate(VdpauDevice, feature_n, features, parameter_n, paramaters, value_ptrs, &decoder->VideoMixer); if (status != VDP_STATUS_OK) { Fatal(_("video/vdpau: can't create video mixer: %s\n"), VdpauGetErrorString(status)); // FIXME: no fatal errors } // // Build default enables table // feature_n = 0; if (VdpauTemporal) { enables[feature_n] = (VideoDeinterlace[decoder->Resolution] == VideoDeinterlaceTemporal || (VideoDeinterlace[decoder->Resolution] == VideoDeinterlaceTemporalSpatial && !VdpauTemporalSpatial)) ? VDP_TRUE : VDP_FALSE; features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL; Debug(3, "video/vdpau: temporal deinterlace %s\n", enables[feature_n - 1] ? "enabled" : "disabled"); } if (VdpauTemporalSpatial) { enables[feature_n] = VideoDeinterlace[decoder->Resolution] == VideoDeinterlaceTemporalSpatial ? VDP_TRUE : VDP_FALSE; features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL; Debug(3, "video/vdpau: temporal spatial deinterlace %s\n", enables[feature_n - 1] ? "enabled" : "disabled"); } if (VdpauInverseTelecine) { enables[feature_n] = VDP_FALSE; features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE; Debug(3, "video/vdpau: inverse telecine %s\n", enables[feature_n - 1] ? "enabled" : "disabled"); } if (VdpauNoiseReduction) { enables[feature_n] = VideoDenoise[decoder->Resolution] ? VDP_TRUE : VDP_FALSE; features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION; Debug(3, "video/vdpau: noise reduction %s\n", enables[feature_n - 1] ? "enabled" : "disabled"); } if (VdpauSharpness) { enables[feature_n] = VideoSharpen[decoder->Resolution] ? VDP_TRUE : VDP_FALSE; features[feature_n++] = VDP_VIDEO_MIXER_FEATURE_SHARPNESS; Debug(3, "video/vdpau: sharpness %s\n", enables[feature_n - 1] ? "enabled" : "disabled"); } for (i = VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1; i <= VdpauHqScalingMax; ++i) { enables[feature_n] = VideoScaling[decoder->Resolution] == VideoScalingHQ ? VDP_TRUE : VDP_FALSE; features[feature_n++] = i; Debug(3, "video/vdpau: high quality scaling %d %s\n", 1 + i - VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1, enables[feature_n - 1] ? "enabled" : "disabled"); } VdpauVideoMixerSetFeatureEnables(decoder->VideoMixer, feature_n, features, enables); // FIXME: check status // // build attributes table // /* FIXME: VDP_VIDEO_MIXER_ATTRIBUTE_LUMA_KEY_MIN_LUMA VDP_VIDEO_MIXER_ATTRIBUTE_LUMA_KEY_MAX_LUMA */ attribute_n = 0; if (VdpauSkipChroma) { skip_chroma_value = VideoSkipChromaDeinterlace[decoder->Resolution]; attributes[attribute_n] = VDP_VIDEO_MIXER_ATTRIBUTE_SKIP_CHROMA_DEINTERLACE; attribute_value_ptrs[attribute_n++] = &skip_chroma_value; Debug(3, "video/vdpau: skip chroma deinterlace %s\n", skip_chroma_value ? "enabled" : "disabled"); } if (VdpauNoiseReduction) { noise_reduction_level = VideoDenoise[decoder->Resolution] / 1000.0; attributes[attribute_n] = VDP_VIDEO_MIXER_ATTRIBUTE_NOISE_REDUCTION_LEVEL; attribute_value_ptrs[attribute_n++] = &noise_reduction_level; Debug(3, "video/vdpau: noise reduction level %1.3f\n", noise_reduction_level); } if (VdpauSharpness) { sharpness_level = VideoSharpen[decoder->Resolution] / 1000.0; attributes[attribute_n] = VDP_VIDEO_MIXER_ATTRIBUTE_SHARPNESS_LEVEL; attribute_value_ptrs[attribute_n++] = &sharpness_level; Debug(3, "video/vdpau: sharpness level %+1.3f\n", sharpness_level); } if (decoder->InputWidth > 1280 || decoder->InputHeight > 576) { // HDTV color_standard = VDP_COLOR_STANDARD_ITUR_BT_709; Debug(3, "video/vdpau: color space ITU-R BT.709\n"); } else { // SDTV color_standard = VDP_COLOR_STANDARD_ITUR_BT_601; Debug(3, "video/vdpau: color space ITU-R BT.601\n"); } status = VdpauGenerateCSCMatrix(&decoder->Procamp, color_standard, csc_matrix); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't generate CSC matrix: %s\n"), VdpauGetErrorString(status)); } attributes[attribute_n] = VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX; attribute_value_ptrs[attribute_n++] = csc_matrix; VdpauVideoMixerSetAttributeValues(decoder->VideoMixer, attribute_n, attributes, attribute_value_ptrs); // FIXME: check status } /// /// Allocate new VDPAU decoder. /// /// @returns a new prepared vdpau hardware decoder. /// static VdpauDecoder *VdpauNewDecoder(void) { VdpauDecoder *decoder; int i; if (VdpauDecoderN == 1) { Fatal(_("video/vdpau: out of decoders\n")); } if (!(decoder = calloc(1, sizeof(*decoder)))) { Fatal(_("video/vdpau: out of memory\n")); } decoder->Device = VdpauDevice; decoder->Window = VideoWindow; decoder->VideoDecoder = VDP_INVALID_HANDLE; decoder->VideoMixer = VDP_INVALID_HANDLE; for (i = 0; i < CODEC_SURFACES_MAX; ++i) { decoder->SurfacesUsed[i] = VDP_INVALID_HANDLE; decoder->SurfacesFree[i] = VDP_INVALID_HANDLE; } // // setup video surface ring buffer // atomic_set(&decoder->SurfacesFilled, 0); for (i = 0; i < VIDEO_SURFACES_MAX; ++i) { decoder->SurfacesRb[i] = VDP_INVALID_HANDLE; } // we advance before display, to loose no surface, we set it before //decoder->SurfaceRead = VIDEO_SURFACES_MAX - 1; //decoder->SurfaceField = 1; #ifdef DEBUG if (VIDEO_SURFACES_MAX < 1 + 1 + 1 + 1) { Fatal(_ ("video/vdpau: need 1 future, 1 current, 1 back and 1 work surface\n")); } #endif decoder->OutputWidth = VideoWindowWidth; decoder->OutputHeight = VideoWindowHeight; // Procamp operation parameterization data decoder->Procamp.struct_version = VDP_PROCAMP_VERSION; decoder->Procamp.brightness = 0.0; decoder->Procamp.contrast = 1.0; decoder->Procamp.saturation = 1.0; decoder->Procamp.hue = 0.0; // default values // FIXME: hack VdpauDecoderN = 1; VdpauDecoders[0] = decoder; return decoder; } /// /// Cleanup VDPAU. /// /// @param decoder VDPAU hw decoder /// static void VdpauCleanup(VdpauDecoder * decoder) { VdpStatus status; int i; if (decoder->VideoDecoder != VDP_INVALID_HANDLE) { status = VdpauDecoderDestroy(decoder->VideoDecoder); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't destroy video decoder: %s\n"), VdpauGetErrorString(status)); } decoder->VideoDecoder = VDP_INVALID_HANDLE; } if (decoder->VideoMixer != VDP_INVALID_HANDLE) { status = VdpauVideoMixerDestroy(decoder->VideoMixer); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't destroy video mixer: %s\n"), VdpauGetErrorString(status)); } decoder->VideoMixer = VDP_INVALID_HANDLE; } if (decoder->SurfaceFreeN || decoder->SurfaceUsedN) { VdpauDestroySurfaces(decoder); } // // reset video surface ring buffer // atomic_set(&decoder->SurfacesFilled, 0); for (i = 0; i < VIDEO_SURFACES_MAX; ++i) { decoder->SurfacesRb[i] = VDP_INVALID_HANDLE; } decoder->SurfaceRead = 0; decoder->SurfaceWrite = 0; decoder->SurfaceField = 0; decoder->PTS = AV_NOPTS_VALUE; } /// /// Destroy a VDPAU decoder. /// /// @param decoder VDPAU hw decoder /// static void VdpauDelDecoder(VdpauDecoder * decoder) { VdpauCleanup(decoder); VdpauPrintFrames(decoder); free(decoder); } /// /// Get the proc address. /// /// @param id VDP function id /// @param[out] addr address of VDP function /// @param name name of function for error message /// static inline void VdpauGetProc(const VdpFuncId id, void *addr, const char *name) { VdpStatus status; status = VdpauGetProcAddress(VdpauDevice, id, addr); if (status != VDP_STATUS_OK) { Fatal(_("video/vdpau: Can't get function address of '%s': %s\n"), name, VdpauGetErrorString(status)); } } /// /// Initialize output queue. /// static void VdpauInitOutputQueue(void) { VdpStatus status; int i; status = VdpauPresentationQueueTargetCreateX11(VdpauDevice, VideoWindow, &VdpauQueueTarget); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't create presentation queue target: %s\n"), VdpauGetErrorString(status)); return; } status = VdpauPresentationQueueCreate(VdpauDevice, VdpauQueueTarget, &VdpauQueue); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't create presentation queue: %s\n"), VdpauGetErrorString(status)); VdpauPresentationQueueTargetDestroy(VdpauQueueTarget); VdpauQueueTarget = 0; return; } VdpauBackgroundColor->red = 0.01; VdpauBackgroundColor->green = 0.02; VdpauBackgroundColor->blue = 0.03; VdpauBackgroundColor->alpha = 1.00; VdpauPresentationQueueSetBackgroundColor(VdpauQueue, VdpauBackgroundColor); // // Create display output surfaces // for (i = 0; i < OUTPUT_SURFACES_MAX; ++i) { VdpRGBAFormat format; format = VDP_RGBA_FORMAT_B8G8R8A8; // FIXME: does a 10bit rgba produce a better output? // format = VDP_RGBA_FORMAT_R10G10B10A2; status = VdpauOutputSurfaceCreate(VdpauDevice, format, VideoWindowWidth, VideoWindowHeight, VdpauSurfacesRb + i); if (status != VDP_STATUS_OK) { Fatal(_("video/vdpau: can't create output surface: %s\n"), VdpauGetErrorString(status)); } Debug(3, "video/vdpau: created output surface %dx%d with id 0x%08x\n", VideoWindowWidth, VideoWindowHeight, VdpauSurfacesRb[i]); } } /// /// Cleanup output queue. /// static void VdpauExitOutputQueue(void) { int i; if (VdpauQueue) { VdpauPresentationQueueDestroy(VdpauQueue); VdpauQueue = 0; } if (VdpauQueueTarget) { VdpauPresentationQueueTargetDestroy(VdpauQueueTarget); VdpauQueueTarget = 0; } // // destroy display output surfaces // for (i = 0; i < OUTPUT_SURFACES_MAX; ++i) { VdpStatus status; Debug(4, "video/vdpau: destroy output surface with id 0x%08x\n", VdpauSurfacesRb[i]); if ( VdpauSurfacesRb[i] != VDP_INVALID_HANDLE ) { status = VdpauOutputSurfaceDestroy(VdpauSurfacesRb[i]); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't destroy output surface: %s\n"), VdpauGetErrorString(status)); } VdpauSurfacesRb[i] = VDP_INVALID_HANDLE; } } } /// /// VDPAU setup. /// /// @param display_name x11/xcb display name /// static void VideoVdpauInit(const char *display_name) { VdpStatus status; VdpGetApiVersion *get_api_version; uint32_t api_version; VdpGetInformationString *get_information_string; const char *information_string; int i; VdpBool flag; uint32_t max_width; uint32_t max_height; status = vdp_device_create_x11(XlibDisplay, DefaultScreen(XlibDisplay), &VdpauDevice, &VdpauGetProcAddress); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: Can't create vdp device on display '%s'\n"), display_name); VideoVdpauEnabled = 0; return; } // get error function first, for better error messages status = VdpauGetProcAddress(VdpauDevice, VDP_FUNC_ID_GET_ERROR_STRING, (void **)&VdpauGetErrorString); if (status != VDP_STATUS_OK) { Error(_ ("video/vdpau: Can't get function address of 'GetErrorString'\n")); VideoVdpauEnabled = 0; // FIXME: destroy_x11 VdpauDeviceDestroy return; } // get destroy device next, for cleaning up VdpauGetProc(VDP_FUNC_ID_DEVICE_DESTROY, &VdpauDeviceDestroy, "DeviceDestroy"); // get version VdpauGetProc(VDP_FUNC_ID_GET_API_VERSION, &get_api_version, "GetApiVersion"); VdpauGetProc(VDP_FUNC_ID_GET_INFORMATION_STRING, &get_information_string, "VdpauGetProc"); status = get_api_version(&api_version); // FIXME: check status status = get_information_string(&information_string); // FIXME: check status Info(_("video/vdpau: VDPAU API version: %u\n"), api_version); Info(_("video/vdpau: VDPAU information: %s\n"), information_string); // FIXME: check if needed capabilities are available VdpauGetProc(VDP_FUNC_ID_GENERATE_CSC_MATRIX, &VdpauGenerateCSCMatrix, "GenerateCSCMatrix"); VdpauGetProc(VDP_FUNC_ID_VIDEO_SURFACE_QUERY_CAPABILITIES, &VdpauVideoSurfaceQueryCapabilities, "VideoSurfaceQueryCapabilities"); VdpauGetProc (VDP_FUNC_ID_VIDEO_SURFACE_QUERY_GET_PUT_BITS_Y_CB_CR_CAPABILITIES, &VdpauVideoSurfaceQueryGetPutBitsYCbCrCapabilities, "VideoSurfaceQueryGetPutBitsYCbCrCapabilities"); VdpauGetProc(VDP_FUNC_ID_VIDEO_SURFACE_CREATE, &VdpauVideoSurfaceCreate, "VideoSurfaceCreate"); VdpauGetProc(VDP_FUNC_ID_VIDEO_SURFACE_DESTROY, &VdpauVideoSurfaceDestroy, "VideoSurfaceDestroy"); VdpauGetProc(VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, &VdpauVideoSurfaceGetParameters, "VideoSurfaceGetParameters"); VdpauGetProc(VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR, &VdpauVideoSurfaceGetBitsYCbCr, "VideoSurfaceGetBitsYCbCr"); VdpauGetProc(VDP_FUNC_ID_VIDEO_SURFACE_PUT_BITS_Y_CB_CR, &VdpauVideoSurfacePutBitsYCbCr, "VideoSurfacePutBitsYCbCr"); VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_QUERY_CAPABILITIES, &VdpauOutputSurfaceQueryCapabilities, "OutputSurfaceQueryCapabilities"); #if 0 VdpauGetProc (VDP_FUNC_ID_OUTPUT_SURFACE_QUERY_GET_PUT_BITS_NATIVE_CAPABILITIES, &, ""); VdpauGetProc (VDP_FUNC_ID_OUTPUT_SURFACE_QUERY_PUT_BITS_INDEXED_CAPABILITIES, &, ""); VdpauGetProc (VDP_FUNC_ID_OUTPUT_SURFACE_QUERY_PUT_BITS_Y_CB_CR_CAPABILITIES, &, ""); #endif VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_CREATE, &VdpauOutputSurfaceCreate, "OutputSurfaceCreate"); VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_DESTROY, &VdpauOutputSurfaceDestroy, "OutputSurfaceDestroy"); #if 0 VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_GET_PARAMETERS, &, ""); VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_GET_BITS_NATIVE, &, ""); #endif VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_PUT_BITS_NATIVE, &VdpauOutputSurfacePutBitsNative, "OutputSurfacePutBitsNative"); #if 0 VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_PUT_BITS_INDEXED, &, ""); VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_PUT_BITS_Y_CB_CR, &, ""); #endif VdpauGetProc(VDP_FUNC_ID_BITMAP_SURFACE_QUERY_CAPABILITIES, &VdpauBitmapSurfaceQueryCapabilities, "BitmapSurfaceQueryCapabilities"); VdpauGetProc(VDP_FUNC_ID_BITMAP_SURFACE_CREATE, &VdpauBitmapSurfaceCreate, "BitmapSurfaceCreate"); VdpauGetProc(VDP_FUNC_ID_BITMAP_SURFACE_DESTROY, &VdpauBitmapSurfaceDestroy, "BitmapSurfaceDestroy"); // VdpauGetProc(VDP_FUNC_ID_BITMAP_SURFACE_GET_PARAMETERS, &VdpauBitmapSurfaceGetParameters, "BitmapSurfaceGetParameters"); VdpauGetProc(VDP_FUNC_ID_BITMAP_SURFACE_PUT_BITS_NATIVE, &VdpauBitmapSurfacePutBitsNative, "BitmapSurfacePutBitsNative"); VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_RENDER_OUTPUT_SURFACE, &VdpauOutputSurfaceRenderOutputSurface, "OutputSurfaceRenderOutputSurface"); VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_RENDER_BITMAP_SURFACE, &VdpauOutputSurfaceRenderBitmapSurface, "OutputSurfaceRenderBitmapSurface"); #if 0 VdpauGetProc(VDP_FUNC_ID_OUTPUT_SURFACE_RENDER_VIDEO_SURFACE_LUMA, &, ""); #endif VdpauGetProc(VDP_FUNC_ID_DECODER_QUERY_CAPABILITIES, &VdpauDecoderQueryCapabilities, "DecoderQueryCapabilities"); VdpauGetProc(VDP_FUNC_ID_DECODER_CREATE, &VdpauDecoderCreate, "DecoderCreate"); VdpauGetProc(VDP_FUNC_ID_DECODER_DESTROY, &VdpauDecoderDestroy, "DecoderDestroy"); #if 0 VdpauGetProc(VDP_FUNC_ID_DECODER_GET_PARAMETERS, &VdpauDecoderGetParameters, "DecoderGetParameters"); #endif VdpauGetProc(VDP_FUNC_ID_DECODER_RENDER, &VdpauDecoderRender, "DecoderRender"); VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_QUERY_FEATURE_SUPPORT, &VdpauVideoMixerQueryFeatureSupport, "VideoMixerQueryFeatureSupport"); #if 0 VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_QUERY_PARAMETER_SUPPORT, &, ""); #endif VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_QUERY_ATTRIBUTE_SUPPORT, &VdpauVideoMixerQueryAttributeSupport, "VideoMixerQueryAttributeSupport"); #if 0 VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_QUERY_PARAMETER_VALUE_RANGE, &, ""); VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_QUERY_ATTRIBUTE_VALUE_RANGE, &, ""); #endif VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_CREATE, &VdpauVideoMixerCreate, "VideoMixerCreate"); VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_SET_FEATURE_ENABLES, &VdpauVideoMixerSetFeatureEnables, "VideoMixerSetFeatureEnables"); VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_SET_ATTRIBUTE_VALUES, &VdpauVideoMixerSetAttributeValues, "VideoMixerSetAttributeValues"); #if 0 VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_GET_FEATURE_SUPPORT, &, ""); VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_GET_FEATURE_ENABLES, &, ""); VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_GET_PARAMETER_VALUES, &, ""); VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_GET_ATTRIBUTE_VALUES, &, ""); #endif VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_DESTROY, &VdpauVideoMixerDestroy, "VideoMixerDestroy"); VdpauGetProc(VDP_FUNC_ID_VIDEO_MIXER_RENDER, &VdpauVideoMixerRender, "VideoMixerRender"); VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_DESTROY, &VdpauPresentationQueueTargetDestroy, "PresentationQueueTargetDestroy"); VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_CREATE, &VdpauPresentationQueueCreate, "PresentationQueueCreate"); VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_DESTROY, &VdpauPresentationQueueDestroy, "PresentationQueueDestroy"); VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_SET_BACKGROUND_COLOR, &VdpauPresentationQueueSetBackgroundColor, "PresentationQueueSetBackgroundColor"); #if 0 VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_GET_BACKGROUND_COLOR, &VdpauPresentationQueueGetBackgroundColor, "PresentationQueueGetBackgroundColor"); #endif VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_GET_TIME, &VdpauPresentationQueueGetTime, "PresentationQueueGetTime"); VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_DISPLAY, &VdpauPresentationQueueDisplay, "PresentationQueueDisplay"); VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_BLOCK_UNTIL_SURFACE_IDLE, &VdpauPresentationQueueBlockUntilSurfaceIdle, "PresentationQueueBlockUntilSurfaceIdle"); VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_QUERY_SURFACE_STATUS, &VdpauPresentationQueueQuerySurfaceStatus, "PresentationQueueQuerySurfaceStatus"); #if 0 VdpauGetProc(VDP_FUNC_ID_PREEMPTION_CALLBACK_REGISTER, &VdpauPreemptionCallback, "PreemptionCallback"); #endif VdpauGetProc(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_CREATE_X11, &VdpauPresentationQueueTargetCreateX11, "PresentationQueueTargetCreateX11"); // vdp_preemption_callback_register // // Look which levels of high quality scaling are supported // for (i = 0; i < 9; ++i) { status = VdpauVideoMixerQueryFeatureSupport(VdpauDevice, VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 + i, &flag); if (status != VDP_STATUS_OK) { Warning(_("video/vdpau: can't query feature '%s': %s\n"), "high-quality-scaling", VdpauGetErrorString(status)); break; } if (!flag) { break; } VdpauHqScalingMax = VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 + i; } // // Cache some features // status = VdpauVideoMixerQueryFeatureSupport(VdpauDevice, VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL, &flag); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query feature '%s': %s\n"), "deinterlace-temporal", VdpauGetErrorString(status)); } else { VdpauTemporal = flag; } status = VdpauVideoMixerQueryFeatureSupport(VdpauDevice, VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL, &flag); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query feature '%s': %s\n"), "deinterlace-temporal-spatial", VdpauGetErrorString(status)); } else { VdpauTemporalSpatial = flag; } status = VdpauVideoMixerQueryFeatureSupport(VdpauDevice, VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE, &flag); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query feature '%s': %s\n"), "inverse-telecine", VdpauGetErrorString(status)); } else { VdpauInverseTelecine = flag; } status = VdpauVideoMixerQueryFeatureSupport(VdpauDevice, VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION, &flag); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query feature '%s': %s\n"), "noise-reduction", VdpauGetErrorString(status)); } else { VdpauNoiseReduction = flag; } status = VdpauVideoMixerQueryFeatureSupport(VdpauDevice, VDP_VIDEO_MIXER_FEATURE_SHARPNESS, &flag); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query feature '%s': %s\n"), "sharpness", VdpauGetErrorString(status)); } else { VdpauSharpness = flag; } status = VdpauVideoMixerQueryAttributeSupport(VdpauDevice, VDP_VIDEO_MIXER_ATTRIBUTE_SKIP_CHROMA_DEINTERLACE, &flag); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query feature '%s': %s\n"), "skip-chroma-deinterlace", VdpauGetErrorString(status)); } else { VdpauSkipChroma = flag; } // VDP_VIDEO_MIXER_ATTRIBUTE_BACKGROUND_COLOR Info(_("video/vdpau: highest supported high quality scaling %d\n"), VdpauHqScalingMax - VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 + 1); Info(_("video/vdpau: feature deinterlace temporal %s\n"), VdpauTemporal ? _("supported") : _("unsupported")); Info(_("video/vdpau: feature deinterlace temporal spatial %s\n"), VdpauTemporalSpatial ? _("supported") : _("unsupported")); Info(_("video/vdpau: attribute skip chroma deinterlace %s\n"), VdpauSkipChroma ? _("supported") : _("unsupported")); // // video formats // flag = VDP_FALSE; status = VdpauVideoSurfaceQueryCapabilities(VdpauDevice, VDP_CHROMA_TYPE_420, &flag, &max_width, &max_height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query video surface: %s\n"), VdpauGetErrorString(status)); } if (flag) { Info(_("video/vdpau: 4:2:0 chroma format with %dx%d supported\n"), max_width, max_height); } flag = VDP_FALSE; status = VdpauVideoSurfaceQueryCapabilities(VdpauDevice, VDP_CHROMA_TYPE_422, &flag, &max_width, &max_height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query video surface: %s\n"), VdpauGetErrorString(status)); } if (flag) { Info(_("video/vdpau: 4:2:2 chroma format with %dx%d supported\n"), max_width, max_height); } flag = VDP_FALSE; status = VdpauVideoSurfaceQueryCapabilities(VdpauDevice, VDP_CHROMA_TYPE_444, &flag, &max_width, &max_height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query video surface: %s\n"), VdpauGetErrorString(status)); } if (flag) { Info(_("video/vdpau: 4:4:4 chroma format with %dx%d supported\n"), max_width, max_height); } // FIXME: does only check for chroma formats, but no action status = VdpauVideoSurfaceQueryGetPutBitsYCbCrCapabilities(VdpauDevice, VDP_CHROMA_TYPE_422, VDP_YCBCR_FORMAT_YUYV, &flag); if (status != VDP_STATUS_OK || !flag) { Error(_("video/vdpau: doesn't support yuvy video surface\n")); } status = VdpauVideoSurfaceQueryGetPutBitsYCbCrCapabilities(VdpauDevice, VDP_CHROMA_TYPE_420, VDP_YCBCR_FORMAT_YV12, &flag); if (status != VDP_STATUS_OK || !flag) { Error(_("video/vdpau: doesn't support yv12 video surface\n")); } flag = VDP_FALSE; status = VdpauOutputSurfaceQueryCapabilities(VdpauDevice, VDP_RGBA_FORMAT_B8G8R8A8, &flag, &max_width, &max_height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query output surface: %s\n"), VdpauGetErrorString(status)); } if (flag) { Info(_("video/vdpau: 8bit BGRA format with %dx%d supported\n"), max_width, max_height); } flag = VDP_FALSE; status = VdpauOutputSurfaceQueryCapabilities(VdpauDevice, VDP_RGBA_FORMAT_R8G8B8A8, &flag, &max_width, &max_height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query output surface: %s\n"), VdpauGetErrorString(status)); } if (flag) { Info(_("video/vdpau: 8bit RGBA format with %dx%d supported\n"), max_width, max_height); } flag = VDP_FALSE; status = VdpauOutputSurfaceQueryCapabilities(VdpauDevice, VDP_RGBA_FORMAT_R10G10B10A2, &flag, &max_width, &max_height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query output surface: %s\n"), VdpauGetErrorString(status)); } if (flag) { Info(_("video/vdpau: 10bit RGBA format with %dx%d supported\n"), max_width, max_height); } flag = VDP_FALSE; status = VdpauOutputSurfaceQueryCapabilities(VdpauDevice, VDP_RGBA_FORMAT_B10G10R10A2, &flag, &max_width, &max_height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query output surface: %s\n"), VdpauGetErrorString(status)); } if (flag) { Info(_("video/vdpau: 8bit BRGA format with %dx%d supported\n"), max_width, max_height); } // FIXME: does only check for rgba formats, but no action // // Create presentation queue, only one queue pro window // VdpauInitOutputQueue(); } /// /// VDPAU cleanup. /// static void VideoVdpauExit(void) { if (VdpauDecoders[0]) { VdpauDelDecoder(VdpauDecoders[0]); VdpauDecoders[0] = NULL; } if (VdpauDevice) { VdpauExitOutputQueue(); // FIXME: more VDPAU cleanups... if (VdpauDeviceDestroy) { VdpauDeviceDestroy(VdpauDevice); } VdpauDevice = 0; } } /// /// Update output for new size or aspect ratio. /// /// @param decoder VDPAU hw decoder /// /// @todo combine VaapiUpdateOutput and VdpauUpdateOutput /// static void VdpauUpdateOutput(VdpauDecoder * decoder) { AVRational input_aspect_ratio; AVRational display_aspect_ratio; input_aspect_ratio = decoder->InputAspect; Debug(3, "video: input aspect %d:%d\n", input_aspect_ratio.num, input_aspect_ratio.den); if (!input_aspect_ratio.num || !input_aspect_ratio.den) { input_aspect_ratio.num = 1; input_aspect_ratio.den = 1; Debug(3, "video: aspect defaults to %d:%d\n", input_aspect_ratio.num, input_aspect_ratio.den); } av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, decoder->InputWidth * input_aspect_ratio.num, decoder->InputHeight * input_aspect_ratio.den, 1024 * 1024); Debug(3, "video: aspect %d:%d\n", display_aspect_ratio.num, display_aspect_ratio.den); // FIXME: store different positions for the ratios if (display_aspect_ratio.num == 4 && display_aspect_ratio.den == 3) { switch (Video4to3ZoomMode) { case VideoNormal: case VideoStretch: case VideoZoom: case VideoAnamorphic: break; } } // FIXME: this overwrites user choosen output position decoder->OutputX = 0; decoder->OutputY = 0; decoder->OutputWidth = (VideoWindowHeight * display_aspect_ratio.num) / display_aspect_ratio.den; decoder->OutputHeight = (VideoWindowWidth * display_aspect_ratio.den) / display_aspect_ratio.num; if ((unsigned)decoder->OutputWidth > VideoWindowWidth) { decoder->OutputWidth = VideoWindowWidth; decoder->OutputY = (VideoWindowHeight - decoder->OutputHeight) / 2; } else if ((unsigned)decoder->OutputHeight > VideoWindowHeight) { decoder->OutputHeight = VideoWindowHeight; decoder->OutputX = (VideoWindowWidth - decoder->OutputWidth) / 2; } Debug(3, "video: aspect output %dx%d+%d+%d\n", decoder->OutputWidth, decoder->OutputHeight, decoder->OutputX, decoder->OutputY); } /// /// Check profile supported. /// /// @param decoder VDPAU hw decoder /// @param profile VDPAU profile requested /// static VdpDecoderProfile VdpauCheckProfile(VdpauDecoder * decoder, VdpDecoderProfile profile) { VdpStatus status; VdpBool is_supported; uint32_t max_level; uint32_t max_macroblocks; uint32_t max_width; uint32_t max_height; status = VdpauDecoderQueryCapabilities(decoder->Device, profile, &is_supported, &max_level, &max_macroblocks, &max_width, &max_height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't query decoder capabilities: %s\n"), VdpauGetErrorString(status)); return VDP_INVALID_HANDLE; } Debug(3, "video/vdpau: profile %d with level %d, macro blocks %d, width %d, height %d %ssupported\n", profile, max_level, max_macroblocks, max_width, max_height, is_supported ? "" : "not "); return is_supported ? profile : VDP_INVALID_HANDLE; } /// /// 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 PixelFormat Vdpau_get_format(VdpauDecoder * decoder, AVCodecContext * video_ctx, const enum PixelFormat *fmt) { const enum PixelFormat *fmt_idx; VdpDecoderProfile profile; VdpStatus status; int max_refs; Debug(3, "video: new stream format %d\n", GetMsTicks() - VideoSwitch); VdpauCleanup(decoder); if (getenv("NO_HW")) { // FIXME: make config option Debug(3, "codec: hardware acceleration disabled\n"); goto slow_path; } // // look through formats // Debug(3, "%s: codec %d fmts:\n", __FUNCTION__, video_ctx->codec_id); for (fmt_idx = fmt; *fmt_idx != 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 PIX_FMT_VDPAU_H264: case PIX_FMT_VDPAU_MPEG1: case PIX_FMT_VDPAU_MPEG2: case PIX_FMT_VDPAU_WMV3: case PIX_FMT_VDPAU_VC1: case PIX_FMT_VDPAU_MPEG4: break; default: continue; } break; } if (*fmt_idx == PIX_FMT_NONE) { Error(_("video/vdpau: no valid vdpau pixfmt found\n")); goto slow_path; } max_refs = CODEC_SURFACES_DEFAULT; // check profile switch (video_ctx->codec_id) { case CODEC_ID_MPEG1VIDEO: max_refs = 2; profile = VdpauCheckProfile(decoder, VDP_DECODER_PROFILE_MPEG1); break; case CODEC_ID_MPEG2VIDEO: max_refs = 2; profile = VdpauCheckProfile(decoder, VDP_DECODER_PROFILE_MPEG2_MAIN); break; case CODEC_ID_MPEG4: case CODEC_ID_H263: /* p = VaapiFindProfile(profiles, profile_n, VAProfileMPEG4AdvancedSimple); */ break; case CODEC_ID_H264: // FIXME: can calculate level 4.1 limits max_refs = 16; // try more simple formats, fallback to better if (video_ctx->profile == FF_PROFILE_H264_BASELINE) { profile = VdpauCheckProfile(decoder, VDP_DECODER_PROFILE_H264_BASELINE); if (profile == VDP_INVALID_HANDLE) { profile = VdpauCheckProfile(decoder, VDP_DECODER_PROFILE_H264_MAIN); } if (profile == VDP_INVALID_HANDLE) { profile = VdpauCheckProfile(decoder, VDP_DECODER_PROFILE_H264_HIGH); } } else if (video_ctx->profile == FF_PROFILE_H264_MAIN) { profile = VdpauCheckProfile(decoder, VDP_DECODER_PROFILE_H264_MAIN); if (profile == VDP_INVALID_HANDLE) { profile = VdpauCheckProfile(decoder, VDP_DECODER_PROFILE_H264_HIGH); } } else { profile = VdpauCheckProfile(decoder, VDP_DECODER_PROFILE_H264_MAIN); } break; case CODEC_ID_WMV3: /* p = VaapiFindProfile(profiles, profile_n, VAProfileVC1Main); */ break; case CODEC_ID_VC1: /* p = VaapiFindProfile(profiles, profile_n, VAProfileVC1Advanced); */ break; default: goto slow_path; } if (profile == VDP_INVALID_HANDLE) { Error(_("video/vdpau: no valid profile found\n")); goto slow_path; } Debug(3, "video/vdpau: create decoder profile=%d %dx%d #%d refs\n", profile, video_ctx->width, video_ctx->height, max_refs); status = VdpauDecoderCreate(VdpauDevice, profile, video_ctx->width, video_ctx->height, max_refs, &decoder->VideoDecoder); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't create decoder: %s\n"), VdpauGetErrorString(status)); abort(); goto slow_path; } // FIXME: Combine this with VdpauSetup decoder->CropX = 0; decoder->CropY = 0; decoder->CropWidth = video_ctx->width; decoder->CropHeight = video_ctx->height; decoder->InputWidth = video_ctx->width; decoder->InputHeight = video_ctx->height; decoder->InputAspect = video_ctx->sample_aspect_ratio; VdpauUpdateOutput(decoder); VdpauMixerSetup(decoder); // FIXME: need only to create and destroy surfaces for size changes // or when number of needed surfaces changed! decoder->Resolution = VideoResolutionGroup(video_ctx->width, video_ctx->height, decoder->Interlaced); VdpauCreateSurfaces(decoder, video_ctx->width, video_ctx->height); Debug(3, "\t%#010x %s\n", fmt_idx[0], av_get_pix_fmt_name(fmt_idx[0])); return *fmt_idx; slow_path: // no accelerated format found video_ctx->hwaccel_context = NULL; return avcodec_default_get_format(video_ctx, fmt); } /// /// Configure VDPAU for new video format. /// /// @param decoder VDPAU hw decoder /// @param video_ctx ffmpeg video codec context /// static void VdpauSetup(VdpauDecoder * decoder, const AVCodecContext * video_ctx) { VdpStatus status; VdpChromaType chroma_type; uint32_t width; uint32_t height; // decoder->Input... already setup by caller VdpauCleanup(decoder); VdpauMixerSetup(decoder); decoder->Resolution = VideoResolutionGroup(video_ctx->width, video_ctx->height, decoder->Interlaced); VdpauCreateSurfaces(decoder, video_ctx->width, video_ctx->height); // get real surface size status = VdpauVideoSurfaceGetParameters(decoder->SurfacesFree[0], &chroma_type, &width, &height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't get video surface parameters: %s\n"), VdpauGetErrorString(status)); } // vdpau can choose different sizes, must use them for putbits if (chroma_type != decoder->ChromaType || width != (uint32_t) video_ctx->width || height != (uint32_t) video_ctx->height) { // FIXME: must rewrite the code to support this case Fatal(_("video/vdpau: video surface type/size mismatch\n")); } // // When window output size changes update VdpauSurfacesRb // } /// /// Grab video surface. /// /// @param decoder VDPAU hw decoder /// static void VdpauGrabSurface(VdpauDecoder * decoder) { VdpVideoSurface surface; VdpStatus status; VdpChromaType chroma_type; uint32_t size; uint32_t width; uint32_t height; void *base; void *data[3]; uint32_t pitches[3]; VdpYCbCrFormat format; // FIXME: test function to grab output surface content // for screen shots, atom light and auto crop. surface = decoder->SurfacesRb[(decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX]; // get real surface size status = VdpauVideoSurfaceGetParameters(surface, &chroma_type, &width, &height); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't get video surface parameters: %s\n"), VdpauGetErrorString(status)); return; } switch (chroma_type) { case VDP_CHROMA_TYPE_420: case VDP_CHROMA_TYPE_422: case VDP_CHROMA_TYPE_444: size = width * height + ((width + 1) / 2) * ((height + 1) / 2) + ((width + 1) / 2) * ((height + 1) / 2); base = malloc(size); if (!base) { Error(_("video/vdpau: out of memory\n")); return; } pitches[0] = width; pitches[1] = width / 2; pitches[2] = width / 2; data[0] = base; data[1] = base + width * height; data[2] = base + width * height + width * height / 4; format = VDP_YCBCR_FORMAT_YV12; break; } status = VdpauVideoSurfaceGetBitsYCbCr(surface, format, data, pitches); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't get video surface bits: %s\n"), VdpauGetErrorString(status)); return; } AutoCropDetect(width, height, data, pitches); free(base); } /// /// Queue output surface. /// /// @param decoder VDPAU hw decoder /// @param surface output surface /// @param softdec software decoder /// /// @note we can't mix software and hardware decoder surfaces /// static void VdpauQueueSurface(VdpauDecoder * decoder, VdpVideoSurface surface, int softdec) { VdpVideoSurface old; ++decoder->FrameCounter; if (1) { // can't wait for output queue empty if (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) { Warning(_ ("video/vdpau: output buffer full, dropping frame (%d/%d)\n"), ++decoder->FramesDropped, decoder->FrameCounter); if (!(decoder->FramesDisplayed % 300)) { VdpauPrintFrames(decoder); } // software surfaces only if (softdec) { VdpauReleaseSurface(decoder, surface); } return; } #if 0 } else { // wait for output queue empty while (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) { VideoDisplayHandler(); } #endif } // // Check and release, old surface // if ((old = decoder->SurfacesRb[decoder->SurfaceWrite]) != VDP_INVALID_HANDLE) { // now we can release the surface, software surfaces only if (softdec) { VdpauReleaseSurface(decoder, old); } } Debug(4, "video/vdpau: yy video surface %#x@%d ready\n", surface, decoder->SurfaceWrite); decoder->SurfacesRb[decoder->SurfaceWrite] = surface; decoder->SurfaceWrite = (decoder->SurfaceWrite + 1) % VIDEO_SURFACES_MAX; atomic_inc(&decoder->SurfacesFilled); } /// /// Render a ffmpeg frame. /// /// @param decoder VDPAU hw decoder /// @param video_ctx ffmpeg video codec context /// @param frame frame to display /// static void VdpauRenderFrame(VdpauDecoder * decoder, const AVCodecContext * video_ctx, const AVFrame * frame) { VdpStatus status; VdpVideoSurface surface; // // Hardware render // // VDPAU: PIX_FMT_VDPAU_H264 .. PIX_FMT_VDPAU_VC1 PIX_FMT_VDPAU_MPEG4 if ((PIX_FMT_VDPAU_H264 <= video_ctx->pix_fmt && video_ctx->pix_fmt <= PIX_FMT_VDPAU_VC1) || video_ctx->pix_fmt == PIX_FMT_VDPAU_MPEG4) { struct vdpau_render_state *vrs; int interlaced; vrs = (struct vdpau_render_state *)frame->data[0]; surface = vrs->surface; Debug(4, "video/vdpau: hw render hw surface %#x\n", surface); // FIXME: some tv-stations toggle interlace on/off // frame->interlaced_frame isn't always correct set interlaced = frame->interlaced_frame; if (video_ctx->height == 720) { if (interlaced && !decoder->WrongInterlacedWarned) { Debug(3, "video/vdpau: wrong interlace flag fixed\n"); decoder->WrongInterlacedWarned = 1; } interlaced = 0; } else { if (!interlaced && !decoder->WrongInterlacedWarned) { Debug(3, "video/vdpau: wrong interlace flag fixed\n"); decoder->WrongInterlacedWarned = 1; } interlaced = 1; } // update aspect ratio changes #ifdef still_to_detect_define if (av_cmp_q(decoder->InputAspect, frame->sample_aspect_ratio)) { Debug(3, "video/vdpau: aspect ratio changed\n"); //decoder->InputWidth = video_ctx->width; //decoder->InputHeight = video_ctx->height; decoder->InputAspect = frame->sample_aspect_ratio; VdpauUpdateOutput(decoder); } #else if (av_cmp_q(decoder->InputAspect, video_ctx->sample_aspect_ratio)) { Debug(3, "video/vdpau: aspect ratio changed\n"); //decoder->InputWidth = video_ctx->width; //decoder->InputHeight = video_ctx->height; decoder->InputAspect = video_ctx->sample_aspect_ratio; VdpauUpdateOutput(decoder); } #endif if (VideoDeinterlace[decoder->Resolution] == VideoDeinterlaceSoftware && interlaced) { // FIXME: software deinterlace avpicture_deinterlace // FIXME: VdpauCpuDeinterlace(decoder, surface); } else { // FIXME: should be done by init if (decoder->Interlaced != interlaced || decoder->TopFieldFirst != frame->top_field_first) { Debug(3, "video/vdpau: interlaced %d top-field-first %d\n", interlaced, frame->top_field_first); decoder->Interlaced = interlaced; decoder->TopFieldFirst = frame->top_field_first; decoder->SurfaceField = 0; } VdpauQueueSurface(decoder, surface, 0); } // // PutBitsYCbCr render // } else { void const *data[3]; uint32_t pitches[3]; // FIXME: aspect change not supported! // // Check image, format, size // if (decoder->PixFmt != video_ctx->pix_fmt || video_ctx->width != decoder->InputWidth || video_ctx->height != decoder->InputHeight) { decoder->PixFmt = video_ctx->pix_fmt; decoder->CropX = 0; decoder->CropY = 0; decoder->CropWidth = video_ctx->width; decoder->CropHeight = video_ctx->height; decoder->InputWidth = video_ctx->width; decoder->InputHeight = video_ctx->height; // // detect interlaced input // Debug(3, "video/vdpau: interlaced %d top-field-first %d\n", frame->interlaced_frame, frame->top_field_first); decoder->Interlaced = frame->interlaced_frame; decoder->TopFieldFirst = frame->top_field_first; decoder->SurfaceField = 0; // FIXME: I hope this didn't change in the middle of the stream VdpauSetup(decoder, video_ctx); } // // Copy data from frame to image // switch (video_ctx->pix_fmt) { case PIX_FMT_YUV420P: break; case PIX_FMT_YUV422P: case PIX_FMT_YUV444P: default: Fatal(_("video/vdpau: pixel format %d not supported\n"), video_ctx->pix_fmt); } // convert ffmpeg order to vdpau data[0] = frame->data[0]; data[1] = frame->data[2]; data[2] = frame->data[1]; pitches[0] = frame->linesize[0]; pitches[1] = frame->linesize[2]; pitches[2] = frame->linesize[1]; surface = VdpauGetSurface(decoder); status = VdpauVideoSurfacePutBitsYCbCr(surface, VDP_YCBCR_FORMAT_YV12, data, pitches); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't put video surface bits: %s\n"), VdpauGetErrorString(status)); } VdpauQueueSurface(decoder, surface, 1); } if (frame->interlaced_frame) { ++decoder->FrameCounter; } } /// /// Render osd surface to output surface. /// static void VdpauMixOsd(void) { VdpOutputSurfaceRenderBlendState blend_state; VdpRect source_rect; VdpRect output_rect; VdpStatus status; //uint32_t start; //uint32_t end; // // blend overlay over output // blend_state.struct_version = VDP_OUTPUT_SURFACE_RENDER_BLEND_STATE_VERSION; blend_state.blend_factor_source_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE; blend_state.blend_factor_source_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE; blend_state.blend_factor_destination_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blend_state.blend_factor_destination_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blend_state.blend_equation_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD; blend_state.blend_equation_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD; source_rect.x0 = 0; source_rect.y0 = 0; source_rect.x1 = VdpauOsdWidth; source_rect.y1 = VdpauOsdHeight; output_rect.x0 = 0; output_rect.y0 = 0; output_rect.x1 = VideoWindowWidth; output_rect.y1 = VideoWindowHeight; //start = GetMsTicks(); VdpauOsdSurfaceIndex = 1; #ifdef USE_BITMAP status = VdpauOutputSurfaceRenderBitmapSurface(VdpauSurfacesRb [VdpauSurfaceIndex], &output_rect, VdpauOsdBitmapSurface[!VdpauOsdSurfaceIndex], &source_rect, NULL, &blend_state, VDP_OUTPUT_SURFACE_RENDER_ROTATE_0); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't render bitmap surface: %s\n"), VdpauGetErrorString(status)); } #else status = VdpauOutputSurfaceRenderOutputSurface(VdpauSurfacesRb [VdpauSurfaceIndex], &output_rect, VdpauOsdOutputSurface[!VdpauOsdSurfaceIndex], &source_rect, NULL, &blend_state, VDP_OUTPUT_SURFACE_RENDER_ROTATE_0); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't render output surface: %s\n"), VdpauGetErrorString(status)); } #endif //end = GetMsTicks(); //Debug(3, "video:/vdpau: osd render %d ms\n", end - start); VdpauOsdSurfaceIndex = !VdpauOsdSurfaceIndex; } /// /// Render video surface to output surface. /// /// @param decoder VDPAU hw decoder /// static void VdpauMixVideo(VdpauDecoder * decoder) { VdpVideoSurface current; VdpRect video_src_rect; VdpRect dst_rect; VdpRect dst_video_rect; VdpStatus status; dst_rect.x0 = 0; // window output (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; #ifdef USE_GRAB VdpauGrabSurface(decoder); #endif if (decoder->Interlaced && VideoDeinterlace[decoder->Resolution] != VideoDeinterlaceWeave) { // // Build deinterlace structures // VdpVideoMixerPictureStructure cps; VdpVideoSurface past[3]; int past_n; VdpVideoSurface future[3]; int future_n; #ifdef DEBUG if (atomic_read(&decoder->SurfacesFilled) < 3) { Debug(3, "only %d\n", atomic_read(&decoder->SurfacesFilled)); } #endif // FIXME: can use VDP_INVALID_HANDLE to support less surface on start if (VideoDeinterlaceSurfaces == 5) { past_n = 2; future_n = 2; // FIXME: wrong for bottom-field first // read: past: B0 T0 current T1 future B1 T2 (0 1 2) // read: past: T1 B0 current B1 future T2 B2 (0 1 2) if (decoder->TopFieldFirst != decoder->SurfaceField) { cps = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD; past[1] = decoder->SurfacesRb[decoder->SurfaceRead]; past[0] = past[1]; current = decoder->SurfacesRb[(decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX]; future[0] = current; future[1] = decoder->SurfacesRb[(decoder->SurfaceRead + 2) % VIDEO_SURFACES_MAX]; // FIXME: can support 1 future more } else { cps = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD; // FIXME: can support 1 past more past[1] = decoder->SurfacesRb[decoder->SurfaceRead]; past[0] = decoder->SurfacesRb[(decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX]; current = past[0]; future[0] = decoder->SurfacesRb[(decoder->SurfaceRead + 2) % VIDEO_SURFACES_MAX]; future[1] = future[0]; } } else if (VideoDeinterlaceSurfaces == 4) { past_n = 2; future_n = 1; // FIXME: wrong for bottom-field first // read: past: B0 T0 current T1 future B1 (0 1 2) // read: past: T1 B0 current B1 future T2 (0 1 2) if (decoder->TopFieldFirst != decoder->SurfaceField) { cps = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD; past[1] = decoder->SurfacesRb[decoder->SurfaceRead]; past[0] = past[1]; current = decoder->SurfacesRb[(decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX]; future[0] = current; } else { cps = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD; past[1] = decoder->SurfacesRb[decoder->SurfaceRead]; past[0] = decoder->SurfacesRb[(decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX]; current = past[0]; future[0] = decoder->SurfacesRb[(decoder->SurfaceRead + 2) % VIDEO_SURFACES_MAX]; } } else { Error(_("video/vdpau: %d surface deinterlace unsupported\n"), VideoDeinterlaceSurfaces); } // FIXME: past_n, future_n here: Debug(4, " %02d %02d(%c%02d) %02d %02d\n", past[1], past[0], cps == VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD ? 'T' : 'B', current, future[0], future[1]); status = VdpauVideoMixerRender(decoder->VideoMixer, VDP_INVALID_HANDLE, NULL, cps, past_n, past, current, future_n, future, &video_src_rect, VdpauSurfacesRb[VdpauSurfaceIndex], &dst_rect, &dst_video_rect, 0, NULL); } else { current = decoder->SurfacesRb[decoder->SurfaceRead]; status = VdpauVideoMixerRender(decoder->VideoMixer, VDP_INVALID_HANDLE, NULL, VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME, 0, NULL, current, 0, NULL, &video_src_rect, VdpauSurfacesRb[VdpauSurfaceIndex], &dst_rect, &dst_video_rect, 0, NULL); } if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't render mixer: %s\n"), VdpauGetErrorString(status)); } Debug(4, "video/vdpau: yy video surface %#x@%d displayed\n", current, decoder->SurfaceRead); } /// /// Create and display a black empty surface. /// /// @param decoder VDPAU hw decoder /// static void VdpauBlackSurface(VdpauDecoder * decoder) { VdpStatus status; void *image; void const *data[1]; uint32_t pitches[1]; VdpRect dst_rect; Debug(3, "video/vdpau: black surface\n"); // FIXME: clear video window area (void)decoder; image = calloc(4, VideoWindowWidth * VideoWindowHeight); dst_rect.x0 = 0; dst_rect.y0 = 0; dst_rect.x1 = dst_rect.x0 + VideoWindowWidth; dst_rect.y1 = dst_rect.y0 + VideoWindowHeight; data[0] = image; pitches[0] = VideoWindowWidth * 4; status = VdpauOutputSurfacePutBitsNative(VdpauSurfacesRb[VdpauSurfaceIndex], data, pitches, &dst_rect); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: output surface put bits failed: %s\n"), VdpauGetErrorString(status)); } free(image); } /// /// Advance displayed frame. /// static void VdpauAdvanceFrame(void) { int i; for (i = 0; i < VdpauDecoderN; ++i) { int filled; VdpauDecoder *decoder; decoder = VdpauDecoders[i]; // next field if (decoder->Interlaced) { decoder->SurfaceField ^= 1; } // next surface, if complete frame is displayed if (!decoder->SurfaceField) { // 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; Warning(_ ("video: display buffer empty, duping frame (%d/%d)\n"), decoder->FramesDuped, decoder->FrameCounter); if (!(decoder->FramesDisplayed % 300)) { VdpauPrintFrames(decoder); } decoder->SurfaceField = decoder->Interlaced; } else { decoder->SurfaceRead = (decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX; atomic_dec(&decoder->SurfacesFilled); } } } } /// /// Display a video frame. /// static void VdpauDisplayFrame(void) { VdpStatus status; VdpTime first_time; static VdpTime last_time; int i; // // wait for surface visible (blocks max ~5ms) // status = VdpauPresentationQueueBlockUntilSurfaceIdle(VdpauQueue, VdpauSurfacesRb[VdpauSurfaceIndex], &first_time); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't block queue: %s\n"), VdpauGetErrorString(status)); } // check if surface was displayed for more than 1 frame if (last_time && first_time > last_time + 21 * 1000 * 1000) { Debug(3, "video/vdpau: %ld display time %ld\n", first_time / 1000, (first_time - last_time) / 1000); // FIXME: can be more than 1 frame long shown for (i = 0; i < VdpauDecoderN; ++i) { VdpauDecoders[i]->FramesMissed++; Warning(_("video: missed frame (%d/%d)\n"), VdpauDecoders[i]->FramesMissed, VdpauDecoders[i]->FrameCounter); if (!(VdpauDecoders[i]->FramesDisplayed % 300)) { VdpauPrintFrames(VdpauDecoders[i]); } } } last_time = first_time; // // Render videos into output // for (i = 0; i < VdpauDecoderN; ++i) { int filled; VdpauDecoder *decoder; decoder = VdpauDecoders[i]; decoder->FramesDisplayed++; filled = atomic_read(&decoder->SurfacesFilled); // need 1 frame for progressive, 3 frames for interlaced if (filled < 1 + 2 * decoder->Interlaced) { // FIXME: rewrite MixVideo to support less surfaces VdpauBlackSurface(decoder); continue; } VdpauMixVideo(decoder); } // // add osd to surface // if (VdpauShowOsd) { // showing costs performance VdpauMixOsd(); } // // place surface in presentation queue // status = VdpauPresentationQueueDisplay(VdpauQueue, VdpauSurfacesRb[VdpauSurfaceIndex], 0, 0, 0); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't queue display: %s\n"), VdpauGetErrorString(status)); } for (i = 0; i < VdpauDecoderN; ++i) { clock_gettime(CLOCK_REALTIME, &VdpauDecoders[i]->FrameTime); } VdpauSurfaceIndex = (VdpauSurfaceIndex + 1) % OUTPUT_SURFACES_MAX; xcb_flush(Connection); } /// /// Sync and display surface. /// /// @param decoder VDPAU hw decoder /// static void VdpauSyncDisplayFrame(VdpauDecoder * decoder) { int filled; int64_t audio_clock; int64_t video_clock; if (!decoder->DupNextFrame && (!Video60HzMode || decoder->FramesDisplayed % 6)) { VdpauAdvanceFrame(); } filled = atomic_read(&decoder->SurfacesFilled); VdpauDisplayFrame(); // // audio/video sync // audio_clock = AudioGetClock(); video_clock = VideoGetClock(); // FIXME: audio not known assume 333ms delay if (decoder->DupNextFrame) { decoder->DupNextFrame = 0; } else if ((uint64_t) audio_clock != AV_NOPTS_VALUE && (uint64_t) video_clock != AV_NOPTS_VALUE) { // both clocks are known if (abs(video_clock - audio_clock) > 5000 * 90) { Debug(3, "video: pts difference too big\n"); } else if (video_clock > audio_clock + VideoAudioDelay + 30 * 90) { Debug(3, "video: slow down video\n"); decoder->DupNextFrame = 1; } else if (audio_clock + VideoAudioDelay > video_clock + 50 * 90 && filled > 1) { Debug(3, "video: speed up video\n"); decoder->DropNextFrame = 1; } } #ifdef DEBUG // debug audio/video sync if (decoder->DupNextFrame || decoder->DropNextFrame || !(decoder->FramesDisplayed % (50 * 10))) { static int64_t last_video_clock; Debug(3, "video: %09" PRIx64 "-%09" PRIx64 " %4" PRId64 " pts %+dms %" PRId64 "\n", audio_clock, video_clock, video_clock - last_video_clock, (int)(audio_clock - video_clock) / 90, AudioGetDelay() / 90); last_video_clock = video_clock; } #endif } /// /// Sync and render a ffmpeg frame /// /// @param decoder VDPAU hw decoder /// @param video_ctx ffmpeg video codec context /// @param frame frame to display /// static void VdpauSyncRenderFrame(VdpauDecoder * decoder, const AVCodecContext * video_ctx, const AVFrame * frame) { #ifdef DEBUG if (!atomic_read(&decoder->SurfacesFilled)) { Debug(3, "video: new stream frame %d\n", GetMsTicks() - VideoSwitch); } #endif if (decoder->DropNextFrame) { // drop frame requested ++decoder->FramesDropped; Warning(_("video: dropping frame (%d/%d)\n"), decoder->FramesDropped, decoder->FrameCounter); if (!(decoder->FramesDisplayed % 300)) { VdpauPrintFrames(decoder); } decoder->DropNextFrame = 0; return; } // if video output buffer is full, wait and display surface. // loop for interlace while (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) { struct timespec abstime; abstime = decoder->FrameTime; abstime.tv_nsec += 14 * 1000 * 1000; if (abstime.tv_nsec >= 1000 * 1000 * 1000) { // avoid overflow abstime.tv_sec++; abstime.tv_nsec -= 1000 * 1000 * 1000; } VideoPollEvent(); // give osd some time slot while (pthread_cond_timedwait(&VideoWakeupCond, &VideoLockMutex, &abstime) != ETIMEDOUT) { // SIGUSR1 Debug(3, "video/vdpau: pthread_cond_timedwait error\n"); } VdpauSyncDisplayFrame(decoder); } VdpauRenderFrame(decoder, video_ctx, frame); } /// /// Get VDPAU decoder video clock. /// /// @param decoder VDPAU hw decoder /// static int64_t VdpauGetClock(const VdpauDecoder * decoder) { // pts is the timestamp of the latest decoded frame if ((uint64_t) decoder->PTS == AV_NOPTS_VALUE) { return AV_NOPTS_VALUE; } // subtract buffered decoded frames if (decoder->Interlaced) { return decoder->PTS - 20 * 90 * (2 * atomic_read(&decoder->SurfacesFilled) - decoder->SurfaceField); } return decoder->PTS - 20 * 90 * (atomic_read(&decoder->SurfacesFilled) - 1); } #ifdef USE_VIDEO_THREAD /// /// Handle a VDPAU display. /// /// @todo FIXME: only a single decoder supported. /// static void VdpauDisplayHandlerThread(void) { int err; int filled; struct timespec nowtime; VdpauDecoder *decoder; if (!(decoder = VdpauDecoders[0])) { // no stream available return; } // // fill frame output ring buffer // filled = atomic_read(&decoder->SurfacesFilled); err = 1; if (filled < VIDEO_SURFACES_MAX) { // FIXME: hot polling pthread_mutex_lock(&VideoLockMutex); // fetch+decode or reopen err = VideoDecode(); pthread_mutex_unlock(&VideoLockMutex); } if (err) { // FIXME: sleep on wakeup usleep(5 * 1000); // nothing buffered } clock_gettime(CLOCK_REALTIME, &nowtime); // time for one frame over? if ( //filledFrameTime.tv_sec) * 1000 * 1000 * 1000 + (nowtime.tv_nsec - decoder->FrameTime.tv_nsec) < 15 * 1000 * 1000) { return; } pthread_mutex_lock(&VideoLockMutex); VdpauSyncDisplayFrame(decoder); pthread_mutex_unlock(&VideoLockMutex); } #endif /// /// Set video output position. /// /// @param decoder VDPAU 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 VdpauSetOutputPosition(VdpauDecoder * decoder, int x, int y, int width, int height) { decoder->OutputX = x; decoder->OutputY = y; decoder->OutputWidth = width; decoder->OutputHeight = height; // next video pictures are automatic rendered to correct position } //---------------------------------------------------------------------------- // VDPAU OSD //---------------------------------------------------------------------------- /// /// Clear subpicture image. /// /// @note looked by caller /// static void VdpauOsdClear(void) { VdpStatus status; void *image; void const *data[1]; uint32_t pitches[1]; VdpRect dst_rect; // osd image available? #ifdef USE_BITMAP if (VdpauOsdBitmapSurface[VdpauOsdSurfaceIndex] == VDP_INVALID_HANDLE) { return; } #else if (VdpauOsdOutputSurface[VdpauOsdSurfaceIndex] == VDP_INVALID_HANDLE) { return; } #endif Debug(3, "video/vdpau: clear image\n"); image = calloc(4, VdpauOsdWidth * VdpauOsdHeight); dst_rect.x0 = 0; dst_rect.y0 = 0; dst_rect.x1 = dst_rect.x0 + VdpauOsdWidth; dst_rect.y1 = dst_rect.y0 + VdpauOsdHeight; data[0] = image; pitches[0] = VdpauOsdWidth * 4; #ifdef USE_BITMAP status = VdpauBitmapSurfacePutBitsNative(VdpauOsdBitmapSurface [VdpauOsdSurfaceIndex], data, pitches, &dst_rect); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: bitmap surface put bits failed: %s\n"), VdpauGetErrorString(status)); } #else status = VdpauOutputSurfacePutBitsNative(VdpauOsdOutputSurface [VdpauOsdSurfaceIndex], data, pitches, &dst_rect); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: output surface put bits failed: %s\n"), VdpauGetErrorString(status)); } #endif free(image); VdpauShowOsd = 0; } /// /// Upload ARGB to subpicture image. /// /// @param x x position of image in osd /// @param y y position of image in osd /// @param width width of image /// @param height height of image /// @param argb argb image /// /// @note looked by caller /// static void VdpauUploadImage(int x, int y, int width, int height, const uint8_t * argb) { VdpStatus status; void const *data[1]; uint32_t pitches[1]; VdpRect dst_rect; // osd image available? #ifdef USE_BITMAP if (VdpauOsdBitmapSurface[VdpauOsdSurfaceIndex] == VDP_INVALID_HANDLE) { return; } #else if (VdpauOsdOutputSurface[VdpauOsdSurfaceIndex] == VDP_INVALID_HANDLE) { return; } #endif Debug(3, "video/vdpau: upload image\n"); dst_rect.x0 = x; dst_rect.y0 = y; dst_rect.x1 = dst_rect.x0 + width; dst_rect.y1 = dst_rect.y0 + height; data[0] = argb; pitches[0] = width * 4; #ifdef USE_BITMAP status = VdpauBitmapSurfacePutBitsNative(VdpauOsdBitmapSurface [VdpauOsdSurfaceIndex], data, pitches, &dst_rect); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: bitmap surface put bits failed: %s\n"), VdpauGetErrorString(status)); } #else status = VdpauOutputSurfacePutBitsNative(VdpauOsdOutputSurface [VdpauOsdSurfaceIndex], data, pitches, &dst_rect); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: output surface put bits failed: %s\n"), VdpauGetErrorString(status)); } #endif VdpauShowOsd = 1; } /// /// VDPAU initialize OSD. /// /// @param width osd width /// @param height osd height /// static void VdpauOsdInit(int width, int height) { int i; VdpStatus status; if (!VdpauDevice) { Debug(3, "video/vdpau: vdpau not setup\n"); return; } VdpauOsdWidth = width; VdpauOsdHeight = height; // // create bitmap/surface for osd // #ifdef USE_BITMAP if (VdpauOsdBitmapSurface[0] == VDP_INVALID_HANDLE) { for (i = 0; i < 2; ++i) { status = VdpauBitmapSurfaceCreate(VdpauDevice, VDP_RGBA_FORMAT_B8G8R8A8, width, height, VDP_TRUE, VdpauOsdBitmapSurface + i); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't create bitmap surface: %s\n"), VdpauGetErrorString(status)); } Debug(4, "video/vdpau: created bitmap surface %dx%d with id 0x%08x\n", width, height, VdpauOsdBitmapSurface[i]); } } #else if (VdpauOsdOutputSurface[0] == VDP_INVALID_HANDLE) { for (i = 0; i < 2; ++i) { status = VdpauOutputSurfaceCreate(VdpauDevice, VDP_RGBA_FORMAT_B8G8R8A8, width, height, VdpauOsdOutputSurface + i); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: can't create output surface: %s\n"), VdpauGetErrorString(status)); } Debug(4, "video/vdpau: created osd output surface %dx%d with id 0x%08x\n", width, height, VdpauOsdOutputSurface[i]); } } #endif Debug(3, "video/vdpau: osd surfaces created\n"); VdpauOsdClear(); } /// /// Cleanup osd. /// static void VdpauOsdExit(void) { Debug(3, "FIXME: %s\n", __FUNCTION__); } #endif //---------------------------------------------------------------------------- // OSD //---------------------------------------------------------------------------- //static int OsdShow; ///< flag show osd static int OsdWidth; ///< osd width static int OsdHeight; ///< osd height /// /// Clear the OSD. /// /// @todo I use glTexImage2D to clear the texture, are there faster and /// better ways to clear a texture? /// void VideoOsdClear(void) { VideoThreadLock(); #ifdef USE_GLX if (GlxEnabled) { void *texbuf; texbuf = calloc(OsdWidth * OsdHeight, 4); glEnable(GL_TEXTURE_2D); // 2d texture glBindTexture(GL_TEXTURE_2D, OsdGlTextures[OsdIndex]); // upload no image data, clears texture (on some drivers only) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, OsdWidth, OsdHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, texbuf); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); GlxCheck(); free(texbuf); } #endif #ifdef USE_VAAPI if (VideoVaapiEnabled) { VaapiOsdClear(); VideoThreadUnlock(); return; } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { VdpauOsdClear(); VideoThreadUnlock(); return; } #endif VideoThreadUnlock(); } /// /// Draw an OSD ARGB image. /// /// @param x x position of image in osd /// @param y y position of image in osd /// @param width width of image /// @param height height of image /// @param argb argb image /// void VideoOsdDrawARGB(int x, int y, int height, int width, const uint8_t * argb) { VideoThreadLock(); #ifdef USE_GLX if (GlxEnabled) { Debug(3, "video: %p <-> %p\n", glXGetCurrentContext(), GlxContext); GlxUploadTexture(x, y, height, width, argb); VideoThreadUnlock(); return; } #endif #ifdef USE_VAAPI if (VideoVaapiEnabled) { VaapiUploadImage(x, y, height, width, argb); VideoThreadUnlock(); return; } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { VdpauUploadImage(x, y, height, width, argb); VideoThreadUnlock(); return; } #endif (void)x; (void)y; (void)height; (void)width; (void)argb; VideoThreadUnlock(); } /// /// Setup osd. /// /// FIXME: looking for BGRA, but this fourcc isn't supported by the /// drawing functions yet. /// void VideoOsdInit(void) { OsdWidth = 1920 / 1; OsdHeight = 1080 / 1; // worst-case //OsdWidth = 768; //OsdHeight = VideoWindowHeight; // FIXME: must be configured #ifdef USE_GLX // FIXME: make an extra function for this if (GlxEnabled) { int i; Debug(3, "video/glx: %p <-> %p\n", glXGetCurrentContext(), GlxContext); // // create a RGBA texture. // glEnable(GL_TEXTURE_2D); // create 2d texture(s) glGenTextures(2, OsdGlTextures); for (i = 0; i < 2; ++i) { glBindTexture(GL_TEXTURE_2D, OsdGlTextures[i]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, OsdWidth, OsdHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); } glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); return; } #endif #ifdef USE_VAAPI if (VideoVaapiEnabled) { VaapiOsdInit(OsdWidth, OsdHeight); return; } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { VdpauOsdInit(OsdWidth, OsdHeight); return; } #endif } /// /// Cleanup OSD. /// void VideoOsdExit(void) { #ifdef USE_VAAPI if (VideoVaapiEnabled) { // FIXME: VaapiOsdExit(); return; } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { VdpauOsdExit(); return; } #endif } #if 0 //---------------------------------------------------------------------------- // Overlay //---------------------------------------------------------------------------- /// /// Render osd surface. /// void VideoRenderOverlay(void) { #ifdef USE_GLX if (GlxEnabled) { GlxRender(OsdWidth, OsdHeight); } else #endif { } } /// /// Display overlay surface. /// void VideoDisplayOverlay(void) { #ifdef USE_GLX if (GlxEnabled) { int osd_x1; int osd_y1; osd_x1 = 0; osd_y1 = 0; #ifdef noDEBUG osd_x1 = 100; osd_y1 = 100; #endif GlxRenderTexture(OsdGlTextures[OsdIndex], osd_x1, osd_y1, VideoWindowWidth, VideoWindowHeight); return; } #endif #ifdef USE_VAAPI { void *image_buffer; static int counter; // upload needs long time if (counter == 5) { //return; } // osd image available? if (VaOsdImage.image_id == VA_INVALID_ID) { return; } // FIXME: this version hangups //return; // map osd surface/image into memory. if (vaMapBuffer(VaDisplay, VaOsdImage.buf, &image_buffer) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't map osd image buffer\n")); return; } // 100% transparent memset(image_buffer, 0x80 | counter++, VaOsdImage.data_size); // convert internal osd to VA-API image //GfxConvert(image_buffer, VaOsdImage.offsets[0], VaOsdImage.pitches[0]); if (vaUnmapBuffer(VaDisplay, VaOsdImage.buf) != VA_STATUS_SUCCESS) { Error(_("video/vaapi: can't unmap osd image buffer\n")); } } #endif } #endif //---------------------------------------------------------------------------- // Frame //---------------------------------------------------------------------------- #if 0 /// /// Display a single frame. /// static void VideoDisplayFrame(void) { #ifdef USE_GLX if (GlxEnabled) { VideoDisplayOverlay(); #ifdef USE_DOUBLEBUFFER glXSwapBuffers(XlibDisplay, VideoWindow); #else glFinish(); // wait for all execution finished #endif GlxCheck(); glClear(GL_COLOR_BUFFER_BIT); } #endif #ifdef USE_VAAPI if (VideoVaapiEnabled) { VaapiDisplayFrame(); return; } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { return; } #endif } #endif //---------------------------------------------------------------------------- // Events //---------------------------------------------------------------------------- /// C callback feed key press extern void FeedKeyPress(const char *, const char *, int, int); /// /// Handle X11 events. /// /// @todo Signal WmDeleteMessage to application. /// static void VideoEvent(void) { XEvent event; KeySym keysym; //char buf[32]; XNextEvent(XlibDisplay, &event); 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); } break; case MapNotify: Debug(3, "video/event: MapNotify\n"); // µwn workaround xcb_change_window_attributes(Connection, VideoWindow, XCB_CW_CURSOR, &VideoBlankCursor); 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 KeyPress: keysym = XLookupKeysym(&event.xkey, 0); #if 0 switch (keysym) { case XK_d: break; case XK_S: break; } #endif if (keysym == NoSymbol) { Warning(_("video/event: No symbol for %d\n"), event.xkey.keycode); } FeedKeyPress("XKeySym", XKeysymToString(keysym), 0, 0); /* if (XLookupString(&event.xkey, buf, sizeof(buf), &keysym, NULL)) { FeedKeyPress("XKeySym", buf, 0, 0); } else { FeedKeyPress("XKeySym", XKeysymToString(keysym), 0, 0); } */ case KeyRelease: 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) { while (XPending(XlibDisplay)) { VideoEvent(); } } //---------------------------------------------------------------------------- // Thread //---------------------------------------------------------------------------- #ifdef USE_VIDEO_THREAD #ifdef USE_GLX static GLXContext GlxThreadContext; ///< our gl context for the thread #endif /// /// Lock video thread. /// static void VideoThreadLock(void) { if (pthread_mutex_lock(&VideoLockMutex)) { Error(_("video: can't lock thread\n")); } } /// /// Unlock video thread. /// static void VideoThreadUnlock(void) { if (pthread_mutex_unlock(&VideoLockMutex)) { Error(_("video: can't unlock thread\n")); } } /// /// Video render thread. /// static void *VideoDisplayHandlerThread(void *dummy) { Debug(3, "video: display thread started\n"); #ifdef USE_GLX if (GlxEnabled) { Debug(3, "video: %p <-> %p\n", glXGetCurrentContext(), GlxThreadContext); GlxThreadContext = glXCreateContext(XlibDisplay, GlxVisualInfo, GlxContext, GL_TRUE); if (!GlxThreadContext) { Error(_("video/glx: can't create glx context\n")); return NULL; } // set glx context if (!glXMakeCurrent(XlibDisplay, VideoWindow, GlxThreadContext)) { GlxCheck(); Error(_("video/glx: can't make glx context current\n")); return NULL; } } #endif for (;;) { VideoPollEvent(); #ifdef USE_VAAPI if (VideoVaapiEnabled) { VaapiDisplayHandlerThread(); } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { VdpauDisplayHandlerThread(); } #endif #if !defined(USE_VAAPI) && !defined(USE_VDPAU) // avoid 100% cpu use if (1) { XEvent event; XPeekEvent(XlibDisplay, &event); } else { usleep(10 * 1000); } #endif } return dummy; } /// /// Initialize video threads. /// static void VideoThreadInit(void) { pthread_mutex_init(&VideoMutex, NULL); pthread_mutex_init(&VideoLockMutex, NULL); pthread_cond_init(&VideoWakeupCond, NULL); pthread_create(&VideoThread, NULL, VideoDisplayHandlerThread, NULL); pthread_setname_np(VideoThread, "softhddev video"); //pthread_detach(VideoThread); } /// /// Exit and cleanup video threads. /// static void VideoThreadExit(void) { if (VideoThread) { void *retval; Debug(3, "video: video thread canceled\n"); if (pthread_cancel(VideoThread)) { Error(_("video: can't queue cancel video display thread\n")); } if (pthread_join(VideoThread, &retval) || retval != PTHREAD_CANCELED) { Error(_("video: can't cancel video display thread\n")); } pthread_cond_destroy(&VideoWakeupCond); pthread_mutex_destroy(&VideoLockMutex); pthread_mutex_destroy(&VideoMutex); VideoThread = 0; } } /// /// Video display wakeup. /// /// New video arrived, wakeup video thread. /// void VideoDisplayWakeup(void) { if (!XlibDisplay) { // not yet started return; } if (!VideoThread) { // start video thread, if needed VideoThreadInit(); } } #endif //---------------------------------------------------------------------------- // Video API //---------------------------------------------------------------------------- /// /// Video hardware decoder /// struct _video_hw_decoder_ { union { #ifdef USE_VAAPI VaapiDecoder Vaapi; ///< VA-API decoder structure #endif #ifdef USE_VDPAU VdpauDecoder Vdpau; ///< vdpau decoder structure #endif }; }; /// /// Allocate new video hw decoder. /// VideoHwDecoder *VideoNewHwDecoder(void) { if (!XlibDisplay) { // waiting for x11 start return NULL; } #ifdef USE_VAAPI if (VideoVaapiEnabled) { return (VideoHwDecoder *) VaapiNewDecoder(); } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { return (VideoHwDecoder *) VdpauNewDecoder(); } #endif return NULL; } /// /// Get a free hardware decoder surface. /// /// @param decoder VDPAU video hardware decoder /// unsigned VideoGetSurface(VideoHwDecoder * decoder) { #ifdef USE_VAAPI if (VideoVaapiEnabled) { return VaapiGetSurface(&decoder->Vaapi); } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { return VdpauGetSurface(&decoder->Vdpau); } #endif (void)decoder; return -1; } /// /// Release a hardware decoder surface. /// /// @param decoder VDPAU video hardware decoder /// @param surface surface no longer used /// void VideoReleaseSurface(VideoHwDecoder * decoder, unsigned surface) { #ifdef USE_VAAPI if (VideoVaapiEnabled) { VaapiReleaseSurface(&decoder->Vaapi, surface); return; } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { return VdpauReleaseSurface(&decoder->Vdpau, surface); } #endif (void)decoder; (void)surface; } /// /// 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. /// enum PixelFormat Video_get_format(VideoHwDecoder * decoder, AVCodecContext * video_ctx, const enum PixelFormat *fmt) { #ifdef USE_VAAPI if (VideoVaapiEnabled) { return Vaapi_get_format(&decoder->Vaapi, video_ctx, fmt); } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { return Vdpau_get_format(&decoder->Vdpau, video_ctx, fmt); } #endif (void)decoder; (void)video_ctx; (void)fmt; return fmt[0]; } /// /// 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 AVFrame * frame) { int64_t pts; int64_t delta; // update video clock if ((uint64_t) * pts_p != AV_NOPTS_VALUE) { *pts_p += interlaced ? 40 * 90 : 20 * 90; } //pts = frame->best_effort_timestamp; pts = frame->pkt_pts; if ((uint64_t) pts == AV_NOPTS_VALUE || !pts) { // libav: 0.8pre didn't set pts pts = frame->pkt_dts; } if (!pts) { pts = AV_NOPTS_VALUE; } // libav: sets only pkt_dts which can be 0 if ((uint64_t) pts != AV_NOPTS_VALUE) { // build a monotonic pts if ((uint64_t) * pts_p != AV_NOPTS_VALUE) { delta = pts - *pts_p; // ignore negative jumps if (delta > -300 * 90 && delta < -15 * 90) { return; } } if (*pts_p != pts) { Debug(3, "video: %#012" PRIx64 "->%#012" PRIx64 " %4" PRId64 " pts\n", *pts_p, pts, pts - *pts_p); *pts_p = pts; } } } /// /// Display a ffmpeg frame /// /// @param decoder VDPAU video hardware decoder /// @param video_ctx ffmpeg video codec context /// @param frame frame to display /// void VideoRenderFrame(VideoHwDecoder * decoder, AVCodecContext * video_ctx, AVFrame * frame) { if (frame->repeat_pict) { Warning("video: repeated pict found, but not handled\n"); } #ifdef USE_VAAPI if (VideoVaapiEnabled) { VideoSetPts(&decoder->Vaapi.PTS, decoder->Vaapi.Interlaced, frame); VaapiSyncRenderFrame(&decoder->Vaapi, video_ctx, frame); return; } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { VideoSetPts(&decoder->Vdpau.PTS, decoder->Vdpau.Interlaced, frame); VdpauSyncRenderFrame(&decoder->Vdpau, video_ctx, frame); return; } #endif (void)decoder; (void)video_ctx; (void)frame; } /// /// Get VA-API ffmpeg context /// /// @param decoder VA-API decoder /// struct vaapi_context *VideoGetVaapiContext(VideoHwDecoder * decoder) { #ifdef USE_VAAPI if (VideoVaapiEnabled) { return decoder->Vaapi.VaapiContext; } #endif (void)decoder; Error(_("video/vaapi: get vaapi context, without vaapi enabled\n")); return NULL; } #ifdef USE_VDPAU /// /// Draw ffmpeg vdpau render state. /// /// @param decoder VDPAU hw decoder /// @param vrs vdpau render state /// void VideoDrawRenderState(VideoHwDecoder * decoder, struct vdpau_render_state *vrs) { if (VideoVdpauEnabled) { VdpStatus status; uint32_t start; uint32_t end; Debug(4, "video/vdpau: decoder render to %#010x\n", vrs->surface); start = GetMsTicks(); status = VdpauDecoderRender(decoder->Vdpau.VideoDecoder, vrs->surface, (VdpPictureInfo const *)&vrs->info, vrs->bitstream_buffers_used, vrs->bitstream_buffers); end = GetMsTicks(); if (status != VDP_STATUS_OK) { Error(_("video/vdpau: decoder rendering failed: %s\n"), VdpauGetErrorString(status)); } if (end - start > 35) { // report this Info(_("video/vdpau: decoder render too slow %u ms\n"), end - start); } return; } (void)decoder; (void)vrs; Error(_("video/vdpau: draw render state, without vdpau enabled\n")); return; } #endif #ifndef USE_VIDEO_THREAD /// /// Video render. /// void VideoDisplayHandler(void) { uint32_t now; if (!XlibDisplay) { // not yet started return; } now = GetMsTicks(); if (now < VaapiDecoders[0]->LastFrameTick) { return; } if (now - VaapiDecoders[0]->LastFrameTick < 500) { return; } VideoPollEvent(); VaapiBlackSurface(VaapiDecoders[0]); return; #ifdef USE_VAAPI if (VideoVaapiEnabled) { VaapiDisplayFrame(); return; } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { return; } #endif VideoDisplayFrame(); } #endif /// /// Get video clock. /// /// @note this isn't monoton, decoding reorders frames, /// setter keeps it monotonic /// @todo we have multiple clocks, for multiple stream /// int64_t VideoGetClock(void) { #ifdef USE_VAAPI if (VideoVaapiEnabled) { return VaapiGetClock(VaapiDecoders[0]); } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { return VdpauGetClock(VdpauDecoders[0]); } #endif return 0L; } //---------------------------------------------------------------------------- // 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_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); // 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("softhddevice") - 1, "softhddevice"); xcb_icccm_set_wm_icon_name(Connection, VideoWindow, XCB_ATOM_STRING, 8, sizeof("softhddevice") - 1, "softhddevice"); #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("softhddevice") - 1, "softhddevice"); xcb_set_wm_icon_name(Connection, VideoWindow, XCB_ATOM_STRING, sizeof("softhddevice") - 1, "softhddevice"); #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); } } xcb_map_window(Connection, VideoWindow); // // 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); VideoBlankCursor = cursor; // FIXME: free cursor/pixmap needed? } /// /// Set video geometry. /// /// @param geometry [=][{xX}][{+-}{+-}] /// int VideoSetGeometry(const char *geometry) { int flags; flags = XParseGeometry(geometry, &VideoWindowX, &VideoWindowY, &VideoWindowWidth, &VideoWindowHeight); return 0; } /// /// Set video output position. /// /// @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. /// void VideoSetOutputPosition(int x, int y, int width, int height) { // FIXME: high level, currently works osd relative if (!OsdWidth || !OsdHeight) { return; } x = (x * VideoWindowWidth) / OsdWidth; y = (y * VideoWindowHeight) / OsdHeight; width = (width * VideoWindowWidth) / OsdWidth; height = (height * VideoWindowHeight) / OsdHeight; #ifdef USE_VDPAU if (VideoVdpauEnabled) { VdpauSetOutputPosition(VdpauDecoders[0], x, y, width, height); } #endif #ifdef USE_VAPI // FIXME: not supported by vaapi without unscaled OSD, // FIXME: if used to position video inside osd #endif } /// /// 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(int x, int y, int width, int height) { Debug(3, "video: %s %dx%d%+d%+d\n", __FUNCTION__, width, height, x, y); if ((unsigned)width == VideoWindowWidth && (unsigned)height == VideoWindowHeight) { return; // same size nothing todo } VideoWindowWidth = width; VideoWindowHeight = height; #ifdef USE_VAAPI if (VideoVaapiEnabled && VaapiDecoders[0]) { // FIXME: must update osd surfaces? VaapiUpdateOutput(VaapiDecoders[0]); return; } #endif #ifdef USE_VDPAU if (VideoVdpauEnabled && VdpauDecoders[0]) { VdpauExitOutputQueue(); VdpauInitOutputQueue(); VdpauUpdateOutput(VdpauDecoders[0]); return; } #endif } /// /// Set deinterlace mode. /// void VideoSetDeinterlace(int mode[VideoResolutionMax]) { VideoDeinterlace[0] = mode[0]; VideoDeinterlace[1] = mode[1]; VideoDeinterlace[2] = mode[2]; VideoDeinterlace[3] = mode[3]; 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]; VideoSurfaceModesChanged = 1; } /// /// Set denoise level (0 .. 1000). /// void VideoSetDenoise(int level[VideoResolutionMax]) { VideoDenoise[0] = level[0]; VideoSharpen[1] = level[1]; VideoSharpen[2] = level[2]; VideoSharpen[3] = level[3]; 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]; VideoSurfaceModesChanged = 1; } /// /// Set scaling mode. /// void VideoSetScaling(int mode[VideoResolutionMax]) { VideoScaling[0] = mode[0]; VideoScaling[1] = mode[1]; VideoScaling[2] = mode[2]; VideoScaling[3] = mode[3]; VideoSurfaceModesChanged = 1; } /// /// Set audio delay. /// /// @param ms delay in ms /// void VideoSetAudioDelay(int ms) { VideoAudioDelay = ms * 90; } /// /// 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 *screen; if (XlibDisplay) { // allow multiple calls Debug(3, "video: x11 already setup\n"); return; } // 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 (!display_name) { // use :0.0 as default display name display_name = ":0.0"; } } if (!(XlibDisplay = XOpenDisplay(display_name))) { Fatal(_("video: Can't connect to X11 server on '%s'"), display_name); // FIXME: we need to retry connection } XInitThreads(); // Convert XLIB display to XCB connection if (!(Connection = XGetXCBConnection(XlibDisplay))) { Fatal(_("video: Can't convert XLIB display to XCB connection")); } // prefetch extensions //xcb_prefetch_extension_data(Connection, &xcb_big_requests_id); //xcb_prefetch_extension_data(Connection, &xcb_dpms_id); //xcb_prefetch_extension_data(Connection, &xcb_glx_id); //xcb_prefetch_extension_data(Connection, &xcb_randr_id); //xcb_prefetch_extension_data(Connection, &xcb_screensaver_id); //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; // // Default window size // if (!VideoWindowHeight) { if (VideoWindowWidth) { VideoWindowHeight = (VideoWindowWidth * 9) / 16; } VideoWindowHeight = 576; } if (!VideoWindowWidth) { VideoWindowWidth = (VideoWindowHeight * 16) / 9; } // // prepare opengl // #ifdef USE_GLX if (GlxEnabled) { GlxInit(); // FIXME: use root window? VideoCreateWindow(screen->root, GlxVisualInfo->visualid, GlxVisualInfo->depth); GlxSetupWindow(VideoWindow, VideoWindowWidth, VideoWindowHeight); } else #endif // // Create output window // if (1) { // FIXME: use window mode VideoCreateWindow(screen->root, screen->root_visual, screen->root_depth); } else { // FIXME: support embedded mode VideoWindow = screen->root; // FIXME: VideoWindowHeight VideoWindowWidth } Debug(3, "video: window prepared\n"); // // prepare hardware decoder VA-API/VDPAU // #ifdef USE_VDPAU if (VideoVdpauEnabled) { VideoVdpauInit(display_name); #ifdef USE_VAAPI // disable va-api, if vdpau succeeded if (VideoVdpauEnabled) { VideoVaapiEnabled = 0; } #endif } #endif #ifdef USE_VAAPI if (VideoVaapiEnabled) { VideoVaapiInit(display_name); } #endif //xcb_prefetch_maximum_request_length(Connection); xcb_flush(Connection); } /// /// Cleanup video output module. /// void VideoExit(void) { if (!XlibDisplay) { // no init or failed return; } #ifdef USE_VIDEO_THREAD VideoThreadExit(); #endif #ifdef USE_VDPAU if (VideoVdpauEnabled) { VideoVdpauExit(); } #endif #ifdef USE_VAAPI if (VideoVaapiEnabled) { VideoVaapiExit(); } #endif #ifdef USE_GLX if (GlxEnabled) { GlxExit(); } #endif // // Reenable screensaver / DPMS. // //X11SuspendScreenSaver(XlibDisplay, False); //X11DPMSEnable(XlibDisplay); // // FIXME: cleanup. // //RandrExit(); } #endif #ifdef VIDEO_TEST #include int SysLogLevel; ///< show additional debug informations /// /// Print version. /// static void PrintVersion(void) { printf("video_test: video tester Version " VERSION #ifdef GIT_REV "(GIT-" GIT_REV ")" #endif ",\n\t(c) 2009 - 2011 by Johns\n" "\tLicense AGPLv3: GNU Affero General Public License version 3\n"); } /// /// Print usage. /// static void PrintUsage(void) { printf("Usage: video_test [-?dhv]\n" "\t-d\tenable debug, more -d increase the verbosity\n" "\t-? -h\tdisplay this message\n" "\t-v\tdisplay version information\n" "Only idiots print usage on stderr!\n"); } /// /// Main entry point. /// /// @param argc number of arguments /// @param argv arguments vector /// /// @returns -1 on failures, 0 clean exit. /// int main(int argc, char *const argv[]) { SysLogLevel = 0; // // Parse command line arguments // for (;;) { switch (getopt(argc, argv, "hv?-c:d")) { case 'd': // enabled debug ++SysLogLevel; continue; case EOF: break; case 'v': // print version PrintVersion(); return 0; case '?': case 'h': // help usage PrintVersion(); PrintUsage(); return 0; case '-': PrintVersion(); PrintUsage(); fprintf(stderr, "\nWe need no long options\n"); return -1; case ':': PrintVersion(); fprintf(stderr, "Missing argument for option '%c'\n", optopt); return -1; default: PrintVersion(); fprintf(stderr, "Unkown option '%c'\n", optopt); return -1; } break; } if (optind < argc) { PrintVersion(); while (optind < argc) { fprintf(stderr, "Unhandled argument '%s'\n", argv[optind++]); } return -1; } // // main loop // VideoInit(NULL); VideoOsdInit(); for (;;) { VideoRenderOverlay(); VideoDisplayOverlay(); glXSwapBuffers(XlibDisplay, VideoWindow); GlxCheck(); glClear(GL_COLOR_BUFFER_BIT); XFlush(XlibDisplay); XSync(XlibDisplay, False); XFlush(XlibDisplay); XSync(XlibDisplay, False); XFlush(XlibDisplay); XSync(XlibDisplay, False); XFlush(XlibDisplay); XSync(XlibDisplay, False); XFlush(XlibDisplay); XSync(XlibDisplay, False); XFlush(XlibDisplay); usleep(20 * 1000); } VideoExit(); return 0; } #endif