mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 11:41:09 +00:00
729 lines
20 KiB
C
729 lines
20 KiB
C
|
/* GStreamer
|
||
|
* Copyright (C) 2022 Intel Corporation
|
||
|
* Author: U. Artie Eoff <ullysses.a.eoff@intel.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 the0
|
||
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||
|
* Boston, MA 02110-1301, USA.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* SECTION:element-avvideocompare
|
||
|
* @title: avvideocompare
|
||
|
* @short_description: A libav based video compare element
|
||
|
*
|
||
|
* avvideocompare accepts two input video streams with the same width, height,
|
||
|
* framerate and format. The two incoming buffers are compared to each other
|
||
|
* via the chosen compare method (e.g. ssim or psnr).
|
||
|
*
|
||
|
* If the stats-file property is specified, then the computed result for each
|
||
|
* frame comparison will be written to the file, or stdout if stats-file is '-'.
|
||
|
*
|
||
|
* The first incoming buffer is passed through, unchanged, to the srcpad.
|
||
|
*
|
||
|
* ## Sample pipelines
|
||
|
* ```
|
||
|
* gst-launch-1.0 videotestsrc num-buffers=100 \
|
||
|
* ! video/x-raw,format=NV12 \
|
||
|
* ! videobalance brightness=0.005 hue=0.005 \
|
||
|
* ! avvideocompare method=psnr stats-file=- name=cmp \
|
||
|
* ! fakesink videotestsrc ! video/x-raw,format=NV12 \
|
||
|
* ! cmp.
|
||
|
* ```
|
||
|
* ```
|
||
|
* gst-launch-1.0 videotestsrc num-buffers=100 \
|
||
|
* ! tee name=orig ! queue ! avenc_mjpeg \
|
||
|
* ! jpegparse ! avdec_mjpeg \
|
||
|
* ! avvideocompare method=ssim stats-file=stats.log name=cmp \
|
||
|
* ! fakesink orig. ! queue ! cmp.
|
||
|
* ```
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
# include "config.h"
|
||
|
#endif
|
||
|
|
||
|
#include <libavcodec/avcodec.h>
|
||
|
#include <libavutil/imgutils.h>
|
||
|
#include <libavfilter/avfilter.h>
|
||
|
#include <libavfilter/buffersrc.h>
|
||
|
#include <libavfilter/buffersink.h>
|
||
|
|
||
|
#include <glib/gprintf.h>
|
||
|
|
||
|
#include <gst/gst.h>
|
||
|
#include <gst/base/gstcollectpads.h>
|
||
|
#include <gst/video/video.h>
|
||
|
|
||
|
#include "gstav.h"
|
||
|
#include "gstavcodecmap.h"
|
||
|
|
||
|
#define GST_FFMPEGVIDCMP_FORMATS "{ " \
|
||
|
"ARGB, BGRA, ABGR, RGBA, xRGB, BGRx, xBGR, RGBx, RGB16, " \
|
||
|
"GRAY8, NV12, NV21, YUY2, UYVY, I420, Y42B, Y444, VUYA, " \
|
||
|
"P010_10LE, Y410, P012_LE, Y212_LE, Y412_LE" \
|
||
|
" }"
|
||
|
|
||
|
/* *INDENT-OFF* */
|
||
|
static GstStaticPadTemplate gst_ffmpegvidcmp_src_tmpl =
|
||
|
GST_STATIC_PAD_TEMPLATE ("src",
|
||
|
GST_PAD_SRC,
|
||
|
GST_PAD_ALWAYS,
|
||
|
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_FFMPEGVIDCMP_FORMATS)));
|
||
|
/* *INDENT-ON* */
|
||
|
|
||
|
/* *INDENT-OFF* */
|
||
|
static GstStaticPadTemplate gst_ffmpegvidcmp_sink1_tmpl =
|
||
|
GST_STATIC_PAD_TEMPLATE ("sink_1",
|
||
|
GST_PAD_SINK,
|
||
|
GST_PAD_ALWAYS,
|
||
|
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_FFMPEGVIDCMP_FORMATS)));
|
||
|
/* *INDENT-ON* */
|
||
|
|
||
|
/* *INDENT-OFF* */
|
||
|
static GstStaticPadTemplate gst_ffmpegvidcmp_sink2_tmpl =
|
||
|
GST_STATIC_PAD_TEMPLATE ("sink_2",
|
||
|
GST_PAD_SINK,
|
||
|
GST_PAD_ALWAYS,
|
||
|
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_FFMPEGVIDCMP_FORMATS)));
|
||
|
/* *INDENT-ON* */
|
||
|
|
||
|
typedef enum
|
||
|
{
|
||
|
GST_FFMPEGVIDCMP_METHOD_SSIM,
|
||
|
GST_FFMPEGVIDCMP_METHOD_PSNR,
|
||
|
} GstFFMpegVidCmpMethod;
|
||
|
|
||
|
#define GST_FFMPEGVIDCMP_METHOD_TYPE (gst_ffmpegvidcmp_method_get_type())
|
||
|
|
||
|
/**
|
||
|
* GstFFMpegVidCmpMethod:
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
static GType
|
||
|
gst_ffmpegvidcmp_method_get_type (void)
|
||
|
{
|
||
|
static gsize g_type = 0;
|
||
|
|
||
|
static const GEnumValue enum_values[] = {
|
||
|
{GST_FFMPEGVIDCMP_METHOD_SSIM, "SSIM", "ssim"},
|
||
|
{GST_FFMPEGVIDCMP_METHOD_PSNR, "PSNR", "psnr"},
|
||
|
{0, NULL, NULL},
|
||
|
};
|
||
|
|
||
|
if (g_once_init_enter (&g_type)) {
|
||
|
const GType type =
|
||
|
g_enum_register_static ("GstFFMpegVidCmpMethod", enum_values);
|
||
|
g_once_init_leave (&g_type, type);
|
||
|
}
|
||
|
return g_type;
|
||
|
}
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
PROP_0,
|
||
|
PROP_STATS_FILE,
|
||
|
PROP_METHOD,
|
||
|
};
|
||
|
|
||
|
#define DEFAULT_STATS_FILE NULL
|
||
|
#define DEFAULT_METHOD GST_FFMPEGVIDCMP_METHOD_SSIM
|
||
|
|
||
|
#define GST_TYPE_FFMPEGVIDCMP (gst_ffmpegvidcmp_get_type())
|
||
|
|
||
|
G_DECLARE_FINAL_TYPE (GstFFMpegVidCmp, gst_ffmpegvidcmp,
|
||
|
GST, FFMPEGVIDCMP, GstElement);
|
||
|
|
||
|
struct _GstFFMpegVidCmp
|
||
|
{
|
||
|
GstElement element;
|
||
|
|
||
|
/* pads */
|
||
|
GstPad *srcpad;
|
||
|
GstPad *sinkpad1;
|
||
|
GstPad *sinkpad2;
|
||
|
|
||
|
GstCollectPads *collect;
|
||
|
GstCollectData *collect_data1;
|
||
|
GstCollectData *collect_data2;
|
||
|
|
||
|
/* negotiated format */
|
||
|
gint width;
|
||
|
gint height;
|
||
|
gint fps_num;
|
||
|
gint fps_denom;
|
||
|
GstVideoInfo vinfo1;
|
||
|
GstVideoInfo vinfo2;
|
||
|
|
||
|
AVFilterGraph *filter_graph;
|
||
|
AVFilterContext *in1_ctx;
|
||
|
AVFilterContext *in2_ctx;
|
||
|
AVFilterContext *out_ctx;
|
||
|
enum AVPixelFormat pixfmt;
|
||
|
|
||
|
gchar *stats_file;
|
||
|
|
||
|
GstFFMpegVidCmpMethod method;
|
||
|
};
|
||
|
|
||
|
G_DEFINE_TYPE (GstFFMpegVidCmp, gst_ffmpegvidcmp, GST_TYPE_ELEMENT);
|
||
|
|
||
|
static void gst_ffmpegvidcmp_finalize (GObject * object);
|
||
|
static GstFlowReturn gst_ffmpegvidcmp_collected (GstCollectPads * pads,
|
||
|
GstFFMpegVidCmp * self);
|
||
|
static void gst_ffmpegvidcmp_set_property (GObject * object, guint prop_id,
|
||
|
const GValue * value, GParamSpec * pspec);
|
||
|
static void gst_ffmpegvidcmp_get_property (GObject * object, guint prop_id,
|
||
|
GValue * value, GParamSpec * pspec);
|
||
|
static GstStateChangeReturn gst_ffmpegvidcmp_change_state (GstElement * element,
|
||
|
GstStateChange transition);
|
||
|
static gboolean gst_ffmpegvidcmp_sink_event (GstCollectPads * pads,
|
||
|
GstCollectData * data, GstEvent * event, gpointer user_data);
|
||
|
static gboolean gst_ffmpegvidcmp_sink_query (GstCollectPads * pads,
|
||
|
GstCollectData * data, GstQuery * query, gpointer user_data);
|
||
|
|
||
|
static void
|
||
|
gst_ffmpegvidcmp_class_init (GstFFMpegVidCmpClass * klass)
|
||
|
{
|
||
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||
|
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
|
||
|
|
||
|
gobject_class->set_property = gst_ffmpegvidcmp_set_property;
|
||
|
gobject_class->get_property = gst_ffmpegvidcmp_get_property;
|
||
|
gobject_class->finalize = (GObjectFinalizeFunc) gst_ffmpegvidcmp_finalize;
|
||
|
|
||
|
g_object_class_install_property (gobject_class, PROP_STATS_FILE,
|
||
|
g_param_spec_string ("stats-file", "Stats File Location",
|
||
|
"Set file where to store per-frame difference information"
|
||
|
", '-' for stdout", DEFAULT_STATS_FILE, G_PARAM_READWRITE |
|
||
|
G_PARAM_STATIC_STRINGS));
|
||
|
g_object_class_install_property (gobject_class, PROP_METHOD,
|
||
|
g_param_spec_enum ("method", "Method", "Method to compare video frames",
|
||
|
GST_FFMPEGVIDCMP_METHOD_TYPE, DEFAULT_METHOD,
|
||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||
|
|
||
|
gst_type_mark_as_plugin_api (GST_FFMPEGVIDCMP_METHOD_TYPE, 0);
|
||
|
|
||
|
gstelement_class->change_state =
|
||
|
GST_DEBUG_FUNCPTR (gst_ffmpegvidcmp_change_state);
|
||
|
|
||
|
gst_element_class_add_static_pad_template (gstelement_class,
|
||
|
&gst_ffmpegvidcmp_sink1_tmpl);
|
||
|
gst_element_class_add_static_pad_template (gstelement_class,
|
||
|
&gst_ffmpegvidcmp_sink2_tmpl);
|
||
|
gst_element_class_add_static_pad_template (gstelement_class,
|
||
|
&gst_ffmpegvidcmp_src_tmpl);
|
||
|
gst_element_class_set_static_metadata (gstelement_class,
|
||
|
"A libav video compare element", "Filter/Compare/Video",
|
||
|
"Compare Video", "U. Artie Eoff <ullysses.a.eoff@intel.com");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_ffmpegvidcmp_reset (GstFFMpegVidCmp * self)
|
||
|
{
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
|
||
|
self->width = -1;
|
||
|
self->height = -1;
|
||
|
self->fps_num = 0;
|
||
|
self->fps_denom = 1;
|
||
|
self->pixfmt = AV_PIX_FMT_NONE;
|
||
|
|
||
|
self->in1_ctx = NULL;
|
||
|
self->in2_ctx = NULL;
|
||
|
self->out_ctx = NULL;
|
||
|
|
||
|
if (self->filter_graph)
|
||
|
avfilter_graph_free (&self->filter_graph);
|
||
|
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_ffmpegvidcmp_init (GstFFMpegVidCmp * self)
|
||
|
{
|
||
|
gst_ffmpegvidcmp_reset (self);
|
||
|
|
||
|
self->stats_file = g_strdup (DEFAULT_STATS_FILE);
|
||
|
self->method = DEFAULT_METHOD;
|
||
|
|
||
|
self->sinkpad1 =
|
||
|
gst_pad_new_from_static_template (&gst_ffmpegvidcmp_sink1_tmpl, "sink_1");
|
||
|
gst_element_add_pad (GST_ELEMENT (self), self->sinkpad1);
|
||
|
|
||
|
self->sinkpad2 =
|
||
|
gst_pad_new_from_static_template (&gst_ffmpegvidcmp_sink2_tmpl, "sink_2");
|
||
|
gst_element_add_pad (GST_ELEMENT (self), self->sinkpad2);
|
||
|
|
||
|
self->srcpad =
|
||
|
gst_pad_new_from_static_template (&gst_ffmpegvidcmp_src_tmpl, "src");
|
||
|
gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
|
||
|
|
||
|
self->collect = gst_collect_pads_new ();
|
||
|
gst_collect_pads_set_function (self->collect,
|
||
|
(GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_ffmpegvidcmp_collected),
|
||
|
self);
|
||
|
gst_collect_pads_set_event_function (self->collect,
|
||
|
GST_DEBUG_FUNCPTR (gst_ffmpegvidcmp_sink_event), self);
|
||
|
gst_collect_pads_set_query_function (self->collect,
|
||
|
GST_DEBUG_FUNCPTR (gst_ffmpegvidcmp_sink_query), self);
|
||
|
|
||
|
self->collect_data1 = gst_collect_pads_add_pad (self->collect, self->sinkpad1,
|
||
|
sizeof (GstCollectData), NULL, TRUE);
|
||
|
self->collect_data2 = gst_collect_pads_add_pad (self->collect, self->sinkpad2,
|
||
|
sizeof (GstCollectData), NULL, TRUE);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_ffmpegvidcmp_finalize (GObject * object)
|
||
|
{
|
||
|
GstFFMpegVidCmp *self = GST_FFMPEGVIDCMP (object);
|
||
|
|
||
|
g_free (self->stats_file);
|
||
|
|
||
|
gst_ffmpegvidcmp_reset (self);
|
||
|
|
||
|
if (self->collect)
|
||
|
gst_object_unref (self->collect);
|
||
|
|
||
|
G_OBJECT_CLASS (gst_ffmpegvidcmp_parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_ffmpegvidcmp_set_property (GObject * object, guint prop_id,
|
||
|
const GValue * value, GParamSpec * pspec)
|
||
|
{
|
||
|
GstFFMpegVidCmp *self = GST_FFMPEGVIDCMP (object);
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
switch (prop_id) {
|
||
|
case PROP_STATS_FILE:
|
||
|
{
|
||
|
if (self->filter_graph) {
|
||
|
GST_WARNING_OBJECT (self, "changing the stats file after the filter "
|
||
|
"graph is initialized is not supported");
|
||
|
break;
|
||
|
}
|
||
|
g_free (self->stats_file);
|
||
|
self->stats_file = g_value_dup_string (value);
|
||
|
break;
|
||
|
}
|
||
|
case PROP_METHOD:
|
||
|
{
|
||
|
if (self->filter_graph) {
|
||
|
GST_WARNING_OBJECT (self, "changing the method after the filter "
|
||
|
"graph is initialized is not supported");
|
||
|
break;
|
||
|
}
|
||
|
self->method = g_value_get_enum (value);
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_ffmpegvidcmp_get_property (GObject * object, guint prop_id, GValue * value,
|
||
|
GParamSpec * pspec)
|
||
|
{
|
||
|
GstFFMpegVidCmp *self = GST_FFMPEGVIDCMP (object);
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
switch (prop_id) {
|
||
|
case PROP_STATS_FILE:
|
||
|
g_value_set_string (value, self->stats_file);
|
||
|
break;
|
||
|
case PROP_METHOD:
|
||
|
g_value_set_enum (value, self->method);
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_ffmpegvidcmp_setcaps (GstFFMpegVidCmp * self, GstPad * pad, GstCaps * caps)
|
||
|
{
|
||
|
GstVideoInfo vinfo;
|
||
|
|
||
|
g_return_val_if_fail (GST_IS_FFMPEGVIDCMP (self), FALSE);
|
||
|
|
||
|
gst_video_info_init (&vinfo);
|
||
|
if (!gst_video_info_from_caps (&vinfo, caps))
|
||
|
return FALSE;
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
|
||
|
self->width = GST_VIDEO_INFO_WIDTH (&vinfo);
|
||
|
self->height = GST_VIDEO_INFO_HEIGHT (&vinfo);
|
||
|
self->fps_num = GST_VIDEO_INFO_FPS_N (&vinfo);
|
||
|
self->fps_denom = GST_VIDEO_INFO_FPS_D (&vinfo);
|
||
|
|
||
|
if (pad == self->sinkpad1)
|
||
|
self->vinfo1 = vinfo;
|
||
|
else
|
||
|
self->vinfo2 = vinfo;
|
||
|
|
||
|
self->pixfmt =
|
||
|
gst_ffmpeg_videoformat_to_pixfmt (GST_VIDEO_INFO_FORMAT (&vinfo));
|
||
|
if (self->pixfmt == AV_PIX_FMT_NONE) {
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
GST_ERROR_OBJECT (self, "failed to find suitable ffmpeg pixfmt");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_ffmpegvidcmp_sink_event (GstCollectPads * pads, GstCollectData * data,
|
||
|
GstEvent * event, gpointer user_data)
|
||
|
{
|
||
|
GstFFMpegVidCmp *self = GST_FFMPEGVIDCMP (user_data);
|
||
|
GstPad *pad = data->pad;
|
||
|
gboolean ret = FALSE;
|
||
|
|
||
|
switch (GST_EVENT_TYPE (event)) {
|
||
|
case GST_EVENT_CAPS:
|
||
|
{
|
||
|
GstCaps *caps;
|
||
|
|
||
|
gst_event_parse_caps (event, &caps);
|
||
|
ret = gst_ffmpegvidcmp_setcaps (self, pad, caps);
|
||
|
|
||
|
/* forward sinkpad1 caps to downstream */
|
||
|
if (ret && pad == self->sinkpad1) {
|
||
|
ret = gst_pad_push_event (self->srcpad, event);
|
||
|
event = NULL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
gst_event_unref (event);
|
||
|
event = NULL;
|
||
|
break;
|
||
|
}
|
||
|
case GST_EVENT_STREAM_START:
|
||
|
case GST_EVENT_SEGMENT:
|
||
|
{
|
||
|
/* forward the sinkpad1 event to downstream */
|
||
|
if (pad == self->sinkpad1) {
|
||
|
ret = gst_pad_push_event (self->srcpad, event);
|
||
|
event = NULL;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (event != NULL)
|
||
|
return gst_collect_pads_event_default (pads, data, event, FALSE);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_ffmpegvidcmp_sink_query (GstCollectPads * pads, GstCollectData * data,
|
||
|
GstQuery * query, gpointer user_data)
|
||
|
{
|
||
|
GstFFMpegVidCmp *self = GST_FFMPEGVIDCMP (user_data);
|
||
|
GstPad *pad = data->pad;
|
||
|
|
||
|
switch (GST_QUERY_TYPE (query)) {
|
||
|
case GST_QUERY_ALLOCATION:
|
||
|
if (pad == self->sinkpad1)
|
||
|
return gst_pad_peer_query (self->srcpad, query);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
return gst_collect_pads_query_default (pads, data, query, FALSE);
|
||
|
}
|
||
|
|
||
|
static GstStateChangeReturn
|
||
|
gst_ffmpegvidcmp_change_state (GstElement * element, GstStateChange transition)
|
||
|
{
|
||
|
GstFFMpegVidCmp *self = GST_FFMPEGVIDCMP (element);
|
||
|
GstStateChangeReturn ret;
|
||
|
|
||
|
switch (transition) {
|
||
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||
|
gst_ffmpegvidcmp_reset (self);
|
||
|
gst_collect_pads_start (self->collect);
|
||
|
break;
|
||
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||
|
gst_collect_pads_stop (self->collect);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ret =
|
||
|
GST_ELEMENT_CLASS (gst_ffmpegvidcmp_parent_class)->change_state (element,
|
||
|
transition);
|
||
|
|
||
|
switch (transition) {
|
||
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||
|
gst_ffmpegvidcmp_reset (self);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static gint
|
||
|
init_filter_graph (GstFFMpegVidCmp * self)
|
||
|
{
|
||
|
AVFilterInOut *inputs = NULL;
|
||
|
AVFilterInOut *outputs = NULL;
|
||
|
GEnumClass *enum_class;
|
||
|
GEnumValue *method;
|
||
|
gchar *args = NULL;
|
||
|
gchar *f = NULL;
|
||
|
gint res = -1;
|
||
|
|
||
|
enum_class = g_type_class_ref (GST_FFMPEGVIDCMP_METHOD_TYPE);
|
||
|
method = g_enum_get_value (enum_class, self->method);
|
||
|
g_type_class_unref (enum_class);
|
||
|
if (!method) {
|
||
|
GST_ERROR_OBJECT (self, "unknown compare method");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
GST_INFO_OBJECT (self, " method : %s", method->value_nick);
|
||
|
GST_INFO_OBJECT (self, "stats-file : %s", self->stats_file);
|
||
|
|
||
|
if (self->stats_file)
|
||
|
f = g_strdup_printf ("=f=\\'%s\\'", self->stats_file);
|
||
|
else
|
||
|
f = g_strdup ("");
|
||
|
|
||
|
args =
|
||
|
g_strdup_printf
|
||
|
("buffer=video_size=%dx%d:pix_fmt=%d:time_base=1/1:pixel_aspect=0/1[in1];"
|
||
|
"buffer=video_size=%dx%d:pix_fmt=%d:time_base=1/1:pixel_aspect=0/1[in2];"
|
||
|
"[in1][in2]%s%s[out];[out]buffersink", self->width, self->height,
|
||
|
self->pixfmt, self->width, self->height, self->pixfmt, method->value_nick,
|
||
|
f);
|
||
|
|
||
|
g_free (f);
|
||
|
|
||
|
self->filter_graph = avfilter_graph_alloc ();
|
||
|
if (!self->filter_graph) {
|
||
|
GST_ERROR_OBJECT (self, "failed to allocate filter graph");
|
||
|
g_free (args);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
res = avfilter_graph_parse2 (self->filter_graph, args, &inputs, &outputs);
|
||
|
g_free (args);
|
||
|
if (res < 0) {
|
||
|
GST_ERROR_OBJECT (self, "failed to parse filter graph");
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
if (inputs || outputs) {
|
||
|
GST_ERROR_OBJECT (self, "unlinked inputs/outputs in filter graph");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
res = avfilter_graph_config (self->filter_graph, NULL);
|
||
|
if (res < 0) {
|
||
|
GST_ERROR_OBJECT (self, "failed to configure filter graph");
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
self->in1_ctx =
|
||
|
avfilter_graph_get_filter (self->filter_graph, "Parsed_buffer_0");
|
||
|
self->in2_ctx =
|
||
|
avfilter_graph_get_filter (self->filter_graph, "Parsed_buffer_1");
|
||
|
self->out_ctx =
|
||
|
avfilter_graph_get_filter (self->filter_graph, "Parsed_buffersink_3");
|
||
|
|
||
|
if (!self->in1_ctx || !self->in2_ctx || !self->out_ctx) {
|
||
|
GST_ERROR_OBJECT (self, "failed to get filter contexts");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static gint
|
||
|
process_filter_graph (GstFFMpegVidCmp * self, AVFrame * in1, AVFrame * in2)
|
||
|
{
|
||
|
AVFrame *out;
|
||
|
gint res;
|
||
|
|
||
|
if (!self->filter_graph) {
|
||
|
res = init_filter_graph (self);
|
||
|
if (res < 0)
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
res = av_buffersrc_add_frame (self->in1_ctx, in1);
|
||
|
if (res < 0)
|
||
|
return res;
|
||
|
|
||
|
res = av_buffersrc_add_frame (self->in2_ctx, in2);
|
||
|
if (res < 0)
|
||
|
return res;
|
||
|
|
||
|
out = av_frame_alloc ();
|
||
|
out->width = self->width;
|
||
|
out->height = self->height;
|
||
|
out->format = self->pixfmt;
|
||
|
|
||
|
res = av_buffersink_get_frame (self->out_ctx, out);
|
||
|
|
||
|
av_frame_unref (out);
|
||
|
av_frame_free (&out);
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
_fill_avpicture (GstFFMpegVidCmp * self, AVFrame * picture,
|
||
|
GstVideoFrame * vframe)
|
||
|
{
|
||
|
gint i;
|
||
|
|
||
|
for (i = 0; i < GST_VIDEO_FRAME_N_PLANES (vframe); ++i) {
|
||
|
picture->data[i] = GST_VIDEO_FRAME_PLANE_DATA (vframe, i);
|
||
|
picture->linesize[i] = GST_VIDEO_FRAME_PLANE_STRIDE (vframe, i);
|
||
|
}
|
||
|
|
||
|
picture->width = GST_VIDEO_FRAME_WIDTH (vframe);
|
||
|
picture->height = GST_VIDEO_FRAME_HEIGHT (vframe);
|
||
|
picture->format = self->pixfmt;
|
||
|
}
|
||
|
|
||
|
static GstFlowReturn
|
||
|
gst_ffmpegvidcmp_collected (GstCollectPads * pads, GstFFMpegVidCmp * self)
|
||
|
{
|
||
|
GstBuffer *buf1 = NULL, *buf2 = NULL;
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
|
||
|
if (G_UNLIKELY (self->fps_num == 0))
|
||
|
goto not_negotiated;
|
||
|
|
||
|
if (!gst_pad_has_current_caps (self->sinkpad1) ||
|
||
|
!gst_pad_has_current_caps (self->sinkpad2))
|
||
|
goto not_negotiated;
|
||
|
|
||
|
if (GST_VIDEO_INFO_WIDTH (&self->vinfo1) !=
|
||
|
GST_VIDEO_INFO_WIDTH (&self->vinfo2) ||
|
||
|
GST_VIDEO_INFO_HEIGHT (&self->vinfo1) !=
|
||
|
GST_VIDEO_INFO_HEIGHT (&self->vinfo2) ||
|
||
|
GST_VIDEO_INFO_FORMAT (&self->vinfo1) !=
|
||
|
GST_VIDEO_INFO_FORMAT (&self->vinfo2) ||
|
||
|
GST_VIDEO_INFO_FPS_D (&self->vinfo1) !=
|
||
|
GST_VIDEO_INFO_FPS_D (&self->vinfo2) ||
|
||
|
GST_VIDEO_INFO_FPS_N (&self->vinfo1) !=
|
||
|
GST_VIDEO_INFO_FPS_N (&self->vinfo2))
|
||
|
goto input_formats_do_not_match;
|
||
|
|
||
|
buf1 = gst_collect_pads_pop (pads, self->collect_data1);
|
||
|
buf2 = gst_collect_pads_pop (pads, self->collect_data2);
|
||
|
|
||
|
/* compare */
|
||
|
if (buf1 && buf2) {
|
||
|
/* *INDENT-OFF* */
|
||
|
AVFrame in1 = { {0,} };
|
||
|
AVFrame in2 = { {0,} };
|
||
|
/* *INDENT-ON* */
|
||
|
GstVideoFrame frame1, frame2;
|
||
|
|
||
|
if (!gst_video_frame_map (&frame1, &self->vinfo1, buf1, GST_MAP_READ))
|
||
|
goto map_failed;
|
||
|
|
||
|
if (!gst_video_frame_map (&frame2, &self->vinfo2, buf2, GST_MAP_READ)) {
|
||
|
gst_video_frame_unmap (&frame1);
|
||
|
goto map_failed;
|
||
|
}
|
||
|
|
||
|
_fill_avpicture (self, &in1, &frame1);
|
||
|
_fill_avpicture (self, &in2, &frame2);
|
||
|
|
||
|
if (process_filter_graph (self, &in1, &in2) < 0)
|
||
|
GST_WARNING_OBJECT (self, "Could not process filter graph");
|
||
|
|
||
|
gst_video_frame_unmap (&frame1);
|
||
|
gst_video_frame_unmap (&frame2);
|
||
|
}
|
||
|
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
|
||
|
if (buf2)
|
||
|
gst_buffer_unref (buf2);
|
||
|
|
||
|
if (!buf1) {
|
||
|
gst_pad_push_event (self->srcpad, gst_event_new_eos ());
|
||
|
return GST_FLOW_EOS;
|
||
|
}
|
||
|
|
||
|
return gst_pad_push (self->srcpad, buf1);
|
||
|
|
||
|
/* ERRORS */
|
||
|
not_negotiated:
|
||
|
{
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
|
||
|
("No input format negotiated"));
|
||
|
return GST_FLOW_NOT_NEGOTIATED;
|
||
|
}
|
||
|
input_formats_do_not_match:
|
||
|
{
|
||
|
GstCaps *caps1, *caps2;
|
||
|
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
caps1 = gst_pad_get_current_caps (self->sinkpad1);
|
||
|
caps2 = gst_pad_get_current_caps (self->sinkpad2);
|
||
|
GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
|
||
|
("input formats don't match: %" GST_PTR_FORMAT " vs. %" GST_PTR_FORMAT,
|
||
|
caps1, caps2));
|
||
|
gst_caps_unref (caps1);
|
||
|
gst_caps_unref (caps2);
|
||
|
return GST_FLOW_ERROR;
|
||
|
}
|
||
|
map_failed:
|
||
|
{
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
GST_DEBUG_OBJECT (self, "Failed to map frame");
|
||
|
gst_buffer_unref (buf2);
|
||
|
gst_buffer_unref (buf1);
|
||
|
return GST_FLOW_ERROR;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
gst_ffmpegvidcmp_register (GstPlugin * plugin)
|
||
|
{
|
||
|
return gst_element_register (plugin, "avvideocompare", GST_RANK_NONE,
|
||
|
GST_TYPE_FFMPEGVIDCMP);
|
||
|
}
|