MythTV master
mythvaapiinterop.cpp
Go to the documentation of this file.
1#include "mythvaapiinterop.h"
2
3#include <QtGlobal>
4#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
5#include <QtEnvironmentVariables>
6#endif
7
8// MythTV
9#include "libmythbase/mythconfig.h"
12
13#if CONFIG_DRM_VIDEO
15#endif
16
17#include "mythvideoout.h"
18#include "mythplayerui.h"
20#include "fourcc.h"
21#if CONFIG_VAAPI_DRM
22#include "mythvaapidrminterop.h"
23#endif
24#if CONFIG_VAAPI_GLX
25#include "mythvaapiglxinterop.h"
26#endif
27
28extern "C" {
29#include "libavfilter/buffersrc.h"
30#include "libavfilter/buffersink.h"
31#include "libavutil/hwcontext_vaapi.h"
32}
33
34#define LOC QString("VAAPIInterop: ")
35
53{
54 if (!Context)
55 return;
56
57 OpenGLLocker locker(Context);
58 [[maybe_unused]] bool egl = Context->IsEGL();
59 [[maybe_unused]] bool opengles = Context->isOpenGLES();
60 [[maybe_unused]] bool wayland = qgetenv("XDG_SESSION_TYPE").contains("wayland");
61
62 // best first
64
65#if CONFIG_DRM_VIDEO
67 vaapitypes.emplace_back(DRM_DRMPRIME);
68#endif
69
70#if CONFIG_VAAPI_DRM && CONFIG_EGL
71 // zero copy
72 if (egl && MythVAAPIInteropDRM::IsSupported(Context))
73 vaapitypes.emplace_back(GL_VAAPIEGLDRM);
74#endif
75#if CONFIG_VAAPI_GLX
76# if CONFIG_VAAPI_X11
77 // 1x copy
78 if (!egl && !wayland && MythVAAPIInteropGLXPixmap::IsSupported(Context))
79 vaapitypes.emplace_back(GL_VAAPIGLXPIX);
80# endif
81 // 2x copy
82 if (!egl && !opengles && !wayland)
83 vaapitypes.emplace_back(GL_VAAPIGLXCOPY);
84#endif
85
86 if (!vaapitypes.empty())
87 Types[FMT_VAAPI] = vaapitypes;
88}
89
91{
92 if (!(Player && Context))
93 return nullptr;
94
95 const auto & types = Player->GetInteropTypes();
96 if (const auto & vaapi = types.find(FMT_VAAPI); vaapi != types.cend())
97 {
98 for ([[maybe_unused]] auto type : vaapi->second)
99 {
100#if CONFIG_VAAPI_DRM && CONFIG_EGL
101 if ((type == GL_VAAPIEGLDRM) || (type == DRM_DRMPRIME))
102 return new MythVAAPIInteropDRM(Player, Context, type);
103#endif
104#if CONFIG_VAAPI_GLX
105# if CONFIG_VAAPI_X11
106 if (type == GL_VAAPIGLXPIX)
107 return new MythVAAPIInteropGLXPixmap(Player, Context);
108# endif
109 if (type == GL_VAAPIGLXCOPY)
110 return new MythVAAPIInteropGLXCopy(Player, Context);
111#endif
112 }
113 }
114 return nullptr;
115}
116
125 : MythOpenGLInterop(Context, Type, Player)
126{
127}
128
130{
132 if (m_vaDisplay)
133 if (vaTerminate(m_vaDisplay) != VA_STATUS_SUCCESS)
134 LOG(VB_GENERAL, LOG_WARNING, LOC + "Error closing VAAPI display");
135}
136
138{
139 return m_vaDisplay;
140}
141
143{
144 return m_vaVendor;
145}
146
148{
149 if (!m_vaDisplay)
150 return;
151 int major = 0;
152 int minor = 0;
153 if (vaInitialize(m_vaDisplay, &major, &minor) != VA_STATUS_SUCCESS)
154 {
155 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to initialise VAAPI display");
156 vaTerminate(m_vaDisplay);
157 m_vaDisplay = nullptr;
158 }
159 else
160 {
161 m_vaVendor = vaQueryVendorString(m_vaDisplay);
162 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Created VAAPI %1.%2 display for %3 (%4)")
163 .arg(major).arg(minor).arg(TypeToString(m_type), m_vaVendor));
164 }
165}
166
168{
169 if (m_filterGraph)
170 LOG(VB_GENERAL, LOG_INFO, LOC + "Destroying VAAPI deinterlacer");
171 avfilter_graph_free(&m_filterGraph);
172 m_filterGraph = nullptr;
173 m_filterSink = nullptr;
174 m_filterSource = nullptr;
176 m_deinterlacer2x = false;
177 m_firstField = true;
180 av_buffer_unref(&m_vppFramesContext);
181}
182
184{
185 VASurfaceID result = 0;
186 if (!Frame)
187 return result;
188
189 if ((Frame->m_pixFmt != AV_PIX_FMT_VAAPI) || (Frame->m_type != FMT_VAAPI) ||
190 !Frame->m_buffer || !Frame->m_priv[1])
191 return result;
192
193 // Sanity check the context
194 if (m_openglContext != Context)
195 {
196 LOG(VB_GENERAL, LOG_ERR, LOC + "Mismatched OpenGL contexts!");
197 return result;
198 }
199
200 // Check size
201 QSize surfacesize(Frame->m_width, Frame->m_height);
202 if (m_textureSize != surfacesize)
203 {
204 if (!m_textureSize.isEmpty())
205 LOG(VB_GENERAL, LOG_WARNING, LOC + "Video texture size changed!");
206 m_textureSize = surfacesize;
207 }
208
209 // Retrieve surface
210 auto id = static_cast<VASurfaceID>(reinterpret_cast<uintptr_t>(Frame->m_buffer));
211 if (id)
212 result = id;
213 return result;
214}
215
216bool MythVAAPIInterop::SetupDeinterlacer(MythDeintType Deinterlacer, bool DoubleRate,
217 AVBufferRef *FramesContext,
218 int Width, int Height,
219 // Outputs
220 AVFilterGraph *&Graph,
221 AVFilterContext *&Source,
222 AVFilterContext *&Sink)
223{
224 if (!FramesContext)
225 {
226 LOG(VB_GENERAL, LOG_ERR, LOC + "No hardware frames context");
227 return false;
228 }
229
230 int ret = 0;
231 QString deinterlacer = "bob";
232 if (DEINT_MEDIUM == Deinterlacer)
233 deinterlacer = "motion_adaptive";
234 else if (DEINT_HIGH == Deinterlacer)
235 deinterlacer = "default";
236
237 // N.B. set auto to 0 otherwise we confuse playback if VAAPI does not deinterlace
238 QString filters = QString("deinterlace_vaapi=mode=%1:rate=%2:auto=0")
239 .arg(deinterlacer, DoubleRate ? "field" : "frame");
240 const AVFilter *buffersrc = avfilter_get_by_name("buffer");
241 const AVFilter *buffersink = avfilter_get_by_name("buffersink");
242 AVFilterInOut *outputs = avfilter_inout_alloc();
243 AVFilterInOut *inputs = avfilter_inout_alloc();
244
245 // Automatically clean up memory allocation at function exit
246 auto cleanup_fn = [&](int */*x*/) {
247 if (ret < 0) {
248 avfilter_free(Source);
249 Source = nullptr;
250 avfilter_graph_free(&Graph);
251 Graph = nullptr;
252 }
253 avfilter_inout_free(&inputs);
254 avfilter_inout_free(&outputs);
255 };
256 std::unique_ptr<int,decltype(cleanup_fn)> cleanup { &ret, cleanup_fn };
257
258 Graph = avfilter_graph_alloc();
259 if (!outputs || !inputs || !Graph)
260 {
261 ret = AVERROR(ENOMEM);
262 return false;
263 }
264
265 /* buffer video source: the decoded frames from the decoder will be inserted here. */
266 Source = avfilter_graph_alloc_filter(Graph, buffersrc, "in");
267 if (Source == nullptr)
268 {
269 ret = AVERROR(ENOMEM);
270 LOG(VB_GENERAL, LOG_ERR, "avfilter_graph_alloc_filter() failed to allocate memory.");
271 return false;
272 }
273
274 AVBufferSrcParameters* params = av_buffersrc_parameters_alloc();
275 if (params == nullptr)
276 {
277 ret = AVERROR(ENOMEM);
278 LOG(VB_GENERAL, LOG_ERR, "av_buffersrc_parameters_alloc() failed to allocate memory.");
279 return false;
280 }
281 params->format = AV_PIX_FMT_VAAPI;
282 params->time_base = {.num=1, .den=1};
283 params->width = Width;
284 params->height = Height;
285 params->hw_frames_ctx = FramesContext;
286 ret = av_buffersrc_parameters_set(Source, params);
287 av_freep(reinterpret_cast<void*>(&params));
288 if (ret < 0)
289 {
290 LOG(VB_GENERAL, LOG_ERR, LOC + "av_buffersrc_parameters_set failed");
291 return false;
292 }
293
294 ret = avfilter_init_str(Source, nullptr);
295 if (ret < 0)
296 {
297 LOG(VB_GENERAL, LOG_ERR, LOC + "avfilter_init_str() failed for buffer source");
298 return false;
299 }
300
301 /* buffer video sink: to terminate the filter chain. */
302 ret = avfilter_graph_create_filter(&Sink, buffersink, "out",
303 nullptr, nullptr, Graph);
304 if (ret < 0)
305 {
306 LOG(VB_GENERAL, LOG_ERR, LOC + "avfilter_graph_create_filter failed for buffer sink");
307 return false;
308 }
309
310 /*
311 * Set the endpoints for the filter graph. The filter_graph will
312 * be linked to the graph described by filters_descr.
313 */
314
315 /*
316 * The buffer source output must be connected to the input pad of
317 * the first filter described by filters_descr; since the first
318 * filter input label is not specified, it is set to "in" by
319 * default.
320 */
321 outputs->name = av_strdup("in");
322 outputs->filter_ctx = Source;
323 outputs->pad_idx = 0;
324 outputs->next = nullptr;
325
326 /*
327 * The buffer sink input must be connected to the output pad of
328 * the last filter described by filters_descr; since the last
329 * filter output label is not specified, it is set to "out" by
330 * default.
331 */
332 inputs->name = av_strdup("out");
333 inputs->filter_ctx = Sink;
334 inputs->pad_idx = 0;
335 inputs->next = nullptr;
336
337 ret = avfilter_graph_parse_ptr(Graph, filters.toLocal8Bit().constData(),
338 &inputs, &outputs, nullptr);
339 if (ret < 0)
340 {
341 LOG(VB_GENERAL, LOG_ERR, LOC + QString("avfilter_graph_parse_ptr failed for %1")
342 .arg(filters));
343 return false;
344 }
345
346 ret = avfilter_graph_config(Graph, nullptr);
347 if (ret < 0)
348 {
349 LOG(VB_GENERAL, LOG_ERR, LOC +
350 QString("VAAPI deinterlacer config failed - '%1' unsupported?").arg(deinterlacer));
351 return false;
352 }
353
354 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Created deinterlacer '%1'")
355 .arg(MythVideoFrame::DeinterlacerName(Deinterlacer | DEINT_DRIVER, DoubleRate, FMT_VAAPI)));
356
357 return true;
358}
359
360VASurfaceID MythVAAPIInterop::Deinterlace(MythVideoFrame *Frame, VASurfaceID Current, FrameScanType Scan)
361{
362 VASurfaceID result = Current;
363 if (!Frame)
364 return result;
365
366 while (!m_filterError && is_interlaced(Scan))
367 {
368 // N.B. for DRM the use of a shader deint is checked before we get here
369 MythDeintType deinterlacer = DEINT_NONE;
370 bool doublerate = true;
371 // no CPU or GLSL deinterlacing so pick up these options as well
372 // N.B. Override deinterlacer_allowed to pick up any preference
373 MythDeintType doublepref = Frame->GetDoubleRateOption(DEINT_DRIVER | DEINT_SHADER | DEINT_CPU, DEINT_ALL);
374 MythDeintType singlepref = Frame->GetSingleRateOption(DEINT_DRIVER | DEINT_SHADER | DEINT_CPU, DEINT_ALL);
375
376 if (doublepref)
377 {
378 deinterlacer = doublepref;
379 }
380 else if (singlepref)
381 {
382 deinterlacer = singlepref;
383 doublerate = false;
384 }
385
386 if ((m_deinterlacer == deinterlacer) && (m_deinterlacer2x == doublerate))
387 break;
388
390
391 if (deinterlacer != DEINT_NONE)
392 {
393 auto* frames = reinterpret_cast<AVBufferRef*>(Frame->m_priv[1]);
394 if (!frames)
395 break;
396
397 AVBufferRef* hwdeviceref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
398 if (!hwdeviceref)
399 break;
400
401 auto* hwdevicecontext = reinterpret_cast<AVHWDeviceContext*>(hwdeviceref->data);
402 hwdevicecontext->free = [](AVHWDeviceContext* /*unused*/) { LOG(VB_PLAYBACK, LOG_INFO, LOC + "VAAPI VPP device context finished"); };
403
404 auto *vaapidevicectx = reinterpret_cast<AVVAAPIDeviceContext*>(hwdevicecontext->hwctx);
405 vaapidevicectx->display = m_vaDisplay; // re-use the existing display
406
407 if (av_hwdevice_ctx_init(hwdeviceref) < 0)
408 {
409 av_buffer_unref(&hwdeviceref);
410 m_filterError = true;
411 break;
412 }
413
414 AVBufferRef *newframes = av_hwframe_ctx_alloc(hwdeviceref);
415 if (!newframes)
416 {
417 m_filterError = true;
418 av_buffer_unref(&hwdeviceref);
419 break;
420 }
421
422 auto* dstframes = reinterpret_cast<AVHWFramesContext*>(newframes->data);
423 auto* srcframes = reinterpret_cast<AVHWFramesContext*>(frames->data);
424
425 m_filterWidth = srcframes->width;
426 m_filterHeight = srcframes->height;
427 static constexpr int kVppPoolSize = 2; // seems to be enough
428 dstframes->sw_format = srcframes->sw_format;
429 dstframes->width = m_filterWidth;
430 dstframes->height = m_filterHeight;
431 dstframes->initial_pool_size = kVppPoolSize;
432 dstframes->format = AV_PIX_FMT_VAAPI;
433 dstframes->free = [](AVHWFramesContext* /*unused*/) { LOG(VB_PLAYBACK, LOG_INFO, LOC + "VAAPI VPP frames context finished"); };
434
435 if (av_hwframe_ctx_init(newframes) < 0)
436 {
437 m_filterError = true;
438 av_buffer_unref(&hwdeviceref);
439 av_buffer_unref(&newframes);
440 break;
441 }
442
443 m_vppFramesContext = newframes;
444 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New VAAPI frame pool with %1 %2x%3 surfaces")
445 .arg(kVppPoolSize).arg(m_filterWidth).arg(m_filterHeight));
446 av_buffer_unref(&hwdeviceref);
447
448 if (!MythVAAPIInterop::SetupDeinterlacer(deinterlacer, doublerate, m_vppFramesContext,
451 {
452 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to create VAAPI deinterlacer %1 - disabling")
453 .arg(MythVideoFrame::DeinterlacerName(deinterlacer | DEINT_DRIVER, doublerate, FMT_VAAPI)));
455 m_filterError = true;
456 }
457 else
458 {
459 m_deinterlacer = deinterlacer;
460 m_deinterlacer2x = doublerate;
462 break;
463 }
464 }
465 break;
466 }
467
468 if (!is_interlaced(Scan) && m_deinterlacer)
470
471 if (m_deinterlacer)
472 {
473 // deinterlacing the pause frame repeatedly is unnecessary and, due to the
474 // buffering in the VAAPI frames context, causes the image to 'jiggle' when using
475 // double rate deinterlacing. If we are confident this is a pause frame we have seen,
476 // return the last deinterlaced frame.
477 if (Frame->m_pauseFrame && m_lastFilteredFrame && (m_lastFilteredFrameCount == Frame->m_frameCounter))
478 return m_lastFilteredFrame;
479
480 Frame->m_deinterlaceInuse = m_deinterlacer | DEINT_DRIVER;
481 Frame->m_deinterlaceInuse2x = m_deinterlacer2x;
482
483 // 'pump' the filter with frames until it starts returning usefull output.
484 // This minimises discontinuities at start up (where we would otherwise
485 // show a couple of progressive frames first) and memory consumption for the DRM
486 // interop as we only cache OpenGL textures for the deinterlacer's frame
487 // pool.
488 int retries = 3;
489 while ((result == Current) && retries--)
490 {
491 while (true)
492 {
493 int ret = 0;
494 MythAVFrame sinkframe;
495 sinkframe->format = AV_PIX_FMT_VAAPI;
496
497 // only ask for a frame if we are expecting another
499 {
500 ret = av_buffersink_get_frame(m_filterSink, sinkframe);
501 if (ret >= 0)
502 {
503 // we have a filtered frame
504 result = m_lastFilteredFrame = static_cast<VASurfaceID>(reinterpret_cast<uintptr_t>(sinkframe->data[3]));
505 m_lastFilteredFrameCount = Frame->m_frameCounter;
506 m_firstField = true;
507 break;
508 }
509 if (ret != AVERROR(EAGAIN))
510 break;
511 }
512
513 // add another frame
514 MythAVFrame sourceframe;
515 sourceframe->flags &= ~AV_FRAME_FLAG_TOP_FIELD_FIRST;
516 if (Frame->m_interlacedReverse ^ Frame->m_topFieldFirst)
517 {
518 sourceframe->flags |= AV_FRAME_FLAG_TOP_FIELD_FIRST;
519 }
520 sourceframe->flags |= AV_FRAME_FLAG_INTERLACED;
521 sourceframe->data[3] = Frame->m_buffer;
522 auto* buffer = reinterpret_cast<AVBufferRef*>(Frame->m_priv[0]);
523 sourceframe->buf[0] = buffer ? av_buffer_ref(buffer) : nullptr;
524 sourceframe->width = m_filterWidth;
525 sourceframe->height = m_filterHeight;
526 sourceframe->format = AV_PIX_FMT_VAAPI;
527 // N.B. This is required after changes in FFmpeg. Using the vpp
528 // frames context doesn't seem correct - but using the 'master'
529 // context does not work
530 sourceframe->hw_frames_ctx = av_buffer_ref(m_vppFramesContext);
531 ret = av_buffersrc_add_frame(m_filterSource, sourceframe);
532 sourceframe->data[3] = nullptr;
533 sourceframe->buf[0] = nullptr;
534 if (ret < 0)
535 break;
536
537 // try again
538 ret = av_buffersink_get_frame(m_filterSink, sinkframe);
539 if (ret >= 0)
540 {
541 // we have a filtered frame
542 result = m_lastFilteredFrame = static_cast<VASurfaceID>(reinterpret_cast<uintptr_t>(sinkframe->data[3]));
543 m_lastFilteredFrameCount = Frame->m_frameCounter;
544 m_firstField = false;
545 break;
546 }
547 break;
548 }
549 }
550 }
551 return result;
552}
MythAVFrame little utility class that act as a safe way to allocate an AVFrame which can then be allo...
Definition: mythavframe.h:27
static bool DirectRenderingAvailable()
bool IsEGL(void)
Definition: mythegl.cpp:32
static QString TypeToString(InteropType Type)
std::vector< InteropType > InteropTypes
InteropType m_type
std::map< VideoFrameType, InteropTypes > InteropMap
MythRenderOpenGL * m_openglContext
static bool IsSupported(MythRenderOpenGL *Context)
virtual void DestroyDeinterlacer(void)
AVBufferRef * m_vppFramesContext
static bool SetupDeinterlacer(MythDeintType Deinterlacer, bool DoubleRate, AVBufferRef *FramesContext, int Width, int Height, AVFilterGraph *&Graph, AVFilterContext *&Source, AVFilterContext *&Sink)
void InitaliseDisplay(void)
~MythVAAPIInterop() override
VASurfaceID m_lastFilteredFrame
QString GetVendor(void)
virtual void PostInitDeinterlacer(void)
static void GetVAAPITypes(MythRenderOpenGL *Context, MythInteropGPU::InteropMap &Types)
Return a list of interops that are supported by the current render device.
MythDeintType m_deinterlacer
AVFilterGraph * m_filterGraph
static MythVAAPIInterop * CreateVAAPI(MythPlayerUI *Player, MythRenderOpenGL *Context)
AVFilterContext * m_filterSource
VASurfaceID VerifySurface(MythRenderOpenGL *Context, MythVideoFrame *Frame)
uint64_t m_lastFilteredFrameCount
VASurfaceID Deinterlace(MythVideoFrame *Frame, VASurfaceID Current, FrameScanType Scan)
AVFilterContext * m_filterSink
MythVAAPIInterop(MythPlayerUI *Player, MythRenderOpenGL *Context, InteropType Type)
VADisplay GetDisplay(void)
static QString DeinterlacerName(MythDeintType Deint, bool DoubleRate, VideoFrameType Format=FMT_NONE)
Definition: mythframe.cpp:466
#define minor(X)
Definition: compat.h:58
static const struct wl_interface * types[]
MythDeintType
Definition: mythframe.h:67
@ DEINT_HIGH
Definition: mythframe.h:71
@ DEINT_DRIVER
Definition: mythframe.h:74
@ DEINT_MEDIUM
Definition: mythframe.h:70
@ DEINT_NONE
Definition: mythframe.h:68
@ DEINT_SHADER
Definition: mythframe.h:73
@ DEINT_ALL
Definition: mythframe.h:75
@ DEINT_CPU
Definition: mythframe.h:72
@ FMT_VAAPI
Definition: mythframe.h:57
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
static QString Source(const QNetworkRequest &request)
Definition: netstream.cpp:139
static QString cleanup(const QString &str)
FrameScanType
Definition: videoouttypes.h:95
bool is_interlaced(FrameScanType Scan)