closedcaption: move cc_data->cdp to shared file
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / ext / closedcaption / gstcccombiner.c
1 /*
2  * GStreamer
3  * Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
4  *
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.
9  *
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.
14  *
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.
19  */
20
21
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25
26 #include <gst/gst.h>
27 #include <gst/base/base.h>
28 #include <gst/video/video.h>
29 #include <string.h>
30
31 #include "ccutils.h"
32 #include "gstcccombiner.h"
33
34 GST_DEBUG_CATEGORY_STATIC (gst_cc_combiner_debug);
35 #define GST_CAT_DEFAULT gst_cc_combiner_debug
36
37 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
38     GST_PAD_SINK,
39     GST_PAD_ALWAYS,
40     GST_STATIC_CAPS_ANY);
41
42 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
43     GST_PAD_SRC,
44     GST_PAD_ALWAYS,
45     GST_STATIC_CAPS_ANY);
46
47 static GstStaticPadTemplate captiontemplate =
48     GST_STATIC_PAD_TEMPLATE ("caption",
49     GST_PAD_SINK,
50     GST_PAD_REQUEST,
51     GST_STATIC_CAPS
52     ("closedcaption/x-cea-608,format={ (string) raw, (string) s334-1a}; "
53         "closedcaption/x-cea-708,format={ (string) cc_data, (string) cdp }"));
54
55 #define parent_class gst_cc_combiner_parent_class
56 G_DEFINE_TYPE (GstCCCombiner, gst_cc_combiner, GST_TYPE_AGGREGATOR);
57 GST_ELEMENT_REGISTER_DEFINE (cccombiner, "cccombiner",
58     GST_RANK_NONE, GST_TYPE_CCCOMBINER);
59
60 enum
61 {
62   PROP_0,
63   PROP_SCHEDULE,
64   PROP_OUTPUT_PADDING,
65   PROP_MAX_SCHEDULED,
66 };
67
68 #define DEFAULT_MAX_SCHEDULED 30
69 #define DEFAULT_SCHEDULE TRUE
70 #define DEFAULT_OUTPUT_PADDING TRUE
71
72 typedef struct
73 {
74   GstVideoCaptionType caption_type;
75   GstBuffer *buffer;
76 } CaptionData;
77
78 typedef struct
79 {
80   GstBuffer *buffer;
81   GstClockTime running_time;
82   GstClockTime stream_time;
83 } CaptionQueueItem;
84
85 static void
86 caption_data_clear (CaptionData * data)
87 {
88   gst_buffer_unref (data->buffer);
89 }
90
91 static void
92 clear_scheduled (CaptionQueueItem * item)
93 {
94   gst_buffer_unref (item->buffer);
95 }
96
97 static void
98 gst_cc_combiner_finalize (GObject * object)
99 {
100   GstCCCombiner *self = GST_CCCOMBINER (object);
101
102   gst_queue_array_free (self->scheduled[0]);
103   gst_queue_array_free (self->scheduled[1]);
104   g_array_unref (self->current_frame_captions);
105   self->current_frame_captions = NULL;
106
107   G_OBJECT_CLASS (parent_class)->finalize (object);
108 }
109
110 #define GST_FLOW_NEED_DATA GST_FLOW_CUSTOM_SUCCESS
111
112 static const guint8 *
113 extract_cdp (const guint8 * cdp, guint cdp_len, guint * cc_data_len)
114 {
115   GstByteReader br;
116   guint16 u16;
117   guint8 u8;
118   guint8 flags;
119   guint len = 0;
120   const guint8 *cc_data = NULL;
121
122   *cc_data_len = 0;
123
124   /* Header + footer length */
125   if (cdp_len < 11) {
126     goto done;
127   }
128
129   gst_byte_reader_init (&br, cdp, cdp_len);
130   u16 = gst_byte_reader_get_uint16_be_unchecked (&br);
131   if (u16 != 0x9669) {
132     goto done;
133   }
134
135   u8 = gst_byte_reader_get_uint8_unchecked (&br);
136   if (u8 != cdp_len) {
137     goto done;
138   }
139
140   gst_byte_reader_skip_unchecked (&br, 1);
141
142   flags = gst_byte_reader_get_uint8_unchecked (&br);
143
144   /* No cc_data? */
145   if ((flags & 0x40) == 0) {
146     goto done;
147   }
148
149   /* cdp_hdr_sequence_cntr */
150   gst_byte_reader_skip_unchecked (&br, 2);
151
152   /* time_code_present */
153   if (flags & 0x80) {
154     if (gst_byte_reader_get_remaining (&br) < 5) {
155       goto done;
156     }
157     gst_byte_reader_skip_unchecked (&br, 5);
158   }
159
160   /* ccdata_present */
161   if (flags & 0x40) {
162     guint8 cc_count;
163
164     if (gst_byte_reader_get_remaining (&br) < 2) {
165       goto done;
166     }
167     u8 = gst_byte_reader_get_uint8_unchecked (&br);
168     if (u8 != 0x72) {
169       goto done;
170     }
171
172     cc_count = gst_byte_reader_get_uint8_unchecked (&br);
173     if ((cc_count & 0xe0) != 0xe0) {
174       goto done;
175     }
176     cc_count &= 0x1f;
177
178     if (cc_count == 0)
179       return 0;
180
181     len = 3 * cc_count;
182     if (gst_byte_reader_get_remaining (&br) < len)
183       goto done;
184
185     cc_data = gst_byte_reader_get_data_unchecked (&br, len);
186     *cc_data_len = len;
187   }
188
189 done:
190   return cc_data;
191 }
192
193 #define MAX_CDP_PACKET_LEN 256
194 #define MAX_CEA608_LEN 32
195 #define CDP_MODE (GST_CC_CDP_MODE_CC_DATA | GST_CC_CDP_MODE_TIME_CODE)
196
197 static GstBuffer *
198 make_cdp (GstCCCombiner * self, const guint8 * cc_data, guint cc_data_len,
199     const struct cdp_fps_entry *fps_entry, const GstVideoTimeCode * tc)
200 {
201   guint len;
202   GstBuffer *ret = gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
203   GstMapInfo map;
204
205   gst_buffer_map (ret, &map, GST_MAP_WRITE);
206
207   len = convert_cea708_cc_data_to_cdp (GST_OBJECT (self), CDP_MODE,
208       self->cdp_hdr_sequence_cntr, cc_data, cc_data_len, map.data, map.size,
209       tc, fps_entry);
210   self->cdp_hdr_sequence_cntr++;
211
212   gst_buffer_unmap (ret, &map);
213
214   gst_buffer_set_size (ret, len);
215
216   return ret;
217 }
218
219 static GstBuffer *
220 make_padding (GstCCCombiner * self, const GstVideoTimeCode * tc, guint field)
221 {
222   GstBuffer *ret = NULL;
223
224   switch (self->caption_type) {
225     case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
226     {
227       const guint8 cc_data[6] = { 0xfc, 0x80, 0x80, 0xf9, 0x80, 0x80 };
228
229       ret = make_cdp (self, cc_data, 6, self->cdp_fps_entry, tc);
230       break;
231     }
232     case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
233     {
234       GstMapInfo map;
235
236       ret = gst_buffer_new_allocate (NULL, 3, NULL);
237
238       gst_buffer_map (ret, &map, GST_MAP_WRITE);
239
240       map.data[0] = 0xfc | (field & 0x01);
241       map.data[1] = 0x80;
242       map.data[2] = 0x80;
243
244       gst_buffer_unmap (ret, &map);
245       break;
246     }
247     case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
248     {
249       GstMapInfo map;
250
251       ret = gst_buffer_new_allocate (NULL, 3, NULL);
252
253       gst_buffer_map (ret, &map, GST_MAP_WRITE);
254
255       map.data[0] = field == 0 ? 0x80 : 0x00;
256       map.data[1] = 0x80;
257       map.data[2] = 0x80;
258
259       gst_buffer_unmap (ret, &map);
260       break;
261     }
262     case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
263     {
264       GstMapInfo map;
265
266       ret = gst_buffer_new_allocate (NULL, 2, NULL);
267
268       gst_buffer_map (ret, &map, GST_MAP_WRITE);
269
270       map.data[0] = 0x80;
271       map.data[1] = 0x80;
272
273       gst_buffer_unmap (ret, &map);
274       break;
275     }
276     default:
277       break;
278   }
279
280   return ret;
281 }
282
283 static void
284 queue_caption (GstCCCombiner * self, GstBuffer * scheduled, guint field)
285 {
286   GstAggregatorPad *caption_pad;
287   CaptionQueueItem item;
288
289   if (self->progressive && field == 1) {
290     gst_buffer_unref (scheduled);
291     return;
292   }
293
294   caption_pad =
295       GST_AGGREGATOR_PAD_CAST (gst_element_get_static_pad (GST_ELEMENT_CAST
296           (self), "caption"));
297
298   g_assert (gst_queue_array_get_length (self->scheduled[field]) <=
299       self->max_scheduled);
300
301   if (gst_queue_array_get_length (self->scheduled[field]) ==
302       self->max_scheduled) {
303     CaptionQueueItem *dropped =
304         gst_queue_array_pop_tail_struct (self->scheduled[field]);
305
306     GST_WARNING_OBJECT (self,
307         "scheduled queue runs too long, dropping %" GST_PTR_FORMAT, dropped);
308
309     gst_element_post_message (GST_ELEMENT_CAST (self),
310         gst_message_new_qos (GST_OBJECT_CAST (self), FALSE,
311             dropped->running_time, dropped->stream_time,
312             GST_BUFFER_PTS (dropped->buffer), GST_BUFFER_DURATION (dropped)));
313
314     gst_buffer_unref (dropped->buffer);
315   }
316
317   gst_object_unref (caption_pad);
318
319   item.buffer = scheduled;
320   item.running_time =
321       gst_segment_to_running_time (&caption_pad->segment, GST_FORMAT_TIME,
322       GST_BUFFER_PTS (scheduled));
323   item.stream_time =
324       gst_segment_to_stream_time (&caption_pad->segment, GST_FORMAT_TIME,
325       GST_BUFFER_PTS (scheduled));
326
327   gst_queue_array_push_tail_struct (self->scheduled[field], &item);
328 }
329
330 static void
331 schedule_cdp (GstCCCombiner * self, const GstVideoTimeCode * tc,
332     const guint8 * data, guint len, GstClockTime pts, GstClockTime duration)
333 {
334   const guint8 *cc_data;
335   guint cc_data_len;
336   gboolean inject = FALSE;
337
338   if ((cc_data = extract_cdp (data, len, &cc_data_len))) {
339     guint8 i;
340
341     for (i = 0; i < cc_data_len / 3; i++) {
342       gboolean cc_valid = (cc_data[i * 3] & 0x04) == 0x04;
343       guint8 cc_type = cc_data[i * 3] & 0x03;
344
345       if (!cc_valid)
346         continue;
347
348       if (cc_type == 0x00 || cc_type == 0x01) {
349         if (cc_data[i * 3 + 1] != 0x80 || cc_data[i * 3 + 2] != 0x80) {
350           inject = TRUE;
351           break;
352         }
353         continue;
354       } else {
355         inject = TRUE;
356         break;
357       }
358     }
359   }
360
361   if (inject) {
362     GstBuffer *buf =
363         make_cdp (self, cc_data, cc_data_len, self->cdp_fps_entry, tc);
364
365     /* We only set those for QoS reporting purposes */
366     GST_BUFFER_PTS (buf) = pts;
367     GST_BUFFER_DURATION (buf) = duration;
368
369     queue_caption (self, buf, 0);
370   }
371 }
372
373 static void
374 schedule_cea608_s334_1a (GstCCCombiner * self, guint8 * data, guint len,
375     GstClockTime pts, GstClockTime duration)
376 {
377   guint8 field0_data[3], field1_data[3];
378   guint field0_len = 0, field1_len = 0;
379   guint i;
380
381   if (len % 3 != 0) {
382     GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple "
383         "of 3", len);
384     len = len - (len % 3);
385   }
386
387   for (i = 0; i < len / 3; i++) {
388     if (data[i * 3] & 0x80) {
389       if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
390         continue;
391
392       field0_data[field0_len++] = data[i * 3];
393       field0_data[field0_len++] = data[i * 3 + 1];
394       field0_data[field0_len++] = data[i * 3 + 2];
395     } else {
396       if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
397         continue;
398
399       field1_data[field1_len++] = data[i * 3];
400       field1_data[field1_len++] = data[i * 3 + 1];
401       field1_data[field1_len++] = data[i * 3 + 2];
402     }
403   }
404
405   if (field0_len > 0) {
406     GstBuffer *buf = gst_buffer_new_allocate (NULL, field0_len, NULL);
407
408     gst_buffer_fill (buf, 0, field0_data, field0_len);
409     GST_BUFFER_PTS (buf) = pts;
410     GST_BUFFER_DURATION (buf) = duration;
411
412     queue_caption (self, buf, 0);
413   }
414
415   if (field1_len > 0) {
416     GstBuffer *buf = gst_buffer_new_allocate (NULL, field1_len, NULL);
417
418     gst_buffer_fill (buf, 0, field1_data, field1_len);
419     GST_BUFFER_PTS (buf) = pts;
420     GST_BUFFER_DURATION (buf) = duration;
421
422     queue_caption (self, buf, 1);
423   }
424 }
425
426 static void
427 schedule_cea708_raw (GstCCCombiner * self, guint8 * data, guint len,
428     GstClockTime pts, GstClockTime duration)
429 {
430   guint8 field0_data[MAX_CDP_PACKET_LEN], field1_data[3];
431   guint field0_len = 0, field1_len = 0;
432   guint i;
433   gboolean started_ccp = FALSE;
434
435   if (len % 3 != 0) {
436     GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple "
437         "of 3", len);
438     len = len - (len % 3);
439   }
440
441   for (i = 0; i < len / 3; i++) {
442     gboolean cc_valid = (data[i * 3] & 0x04) == 0x04;
443     guint8 cc_type = data[i * 3] & 0x03;
444
445     if (!started_ccp) {
446       if (cc_type == 0x00) {
447         if (!cc_valid)
448           continue;
449
450         if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
451           continue;
452
453         field0_data[field0_len++] = data[i * 3];
454         field0_data[field0_len++] = data[i * 3 + 1];
455         field0_data[field0_len++] = data[i * 3 + 2];
456       } else if (cc_type == 0x01) {
457         if (!cc_valid)
458           continue;
459
460         if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
461           continue;
462
463         field1_data[field1_len++] = data[i * 3];
464         field1_data[field1_len++] = data[i * 3 + 1];
465         field1_data[field1_len++] = data[i * 3 + 2];
466       }
467
468       continue;
469     }
470
471     if (cc_type & 0x10)
472       started_ccp = TRUE;
473
474     if (!cc_valid)
475       continue;
476
477     if (cc_type == 0x00 || cc_type == 0x01)
478       continue;
479
480     field0_data[field0_len++] = data[i * 3];
481     field0_data[field0_len++] = data[i * 3 + 1];
482     field0_data[field0_len++] = data[i * 3 + 2];
483   }
484
485   if (field0_len > 0) {
486     GstBuffer *buf = gst_buffer_new_allocate (NULL, field0_len, NULL);
487
488     gst_buffer_fill (buf, 0, field0_data, field0_len);
489     GST_BUFFER_PTS (buf) = pts;
490     GST_BUFFER_DURATION (buf) = duration;
491
492     queue_caption (self, buf, 0);
493   }
494
495   if (field1_len > 0) {
496     GstBuffer *buf = gst_buffer_new_allocate (NULL, field1_len, NULL);
497
498     gst_buffer_fill (buf, 0, field1_data, field1_len);
499     GST_BUFFER_PTS (buf) = pts;
500     GST_BUFFER_DURATION (buf) = duration;
501
502     queue_caption (self, buf, 1);
503   }
504 }
505
506 static void
507 schedule_cea608_raw (GstCCCombiner * self, guint8 * data, guint len,
508     GstBuffer * buffer)
509 {
510   if (len < 2) {
511     return;
512   }
513
514   if (data[0] != 0x80 || data[1] != 0x80) {
515     queue_caption (self, gst_buffer_ref (buffer), 0);
516   }
517 }
518
519
520 static void
521 schedule_caption (GstCCCombiner * self, GstBuffer * caption_buf,
522     const GstVideoTimeCode * tc)
523 {
524   GstMapInfo map;
525   GstClockTime pts, duration;
526
527   pts = GST_BUFFER_PTS (caption_buf);
528   duration = GST_BUFFER_DURATION (caption_buf);
529
530   gst_buffer_map (caption_buf, &map, GST_MAP_READ);
531
532   switch (self->caption_type) {
533     case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
534       schedule_cdp (self, tc, map.data, map.size, pts, duration);
535       break;
536     case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
537       schedule_cea708_raw (self, map.data, map.size, pts, duration);
538       break;
539     case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
540       schedule_cea608_s334_1a (self, map.data, map.size, pts, duration);
541       break;
542     case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
543       schedule_cea608_raw (self, map.data, map.size, caption_buf);
544       break;
545     default:
546       break;
547   }
548
549   gst_buffer_unmap (caption_buf, &map);
550 }
551
552 static void
553 dequeue_caption_one_field (GstCCCombiner * self, const GstVideoTimeCode * tc,
554     guint field, gboolean drain)
555 {
556   CaptionQueueItem *scheduled;
557   CaptionData caption_data;
558
559   if ((scheduled = gst_queue_array_pop_head_struct (self->scheduled[field]))) {
560     caption_data.buffer = scheduled->buffer;
561     caption_data.caption_type = self->caption_type;
562     g_array_append_val (self->current_frame_captions, caption_data);
563   } else if (!drain && self->output_padding) {
564     caption_data.caption_type = self->caption_type;
565     caption_data.buffer = make_padding (self, tc, field);
566     g_array_append_val (self->current_frame_captions, caption_data);
567   }
568 }
569
570 static void
571 dequeue_caption_both_fields (GstCCCombiner * self, const GstVideoTimeCode * tc,
572     gboolean drain)
573 {
574   CaptionQueueItem *field0_scheduled, *field1_scheduled;
575   GstBuffer *field0_buffer = NULL, *field1_buffer = NULL;
576   CaptionData caption_data;
577
578   field0_scheduled = gst_queue_array_pop_head_struct (self->scheduled[0]);
579   field1_scheduled = gst_queue_array_pop_head_struct (self->scheduled[1]);
580
581   if (drain && !field0_scheduled && !field1_scheduled) {
582     return;
583   }
584
585   if (field0_scheduled) {
586     field0_buffer = field0_scheduled->buffer;
587   } else if (self->output_padding) {
588     field0_buffer = make_padding (self, tc, 0);
589   }
590
591   if (field1_scheduled) {
592     field1_buffer = field1_scheduled->buffer;
593   } else if (self->output_padding) {
594     field1_buffer = make_padding (self, tc, 1);
595   }
596
597   if (field0_buffer || field1_buffer) {
598     if (field0_buffer && field1_buffer) {
599       caption_data.buffer = gst_buffer_append (field0_buffer, field1_buffer);
600     } else if (field0_buffer) {
601       caption_data.buffer = field0_buffer;
602     } else if (field1_buffer) {
603       caption_data.buffer = field1_buffer;
604     } else {
605       g_assert_not_reached ();
606     }
607
608     caption_data.caption_type = self->caption_type;
609
610     g_array_append_val (self->current_frame_captions, caption_data);
611   }
612 }
613
614 static GstFlowReturn
615 gst_cc_combiner_collect_captions (GstCCCombiner * self, gboolean timeout)
616 {
617   GstAggregatorPad *src_pad =
618       GST_AGGREGATOR_PAD (GST_AGGREGATOR_SRC_PAD (self));
619   GstAggregatorPad *caption_pad;
620   GstBuffer *video_buf;
621   GstVideoTimeCodeMeta *tc_meta;
622   GstVideoTimeCode *tc = NULL;
623   gboolean caption_pad_is_eos = FALSE;
624
625   g_assert (self->current_video_buffer != NULL);
626
627   caption_pad =
628       GST_AGGREGATOR_PAD_CAST (gst_element_get_static_pad (GST_ELEMENT_CAST
629           (self), "caption"));
630   /* No caption pad, forward buffer directly */
631   if (!caption_pad) {
632     GST_LOG_OBJECT (self, "No caption pad, passing through video");
633     video_buf = self->current_video_buffer;
634     gst_aggregator_selected_samples (GST_AGGREGATOR_CAST (self),
635         GST_BUFFER_PTS (video_buf), GST_BUFFER_DTS (video_buf),
636         GST_BUFFER_DURATION (video_buf), NULL);
637     self->current_video_buffer = NULL;
638     goto done;
639   }
640
641   tc_meta = gst_buffer_get_video_time_code_meta (self->current_video_buffer);
642
643   if (tc_meta) {
644     tc = &tc_meta->tc;
645   }
646
647   GST_LOG_OBJECT (self, "Trying to collect captions for queued video buffer");
648   do {
649     GstBuffer *caption_buf;
650     GstClockTime caption_time;
651     CaptionData caption_data;
652
653     caption_buf = gst_aggregator_pad_peek_buffer (caption_pad);
654     if (!caption_buf) {
655       if (gst_aggregator_pad_is_eos (caption_pad)) {
656         GST_DEBUG_OBJECT (self, "Caption pad is EOS, we're done");
657
658         caption_pad_is_eos = TRUE;
659         break;
660       } else if (!timeout) {
661         GST_DEBUG_OBJECT (self, "Need more caption data");
662         gst_object_unref (caption_pad);
663         return GST_FLOW_NEED_DATA;
664       } else {
665         GST_DEBUG_OBJECT (self, "No caption data on timeout");
666         break;
667       }
668     }
669
670     caption_time = GST_BUFFER_PTS (caption_buf);
671     if (!GST_CLOCK_TIME_IS_VALID (caption_time)) {
672       GST_ERROR_OBJECT (self, "Caption buffer without PTS");
673
674       gst_buffer_unref (caption_buf);
675       gst_object_unref (caption_pad);
676
677       return GST_FLOW_ERROR;
678     }
679
680     caption_time =
681         gst_segment_to_running_time (&caption_pad->segment, GST_FORMAT_TIME,
682         caption_time);
683
684     if (!GST_CLOCK_TIME_IS_VALID (caption_time)) {
685       GST_DEBUG_OBJECT (self, "Caption buffer outside segment, dropping");
686
687       gst_aggregator_pad_drop_buffer (caption_pad);
688       gst_buffer_unref (caption_buf);
689
690       continue;
691     }
692
693     if (gst_buffer_get_size (caption_buf) == 0 &&
694         GST_BUFFER_FLAG_IS_SET (caption_buf, GST_BUFFER_FLAG_GAP)) {
695       /* This is a gap, we can go ahead. We only consume it once its end point
696        * is behind the current video running time. Important to note that
697        * we can't deal with gaps with no duration (-1)
698        */
699       if (!GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (caption_buf))) {
700         GST_ERROR_OBJECT (self, "GAP buffer without a duration");
701
702         gst_buffer_unref (caption_buf);
703         gst_object_unref (caption_pad);
704
705         return GST_FLOW_ERROR;
706       }
707
708       gst_buffer_unref (caption_buf);
709
710       if (caption_time + GST_BUFFER_DURATION (caption_buf) <
711           self->current_video_running_time_end) {
712         gst_aggregator_pad_drop_buffer (caption_pad);
713         continue;
714       } else {
715         break;
716       }
717     }
718
719     /* Collected all caption buffers for this video buffer */
720     if (caption_time >= self->current_video_running_time_end) {
721       gst_buffer_unref (caption_buf);
722       break;
723     } else if (!self->schedule) {
724       if (GST_CLOCK_TIME_IS_VALID (self->previous_video_running_time_end)) {
725         if (caption_time < self->previous_video_running_time_end) {
726           GST_WARNING_OBJECT (self,
727               "Caption buffer before end of last video frame, dropping");
728
729           gst_aggregator_pad_drop_buffer (caption_pad);
730           gst_buffer_unref (caption_buf);
731           continue;
732         }
733       } else if (caption_time < self->current_video_running_time) {
734         GST_WARNING_OBJECT (self,
735             "Caption buffer before current video frame, dropping");
736
737         gst_aggregator_pad_drop_buffer (caption_pad);
738         gst_buffer_unref (caption_buf);
739         continue;
740       }
741     }
742
743     /* This caption buffer has to be collected */
744     GST_LOG_OBJECT (self,
745         "Collecting caption buffer %p %" GST_TIME_FORMAT " for video buffer %p",
746         caption_buf, GST_TIME_ARGS (caption_time), self->current_video_buffer);
747
748     caption_data.caption_type = self->caption_type;
749
750     gst_aggregator_pad_drop_buffer (caption_pad);
751
752     if (!self->schedule) {
753       caption_data.buffer = caption_buf;
754       g_array_append_val (self->current_frame_captions, caption_data);
755     } else {
756       schedule_caption (self, caption_buf, tc);
757       gst_buffer_unref (caption_buf);
758     }
759   } while (TRUE);
760
761   /* FIXME pad correctly according to fps */
762   if (self->schedule) {
763     g_assert (self->current_frame_captions->len == 0);
764
765     switch (self->caption_type) {
766       case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
767       {
768         /* Only relevant in alternate and mixed mode, no need to look at the caps */
769         if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
770                 GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
771           if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
772             dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
773           }
774         } else {
775           dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
776         }
777         break;
778       }
779       case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
780       case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
781       {
782         if (self->progressive) {
783           dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
784         } else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
785                 GST_VIDEO_BUFFER_FLAG_INTERLACED) &&
786             GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
787                 GST_VIDEO_BUFFER_FLAG_ONEFIELD)) {
788           if (GST_VIDEO_BUFFER_IS_TOP_FIELD (self->current_video_buffer)) {
789             dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
790           } else {
791             dequeue_caption_one_field (self, tc, 1, caption_pad_is_eos);
792           }
793         } else {
794           dequeue_caption_both_fields (self, tc, caption_pad_is_eos);
795         }
796         break;
797       }
798       case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
799       {
800         if (self->progressive) {
801           dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
802         } else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
803                 GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
804           if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
805             dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
806           }
807         } else {
808           dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
809         }
810         break;
811       }
812       default:
813         break;
814     }
815   }
816
817   gst_aggregator_selected_samples (GST_AGGREGATOR_CAST (self),
818       GST_BUFFER_PTS (self->current_video_buffer),
819       GST_BUFFER_DTS (self->current_video_buffer),
820       GST_BUFFER_DURATION (self->current_video_buffer), NULL);
821
822   GST_LOG_OBJECT (self, "Attaching %u captions to buffer %p",
823       self->current_frame_captions->len, self->current_video_buffer);
824
825   if (self->current_frame_captions->len > 0) {
826     guint i;
827
828     video_buf = gst_buffer_make_writable (self->current_video_buffer);
829     self->current_video_buffer = NULL;
830
831     for (i = 0; i < self->current_frame_captions->len; i++) {
832       CaptionData *caption_data =
833           &g_array_index (self->current_frame_captions, CaptionData, i);
834       GstMapInfo map;
835
836       gst_buffer_map (caption_data->buffer, &map, GST_MAP_READ);
837       gst_buffer_add_video_caption_meta (video_buf, caption_data->caption_type,
838           map.data, map.size);
839       gst_buffer_unmap (caption_data->buffer, &map);
840     }
841
842     g_array_set_size (self->current_frame_captions, 0);
843   } else {
844     GST_LOG_OBJECT (self, "No captions for buffer %p",
845         self->current_video_buffer);
846     video_buf = self->current_video_buffer;
847     self->current_video_buffer = NULL;
848   }
849
850   gst_object_unref (caption_pad);
851
852 done:
853   src_pad->segment.position =
854       GST_BUFFER_PTS (video_buf) + GST_BUFFER_DURATION (video_buf);
855
856   return gst_aggregator_finish_buffer (GST_AGGREGATOR_CAST (self), video_buf);
857 }
858
859 static GstFlowReturn
860 gst_cc_combiner_aggregate (GstAggregator * aggregator, gboolean timeout)
861 {
862   GstCCCombiner *self = GST_CCCOMBINER (aggregator);
863   GstFlowReturn flow_ret = GST_FLOW_OK;
864
865   /* If we have no current video buffer, queue one. If we have one but
866    * its end running time is not known yet, try to determine it from the
867    * next video buffer */
868   if (!self->current_video_buffer
869       || !GST_CLOCK_TIME_IS_VALID (self->current_video_running_time_end)) {
870     GstAggregatorPad *video_pad;
871     GstClockTime video_start;
872     GstBuffer *video_buf;
873
874     video_pad =
875         GST_AGGREGATOR_PAD_CAST (gst_element_get_static_pad (GST_ELEMENT_CAST
876             (aggregator), "sink"));
877     video_buf = gst_aggregator_pad_peek_buffer (video_pad);
878     if (!video_buf) {
879       if (gst_aggregator_pad_is_eos (video_pad)) {
880         GST_DEBUG_OBJECT (aggregator, "Video pad is EOS, we're done");
881
882         /* Assume that this buffer ends where it started +50ms (25fps) and handle it */
883         if (self->current_video_buffer) {
884           self->current_video_running_time_end =
885               self->current_video_running_time + 50 * GST_MSECOND;
886           flow_ret = gst_cc_combiner_collect_captions (self, timeout);
887         }
888
889         /* If we collected all captions for the remaining video frame we're
890          * done, otherwise get called another time and go directly into the
891          * outer branch for finishing the current video frame */
892         if (flow_ret == GST_FLOW_NEED_DATA)
893           flow_ret = GST_FLOW_OK;
894         else
895           flow_ret = GST_FLOW_EOS;
896       } else {
897         flow_ret = GST_FLOW_OK;
898       }
899
900       gst_object_unref (video_pad);
901       return flow_ret;
902     }
903
904     video_start = GST_BUFFER_PTS (video_buf);
905     if (!GST_CLOCK_TIME_IS_VALID (video_start)) {
906       gst_buffer_unref (video_buf);
907       gst_object_unref (video_pad);
908
909       GST_ERROR_OBJECT (aggregator, "Video buffer without PTS");
910
911       return GST_FLOW_ERROR;
912     }
913
914     video_start =
915         gst_segment_to_running_time (&video_pad->segment, GST_FORMAT_TIME,
916         video_start);
917     if (!GST_CLOCK_TIME_IS_VALID (video_start)) {
918       GST_DEBUG_OBJECT (aggregator, "Buffer outside segment, dropping");
919       gst_aggregator_pad_drop_buffer (video_pad);
920       gst_buffer_unref (video_buf);
921       gst_object_unref (video_pad);
922       return GST_FLOW_OK;
923     }
924
925     if (self->current_video_buffer) {
926       /* If we already have a video buffer just update the current end running
927        * time accordingly. That's what was missing and why we got here */
928       self->current_video_running_time_end = video_start;
929       gst_buffer_unref (video_buf);
930       GST_LOG_OBJECT (self,
931           "Determined end timestamp for video buffer: %p %" GST_TIME_FORMAT
932           " - %" GST_TIME_FORMAT, self->current_video_buffer,
933           GST_TIME_ARGS (self->current_video_running_time),
934           GST_TIME_ARGS (self->current_video_running_time_end));
935     } else {
936       /* Otherwise we had no buffer queued currently. Let's do that now
937        * so that we can collect captions for it */
938       gst_buffer_replace (&self->current_video_buffer, video_buf);
939       self->current_video_running_time = video_start;
940       gst_aggregator_pad_drop_buffer (video_pad);
941       gst_buffer_unref (video_buf);
942
943       if (GST_BUFFER_DURATION_IS_VALID (video_buf)) {
944         GstClockTime end_time =
945             GST_BUFFER_PTS (video_buf) + GST_BUFFER_DURATION (video_buf);
946         if (video_pad->segment.stop != -1 && end_time > video_pad->segment.stop)
947           end_time = video_pad->segment.stop;
948         self->current_video_running_time_end =
949             gst_segment_to_running_time (&video_pad->segment, GST_FORMAT_TIME,
950             end_time);
951       } else if (self->video_fps_n != 0 && self->video_fps_d != 0) {
952         GstClockTime end_time =
953             GST_BUFFER_PTS (video_buf) + gst_util_uint64_scale_int (GST_SECOND,
954             self->video_fps_d, self->video_fps_n);
955         if (video_pad->segment.stop != -1 && end_time > video_pad->segment.stop)
956           end_time = video_pad->segment.stop;
957         self->current_video_running_time_end =
958             gst_segment_to_running_time (&video_pad->segment, GST_FORMAT_TIME,
959             end_time);
960       } else {
961         self->current_video_running_time_end = GST_CLOCK_TIME_NONE;
962       }
963
964       GST_LOG_OBJECT (self,
965           "Queued new video buffer: %p %" GST_TIME_FORMAT " - %"
966           GST_TIME_FORMAT, self->current_video_buffer,
967           GST_TIME_ARGS (self->current_video_running_time),
968           GST_TIME_ARGS (self->current_video_running_time_end));
969     }
970
971     gst_object_unref (video_pad);
972   }
973
974   /* At this point we have a video buffer queued and can start collecting
975    * caption buffers for it */
976   g_assert (self->current_video_buffer != NULL);
977   g_assert (GST_CLOCK_TIME_IS_VALID (self->current_video_running_time));
978   g_assert (GST_CLOCK_TIME_IS_VALID (self->current_video_running_time_end));
979
980   flow_ret = gst_cc_combiner_collect_captions (self, timeout);
981
982   /* Only if we collected all captions we replace the current video buffer
983    * with NULL and continue with the next one on the next call */
984   if (flow_ret == GST_FLOW_NEED_DATA) {
985     flow_ret = GST_FLOW_OK;
986   } else {
987     gst_buffer_replace (&self->current_video_buffer, NULL);
988     self->previous_video_running_time_end =
989         self->current_video_running_time_end;
990     self->current_video_running_time = self->current_video_running_time_end =
991         GST_CLOCK_TIME_NONE;
992   }
993
994   return flow_ret;
995 }
996
997 static gboolean
998 gst_cc_combiner_sink_event (GstAggregator * aggregator,
999     GstAggregatorPad * agg_pad, GstEvent * event)
1000 {
1001   GstCCCombiner *self = GST_CCCOMBINER (aggregator);
1002
1003   switch (GST_EVENT_TYPE (event)) {
1004     case GST_EVENT_CAPS:{
1005       GstCaps *caps;
1006       GstStructure *s;
1007
1008       gst_event_parse_caps (event, &caps);
1009       s = gst_caps_get_structure (caps, 0);
1010
1011       if (strcmp (GST_OBJECT_NAME (agg_pad), "caption") == 0) {
1012         GstVideoCaptionType caption_type =
1013             gst_video_caption_type_from_caps (caps);
1014
1015         if (self->caption_type != GST_VIDEO_CAPTION_TYPE_UNKNOWN &&
1016             caption_type != self->caption_type) {
1017           GST_ERROR_OBJECT (self, "Changing caption type is not allowed");
1018
1019           GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1020               ("Changing caption type is not allowed"));
1021
1022           return FALSE;
1023         }
1024         self->caption_type = caption_type;
1025       } else {
1026         gint fps_n, fps_d;
1027         const gchar *interlace_mode;
1028
1029         fps_n = fps_d = 0;
1030
1031         gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d);
1032
1033         interlace_mode = gst_structure_get_string (s, "interlace-mode");
1034
1035         self->progressive = !interlace_mode
1036             || !g_strcmp0 (interlace_mode, "progressive");
1037
1038         if (fps_n != self->video_fps_n || fps_d != self->video_fps_d) {
1039           GstClockTime latency;
1040
1041           latency = gst_util_uint64_scale (GST_SECOND, fps_d, fps_n);
1042           gst_aggregator_set_latency (aggregator, latency, latency);
1043         }
1044
1045         self->video_fps_n = fps_n;
1046         self->video_fps_d = fps_d;
1047
1048         self->cdp_fps_entry = cdp_fps_entry_from_fps (fps_n, fps_d);
1049
1050         gst_aggregator_set_src_caps (aggregator, caps);
1051       }
1052
1053       break;
1054     }
1055     case GST_EVENT_SEGMENT:{
1056       if (strcmp (GST_OBJECT_NAME (agg_pad), "sink") == 0) {
1057         const GstSegment *segment;
1058
1059         gst_event_parse_segment (event, &segment);
1060         gst_aggregator_update_segment (aggregator, segment);
1061       }
1062       break;
1063     }
1064     default:
1065       break;
1066   }
1067
1068   return GST_AGGREGATOR_CLASS (parent_class)->sink_event (aggregator, agg_pad,
1069       event);
1070 }
1071
1072 static gboolean
1073 gst_cc_combiner_stop (GstAggregator * aggregator)
1074 {
1075   GstCCCombiner *self = GST_CCCOMBINER (aggregator);
1076
1077   self->video_fps_n = self->video_fps_d = 0;
1078   self->current_video_running_time = self->current_video_running_time_end =
1079       self->previous_video_running_time_end = GST_CLOCK_TIME_NONE;
1080   gst_buffer_replace (&self->current_video_buffer, NULL);
1081
1082   g_array_set_size (self->current_frame_captions, 0);
1083   self->caption_type = GST_VIDEO_CAPTION_TYPE_UNKNOWN;
1084
1085   gst_queue_array_clear (self->scheduled[0]);
1086   gst_queue_array_clear (self->scheduled[1]);
1087   self->cdp_fps_entry = &null_fps_entry;
1088
1089   return TRUE;
1090 }
1091
1092 static GstFlowReturn
1093 gst_cc_combiner_flush (GstAggregator * aggregator)
1094 {
1095   GstCCCombiner *self = GST_CCCOMBINER (aggregator);
1096   GstAggregatorPad *src_pad =
1097       GST_AGGREGATOR_PAD (GST_AGGREGATOR_SRC_PAD (aggregator));
1098
1099   self->current_video_running_time = self->current_video_running_time_end =
1100       self->previous_video_running_time_end = GST_CLOCK_TIME_NONE;
1101   gst_buffer_replace (&self->current_video_buffer, NULL);
1102
1103   g_array_set_size (self->current_frame_captions, 0);
1104
1105   src_pad->segment.position = GST_CLOCK_TIME_NONE;
1106
1107   self->cdp_hdr_sequence_cntr = 0;
1108   gst_queue_array_clear (self->scheduled[0]);
1109   gst_queue_array_clear (self->scheduled[1]);
1110
1111   return GST_FLOW_OK;
1112 }
1113
1114 static GstAggregatorPad *
1115 gst_cc_combiner_create_new_pad (GstAggregator * aggregator,
1116     GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
1117 {
1118   GstCCCombiner *self = GST_CCCOMBINER (aggregator);
1119   GstAggregatorPad *agg_pad;
1120
1121   if (templ->direction != GST_PAD_SINK)
1122     return NULL;
1123
1124   if (templ->presence != GST_PAD_REQUEST)
1125     return NULL;
1126
1127   if (strcmp (templ->name_template, "caption") != 0)
1128     return NULL;
1129
1130   GST_OBJECT_LOCK (self);
1131   agg_pad = g_object_new (GST_TYPE_AGGREGATOR_PAD,
1132       "name", "caption", "direction", GST_PAD_SINK, "template", templ, NULL);
1133   self->caption_type = GST_VIDEO_CAPTION_TYPE_UNKNOWN;
1134   GST_OBJECT_UNLOCK (self);
1135
1136   return agg_pad;
1137 }
1138
1139 static gboolean
1140 gst_cc_combiner_src_query (GstAggregator * aggregator, GstQuery * query)
1141 {
1142   GstPad *video_sinkpad =
1143       gst_element_get_static_pad (GST_ELEMENT_CAST (aggregator), "sink");
1144   gboolean ret;
1145
1146   switch (GST_QUERY_TYPE (query)) {
1147     case GST_QUERY_POSITION:
1148     case GST_QUERY_DURATION:
1149     case GST_QUERY_URI:
1150     case GST_QUERY_CAPS:
1151     case GST_QUERY_ALLOCATION:
1152       ret = gst_pad_peer_query (video_sinkpad, query);
1153       break;
1154     case GST_QUERY_ACCEPT_CAPS:{
1155       GstCaps *caps;
1156       GstCaps *templ = gst_static_pad_template_get_caps (&srctemplate);
1157
1158       gst_query_parse_accept_caps (query, &caps);
1159       gst_query_set_accept_caps_result (query, gst_caps_is_subset (caps,
1160               templ));
1161       gst_caps_unref (templ);
1162       ret = TRUE;
1163       break;
1164     }
1165     default:
1166       ret = GST_AGGREGATOR_CLASS (parent_class)->src_query (aggregator, query);
1167       break;
1168   }
1169
1170   gst_object_unref (video_sinkpad);
1171
1172   return ret;
1173 }
1174
1175 static gboolean
1176 gst_cc_combiner_sink_query (GstAggregator * aggregator,
1177     GstAggregatorPad * aggpad, GstQuery * query)
1178 {
1179   GstPad *video_sinkpad =
1180       gst_element_get_static_pad (GST_ELEMENT_CAST (aggregator), "sink");
1181   GstPad *srcpad = GST_AGGREGATOR_SRC_PAD (aggregator);
1182
1183   gboolean ret;
1184
1185   switch (GST_QUERY_TYPE (query)) {
1186     case GST_QUERY_POSITION:
1187     case GST_QUERY_DURATION:
1188     case GST_QUERY_URI:
1189     case GST_QUERY_ALLOCATION:
1190       if (GST_PAD_CAST (aggpad) == video_sinkpad) {
1191         ret = gst_pad_peer_query (srcpad, query);
1192       } else {
1193         ret =
1194             GST_AGGREGATOR_CLASS (parent_class)->sink_query (aggregator,
1195             aggpad, query);
1196       }
1197       break;
1198     case GST_QUERY_CAPS:
1199       if (GST_PAD_CAST (aggpad) == video_sinkpad) {
1200         ret = gst_pad_peer_query (srcpad, query);
1201       } else {
1202         GstCaps *filter;
1203         GstCaps *templ = gst_static_pad_template_get_caps (&captiontemplate);
1204
1205         gst_query_parse_caps (query, &filter);
1206
1207         if (filter) {
1208           GstCaps *caps =
1209               gst_caps_intersect_full (filter, templ, GST_CAPS_INTERSECT_FIRST);
1210           gst_query_set_caps_result (query, caps);
1211           gst_caps_unref (caps);
1212         } else {
1213           gst_query_set_caps_result (query, templ);
1214         }
1215         gst_caps_unref (templ);
1216         ret = TRUE;
1217       }
1218       break;
1219     case GST_QUERY_ACCEPT_CAPS:
1220       if (GST_PAD_CAST (aggpad) == video_sinkpad) {
1221         ret = gst_pad_peer_query (srcpad, query);
1222       } else {
1223         GstCaps *caps;
1224         GstCaps *templ = gst_static_pad_template_get_caps (&captiontemplate);
1225
1226         gst_query_parse_accept_caps (query, &caps);
1227         gst_query_set_accept_caps_result (query, gst_caps_is_subset (caps,
1228                 templ));
1229         gst_caps_unref (templ);
1230         ret = TRUE;
1231       }
1232       break;
1233     default:
1234       ret = GST_AGGREGATOR_CLASS (parent_class)->sink_query (aggregator,
1235           aggpad, query);
1236       break;
1237   }
1238
1239   gst_object_unref (video_sinkpad);
1240
1241   return ret;
1242 }
1243
1244 static GstSample *
1245 gst_cc_combiner_peek_next_sample (GstAggregator * agg,
1246     GstAggregatorPad * aggpad)
1247 {
1248   GstAggregatorPad *caption_pad, *video_pad;
1249   GstCCCombiner *self = GST_CCCOMBINER (agg);
1250   GstSample *res = NULL;
1251
1252   caption_pad =
1253       GST_AGGREGATOR_PAD_CAST (gst_element_get_static_pad (GST_ELEMENT_CAST
1254           (self), "caption"));
1255   video_pad =
1256       GST_AGGREGATOR_PAD_CAST (gst_element_get_static_pad (GST_ELEMENT_CAST
1257           (self), "sink"));
1258
1259   if (aggpad == caption_pad) {
1260     if (self->current_frame_captions->len > 0) {
1261       GstCaps *caps = gst_pad_get_current_caps (GST_PAD (aggpad));
1262       GstBufferList *buflist = gst_buffer_list_new ();
1263       guint i;
1264
1265       for (i = 0; i < self->current_frame_captions->len; i++) {
1266         CaptionData *caption_data =
1267             &g_array_index (self->current_frame_captions, CaptionData, i);
1268         gst_buffer_list_add (buflist, gst_buffer_ref (caption_data->buffer));
1269       }
1270
1271       res = gst_sample_new (NULL, caps, &aggpad->segment, NULL);
1272       gst_caps_unref (caps);
1273
1274       gst_sample_set_buffer_list (res, buflist);
1275       gst_buffer_list_unref (buflist);
1276     }
1277   } else if (aggpad == video_pad) {
1278     if (self->current_video_buffer) {
1279       GstCaps *caps = gst_pad_get_current_caps (GST_PAD (aggpad));
1280       res = gst_sample_new (self->current_video_buffer,
1281           caps, &aggpad->segment, NULL);
1282       gst_caps_unref (caps);
1283     }
1284   }
1285
1286   if (caption_pad)
1287     gst_object_unref (caption_pad);
1288
1289   if (video_pad)
1290     gst_object_unref (video_pad);
1291
1292   return res;
1293 }
1294
1295 static GstStateChangeReturn
1296 gst_cc_combiner_change_state (GstElement * element, GstStateChange transition)
1297 {
1298   GstCCCombiner *self = GST_CCCOMBINER (element);
1299
1300   switch (transition) {
1301     case GST_STATE_CHANGE_READY_TO_PAUSED:
1302       self->schedule = self->prop_schedule;
1303       self->max_scheduled = self->prop_max_scheduled;
1304       self->output_padding = self->prop_output_padding;
1305       break;
1306     default:
1307       break;
1308   }
1309
1310   return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1311 }
1312
1313 static void
1314 gst_cc_combiner_set_property (GObject * object, guint prop_id,
1315     const GValue * value, GParamSpec * pspec)
1316 {
1317   GstCCCombiner *self = GST_CCCOMBINER (object);
1318
1319   switch (prop_id) {
1320     case PROP_SCHEDULE:
1321       self->prop_schedule = g_value_get_boolean (value);
1322       break;
1323     case PROP_MAX_SCHEDULED:
1324       self->prop_max_scheduled = g_value_get_uint (value);
1325       break;
1326     case PROP_OUTPUT_PADDING:
1327       self->prop_output_padding = g_value_get_boolean (value);
1328       break;
1329     default:
1330       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1331       break;
1332   }
1333 }
1334
1335 static void
1336 gst_cc_combiner_get_property (GObject * object, guint prop_id, GValue * value,
1337     GParamSpec * pspec)
1338 {
1339   GstCCCombiner *self = GST_CCCOMBINER (object);
1340
1341   switch (prop_id) {
1342     case PROP_SCHEDULE:
1343       g_value_set_boolean (value, self->prop_schedule);
1344       break;
1345     case PROP_MAX_SCHEDULED:
1346       g_value_set_uint (value, self->prop_max_scheduled);
1347       break;
1348     case PROP_OUTPUT_PADDING:
1349       g_value_set_boolean (value, self->prop_output_padding);
1350       break;
1351     default:
1352       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1353       break;
1354   }
1355 }
1356
1357 static void
1358 gst_cc_combiner_class_init (GstCCCombinerClass * klass)
1359 {
1360   GObjectClass *gobject_class;
1361   GstElementClass *gstelement_class;
1362   GstAggregatorClass *aggregator_class;
1363
1364   gobject_class = (GObjectClass *) klass;
1365   gstelement_class = (GstElementClass *) klass;
1366   aggregator_class = (GstAggregatorClass *) klass;
1367
1368   gobject_class->finalize = gst_cc_combiner_finalize;
1369   gobject_class->set_property = gst_cc_combiner_set_property;
1370   gobject_class->get_property = gst_cc_combiner_get_property;
1371
1372   gst_element_class_set_static_metadata (gstelement_class,
1373       "Closed Caption Combiner",
1374       "Filter",
1375       "Combines GstVideoCaptionMeta with video input stream",
1376       "Sebastian Dröge <sebastian@centricular.com>");
1377
1378   /**
1379    * GstCCCombiner:schedule:
1380    *
1381    * Controls whether caption buffers should be smoothly scheduled
1382    * in order to have exactly one per output video buffer.
1383    *
1384    * This can involve rewriting input captions, for example when the
1385    * input is CDP sequence counters are rewritten, time codes are dropped
1386    * and potentially re-injected if the input video frame had a time code
1387    * meta.
1388    *
1389    * Caption buffers may also get split up in order to assign captions to
1390    * the correct field when the input is interlaced.
1391    *
1392    * This can also imply that the input will drift from synchronization,
1393    * when there isn't enough padding in the input stream to catch up. In
1394    * that case the element will start dropping old caption buffers once
1395    * the number of buffers in its internal queue reaches
1396    * #GstCCCombiner:max-scheduled.
1397    *
1398    * When this is set to %FALSE, the behaviour of this element is essentially
1399    * that of a funnel.
1400    *
1401    * Since: 1.20
1402    */
1403   g_object_class_install_property (G_OBJECT_CLASS (klass),
1404       PROP_SCHEDULE, g_param_spec_boolean ("schedule",
1405           "Schedule",
1406           "Schedule caption buffers so that exactly one is output per video frame",
1407           DEFAULT_SCHEDULE,
1408           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
1409           GST_PARAM_MUTABLE_READY));
1410
1411   /**
1412    * GstCCCombiner:max-scheduled:
1413    *
1414    * Controls the number of scheduled buffers after which the element
1415    * will start dropping old buffers from its internal queues. See
1416    * #GstCCCombiner:schedule.
1417    *
1418    * Since: 1.20
1419    */
1420   g_object_class_install_property (G_OBJECT_CLASS (klass),
1421       PROP_MAX_SCHEDULED, g_param_spec_uint ("max-scheduled",
1422           "Max Scheduled",
1423           "Maximum number of buffers to queue for scheduling", 0, G_MAXUINT,
1424           DEFAULT_MAX_SCHEDULED,
1425           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
1426           GST_PARAM_MUTABLE_READY));
1427
1428   /**
1429    * GstCCCombiner:output-padding:
1430    *
1431    * When #GstCCCombiner:schedule is %TRUE, this property controls
1432    * whether the output closed caption meta stream will be padded.
1433    *
1434    * Since: 1.22
1435    */
1436   g_object_class_install_property (G_OBJECT_CLASS (klass),
1437       PROP_OUTPUT_PADDING, g_param_spec_boolean ("output-padding",
1438           "Output padding",
1439           "Whether to output padding packets when schedule=true",
1440           DEFAULT_OUTPUT_PADDING,
1441           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
1442           GST_PARAM_MUTABLE_READY));
1443
1444
1445   gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
1446       &sinktemplate, GST_TYPE_AGGREGATOR_PAD);
1447   gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
1448       &srctemplate, GST_TYPE_AGGREGATOR_PAD);
1449   gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
1450       &captiontemplate, GST_TYPE_AGGREGATOR_PAD);
1451
1452   gstelement_class->change_state =
1453       GST_DEBUG_FUNCPTR (gst_cc_combiner_change_state);
1454
1455   aggregator_class->aggregate = gst_cc_combiner_aggregate;
1456   aggregator_class->stop = gst_cc_combiner_stop;
1457   aggregator_class->flush = gst_cc_combiner_flush;
1458   aggregator_class->create_new_pad = gst_cc_combiner_create_new_pad;
1459   aggregator_class->sink_event = gst_cc_combiner_sink_event;
1460   aggregator_class->negotiate = NULL;
1461   aggregator_class->get_next_time = gst_aggregator_simple_get_next_time;
1462   aggregator_class->src_query = gst_cc_combiner_src_query;
1463   aggregator_class->sink_query = gst_cc_combiner_sink_query;
1464   aggregator_class->peek_next_sample = gst_cc_combiner_peek_next_sample;
1465
1466   GST_DEBUG_CATEGORY_INIT (gst_cc_combiner_debug, "cccombiner",
1467       0, "Closed Caption combiner");
1468 }
1469
1470 static void
1471 gst_cc_combiner_init (GstCCCombiner * self)
1472 {
1473   GstPadTemplate *templ;
1474   GstAggregatorPad *agg_pad;
1475
1476   templ = gst_static_pad_template_get (&sinktemplate);
1477   agg_pad = g_object_new (GST_TYPE_AGGREGATOR_PAD,
1478       "name", "sink", "direction", GST_PAD_SINK, "template", templ, NULL);
1479   gst_object_unref (templ);
1480   gst_element_add_pad (GST_ELEMENT_CAST (self), GST_PAD_CAST (agg_pad));
1481
1482   self->current_frame_captions =
1483       g_array_new (FALSE, FALSE, sizeof (CaptionData));
1484   g_array_set_clear_func (self->current_frame_captions,
1485       (GDestroyNotify) caption_data_clear);
1486
1487   self->current_video_running_time = self->current_video_running_time_end =
1488       self->previous_video_running_time_end = GST_CLOCK_TIME_NONE;
1489
1490   self->caption_type = GST_VIDEO_CAPTION_TYPE_UNKNOWN;
1491
1492   self->prop_schedule = DEFAULT_SCHEDULE;
1493   self->prop_max_scheduled = DEFAULT_MAX_SCHEDULED;
1494   self->prop_output_padding = DEFAULT_OUTPUT_PADDING;
1495   self->scheduled[0] =
1496       gst_queue_array_new_for_struct (sizeof (CaptionQueueItem), 0);
1497   self->scheduled[1] =
1498       gst_queue_array_new_for_struct (sizeof (CaptionQueueItem), 0);
1499   gst_queue_array_set_clear_func (self->scheduled[0],
1500       (GDestroyNotify) clear_scheduled);
1501   gst_queue_array_set_clear_func (self->scheduled[1],
1502       (GDestroyNotify) clear_scheduled);
1503   self->cdp_hdr_sequence_cntr = 0;
1504   self->cdp_fps_entry = &null_fps_entry;
1505 }