/* GStreamer * * Copyright (C) 2018 Sebastian Dröge * * 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 #include #include #include #define VIDEO_WIDTH 320 #define VIDEO_HEIGHT 240 #define OVERLAY_WIDTH 16 #define OVERLAY_HEIGHT 16 #if G_BYTE_ORDER == G_LITTLE_ENDIAN #define VIDEO_FORMAT_STR "BGRA" #define VIDEO_FORMAT GST_VIDEO_FORMAT_BGRA #else #define VIDEO_FORMAT_STR "ARGB" #define VIDEO_FORMAT GST_VIDEO_FORMAT_ARGB #endif #define VIDEO_CAPS "video/x-raw, " \ "format = (string) " VIDEO_FORMAT_STR ", " \ "width = (int) 320, " \ "height = (int) 240, " \ "framerate = (fraction) 30/1" #define VIDEO_CAPS_WITH_META "video/x-raw(" GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION "), " \ "format = (string) " VIDEO_FORMAT_STR ", " \ "width = (int) 320, " \ "height = (int) 240, " \ "framerate = (fraction) 30/1" #if (G_BYTE_ORDER == G_BIG_ENDIAN) #define GST_READ_UINT32_NATIVE(data) GST_READ_UINT32_BE(data) #define GST_WRITE_UINT32_NATIVE(data,val) GST_WRITE_UINT32_BE(data,val) #else #define GST_READ_UINT32_NATIVE(data) GST_READ_UINT32_LE(data) #define GST_WRITE_UINT32_NATIVE(data,val) GST_WRITE_UINT32_LE(data,val) #endif static GstBuffer * create_video_frame (void) { GstBuffer *buffer; GstMapInfo map; guint i; buffer = gst_buffer_new_and_alloc (VIDEO_WIDTH * VIDEO_HEIGHT * 4); gst_buffer_add_video_meta (buffer, GST_VIDEO_FRAME_FLAG_NONE, VIDEO_FORMAT, VIDEO_WIDTH, VIDEO_HEIGHT); gst_buffer_map (buffer, &map, GST_MAP_READWRITE); for (i = 0; i < map.size; i += 4) GST_WRITE_UINT32_NATIVE (map.data + i, 0xff000000); gst_buffer_unmap (buffer, &map); return buffer; } static GstBuffer * create_overlay_frame (guint32 color) { GstBuffer *buffer; GstMapInfo map; guint i; buffer = gst_buffer_new_and_alloc (VIDEO_WIDTH * VIDEO_HEIGHT * 4); gst_buffer_add_video_meta (buffer, GST_VIDEO_FRAME_FLAG_NONE, VIDEO_FORMAT, VIDEO_WIDTH, VIDEO_HEIGHT); gst_buffer_map (buffer, &map, GST_MAP_READWRITE); for (i = 0; i < map.size; i += 4) GST_WRITE_UINT32_NATIVE (map.data + i, color); gst_buffer_unmap (buffer, &map); return buffer; } typedef struct { gboolean valid; GstVideoInfo info; guint expected_window_width, expected_window_height; GstVideoOverlayComposition *comp; } State; static void on_caps_changed (GstElement * element, GstCaps * caps, guint window_width, guint window_height, State * s) { fail_unless (gst_video_info_from_caps (&s->info, caps)); s->valid = TRUE; fail_unless_equals_int (s->expected_window_width, window_width); fail_unless_equals_int (s->expected_window_height, window_height); } static GstVideoOverlayComposition * on_draw (GstElement * element, GstSample * sample, State * s) { fail_unless (s->valid); fail_unless (GST_IS_SAMPLE (sample)); return gst_video_overlay_composition_ref (s->comp); } GST_START_TEST (render_fallback) { GstHarness *h; GstVideoOverlayComposition *comp; GstVideoOverlayRectangle *rect; GstBuffer *buffer, *overlay; State s = { 0, }; GstMapInfo map; guint x, y; h = gst_harness_new ("overlaycomposition"); g_signal_connect (h->element, "draw", G_CALLBACK (on_draw), &s); g_signal_connect (h->element, "caps-changed", G_CALLBACK (on_caps_changed), &s); buffer = create_video_frame (); overlay = create_overlay_frame (0x80ffffff); rect = gst_video_overlay_rectangle_new_raw (overlay, 32, 32, OVERLAY_WIDTH, OVERLAY_HEIGHT, GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE); gst_buffer_unref (overlay); comp = gst_video_overlay_composition_new (rect); gst_video_overlay_rectangle_unref (rect); s.comp = comp; s.expected_window_width = VIDEO_WIDTH; s.expected_window_height = VIDEO_HEIGHT; gst_harness_set_src_caps_str (h, VIDEO_CAPS); buffer = gst_harness_push_and_pull (h, buffer); gst_buffer_map (buffer, &map, GST_MAP_READ); fail_unless_equals_int (map.size, VIDEO_WIDTH * VIDEO_HEIGHT * 4); for (y = 0; y < VIDEO_HEIGHT; y++) { for (x = 0; x < VIDEO_WIDTH; x++) { guint32 val = GST_READ_UINT32_NATIVE (map.data + y * VIDEO_WIDTH * 4 + x * 4); guint32 expected_val; if ((x >= 32 && x < 48) && (y >= 32 && y < 48)) { expected_val = 0xff808080; } else { expected_val = 0xff000000; } fail_unless (val == expected_val, "Expected %08x but got %08x at (%u,%u)", expected_val, val, x, y); } } gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); gst_video_overlay_composition_unref (s.comp); gst_harness_teardown (h); } GST_END_TEST; GST_START_TEST (render_fallback_2) { GstHarness *h; GstVideoOverlayComposition *comp; GstVideoOverlayRectangle *rect; GstBuffer *buffer, *overlay; State s = { 0, }; GstMapInfo map; guint x, y; h = gst_harness_new ("overlaycomposition"); g_signal_connect (h->element, "draw", G_CALLBACK (on_draw), &s); g_signal_connect (h->element, "caps-changed", G_CALLBACK (on_caps_changed), &s); overlay = create_overlay_frame (0xffff0000); rect = gst_video_overlay_rectangle_new_raw (overlay, 32, 32, OVERLAY_WIDTH, OVERLAY_HEIGHT, GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE); gst_buffer_unref (overlay); comp = gst_video_overlay_composition_new (rect); gst_video_overlay_rectangle_unref (rect); overlay = create_overlay_frame (0xff0000ff); rect = gst_video_overlay_rectangle_new_raw (overlay, 64, 64, OVERLAY_WIDTH, OVERLAY_HEIGHT, GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE); gst_buffer_unref (overlay); gst_video_overlay_composition_add_rectangle (comp, rect); gst_video_overlay_rectangle_unref (rect); s.comp = comp; s.expected_window_width = VIDEO_WIDTH; s.expected_window_height = VIDEO_HEIGHT; gst_harness_set_src_caps_str (h, VIDEO_CAPS); buffer = create_video_frame (); buffer = gst_harness_push_and_pull (h, buffer); gst_buffer_map (buffer, &map, GST_MAP_READ); fail_unless_equals_int (map.size, VIDEO_WIDTH * VIDEO_HEIGHT * 4); for (y = 0; y < VIDEO_HEIGHT; y++) { for (x = 0; x < VIDEO_WIDTH; x++) { guint32 val = GST_READ_UINT32_NATIVE (map.data + y * VIDEO_WIDTH * 4 + x * 4); guint32 expected_val; if ((x >= 32 && x < 48) && (y >= 32 && y < 48)) { expected_val = 0xffff0000; } else if ((x >= 64 && x < 80) && (y >= 64 && y < 80)) { expected_val = 0xff0000ff; } else { expected_val = 0xff000000; } fail_unless (val == expected_val, "Expected %08x but got %08x at (%u,%u)", expected_val, val, x, y); } } gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); gst_video_overlay_composition_unref (s.comp); gst_harness_teardown (h); } GST_END_TEST; GST_START_TEST (render_meta) { GstHarness *h; GstVideoOverlayComposition *comp; GstVideoOverlayRectangle *rect; GstBuffer *buffer, *overlay; State s = { 0, }; GstMapInfo map; guint x, y; GstVideoOverlayCompositionMeta *meta; h = gst_harness_new ("overlaycomposition"); g_signal_connect (h->element, "draw", G_CALLBACK (on_draw), &s); g_signal_connect (h->element, "caps-changed", G_CALLBACK (on_caps_changed), &s); overlay = create_overlay_frame (0xffff0000); rect = gst_video_overlay_rectangle_new_raw (overlay, 32, 32, OVERLAY_WIDTH, OVERLAY_HEIGHT, GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE); gst_buffer_unref (overlay); comp = gst_video_overlay_composition_new (rect); gst_video_overlay_rectangle_unref (rect); overlay = create_overlay_frame (0xff0000ff); rect = gst_video_overlay_rectangle_new_raw (overlay, 64, 64, OVERLAY_WIDTH, OVERLAY_HEIGHT, GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE); gst_buffer_unref (overlay); gst_video_overlay_composition_add_rectangle (comp, rect); gst_video_overlay_rectangle_unref (rect); s.comp = comp; s.expected_window_width = VIDEO_WIDTH; s.expected_window_height = VIDEO_HEIGHT; gst_harness_add_propose_allocation_meta (h, GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL); gst_harness_set_src_caps_str (h, VIDEO_CAPS); buffer = create_video_frame (); buffer = gst_harness_push_and_pull (h, buffer); gst_buffer_map (buffer, &map, GST_MAP_READ); fail_unless_equals_int (map.size, VIDEO_WIDTH * VIDEO_HEIGHT * 4); for (y = 0; y < VIDEO_HEIGHT; y++) { for (x = 0; x < VIDEO_WIDTH; x++) { guint32 val = GST_READ_UINT32_NATIVE (map.data + y * VIDEO_WIDTH * 4 + x * 4); guint32 expected_val = 0xff000000; fail_unless (val == expected_val, "Expected %08x but got %08x at (%u,%u)", expected_val, val, x, y); } } gst_buffer_unmap (buffer, &map); meta = gst_buffer_get_video_overlay_composition_meta (buffer); fail_unless (meta); fail_unless (meta->overlay == s.comp); gst_buffer_unref (buffer); gst_video_overlay_composition_unref (s.comp); gst_harness_teardown (h); } GST_END_TEST; static Suite * overlaycomposition_suite (void) { Suite *s = suite_create ("overlaycomposition"); TCase *tc = tcase_create ("general"); suite_add_tcase (s, tc); tcase_add_test (tc, render_fallback); tcase_add_test (tc, render_fallback_2); tcase_add_test (tc, render_meta); return s; } GST_CHECK_MAIN (overlaycomposition);