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