3 * Copyright (C) 2018 Edward Hervey <edward@centricular.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
22 * SECTION:element-ccextractor
24 * @short_description: Extract GstVideoCaptionMeta from input stream
26 * Note: This element must be added after a pipeline's decoder, otherwise closed captions may
27 * be extracted out of order.
36 #include <gst/video/video.h>
39 #include "gstccextractor.h"
41 GST_DEBUG_CATEGORY_STATIC (gst_cc_extractor_debug);
42 #define GST_CAT_DEFAULT gst_cc_extractor_debug
49 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
54 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
59 static GstStaticPadTemplate captiontemplate =
60 GST_STATIC_PAD_TEMPLATE ("caption",
64 ("closedcaption/x-cea-608,format={ (string) raw, (string) s334-1a}; "
65 "closedcaption/x-cea-708,format={ (string) cc_data, (string) cdp }"));
67 G_DEFINE_TYPE (GstCCExtractor, gst_cc_extractor, GST_TYPE_ELEMENT);
68 #define parent_class gst_cc_extractor_parent_class
70 static gboolean gst_cc_extractor_sink_event (GstPad * pad, GstObject * parent,
72 static gboolean gst_cc_extractor_sink_query (GstPad * pad, GstObject * parent,
74 static GstFlowReturn gst_cc_extractor_chain (GstPad * pad, GstObject * parent,
76 static GstStateChangeReturn gst_cc_extractor_change_state (GstElement *
77 element, GstStateChange transition);
78 static void gst_cc_extractor_finalize (GObject * self);
82 gst_cc_extractor_class_init (GstCCExtractorClass * klass)
84 GObjectClass *gobject_class;
85 GstElementClass *gstelement_class;
87 gobject_class = (GObjectClass *) klass;
88 gstelement_class = (GstElementClass *) klass;
90 gobject_class->finalize = gst_cc_extractor_finalize;
92 gstelement_class->change_state =
93 GST_DEBUG_FUNCPTR (gst_cc_extractor_change_state);
95 gst_element_class_set_static_metadata (gstelement_class,
96 "Closed Caption Extractor",
98 "Extract GstVideoCaptionMeta from input stream",
99 "Edward Hervey <edward@centricular.com>");
101 gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
102 gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
103 gst_element_class_add_static_pad_template (gstelement_class,
106 GST_DEBUG_CATEGORY_INIT (gst_cc_extractor_debug, "ccextractor",
107 0, "Closed Caption extractor");
111 gst_cc_extractor_iterate_internal_links (GstPad * pad, GstObject * parent)
113 GstCCExtractor *filter = (GstCCExtractor *) parent;
114 GstIterator *it = NULL;
117 if (pad == filter->sinkpad)
118 opad = filter->srcpad;
119 else if (pad == filter->srcpad || pad == filter->captionpad)
120 opad = filter->sinkpad;
123 GValue value = { 0, };
125 g_value_init (&value, GST_TYPE_PAD);
126 g_value_set_object (&value, opad);
127 it = gst_iterator_new_single (GST_TYPE_PAD, &value);
128 g_value_unset (&value);
135 gst_cc_extractor_reset (GstCCExtractor * filter)
137 filter->caption_type = GST_VIDEO_CAPTION_TYPE_UNKNOWN;
138 gst_flow_combiner_reset (filter->combiner);
139 gst_flow_combiner_add_pad (filter->combiner, filter->srcpad);
141 if (filter->captionpad) {
142 gst_flow_combiner_remove_pad (filter->combiner, filter->captionpad);
143 gst_pad_set_active (filter->captionpad, FALSE);
144 gst_element_remove_pad ((GstElement *) filter, filter->captionpad);
145 filter->captionpad = NULL;
148 memset (&filter->video_info, 0, sizeof (filter->video_info));
152 gst_cc_extractor_init (GstCCExtractor * filter)
154 filter->sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink");
155 gst_pad_set_event_function (filter->sinkpad,
156 GST_DEBUG_FUNCPTR (gst_cc_extractor_sink_event));
157 gst_pad_set_query_function (filter->sinkpad,
158 GST_DEBUG_FUNCPTR (gst_cc_extractor_sink_query));
159 gst_pad_set_chain_function (filter->sinkpad,
160 GST_DEBUG_FUNCPTR (gst_cc_extractor_chain));
161 gst_pad_set_iterate_internal_links_function (filter->sinkpad,
162 GST_DEBUG_FUNCPTR (gst_cc_extractor_iterate_internal_links));
163 GST_PAD_SET_PROXY_CAPS (filter->sinkpad);
164 GST_PAD_SET_PROXY_ALLOCATION (filter->sinkpad);
165 GST_PAD_SET_PROXY_SCHEDULING (filter->sinkpad);
167 filter->srcpad = gst_pad_new_from_static_template (&srctemplate, "src");
168 gst_pad_set_iterate_internal_links_function (filter->srcpad,
169 GST_DEBUG_FUNCPTR (gst_cc_extractor_iterate_internal_links));
170 GST_PAD_SET_PROXY_CAPS (filter->srcpad);
171 GST_PAD_SET_PROXY_ALLOCATION (filter->srcpad);
172 GST_PAD_SET_PROXY_SCHEDULING (filter->srcpad);
174 gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
175 gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
177 filter->combiner = gst_flow_combiner_new ();
179 gst_cc_extractor_reset (filter);
183 create_stream_start_event_from_stream_start_event (GstEvent * event)
186 const gchar *stream_id;
187 gchar *new_stream_id;
190 gst_event_parse_stream_start (event, &stream_id);
191 new_stream_id = g_strdup_printf ("%s/caption", stream_id);
193 new_event = gst_event_new_stream_start (new_stream_id);
194 g_free (new_stream_id);
195 if (gst_event_parse_group_id (event, &group_id))
196 gst_event_set_group_id (new_event, group_id);
202 gst_cc_extractor_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
204 GstCCExtractor *filter = GST_CCEXTRACTOR (parent);
206 GST_LOG_OBJECT (pad, "received %s event: %" GST_PTR_FORMAT,
207 GST_EVENT_TYPE_NAME (event), event);
208 switch (GST_EVENT_TYPE (event)) {
209 case GST_EVENT_CAPS:{
212 gst_event_parse_caps (event, &caps);
213 if (!gst_video_info_from_caps (&filter->video_info, caps)) {
214 /* We require any kind of video caps here */
215 gst_event_unref (event);
220 case GST_EVENT_STREAM_START:
221 if (filter->captionpad) {
222 GstEvent *new_event =
223 create_stream_start_event_from_stream_start_event (event);
224 gst_pad_push_event (filter->captionpad, new_event);
228 /* Also forward all other events to the caption pad if present */
229 if (filter->captionpad)
230 gst_pad_push_event (filter->captionpad, gst_event_ref (event));
234 /* This only forwards to the non-caption source pad */
235 return gst_pad_event_default (pad, parent, event);
239 gst_cc_extractor_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
241 GST_LOG_OBJECT (pad, "received %s query: %" GST_PTR_FORMAT,
242 GST_QUERY_TYPE_NAME (query), query);
243 switch (GST_QUERY_TYPE (query)) {
244 case GST_QUERY_ACCEPT_CAPS:{
246 const GstStructure *s;
248 gst_query_parse_accept_caps (query, &caps);
250 /* FIXME: Ideally we would declare this in our caps but there's no way
251 * to declare caps of type "video/" and "image/" that would match all
254 s = gst_caps_get_structure (caps, 0);
255 if (s && (g_str_has_prefix (gst_structure_get_name (s), "video/")
256 || g_str_has_prefix (gst_structure_get_name (s), "image/")))
257 gst_query_set_accept_caps_result (query, TRUE);
259 gst_query_set_accept_caps_result (query, FALSE);
267 return gst_pad_query_default (pad, parent, query);
271 create_caps_from_caption_type (GstVideoCaptionType caption_type,
272 const GstVideoInfo * video_info)
274 GstCaps *caption_caps = gst_video_caption_type_to_caps (caption_type);
276 gst_caps_set_simple (caption_caps, "framerate", GST_TYPE_FRACTION,
277 video_info->fps_n, video_info->fps_d, NULL);
283 forward_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
285 GstCCExtractor *filter = user_data;
287 switch (GST_EVENT_TYPE (*event)) {
288 case GST_EVENT_CAPS:{
289 GstCaps *caption_caps =
290 create_caps_from_caption_type (filter->caption_type,
291 &filter->video_info);
294 GstEvent *new_event = gst_event_new_caps (caption_caps);
295 gst_event_set_seqnum (new_event, gst_event_get_seqnum (*event));
296 gst_pad_store_sticky_event (filter->captionpad, new_event);
297 gst_event_unref (new_event);
298 gst_caps_unref (caption_caps);
303 case GST_EVENT_STREAM_START:{
304 GstEvent *new_event =
305 create_stream_start_event_from_stream_start_event (*event);
306 gst_pad_store_sticky_event (filter->captionpad, new_event);
307 gst_event_unref (new_event);
312 gst_pad_store_sticky_event (filter->captionpad, *event);
320 gst_cc_extractor_handle_meta (GstCCExtractor * filter, GstBuffer * buf,
321 GstVideoCaptionMeta * meta, GstVideoTimeCodeMeta * tc_meta)
323 GstBuffer *outbuf = NULL;
326 GST_DEBUG_OBJECT (filter, "Handling meta");
328 /* Check if the meta type matches the configured one */
329 if (filter->captionpad == NULL) {
330 GST_DEBUG_OBJECT (filter, "Creating new caption pad");
332 /* Create the caption pad and set the caps */
334 gst_pad_new_from_static_template (&captiontemplate, "caption");
335 gst_pad_set_iterate_internal_links_function (filter->sinkpad,
336 GST_DEBUG_FUNCPTR (gst_cc_extractor_iterate_internal_links));
337 gst_pad_set_active (filter->captionpad, TRUE);
339 filter->caption_type = meta->caption_type;
341 gst_pad_sticky_events_foreach (filter->sinkpad, forward_sticky_events,
344 if (!gst_pad_has_current_caps (filter->captionpad)) {
345 GST_ERROR_OBJECT (filter, "Unknown/invalid caption type");
346 return GST_FLOW_NOT_NEGOTIATED;
349 gst_element_add_pad (GST_ELEMENT (filter), filter->captionpad);
350 gst_flow_combiner_add_pad (filter->combiner, filter->captionpad);
351 } else if (meta->caption_type != filter->caption_type) {
352 GstCaps *caption_caps =
353 create_caps_from_caption_type (meta->caption_type, &filter->video_info);
355 GST_DEBUG_OBJECT (filter, "Caption type changed from %d to %d",
356 filter->caption_type, meta->caption_type);
357 if (caption_caps == NULL) {
358 GST_ERROR_OBJECT (filter, "Unknown/invalid caption type");
359 return GST_FLOW_NOT_NEGOTIATED;
362 gst_pad_push_event (filter->captionpad, gst_event_new_caps (caption_caps));
363 gst_caps_unref (caption_caps);
365 filter->caption_type = meta->caption_type;
368 GST_DEBUG_OBJECT (filter,
369 "Creating new buffer of size %" G_GSIZE_FORMAT " bytes", meta->size);
370 /* Extract caption data into new buffer with identical buffer timestamps */
371 outbuf = gst_buffer_new_allocate (NULL, meta->size, NULL);
372 gst_buffer_fill (outbuf, 0, meta->data, meta->size);
373 GST_BUFFER_PTS (outbuf) = GST_BUFFER_PTS (buf);
374 GST_BUFFER_DTS (outbuf) = GST_BUFFER_DTS (buf);
375 GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buf);
378 gst_buffer_add_video_time_code_meta (outbuf, &tc_meta->tc);
380 /* We don't really care about the flow return */
381 flow = gst_pad_push (filter->captionpad, outbuf);
383 /* Set flow return on pad and return combined value */
384 return gst_flow_combiner_update_pad_flow (filter->combiner,
385 filter->captionpad, flow);
389 gst_cc_extractor_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
391 GstCCExtractor *filter = (GstCCExtractor *) parent;
392 GstFlowReturn flow = GST_FLOW_OK;
393 GstVideoCaptionMeta *cc_meta;
394 GstVideoTimeCodeMeta *tc_meta;
395 gpointer iter = NULL;
397 tc_meta = gst_buffer_get_video_time_code_meta (buf);
400 (GstVideoCaptionMeta *) gst_buffer_iterate_meta_filtered (buf, &iter,
401 GST_VIDEO_CAPTION_META_API_TYPE)) && flow == GST_FLOW_OK) {
402 flow = gst_cc_extractor_handle_meta (filter, buf, cc_meta, tc_meta);
405 /* If there's an issue handling the CC, return immediately */
406 if (flow != GST_FLOW_OK) {
407 gst_buffer_unref (buf);
411 /* Push the buffer downstream and return the combined flow return */
412 return gst_flow_combiner_update_pad_flow (filter->combiner, filter->srcpad,
413 gst_pad_push (filter->srcpad, buf));
416 static GstStateChangeReturn
417 gst_cc_extractor_change_state (GstElement * element, GstStateChange transition)
419 GstStateChangeReturn ret;
420 GstCCExtractor *filter = GST_CCEXTRACTOR (element);
422 switch (transition) {
423 case GST_STATE_CHANGE_NULL_TO_READY:
425 case GST_STATE_CHANGE_READY_TO_PAUSED:
427 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
433 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
434 if (ret != GST_STATE_CHANGE_SUCCESS)
437 switch (transition) {
438 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
440 case GST_STATE_CHANGE_PAUSED_TO_READY:
441 gst_cc_extractor_reset (filter);
443 case GST_STATE_CHANGE_READY_TO_NULL:
452 gst_cc_extractor_finalize (GObject * object)
454 GstCCExtractor *filter = GST_CCEXTRACTOR (object);
456 gst_flow_combiner_free (filter->combiner);
458 G_OBJECT_CLASS (parent_class)->finalize (object);