| /* |
| * GStreamer |
| * Copyright (C) 2015 Lubosz Sarnecki <lubosz.sarnecki@collabora.co.uk> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <stdio.h> |
| |
| #include "gl.h" |
| #include "gstgloverlaycompositor.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_gl_overlay_compositor_debug); |
| #define GST_CAT_DEFAULT gst_gl_overlay_compositor_debug |
| |
| /***************************************************************************** |
| * GstGLCompositionOverlay object is internally used by GstGLOverlayCompositor |
| *****************************************************************************/ |
| |
| #define GST_TYPE_GL_COMPOSITION_OVERLAY (gst_gl_composition_overlay_get_type()) |
| #define GST_GL_COMPOSITION_OVERLAY(obj) \ |
| (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_COMPOSITION_OVERLAY,\ |
| GstGLCompositionOverlay)) |
| |
| typedef struct _GstGLCompositionOverlay GstGLCompositionOverlay; |
| typedef struct _GstGLCompositionOverlayClass GstGLCompositionOverlayClass; |
| |
| static GType gst_gl_composition_overlay_get_type (void); |
| |
| /* *INDENT-OFF* */ |
| const gchar *fragment_shader = |
| "#ifdef GL_ES\n" |
| "precision mediump float;\n" |
| "#endif\n" |
| "varying vec2 v_texcoord;\n" |
| "uniform sampler2D tex;\n" |
| "void main(void)\n" |
| "{\n" |
| " vec4 t = texture2D(tex, v_texcoord);\n" |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN |
| " gl_FragColor = t.bgra;\n" |
| #else |
| " gl_FragColor = t.gbar;\n" |
| #endif |
| "}"; |
| /* *INDENT-ON* */ |
| |
| struct _GstGLCompositionOverlay |
| { |
| GstObject parent; |
| GstGLContext *context; |
| |
| GLuint vao; |
| GLuint index_buffer; |
| GLuint position_buffer; |
| GLuint texcoord_buffer; |
| GLint position_attrib; |
| GLint texcoord_attrib; |
| |
| GLfloat positions[16]; |
| |
| GLuint texture_id; |
| GstGLMemory *gl_memory; |
| GstVideoOverlayRectangle *rectangle; |
| }; |
| |
| struct _GstGLCompositionOverlayClass |
| { |
| GstObjectClass object_class; |
| }; |
| |
| G_DEFINE_TYPE (GstGLCompositionOverlay, gst_gl_composition_overlay, |
| GST_TYPE_OBJECT); |
| |
| static void |
| gst_gl_composition_overlay_init_vertex_buffer (GstGLContext * context, |
| gpointer overlay_pointer) |
| { |
| const GstGLFuncs *gl = context->gl_vtable; |
| GstGLCompositionOverlay *overlay = |
| (GstGLCompositionOverlay *) overlay_pointer; |
| |
| /* *INDENT-OFF* */ |
| static const GLfloat texcoords[] = { |
| 1.0f, 0.0f, |
| 0.0f, 0.0f, |
| 0.0f, 1.0f, |
| 1.0f, 1.0f |
| }; |
| |
| static const GLushort indices[] = { |
| 0, 1, 2, 0, 2, 3 |
| }; |
| /* *INDENT-ON* */ |
| |
| if (gl->GenVertexArrays) { |
| gl->GenVertexArrays (1, &overlay->vao); |
| gl->BindVertexArray (overlay->vao); |
| } |
| |
| gl->GenBuffers (1, &overlay->position_buffer); |
| gl->BindBuffer (GL_ARRAY_BUFFER, overlay->position_buffer); |
| gl->BufferData (GL_ARRAY_BUFFER, 4 * 4 * sizeof (GLfloat), overlay->positions, |
| GL_STATIC_DRAW); |
| |
| /* Load the vertex position */ |
| gl->VertexAttribPointer (overlay->position_attrib, 4, GL_FLOAT, GL_FALSE, |
| 4 * sizeof (GLfloat), NULL); |
| |
| gl->GenBuffers (1, &overlay->texcoord_buffer); |
| gl->BindBuffer (GL_ARRAY_BUFFER, overlay->texcoord_buffer); |
| gl->BufferData (GL_ARRAY_BUFFER, 4 * 2 * sizeof (GLfloat), texcoords, |
| GL_STATIC_DRAW); |
| |
| /* Load the texture coordinate */ |
| gl->VertexAttribPointer (overlay->texcoord_attrib, 2, GL_FLOAT, GL_FALSE, |
| 2 * sizeof (GLfloat), NULL); |
| |
| gl->GenBuffers (1, &overlay->index_buffer); |
| gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, overlay->index_buffer); |
| gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices, |
| GL_STATIC_DRAW); |
| |
| gl->EnableVertexAttribArray (overlay->position_attrib); |
| gl->EnableVertexAttribArray (overlay->texcoord_attrib); |
| |
| if (gl->GenVertexArrays) { |
| gl->BindVertexArray (0); |
| } |
| |
| gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); |
| gl->BindBuffer (GL_ARRAY_BUFFER, 0); |
| } |
| |
| static void |
| gst_gl_composition_overlay_free_vertex_buffer (GstGLContext * context, |
| gpointer overlay_pointer) |
| { |
| const GstGLFuncs *gl = context->gl_vtable; |
| GstGLCompositionOverlay *overlay = |
| (GstGLCompositionOverlay *) overlay_pointer; |
| if (overlay->vao) { |
| gl->DeleteVertexArrays (1, &overlay->vao); |
| overlay->vao = 0; |
| } |
| |
| if (overlay->position_buffer) { |
| gl->DeleteBuffers (1, &overlay->position_buffer); |
| overlay->position_buffer = 0; |
| } |
| |
| if (overlay->texcoord_buffer) { |
| gl->DeleteBuffers (1, &overlay->position_buffer); |
| overlay->position_buffer = 0; |
| } |
| |
| if (overlay->index_buffer) { |
| gl->DeleteBuffers (1, &overlay->index_buffer); |
| overlay->index_buffer = 0; |
| } |
| } |
| |
| static void |
| gst_gl_composition_overlay_bind_vertex_buffer (GstGLCompositionOverlay * |
| overlay) |
| { |
| const GstGLFuncs *gl = overlay->context->gl_vtable; |
| gl->BindBuffer (GL_ARRAY_BUFFER, overlay->position_buffer); |
| gl->VertexAttribPointer (overlay->position_attrib, 4, GL_FLOAT, GL_FALSE, |
| 4 * sizeof (GLfloat), NULL); |
| |
| gl->BindBuffer (GL_ARRAY_BUFFER, overlay->texcoord_buffer); |
| gl->VertexAttribPointer (overlay->texcoord_attrib, 2, GL_FLOAT, GL_FALSE, |
| 2 * sizeof (GLfloat), NULL); |
| |
| gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, overlay->index_buffer); |
| |
| gl->EnableVertexAttribArray (overlay->position_attrib); |
| gl->EnableVertexAttribArray (overlay->texcoord_attrib); |
| } |
| |
| static void |
| gst_gl_composition_overlay_finalize (GObject * object) |
| { |
| GstGLCompositionOverlay *overlay; |
| |
| overlay = GST_GL_COMPOSITION_OVERLAY (object); |
| |
| if (overlay->gl_memory) |
| gst_memory_unref ((GstMemory *) overlay->gl_memory); |
| |
| if (overlay->context) { |
| gst_gl_context_thread_add (overlay->context, |
| gst_gl_composition_overlay_free_vertex_buffer, overlay); |
| gst_object_unref (overlay->context); |
| } |
| |
| G_OBJECT_CLASS (gst_gl_composition_overlay_parent_class)->finalize (object); |
| } |
| |
| |
| static void |
| gst_gl_composition_overlay_class_init (GstGLCompositionOverlayClass * klass) |
| { |
| G_OBJECT_CLASS (klass)->finalize = gst_gl_composition_overlay_finalize; |
| } |
| |
| static void |
| gst_gl_composition_overlay_init (GstGLCompositionOverlay * overlay) |
| { |
| } |
| |
| static void |
| gst_gl_composition_overlay_add_transformation (GstGLCompositionOverlay * |
| overlay, GstBuffer * video_buffer) |
| { |
| gint comp_x, comp_y; |
| guint comp_width, comp_height; |
| GstVideoMeta *meta; |
| guint width, height; |
| |
| float rel_x, rel_y, rel_w, rel_h; |
| |
| meta = gst_buffer_get_video_meta (video_buffer); |
| |
| gst_video_overlay_rectangle_get_render_rectangle (overlay->rectangle, |
| &comp_x, &comp_y, &comp_width, &comp_height); |
| |
| width = meta->width; |
| height = meta->height; |
| |
| /* calculate relative position */ |
| rel_x = (float) comp_x / (float) width; |
| rel_y = (float) comp_y / (float) height; |
| |
| rel_w = (float) comp_width / (float) width; |
| rel_h = (float) comp_height / (float) height; |
| |
| /* transform from [0,1] to [-1,1], invert y axis */ |
| rel_x = rel_x * 2.0 - 1.0; |
| rel_y = (1.0 - rel_y) * 2.0 - 1.0; |
| rel_w = rel_w * 2.0; |
| rel_h = rel_h * 2.0; |
| |
| /* initialize position array */ |
| overlay->positions[0] = rel_x + rel_w; |
| overlay->positions[1] = rel_y; |
| overlay->positions[2] = 0.0; |
| overlay->positions[3] = 1.0; |
| overlay->positions[4] = rel_x; |
| overlay->positions[5] = rel_y; |
| overlay->positions[6] = 0.0; |
| overlay->positions[7] = 1.0; |
| overlay->positions[8] = rel_x; |
| overlay->positions[9] = rel_y - rel_h; |
| overlay->positions[10] = 0.0; |
| overlay->positions[11] = 1.0; |
| overlay->positions[12] = rel_x + rel_w; |
| overlay->positions[13] = rel_y - rel_h; |
| overlay->positions[14] = 0.0; |
| overlay->positions[15] = 1.0; |
| |
| gst_gl_context_thread_add (overlay->context, |
| gst_gl_composition_overlay_free_vertex_buffer, overlay); |
| |
| gst_gl_context_thread_add (overlay->context, |
| gst_gl_composition_overlay_init_vertex_buffer, overlay); |
| |
| GST_DEBUG |
| ("overlay position: (%d,%d) size: %dx%d video size: %dx%d", |
| comp_x, comp_y, comp_width, comp_height, meta->width, meta->height); |
| } |
| |
| /* helper object API functions */ |
| |
| static GstGLCompositionOverlay * |
| gst_gl_composition_overlay_new (GstGLContext * context, |
| GstVideoOverlayRectangle * rectangle, |
| GLint position_attrib, GLint texcoord_attrib) |
| { |
| GstGLCompositionOverlay *overlay = |
| g_object_new (GST_TYPE_GL_COMPOSITION_OVERLAY, NULL); |
| |
| overlay->gl_memory = NULL; |
| overlay->texture_id = -1; |
| overlay->rectangle = rectangle; |
| overlay->context = gst_object_ref (context); |
| overlay->vao = 0; |
| overlay->position_attrib = position_attrib; |
| overlay->texcoord_attrib = texcoord_attrib; |
| |
| GST_DEBUG_OBJECT (overlay, "Created new GstGLCompositionOverlay"); |
| |
| return overlay; |
| } |
| |
| static void |
| _video_frame_unmap_and_free (gpointer user_data) |
| { |
| GstVideoFrame *frame = user_data; |
| |
| gst_video_frame_unmap (frame); |
| g_slice_free (GstVideoFrame, frame); |
| } |
| |
| static void |
| gst_gl_composition_overlay_upload (GstGLCompositionOverlay * overlay, |
| GstBuffer * buf) |
| { |
| GstGLMemory *comp_gl_memory = NULL; |
| GstBuffer *comp_buffer = NULL; |
| GstBuffer *overlay_buffer = NULL; |
| GstVideoInfo vinfo; |
| GstVideoMeta *vmeta; |
| GstVideoFrame *comp_frame; |
| GstVideoFrame gl_frame; |
| |
| comp_buffer = |
| gst_video_overlay_rectangle_get_pixels_unscaled_argb (overlay->rectangle, |
| GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA); |
| |
| comp_frame = g_slice_new (GstVideoFrame); |
| |
| vmeta = gst_buffer_get_video_meta (comp_buffer); |
| gst_video_info_set_format (&vinfo, vmeta->format, vmeta->width, |
| vmeta->height); |
| vinfo.stride[0] = vmeta->stride[0]; |
| |
| if (gst_video_frame_map (comp_frame, &vinfo, comp_buffer, GST_MAP_READ)) { |
| GstGLVideoAllocationParams *params; |
| GstGLBaseMemoryAllocator *mem_allocator; |
| GstAllocator *allocator; |
| |
| allocator = |
| GST_ALLOCATOR (gst_gl_memory_allocator_get_default (overlay->context)); |
| mem_allocator = GST_GL_BASE_MEMORY_ALLOCATOR (allocator); |
| |
| gst_gl_composition_overlay_add_transformation (overlay, buf); |
| |
| params = gst_gl_video_allocation_params_new_wrapped_data (overlay->context, |
| NULL, &comp_frame->info, 0, NULL, GST_GL_TEXTURE_TARGET_2D, |
| comp_frame->data[0], comp_frame, _video_frame_unmap_and_free); |
| |
| comp_gl_memory = |
| (GstGLMemory *) gst_gl_base_memory_alloc (mem_allocator, |
| (GstGLAllocationParams *) params); |
| |
| gst_gl_allocation_params_free ((GstGLAllocationParams *) params); |
| gst_object_unref (allocator); |
| |
| overlay_buffer = gst_buffer_new (); |
| gst_buffer_append_memory (overlay_buffer, (GstMemory *) comp_gl_memory); |
| |
| if (!gst_video_frame_map (&gl_frame, &comp_frame->info, overlay_buffer, |
| GST_MAP_READ | GST_MAP_GL)) { |
| gst_buffer_unref (overlay_buffer); |
| _video_frame_unmap_and_free (comp_frame); |
| GST_WARNING_OBJECT (overlay, "Cannot upload overlay texture"); |
| return; |
| } |
| |
| gst_memory_ref ((GstMemory *) comp_gl_memory); |
| overlay->gl_memory = comp_gl_memory; |
| overlay->texture_id = comp_gl_memory->tex_id; |
| |
| gst_buffer_unref (overlay_buffer); |
| gst_video_frame_unmap (&gl_frame); |
| |
| GST_DEBUG ("uploaded overlay texture %d", overlay->texture_id); |
| } else { |
| g_slice_free (GstVideoFrame, comp_frame); |
| } |
| } |
| |
| static void |
| gst_gl_composition_overlay_draw (GstGLCompositionOverlay * overlay, |
| GstGLShader * shader) |
| { |
| const GstGLFuncs *gl = overlay->context->gl_vtable; |
| if (gl->GenVertexArrays) |
| gl->BindVertexArray (overlay->vao); |
| else |
| gst_gl_composition_overlay_bind_vertex_buffer (overlay); |
| |
| if (overlay->texture_id != -1) |
| gl->BindTexture (GL_TEXTURE_2D, overlay->texture_id); |
| gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); |
| } |
| |
| |
| /******************************************************************** |
| * GstGLOverlayCompositor object, the public helper object to render |
| * GstVideoCompositionOverlayMeta |
| ********************************************************************/ |
| |
| #define DEBUG_INIT \ |
| GST_DEBUG_CATEGORY_INIT (gst_gl_overlay_compositor_debug, \ |
| "gloverlaycompositor", 0, "overlaycompositor"); |
| |
| G_DEFINE_TYPE_WITH_CODE (GstGLOverlayCompositor, gst_gl_overlay_compositor, |
| GST_TYPE_OBJECT, DEBUG_INIT); |
| |
| static void gst_gl_overlay_compositor_finalize (GObject * object); |
| static gboolean _is_rectangle_in_overlays (GList * overlays, |
| GstVideoOverlayRectangle * rectangle); |
| static gboolean _is_overlay_in_rectangles (GstVideoOverlayComposition * |
| composition, GstGLCompositionOverlay * overlay); |
| |
| static void |
| gst_gl_overlay_compositor_class_init (GstGLOverlayCompositorClass * klass) |
| { |
| G_OBJECT_CLASS (klass)->finalize = gst_gl_overlay_compositor_finalize; |
| } |
| |
| static void |
| gst_gl_overlay_compositor_init (GstGLOverlayCompositor * compositor) |
| { |
| } |
| |
| static void |
| gst_gl_overlay_compositor_init_gl (GstGLContext * context, |
| gpointer compositor_pointer) |
| { |
| GstGLOverlayCompositor *compositor = |
| (GstGLOverlayCompositor *) compositor_pointer; |
| GError *error = NULL; |
| |
| if (!(compositor->shader = |
| gst_gl_shader_new_link_with_stages (context, &error, |
| gst_glsl_stage_new_default_vertex (context), |
| gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER, |
| GST_GLSL_VERSION_NONE, |
| GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY, |
| fragment_shader), NULL))) { |
| GST_ERROR_OBJECT (compositor, "could not initialize shader: %s", |
| error->message); |
| return; |
| } |
| |
| compositor->position_attrib = |
| gst_gl_shader_get_attribute_location (compositor->shader, "a_position"); |
| compositor->texcoord_attrib = |
| gst_gl_shader_get_attribute_location (compositor->shader, "a_texcoord"); |
| } |
| |
| GstGLOverlayCompositor * |
| gst_gl_overlay_compositor_new (GstGLContext * context) |
| { |
| GstGLOverlayCompositor *compositor = |
| g_object_new (GST_TYPE_GL_OVERLAY_COMPOSITOR, NULL); |
| |
| compositor->context = gst_object_ref (context); |
| |
| gst_gl_context_thread_add (compositor->context, |
| gst_gl_overlay_compositor_init_gl, compositor); |
| |
| GST_DEBUG_OBJECT (compositor, "Created new GstGLOverlayCompositor"); |
| |
| return compositor; |
| } |
| |
| static void |
| gst_gl_overlay_compositor_finalize (GObject * object) |
| { |
| GstGLOverlayCompositor *compositor; |
| |
| compositor = GST_GL_OVERLAY_COMPOSITOR (object); |
| |
| gst_gl_overlay_compositor_free_overlays (compositor); |
| |
| if (compositor->context) |
| gst_object_unref (compositor->context); |
| |
| if (compositor->shader) { |
| gst_object_unref (compositor->shader); |
| compositor->shader = NULL; |
| } |
| |
| G_OBJECT_CLASS (gst_gl_overlay_compositor_parent_class)->finalize (object); |
| } |
| |
| static gboolean |
| _is_rectangle_in_overlays (GList * overlays, |
| GstVideoOverlayRectangle * rectangle) |
| { |
| GList *l; |
| |
| for (l = overlays; l != NULL; l = l->next) { |
| GstGLCompositionOverlay *overlay = (GstGLCompositionOverlay *) l->data; |
| if (overlay->rectangle == rectangle) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| _is_overlay_in_rectangles (GstVideoOverlayComposition * composition, |
| GstGLCompositionOverlay * overlay) |
| { |
| guint i; |
| |
| for (i = 0; i < gst_video_overlay_composition_n_rectangles (composition); i++) { |
| GstVideoOverlayRectangle *rectangle = |
| gst_video_overlay_composition_get_rectangle (composition, i); |
| if (overlay->rectangle == rectangle) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| |
| void |
| gst_gl_overlay_compositor_free_overlays (GstGLOverlayCompositor * compositor) |
| { |
| GList *l = compositor->overlays; |
| while (l != NULL) { |
| GList *next = l->next; |
| GstGLCompositionOverlay *overlay = (GstGLCompositionOverlay *) l->data; |
| compositor->overlays = g_list_delete_link (compositor->overlays, l); |
| gst_object_unref (overlay); |
| l = next; |
| } |
| g_list_free (compositor->overlays); |
| compositor->overlays = NULL; |
| } |
| |
| void |
| gst_gl_overlay_compositor_upload_overlays (GstGLOverlayCompositor * compositor, |
| GstBuffer * buf) |
| { |
| GstVideoOverlayCompositionMeta *composition_meta; |
| |
| composition_meta = gst_buffer_get_video_overlay_composition_meta (buf); |
| if (composition_meta) { |
| GstVideoOverlayComposition *composition = NULL; |
| guint num_overlays, i; |
| GList *l = compositor->overlays; |
| |
| GST_DEBUG ("GstVideoOverlayCompositionMeta found."); |
| |
| composition = composition_meta->overlay; |
| num_overlays = gst_video_overlay_composition_n_rectangles (composition); |
| |
| /* add new overlays to list */ |
| for (i = 0; i < num_overlays; i++) { |
| GstVideoOverlayRectangle *rectangle = |
| gst_video_overlay_composition_get_rectangle (composition, i); |
| |
| if (!_is_rectangle_in_overlays (compositor->overlays, rectangle)) { |
| GstGLCompositionOverlay *overlay = |
| gst_gl_composition_overlay_new (compositor->context, rectangle, |
| compositor->position_attrib, compositor->texcoord_attrib); |
| |
| gst_gl_composition_overlay_upload (overlay, buf); |
| |
| compositor->overlays = g_list_append (compositor->overlays, overlay); |
| } |
| } |
| |
| /* remove old overlays from list */ |
| while (l != NULL) { |
| GList *next = l->next; |
| GstGLCompositionOverlay *overlay = (GstGLCompositionOverlay *) l->data; |
| if (!_is_overlay_in_rectangles (composition, overlay)) { |
| compositor->overlays = g_list_delete_link (compositor->overlays, l); |
| gst_object_unref (overlay); |
| } |
| l = next; |
| } |
| } else { |
| gst_gl_overlay_compositor_free_overlays (compositor); |
| } |
| } |
| |
| void |
| gst_gl_overlay_compositor_draw_overlays (GstGLOverlayCompositor * compositor) |
| { |
| const GstGLFuncs *gl = compositor->context->gl_vtable; |
| if (compositor->overlays != NULL) { |
| GList *l; |
| |
| gl->Enable (GL_BLEND); |
| gl->BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| |
| gst_gl_shader_use (compositor->shader); |
| gl->ActiveTexture (GL_TEXTURE0); |
| gst_gl_shader_set_uniform_1i (compositor->shader, "tex", 0); |
| |
| for (l = compositor->overlays; l != NULL; l = l->next) { |
| GstGLCompositionOverlay *overlay = (GstGLCompositionOverlay *) l->data; |
| gst_gl_composition_overlay_draw (overlay, compositor->shader); |
| } |
| |
| gl->BindTexture (GL_TEXTURE_2D, 0); |
| gl->Disable (GL_BLEND); |
| } |
| } |
| |
| GstCaps * |
| gst_gl_overlay_compositor_add_caps (GstCaps * caps) |
| { |
| GstCaps *composition_caps; |
| int i; |
| |
| composition_caps = gst_caps_copy (caps); |
| |
| for (i = 0; i < gst_caps_get_size (composition_caps); i++) { |
| GstCapsFeatures *f = gst_caps_get_features (composition_caps, i); |
| gst_caps_features_add (f, |
| GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION); |
| } |
| |
| caps = gst_caps_merge (composition_caps, caps); |
| |
| return caps; |
| } |