Add VA context abstraction.
[vaapi:gstreamer-vaapi.git] / gst-libs / gst / vaapi / gstvaapicontext.c
1 /*
2  *  gstvaapicontext.c - VA context abstraction
3  *
4  *  gstreamer-vaapi (C) 2010 Splitted-Desktop Systems
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
19  */
20
21 /**
22  * SECTION:gstvaapicontext
23  * @short_description: VA context abstraction
24  */
25
26 #include "config.h"
27 #include <assert.h>
28 #include "gstvaapicompat.h"
29 #include "gstvaapicontext.h"
30 #include "gstvaapisurfacepool.h"
31 #include "gstvaapiutils.h"
32 #include "gstvaapi_priv.h"
33
34 #define DEBUG 1
35 #include "gstvaapidebug.h"
36
37 G_DEFINE_TYPE(GstVaapiContext, gst_vaapi_context, GST_VAAPI_TYPE_OBJECT);
38
39 #define GST_VAAPI_CONTEXT_GET_PRIVATE(obj)                      \
40     (G_TYPE_INSTANCE_GET_PRIVATE((obj),                         \
41                                  GST_VAAPI_TYPE_CONTEXT,        \
42                                  GstVaapiContextPrivate))
43
44 /* XXX: optimize for the effective number of reference frames */
45 struct _GstVaapiContextPrivate {
46     VAConfigID          config_id;
47     GPtrArray          *surfaces;
48     GstVaapiVideoPool  *surfaces_pool;
49     GstVaapiProfile     profile;
50     GstVaapiEntrypoint  entrypoint;
51     guint               width;
52     guint               height;
53     guint               is_constructed  : 1;
54 };
55
56 enum {
57     PROP_0,
58
59     PROP_PROFILE,
60     PROP_ENTRYPOINT,
61     PROP_WIDTH,
62     PROP_HEIGHT
63 };
64
65 static void
66 unref_surface_cb(gpointer data, gpointer user_data)
67 {
68     g_object_unref(GST_VAAPI_SURFACE(data));
69 }
70
71 static void
72 gst_vaapi_context_destroy_surfaces(GstVaapiContext *context)
73 {
74     GstVaapiContextPrivate * const priv = context->priv;
75
76     if (priv->surfaces) {
77         g_ptr_array_foreach(priv->surfaces, unref_surface_cb, NULL);
78         g_ptr_array_free(priv->surfaces, TRUE);
79         priv->surfaces = NULL;
80     }
81
82     if (priv->surfaces_pool) {
83         g_object_unref(priv->surfaces_pool);
84         priv->surfaces_pool = NULL;
85     }
86 }
87
88 static void
89 gst_vaapi_context_destroy(GstVaapiContext *context)
90 {
91     GstVaapiDisplay * const display = GST_VAAPI_OBJECT_DISPLAY(context);
92     GstVaapiContextPrivate * const priv = context->priv;
93     VAContextID context_id;
94     VAStatus status;
95
96     context_id = GST_VAAPI_OBJECT_ID(context);
97     GST_DEBUG("context %" GST_VAAPI_ID_FORMAT, GST_VAAPI_ID_ARGS(context_id));
98
99     if (context_id != VA_INVALID_ID) {
100         GST_VAAPI_DISPLAY_LOCK(display);
101         status = vaDestroyContext(
102             GST_VAAPI_DISPLAY_VADISPLAY(display),
103             context_id
104         );
105         GST_VAAPI_DISPLAY_UNLOCK(display);
106         if (!vaapi_check_status(status, "vaDestroyContext()"))
107             g_warning("failed to destroy context %" GST_VAAPI_ID_FORMAT,
108                       GST_VAAPI_ID_ARGS(context_id));
109         GST_VAAPI_OBJECT_ID(context) = VA_INVALID_ID;
110     }
111
112     if (priv->config_id != VA_INVALID_ID) {
113         GST_VAAPI_DISPLAY_LOCK(display);
114         status = vaDestroyConfig(
115             GST_VAAPI_DISPLAY_VADISPLAY(display),
116             priv->config_id
117         );
118         GST_VAAPI_DISPLAY_UNLOCK(display);
119         if (!vaapi_check_status(status, "vaDestroyConfig()"))
120             g_warning("failed to destroy config %" GST_VAAPI_ID_FORMAT,
121                       GST_VAAPI_ID_ARGS(priv->config_id));
122         priv->config_id = VA_INVALID_ID;
123     }
124 }
125
126 static gboolean
127 gst_vaapi_context_create_surfaces(GstVaapiContext *context)
128 {
129     GstVaapiContextPrivate * const priv = context->priv;
130     GstCaps *caps;
131     GstVaapiSurface *surface;
132     guint i, num_ref_frames, num_surfaces;
133
134     /* Number of scratch surfaces beyond those used as reference */
135     const guint SCRATCH_SURFACES_COUNT = 4;
136
137     if (!priv->surfaces) {
138         priv->surfaces = g_ptr_array_new();
139         if (!priv->surfaces)
140             return FALSE;
141     }
142
143     if (!priv->surfaces_pool) {
144         caps = gst_caps_new_simple(
145             "video/x-vaapi-surface",
146             "width",  G_TYPE_INT, priv->width,
147             "height", G_TYPE_INT, priv->height,
148             NULL
149         );
150         if (!caps)
151             return FALSE;
152         priv->surfaces_pool = gst_vaapi_surface_pool_new(
153             GST_VAAPI_OBJECT_DISPLAY(context),
154             caps
155         );
156         gst_caps_unref(caps);
157         if (!priv->surfaces_pool)
158             return FALSE;
159     }
160
161     num_ref_frames = 2;
162     if (gst_vaapi_profile_get_codec(priv->profile) == GST_VAAPI_CODEC_H264)
163         num_ref_frames = 16;
164     num_surfaces = num_ref_frames + SCRATCH_SURFACES_COUNT;
165
166     gst_vaapi_video_pool_set_capacity(priv->surfaces_pool, num_surfaces);
167
168     for (i = priv->surfaces->len; i < num_surfaces; i++) {
169         surface = gst_vaapi_surface_new(
170             GST_VAAPI_OBJECT_DISPLAY(context),
171             GST_VAAPI_CHROMA_TYPE_YUV420,
172             priv->width, priv->height
173         );
174         if (!surface)
175             return FALSE;
176         g_ptr_array_add(priv->surfaces, surface);
177         if (!gst_vaapi_video_pool_add_object(priv->surfaces_pool, surface))
178             return FALSE;
179     }
180     return TRUE;
181 }
182
183 static gboolean
184 gst_vaapi_context_create(GstVaapiContext *context)
185 {
186     GstVaapiDisplay * const display = GST_VAAPI_OBJECT_DISPLAY(context);
187     GstVaapiContextPrivate * const priv = context->priv;
188     VAProfile va_profile;
189     VAEntrypoint va_entrypoint;
190     VAConfigAttrib attrib;
191     VAContextID context_id;
192     VASurfaceID surface_id;
193     VAStatus status;
194     GArray *surfaces = NULL;
195     gboolean success = FALSE;
196     guint i;
197
198     if (!priv->surfaces && !gst_vaapi_context_create_surfaces(context))
199         goto end;
200
201     surfaces = g_array_sized_new(
202         FALSE,
203         FALSE,
204         sizeof(VASurfaceID),
205         priv->surfaces->len
206     );
207     if (!surfaces)
208         goto end;
209
210     for (i = 0; i < priv->surfaces->len; i++) {
211         GstVaapiSurface * const surface = g_ptr_array_index(priv->surfaces, i);
212         if (!surface)
213             goto end;
214         surface_id = GST_VAAPI_OBJECT_ID(surface);
215         g_array_append_val(surfaces, surface_id);
216     }
217     assert(surfaces->len == priv->surfaces->len);
218
219     if (!priv->profile || !priv->entrypoint)
220         goto end;
221     va_profile    = gst_vaapi_profile_get_va_profile(priv->profile);
222     va_entrypoint = gst_vaapi_entrypoint_get_va_entrypoint(priv->entrypoint);
223
224     GST_VAAPI_DISPLAY_LOCK(display);
225     attrib.type = VAConfigAttribRTFormat;
226     status = vaGetConfigAttributes(
227         GST_VAAPI_DISPLAY_VADISPLAY(display),
228         va_profile,
229         va_entrypoint,
230         &attrib, 1
231     );
232     GST_VAAPI_DISPLAY_UNLOCK(display);
233     if (!vaapi_check_status(status, "vaGetConfigAttributes()"))
234         goto end;
235     if (!(attrib.value & VA_RT_FORMAT_YUV420))
236         goto end;
237
238     GST_VAAPI_DISPLAY_LOCK(display);
239     status = vaCreateConfig(
240         GST_VAAPI_DISPLAY_VADISPLAY(display),
241         va_profile,
242         va_entrypoint,
243         &attrib, 1,
244         &priv->config_id
245     );
246     GST_VAAPI_DISPLAY_UNLOCK(display);
247     if (!vaapi_check_status(status, "vaCreateConfig()"))
248         goto end;
249
250     GST_VAAPI_DISPLAY_LOCK(display);
251     status = vaCreateContext(
252         GST_VAAPI_DISPLAY_VADISPLAY(display),
253         priv->config_id,
254         priv->width, priv->height,
255         VA_PROGRESSIVE,
256         (VASurfaceID *)surfaces->data, surfaces->len,
257         &context_id
258     );
259     GST_VAAPI_DISPLAY_UNLOCK(display);
260     if (!vaapi_check_status(status, "vaCreateContext()"))
261         goto end;
262
263     GST_DEBUG("context %" GST_VAAPI_ID_FORMAT, GST_VAAPI_ID_ARGS(context_id));
264     GST_VAAPI_OBJECT_ID(context) = context_id;
265     success = TRUE;
266 end:
267     if (surfaces)
268         g_array_free(surfaces, TRUE);
269     return success;
270 }
271
272 static void
273 gst_vaapi_context_finalize(GObject *object)
274 {
275     GstVaapiContext * const context = GST_VAAPI_CONTEXT(object);
276
277     gst_vaapi_context_destroy(context);
278     gst_vaapi_context_destroy_surfaces(context);
279
280     G_OBJECT_CLASS(gst_vaapi_context_parent_class)->finalize(object);
281 }
282
283 static void
284 gst_vaapi_context_set_property(
285     GObject      *object,
286     guint         prop_id,
287     const GValue *value,
288     GParamSpec   *pspec
289 )
290 {
291     GstVaapiContext        * const context = GST_VAAPI_CONTEXT(object);
292     GstVaapiContextPrivate * const priv    = context->priv;
293
294     switch (prop_id) {
295     case PROP_PROFILE:
296         gst_vaapi_context_set_profile(context, g_value_get_uint(value));
297         break;
298     case PROP_ENTRYPOINT:
299         priv->entrypoint = g_value_get_uint(value);
300         break;
301     case PROP_WIDTH:
302         priv->width = g_value_get_uint(value);
303         break;
304     case PROP_HEIGHT:
305         priv->height = g_value_get_uint(value);
306         break;
307     default:
308         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
309         break;
310     }
311 }
312
313 static void
314 gst_vaapi_context_get_property(
315     GObject    *object,
316     guint       prop_id,
317     GValue     *value,
318     GParamSpec *pspec
319 )
320 {
321     GstVaapiContext        * const context = GST_VAAPI_CONTEXT(object);
322     GstVaapiContextPrivate * const priv    = context->priv;
323
324     switch (prop_id) {
325     case PROP_PROFILE:
326         g_value_set_uint(value, gst_vaapi_context_get_profile(context));
327         break;
328     case PROP_ENTRYPOINT:
329         g_value_set_uint(value, gst_vaapi_context_get_entrypoint(context));
330         break;
331     case PROP_WIDTH:
332         g_value_set_uint(value, priv->width);
333         break;
334     case PROP_HEIGHT:
335         g_value_set_uint(value, priv->height);
336         break;
337     default:
338         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
339         break;
340     }
341 }
342
343 static void
344 gst_vaapi_context_class_init(GstVaapiContextClass *klass)
345 {
346     GObjectClass * const object_class = G_OBJECT_CLASS(klass);
347
348     g_type_class_add_private(klass, sizeof(GstVaapiContextPrivate));
349
350     object_class->finalize     = gst_vaapi_context_finalize;
351     object_class->set_property = gst_vaapi_context_set_property;
352     object_class->get_property = gst_vaapi_context_get_property;
353
354     g_object_class_install_property
355         (object_class,
356          PROP_PROFILE,
357          g_param_spec_uint("profile",
358                            "Profile",
359                            "The profile used for decoding",
360                            0, G_MAXUINT32, 0,
361                            G_PARAM_READWRITE));
362
363     g_object_class_install_property
364         (object_class,
365          PROP_ENTRYPOINT,
366          g_param_spec_uint("entrypoint",
367                            "Entrypoint",
368                            "The decoder entrypoint",
369                            0, G_MAXUINT32, 0,
370                            G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
371
372     g_object_class_install_property
373         (object_class,
374          PROP_WIDTH,
375          g_param_spec_uint("width",
376                            "Width",
377                            "The width of decoded surfaces",
378                            0, G_MAXINT32, 0,
379                            G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
380
381     g_object_class_install_property
382         (object_class,
383          PROP_HEIGHT,
384          g_param_spec_uint("height",
385                            "Height",
386                            "The height of the decoded surfaces",
387                            0, G_MAXINT32, 0,
388                            G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
389 }
390
391 static void
392 gst_vaapi_context_init(GstVaapiContext *context)
393 {
394     GstVaapiContextPrivate *priv = GST_VAAPI_CONTEXT_GET_PRIVATE(context);
395
396     context->priv       = priv;
397     priv->config_id     = VA_INVALID_ID;
398     priv->surfaces      = NULL;
399     priv->surfaces_pool = NULL;
400     priv->profile       = 0;
401     priv->entrypoint    = 0;
402     priv->width         = 0;
403     priv->height        = 0;
404 }
405
406 /**
407  * gst_vaapi_context_new:
408  * @display: a #GstVaapiDisplay
409  * @profile: a #GstVaapiProfile
410  * @entrypoint: a #GstVaapiEntrypoint
411  * @width: coded width from the bitstream
412  * @height: coded height from the bitstream
413  *
414  * Creates a new #GstVaapiContext with the specified codec @profile
415  * and @entrypoint.
416  *
417  * Return value: the newly allocated #GstVaapiContext object
418  */
419 GstVaapiContext *
420 gst_vaapi_context_new(
421     GstVaapiDisplay    *display,
422     GstVaapiProfile     profile,
423     GstVaapiEntrypoint  entrypoint,
424     unsigned int        width,
425     unsigned int        height
426 )
427 {
428     GstVaapiContext *context;
429
430     g_return_val_if_fail(GST_VAAPI_IS_DISPLAY(display), NULL);
431     g_return_val_if_fail(profile, NULL);
432     g_return_val_if_fail(entrypoint, NULL);
433     g_return_val_if_fail(width > 0, NULL);
434     g_return_val_if_fail(height > 0, NULL);
435
436     context = g_object_new(
437         GST_VAAPI_TYPE_CONTEXT,
438         "display",      display,
439         "id",           GST_VAAPI_ID(VA_INVALID_ID),
440         "profile",      profile,
441         "entrypoint",   entrypoint,
442         "width",        width,
443         "height",       height,
444         NULL
445     );
446     if (!context->priv->is_constructed) {
447         g_object_unref(context);
448         return NULL;
449     }
450     return context;
451 }
452
453 /**
454  * gst_vaapi_context_reset:
455  * @context: a #GstVaapiContext
456  * @profile: a #GstVaapiProfile
457  * @entrypoint: a #GstVaapiEntrypoint
458  * @width: coded width from the bitstream
459  * @height: coded height from the bitstream
460  *
461  * Resets @context to the specified codec @profile and @entrypoint.
462  * The surfaces will be reallocated if the coded size changed.
463  *
464  * Return value: %TRUE on success
465  */
466 gboolean
467 gst_vaapi_context_reset(
468     GstVaapiContext    *context,
469     GstVaapiProfile     profile,
470     GstVaapiEntrypoint  entrypoint,
471     unsigned int        width,
472     unsigned int        height
473 )
474 {
475     GstVaapiContextPrivate * const priv = context->priv;
476     gboolean size_changed, codec_changed;
477
478     size_changed = priv->width != width || priv->height != height;
479     if (size_changed) {
480         gst_vaapi_context_destroy_surfaces(context);
481         priv->width  = width;
482         priv->height = height;
483     }
484
485     codec_changed = priv->profile != profile || priv->entrypoint != entrypoint;
486     if (codec_changed) {
487         gst_vaapi_context_destroy(context);
488         priv->profile    = profile;
489         priv->entrypoint = entrypoint;
490     }
491
492     if (size_changed && !gst_vaapi_context_create_surfaces(context))
493         return FALSE;
494
495     if (codec_changed && !gst_vaapi_context_create(context))
496         return FALSE;
497
498     priv->is_constructed = TRUE;
499     return TRUE;
500 }
501
502 /**
503  * gst_vaapi_context_get_id:
504  * @context: a #GstVaapiContext
505  *
506  * Returns the underlying VAContextID of the @context.
507  *
508  * Return value: the underlying VA context id
509  */
510 GstVaapiID
511 gst_vaapi_context_get_id(GstVaapiContext *context)
512 {
513     g_return_val_if_fail(GST_VAAPI_IS_CONTEXT(context), VA_INVALID_ID);
514
515     return GST_VAAPI_OBJECT_ID(context);
516 }
517
518 /**
519  * gst_vaapi_context_get_profile:
520  * @context: a #GstVaapiContext
521  *
522  * Returns the VA profile used by the @context.
523  *
524  * Return value: the VA profile used by the @context
525  */
526 GstVaapiProfile
527 gst_vaapi_context_get_profile(GstVaapiContext *context)
528 {
529     g_return_val_if_fail(GST_VAAPI_IS_CONTEXT(context), 0);
530
531     return context->priv->profile;
532 }
533
534 /**
535  * gst_vaapi_context_set_profile:
536  * @context: a #GstVaapiContext
537  * @profile: the new #GstVaapiProfile to use
538  *
539  * Sets the new @profile to use with the @context. If @profile matches
540  * the previous profile, this call has no effect. Otherwise, the
541  * underlying VA context is recreated, while keeping the previously
542  * allocated surfaces.
543  *
544  * Return value: %TRUE on success
545  */
546 gboolean
547 gst_vaapi_context_set_profile(GstVaapiContext *context, GstVaapiProfile profile)
548 {
549     g_return_val_if_fail(GST_VAAPI_IS_CONTEXT(context), FALSE);
550     g_return_val_if_fail(profile, FALSE);
551
552     return gst_vaapi_context_reset(context,
553                                    profile,
554                                    context->priv->entrypoint,
555                                    context->priv->width,
556                                    context->priv->height);
557 }
558
559 /**
560  * gst_vaapi_context_get_entrypoint:
561  * @context: a #GstVaapiContext
562  *
563  * Returns the VA entrypoint used by the @context
564  *
565  * Return value: the VA entrypoint used by the @context
566  */
567 GstVaapiEntrypoint
568 gst_vaapi_context_get_entrypoint(GstVaapiContext *context)
569 {
570     g_return_val_if_fail(GST_VAAPI_IS_CONTEXT(context), 0);
571
572     return context->priv->entrypoint;
573 }
574
575 /**
576  * gst_vaapi_context_get_size:
577  * @context: a #GstVaapiContext
578  * @pwidth: return location for the width, or %NULL
579  * @pheight: return location for the height, or %NULL
580  *
581  * Retrieves the size of the surfaces attached to @context.
582  */
583 void
584 gst_vaapi_context_get_size(
585     GstVaapiContext *context,
586     guint           *pwidth,
587     guint           *pheight
588 )
589 {
590     g_return_if_fail(GST_VAAPI_IS_CONTEXT(context));
591
592     if (pwidth)
593         *pwidth = context->priv->width;
594
595     if (pheight)
596         *pheight = context->priv->height;
597 }
598
599 /**
600  * gst_vaapi_context_get_surface:
601  * @context: a #GstVaapiContext
602  *
603  * Acquires a free surface. The returned surface but be released with
604  * gst_vaapi_context_put_surface(). This function returns %NULL if
605  * there is no free surface available in the pool. The surfaces are
606  * pre-allocated during context creation though.
607  *
608  * Return value: a free surface, or %NULL if none is available
609  */
610 GstVaapiSurface *
611 gst_vaapi_context_get_surface(GstVaapiContext *context)
612 {
613     g_return_val_if_fail(GST_VAAPI_IS_CONTEXT(context), NULL);
614
615     return gst_vaapi_video_pool_get_object(context->priv->surfaces_pool);
616 }
617
618 /**
619  * gst_vaapi_context_put_surface:
620  * @context: a #GstVaapiContext
621  * @surface: the #GstVaapiSurface to release
622  *
623  * Releases a surface acquired by gst_vaapi_context_get_surface().
624  */
625 void
626 gst_vaapi_context_put_surface(GstVaapiContext *context, GstVaapiSurface *surface)
627 {
628     g_return_if_fail(GST_VAAPI_IS_CONTEXT(context));
629     g_return_if_fail(GST_VAAPI_IS_SURFACE(surface));
630
631     gst_vaapi_video_pool_put_object(context->priv->surfaces_pool, surface);
632 }
633
634 /**
635  * gst_vaapi_context_find_surface_by_id:
636  * @context: a #GstVaapiContext
637  * @id: the VA surface id to find
638  *
639  * Finds VA surface by @id in the list of surfaces attached to the @context.
640  *
641  * Return value: the matching #GstVaapiSurface object, or %NULL if
642  *   none was found
643  */
644 GstVaapiSurface *
645 gst_vaapi_context_find_surface_by_id(GstVaapiContext *context, GstVaapiID id)
646 {
647     GstVaapiContextPrivate *priv;
648     GstVaapiSurface *surface;
649     guint i;
650
651     g_return_val_if_fail(GST_VAAPI_IS_CONTEXT(context), NULL);
652
653     priv = context->priv;
654     g_return_val_if_fail(priv->surfaces, NULL);
655
656     for (i = 0; i < priv->surfaces->len; i++) {
657         surface = g_ptr_array_index(priv->surfaces, i);
658         if (GST_VAAPI_OBJECT_ID(surface) == id)
659             return surface;
660     }
661     return NULL;
662 }