msdk: make sure the found response meets the request
[platform/upstream/gstreamer.git] / sys / msdk / gstmsdkcontext.c
1 /* GStreamer Intel MSDK plugin
2  * Copyright (c) 2018, Intel Corporation
3  * Copyright (c) 2018, Igalia S.L.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright notice,
10  *    this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of the copyright holder nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
27  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGDECE
29  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "gstmsdkcontext.h"
34 #ifndef _WIN32
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <va/va_drm.h>
38 #include <gudev/gudev.h>
39 #endif
40
41 GST_DEBUG_CATEGORY_STATIC (gst_debug_msdkcontext);
42 #define GST_CAT_DEFAULT gst_debug_msdkcontext
43
44 struct _GstMsdkContextPrivate
45 {
46   mfxSession session;
47   GList *cached_alloc_responses;
48   gboolean hardware;
49   gboolean is_joined;
50   gboolean has_frame_allocator;
51   GstMsdkContextJobType job_type;
52   gint shared_async_depth;
53   GMutex mutex;
54   GList *child_session_list;
55 #ifndef _WIN32
56   gint fd;
57   VADisplay dpy;
58 #endif
59 };
60
61 #define gst_msdk_context_parent_class parent_class
62 G_DEFINE_TYPE_WITH_CODE (GstMsdkContext, gst_msdk_context, GST_TYPE_OBJECT,
63     G_ADD_PRIVATE (GstMsdkContext)
64     GST_DEBUG_CATEGORY_INIT (gst_debug_msdkcontext, "msdkcontext", 0,
65         "MSDK Context"));
66
67 #ifndef _WIN32
68
69 static gint
70 get_device_id (void)
71 {
72   GUdevClient *client = NULL;
73   GUdevEnumerator *e = NULL;
74   GList *devices, *l;
75   GUdevDevice *dev, *parent;
76   const gchar *devnode_path;
77   const gchar *devnode_files[2] = { "renderD[0-9]*", "card[0-9]*" };
78   int fd = -1, i;
79
80   client = g_udev_client_new (NULL);
81   if (!client)
82     goto done;
83
84   e = g_udev_enumerator_new (client);
85   if (!e)
86     goto done;
87
88   g_udev_enumerator_add_match_subsystem (e, "drm");
89   for (i = 0; i < 2; i++) {
90     g_udev_enumerator_add_match_name (e, devnode_files[i]);
91     devices = g_udev_enumerator_execute (e);
92
93     for (l = devices; l != NULL; l = l->next) {
94       dev = (GUdevDevice *) l->data;
95
96       parent = g_udev_device_get_parent (dev);
97       if (strcmp (g_udev_device_get_subsystem (parent), "pci") != 0 ||
98           strcmp (g_udev_device_get_driver (parent), "i915") != 0) {
99         g_object_unref (parent);
100         continue;
101       }
102       g_object_unref (parent);
103
104       devnode_path = g_udev_device_get_device_file (dev);
105       fd = open (devnode_path, O_RDWR | O_CLOEXEC);
106       if (fd < 0)
107         continue;
108       GST_DEBUG ("Opened the drm device node %s", devnode_path);
109       break;
110     }
111
112     g_list_foreach (devices, (GFunc) gst_object_unref, NULL);
113     g_list_free (devices);
114     if (fd >= 0)
115       goto done;
116   }
117
118 done:
119   if (e)
120     g_object_unref (e);
121   if (client)
122     g_object_unref (client);
123
124   return fd;
125 }
126
127
128 static gboolean
129 gst_msdk_context_use_vaapi (GstMsdkContext * context)
130 {
131   gint fd;
132   gint maj_ver, min_ver;
133   VADisplay va_dpy = NULL;
134   VAStatus va_status;
135   mfxStatus status;
136   GstMsdkContextPrivate *priv = context->priv;
137
138   fd = get_device_id ();
139   if (fd < 0) {
140     GST_ERROR ("Couldn't find a drm device node to open");
141     return FALSE;
142   }
143
144   va_dpy = vaGetDisplayDRM (fd);
145   if (!va_dpy) {
146     GST_ERROR ("Couldn't get a VA DRM display");
147     goto failed;
148   }
149
150   va_status = vaInitialize (va_dpy, &maj_ver, &min_ver);
151   if (va_status != VA_STATUS_SUCCESS) {
152     GST_ERROR ("Couldn't initialize VA DRM display");
153     goto failed;
154   }
155
156   status = MFXVideoCORE_SetHandle (priv->session, MFX_HANDLE_VA_DISPLAY,
157       (mfxHDL) va_dpy);
158   if (status != MFX_ERR_NONE) {
159     GST_ERROR ("Setting VAAPI handle failed (%s)",
160         msdk_status_to_string (status));
161     goto failed;
162   }
163
164   priv->fd = fd;
165   priv->dpy = va_dpy;
166
167   return TRUE;
168
169 failed:
170   if (va_dpy)
171     vaTerminate (va_dpy);
172   close (fd);
173   return FALSE;
174 }
175 #endif
176
177 static gboolean
178 gst_msdk_context_open (GstMsdkContext * context, gboolean hardware,
179     GstMsdkContextJobType job_type)
180 {
181   GstMsdkContextPrivate *priv = context->priv;
182
183   priv->job_type = job_type;
184   priv->hardware = hardware;
185   priv->session =
186       msdk_open_session (hardware ? MFX_IMPL_HARDWARE_ANY : MFX_IMPL_SOFTWARE);
187   if (!priv->session)
188     goto failed;
189
190 #ifndef _WIN32
191   priv->fd = -1;
192
193   if (hardware) {
194     if (!gst_msdk_context_use_vaapi (context))
195       goto failed;
196   }
197 #endif
198
199   return TRUE;
200
201 failed:
202   msdk_close_session (priv->session);
203   return FALSE;
204 }
205
206 static void
207 gst_msdk_context_init (GstMsdkContext * context)
208 {
209   GstMsdkContextPrivate *priv = gst_msdk_context_get_instance_private (context);
210
211   context->priv = priv;
212
213   g_mutex_init (&priv->mutex);
214 }
215
216 static void
217 release_child_session (gpointer session)
218 {
219   mfxStatus status;
220
221   mfxSession _session = session;
222   status = MFXDisjoinSession (_session);
223   if (status != MFX_ERR_NONE)
224     GST_WARNING ("failed to disjoin (%s)", msdk_status_to_string (status));
225   msdk_close_session (_session);
226 }
227
228 static void
229 gst_msdk_context_finalize (GObject * obj)
230 {
231   GstMsdkContext *context = GST_MSDK_CONTEXT_CAST (obj);
232   GstMsdkContextPrivate *priv = context->priv;
233
234   /* child sessions will be closed when the parent session is closed */
235   if (priv->is_joined)
236     goto done;
237   else
238     g_list_free_full (priv->child_session_list, release_child_session);
239
240   msdk_close_session (priv->session);
241   g_mutex_clear (&priv->mutex);
242
243 #ifndef _WIN32
244   if (priv->dpy)
245     vaTerminate (priv->dpy);
246   if (priv->fd >= 0)
247     close (priv->fd);
248 #endif
249
250 done:
251   G_OBJECT_CLASS (parent_class)->finalize (obj);
252 }
253
254 static void
255 gst_msdk_context_class_init (GstMsdkContextClass * klass)
256 {
257   GObjectClass *const g_object_class = G_OBJECT_CLASS (klass);
258
259   g_object_class->finalize = gst_msdk_context_finalize;
260 }
261
262 GstMsdkContext *
263 gst_msdk_context_new (gboolean hardware, GstMsdkContextJobType job_type)
264 {
265   GstMsdkContext *obj = g_object_new (GST_TYPE_MSDK_CONTEXT, NULL);
266
267   if (obj && !gst_msdk_context_open (obj, hardware, job_type)) {
268     if (obj)
269       gst_object_unref (obj);
270     return NULL;
271   }
272
273   return obj;
274 }
275
276 GstMsdkContext *
277 gst_msdk_context_new_with_parent (GstMsdkContext * parent)
278 {
279   mfxStatus status;
280   GstMsdkContext *obj = g_object_new (GST_TYPE_MSDK_CONTEXT, NULL);
281   GstMsdkContextPrivate *priv = obj->priv;
282   GstMsdkContextPrivate *parent_priv = parent->priv;
283
284   status = MFXCloneSession (parent_priv->session, &priv->session);
285   if (status != MFX_ERR_NONE) {
286     GST_ERROR ("Failed to clone mfx session");
287     g_object_unref (obj);
288     return NULL;
289   }
290
291   priv->is_joined = TRUE;
292   priv->hardware = parent_priv->hardware;
293   priv->job_type = parent_priv->job_type;
294   parent_priv->child_session_list =
295       g_list_prepend (parent_priv->child_session_list, priv->session);
296 #ifndef _WIN32
297   priv->dpy = parent_priv->dpy;
298   priv->fd = parent_priv->fd;
299
300   if (priv->hardware) {
301     status = MFXVideoCORE_SetHandle (priv->session, MFX_HANDLE_VA_DISPLAY,
302         (mfxHDL) parent_priv->dpy);
303
304     if (status != MFX_ERR_NONE) {
305       GST_ERROR ("Setting VA handle failed (%s)",
306           msdk_status_to_string (status));
307       g_object_unref (obj);
308       return NULL;
309     }
310
311   }
312 #endif
313
314   return obj;
315 }
316
317 mfxSession
318 gst_msdk_context_get_session (GstMsdkContext * context)
319 {
320   return context->priv->session;
321 }
322
323 gpointer
324 gst_msdk_context_get_handle (GstMsdkContext * context)
325 {
326 #ifndef _WIN32
327   return context->priv->dpy;
328 #else
329   return NULL;
330 #endif
331 }
332
333 gint
334 gst_msdk_context_get_fd (GstMsdkContext * context)
335 {
336 #ifndef _WIN32
337   return context->priv->fd;
338 #else
339   return -1;
340 #endif
341 }
342
343 static gint
344 _find_response (gconstpointer resp, gconstpointer comp_resp)
345 {
346   GstMsdkAllocResponse *cached_resp = (GstMsdkAllocResponse *) resp;
347   mfxFrameAllocResponse *_resp = (mfxFrameAllocResponse *) comp_resp;
348
349   return cached_resp ? cached_resp->response.mids != _resp->mids : -1;
350 }
351
352 static gint
353 _find_request (gconstpointer resp, gconstpointer req)
354 {
355   GstMsdkAllocResponse *cached_resp = (GstMsdkAllocResponse *) resp;
356   mfxFrameAllocRequest *_req = (mfxFrameAllocRequest *) req;
357
358   /* Confirm if it's under the size of the cached response */
359   if (_req->Info.Width <= cached_resp->request.Info.Width &&
360       _req->Info.Height <= cached_resp->request.Info.Height &&
361       _req->NumFrameSuggested <= cached_resp->request.NumFrameSuggested) {
362     return _req->Type & cached_resp->
363         request.Type & MFX_MEMTYPE_FROM_DECODE ? 0 : -1;
364   }
365
366   return -1;
367 }
368
369 GstMsdkAllocResponse *
370 gst_msdk_context_get_cached_alloc_responses (GstMsdkContext * context,
371     mfxFrameAllocResponse * resp)
372 {
373   GstMsdkContextPrivate *priv = context->priv;
374   GList *l =
375       g_list_find_custom (priv->cached_alloc_responses, resp, _find_response);
376
377   if (l)
378     return l->data;
379   else
380     return NULL;
381 }
382
383 GstMsdkAllocResponse *
384 gst_msdk_context_get_cached_alloc_responses_by_request (GstMsdkContext *
385     context, mfxFrameAllocRequest * req)
386 {
387   GstMsdkContextPrivate *priv = context->priv;
388   GList *l =
389       g_list_find_custom (priv->cached_alloc_responses, req, _find_request);
390
391   if (l)
392     return l->data;
393   else
394     return NULL;
395 }
396
397 static void
398 create_surfaces (GstMsdkContext * context, GstMsdkAllocResponse * resp)
399 {
400   gint i;
401   mfxMemId *mem_id;
402   mfxFrameSurface1 *surface;
403
404   for (i = 0; i < resp->response.NumFrameActual; i++) {
405     mem_id = resp->response.mids[i];
406     surface = (mfxFrameSurface1 *) g_slice_new0 (mfxFrameSurface1);
407     if (!surface) {
408       GST_ERROR ("failed to allocate surface");
409       break;
410     }
411     surface->Data.MemId = mem_id;
412     resp->surfaces_avail = g_list_prepend (resp->surfaces_avail, surface);
413   }
414 }
415
416 static void
417 free_surface (gpointer surface)
418 {
419   g_slice_free1 (sizeof (mfxFrameSurface1), surface);
420 }
421
422 static void
423 remove_surfaces (GstMsdkContext * context, GstMsdkAllocResponse * resp)
424 {
425   g_list_free_full (resp->surfaces_used, free_surface);
426   g_list_free_full (resp->surfaces_avail, free_surface);
427   g_list_free_full (resp->surfaces_locked, free_surface);
428 }
429
430 void
431 gst_msdk_context_add_alloc_response (GstMsdkContext * context,
432     GstMsdkAllocResponse * resp)
433 {
434   context->priv->cached_alloc_responses =
435       g_list_prepend (context->priv->cached_alloc_responses, resp);
436
437   create_surfaces (context, resp);
438 }
439
440 gboolean
441 gst_msdk_context_remove_alloc_response (GstMsdkContext * context,
442     mfxFrameAllocResponse * resp)
443 {
444   GstMsdkAllocResponse *msdk_resp;
445   GstMsdkContextPrivate *priv = context->priv;
446   GList *l =
447       g_list_find_custom (priv->cached_alloc_responses, resp, _find_response);
448
449   if (!l)
450     return FALSE;
451
452   msdk_resp = l->data;
453
454   remove_surfaces (context, msdk_resp);
455
456   g_slice_free1 (sizeof (GstMsdkAllocResponse), msdk_resp);
457   priv->cached_alloc_responses =
458       g_list_delete_link (priv->cached_alloc_responses, l);
459
460   return TRUE;
461 }
462
463 static gboolean
464 check_surfaces_available (GstMsdkContext * context, GstMsdkAllocResponse * resp)
465 {
466   GList *l;
467   mfxFrameSurface1 *surface = NULL;
468   GstMsdkContextPrivate *priv = context->priv;
469   gboolean ret = FALSE;
470
471   g_mutex_lock (&priv->mutex);
472   for (l = resp->surfaces_locked; l;) {
473     surface = l->data;
474     l = l->next;
475     if (!surface->Data.Locked) {
476       resp->surfaces_locked = g_list_remove (resp->surfaces_locked, surface);
477       resp->surfaces_avail = g_list_prepend (resp->surfaces_avail, surface);
478       ret = TRUE;
479     }
480   }
481   g_mutex_unlock (&priv->mutex);
482
483   return ret;
484 }
485
486 /*
487  * There are 3 lists here in GstMsdkContext as the following:
488  * 1. surfaces_avail : surfaces which are free and unused anywhere
489  * 2. surfaces_used : surfaces coupled with a gst buffer and being used now.
490  * 3. surfaces_locked : surfaces still locked even after the gst buffer is released.
491  *
492  * Note that they need to be protected by mutex to be thread-safe.
493  */
494
495 mfxFrameSurface1 *
496 gst_msdk_context_get_surface_available (GstMsdkContext * context,
497     mfxFrameAllocResponse * resp)
498 {
499   GList *l;
500   mfxFrameSurface1 *surface = NULL;
501   GstMsdkAllocResponse *msdk_resp =
502       gst_msdk_context_get_cached_alloc_responses (context, resp);
503   gint retry = 0;
504   GstMsdkContextPrivate *priv = context->priv;
505
506 retry:
507   g_mutex_lock (&priv->mutex);
508   for (l = msdk_resp->surfaces_avail; l;) {
509     surface = l->data;
510     l = l->next;
511     if (!surface->Data.Locked) {
512       msdk_resp->surfaces_avail =
513           g_list_remove (msdk_resp->surfaces_avail, surface);
514       msdk_resp->surfaces_used =
515           g_list_prepend (msdk_resp->surfaces_used, surface);
516       break;
517     }
518   }
519   g_mutex_unlock (&priv->mutex);
520
521   /*
522    * If a msdk context is shared by multiple msdk elements,
523    * upstream msdk element sometimes needs to wait for a gst buffer
524    * to be released in downstream.
525    *
526    * Poll the pool for a maximum of 20 milisecnds.
527    *
528    * FIXME: Is there any better way to handle this case?
529    */
530   if (!surface && retry < 20) {
531     /* If there's no surface available, find unlocked surfaces in the locked list,
532      * take it back to the available list and then search again.
533      */
534     check_surfaces_available (context, msdk_resp);
535     retry++;
536     g_usleep (1000);
537     goto retry;
538   }
539
540   return surface;
541 }
542
543 void
544 gst_msdk_context_put_surface_locked (GstMsdkContext * context,
545     mfxFrameAllocResponse * resp, mfxFrameSurface1 * surface)
546 {
547   GstMsdkContextPrivate *priv = context->priv;
548   GstMsdkAllocResponse *msdk_resp =
549       gst_msdk_context_get_cached_alloc_responses (context, resp);
550
551   g_mutex_lock (&priv->mutex);
552   if (!g_list_find (msdk_resp->surfaces_locked, surface)) {
553     msdk_resp->surfaces_used =
554         g_list_remove (msdk_resp->surfaces_used, surface);
555     msdk_resp->surfaces_locked =
556         g_list_prepend (msdk_resp->surfaces_locked, surface);
557   }
558   g_mutex_unlock (&priv->mutex);
559 }
560
561 void
562 gst_msdk_context_put_surface_available (GstMsdkContext * context,
563     mfxFrameAllocResponse * resp, mfxFrameSurface1 * surface)
564 {
565   GstMsdkContextPrivate *priv = context->priv;
566   GstMsdkAllocResponse *msdk_resp =
567       gst_msdk_context_get_cached_alloc_responses (context, resp);
568
569   g_mutex_lock (&priv->mutex);
570   if (!g_list_find (msdk_resp->surfaces_avail, surface)) {
571     msdk_resp->surfaces_used =
572         g_list_remove (msdk_resp->surfaces_used, surface);
573     msdk_resp->surfaces_avail =
574         g_list_prepend (msdk_resp->surfaces_avail, surface);
575   }
576   g_mutex_unlock (&priv->mutex);
577 }
578
579 GstMsdkContextJobType
580 gst_msdk_context_get_job_type (GstMsdkContext * context)
581 {
582   return context->priv->job_type;
583 }
584
585 void
586 gst_msdk_context_add_job_type (GstMsdkContext * context,
587     GstMsdkContextJobType job_type)
588 {
589   context->priv->job_type |= job_type;
590 }
591
592 gint
593 gst_msdk_context_get_shared_async_depth (GstMsdkContext * context)
594 {
595   return context->priv->shared_async_depth;
596 }
597
598 void
599 gst_msdk_context_add_shared_async_depth (GstMsdkContext * context,
600     gint async_depth)
601 {
602   context->priv->shared_async_depth += async_depth;
603 }
604
605 void
606 gst_msdk_context_set_frame_allocator (GstMsdkContext * context,
607     mfxFrameAllocator * allocator)
608 {
609   GstMsdkContextPrivate *priv = context->priv;
610
611   g_mutex_lock (&priv->mutex);
612
613   if (!priv->has_frame_allocator) {
614     mfxStatus status;
615
616     status = MFXVideoCORE_SetFrameAllocator (priv->session, allocator);
617
618     if (status != MFX_ERR_NONE)
619       GST_ERROR ("Failed to set frame allocator");
620     else
621       priv->has_frame_allocator = 1;
622   }
623
624   g_mutex_unlock (&priv->mutex);
625 }