Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| /* | |
| * Copyright (C) 2005-2014 Team XBMC | |
| * http://xbmc.org | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library 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 | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with XBMC; see the file COPYING. If not, see | |
| * <http://www.gnu.org/licenses/>. | |
| * | |
| */ | |
| #include "system.h" | |
| #ifdef HAVE_LIBVA | |
| #include "VAAPI.h" | |
| #include "windowing/WindowingFactory.h" | |
| #include "DVDVideoCodec.h" | |
| #include "cores/VideoPlayer/DVDCodecs/DVDCodecUtils.h" | |
| #include "cores/VideoPlayer/DVDClock.h" | |
| #include "cores/VideoPlayer/Process/ProcessInfo.h" | |
| #include "utils/log.h" | |
| #include "utils/StringUtils.h" | |
| #include "threads/SingleLock.h" | |
| #include "settings/Settings.h" | |
| #include "guilib/GraphicContext.h" | |
| #include "settings/MediaSettings.h" | |
| #include "settings/AdvancedSettings.h" | |
| #include <va/va_drm.h> | |
| #include <va/va_drmcommon.h> | |
| #include <drm_fourcc.h> | |
| #include "linux/XTimeUtils.h" | |
| #include "linux/XMemUtils.h" | |
| extern "C" { | |
| #include "libavutil/avutil.h" | |
| #include "libavutil/opt.h" | |
| #include "libavfilter/buffersink.h" | |
| #include "libavfilter/buffersrc.h" | |
| } | |
| #include <va/va_vpp.h> | |
| #include <xf86drm.h> | |
| using namespace VAAPI; | |
| #define NUM_RENDER_PICS 7 | |
| //----------------------------------------------------------------------------- | |
| //----------------------------------------------------------------------------- | |
| CVAAPIContext *CVAAPIContext::m_context = 0; | |
| CCriticalSection CVAAPIContext::m_section; | |
| CVAAPIContext::CVAAPIContext() | |
| { | |
| m_context = 0; | |
| m_refCount = 0; | |
| m_attributes = NULL; | |
| m_profiles = NULL; | |
| m_display = NULL; | |
| } | |
| void CVAAPIContext::Release(CDecoder *decoder) | |
| { | |
| CSingleLock lock(m_section); | |
| auto it = find(m_decoders.begin(), m_decoders.end(), decoder); | |
| if (it != m_decoders.end()) | |
| m_decoders.erase(it); | |
| m_refCount--; | |
| if (m_refCount <= 0) | |
| { | |
| Close(); | |
| delete this; | |
| m_context = 0; | |
| } | |
| } | |
| void CVAAPIContext::Close() | |
| { | |
| CLog::Log(LOGNOTICE, "VAAPI::Close - closing decoder context"); | |
| if (m_fd >= 0) | |
| { | |
| close(m_fd); | |
| } | |
| DestroyContext(); | |
| } | |
| bool CVAAPIContext::EnsureContext(CVAAPIContext **ctx, CDecoder *decoder) | |
| { | |
| CSingleLock lock(m_section); | |
| if (m_context) | |
| { | |
| m_context->m_refCount++; | |
| *ctx = m_context; | |
| if (!m_context->IsValidDecoder(decoder)) | |
| m_context->m_decoders.push_back(decoder); | |
| return true; | |
| } | |
| m_context = new CVAAPIContext(); | |
| *ctx = m_context; | |
| { | |
| CSingleLock gLock(g_graphicsContext); | |
| if (!m_context->CreateContext()) | |
| { | |
| delete m_context; | |
| m_context = 0; | |
| *ctx = NULL; | |
| return false; | |
| } | |
| } | |
| m_context->m_refCount++; | |
| if (!m_context->IsValidDecoder(decoder)) | |
| m_context->m_decoders.push_back(decoder); | |
| *ctx = m_context; | |
| return true; | |
| } | |
| namespace | |
| { | |
| int find_open_render_node_fd() | |
| { | |
| int const buf_size{128}; | |
| char name[buf_size]; | |
| int fd{-1}; | |
| for (int i = 128; i < (128 + 16); i++) | |
| { | |
| snprintf(name, buf_size, "/dev/dri/renderD%u", i); | |
| fd = open(name, O_RDWR); | |
| if (fd < 0) | |
| { | |
| continue; | |
| } | |
| return fd; | |
| } | |
| return -1; | |
| } | |
| } | |
| bool CVAAPIContext::CreateContext() | |
| { | |
| // Render nodes depends on kernel >= 3.15 | |
| m_fd = find_open_render_node_fd(); | |
| if (m_fd < 0) | |
| { | |
| CLog::Log(LOGERROR, "FAILED To find any open render nodes in /dev/dri/renderD<num>\n"); | |
| m_display = NULL; | |
| return false; | |
| } | |
| m_display = vaGetDisplayDRM(m_fd); | |
| int major_version, minor_version; | |
| if (!CheckSuccess(vaInitialize(m_display, &major_version, &minor_version))) | |
| { | |
| m_display = NULL; | |
| return false; | |
| } | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "VAAPI - initialize version %d.%d", major_version, minor_version); | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "VAAPI - driver in use: %s", vaQueryVendorString(m_display)); | |
| QueryCaps(); | |
| if (!m_profileCount) | |
| return false; | |
| if (!m_attributeCount) | |
| CLog::Log(LOGWARNING, "VAAPI - driver did not return anything from vlVaQueryDisplayAttributes"); | |
| return true; | |
| } | |
| void CVAAPIContext::DestroyContext() | |
| { | |
| delete[] m_attributes; | |
| delete[] m_profiles; | |
| if (m_display) | |
| CheckSuccess(vaTerminate(m_display)); | |
| } | |
| void CVAAPIContext::QueryCaps() | |
| { | |
| m_attributeCount = 0; | |
| m_profileCount = 0; | |
| int max_attributes = vaMaxNumDisplayAttributes(m_display); | |
| m_attributes = new VADisplayAttribute[max_attributes]; | |
| if (!CheckSuccess(vaQueryDisplayAttributes(m_display, m_attributes, &m_attributeCount))) | |
| return; | |
| for(int i = 0; i < m_attributeCount; i++) | |
| { | |
| VADisplayAttribute * const display_attr = &m_attributes[i]; | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| { | |
| CLog::Log(LOGDEBUG, "VAAPI - attrib %d (%s/%s) min %d max %d value 0x%x\n" | |
| , display_attr->type | |
| ,(display_attr->flags & VA_DISPLAY_ATTRIB_GETTABLE) ? "get" : "---" | |
| ,(display_attr->flags & VA_DISPLAY_ATTRIB_SETTABLE) ? "set" : "---" | |
| , display_attr->min_value | |
| , display_attr->max_value | |
| , display_attr->value); | |
| } | |
| } | |
| int max_profiles = vaMaxNumProfiles(m_display); | |
| m_profiles = new VAProfile[max_profiles]; | |
| if (!CheckSuccess(vaQueryConfigProfiles(m_display, m_profiles, &m_profileCount))) | |
| return; | |
| for(int i = 0; i < m_profileCount; i++) | |
| { | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "VAAPI - profile %d", m_profiles[i]); | |
| } | |
| } | |
| VAConfigAttrib CVAAPIContext::GetAttrib(VAProfile profile) | |
| { | |
| CSingleLock lock(m_section); | |
| VAConfigAttrib attrib; | |
| attrib.type = VAConfigAttribRTFormat; | |
| CheckSuccess(vaGetConfigAttributes(m_display, profile, VAEntrypointVLD, &attrib, 1)); | |
| return attrib; | |
| } | |
| bool CVAAPIContext::SupportsProfile(VAProfile profile) | |
| { | |
| CSingleLock lock(m_section); | |
| for (int i=0; i<m_profileCount; i++) | |
| { | |
| if (m_profiles[i] == profile) | |
| return true; | |
| } | |
| return false; | |
| } | |
| VAConfigID CVAAPIContext::CreateConfig(VAProfile profile, VAConfigAttrib attrib) | |
| { | |
| CSingleLock lock(m_section); | |
| VAConfigID config = VA_INVALID_ID; | |
| CheckSuccess(vaCreateConfig(m_display, profile, VAEntrypointVLD, &attrib, 1, &config)); | |
| return config; | |
| } | |
| bool CVAAPIContext::CheckSuccess(VAStatus status) | |
| { | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s error: %s", __FUNCTION__, vaErrorStr(status)); | |
| return false; | |
| } | |
| return true; | |
| } | |
| VADisplay CVAAPIContext::GetDisplay() | |
| { | |
| return m_display; | |
| } | |
| bool CVAAPIContext::IsValidDecoder(CDecoder *decoder) | |
| { | |
| auto it = find(m_decoders.begin(), m_decoders.end(), decoder); | |
| if (it != m_decoders.end()) | |
| return true; | |
| return false; | |
| } | |
| void CVAAPIContext::FFReleaseBuffer(void *opaque, uint8_t *data) | |
| { | |
| CDecoder *va = (CDecoder*)opaque; | |
| if (m_context && m_context->IsValidDecoder(va)) | |
| { | |
| va->FFReleaseBuffer(data); | |
| } | |
| } | |
| //----------------------------------------------------------------------------- | |
| // VAAPI Video Surface states | |
| //----------------------------------------------------------------------------- | |
| #define SURFACE_USED_FOR_REFERENCE 0x01 | |
| #define SURFACE_USED_FOR_RENDER 0x02 | |
| void CVideoSurfaces::AddSurface(VASurfaceID surf) | |
| { | |
| CSingleLock lock(m_section); | |
| m_state[surf] = 0; | |
| m_freeSurfaces.push_back(surf); | |
| } | |
| void CVideoSurfaces::ClearReference(VASurfaceID surf) | |
| { | |
| CSingleLock lock(m_section); | |
| if (m_state.find(surf) == m_state.end()) | |
| { | |
| CLog::Log(LOGWARNING, "CVideoSurfaces::ClearReference - surface invalid"); | |
| return; | |
| } | |
| m_state[surf] &= ~SURFACE_USED_FOR_REFERENCE; | |
| if (m_state[surf] == 0) | |
| { | |
| m_freeSurfaces.push_back(surf); | |
| } | |
| } | |
| bool CVideoSurfaces::MarkRender(VASurfaceID surf) | |
| { | |
| CSingleLock lock(m_section); | |
| if (m_state.find(surf) == m_state.end()) | |
| { | |
| CLog::Log(LOGWARNING, "CVideoSurfaces::MarkRender - surface invalid"); | |
| return false; | |
| } | |
| auto it = std::find(m_freeSurfaces.begin(), m_freeSurfaces.end(), surf); | |
| if (it != m_freeSurfaces.end()) | |
| { | |
| m_freeSurfaces.erase(it); | |
| } | |
| m_state[surf] |= SURFACE_USED_FOR_RENDER; | |
| return true; | |
| } | |
| void CVideoSurfaces::ClearRender(VASurfaceID surf) | |
| { | |
| CSingleLock lock(m_section); | |
| if (m_state.find(surf) == m_state.end()) | |
| { | |
| CLog::Log(LOGWARNING, "CVideoSurfaces::ClearRender - surface invalid"); | |
| return; | |
| } | |
| m_state[surf] &= ~SURFACE_USED_FOR_RENDER; | |
| if (m_state[surf] == 0) | |
| { | |
| m_freeSurfaces.push_back(surf); | |
| } | |
| } | |
| bool CVideoSurfaces::IsValid(VASurfaceID surf) | |
| { | |
| CSingleLock lock(m_section); | |
| if (m_state.find(surf) != m_state.end()) | |
| return true; | |
| else | |
| return false; | |
| } | |
| VASurfaceID CVideoSurfaces::GetFree(VASurfaceID surf) | |
| { | |
| CSingleLock lock(m_section); | |
| if (m_state.find(surf) != m_state.end()) | |
| { | |
| auto it = std::find(m_freeSurfaces.begin(), m_freeSurfaces.end(), surf); | |
| if (it == m_freeSurfaces.end()) | |
| { | |
| CLog::Log(LOGWARNING, "CVideoSurfaces::GetFree - surface not free"); | |
| } | |
| else | |
| { | |
| m_freeSurfaces.erase(it); | |
| m_state[surf] = SURFACE_USED_FOR_REFERENCE; | |
| return surf; | |
| } | |
| } | |
| if (!m_freeSurfaces.empty()) | |
| { | |
| VASurfaceID freeSurf = m_freeSurfaces.front(); | |
| m_freeSurfaces.pop_front(); | |
| m_state[freeSurf] = SURFACE_USED_FOR_REFERENCE; | |
| return freeSurf; | |
| } | |
| return VA_INVALID_SURFACE; | |
| } | |
| VASurfaceID CVideoSurfaces::GetAtIndex(int idx) | |
| { | |
| if ((size_t) idx >= m_state.size()) | |
| return VA_INVALID_SURFACE; | |
| auto it = m_state.begin(); | |
| for(int i = 0; i < idx; i++) | |
| ++it; | |
| return it->first; | |
| } | |
| VASurfaceID CVideoSurfaces::RemoveNext(bool skiprender) | |
| { | |
| CSingleLock lock(m_section); | |
| VASurfaceID surf; | |
| for(auto it = m_state.begin(); it != m_state.end(); ++it) | |
| { | |
| if (skiprender && it->second & SURFACE_USED_FOR_RENDER) | |
| continue; | |
| surf = it->first; | |
| m_state.erase(surf); | |
| auto it2 = std::find(m_freeSurfaces.begin(), m_freeSurfaces.end(), surf); | |
| if (it2 != m_freeSurfaces.end()) | |
| m_freeSurfaces.erase(it2); | |
| return surf; | |
| } | |
| return VA_INVALID_SURFACE; | |
| } | |
| void CVideoSurfaces::Reset() | |
| { | |
| CSingleLock lock(m_section); | |
| m_freeSurfaces.clear(); | |
| m_state.clear(); | |
| } | |
| int CVideoSurfaces::Size() | |
| { | |
| CSingleLock lock(m_section); | |
| return m_state.size(); | |
| } | |
| bool CVideoSurfaces::HasFree() | |
| { | |
| CSingleLock lock(m_section); | |
| return !m_freeSurfaces.empty(); | |
| } | |
| int CVideoSurfaces::NumFree() | |
| { | |
| CSingleLock lock(m_section); | |
| return m_freeSurfaces.size(); | |
| } | |
| bool CVideoSurfaces::HasRefs() | |
| { | |
| CSingleLock lock(m_section); | |
| for (const auto &i : m_state) | |
| { | |
| if (i.second & SURFACE_USED_FOR_REFERENCE) | |
| return true; | |
| } | |
| return false; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // VAAPI | |
| //----------------------------------------------------------------------------- | |
| CDecoder::CDecoder(CProcessInfo& processInfo) : | |
| m_vaapiOutput(&m_inMsgEvent), | |
| m_processInfo(processInfo) | |
| { | |
| m_vaapiConfig.videoSurfaces = &m_videoSurfaces; | |
| m_vaapiConfigured = false; | |
| m_DisplayState = VAAPI_OPEN; | |
| m_vaapiConfig.context = 0; | |
| m_vaapiConfig.contextId = VA_INVALID_ID; | |
| m_vaapiConfig.configId = VA_INVALID_ID; | |
| m_vaapiConfig.processInfo = &m_processInfo; | |
| m_avctx = NULL; | |
| m_getBufferError = 0; | |
| } | |
| CDecoder::~CDecoder() | |
| { | |
| Close(); | |
| } | |
| bool CDecoder::Open(AVCodecContext* avctx, AVCodecContext* mainctx, const enum AVPixelFormat fmt, unsigned int surfaces) | |
| { | |
| // don't support broken wrappers by default | |
| // nvidia cards with a vaapi to vdpau wrapper | |
| // fglrx cards with xvba-va-driver | |
| std::string gpuvendor = g_Windowing.GetRenderVendor(); | |
| std::transform(gpuvendor.begin(), gpuvendor.end(), gpuvendor.begin(), ::tolower); | |
| if (gpuvendor.compare(0, 5, "intel") != 0) | |
| { | |
| // user might force VAAPI enabled, cause he might know better | |
| if (g_advancedSettings.m_videoVAAPIforced) | |
| { | |
| CLog::Log(LOGWARNING, "VAAPI was not tested on your hardware / driver stack: %s. If it will crash and burn complain with your gpu vendor.", gpuvendor.c_str()); | |
| } | |
| else | |
| { | |
| return false; | |
| } | |
| } | |
| // check if user wants to decode this format with VAAPI | |
| std::map<AVCodecID, std::string> settings_map = { | |
| { AV_CODEC_ID_H263, CSettings::SETTING_VIDEOPLAYER_USEVAAPIMPEG4 }, | |
| { AV_CODEC_ID_MPEG4, CSettings::SETTING_VIDEOPLAYER_USEVAAPIMPEG4 }, | |
| { AV_CODEC_ID_WMV3, CSettings::SETTING_VIDEOPLAYER_USEVAAPIVC1 }, | |
| { AV_CODEC_ID_VC1, CSettings::SETTING_VIDEOPLAYER_USEVAAPIVC1 }, | |
| { AV_CODEC_ID_MPEG2VIDEO, CSettings::SETTING_VIDEOPLAYER_USEVAAPIMPEG2 }, | |
| }; | |
| if (CDVDVideoCodec::IsCodecDisabled(settings_map, avctx->codec_id)) | |
| return false; | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG,"VAAPI - open decoder"); | |
| if (!CVAAPIContext::EnsureContext(&m_vaapiConfig.context, this)) | |
| return false; | |
| if(avctx->coded_width == 0 | |
| || avctx->coded_height == 0) | |
| { | |
| CLog::Log(LOGWARNING,"VAAPI::Open: no width/height available, can't init"); | |
| return false; | |
| } | |
| m_vaapiConfig.vidWidth = avctx->width; | |
| m_vaapiConfig.vidHeight = avctx->height; | |
| m_vaapiConfig.outWidth = avctx->width; | |
| m_vaapiConfig.outHeight = avctx->height; | |
| m_vaapiConfig.surfaceWidth = avctx->coded_width; | |
| m_vaapiConfig.surfaceHeight = avctx->coded_height; | |
| m_vaapiConfig.aspect = avctx->sample_aspect_ratio; | |
| m_decoderThread = CThread::GetCurrentThreadId(); | |
| m_DisplayState = VAAPI_OPEN; | |
| m_vaapiConfigured = false; | |
| m_presentPicture = 0; | |
| m_getBufferError = 0; | |
| VAProfile profile; | |
| switch (avctx->codec_id) | |
| { | |
| case AV_CODEC_ID_MPEG2VIDEO: | |
| profile = VAProfileMPEG2Main; | |
| if (!m_vaapiConfig.context->SupportsProfile(profile)) | |
| return false; | |
| break; | |
| case AV_CODEC_ID_MPEG4: | |
| case AV_CODEC_ID_H263: | |
| profile = VAProfileMPEG4AdvancedSimple; | |
| if (!m_vaapiConfig.context->SupportsProfile(profile)) | |
| return false; | |
| break; | |
| case AV_CODEC_ID_H264: | |
| { | |
| if (avctx->profile == FF_PROFILE_H264_BASELINE) | |
| { | |
| profile = VAProfileH264Baseline; | |
| if (!m_vaapiConfig.context->SupportsProfile(profile)) | |
| return false; | |
| } | |
| else | |
| { | |
| if(avctx->profile == FF_PROFILE_H264_MAIN) | |
| { | |
| profile = VAProfileH264Main; | |
| if (m_vaapiConfig.context->SupportsProfile(profile)) | |
| break; | |
| } | |
| profile = VAProfileH264High; | |
| if (!m_vaapiConfig.context->SupportsProfile(profile)) | |
| return false; | |
| } | |
| break; | |
| } | |
| case AV_CODEC_ID_HEVC: | |
| { | |
| profile = VAProfileHEVCMain; | |
| if (!m_vaapiConfig.context->SupportsProfile(profile)) | |
| return false; | |
| break; | |
| } | |
| #if VA_CHECK_VERSION(0,38,1) | |
| case AV_CODEC_ID_VP9: | |
| { | |
| profile = VAProfileVP9Profile0; | |
| if (!m_vaapiConfig.context->SupportsProfile(profile)) | |
| return false; | |
| break; | |
| } | |
| #endif | |
| case AV_CODEC_ID_WMV3: | |
| profile = VAProfileVC1Main; | |
| if (!m_vaapiConfig.context->SupportsProfile(profile)) | |
| return false; | |
| break; | |
| case AV_CODEC_ID_VC1: | |
| profile = VAProfileVC1Advanced; | |
| if (!m_vaapiConfig.context->SupportsProfile(profile)) | |
| return false; | |
| break; | |
| default: | |
| return false; | |
| } | |
| m_vaapiConfig.profile = profile; | |
| m_vaapiConfig.attrib = m_vaapiConfig.context->GetAttrib(profile); | |
| if ((m_vaapiConfig.attrib.value & VA_RT_FORMAT_YUV420) == 0) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI - invalid yuv format %x", m_vaapiConfig.attrib.value); | |
| return false; | |
| } | |
| if (avctx->codec_id == AV_CODEC_ID_H264) | |
| { | |
| m_vaapiConfig.maxReferences = avctx->refs; | |
| if (m_vaapiConfig.maxReferences > 16) | |
| m_vaapiConfig.maxReferences = 16; | |
| if (m_vaapiConfig.maxReferences < 5) | |
| m_vaapiConfig.maxReferences = 5; | |
| } | |
| else if (avctx->codec_id == AV_CODEC_ID_HEVC) | |
| m_vaapiConfig.maxReferences = 16; | |
| else if (avctx->codec_id == AV_CODEC_ID_VP9) | |
| m_vaapiConfig.maxReferences = 8; | |
| else | |
| m_vaapiConfig.maxReferences = 2; | |
| // add an extra surface for safety, some faulty material | |
| // make ffmpeg require more buffers | |
| m_vaapiConfig.maxReferences += surfaces + 1; | |
| if (!ConfigVAAPI()) | |
| { | |
| return false; | |
| } | |
| avctx->hwaccel_context = &m_hwContext; | |
| avctx->get_buffer2 = CDecoder::FFGetBuffer; | |
| avctx->slice_flags = SLICE_FLAG_CODED_ORDER|SLICE_FLAG_ALLOW_FIELD; | |
| mainctx->hwaccel_context = &m_hwContext; | |
| mainctx->get_buffer2 = CDecoder::FFGetBuffer; | |
| mainctx->slice_flags = SLICE_FLAG_CODED_ORDER|SLICE_FLAG_ALLOW_FIELD; | |
| m_avctx = mainctx; | |
| return true; | |
| } | |
| void CDecoder::Close() | |
| { | |
| CLog::Log(LOGNOTICE, "VAAPI::%s", __FUNCTION__); | |
| CSingleLock lock(m_DecoderSection); | |
| FiniVAAPIOutput(); | |
| if (m_vaapiConfig.context) | |
| m_vaapiConfig.context->Release(this); | |
| m_vaapiConfig.context = 0; | |
| } | |
| long CDecoder::Release() | |
| { | |
| // if ffmpeg holds any references, flush buffers | |
| if (m_avctx && m_videoSurfaces.HasRefs()) | |
| { | |
| avcodec_flush_buffers(m_avctx); | |
| } | |
| // check if we should do some pre-cleanup here | |
| // a second decoder might need resources | |
| if (m_vaapiConfigured == true) | |
| { | |
| CSingleLock lock(m_DecoderSection); | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG,"VAAPI::Release pre-cleanup"); | |
| CSingleLock lock1(g_graphicsContext); | |
| Message *reply; | |
| if (m_vaapiOutput.m_controlPort.SendOutMessageSync(COutputControlProtocol::PRECLEANUP, | |
| &reply, | |
| 2000)) | |
| { | |
| bool success = reply->signal == COutputControlProtocol::ACC ? true : false; | |
| reply->Release(); | |
| if (!success) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - pre-cleanup returned error", __FUNCTION__); | |
| m_DisplayState = VAAPI_ERROR; | |
| } | |
| } | |
| else | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - pre-cleanup timed out", __FUNCTION__); | |
| m_DisplayState = VAAPI_ERROR; | |
| } | |
| VASurfaceID surf; | |
| while((surf = m_videoSurfaces.RemoveNext(true)) != VA_INVALID_SURFACE) | |
| { | |
| CheckSuccess(vaDestroySurfaces(m_vaapiConfig.dpy, &surf, 1)); | |
| } | |
| } | |
| return IHardwareDecoder::Release(); | |
| } | |
| long CDecoder::ReleasePicReference() | |
| { | |
| return IHardwareDecoder::Release(); | |
| } | |
| int CDecoder::FFGetBuffer(AVCodecContext *avctx, AVFrame *pic, int flags) | |
| { | |
| CDVDVideoCodecFFmpeg* ctx = (CDVDVideoCodecFFmpeg*)avctx->opaque; | |
| CDecoder* va = (CDecoder*)ctx->GetHardware(); | |
| // while we are waiting to recover we can't do anything | |
| CSingleLock lock(va->m_DecoderSection); | |
| if(va->m_DisplayState != VAAPI_OPEN) | |
| { | |
| CLog::Log(LOGWARNING, "VAAPI::FFGetBuffer - returning due to awaiting recovery"); | |
| return -1; | |
| } | |
| VASurfaceID surf = (VASurfaceID)(uintptr_t)pic->data[3]; | |
| surf = va->m_videoSurfaces.GetFree(surf != 0 ? surf : VA_INVALID_SURFACE); | |
| if (surf == VA_INVALID_SURFACE) | |
| { | |
| uint16_t decoded, processed, render; | |
| bool vpp; | |
| va->m_bufferStats.Get(decoded, processed, render, vpp); | |
| CLog::Log(LOGWARNING, "VAAPI::FFGetBuffer - no surface available - dec: %d, render: %d", | |
| decoded, render); | |
| va->m_getBufferError++; | |
| return -1; | |
| } | |
| va->m_getBufferError = 0; | |
| pic->data[1] = pic->data[2] = NULL; | |
| pic->data[0] = (uint8_t*)(uintptr_t)surf; | |
| pic->data[3] = (uint8_t*)(uintptr_t)surf; | |
| pic->linesize[0] = pic->linesize[1] = pic->linesize[2] = 0; | |
| AVBufferRef *buffer = av_buffer_create(pic->data[3], 0, CVAAPIContext::FFReleaseBuffer, va, 0); | |
| if (!buffer) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - error creating buffer", __FUNCTION__); | |
| return -1; | |
| } | |
| pic->buf[0] = buffer; | |
| pic->reordered_opaque = avctx->reordered_opaque; | |
| va->Acquire(); | |
| return 0; | |
| } | |
| void CDecoder::FFReleaseBuffer(uint8_t *data) | |
| { | |
| { | |
| VASurfaceID surf; | |
| CSingleLock lock(m_DecoderSection); | |
| surf = (VASurfaceID)(uintptr_t)data; | |
| m_videoSurfaces.ClearReference(surf); | |
| } | |
| IHardwareDecoder::Release(); | |
| } | |
| void CDecoder::SetCodecControl(int flags) | |
| { | |
| m_codecControl = flags & (DVD_CODEC_CTRL_DRAIN | DVD_CODEC_CTRL_HURRY); | |
| } | |
| int CDecoder::Decode(AVCodecContext* avctx, AVFrame* pFrame) | |
| { | |
| int result = Check(avctx); | |
| if (result && result != VC_NOBUFFER) | |
| return result; | |
| CSingleLock lock(m_DecoderSection); | |
| if (!m_vaapiConfigured) | |
| return VC_ERROR; | |
| if (pFrame) | |
| { // we have a new frame from decoder | |
| VASurfaceID surf = (VASurfaceID)(uintptr_t)pFrame->data[3]; | |
| // ffmpeg vc-1 decoder does not flush, make sure the data buffer is still valid | |
| if (!m_videoSurfaces.IsValid(surf)) | |
| { | |
| CLog::Log(LOGWARNING, "VAAPI::Decode - ignoring invalid buffer"); | |
| return VC_BUFFER; | |
| } | |
| m_videoSurfaces.MarkRender(surf); | |
| // send frame to output for processing | |
| CVaapiDecodedPicture pic; | |
| memset(&pic.DVDPic, 0, sizeof(pic.DVDPic)); | |
| ((CDVDVideoCodecFFmpeg*)avctx->opaque)->GetPictureCommon(&pic.DVDPic); | |
| pic.videoSurface = surf; | |
| pic.DVDPic.color_matrix = avctx->colorspace; | |
| m_bufferStats.IncDecoded(); | |
| m_vaapiOutput.m_dataPort.SendOutMessage(COutputDataProtocol::NEWFRAME, &pic, sizeof(pic)); | |
| m_codecControl = pic.DVDPic.iFlags & (DVD_CODEC_CTRL_HURRY | DVD_CODEC_CTRL_NO_POSTPROC); | |
| } | |
| int retval = 0; | |
| uint16_t decoded, processed, render; | |
| bool vpp; | |
| Message *msg; | |
| while (m_vaapiOutput.m_controlPort.ReceiveInMessage(&msg)) | |
| { | |
| if (msg->signal == COutputControlProtocol::ERROR) | |
| { | |
| m_DisplayState = VAAPI_ERROR; | |
| retval |= VC_ERROR; | |
| } | |
| msg->Release(); | |
| } | |
| m_bufferStats.Get(decoded, processed, render, vpp); | |
| while (!retval) | |
| { | |
| bool drain = (m_codecControl & DVD_CODEC_CTRL_DRAIN); | |
| // if all pics are drained, break the loop by setting VC_BUFFER | |
| if (drain && decoded <= 0 && processed <= 0 && render <= 0) | |
| drain = false; | |
| // first fill the buffers to keep vaapi busy | |
| if (decoded < 2 && processed < 3 && m_videoSurfaces.HasFree() && !drain) | |
| { | |
| retval |= VC_BUFFER; | |
| } | |
| else if (m_vaapiOutput.m_dataPort.ReceiveInMessage(&msg)) | |
| { | |
| if (msg->signal == COutputDataProtocol::PICTURE) | |
| { | |
| if (m_presentPicture) | |
| { | |
| m_presentPicture->ReturnUnused(); | |
| m_presentPicture = 0; | |
| } | |
| m_presentPicture = *(CVaapiRenderPicture**)msg->data; | |
| m_presentPicture->vaapi = this; | |
| m_bufferStats.DecRender(); | |
| m_bufferStats.Get(decoded, processed, render, vpp); | |
| retval |= VC_PICTURE; | |
| msg->Release(); | |
| break; | |
| } | |
| msg->Release(); | |
| } | |
| else if (m_vaapiOutput.m_controlPort.ReceiveInMessage(&msg)) | |
| { | |
| if (msg->signal == COutputControlProtocol::STATS) | |
| { | |
| m_bufferStats.Get(decoded, processed, render, vpp); | |
| } | |
| else | |
| { | |
| m_DisplayState = VAAPI_ERROR; | |
| retval |= VC_ERROR; | |
| } | |
| msg->Release(); | |
| } | |
| if (decoded < 2 && processed < 3) | |
| { | |
| retval |= VC_BUFFER; | |
| } | |
| if (!retval && !m_inMsgEvent.WaitMSec(2000)) | |
| break; | |
| } | |
| if (retval & VC_PICTURE) | |
| { | |
| m_bufferStats.SetParams(0, m_codecControl); | |
| } | |
| if (!retval) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - timed out waiting for output message - decoded: %d, proc: %d, has free surface: %s", | |
| __FUNCTION__, decoded, processed, m_videoSurfaces.HasFree() ? "yes" : "no"); | |
| m_DisplayState = VAAPI_ERROR; | |
| retval |= VC_ERROR; | |
| } | |
| return retval; | |
| } | |
| int CDecoder::Check(AVCodecContext* avctx) | |
| { | |
| int ret = 0; | |
| EDisplayState state; | |
| { CSingleLock lock(m_DecoderSection); | |
| state = m_DisplayState; | |
| } | |
| if (state == VAAPI_LOST) | |
| { | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG,"VAAPI::Check waiting for display reset event"); | |
| if (!m_DisplayEvent.WaitMSec(4000)) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::Check - device didn't reset in reasonable time"); | |
| state = VAAPI_RESET; | |
| } | |
| else | |
| { | |
| CSingleLock lock(m_DecoderSection); | |
| state = m_DisplayState; | |
| } | |
| } | |
| if (state == VAAPI_RESET || state == VAAPI_ERROR) | |
| { | |
| CSingleLock lock(m_DecoderSection); | |
| avcodec_flush_buffers(avctx); | |
| FiniVAAPIOutput(); | |
| if (m_vaapiConfig.context) | |
| m_vaapiConfig.context->Release(this); | |
| m_vaapiConfig.context = 0; | |
| if (CVAAPIContext::EnsureContext(&m_vaapiConfig.context, this) && ConfigVAAPI()) | |
| { | |
| m_DisplayState = VAAPI_OPEN; | |
| } | |
| if (state == VAAPI_RESET) | |
| return VC_FLUSHED; | |
| else | |
| return VC_ERROR; | |
| } | |
| if (m_getBufferError > 0 && m_getBufferError < 5) | |
| { | |
| // if there is no other error, sleep for a short while | |
| // in order not to drain player's message queue | |
| if (!ret) | |
| Sleep(20); | |
| ret |= VC_NOBUFFER; | |
| } | |
| return ret; | |
| } | |
| bool CDecoder::GetPicture(AVCodecContext* avctx, AVFrame* frame, DVDVideoPicture* picture) | |
| { | |
| CSingleLock lock(m_DecoderSection); | |
| if (m_DisplayState != VAAPI_OPEN) | |
| return false; | |
| *picture = m_presentPicture->DVDPic; | |
| picture->vaapi = m_presentPicture; | |
| return true; | |
| } | |
| void CDecoder::Reset() | |
| { | |
| CSingleLock lock(m_DecoderSection); | |
| if (!m_vaapiConfigured) | |
| return; | |
| Message *reply; | |
| if (m_vaapiOutput.m_controlPort.SendOutMessageSync(COutputControlProtocol::FLUSH, | |
| &reply, | |
| 2000)) | |
| { | |
| bool success = reply->signal == COutputControlProtocol::ACC ? true : false; | |
| reply->Release(); | |
| if (!success) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - flush returned error", __FUNCTION__); | |
| m_DisplayState = VAAPI_ERROR; | |
| } | |
| else | |
| { | |
| m_bufferStats.Reset(); | |
| m_getBufferError = 0; | |
| } | |
| } | |
| else | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - flush timed out", __FUNCTION__); | |
| m_DisplayState = VAAPI_ERROR; | |
| } | |
| } | |
| bool CDecoder::CanSkipDeint() | |
| { | |
| return m_bufferStats.CanSkipDeint(); | |
| } | |
| bool CDecoder::CheckSuccess(VAStatus status) | |
| { | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - error: %s", __FUNCTION__, vaErrorStr(status)); | |
| m_ErrorCount++; | |
| if(m_DisplayState == VAAPI_OPEN) | |
| { | |
| if (m_ErrorCount > 2) | |
| m_DisplayState = VAAPI_ERROR; | |
| } | |
| return false; | |
| } | |
| m_ErrorCount = 0; | |
| return true; | |
| } | |
| bool CDecoder::ConfigVAAPI() | |
| { | |
| memset(&m_hwContext, 0, sizeof(vaapi_context)); | |
| m_vaapiConfig.dpy = m_vaapiConfig.context->GetDisplay(); | |
| m_vaapiConfig.attrib = m_vaapiConfig.context->GetAttrib(m_vaapiConfig.profile); | |
| if ((m_vaapiConfig.attrib.value & VA_RT_FORMAT_YUV420) == 0) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI - invalid yuv format %x", m_vaapiConfig.attrib.value); | |
| return false; | |
| } | |
| m_vaapiConfig.configId = m_vaapiConfig.context->CreateConfig(m_vaapiConfig.profile, | |
| m_vaapiConfig.attrib); | |
| if (m_vaapiConfig.configId == VA_INVALID_ID) | |
| return false; | |
| // create surfaces | |
| VASurfaceID surfaces[32]; | |
| int nb_surfaces = m_vaapiConfig.maxReferences; | |
| if (!CheckSuccess(vaCreateSurfaces(m_vaapiConfig.dpy, | |
| VA_RT_FORMAT_YUV420, | |
| m_vaapiConfig.surfaceWidth, | |
| m_vaapiConfig.surfaceHeight, | |
| surfaces, | |
| nb_surfaces, | |
| NULL, 0))) | |
| { | |
| return false; | |
| } | |
| for (int i=0; i<nb_surfaces; i++) | |
| { | |
| m_videoSurfaces.AddSurface(surfaces[i]); | |
| } | |
| // create vaapi decoder context | |
| if (!CheckSuccess(vaCreateContext(m_vaapiConfig.dpy, | |
| m_vaapiConfig.configId, | |
| m_vaapiConfig.surfaceWidth, | |
| m_vaapiConfig.surfaceHeight, | |
| VA_PROGRESSIVE, | |
| surfaces, | |
| nb_surfaces, | |
| &m_vaapiConfig.contextId))) | |
| { | |
| m_vaapiConfig.contextId = VA_INVALID_ID; | |
| return false; | |
| } | |
| // initialize output | |
| CSingleLock lock(g_graphicsContext); | |
| m_vaapiConfig.stats = &m_bufferStats; | |
| m_vaapiConfig.vaapi = this; | |
| m_bufferStats.Reset(); | |
| m_vaapiOutput.Start(); | |
| Message *reply; | |
| if (m_vaapiOutput.m_controlPort.SendOutMessageSync(COutputControlProtocol::INIT, | |
| &reply, | |
| 2000, | |
| &m_vaapiConfig, | |
| sizeof(m_vaapiConfig))) | |
| { | |
| bool success = reply->signal == COutputControlProtocol::ACC ? true : false; | |
| if (!success) | |
| { | |
| reply->Release(); | |
| CLog::Log(LOGERROR, "VAAPI::%s - vaapi output returned error", __FUNCTION__); | |
| m_vaapiOutput.Dispose(); | |
| return false; | |
| } | |
| reply->Release(); | |
| } | |
| else | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - failed to init output", __FUNCTION__); | |
| m_vaapiOutput.Dispose(); | |
| return false; | |
| } | |
| m_hwContext.config_id = m_vaapiConfig.configId; | |
| m_hwContext.context_id = m_vaapiConfig.contextId; | |
| m_hwContext.display = m_vaapiConfig.dpy; | |
| m_inMsgEvent.Reset(); | |
| m_vaapiConfigured = true; | |
| m_ErrorCount = 0; | |
| return true; | |
| } | |
| void CDecoder::FiniVAAPIOutput() | |
| { | |
| if (!m_vaapiConfigured) | |
| return; | |
| memset(&m_hwContext, 0, sizeof(vaapi_context)); | |
| // uninit output | |
| m_vaapiOutput.Dispose(); | |
| m_vaapiConfigured = false; | |
| // destroy decoder context | |
| if (m_vaapiConfig.contextId != VA_INVALID_ID) | |
| CheckSuccess(vaDestroyContext(m_vaapiConfig.dpy, m_vaapiConfig.contextId)); | |
| m_vaapiConfig.contextId = VA_INVALID_ID; | |
| // detroy surfaces | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "VAAPI::FiniVAAPIOutput destroying %d video surfaces", m_videoSurfaces.Size()); | |
| VASurfaceID surf; | |
| while((surf = m_videoSurfaces.RemoveNext()) != VA_INVALID_SURFACE) | |
| { | |
| CheckSuccess(vaDestroySurfaces(m_vaapiConfig.dpy, &surf, 1)); | |
| } | |
| m_videoSurfaces.Reset(); | |
| // destroy vaapi config | |
| if (m_vaapiConfig.configId != VA_INVALID_ID) | |
| CheckSuccess(vaDestroyConfig(m_vaapiConfig.dpy, m_vaapiConfig.configId)); | |
| m_vaapiConfig.configId = VA_INVALID_ID; | |
| } | |
| void CDecoder::ReturnRenderPicture(CVaapiRenderPicture *renderPic) | |
| { | |
| m_vaapiOutput.m_dataPort.SendOutMessage(COutputDataProtocol::RETURNPIC, &renderPic, sizeof(renderPic)); | |
| } | |
| void CDecoder::ReturnProcPicture(int id) | |
| { | |
| m_vaapiOutput.m_dataPort.SendOutMessage(COutputDataProtocol::RETURNPROCPIC, &id, sizeof(int)); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // RenderPicture | |
| //----------------------------------------------------------------------------- | |
| CVaapiRenderPicture* CVaapiRenderPicture::Acquire() | |
| { | |
| CSingleLock lock(renderPicSection); | |
| if (refCount == 0) | |
| vaapi->Acquire(); | |
| refCount++; | |
| return this; | |
| } | |
| long CVaapiRenderPicture::Release() | |
| { | |
| CSingleLock lock(renderPicSection); | |
| refCount--; | |
| if (refCount > 0) | |
| return refCount; | |
| lock.Leave(); | |
| vaapi->ReturnRenderPicture(this); | |
| vaapi->ReleasePicReference(); | |
| return 0; | |
| } | |
| void CVaapiRenderPicture::ReturnUnused() | |
| { | |
| { CSingleLock lock(renderPicSection); | |
| if (refCount > 0) | |
| return; | |
| } | |
| if (vaapi) | |
| vaapi->ReturnRenderPicture(this); | |
| } | |
| void CVaapiRenderPicture::Sync() | |
| { | |
| #ifdef GL_ARB_sync | |
| CSingleLock lock(renderPicSection); | |
| if (usefence) | |
| { | |
| if(glIsSync(fence)) | |
| { | |
| glDeleteSync(fence); | |
| fence = None; | |
| } | |
| fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | |
| } | |
| #endif | |
| } | |
| bool CVaapiRenderPicture::GLMapSurface() | |
| { | |
| VAStatus status; | |
| glInterop.vaImage.image_id = VA_INVALID_ID; | |
| vaSyncSurface(glInterop.vadsp, glInterop.procPic.videoSurface); | |
| status = vaDeriveImage(glInterop.vadsp, glInterop.procPic.videoSurface, | |
| &glInterop.vaImage); | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - Error: %s(%d)", __FUNCTION__, vaErrorStr(status), status); | |
| return false; | |
| } | |
| memset(&glInterop.vBufInfo, 0, sizeof(glInterop.vBufInfo)); | |
| glInterop.vBufInfo.mem_type = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME; | |
| status = vaAcquireBufferHandle(glInterop.vadsp, glInterop.vaImage.buf, | |
| &glInterop.vBufInfo); | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - Error: %s(%d)", __FUNCTION__, vaErrorStr(status), status); | |
| return false; | |
| } | |
| texWidth = glInterop.vaImage.width; | |
| texHeight = glInterop.vaImage.height; | |
| GLint attribs[23], *attrib; | |
| switch (glInterop.vaImage.format.fourcc) | |
| { | |
| case VA_FOURCC('N','V','1','2'): | |
| { | |
| attrib = attribs; | |
| *attrib++ = EGL_LINUX_DRM_FOURCC_EXT; | |
| *attrib++ = fourcc_code('R', '8', ' ', ' '); | |
| *attrib++ = EGL_WIDTH; | |
| *attrib++ = glInterop.vaImage.width; | |
| *attrib++ = EGL_HEIGHT; | |
| *attrib++ = glInterop.vaImage.height; | |
| *attrib++ = EGL_DMA_BUF_PLANE0_FD_EXT; | |
| *attrib++ = (intptr_t)glInterop.vBufInfo.handle; | |
| *attrib++ = EGL_DMA_BUF_PLANE0_OFFSET_EXT; | |
| *attrib++ = glInterop.vaImage.offsets[0]; | |
| *attrib++ = EGL_DMA_BUF_PLANE0_PITCH_EXT; | |
| *attrib++ = glInterop.vaImage.pitches[0]; | |
| *attrib++ = EGL_NONE; | |
| glInterop.eglImageY = glInterop.eglCreateImageKHR(glInterop.eglDisplay, | |
| EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL, | |
| attribs); | |
| if (!glInterop.eglImageY) | |
| { | |
| EGLint err = eglGetError(); | |
| CLog::Log(LOGERROR, "failed to import VA buffer NV12 into EGL image: %d", err); | |
| return false; | |
| } | |
| attrib = attribs; | |
| *attrib++ = EGL_LINUX_DRM_FOURCC_EXT; | |
| *attrib++ = fourcc_code('G', 'R', '8', '8'); | |
| *attrib++ = EGL_WIDTH; | |
| *attrib++ = (glInterop.vaImage.width + 1) >> 1; | |
| *attrib++ = EGL_HEIGHT; | |
| *attrib++ = (glInterop.vaImage.height + 1) >> 1; | |
| *attrib++ = EGL_DMA_BUF_PLANE0_FD_EXT; | |
| *attrib++ = (intptr_t)glInterop.vBufInfo.handle; | |
| *attrib++ = EGL_DMA_BUF_PLANE0_OFFSET_EXT; | |
| *attrib++ = glInterop.vaImage.offsets[1]; | |
| *attrib++ = EGL_DMA_BUF_PLANE0_PITCH_EXT; | |
| *attrib++ = glInterop.vaImage.pitches[1]; | |
| *attrib++ = EGL_NONE; | |
| glInterop.eglImageVU = glInterop.eglCreateImageKHR(glInterop.eglDisplay, | |
| EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL, | |
| attribs); | |
| if (!glInterop.eglImageVU) | |
| { | |
| EGLint err = eglGetError(); | |
| CLog::Log(LOGERROR, "failed to import VA buffer NV12 into EGL image: %d", err); | |
| return false; | |
| } | |
| GLint format; | |
| glGenTextures(1, &textureY); | |
| glEnable(glInterop.textureTarget); | |
| glBindTexture(glInterop.textureTarget, textureY); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| glInterop.glEGLImageTargetTexture2DOES(glInterop.textureTarget, glInterop.eglImageY); | |
| glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &format); | |
| glGenTextures(1, &textureVU); | |
| glEnable(glInterop.textureTarget); | |
| glBindTexture(glInterop.textureTarget, textureVU); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| glInterop.glEGLImageTargetTexture2DOES(glInterop.textureTarget, glInterop.eglImageVU); | |
| glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &format); | |
| glBindTexture(glInterop.textureTarget, 0); | |
| glDisable(glInterop.textureTarget); | |
| break; | |
| } | |
| case VA_FOURCC('B','G','R','A'): | |
| { | |
| attrib = attribs; | |
| *attrib++ = EGL_DRM_BUFFER_FORMAT_MESA; | |
| *attrib++ = EGL_DRM_BUFFER_FORMAT_ARGB32_MESA; | |
| *attrib++ = EGL_WIDTH; | |
| *attrib++ = glInterop.vaImage.width; | |
| *attrib++ = EGL_HEIGHT; | |
| *attrib++ = glInterop.vaImage.height; | |
| *attrib++ = EGL_DRM_BUFFER_STRIDE_MESA; | |
| *attrib++ = glInterop.vaImage.pitches[0] / 4; | |
| *attrib++ = EGL_NONE; | |
| glInterop.eglImage = glInterop.eglCreateImageKHR(glInterop.eglDisplay, EGL_NO_CONTEXT, | |
| EGL_DRM_BUFFER_MESA, | |
| (EGLClientBuffer)glInterop.vBufInfo.handle, | |
| attribs); | |
| if (!glInterop.eglImage) | |
| { | |
| EGLint err = eglGetError(); | |
| CLog::Log(LOGERROR, "failed to import VA buffer BGRA into EGL image: %d", err); | |
| return false; | |
| } | |
| glGenTextures(1, &texture); | |
| glEnable(glInterop.textureTarget); | |
| glBindTexture(glInterop.textureTarget, texture); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| glTexParameteri(glInterop.textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| glInterop.glEGLImageTargetTexture2DOES(glInterop.textureTarget, glInterop.eglImage); | |
| glBindTexture(glInterop.textureTarget, 0); | |
| glDisable(glInterop.textureTarget); | |
| break; | |
| } | |
| default: | |
| return false; | |
| } | |
| return true; | |
| } | |
| void CVaapiRenderPicture::GLUnMapSurface() | |
| { | |
| if (glInterop.vaImage.image_id == VA_INVALID_ID) | |
| return; | |
| glInterop.eglDestroyImageKHR(glInterop.eglDisplay, glInterop.eglImageY); | |
| glInterop.eglDestroyImageKHR(glInterop.eglDisplay, glInterop.eglImageVU); | |
| VAStatus status; | |
| status = vaReleaseBufferHandle(glInterop.vadsp, glInterop.vaImage.buf); | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - Error: %s(%d)", __FUNCTION__, vaErrorStr(status), status); | |
| } | |
| status = vaDestroyImage(glInterop.vadsp, glInterop.vaImage.image_id); | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - Error: %s(%d)", __FUNCTION__, vaErrorStr(status), status); | |
| } | |
| glInterop.mapped = false; | |
| glInterop.vaImage.image_id = VA_INVALID_ID; | |
| glDeleteTextures(1, &textureY); | |
| glDeleteTextures(1, &textureVU); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Buffer Pool | |
| //----------------------------------------------------------------------------- | |
| VaapiBufferPool::VaapiBufferPool() | |
| { | |
| CVaapiRenderPicture *pic; | |
| for (unsigned int i = 0; i < NUM_RENDER_PICS; i++) | |
| { | |
| pic = new CVaapiRenderPicture(renderPicSec); | |
| allRenderPics.push_back(pic); | |
| } | |
| } | |
| VaapiBufferPool::~VaapiBufferPool() | |
| { | |
| CVaapiRenderPicture *pic; | |
| for (unsigned int i = 0; i < NUM_RENDER_PICS; i++) | |
| { | |
| pic = allRenderPics[i]; | |
| delete pic; | |
| } | |
| allRenderPics.clear(); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Output | |
| //----------------------------------------------------------------------------- | |
| COutput::COutput(CEvent *inMsgEvent) : | |
| CThread("Vaapi-Output"), | |
| m_controlPort("OutputControlPort", inMsgEvent, &m_outMsgEvent), | |
| m_dataPort("OutputDataPort", inMsgEvent, &m_outMsgEvent) | |
| { | |
| m_inMsgEvent = inMsgEvent; | |
| for (unsigned int i = 0; i < m_bufferPool.allRenderPics.size(); ++i) | |
| { | |
| m_bufferPool.freeRenderPics.push_back(i); | |
| } | |
| } | |
| void COutput::Start() | |
| { | |
| Create(); | |
| } | |
| COutput::~COutput() | |
| { | |
| Dispose(); | |
| m_bufferPool.freeRenderPics.clear(); | |
| m_bufferPool.usedRenderPics.clear(); | |
| } | |
| void COutput::Dispose() | |
| { | |
| CSingleLock lock(g_graphicsContext); | |
| m_bStop = true; | |
| m_outMsgEvent.Set(); | |
| StopThread(); | |
| m_controlPort.Purge(); | |
| m_dataPort.Purge(); | |
| } | |
| void COutput::OnStartup() | |
| { | |
| CLog::Log(LOGNOTICE, "COutput::OnStartup: Output Thread created"); | |
| } | |
| void COutput::OnExit() | |
| { | |
| CLog::Log(LOGNOTICE, "COutput::OnExit: Output Thread terminated"); | |
| } | |
| enum OUTPUT_STATES | |
| { | |
| O_TOP = 0, // 0 | |
| O_TOP_ERROR, // 1 | |
| O_TOP_UNCONFIGURED, // 2 | |
| O_TOP_CONFIGURED, // 3 | |
| O_TOP_CONFIGURED_IDLE, // 4 | |
| O_TOP_CONFIGURED_WORK, // 5 | |
| O_TOP_CONFIGURED_STEP1, // 6 | |
| O_TOP_CONFIGURED_STEP2, // 7 | |
| O_TOP_CONFIGURED_OUTPUT, // 8 | |
| }; | |
| int VAAPI_OUTPUT_parentStates[] = { | |
| -1, | |
| 0, //TOP_ERROR | |
| 0, //TOP_UNCONFIGURED | |
| 0, //TOP_CONFIGURED | |
| 3, //TOP_CONFIGURED_IDLE | |
| 3, //TOP_CONFIGURED_WORK | |
| 3, //TOP_CONFIGURED_STEP1 | |
| 3, //TOP_CONFIGURED_STEP2 | |
| 3, //TOP_CONFIGURED_OUTPUT | |
| }; | |
| void COutput::StateMachine(int signal, Protocol *port, Message *msg) | |
| { | |
| for (int state = m_state; ; state = VAAPI_OUTPUT_parentStates[state]) | |
| { | |
| switch (state) | |
| { | |
| case O_TOP: // TOP | |
| if (port == &m_controlPort) | |
| { | |
| switch (signal) | |
| { | |
| case COutputControlProtocol::FLUSH: | |
| msg->Reply(COutputControlProtocol::ACC); | |
| return; | |
| case COutputControlProtocol::PRECLEANUP: | |
| msg->Reply(COutputControlProtocol::ACC); | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| else if (port == &m_dataPort) | |
| { | |
| switch (signal) | |
| { | |
| case COutputDataProtocol::RETURNPIC: | |
| CVaapiRenderPicture *pic; | |
| pic = *((CVaapiRenderPicture**)msg->data); | |
| QueueReturnPicture(pic); | |
| return; | |
| case COutputDataProtocol::RETURNPROCPIC: | |
| int id; | |
| id = *((int*)msg->data); | |
| ProcessReturnProcPicture(id); | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| { | |
| std::string portName = port == NULL ? "timer" : port->portName; | |
| CLog::Log(LOGWARNING, "COutput::%s - signal: %d form port: %s not handled for state: %d", __FUNCTION__, signal, portName.c_str(), m_state); | |
| } | |
| return; | |
| case O_TOP_ERROR: | |
| break; | |
| case O_TOP_UNCONFIGURED: | |
| if (port == &m_controlPort) | |
| { | |
| switch (signal) | |
| { | |
| case COutputControlProtocol::INIT: | |
| CVaapiConfig *data; | |
| data = (CVaapiConfig*)msg->data; | |
| if (data) | |
| { | |
| m_config = *data; | |
| } | |
| Init(); | |
| // set initial number of | |
| EnsureBufferPool(); | |
| if (!m_vaError) | |
| { | |
| m_state = O_TOP_CONFIGURED_IDLE; | |
| msg->Reply(COutputControlProtocol::ACC); | |
| } | |
| else | |
| { | |
| m_state = O_TOP_ERROR; | |
| msg->Reply(COutputControlProtocol::ERROR); | |
| } | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| break; | |
| case O_TOP_CONFIGURED: | |
| if (port == &m_controlPort) | |
| { | |
| switch (signal) | |
| { | |
| case COutputControlProtocol::FLUSH: | |
| Flush(); | |
| msg->Reply(COutputControlProtocol::ACC); | |
| return; | |
| case COutputControlProtocol::PRECLEANUP: | |
| Flush(); | |
| ReleaseBufferPool(true); | |
| msg->Reply(COutputControlProtocol::ACC); | |
| m_state = O_TOP_UNCONFIGURED; | |
| m_extTimeout = 10000; | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| else if (port == &m_dataPort) | |
| { | |
| switch (signal) | |
| { | |
| case COutputDataProtocol::NEWFRAME: | |
| CVaapiDecodedPicture *frame; | |
| frame = (CVaapiDecodedPicture*)msg->data; | |
| if (frame) | |
| { | |
| m_bufferPool.decodedPics.push_back(*frame); | |
| m_extTimeout = 0; | |
| } | |
| return; | |
| case COutputDataProtocol::RETURNPIC: | |
| CVaapiRenderPicture *pic; | |
| pic = *((CVaapiRenderPicture**)msg->data); | |
| QueueReturnPicture(pic); | |
| m_controlPort.SendInMessage(COutputControlProtocol::STATS); | |
| m_extTimeout = 0; | |
| return; | |
| case COutputDataProtocol::RETURNPROCPIC: | |
| int id; | |
| id = *((int*)msg->data); | |
| ProcessReturnProcPicture(id); | |
| m_extTimeout = 0; | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| break; | |
| case O_TOP_CONFIGURED_IDLE: | |
| if (port == NULL) // timeout | |
| { | |
| switch (signal) | |
| { | |
| case COutputControlProtocol::TIMEOUT: | |
| if (ProcessSyncPicture()) | |
| m_extTimeout = 10; | |
| else | |
| m_extTimeout = 100; | |
| if (HasWork()) | |
| { | |
| m_state = O_TOP_CONFIGURED_WORK; | |
| m_extTimeout = 0; | |
| } | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| break; | |
| case O_TOP_CONFIGURED_WORK: | |
| if (port == NULL) // timeout | |
| { | |
| switch (signal) | |
| { | |
| case COutputControlProtocol::TIMEOUT: | |
| if (PreferPP()) | |
| { | |
| m_currentPicture = m_bufferPool.decodedPics.front(); | |
| m_bufferPool.decodedPics.pop_front(); | |
| InitCycle(); | |
| m_state = O_TOP_CONFIGURED_STEP1; | |
| m_extTimeout = 0; | |
| return; | |
| } | |
| else if (!m_bufferPool.freeRenderPics.empty() && | |
| !m_bufferPool.processedPics.empty()) | |
| { | |
| m_state = O_TOP_CONFIGURED_OUTPUT; | |
| m_extTimeout = 0; | |
| return; | |
| } | |
| else | |
| m_state = O_TOP_CONFIGURED_IDLE; | |
| m_extTimeout = 100; | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| break; | |
| case O_TOP_CONFIGURED_STEP1: | |
| if (port == NULL) // timeout | |
| { | |
| switch (signal) | |
| { | |
| case COutputControlProtocol::TIMEOUT: | |
| if (!m_pp->AddPicture(m_currentPicture)) | |
| { | |
| m_state = O_TOP_ERROR; | |
| return; | |
| } | |
| CVaapiProcessedPicture outPic; | |
| if (m_pp->Filter(outPic)) | |
| { | |
| m_config.stats->IncProcessed(); | |
| m_bufferPool.processedPics.push_back(outPic); | |
| m_state = O_TOP_CONFIGURED_STEP2; | |
| } | |
| else | |
| { | |
| m_state = O_TOP_CONFIGURED_IDLE; | |
| } | |
| m_config.stats->DecDecoded(); | |
| m_controlPort.SendInMessage(COutputControlProtocol::STATS); | |
| m_extTimeout = 0; | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| break; | |
| case O_TOP_CONFIGURED_STEP2: | |
| if (port == NULL) // timeout | |
| { | |
| switch (signal) | |
| { | |
| case COutputControlProtocol::TIMEOUT: | |
| CVaapiProcessedPicture outPic; | |
| if (m_pp->Filter(outPic)) | |
| { | |
| m_bufferPool.processedPics.push_back(outPic); | |
| m_config.stats->IncProcessed(); | |
| m_extTimeout = 0; | |
| return; | |
| } | |
| m_state = O_TOP_CONFIGURED_IDLE; | |
| m_extTimeout = 0; | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| break; | |
| case O_TOP_CONFIGURED_OUTPUT: | |
| if (port == NULL) // timeout | |
| { | |
| switch (signal) | |
| { | |
| case COutputControlProtocol::TIMEOUT: | |
| if (!m_bufferPool.processedPics.empty()) | |
| { | |
| CVaapiRenderPicture *outPic; | |
| CVaapiProcessedPicture procPic; | |
| procPic = m_bufferPool.processedPics.front(); | |
| m_config.stats->DecProcessed(); | |
| m_bufferPool.processedPics.pop_front(); | |
| outPic = ProcessPicture(procPic); | |
| if (outPic) | |
| { | |
| m_config.stats->IncRender(); | |
| m_dataPort.SendInMessage(COutputDataProtocol::PICTURE, &outPic, sizeof(outPic)); | |
| } | |
| if (m_vaError) | |
| { | |
| m_state = O_TOP_ERROR; | |
| return; | |
| } | |
| } | |
| m_state = O_TOP_CONFIGURED_IDLE; | |
| m_extTimeout = 0; | |
| return; | |
| default: | |
| break; | |
| } | |
| } | |
| break; | |
| default: // we are in no state, should not happen | |
| CLog::Log(LOGERROR, "COutput::%s - no valid state: %d", __FUNCTION__, m_state); | |
| return; | |
| } | |
| } // for | |
| } | |
| void COutput::Process() | |
| { | |
| Message *msg = NULL; | |
| Protocol *port = NULL; | |
| bool gotMsg; | |
| m_state = O_TOP_UNCONFIGURED; | |
| m_extTimeout = 1000; | |
| m_bStateMachineSelfTrigger = false; | |
| while (!m_bStop) | |
| { | |
| gotMsg = false; | |
| if (m_bStateMachineSelfTrigger) | |
| { | |
| m_bStateMachineSelfTrigger = false; | |
| // self trigger state machine | |
| StateMachine(msg->signal, port, msg); | |
| if (!m_bStateMachineSelfTrigger) | |
| { | |
| msg->Release(); | |
| msg = NULL; | |
| } | |
| continue; | |
| } | |
| // check control port | |
| else if (m_controlPort.ReceiveOutMessage(&msg)) | |
| { | |
| gotMsg = true; | |
| port = &m_controlPort; | |
| } | |
| // check data port | |
| else if (m_dataPort.ReceiveOutMessage(&msg)) | |
| { | |
| gotMsg = true; | |
| port = &m_dataPort; | |
| } | |
| if (gotMsg) | |
| { | |
| StateMachine(msg->signal, port, msg); | |
| if (!m_bStateMachineSelfTrigger) | |
| { | |
| msg->Release(); | |
| msg = NULL; | |
| } | |
| continue; | |
| } | |
| // wait for message | |
| else if (m_outMsgEvent.WaitMSec(m_extTimeout)) | |
| { | |
| continue; | |
| } | |
| // time out | |
| else | |
| { | |
| msg = m_controlPort.GetMessage(); | |
| msg->signal = COutputControlProtocol::TIMEOUT; | |
| port = 0; | |
| // signal timeout to state machine | |
| StateMachine(msg->signal, port, msg); | |
| if (!m_bStateMachineSelfTrigger) | |
| { | |
| msg->Release(); | |
| msg = NULL; | |
| } | |
| } | |
| } | |
| Flush(); | |
| Uninit(); | |
| } | |
| bool COutput::Init() | |
| { | |
| if (!CreateEGLContext()) | |
| return false; | |
| if (!GLInit()) | |
| return false; | |
| m_diMethods.numDiMethods = 0; | |
| m_pp = new CFFmpegPostproc(); | |
| m_pp->PreInit(m_config, &m_diMethods); | |
| delete m_pp; | |
| m_pp = new CVppPostproc(); | |
| m_pp->PreInit(m_config, &m_diMethods); | |
| delete m_pp; | |
| m_pp = nullptr; | |
| std::list<EINTERLACEMETHOD> deintMethods; | |
| deintMethods.assign(m_diMethods.diMethods, m_diMethods.diMethods + m_diMethods.numDiMethods); | |
| m_config.processInfo->UpdateDeinterlacingMethods(deintMethods); | |
| m_config.processInfo->SetDeinterlacingMethodDefault(EINTERLACEMETHOD::VS_INTERLACEMETHOD_VAAPI_BOB); | |
| m_vaError = false; | |
| return true; | |
| } | |
| bool COutput::Uninit() | |
| { | |
| glFlush(); | |
| while(ProcessSyncPicture()) | |
| { | |
| Sleep(10); | |
| } | |
| ReleaseBufferPool(); | |
| delete m_pp; | |
| m_pp = NULL; | |
| DestroyEGLContext(); | |
| return true; | |
| } | |
| void COutput::Flush() | |
| { | |
| Message *msg; | |
| while (m_dataPort.ReceiveOutMessage(&msg)) | |
| { | |
| if (msg->signal == COutputDataProtocol::NEWFRAME) | |
| { | |
| CVaapiDecodedPicture pic = *(CVaapiDecodedPicture*)msg->data; | |
| m_config.videoSurfaces->ClearRender(pic.videoSurface); | |
| } | |
| else if (msg->signal == COutputDataProtocol::RETURNPIC) | |
| { | |
| CVaapiRenderPicture *pic; | |
| pic = *((CVaapiRenderPicture**)msg->data); | |
| QueueReturnPicture(pic); | |
| } | |
| msg->Release(); | |
| } | |
| while (m_dataPort.ReceiveInMessage(&msg)) | |
| { | |
| if (msg->signal == COutputDataProtocol::PICTURE) | |
| { | |
| CVaapiRenderPicture *pic; | |
| pic = *((CVaapiRenderPicture**)msg->data); | |
| QueueReturnPicture(pic); | |
| } | |
| msg->Release(); | |
| } | |
| for (unsigned int i = 0; i < m_bufferPool.decodedPics.size(); i++) | |
| { | |
| m_config.videoSurfaces->ClearRender(m_bufferPool.decodedPics[i].videoSurface); | |
| } | |
| m_bufferPool.decodedPics.clear(); | |
| for (unsigned int i = 0; i < m_bufferPool.processedPics.size(); i++) | |
| { | |
| ReleaseProcessedPicture(m_bufferPool.processedPics[i]); | |
| } | |
| m_bufferPool.processedPics.clear(); | |
| if (m_pp) | |
| m_pp->Flush(); | |
| } | |
| bool COutput::HasWork() | |
| { | |
| // send a pic to renderer | |
| if (!m_bufferPool.freeRenderPics.empty() && !m_bufferPool.processedPics.empty()) | |
| return true; | |
| bool ppWantsPic = true; | |
| if (m_pp) | |
| ppWantsPic = m_pp->WantsPic(); | |
| if (!m_bufferPool.decodedPics.empty() && m_bufferPool.processedPics.size() < 4 && ppWantsPic) | |
| return true; | |
| return false; | |
| } | |
| bool COutput::PreferPP() | |
| { | |
| if (!m_bufferPool.decodedPics.empty()) | |
| { | |
| if (!m_pp) | |
| return true; | |
| if (!m_pp->WantsPic()) | |
| return false; | |
| if (!m_pp->DoesSync() && m_bufferPool.processedPics.size() < 4) | |
| return true; | |
| if (m_bufferPool.freeRenderPics.empty() || m_bufferPool.processedPics.empty()) | |
| return true; | |
| } | |
| return false; | |
| } | |
| void COutput::InitCycle() | |
| { | |
| uint64_t latency; | |
| int flags; | |
| m_config.stats->GetParams(latency, flags); | |
| m_config.stats->SetCanSkipDeint(false); | |
| EINTERLACEMETHOD method = CMediaSettings::GetInstance().GetCurrentVideoSettings().m_InterlaceMethod; | |
| bool interlaced = m_currentPicture.DVDPic.iFlags & DVP_FLAG_INTERLACED; | |
| if (!(flags & DVD_CODEC_CTRL_NO_POSTPROC) && | |
| interlaced && | |
| method != VS_INTERLACEMETHOD_NONE) | |
| { | |
| if (!m_config.processInfo->Supports(method)) | |
| method = VS_INTERLACEMETHOD_VAAPI_BOB; | |
| if (m_pp && (method != m_currentDiMethod || !m_pp->Compatible(method))) | |
| { | |
| delete m_pp; | |
| m_pp = NULL; | |
| DropVppProcessedPictures(); | |
| m_config.processInfo->SetVideoDeintMethod("unknown"); | |
| } | |
| if (!m_pp) | |
| { | |
| if (method == VS_INTERLACEMETHOD_DEINTERLACE || | |
| method == VS_INTERLACEMETHOD_RENDER_BOB) | |
| { | |
| m_pp = new CFFmpegPostproc(); | |
| m_config.stats->SetVpp(false); | |
| } | |
| else | |
| { | |
| m_pp = new CVppPostproc(); | |
| m_config.stats->SetVpp(true); | |
| } | |
| if (m_pp->PreInit(m_config)) | |
| { | |
| m_pp->Init(method); | |
| m_currentDiMethod = method; | |
| if (method == VS_INTERLACEMETHOD_DEINTERLACE) | |
| m_config.processInfo->SetVideoDeintMethod("yadif"); | |
| else if (method == VS_INTERLACEMETHOD_RENDER_BOB) | |
| m_config.processInfo->SetVideoDeintMethod("render-bob"); | |
| else if (method == VS_INTERLACEMETHOD_VAAPI_BOB) | |
| m_config.processInfo->SetVideoDeintMethod("vaapi-bob"); | |
| else if (method == VS_INTERLACEMETHOD_VAAPI_MADI) | |
| m_config.processInfo->SetVideoDeintMethod("vaapi-madi"); | |
| else if (method == VS_INTERLACEMETHOD_VAAPI_MACI) | |
| m_config.processInfo->SetVideoDeintMethod("vaapi-mcdi"); | |
| } | |
| else | |
| { | |
| delete m_pp; | |
| m_pp = NULL; | |
| } | |
| } | |
| } | |
| // progressive | |
| else | |
| { | |
| method = VS_INTERLACEMETHOD_NONE; | |
| if (m_pp && !m_pp->Compatible(method)) | |
| { | |
| delete m_pp; | |
| m_pp = NULL; | |
| DropVppProcessedPictures(); | |
| } | |
| if (!m_pp) | |
| { | |
| m_config.stats->SetVpp(false); | |
| if (!CSettings::GetInstance().GetBool(CSettings::SETTING_VIDEOPLAYER_PREFERVAAPIRENDER)) | |
| m_pp = new CFFmpegPostproc(); | |
| else | |
| { | |
| m_pp = new CSkipPostproc(); | |
| m_config.stats->SetVpp(true); | |
| } | |
| if (m_pp->PreInit(m_config)) | |
| { | |
| m_pp->Init(method); | |
| m_currentDiMethod = method; | |
| m_config.processInfo->SetVideoDeintMethod("none"); | |
| } | |
| else | |
| { | |
| delete m_pp; | |
| m_pp = NULL; | |
| } | |
| } | |
| } | |
| if (!m_pp) // fallback | |
| { | |
| m_pp = new CSkipPostproc(); | |
| if (m_pp->PreInit(m_config)) | |
| m_pp->Init(method); | |
| } | |
| } | |
| CVaapiRenderPicture* COutput::ProcessPicture(CVaapiProcessedPicture &pic) | |
| { | |
| CVaapiRenderPicture *retPic; | |
| int idx = m_bufferPool.freeRenderPics.front(); | |
| retPic = m_bufferPool.allRenderPics[idx]; | |
| retPic->DVDPic = pic.DVDPic; | |
| if (pic.source == CVaapiProcessedPicture::SKIP_SRC || | |
| pic.source == CVaapiProcessedPicture::VPP_SRC) | |
| { | |
| pic.id = m_bufferPool.procPicId++; | |
| m_bufferPool.processedPicsAway.push_back(pic); | |
| retPic->DVDPic.format = RENDER_FMT_VAAPI; | |
| retPic->glInterop.procPic = pic; | |
| retPic->glInterop.mapped = false; | |
| retPic->GLMapSurface(); | |
| } | |
| else if (pic.source == CVaapiProcessedPicture::FFMPEG_SRC) | |
| { | |
| av_frame_move_ref(retPic->avFrame, pic.frame); | |
| av_frame_free(&pic.frame); | |
| retPic->DVDPic.format = RENDER_FMT_VAAPINV12; | |
| } | |
| else | |
| return NULL; | |
| m_bufferPool.freeRenderPics.pop_front(); | |
| m_bufferPool.usedRenderPics.push_back(idx); | |
| retPic->DVDPic.dts = DVD_NOPTS_VALUE; | |
| retPic->DVDPic.iWidth = m_config.vidWidth; | |
| retPic->DVDPic.iHeight = m_config.vidHeight; | |
| retPic->valid = true; | |
| retPic->crop.x1 = 0; | |
| retPic->crop.y1 = 0; | |
| retPic->crop.x2 = m_config.outWidth; | |
| retPic->crop.y2 = m_config.outHeight; | |
| return retPic; | |
| } | |
| void COutput::ReleaseProcessedPicture(CVaapiProcessedPicture &pic) | |
| { | |
| if (pic.source == CVaapiProcessedPicture::VPP_SRC && m_pp) | |
| { | |
| CVppPostproc *pp = dynamic_cast<CVppPostproc*>(m_pp); | |
| if (pp) | |
| { | |
| pp->ClearRef(pic.videoSurface); | |
| } | |
| } | |
| else if (pic.source == CVaapiProcessedPicture::SKIP_SRC) | |
| { | |
| m_config.videoSurfaces->ClearRender(pic.videoSurface); | |
| } | |
| else if (pic.source == CVaapiProcessedPicture::FFMPEG_SRC) | |
| { | |
| av_frame_free(&pic.frame); | |
| } | |
| } | |
| void COutput::DropVppProcessedPictures() | |
| { | |
| auto it = m_bufferPool.processedPics.begin(); | |
| while (it != m_bufferPool.processedPics.end()) | |
| { | |
| if (it->source == CVaapiProcessedPicture::VPP_SRC) | |
| { | |
| it = m_bufferPool.processedPics.erase(it); | |
| m_config.stats->DecProcessed(); | |
| } | |
| else | |
| ++it; | |
| } | |
| it = m_bufferPool.processedPicsAway.begin(); | |
| while (it != m_bufferPool.processedPicsAway.end()) | |
| { | |
| if (it->source == CVaapiProcessedPicture::VPP_SRC) | |
| { | |
| it = m_bufferPool.processedPicsAway.erase(it); | |
| } | |
| else | |
| ++it; | |
| } | |
| m_controlPort.SendInMessage(COutputControlProtocol::STATS); | |
| } | |
| void COutput::QueueReturnPicture(CVaapiRenderPicture *pic) | |
| { | |
| std::deque<int>::iterator it; | |
| for (it = m_bufferPool.usedRenderPics.begin(); it != m_bufferPool.usedRenderPics.end(); ++it) | |
| { | |
| if (m_bufferPool.allRenderPics[*it] == pic) | |
| { | |
| break; | |
| } | |
| } | |
| if (it == m_bufferPool.usedRenderPics.end()) | |
| { | |
| CLog::Log(LOGWARNING, "COutput::QueueReturnPicture - pic not found"); | |
| return; | |
| } | |
| // check if already queued | |
| auto it2 = find(m_bufferPool.syncRenderPics.begin(), | |
| m_bufferPool.syncRenderPics.end(), | |
| *it); | |
| if (it2 == m_bufferPool.syncRenderPics.end()) | |
| { | |
| m_bufferPool.syncRenderPics.push_back(*it); | |
| } | |
| ProcessSyncPicture(); | |
| } | |
| bool COutput::ProcessSyncPicture() | |
| { | |
| CVaapiRenderPicture *pic; | |
| bool busy = false; | |
| for (auto it = m_bufferPool.syncRenderPics.begin(); it != m_bufferPool.syncRenderPics.end(); ) | |
| { | |
| pic = m_bufferPool.allRenderPics[*it]; | |
| #ifdef GL_ARB_sync | |
| if (pic->usefence) | |
| { | |
| if (pic->fence) | |
| { | |
| GLint state; | |
| GLsizei length; | |
| glGetSynciv(pic->fence, GL_SYNC_STATUS, 1, &length, &state); | |
| if(state == GL_SIGNALED) | |
| { | |
| glDeleteSync(pic->fence); | |
| pic->fence = None; | |
| } | |
| else | |
| { | |
| busy = true; | |
| ++it; | |
| continue; | |
| } | |
| } | |
| } | |
| #endif | |
| m_bufferPool.freeRenderPics.push_back(*it); | |
| auto it2 = find(m_bufferPool.usedRenderPics.begin(), | |
| m_bufferPool.usedRenderPics.end(), | |
| *it); | |
| if (it2 == m_bufferPool.usedRenderPics.end()) | |
| { | |
| CLog::Log(LOGERROR, "COutput::ProcessSyncPicture - pic not found in queue"); | |
| } | |
| else | |
| { | |
| m_bufferPool.usedRenderPics.erase(it2); | |
| } | |
| it = m_bufferPool.syncRenderPics.erase(it); | |
| if (pic->valid) | |
| { | |
| ProcessReturnPicture(pic); | |
| } | |
| else | |
| { | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "COutput::%s - return of invalid render pic", __FUNCTION__); | |
| } | |
| } | |
| return busy; | |
| } | |
| void COutput::ProcessReturnPicture(CVaapiRenderPicture *pic) | |
| { | |
| if (pic->avFrame) | |
| av_frame_unref(pic->avFrame); | |
| pic->GLUnMapSurface(); | |
| ProcessReturnProcPicture(pic->glInterop.procPic.id); | |
| pic->valid = false; | |
| } | |
| void COutput::ProcessReturnProcPicture(int id) | |
| { | |
| for (auto it=m_bufferPool.processedPicsAway.begin(); it!=m_bufferPool.processedPicsAway.end(); ++it) | |
| { | |
| if (it->id == id) | |
| { | |
| ReleaseProcessedPicture(*it); | |
| m_bufferPool.processedPicsAway.erase(it); | |
| break; | |
| } | |
| } | |
| } | |
| bool COutput::EnsureBufferPool() | |
| { | |
| // create avFrames and init interop | |
| CVaapiRenderPicture *pic; | |
| for (unsigned int i = 0; i < m_bufferPool.allRenderPics.size(); i++) | |
| { | |
| pic = m_bufferPool.allRenderPics[i]; | |
| pic->glInterop.vadsp = m_config.dpy; | |
| pic->glInterop.eglDisplay = m_eglDisplay; | |
| pic->glInterop.textureTarget = m_textureTarget; | |
| pic->glInterop.eglCreateImageKHR = eglCreateImageKHR; | |
| pic->glInterop.eglDestroyImageKHR = eglDestroyImageKHR; | |
| pic->glInterop.glEGLImageTargetTexture2DOES = glEGLImageTargetTexture2DOES; | |
| pic->glInterop.vaImage.image_id = VA_INVALID_ID; | |
| pic->glInterop.mapped = false; | |
| pic->avFrame = av_frame_alloc(); | |
| pic->valid = false; | |
| } | |
| m_bufferPool.procPicId = 0; | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "VAAPI::COutput::InitBufferPool - Surfaces created"); | |
| return true; | |
| } | |
| void COutput::ReleaseBufferPool(bool precleanup) | |
| { | |
| CVaapiRenderPicture *pic; | |
| CSingleLock lock(m_bufferPool.renderPicSec); | |
| if (!precleanup) | |
| { | |
| // wait for all fences | |
| XbmcThreads::EndTime timeout(1000); | |
| for (unsigned int i = 0; i < m_bufferPool.allRenderPics.size(); i++) | |
| { | |
| pic = m_bufferPool.allRenderPics[i]; | |
| if (pic->usefence) | |
| { | |
| #ifdef GL_ARB_sync | |
| while (pic->fence) | |
| { | |
| GLint state; | |
| GLsizei length; | |
| glGetSynciv(pic->fence, GL_SYNC_STATUS, 1, &length, &state); | |
| if(state == GL_SIGNALED || timeout.IsTimePast()) | |
| { | |
| glDeleteSync(pic->fence); | |
| pic->fence = None; | |
| } | |
| else | |
| { | |
| Sleep(5); | |
| } | |
| } | |
| pic->fence = None; | |
| #endif | |
| } | |
| } | |
| if (timeout.IsTimePast()) | |
| { | |
| CLog::Log(LOGERROR, "COutput::%s - timeout waiting for fence", __FUNCTION__); | |
| } | |
| } | |
| ProcessSyncPicture(); | |
| for (unsigned int i = 0; i < m_bufferPool.allRenderPics.size(); i++) | |
| { | |
| pic = m_bufferPool.allRenderPics[i]; | |
| if (precleanup && pic->valid) | |
| continue; | |
| if (pic->texture) | |
| { | |
| glDeleteTextures(1, &pic->texture); | |
| pic->texture = None; | |
| } | |
| av_frame_free(&pic->avFrame); | |
| pic->valid = false; | |
| } | |
| for (unsigned int i = 0; i < m_bufferPool.decodedPics.size(); i++) | |
| { | |
| m_config.videoSurfaces->ClearRender(m_bufferPool.decodedPics[i].videoSurface); | |
| } | |
| m_bufferPool.decodedPics.clear(); | |
| for (unsigned int i = 0; i < m_bufferPool.processedPics.size(); i++) | |
| { | |
| ReleaseProcessedPicture(m_bufferPool.processedPics[i]); | |
| } | |
| m_bufferPool.processedPics.clear(); | |
| } | |
| bool COutput::GLInit() | |
| { | |
| #ifdef GL_ARB_sync | |
| bool hasfence = g_Windowing.IsExtSupported("GL_ARB_sync"); | |
| for (unsigned int i = 0; i < m_bufferPool.allRenderPics.size(); i++) | |
| { | |
| m_bufferPool.allRenderPics[i]->usefence = hasfence; | |
| } | |
| #endif | |
| if (!g_Windowing.IsExtSupported("GL_ARB_texture_non_power_of_two") && | |
| g_Windowing.IsExtSupported("GL_ARB_texture_rectangle")) | |
| { | |
| m_textureTarget = GL_TEXTURE_RECTANGLE_ARB; | |
| } | |
| else | |
| m_textureTarget = GL_TEXTURE_2D; | |
| eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); | |
| eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); | |
| glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); | |
| return true; | |
| } | |
| bool COutput::CheckSuccess(VAStatus status) | |
| { | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - Error: %s(%d)", __FUNCTION__, vaErrorStr(status), status); | |
| m_vaError = true; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool COutput::CreateEGLContext() | |
| { | |
| EGLDisplay eglDisplay = g_Windowing.GetEGLDisplay(); | |
| EGLContext eglMainContext = g_Windowing.GetEGLContext(); | |
| EGLConfig eglMainConfig = g_Windowing.GetEGLConfig(); | |
| EGLint pbufferAttribs[] = | |
| { | |
| EGL_WIDTH, 8, | |
| EGL_HEIGHT, 8, | |
| EGL_TEXTURE_TARGET, EGL_NO_TEXTURE, | |
| EGL_TEXTURE_FORMAT, EGL_NO_TEXTURE, | |
| EGL_NONE | |
| }; | |
| EGLint contextAttributes[] = | |
| { | |
| EGL_CONTEXT_CLIENT_VERSION, 2, | |
| EGL_NONE | |
| }; | |
| if (!eglBindAPI(EGL_OPENGL_API)) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::COutput::CreateEGLContext -failed to bind egl API"); | |
| return false; | |
| } | |
| m_eglSurface = eglCreatePbufferSurface(eglDisplay, eglMainConfig, pbufferAttribs); | |
| m_eglContext = eglCreateContext(eglDisplay, eglMainConfig, eglMainContext, contextAttributes); | |
| m_eglDisplay = eglDisplay; | |
| if (!eglMakeCurrent(eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::COutput::CreateEGLContext - Could not make surface current"); | |
| return false; | |
| } | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "VAAPI::COutput::CreateEGLContext - created context"); | |
| return true; | |
| } | |
| bool COutput::DestroyEGLContext() | |
| { | |
| if (m_eglContext) | |
| { | |
| glFinish(); | |
| //eglMakeCurrent(m_eglContext, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |
| eglDestroyContext(m_eglDisplay, m_eglContext); | |
| } | |
| m_eglContext = EGL_NO_CONTEXT; | |
| if (m_eglSurface) | |
| eglDestroySurface(m_eglDisplay, m_eglSurface); | |
| m_eglSurface = EGL_NO_SURFACE; | |
| return true; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Postprocessing | |
| //----------------------------------------------------------------------------- | |
| bool CSkipPostproc::PreInit(CVaapiConfig &config, SDiMethods *methods) | |
| { | |
| m_config = config; | |
| return true; | |
| } | |
| bool CSkipPostproc::Init(EINTERLACEMETHOD method) | |
| { | |
| return true; | |
| } | |
| bool CSkipPostproc::AddPicture(CVaapiDecodedPicture &inPic) | |
| { | |
| m_pic = inPic; | |
| m_step = 0; | |
| return true; | |
| } | |
| bool CSkipPostproc::Filter(CVaapiProcessedPicture &outPic) | |
| { | |
| if (m_step>0) | |
| return false; | |
| outPic.DVDPic = m_pic.DVDPic; | |
| outPic.videoSurface = m_pic.videoSurface; | |
| outPic.source = CVaapiProcessedPicture::SKIP_SRC; | |
| outPic.DVDPic.iFlags &= ~(DVP_FLAG_TOP_FIELD_FIRST | | |
| DVP_FLAG_REPEAT_TOP_FIELD | | |
| DVP_FLAG_INTERLACED); | |
| m_step++; | |
| return true; | |
| } | |
| void CSkipPostproc::ClearRef(VASurfaceID surf) | |
| { | |
| } | |
| void CSkipPostproc::Flush() | |
| { | |
| } | |
| bool CSkipPostproc::Compatible(EINTERLACEMETHOD method) | |
| { | |
| if (method == VS_INTERLACEMETHOD_NONE) | |
| return true; | |
| return false; | |
| } | |
| bool CSkipPostproc::DoesSync() | |
| { | |
| return false; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // VPP Postprocessing | |
| //----------------------------------------------------------------------------- | |
| CVppPostproc::CVppPostproc() | |
| { | |
| m_contextId = VA_INVALID_ID; | |
| m_configId = VA_INVALID_ID; | |
| m_filter = VA_INVALID_ID; | |
| } | |
| CVppPostproc::~CVppPostproc() | |
| { | |
| Dispose(); | |
| } | |
| bool CVppPostproc::PreInit(CVaapiConfig &config, SDiMethods *methods) | |
| { | |
| m_config = config; | |
| // create config | |
| if (!CheckSuccess(vaCreateConfig(m_config.dpy, VAProfileNone, VAEntrypointVideoProc, NULL, 0, &m_configId))) | |
| { | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "CVppPostproc::PreInit - VPP init failed"); | |
| return false; | |
| } | |
| VASurfaceAttrib attribs[1], *attrib; | |
| attrib = attribs; | |
| attrib->flags = VA_SURFACE_ATTRIB_SETTABLE; | |
| attrib->type = VASurfaceAttribPixelFormat; | |
| attrib->value.type = VAGenericValueTypeInteger; | |
| attrib->value.value.i = VA_FOURCC_NV12; | |
| // create surfaces | |
| VASurfaceID surfaces[32]; | |
| int nb_surfaces = NUM_RENDER_PICS; | |
| if (!CheckSuccess(vaCreateSurfaces(m_config.dpy, | |
| VA_RT_FORMAT_YUV420, | |
| m_config.surfaceWidth, | |
| m_config.surfaceHeight, | |
| surfaces, | |
| nb_surfaces, | |
| attribs, 1))) | |
| { | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "CVppPostproc::PreInit - VPP init failed"); | |
| return false; | |
| } | |
| for (int i=0; i<nb_surfaces; i++) | |
| { | |
| m_videoSurfaces.AddSurface(surfaces[i]); | |
| } | |
| // create vaapi decoder context | |
| if (!CheckSuccess(vaCreateContext(m_config.dpy, | |
| m_configId, | |
| 0, | |
| 0, | |
| 0, | |
| surfaces, | |
| nb_surfaces, | |
| &m_contextId))) | |
| { | |
| m_contextId = VA_INVALID_ID; | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "CVppPostproc::PreInit - VPP init failed"); | |
| return false; | |
| } | |
| VAProcFilterType filters[VAProcFilterCount]; | |
| unsigned int numFilters = VAProcFilterCount; | |
| VAProcFilterCapDeinterlacing deinterlacingCaps[VAProcDeinterlacingCount]; | |
| unsigned int numDeinterlacingCaps = VAProcDeinterlacingCount; | |
| if (!CheckSuccess(vaQueryVideoProcFilters(m_config.dpy, m_contextId, filters, &numFilters))) | |
| { | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "CVppPostproc::PreInit - VPP init failed"); | |
| return false; | |
| } | |
| if (!CheckSuccess(vaQueryVideoProcFilterCaps(m_config.dpy, | |
| m_contextId, | |
| VAProcFilterDeinterlacing, | |
| deinterlacingCaps, | |
| &numDeinterlacingCaps))) | |
| { | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "CVppPostproc::PreInit - VPP init failed"); | |
| return false; | |
| } | |
| if (methods) | |
| { | |
| for (unsigned int i = 0; i < numFilters; i++) | |
| { | |
| if (filters[i] == VAProcFilterDeinterlacing) | |
| { | |
| for (unsigned int j = 0; j < numDeinterlacingCaps; j++) | |
| { | |
| if (deinterlacingCaps[j].type == VAProcDeinterlacingBob) | |
| { | |
| methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_VAAPI_BOB; | |
| } | |
| else if (deinterlacingCaps[j].type == VAProcDeinterlacingMotionAdaptive) | |
| { | |
| methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_VAAPI_MADI; | |
| } | |
| else if (deinterlacingCaps[j].type == VAProcDeinterlacingMotionCompensated) | |
| { | |
| methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_VAAPI_MACI; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool CVppPostproc::Init(EINTERLACEMETHOD method) | |
| { | |
| m_vppMethod = method; | |
| VAProcDeinterlacingType vppMethod = VAProcDeinterlacingNone; | |
| switch (method) | |
| { | |
| case VS_INTERLACEMETHOD_VAAPI_BOB: | |
| vppMethod = VAProcDeinterlacingBob; | |
| break; | |
| case VS_INTERLACEMETHOD_VAAPI_MADI: | |
| vppMethod = VAProcDeinterlacingMotionAdaptive; | |
| break; | |
| case VS_INTERLACEMETHOD_VAAPI_MACI: | |
| vppMethod = VAProcDeinterlacingMotionCompensated; | |
| break; | |
| case VS_INTERLACEMETHOD_NONE: | |
| // vppMethod = VAProcDeinterlacingNone; | |
| break; | |
| default: | |
| return false; | |
| } | |
| m_forwardRefs = 0; | |
| m_backwardRefs = 0; | |
| m_currentIdx = 0; | |
| m_frameCount = 0; | |
| // if (method == VS_INTERLACEMETHOD_NONE) | |
| // return true; | |
| VAProcFilterParameterBufferDeinterlacing filterparams; | |
| filterparams.type = VAProcFilterDeinterlacing; | |
| filterparams.algorithm = vppMethod; | |
| filterparams.flags = 0; | |
| if (!CheckSuccess(vaCreateBuffer(m_config.dpy, m_contextId, | |
| VAProcFilterParameterBufferType, | |
| sizeof(filterparams), 1, &filterparams, &m_filter))) | |
| { | |
| m_filter = VA_INVALID_ID; | |
| return false; | |
| } | |
| VAProcPipelineCaps pplCaps; | |
| if (!CheckSuccess(vaQueryVideoProcPipelineCaps(m_config.dpy, m_contextId, | |
| &m_filter, 1, &pplCaps))) | |
| { | |
| return false; | |
| } | |
| m_forwardRefs = pplCaps.num_forward_references; | |
| m_backwardRefs = pplCaps.num_backward_references; | |
| return true; | |
| } | |
| void CVppPostproc::Dispose() | |
| { | |
| // make sure surfaces are idle | |
| for (int i=0; i<m_videoSurfaces.Size(); i++) | |
| { | |
| CheckSuccess(vaSyncSurface(m_config.dpy, m_videoSurfaces.GetAtIndex(i))); | |
| } | |
| if (m_filter != VA_INVALID_ID) | |
| { | |
| CheckSuccess(vaDestroyBuffer(m_config.dpy, m_filter)); | |
| m_filter = VA_INVALID_ID; | |
| } | |
| if (m_contextId != VA_INVALID_ID) | |
| { | |
| CheckSuccess(vaDestroyContext(m_config.dpy, m_contextId)); | |
| m_contextId = VA_INVALID_ID; | |
| } | |
| VASurfaceID surf; | |
| while((surf = m_videoSurfaces.RemoveNext()) != VA_INVALID_SURFACE) | |
| { | |
| CheckSuccess(vaDestroySurfaces(m_config.dpy, &surf, 1)); | |
| } | |
| m_videoSurfaces.Reset(); | |
| if (m_configId != VA_INVALID_ID) | |
| { | |
| CheckSuccess(vaDestroyConfig(m_config.dpy, m_configId)); | |
| m_configId = VA_INVALID_ID; | |
| } | |
| // release all decoded pictures | |
| Flush(); | |
| } | |
| bool CVppPostproc::AddPicture(CVaapiDecodedPicture &pic) | |
| { | |
| pic.index = m_frameCount; | |
| m_decodedPics.push_front(pic); | |
| m_frameCount++; | |
| m_step = 0; | |
| m_config.stats->SetCanSkipDeint(true); | |
| return true; | |
| } | |
| bool CVppPostproc::Filter(CVaapiProcessedPicture &outPic) | |
| { | |
| if (m_step>1) | |
| { | |
| Advance(); | |
| return false; | |
| } | |
| // we need a free render target | |
| VASurfaceID surf = m_videoSurfaces.GetFree(VA_INVALID_SURFACE); | |
| if (surf == VA_INVALID_SURFACE) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI - VPP - no free render target"); | |
| return false; | |
| } | |
| // clear reference in case we return false | |
| m_videoSurfaces.ClearReference(surf); | |
| // make sure we have all needed forward refs | |
| if ((m_currentIdx - m_forwardRefs) < m_decodedPics.back().index) | |
| { | |
| Advance(); | |
| return false; | |
| } | |
| const auto currentIdx = m_currentIdx; | |
| auto it = std::find_if(m_decodedPics.begin(), m_decodedPics.end(), | |
| [currentIdx](const CVaapiDecodedPicture &picture){ | |
| return picture.index == currentIdx; | |
| }); | |
| if (it==m_decodedPics.end()) | |
| { | |
| return false; | |
| } | |
| outPic.DVDPic = it->DVDPic; | |
| // skip deinterlacing cycle if requested | |
| if ((m_step == 1) && | |
| ((outPic.DVDPic.iFlags & DVD_CODEC_CTRL_SKIPDEINT) || (m_vppMethod == VS_INTERLACEMETHOD_NONE))) | |
| { | |
| Advance(); | |
| return false; | |
| } | |
| // vpp deinterlacing | |
| VAProcFilterParameterBufferDeinterlacing *filterParams; | |
| VABufferID pipelineBuf; | |
| VAProcPipelineParameterBuffer *pipelineParams; | |
| VARectangle inputRegion; | |
| VARectangle outputRegion; | |
| if (!CheckSuccess(vaBeginPicture(m_config.dpy, m_contextId, surf))) | |
| { | |
| return false; | |
| } | |
| if (!CheckSuccess(vaCreateBuffer(m_config.dpy, m_contextId, | |
| VAProcPipelineParameterBufferType, | |
| sizeof(VAProcPipelineParameterBuffer), 1, NULL, &pipelineBuf))) | |
| { | |
| return false; | |
| } | |
| if (!CheckSuccess(vaMapBuffer(m_config.dpy, pipelineBuf, (void**)&pipelineParams))) | |
| { | |
| return false; | |
| } | |
| memset(pipelineParams, 0, sizeof(VAProcPipelineParameterBuffer)); | |
| inputRegion.x = outputRegion.x = 0; | |
| inputRegion.y = outputRegion.y = 0; | |
| inputRegion.width = outputRegion.width = m_config.surfaceWidth; | |
| inputRegion.height = outputRegion.height = m_config.surfaceHeight; | |
| pipelineParams->output_region = &outputRegion; | |
| pipelineParams->surface_region = &inputRegion; | |
| pipelineParams->output_background_color = 0xff000000; | |
| VASurfaceID forwardRefs[32]; | |
| VASurfaceID backwardRefs[32]; | |
| pipelineParams->forward_references = forwardRefs; | |
| pipelineParams->backward_references = backwardRefs; | |
| pipelineParams->num_forward_references = 0; | |
| pipelineParams->num_backward_references = 0; | |
| int maxPic = m_currentIdx + m_backwardRefs; | |
| int minPic = m_currentIdx - m_forwardRefs; | |
| int curPic = m_currentIdx; | |
| // deinterlace flag | |
| if (m_vppMethod != VS_INTERLACEMETHOD_NONE) | |
| { | |
| unsigned int flags = 0; | |
| if (it->DVDPic.iFlags & DVP_FLAG_TOP_FIELD_FIRST) | |
| flags = 0; | |
| else | |
| flags = VA_DEINTERLACING_BOTTOM_FIELD_FIRST | VA_DEINTERLACING_BOTTOM_FIELD; | |
| if (m_step) | |
| { | |
| if (flags & VA_DEINTERLACING_BOTTOM_FIELD) | |
| flags &= ~VA_DEINTERLACING_BOTTOM_FIELD; | |
| else | |
| flags |= VA_DEINTERLACING_BOTTOM_FIELD; | |
| } | |
| if (!CheckSuccess(vaMapBuffer(m_config.dpy, m_filter, (void**)&filterParams))) | |
| { | |
| return false; | |
| } | |
| filterParams->flags = flags; | |
| if (!CheckSuccess(vaUnmapBuffer(m_config.dpy, m_filter))) | |
| { | |
| return false; | |
| } | |
| if (m_vppMethod == VS_INTERLACEMETHOD_VAAPI_BOB) | |
| pipelineParams->filter_flags = (flags & VA_DEINTERLACING_BOTTOM_FIELD) ? VA_BOTTOM_FIELD : VA_TOP_FIELD; | |
| else | |
| pipelineParams->filter_flags = 0; | |
| pipelineParams->filters = &m_filter; | |
| pipelineParams->num_filters = 1; | |
| } | |
| else | |
| { | |
| pipelineParams->num_filters = 0; | |
| } | |
| // references | |
| double ptsLast = DVD_NOPTS_VALUE; | |
| double pts = DVD_NOPTS_VALUE; | |
| pipelineParams->surface = VA_INVALID_SURFACE; | |
| for (const auto &picture : m_decodedPics) | |
| { | |
| if (picture.index >= minPic && picture.index <= maxPic) | |
| { | |
| if (picture.index > curPic) | |
| { | |
| backwardRefs[(picture.index - curPic) - 1] = picture.videoSurface; | |
| pipelineParams->num_backward_references++; | |
| } | |
| else if (picture.index == curPic) | |
| { | |
| pipelineParams->surface = picture.videoSurface; | |
| pts = picture.DVDPic.pts; | |
| } | |
| if (picture.index < curPic) | |
| { | |
| forwardRefs[(curPic - picture.index) - 1] = picture.videoSurface; | |
| pipelineParams->num_forward_references++; | |
| if (picture.index == curPic - 1) | |
| ptsLast = picture.DVDPic.pts; | |
| } | |
| } | |
| } | |
| // set pts for 2nd frame | |
| if (m_step && pts != DVD_NOPTS_VALUE && ptsLast != DVD_NOPTS_VALUE) | |
| outPic.DVDPic.pts += (pts-ptsLast)/2; | |
| if (pipelineParams->surface == VA_INVALID_SURFACE) | |
| return false; | |
| if (!CheckSuccess(vaUnmapBuffer(m_config.dpy, pipelineBuf))) | |
| { | |
| return false; | |
| } | |
| if (!CheckSuccess(vaRenderPicture(m_config.dpy, m_contextId, | |
| &pipelineBuf, 1))) | |
| { | |
| return false; | |
| } | |
| if (!CheckSuccess(vaEndPicture(m_config.dpy, m_contextId))) | |
| { | |
| return false; | |
| } | |
| if (!CheckSuccess(vaDestroyBuffer(m_config.dpy, pipelineBuf))) | |
| { | |
| return false; | |
| } | |
| m_step++; | |
| outPic.videoSurface = m_videoSurfaces.GetFree(surf); | |
| outPic.source = CVaapiProcessedPicture::VPP_SRC; | |
| outPic.DVDPic.iFlags &= ~(DVP_FLAG_TOP_FIELD_FIRST | | |
| DVP_FLAG_REPEAT_TOP_FIELD | | |
| DVP_FLAG_INTERLACED); | |
| return true; | |
| } | |
| void CVppPostproc::Advance() | |
| { | |
| m_currentIdx++; | |
| // release all unneeded refs | |
| auto it = m_decodedPics.begin(); | |
| while (it != m_decodedPics.end()) | |
| { | |
| if (it->index < m_currentIdx - m_forwardRefs) | |
| { | |
| m_config.videoSurfaces->ClearRender(it->videoSurface); | |
| it = m_decodedPics.erase(it); | |
| } | |
| else | |
| ++it; | |
| } | |
| } | |
| void CVppPostproc::ClearRef(VASurfaceID surf) | |
| { | |
| m_videoSurfaces.ClearReference(surf); | |
| } | |
| void CVppPostproc::Flush() | |
| { | |
| // release all decoded pictures | |
| auto it = m_decodedPics.begin(); | |
| while (it != m_decodedPics.end()) | |
| { | |
| m_config.videoSurfaces->ClearRender(it->videoSurface); | |
| it = m_decodedPics.erase(it); | |
| } | |
| } | |
| bool CVppPostproc::Compatible(EINTERLACEMETHOD method) | |
| { | |
| if (method == VS_INTERLACEMETHOD_VAAPI_BOB || | |
| method == VS_INTERLACEMETHOD_VAAPI_MADI || | |
| method == VS_INTERLACEMETHOD_VAAPI_MACI || | |
| method == VS_INTERLACEMETHOD_NONE) | |
| return true; | |
| return false; | |
| } | |
| bool CVppPostproc::DoesSync() | |
| { | |
| return false; | |
| } | |
| bool CVppPostproc::WantsPic() | |
| { | |
| // need at least 2 for deinterlacing | |
| if (m_videoSurfaces.NumFree() > 1) | |
| return true; | |
| return false; | |
| } | |
| bool CVppPostproc::CheckSuccess(VAStatus status) | |
| { | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI::%s - Error: %s(%d)", __FUNCTION__, vaErrorStr(status), status); | |
| return false; | |
| } | |
| return true; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // FFmpeg Postprocessing | |
| //----------------------------------------------------------------------------- | |
| #define CACHED_BUFFER_SIZE 4096 | |
| CFFmpegPostproc::CFFmpegPostproc() | |
| { | |
| m_cache = NULL; | |
| m_pFilterFrameIn = NULL; | |
| m_pFilterFrameOut = NULL; | |
| m_pFilterGraph = NULL; | |
| m_DVDPic.pts = DVD_NOPTS_VALUE; | |
| m_frametime = 0; | |
| m_lastOutPts = DVD_NOPTS_VALUE; | |
| } | |
| CFFmpegPostproc::~CFFmpegPostproc() | |
| { | |
| Close(); | |
| _aligned_free(m_cache); | |
| m_dllSSE4.Unload(); | |
| av_frame_free(&m_pFilterFrameIn); | |
| av_frame_free(&m_pFilterFrameOut); | |
| } | |
| bool CFFmpegPostproc::PreInit(CVaapiConfig &config, SDiMethods *methods) | |
| { | |
| m_config = config; | |
| bool use_filter = true; | |
| if (!m_dllSSE4.Load()) | |
| { | |
| CLog::Log(LOGERROR,"VAAPI::SupportsFilter failed loading sse4 lib"); | |
| return false; | |
| } | |
| // copying large surfaces via sse4 is a bit slow | |
| // we just return false here as the primary use case the | |
| // sse4 copy method is deinterlacing of max 1080i content | |
| if (m_config.vidWidth > 1920 || m_config.vidHeight > 1088) | |
| return false; | |
| VAImage image; | |
| image.image_id = VA_INVALID_ID; | |
| VASurfaceID surface = config.videoSurfaces->GetAtIndex(0); | |
| VAStatus status = vaDeriveImage(config.dpy, surface, &image); | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGWARNING,"VAAPI::SupportsFilter vaDeriveImage not supported"); | |
| use_filter = false; | |
| } | |
| if (use_filter && (image.format.fourcc != VA_FOURCC_NV12)) | |
| { | |
| CLog::Log(LOGWARNING,"VAAPI::SupportsFilter image format not NV12"); | |
| use_filter = false; | |
| } | |
| if (use_filter && ((image.pitches[0] % 64) || (image.pitches[1] % 64))) | |
| { | |
| CLog::Log(LOGWARNING,"VAAPI::SupportsFilter patches no multiple of 64"); | |
| use_filter = false; | |
| } | |
| if (image.image_id != VA_INVALID_ID) | |
| CheckSuccess(vaDestroyImage(config.dpy,image.image_id)); | |
| if (use_filter) | |
| { | |
| m_cache = (uint8_t*)_aligned_malloc(CACHED_BUFFER_SIZE, 64); | |
| if (methods) | |
| { | |
| methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_DEINTERLACE; | |
| methods->diMethods[methods->numDiMethods++] = VS_INTERLACEMETHOD_RENDER_BOB; | |
| } | |
| } | |
| return use_filter; | |
| } | |
| bool CFFmpegPostproc::Init(EINTERLACEMETHOD method) | |
| { | |
| if (!(m_pFilterGraph = avfilter_graph_alloc())) | |
| { | |
| CLog::Log(LOGERROR, "CFFmpegPostproc::Init - unable to alloc filter graph"); | |
| return false; | |
| } | |
| AVFilter* srcFilter = avfilter_get_by_name("buffer"); | |
| AVFilter* outFilter = avfilter_get_by_name("buffersink"); | |
| std::string args = StringUtils::Format("%d:%d:%d:%d:%d:%d:%d", | |
| m_config.vidWidth, | |
| m_config.vidHeight, | |
| AV_PIX_FMT_NV12, | |
| 1, | |
| 1, | |
| (m_config.aspect.num != 0) ? m_config.aspect.num : 1, | |
| (m_config.aspect.num != 0) ? m_config.aspect.den : 1); | |
| if (avfilter_graph_create_filter(&m_pFilterIn, srcFilter, "src", args.c_str(), NULL, m_pFilterGraph) < 0) | |
| { | |
| CLog::Log(LOGERROR, "CFFmpegPostproc::Init - avfilter_graph_create_filter: src"); | |
| return false; | |
| } | |
| if (avfilter_graph_create_filter(&m_pFilterOut, outFilter, "out", NULL, NULL, m_pFilterGraph) < 0) | |
| { | |
| CLog::Log(LOGERROR, "CFFmpegPostproc::Init - avfilter_graph_create_filter: out"); | |
| return false; | |
| } | |
| enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_NV12, AV_PIX_FMT_NONE }; | |
| if (av_opt_set_int_list(m_pFilterOut, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0) | |
| { | |
| CLog::Log(LOGERROR, "CFFmpegPostproc::Init - failed settings pix formats"); | |
| return false; | |
| } | |
| AVFilterInOut* outputs = avfilter_inout_alloc(); | |
| AVFilterInOut* inputs = avfilter_inout_alloc(); | |
| outputs->name = av_strdup("in"); | |
| outputs->filter_ctx = m_pFilterIn; | |
| outputs->pad_idx = 0; | |
| outputs->next = NULL; | |
| inputs->name = av_strdup("out"); | |
| inputs->filter_ctx = m_pFilterOut; | |
| inputs->pad_idx = 0; | |
| inputs->next = NULL; | |
| if (method == VS_INTERLACEMETHOD_DEINTERLACE) | |
| { | |
| std::string filter; | |
| filter = "yadif=1:-1"; | |
| if (avfilter_graph_parse_ptr(m_pFilterGraph, filter.c_str(), &inputs, &outputs, NULL) < 0) | |
| { | |
| CLog::Log(LOGERROR, "CFFmpegPostproc::Init - avfilter_graph_parse"); | |
| avfilter_inout_free(&outputs); | |
| avfilter_inout_free(&inputs); | |
| return false; | |
| } | |
| avfilter_inout_free(&outputs); | |
| avfilter_inout_free(&inputs); | |
| if (avfilter_graph_config(m_pFilterGraph, NULL) < 0) | |
| { | |
| CLog::Log(LOGERROR, "CFFmpegPostproc::Init - avfilter_graph_config"); | |
| return false; | |
| } | |
| } | |
| else if (method == VS_INTERLACEMETHOD_RENDER_BOB || | |
| method == VS_INTERLACEMETHOD_NONE) | |
| { | |
| if (g_advancedSettings.CanLogComponent(LOGVIDEO)) | |
| CLog::Log(LOGDEBUG, "CFFmpegPostproc::Init - skip deinterlacing"); | |
| avfilter_inout_free(&outputs); | |
| avfilter_inout_free(&inputs); | |
| } | |
| else | |
| { | |
| avfilter_inout_free(&outputs); | |
| avfilter_inout_free(&inputs); | |
| return false; | |
| } | |
| m_diMethod = method; | |
| m_pFilterFrameIn = av_frame_alloc(); | |
| m_pFilterFrameOut = av_frame_alloc(); | |
| return true; | |
| } | |
| bool CFFmpegPostproc::AddPicture(CVaapiDecodedPicture &inPic) | |
| { | |
| VASurfaceID surf = inPic.videoSurface; | |
| VAImage image; | |
| uint8_t *buf; | |
| if (m_DVDPic.pts != DVD_NOPTS_VALUE && inPic.DVDPic.pts != DVD_NOPTS_VALUE) | |
| { | |
| m_frametime = inPic.DVDPic.pts - m_DVDPic.pts; | |
| } | |
| m_DVDPic = inPic.DVDPic; | |
| bool result = false; | |
| if (!CheckSuccess(vaSyncSurface(m_config.dpy, surf))) | |
| goto error; | |
| if (!CheckSuccess(vaDeriveImage(m_config.dpy, surf, &image))) | |
| goto error; | |
| if (!CheckSuccess(vaMapBuffer(m_config.dpy, image.buf, (void**)&buf))) | |
| goto error; | |
| m_pFilterFrameIn->format = AV_PIX_FMT_NV12; | |
| m_pFilterFrameIn->width = m_config.vidWidth; | |
| m_pFilterFrameIn->height = m_config.vidHeight; | |
| m_pFilterFrameIn->linesize[0] = image.pitches[0]; | |
| m_pFilterFrameIn->linesize[1] = image.pitches[1]; | |
| m_pFilterFrameIn->interlaced_frame = (inPic.DVDPic.iFlags & DVP_FLAG_INTERLACED) ? 1 : 0; | |
| m_pFilterFrameIn->top_field_first = (inPic.DVDPic.iFlags & DVP_FLAG_TOP_FIELD_FIRST) ? 1 : 0; | |
| if (inPic.DVDPic.pts == DVD_NOPTS_VALUE) | |
| m_pFilterFrameIn->pkt_pts = AV_NOPTS_VALUE; | |
| else | |
| m_pFilterFrameIn->pkt_pts = (inPic.DVDPic.pts / DVD_TIME_BASE) * AV_TIME_BASE; | |
| av_frame_get_buffer(m_pFilterFrameIn, 64); | |
| uint8_t *src, *dst; | |
| src = buf + image.offsets[0]; | |
| dst = m_pFilterFrameIn->data[0]; | |
| m_dllSSE4.copy_frame(src, dst, m_cache, m_config.vidWidth, m_config.vidHeight, image.pitches[0]); | |
| src = buf + image.offsets[1]; | |
| dst = m_pFilterFrameIn->data[1]; | |
| m_dllSSE4.copy_frame(src, dst, m_cache, image.width, image.height/2, image.pitches[1]); | |
| m_pFilterFrameIn->linesize[0] = image.pitches[0]; | |
| m_pFilterFrameIn->linesize[1] = image.pitches[1]; | |
| m_pFilterFrameIn->data[2] = NULL; | |
| m_pFilterFrameIn->data[3] = NULL; | |
| m_pFilterFrameIn->pkt_size = image.data_size; | |
| CheckSuccess(vaUnmapBuffer(m_config.dpy, image.buf)); | |
| CheckSuccess(vaDestroyImage(m_config.dpy,image.image_id)); | |
| if (m_diMethod == VS_INTERLACEMETHOD_DEINTERLACE) | |
| { | |
| if (av_buffersrc_add_frame(m_pFilterIn, m_pFilterFrameIn) < 0) | |
| { | |
| CLog::Log(LOGERROR, "CFFmpegPostproc::AddPicture - av_buffersrc_add_frame"); | |
| goto error; | |
| } | |
| } | |
| else if (m_diMethod == VS_INTERLACEMETHOD_RENDER_BOB || | |
| m_diMethod == VS_INTERLACEMETHOD_NONE) | |
| { | |
| av_frame_move_ref(m_pFilterFrameOut, m_pFilterFrameIn); | |
| m_step = 0; | |
| } | |
| av_frame_unref(m_pFilterFrameIn); | |
| result = true; | |
| error: | |
| m_config.videoSurfaces->ClearRender(surf); | |
| return result; | |
| } | |
| bool CFFmpegPostproc::Filter(CVaapiProcessedPicture &outPic) | |
| { | |
| outPic.DVDPic = m_DVDPic; | |
| if (m_diMethod == VS_INTERLACEMETHOD_DEINTERLACE) | |
| { | |
| int result; | |
| result = av_buffersink_get_frame(m_pFilterOut, m_pFilterFrameOut); | |
| if(result == AVERROR(EAGAIN) || result == AVERROR_EOF) | |
| return false; | |
| else if(result < 0) | |
| { | |
| CLog::Log(LOGERROR, "CFFmpegPostproc::Filter - av_buffersink_get_frame"); | |
| return false; | |
| } | |
| outPic.DVDPic.iFlags &= ~(DVP_FLAG_TOP_FIELD_FIRST | | |
| DVP_FLAG_REPEAT_TOP_FIELD | | |
| DVP_FLAG_INTERLACED); | |
| } | |
| else if (m_diMethod == VS_INTERLACEMETHOD_RENDER_BOB || | |
| m_diMethod == VS_INTERLACEMETHOD_NONE) | |
| { | |
| if (m_step>0) | |
| return false; | |
| } | |
| m_step++; | |
| outPic.frame = av_frame_clone(m_pFilterFrameOut); | |
| av_frame_unref(m_pFilterFrameOut); | |
| outPic.source = CVaapiProcessedPicture::FFMPEG_SRC; | |
| if(outPic.frame->pkt_pts != AV_NOPTS_VALUE) | |
| { | |
| outPic.DVDPic.pts = (double)outPic.frame->pkt_pts * DVD_TIME_BASE / AV_TIME_BASE; | |
| } | |
| else | |
| outPic.DVDPic.pts = DVD_NOPTS_VALUE; | |
| double pts = outPic.DVDPic.pts; | |
| if (m_lastOutPts != DVD_NOPTS_VALUE && m_lastOutPts == pts) | |
| { | |
| outPic.DVDPic.pts += m_frametime/2; | |
| } | |
| m_lastOutPts = pts; | |
| for (int i = 0; i < 4; i++) | |
| outPic.DVDPic.data[i] = outPic.frame->data[i]; | |
| for (int i = 0; i < 4; i++) | |
| outPic.DVDPic.iLineSize[i] = outPic.frame->linesize[i]; | |
| return true; | |
| } | |
| void CFFmpegPostproc::ClearRef(VASurfaceID surf) | |
| { | |
| } | |
| void CFFmpegPostproc::Close() | |
| { | |
| if (m_pFilterGraph) | |
| { | |
| avfilter_graph_free(&m_pFilterGraph); | |
| } | |
| } | |
| void CFFmpegPostproc::Flush() | |
| { | |
| Close(); | |
| Init(m_diMethod); | |
| m_DVDPic.pts = DVD_NOPTS_VALUE; | |
| m_frametime = 0; | |
| m_lastOutPts = DVD_NOPTS_VALUE; | |
| } | |
| bool CFFmpegPostproc::Compatible(EINTERLACEMETHOD method) | |
| { | |
| if (method == VS_INTERLACEMETHOD_DEINTERLACE) | |
| return true; | |
| else if (method == VS_INTERLACEMETHOD_RENDER_BOB) | |
| return true; | |
| else if (method == VS_INTERLACEMETHOD_NONE && | |
| !CSettings::GetInstance().GetBool(CSettings::SETTING_VIDEOPLAYER_PREFERVAAPIRENDER)) | |
| return true; | |
| return false; | |
| } | |
| bool CFFmpegPostproc::DoesSync() | |
| { | |
| return true; | |
| } | |
| bool CFFmpegPostproc::CheckSuccess(VAStatus status) | |
| { | |
| if (status != VA_STATUS_SUCCESS) | |
| { | |
| CLog::Log(LOGERROR, "VAAPI - Error: %s(%d)", vaErrorStr(status), status); | |
| return false; | |
| } | |
| return true; | |
| } | |
| #endif |