| /* GStreamer |
| * Copyright (C) 2013 David Schleef <ds@schleef.org> |
| * |
| * 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 <gst/gst.h> |
| #include <gst/video/video.h> |
| #include <gst/video/gstvideofilter.h> |
| #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 <ds@schleef.org>"); |
| |
| 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; |
| } |