MythTV  master
mythvdpauinterop.cpp
Go to the documentation of this file.
1 // MythTV
2 #include "mythcorecontext.h"
3 #include "videocolourspace.h"
4 #include "mythvdpauhelper.h"
5 #include "mythvdpauinterop.h"
6 
7 #define LOC QString("VDPAUInterop: ")
8 
10 {
11  if (Context)
12  return new MythVDPAUInterop(Context, CodecId);
13  return nullptr;
14 }
15 
17 {
18  if ((FMT_VDPAU != Format) || !gCoreContext->IsUIThread())
19  return Unsupported;
20 
22  if (!context)
23  return Unsupported;
24 
25  if (context->hasExtension("GL_NV_vdpau_interop") && MythVDPAUHelper::HaveVDPAU())
26  return VDPAU;
27  return Unsupported;
28 }
29 
31  : MythOpenGLInterop(Context, VDPAU),
32  m_codec(CodecId)
33 {
34 }
35 
37 {
38  if (!m_context)
39  return;
40 
41  if (m_colourSpace)
43 
44  OpenGLLocker locker(m_context);
46  Cleanup();
47  delete m_helper;
48 }
49 
51 {
52  OpenGLLocker locker(m_context);
53 
54  // per the spec, this should automatically release any registered
55  // and mapped surfaces
56  if (m_finiNV)
57  m_finiNV();
58 
59  if (m_helper && !m_preempted)
60  {
63  }
64 
65  m_mixer = 0;
66  m_outputSurface = 0;
69  m_mixerSize = QSize();
70  m_mixerChroma = VDP_CHROMA_TYPE_420;
71 
73 }
74 
76 {
77  while (!m_referenceFrames.isEmpty())
78  {
79  AVBufferRef* ref = m_referenceFrames.takeLast();
80  av_buffer_unref(&ref);
81  }
82 }
83 
85 {
86  if (!Buffer)
87  return;
88 
89  // don't retain twice for double rate
90  if (!m_referenceFrames.empty() &&
91  (static_cast<VdpVideoSurface>(reinterpret_cast<uintptr_t>(m_referenceFrames[0]->data)) ==
92  static_cast<VdpVideoSurface>(reinterpret_cast<uintptr_t>(Buffer->data))))
93  {
94  return;
95  }
96 
97  m_referenceFrames.push_front(av_buffer_ref(Buffer));
98 
99  // release old frames
100  while (m_referenceFrames.size() > 3)
101  {
102  AVBufferRef* ref = m_referenceFrames.takeLast();
103  av_buffer_unref(&ref);
104  }
105 }
106 
107 bool MythVDPAUInterop::InitNV(AVVDPAUDeviceContext* DeviceContext)
108 {
109  if (!DeviceContext || !m_context)
110  return false;
111 
112  if (m_initNV && m_finiNV && m_registerNV && m_accessNV && m_mapNV &&
113  m_helper && m_helper->IsValid())
114  return true;
115 
116  OpenGLLocker locker(m_context);
117  m_initNV = reinterpret_cast<MYTH_VDPAUINITNV>(m_context->GetProcAddress("glVDPAUInitNV"));
118  m_finiNV = reinterpret_cast<MYTH_VDPAUFININV>(m_context->GetProcAddress("glVDPAUFiniNV"));
119  m_registerNV = reinterpret_cast<MYTH_VDPAUREGOUTSURFNV>(m_context->GetProcAddress("glVDPAURegisterOutputSurfaceNV"));
120  m_accessNV = reinterpret_cast<MYTH_VDPAUSURFACCESSNV>(m_context->GetProcAddress("glVDPAUSurfaceAccessNV"));
121  m_mapNV = reinterpret_cast<MYTH_VDPAUMAPSURFNV>(m_context->GetProcAddress("glVDPAUMapSurfacesNV"));
122 
123  delete m_helper;
124  m_helper = nullptr;
125 
127  {
128  m_helper = new MythVDPAUHelper(DeviceContext);
129  if (m_helper->IsValid())
130  {
131  connect(m_helper, &MythVDPAUHelper::DisplayPreempted, this, &MythVDPAUInterop::DisplayPreempted, Qt::DirectConnection);
132  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Ready");
133  return true;
134  }
135  delete m_helper;
136  m_helper = nullptr;
137  }
138 
139  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to retrieve procs");
140  return false;
141 }
142 
143 bool MythVDPAUInterop::InitVDPAU(AVVDPAUDeviceContext* DeviceContext, VdpVideoSurface Surface,
144  MythDeintType Deint, bool DoubleRate)
145 {
146  if (!m_helper || !m_context || !Surface || !DeviceContext)
147  return false;
148 
149  VdpChromaType chroma = VDP_CHROMA_TYPE_420;
150  QSize size = m_helper->GetSurfaceParameters(Surface, chroma);
151 
152  if (m_mixer && (chroma != m_mixerChroma || size != m_mixerSize || Deint != m_deinterlacer))
153  Cleanup();
154 
155  if (!m_mixer)
156  {
157  m_mixer = m_helper->CreateMixer(size, chroma, Deint);
158  m_deinterlacer = Deint;
159  m_mixerChroma = chroma;
160  m_mixerSize = size;
161  if (DEINT_NONE != m_deinterlacer)
162  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Setup deinterlacer '%1'")
163  .arg(DeinterlacerName(m_deinterlacer | DEINT_DRIVER, DoubleRate, FMT_VDPAU)));
164  }
165 
166  if (!m_outputSurface)
167  {
169  if (m_outputSurface)
170  {
171  vector<QSize> sizes;
172  sizes.push_back(size);
173  vector<MythVideoTexture*> textures =
175  if (textures.empty())
176  return false;
177  m_openglTextures.insert(DUMMY_INTEROP_ID, textures);
178  }
179  }
180 
181  if (m_mixer && m_outputSurface)
182  {
183  if (!m_outputSurfaceReg && !m_openglTextures.empty())
184  {
185  // This may fail if another interop is registered (but should not happen if
186  // decoder creataion is working properly). Subsequent surface
187  // registration will then fail and we will try again on the next pass
188  m_initNV(reinterpret_cast<void*>(static_cast<uintptr_t>(DeviceContext->device)),
189  reinterpret_cast<const void*>(DeviceContext->get_proc_address));
190  GLuint texid = m_openglTextures[DUMMY_INTEROP_ID][0]->m_textureId;
191  m_outputSurfaceReg = m_registerNV(reinterpret_cast<void*>(static_cast<uintptr_t>(m_outputSurface)),
192  QOpenGLTexture::Target2D, 1, &texid);
193  // this happens if there is another interop registered to this OpenGL context
194  if (!m_outputSurfaceReg)
195  {
196  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to register VdpOutputSurface. Will retry.");
197  }
198  else
199  {
200  m_accessNV(m_outputSurfaceReg, QOpenGLBuffer::ReadOnly);
202  }
203  }
204  return true;
205  }
206 
207  return (m_mixer != 0U) && (m_outputSurface != 0U);
208 }
209 
218  VideoColourSpace *ColourSpace,
219  VideoFrame *Frame,
220  FrameScanType Scan)
221 {
222  vector<MythVideoTexture*> result;
223  if (!Frame)
224  return result;
225 
226  if (m_preempted)
227  {
228  // Don't spam the logs with this warning
229  if (!m_preemptedWarning)
230  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Display preempted. Decoder needs to be reset");
231  m_preemptedWarning = true;
232  return result;
233  }
234 
235  if (Context && (Context != m_context))
236  LOG(VB_GENERAL, LOG_WARNING, LOC + "Mismatched OpenGL contexts");
237 
238  // Check size
239  QSize surfacesize(Frame->width, Frame->height);
240  if (m_openglTextureSize != surfacesize)
241  {
242  if (!m_openglTextureSize.isEmpty())
243  LOG(VB_GENERAL, LOG_WARNING, LOC + "Video texture size changed!");
244  m_openglTextureSize = surfacesize;
245  }
246 
247  // Lock
248  OpenGLLocker locker(m_context);
249 
250  // Retrieve hardware frames context and AVVDPAUDeviceContext
251  if ((Frame->pix_fmt != AV_PIX_FMT_VDPAU) || (Frame->codec != FMT_VDPAU) ||
252  !Frame->buf || !Frame->priv[1])
253  return result;
254 
255  auto* buffer = reinterpret_cast<AVBufferRef*>(Frame->priv[1]);
256  if (!buffer || (buffer && !buffer->data))
257  return result;
258  auto* frames = reinterpret_cast<AVHWFramesContext*>(buffer->data);
259  if (!frames || (frames && !frames->device_ctx))
260  return result;
261  auto *devicecontext = reinterpret_cast<AVVDPAUDeviceContext*>(frames->device_ctx->hwctx);
262  if (!devicecontext)
263  return result;
264 
265  // Initialise
266  if (!InitNV(devicecontext))
267  return result;
268 
269  // Retrieve surface - we need its size to create the mixer and output surface
270  auto surface = static_cast<VdpVideoSurface>(reinterpret_cast<uintptr_t>(Frame->buf));
271  if (!surface)
272  return result;
273 
274  // Workaround HEVC interlaced bug
275  // VDPAU driver hangs if we try to render progressive HEVC as interlaced (tested with version 418.56)
276  // FFmpeg clearly currently has issues with interlaced HEVC (https://trac.ffmpeg.org/ticket/4141).
277  // Streams are always return with the field height.
278  // Deinterlacing does work with (some?) HEVC material flagged as interlaced.
279  if ((kCodec_HEVC_VDPAU == m_codec) && is_interlaced(Scan) && !Frame->interlaced_frame)
280  {
281  // This should only be logged a couple of times before the scan is detected as progressive
282  LOG(VB_GENERAL, LOG_INFO, LOC + "Ignoring scan for non-interlaced HEVC frame");
283  Scan = kScan_Progressive;
284  }
285 
286  // Check for deinterlacing - VDPAU deinterlacers trump all others as we can only
287  // deinterlace VDPAU frames here. So accept any deinterlacer.
288  // N.B. basic deinterlacing requires no additional setup and is managed with
289  // the field/frame parameter
290  bool doublerate = true;
291  MythDeintType deinterlacer = DEINT_BASIC;
292  if (is_interlaced(Scan))
293  {
295  DEINT_ALL);
296  if (!driverdeint)
297  {
298  doublerate = false;
300  }
301 
302  if (driverdeint)
303  {
304  Frame->deinterlace_inuse = driverdeint | DEINT_DRIVER;
305  Frame->deinterlace_inuse2x = doublerate;
306  deinterlacer = driverdeint;
307  }
308  }
309 
310  if ((deinterlacer == DEINT_HIGH) || (deinterlacer == DEINT_MEDIUM))
311  {
312  if (abs(Frame->frameCounter - m_discontinuityCounter) > 1)
314  RotateReferenceFrames(reinterpret_cast<AVBufferRef*>(Frame->priv[0]));
315  }
316  else
317  {
319  }
320  m_discontinuityCounter = Frame->frameCounter;
321 
322  // We need a mixer, an output surface and mapped texture
323  if (!InitVDPAU(devicecontext, surface, deinterlacer, doublerate))
324  return result;
325 
326  // Update colourspace and initialise on first frame - after mixer is created
327  if (ColourSpace)
328  {
329  if (!m_colourSpace)
330  {
331  if (m_helper->IsFeatureAvailable(VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX))
332  {
334  connect(ColourSpace, &VideoColourSpace::Updated, this, &MythVDPAUInterop::UpdateColourSpace);
335  }
336  else
337  {
338  // N.B. CSC matrix support should always be available so there is no fallback.
340  LOG(VB_GENERAL, LOG_WARNING, LOC + "No VDPAU CSC matrix support");
341  }
342 
343  ColourSpace->IncrRef();
344  m_colourSpace = ColourSpace;
345  }
346  ColourSpace->UpdateColourSpace(Frame);
347  }
348 
349  // Render surface
350  m_helper->MixerRender(m_mixer, surface, m_outputSurface, Scan,
351  static_cast<int>(Frame->interlaced_reversed ? !Frame->top_field_first :
352  Frame->top_field_first), m_referenceFrames);
354 }
355 
356 void MythVDPAUInterop::UpdateColourSpace(bool /*PrimariesChanged*/)
357 {
358  if (!m_mixer || !m_context || !m_colourSpace || !m_helper)
359  return;
360 
361  OpenGLLocker locker(m_context);
363 }
364 
366 {
367  // N.B. Pre-emption is irrecoverable here. We ensure the error state is recorded
368  // and when AvFormatDecoder/MythCodecContext hit a problem, IsPreempted is checked.
369  // The decoder context is then released, along with the associated interop
370  // class (i.e. this) and a new interop is created.
371  LOG(VB_GENERAL, LOG_INFO, LOC + "VDPAU display preempted");
372  m_preempted = true;
373 }
374 
376 {
377  return m_preempted;
378 }
VideoColourSpace contains a QMatrix4x4 that can convert YCbCr data to RGB.
static vector< MythVideoTexture * > CreateTextures(MythRenderOpenGL *Context, VideoFrameType Type, VideoFrameType Format, vector< QSize > Sizes, GLenum Target=QOpenGLTexture::Target2D)
Create a set of textures suitable for the given Type and Format.
MythDeintType GetDoubleRateOption(const VideoFrame *Frame, MythDeintType Type, MythDeintType Override)
Definition: mythframe.cpp:847
long long m_discontinuityCounter
MythDeintType m_deinterlacer
QHash< QString, Action * > Context
Definition: action.h:77
MythCodecID
Definition: mythcodecid.h:10
MYTH_VDPAUFININV m_finiNV
MYTH_VDPAUREGOUTSURFNV m_registerNV
void DeleteOutputSurface(VdpOutputSurface Surface)
QFunctionPointer GetProcAddress(const QString &Proc) const
#define DUMMY_INTEROP_ID
VdpOutputSurface CreateOutputSurface(QSize Size)
FrameScanType
Definition: videoouttypes.h:78
VideoFrameType
Definition: mythframe.h:23
MYTH_VDPAUMAPSURFNV m_mapNV
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void DisplayPreempted(void)
#define LOC
MythCodecID m_codec
MYTH_VDPAUSURFACCESSNV m_accessNV
MythDeintType
Definition: mythframe.h:120
QString DeinterlacerName(MythDeintType Deint, bool DoubleRate, VideoFrameType Format)
Return a user friendly description of the given deinterlacer.
Definition: mythavutil.cpp:114
QVector< AVBufferRef * > m_referenceFrames
vector< MythVideoTexture * > Acquire(MythRenderOpenGL *Context, VideoColourSpace *ColourSpace, VideoFrame *Frame, FrameScanType Scan) override
Map VDPAU video surfaces to an OpenGL texture.
void DeleteMixer(VdpVideoMixer Mixer)
virtual int IncrRef(void)
Increments reference count.
VdpVideoMixer CreateMixer(QSize Size, VdpChromaType ChromaType=VDP_CHROMA_TYPE_420, MythDeintType Deinterlacer=DEINT_BASIC)
MYTH_VDPAUINITNV m_initNV
VideoColourSpace * m_colourSpace
void SetCSCMatrix(VdpVideoMixer Mixer, VideoColourSpace *ColourSpace)
void UpdateColourSpace(bool PrimariesChanged)
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
VdpVideoMixer m_mixer
bool IsPreempted(void) const
#define ALL_PICTURE_ATTRIBUTES
void DisplayPreempted(void)
static MythVDPAUInterop * Create(MythRenderOpenGL *Context, MythCodecID CodecId)
void Updated(bool PrimariesChanged)
void MixerRender(VdpVideoMixer Mixer, VdpVideoSurface Source, VdpOutputSurface Dest, FrameScanType Scan, int TopFieldFirst, QVector< AVBufferRef * > &Frames)
static bool HaveVDPAU(void)
MythVDPAUSurfaceNV m_outputSurfaceReg
A simple wrapper around VDPAU functionality.
bool IsFeatureAvailable(uint Feature)
bool InitVDPAU(AVVDPAUDeviceContext *DeviceContext, VdpVideoSurface Surface, MythDeintType Deint, bool DoubleRate)
void SetSupportedAttributes(PictureAttributeSupported Supported)
Enable the given set of picture attributes.
Definition: surface.h:4
virtual void DeleteTextures(void)
MythRenderOpenGL * m_context
bool IsValid(void) const
static MythRenderOpenGL * GetOpenGLRender(void)
VdpOutputSurface m_outputSurface
MythVDPAUInterop(MythRenderOpenGL *Context, MythCodecID CodecID)
QHash< unsigned long long, vector< MythVideoTexture * > > m_openglTextures
static Type GetInteropType(VideoFrameType Format)
void RotateReferenceFrames(AVBufferRef *Buffer)
~MythVDPAUInterop() override
bool InitNV(AVVDPAUDeviceContext *DeviceContext)
bool is_interlaced(FrameScanType Scan)
void CleanupDeinterlacer(void)
MythVDPAUHelper * m_helper
QSize GetSurfaceParameters(VdpVideoSurface Surface, VdpChromaType &Chroma)
MythDeintType GetSingleRateOption(const VideoFrame *Frame, MythDeintType Type, MythDeintType Override)
Definition: mythframe.cpp:834
bool UpdateColourSpace(const VideoFrame *Frame)
Set the current colourspace to use.
VdpChromaType m_mixerChroma
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23