| /* GStreamer Video Overlay Composition |
| * Copyright (C) 2011 Intel Corporation |
| * Copyright (C) 2011 Collabora Ltd. |
| * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net> |
| * |
| * 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. |
| */ |
| |
| /** |
| * SECTION:gstvideooverlaycomposition |
| * @title: GstVideoOverlayRectangle |
| * @short_description: Video Buffer Overlay Compositions (Subtitles, Logos) |
| * |
| * Functions to create and handle overlay compositions on video buffers. |
| * |
| * An overlay composition describes one or more overlay rectangles to be |
| * blended on top of a video buffer. |
| * |
| * This API serves two main purposes: |
| * |
| * * it can be used to attach overlay information (subtitles or logos) |
| * to non-raw video buffers such as GL/VAAPI/VDPAU surfaces. The actual |
| * blending of the overlay can then be done by e.g. the video sink that |
| * processes these non-raw buffers. |
| * |
| * * it can also be used to blend overlay rectangles on top of raw video |
| * buffers, thus consolidating blending functionality for raw video in |
| * one place. |
| * |
| * Together, this allows existing overlay elements to easily handle raw |
| * and non-raw video as input in without major changes (once the overlays |
| * have been put into a #GstOverlayComposition object anyway) - for raw |
| * video the overlay can just use the blending function to blend the data |
| * on top of the video, and for surface buffers it can just attach them to |
| * the buffer and let the sink render the overlays. |
| * |
| */ |
| |
| /* TODO: |
| * - provide accessors for seq_num and other fields (as needed) |
| * - allow overlay to set/get original pango markup string on/from rectangle |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "video-overlay-composition.h" |
| #include "video-blend.h" |
| #include "gstvideometa.h" |
| #include <string.h> |
| |
| struct _GstVideoOverlayComposition |
| { |
| GstMiniObject parent; |
| |
| guint num_rectangles; |
| GstVideoOverlayRectangle **rectangles; |
| |
| /* lowest rectangle sequence number still used by the upstream |
| * overlay element. This way a renderer maintaining some kind of |
| * rectangles <-> surface cache can know when to free cached |
| * surfaces/rectangles. */ |
| guint min_seq_num_used; |
| |
| /* sequence number for the composition (same series as rectangles) */ |
| guint seq_num; |
| }; |
| |
| struct _GstVideoOverlayRectangle |
| { |
| GstMiniObject parent; |
| |
| /* Position on video frame and dimension of output rectangle in |
| * output frame terms (already adjusted for the PAR of the output |
| * frame). x/y can be negative (overlay will be clipped then) */ |
| gint x, y; |
| guint render_width, render_height; |
| |
| /* Info on overlay pixels (format, width, height) */ |
| GstVideoInfo info; |
| |
| /* The flags associated to this rectangle */ |
| GstVideoOverlayFormatFlags flags; |
| |
| /* Refcounted blob of memory, no caps or timestamps */ |
| GstBuffer *pixels; |
| |
| /* FIXME: how to express source like text or pango markup? |
| * (just add source type enum + source buffer with data) |
| * |
| * FOR 0.10: always send pixel blobs, but attach source data in |
| * addition (reason: if downstream changes, we can't renegotiate |
| * that properly, if we just do a query of supported formats from |
| * the start). Sink will just ignore pixels and use pango markup |
| * from source data if it supports that. |
| * |
| * FOR 0.11: overlay should query formats (pango markup, pixels) |
| * supported by downstream and then only send that. We can |
| * renegotiate via the reconfigure event. |
| */ |
| |
| /* sequence number: useful for backends/renderers/sinks that want |
| * to maintain a cache of rectangles <-> surfaces. The value of |
| * the min_seq_num_used in the composition tells the renderer which |
| * rectangles have expired. */ |
| guint seq_num; |
| |
| /* global alpha: global alpha value of the rectangle. Each each per-pixel |
| * alpha value of image-data will be multiplied with the global alpha value |
| * during blending. |
| * Can be used for efficient fading in/out of overlay rectangles. |
| * GstElements that render OverlayCompositions and don't support global alpha |
| * should simply ignore it.*/ |
| gfloat global_alpha; |
| |
| /* track alpha-values already applied: */ |
| gfloat applied_global_alpha; |
| /* store initial per-pixel alpha values: */ |
| guint8 *initial_alpha; |
| |
| /* FIXME: we may also need a (private) way to cache converted/scaled |
| * pixel blobs */ |
| GMutex lock; |
| |
| GList *scaled_rectangles; |
| }; |
| |
| #define GST_RECTANGLE_LOCK(rect) g_mutex_lock(&rect->lock) |
| #define GST_RECTANGLE_UNLOCK(rect) g_mutex_unlock(&rect->lock) |
| |
| /* --------------------------- utility functions --------------------------- */ |
| |
| #ifndef GST_DISABLE_GST_DEBUG |
| |
| #define GST_CAT_DEFAULT ensure_debug_category() |
| |
| static GstDebugCategory * |
| ensure_debug_category (void) |
| { |
| static gsize cat_gonce = 0; |
| |
| if (g_once_init_enter (&cat_gonce)) { |
| gsize cat_done; |
| |
| cat_done = (gsize) _gst_debug_category_new ("video-composition", 0, |
| "video overlay composition"); |
| |
| g_once_init_leave (&cat_gonce, cat_done); |
| } |
| |
| return (GstDebugCategory *) cat_gonce; |
| } |
| |
| #else |
| |
| #define ensure_debug_category() /* NOOP */ |
| |
| #endif /* GST_DISABLE_GST_DEBUG */ |
| |
| static guint |
| gst_video_overlay_get_seqnum (void) |
| { |
| static gint seqnum; /* 0 */ |
| |
| return (guint) g_atomic_int_add (&seqnum, 1); |
| } |
| |
| static gboolean |
| gst_video_overlay_composition_meta_init (GstMeta * meta, gpointer params, |
| GstBuffer * buf) |
| { |
| GstVideoOverlayCompositionMeta *ometa; |
| |
| ometa = (GstVideoOverlayCompositionMeta *) meta; |
| |
| ometa->overlay = NULL; |
| |
| return TRUE; |
| } |
| |
| static void |
| gst_video_overlay_composition_meta_free (GstMeta * meta, GstBuffer * buf) |
| { |
| GstVideoOverlayCompositionMeta *ometa; |
| |
| ometa = (GstVideoOverlayCompositionMeta *) meta; |
| |
| if (ometa->overlay) |
| gst_video_overlay_composition_unref (ometa->overlay); |
| } |
| |
| static gboolean |
| gst_video_overlay_composition_meta_transform (GstBuffer * dest, GstMeta * meta, |
| GstBuffer * buffer, GQuark type, gpointer data) |
| { |
| GstVideoOverlayCompositionMeta *dmeta, *smeta; |
| |
| smeta = (GstVideoOverlayCompositionMeta *) meta; |
| |
| if (GST_META_TRANSFORM_IS_COPY (type)) { |
| GstMetaTransformCopy *copy = data; |
| |
| if (!copy->region) { |
| GST_DEBUG ("copy video overlay composition metadata"); |
| |
| /* only copy if the complete data is copied as well */ |
| dmeta = |
| (GstVideoOverlayCompositionMeta *) gst_buffer_add_meta (dest, |
| GST_VIDEO_OVERLAY_COMPOSITION_META_INFO, NULL); |
| if (!dmeta) |
| return FALSE; |
| |
| dmeta->overlay = gst_video_overlay_composition_ref (smeta->overlay); |
| } |
| } else { |
| /* return FALSE, if transform type is not supported */ |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| GType |
| gst_video_overlay_composition_meta_api_get_type (void) |
| { |
| static volatile GType type = 0; |
| static const gchar *tags[] = { NULL }; |
| |
| if (g_once_init_enter (&type)) { |
| GType _type = |
| gst_meta_api_type_register ("GstVideoOverlayCompositionMetaAPI", tags); |
| g_once_init_leave (&type, _type); |
| } |
| return type; |
| } |
| |
| /* video overlay composition metadata */ |
| const GstMetaInfo * |
| gst_video_overlay_composition_meta_get_info (void) |
| { |
| static const GstMetaInfo *video_overlay_composition_meta_info = NULL; |
| |
| if (g_once_init_enter ((GstMetaInfo **) & |
| video_overlay_composition_meta_info)) { |
| const GstMetaInfo *meta = |
| gst_meta_register (GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, |
| "GstVideoOverlayCompositionMeta", |
| sizeof (GstVideoOverlayCompositionMeta), |
| (GstMetaInitFunction) gst_video_overlay_composition_meta_init, |
| (GstMetaFreeFunction) gst_video_overlay_composition_meta_free, |
| (GstMetaTransformFunction) |
| gst_video_overlay_composition_meta_transform); |
| g_once_init_leave ((GstMetaInfo **) & video_overlay_composition_meta_info, |
| (GstMetaInfo *) meta); |
| } |
| return video_overlay_composition_meta_info; |
| } |
| |
| /** |
| * gst_buffer_add_video_overlay_composition_meta: |
| * @buf: a #GstBuffer |
| * @comp: (allow-none): a #GstVideoOverlayComposition |
| * |
| * Sets an overlay composition on a buffer. The buffer will obtain its own |
| * reference to the composition, meaning this function does not take ownership |
| * of @comp. |
| * |
| * Returns: (transfer none): a #GstVideoOverlayCompositionMeta |
| */ |
| GstVideoOverlayCompositionMeta * |
| gst_buffer_add_video_overlay_composition_meta (GstBuffer * buf, |
| GstVideoOverlayComposition * comp) |
| { |
| GstVideoOverlayCompositionMeta *ometa; |
| |
| g_return_val_if_fail (gst_buffer_is_writable (buf), NULL); |
| |
| ometa = (GstVideoOverlayCompositionMeta *) |
| gst_buffer_add_meta (buf, GST_VIDEO_OVERLAY_COMPOSITION_META_INFO, NULL); |
| |
| ometa->overlay = gst_video_overlay_composition_ref (comp); |
| |
| return ometa; |
| } |
| |
| /* ------------------------------ composition ------------------------------ */ |
| |
| #define RECTANGLE_ARRAY_STEP 4 /* premature optimization */ |
| |
| GST_DEFINE_MINI_OBJECT_TYPE (GstVideoOverlayComposition, |
| gst_video_overlay_composition); |
| |
| static void |
| gst_video_overlay_composition_free (GstMiniObject * mini_obj) |
| { |
| GstVideoOverlayComposition *comp = (GstVideoOverlayComposition *) mini_obj; |
| guint num; |
| |
| num = comp->num_rectangles; |
| |
| while (num > 0) { |
| gst_video_overlay_rectangle_unref (comp->rectangles[num - 1]); |
| --num; |
| } |
| |
| g_free (comp->rectangles); |
| comp->rectangles = NULL; |
| comp->num_rectangles = 0; |
| |
| g_slice_free (GstVideoOverlayComposition, comp); |
| } |
| |
| /** |
| * gst_video_overlay_composition_new: |
| * @rectangle: (transfer none): a #GstVideoOverlayRectangle to add to the |
| * composition |
| * |
| * Creates a new video overlay composition object to hold one or more |
| * overlay rectangles. |
| * |
| * Returns: (transfer full): a new #GstVideoOverlayComposition. Unref with |
| * gst_video_overlay_composition_unref() when no longer needed. |
| */ |
| GstVideoOverlayComposition * |
| gst_video_overlay_composition_new (GstVideoOverlayRectangle * rectangle) |
| { |
| GstVideoOverlayComposition *comp; |
| |
| |
| /* FIXME: should we allow empty compositions? Could also be expressed as |
| * buffer without a composition on it. Maybe there are cases where doing |
| * an empty new + _add() in a loop is easier? */ |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
| |
| comp = g_slice_new0 (GstVideoOverlayComposition); |
| |
| gst_mini_object_init (GST_MINI_OBJECT_CAST (comp), 0, |
| GST_TYPE_VIDEO_OVERLAY_COMPOSITION, |
| (GstMiniObjectCopyFunction) gst_video_overlay_composition_copy, |
| NULL, (GstMiniObjectFreeFunction) gst_video_overlay_composition_free); |
| |
| comp->rectangles = g_new0 (GstVideoOverlayRectangle *, RECTANGLE_ARRAY_STEP); |
| comp->rectangles[0] = gst_video_overlay_rectangle_ref (rectangle); |
| comp->num_rectangles = 1; |
| |
| comp->seq_num = gst_video_overlay_get_seqnum (); |
| |
| /* since the rectangle was created earlier, its seqnum is smaller than ours */ |
| comp->min_seq_num_used = rectangle->seq_num; |
| |
| GST_LOG ("new composition %p: seq_num %u with rectangle %p", comp, |
| comp->seq_num, rectangle); |
| |
| return comp; |
| } |
| |
| /** |
| * gst_video_overlay_composition_add_rectangle: |
| * @comp: a #GstVideoOverlayComposition |
| * @rectangle: (transfer none): a #GstVideoOverlayRectangle to add to the |
| * composition |
| * |
| * Adds an overlay rectangle to an existing overlay composition object. This |
| * must be done right after creating the overlay composition. |
| */ |
| void |
| gst_video_overlay_composition_add_rectangle (GstVideoOverlayComposition * comp, |
| GstVideoOverlayRectangle * rectangle) |
| { |
| g_return_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp)); |
| g_return_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle)); |
| g_return_if_fail (GST_MINI_OBJECT_REFCOUNT_VALUE (comp) == 1); |
| |
| if (comp->num_rectangles % RECTANGLE_ARRAY_STEP == 0) { |
| comp->rectangles = |
| g_renew (GstVideoOverlayRectangle *, comp->rectangles, |
| comp->num_rectangles + RECTANGLE_ARRAY_STEP); |
| } |
| |
| comp->rectangles[comp->num_rectangles] = |
| gst_video_overlay_rectangle_ref (rectangle); |
| comp->num_rectangles += 1; |
| |
| comp->min_seq_num_used = MIN (comp->min_seq_num_used, rectangle->seq_num); |
| |
| GST_LOG ("composition %p: added rectangle %p", comp, rectangle); |
| } |
| |
| /** |
| * gst_video_overlay_composition_n_rectangles: |
| * @comp: a #GstVideoOverlayComposition |
| * |
| * Returns the number of #GstVideoOverlayRectangle<!-- -->s contained in @comp. |
| * |
| * Returns: the number of rectangles |
| */ |
| guint |
| gst_video_overlay_composition_n_rectangles (GstVideoOverlayComposition * comp) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), 0); |
| |
| return comp->num_rectangles; |
| } |
| |
| /** |
| * gst_video_overlay_composition_get_rectangle: |
| * @comp: a #GstVideoOverlayComposition |
| * @n: number of the rectangle to get |
| * |
| * Returns the @n-th #GstVideoOverlayRectangle contained in @comp. |
| * |
| * Returns: (transfer none): the @n-th rectangle, or NULL if @n is out of |
| * bounds. Will not return a new reference, the caller will need to |
| * obtain her own reference using gst_video_overlay_rectangle_ref() |
| * if needed. |
| */ |
| GstVideoOverlayRectangle * |
| gst_video_overlay_composition_get_rectangle (GstVideoOverlayComposition * comp, |
| guint n) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), NULL); |
| |
| if (n >= comp->num_rectangles) |
| return NULL; |
| |
| return comp->rectangles[n]; |
| } |
| |
| static gboolean |
| gst_video_overlay_rectangle_needs_scaling (GstVideoOverlayRectangle * r) |
| { |
| return (GST_VIDEO_INFO_WIDTH (&r->info) != r->render_width || |
| GST_VIDEO_INFO_HEIGHT (&r->info) != r->render_height); |
| } |
| |
| /** |
| * gst_video_overlay_composition_blend: |
| * @comp: a #GstVideoOverlayComposition |
| * @video_buf: a #GstVideoFrame containing raw video data in a |
| * supported format. It should be mapped using GST_MAP_READWRITE |
| * |
| * Blends the overlay rectangles in @comp on top of the raw video data |
| * contained in @video_buf. The data in @video_buf must be writable and |
| * mapped appropriately. |
| * |
| * Since @video_buf data is read and will be modified, it ought be |
| * mapped with flag GST_MAP_READWRITE. |
| */ |
| /* FIXME: formats with more than 8 bit per component which get unpacked into |
| * ARGB64 or AYUV64 (such as v210, v216, UYVP, GRAY16_LE and GRAY16_BE) |
| * are not supported yet by the code in video-blend.c. |
| */ |
| gboolean |
| gst_video_overlay_composition_blend (GstVideoOverlayComposition * comp, |
| GstVideoFrame * video_buf) |
| { |
| GstVideoInfo scaled_info; |
| GstVideoInfo *vinfo; |
| GstVideoFrame rectangle_frame; |
| GstVideoFormat fmt; |
| GstBuffer *pixels = NULL; |
| gboolean ret = TRUE; |
| guint n, num; |
| int w, h; |
| |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), FALSE); |
| g_return_val_if_fail (video_buf != NULL, FALSE); |
| |
| w = GST_VIDEO_FRAME_WIDTH (video_buf); |
| h = GST_VIDEO_FRAME_HEIGHT (video_buf); |
| fmt = GST_VIDEO_FRAME_FORMAT (video_buf); |
| |
| num = comp->num_rectangles; |
| GST_LOG ("Blending composition %p with %u rectangles onto video buffer %p " |
| "(%ux%u, format %u)", comp, num, video_buf, w, h, fmt); |
| |
| for (n = 0; n < num; ++n) { |
| GstVideoOverlayRectangle *rect; |
| gboolean needs_scaling; |
| |
| rect = comp->rectangles[n]; |
| |
| GST_LOG (" rectangle %u %p: %ux%u, format %u", n, rect, |
| GST_VIDEO_INFO_WIDTH (&rect->info), GST_VIDEO_INFO_HEIGHT (&rect->info), |
| GST_VIDEO_INFO_FORMAT (&rect->info)); |
| |
| needs_scaling = gst_video_overlay_rectangle_needs_scaling (rect); |
| if (needs_scaling) { |
| gst_video_blend_scale_linear_RGBA (&rect->info, rect->pixels, |
| rect->render_height, rect->render_width, &scaled_info, &pixels); |
| vinfo = &scaled_info; |
| } else { |
| pixels = gst_buffer_ref (rect->pixels); |
| vinfo = &rect->info; |
| } |
| |
| gst_video_frame_map (&rectangle_frame, vinfo, pixels, GST_MAP_READ); |
| |
| ret = gst_video_blend (video_buf, &rectangle_frame, rect->x, rect->y, |
| rect->global_alpha); |
| gst_video_frame_unmap (&rectangle_frame); |
| if (!ret) { |
| GST_WARNING ("Could not blend overlay rectangle onto video buffer"); |
| } |
| |
| /* FIXME: should cache scaled pixels in the rectangle struct */ |
| gst_buffer_unref (pixels); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * gst_video_overlay_composition_copy: |
| * @comp: (transfer none): a #GstVideoOverlayComposition to copy |
| * |
| * Makes a copy of @comp and all contained rectangles, so that it is possible |
| * to modify the composition and contained rectangles (e.g. add additional |
| * rectangles or change the render co-ordinates or render dimension). The |
| * actual overlay pixel data buffers contained in the rectangles are not |
| * copied. |
| * |
| * Returns: (transfer full): a new #GstVideoOverlayComposition equivalent |
| * to @comp. |
| */ |
| GstVideoOverlayComposition * |
| gst_video_overlay_composition_copy (GstVideoOverlayComposition * comp) |
| { |
| GstVideoOverlayComposition *copy; |
| GstVideoOverlayRectangle *rect; |
| guint n; |
| |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), NULL); |
| |
| if (G_LIKELY (comp->num_rectangles == 0)) |
| return gst_video_overlay_composition_new (NULL); |
| |
| rect = gst_video_overlay_rectangle_copy (comp->rectangles[0]); |
| copy = gst_video_overlay_composition_new (rect); |
| gst_video_overlay_rectangle_unref (rect); |
| |
| for (n = 1; n < comp->num_rectangles; ++n) { |
| rect = gst_video_overlay_rectangle_copy (comp->rectangles[n]); |
| gst_video_overlay_composition_add_rectangle (copy, rect); |
| gst_video_overlay_rectangle_unref (rect); |
| } |
| |
| return copy; |
| } |
| |
| /** |
| * gst_video_overlay_composition_make_writable: |
| * @comp: (transfer full): a #GstVideoOverlayComposition to copy |
| * |
| * Takes ownership of @comp and returns a version of @comp that is writable |
| * (i.e. can be modified). Will either return @comp right away, or create a |
| * new writable copy of @comp and unref @comp itself. All the contained |
| * rectangles will also be copied, but the actual overlay pixel data buffers |
| * contained in the rectangles are not copied. |
| * |
| * Returns: (transfer full): a writable #GstVideoOverlayComposition |
| * equivalent to @comp. |
| */ |
| GstVideoOverlayComposition * |
| gst_video_overlay_composition_make_writable (GstVideoOverlayComposition * comp) |
| { |
| GstVideoOverlayComposition *writable_comp; |
| |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), NULL); |
| |
| if (GST_MINI_OBJECT_REFCOUNT_VALUE (comp) == 1) { |
| guint n; |
| |
| for (n = 0; n < comp->num_rectangles; ++n) { |
| if (GST_MINI_OBJECT_REFCOUNT_VALUE (comp->rectangles[n]) != 1) |
| goto copy; |
| } |
| return comp; |
| } |
| |
| copy: |
| |
| writable_comp = gst_video_overlay_composition_copy (comp); |
| gst_video_overlay_composition_unref (comp); |
| |
| return writable_comp; |
| } |
| |
| /** |
| * gst_video_overlay_composition_get_seqnum: |
| * @comp: a #GstVideoOverlayComposition |
| * |
| * Returns the sequence number of this composition. Sequence numbers are |
| * monotonically increasing and unique for overlay compositions and rectangles |
| * (meaning there will never be a rectangle with the same sequence number as |
| * a composition). |
| * |
| * Returns: the sequence number of @comp |
| */ |
| guint |
| gst_video_overlay_composition_get_seqnum (GstVideoOverlayComposition * comp) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_COMPOSITION (comp), 0); |
| |
| return comp->seq_num; |
| } |
| |
| /* ------------------------------ rectangles ------------------------------ -*/ |
| |
| GST_DEFINE_MINI_OBJECT_TYPE (GstVideoOverlayRectangle, |
| gst_video_overlay_rectangle); |
| |
| static void |
| gst_video_overlay_rectangle_free (GstMiniObject * mini_obj) |
| { |
| GstVideoOverlayRectangle *rect = (GstVideoOverlayRectangle *) mini_obj; |
| |
| gst_buffer_replace (&rect->pixels, NULL); |
| |
| while (rect->scaled_rectangles != NULL) { |
| GstVideoOverlayRectangle *scaled_rect = rect->scaled_rectangles->data; |
| |
| gst_video_overlay_rectangle_unref (scaled_rect); |
| |
| rect->scaled_rectangles = |
| g_list_delete_link (rect->scaled_rectangles, rect->scaled_rectangles); |
| } |
| |
| g_free (rect->initial_alpha); |
| g_mutex_clear (&rect->lock); |
| |
| g_slice_free (GstVideoOverlayRectangle, rect); |
| } |
| |
| static inline gboolean |
| gst_video_overlay_rectangle_check_flags (GstVideoOverlayFormatFlags flags) |
| { |
| /* Check flags only contains flags we know about */ |
| return (flags & ~(GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA | |
| GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA)) == 0; |
| } |
| |
| static gboolean |
| gst_video_overlay_rectangle_is_same_alpha_type (GstVideoOverlayFormatFlags |
| flags1, GstVideoOverlayFormatFlags flags2) |
| { |
| return ((flags1 ^ flags2) & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA) |
| == 0; |
| } |
| |
| |
| /** |
| * gst_video_overlay_rectangle_new_raw: |
| * @pixels: (transfer none): a #GstBuffer pointing to the pixel memory |
| * @render_x: the X co-ordinate on the video where the top-left corner of this |
| * overlay rectangle should be rendered to |
| * @render_y: the Y co-ordinate on the video where the top-left corner of this |
| * overlay rectangle should be rendered to |
| * @render_width: the render width of this rectangle on the video |
| * @render_height: the render height of this rectangle on the video |
| * @flags: flags |
| * |
| * Creates a new video overlay rectangle with ARGB or AYUV pixel data. |
| * The layout in case of ARGB of the components in memory is B-G-R-A |
| * on little-endian platforms |
| * (corresponding to #GST_VIDEO_FORMAT_BGRA) and A-R-G-B on big-endian |
| * platforms (corresponding to #GST_VIDEO_FORMAT_ARGB). In other words, |
| * pixels are treated as 32-bit words and the lowest 8 bits then contain |
| * the blue component value and the highest 8 bits contain the alpha |
| * component value. Unless specified in the flags, the RGB values are |
| * non-premultiplied. This is the format that is used by most hardware, |
| * and also many rendering libraries such as Cairo, for example. |
| * The pixel data buffer must have #GstVideoMeta set. |
| * |
| * Returns: (transfer full): a new #GstVideoOverlayRectangle. Unref with |
| * gst_video_overlay_rectangle_unref() when no longer needed. |
| */ |
| GstVideoOverlayRectangle * |
| gst_video_overlay_rectangle_new_raw (GstBuffer * pixels, |
| gint render_x, gint render_y, guint render_width, guint render_height, |
| GstVideoOverlayFormatFlags flags) |
| { |
| GstVideoOverlayRectangle *rect; |
| GstVideoMeta *vmeta; |
| GstVideoFormat format; |
| guint width, height; |
| |
| g_return_val_if_fail (GST_IS_BUFFER (pixels), NULL); |
| g_return_val_if_fail (render_height > 0 && render_width > 0, NULL); |
| g_return_val_if_fail (gst_video_overlay_rectangle_check_flags (flags), NULL); |
| |
| /* buffer must have video meta with some expected settings */ |
| vmeta = gst_buffer_get_video_meta (pixels); |
| g_return_val_if_fail (vmeta, NULL); |
| g_return_val_if_fail (vmeta->format == |
| GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB || |
| vmeta->format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV, NULL); |
| g_return_val_if_fail (vmeta->flags == GST_VIDEO_FRAME_FLAG_NONE, NULL); |
| |
| format = vmeta->format; |
| width = vmeta->width; |
| height = vmeta->height; |
| |
| /* technically ((height-1)*stride)+width might be okay too */ |
| g_return_val_if_fail (gst_buffer_get_size (pixels) >= height * width * 4, |
| NULL); |
| g_return_val_if_fail (height > 0 && width > 0, NULL); |
| |
| rect = g_slice_new0 (GstVideoOverlayRectangle); |
| |
| gst_mini_object_init (GST_MINI_OBJECT_CAST (rect), 0, |
| GST_TYPE_VIDEO_OVERLAY_RECTANGLE, |
| (GstMiniObjectCopyFunction) gst_video_overlay_rectangle_copy, |
| NULL, (GstMiniObjectFreeFunction) gst_video_overlay_rectangle_free); |
| |
| g_mutex_init (&rect->lock); |
| |
| rect->pixels = gst_buffer_ref (pixels); |
| rect->scaled_rectangles = NULL; |
| |
| gst_video_info_init (&rect->info); |
| if (!gst_video_info_set_format (&rect->info, format, width, height)) { |
| gst_mini_object_unref (GST_MINI_OBJECT_CAST (rect)); |
| return NULL; |
| } |
| if (flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA) |
| rect->info.flags |= GST_VIDEO_FLAG_PREMULTIPLIED_ALPHA; |
| |
| rect->x = render_x; |
| rect->y = render_y; |
| rect->render_width = render_width; |
| rect->render_height = render_height; |
| |
| rect->global_alpha = 1.0; |
| rect->applied_global_alpha = 1.0; |
| rect->initial_alpha = NULL; |
| |
| rect->flags = flags; |
| |
| rect->seq_num = gst_video_overlay_get_seqnum (); |
| |
| GST_LOG ("new rectangle %p: %ux%u => %ux%u @ %u,%u, seq_num %u, format %u, " |
| "flags %x, pixels %p, global_alpha=%f", rect, width, height, render_width, |
| render_height, render_x, render_y, rect->seq_num, format, |
| rect->flags, pixels, rect->global_alpha); |
| |
| return rect; |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_get_render_rectangle: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * @render_x: (out) (allow-none): address where to store the X render offset |
| * @render_y: (out) (allow-none): address where to store the Y render offset |
| * @render_width: (out) (allow-none): address where to store the render width |
| * @render_height: (out) (allow-none): address where to store the render height |
| * |
| * Retrieves the render position and render dimension of the overlay |
| * rectangle on the video. |
| * |
| * Returns: TRUE if valid render dimensions were retrieved. |
| */ |
| gboolean |
| gst_video_overlay_rectangle_get_render_rectangle (GstVideoOverlayRectangle * |
| rectangle, gint * render_x, gint * render_y, guint * render_width, |
| guint * render_height) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), FALSE); |
| |
| if (render_x) |
| *render_x = rectangle->x; |
| if (render_y) |
| *render_y = rectangle->y; |
| if (render_width) |
| *render_width = rectangle->render_width; |
| if (render_height) |
| *render_height = rectangle->render_height; |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_set_render_rectangle: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * @render_x: render X position of rectangle on video |
| * @render_y: render Y position of rectangle on video |
| * @render_width: render width of rectangle |
| * @render_height: render height of rectangle |
| * |
| * Sets the render position and dimensions of the rectangle on the video. |
| * This function is mainly for elements that modify the size of the video |
| * in some way (e.g. through scaling or cropping) and need to adjust the |
| * details of any overlays to match the operation that changed the size. |
| * |
| * @rectangle must be writable, meaning its refcount must be 1. You can |
| * make the rectangles inside a #GstVideoOverlayComposition writable using |
| * gst_video_overlay_composition_make_writable() or |
| * gst_video_overlay_composition_copy(). |
| */ |
| void |
| gst_video_overlay_rectangle_set_render_rectangle (GstVideoOverlayRectangle * |
| rectangle, gint render_x, gint render_y, guint render_width, |
| guint render_height) |
| { |
| g_return_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle)); |
| g_return_if_fail (GST_MINI_OBJECT_REFCOUNT_VALUE (rectangle) == 1); |
| |
| rectangle->x = render_x; |
| rectangle->y = render_y; |
| rectangle->render_width = render_width; |
| rectangle->render_height = render_height; |
| } |
| |
| /* FIXME: orc-ify */ |
| static void |
| gst_video_overlay_rectangle_premultiply_0 (GstVideoFrame * frame) |
| { |
| int i, j; |
| for (j = 0; j < GST_VIDEO_FRAME_HEIGHT (frame); ++j) { |
| guint8 *line; |
| |
| line = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); |
| line += GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) * j; |
| for (i = 0; i < GST_VIDEO_FRAME_WIDTH (frame); ++i) { |
| int a = line[0]; |
| line[1] = line[1] * a / 255; |
| line[2] = line[2] * a / 255; |
| line[3] = line[3] * a / 255; |
| line += 4; |
| } |
| } |
| } |
| |
| static void |
| gst_video_overlay_rectangle_premultiply_3 (GstVideoFrame * frame) |
| { |
| int i, j; |
| for (j = 0; j < GST_VIDEO_FRAME_HEIGHT (frame); ++j) { |
| guint8 *line; |
| |
| line = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); |
| line += GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) * j; |
| for (i = 0; i < GST_VIDEO_FRAME_WIDTH (frame); ++i) { |
| int a = line[3]; |
| line[0] = line[0] * a / 255; |
| line[1] = line[1] * a / 255; |
| line[2] = line[2] * a / 255; |
| line += 4; |
| } |
| } |
| } |
| |
| static void |
| gst_video_overlay_rectangle_premultiply (GstVideoFrame * frame) |
| { |
| gint alpha_offset; |
| |
| alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3); |
| switch (alpha_offset) { |
| case 0: |
| gst_video_overlay_rectangle_premultiply_0 (frame); |
| break; |
| case 3: |
| gst_video_overlay_rectangle_premultiply_3 (frame); |
| break; |
| default: |
| g_assert_not_reached (); |
| break; |
| } |
| } |
| |
| /* FIXME: orc-ify */ |
| static void |
| gst_video_overlay_rectangle_unpremultiply_0 (GstVideoFrame * frame) |
| { |
| int i, j; |
| for (j = 0; j < GST_VIDEO_FRAME_HEIGHT (frame); ++j) { |
| guint8 *line; |
| |
| line = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); |
| line += GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) * j; |
| for (i = 0; i < GST_VIDEO_FRAME_WIDTH (frame); ++i) { |
| int a = line[0]; |
| if (a) { |
| line[1] = MIN ((line[1] * 255 + a / 2) / a, 255); |
| line[2] = MIN ((line[2] * 255 + a / 2) / a, 255); |
| line[3] = MIN ((line[3] * 255 + a / 2) / a, 255); |
| } |
| line += 4; |
| } |
| } |
| } |
| |
| static void |
| gst_video_overlay_rectangle_unpremultiply_3 (GstVideoFrame * frame) |
| { |
| int i, j; |
| for (j = 0; j < GST_VIDEO_FRAME_HEIGHT (frame); ++j) { |
| guint8 *line; |
| |
| line = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); |
| line += GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) * j; |
| for (i = 0; i < GST_VIDEO_FRAME_WIDTH (frame); ++i) { |
| int a = line[3]; |
| if (a) { |
| line[0] = MIN ((line[0] * 255 + a / 2) / a, 255); |
| line[1] = MIN ((line[1] * 255 + a / 2) / a, 255); |
| line[2] = MIN ((line[2] * 255 + a / 2) / a, 255); |
| } |
| line += 4; |
| } |
| } |
| } |
| |
| static void |
| gst_video_overlay_rectangle_unpremultiply (GstVideoFrame * frame) |
| { |
| gint alpha_offset; |
| |
| alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3); |
| switch (alpha_offset) { |
| case 0: |
| gst_video_overlay_rectangle_unpremultiply_0 (frame); |
| break; |
| case 3: |
| gst_video_overlay_rectangle_unpremultiply_3 (frame); |
| break; |
| default: |
| g_assert_not_reached (); |
| break; |
| } |
| } |
| |
| |
| static void |
| gst_video_overlay_rectangle_extract_alpha (GstVideoOverlayRectangle * rect) |
| { |
| guint8 *src, *dst; |
| GstVideoFrame frame; |
| gint i, j, w, h, stride, alpha_offset; |
| |
| alpha_offset = GST_VIDEO_INFO_COMP_POFFSET (&rect->info, 3); |
| g_return_if_fail (alpha_offset == 0 || alpha_offset == 3); |
| |
| gst_video_frame_map (&frame, &rect->info, rect->pixels, GST_MAP_READ); |
| src = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0); |
| w = GST_VIDEO_INFO_WIDTH (&rect->info); |
| h = GST_VIDEO_INFO_HEIGHT (&rect->info); |
| stride = GST_VIDEO_INFO_PLANE_STRIDE (&rect->info, 0); |
| |
| g_free (rect->initial_alpha); |
| rect->initial_alpha = g_malloc (w * h); |
| dst = rect->initial_alpha; |
| |
| for (i = 0; i < h; i++) { |
| for (j = 0; j < w; j++) { |
| *dst = src[alpha_offset]; |
| dst++; |
| src += 4; |
| } |
| src += stride - 4 * w; |
| } |
| gst_video_frame_unmap (&frame); |
| } |
| |
| |
| static void |
| gst_video_overlay_rectangle_apply_global_alpha (GstVideoOverlayRectangle * rect, |
| float global_alpha) |
| { |
| guint8 *src, *dst; |
| GstVideoFrame frame; |
| gint i, j, w, h, stride; |
| gint argb_a, argb_r, argb_g, argb_b; |
| gint alpha_offset; |
| |
| g_assert (!(rect->applied_global_alpha != 1.0 |
| && rect->initial_alpha == NULL)); |
| |
| alpha_offset = GST_VIDEO_INFO_COMP_POFFSET (&rect->info, 3); |
| g_return_if_fail (alpha_offset == 0 || alpha_offset == 3); |
| |
| if (global_alpha == rect->applied_global_alpha) |
| return; |
| |
| if (rect->initial_alpha == NULL) |
| gst_video_overlay_rectangle_extract_alpha (rect); |
| |
| src = rect->initial_alpha; |
| rect->pixels = gst_buffer_make_writable (rect->pixels); |
| |
| gst_video_frame_map (&frame, &rect->info, rect->pixels, GST_MAP_READ); |
| dst = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0); |
| w = GST_VIDEO_INFO_WIDTH (&rect->info); |
| h = GST_VIDEO_INFO_HEIGHT (&rect->info); |
| stride = GST_VIDEO_INFO_PLANE_STRIDE (&rect->info, 0); |
| |
| argb_a = GST_VIDEO_INFO_COMP_POFFSET (&rect->info, 3); |
| argb_r = (argb_a + 1) % 4; |
| argb_g = (argb_a + 2) % 4; |
| argb_b = (argb_a + 3) % 4; |
| |
| for (i = 0; i < h; i++) { |
| for (j = 0; j < w; j++) { |
| guint8 na = (guint8) (*src * global_alpha); |
| |
| if (! !(rect->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)) { |
| dst[argb_r] = |
| (guint8) ((double) (dst[argb_r] * 255) / (double) dst[argb_a]) * |
| na / 255; |
| dst[argb_g] = |
| (guint8) ((double) (dst[argb_g] * 255) / (double) dst[argb_a]) * |
| na / 255; |
| dst[argb_b] = |
| (guint8) ((double) (dst[argb_b] * 255) / (double) dst[argb_a]) * |
| na / 255; |
| } |
| dst[argb_a] = na; |
| src++; |
| dst += 4; |
| } |
| dst += stride - 4 * w; |
| } |
| gst_video_frame_unmap (&frame); |
| |
| rect->applied_global_alpha = global_alpha; |
| } |
| |
| static void |
| gst_video_overlay_rectangle_convert (GstVideoInfo * src, GstBuffer * src_buffer, |
| GstVideoFormat dest_format, GstVideoInfo * dest, GstBuffer ** dest_buffer) |
| { |
| gint width, height, stride; |
| GstVideoFrame src_frame, dest_frame; |
| GstVideoFormat format; |
| gint k, l; |
| guint8 *sdata, *ddata; |
| |
| format = GST_VIDEO_INFO_FORMAT (src); |
| |
| width = GST_VIDEO_INFO_WIDTH (src); |
| height = GST_VIDEO_INFO_HEIGHT (src); |
| |
| gst_video_info_init (dest); |
| if (!gst_video_info_set_format (dest, dest_format, width, height)) { |
| g_warn_if_reached (); |
| return; |
| } |
| |
| *dest_buffer = gst_buffer_new_and_alloc (GST_VIDEO_INFO_SIZE (dest)); |
| |
| gst_video_frame_map (&src_frame, src, src_buffer, GST_MAP_READ); |
| gst_video_frame_map (&dest_frame, dest, *dest_buffer, GST_MAP_WRITE); |
| |
| sdata = GST_VIDEO_FRAME_PLANE_DATA (&src_frame, 0); |
| ddata = GST_VIDEO_FRAME_PLANE_DATA (&dest_frame, 0); |
| stride = GST_VIDEO_FRAME_PLANE_STRIDE (&src_frame, 0); |
| |
| if (format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV && |
| dest_format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB) { |
| gint ayuv; |
| gint a, y, u, v, r, g, b; |
| |
| for (k = 0; k < height; k++) { |
| for (l = 0; l < width; l++) { |
| ayuv = GST_READ_UINT32_BE (sdata); |
| a = ayuv >> 24; |
| y = (ayuv >> 16) & 0xff; |
| u = (ayuv >> 8) & 0xff; |
| v = (ayuv & 0xff); |
| |
| r = (298 * y + 459 * v - 63514) >> 8; |
| g = (298 * y - 55 * u - 136 * v + 19681) >> 8; |
| b = (298 * y + 541 * u - 73988) >> 8; |
| |
| r = CLAMP (r, 0, 255); |
| g = CLAMP (g, 0, 255); |
| b = CLAMP (b, 0, 255); |
| |
| /* native endian ARGB */ |
| *(guint32 *) ddata = ((a << 24) | (r << 16) | (g << 8) | b); |
| |
| sdata += 4; |
| ddata += 4; |
| } |
| sdata += stride - 4 * width; |
| } |
| } else if (format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB && |
| dest_format == GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV) { |
| gint argb; |
| gint a, y, u, v, r, g, b; |
| |
| for (k = 0; k < height; k++) { |
| for (l = 0; l < width; l++) { |
| /* native endian ARGB */ |
| argb = *(guint32 *) sdata; |
| a = argb >> 24; |
| r = (argb >> 16) & 0xff; |
| g = (argb >> 8) & 0xff; |
| b = (argb & 0xff); |
| |
| y = (47 * r + 157 * g + 16 * b + 4096) >> 8; |
| u = (-26 * r - 87 * g + 112 * b + 32768) >> 8; |
| v = (112 * r - 102 * g - 10 * b + 32768) >> 8; |
| |
| y = CLAMP (y, 0, 255); |
| u = CLAMP (u, 0, 255); |
| v = CLAMP (v, 0, 255); |
| |
| GST_WRITE_UINT32_BE (ddata, ((a << 24) | (y << 16) | (u << 8) | v)); |
| |
| sdata += 4; |
| ddata += 4; |
| } |
| sdata += stride - 4 * width; |
| } |
| } else { |
| GST_ERROR ("unsupported conversion"); |
| g_assert_not_reached (); |
| } |
| |
| gst_video_frame_unmap (&src_frame); |
| gst_video_frame_unmap (&dest_frame); |
| } |
| |
| static GstBuffer * |
| gst_video_overlay_rectangle_get_pixels_raw_internal (GstVideoOverlayRectangle * |
| rectangle, GstVideoOverlayFormatFlags flags, gboolean unscaled, |
| GstVideoFormat wanted_format) |
| { |
| GstVideoOverlayFormatFlags new_flags; |
| GstVideoOverlayRectangle *scaled_rect = NULL, *conv_rect = NULL; |
| GstVideoInfo info; |
| GstVideoFrame frame; |
| GstBuffer *buf; |
| GList *l; |
| guint width, height; |
| guint wanted_width; |
| guint wanted_height; |
| gboolean apply_global_alpha; |
| gboolean revert_global_alpha; |
| GstVideoFormat format; |
| |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
| g_return_val_if_fail (gst_video_overlay_rectangle_check_flags (flags), NULL); |
| |
| width = GST_VIDEO_INFO_WIDTH (&rectangle->info); |
| height = GST_VIDEO_INFO_HEIGHT (&rectangle->info); |
| wanted_width = unscaled ? width : rectangle->render_width; |
| wanted_height = unscaled ? height : rectangle->render_height; |
| format = GST_VIDEO_INFO_FORMAT (&rectangle->info); |
| |
| apply_global_alpha = |
| (! !(rectangle->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA) |
| && !(flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA)); |
| revert_global_alpha = |
| (! !(rectangle->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA) |
| && ! !(flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA)); |
| |
| /* This assumes we don't need to adjust the format */ |
| if (wanted_width == width && |
| wanted_height == height && |
| wanted_format == format && |
| gst_video_overlay_rectangle_is_same_alpha_type (rectangle->flags, |
| flags)) { |
| /* don't need to apply/revert global-alpha either: */ |
| if ((!apply_global_alpha |
| || rectangle->applied_global_alpha == rectangle->global_alpha) |
| && (!revert_global_alpha || rectangle->applied_global_alpha == 1.0)) { |
| return rectangle->pixels; |
| } else { |
| /* only apply/revert global-alpha */ |
| scaled_rect = rectangle; |
| goto done; |
| } |
| } |
| |
| /* see if we've got one cached already */ |
| GST_RECTANGLE_LOCK (rectangle); |
| for (l = rectangle->scaled_rectangles; l != NULL; l = l->next) { |
| GstVideoOverlayRectangle *r = l->data; |
| |
| if (GST_VIDEO_INFO_WIDTH (&r->info) == wanted_width && |
| GST_VIDEO_INFO_HEIGHT (&r->info) == wanted_height && |
| GST_VIDEO_INFO_FORMAT (&r->info) == wanted_format && |
| gst_video_overlay_rectangle_is_same_alpha_type (r->flags, flags)) { |
| /* we'll keep these rectangles around until finalize, so it's ok not |
| * to take our own ref here */ |
| scaled_rect = r; |
| break; |
| } |
| } |
| GST_RECTANGLE_UNLOCK (rectangle); |
| |
| if (scaled_rect != NULL) |
| goto done; |
| |
| /* maybe have one in the right format though */ |
| if (format != wanted_format) { |
| GST_RECTANGLE_LOCK (rectangle); |
| for (l = rectangle->scaled_rectangles; l != NULL; l = l->next) { |
| GstVideoOverlayRectangle *r = l->data; |
| |
| if (GST_VIDEO_INFO_FORMAT (&r->info) == wanted_format && |
| gst_video_overlay_rectangle_is_same_alpha_type (r->flags, flags)) { |
| /* we'll keep these rectangles around until finalize, so it's ok not |
| * to take our own ref here */ |
| conv_rect = r; |
| break; |
| } |
| } |
| GST_RECTANGLE_UNLOCK (rectangle); |
| } else { |
| conv_rect = rectangle; |
| } |
| |
| if (conv_rect == NULL) { |
| GstVideoInfo conv_info; |
| |
| gst_video_overlay_rectangle_convert (&rectangle->info, rectangle->pixels, |
| wanted_format, &conv_info, &buf); |
| gst_buffer_add_video_meta (buf, GST_VIDEO_FRAME_FLAG_NONE, |
| GST_VIDEO_INFO_FORMAT (&conv_info), width, height); |
| conv_rect = gst_video_overlay_rectangle_new_raw (buf, |
| 0, 0, width, height, rectangle->flags); |
| if (rectangle->global_alpha != 1.0) |
| gst_video_overlay_rectangle_set_global_alpha (scaled_rect, |
| rectangle->global_alpha); |
| gst_buffer_unref (buf); |
| /* keep this converted one around as well in any case */ |
| GST_RECTANGLE_LOCK (rectangle); |
| rectangle->scaled_rectangles = |
| g_list_prepend (rectangle->scaled_rectangles, conv_rect); |
| GST_RECTANGLE_UNLOCK (rectangle); |
| } |
| |
| /* now we continue from conv_rect */ |
| width = GST_VIDEO_INFO_WIDTH (&conv_rect->info); |
| height = GST_VIDEO_INFO_HEIGHT (&conv_rect->info); |
| format = GST_VIDEO_INFO_FORMAT (&conv_rect->info); |
| |
| /* not cached yet, do the preprocessing and put the result into our cache */ |
| if (wanted_width != width || wanted_height != height) { |
| GstVideoInfo scaled_info; |
| |
| /* we could check the cache for a scaled rect with global_alpha == 1 here */ |
| gst_video_blend_scale_linear_RGBA (&conv_rect->info, conv_rect->pixels, |
| wanted_height, wanted_width, &scaled_info, &buf); |
| info = scaled_info; |
| gst_buffer_add_video_meta (buf, GST_VIDEO_FRAME_FLAG_NONE, |
| GST_VIDEO_INFO_FORMAT (&conv_rect->info), wanted_width, wanted_height); |
| } else if (!gst_video_overlay_rectangle_is_same_alpha_type (conv_rect->flags, |
| flags)) { |
| /* if we don't have to scale, we have to modify the alpha values, so we |
| * need to make a copy of the pixel memory (and we take ownership below) */ |
| buf = gst_buffer_copy (conv_rect->pixels); |
| info = conv_rect->info; |
| } else { |
| /* do not need to scale or modify alpha values, almost done then */ |
| scaled_rect = conv_rect; |
| goto done; |
| } |
| |
| new_flags = conv_rect->flags; |
| gst_video_frame_map (&frame, &info, buf, GST_MAP_READWRITE); |
| if (!gst_video_overlay_rectangle_is_same_alpha_type (conv_rect->flags, flags)) { |
| if (rectangle->flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA) { |
| gst_video_overlay_rectangle_unpremultiply (&frame); |
| new_flags &= ~GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA; |
| } else { |
| gst_video_overlay_rectangle_premultiply (&frame); |
| new_flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA; |
| } |
| } |
| gst_video_frame_unmap (&frame); |
| |
| scaled_rect = gst_video_overlay_rectangle_new_raw (buf, |
| 0, 0, wanted_width, wanted_height, new_flags); |
| if (conv_rect->global_alpha != 1.0) |
| gst_video_overlay_rectangle_set_global_alpha (scaled_rect, |
| conv_rect->global_alpha); |
| gst_buffer_unref (buf); |
| |
| GST_RECTANGLE_LOCK (rectangle); |
| rectangle->scaled_rectangles = |
| g_list_prepend (rectangle->scaled_rectangles, scaled_rect); |
| GST_RECTANGLE_UNLOCK (rectangle); |
| |
| done: |
| |
| GST_RECTANGLE_LOCK (rectangle); |
| if (apply_global_alpha |
| && scaled_rect->applied_global_alpha != rectangle->global_alpha) { |
| gst_video_overlay_rectangle_apply_global_alpha (scaled_rect, |
| rectangle->global_alpha); |
| gst_video_overlay_rectangle_set_global_alpha (scaled_rect, |
| rectangle->global_alpha); |
| } else if (revert_global_alpha && scaled_rect->applied_global_alpha != 1.0) { |
| gst_video_overlay_rectangle_apply_global_alpha (scaled_rect, 1.0); |
| } |
| GST_RECTANGLE_UNLOCK (rectangle); |
| |
| return scaled_rect->pixels; |
| } |
| |
| |
| /** |
| * gst_video_overlay_rectangle_get_pixels_raw: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * @flags: flags |
| * If a global_alpha value != 1 is set for the rectangle, the caller |
| * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
| * if he wants to apply global-alpha himself. If the flag is not set |
| * global_alpha is applied internally before returning the pixel-data. |
| * |
| * Returns: (transfer none): a #GstBuffer holding the pixel data with |
| * format as originally provided and specified in video meta with |
| * width and height of the render dimensions as per |
| * gst_video_overlay_rectangle_get_render_rectangle(). This function does |
| * not return a reference, the caller should obtain a reference of her own |
| * with gst_buffer_ref() if needed. |
| */ |
| GstBuffer * |
| gst_video_overlay_rectangle_get_pixels_raw (GstVideoOverlayRectangle * |
| rectangle, GstVideoOverlayFormatFlags flags) |
| { |
| return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
| flags, FALSE, GST_VIDEO_INFO_FORMAT (&rectangle->info)); |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_get_pixels_argb: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * @flags: flags |
| * If a global_alpha value != 1 is set for the rectangle, the caller |
| * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
| * if he wants to apply global-alpha himself. If the flag is not set |
| * global_alpha is applied internally before returning the pixel-data. |
| * |
| * Returns: (transfer none): a #GstBuffer holding the ARGB pixel data with |
| * width and height of the render dimensions as per |
| * gst_video_overlay_rectangle_get_render_rectangle(). This function does |
| * not return a reference, the caller should obtain a reference of her own |
| * with gst_buffer_ref() if needed. |
| */ |
| GstBuffer * |
| gst_video_overlay_rectangle_get_pixels_argb (GstVideoOverlayRectangle * |
| rectangle, GstVideoOverlayFormatFlags flags) |
| { |
| return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
| flags, FALSE, GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB); |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_get_pixels_ayuv: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * @flags: flags |
| * If a global_alpha value != 1 is set for the rectangle, the caller |
| * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
| * if he wants to apply global-alpha himself. If the flag is not set |
| * global_alpha is applied internally before returning the pixel-data. |
| * |
| * Returns: (transfer none): a #GstBuffer holding the AYUV pixel data with |
| * width and height of the render dimensions as per |
| * gst_video_overlay_rectangle_get_render_rectangle(). This function does |
| * not return a reference, the caller should obtain a reference of her own |
| * with gst_buffer_ref() if needed. |
| */ |
| GstBuffer * |
| gst_video_overlay_rectangle_get_pixels_ayuv (GstVideoOverlayRectangle * |
| rectangle, GstVideoOverlayFormatFlags flags) |
| { |
| return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
| flags, FALSE, GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV); |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_get_pixels_unscaled_raw: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * @flags: flags. |
| * If a global_alpha value != 1 is set for the rectangle, the caller |
| * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
| * if he wants to apply global-alpha himself. If the flag is not set |
| * global_alpha is applied internally before returning the pixel-data. |
| * |
| * Retrieves the pixel data as it is. This is useful if the caller can |
| * do the scaling itself when handling the overlaying. The rectangle will |
| * need to be scaled to the render dimensions, which can be retrieved using |
| * gst_video_overlay_rectangle_get_render_rectangle(). |
| * |
| * Returns: (transfer none): a #GstBuffer holding the pixel data with |
| * #GstVideoMeta set. This function does not return a reference, the caller |
| * should obtain a reference of her own with gst_buffer_ref() if needed. |
| */ |
| GstBuffer * |
| gst_video_overlay_rectangle_get_pixels_unscaled_raw (GstVideoOverlayRectangle * |
| rectangle, GstVideoOverlayFormatFlags flags) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
| |
| return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
| flags, TRUE, GST_VIDEO_INFO_FORMAT (&rectangle->info)); |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_get_pixels_unscaled_argb: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * @flags: flags. |
| * If a global_alpha value != 1 is set for the rectangle, the caller |
| * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
| * if he wants to apply global-alpha himself. If the flag is not set |
| * global_alpha is applied internally before returning the pixel-data. |
| * |
| * Retrieves the pixel data as it is. This is useful if the caller can |
| * do the scaling itself when handling the overlaying. The rectangle will |
| * need to be scaled to the render dimensions, which can be retrieved using |
| * gst_video_overlay_rectangle_get_render_rectangle(). |
| * |
| * Returns: (transfer none): a #GstBuffer holding the ARGB pixel data with |
| * #GstVideoMeta set. This function does not return a reference, the caller |
| * should obtain a reference of her own with gst_buffer_ref() if needed. |
| */ |
| GstBuffer * |
| gst_video_overlay_rectangle_get_pixels_unscaled_argb (GstVideoOverlayRectangle * |
| rectangle, GstVideoOverlayFormatFlags flags) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
| |
| return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
| flags, TRUE, GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB); |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_get_pixels_unscaled_ayuv: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * @flags: flags. |
| * If a global_alpha value != 1 is set for the rectangle, the caller |
| * should set the #GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA flag |
| * if he wants to apply global-alpha himself. If the flag is not set |
| * global_alpha is applied internally before returning the pixel-data. |
| * |
| * Retrieves the pixel data as it is. This is useful if the caller can |
| * do the scaling itself when handling the overlaying. The rectangle will |
| * need to be scaled to the render dimensions, which can be retrieved using |
| * gst_video_overlay_rectangle_get_render_rectangle(). |
| * |
| * Returns: (transfer none): a #GstBuffer holding the AYUV pixel data with |
| * #GstVideoMeta set. This function does not return a reference, the caller |
| * should obtain a reference of her own with gst_buffer_ref() if needed. |
| */ |
| GstBuffer * |
| gst_video_overlay_rectangle_get_pixels_unscaled_ayuv (GstVideoOverlayRectangle * |
| rectangle, GstVideoOverlayFormatFlags flags) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
| |
| return gst_video_overlay_rectangle_get_pixels_raw_internal (rectangle, |
| flags, TRUE, GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV); |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_get_flags: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * |
| * Retrieves the flags associated with a #GstVideoOverlayRectangle. |
| * This is useful if the caller can handle both premultiplied alpha and |
| * non premultiplied alpha, for example. By knowing whether the rectangle |
| * uses premultiplied or not, it can request the pixel data in the format |
| * it is stored in, to avoid unnecessary conversion. |
| * |
| * Returns: the #GstVideoOverlayFormatFlags associated with the rectangle. |
| */ |
| GstVideoOverlayFormatFlags |
| gst_video_overlay_rectangle_get_flags (GstVideoOverlayRectangle * rectangle) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), |
| GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE); |
| |
| return rectangle->flags; |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_get_global_alpha: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * |
| * Retrieves the global-alpha value associated with a #GstVideoOverlayRectangle. |
| * |
| * Returns: the global-alpha value associated with the rectangle. |
| */ |
| gfloat |
| gst_video_overlay_rectangle_get_global_alpha (GstVideoOverlayRectangle * |
| rectangle) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), -1); |
| |
| return rectangle->global_alpha; |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_set_global_alpha: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * @global_alpha: Global alpha value (0 to 1.0) |
| * |
| * Sets the global alpha value associated with a #GstVideoOverlayRectangle. Per- |
| * pixel alpha values are multiplied with this value. Valid |
| * values: 0 <= global_alpha <= 1; 1 to deactivate. |
| * |
| * @rectangle must be writable, meaning its refcount must be 1. You can |
| * make the rectangles inside a #GstVideoOverlayComposition writable using |
| * gst_video_overlay_composition_make_writable() or |
| * gst_video_overlay_composition_copy(). |
| */ |
| void |
| gst_video_overlay_rectangle_set_global_alpha (GstVideoOverlayRectangle * |
| rectangle, gfloat global_alpha) |
| { |
| g_return_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle)); |
| g_return_if_fail (global_alpha >= 0 && global_alpha <= 1); |
| |
| if (rectangle->global_alpha != global_alpha) { |
| rectangle->global_alpha = global_alpha; |
| if (global_alpha != 1) |
| rectangle->flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA; |
| else |
| rectangle->flags &= ~GST_VIDEO_OVERLAY_FORMAT_FLAG_GLOBAL_ALPHA; |
| /* update seq_num automatically to signal the consumer, that data has changed |
| * note, that this might mislead renderers, that can handle global-alpha |
| * themselves, because what they want to know is whether the actual pixel data |
| * has changed. */ |
| rectangle->seq_num = gst_video_overlay_get_seqnum (); |
| } |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_copy: |
| * @rectangle: (transfer none): a #GstVideoOverlayRectangle to copy |
| * |
| * Makes a copy of @rectangle, so that it is possible to modify it |
| * (e.g. to change the render co-ordinates or render dimension). The |
| * actual overlay pixel data buffers contained in the rectangle are not |
| * copied. |
| * |
| * Returns: (transfer full): a new #GstVideoOverlayRectangle equivalent |
| * to @rectangle. |
| */ |
| GstVideoOverlayRectangle * |
| gst_video_overlay_rectangle_copy (GstVideoOverlayRectangle * rectangle) |
| { |
| GstVideoOverlayRectangle *copy; |
| |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), NULL); |
| |
| copy = gst_video_overlay_rectangle_new_raw (rectangle->pixels, |
| rectangle->x, rectangle->y, |
| rectangle->render_width, rectangle->render_height, rectangle->flags); |
| if (rectangle->global_alpha != 1) |
| gst_video_overlay_rectangle_set_global_alpha (copy, |
| rectangle->global_alpha); |
| |
| return copy; |
| } |
| |
| /** |
| * gst_video_overlay_rectangle_get_seqnum: |
| * @rectangle: a #GstVideoOverlayRectangle |
| * |
| * Returns the sequence number of this rectangle. Sequence numbers are |
| * monotonically increasing and unique for overlay compositions and rectangles |
| * (meaning there will never be a rectangle with the same sequence number as |
| * a composition). |
| * |
| * Using the sequence number of a rectangle as an indicator for changed |
| * pixel-data of a rectangle is dangereous. Some API calls, like e.g. |
| * gst_video_overlay_rectangle_set_global_alpha(), automatically update |
| * the per rectangle sequence number, which is misleading for renderers/ |
| * consumers, that handle global-alpha themselves. For them the |
| * pixel-data returned by gst_video_overlay_rectangle_get_pixels_*() |
| * wont be different for different global-alpha values. In this case a |
| * renderer could also use the GstBuffer pointers as a hint for changed |
| * pixel-data. |
| * |
| * Returns: the sequence number of @rectangle |
| */ |
| guint |
| gst_video_overlay_rectangle_get_seqnum (GstVideoOverlayRectangle * rectangle) |
| { |
| g_return_val_if_fail (GST_IS_VIDEO_OVERLAY_RECTANGLE (rectangle), 0); |
| |
| return rectangle->seq_num; |
| } |