/* GStreamer * Copyright (C) 2013 David Schleef * * 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 Street, Suite 500, * Boston, MA 02110-1335, USA. */ /** * SECTION:element-gstvideodiff * @title: gstvideodiff * * The videodiff element highlights the difference between a frame and its * previous on the luma plane. * * ## Example launch line * |[ * gst-launch-1.0 -v videotestsrc pattern=ball ! videodiff ! videoconvert ! autovideosink * ]| * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "gstvideodiff.h" GST_DEBUG_CATEGORY_STATIC (gst_video_diff_debug_category); #define GST_CAT_DEFAULT gst_video_diff_debug_category /* prototypes */ static GstFlowReturn gst_video_diff_transform_frame (GstVideoFilter * filter, GstVideoFrame * inframe, GstVideoFrame * outframe); #define VIDEO_SRC_CAPS \ GST_VIDEO_CAPS_MAKE("{ I420, Y444, Y42B, Y41B }") #define VIDEO_SINK_CAPS \ GST_VIDEO_CAPS_MAKE("{ I420, Y444, Y42B, Y41B }") G_DEFINE_TYPE_WITH_CODE (GstVideoDiff, gst_video_diff, GST_TYPE_VIDEO_FILTER, GST_DEBUG_CATEGORY_INIT (gst_video_diff_debug_category, "videodiff", 0, "debug category for videodiff element")); static void gst_video_diff_class_init (GstVideoDiffClass * klass) { GstVideoFilterClass *video_filter_class = GST_VIDEO_FILTER_CLASS (klass); gst_element_class_add_pad_template (GST_ELEMENT_CLASS (klass), gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, gst_caps_from_string (VIDEO_SRC_CAPS))); gst_element_class_add_pad_template (GST_ELEMENT_CLASS (klass), gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, gst_caps_from_string (VIDEO_SINK_CAPS))); gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass), "Video Diff", "Video/Filter", "Visualize differences between adjacent video frames", "David Schleef "); video_filter_class->transform_frame = GST_DEBUG_FUNCPTR (gst_video_diff_transform_frame); } static void gst_video_diff_init (GstVideoDiff * videodiff) { videodiff->threshold = 10; } static GstFlowReturn gst_video_diff_transform_frame_ip_planarY (GstVideoDiff * videodiff, GstVideoFrame * outframe, GstVideoFrame * inframe, GstVideoFrame * oldframe) { int width = inframe->info.width; int height = inframe->info.height; int i, j; int threshold = videodiff->threshold; int t = videodiff->t; for (j = 0; j < height; j++) { guint8 *d = (guint8 *) outframe->data[0] + outframe->info.stride[0] * j; guint8 *s1 = (guint8 *) oldframe->data[0] + oldframe->info.stride[0] * j; guint8 *s2 = (guint8 *) inframe->data[0] + inframe->info.stride[0] * j; for (i = 0; i < width; i++) { if ((s2[i] < s1[i] - threshold) || (s2[i] > s1[i] + threshold)) { if ((i + j + t) & 0x4) { d[i] = 16; } else { d[i] = 240; } } else { d[i] = s2[i]; } } } for (j = 0; j < GST_VIDEO_FRAME_COMP_HEIGHT (inframe, 1); j++) { guint8 *d = (guint8 *) outframe->data[1] + outframe->info.stride[1] * j; guint8 *s = (guint8 *) inframe->data[1] + inframe->info.stride[1] * j; memcpy (d, s, GST_VIDEO_FRAME_COMP_WIDTH (inframe, 1)); } for (j = 0; j < GST_VIDEO_FRAME_COMP_HEIGHT (inframe, 2); j++) { guint8 *d = (guint8 *) outframe->data[2] + outframe->info.stride[2] * j; guint8 *s = (guint8 *) inframe->data[2] + inframe->info.stride[2] * j; memcpy (d, s, GST_VIDEO_FRAME_COMP_WIDTH (inframe, 2)); } return GST_FLOW_OK; } static GstFlowReturn gst_video_diff_transform_frame (GstVideoFilter * filter, GstVideoFrame * inframe, GstVideoFrame * outframe) { GstVideoDiff *videodiff = GST_VIDEO_DIFF (filter); GST_DEBUG_OBJECT (videodiff, "transform_frame_ip"); if (videodiff->previous_buffer) { GstVideoFrame oldframe; gst_video_frame_map (&oldframe, &videodiff->oldinfo, videodiff->previous_buffer, GST_MAP_READ); switch (inframe->info.finfo->format) { case GST_VIDEO_FORMAT_I420: case GST_VIDEO_FORMAT_Y41B: case GST_VIDEO_FORMAT_Y444: case GST_VIDEO_FORMAT_Y42B: gst_video_diff_transform_frame_ip_planarY (videodiff, outframe, inframe, &oldframe); break; default: g_assert_not_reached (); } gst_video_frame_unmap (&oldframe); gst_buffer_unref (videodiff->previous_buffer); } else { int k; int j; for (k = 0; k < 3; k++) { for (j = 0; j < GST_VIDEO_FRAME_COMP_HEIGHT (inframe, k); j++) { guint8 *d = (guint8 *) outframe->data[k] + outframe->info.stride[k] * j; guint8 *s = (guint8 *) inframe->data[k] + inframe->info.stride[k] * j; memcpy (d, s, GST_VIDEO_FRAME_COMP_WIDTH (inframe, k)); } } } videodiff->previous_buffer = gst_buffer_ref (inframe->buffer); memcpy (&videodiff->oldinfo, &inframe->info, sizeof (GstVideoInfo)); return GST_FLOW_OK; }