mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 10:40:34 +00:00
f9d110f596
The latency query originally had a fallthrough to the default label at the end as fallback, but that got messed up when the DURATION and POSITION queries were added, so it then fell through to the duration query handler instead. Restore original behaviour. https://bugzilla.gnome.org/show_bug.cgi?id=699077
1658 lines
54 KiB
C
1658 lines
54 KiB
C
/* GStreamer
|
|
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-videorate
|
|
*
|
|
* This element takes an incoming stream of timestamped video frames.
|
|
* It will produce a perfect stream that matches the source pad's framerate.
|
|
*
|
|
* The correction is performed by dropping and duplicating frames, no fancy
|
|
* algorithm is used to interpolate frames (yet).
|
|
*
|
|
* By default the element will simply negotiate the same framerate on its
|
|
* source and sink pad.
|
|
*
|
|
* This operation is useful to link to elements that require a perfect stream.
|
|
* Typical examples are formats that do not store timestamps for video frames,
|
|
* but only store a framerate, like Ogg and AVI.
|
|
*
|
|
* A conversion to a specific framerate can be forced by using filtered caps on
|
|
* the source pad.
|
|
*
|
|
* The properties #GstVideoRate:in, #GstVideoRate:out, #GstVideoRate:duplicate
|
|
* and #GstVideoRate:drop can be read to obtain information about number of
|
|
* input frames, output frames, dropped frames (i.e. the number of unused input
|
|
* frames) and duplicated frames (i.e. the number of times an input frame was
|
|
* duplicated, beside being used normally).
|
|
*
|
|
* An input stream that needs no adjustments will thus never have dropped or
|
|
* duplicated frames.
|
|
*
|
|
* When the #GstVideoRate:silent property is set to FALSE, a GObject property
|
|
* notification will be emitted whenever one of the #GstVideoRate:duplicate or
|
|
* #GstVideoRate:drop values changes.
|
|
* This can potentially cause performance degradation.
|
|
* Note that property notification will happen from the streaming thread, so
|
|
* applications should be prepared for this.
|
|
*
|
|
* The property #GstVideoRate:rate allows the modification of video speed by a
|
|
* certain factor. It must not be confused with framerate. Think of rate as
|
|
* speed and framerate as flow.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example pipelines</title>
|
|
* |[
|
|
* gst-launch-1.0 -v uridecodebin uri=file:///path/to/video.ogg ! videoconvert ! videoscale ! videorate ! video/x-raw,framerate=15/1 ! autovideosink
|
|
* ]| Decode a video file and adjust the framerate to 15 fps before playing.
|
|
* To create a test Ogg/Theora file refer to the documentation of theoraenc.
|
|
* |[
|
|
* gst-launch-1.0 -v v4l2src ! videorate ! video/x-raw,framerate=25/2 ! theoraenc ! oggmux ! filesink location=recording.ogg
|
|
* ]| Capture video from a V4L device, and adjust the stream to 12.5 fps before
|
|
* encoding to Ogg/Theora.
|
|
* |[
|
|
* gst-launch-1.0 -v uridecodebin uri=file:///path/to/video.ogg ! videoconvert ! videoscale ! videorate ! video/x-raw,framerate=1/5 ! jpegenc ! multifilesink location=snapshot-%05d.jpg
|
|
* ]| Decode a video file and save a snapshot every 5 seconds as consecutively numbered jpeg file.
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstvideorate.h"
|
|
#include <gst/video/video.h>
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (video_rate_debug);
|
|
#define GST_CAT_DEFAULT video_rate_debug
|
|
|
|
/* GstVideoRate signals and args */
|
|
enum
|
|
{
|
|
/* FILL ME */
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
#define DEFAULT_SILENT TRUE
|
|
#define DEFAULT_NEW_PREF 1.0
|
|
#define DEFAULT_SKIP_TO_FIRST FALSE
|
|
#define DEFAULT_DROP_ONLY FALSE
|
|
#define DEFAULT_AVERAGE_PERIOD 0
|
|
#define DEFAULT_MAX_RATE G_MAXINT
|
|
#define DEFAULT_RATE 1.0
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_IN,
|
|
PROP_OUT,
|
|
PROP_DUP,
|
|
PROP_DROP,
|
|
PROP_SILENT,
|
|
PROP_NEW_PREF,
|
|
PROP_SKIP_TO_FIRST,
|
|
PROP_DROP_ONLY,
|
|
PROP_AVERAGE_PERIOD,
|
|
PROP_MAX_RATE,
|
|
PROP_RATE
|
|
};
|
|
|
|
static GstStaticPadTemplate gst_video_rate_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-raw(ANY);" "video/x-bayer(ANY);"
|
|
"image/jpeg(ANY);" "image/png(ANY)")
|
|
);
|
|
|
|
static GstStaticPadTemplate gst_video_rate_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-raw(ANY);" "video/x-bayer(ANY);"
|
|
"image/jpeg(ANY);" "image/png(ANY)")
|
|
);
|
|
|
|
static void gst_video_rate_swap_prev (GstVideoRate * videorate,
|
|
GstBuffer * buffer, gint64 time);
|
|
static gboolean gst_video_rate_sink_event (GstBaseTransform * trans,
|
|
GstEvent * event);
|
|
static gboolean gst_video_rate_src_event (GstBaseTransform * trans,
|
|
GstEvent * event);
|
|
static gboolean gst_video_rate_query (GstBaseTransform * trans,
|
|
GstPadDirection direction, GstQuery * query);
|
|
|
|
static gboolean gst_video_rate_setcaps (GstBaseTransform * trans,
|
|
GstCaps * in_caps, GstCaps * out_caps);
|
|
|
|
static GstCaps *gst_video_rate_transform_caps (GstBaseTransform * trans,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * filter);
|
|
|
|
static GstCaps *gst_video_rate_fixate_caps (GstBaseTransform * trans,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
|
|
|
|
static GstFlowReturn gst_video_rate_transform_ip (GstBaseTransform * trans,
|
|
GstBuffer * buf);
|
|
|
|
static gboolean gst_video_rate_propose_allocation (GstBaseTransform * trans,
|
|
GstQuery * decide_query, GstQuery * query);
|
|
|
|
static gboolean gst_video_rate_start (GstBaseTransform * trans);
|
|
static gboolean gst_video_rate_stop (GstBaseTransform * trans);
|
|
|
|
|
|
static void gst_video_rate_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec);
|
|
static void gst_video_rate_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec);
|
|
|
|
static GParamSpec *pspec_drop = NULL;
|
|
static GParamSpec *pspec_duplicate = NULL;
|
|
|
|
#define gst_video_rate_parent_class parent_class
|
|
G_DEFINE_TYPE (GstVideoRate, gst_video_rate, GST_TYPE_BASE_TRANSFORM);
|
|
|
|
static void
|
|
gst_video_rate_class_init (GstVideoRateClass * klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
GstBaseTransformClass *base_class = GST_BASE_TRANSFORM_CLASS (klass);
|
|
|
|
object_class->set_property = gst_video_rate_set_property;
|
|
object_class->get_property = gst_video_rate_get_property;
|
|
|
|
base_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_rate_setcaps);
|
|
base_class->transform_caps =
|
|
GST_DEBUG_FUNCPTR (gst_video_rate_transform_caps);
|
|
base_class->transform_ip = GST_DEBUG_FUNCPTR (gst_video_rate_transform_ip);
|
|
base_class->sink_event = GST_DEBUG_FUNCPTR (gst_video_rate_sink_event);
|
|
base_class->src_event = GST_DEBUG_FUNCPTR (gst_video_rate_src_event);
|
|
base_class->start = GST_DEBUG_FUNCPTR (gst_video_rate_start);
|
|
base_class->stop = GST_DEBUG_FUNCPTR (gst_video_rate_stop);
|
|
base_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_video_rate_fixate_caps);
|
|
base_class->query = GST_DEBUG_FUNCPTR (gst_video_rate_query);
|
|
base_class->propose_allocation =
|
|
GST_DEBUG_FUNCPTR (gst_video_rate_propose_allocation);
|
|
|
|
g_object_class_install_property (object_class, PROP_IN,
|
|
g_param_spec_uint64 ("in", "In",
|
|
"Number of input frames", 0, G_MAXUINT64, 0,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (object_class, PROP_OUT,
|
|
g_param_spec_uint64 ("out", "Out", "Number of output frames", 0,
|
|
G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
pspec_duplicate = g_param_spec_uint64 ("duplicate", "Duplicate",
|
|
"Number of duplicated frames", 0, G_MAXUINT64, 0,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_DUP, pspec_duplicate);
|
|
pspec_drop = g_param_spec_uint64 ("drop", "Drop", "Number of dropped frames",
|
|
0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_DROP, pspec_drop);
|
|
g_object_class_install_property (object_class, PROP_SILENT,
|
|
g_param_spec_boolean ("silent", "silent",
|
|
"Don't emit notify for dropped and duplicated frames", DEFAULT_SILENT,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (object_class, PROP_NEW_PREF,
|
|
g_param_spec_double ("new-pref", "New Pref",
|
|
"Value indicating how much to prefer new frames (unused)", 0.0, 1.0,
|
|
DEFAULT_NEW_PREF, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* GstVideoRate:skip-to-first:
|
|
*
|
|
* Don't produce buffers before the first one we receive.
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_SKIP_TO_FIRST,
|
|
g_param_spec_boolean ("skip-to-first", "Skip to first buffer",
|
|
"Don't produce buffers before the first one we receive",
|
|
DEFAULT_SKIP_TO_FIRST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* GstVideoRate:drop-only:
|
|
*
|
|
* Only drop frames, no duplicates are produced.
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_DROP_ONLY,
|
|
g_param_spec_boolean ("drop-only", "Only Drop",
|
|
"Only drop frames, no duplicates are produced",
|
|
DEFAULT_DROP_ONLY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* GstVideoRate:average-period:
|
|
*
|
|
* Arrange for maximum framerate by dropping frames beyond a certain framerate,
|
|
* where the framerate is calculated using a moving average over the
|
|
* configured.
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_AVERAGE_PERIOD,
|
|
g_param_spec_uint64 ("average-period", "Period over which to average",
|
|
"Period over which to average the framerate (in ns) (0 = disabled)",
|
|
0, G_MAXINT64, DEFAULT_AVERAGE_PERIOD,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* GstVideoRate:max-rate:
|
|
*
|
|
* maximum framerate to pass through
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_MAX_RATE,
|
|
g_param_spec_int ("max-rate", "maximum framerate",
|
|
"Maximum framerate allowed to pass through "
|
|
"(in frames per second, implies drop-only)",
|
|
1, G_MAXINT, DEFAULT_MAX_RATE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* GstVideoRate:rate:
|
|
*
|
|
* Factor of speed for frame displaying
|
|
*
|
|
* Since: 1.12
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_RATE,
|
|
g_param_spec_double ("rate", "Rate",
|
|
"Factor of speed for frame displaying", 0.0, G_MAXDOUBLE,
|
|
DEFAULT_RATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
GST_PARAM_MUTABLE_READY));
|
|
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"Video rate adjuster", "Filter/Effect/Video",
|
|
"Drops/duplicates/adjusts timestamps on video frames to make a perfect stream",
|
|
"Wim Taymans <wim@fluendo.com>");
|
|
|
|
gst_element_class_add_static_pad_template (element_class,
|
|
&gst_video_rate_sink_template);
|
|
gst_element_class_add_static_pad_template (element_class,
|
|
&gst_video_rate_src_template);
|
|
}
|
|
|
|
static void
|
|
gst_value_fraction_get_extremes (const GValue * v,
|
|
gint * min_num, gint * min_denom, gint * max_num, gint * max_denom)
|
|
{
|
|
if (GST_VALUE_HOLDS_FRACTION (v)) {
|
|
*min_num = *max_num = gst_value_get_fraction_numerator (v);
|
|
*min_denom = *max_denom = gst_value_get_fraction_denominator (v);
|
|
} else if (GST_VALUE_HOLDS_FRACTION_RANGE (v)) {
|
|
const GValue *min, *max;
|
|
|
|
min = gst_value_get_fraction_range_min (v);
|
|
*min_num = gst_value_get_fraction_numerator (min);
|
|
*min_denom = gst_value_get_fraction_denominator (min);
|
|
|
|
max = gst_value_get_fraction_range_max (v);
|
|
*max_num = gst_value_get_fraction_numerator (max);
|
|
*max_denom = gst_value_get_fraction_denominator (max);
|
|
} else if (GST_VALUE_HOLDS_LIST (v)) {
|
|
gint min_n = G_MAXINT, min_d = 1, max_n = 0, max_d = 1;
|
|
int i, n;
|
|
|
|
*min_num = G_MAXINT;
|
|
*min_denom = 1;
|
|
*max_num = 0;
|
|
*max_denom = 1;
|
|
|
|
n = gst_value_list_get_size (v);
|
|
|
|
g_assert (n > 0);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
const GValue *t = gst_value_list_get_value (v, i);
|
|
|
|
gst_value_fraction_get_extremes (t, &min_n, &min_d, &max_n, &max_d);
|
|
if (gst_util_fraction_compare (min_n, min_d, *min_num, *min_denom) < 0) {
|
|
*min_num = min_n;
|
|
*min_denom = min_d;
|
|
}
|
|
|
|
if (gst_util_fraction_compare (max_n, max_d, *max_num, *max_denom) > 0) {
|
|
*max_num = max_n;
|
|
*max_denom = max_d;
|
|
}
|
|
}
|
|
} else {
|
|
g_warning ("Unknown type for framerate");
|
|
*min_num = 0;
|
|
*min_denom = 1;
|
|
*max_num = G_MAXINT;
|
|
*max_denom = 1;
|
|
}
|
|
}
|
|
|
|
/* Clamp the framerate in a caps structure to be a smaller range then
|
|
* [1...max_rate], otherwise return false */
|
|
static gboolean
|
|
gst_video_max_rate_clamp_structure (GstStructure * s, gint maxrate,
|
|
gint * min_num, gint * min_denom, gint * max_num, gint * max_denom)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
if (!gst_structure_has_field (s, "framerate")) {
|
|
/* No framerate field implies any framerate, clamping would result in
|
|
* [1..max_rate] so not a real subset */
|
|
goto out;
|
|
} else {
|
|
const GValue *v;
|
|
GValue intersection = { 0, };
|
|
GValue clamp = { 0, };
|
|
gint tmp_num, tmp_denom;
|
|
|
|
g_value_init (&clamp, GST_TYPE_FRACTION_RANGE);
|
|
gst_value_set_fraction_range_full (&clamp, 0, 1, maxrate, 1);
|
|
|
|
v = gst_structure_get_value (s, "framerate");
|
|
ret = gst_value_intersect (&intersection, v, &clamp);
|
|
g_value_unset (&clamp);
|
|
|
|
if (!ret)
|
|
goto out;
|
|
|
|
gst_value_fraction_get_extremes (&intersection,
|
|
min_num, min_denom, max_num, max_denom);
|
|
|
|
gst_value_fraction_get_extremes (v,
|
|
&tmp_num, &tmp_denom, max_num, max_denom);
|
|
|
|
if (gst_util_fraction_compare (*max_num, *max_denom, maxrate, 1) > 0) {
|
|
*max_num = maxrate;
|
|
*max_denom = 1;
|
|
}
|
|
|
|
gst_structure_take_value (s, "framerate", &intersection);
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_video_rate_transform_caps (GstBaseTransform * trans,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * filter)
|
|
{
|
|
GstVideoRate *videorate = GST_VIDEO_RATE (trans);
|
|
GstCaps *ret;
|
|
GstStructure *s, *s1, *s2, *s3 = NULL;
|
|
int maxrate = g_atomic_int_get (&videorate->max_rate);
|
|
gint i;
|
|
|
|
ret = gst_caps_new_empty ();
|
|
|
|
for (i = 0; i < gst_caps_get_size (caps); i++) {
|
|
s = gst_caps_get_structure (caps, i);
|
|
|
|
s1 = gst_structure_copy (s);
|
|
|
|
if (videorate->updating_caps && direction == GST_PAD_SINK) {
|
|
GST_INFO_OBJECT (trans,
|
|
"Only updating caps %" GST_PTR_FORMAT " with framerate" " %d/%d",
|
|
caps, videorate->to_rate_numerator, videorate->to_rate_denominator);
|
|
|
|
gst_structure_set (s1, "framerate", GST_TYPE_FRACTION,
|
|
videorate->to_rate_numerator, videorate->to_rate_denominator, NULL);
|
|
ret = gst_caps_merge_structure (ret, s1);
|
|
|
|
continue;
|
|
}
|
|
|
|
s2 = gst_structure_copy (s);
|
|
s3 = NULL;
|
|
|
|
if (videorate->drop_only) {
|
|
gint min_num = 0, min_denom = 1;
|
|
gint max_num = G_MAXINT, max_denom = 1;
|
|
|
|
/* Clamp the caps to our maximum rate as the first caps if possible */
|
|
if (!gst_video_max_rate_clamp_structure (s1, maxrate,
|
|
&min_num, &min_denom, &max_num, &max_denom)) {
|
|
min_num = 0;
|
|
min_denom = 1;
|
|
max_num = maxrate;
|
|
max_denom = 1;
|
|
|
|
/* clamp wouldn't be a real subset of 1..maxrate, in this case the sink
|
|
* caps should become [1..maxrate], [1..maxint] and the src caps just
|
|
* [1..maxrate]. In case there was a caps incompatibility things will
|
|
* explode later as appropriate :)
|
|
*
|
|
* In case [X..maxrate] == [X..maxint], skip as we'll set it later
|
|
*/
|
|
if (direction == GST_PAD_SRC && maxrate != G_MAXINT)
|
|
gst_structure_set (s1, "framerate", GST_TYPE_FRACTION_RANGE,
|
|
min_num, min_denom, maxrate, 1, NULL);
|
|
else {
|
|
gst_structure_free (s1);
|
|
s1 = NULL;
|
|
}
|
|
}
|
|
|
|
if (direction == GST_PAD_SRC) {
|
|
/* We can accept anything as long as it's at least the minimal framerate
|
|
* the the sink needs */
|
|
gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE,
|
|
min_num, min_denom, G_MAXINT, 1, NULL);
|
|
|
|
/* Also allow unknown framerate, if it isn't already */
|
|
if (min_num != 0 || min_denom != 1) {
|
|
s3 = gst_structure_copy (s);
|
|
gst_structure_set (s3, "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
|
|
}
|
|
} else if (max_num != 0 || max_denom != 1) {
|
|
/* We can provide everything upto the maximum framerate at the src */
|
|
gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE,
|
|
0, 1, max_num, max_denom, NULL);
|
|
}
|
|
} else if (direction == GST_PAD_SINK) {
|
|
gint min_num = 0, min_denom = 1;
|
|
gint max_num = G_MAXINT, max_denom = 1;
|
|
|
|
if (!gst_video_max_rate_clamp_structure (s1, maxrate,
|
|
&min_num, &min_denom, &max_num, &max_denom)) {
|
|
gst_structure_free (s1);
|
|
s1 = NULL;
|
|
}
|
|
gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1,
|
|
maxrate, 1, NULL);
|
|
} else {
|
|
/* set the framerate as a range */
|
|
gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1,
|
|
G_MAXINT, 1, NULL);
|
|
}
|
|
if (s1 != NULL)
|
|
ret = gst_caps_merge_structure_full (ret, s1,
|
|
gst_caps_features_copy (gst_caps_get_features (caps, i)));
|
|
ret = gst_caps_merge_structure_full (ret, s2,
|
|
gst_caps_features_copy (gst_caps_get_features (caps, i)));
|
|
if (s3 != NULL)
|
|
ret = gst_caps_merge_structure_full (ret, s3,
|
|
gst_caps_features_copy (gst_caps_get_features (caps, i)));
|
|
}
|
|
if (filter) {
|
|
GstCaps *intersection;
|
|
|
|
intersection =
|
|
gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (ret);
|
|
ret = intersection;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_video_rate_fixate_caps (GstBaseTransform * trans,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
|
|
{
|
|
GstStructure *s;
|
|
gint num, denom;
|
|
const GValue *par;
|
|
|
|
s = gst_caps_get_structure (caps, 0);
|
|
if (G_UNLIKELY (!gst_structure_get_fraction (s, "framerate", &num, &denom)))
|
|
return othercaps;
|
|
|
|
othercaps = gst_caps_truncate (othercaps);
|
|
othercaps = gst_caps_make_writable (othercaps);
|
|
s = gst_caps_get_structure (othercaps, 0);
|
|
gst_structure_fixate_field_nearest_fraction (s, "framerate", num, denom);
|
|
|
|
if ((par = gst_structure_get_value (s, "pixel-aspect-ratio")))
|
|
gst_structure_fixate_field_nearest_fraction (s, "pixel-aspect-ratio", 1, 1);
|
|
|
|
return othercaps;
|
|
}
|
|
|
|
static gboolean
|
|
gst_video_rate_setcaps (GstBaseTransform * trans, GstCaps * in_caps,
|
|
GstCaps * out_caps)
|
|
{
|
|
GstVideoRate *videorate = GST_VIDEO_RATE (trans);
|
|
GstStructure *structure;
|
|
gboolean ret = TRUE;
|
|
gint rate_numerator, rate_denominator;
|
|
|
|
GST_DEBUG_OBJECT (trans, "setcaps called in: %" GST_PTR_FORMAT
|
|
" out: %" GST_PTR_FORMAT, in_caps, out_caps);
|
|
|
|
structure = gst_caps_get_structure (in_caps, 0);
|
|
if (!gst_structure_get_fraction (structure, "framerate",
|
|
&rate_numerator, &rate_denominator))
|
|
goto no_framerate;
|
|
|
|
videorate->from_rate_numerator = rate_numerator;
|
|
videorate->from_rate_denominator = rate_denominator;
|
|
|
|
structure = gst_caps_get_structure (out_caps, 0);
|
|
if (!gst_structure_get_fraction (structure, "framerate",
|
|
&rate_numerator, &rate_denominator))
|
|
goto no_framerate;
|
|
|
|
/* out_frame_count is scaled by the frame rate caps when calculating next_ts.
|
|
* when the frame rate caps change, we must update base_ts and reset
|
|
* out_frame_count */
|
|
if (videorate->to_rate_numerator) {
|
|
videorate->base_ts +=
|
|
gst_util_uint64_scale (videorate->out_frame_count,
|
|
videorate->to_rate_denominator * GST_SECOND,
|
|
videorate->to_rate_numerator);
|
|
}
|
|
videorate->out_frame_count = 0;
|
|
videorate->to_rate_numerator = rate_numerator;
|
|
videorate->to_rate_denominator = rate_denominator;
|
|
|
|
if (rate_numerator)
|
|
videorate->wanted_diff = gst_util_uint64_scale_int (GST_SECOND,
|
|
rate_denominator, rate_numerator);
|
|
else
|
|
videorate->wanted_diff = 0;
|
|
|
|
done:
|
|
/* After a setcaps, our caps may have changed. In that case, we can't use
|
|
* the old buffer, if there was one (it might have different dimensions) */
|
|
GST_DEBUG_OBJECT (videorate, "swapping old buffers");
|
|
gst_video_rate_swap_prev (videorate, NULL, GST_CLOCK_TIME_NONE);
|
|
videorate->last_ts = GST_CLOCK_TIME_NONE;
|
|
videorate->average = 0;
|
|
|
|
return ret;
|
|
|
|
no_framerate:
|
|
{
|
|
GST_DEBUG_OBJECT (videorate, "no framerate specified");
|
|
ret = FALSE;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_video_rate_reset (GstVideoRate * videorate)
|
|
{
|
|
GST_DEBUG_OBJECT (videorate, "resetting internal variables");
|
|
|
|
videorate->in = 0;
|
|
videorate->out = 0;
|
|
videorate->base_ts = 0;
|
|
videorate->out_frame_count = 0;
|
|
videorate->drop = 0;
|
|
videorate->dup = 0;
|
|
videorate->next_ts = GST_CLOCK_TIME_NONE;
|
|
videorate->last_ts = GST_CLOCK_TIME_NONE;
|
|
videorate->discont = TRUE;
|
|
videorate->average = 0;
|
|
videorate->force_variable_rate = FALSE;
|
|
gst_video_rate_swap_prev (videorate, NULL, 0);
|
|
|
|
gst_segment_init (&videorate->segment, GST_FORMAT_TIME);
|
|
}
|
|
|
|
static void
|
|
gst_video_rate_init (GstVideoRate * videorate)
|
|
{
|
|
gst_video_rate_reset (videorate);
|
|
videorate->silent = DEFAULT_SILENT;
|
|
videorate->new_pref = DEFAULT_NEW_PREF;
|
|
videorate->drop_only = DEFAULT_DROP_ONLY;
|
|
videorate->average_period = DEFAULT_AVERAGE_PERIOD;
|
|
videorate->average_period_set = DEFAULT_AVERAGE_PERIOD;
|
|
videorate->max_rate = DEFAULT_MAX_RATE;
|
|
videorate->rate = DEFAULT_RATE;
|
|
|
|
videorate->from_rate_numerator = 0;
|
|
videorate->from_rate_denominator = 0;
|
|
videorate->to_rate_numerator = 0;
|
|
videorate->to_rate_denominator = 0;
|
|
|
|
gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (videorate), TRUE);
|
|
}
|
|
|
|
/* flush the oldest buffer */
|
|
static GstFlowReturn
|
|
gst_video_rate_flush_prev (GstVideoRate * videorate, gboolean duplicate,
|
|
GstClockTime next_intime)
|
|
{
|
|
GstFlowReturn res;
|
|
GstBuffer *outbuf;
|
|
GstClockTime push_ts;
|
|
|
|
if (!videorate->prevbuf)
|
|
goto eos_before_buffers;
|
|
|
|
outbuf = gst_buffer_ref (videorate->prevbuf);
|
|
if (videorate->drop_only)
|
|
gst_buffer_replace (&videorate->prevbuf, NULL);
|
|
|
|
/* make sure we can write to the metadata */
|
|
outbuf = gst_buffer_make_writable (outbuf);
|
|
|
|
GST_BUFFER_OFFSET (outbuf) = videorate->out;
|
|
GST_BUFFER_OFFSET_END (outbuf) = videorate->out + 1;
|
|
|
|
if (videorate->discont) {
|
|
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
|
|
videorate->discont = FALSE;
|
|
} else
|
|
GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_DISCONT);
|
|
|
|
if (duplicate)
|
|
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_GAP);
|
|
else
|
|
GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_GAP);
|
|
|
|
/* this is the timestamp we put on the buffer */
|
|
push_ts = videorate->next_ts;
|
|
|
|
videorate->out++;
|
|
videorate->out_frame_count++;
|
|
if (videorate->segment.rate < 0.0) {
|
|
if (videorate->to_rate_numerator) {
|
|
/* interpolate next expected timestamp in the segment */
|
|
|
|
videorate->next_ts =
|
|
videorate->segment.base + videorate->segment.stop -
|
|
videorate->base_ts -
|
|
gst_util_uint64_scale (videorate->out_frame_count,
|
|
videorate->to_rate_denominator * GST_SECOND,
|
|
videorate->to_rate_numerator);
|
|
GST_BUFFER_DURATION (outbuf) = push_ts - videorate->next_ts;
|
|
} else if (next_intime != GST_CLOCK_TIME_NONE) {
|
|
videorate->next_ts = next_intime;
|
|
} else {
|
|
GST_FIXME_OBJECT (videorate, "No next intime for reverse playback");
|
|
}
|
|
} else {
|
|
if (videorate->to_rate_numerator) {
|
|
/* interpolate next expected timestamp in the segment */
|
|
videorate->next_ts =
|
|
videorate->segment.base + videorate->segment.start +
|
|
videorate->base_ts +
|
|
gst_util_uint64_scale (videorate->out_frame_count,
|
|
videorate->to_rate_denominator * GST_SECOND,
|
|
videorate->to_rate_numerator);
|
|
GST_BUFFER_DURATION (outbuf) = videorate->next_ts - push_ts;
|
|
} else if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (outbuf))) {
|
|
videorate->next_ts =
|
|
GST_BUFFER_PTS (outbuf) + GST_BUFFER_DURATION (outbuf);
|
|
} else {
|
|
/* There must always be a valid duration on prevbuf if rate > 0,
|
|
* it is ensured in the transform_ip function */
|
|
GST_FIXME_OBJECT (videorate, "No buffer duration known");
|
|
}
|
|
}
|
|
|
|
/* We do not need to update time in VFR (variable frame rate) mode */
|
|
if (!videorate->drop_only) {
|
|
/* adapt for looping, bring back to time in current segment. */
|
|
GST_BUFFER_TIMESTAMP (outbuf) = push_ts - videorate->segment.base;
|
|
}
|
|
|
|
GST_LOG_OBJECT (videorate,
|
|
"old is best, dup, pushing buffer outgoing ts %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (push_ts));
|
|
|
|
res = gst_pad_push (GST_BASE_TRANSFORM_SRC_PAD (videorate), outbuf);
|
|
|
|
return res;
|
|
|
|
/* WARNINGS */
|
|
eos_before_buffers:
|
|
{
|
|
GST_INFO_OBJECT (videorate, "got EOS before any buffer was received");
|
|
return GST_FLOW_OK;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_video_rate_swap_prev (GstVideoRate * videorate, GstBuffer * buffer,
|
|
gint64 time)
|
|
{
|
|
GST_LOG_OBJECT (videorate, "swap_prev: storing buffer %p in prev", buffer);
|
|
if (videorate->prevbuf)
|
|
gst_buffer_unref (videorate->prevbuf);
|
|
videorate->prevbuf = buffer != NULL ? gst_buffer_ref (buffer) : NULL;
|
|
videorate->prev_ts = time;
|
|
}
|
|
|
|
static void
|
|
gst_video_rate_notify_drop (GstVideoRate * videorate)
|
|
{
|
|
g_object_notify_by_pspec ((GObject *) videorate, pspec_drop);
|
|
}
|
|
|
|
static void
|
|
gst_video_rate_notify_duplicate (GstVideoRate * videorate)
|
|
{
|
|
g_object_notify_by_pspec ((GObject *) videorate, pspec_duplicate);
|
|
}
|
|
|
|
#define MAGIC_LIMIT 25
|
|
static gboolean
|
|
gst_video_rate_sink_event (GstBaseTransform * trans, GstEvent * event)
|
|
{
|
|
GstVideoRate *videorate;
|
|
|
|
videorate = GST_VIDEO_RATE (trans);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_SEGMENT:
|
|
{
|
|
GstSegment segment;
|
|
gint seqnum;
|
|
|
|
gst_event_copy_segment (event, &segment);
|
|
if (segment.format != GST_FORMAT_TIME)
|
|
goto format_error;
|
|
|
|
GST_DEBUG_OBJECT (videorate, "handle NEWSEGMENT");
|
|
|
|
/* close up the previous segment, if appropriate */
|
|
if (videorate->prevbuf) {
|
|
gint count = 0;
|
|
GstFlowReturn res;
|
|
|
|
res = GST_FLOW_OK;
|
|
/* fill up to the end of current segment,
|
|
* or only send out the stored buffer if there is no specific stop.
|
|
* regardless, prevent going loopy in strange cases */
|
|
while (res == GST_FLOW_OK && count <= MAGIC_LIMIT
|
|
&& !videorate->drop_only
|
|
&& ((videorate->segment.rate > 0.0
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->segment.stop)
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
|
|
&& videorate->next_ts - videorate->segment.base <
|
|
videorate->segment.stop) || (videorate->segment.rate < 0.0
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->segment.start)
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
|
|
&& videorate->next_ts - videorate->segment.base >=
|
|
videorate->segment.start)
|
|
|| count < 1)) {
|
|
res =
|
|
gst_video_rate_flush_prev (videorate, count > 0,
|
|
GST_CLOCK_TIME_NONE);
|
|
count++;
|
|
}
|
|
if (count > 1) {
|
|
videorate->dup += count - 1;
|
|
if (!videorate->silent)
|
|
gst_video_rate_notify_duplicate (videorate);
|
|
}
|
|
/* clean up for the new one; _chain will resume from the new start */
|
|
gst_video_rate_swap_prev (videorate, NULL, 0);
|
|
}
|
|
|
|
videorate->base_ts = 0;
|
|
videorate->out_frame_count = 0;
|
|
videorate->next_ts = GST_CLOCK_TIME_NONE;
|
|
|
|
/* We just want to update the accumulated stream_time */
|
|
|
|
segment.start = (gint64) (segment.start / videorate->rate);
|
|
segment.position = (gint64) (segment.position / videorate->rate);
|
|
if (GST_CLOCK_TIME_IS_VALID (segment.stop))
|
|
segment.stop = (gint64) (segment.stop / videorate->rate);
|
|
segment.time = (gint64) (segment.time / videorate->rate);
|
|
|
|
gst_segment_copy_into (&segment, &videorate->segment);
|
|
GST_DEBUG_OBJECT (videorate, "updated segment: %" GST_SEGMENT_FORMAT,
|
|
&videorate->segment);
|
|
|
|
|
|
seqnum = gst_event_get_seqnum (event);
|
|
gst_event_unref (event);
|
|
event = gst_event_new_segment (&segment);
|
|
gst_event_set_seqnum (event, seqnum);
|
|
|
|
break;
|
|
}
|
|
case GST_EVENT_EOS:{
|
|
gint count = 0;
|
|
GstFlowReturn res = GST_FLOW_OK;
|
|
|
|
GST_DEBUG_OBJECT (videorate, "Got EOS");
|
|
|
|
/* If the segment has a stop position, fill the segment */
|
|
if (GST_CLOCK_TIME_IS_VALID (videorate->segment.stop)) {
|
|
/* fill up to the end of current segment,
|
|
* or only send out the stored buffer if there is no specific stop.
|
|
* regardless, prevent going loopy in strange cases */
|
|
while (res == GST_FLOW_OK && count <= MAGIC_LIMIT
|
|
&& !videorate->drop_only
|
|
&& ((videorate->segment.rate > 0.0
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->segment.stop)
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
|
|
&& videorate->next_ts - videorate->segment.base <
|
|
videorate->segment.stop) || (videorate->segment.rate < 0.0
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->segment.start)
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
|
|
&& videorate->next_ts - videorate->segment.base >=
|
|
videorate->segment.start)
|
|
|| count < 1)) {
|
|
res =
|
|
gst_video_rate_flush_prev (videorate, count > 0,
|
|
GST_CLOCK_TIME_NONE);
|
|
count++;
|
|
}
|
|
} else if (!videorate->drop_only && videorate->prevbuf) {
|
|
/* Output at least one frame but if the buffer duration is valid, output
|
|
* enough frames to use the complete buffer duration */
|
|
if (GST_BUFFER_DURATION_IS_VALID (videorate->prevbuf)) {
|
|
GstClockTime end_ts =
|
|
videorate->next_ts + GST_BUFFER_DURATION (videorate->prevbuf);
|
|
|
|
while (res == GST_FLOW_OK && count <= MAGIC_LIMIT &&
|
|
((videorate->segment.rate > 0.0
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->segment.stop)
|
|
&& GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
|
|
&& videorate->next_ts - videorate->segment.base < end_ts)
|
|
|| count < 1)) {
|
|
res =
|
|
gst_video_rate_flush_prev (videorate, count > 0,
|
|
GST_CLOCK_TIME_NONE);
|
|
count++;
|
|
}
|
|
} else {
|
|
res =
|
|
gst_video_rate_flush_prev (videorate, FALSE, GST_CLOCK_TIME_NONE);
|
|
count = 1;
|
|
}
|
|
}
|
|
|
|
if (count > 1) {
|
|
videorate->dup += count - 1;
|
|
if (!videorate->silent)
|
|
gst_video_rate_notify_duplicate (videorate);
|
|
} else if (count == 0) {
|
|
videorate->drop++;
|
|
if (!videorate->silent)
|
|
gst_video_rate_notify_drop (videorate);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case GST_EVENT_FLUSH_STOP:
|
|
/* also resets the segment */
|
|
GST_DEBUG_OBJECT (videorate, "Got FLUSH_STOP");
|
|
gst_video_rate_reset (videorate);
|
|
break;
|
|
case GST_EVENT_GAP:
|
|
/* no gaps after videorate, ignore the event */
|
|
gst_event_unref (event);
|
|
return TRUE;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
|
|
|
|
/* ERRORS */
|
|
format_error:
|
|
{
|
|
GST_WARNING_OBJECT (videorate,
|
|
"Got segment but doesn't have GST_FORMAT_TIME value");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_video_rate_src_event (GstBaseTransform * trans, GstEvent * event)
|
|
{
|
|
GstVideoRate *videorate;
|
|
GstPad *sinkpad;
|
|
gboolean res = FALSE;
|
|
|
|
videorate = GST_VIDEO_RATE (trans);
|
|
sinkpad = GST_BASE_TRANSFORM_SINK_PAD (trans);
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_SEEK:
|
|
{
|
|
gdouble srate;
|
|
GstSeekFlags flags;
|
|
GstSeekType start_type, stop_type;
|
|
gint64 start, stop;
|
|
gint seqnum = gst_event_get_seqnum (event);
|
|
|
|
gst_event_parse_seek (event, &srate, NULL, &flags, &start_type, &start,
|
|
&stop_type, &stop);
|
|
|
|
start = (gint64) (start * videorate->rate);
|
|
if (GST_CLOCK_TIME_IS_VALID (stop)) {
|
|
stop = (gint64) (stop * videorate->rate);
|
|
}
|
|
|
|
gst_event_unref (event);
|
|
event = gst_event_new_seek (srate, GST_FORMAT_TIME,
|
|
flags, start_type, start, stop_type, stop);
|
|
gst_event_set_seqnum (event, seqnum);
|
|
|
|
res = gst_pad_push_event (sinkpad, event);
|
|
break;
|
|
}
|
|
default:
|
|
res = gst_pad_push_event (sinkpad, event);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
gst_video_rate_query (GstBaseTransform * trans, GstPadDirection direction,
|
|
GstQuery * query)
|
|
{
|
|
GstVideoRate *videorate = GST_VIDEO_RATE (trans);
|
|
gboolean res = FALSE;
|
|
GstPad *otherpad;
|
|
|
|
otherpad = (direction == GST_PAD_SRC) ?
|
|
GST_BASE_TRANSFORM_SINK_PAD (trans) : GST_BASE_TRANSFORM_SRC_PAD (trans);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_LATENCY:
|
|
{
|
|
GstClockTime min, max;
|
|
gboolean live;
|
|
guint64 latency;
|
|
guint64 avg_period;
|
|
gboolean drop_only;
|
|
GstPad *peer;
|
|
|
|
GST_OBJECT_LOCK (videorate);
|
|
avg_period = videorate->average_period_set;
|
|
drop_only = videorate->drop_only;
|
|
GST_OBJECT_UNLOCK (videorate);
|
|
|
|
if (avg_period == 0 && (peer = gst_pad_get_peer (otherpad))) {
|
|
if ((res = gst_pad_query (peer, query))) {
|
|
gst_query_parse_latency (query, &live, &min, &max);
|
|
|
|
GST_DEBUG_OBJECT (videorate, "Peer latency: min %"
|
|
GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (min), GST_TIME_ARGS (max));
|
|
|
|
/* Drop only has no latency, other modes have one frame latency */
|
|
if (!drop_only && videorate->from_rate_numerator != 0) {
|
|
/* add latency. We don't really know since we hold on to the frames
|
|
* until we get a next frame, which can be anything. We assume
|
|
* however that this will take from_rate time. */
|
|
latency = gst_util_uint64_scale (GST_SECOND,
|
|
videorate->from_rate_denominator,
|
|
videorate->from_rate_numerator);
|
|
} else {
|
|
/* no input framerate, we don't know */
|
|
latency = 0;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (videorate, "Our latency: %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (latency));
|
|
|
|
min += latency;
|
|
if (max != -1)
|
|
max += latency;
|
|
|
|
GST_DEBUG_OBJECT (videorate, "Calculated total latency : min %"
|
|
GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (min), GST_TIME_ARGS (max));
|
|
|
|
gst_query_set_latency (query, live, min, max);
|
|
}
|
|
gst_object_unref (peer);
|
|
break;
|
|
}
|
|
/* Simple fall back if we don't have a latency or a peer that we
|
|
* can ask about its latency yet.. */
|
|
res =
|
|
GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
|
|
query);
|
|
break;
|
|
}
|
|
case GST_QUERY_DURATION:
|
|
{
|
|
GstFormat format;
|
|
gint64 duration;
|
|
gdouble rate;
|
|
|
|
res =
|
|
GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
|
|
query);
|
|
|
|
if (!res)
|
|
break;
|
|
|
|
GST_OBJECT_LOCK (videorate);
|
|
rate = videorate->rate;
|
|
GST_OBJECT_UNLOCK (videorate);
|
|
|
|
if (rate == 1.0)
|
|
break;
|
|
|
|
gst_query_parse_duration (query, &format, &duration);
|
|
|
|
if (format != GST_FORMAT_TIME) {
|
|
GST_DEBUG_OBJECT (videorate, "not TIME format");
|
|
break;
|
|
}
|
|
GST_LOG_OBJECT (videorate, "upstream duration: %" G_GINT64_FORMAT,
|
|
duration);
|
|
/* Shouldn't this be a multiplication if the direction is downstream? */
|
|
if (GST_CLOCK_TIME_IS_VALID (duration)) {
|
|
duration = (gint64) (duration / rate);
|
|
}
|
|
GST_LOG_OBJECT (videorate, "our duration: %" G_GINT64_FORMAT, duration);
|
|
gst_query_set_duration (query, format, duration);
|
|
break;
|
|
}
|
|
case GST_QUERY_POSITION:
|
|
{
|
|
GstFormat dst_format;
|
|
gint64 dst_value;
|
|
gdouble rate;
|
|
|
|
GST_OBJECT_LOCK (videorate);
|
|
rate = videorate->rate;
|
|
GST_OBJECT_UNLOCK (videorate);
|
|
|
|
gst_query_parse_position (query, &dst_format, NULL);
|
|
|
|
if (dst_format != GST_FORMAT_TIME) {
|
|
GST_DEBUG_OBJECT (videorate, "not TIME format");
|
|
break;
|
|
}
|
|
/* Shouldn't this be a multiplication if the direction is downstream? */
|
|
dst_value =
|
|
(gint64) (gst_segment_to_stream_time (&videorate->segment,
|
|
GST_FORMAT_TIME, videorate->last_ts / rate));
|
|
GST_LOG_OBJECT (videorate, "our position: %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (dst_value));
|
|
gst_query_set_position (query, dst_format, dst_value);
|
|
res = TRUE;
|
|
break;
|
|
}
|
|
default:
|
|
res =
|
|
GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
|
|
query);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
gst_video_rate_propose_allocation (GstBaseTransform * trans,
|
|
GstQuery * decide_query, GstQuery * query)
|
|
{
|
|
GstBaseTransformClass *klass = GST_BASE_TRANSFORM_CLASS (parent_class);
|
|
gboolean res;
|
|
|
|
/* We should always be passthrough */
|
|
g_return_val_if_fail (decide_query == NULL, FALSE);
|
|
|
|
res = klass->propose_allocation (trans, NULL, query);
|
|
|
|
if (res) {
|
|
guint i = 0;
|
|
guint n_allocation;
|
|
guint down_min = 0;
|
|
|
|
n_allocation = gst_query_get_n_allocation_pools (query);
|
|
|
|
while (i < n_allocation) {
|
|
GstBufferPool *pool = NULL;
|
|
guint size, min, max;
|
|
|
|
gst_query_parse_nth_allocation_pool (query, i, &pool, &size, &min, &max);
|
|
|
|
if (min == max) {
|
|
if (pool)
|
|
gst_object_unref (pool);
|
|
gst_query_remove_nth_allocation_pool (query, i);
|
|
n_allocation--;
|
|
down_min = MAX (min, down_min);
|
|
continue;
|
|
}
|
|
|
|
gst_query_set_nth_allocation_pool (query, i, pool, size, min + 1, max);
|
|
if (pool)
|
|
gst_object_unref (pool);
|
|
i++;
|
|
}
|
|
|
|
if (n_allocation == 0) {
|
|
GstCaps *caps;
|
|
GstVideoInfo info;
|
|
|
|
gst_query_parse_allocation (query, &caps, NULL);
|
|
gst_video_info_from_caps (&info, caps);
|
|
|
|
gst_query_add_allocation_pool (query, NULL, info.size, down_min + 1, 0);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_video_rate_trans_ip_max_avg (GstVideoRate * videorate, GstBuffer * buf)
|
|
{
|
|
GstClockTime ts = GST_BUFFER_TIMESTAMP (buf);
|
|
|
|
videorate->in++;
|
|
|
|
if (!GST_CLOCK_TIME_IS_VALID (ts) || videorate->wanted_diff == 0)
|
|
goto push;
|
|
|
|
/* drop frames if they exceed our output rate */
|
|
if (GST_CLOCK_TIME_IS_VALID (videorate->last_ts)) {
|
|
GstClockTimeDiff diff =
|
|
videorate->segment.rate <
|
|
0 ? videorate->last_ts - ts : ts - videorate->last_ts;
|
|
|
|
/* Drop buffer if its early compared to the desired frame rate and
|
|
* the current average is higher than the desired average
|
|
*/
|
|
if (diff < videorate->wanted_diff &&
|
|
videorate->average < videorate->wanted_diff)
|
|
goto drop;
|
|
|
|
/* Update average */
|
|
if (videorate->average) {
|
|
GstClockTimeDiff wanted_diff;
|
|
|
|
if (G_LIKELY (videorate->average_period > videorate->wanted_diff))
|
|
wanted_diff = videorate->wanted_diff;
|
|
else
|
|
wanted_diff = videorate->average_period * 10;
|
|
|
|
videorate->average =
|
|
gst_util_uint64_scale_round (videorate->average,
|
|
videorate->average_period - wanted_diff,
|
|
videorate->average_period) +
|
|
gst_util_uint64_scale_round (diff, wanted_diff,
|
|
videorate->average_period);
|
|
} else {
|
|
videorate->average = diff;
|
|
}
|
|
}
|
|
|
|
videorate->last_ts = ts;
|
|
|
|
push:
|
|
videorate->out++;
|
|
return GST_FLOW_OK;
|
|
|
|
drop:
|
|
if (!videorate->silent)
|
|
gst_video_rate_notify_drop (videorate);
|
|
return GST_BASE_TRANSFORM_FLOW_DROPPED;
|
|
}
|
|
|
|
/* Check if downstream forces variable framerate (0/1) and if
|
|
* it is the case, use variable framerate ourself
|
|
* Otherwise compute the framerate from the 2 buffers that we
|
|
* have already received and make use of it as wanted framerate
|
|
*/
|
|
static void
|
|
gst_video_rate_check_variable_rate (GstVideoRate * videorate,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstStructure *st;
|
|
gint fps_d, fps_n;
|
|
GstCaps *srcpadcaps, *tmpcaps, *downstream_caps;
|
|
GstPad *pad = NULL;
|
|
|
|
srcpadcaps =
|
|
gst_pad_get_current_caps (GST_BASE_TRANSFORM_SRC_PAD (videorate));
|
|
|
|
gst_video_guess_framerate (GST_BUFFER_PTS (buffer) -
|
|
GST_BUFFER_PTS (videorate->prevbuf), &fps_n, &fps_d);
|
|
|
|
tmpcaps = gst_caps_copy (srcpadcaps);
|
|
st = gst_caps_get_structure (tmpcaps, 0);
|
|
gst_structure_set (st, "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL);
|
|
gst_caps_unref (srcpadcaps);
|
|
|
|
pad = gst_pad_get_peer (GST_BASE_TRANSFORM_SRC_PAD (videorate));
|
|
downstream_caps = gst_pad_query_caps (pad, NULL);
|
|
if (pad && !gst_caps_can_intersect (tmpcaps, downstream_caps)) {
|
|
videorate->force_variable_rate = TRUE;
|
|
gst_caps_unref (downstream_caps);
|
|
GST_DEBUG_OBJECT (videorate, "Downstream forces variable framerate"
|
|
" respecting it");
|
|
|
|
goto done;
|
|
}
|
|
gst_caps_unref (downstream_caps);
|
|
|
|
videorate->to_rate_numerator = fps_n;
|
|
videorate->to_rate_denominator = fps_d;
|
|
|
|
GST_INFO_OBJECT (videorate, "Computed framerate to %d/%d",
|
|
videorate->to_rate_numerator, videorate->to_rate_denominator);
|
|
|
|
videorate->updating_caps = TRUE;
|
|
gst_base_transform_update_src_caps (GST_BASE_TRANSFORM (videorate), tmpcaps);
|
|
|
|
done:
|
|
gst_caps_unref (tmpcaps);
|
|
if (pad)
|
|
gst_object_unref (pad);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_video_rate_transform_ip (GstBaseTransform * trans, GstBuffer * buffer)
|
|
{
|
|
GstVideoRate *videorate;
|
|
GstFlowReturn res = GST_BASE_TRANSFORM_FLOW_DROPPED;
|
|
GstClockTime intime, in_ts, in_dur, last_ts;
|
|
GstClockTime avg_period;
|
|
gboolean skip = FALSE;
|
|
|
|
videorate = GST_VIDEO_RATE (trans);
|
|
|
|
/* make sure the denominators are not 0 */
|
|
if (videorate->from_rate_denominator == 0 ||
|
|
videorate->to_rate_denominator == 0)
|
|
goto not_negotiated;
|
|
|
|
if (videorate->to_rate_numerator == 0 && videorate->prevbuf &&
|
|
!videorate->force_variable_rate) {
|
|
gst_video_rate_check_variable_rate (videorate, buffer);
|
|
}
|
|
|
|
GST_OBJECT_LOCK (videorate);
|
|
avg_period = videorate->average_period_set;
|
|
GST_OBJECT_UNLOCK (videorate);
|
|
|
|
/* MT-safe switching between modes */
|
|
if (G_UNLIKELY (avg_period != videorate->average_period)) {
|
|
gboolean switch_mode = (avg_period == 0 || videorate->average_period == 0);
|
|
videorate->average_period = avg_period;
|
|
videorate->last_ts = GST_CLOCK_TIME_NONE;
|
|
|
|
if (switch_mode) {
|
|
if (avg_period) {
|
|
/* enabling average mode */
|
|
videorate->average = 0;
|
|
/* make sure no cached buffers from regular mode are left */
|
|
gst_video_rate_swap_prev (videorate, NULL, 0);
|
|
} else {
|
|
/* enable regular mode */
|
|
videorate->next_ts = GST_CLOCK_TIME_NONE;
|
|
skip = TRUE;
|
|
}
|
|
|
|
/* max averaging mode has a no latency, normal mode does */
|
|
gst_element_post_message (GST_ELEMENT (videorate),
|
|
gst_message_new_latency (GST_OBJECT (videorate)));
|
|
}
|
|
}
|
|
|
|
if (videorate->average_period > 0)
|
|
return gst_video_rate_trans_ip_max_avg (videorate, buffer);
|
|
|
|
in_ts = GST_BUFFER_TIMESTAMP (buffer);
|
|
in_dur = GST_BUFFER_DURATION (buffer);
|
|
|
|
if (G_UNLIKELY (in_ts == GST_CLOCK_TIME_NONE)) {
|
|
/* For reverse playback, we need all input timestamps as we can't
|
|
* guess from the previous buffers timestamp and duration */
|
|
if (G_UNLIKELY (in_ts == GST_CLOCK_TIME_NONE
|
|
&& videorate->segment.rate < 0.0))
|
|
goto invalid_buffer;
|
|
in_ts = videorate->last_ts;
|
|
if (G_UNLIKELY (in_ts == GST_CLOCK_TIME_NONE))
|
|
goto invalid_buffer;
|
|
}
|
|
|
|
/* get the time of the next expected buffer timestamp, we use this when the
|
|
* next buffer has -1 as a timestamp */
|
|
last_ts = videorate->last_ts;
|
|
videorate->last_ts = in_ts;
|
|
if (in_dur != GST_CLOCK_TIME_NONE && videorate->segment.rate > 0.0)
|
|
videorate->last_ts += in_dur;
|
|
|
|
GST_DEBUG_OBJECT (videorate, "got buffer with timestamp %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (in_ts));
|
|
|
|
/* the input time is the time in the segment + all previously accumulated
|
|
* segments */
|
|
intime = in_ts + videorate->segment.base;
|
|
|
|
/* we need to have two buffers to compare */
|
|
if (videorate->prevbuf == NULL || videorate->drop_only) {
|
|
/* We can calculate the duration of the buffer here if not given for
|
|
* reverse playback. We need this later */
|
|
if (videorate->segment.rate < 0.0 && !GST_BUFFER_DURATION_IS_VALID (buffer)) {
|
|
/* As we require valid timestamps all the time for reverse playback, we either
|
|
* have a valid last_ts or we're at the very first buffer. */
|
|
if (!GST_CLOCK_TIME_IS_VALID (last_ts))
|
|
GST_BUFFER_DURATION (buffer) = videorate->segment.stop - in_ts;
|
|
else
|
|
GST_BUFFER_DURATION (buffer) = last_ts - in_ts;
|
|
}
|
|
|
|
gst_video_rate_swap_prev (videorate, buffer, intime);
|
|
videorate->in++;
|
|
if (!GST_CLOCK_TIME_IS_VALID (videorate->next_ts)) {
|
|
/* new buffer, we expect to output a buffer that matches the first
|
|
* timestamp in the segment */
|
|
if (videorate->skip_to_first || skip) {
|
|
videorate->next_ts = intime;
|
|
if (videorate->segment.rate < 0.0)
|
|
videorate->base_ts = videorate->segment.stop - in_ts;
|
|
else
|
|
videorate->base_ts = in_ts - videorate->segment.start;
|
|
videorate->out_frame_count = 0;
|
|
} else {
|
|
if (videorate->segment.rate < 0.0)
|
|
videorate->next_ts =
|
|
videorate->segment.stop + videorate->segment.base;
|
|
else
|
|
videorate->next_ts =
|
|
videorate->segment.start + videorate->segment.base;
|
|
}
|
|
}
|
|
|
|
/* In drop-only mode we can already decide here if we should output the
|
|
* current frame or drop it because it's coming earlier than our minimum
|
|
* allowed frame period. This also keeps latency down to 0 frames
|
|
*/
|
|
if (videorate->drop_only) {
|
|
if ((videorate->segment.rate > 0.0 && intime >= videorate->next_ts) ||
|
|
(videorate->segment.rate < 0.0 && intime <= videorate->next_ts)) {
|
|
GstFlowReturn r;
|
|
|
|
/* on error the _flush function posted a warning already */
|
|
if ((r = gst_video_rate_flush_prev (videorate, FALSE,
|
|
GST_CLOCK_TIME_NONE)) != GST_FLOW_OK) {
|
|
res = r;
|
|
goto done;
|
|
}
|
|
}
|
|
/* No need to keep the buffer around for longer */
|
|
gst_buffer_replace (&videorate->prevbuf, NULL);
|
|
}
|
|
} else {
|
|
GstClockTime prevtime;
|
|
gint count = 0;
|
|
gint64 diff1, diff2;
|
|
|
|
prevtime = videorate->prev_ts;
|
|
|
|
GST_LOG_OBJECT (videorate,
|
|
"BEGINNING prev buf %" GST_TIME_FORMAT " new buf %" GST_TIME_FORMAT
|
|
" outgoing ts %" GST_TIME_FORMAT, GST_TIME_ARGS (prevtime),
|
|
GST_TIME_ARGS (intime), GST_TIME_ARGS (videorate->next_ts));
|
|
|
|
videorate->in++;
|
|
|
|
/* drop new buffer if it's before previous one */
|
|
if ((videorate->segment.rate > 0.0 && intime < prevtime) ||
|
|
(videorate->segment.rate < 0.0 && intime > prevtime)) {
|
|
GST_DEBUG_OBJECT (videorate,
|
|
"The new buffer (%" GST_TIME_FORMAT
|
|
") is before the previous buffer (%"
|
|
GST_TIME_FORMAT "). Dropping new buffer.",
|
|
GST_TIME_ARGS (intime), GST_TIME_ARGS (prevtime));
|
|
videorate->drop++;
|
|
if (!videorate->silent)
|
|
gst_video_rate_notify_drop (videorate);
|
|
goto done;
|
|
}
|
|
|
|
/* got 2 buffers, see which one is the best */
|
|
do {
|
|
GstClockTime next_ts = videorate->next_ts * videorate->rate;
|
|
|
|
/* take absolute values, beware: abs and ABS don't work for gint64 */
|
|
if (prevtime > next_ts)
|
|
diff1 = prevtime - next_ts;
|
|
else
|
|
diff1 = next_ts - prevtime;
|
|
|
|
if (intime > next_ts)
|
|
diff2 = intime - next_ts;
|
|
else
|
|
diff2 = next_ts - intime;
|
|
|
|
GST_LOG_OBJECT (videorate,
|
|
"diff with prev %" GST_TIME_FORMAT " diff with new %"
|
|
GST_TIME_FORMAT " outgoing ts %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (diff1), GST_TIME_ARGS (diff2),
|
|
GST_TIME_ARGS (next_ts));
|
|
|
|
if (videorate->segment.rate < 0.0) {
|
|
/* Make sure that we have a duration for this buffer. The previous
|
|
* buffer already has a duration given by either exactly this code,
|
|
* or the code above for the very first buffer */
|
|
g_assert (GST_BUFFER_DURATION_IS_VALID (videorate->prevbuf));
|
|
if (!GST_BUFFER_DURATION_IS_VALID (buffer) && prevtime > intime)
|
|
GST_BUFFER_DURATION (buffer) = prevtime - intime;
|
|
} else {
|
|
/* Make sure that we have a duration for previous buffer */
|
|
if (!GST_BUFFER_DURATION_IS_VALID (videorate->prevbuf)
|
|
&& intime > prevtime)
|
|
GST_BUFFER_DURATION (videorate->prevbuf) = intime - prevtime;
|
|
}
|
|
|
|
/* output first one when its the best */
|
|
if (diff1 <= diff2) {
|
|
GstFlowReturn r;
|
|
count++;
|
|
|
|
/* on error the _flush function posted a warning already */
|
|
if ((r = gst_video_rate_flush_prev (videorate,
|
|
count > 1, intime)) != GST_FLOW_OK) {
|
|
res = r;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* continue while the first one was the best, if they were equal avoid
|
|
* going into an infinite loop */
|
|
}
|
|
while (diff1 < diff2);
|
|
|
|
/* if we outputed the first buffer more then once, we have dups */
|
|
if (count > 1) {
|
|
videorate->dup += count - 1;
|
|
if (!videorate->silent)
|
|
gst_video_rate_notify_duplicate (videorate);
|
|
}
|
|
/* if we didn't output the first buffer, we have a drop */
|
|
else if (count == 0) {
|
|
videorate->drop++;
|
|
|
|
if (!videorate->silent)
|
|
gst_video_rate_notify_drop (videorate);
|
|
|
|
GST_LOG_OBJECT (videorate,
|
|
"new is best, old never used, drop, outgoing ts %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (videorate->next_ts));
|
|
}
|
|
GST_LOG_OBJECT (videorate,
|
|
"END, putting new in old, diff1 %" GST_TIME_FORMAT
|
|
", diff2 %" GST_TIME_FORMAT ", next_ts %" GST_TIME_FORMAT
|
|
", in %" G_GUINT64_FORMAT ", out %" G_GUINT64_FORMAT ", drop %"
|
|
G_GUINT64_FORMAT ", dup %" G_GUINT64_FORMAT, GST_TIME_ARGS (diff1),
|
|
GST_TIME_ARGS (diff2), GST_TIME_ARGS (videorate->next_ts),
|
|
videorate->in, videorate->out, videorate->drop, videorate->dup);
|
|
|
|
/* swap in new one when it's the best */
|
|
gst_video_rate_swap_prev (videorate, buffer, intime);
|
|
}
|
|
done:
|
|
return res;
|
|
|
|
/* ERRORS */
|
|
not_negotiated:
|
|
{
|
|
GST_WARNING_OBJECT (videorate, "no framerate negotiated");
|
|
res = GST_FLOW_NOT_NEGOTIATED;
|
|
goto done;
|
|
}
|
|
|
|
invalid_buffer:
|
|
{
|
|
GST_WARNING_OBJECT (videorate,
|
|
"Got buffer with GST_CLOCK_TIME_NONE timestamp, discarding it");
|
|
res = GST_BASE_TRANSFORM_FLOW_DROPPED;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_video_rate_start (GstBaseTransform * trans)
|
|
{
|
|
gst_video_rate_reset (GST_VIDEO_RATE (trans));
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_video_rate_stop (GstBaseTransform * trans)
|
|
{
|
|
gst_video_rate_reset (GST_VIDEO_RATE (trans));
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_videorate_update_duration (GstVideoRate * videorate)
|
|
{
|
|
GstMessage *m;
|
|
|
|
m = gst_message_new_duration_changed (GST_OBJECT (videorate));
|
|
gst_element_post_message (GST_ELEMENT (videorate), m);
|
|
}
|
|
|
|
static void
|
|
gst_video_rate_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstVideoRate *videorate = GST_VIDEO_RATE (object);
|
|
gboolean latency_changed = FALSE;
|
|
|
|
GST_OBJECT_LOCK (videorate);
|
|
switch (prop_id) {
|
|
case PROP_SILENT:
|
|
videorate->silent = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_NEW_PREF:
|
|
videorate->new_pref = g_value_get_double (value);
|
|
break;
|
|
case PROP_SKIP_TO_FIRST:
|
|
videorate->skip_to_first = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_DROP_ONLY:{
|
|
gboolean new_value = g_value_get_boolean (value);
|
|
|
|
/* Latency changes if we switch drop-only mode */
|
|
latency_changed = new_value != videorate->drop_only;
|
|
videorate->drop_only = g_value_get_boolean (value);
|
|
goto reconfigure;
|
|
}
|
|
case PROP_AVERAGE_PERIOD:
|
|
videorate->average_period_set = g_value_get_uint64 (value);
|
|
break;
|
|
case PROP_MAX_RATE:
|
|
g_atomic_int_set (&videorate->max_rate, g_value_get_int (value));
|
|
goto reconfigure;
|
|
case PROP_RATE:
|
|
videorate->rate = g_value_get_double (value);
|
|
GST_OBJECT_UNLOCK (videorate);
|
|
|
|
gst_videorate_update_duration (videorate);
|
|
return;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
GST_OBJECT_UNLOCK (videorate);
|
|
|
|
return;
|
|
|
|
reconfigure:
|
|
GST_OBJECT_UNLOCK (videorate);
|
|
gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (videorate));
|
|
|
|
if (latency_changed) {
|
|
gst_element_post_message (GST_ELEMENT (videorate),
|
|
gst_message_new_latency (GST_OBJECT (videorate)));
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_video_rate_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstVideoRate *videorate = GST_VIDEO_RATE (object);
|
|
|
|
GST_OBJECT_LOCK (videorate);
|
|
switch (prop_id) {
|
|
case PROP_IN:
|
|
g_value_set_uint64 (value, videorate->in);
|
|
break;
|
|
case PROP_OUT:
|
|
g_value_set_uint64 (value, videorate->out);
|
|
break;
|
|
case PROP_DUP:
|
|
g_value_set_uint64 (value, videorate->dup);
|
|
break;
|
|
case PROP_DROP:
|
|
g_value_set_uint64 (value, videorate->drop);
|
|
break;
|
|
case PROP_SILENT:
|
|
g_value_set_boolean (value, videorate->silent);
|
|
break;
|
|
case PROP_NEW_PREF:
|
|
g_value_set_double (value, videorate->new_pref);
|
|
break;
|
|
case PROP_SKIP_TO_FIRST:
|
|
g_value_set_boolean (value, videorate->skip_to_first);
|
|
break;
|
|
case PROP_DROP_ONLY:
|
|
g_value_set_boolean (value, videorate->drop_only);
|
|
break;
|
|
case PROP_AVERAGE_PERIOD:
|
|
g_value_set_uint64 (value, videorate->average_period_set);
|
|
break;
|
|
case PROP_MAX_RATE:
|
|
g_value_set_int (value, g_atomic_int_get (&videorate->max_rate));
|
|
break;
|
|
case PROP_RATE:
|
|
g_value_set_double (value, videorate->rate);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
GST_OBJECT_UNLOCK (videorate);
|
|
}
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (video_rate_debug, "videorate", 0,
|
|
"VideoRate stream fixer");
|
|
|
|
return gst_element_register (plugin, "videorate", GST_RANK_NONE,
|
|
GST_TYPE_VIDEO_RATE);
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
videorate,
|
|
"Adjusts video frames",
|
|
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|