mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-04-26 06:54:49 +00:00
tests: add visual overlay composition blending test
Shows visual result of blending a logo on top of a video surface, esp. when the logo is partially outside of the video surface and needs to be clipped. https://bugzilla.gnome.org/show_bug.cgi?id=739281
This commit is contained in:
parent
c61604fd9b
commit
07f254e4b2
3 changed files with 295 additions and 1 deletions
1
tests/icles/.gitignore
vendored
1
tests/icles/.gitignore
vendored
|
@ -6,6 +6,7 @@ position-formats
|
||||||
stress-playbin
|
stress-playbin
|
||||||
stress-videooverlay
|
stress-videooverlay
|
||||||
test-effect-switch
|
test-effect-switch
|
||||||
|
test-overlay-blending
|
||||||
test-textoverlay
|
test-textoverlay
|
||||||
test-scale
|
test-scale
|
||||||
test-box
|
test-box
|
||||||
|
|
|
@ -76,6 +76,14 @@ test_effect_switch_SOURCES = test-effect-switch.c
|
||||||
test_effect_switch_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
|
test_effect_switch_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
|
||||||
test_effect_switch_LDADD = $(GST_LIBS)
|
test_effect_switch_LDADD = $(GST_LIBS)
|
||||||
|
|
||||||
|
test_overlay_blending_SOURCES = test-overlay-blending.c
|
||||||
|
test_overlay_blending_CFLAGS = \
|
||||||
|
$(GST_PLUGINS_BASE_CFLAGS) \
|
||||||
|
$(GST_CFLAGS) $(GIO_CFLAGS)
|
||||||
|
test_overlay_blending_LDADD = \
|
||||||
|
$(top_builddir)/gst-libs/gst/video/libgstvideo-$(GST_API_VERSION).la \
|
||||||
|
$(GST_LIBS) $(GIO_LIBS)
|
||||||
|
|
||||||
test_scale_SOURCES = test-scale.c
|
test_scale_SOURCES = test-scale.c
|
||||||
test_scale_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
|
test_scale_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
|
||||||
test_scale_LDADD = $(GST_LIBS) $(LIBM)
|
test_scale_LDADD = $(GST_LIBS) $(LIBM)
|
||||||
|
@ -86,4 +94,4 @@ test_box_LDADD = $(GST_LIBS) $(LIBM)
|
||||||
|
|
||||||
noinst_PROGRAMS = $(X_TESTS) $(PANGO_TESTS) \
|
noinst_PROGRAMS = $(X_TESTS) $(PANGO_TESTS) \
|
||||||
audio-trickplay playbin-text position-formats stress-playbin \
|
audio-trickplay playbin-text position-formats stress-playbin \
|
||||||
test-scale test-box test-effect-switch
|
test-scale test-box test-effect-switch test-overlay-blending
|
||||||
|
|
285
tests/icles/test-overlay-blending.c
Normal file
285
tests/icles/test-overlay-blending.c
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
/* GStreamer video overlay composition blending test app
|
||||||
|
* Copyright (C) 2014 Tim-Philipp Müller <tim centricular com>
|
||||||
|
|
||||||
|
* 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 <gio/gio.h>
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <gst/video/video.h>
|
||||||
|
|
||||||
|
#define X_START -230
|
||||||
|
#define X_END (720 + 230)
|
||||||
|
|
||||||
|
#define Y_START -25
|
||||||
|
#define Y_END (480 + 58)
|
||||||
|
|
||||||
|
#define X_INC 1
|
||||||
|
#define Y_INC 3
|
||||||
|
|
||||||
|
/* GdkPixbuf RGBA C-Source image dump from gdk-pixbuf-csource --raw,
|
||||||
|
* gzipped and then base64 encoded */
|
||||||
|
const gchar gzipped_pixdata_base64[] =
|
||||||
|
"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";
|
||||||
|
|
||||||
|
static guint8 *pixdata;
|
||||||
|
static gsize pixdata_size;
|
||||||
|
|
||||||
|
static GMainLoop *main_loop;
|
||||||
|
static gint x, y;
|
||||||
|
|
||||||
|
static GstVideoOverlayComposition *
|
||||||
|
create_overlay_composition (guint x, guint y)
|
||||||
|
{
|
||||||
|
GstVideoOverlayComposition *logo_comp;
|
||||||
|
GstVideoOverlayRectangle *logo_rect;
|
||||||
|
const guint8 *p = pixdata;
|
||||||
|
GstBuffer *logo_pixels;
|
||||||
|
//const header_size = 24;
|
||||||
|
guint w, h, stride;
|
||||||
|
|
||||||
|
/* 0: Pixbuf magic (0x47646b50) */
|
||||||
|
g_assert (GST_READ_UINT32_BE (p) == 0x47646b50);
|
||||||
|
|
||||||
|
/* 4: length incl. header */
|
||||||
|
/* 8: pixdata_type */
|
||||||
|
/* 12: rowstride (900) */
|
||||||
|
stride = GST_READ_UINT32_BE (p + 12);
|
||||||
|
/* 16: width (225) */
|
||||||
|
w = GST_READ_UINT32_BE (p + 16);
|
||||||
|
/* 20: height (57) */
|
||||||
|
h = GST_READ_UINT32_BE (p + 20);
|
||||||
|
/* 24: pixel_data */
|
||||||
|
GST_LOG ("%dx%d @ %d", w, h, stride);
|
||||||
|
/* we assume that the last line also has padding at the end */
|
||||||
|
g_assert (pixdata_size - 24 >= h * stride);
|
||||||
|
|
||||||
|
logo_pixels = gst_buffer_new_and_alloc (h * stride);
|
||||||
|
gst_buffer_fill (logo_pixels, 0, p + 24, h * stride);
|
||||||
|
gst_buffer_add_video_meta (logo_pixels, GST_VIDEO_FRAME_FLAG_NONE,
|
||||||
|
GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB, w, h);
|
||||||
|
logo_rect = gst_video_overlay_rectangle_new_raw (logo_pixels,
|
||||||
|
x, y, w, h, GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE);
|
||||||
|
gst_buffer_unref (logo_pixels);
|
||||||
|
|
||||||
|
logo_comp = gst_video_overlay_composition_new (logo_rect);
|
||||||
|
gst_video_overlay_rectangle_unref (logo_rect);
|
||||||
|
return logo_comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
bus_cb (GstBus * bus, GstMessage * msg, gpointer user_data)
|
||||||
|
{
|
||||||
|
GMainLoop *loop = user_data;
|
||||||
|
|
||||||
|
switch (GST_MESSAGE_TYPE (msg)) {
|
||||||
|
case GST_MESSAGE_ERROR:{
|
||||||
|
GError *err = NULL;
|
||||||
|
gchar *dbg;
|
||||||
|
|
||||||
|
gst_message_parse_error (msg, &err, &dbg);
|
||||||
|
gst_object_default_error (msg->src, err, dbg);
|
||||||
|
g_error_free (err);
|
||||||
|
g_free (dbg);
|
||||||
|
g_main_loop_quit (loop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
update_position (void)
|
||||||
|
{
|
||||||
|
x += X_INC;
|
||||||
|
if (x < X_END)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
x = X_START;
|
||||||
|
y += Y_INC;
|
||||||
|
if (y < Y_END)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstPadProbeReturn
|
||||||
|
buffer_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
||||||
|
{
|
||||||
|
GstVideoOverlayComposition *comp;
|
||||||
|
GstVideoFrame frame;
|
||||||
|
GstVideoInfo vinfo;
|
||||||
|
GstCaps *caps;
|
||||||
|
|
||||||
|
if (!update_position ()) {
|
||||||
|
g_main_loop_quit (main_loop);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
caps = gst_pad_get_current_caps (pad);
|
||||||
|
gst_video_info_from_caps (&vinfo, caps);
|
||||||
|
gst_caps_unref (caps);
|
||||||
|
|
||||||
|
info->data = gst_buffer_make_writable (info->data);
|
||||||
|
|
||||||
|
GST_LOG ("%3d, %3d", x, y);
|
||||||
|
comp = create_overlay_composition (x, y);
|
||||||
|
|
||||||
|
gst_video_frame_map (&frame, &vinfo, info->data, GST_MAP_READWRITE);
|
||||||
|
|
||||||
|
if (!gst_video_overlay_composition_blend (comp, &frame))
|
||||||
|
g_warning ("Error blending overlay at position (%d,%d)", x, y);
|
||||||
|
|
||||||
|
gst_video_frame_unmap (&frame);
|
||||||
|
|
||||||
|
gst_video_overlay_composition_unref (comp);
|
||||||
|
|
||||||
|
out:
|
||||||
|
|
||||||
|
return GST_PAD_PROBE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char **argv)
|
||||||
|
{
|
||||||
|
GOptionEntry options[] = {
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
GOptionContext *ctx;
|
||||||
|
GError *err = NULL;
|
||||||
|
GstElement *src, *q, *capsfilter, *sink;
|
||||||
|
GstElement *pipeline;
|
||||||
|
GstPad *src_pad;
|
||||||
|
GZlibDecompressor *decompress;
|
||||||
|
GConverterResult decomp_res;
|
||||||
|
guchar *gzipped_pixdata;
|
||||||
|
gsize gzipped_size, bytes_read;
|
||||||
|
|
||||||
|
gzipped_pixdata = g_base64_decode (gzipped_pixdata_base64, &gzipped_size);
|
||||||
|
g_assert (gzipped_pixdata != NULL);
|
||||||
|
|
||||||
|
pixdata = g_malloc (64 * 1024);
|
||||||
|
|
||||||
|
decompress = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
|
||||||
|
decomp_res = g_converter_convert (G_CONVERTER (decompress),
|
||||||
|
gzipped_pixdata, gzipped_size, pixdata, 64 * 1024,
|
||||||
|
G_CONVERTER_INPUT_AT_END, &bytes_read, &pixdata_size, NULL);
|
||||||
|
g_assert (decomp_res == G_CONVERTER_FINISHED);
|
||||||
|
g_assert (bytes_read == gzipped_size);
|
||||||
|
g_free (gzipped_pixdata);
|
||||||
|
|
||||||
|
ctx = g_option_context_new ("");
|
||||||
|
g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
|
||||||
|
g_option_context_add_group (ctx, gst_init_get_option_group ());
|
||||||
|
if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
|
||||||
|
g_print ("Error initializing: %s\n", err->message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
g_option_context_free (ctx);
|
||||||
|
|
||||||
|
main_loop = g_main_loop_new (NULL, FALSE);
|
||||||
|
|
||||||
|
pipeline = gst_pipeline_new ("pipeline");
|
||||||
|
|
||||||
|
src = gst_element_factory_make ("videotestsrc", NULL);
|
||||||
|
gst_util_set_object_arg (G_OBJECT (src), "pattern", "white");
|
||||||
|
|
||||||
|
src_pad = gst_element_get_static_pad (src, "src");
|
||||||
|
gst_pad_add_probe (src_pad, GST_PAD_PROBE_TYPE_BUFFER, buffer_cb,
|
||||||
|
main_loop, NULL);
|
||||||
|
gst_object_unref (src_pad);
|
||||||
|
|
||||||
|
q = gst_element_factory_make ("queue", NULL);
|
||||||
|
|
||||||
|
capsfilter = gst_element_factory_make ("capsfilter", NULL);
|
||||||
|
gst_util_set_object_arg (G_OBJECT (capsfilter), "caps",
|
||||||
|
"video/x-raw, width=720, height=480, framerate=250/1, "
|
||||||
|
"format={ I420, YV12, YUY2, UYVY, AYUV, Y41B, Y42B, "
|
||||||
|
"YVYU, Y444, v210, v216, NV12, NV21, UYVP, A420, YUV9, YVU9, IYU1, "
|
||||||
|
"RGBx, BGRx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR }");
|
||||||
|
|
||||||
|
sink = gst_element_factory_make ("ximagesink", NULL);
|
||||||
|
|
||||||
|
gst_bin_add_many (GST_BIN (pipeline), src, q, capsfilter, sink, NULL);
|
||||||
|
|
||||||
|
gst_element_link_many (src, q, capsfilter, sink, NULL);
|
||||||
|
|
||||||
|
x = X_START;
|
||||||
|
y = Y_START;
|
||||||
|
|
||||||
|
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||||
|
|
||||||
|
gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_cb, main_loop);
|
||||||
|
|
||||||
|
//g_timeout_add_seconds (1, timeout_cb, loop);
|
||||||
|
|
||||||
|
g_main_loop_run (main_loop);
|
||||||
|
|
||||||
|
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||||
|
gst_object_unref (pipeline);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in a new issue