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