diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index 1d3f1e67eb..9b5ca94ebf 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -5388,6 +5388,83 @@ "tracers": {}, "url": "Unknown package origin" }, + "codectimestamper": { + "description": "codectimestamper", + "elements": { + "h264timestamper": { + "author": "Seungha Yang ", + "description": "Timestamp H.264 streams", + "hierarchy": [ + "GstH264Timestamper", + "GstCodecTimestamper", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Codec/Video", + "long-name": "H.264 timestamper", + "pad-templates": { + "sink": { + "caps": "video/x-h264:\n alignment: au\n", + "direction": "sink", + "presence": "always" + }, + "src": { + "caps": "video/x-h264:\n alignment: au\n", + "direction": "src", + "presence": "always" + } + }, + "rank": "none" + }, + "h265timestamper": { + "author": "Seungha Yang ", + "description": "Timestamp H.265 streams", + "hierarchy": [ + "GstH265Timestamper", + "GstCodecTimestamper", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Codec/Video", + "long-name": "H.265 timestamper", + "pad-templates": { + "sink": { + "caps": "video/x-h265:\n alignment: au\n", + "direction": "sink", + "presence": "always" + }, + "src": { + "caps": "video/x-h265:\n alignment: au\n", + "direction": "src", + "presence": "always" + } + }, + "rank": "none" + } + }, + "filename": "gstcodectimestamper", + "license": "LGPL", + "other-types": { + "GstCodecTimestamper": { + "hierarchy": [ + "GstCodecTimestamper", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "kind": "object" + } + }, + "package": "GStreamer Bad Plug-ins", + "source": "gst-plugins-bad", + "tracers": {}, + "url": "Unknown package origin" + }, "coloreffects": { "description": "Color Look-up Table filters", "elements": { diff --git a/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.c b/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.c new file mode 100644 index 0000000000..e5c1579121 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.c @@ -0,0 +1,708 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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. + */ + +/* TODO: + * Parse POC and correct PTS if it's is unknown + * Reverse playback support + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstcodectimestamper.h" +#include + +GST_DEBUG_CATEGORY_STATIC (gst_codec_timestamper_debug); +#define GST_CAT_DEFAULT gst_codec_timestamper_debug + +typedef struct +{ + GstBuffer *buffer; + GList *events; + + GstClockTime pts; +} GstCodecTimestamperFrame; + +struct _GstCodecTimestamperPrivate +{ + GRecMutex lock; + + GstSegment in_segment; + + GList *current_frame_events; + GstQueueArray *queue; + GArray *timestamp_queue; + + gint fps_n; + gint fps_d; + + guint max_bframes; + guint max_dpb_frames; + guint max_reorder_frames; + gboolean interlaced; + guint window_size; + GstClockTime last_dts; + GstClockTime dts_offset; + GstClockTime time_adjustment; + + GstClockTime latency; +}; + +static void gst_codec_timestamper_class_init (GstCodecTimestamperClass * klass); +static void gst_codec_timestamper_init (GstCodecTimestamper * self, + GstCodecTimestamperClass * klass); +static void gst_codec_timestamper_finalize (GObject * object); + +static GstFlowReturn gst_codec_timestamper_chain (GstPad * pad, + GstObject * parent, GstBuffer * buffer); +static gboolean gst_codec_timestamper_sink_event (GstPad * pad, + GstObject * parent, GstEvent * event); +static gboolean gst_codec_timestamper_src_query (GstPad * pad, + GstObject * parent, GstQuery * query); +static GstStateChangeReturn +gst_codec_timestamper_change_state (GstElement * element, + GstStateChange transition); +static void +gst_codec_timestamper_clear_frame (GstCodecTimestamperFrame * frame); +static void gst_codec_timestamper_reset (GstCodecTimestamper * self); +static void gst_codec_timestamper_drain (GstCodecTimestamper * self); + +static GTypeClass *parent_class = NULL; +static gint private_offset = 0; + +/* we can't use G_DEFINE_ABSTRACT_TYPE because we need the klass in the _init + * method to get to the padtemplates */ +GType +gst_codec_timestamper_get_type (void) +{ + static gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + static const GTypeInfo info = { + sizeof (GstCodecTimestamperClass), + NULL, + NULL, + (GClassInitFunc) gst_codec_timestamper_class_init, + NULL, + NULL, + sizeof (GstCodecTimestamper), + 0, + (GInstanceInitFunc) gst_codec_timestamper_init, + }; + + _type = g_type_register_static (GST_TYPE_ELEMENT, + "GstCodecTimestamper", &info, G_TYPE_FLAG_ABSTRACT); + + private_offset = g_type_add_instance_private (_type, + sizeof (GstCodecTimestamperPrivate)); + + g_once_init_leave (&type, _type); + } + return type; +} + +static inline GstCodecTimestamperPrivate * +gst_codec_timestamper_get_instance_private (GstCodecTimestamper * self) +{ + return (G_STRUCT_MEMBER_P (self, private_offset)); +} + +static void +gst_codec_timestamper_class_init (GstCodecTimestamperClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + if (private_offset) + g_type_class_adjust_private_offset (klass, &private_offset); + + object_class->finalize = gst_codec_timestamper_finalize; + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_codec_timestamper_change_state); + + GST_DEBUG_CATEGORY_INIT (gst_codec_timestamper_debug, "codectimestamper", 0, + "codectimestamper"); + + /** + * GstCodecTimestamper: + * + * Since: 1.22 + */ + gst_type_mark_as_plugin_api (GST_TYPE_CODEC_TIMESTAMPER, 0); +} + +static void +gst_codec_timestamper_init (GstCodecTimestamper * self, + GstCodecTimestamperClass * klass) +{ + GstCodecTimestamperPrivate *priv; + GstPadTemplate *template; + + self->priv = priv = gst_codec_timestamper_get_instance_private (self); + + template = gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), + "sink"); + self->sinkpad = gst_pad_new_from_template (template, "sink"); + gst_pad_set_chain_function (self->sinkpad, + GST_DEBUG_FUNCPTR (gst_codec_timestamper_chain)); + gst_pad_set_event_function (self->sinkpad, + GST_DEBUG_FUNCPTR (gst_codec_timestamper_sink_event)); + GST_PAD_SET_PROXY_SCHEDULING (self->sinkpad); + gst_element_add_pad (GST_ELEMENT (self), self->sinkpad); + + template = gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), + "src"); + self->srcpad = gst_pad_new_from_template (template, "src"); + gst_pad_set_query_function (self->srcpad, + GST_DEBUG_FUNCPTR (gst_codec_timestamper_src_query)); + GST_PAD_SET_PROXY_SCHEDULING (self->srcpad); + + gst_element_add_pad (GST_ELEMENT (self), self->srcpad); + + priv->queue = + gst_queue_array_new_for_struct (sizeof (GstCodecTimestamperFrame), 16); + gst_queue_array_set_clear_func (priv->queue, + (GDestroyNotify) gst_codec_timestamper_clear_frame); + priv->timestamp_queue = + g_array_sized_new (FALSE, FALSE, sizeof (GstClockTime), 16); + + g_rec_mutex_init (&priv->lock); +} + +static void +gst_codec_timestamper_finalize (GObject * object) +{ + GstCodecTimestamper *self = GST_CODEC_TIMESTAMPER (object); + GstCodecTimestamperPrivate *priv = self->priv; + + gst_queue_array_free (priv->queue); + g_array_unref (priv->timestamp_queue); + g_rec_mutex_clear (&priv->lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_codec_timestamper_set_caps (GstCodecTimestamper * self, GstCaps * caps) +{ + GstCodecTimestamperClass *klass = GST_CODEC_TIMESTAMPER_GET_CLASS (self); + GstCodecTimestamperPrivate *priv = self->priv; + GstStructure *s = gst_caps_get_structure (caps, 0); + + priv->fps_n = 0; + priv->fps_d = 1; + + gst_structure_get_fraction (s, "framerate", &priv->fps_n, &priv->fps_d); + + if (priv->fps_n <= 0 && priv->fps_d <= 0) { + GST_WARNING_OBJECT (self, "Unknown frame rate, assume 25/1"); + priv->fps_n = 25; + priv->fps_d = 1; + } + + if (!klass->set_caps (self, caps)) + return FALSE; + + return TRUE; +} + +static gboolean +gst_codec_timestamper_push_event (GstCodecTimestamper * self, GstEvent * event) +{ + GstCodecTimestamperPrivate *priv = self->priv; + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { + GstSegment segment; + guint32 seqnum; + + gst_event_copy_segment (event, &segment); + + if (segment.format != GST_FORMAT_TIME) { + GST_ELEMENT_ERROR (self, CORE, EVENT, (NULL), + ("Non-time format segment")); + gst_event_unref (event); + return FALSE; + } + + if (priv->time_adjustment != GST_CLOCK_TIME_NONE) { + segment.start += priv->time_adjustment; + if (GST_CLOCK_TIME_IS_VALID (segment.position)) + segment.position += priv->time_adjustment; + if (GST_CLOCK_TIME_IS_VALID (segment.stop)) + segment.stop += priv->time_adjustment; + } + + seqnum = gst_event_get_seqnum (event); + + gst_event_unref (event); + event = gst_event_new_segment (&segment); + gst_event_set_seqnum (event, seqnum); + } + + return gst_pad_push_event (self->srcpad, event); +} + +static void +gst_codec_timestamper_flush_events (GstCodecTimestamper * self, GList ** events) +{ + GList *iter; + + for (iter = *events; iter; iter = g_list_next (iter)) { + GstEvent *ev = GST_EVENT (iter->data); + + if (GST_EVENT_IS_STICKY (ev) && GST_EVENT_TYPE (ev) != GST_EVENT_EOS && + GST_EVENT_TYPE (ev) != GST_EVENT_SEGMENT) { + gst_pad_store_sticky_event (self->srcpad, ev); + } + + gst_event_unref (ev); + } + + g_clear_pointer (events, g_list_free); +} + +static void +gst_codec_timestamper_flush (GstCodecTimestamper * self) +{ + GstCodecTimestamperPrivate *priv = self->priv; + + while (gst_queue_array_get_length (priv->queue) > 0) { + GstCodecTimestamperFrame *frame = (GstCodecTimestamperFrame *) + gst_queue_array_pop_head_struct (priv->queue); + + gst_codec_timestamper_flush_events (self, &frame->events); + gst_codec_timestamper_clear_frame (frame); + } + + gst_codec_timestamper_flush_events (self, &priv->current_frame_events); + + priv->time_adjustment = GST_CLOCK_TIME_NONE; + priv->last_dts = GST_CLOCK_TIME_NONE; + g_rec_mutex_lock (&priv->lock); + priv->latency = GST_CLOCK_TIME_NONE; + g_rec_mutex_unlock (&priv->lock); +} + +static gboolean +gst_codec_timestamper_sink_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + GstCodecTimestamper *self = GST_CODEC_TIMESTAMPER (parent); + GstCodecTimestamperPrivate *priv = self->priv; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CAPS:{ + GstCaps *caps; + + gst_event_parse_caps (event, &caps); + gst_codec_timestamper_set_caps (self, caps); + break; + } + case GST_EVENT_SEGMENT:{ + GstSegment segment; + + gst_event_copy_segment (event, &segment); + if (segment.format != GST_FORMAT_TIME) { + GST_WARNING_OBJECT (self, "Not a time format segment"); + gst_event_unref (event); + return FALSE; + } + + if (segment.rate < 0) { + GST_WARNING_OBJECT (self, "Negative rate is not supported"); + gst_event_unref (event); + return FALSE; + } + + /* Drain on segment update */ + if (memcmp (&self->in_segment, &segment, sizeof (GstSegment))) + gst_codec_timestamper_drain (self); + + self->in_segment = segment; + break; + } + case GST_EVENT_EOS: + gst_codec_timestamper_drain (self); + if (priv->current_frame_events) { + GList *iter; + + for (iter = priv->current_frame_events; iter; iter = g_list_next (iter)) + gst_codec_timestamper_push_event (self, GST_EVENT (iter->data)); + + g_clear_pointer (&priv->current_frame_events, g_list_free); + } + break; + case GST_EVENT_FLUSH_STOP: + gst_codec_timestamper_flush (self); + break; + default: + break; + } + + if (!GST_EVENT_IS_SERIALIZED (event) || + GST_EVENT_TYPE (event) == GST_EVENT_EOS || + GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) { + return gst_pad_event_default (pad, parent, event); + } + + /* Store event to serialize queued frames */ + priv->current_frame_events = g_list_append (priv->current_frame_events, + event); + + return TRUE; +} + +static void +gst_codec_timestamper_frame_init (GstCodecTimestamperFrame * frame) +{ + memset (frame, 0, sizeof (GstCodecTimestamperFrame)); + + frame->pts = GST_CLOCK_TIME_NONE; +} + +static void +gst_codec_timestamper_clear_frame (GstCodecTimestamperFrame * frame) +{ + if (!frame) + return; + + gst_clear_buffer (&frame->buffer); + if (frame->events) { + g_list_free_full (frame->events, (GDestroyNotify) gst_event_unref); + frame->events = NULL; + } +} + +static GstFlowReturn +gst_codec_timestamper_output_frame (GstCodecTimestamper * self, + GstCodecTimestamperFrame * frame) +{ + GstCodecTimestamperPrivate *priv = self->priv; + GList *iter; + GstFlowReturn ret; + GstClockTime dts = GST_CLOCK_TIME_NONE; + + for (iter = frame->events; iter; iter = g_list_next (iter)) { + GstEvent *event = GST_EVENT (iter->data); + + gst_codec_timestamper_push_event (self, event); + } + + g_clear_pointer (&frame->events, g_list_free); + + if (GST_CLOCK_TIME_IS_VALID (frame->pts)) { + g_assert (priv->timestamp_queue->len > 0); + dts = g_array_index (priv->timestamp_queue, GstClockTime, 0); + g_array_remove_index (priv->timestamp_queue, 0); + + if (GST_CLOCK_TIME_IS_VALID (priv->dts_offset)) + dts -= priv->dts_offset; + } + + if (GST_CLOCK_TIME_IS_VALID (dts)) { + if (!GST_CLOCK_TIME_IS_VALID (priv->last_dts)) + priv->last_dts = dts; + + /* make sure DTS <= PTS */ + if (GST_CLOCK_TIME_IS_VALID (frame->pts)) { + if (dts > frame->pts) { + if (frame->pts >= priv->last_dts) + dts = frame->pts; + else + dts = GST_CLOCK_TIME_NONE; + } + + if (GST_CLOCK_TIME_IS_VALID (dts)) + priv->last_dts = dts; + } + } + + frame->buffer = gst_buffer_make_writable (frame->buffer); + GST_BUFFER_PTS (frame->buffer) = frame->pts; + GST_BUFFER_DTS (frame->buffer) = dts; + + GST_TRACE_OBJECT (self, "Output %" GST_PTR_FORMAT, frame->buffer); + + ret = gst_pad_push (self->srcpad, g_steal_pointer (&frame->buffer)); + + return ret; +} + +static GstFlowReturn +gst_codec_timestamper_process_output_frame (GstCodecTimestamper * self) +{ + GstCodecTimestamperPrivate *priv = self->priv; + guint len; + GstCodecTimestamperFrame *frame; + + len = gst_queue_array_get_length (priv->queue); + if (len < priv->window_size) { + GST_TRACE_OBJECT (self, "Need more data, queued %d/%d", len, + priv->window_size); + return GST_FLOW_OK; + } + + frame = (GstCodecTimestamperFrame *) + gst_queue_array_pop_head_struct (priv->queue); + + return gst_codec_timestamper_output_frame (self, frame); +} + +static void +gst_codec_timestamper_drain (GstCodecTimestamper * self) +{ + GstCodecTimestamperPrivate *priv = self->priv; + + while (gst_queue_array_get_length (priv->queue) > 0) { + GstCodecTimestamperFrame *frame = (GstCodecTimestamperFrame *) + gst_queue_array_pop_head_struct (priv->queue); + gst_codec_timestamper_output_frame (self, frame); + } + + priv->time_adjustment = GST_CLOCK_TIME_NONE; + priv->last_dts = GST_CLOCK_TIME_NONE; +} + +static gint +pts_compare_func (const GstClockTime * a, const GstClockTime * b) +{ + return (*a) - (*b); +} + +static GstFlowReturn +gst_codec_timestamper_chain (GstPad * pad, GstObject * parent, + GstBuffer * buffer) +{ + GstCodecTimestamper *self = GST_CODEC_TIMESTAMPER (parent); + GstCodecTimestamperPrivate *priv = self->priv; + GstCodecTimestamperClass *klass = GST_CODEC_TIMESTAMPER_GET_CLASS (self); + GstClockTime pts, dts; + /* The same hack as x264 for negative DTS */ + static const GstClockTime min_pts = GST_SECOND * 60 * 60 * 1000; + GstCodecTimestamperFrame frame; + GstFlowReturn ret; + + gst_codec_timestamper_frame_init (&frame); + + GST_TRACE_OBJECT (self, "Handle %" GST_PTR_FORMAT, buffer); + + pts = GST_BUFFER_PTS (buffer); + dts = GST_BUFFER_DTS (buffer); + + if (!GST_CLOCK_TIME_IS_VALID (priv->time_adjustment)) { + GstClockTime start_time = GST_CLOCK_TIME_NONE; + + if (GST_CLOCK_TIME_IS_VALID (pts)) + start_time = MAX (pts, self->in_segment.start); + else if (GST_CLOCK_TIME_IS_VALID (dts)) + start_time = MAX (dts, self->in_segment.start); + else + start_time = priv->in_segment.start; + + if (start_time < min_pts) + priv->time_adjustment = min_pts - start_time; + } + + if (GST_CLOCK_TIME_IS_VALID (priv->time_adjustment)) { + if (GST_CLOCK_TIME_IS_VALID (pts)) + pts += priv->time_adjustment; + if (GST_CLOCK_TIME_IS_VALID (dts)) + dts += priv->time_adjustment; + } + + ret = klass->handle_buffer (self, buffer); + if (ret != GST_FLOW_OK) { + GST_INFO_OBJECT (self, "Handle buffer returned %s", + gst_flow_get_name (ret)); + + gst_buffer_unref (buffer); + return ret; + } + + frame.pts = pts; + frame.buffer = buffer; + frame.events = priv->current_frame_events; + priv->current_frame_events = NULL; + + gst_queue_array_push_tail_struct (priv->queue, &frame); + if (GST_CLOCK_TIME_IS_VALID (frame.pts)) { + g_array_append_val (priv->timestamp_queue, frame.pts); + g_array_sort (priv->timestamp_queue, (GCompareFunc) pts_compare_func); + } + + return gst_codec_timestamper_process_output_frame (self); +} + +static gboolean +gst_codec_timestamper_src_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + GstCodecTimestamper *self = GST_CODEC_TIMESTAMPER (parent); + GstCodecTimestamperPrivate *priv = self->priv; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY:{ + gboolean ret; + + ret = gst_pad_peer_query (self->sinkpad, query); + if (ret) { + GstClockTime min, max; + gboolean live; + + gst_query_parse_latency (query, &live, &min, &max); + + g_rec_mutex_lock (&priv->lock); + if (GST_CLOCK_TIME_IS_VALID (priv->latency)) + min += priv->latency; + g_rec_mutex_unlock (&priv->lock); + + gst_query_set_latency (query, live, min, max); + } + + return ret; + } + default: + break; + } + + return gst_pad_query_default (pad, parent, query); +} + +static void +gst_codec_timestamper_reset (GstCodecTimestamper * self) +{ + GstCodecTimestamperPrivate *priv = self->priv; + + gst_queue_array_clear (priv->queue); + g_array_set_size (priv->timestamp_queue, 0); + priv->fps_n = 0; + priv->fps_d = 1; + priv->dts_offset = 0; + priv->time_adjustment = GST_CLOCK_TIME_NONE; + priv->latency = GST_CLOCK_TIME_NONE; + priv->window_size = 0; + priv->last_dts = GST_CLOCK_TIME_NONE; + + if (priv->current_frame_events) { + g_list_free_full (priv->current_frame_events, + (GDestroyNotify) gst_event_unref); + priv->current_frame_events = NULL; + } +} + +static gboolean +gst_codec_timestamper_start (GstCodecTimestamper * self) +{ + GstCodecTimestamperClass *klass = GST_CODEC_TIMESTAMPER_GET_CLASS (self); + + gst_codec_timestamper_reset (self); + + if (klass->start) + return klass->start (self); + + return TRUE; +} + +static gboolean +gst_codec_timestamper_stop (GstCodecTimestamper * self) +{ + GstCodecTimestamperClass *klass = GST_CODEC_TIMESTAMPER_GET_CLASS (self); + + gst_codec_timestamper_reset (self); + + if (klass->stop) + return klass->stop (self); + + return TRUE; +} + +static GstStateChangeReturn +gst_codec_timestamper_change_state (GstElement * element, + GstStateChange transition) +{ + GstCodecTimestamper *self = GST_CODEC_TIMESTAMPER (element); + GstStateChangeReturn ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_codec_timestamper_start (self); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_codec_timestamper_stop (self); + break; + default: + break; + } + + return ret; +} + +void +gst_codec_timestamper_set_window_size (GstCodecTimestamper * timestamper, + guint window_size) +{ + GstCodecTimestamperPrivate *priv = timestamper->priv; + gboolean updated = FALSE; + GstClockTime latency = 0; + + g_rec_mutex_lock (&priv->lock); + priv->dts_offset = 0; + priv->window_size = 0; + + if (window_size) { + priv->dts_offset = gst_util_uint64_scale_int (window_size * GST_SECOND, + priv->fps_d, priv->fps_n); + + /* Add margin to be robust against PTS errors and in order for boundary + * frames' PTS can be referenced */ + window_size += 2; + latency = gst_util_uint64_scale_int (window_size * GST_SECOND, + priv->fps_d, priv->fps_n); + + priv->window_size = window_size; + } + + if (priv->latency != latency) { + updated = TRUE; + priv->latency = latency; + } + + GST_DEBUG_OBJECT (timestamper, + "New window size %d, latency %" GST_TIME_FORMAT ", framerate %d/%d", + priv->window_size, GST_TIME_ARGS (latency), priv->fps_n, priv->fps_d); + g_rec_mutex_unlock (&priv->lock); + + if (updated) { + gst_codec_timestamper_drain (timestamper); + gst_element_post_message (GST_ELEMENT_CAST (timestamper), + gst_message_new_latency (GST_OBJECT_CAST (timestamper))); + } +} diff --git a/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.h b/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.h new file mode 100644 index 0000000000..0abb8bd52b --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/codectimestamper/gstcodectimestamper.h @@ -0,0 +1,70 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_CODEC_TIMESTAMPER (gst_codec_timestamper_get_type()) +#define GST_CODEC_TIMESTAMPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CODEC_TIMESTAMPER,GstCodecTimestamper)) +#define GST_CODEC_TIMESTAMPER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CODEC_TIMESTAMPER,GstCodecTimestamperClass)) +#define GST_CODEC_TIMESTAMPER_GET_CLASS(obj) (GST_CODEC_TIMESTAMPER_CLASS(G_OBJECT_GET_CLASS(obj))) +#define GST_CODEC_TIMESTAMPER_CAST(obj) ((GstCodecTimestamper*)(obj)) + +typedef struct _GstCodecTimestamper GstCodecTimestamper; +typedef struct _GstCodecTimestamperClass GstCodecTimestamperClass; +typedef struct _GstCodecTimestamperPrivate GstCodecTimestamperPrivate; + +struct _GstCodecTimestamper +{ + GstElement parent; + + GstPad *sinkpad; + GstPad *srcpad; + + GstSegment in_segment; + + GstCodecTimestamperPrivate *priv; +}; + +struct _GstCodecTimestamperClass +{ + GstElementClass parent_class; + + gboolean (*start) (GstCodecTimestamper * timestamper); + + gboolean (*stop) (GstCodecTimestamper * timestamper); + + gboolean (*set_caps) (GstCodecTimestamper * timestamper, + GstCaps * caps); + + GstFlowReturn (*handle_buffer) (GstCodecTimestamper * timestamper, + GstBuffer * buffer); +}; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstCodecTimestamper, gst_object_unref); + +GType gst_codec_timestamper_get_type (void); + +void gst_codec_timestamper_set_window_size (GstCodecTimestamper * timestamper, + guint window_size); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst/codectimestamper/gsth264timestamper.c b/subprojects/gst-plugins-bad/gst/codectimestamper/gsth264timestamper.c new file mode 100644 index 0000000000..ce798dee07 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/codectimestamper/gsth264timestamper.c @@ -0,0 +1,413 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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-h264timestamper + * @title: h264timestamper + * + * A timestamp correction element for H.264 stream. + * + * ## Example launch line + * ``` + * gst-launch-1.0 filesrc location=video.mkv ! matroskademux ! h264parse ! h264timestamper ! mp4mux ! filesink location=output.mp4 + * ``` + * + * Since: 1.22 + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "gsth264timestamper.h" + +GST_DEBUG_CATEGORY_STATIC (gst_h264_timestamper_debug); +#define GST_CAT_DEFAULT gst_h264_timestamper_debug + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-h264, alignment=(string) au")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-h264, alignment=(string) au")); + +struct _GstH264Timestamper +{ + GstCodecTimestamper parent; + + GstH264NalParser *parser; + gboolean packetized; + guint nal_length_size; +}; + +static gboolean gst_h264_timestamper_start (GstCodecTimestamper * timestamper); +static gboolean gst_h264_timestamper_stop (GstCodecTimestamper * timestamper); +static gboolean gst_h264_timestamper_set_caps (GstCodecTimestamper * + timestamper, GstCaps * caps); +static GstFlowReturn gst_h264_timestamper_handle_buffer (GstCodecTimestamper * + timestamper, GstBuffer * buffer); +static void gst_h264_timestamper_process_nal (GstH264Timestamper * self, + GstH264NalUnit * nalu); + +G_DEFINE_TYPE (GstH264Timestamper, + gst_h264_timestamper, GST_TYPE_CODEC_TIMESTAMPER); + +GST_ELEMENT_REGISTER_DEFINE (h264timestamper, "h264timestamper", + GST_RANK_NONE, GST_TYPE_H264_TIMESTAMPER); + +static void +gst_h264_timestamper_class_init (GstH264TimestamperClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstCodecTimestamperClass *timestamper_class = + GST_CODEC_TIMESTAMPER_CLASS (klass); + + gst_element_class_add_static_pad_template (element_class, &sinktemplate); + gst_element_class_add_static_pad_template (element_class, &srctemplate); + + gst_element_class_set_static_metadata (element_class, "H.264 timestamper", + "Codec/Video", "Timestamp H.264 streams", + "Seungha Yang "); + + timestamper_class->start = GST_DEBUG_FUNCPTR (gst_h264_timestamper_start); + timestamper_class->stop = GST_DEBUG_FUNCPTR (gst_h264_timestamper_stop); + timestamper_class->set_caps = + GST_DEBUG_FUNCPTR (gst_h264_timestamper_set_caps); + timestamper_class->handle_buffer = + GST_DEBUG_FUNCPTR (gst_h264_timestamper_handle_buffer); + + GST_DEBUG_CATEGORY_INIT (gst_h264_timestamper_debug, "h264timestamper", 0, + "h264timestamper"); +} + +static void +gst_h264_timestamper_init (GstH264Timestamper * self) +{ +} + +static gboolean +gst_h264_timestamper_set_caps (GstCodecTimestamper * timestamper, + GstCaps * caps) +{ + GstH264Timestamper *self = GST_H264_TIMESTAMPER (timestamper); + GstStructure *s = gst_caps_get_structure (caps, 0); + const gchar *str; + gboolean found_format = FALSE; + const GValue *codec_data_val; + + self->packetized = FALSE; + self->nal_length_size = 4; + str = gst_structure_get_string (s, "stream-format"); + if (g_strcmp0 (str, "avc") == 0 || g_strcmp0 (str, "avc3") == 0) { + self->packetized = TRUE; + found_format = TRUE; + } else if (g_strcmp0 (str, "byte-stream") == 0) { + found_format = TRUE; + } + + codec_data_val = gst_structure_get_value (s, "codec_data"); + if (codec_data_val && GST_VALUE_HOLDS_BUFFER (codec_data_val)) { + GstBuffer *codec_data = gst_value_get_buffer (codec_data_val); + GstMapInfo map; + GstH264NalUnit *nalu; + GstH264ParserResult ret; + GstH264DecoderConfigRecord *config = NULL; + guint i; + + if (!gst_buffer_map (codec_data, &map, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Unable to map codec-data buffer"); + return FALSE; + } + + ret = gst_h264_parser_parse_decoder_config_record (self->parser, + map.data, map.size, &config); + if (ret != GST_H264_PARSER_OK) { + GST_WARNING_OBJECT (self, "Failed to parse codec-data"); + goto unmap; + } + + self->nal_length_size = config->length_size_minus_one + 1; + for (i = 0; i < config->sps->len; i++) { + nalu = &g_array_index (config->sps, GstH264NalUnit, i); + gst_h264_timestamper_process_nal (self, nalu); + } + + for (i = 0; i < config->pps->len; i++) { + nalu = &g_array_index (config->pps, GstH264NalUnit, i); + gst_h264_timestamper_process_nal (self, nalu); + } + + /* codec_data would mean packetized format */ + if (!found_format) + self->packetized = TRUE; + + unmap: + gst_buffer_unmap (codec_data, &map); + g_clear_pointer (&config, gst_h264_decoder_config_record_free); + } + + return TRUE; +} + +typedef enum +{ + GST_H264_LEVEL_L1 = 10, + GST_H264_LEVEL_L1B = 9, + GST_H264_LEVEL_L1_1 = 11, + GST_H264_LEVEL_L1_2 = 12, + GST_H264_LEVEL_L1_3 = 13, + GST_H264_LEVEL_L2_0 = 20, + GST_H264_LEVEL_L2_1 = 21, + GST_H264_LEVEL_L2_2 = 22, + GST_H264_LEVEL_L3 = 30, + GST_H264_LEVEL_L3_1 = 31, + GST_H264_LEVEL_L3_2 = 32, + GST_H264_LEVEL_L4 = 40, + GST_H264_LEVEL_L4_1 = 41, + GST_H264_LEVEL_L4_2 = 42, + GST_H264_LEVEL_L5 = 50, + GST_H264_LEVEL_L5_1 = 51, + GST_H264_LEVEL_L5_2 = 52, + GST_H264_LEVEL_L6 = 60, + GST_H264_LEVEL_L6_1 = 61, + GST_H264_LEVEL_L6_2 = 62, +} GstH264DecoderLevel; + +typedef struct +{ + GstH264DecoderLevel level; + + guint32 max_mbps; + guint32 max_fs; + guint32 max_dpb_mbs; + guint32 max_main_br; +} LevelLimits; + +static const LevelLimits level_limits_map[] = { + {GST_H264_LEVEL_L1, 1485, 99, 396, 64}, + {GST_H264_LEVEL_L1B, 1485, 99, 396, 128}, + {GST_H264_LEVEL_L1_1, 3000, 396, 900, 192}, + {GST_H264_LEVEL_L1_2, 6000, 396, 2376, 384}, + {GST_H264_LEVEL_L1_3, 11800, 396, 2376, 768}, + {GST_H264_LEVEL_L2_0, 11880, 396, 2376, 2000}, + {GST_H264_LEVEL_L2_1, 19800, 792, 4752, 4000}, + {GST_H264_LEVEL_L2_2, 20250, 1620, 8100, 4000}, + {GST_H264_LEVEL_L3, 40500, 1620, 8100, 10000}, + {GST_H264_LEVEL_L3_1, 108000, 3600, 18000, 14000}, + {GST_H264_LEVEL_L3_2, 216000, 5120, 20480, 20000}, + {GST_H264_LEVEL_L4, 245760, 8192, 32768, 20000}, + {GST_H264_LEVEL_L4_1, 245760, 8192, 32768, 50000}, + {GST_H264_LEVEL_L4_2, 522240, 8704, 34816, 50000}, + {GST_H264_LEVEL_L5, 589824, 22080, 110400, 135000}, + {GST_H264_LEVEL_L5_1, 983040, 36864, 184320, 240000}, + {GST_H264_LEVEL_L5_2, 2073600, 36864, 184320, 240000}, + {GST_H264_LEVEL_L6, 4177920, 139264, 696320, 240000}, + {GST_H264_LEVEL_L6_1, 8355840, 139264, 696320, 480000}, + {GST_H264_LEVEL_L6_2, 16711680, 139264, 696320, 800000} +}; + +static guint +h264_level_to_max_dpb_mbs (GstH264DecoderLevel level) +{ + gint i; + for (i = 0; i < G_N_ELEMENTS (level_limits_map); i++) { + if (level == level_limits_map[i].level) + return level_limits_map[i].max_dpb_mbs; + } + + return 0; +} + +static void +gst_h264_timestamper_process_sps (GstH264Timestamper * self, GstH264SPS * sps) +{ + guint8 level; + guint max_dpb_mbs; + guint width_mb, height_mb; + guint max_dpb_frames = 0; + guint max_reorder_frames = 0; + + /* Spec A.3.1 and A.3.2 + * For Baseline, Constrained Baseline and Main profile, the indicated level is + * Level 1b if level_idc is equal to 11 and constraint_set3_flag is equal to 1 + */ + level = sps->level_idc; + if (level == 11 && (sps->profile_idc == 66 || sps->profile_idc == 77) && + sps->constraint_set3_flag) { + /* Level 1b */ + level = 9; + } + + max_dpb_mbs = h264_level_to_max_dpb_mbs ((GstH264DecoderLevel) level); + if (sps->vui_parameters_present_flag + && sps->vui_parameters.bitstream_restriction_flag) { + max_dpb_frames = MAX (1, sps->vui_parameters.max_dec_frame_buffering); + } else if (max_dpb_mbs != 0) { + width_mb = sps->width / 16; + height_mb = sps->height / 16; + + max_dpb_frames = MIN (max_dpb_mbs / (width_mb * height_mb), 16); + } else { + GST_WARNING_OBJECT (self, "Unable to get MAX DPB MBs"); + max_dpb_frames = 16; + } + + GST_DEBUG_OBJECT (self, "Max DPB size %d", max_dpb_frames); + + max_reorder_frames = max_dpb_frames; + if (sps->vui_parameters_present_flag + && sps->vui_parameters.bitstream_restriction_flag) { + max_reorder_frames = sps->vui_parameters.num_reorder_frames; + if (max_reorder_frames > max_dpb_frames) { + GST_WARNING_OBJECT (self, "num_reorder_frames %d > dpb size %d", + max_reorder_frames, max_dpb_frames); + max_reorder_frames = max_dpb_frames; + } + } else { + if (sps->profile_idc == 66 || sps->profile_idc == 83) { + /* baseline, constrained baseline and scalable-baseline profiles + only contain I/P frames. */ + max_reorder_frames = 0; + } else if (sps->constraint_set3_flag) { + /* constraint_set3_flag may mean the -intra only profile. */ + switch (sps->profile_idc) { + case 44: + case 86: + case 100: + case 110: + case 122: + case 244: + max_reorder_frames = 0; + break; + default: + break; + } + } + } + + GST_DEBUG_OBJECT (self, "Max num reorder frames %d", max_reorder_frames); + + gst_codec_timestamper_set_window_size (GST_CODEC_TIMESTAMPER_CAST (self), + max_reorder_frames); +} + +static void +gst_h264_timestamper_process_nal (GstH264Timestamper * self, + GstH264NalUnit * nalu) +{ + GstH264ParserResult ret; + + switch (nalu->type) { + case GST_H264_NAL_SPS:{ + GstH264SPS sps; + ret = gst_h264_parser_parse_sps (self->parser, nalu, &sps); + if (ret != GST_H264_PARSER_OK) { + GST_WARNING_OBJECT (self, "Failed to parse SPS"); + break; + } + + gst_h264_timestamper_process_sps (self, &sps); + gst_h264_sps_clear (&sps); + break; + } + /* TODO: parse PPS/SLICE and correct PTS based on POC if needed */ + default: + break; + } +} + +static GstFlowReturn +gst_h264_timestamper_handle_buffer (GstCodecTimestamper * timestamper, + GstBuffer * buffer) +{ + GstH264Timestamper *self = GST_H264_TIMESTAMPER (timestamper); + GstMapInfo map; + + /* Ignore any error while parsing NAL */ + if (gst_buffer_map (buffer, &map, GST_MAP_READ)) { + GstH264ParserResult ret; + GstH264NalUnit nalu; + + if (self->packetized) { + ret = gst_h264_parser_identify_nalu_avc (self->parser, + map.data, 0, map.size, self->nal_length_size, &nalu); + + while (ret == GST_H264_PARSER_OK) { + gst_h264_timestamper_process_nal (self, &nalu); + + ret = gst_h264_parser_identify_nalu_avc (self->parser, + map.data, nalu.offset + nalu.size, map.size, self->nal_length_size, + &nalu); + } + } else { + ret = gst_h264_parser_identify_nalu (self->parser, + map.data, 0, map.size, &nalu); + + if (ret == GST_H264_PARSER_NO_NAL_END) + ret = GST_H264_PARSER_OK; + + while (ret == GST_H264_PARSER_OK) { + gst_h264_timestamper_process_nal (self, &nalu); + + ret = gst_h264_parser_identify_nalu (self->parser, + map.data, nalu.offset + nalu.size, map.size, &nalu); + + if (ret == GST_H264_PARSER_NO_NAL_END) + ret = GST_H264_PARSER_OK; + } + } + gst_buffer_unmap (buffer, &map); + } + + return GST_FLOW_OK; +} + +static void +gst_h264_timestamper_reset (GstH264Timestamper * self) +{ + g_clear_pointer (&self->parser, gst_h264_nal_parser_free); +} + +static gboolean +gst_h264_timestamper_start (GstCodecTimestamper * timestamper) +{ + GstH264Timestamper *self = GST_H264_TIMESTAMPER (timestamper); + + gst_h264_timestamper_reset (self); + + self->parser = gst_h264_nal_parser_new (); + + return TRUE; +} + +static gboolean +gst_h264_timestamper_stop (GstCodecTimestamper * timestamper) +{ + GstH264Timestamper *self = GST_H264_TIMESTAMPER (timestamper); + + gst_h264_timestamper_reset (self); + + return TRUE; +} diff --git a/subprojects/gst-plugins-bad/gst/codectimestamper/gsth264timestamper.h b/subprojects/gst-plugins-bad/gst/codectimestamper/gsth264timestamper.h new file mode 100644 index 0000000000..a5ba0a917e --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/codectimestamper/gsth264timestamper.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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. + */ + +#pragma once + +#include +#include "gstcodectimestamper.h" + +G_BEGIN_DECLS + +#define GST_TYPE_H264_TIMESTAMPER (gst_h264_timestamper_get_type()) +G_DECLARE_FINAL_TYPE (GstH264Timestamper, gst_h264_timestamper, + GST, H264_TIMESTAMPER, GstCodecTimestamper); + +GST_ELEMENT_REGISTER_DECLARE (h264timestamper); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst/codectimestamper/gsth265timestamper.c b/subprojects/gst-plugins-bad/gst/codectimestamper/gsth265timestamper.c new file mode 100644 index 0000000000..2151268da6 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/codectimestamper/gsth265timestamper.c @@ -0,0 +1,316 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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-h265timestamper + * @title: h265timestamper + * + * A timestamp correction element for H.265 stream. + * + * ## Example launch line + * ``` + * gst-launch-1.0 filesrc location=video.mkv ! matroskademux ! h265parse ! h265timestamper ! mp4mux ! filesink location=output.mp4 + * ``` + * + * Since: 1.22 + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "gsth265timestamper.h" + +GST_DEBUG_CATEGORY_STATIC (gst_h265_timestamper_debug); +#define GST_CAT_DEFAULT gst_h265_timestamper_debug + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-h265, alignment=(string) au")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-h265, alignment=(string) au")); + +struct _GstH265Timestamper +{ + GstCodecTimestamper parent; + + GstH265Parser *parser; + gboolean packetized; + guint nal_length_size; +}; + +static gboolean gst_h265_timestamper_start (GstCodecTimestamper * timestamper); +static gboolean gst_h265_timestamper_stop (GstCodecTimestamper * timestamper); +static gboolean gst_h265_timestamper_set_caps (GstCodecTimestamper * + timestamper, GstCaps * caps); +static GstFlowReturn gst_h265_timestamper_handle_buffer (GstCodecTimestamper * + timestamper, GstBuffer * buffer); +static void gst_h265_timestamper_process_nal (GstH265Timestamper * self, + GstH265NalUnit * nalu); + +G_DEFINE_TYPE (GstH265Timestamper, + gst_h265_timestamper, GST_TYPE_CODEC_TIMESTAMPER); + +GST_ELEMENT_REGISTER_DEFINE (h265timestamper, "h265timestamper", + GST_RANK_NONE, GST_TYPE_H265_TIMESTAMPER); + +static void +gst_h265_timestamper_class_init (GstH265TimestamperClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstCodecTimestamperClass *timestamper_class = + GST_CODEC_TIMESTAMPER_CLASS (klass); + + gst_element_class_add_static_pad_template (element_class, &sinktemplate); + gst_element_class_add_static_pad_template (element_class, &srctemplate); + + gst_element_class_set_static_metadata (element_class, "H.265 timestamper", + "Codec/Video", "Timestamp H.265 streams", + "Seungha Yang "); + + timestamper_class->start = GST_DEBUG_FUNCPTR (gst_h265_timestamper_start); + timestamper_class->stop = GST_DEBUG_FUNCPTR (gst_h265_timestamper_stop); + timestamper_class->set_caps = + GST_DEBUG_FUNCPTR (gst_h265_timestamper_set_caps); + timestamper_class->handle_buffer = + GST_DEBUG_FUNCPTR (gst_h265_timestamper_handle_buffer); + + GST_DEBUG_CATEGORY_INIT (gst_h265_timestamper_debug, "h265timestamper", 0, + "h265timestamper"); +} + +static void +gst_h265_timestamper_init (GstH265Timestamper * self) +{ +} + +static gboolean +gst_h265_timestamper_set_caps (GstCodecTimestamper * timestamper, + GstCaps * caps) +{ + GstH265Timestamper *self = GST_H265_TIMESTAMPER (timestamper); + GstStructure *s = gst_caps_get_structure (caps, 0); + const gchar *str; + gboolean found_format = FALSE; + const GValue *codec_data_val; + + self->packetized = FALSE; + self->nal_length_size = 4; + str = gst_structure_get_string (s, "stream-format"); + if (g_strcmp0 (str, "hvc1") == 0 || g_strcmp0 (str, "hev1") == 0) { + self->packetized = TRUE; + found_format = TRUE; + } else if (g_strcmp0 (str, "byte-stream") == 0) { + found_format = TRUE; + } + + codec_data_val = gst_structure_get_value (s, "codec_data"); + if (codec_data_val && GST_VALUE_HOLDS_BUFFER (codec_data_val)) { + GstBuffer *codec_data = gst_value_get_buffer (codec_data_val); + GstH265Parser *parser = self->parser; + GstMapInfo map; + GstH265NalUnit nalu; + GstH265ParserResult pres; + guint num_nal_arrays; + guint off; + guint num_nals, i, j; + guint8 *data; + gsize size; + + if (!gst_buffer_map (codec_data, &map, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Unable to map codec-data buffer"); + return FALSE; + } + + data = map.data; + size = map.size; + + /* parse the hvcC data */ + if (size < 23) { + GST_WARNING_OBJECT (self, "hvcC too small"); + goto unmap; + } + + /* wrong hvcC version */ + if (data[0] != 0 && data[0] != 1) { + goto unmap; + } + + self->nal_length_size = (data[21] & 0x03) + 1; + GST_DEBUG_OBJECT (self, "nal length size %u", self->nal_length_size); + + num_nal_arrays = data[22]; + off = 23; + + for (i = 0; i < num_nal_arrays; i++) { + if (off + 3 >= size) { + GST_WARNING_OBJECT (self, "hvcC too small"); + goto unmap; + } + + num_nals = GST_READ_UINT16_BE (data + off + 1); + off += 3; + for (j = 0; j < num_nals; j++) { + pres = gst_h265_parser_identify_nalu_hevc (parser, + data, off, size, 2, &nalu); + + if (pres != GST_H265_PARSER_OK) { + GST_WARNING_OBJECT (self, "hvcC too small"); + goto unmap; + } + + gst_h265_timestamper_process_nal (self, &nalu); + + off = nalu.offset + nalu.size; + } + } + /* codec_data would mean packetized format */ + if (!found_format) + self->packetized = TRUE; + + unmap: + gst_buffer_unmap (codec_data, &map); + } + + return TRUE; +} + +static void +gst_h265_timestamper_process_sps (GstH265Timestamper * self, GstH265SPS * sps) +{ + guint max_reorder_frames = + sps->max_num_reorder_pics[sps->max_sub_layers_minus1]; + + GST_DEBUG_OBJECT (self, "Max num reorder frames %d", max_reorder_frames); + + gst_codec_timestamper_set_window_size (GST_CODEC_TIMESTAMPER_CAST (self), + max_reorder_frames); +} + +static void +gst_h265_timestamper_process_nal (GstH265Timestamper * self, + GstH265NalUnit * nalu) +{ + GstH265ParserResult ret; + + switch (nalu->type) { + case GST_H265_NAL_VPS:{ + GstH265VPS vps; + ret = gst_h265_parser_parse_vps (self->parser, nalu, &vps); + if (ret != GST_H265_PARSER_OK) + GST_WARNING_OBJECT (self, "Failed to parse SPS"); + break; + } + case GST_H265_NAL_SPS:{ + GstH265SPS sps; + ret = gst_h265_parser_parse_sps (self->parser, nalu, &sps, FALSE); + if (ret != GST_H265_PARSER_OK) { + GST_WARNING_OBJECT (self, "Failed to parse SPS"); + break; + } + + gst_h265_timestamper_process_sps (self, &sps); + break; + } + /* TODO: parse PPS/SLICE and correct PTS based on POC if needed */ + default: + break; + } +} + +static GstFlowReturn +gst_h265_timestamper_handle_buffer (GstCodecTimestamper * timestamper, + GstBuffer * buffer) +{ + GstH265Timestamper *self = GST_H265_TIMESTAMPER (timestamper); + GstMapInfo map; + + /* Ignore any error while parsing NAL */ + if (gst_buffer_map (buffer, &map, GST_MAP_READ)) { + GstH265ParserResult ret; + GstH265NalUnit nalu; + + if (self->packetized) { + ret = gst_h265_parser_identify_nalu_hevc (self->parser, + map.data, 0, map.size, self->nal_length_size, &nalu); + + while (ret == GST_H265_PARSER_OK) { + gst_h265_timestamper_process_nal (self, &nalu); + + ret = gst_h265_parser_identify_nalu_hevc (self->parser, + map.data, nalu.offset + nalu.size, map.size, self->nal_length_size, + &nalu); + } + } else { + ret = gst_h265_parser_identify_nalu (self->parser, + map.data, 0, map.size, &nalu); + + if (ret == GST_H265_PARSER_NO_NAL_END) + ret = GST_H265_PARSER_OK; + + while (ret == GST_H265_PARSER_OK) { + gst_h265_timestamper_process_nal (self, &nalu); + + ret = gst_h265_parser_identify_nalu (self->parser, + map.data, nalu.offset + nalu.size, map.size, &nalu); + + if (ret == GST_H265_PARSER_NO_NAL_END) + ret = GST_H265_PARSER_OK; + } + } + gst_buffer_unmap (buffer, &map); + } + + return GST_FLOW_OK; +} + +static void +gst_h265_timestamper_reset (GstH265Timestamper * self) +{ + g_clear_pointer (&self->parser, gst_h265_parser_free); +} + +static gboolean +gst_h265_timestamper_start (GstCodecTimestamper * timestamper) +{ + GstH265Timestamper *self = GST_H265_TIMESTAMPER (timestamper); + + gst_h265_timestamper_reset (self); + + self->parser = gst_h265_parser_new (); + + return TRUE; +} + +static gboolean +gst_h265_timestamper_stop (GstCodecTimestamper * timestamper) +{ + GstH265Timestamper *self = GST_H265_TIMESTAMPER (timestamper); + + gst_h265_timestamper_reset (self); + + return TRUE; +} diff --git a/subprojects/gst-plugins-bad/gst/codectimestamper/gsth265timestamper.h b/subprojects/gst-plugins-bad/gst/codectimestamper/gsth265timestamper.h new file mode 100644 index 0000000000..2b24c542a7 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/codectimestamper/gsth265timestamper.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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. + */ + +#pragma once + +#include +#include "gstcodectimestamper.h" + +G_BEGIN_DECLS + +#define GST_TYPE_H265_TIMESTAMPER (gst_h265_timestamper_get_type()) +G_DECLARE_FINAL_TYPE (GstH265Timestamper, gst_h265_timestamper, + GST, H265_TIMESTAMPER, GstCodecTimestamper); + +GST_ELEMENT_REGISTER_DECLARE (h265timestamper); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst/codectimestamper/meson.build b/subprojects/gst-plugins-bad/gst/codectimestamper/meson.build new file mode 100644 index 0000000000..bf8a17bde0 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/codectimestamper/meson.build @@ -0,0 +1,17 @@ +codectimestamper_sources = [ + 'gstcodectimestamper.c', + 'gsth264timestamper.c', + 'gsth265timestamper.c', + 'plugin.c', +] + +gstcodectimestamper = library('gstcodectimestamper', + codectimestamper_sources, + c_args : gst_plugins_bad_args + [ '-DGST_USE_UNSTABLE_API' ], + include_directories : [configinc], + dependencies : [gstcodecparsers_dep, gstbase_dep, gstvideo_dep], + install : true, + install_dir : plugins_install_dir, +) +pkgconfig.generate(gstcodectimestamper, install_dir : plugins_pkgconfig_install_dir) +plugins += [gstcodectimestamper] diff --git a/subprojects/gst-plugins-bad/gst/codectimestamper/plugin.c b/subprojects/gst-plugins-bad/gst/codectimestamper/plugin.c new file mode 100644 index 0000000000..748faab81c --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/codectimestamper/plugin.c @@ -0,0 +1,47 @@ +/* + * GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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. + */ + +/** + * plugin-codectimestamper: + * + * Since: 1.22 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gsth264timestamper.h" +#include "gsth265timestamper.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_ELEMENT_REGISTER (h264timestamper, plugin); + GST_ELEMENT_REGISTER (h265timestamper, plugin); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + codectimestamper, + "codectimestamper", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/subprojects/gst-plugins-bad/gst/meson.build b/subprojects/gst-plugins-bad/gst/meson.build index 9cf62db983..4e5226b317 100644 --- a/subprojects/gst-plugins-bad/gst/meson.build +++ b/subprojects/gst-plugins-bad/gst/meson.build @@ -1,7 +1,8 @@ foreach plugin : ['accurip', 'adpcmdec', 'adpcmenc', 'aiff', 'asfmux', 'audiobuffersplit', 'audiofxbad', 'audiomixmatrix', 'audiolatency', 'audiovisualizers', 'autoconvert', 'bayer', - 'camerabin2', 'codecalpha', 'coloreffects', 'debugutils', 'dvbsubenc', + 'camerabin2', 'codecalpha', 'codectimestamper', 'coloreffects', + 'debugutils', 'dvbsubenc', 'dvbsuboverlay', 'dvdspu', 'faceoverlay', 'festival', 'fieldanalysis', 'freeverb', 'frei0r', 'gaudieffects', 'gdp', 'geometrictransform', 'id3tag', 'inter', 'interlace', diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt index 2ab309761a..ad123512be 100644 --- a/subprojects/gst-plugins-bad/meson_options.txt +++ b/subprojects/gst-plugins-bad/meson_options.txt @@ -16,6 +16,7 @@ option('autoconvert', type : 'feature', value : 'auto') option('bayer', type : 'feature', value : 'auto') option('camerabin2', type : 'feature', value : 'auto') option('codecalpha', type : 'feature', value : 'auto') +option('codectimestamper', type : 'feature', value : 'auto') option('coloreffects', type : 'feature', value : 'auto') option('debugutils', type : 'feature', value : 'auto') option('dvbsubenc', type : 'feature', value : 'auto')