mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-29 03:30:35 +00:00
55175561f6
This reverts commit d46631c5c7
.
pad only handle EOS events but not EOS flow, and will push the buffer again
resulting in an assertion error. So we should not handle the buffer
and return EOS flow.
910 lines
25 KiB
C
910 lines
25 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
|
|
*
|
|
* 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.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch-1.0 -v filesrc location=some.png ! decodebin ! imagefreeze ! autovideosink
|
|
* ]| This pipeline shows a still frame stream of a PNG file.
|
|
* </refsect2>
|
|
*/
|
|
|
|
/* 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"
|
|
|
|
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 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"));
|
|
|
|
static GstStaticPadTemplate src_pad_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-raw"));
|
|
|
|
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;
|
|
|
|
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_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&sink_pad_template));
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&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);
|
|
|
|
gst_image_freeze_reset (self);
|
|
}
|
|
|
|
static void
|
|
gst_image_freeze_finalize (GObject * object)
|
|
{
|
|
GstImageFreeze *self = GST_IMAGE_FREEZE (object);
|
|
|
|
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_segment_init (&self->segment, GST_FORMAT_TIME);
|
|
self->need_segment = TRUE;
|
|
|
|
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);
|
|
|
|
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 check if it's accepted downstream
|
|
* and fixate framerate to nearest 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)) {
|
|
if (gst_pad_peer_query_accept_caps (self->srcpad, candidate)) {
|
|
gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d);
|
|
if (fps_d != 0) {
|
|
g_mutex_lock (&self->lock);
|
|
self->fps_n = fps_n;
|
|
self->fps_d = fps_d;
|
|
g_mutex_unlock (&self->lock);
|
|
GST_DEBUG_OBJECT (pad, "Setting caps %" GST_PTR_FORMAT, candidate);
|
|
gst_pad_set_caps (self->srcpad, candidate);
|
|
gst_caps_unref (candidate);
|
|
ret = TRUE;
|
|
goto done;
|
|
} else {
|
|
GST_WARNING_OBJECT (pad, "Invalid caps with framerate %d/%d", fps_n,
|
|
fps_d);
|
|
}
|
|
}
|
|
}
|
|
gst_caps_unref (candidate);
|
|
}
|
|
|
|
done:
|
|
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 (gst_pad_has_current_caps (pad)) {
|
|
ret = gst_pad_get_current_caps (pad);
|
|
goto done;
|
|
}
|
|
|
|
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);
|
|
|
|
done:
|
|
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;
|
|
}
|
|
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;
|
|
|
|
gst_event_parse_caps (event, &caps);
|
|
gst_image_freeze_sink_setcaps (self, caps);
|
|
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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
seqnum = gst_event_get_seqnum (event);
|
|
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 GstFlowReturn
|
|
gst_image_freeze_sink_chain (GstPad * pad, GstObject * parent,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
|
|
|
|
g_mutex_lock (&self->lock);
|
|
if (self->buffer) {
|
|
GST_DEBUG_OBJECT (pad, "Already have a buffer, dropping");
|
|
gst_buffer_unref (buffer);
|
|
g_mutex_unlock (&self->lock);
|
|
return GST_FLOW_EOS;
|
|
}
|
|
|
|
self->buffer = buffer;
|
|
|
|
gst_pad_start_task (self->srcpad, (GstTaskFunction) gst_image_freeze_src_loop,
|
|
self->srcpad, NULL);
|
|
g_mutex_unlock (&self->lock);
|
|
return GST_FLOW_EOS;
|
|
}
|
|
|
|
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;
|
|
|
|
g_mutex_lock (&self->lock);
|
|
if (!gst_pad_has_current_caps (self->srcpad)) {
|
|
GST_ERROR_OBJECT (pad, "Not negotiated yet");
|
|
flow_ret = GST_FLOW_NOT_NEGOTIATED;
|
|
g_mutex_unlock (&self->lock);
|
|
goto pause_task;
|
|
}
|
|
|
|
if (!self->buffer) {
|
|
GST_ERROR_OBJECT (pad, "Have no buffer yet");
|
|
flow_ret = GST_FLOW_ERROR;
|
|
g_mutex_unlock (&self->lock);
|
|
goto pause_task;
|
|
}
|
|
buffer = gst_buffer_copy (self->buffer);
|
|
|
|
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;
|
|
|
|
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;
|
|
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_ERROR (self, STREAM, FAILED,
|
|
("Internal data stream error."),
|
|
("stream stopped, reason %s", reason));
|
|
|
|
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)
|