gstreamer/gst/encoding/gstsmartencoder.c
Thibault Saunier 1cead20777 encodebin: Fix and refactor smart encoding
It was not working properly and the implementation of the smartencoder
element was weird. This introduce a number of changes (which are all
in one single commit because they basically all work together and lead
to basically reimplementing the element):

* Make smartencoder a bin so that the reencoding chain of elements are
  inside of it instead of not having any parent. Those elements were not
  be visible when dumping the pipeline which was very confusing.
* Make encodebin create the right encoder with a capsfilter (and parser)
  to properly enforce the format specified by the user, and so that the
  encoder properties specified in the encoding profile are respected.
* Use `decodebin` to do the decoding instead of selecting a decoder
  ourself and not plug any parser etc...
* Ensure that negotiated format in the sinkpad of smart encoder is fixed
  through time when the user requested a non dynamic output
* Add a parser at the beginning of the smart encoder
* Handle errors when reencoding

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/751>
2020-07-22 16:01:25 +00:00

836 lines
26 KiB
C

/* GStreamer Smart Video Encoder element
* Copyright (C) <2010> Edward Hervey <bilboed@gmail.com>
* Copyright (C) <2020> Thibault Saunier <tsaunier@igalia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include "gstsmartencoder.h"
GST_DEBUG_CATEGORY_STATIC (smart_encoder_debug);
#define GST_CAT_DEFAULT smart_encoder_debug
/* FIXME : Update this with new caps */
/* WARNING : We can only allow formats with closed-GOP */
#define ALLOWED_CAPS "video/x-h263;video/x-intel-h263;"\
"video/x-h264;"\
"video/mpeg,mpegversion=(int)1,systemstream=(boolean)false;"\
"video/mpeg,mpegversion=(int)2,systemstream=(boolean)false;"
static GstStaticPadTemplate src_template =
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (ALLOWED_CAPS)
);
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (ALLOWED_CAPS)
);
G_DEFINE_TYPE (GstSmartEncoder, gst_smart_encoder, GST_TYPE_BIN);
static void
smart_encoder_reset (GstSmartEncoder * self)
{
gst_segment_init (&self->internal_segment, GST_FORMAT_UNDEFINED);
gst_segment_init (&self->input_segment, GST_FORMAT_UNDEFINED);
gst_segment_init (&self->output_segment, GST_FORMAT_UNDEFINED);
if (self->decoder) {
/* Clean up/remove internal encoding elements */
gst_element_set_state (self->encoder, GST_STATE_NULL);
gst_element_set_state (self->decoder, GST_STATE_NULL);
gst_clear_object (&self->internal_srcpad);
gst_element_remove_pad (GST_ELEMENT (self), self->internal_sinkpad);
gst_bin_remove (GST_BIN (self), gst_object_ref (self->encoder));
gst_bin_remove (GST_BIN (self), self->decoder);
self->decoder = NULL;
self->internal_sinkpad = NULL;
}
gst_clear_event (&self->segment_event);
}
static void
translate_timestamp_from_internal_to_src (GstSmartEncoder * self,
GstClockTime * ts)
{
GstClockTime running_time;
if (gst_segment_to_running_time_full (&self->internal_segment,
GST_FORMAT_TIME, *ts, &running_time) > 0)
*ts = running_time + self->output_segment.start;
else /* Negative timestamp */
*ts = self->output_segment.start - running_time;
}
static GstFlowReturn
gst_smart_encoder_finish_buffer (GstSmartEncoder * self, GstBuffer * buf)
{
translate_timestamp_from_internal_to_src (self, &GST_BUFFER_PTS (buf));
translate_timestamp_from_internal_to_src (self, &GST_BUFFER_DTS (buf));
GST_BUFFER_DTS (buf) = GST_BUFFER_DTS (buf);
if (self->last_dts > GST_BUFFER_DTS (buf)) {
/* Hack to always produces dts increasing DTS-s that are close to what the
* encoder produced. */
GST_BUFFER_DTS (buf) = self->last_dts + 1;
}
self->last_dts = GST_BUFFER_DTS (buf);
return gst_pad_push (self->srcpad, buf);
}
/*****************************************
* Internal encoder/decoder pipeline *
******************************************/
static gboolean
internal_event_func (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstSmartEncoder *self = GST_SMART_ENCODER (parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
g_mutex_lock (&self->internal_flow_lock);
if (self->internal_flow == GST_FLOW_CUSTOM_SUCCESS)
self->internal_flow = GST_FLOW_OK;
g_cond_signal (&self->internal_flow_cond);
g_mutex_unlock (&self->internal_flow_lock);
break;
case GST_EVENT_SEGMENT:
gst_event_copy_segment (event, &self->internal_segment);
break;
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
caps = gst_caps_copy (caps);
if (self->last_caps) {
GstBuffer *codec_data;
GstCaps *new_caps;
GstStructure *last_struct = gst_caps_get_structure (self->last_caps, 0);
gst_structure_get (last_struct, "codec_data", GST_TYPE_BUFFER,
&codec_data, NULL);
if (codec_data)
gst_structure_set (gst_caps_get_structure (caps, 0), "codec_data",
GST_TYPE_BUFFER, codec_data, NULL);
new_caps = gst_caps_intersect (self->last_caps, caps);
if (!new_caps || gst_caps_is_empty (new_caps)) {
GST_ERROR_OBJECT (parent, "New caps from reencoder %" GST_PTR_FORMAT
" are not compatible with previous caps: %" GST_PTR_FORMAT, caps,
self->last_caps);
g_mutex_lock (&self->internal_flow_lock);
self->internal_flow = GST_FLOW_NOT_NEGOTIATED;
g_cond_signal (&self->internal_flow_cond);
g_mutex_unlock (&self->internal_flow_lock);
return FALSE;
}
gst_caps_unref (caps);
caps = new_caps;
}
event = gst_event_new_caps (caps);
self->last_caps = caps;
return gst_pad_push_event (self->srcpad, event);
}
default:
break;
}
return gst_pad_event_default (pad, parent, event);
}
static GstFlowReturn
internal_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
{
return gst_smart_encoder_finish_buffer (GST_SMART_ENCODER (parent), buf);
}
static void
decodebin_src_pad_added_cb (GstElement * decodebin, GstPad * srcpad,
GstSmartEncoder * self)
{
GstPadLinkReturn ret = gst_pad_link (srcpad, self->encoder->sinkpads->data);
if (ret != GST_PAD_LINK_OK) {
GST_ERROR_OBJECT (self, "Could not link decoder with encoder! %s",
gst_pad_link_get_name (ret));
g_mutex_lock (&self->internal_flow_lock);
self->internal_flow = GST_FLOW_NOT_LINKED;
g_mutex_unlock (&self->internal_flow_lock);
}
}
static gboolean
setup_recoder_pipeline (GstSmartEncoder * self)
{
GstPad *tmppad;
GstElement *capsfilter;
GstPadLinkReturn lret;
/* Fast path */
if (G_UNLIKELY (self->decoder))
return TRUE;
g_assert (self->encoder);
GST_DEBUG ("Creating internal decoder and encoder");
/* Create decoder/encoder */
self->decoder = gst_element_factory_make ("decodebin", NULL);
if (G_UNLIKELY (self->decoder == NULL))
goto no_decoder;
g_signal_connect (self->decoder, "pad-added",
G_CALLBACK (decodebin_src_pad_added_cb), self);
gst_element_set_locked_state (self->decoder, TRUE);
gst_bin_add (GST_BIN (self), self->decoder);
gst_bin_add (GST_BIN (self), gst_object_ref (self->encoder));
GST_DEBUG_OBJECT (self, "Creating internal pads");
/* Create internal pads */
/* Source pad which we'll use to feed data to decoders */
self->internal_srcpad = gst_pad_new ("internal_src", GST_PAD_SRC);
self->internal_sinkpad = gst_pad_new ("internal_sink", GST_PAD_SINK);
gst_pad_set_iterate_internal_links_function (self->internal_sinkpad, NULL);
if (!gst_element_add_pad (GST_ELEMENT (self), self->internal_sinkpad)) {
GST_ERROR_OBJECT (self, "Could not add internal sinkpad %" GST_PTR_FORMAT,
self->internal_sinkpad);
return FALSE;
}
gst_pad_set_chain_function (self->internal_sinkpad,
GST_DEBUG_FUNCPTR (internal_chain));
gst_pad_set_event_function (self->internal_sinkpad,
GST_DEBUG_FUNCPTR (internal_event_func));
gst_pad_set_active (self->internal_sinkpad, TRUE);
gst_pad_set_active (self->internal_srcpad, TRUE);
GST_DEBUG_OBJECT (self, "Linking pads to elements");
/* Link everything */
capsfilter = gst_element_factory_make ("capsfilter", NULL);
if (!gst_bin_add (GST_BIN (self), capsfilter)) {
GST_ERROR_OBJECT (self, "Could not add capsfilter!");
return FALSE;
}
gst_element_sync_state_with_parent (capsfilter);
if (!gst_element_link (self->encoder, capsfilter))
goto encoder_capsfilter_link_fail;
tmppad = gst_element_get_static_pad (capsfilter, "src");
if ((lret =
gst_pad_link_full (tmppad, self->internal_sinkpad,
GST_PAD_LINK_CHECK_NOTHING)) < GST_PAD_LINK_OK)
goto sinkpad_link_fail;
gst_object_unref (tmppad);
tmppad = gst_element_get_static_pad (self->decoder, "sink");
if (GST_PAD_LINK_FAILED (gst_pad_link_full (self->internal_srcpad,
tmppad, GST_PAD_LINK_CHECK_NOTHING)))
goto srcpad_link_fail;
gst_object_unref (tmppad);
GST_DEBUG ("Done creating internal elements/pads");
return TRUE;
no_decoder:
{
GST_WARNING ("Couldn't find a decodebin?!");
return FALSE;
}
srcpad_link_fail:
{
gst_object_unref (tmppad);
GST_WARNING ("Couldn't link internal srcpad to decoder");
return FALSE;
}
sinkpad_link_fail:
{
gst_object_unref (tmppad);
GST_WARNING ("Couldn't link encoder to internal sinkpad: %s",
gst_pad_link_get_name (lret));
return FALSE;
}
encoder_capsfilter_link_fail:
{
GST_WARNING ("Couldn't link encoder to capsfilter");
return FALSE;
}
}
static GstFlowReturn
gst_smart_encoder_reencode_gop (GstSmartEncoder * self)
{
GstFlowReturn res = GST_FLOW_OK;
GstCaps *caps = NULL;
GST_DEBUG_OBJECT (self, "Reencoding GOP!");
if (self->decoder == NULL) {
if (!setup_recoder_pipeline (self)) {
GST_ERROR_OBJECT (self, "Could not setup reencoder pipeline");
return GST_FLOW_ERROR;
}
}
/* Activate elements */
/* Set elements to PAUSED */
gst_element_set_state (self->encoder, GST_STATE_PLAYING);
gst_element_set_state (self->decoder, GST_STATE_PLAYING);
GST_INFO ("Pushing Flush start/stop to clean decoder/encoder");
gst_pad_push_event (self->internal_srcpad, gst_event_new_flush_start ());
gst_pad_push_event (self->internal_srcpad, gst_event_new_flush_stop (TRUE));
/* push segment_event */
GST_INFO ("Pushing segment_event %" GST_PTR_FORMAT, self->segment_event);
gst_pad_push_event (self->internal_srcpad,
gst_event_ref (self->stream_start_event));
caps = gst_pad_get_current_caps (self->sinkpad);
gst_pad_push_event (self->internal_srcpad, gst_event_new_caps (caps));
gst_caps_unref (caps);
gst_pad_push_event (self->internal_srcpad,
gst_event_ref (self->segment_event));
/* Push buffers through our pads */
GST_DEBUG ("Pushing %d pending buffers", g_list_length (self->pending_gop));
g_mutex_lock (&self->internal_flow_lock);
self->internal_flow = GST_FLOW_CUSTOM_SUCCESS;
g_mutex_unlock (&self->internal_flow_lock);
while (self->pending_gop) {
GstBuffer *buf = (GstBuffer *) self->pending_gop->data;
self->pending_gop =
g_list_remove_link (self->pending_gop, self->pending_gop);
res = gst_pad_push (self->internal_srcpad, buf);
if (res == GST_FLOW_EOS) {
GST_INFO_OBJECT (self, "Got eos... waiting for the event"
" waiting for encoding to be done");
break;
}
if (res != GST_FLOW_OK) {
GST_WARNING ("Error pushing pending buffers : %s",
gst_flow_get_name (res));
goto done;
}
}
GST_DEBUG_OBJECT (self, "-> Drain encoder.");
gst_pad_push_event (self->internal_srcpad, gst_event_new_eos ());
g_mutex_lock (&self->internal_flow_lock);
while (self->internal_flow == GST_FLOW_CUSTOM_SUCCESS) {
g_cond_wait (&self->internal_flow_cond, &self->internal_flow_lock);
}
g_mutex_unlock (&self->internal_flow_lock);
res = self->internal_flow;
GST_DEBUG_OBJECT (self, "Done reencoding GOP.");
gst_element_set_state (self->encoder, GST_STATE_NULL);
gst_element_set_state (self->decoder, GST_STATE_NULL);
GST_OBJECT_FLAG_UNSET (self->internal_sinkpad, GST_PAD_FLAG_EOS);
GST_OBJECT_FLAG_UNSET (self->internal_srcpad, GST_PAD_FLAG_EOS);
done:
g_list_free_full (self->pending_gop, (GDestroyNotify) gst_buffer_unref);
self->pending_gop = NULL;
return res;
}
static GstFlowReturn
gst_smart_encoder_push_pending_gop (GstSmartEncoder * self)
{
guint64 cstart, cstop;
GList *tmp;
GstFlowReturn res = GST_FLOW_OK;
GST_DEBUG ("Pushing pending GOP (%" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT
")", GST_TIME_ARGS (self->gop_start), GST_TIME_ARGS (self->gop_stop));
if (self->output_segment.format == GST_FORMAT_UNDEFINED) {
gst_segment_init (&self->output_segment, GST_FORMAT_TIME);
/* Ensure that we can represent negative DTS in our 'single' segment */
self->output_segment.start = 60 * 60 * GST_SECOND * 1000;
if (!gst_pad_push_event (self->srcpad,
gst_event_new_segment (&self->output_segment))) {
GST_ERROR_OBJECT (self, "Could not push segment!");
GST_ELEMENT_FLOW_ERROR (self, GST_FLOW_ERROR);
return GST_FLOW_ERROR;
}
}
if (!self->pending_gop) {
/* This might happen on EOS */
GST_INFO_OBJECT (self, "Empty gop!");
goto done;
}
if (!gst_segment_clip (&self->input_segment, GST_FORMAT_TIME, self->gop_start,
self->gop_stop, &cstart, &cstop)) {
/* The whole GOP is outside the segment, there's most likely
* a bug somewhere. */
GST_DEBUG_OBJECT (self,
"GOP is entirely outside of the segment, upstream gave us too much data: (%"
GST_TIME_FORMAT " -- %" GST_TIME_FORMAT ")",
GST_TIME_ARGS (self->gop_start), GST_TIME_ARGS (self->gop_stop));
for (tmp = self->pending_gop; tmp; tmp = tmp->next)
gst_buffer_unref ((GstBuffer *) tmp->data);
goto done;
}
if ((cstart != self->gop_start)
|| (cstop != self->gop_stop)) {
GST_INFO_OBJECT (self,
"GOP needs to be re-encoded from %" GST_TIME_FORMAT " to %"
GST_TIME_FORMAT " - %" GST_SEGMENT_FORMAT, GST_TIME_ARGS (cstart),
GST_TIME_ARGS (cstop), &self->input_segment);
res = gst_smart_encoder_reencode_gop (self);
} else {
/* The whole GOP is within the segment, push all pending buffers downstream */
GST_INFO_OBJECT (self,
"GOP doesn't need to be modified, pushing downstream: %" GST_TIME_FORMAT
" to %" GST_TIME_FORMAT, GST_TIME_ARGS (cstart), GST_TIME_ARGS (cstop));
self->internal_segment = self->input_segment;
for (tmp = self->pending_gop; tmp; tmp = tmp->next) {
GstBuffer *buf = (GstBuffer *) tmp->data;
res = gst_smart_encoder_finish_buffer (self, buf);
if (G_UNLIKELY (res != GST_FLOW_OK))
break;
}
}
done:
g_list_free (self->pending_gop);
self->pending_gop = NULL;
self->gop_start = GST_CLOCK_TIME_NONE;
self->gop_stop = 0;
return res;
}
static GstFlowReturn
gst_smart_encoder_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
{
GstSmartEncoder *self;
GstFlowReturn res = GST_FLOW_OK;
gboolean discont, keyframe;
GstClockTime end_time;
self = GST_SMART_ENCODER (parent->parent);
discont = GST_BUFFER_IS_DISCONT (buf);
keyframe = !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
end_time = GST_BUFFER_PTS (buf);
if (GST_CLOCK_TIME_IS_VALID (end_time))
end_time += (GST_BUFFER_DURATION_IS_VALID (buf) ? buf->duration : 0);
GST_DEBUG_OBJECT (pad,
"New buffer %s %s %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
discont ? "discont" : "", keyframe ? "keyframe" : "",
GST_TIME_ARGS (GST_BUFFER_PTS (buf)), GST_TIME_ARGS (end_time));
if (keyframe) {
/* If there's a pending GOP, flush it out */
if (self->pending_gop) {
/* Mark stop of previous gop */
if (GST_BUFFER_PTS_IS_VALID (buf)) {
if (self->gop_stop > buf->pts)
GST_WARNING_OBJECT (self, "Next gop start < current gop" " end");
self->gop_stop = buf->pts;
}
/* flush pending */
res = gst_smart_encoder_push_pending_gop (self);
if (G_UNLIKELY (res != GST_FLOW_OK))
goto beach;
}
/* Mark gop_start for new gop */
self->gop_start = GST_BUFFER_TIMESTAMP (buf);
}
/* Store buffer */
self->pending_gop = g_list_append (self->pending_gop, buf);
/* Update GOP stop position */
if (GST_CLOCK_TIME_IS_VALID (end_time))
self->gop_stop = MAX (self->gop_stop, end_time);
GST_DEBUG_OBJECT (self, "Buffer stored , Current GOP : %"
GST_TIME_FORMAT " -- %" GST_TIME_FORMAT,
GST_TIME_ARGS (self->gop_start), GST_TIME_ARGS (self->gop_stop));
beach:
return res;
}
static gboolean
smart_encoder_sink_event (GstPad * pad, GstObject * ghostpad, GstEvent * event)
{
gboolean res = TRUE;
GstSmartEncoder *self = GST_SMART_ENCODER (ghostpad->parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_STOP:
smart_encoder_reset (self);
break;
case GST_EVENT_CAPS:
if (self->last_caps) {
gst_clear_event (&event);
} else {
gst_event_parse_caps (event, &self->last_caps);
self->last_caps = gst_caps_copy (self->last_caps);
}
break;
case GST_EVENT_STREAM_START:
gst_event_replace (&self->stream_start_event, gst_event_ref (event));
break;
case GST_EVENT_SEGMENT:
{
GST_INFO_OBJECT (self, "Pushing pending GOP on new segment");
gst_smart_encoder_push_pending_gop (self);
gst_event_copy_segment (event, &self->input_segment);
GST_DEBUG_OBJECT (self, "input_segment: %" GST_SEGMENT_FORMAT,
&self->input_segment);
if (self->input_segment.format != GST_FORMAT_TIME) {
GST_ERROR_OBJECT (self, "Can't handle streams %s format",
gst_format_get_name (self->input_segment.format));
gst_event_unref (event);
return FALSE;
}
self->segment_event = event;
event = NULL;
GST_INFO_OBJECT (self, "Eating segment");
break;
}
case GST_EVENT_EOS:
if (self->input_segment.format == GST_FORMAT_TIME)
gst_smart_encoder_push_pending_gop (self);
break;
default:
break;
}
if (event)
res = gst_pad_push_event (self->srcpad, event);
return res;
}
static GstCaps *
smart_encoder_sink_getcaps (GstSmartEncoder * self, GstPad * pad,
GstCaps * filter)
{
GstCaps *peer, *tmpl, *res;
tmpl = gst_static_pad_template_get_caps (&src_template);
/* Try getting it from downstream */
peer = gst_pad_peer_query_caps (self->srcpad, tmpl);
if (peer == NULL) {
res = tmpl;
} else {
res = peer;
gst_caps_unref (tmpl);
}
if (filter) {
GstCaps *filtered_res = gst_caps_intersect (res, filter);
gst_caps_unref (res);
if (!filtered_res || gst_caps_is_empty (filtered_res)) {
res = NULL;
} else {
res = filtered_res;
}
}
return res;
}
static gboolean
_pad_sink_acceptcaps (GstPad * pad, GstSmartEncoder * self, GstCaps * caps)
{
gboolean ret;
GstCaps *modified_caps;
GstCaps *accepted_caps;
gint i, n;
GstStructure *s;
GST_DEBUG_OBJECT (pad, "%" GST_PTR_FORMAT, caps);
accepted_caps = gst_pad_get_current_caps (GST_PAD (self->srcpad));
if (accepted_caps == NULL)
accepted_caps = gst_pad_get_pad_template_caps (GST_PAD (self->srcpad));
accepted_caps = gst_caps_make_writable (accepted_caps);
GST_LOG_OBJECT (pad, "src caps %" GST_PTR_FORMAT, accepted_caps);
n = gst_caps_get_size (accepted_caps);
for (i = 0; i < n; i++) {
s = gst_caps_get_structure (accepted_caps, i);
gst_structure_remove_fields (s, "codec_data", NULL);
}
modified_caps = gst_caps_copy (caps);
n = gst_caps_get_size (modified_caps);
for (i = 0; i < n; i++) {
s = gst_caps_get_structure (modified_caps, i);
gst_structure_remove_fields (s, "codec_data", NULL);
}
ret = gst_caps_can_intersect (modified_caps, accepted_caps);
GST_DEBUG_OBJECT (pad, "%saccepted caps %" GST_PTR_FORMAT,
(ret ? "" : "Doesn't "), caps);
return ret;
}
static gboolean
smart_encoder_sink_query (GstPad * pad, GstObject * ghostpad, GstQuery * query)
{
gboolean res;
GstSmartEncoder *self = GST_SMART_ENCODER (ghostpad->parent);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CAPS:
{
GstCaps *filter, *caps;
gst_query_parse_caps (query, &filter);
caps = smart_encoder_sink_getcaps (self, pad, filter);
GST_DEBUG_OBJECT (self, "Got caps: %" GST_PTR_FORMAT, caps);
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
res = TRUE;
break;
}
case GST_QUERY_ACCEPT_CAPS:
{
GstCaps *caps;
gst_query_parse_accept_caps (query, &caps);
res = _pad_sink_acceptcaps (GST_PAD (pad), self, caps);
gst_query_set_accept_caps_result (query, res);
res = TRUE;
break;
}
default:
res = gst_pad_query_default (pad, ghostpad, query);
break;
}
return res;
}
static gboolean
gst_smart_encoder_add_parser (GstSmartEncoder * self, GstCaps * format)
{
GstPad *chainpad, *internal_chainpad, *sinkpad;
GstElement *capsfilter = gst_element_factory_make ("capsfilter", NULL);
gst_bin_add (GST_BIN (self), capsfilter);
g_object_set (capsfilter, "caps", format, NULL);
if (gst_structure_has_name (gst_caps_get_structure (format, 0),
"video/x-h264")) {
GstElement *parser = gst_element_factory_make ("h264parse", NULL);
if (!parser) {
GST_ERROR_OBJECT (self, "`h264parse` is missing, can't encode smartly");
goto failed;
}
/* Add SPS/PPS before each gop to ensure that they can be decoded
* independently */
g_object_set (parser, "config-interval", -1, NULL);
if (!gst_bin_add (GST_BIN (self), parser)) {
GST_ERROR_OBJECT (self, "Could not add parser.");
goto failed;
}
if (!gst_element_link (parser, capsfilter)) {
GST_ERROR_OBJECT (self, "Could not link capfilter and parser.");
goto failed;
}
sinkpad = gst_element_get_static_pad (parser, "sink");
} else {
sinkpad = gst_element_get_static_pad (capsfilter, "sink");
}
g_assert (sinkpad);
/* The chainpad is the pad that is linked to the srcpad of the chain
* of element that is linked to our public sinkpad, this is the pad where
* we chain the buffers either directly to our srcpad or through the
* reencoding sub chain. */
chainpad =
GST_PAD (gst_ghost_pad_new ("chainpad", capsfilter->srcpads->data));
gst_element_add_pad (GST_ELEMENT (self), chainpad);
internal_chainpad =
GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (chainpad)));
gst_pad_set_chain_function (internal_chainpad, gst_smart_encoder_chain);
gst_pad_set_event_function (internal_chainpad, smart_encoder_sink_event);
gst_pad_set_query_function (internal_chainpad, smart_encoder_sink_query);
gst_ghost_pad_set_target (GST_GHOST_PAD (self->sinkpad), sinkpad);
gst_object_unref (sinkpad);
return TRUE;
failed:
return FALSE;
}
gboolean
gst_smart_encoder_set_encoder (GstSmartEncoder * self, GstCaps * format,
GstElement * encoder)
{
self->encoder = g_object_ref_sink (encoder);
gst_element_set_locked_state (self->encoder, TRUE);
return gst_smart_encoder_add_parser (self, format);
}
/******************************************
* GstElement vmethod implementations *
******************************************/
static GstStateChangeReturn
gst_smart_encoder_change_state (GstElement * element, GstStateChange transition)
{
GstSmartEncoder *self;
GstStateChangeReturn ret;
g_return_val_if_fail (GST_IS_SMART_ENCODER (element),
GST_STATE_CHANGE_FAILURE);
self = GST_SMART_ENCODER (element);
ret =
GST_ELEMENT_CLASS (gst_smart_encoder_parent_class)->change_state (element,
transition);
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
smart_encoder_reset (self);
break;
default:
break;
}
return ret;
}
/******************************************
* GObject vmethods *
******************************************/
static void
gst_smart_encoder_finalize (GObject * object)
{
GstSmartEncoder *self = (GstSmartEncoder *) object;
g_mutex_clear (&self->internal_flow_lock);
g_cond_clear (&self->internal_flow_cond);
G_OBJECT_CLASS (gst_smart_encoder_parent_class)->finalize (object);
}
static void
gst_smart_encoder_dispose (GObject * object)
{
GstSmartEncoder *self = (GstSmartEncoder *) object;
gst_clear_object (&self->encoder);
G_OBJECT_CLASS (gst_smart_encoder_parent_class)->dispose (object);
}
static void
gst_smart_encoder_class_init (GstSmartEncoderClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *element_class;
element_class = (GstElementClass *) klass;
gobject_class = G_OBJECT_CLASS (klass);
gst_smart_encoder_parent_class = g_type_class_peek_parent (klass);
gst_element_class_add_static_pad_template (element_class, &src_template);
gst_element_class_add_static_pad_template (element_class, &sink_template);
gst_element_class_set_static_metadata (element_class, "Smart Video Encoder",
"Codec/Recoder/Video",
"Re-encodes portions of Video that lay on segment boundaries",
"Edward Hervey <bilboed@gmail.com>");
gobject_class->dispose = (GObjectFinalizeFunc) gst_smart_encoder_dispose;
gobject_class->finalize = (GObjectFinalizeFunc) gst_smart_encoder_finalize;
element_class->change_state = gst_smart_encoder_change_state;
GST_DEBUG_CATEGORY_INIT (smart_encoder_debug, "smartencoder", 0,
"Smart Encoder");
}
static void
gst_smart_encoder_init (GstSmartEncoder * self)
{
GstPadTemplate *template = gst_static_pad_template_get (&sink_template);
self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", template);
gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
gst_object_unref (template);
self->srcpad = gst_pad_new_from_static_template (&src_template, "src");
gst_pad_use_fixed_caps (self->srcpad);
gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
g_mutex_init (&self->internal_flow_lock);
g_cond_init (&self->internal_flow_cond);
smart_encoder_reset (self);
}