gstreamer/gst/imagefreeze/gstimagefreeze.c
Sebastian Dröge c17d5e36ad imagefreeze: Add support for replacing the output buffer
By default imagefreeze will still reject new buffers after the first one
and immediately return GST_FLOW_EOS but the new allow-replace property
allows to change this.

Whenever updating the buffer we now also keep track of the configured
caps of the buffer and from the source pad task negotiate correctly
based on the potentially updated caps.

Only the very first time negotiation of a framerate with downstream is
performed, afterwards only the caps themselves apart from the framerate
are updated.
2020-01-11 08:04:43 +00:00

1035 lines
29 KiB
C

/* GStreamer
* Copyright (c) 2005 Edward Hervey <bilboed@bilboed.com>
* Copyright (C) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* 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-imagefreeze
* @title: imagefreeze
*
* The imagefreeze element generates a still frame video stream from
* the input. It duplicates the first frame with the framerate requested
* by downstream, allows seeking and answers queries.
*
* ## Example launch line
* |[
* gst-launch-1.0 -v filesrc location=some.png ! decodebin ! imagefreeze ! autovideosink
* ]| This pipeline shows a still frame stream of a PNG file.
*
*/
/* This is based on the imagefreeze element from PiTiVi:
* http://git.gnome.org/browse/pitivi/tree/pitivi/elements/imagefreeze.py
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/glib-compat-private.h>
#include "gstimagefreeze.h"
#define DEFAULT_NUM_BUFFERS -1
#define DEFAULT_ALLOW_REPLACE FALSE
enum
{
PROP_0,
PROP_NUM_BUFFERS,
PROP_ALLOW_REPLACE,
};
static void gst_image_freeze_finalize (GObject * object);
static void gst_image_freeze_reset (GstImageFreeze * self);
static GstStateChangeReturn gst_image_freeze_change_state (GstElement * element,
GstStateChange transition);
static void gst_image_freeze_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_image_freeze_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstFlowReturn gst_image_freeze_sink_chain (GstPad * pad,
GstObject * parent, GstBuffer * buffer);
static gboolean gst_image_freeze_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static gboolean gst_image_freeze_sink_setcaps (GstImageFreeze * self,
GstCaps * caps);
static GstCaps *gst_image_freeze_sink_getcaps (GstImageFreeze * self,
GstCaps * filter);
static gboolean gst_image_freeze_sink_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static void gst_image_freeze_src_loop (GstPad * pad);
static gboolean gst_image_freeze_src_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static gboolean gst_image_freeze_src_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-raw(ANY)"));
static GstStaticPadTemplate src_pad_template =
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-raw(ANY)"));
GST_DEBUG_CATEGORY_STATIC (gst_image_freeze_debug);
#define GST_CAT_DEFAULT gst_image_freeze_debug
#define gst_image_freeze_parent_class parent_class
G_DEFINE_TYPE (GstImageFreeze, gst_image_freeze, GST_TYPE_ELEMENT);
static void
gst_image_freeze_class_init (GstImageFreezeClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
gobject_class->finalize = gst_image_freeze_finalize;
gobject_class->set_property = gst_image_freeze_set_property;
gobject_class->get_property = gst_image_freeze_get_property;
g_object_class_install_property (gobject_class, PROP_NUM_BUFFERS,
g_param_spec_int ("num-buffers", "Number of buffers",
"Number of buffers to output before sending EOS (-1 = unlimited)",
-1, G_MAXINT, DEFAULT_NUM_BUFFERS, G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_ALLOW_REPLACE,
g_param_spec_boolean ("allow-replace", "Allow Replace",
"Allow replacing the input buffer and always output the latest",
DEFAULT_ALLOW_REPLACE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_image_freeze_change_state);
gst_element_class_set_static_metadata (gstelement_class,
"Still frame stream generator",
"Filter/Video",
"Generates a still frame stream from an image",
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
gst_element_class_add_static_pad_template (gstelement_class,
&sink_pad_template);
gst_element_class_add_static_pad_template (gstelement_class,
&src_pad_template);
}
static void
gst_image_freeze_init (GstImageFreeze * self)
{
self->sinkpad = gst_pad_new_from_static_template (&sink_pad_template, "sink");
gst_pad_set_chain_function (self->sinkpad,
GST_DEBUG_FUNCPTR (gst_image_freeze_sink_chain));
gst_pad_set_event_function (self->sinkpad,
GST_DEBUG_FUNCPTR (gst_image_freeze_sink_event));
gst_pad_set_query_function (self->sinkpad,
GST_DEBUG_FUNCPTR (gst_image_freeze_sink_query));
GST_PAD_SET_PROXY_ALLOCATION (self->sinkpad);
gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
self->srcpad = gst_pad_new_from_static_template (&src_pad_template, "src");
gst_pad_set_event_function (self->srcpad,
GST_DEBUG_FUNCPTR (gst_image_freeze_src_event));
gst_pad_set_query_function (self->srcpad,
GST_DEBUG_FUNCPTR (gst_image_freeze_src_query));
gst_pad_use_fixed_caps (self->srcpad);
gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
g_mutex_init (&self->lock);
self->num_buffers = DEFAULT_NUM_BUFFERS;
self->allow_replace = DEFAULT_ALLOW_REPLACE;
gst_image_freeze_reset (self);
}
static void
gst_image_freeze_finalize (GObject * object)
{
GstImageFreeze *self = GST_IMAGE_FREEZE (object);
self->num_buffers = DEFAULT_NUM_BUFFERS;
gst_image_freeze_reset (self);
g_mutex_clear (&self->lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_image_freeze_reset (GstImageFreeze * self)
{
GST_DEBUG_OBJECT (self, "Resetting internal state");
g_mutex_lock (&self->lock);
gst_buffer_replace (&self->buffer, NULL);
gst_caps_replace (&self->buffer_caps, NULL);
gst_caps_replace (&self->current_caps, NULL);
self->buffer_caps_updated = FALSE;
self->num_buffers_left = self->num_buffers;
gst_segment_init (&self->segment, GST_FORMAT_TIME);
self->need_segment = TRUE;
self->negotiated_framerate = FALSE;
self->fps_n = self->fps_d = 0;
self->offset = 0;
self->seqnum = 0;
g_mutex_unlock (&self->lock);
g_atomic_int_set (&self->seeking, 0);
}
static gboolean
gst_image_freeze_sink_setcaps (GstImageFreeze * self, GstCaps * caps)
{
gboolean ret = FALSE;
GstStructure *s;
gint fps_n, fps_d;
GstCaps *othercaps, *intersection;
guint i, n;
GstPad *pad;
pad = self->sinkpad;
caps = gst_caps_copy (caps);
/* If we already negotiated a framerate then only update for the
* caps of the new buffer */
if (self->negotiated_framerate) {
gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, self->fps_n,
self->fps_d, NULL);
GST_DEBUG_OBJECT (pad, "Setting caps %" GST_PTR_FORMAT, caps);
ret = gst_pad_set_caps (self->srcpad, caps);
gst_caps_unref (caps);
return ret;
}
/* Else negotiate a framerate with downstream */
GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps);
s = gst_caps_get_structure (caps, 0);
/* 1. Remove framerate */
gst_structure_remove_field (s, "framerate");
gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1,
NULL);
/* 2. Intersect with template caps */
othercaps = (GstCaps *) gst_pad_get_pad_template_caps (pad);
intersection = gst_caps_intersect (caps, othercaps);
GST_DEBUG_OBJECT (pad, "Intersecting: %" GST_PTR_FORMAT, caps);
GST_DEBUG_OBJECT (pad, "with: %" GST_PTR_FORMAT, othercaps);
GST_DEBUG_OBJECT (pad, "gave: %" GST_PTR_FORMAT, intersection);
gst_caps_unref (caps);
gst_caps_unref (othercaps);
caps = intersection;
intersection = othercaps = NULL;
/* 3. Intersect with downstream peer caps */
othercaps = gst_pad_peer_query_caps (self->srcpad, caps);
GST_DEBUG_OBJECT (pad, "Peer query resulted: %" GST_PTR_FORMAT, othercaps);
gst_caps_unref (caps);
caps = othercaps;
othercaps = NULL;
/* 4. For every candidate try to use it downstream with framerate as
* near as possible to 25/1 */
n = gst_caps_get_size (caps);
for (i = 0; i < n; i++) {
GstCaps *candidate = gst_caps_new_empty ();
GstStructure *s = gst_structure_copy (gst_caps_get_structure (caps, i));
gst_caps_append_structure (candidate, s);
if (gst_structure_has_field_typed (s, "framerate", GST_TYPE_FRACTION) ||
gst_structure_fixate_field_nearest_fraction (s, "framerate", 25, 1)) {
gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d);
if (fps_d != 0) {
if (gst_pad_set_caps (self->srcpad, candidate)) {
g_mutex_lock (&self->lock);
self->fps_n = fps_n;
self->fps_d = fps_d;
g_mutex_unlock (&self->lock);
self->negotiated_framerate = TRUE;
GST_DEBUG_OBJECT (pad, "Setting caps %" GST_PTR_FORMAT, candidate);
ret = TRUE;
gst_caps_unref (candidate);
break;
}
} else {
GST_WARNING_OBJECT (pad, "Invalid caps with framerate %d/%d", fps_n,
fps_d);
}
}
gst_caps_unref (candidate);
}
if (!ret)
GST_ERROR_OBJECT (pad, "No usable caps found");
gst_caps_unref (caps);
return ret;
}
/* remove framerate in writable @caps */
static void
gst_image_freeze_remove_fps (GstImageFreeze * self, GstCaps * caps)
{
gint i, n;
n = gst_caps_get_size (caps);
for (i = 0; i < n; i++) {
GstStructure *s = gst_caps_get_structure (caps, i);
gst_structure_remove_field (s, "framerate");
gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT,
1, NULL);
}
}
static GstCaps *
gst_image_freeze_sink_getcaps (GstImageFreeze * self, GstCaps * filter)
{
GstCaps *ret, *tmp, *templ;
GstPad *pad;
pad = self->sinkpad;
if (filter) {
filter = gst_caps_copy (filter);
gst_image_freeze_remove_fps (self, filter);
}
templ = gst_pad_get_pad_template_caps (pad);
tmp = gst_pad_peer_query_caps (self->srcpad, filter);
if (tmp) {
GST_LOG_OBJECT (self, "peer caps %" GST_PTR_FORMAT, tmp);
ret = gst_caps_intersect (tmp, templ);
gst_caps_unref (tmp);
} else {
GST_LOG_OBJECT (self, "going to copy");
ret = gst_caps_copy (templ);
}
if (templ)
gst_caps_unref (templ);
if (filter)
gst_caps_unref (filter);
ret = gst_caps_make_writable (ret);
gst_image_freeze_remove_fps (self, ret);
GST_LOG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
return ret;
}
static gboolean
gst_image_freeze_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
gboolean ret;
GST_LOG_OBJECT (pad, "Handling query of type '%s'",
gst_query_type_get_name (GST_QUERY_TYPE (query)));
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CAPS:
{
GstCaps *caps;
gst_query_parse_caps (query, &caps);
caps = gst_image_freeze_sink_getcaps (self, caps);
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
ret = TRUE;
break;
}
default:
ret = gst_pad_query_default (pad, parent, query);
}
return ret;
}
static gboolean
gst_image_freeze_convert (GstImageFreeze * self,
GstFormat src_format, gint64 src_value,
GstFormat * dest_format, gint64 * dest_value)
{
gboolean ret = FALSE;
if (src_format == *dest_format) {
*dest_value = src_value;
return TRUE;
}
if (src_value == -1) {
*dest_value = -1;
return TRUE;
}
switch (src_format) {
case GST_FORMAT_DEFAULT:{
switch (*dest_format) {
case GST_FORMAT_TIME:
g_mutex_lock (&self->lock);
if (self->fps_n == 0)
*dest_value = -1;
else
*dest_value =
gst_util_uint64_scale (src_value, GST_SECOND * self->fps_d,
self->fps_n);
g_mutex_unlock (&self->lock);
ret = TRUE;
break;
default:
break;
}
break;
}
case GST_FORMAT_TIME:{
switch (*dest_format) {
case GST_FORMAT_DEFAULT:
g_mutex_lock (&self->lock);
*dest_value =
gst_util_uint64_scale (src_value, self->fps_n,
self->fps_d * GST_SECOND);
g_mutex_unlock (&self->lock);
ret = TRUE;
break;
default:
break;
}
break;
}
default:
break;
}
return ret;
}
static gboolean
gst_image_freeze_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
gboolean ret = FALSE;
GST_LOG_OBJECT (pad, "Handling query of type '%s'",
gst_query_type_get_name (GST_QUERY_TYPE (query)));
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONVERT:{
GstFormat src_format, dest_format;
gint64 src_value, dest_value;
gst_query_parse_convert (query, &src_format, &src_value, &dest_format,
&dest_value);
ret =
gst_image_freeze_convert (self, src_format, src_value, &dest_format,
&dest_value);
if (ret)
gst_query_set_convert (query, src_format, src_value, dest_format,
dest_value);
break;
}
case GST_QUERY_POSITION:{
GstFormat format;
gint64 position;
gst_query_parse_position (query, &format, NULL);
switch (format) {
case GST_FORMAT_DEFAULT:{
g_mutex_lock (&self->lock);
position = self->offset;
g_mutex_unlock (&self->lock);
ret = TRUE;
break;
}
case GST_FORMAT_TIME:{
g_mutex_lock (&self->lock);
position = self->segment.position;
g_mutex_unlock (&self->lock);
ret = TRUE;
break;
}
default:
break;
}
if (ret) {
gst_query_set_position (query, format, position);
GST_DEBUG_OBJECT (pad,
"Returning position %" G_GINT64_FORMAT " in format %s", position,
gst_format_get_name (format));
} else {
GST_DEBUG_OBJECT (pad, "Position query failed");
}
break;
}
case GST_QUERY_DURATION:{
GstFormat format;
gint64 duration;
gst_query_parse_duration (query, &format, NULL);
switch (format) {
case GST_FORMAT_TIME:{
g_mutex_lock (&self->lock);
duration = self->segment.stop;
g_mutex_unlock (&self->lock);
ret = TRUE;
break;
}
case GST_FORMAT_DEFAULT:{
g_mutex_lock (&self->lock);
duration = self->segment.stop;
if (duration != -1)
duration =
gst_util_uint64_scale (duration, self->fps_n,
GST_SECOND * self->fps_d);
g_mutex_unlock (&self->lock);
ret = TRUE;
break;
}
default:
break;
}
if (ret) {
gst_query_set_duration (query, format, duration);
GST_DEBUG_OBJECT (pad,
"Returning duration %" G_GINT64_FORMAT " in format %s", duration,
gst_format_get_name (format));
} else {
GST_DEBUG_OBJECT (pad, "Duration query failed");
}
break;
}
case GST_QUERY_SEEKING:{
GstFormat format;
gboolean seekable;
gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
seekable = (format == GST_FORMAT_TIME || format == GST_FORMAT_DEFAULT);
gst_query_set_seeking (query, format, seekable, (seekable ? 0 : -1), -1);
ret = TRUE;
break;
}
case GST_QUERY_LATENCY:
/* This will only return an accurate latency for the first buffer since
* all further buffers outputted by us are just copies of that one, and
* the latency is 0 in that case. However, latency changes are not
* straightforward, so let's do the conservative fix for now. */
ret = gst_pad_query_default (pad, parent, query);
break;
default:
ret = FALSE;
break;
}
return ret;
}
static gboolean
gst_image_freeze_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
gboolean ret;
GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
g_mutex_lock (&self->lock);
gst_event_parse_caps (event, &caps);
gst_caps_replace (&self->current_caps, caps);
g_mutex_unlock (&self->lock);
gst_event_unref (event);
ret = TRUE;
break;
}
case GST_EVENT_EOS:
if (!self->buffer) {
/* if we receive EOS before a buffer arrives, then let it pass */
GST_DEBUG_OBJECT (self, "EOS without input buffer, passing on");
ret = gst_pad_push_event (self->srcpad, event);
break;
}
/* fall-through */
case GST_EVENT_SEGMENT:
GST_DEBUG_OBJECT (pad, "Dropping event");
gst_event_unref (event);
ret = TRUE;
break;
case GST_EVENT_FLUSH_START:
gst_image_freeze_reset (self);
/* fall through */
default:
ret = gst_pad_push_event (self->srcpad, event);
break;
}
return ret;
}
static gboolean
gst_image_freeze_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
gboolean ret;
GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_NAVIGATION:
case GST_EVENT_QOS:
case GST_EVENT_LATENCY:
case GST_EVENT_STEP:
GST_DEBUG_OBJECT (pad, "Dropping event");
gst_event_unref (event);
ret = TRUE;
break;
case GST_EVENT_SEEK:{
gdouble rate;
GstFormat format;
GstSeekFlags flags;
GstSeekType start_type, stop_type;
gint64 start, stop;
gint64 last_stop;
gboolean start_task;
gboolean flush;
guint32 seqnum;
seqnum = gst_event_get_seqnum (event);
gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
&stop_type, &stop);
gst_event_unref (event);
flush = ! !(flags & GST_SEEK_FLAG_FLUSH);
if (format != GST_FORMAT_TIME && format != GST_FORMAT_DEFAULT) {
GST_ERROR_OBJECT (pad, "Seek in invalid format: %s",
gst_format_get_name (format));
ret = FALSE;
break;
}
if (format == GST_FORMAT_DEFAULT) {
format = GST_FORMAT_TIME;
if (!gst_image_freeze_convert (self, GST_FORMAT_DEFAULT, start, &format,
&start)
|| !gst_image_freeze_convert (self, GST_FORMAT_DEFAULT, stop,
&format, &stop)
|| start == -1 || stop == -1) {
GST_ERROR_OBJECT (pad,
"Failed to convert seek from DEFAULT format into TIME format");
ret = FALSE;
break;
}
}
if (flush) {
GstEvent *e;
g_atomic_int_set (&self->seeking, 1);
e = gst_event_new_flush_start ();
gst_event_set_seqnum (e, seqnum);
gst_pad_push_event (self->srcpad, e);
} else {
gst_pad_pause_task (self->srcpad);
}
GST_PAD_STREAM_LOCK (self->srcpad);
g_mutex_lock (&self->lock);
gst_segment_do_seek (&self->segment, rate, format, flags, start_type,
start, stop_type, stop, NULL);
self->need_segment = TRUE;
last_stop = self->segment.position;
start_task = self->buffer != NULL;
g_mutex_unlock (&self->lock);
if (flush) {
GstEvent *e;
e = gst_event_new_flush_stop (TRUE);
gst_event_set_seqnum (e, seqnum);
gst_pad_push_event (self->srcpad, e);
g_atomic_int_set (&self->seeking, 0);
}
if (flags & GST_SEEK_FLAG_SEGMENT) {
GstMessage *m;
m = gst_message_new_segment_start (GST_OBJECT (self),
format, last_stop);
gst_element_post_message (GST_ELEMENT (self), m);
}
self->seqnum = seqnum;
GST_PAD_STREAM_UNLOCK (self->srcpad);
GST_DEBUG_OBJECT (pad, "Seek successful");
if (start_task) {
g_mutex_lock (&self->lock);
if (self->buffer != NULL)
gst_pad_start_task (self->srcpad,
(GstTaskFunction) gst_image_freeze_src_loop, self->srcpad, NULL);
g_mutex_unlock (&self->lock);
}
ret = TRUE;
break;
}
case GST_EVENT_FLUSH_START:
gst_image_freeze_reset (self);
/* fall through */
default:
ret = gst_pad_push_event (self->sinkpad, event);
break;
}
return ret;
}
static void
gst_image_freeze_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstImageFreeze *self;
self = GST_IMAGE_FREEZE (object);
switch (prop_id) {
case PROP_NUM_BUFFERS:
self->num_buffers = g_value_get_int (value);
break;
case PROP_ALLOW_REPLACE:
self->allow_replace = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_image_freeze_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstImageFreeze *self;
self = GST_IMAGE_FREEZE (object);
switch (prop_id) {
case PROP_NUM_BUFFERS:
g_value_set_int (value, self->num_buffers);
break;
case PROP_ALLOW_REPLACE:
g_value_set_boolean (value, self->allow_replace);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstFlowReturn
gst_image_freeze_sink_chain (GstPad * pad, GstObject * parent,
GstBuffer * buffer)
{
GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
GstFlowReturn flow_ret;
g_mutex_lock (&self->lock);
if (self->buffer && !self->allow_replace) {
GST_DEBUG_OBJECT (pad, "Already have a buffer, dropping");
gst_buffer_unref (buffer);
g_mutex_unlock (&self->lock);
return GST_FLOW_EOS;
}
if (!self->current_caps) {
GST_ERROR_OBJECT (pad, "Not negotiated yet");
g_mutex_unlock (&self->lock);
return GST_FLOW_NOT_NEGOTIATED;
}
gst_buffer_replace (&self->buffer, buffer);
self->buffer_caps_updated = !self->buffer_caps
|| !gst_caps_is_equal (self->buffer_caps, self->current_caps);
gst_caps_replace (&self->buffer_caps, self->current_caps);
gst_buffer_unref (buffer);
gst_pad_start_task (self->srcpad, (GstTaskFunction) gst_image_freeze_src_loop,
self->srcpad, NULL);
flow_ret = self->allow_replace ? GST_FLOW_OK : GST_FLOW_EOS;
g_mutex_unlock (&self->lock);
return flow_ret;
}
static void
gst_image_freeze_src_loop (GstPad * pad)
{
GstImageFreeze *self = GST_IMAGE_FREEZE (GST_PAD_PARENT (pad));
GstBuffer *buffer;
guint64 offset;
GstClockTime timestamp, timestamp_end;
guint64 cstart, cstop;
gboolean in_seg, eos;
GstFlowReturn flow_ret = GST_FLOW_OK;
gboolean first = FALSE;
g_mutex_lock (&self->lock);
if (!self->buffer) {
GST_ERROR_OBJECT (pad, "Have no buffer yet");
flow_ret = GST_FLOW_ERROR;
g_mutex_unlock (&self->lock);
goto pause_task;
}
/* normally we don't count buffers */
if (G_UNLIKELY (self->num_buffers_left >= 0)) {
GST_DEBUG_OBJECT (pad, "Buffers left %d", self->num_buffers_left);
if (self->num_buffers_left == 0) {
flow_ret = GST_FLOW_EOS;
g_mutex_unlock (&self->lock);
goto pause_task;
} else {
self->num_buffers_left--;
}
}
buffer = gst_buffer_copy (self->buffer);
if (self->buffer_caps_updated) {
GstCaps *buffer_caps = gst_caps_ref (self->buffer_caps);
self->buffer_caps_updated = FALSE;
g_mutex_unlock (&self->lock);
gst_image_freeze_sink_setcaps (self, buffer_caps);
gst_caps_unref (buffer_caps);
} else {
g_mutex_unlock (&self->lock);
}
if (self->need_segment) {
GstEvent *e;
GST_DEBUG_OBJECT (pad, "Pushing SEGMENT event: %" GST_SEGMENT_FORMAT,
&self->segment);
e = gst_event_new_segment (&self->segment);
if (self->seqnum)
gst_event_set_seqnum (e, self->seqnum);
g_mutex_lock (&self->lock);
if (self->segment.rate >= 0) {
self->offset =
gst_util_uint64_scale (self->segment.start, self->fps_n,
self->fps_d * GST_SECOND);
} else {
self->offset =
gst_util_uint64_scale (self->segment.stop, self->fps_n,
self->fps_d * GST_SECOND);
}
g_mutex_unlock (&self->lock);
self->need_segment = FALSE;
first = TRUE;
gst_pad_push_event (self->srcpad, e);
}
g_mutex_lock (&self->lock);
offset = self->offset;
if (self->fps_n != 0) {
timestamp =
gst_util_uint64_scale (offset, self->fps_d * GST_SECOND, self->fps_n);
timestamp_end =
gst_util_uint64_scale (offset + 1, self->fps_d * GST_SECOND,
self->fps_n);
} else {
timestamp = self->segment.start;
timestamp_end = GST_CLOCK_TIME_NONE;
}
eos = (self->fps_n == 0 && offset > 0) ||
(self->segment.rate >= 0 && self->segment.stop != -1
&& timestamp > self->segment.stop) || (self->segment.rate < 0
&& offset == 0) || (self->segment.rate < 0
&& self->segment.start != -1 && timestamp_end < self->segment.start);
if (self->fps_n == 0 && offset > 0)
in_seg = FALSE;
else
in_seg =
gst_segment_clip (&self->segment, GST_FORMAT_TIME, timestamp,
timestamp_end, &cstart, &cstop);
if (in_seg) {
self->segment.position = cstart;
if (self->segment.rate >= 0)
self->segment.position = cstop;
}
if (self->segment.rate >= 0)
self->offset++;
else
self->offset--;
g_mutex_unlock (&self->lock);
GST_DEBUG_OBJECT (pad, "Handling buffer with timestamp %" GST_TIME_FORMAT,
GST_TIME_ARGS (timestamp));
if (in_seg) {
GST_BUFFER_DTS (buffer) = GST_CLOCK_TIME_NONE;
GST_BUFFER_PTS (buffer) = cstart;
GST_BUFFER_DURATION (buffer) = cstop - cstart;
GST_BUFFER_OFFSET (buffer) = offset;
GST_BUFFER_OFFSET_END (buffer) = offset + 1;
if (first)
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
else
GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT);
flow_ret = gst_pad_push (self->srcpad, buffer);
GST_DEBUG_OBJECT (pad, "Pushing buffer resulted in %s",
gst_flow_get_name (flow_ret));
if (flow_ret != GST_FLOW_OK)
goto pause_task;
} else {
gst_buffer_unref (buffer);
}
if (eos) {
flow_ret = GST_FLOW_EOS;
goto pause_task;
}
return;
pause_task:
{
const gchar *reason = gst_flow_get_name (flow_ret);
GST_LOG_OBJECT (self, "pausing task, reason %s", reason);
gst_pad_pause_task (pad);
if (flow_ret == GST_FLOW_EOS) {
if ((self->segment.flags & GST_SEEK_FLAG_SEGMENT)) {
GstMessage *m;
GstEvent *e;
GST_DEBUG_OBJECT (pad, "Sending segment done at end of segment");
if (self->segment.rate >= 0) {
m = gst_message_new_segment_done (GST_OBJECT_CAST (self),
GST_FORMAT_TIME, self->segment.stop);
e = gst_event_new_segment_done (GST_FORMAT_TIME, self->segment.stop);
} else {
m = gst_message_new_segment_done (GST_OBJECT_CAST (self),
GST_FORMAT_TIME, self->segment.start);
e = gst_event_new_segment_done (GST_FORMAT_TIME, self->segment.start);
}
gst_element_post_message (GST_ELEMENT_CAST (self), m);
gst_pad_push_event (self->srcpad, e);
} else {
GstEvent *e = gst_event_new_eos ();
GST_DEBUG_OBJECT (pad, "Sending EOS at end of segment");
if (self->seqnum)
gst_event_set_seqnum (e, self->seqnum);
gst_pad_push_event (self->srcpad, e);
}
} else if (flow_ret == GST_FLOW_NOT_LINKED || flow_ret < GST_FLOW_EOS) {
GstEvent *e = gst_event_new_eos ();
GST_ELEMENT_FLOW_ERROR (self, flow_ret);
if (self->seqnum)
gst_event_set_seqnum (e, self->seqnum);
gst_pad_push_event (self->srcpad, e);
}
return;
}
}
static GstStateChangeReturn
gst_image_freeze_change_state (GstElement * element, GstStateChange transition)
{
GstImageFreeze *self = GST_IMAGE_FREEZE (element);
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
gst_image_freeze_reset (self);
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_pad_stop_task (self->srcpad);
gst_image_freeze_reset (self);
break;
default:
break;
}
if (GST_ELEMENT_CLASS (parent_class)->change_state)
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
default:
break;
}
return ret;
}
static gboolean
plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (gst_image_freeze_debug, "imagefreeze", 0,
"imagefreeze element");
if (!gst_element_register (plugin, "imagefreeze", GST_RANK_NONE,
GST_TYPE_IMAGE_FREEZE))
return FALSE;
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
imagefreeze,
"Still frame stream generator",
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)