From c4bc3e9eb9a8a082d243b9d091018e58072eb3b6 Mon Sep 17 00:00:00 2001 From: Mark Kendall Date: Sat, 18 Jun 2011 10:24:16 +0800 Subject: [PATCH] Add VAAPI support. This seems to be pretty stable here although only tested using the xorg edgers ubuntu PPA on an Intel i3 530. - you must be using OpenGL to draw the main UI (otherwise initialisation will fail). - rendering is subclassed from the standard OpenGL video renderer, so OSD, subtitles etc all work as expected. - there is no deinterlacing support. - picture adjustments will follow. - there are some still frame issues with DVD playback. I haven't yet found a file that VAAPI won't accelerate for H.264 and MPEG2, including flawless playback of killa_sampla. Refs #8593 --- mythtv/configure | 17 + mythtv/libs/libmythtv/avformatdecoder.cpp | 63 +++ mythtv/libs/libmythtv/libmythtv.pro | 5 + mythtv/libs/libmythtv/mythplayer.cpp | 4 +- mythtv/libs/libmythtv/mythplayer.h | 2 +- mythtv/libs/libmythtv/vaapicontext.cpp | 533 ++++++++++++++++++ mythtv/libs/libmythtv/vaapicontext.h | 56 ++ mythtv/libs/libmythtv/videodisplayprofile.cpp | 20 + mythtv/libs/libmythtv/videoout_d3d.cpp | 4 +- mythtv/libs/libmythtv/videoout_d3d.h | 2 +- .../libs/libmythtv/videoout_openglvaapi.cpp | 266 +++++++++ mythtv/libs/libmythtv/videoout_openglvaapi.h | 48 ++ mythtv/libs/libmythtv/videooutbase.cpp | 17 + mythtv/libs/libmythtv/videooutbase.h | 2 +- 14 files changed, 1033 insertions(+), 6 deletions(-) create mode 100644 mythtv/libs/libmythtv/vaapicontext.cpp create mode 100644 mythtv/libs/libmythtv/vaapicontext.h create mode 100644 mythtv/libs/libmythtv/videoout_openglvaapi.cpp create mode 100644 mythtv/libs/libmythtv/videoout_openglvaapi.h diff --git a/mythtv/configure b/mythtv/configure index 667aadca430..36da7f32cbc 100755 --- a/mythtv/configure +++ b/mythtv/configure @@ -122,6 +122,7 @@ Advanced options (experts only): --disable-xv disable XVideo (X11 video output accel.) --enable-vdpau enable NVidia VDPAU hardware acceleration. --enable-crystalhd enable Broadcom CrystalHD hardware decoder support + --enablevaapi enable VAAPI hardware accelerated video decoding --enable-dxva2 enable hardware accelerated decoding on windows --disable-opengl-video disable OpenGL based video display --disable-quartz-video disable Mac OS X CoreVideo based video display @@ -1399,6 +1400,7 @@ USING_LIST=' dxva2 opengl opengles + vaapi vdpau ' @@ -1775,6 +1777,7 @@ v4l1_deps="backend v4l2 linux_videodev_h" vdpau_deps="opengl vdpau_vdpau_h vdpau_vdpau_x11_h" xrandr_deps="x11" xv_deps="x11" +vaapi_deps="x11 opengl" asi_deps="backend" <
= 0x001F0000" || + { echolog "VAAPI requires libva >= 0.31.1" && disable vaapi; } + fi +else + disable vaapi +fi + if enabled dxva2; then enabled dxva2api_h && enabled windows || disable dxva2 else @@ -4473,6 +4489,7 @@ if enabled x11 ; then echo "xrandr support ${xrandr-no}" echo "xv support ${xv-no}" echo "VDPAU support ${vdpau-no}" + echo "VAAPI support ${vaapi-no}" echo "CrystalHD support ${crystalhd-no}" fi echo "OpenGL video ${opengl_video-no}" diff --git a/mythtv/libs/libmythtv/avformatdecoder.cpp b/mythtv/libs/libmythtv/avformatdecoder.cpp index 47b346c2238..85c64956081 100644 --- a/mythtv/libs/libmythtv/avformatdecoder.cpp +++ b/mythtv/libs/libmythtv/avformatdecoder.cpp @@ -53,6 +53,11 @@ extern "C" { #include "videoout_d3d.h" #endif +#ifdef USING_VAAPI +#include "videoout_openglvaapi.h" +#include "vaapicontext.h" +#endif // USING_VAAPI + extern "C" { #include "libavutil/avutil.h" #include "libavcodec/ac3_parser.h" @@ -125,6 +130,7 @@ void release_avf_buffer_vdpau(struct AVCodecContext *c, AVFrame *pic); void render_slice_vdpau(struct AVCodecContext *s, const AVFrame *src, int offset[4], int y, int type, int height); int get_avf_buffer_dxva2(struct AVCodecContext *c, AVFrame *pic); +int get_avf_buffer_vaapi(struct AVCodecContext *c, AVFrame *pic); static AVCodec *find_vdpau_decoder(AVCodec *c, enum CodecID id) { @@ -238,6 +244,11 @@ void AvFormatDecoder::GetDecoders(render_opts &opts) (*opts.equiv_decoders)["dxva2"].append("dummy"); #endif +#ifdef USING_VAAPI + opts.decoders->append("vaapi"); + (*opts.equiv_decoders)["vaapi"].append("dummy"); +#endif + PrivateDecoder::GetDecoders(opts); } @@ -1270,6 +1281,13 @@ void AvFormatDecoder::InitVideoCodec(AVStream *stream, AVCodecContext *enc, enc->get_format = get_format_dxva2; enc->release_buffer = release_avf_buffer; } + else if (CODEC_IS_VAAPI(codec, enc)) + { + enc->get_buffer = get_avf_buffer_vaapi; + enc->get_format = get_format_vaapi; + enc->release_buffer = release_avf_buffer; + enc->slice_flags = SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD; + } else if (codec && codec->capabilities & CODEC_CAP_DR1) { enc->flags |= CODEC_FLAG_EMU_EDGE; @@ -1805,6 +1823,25 @@ int AvFormatDecoder::ScanStreams(bool novideo) handled = true; } #endif // USING_VDPAU +#ifdef USING_VAAPI + MythCodecID vaapi_mcid; + PixelFormat pix_fmt = PIX_FMT_YUV420P; + vaapi_mcid = VideoOutputOpenGLVAAPI::GetBestSupportedCodec( + width, height, mpeg_version(enc->codec_id), + no_hardware_decoders, pix_fmt); + + if (vaapi_mcid >= video_codec_id) + { + enc->codec_id = (CodecID)myth2av_codecid(vaapi_mcid); + video_codec_id = vaapi_mcid; + handled = true; + if (!no_hardware_decoders && + codec_is_vaapi(video_codec_id)) + { + enc->pix_fmt = pix_fmt; + } + } +#endif // USING_VAAPI #ifdef USING_DXVA2 MythCodecID dxva2_mcid; PixelFormat pix_fmt = PIX_FMT_YUV420P; @@ -2391,6 +2428,32 @@ int get_avf_buffer_dxva2(struct AVCodecContext *c, AVFrame *pic) return 0; } +int get_avf_buffer_vaapi(struct AVCodecContext *c, AVFrame *pic) +{ + AvFormatDecoder *nd = (AvFormatDecoder *)(c->opaque); + VideoFrame *frame = nd->GetPlayer()->GetNextVideoFrame(); + + pic->data[0] = frame->buf; + pic->data[1] = NULL; + pic->data[2] = NULL; + pic->data[3] = NULL; + pic->linesize[0] = 0; + pic->linesize[1] = 0; + pic->linesize[2] = 0; + pic->linesize[3] = 0; + pic->opaque = frame; + pic->type = FF_BUFFER_TYPE_USER; + pic->age = 256 * 256 * 256 * 64; + frame->pix_fmt = c->pix_fmt; + +#ifdef USING_VAAPI + if (nd->GetPlayer()) + c->hwaccel_context = (vaapi_context*)nd->GetPlayer()->GetDecoderContext(frame->buf, pic->data[3]); +#endif + + return 0; +} + void AvFormatDecoder::DecodeDTVCC(const uint8_t *buf, uint len) { if (!len) diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro index 7f03f7980c6..f87d6293827 100644 --- a/mythtv/libs/libmythtv/libmythtv.pro +++ b/mythtv/libs/libmythtv/libmythtv.pro @@ -370,6 +370,11 @@ using_frontend { using_opengl_video:HEADERS += openglvideo.h videoout_opengl.h using_opengl_video:SOURCES += openglvideo.cpp videoout_opengl.cpp + using_vaapi: DEFINES += USING_VAAPI + using_vaapi: DEFINES += vaapicontext.h videoout_openglvaapi.h + using_vaapi: SOURCES += vaapicontext.cpp videoout_openglvaapi.cpp + using_vaapi: LIBS += -lva -lva-x11 -lva-glx + # Misc. frontend HEADERS += DetectLetterbox.h SOURCES += DetectLetterbox.cpp diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp index e7276c3f0df..c3749568d23 100644 --- a/mythtv/libs/libmythtv/mythplayer.cpp +++ b/mythtv/libs/libmythtv/mythplayer.cpp @@ -1157,10 +1157,10 @@ void MythPlayer::DrawSlice(VideoFrame *frame, int x, int y, int w, int h) videoOutput->DrawSlice(frame, x, y, w, h); } -void* MythPlayer::GetDecoderContext(void) +void* MythPlayer::GetDecoderContext(unsigned char* buf, uint8_t*& id) { if (videoOutput) - return videoOutput->GetDecoderContext(); + return videoOutput->GetDecoderContext(buf, id); return NULL; } diff --git a/mythtv/libs/libmythtv/mythplayer.h b/mythtv/libs/libmythtv/mythplayer.h index dd1c9729018..b8b72446cef 100644 --- a/mythtv/libs/libmythtv/mythplayer.h +++ b/mythtv/libs/libmythtv/mythplayer.h @@ -237,7 +237,7 @@ class MTV_PUBLIC MythPlayer void DrawSlice(VideoFrame *frame, int x, int y, int w, int h); /// Returns the stream decoder currently in use. DecoderBase *GetDecoder(void) { return decoder; } - void *GetDecoderContext(void); + void *GetDecoderContext(unsigned char* buf, uint8_t*& id); // Preview Image stuff void SaveScreenshot(void); diff --git a/mythtv/libs/libmythtv/vaapicontext.cpp b/mythtv/libs/libmythtv/vaapicontext.cpp new file mode 100644 index 00000000000..647d9678781 --- /dev/null +++ b/mythtv/libs/libmythtv/vaapicontext.cpp @@ -0,0 +1,533 @@ +#include "openglvideo.h" +#include "mythverbose.h" +#include "mythxdisplay.h" +#include "mythcodecid.h" +#include "frame.h" +#include "vaapicontext.h" +#include "mythmainwindow.h" + +#define LOC QString("VAAPI: ") +#define ERR QString("VAAPI Error: ") +#define NUM_VAAPI_BUFFERS 24 + +#define INIT_ST \ + VAStatus va_status; \ + bool ok = true; + +#define CHECK_ST \ + ok &= (va_status == VA_STATUS_SUCCESS); \ + if (!ok) { \ + VERBOSE(VB_IMPORTANT, ERR + QString("Error at %1:%2 (#%3, %4)") \ + .arg(__FILE__).arg( __LINE__).arg(va_status) \ + .arg(vaErrorStr(va_status))); \ + } + +#define CREATE_CHECK(arg1, arg2) \ + if (ok) \ + { \ + ok = arg1; \ + if (!ok) \ + VERBOSE(VB_IMPORTANT, ERR + arg2); \ + } + +QString profileToString(VAProfile profile); +QString entryToString(VAEntrypoint entry); +VAProfile preferredProfile(MythCodecID codec); + +QString profileToString(VAProfile profile) +{ + if (VAProfileMPEG2Simple == profile) return "MPEG2Simple"; + if (VAProfileMPEG2Main == profile) return "MPEG2Main"; + if (VAProfileMPEG4Simple == profile) return "MPEG4Simple"; + if (VAProfileMPEG4AdvancedSimple == profile) return "MPEG4AdvSimple"; + if (VAProfileMPEG4Main == profile) return "MPEG4Main"; + if (VAProfileH264Baseline == profile) return "H264Base"; + if (VAProfileH264Main == profile) return "H264Main"; + if (VAProfileH264High == profile) return "H264High"; + if (VAProfileVC1Simple == profile) return "VC1Simple"; + if (VAProfileVC1Main == profile) return "VC1Main"; + if (VAProfileVC1Advanced == profile) return "VC1Advanced"; + if (VAProfileH263Baseline == profile) return "H263Base"; + return "Unknown"; +} + +QString entryToString(VAEntrypoint entry) +{ + if (VAEntrypointVLD == entry) return "VLD "; + if (VAEntrypointIZZ == entry) return "IZZ (UNSUPPORTED) "; + if (VAEntrypointIDCT == entry) return "IDCT (UNSUPPORTED) "; + if (VAEntrypointMoComp == entry) return "MC (UNSUPPORTED) "; + if (VAEntrypointDeblocking == entry) return "Deblock (UNSUPPORTED) "; + if (VAEntrypointEncSlice == entry) return "EncSlice (UNSUPPORTED) "; + return "Unknown"; +} + +VAProfile preferredProfile(MythCodecID codec) +{ + // FIXME handle unsupported codecs properly + if (kCodec_H263_VAAPI == codec) return VAProfileMPEG4AdvancedSimple; + if (kCodec_MPEG4_VAAPI == codec) return VAProfileMPEG4AdvancedSimple; + if (kCodec_H264_VAAPI == codec) return VAProfileH264High; + if (kCodec_VC1_VAAPI == codec) return VAProfileVC1Advanced; + if (kCodec_WMV3_VAAPI == codec) return VAProfileVC1Main; + return VAProfileMPEG2Main; +} + +class VAAPIDisplay +{ + protected: + VAAPIDisplay() : m_va_disp(NULL), m_x_disp(NULL), m_ref_count(0) { } + public: + ~VAAPIDisplay() + { + if (m_va_disp) + { + INIT_ST + XLOCK(m_x_disp, va_status = vaTerminate(m_va_disp)); + CHECK_ST + } + if (m_x_disp) + { + m_x_disp->Sync(true); + delete m_x_disp; + } + } + + bool Create(void) + { + MythMainWindow *mw = GetMythMainWindow(); + if (!mw) + return false; + + MythRenderOpenGL *gl = static_cast(mw->GetRenderDevice()); + if (!gl) + { + VERBOSE(VB_PLAYBACK, LOC + + QString("Failed to get OpenGL context - you must use the " + "OpenGL UI painter for VAAPI support.")); + return false; + } + gl->makeCurrent(); + display = glXGetCurrentDisplay(); + gl->doneCurrent(); + + m_x_disp = OpenMythXDisplay(); + if (!m_x_disp) + return false; + + MythXLocker locker(m_x_disp); + int major_ver, minor_ver; + + //m_va_disp = vaGetDisplayGLX(m_x_disp->GetDisplay()); + m_va_disp = vaGetDisplayGLX(display); + + if (!m_va_disp) + { + VERBOSE(VB_IMPORTANT, ERR + "Failed to create VADisplay"); + return false; + } + + INIT_ST + va_status = vaInitialize(m_va_disp, &major_ver, &minor_ver); + CHECK_ST + + static bool debugged = false; + if (ok && !debugged) + { + debugged = true; + VERBOSE(VB_IMPORTANT, LOC + QString("Version: %1.%2") + .arg(major_ver).arg(minor_ver)); + VERBOSE(VB_IMPORTANT, LOC + QString("Vendor : %1") + .arg(vaQueryVendorString(m_va_disp))); + } + if (ok) + { + UpRef(); + VERBOSE(VB_PLAYBACK, LOC + QString("Created VAAPI GLX display")); + } + return ok; + } + + void UpRef(void) + { + XLOCK(m_x_disp, m_ref_count++) + } + + void DownRef(void) + { + m_x_disp->Lock(); + m_ref_count--; + if (m_ref_count <= 0) + { + if (gVAAPIDisplay == this) + gVAAPIDisplay = NULL; + VERBOSE(VB_PLAYBACK, LOC + "Deleting VAAPI display."); + m_x_disp->Unlock(); + delete this; + return; + } + m_x_disp->Unlock(); + } + + static VAAPIDisplay* GetDisplay(void) + { + if (gVAAPIDisplay) + { + gVAAPIDisplay->UpRef(); + return gVAAPIDisplay; + } + + gVAAPIDisplay = new VAAPIDisplay(); + if (gVAAPIDisplay && gVAAPIDisplay->Create()) + return gVAAPIDisplay; + + delete gVAAPIDisplay; + gVAAPIDisplay = NULL; + return NULL; + } + + static VAAPIDisplay *gVAAPIDisplay; + void *m_va_disp; + MythXDisplay *m_x_disp; + Display *display; + int m_ref_count; +}; + +VAAPIDisplay* VAAPIDisplay::gVAAPIDisplay = NULL; + +bool VAAPIContext::IsFormatAccelerated(QSize size, MythCodecID codec, + PixelFormat &pix_fmt) +{ + bool result = false; + VAAPIContext *ctx = new VAAPIContext(codec); + if (ctx && ctx->CreateDisplay(size)) + { + pix_fmt = ctx->GetPixelFormat(); + result = pix_fmt == PIX_FMT_VAAPI_VLD; + } + delete ctx; + return result; +} + +VAAPIContext::VAAPIContext(MythCodecID codec) + : m_codec(codec), + m_vaProfile(VAProfileMPEG2Main)/* ?? */, + m_vaEntrypoint(VAEntrypointEncSlice), + m_pix_fmt(PIX_FMT_YUV420P), m_numSurfaces(NUM_VAAPI_BUFFERS), + m_surfaces(NULL), m_surfaceData(NULL) +{ + memset(&m_ctx, 0, sizeof(vaapi_context)); +} + +VAAPIContext::~VAAPIContext() +{ + ClearGLXSurfaces(); + + if (m_display) + { + m_display->m_x_disp->Lock(); + + INIT_ST + if (m_ctx.context_id) + { + va_status = vaDestroyContext(m_ctx.display, m_ctx.context_id); + CHECK_ST + } + if (m_ctx.config_id) + { + va_status = vaDestroyConfig(m_ctx.display, m_ctx.config_id); + CHECK_ST + } + if (m_surfaces) + { + va_status = vaDestroySurfaces(m_ctx.display, m_surfaces, m_numSurfaces); + CHECK_ST + } + } + + if (m_surfaces) + delete [] m_surfaces; + if (m_surfaceData) + delete [] m_surfaceData; + + if (m_display) + { + m_display->m_x_disp->Unlock(); + m_display->DownRef(); + } + + VERBOSE(VB_PLAYBACK, LOC + "Deleted context"); +} + +bool VAAPIContext::CreateDisplay(QSize size) +{ + m_size = size; + bool ok = true; + m_display = VAAPIDisplay::GetDisplay(); + CREATE_CHECK(!m_size.isEmpty(), "Invalid size") + CREATE_CHECK(m_display != NULL, "Invalid display") + CREATE_CHECK(InitDisplay(), "Invalid VADisplay") + CREATE_CHECK(InitProfiles(), "No supported profiles") + if (ok) + VERBOSE(VB_PLAYBACK, LOC + QString("Created context (%1x%2->%3x%4)") + .arg(size.width()).arg(size.height()) + .arg(m_size.width()).arg(m_size.height())); + return ok; +} + +bool VAAPIContext::CreateBuffers(void) +{ + bool ok = true; + CREATE_CHECK(!m_size.isEmpty(), "Invalid size") + CREATE_CHECK(InitBuffers(), "Failed to create buffers.") + CREATE_CHECK(InitContext(), "Failed to create context") + if (ok) + VERBOSE(VB_PLAYBACK, LOC + QString("Created %1 buffers").arg(m_numSurfaces)); + return ok; +} + +bool VAAPIContext::InitDisplay(void) +{ + if (!m_display) + return false; + m_ctx.display = m_display->m_va_disp; + return m_ctx.display; +} + +bool VAAPIContext::InitProfiles(void) +{ + if (!(codec_is_vaapi(m_codec)) || !m_ctx.display) + return false; + + MythXLocker locker(m_display->m_x_disp); + int max_profiles, max_entrypoints; + VAProfile profile_wanted = preferredProfile(m_codec); + VAProfile profile_found = VAProfileMPEG2Main; // FIXME + VAEntrypoint entry_found = VAEntrypointEncSlice; // unsupported value + + max_profiles = vaMaxNumProfiles(m_ctx.display); + max_entrypoints = vaMaxNumEntrypoints(m_ctx.display); + VAProfile *profiles = new VAProfile[max_profiles]; + VAEntrypoint *entries = new VAEntrypoint[max_entrypoints]; + + static bool debugged = false; + if (profiles && entries) + { + INIT_ST + int act_profiles, act_entries; + va_status = vaQueryConfigProfiles(m_ctx.display, + profiles, + &act_profiles); + CHECK_ST + if (ok && act_profiles > 0) + { + for (int i = 0; i < act_profiles; i++) + { + va_status = vaQueryConfigEntrypoints(m_ctx.display, + profiles[i], + entries, + &act_entries); + if (va_status == VA_STATUS_SUCCESS && act_entries > 0) + { + if (profiles[i] == profile_wanted) + { + profile_found = profile_wanted; + for (int j = 0; j < act_entries; j++) + if (entries[j] < entry_found) + entry_found = entries[j]; + } + + if (!debugged) + { + QString entrylist = "Entrypoints: "; + for (int j = 0; j < act_entries; j++) + entrylist += entryToString(entries[j]); + VERBOSE(VB_IMPORTANT, LOC + QString("Profile: %1 %2") + .arg(profileToString(profiles[i])).arg(entrylist)); + } + } + } + } + debugged = true; + } + delete [] profiles; + delete [] entries; + + VERBOSE(VB_PLAYBACK, LOC + QString("Desired profile for '%1': %2") + .arg(toString(m_codec)).arg(profileToString(profile_wanted))); + VERBOSE(VB_PLAYBACK, LOC + QString("Found profile %1 with entry %2") + .arg(profileToString(profile_found)).arg(entryToString(entry_found))); + + if (profile_wanted != profile_found) + { + VERBOSE(VB_IMPORTANT, ERR + "Failed to find supported profile."); + return false; + } + + if (entry_found > VAEntrypointVLD) + { + VERBOSE(VB_IMPORTANT, ERR + "Failed to find suitable entry point."); + return false; + } + + m_vaProfile = profile_wanted; + m_vaEntrypoint = entry_found; + if (VAEntrypointVLD == m_vaEntrypoint) + m_pix_fmt = PIX_FMT_VAAPI_VLD; + return true; +} + +bool VAAPIContext::InitBuffers(void) +{ + if (!m_ctx.display) + return false; + + MythXLocker locker(m_display->m_x_disp); + m_surfaces = new VASurfaceID[m_numSurfaces]; + m_surfaceData = new vaapi_surface[m_numSurfaces]; + + if (!m_surfaces || !m_surfaceData) + return false; + + memset(m_surfaces, 0, sizeof(m_surfaces)); + memset(m_surfaceData, 0, sizeof(m_surfaceData)); + + INIT_ST + va_status = vaCreateSurfaces(m_ctx.display, m_size.width(), m_size.height(), + VA_RT_FORMAT_YUV420, m_numSurfaces, + m_surfaces); + CHECK_ST + + for (int i = 0; i < m_numSurfaces; i++) + m_surfaceData[i].m_id = m_surfaces[i]; + return ok; +} + +bool VAAPIContext::InitContext(void) +{ + if (!m_ctx.display || m_vaEntrypoint > VAEntrypointVLD) + return false; + + MythXLocker locker(m_display->m_x_disp); + VAConfigAttrib attrib; + attrib.type = VAConfigAttribRTFormat; + INIT_ST + va_status = vaGetConfigAttributes(m_ctx.display, m_vaProfile, + m_vaEntrypoint, &attrib, 1); + CHECK_ST + + if (!ok || !(attrib.value & VA_RT_FORMAT_YUV420)) + { + VERBOSE(VB_IMPORTANT, ERR + "Failed to confirm YUV420 chroma"); + return false; + } + + va_status = vaCreateConfig(m_ctx.display, m_vaProfile, m_vaEntrypoint, + &attrib, 1, &m_ctx.config_id); + CHECK_ST + if (!ok) + { + VERBOSE(VB_IMPORTANT, ERR + "Failed to create decoder config."); + return false; + } + + va_status = vaCreateContext(m_ctx.display, m_ctx.config_id, + m_size.width(), m_size.height(), VA_PROGRESSIVE, + m_surfaces, m_numSurfaces, + &m_ctx.context_id); + CHECK_ST + if (!ok) + { + VERBOSE(VB_IMPORTANT, ERR + "Failed to create decoder context."); + return false; + } + return true; +} + +void* VAAPIContext::GetVideoSurface(int i) +{ + if (i < 0 || i >= m_numSurfaces) + return NULL; + return &m_surfaceData[i]; +} + +uint8_t* VAAPIContext::GetSurfaceIDPointer(void* buf) +{ + if (!buf) + return NULL; + + const vaapi_surface *surf = (vaapi_surface*)buf; + if (!surf->m_id) + return NULL; + + INIT_ST + va_status = vaSyncSurface(m_ctx.display, surf->m_id); + CHECK_ST + return (uint8_t*)(uintptr_t)surf->m_id; +} + +bool VAAPIContext::CopySurfaceToTexture(const void* buf, uint texture, + uint texture_type, FrameScanType scan) +{ + if (!buf) + return false; + + const vaapi_surface *surf = (vaapi_surface*)buf; + void* glx_surface = GetGLXSurface(texture, texture_type); + if (!glx_surface) + return false; + + int field = VA_FRAME_PICTURE; + //if (scan == kScan_Interlaced) + // field = VA_TOP_FIELD; + //else if (scan == kScan_Intr2ndField) + // field = VA_BOTTOM_FIELD; + + //INIT_ST + //va_status = vaSyncSurface(m_ctx.display, surf->m_id); + //CHECK_ST + + INIT_ST + va_status = vaCopySurfaceGLX(m_ctx.display, glx_surface, surf->m_id, field); + CHECK_ST + return true; +} + +void* VAAPIContext::GetGLXSurface(uint texture, uint texture_type) +{ + if (m_glxSurfaces.contains(texture)) + return m_glxSurfaces.value(texture); + + void *glx_surface = NULL; + INIT_ST + va_status = vaCreateSurfaceGLX(m_ctx.display, texture_type, + texture, &glx_surface); + CHECK_ST + if (!glx_surface) + { + VERBOSE(VB_IMPORTANT, ERR + "Failed to create GLX surface."); + return NULL; + } + + m_glxSurfaces.insert(texture, glx_surface); + + VERBOSE(VB_PLAYBACK, LOC + QString("Number of VAAPI GLX surfaces: %1") + .arg(m_glxSurfaces.size())); + return glx_surface; +} + +void VAAPIContext::ClearGLXSurfaces(void) +{ + if (!m_display) + return; + + m_display->m_x_disp->Lock(); + INIT_ST + foreach (void* surface, m_glxSurfaces) + { + va_status = vaDestroySurfaceGLX(m_ctx.display, surface); + CHECK_ST + } + m_glxSurfaces.clear(); + m_display->m_x_disp->Unlock(); +} diff --git a/mythtv/libs/libmythtv/vaapicontext.h b/mythtv/libs/libmythtv/vaapicontext.h new file mode 100644 index 00000000000..a97cdb1bd41 --- /dev/null +++ b/mythtv/libs/libmythtv/vaapicontext.h @@ -0,0 +1,56 @@ +#ifndef VAAPICONTEXT_H +#define VAAPICONTEXT_H + +extern "C" { +#include "libavcodec/vaapi.h" +} +#include "va/va_glx.h" + +struct vaapi_surface +{ + VASurfaceID m_id; +}; + +class VAAPIDisplay; +class OpenGLVideo; + +class VAAPIContext +{ + public: + static bool IsFormatAccelerated(QSize size, MythCodecID codec, + PixelFormat &pix_fmt); + VAAPIContext(MythCodecID codec); + ~VAAPIContext(); + + bool CreateDisplay(QSize size); + bool CreateBuffers(void); + void* GetVideoSurface(int i); + uint8_t* GetSurfaceIDPointer(void* buf); + + int GetNumBuffers(void) { return m_numSurfaces; } + PixelFormat GetPixelFormat(void) { return m_pix_fmt; } + + bool CopySurfaceToTexture(const void* buf, uint texture, + uint texture_type, FrameScanType scan); + void* GetGLXSurface(uint texture, uint texture_type); + void ClearGLXSurfaces(void); + + bool InitDisplay(void); + bool InitProfiles(void); + bool InitBuffers(void); + bool InitContext(void); + + vaapi_context m_ctx; + MythCodecID m_codec; + QSize m_size; + VAAPIDisplay *m_display; + VAProfile m_vaProfile; + VAEntrypoint m_vaEntrypoint; + PixelFormat m_pix_fmt; + int m_numSurfaces; + VASurfaceID *m_surfaces; + vaapi_surface *m_surfaceData; + QHash m_glxSurfaces; +}; + +#endif // VAAPICONTEXT_H diff --git a/mythtv/libs/libmythtv/videodisplayprofile.cpp b/mythtv/libs/libmythtv/videodisplayprofile.cpp index f78c5c1a8a9..bb31bb09d2d 100644 --- a/mythtv/libs/libmythtv/videodisplayprofile.cpp +++ b/mythtv/libs/libmythtv/videodisplayprofile.cpp @@ -639,6 +639,7 @@ QString VideoDisplayProfile::GetDecoderName(const QString &decoder) dec_name["ffmpeg"] = QObject::tr("Standard"); dec_name["macaccel"] = QObject::tr("Mac hardware acceleration"); dec_name["vdpau"] = QObject::tr("NVidia VDPAU acceleration"); + dec_name["vaapi"] = QObject::tr("VAAPI acceleration"); dec_name["dxva2"] = QObject::tr("Windows hardware acceleration"); } @@ -680,6 +681,10 @@ QString VideoDisplayProfile::GetDecoderHelp(QString decoder) "accelerate video decoding and playback " "(requires Windows Vista or later)."); + if (decoder == "vaapi") + msg += QObject::tr( + "VAAPI will attempt to use the graphics hardware to " + "accelerate video decoding."); return msg; } @@ -737,6 +742,10 @@ QString VideoDisplayProfile::GetDeinterlacerName(const QString short_name) return QObject::tr("Advanced (1x, HW)"); else if ("vdpauadvanceddoublerate" == short_name) return QObject::tr("Advanced (2x, HW)"); + else if ("vaapionefield" == short_name) + return QObject::tr("One Field (1x, HW)"); + else if ("vaapibobdeint" == short_name) + return QObject::tr("Bob (2x, HW)"); return ""; } @@ -1167,6 +1176,13 @@ QString VideoDisplayProfile::GetVideoRendererHelp(const QString &renderer) "This is the only video renderer for NVidia VDPAU decoding."); } + if (renderer == "openglvaapi") + { + msg = QObject::tr( + "This video renderer uses VAAPI for video decoding and " + "OpenGL for scaling and color conversion."); + } + return msg; } @@ -1300,6 +1316,10 @@ QString VideoDisplayProfile::GetDeinterlacerHelp(const QString &deint) msg = kBasicMsg + " " + kDoubleRateMsg + " " + kUsingGPU; else if (deint == "vdpauadvanceddoublerate") msg = kAdvMsg + " " + kDoubleRateMsg + " " + kUsingGPU; + else if (deint == "vaapionefield") + msg = kOneFieldMsg + " " + kUsingGPU; + else if (deint == "vaapibobdeint") + msg = kBobMsg + " " + kUsingGPU; else msg = QObject::tr("'%1' has not been documented yet.").arg(deint); diff --git a/mythtv/libs/libmythtv/videoout_d3d.cpp b/mythtv/libs/libmythtv/videoout_d3d.cpp index 79b31a46c5f..e60245008dc 100644 --- a/mythtv/libs/libmythtv/videoout_d3d.cpp +++ b/mythtv/libs/libmythtv/videoout_d3d.cpp @@ -700,8 +700,10 @@ MythCodecID VideoOutputD3D::GetBestSupportedCodec( } -void* VideoOutputD3D::GetDecoderContext(void) +void* VideoOutputD3D::GetDecoderContext(unsigned char* buf, uint8_t*& id) { + (void)buf; + (void)id; #ifdef USING_DXVA2 if (m_decoder) return (void*)&m_decoder->m_context; diff --git a/mythtv/libs/libmythtv/videoout_d3d.h b/mythtv/libs/libmythtv/videoout_d3d.h index 00a8aeca608..1dcff21b257 100644 --- a/mythtv/libs/libmythtv/videoout_d3d.h +++ b/mythtv/libs/libmythtv/videoout_d3d.h @@ -55,7 +55,7 @@ class VideoOutputD3D : public VideoOutput virtual MythPainter *GetOSDPainter(void) { return (MythPainter*)m_osd_painter; } bool hasHWAcceleration(void) const { return !codec_is_std(video_codec_id); } virtual bool ApproveDeintFilter(const QString& filtername) const; - virtual void* GetDecoderContext(void); + virtual void* GetDecoderContext(unsigned char* buf, uint8_t*& id); virtual bool CanVisualise(AudioPlayer *audio, MythRender *render) { return VideoOutput::CanVisualise(audio, (MythRender*)m_render); } diff --git a/mythtv/libs/libmythtv/videoout_openglvaapi.cpp b/mythtv/libs/libmythtv/videoout_openglvaapi.cpp new file mode 100644 index 00000000000..8feab6d0873 --- /dev/null +++ b/mythtv/libs/libmythtv/videoout_openglvaapi.cpp @@ -0,0 +1,266 @@ +#include "videoout_openglvaapi.h" +#include "vaapicontext.h" + +#define LOC QString("VidOutGLVAAPI: ") +#define ERR QString("VidOutGLVAAPI Error: ") + +void VideoOutputOpenGLVAAPI::GetRenderOptions(render_opts &opts) +{ + opts.renderers->append("openglvaapi"); + + (*opts.deints)["openglvaapi"].append("vaapionefield"); + (*opts.deints)["openglvaapi"].append("vaapibobdeint"); + (*opts.deints)["openglvaapi"].append("none"); + (*opts.osds)["openglvaapi"].append("opengl2"); + + if (opts.decoders->contains("vaapi")) + (*opts.safe_renderers)["vaapi"].append("openglvaapi"); + + if (opts.decoders->contains("ffmpeg")) + (*opts.safe_renderers)["ffmpeg"].append("openglvaapi"); + + (*opts.safe_renderers)["dummy"].append("openglvaapi"); + (*opts.safe_renderers)["nuppel"].append("openglvaapi"); + + opts.priorities->insert("openglvaapi", 120); +} + +VideoOutputOpenGLVAAPI::VideoOutputOpenGLVAAPI() + : VideoOutputOpenGL(), m_ctx(NULL) +{ +} + +VideoOutputOpenGLVAAPI::~VideoOutputOpenGLVAAPI() +{ + TearDown(); +} + +void VideoOutputOpenGLVAAPI::TearDown(void) +{ + DeleteVAAPIContext(); +} + +bool VideoOutputOpenGLVAAPI::InputChanged(const QSize &input_size, float aspect, + MythCodecID av_codec_id, void *codec_private, + bool &aspect_only) +{ + VERBOSE(VB_PLAYBACK, LOC + QString("InputChanged(%1,%2,%3) %4->%5") + .arg(input_size.width()).arg(input_size.height()).arg(aspect) + .arg(toString(video_codec_id)).arg(toString(av_codec_id))); + + if (!codec_is_vaapi(av_codec_id)) + return VideoOutputOpenGL::InputChanged(input_size, aspect, av_codec_id, + codec_private, aspect_only); + + QMutexLocker locker(&gl_context_lock); + + bool wasembedding = window.IsEmbedding(); + QRect oldrect; + if (wasembedding) + { + oldrect = window.GetEmbeddingRect(); + StopEmbedding(); + } + + bool cid_changed = (video_codec_id != av_codec_id); + bool res_changed = input_size != window.GetActualVideoDim(); + bool asp_changed = aspect != window.GetVideoAspect(); + + if (!res_changed && !cid_changed) + { + if (asp_changed) + { + aspect_only = true; + VideoAspectRatioChanged(aspect); + MoveResize(); + if (wasembedding) + EmbedInWidget(oldrect); + } + return true; + } + + if (gCoreContext->IsUIThread()) + TearDown(); + else + DestroyCPUResources(); + + QRect disp = window.GetDisplayVisibleRect(); + if (Init(input_size.width(), input_size.height(), + aspect, gl_parent_win, disp, av_codec_id)) + { + if (wasembedding) + EmbedInWidget(oldrect); + if (gCoreContext->IsUIThread()) + BestDeint(); + return true; + } + + VERBOSE(VB_IMPORTANT, ERR + + QString("Failed to re-initialise video output.")); + errorState = kError_Unknown; + + return false; +} + +bool VideoOutputOpenGLVAAPI::Init(int width, int height, float aspect, + WId winid, const QRect &win_rect, + MythCodecID codec_id) +{ + bool ok = VideoOutputOpenGL::Init(width, height, aspect, winid, + win_rect, codec_id); + if (ok && codec_is_vaapi(video_codec_id)) + return CreateVAAPIContext(window.GetActualVideoDim()); + return ok; +} + +bool VideoOutputOpenGLVAAPI::CreateVAAPIContext(QSize size) +{ + // FIXME During a video stream change this is called from the decoder + // thread - which breaks all other efforts to remove non-UI thread + // access to the OpenGL context. There is no obvious fix however - if we + // don't delete and re-create the VAAPI decoder context immediately then + // the decoder fails and playback exits. + OpenGLLocker ctx_lock(gl_context); + + if (m_ctx) + DeleteVAAPIContext(); + + m_ctx = new VAAPIContext(video_codec_id); + if (m_ctx && m_ctx->CreateDisplay(size) && m_ctx->CreateBuffers()) + { + int num_buffers = m_ctx->GetNumBuffers(); + const QSize video_dim = window.GetActualVideoDim(); + + bool ok = true; + for (int i = 0; i < num_buffers; i++) + { + ok &= vbuffers.CreateBuffer(video_dim.width(), + video_dim.height(), i, + m_ctx->GetVideoSurface(i), + FMT_VAAPI); + } + return ok; + } + + VERBOSE(VB_IMPORTANT, ERR + QString("Failed to create VAAPI context.")); + errorState = kError_Unknown; + return false; +} + +void VideoOutputOpenGLVAAPI::DeleteVAAPIContext(void) +{ + QMutexLocker locker(&gl_context_lock); + delete m_ctx; + m_ctx = NULL; +} + +bool VideoOutputOpenGLVAAPI::CreateBuffers(void) +{ + QMutexLocker locker(&gl_context_lock); + if (codec_is_vaapi(video_codec_id)) + { + vbuffers.Init(24, true, 2, 1, 4, 1); + return true; + } + return VideoOutputOpenGL::CreateBuffers(); +} + +void* VideoOutputOpenGLVAAPI::GetDecoderContext(unsigned char* buf, uint8_t*& id) +{ + if (m_ctx) + { + id = GetSurfaceIDPointer(buf); + return &m_ctx->m_ctx; + } + return NULL; +} + +uint8_t* VideoOutputOpenGLVAAPI::GetSurfaceIDPointer(void* buf) +{ + if (m_ctx) + return m_ctx->GetSurfaceIDPointer(buf); + return NULL; +} + +void VideoOutputOpenGLVAAPI::SetProfile(void) +{ + if (db_vdisp_profile) + db_vdisp_profile->SetVideoRenderer("openglvaapi"); +} + +bool VideoOutputOpenGLVAAPI::ApproveDeintFilter(const QString &filtername) const +{ + return filtername.contains("vaapi"); +} + +bool VideoOutputOpenGLVAAPI::SetDeinterlacingEnabled(bool enable) +{ + m_deinterlacing = enable; + SetupDeinterlace(enable); + return m_deinterlacing; +} + +bool VideoOutputOpenGLVAAPI::SetupDeinterlace(bool i, const QString& ovrf) +{ + //m_deintfiltername = !db_vdisp_profile ? "" : + // db_vdisp_profile->GetFilteredDeint(ovrf); + m_deinterlacing = i; + return m_deinterlacing; +} + +void VideoOutputOpenGLVAAPI::ProcessFrame(VideoFrame *frame, OSD *osd, + FilterChain *filterList, + const PIPMap &pipPlayers, + FrameScanType scan) +{ + QMutexLocker locker(&gl_context_lock); + VideoOutputOpenGL::ProcessFrame(frame, osd, filterList, pipPlayers, scan); + + if (codec_is_vaapi(video_codec_id) && m_ctx && gl_videochain && frame) + { + gl_context->makeCurrent(); + m_ctx->CopySurfaceToTexture(frame->buf, + gl_videochain->GetInputTexture(), + gl_videochain->GetTextureType(), scan); + gl_videochain->SetInputUpdated(); + gl_context->doneCurrent(); + } +} + +QStringList VideoOutputOpenGLVAAPI::GetAllowedRenderers( + MythCodecID myth_codec_id, const QSize &video_dim) +{ + (void) video_dim; + QStringList list; + if ((codec_is_std(myth_codec_id) || (codec_is_vaapi(myth_codec_id))) && + !getenv("NO_VAAPI")) + { + list += "openglvaapi"; + } + return list; +} + +MythCodecID VideoOutputOpenGLVAAPI::GetBestSupportedCodec( + uint width, uint height, + uint stream_type, bool no_acceleration, + PixelFormat &pix_fmt) +{ + QSize size(width, height); + bool use_cpu = no_acceleration; + VideoDisplayProfile vdp; + vdp.SetInput(size); + QString dec = vdp.GetDecoder(); + + PixelFormat fmt = PIX_FMT_YUV420P; + MythCodecID test_cid = (MythCodecID)(kCodec_MPEG1_VAAPI + (stream_type - 1)); + if (codec_is_vaapi(test_cid)) + use_cpu |= !VAAPIContext::IsFormatAccelerated(size, test_cid, fmt); + else + use_cpu = true; + + if ((dec != "vaapi") || getenv("NO_VAAPI") || use_cpu) + return (MythCodecID)(kCodec_MPEG1 + (stream_type - 1)); + + pix_fmt = fmt; + return test_cid; +} diff --git a/mythtv/libs/libmythtv/videoout_openglvaapi.h b/mythtv/libs/libmythtv/videoout_openglvaapi.h new file mode 100644 index 00000000000..f403fc5a529 --- /dev/null +++ b/mythtv/libs/libmythtv/videoout_openglvaapi.h @@ -0,0 +1,48 @@ +#ifndef VIDEOOUTPUTOPENGLVAAPI_H +#define VIDEOOUTPUTOPENGLVAAPI_H + +#include "videoout_opengl.h" + +class VAAPIContext; + +class VideoOutputOpenGLVAAPI : public VideoOutputOpenGL +{ + public: + static void GetRenderOptions(render_opts &opts); + + VideoOutputOpenGLVAAPI(); + ~VideoOutputOpenGLVAAPI(); + + bool Init(int width, int height, float aspect, WId winid, + const QRect &win_rect, MythCodecID codec_id); + bool CreateVAAPIContext(QSize size); + void DeleteVAAPIContext(void); + bool CreateBuffers(void); + virtual void* GetDecoderContext(unsigned char* buf, uint8_t*& id); + uint8_t* GetSurfaceIDPointer(void* buf); + void SetProfile(void); + void TearDown(void); + bool InputChanged(const QSize &input_size, float aspect, + MythCodecID av_codec_id, void *codec_private, + bool &aspect_only); + void ProcessFrame(VideoFrame *frame, OSD *osd, + FilterChain *filterList, + const PIPMap &pipPlayers, + FrameScanType scan); + bool ApproveDeintFilter(const QString& filtername) const; + bool SetDeinterlacingEnabled(bool enable); + bool SetupDeinterlace(bool i, const QString& ovrf=""); + void InitPictureAttributes(void) { } + + static QStringList GetAllowedRenderers(MythCodecID myth_codec_id, + const QSize &video_dim); + static MythCodecID GetBestSupportedCodec(uint width, uint height, + uint stream_type, + bool no_acceleration, + PixelFormat &pix_fmt); + + private: + VAAPIContext *m_ctx; +}; +#endif // VIDEOOUTPUTOPENGLVAAPI_H + diff --git a/mythtv/libs/libmythtv/videooutbase.cpp b/mythtv/libs/libmythtv/videooutbase.cpp index 8f246271844..162ad926021 100644 --- a/mythtv/libs/libmythtv/videooutbase.cpp +++ b/mythtv/libs/libmythtv/videooutbase.cpp @@ -36,6 +36,10 @@ #include "videoout_vdpau.h" #endif +#ifdef USING_VAAPI +#include "videoout_openglvaapi.h" +#endif + #include "videoout_null.h" #include "dithertable.h" @@ -88,6 +92,10 @@ void VideoOutput::GetRenderOptions(render_opts &opts) #ifdef USING_VDPAU VideoOutputVDPAU::GetRenderOptions(opts); #endif // USING_VDPAU + +#ifdef USING_VAAPI + VideoOutputOpenGLVAAPI::GetRenderOptions(opts); +#endif // USING_VAAPI } /** @@ -128,6 +136,10 @@ VideoOutput *VideoOutput::Create( renderers += VideoOutputVDPAU::GetAllowedRenderers(codec_id, video_dim); #endif // USING_VDPAU +#ifdef USING_VAAPI + renderers += VideoOutputOpenGLVAAPI::GetAllowedRenderers(codec_id, video_dim); +#endif // USING_VAAPI + VERBOSE(VB_PLAYBACK, LOC + "Allowed renderers: " + to_comma_list(renderers)); @@ -185,6 +197,11 @@ VideoOutput *VideoOutput::Create( vo = new VideoOutputVDPAU(); #endif // USING_VDPAU +#ifdef USING_VAAPI + if (renderer == "openglvaapi") + vo = new VideoOutputOpenGLVAAPI(); +#endif // USING_VAAPI + #ifdef USING_XV if (xvlist.contains(renderer)) vo = new VideoOutputXv(); diff --git a/mythtv/libs/libmythtv/videooutbase.h b/mythtv/libs/libmythtv/videooutbase.h index b19e2274041..334d08c67ea 100644 --- a/mythtv/libs/libmythtv/videooutbase.h +++ b/mythtv/libs/libmythtv/videooutbase.h @@ -144,7 +144,7 @@ class VideoOutput /// \brief Return true if HW Acceleration is running virtual bool hasHWAcceleration(void) const { return false; } - virtual void* GetDecoderContext(void) { return NULL; } + virtual void* GetDecoderContext(unsigned char* buf, uint8_t*& id) { return NULL; } /// \brief Sets the number of frames played virtual void SetFramesPlayed(long long fp) { framesPlayed = fp; };