3 * Copyright (C) 2018 Sebastian Dröge <sebastian@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.
27 #include <gst/base/base.h>
28 #include <gst/video/video.h>
31 #include "gstccconverter.h"
33 GST_DEBUG_CATEGORY_STATIC (gst_cc_converter_debug);
34 #define GST_CAT_DEFAULT gst_cc_converter_debug
37 * GstCCConverterCDPMode:
38 * @GST_CC_CONVERTER_CDP_MODE_TIME_CODE: Store time code information in CDP packets
39 * @GST_CC_CONVERTER_CDP_MODE_CC_DATA: Store CC data in CDP packets
40 * @GST_CC_CONVERTER_CDP_MODE_CC_SVC_INFO: Store CC service information in CDP packets
51 #define DEFAULT_CDP_MODE (GST_CC_CONVERTER_CDP_MODE_TIME_CODE | GST_CC_CONVERTER_CDP_MODE_CC_DATA | GST_CC_CONVERTER_CDP_MODE_CC_SVC_INFO)
53 /* Ordered by the amount of information they can contain */
55 "closedcaption/x-cea-708,format=(string) cdp; " \
56 "closedcaption/x-cea-708,format=(string) cc_data; " \
57 "closedcaption/x-cea-608,format=(string) s334-1a; " \
58 "closedcaption/x-cea-608,format=(string) raw"
60 #define VAL_OR_0(v) ((v) ? (*(v)) : 0)
62 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
65 GST_STATIC_CAPS (CC_CAPS));
67 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
70 GST_STATIC_CAPS (CC_CAPS));
72 #define parent_class gst_cc_converter_parent_class
73 G_DEFINE_TYPE (GstCCConverter, gst_cc_converter, GST_TYPE_BASE_TRANSFORM);
74 GST_ELEMENT_REGISTER_DEFINE (ccconverter, "ccconverter",
75 GST_RANK_NONE, GST_TYPE_CCCONVERTER);
77 #define GST_TYPE_CC_CONVERTER_CDP_MODE (gst_cc_converter_cdp_mode_get_type())
79 gst_cc_converter_cdp_mode_get_type (void)
81 static const GFlagsValue values[] = {
82 {GST_CC_CONVERTER_CDP_MODE_TIME_CODE,
83 "Store time code information in CDP packets", "time-code"},
84 {GST_CC_CONVERTER_CDP_MODE_CC_DATA, "Store CC data in CDP packets",
86 {GST_CC_CONVERTER_CDP_MODE_CC_SVC_INFO,
87 "Store CC service information in CDP packets", "cc-svc-info"},
92 if (g_once_init_enter ((gsize *) & id)) {
95 _id = g_flags_register_static ("GstCCConverterCDPMode", values);
97 g_once_init_leave ((gsize *) & id, _id);
104 gst_cc_converter_transform_size (GstBaseTransform * base,
105 GstPadDirection direction,
106 GstCaps * caps, gsize size, GstCaps * othercaps, gsize * othersize)
108 /* We can't really convert from an output size to an input size */
109 if (direction != GST_PAD_SINK)
112 /* Assume worst-case here and over-allocate, and in ::transform() we then
113 * downsize the buffer as needed. The worst-case is one CDP packet, which
114 * can be up to MAX_CDP_PACKET_LEN bytes large */
116 *othersize = MAX_CDP_PACKET_LEN;
122 gst_cc_converter_transform_caps (GstBaseTransform * base,
123 GstPadDirection direction, GstCaps * caps, GstCaps * filter)
125 static GstStaticCaps non_cdp_caps =
126 GST_STATIC_CAPS ("closedcaption/x-cea-708, format=(string)cc_data; "
127 "closedcaption/x-cea-608,format=(string) s334-1a; "
128 "closedcaption/x-cea-608,format=(string) raw");
129 static GstStaticCaps cdp_caps =
130 GST_STATIC_CAPS ("closedcaption/x-cea-708, format=(string)cdp");
131 static GstStaticCaps cdp_caps_framerate =
132 GST_STATIC_CAPS ("closedcaption/x-cea-708, format=(string)cdp, "
133 "framerate=(fraction){60/1, 60000/1001, 50/1, 30/1, 30000/1001, 25/1, 24/1, 24000/1001}");
135 GstCCConverter *self = GST_CCCONVERTER (base);
137 GstCaps *res, *templ;
139 templ = gst_pad_get_pad_template_caps (base->srcpad);
141 GST_DEBUG_OBJECT (self, "direction %s from caps %" GST_PTR_FORMAT,
142 direction == GST_PAD_SRC ? "src" : "sink", caps);
144 res = gst_caps_new_empty ();
145 n = gst_caps_get_size (caps);
146 for (i = 0; i < n; i++) {
147 const GstStructure *s = gst_caps_get_structure (caps, i);
148 const GValue *framerate = gst_structure_get_value (s, "framerate");
150 if (gst_structure_has_name (s, "closedcaption/x-cea-608")) {
152 if (direction == GST_PAD_SRC) {
153 /* SRC direction: We produce upstream caps
155 * Downstream wanted CEA608 caps. If it had a framerate, we
156 * also need upstream to provide exactly that same framerate
157 * and otherwise we don't care.
159 * We can convert everything to CEA608.
161 res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps_framerate));
163 /* we can only keep the same framerate for non-cdp */
166 tmp = gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
167 gst_caps_set_value (tmp, "framerate", framerate);
168 res = gst_caps_merge (res, tmp);
170 res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
173 /* SINK: We produce downstream caps
175 * Upstream provided CEA608 caps. We can convert that to CDP if
176 * also a CDP compatible framerate was provided, and we can convert
177 * it to anything else regardless.
179 * If upstream provided a framerate we can pass that through, possibly
180 * filtered for the CDP case.
186 /* Create caps that contain the intersection of all framerates with
187 * the CDP allowed framerates */
189 gst_caps_make_writable (gst_static_caps_get
190 (&cdp_caps_framerate));
191 t = gst_caps_get_structure (tmp, 0);
192 gst_structure_set_name (t, "closedcaption/x-cea-608");
193 gst_structure_remove_field (t, "format");
194 if (gst_structure_can_intersect (s, t)) {
195 gst_caps_unref (tmp);
198 gst_caps_make_writable (gst_static_caps_get
199 (&cdp_caps_framerate));
201 res = gst_caps_merge (res, tmp);
203 gst_caps_unref (tmp);
205 /* And we can convert to everything else with the given framerate */
206 tmp = gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
207 gst_caps_set_value (tmp, "framerate", framerate);
208 res = gst_caps_merge (res, tmp);
210 res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
213 } else if (gst_structure_has_name (s, "closedcaption/x-cea-708")) {
214 if (direction == GST_PAD_SRC) {
215 /* SRC direction: We produce upstream caps
217 * Downstream wanted CEA708 caps. If downstream wants *only* CDP we
218 * either need CDP from upstream, or anything else with a CDP
220 * If downstream also wants non-CDP we can accept anything.
222 * We pass through any framerate as-is, except for filtering
223 * for CDP framerates if downstream wants only CDP.
226 if (g_strcmp0 (gst_structure_get_string (s, "format"), "cdp") == 0) {
227 /* Downstream wants only CDP */
229 /* We need CDP from upstream in that case */
230 res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps_framerate));
232 /* Or anything else with a CDP framerate */
236 const GValue *cdp_framerate;
238 /* Create caps that contain the intersection of all framerates with
239 * the CDP allowed framerates */
241 gst_caps_make_writable (gst_static_caps_get
242 (&cdp_caps_framerate));
243 t = gst_caps_get_structure (tmp, 0);
245 /* There's an intersection between the framerates so we can convert
246 * into CDP with exactly those framerates from anything else */
247 cdp_framerate = gst_structure_get_value (t, "framerate");
248 tmp = gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
249 gst_caps_set_value (tmp, "framerate", cdp_framerate);
250 res = gst_caps_merge (res, tmp);
252 GstCaps *tmp, *cdp_caps;
253 const GValue *cdp_framerate;
255 /* Get all CDP framerates, we can accept anything that has those
257 cdp_caps = gst_static_caps_get (&cdp_caps_framerate);
259 gst_structure_get_value (gst_caps_get_structure (cdp_caps, 0),
262 tmp = gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
263 gst_caps_set_value (tmp, "framerate", cdp_framerate);
264 gst_caps_unref (cdp_caps);
266 res = gst_caps_merge (res, tmp);
269 /* Downstream wants not only CDP, we can do everything */
270 res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps_framerate));
272 /* we can only keep the same framerate for non-cdp */
275 tmp = gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
276 gst_caps_set_value (tmp, "framerate", framerate);
277 res = gst_caps_merge (res, tmp);
279 res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
285 /* SINK: We produce downstream caps
287 * Upstream provided CEA708 caps. If upstream provided CDP we can
288 * output CDP, no matter what (-> passthrough). If upstream did not
289 * provide CDP, we can output CDP only if the framerate fits.
290 * We can always produce everything else apart from CDP.
292 * If upstream provided a framerate we pass that through for non-CDP
293 * output, and pass it through filtered for CDP output.
296 if (gst_structure_can_intersect (s,
297 gst_caps_get_structure (gst_static_caps_get (&cdp_caps), 0))) {
298 /* Upstream provided CDP caps, we can do everything independent of
300 res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps_framerate));
301 } else if (framerate) {
302 const GValue *cdp_framerate;
305 /* Upstream did not provide CDP. We can only do CDP if upstream
306 * happened to have a CDP framerate */
308 /* Create caps that contain the intersection of all framerates with
309 * the CDP allowed framerates */
311 gst_caps_make_writable (gst_static_caps_get
312 (&cdp_caps_framerate));
313 t = gst_caps_get_structure (tmp, 0);
315 /* There's an intersection between the framerates so we can convert
316 * into CDP with exactly those framerates */
317 cdp_framerate = gst_structure_get_value (t, "framerate");
318 if (gst_value_intersect (NULL, cdp_framerate, framerate)) {
319 gst_caps_set_value (tmp, "framerate", cdp_framerate);
321 res = gst_caps_merge (res, tmp);
323 gst_clear_caps (&tmp);
326 /* We can always convert CEA708 to all non-CDP formats */
328 /* we can only keep the same framerate for non-cdp */
331 tmp = gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
332 gst_caps_set_value (tmp, "framerate", framerate);
333 res = gst_caps_merge (res, tmp);
335 res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
339 g_assert_not_reached ();
343 GST_DEBUG_OBJECT (self, "pre filter caps %" GST_PTR_FORMAT, res);
345 /* We can convert anything into anything but it might involve loss of
346 * information so always filter according to the order in our template caps
350 filter = gst_caps_intersect_full (templ, filter, GST_CAPS_INTERSECT_FIRST);
352 tmp = gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
353 gst_caps_unref (res);
354 gst_caps_unref (filter);
358 gst_caps_unref (templ);
360 GST_DEBUG_OBJECT (self, "Transformed in direction %s caps %" GST_PTR_FORMAT,
361 direction == GST_PAD_SRC ? "src" : "sink", caps);
362 GST_DEBUG_OBJECT (self, "filter %" GST_PTR_FORMAT, filter);
363 GST_DEBUG_OBJECT (self, "to %" GST_PTR_FORMAT, res);
369 gst_cc_converter_fixate_caps (GstBaseTransform * base,
370 GstPadDirection direction, GstCaps * incaps, GstCaps * outcaps)
372 GstCCConverter *self = GST_CCCONVERTER (base);
373 const GstStructure *s;
375 const GValue *framerate;
376 GstCaps *intersection, *templ;
378 GST_DEBUG_OBJECT (self, "Fixating in direction %s incaps %" GST_PTR_FORMAT,
379 direction == GST_PAD_SRC ? "src" : "sink", incaps);
380 GST_DEBUG_OBJECT (self, "and outcaps %" GST_PTR_FORMAT, outcaps);
382 /* Prefer passthrough if we can */
383 if (gst_caps_is_subset (incaps, outcaps)) {
384 gst_caps_unref (outcaps);
385 return GST_BASE_TRANSFORM_CLASS (parent_class)->fixate_caps (base,
386 direction, incaps, gst_caps_ref (incaps));
389 /* Otherwise prefer caps in the order of our template caps */
390 templ = gst_pad_get_pad_template_caps (base->srcpad);
392 gst_caps_intersect_full (templ, outcaps, GST_CAPS_INTERSECT_FIRST);
393 gst_caps_unref (outcaps);
394 outcaps = intersection;
397 GST_BASE_TRANSFORM_CLASS (parent_class)->fixate_caps (base, direction,
400 s = gst_caps_get_structure (incaps, 0);
401 framerate = gst_structure_get_value (s, "framerate");
402 outcaps = gst_caps_make_writable (outcaps);
403 t = gst_caps_get_structure (outcaps, 0);
405 /* remove any output framerate that might've been added by basetransform
406 * due to intersecting with downstream */
407 gst_structure_remove_field (t, "framerate");
409 /* or passthrough the input framerate if possible */
412 n = gst_value_get_fraction_numerator (framerate);
413 d = gst_value_get_fraction_denominator (framerate);
415 if (gst_structure_has_field (t, "framerate"))
416 gst_structure_fixate_field_nearest_fraction (t, "framerate", n, d);
418 gst_structure_set (t, "framerate", GST_TYPE_FRACTION, n, d, NULL);
421 GST_DEBUG_OBJECT (self,
422 "Fixated caps %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT, incaps, outcaps);
428 gst_cc_converter_set_caps (GstBaseTransform * base, GstCaps * incaps,
431 GstCCConverter *self = GST_CCCONVERTER (base);
432 const GstStructure *s;
433 gboolean passthrough;
435 self->input_caption_type = gst_video_caption_type_from_caps (incaps);
436 self->output_caption_type = gst_video_caption_type_from_caps (outcaps);
438 if (self->input_caption_type == GST_VIDEO_CAPTION_TYPE_UNKNOWN ||
439 self->output_caption_type == GST_VIDEO_CAPTION_TYPE_UNKNOWN)
442 s = gst_caps_get_structure (incaps, 0);
443 if (!gst_structure_get_fraction (s, "framerate", &self->in_fps_n,
445 self->in_fps_n = self->in_fps_d = 0;
447 s = gst_caps_get_structure (outcaps, 0);
448 if (!gst_structure_get_fraction (s, "framerate", &self->out_fps_n,
450 self->out_fps_n = self->out_fps_d = 0;
452 gst_video_time_code_clear (&self->current_output_timecode);
454 /* Caps can be different but we can passthrough as long as they can
455 * intersect, i.e. have same caps name and format */
456 passthrough = gst_caps_can_intersect (incaps, outcaps);
457 gst_base_transform_set_passthrough (base, passthrough);
459 GST_DEBUG_OBJECT (self,
460 "Got caps %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT " (passthrough %d)",
461 incaps, outcaps, passthrough);
467 GST_ERROR_OBJECT (self,
468 "Invalid caps: in %" GST_PTR_FORMAT " out: %" GST_PTR_FORMAT, incaps,
480 guint max_cea608_count;
483 static const struct cdp_fps_entry cdp_fps_table[] = {
484 {0x1f, 24000, 1001, 25, 22, 3 /* FIXME: alternating max cea608 count! */ },
485 {0x2f, 24, 1, 25, 22, 2},
486 {0x3f, 25, 1, 24, 22, 2},
487 {0x4f, 30000, 1001, 20, 18, 2},
488 {0x5f, 30, 1, 20, 18, 2},
489 {0x6f, 50, 1, 12, 11, 1},
490 {0x7f, 60000, 1001, 10, 9, 1},
491 {0x8f, 60, 1, 10, 9, 1},
493 static const struct cdp_fps_entry null_fps_entry = { 0, 0, 0, 0 };
495 static const struct cdp_fps_entry *
496 cdp_fps_entry_from_id (guint8 id)
499 for (i = 0; i < G_N_ELEMENTS (cdp_fps_table); i++) {
500 if (cdp_fps_table[i].fps_idx == id)
501 return &cdp_fps_table[i];
503 return &null_fps_entry;
506 static const struct cdp_fps_entry *
507 cdp_fps_entry_from_fps (guint fps_n, guint fps_d)
510 for (i = 0; i < G_N_ELEMENTS (cdp_fps_table); i++) {
511 if (cdp_fps_table[i].fps_n == fps_n && cdp_fps_table[i].fps_d == fps_d)
512 return &cdp_fps_table[i];
514 return &null_fps_entry;
518 get_framerate_output_scale (GstCCConverter * self,
519 const struct cdp_fps_entry *in_fps_entry, gint * scale_n, gint * scale_d)
521 if (self->in_fps_n == 0 || self->out_fps_d == 0) {
527 /* compute the relative rates of the two framerates */
528 if (!gst_util_fraction_multiply (in_fps_entry->fps_d, in_fps_entry->fps_n,
529 self->out_fps_n, self->out_fps_d, scale_n, scale_d))
530 /* we should never overflow */
531 g_assert_not_reached ();
535 interpolate_time_code_with_framerate (GstCCConverter * self,
536 const GstVideoTimeCode * tc, gint out_fps_n, gint out_fps_d,
537 gint scale_n, gint scale_d, GstVideoTimeCode * out)
540 gint output_n, output_d;
542 GstVideoTimeCodeFlags flags;
544 g_return_val_if_fail (tc != NULL, FALSE);
545 g_return_val_if_fail (out != NULL, FALSE);
546 /* out_n/d can only be 0 if scale_n/d are 1/1 */
547 g_return_val_if_fail ((scale_n == 1 && scale_d == 1) || (out_fps_n != 0
548 && out_fps_d != 0), FALSE);
550 if (!tc || tc->config.fps_n == 0)
553 if (!gst_util_fraction_multiply (tc->frames, 1, scale_n, scale_d, &output_n,
555 /* we should never overflow */
556 g_assert_not_reached ();
558 tc_str = gst_video_time_code_to_string (tc);
559 GST_TRACE_OBJECT (self, "interpolating time code %s with scale %d/%d "
560 "to frame %d/%d", tc_str, scale_n, scale_d, output_n, output_d);
563 if (out_fps_n == 0 || out_fps_d == 0) {
564 out_fps_n = tc->config.fps_n;
565 out_fps_d = tc->config.fps_d;
568 flags = tc->config.flags;
569 if ((flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) != 0 && out_fps_d != 1001
570 && out_fps_n != 60000 && out_fps_n != 30000) {
571 flags &= ~GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
572 } else if ((flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) == 0
573 && out_fps_d == 1001 && (out_fps_n == 60000 || out_fps_n == 30000)) {
574 /* XXX: theoretically, not quite correct however this is an assumption
575 * we have elsewhere that these framerates are always drop-framed */
576 flags |= GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
579 output_frame = output_n / output_d;
581 *out = (GstVideoTimeCode) GST_VIDEO_TIME_CODE_INIT;
583 /* here we try to find the next available valid timecode. The dropped
584 * (when they exist) frames in time codes are that the beginning of each
586 gst_video_time_code_clear (out);
587 gst_video_time_code_init (out, out_fps_n, out_fps_d,
588 tc->config.latest_daily_jam, flags, tc->hours, tc->minutes,
589 tc->seconds, output_frame, tc->field_count);
591 } while ((flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) != 0
592 && output_frame < 10 && !gst_video_time_code_is_valid (out));
594 tc_str = gst_video_time_code_to_string (out);
595 GST_TRACE_OBJECT (self, "interpolated to %s", tc_str);
601 /* remove padding bytes from a cc_data packet. Returns the length of the new
602 * data in @cc_data */
604 compact_cc_data (guint8 * cc_data, guint cc_data_len)
606 gboolean started_ccp = FALSE;
610 if (cc_data_len % 3 != 0) {
611 GST_WARNING ("Invalid cc_data buffer size");
612 cc_data_len = cc_data_len - (cc_data_len % 3);
615 for (i = 0; i < cc_data_len / 3; i++) {
616 gboolean cc_valid = (cc_data[i * 3] & 0x04) == 0x04;
617 guint8 cc_type = cc_data[i * 3] & 0x03;
619 if (!started_ccp && (cc_type == 0x00 || cc_type == 0x01)) {
621 /* copy over valid 608 data */
622 cc_data[out_len++] = cc_data[i * 3];
623 cc_data[out_len++] = cc_data[i * 3 + 1];
624 cc_data[out_len++] = cc_data[i * 3 + 2];
635 if (cc_type == 0x00 || cc_type == 0x01) {
636 GST_WARNING ("Invalid cc_data. cea608 bytes after cea708");
640 cc_data[out_len++] = cc_data[i * 3];
641 cc_data[out_len++] = cc_data[i * 3 + 1];
642 cc_data[out_len++] = cc_data[i * 3 + 2];
645 GST_LOG ("compacted cc_data from %u to %u", cc_data_len, out_len);
651 cc_data_extract_cea608 (guint8 * cc_data, guint cc_data_len,
652 guint8 * cea608_field1, guint * cea608_field1_len,
653 guint8 * cea608_field2, guint * cea608_field2_len)
655 guint i, field_1_len = 0, field_2_len = 0;
657 if (cea608_field1_len) {
658 field_1_len = *cea608_field1_len;
659 *cea608_field1_len = 0;
661 if (cea608_field2_len) {
662 field_2_len = *cea608_field2_len;
663 *cea608_field2_len = 0;
666 if (cc_data_len % 3 != 0) {
667 GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple "
668 "of 3", cc_data_len);
669 cc_data_len = cc_data_len - (cc_data_len % 3);
672 for (i = 0; i < cc_data_len / 3; i++) {
673 guint8 byte0 = cc_data[i * 3 + 0];
674 guint8 byte1 = cc_data[i * 3 + 1];
675 guint8 byte2 = cc_data[i * 3 + 2];
676 gboolean cc_valid = (byte0 & 0x04) == 0x04;
677 guint8 cc_type = byte0 & 0x03;
679 GST_TRACE ("0x%02x 0x%02x 0x%02x, valid: %u, type: 0b%u%u", byte0, byte1,
680 byte2, cc_valid, (cc_type & 0x2) == 0x2, (cc_type & 0x1) == 0x1);
682 if (cc_type == 0x00) {
686 if (cea608_field1 && cea608_field1_len) {
687 if (*cea608_field1_len + 2 > field_1_len) {
688 GST_WARNING ("Too many cea608 input bytes %u for field 1",
689 *cea608_field1_len + 2);
693 if (byte1 != 0x80 || byte2 != 0x80) {
694 cea608_field1[(*cea608_field1_len)++] = byte1;
695 cea608_field1[(*cea608_field1_len)++] = byte2;
698 } else if (cc_type == 0x01) {
702 if (cea608_field2 && cea608_field2_len) {
703 if (*cea608_field2_len + 2 > field_2_len) {
704 GST_WARNING ("Too many cea608 input bytes %u for field 2",
705 *cea608_field2_len + 2);
708 if (byte1 != 0x80 || byte2 != 0x80) {
709 cea608_field2[(*cea608_field2_len)++] = byte1;
710 cea608_field2[(*cea608_field2_len)++] = byte2;
714 /* all cea608 packets must be at the beginning of a cc_data */
719 g_assert_cmpint (i * 3, <=, cc_data_len);
721 GST_LOG ("Extracted cea608-1 of length %u and cea608-2 of length %u",
722 VAL_OR_0 (cea608_field1_len), VAL_OR_0 (cea608_field2_len));
728 store_cc_data (GstCCConverter * self, const guint8 * ccp_data,
729 guint ccp_data_len, const guint8 * cea608_1, guint cea608_1_len,
730 const guint8 * cea608_2, guint cea608_2_len)
732 GST_TRACE_OBJECT (self, "attempting to hold data of len ccp:%u, cea608 1:%u, "
733 "cea608 2:%u until next output buffer", ccp_data_len, cea608_1_len,
736 if (ccp_data && ccp_data_len > 0) {
737 if (ccp_data_len > sizeof (self->scratch_ccp)) {
738 GST_ELEMENT_WARNING (self, STREAM, DECODE,
739 ("Closed Caption internal buffer overun. Dropping data"),
740 ("CCP scratch buffer requires space for %u bytes but only %"
741 G_GSIZE_FORMAT " bytes are available", ccp_data_len,
742 sizeof (self->scratch_ccp)));
743 self->scratch_ccp_len = 0;
745 memcpy (self->scratch_ccp, ccp_data, ccp_data_len);
746 self->scratch_ccp_len = ccp_data_len;
749 self->scratch_ccp_len = 0;
751 g_assert_cmpint (self->scratch_ccp_len, <=, sizeof (self->scratch_ccp));
753 if (cea608_1 && cea608_1_len > 0) {
754 if (cea608_1_len > sizeof (self->scratch_cea608_1)) {
755 GST_ELEMENT_WARNING (self, STREAM, DECODE,
756 ("Closed Caption internal buffer overun. Dropping data"),
757 ("CEA608 field 1 scratch buffer requires space for %u bytes but "
758 "only %" G_GSIZE_FORMAT " bytes are available", cea608_1_len,
759 sizeof (self->scratch_cea608_1_len)));
760 self->scratch_cea608_1_len = 0;
762 memcpy (self->scratch_cea608_1, cea608_1, cea608_1_len);
763 self->scratch_cea608_1_len = cea608_1_len;
766 self->scratch_cea608_1_len = 0;
768 g_assert_cmpint (self->scratch_cea608_1_len, <=,
769 sizeof (self->scratch_cea608_1));
771 if (cea608_2 && cea608_2_len > 0) {
772 if (cea608_2_len > sizeof (self->scratch_cea608_2)) {
773 GST_ELEMENT_WARNING (self, STREAM, DECODE,
774 ("Closed Caption internal buffer overun. Dropping data"),
775 ("CEA608 field 2 scratch buffer requires space for %u bytes but "
776 "only %" G_GSIZE_FORMAT " bytes are available", cea608_2_len,
777 sizeof (self->scratch_cea608_2_len)));
778 self->scratch_cea608_2_len = 0;
780 memcpy (self->scratch_cea608_2, cea608_2, cea608_2_len);
781 self->scratch_cea608_2_len = cea608_2_len;
784 self->scratch_cea608_2_len = 0;
786 g_assert_cmpint (self->scratch_cea608_2_len, <=,
787 sizeof (self->scratch_cea608_2));
789 GST_DEBUG_OBJECT (self, "holding data of len ccp:%u, cea608 1:%u, "
790 "cea608 2:%u until next output buffer", self->scratch_ccp_len,
791 self->scratch_cea608_1_len, self->scratch_cea608_2_len);
795 write_cea608 (GstCCConverter * self, gboolean pad_cea608,
796 const struct cdp_fps_entry *out_fps_entry, const guint8 * cea608_1,
797 guint cea608_1_len, const guint8 * cea608_2, guint cea608_2_len,
798 guint8 * out, guint * out_size, gboolean * is_last_cea608_field1)
800 guint i = 0, out_i = 0, max_size = 0, cea608_1_i = 0, cea608_2_i = 0;
801 guint cea608_output_count;
802 guint total_cea608_1_count, total_cea608_2_count;
803 gboolean write_cea608_field2_first = FALSE;
804 gboolean wrote_field1_last = FALSE;
805 gboolean wrote_first = FALSE;
809 g_assert (!cea608_1 || cea608_1_len % 2 == 0);
810 g_assert (!cea608_2 || cea608_2_len % 2 == 0);
811 g_assert (cea608_1 || cea608_2);
813 if (is_last_cea608_field1)
814 write_cea608_field2_first = *is_last_cea608_field1;
819 /* FIXME: if cea608 field 2 is generated, field 1 needs to be generated,
820 * However that is not possible for 60fps (where only one cea608 field fits)
821 * without adding previous output buffer tracking */
822 g_assert_cmpint (cea608_1_len >= cea608_2_len);
824 g_assert_cmpint (cea608_1_len + cea608_2_len, <=,
825 out_fps_entry->max_cea608_count);
828 /* FIXME: if cea608 field 2 is generated, field 1 needs to be generated. */
829 if (cea608_1_len < cea608_2_len)
830 total_cea608_1_count += cea608_2_len - cea608_1_len;
834 max_size = out_fps_entry->max_cea608_count * 3;
836 max_size = (cea608_1_len + cea608_2_len) * 3;
838 if (*out_size < max_size) {
839 GST_WARNING_OBJECT (self, "Output data too small (%u < %u) for cea608 data",
840 *out_size, max_size);
844 /* FIXME: interlacing, tff, rff, ensuring cea608 field1 is generated if
845 * field2 exists even across packets */
847 total_cea608_1_count = cea608_1_len;
848 total_cea608_2_count = cea608_2_len;
849 cea608_output_count = cea608_1_len + cea608_2_len;
851 guint max_cea608_count = out_fps_entry->max_cea608_count;
853 total_cea608_1_count = max_cea608_count / 2;
854 total_cea608_2_count = max_cea608_count / 2;
856 if (total_cea608_1_count + total_cea608_2_count < max_cea608_count) {
857 if (write_cea608_field2_first) {
858 total_cea608_2_count++;
860 total_cea608_1_count++;
864 cea608_output_count = total_cea608_1_count + total_cea608_2_count;
867 GST_LOG ("writing %u cea608-1 fields and %u cea608-2 fields",
868 total_cea608_1_count, total_cea608_2_count);
869 g_assert_cmpint (total_cea608_1_count + total_cea608_2_count, <=,
870 out_fps_entry->max_cea608_count);
872 wrote_first = !write_cea608_field2_first;
873 while (cea608_1_i + cea608_2_i < cea608_output_count) {
875 if (cea608_1_i < cea608_1_len) {
877 out[out_i++] = cea608_1[cea608_1_i * 2];
878 out[out_i++] = cea608_1[cea608_1_i * 2 + 1];
881 wrote_field1_last = TRUE;
882 } else if (cea608_1_i < total_cea608_1_count) {
888 wrote_field1_last = TRUE;
892 if (cea608_2_i < cea608_2_len) {
894 out[out_i++] = cea608_2[cea608_2_i * 2];
895 out[out_i++] = cea608_2[cea608_2_i * 2 + 1];
898 wrote_field1_last = FALSE;
899 } else if (cea608_2_i < total_cea608_2_count) {
905 wrote_field1_last = FALSE;
911 g_assert_cmpint (out_i / 3, <=, out_fps_entry->max_cea608_count);
914 if (is_last_cea608_field1)
915 *is_last_cea608_field1 = wrote_field1_last;
921 combine_cc_data (GstCCConverter * self, gboolean pad_cea608,
922 const struct cdp_fps_entry *out_fps_entry, const guint8 * ccp_data,
923 guint ccp_data_len, const guint8 * cea608_1, guint cea608_1_len,
924 const guint8 * cea608_2, guint cea608_2_len, guint8 * out, guint * out_size,
925 gboolean * last_cea608_is_field1)
927 guint out_i = 0, max_size = 0;
928 guint cea608_size = *out_size;
932 g_assert (!ccp_data || ccp_data_len % 3 == 0);
934 if (cea608_1 || cea608_2) {
935 if (!write_cea608 (self, pad_cea608, out_fps_entry, cea608_1, cea608_1_len,
936 cea608_2, cea608_2_len, out, &cea608_size, last_cea608_is_field1))
942 max_size = ccp_data_len + cea608_size;
943 if (*out_size < max_size) {
944 GST_WARNING_OBJECT (self, "Output data too small (%u < %u)", *out_size,
949 *out_size = cea608_size;
952 memcpy (&out[out_i], ccp_data, ccp_data_len);
953 *out_size += ccp_data_len;
956 g_assert_cmpint (*out_size, <, MAX_CDP_PACKET_LEN);
961 /* takes cc_data cea608_1, cea608_2 and attempts to fit it into a hypothetical
962 * output packet. Any leftover data is stored for later addition. Returns
963 * whether any output can be generated. @ccp_data_len, @cea608_1_len,
964 * @cea608_2_len are also updated to reflect the size of that data to add to
965 * the output packet */
967 fit_and_scale_cc_data (GstCCConverter * self,
968 const struct cdp_fps_entry *in_fps_entry,
969 const struct cdp_fps_entry *out_fps_entry, const guint8 * ccp_data,
970 guint * ccp_data_len, const guint8 * cea608_1, guint * cea608_1_len,
971 const guint8 * cea608_2, guint * cea608_2_len, const GstVideoTimeCode * tc,
972 gboolean use_cea608_field2_first)
974 if (!in_fps_entry || in_fps_entry->fps_n == 0) {
975 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
976 if (!in_fps_entry || in_fps_entry->fps_n == 0)
977 g_assert_not_reached ();
980 /* This is slightly looser than checking for the exact framerate as the cdp
981 * spec allow for 0.1% difference between framerates to be considered equal */
982 if (in_fps_entry->max_cc_count == out_fps_entry->max_cc_count) {
983 if (tc && tc->config.fps_n != 0)
984 interpolate_time_code_with_framerate (self, tc, out_fps_entry->fps_n,
985 out_fps_entry->fps_d, 1, 1, &self->current_output_timecode);
987 self->scratch_ccp_len = 0;
988 self->scratch_cea608_1_len = 0;
989 self->scratch_cea608_2_len = 0;
990 self->input_frames = 0;
991 self->output_frames = 0;
993 int input_frame_n, input_frame_d, output_frame_n, output_frame_d;
994 int output_time_cmp, scale_n, scale_d, rate_cmp;
996 /* TODO: handle input discont */
998 /* compute the relative frame count for each */
999 if (!gst_util_fraction_multiply (self->in_fps_d, self->in_fps_n,
1000 self->input_frames, 1, &input_frame_n, &input_frame_d))
1001 /* we should never overflow */
1002 g_assert_not_reached ();
1004 if (!gst_util_fraction_multiply (self->out_fps_d, self->out_fps_n,
1005 self->output_frames, 1, &output_frame_n, &output_frame_d))
1006 /* we should never overflow */
1007 g_assert_not_reached ();
1009 output_time_cmp = gst_util_fraction_compare (input_frame_n, input_frame_d,
1010 output_frame_n, output_frame_d);
1012 /* compute the relative rates of the two framerates */
1013 get_framerate_output_scale (self, in_fps_entry, &scale_n, &scale_d);
1015 rate_cmp = gst_util_fraction_compare (scale_n, scale_d, 1, 1);
1017 GST_TRACE_OBJECT (self, "performing framerate conversion at scale %d/%d "
1018 "of cc data of with sizes, ccp:%u, cea608-1:%u, cea608-2:%u", scale_n,
1019 scale_d, VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
1020 VAL_OR_0 (cea608_2_len));
1022 if (rate_cmp == 0) {
1023 /* we are not scaling. Should never happen with current conditions
1025 g_assert_not_reached ();
1026 } else if (output_time_cmp < 0) {
1027 /* we can't generate an output yet */
1028 guint cd_len = ccp_data_len ? *ccp_data_len : 0;
1029 guint c1_len = cea608_1_len ? *cea608_1_len : 0;
1030 guint c2_len = cea608_2_len ? *cea608_2_len : 0;
1032 store_cc_data (self, ccp_data, cd_len, cea608_1, c1_len, cea608_2,
1041 } else if (rate_cmp != 0) {
1042 /* we are changing the framerate and may overflow the max output packet
1043 * size. Split them where necessary. */
1044 gint extra_ccp = 0, extra_cea608_1 = 0, extra_cea608_2 = 0;
1045 gint ccp_off = 0, cea608_1_off = 0, cea608_2_off = 0;
1046 gboolean wrote_first = FALSE;
1047 gint field2_padding = 0;
1049 if (output_time_cmp == 0) {
1050 /* we have completed a cycle and can reset our counters to avoid
1051 * overflow. Anything that fits into the output packet will be written */
1052 GST_LOG_OBJECT (self, "cycle completed, resetting frame counters");
1053 self->scratch_ccp_len = 0;
1054 self->scratch_cea608_1_len = 0;
1055 self->scratch_cea608_2_len = 0;
1056 self->input_frames = 0;
1057 self->output_frames = 0;
1061 extra_ccp = *ccp_data_len - 3 * out_fps_entry->max_ccp_count;
1062 extra_ccp = MAX (0, extra_ccp);
1063 ccp_off = *ccp_data_len - extra_ccp;
1066 extra_cea608_1 = VAL_OR_0 (cea608_1_len);
1067 extra_cea608_2 = VAL_OR_0 (cea608_2_len);
1069 wrote_first = !use_cea608_field2_first;
1070 /* try to push data into the packets. Anything 'extra' will be
1071 * stored for later */
1072 while (extra_cea608_1 > 0 || extra_cea608_2 > 0) {
1073 gint avail_1, avail_2;
1075 avail_1 = VAL_OR_0 (cea608_1_len) - extra_cea608_1;
1076 avail_2 = VAL_OR_0 (cea608_2_len) - extra_cea608_2;
1077 if (avail_1 + avail_2 + field2_padding >=
1078 2 * out_fps_entry->max_cea608_count)
1081 if (wrote_first && extra_cea608_1 > 0) {
1082 extra_cea608_1 -= 2;
1083 g_assert_cmpint (extra_cea608_1, >=, 0);
1085 g_assert_cmpint (cea608_1_off, <=, *cea608_1_len);
1088 avail_1 = VAL_OR_0 (cea608_1_len) - extra_cea608_1;
1089 avail_2 = VAL_OR_0 (cea608_2_len) - extra_cea608_2;
1090 if (avail_1 + avail_2 + field2_padding >=
1091 2 * out_fps_entry->max_cea608_count)
1094 if (extra_cea608_2 > 0) {
1095 extra_cea608_2 -= 2;
1096 g_assert_cmpint (extra_cea608_2, >=, 0);
1098 g_assert_cmpint (cea608_2_off, <=, *cea608_2_len);
1099 } else if (!wrote_first) {
1100 /* we need to insert field 2 padding if we don't have data and are
1101 * requested to start with field2 */
1102 field2_padding += 2;
1107 GST_TRACE_OBJECT (self, "allocated sizes ccp:%u, cea608-1:%u, "
1108 "cea608-2:%u", ccp_off, cea608_1_off, cea608_2_off);
1110 if (extra_ccp > 0 || extra_cea608_1 > 0 || extra_cea608_2 > 0) {
1111 /* packet would overflow, push extra bytes into the next packet */
1112 GST_DEBUG_OBJECT (self, "buffer would overflow by %u ccp bytes, "
1113 "%u cea608 field 1 bytes, or %u cea608 field 2 bytes", extra_ccp,
1114 extra_cea608_1, extra_cea608_2);
1115 store_cc_data (self, &ccp_data[ccp_off], extra_ccp,
1116 &cea608_1[cea608_1_off], extra_cea608_1, &cea608_2[cea608_2_off],
1119 *ccp_data_len = MIN (*ccp_data_len, ccp_off);
1121 *cea608_1_len = MIN (*cea608_1_len, cea608_1_off);
1123 *cea608_2_len = MIN (*cea608_2_len, cea608_2_off);
1125 GST_DEBUG_OBJECT (self, "section sizes of %u ccp bytes, "
1126 "%u cea608 field 1 bytes, and %u cea608 field 2 bytes fit within "
1127 "output packet", VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
1128 VAL_OR_0 (cea608_2_len));
1129 self->scratch_ccp_len = 0;
1130 self->scratch_cea608_1_len = 0;
1131 self->scratch_cea608_2_len = 0;
1134 g_assert_not_reached ();
1137 if (tc && tc->config.fps_n != 0)
1138 interpolate_time_code_with_framerate (self, tc, out_fps_entry->fps_n,
1139 out_fps_entry->fps_d, scale_n, scale_d,
1140 &self->current_output_timecode);
1143 g_assert_cmpint (VAL_OR_0 (ccp_data_len) + (VAL_OR_0 (cea608_1_len) +
1144 VAL_OR_0 (cea608_2_len)) / 2 * 3, <=,
1145 3 * out_fps_entry->max_cc_count);
1147 GST_DEBUG_OBJECT (self, "write out packet with lengths ccp:%u, cea608-1:%u, "
1148 "cea608-2:%u", VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
1149 VAL_OR_0 (cea608_2_len));
1154 /* Converts raw CEA708 cc_data and an optional timecode into CDP */
1156 convert_cea708_cc_data_cea708_cdp_internal (GstCCConverter * self,
1157 const guint8 * cc_data, guint cc_data_len, guint8 * cdp, guint cdp_len,
1158 const GstVideoTimeCode * tc, const struct cdp_fps_entry *fps_entry)
1161 guint8 flags, checksum;
1164 GST_DEBUG_OBJECT (self, "writing out cdp packet from cc_data with length %u",
1167 gst_byte_writer_init_with_data (&bw, cdp, cdp_len, FALSE);
1168 gst_byte_writer_put_uint16_be_unchecked (&bw, 0x9669);
1169 /* Write a length of 0 for now */
1170 gst_byte_writer_put_uint8_unchecked (&bw, 0);
1172 gst_byte_writer_put_uint8_unchecked (&bw, fps_entry->fps_idx);
1174 if (cc_data_len / 3 > fps_entry->max_cc_count) {
1175 GST_WARNING_OBJECT (self, "Too many cc_data triplets for framerate: %u. "
1176 "Truncating to %u", cc_data_len / 3, fps_entry->max_cc_count);
1177 cc_data_len = 3 * fps_entry->max_cc_count;
1180 /* caption_service_active */
1183 /* ccdata_present */
1184 if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_CC_DATA))
1187 /* time_code_present */
1188 if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_TIME_CODE) && tc
1189 && tc->config.fps_n > 0)
1195 gst_byte_writer_put_uint8_unchecked (&bw, flags);
1197 gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
1199 if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_TIME_CODE) && tc
1200 && tc->config.fps_n > 0) {
1203 gst_byte_writer_put_uint8_unchecked (&bw, 0x71);
1204 /* reserved 11 - 2 bits */
1206 /* tens of hours - 2 bits */
1207 u8 |= ((tc->hours / 10) & 0x3) << 4;
1208 /* units of hours - 4 bits */
1209 u8 |= (tc->hours % 10) & 0xf;
1210 gst_byte_writer_put_uint8_unchecked (&bw, u8);
1212 /* reserved 1 - 1 bit */
1214 /* tens of minutes - 3 bits */
1215 u8 |= ((tc->minutes / 10) & 0x7) << 4;
1216 /* units of minutes - 4 bits */
1217 u8 |= (tc->minutes % 10) & 0xf;
1218 gst_byte_writer_put_uint8_unchecked (&bw, u8);
1220 /* field flag - 1 bit */
1221 u8 = tc->field_count < 2 ? 0x00 : 0x80;
1222 /* tens of seconds - 3 bits */
1223 u8 |= ((tc->seconds / 10) & 0x7) << 4;
1224 /* units of seconds - 4 bits */
1225 u8 |= (tc->seconds % 10) & 0xf;
1226 gst_byte_writer_put_uint8_unchecked (&bw, u8);
1228 /* drop frame flag - 1 bit */
1229 u8 = (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) ? 0x80 :
1231 /* reserved0 - 1 bit */
1232 /* tens of frames - 2 bits */
1233 u8 |= ((tc->frames / 10) & 0x3) << 4;
1234 /* units of frames 4 bits */
1235 u8 |= (tc->frames % 10) & 0xf;
1236 gst_byte_writer_put_uint8_unchecked (&bw, u8);
1239 if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_CC_DATA)) {
1240 gst_byte_writer_put_uint8_unchecked (&bw, 0x72);
1241 gst_byte_writer_put_uint8_unchecked (&bw, 0xe0 | fps_entry->max_cc_count);
1242 gst_byte_writer_put_data_unchecked (&bw, cc_data, cc_data_len);
1243 while (fps_entry->max_cc_count > cc_data_len / 3) {
1244 gst_byte_writer_put_uint8_unchecked (&bw, 0xfa);
1245 gst_byte_writer_put_uint8_unchecked (&bw, 0x00);
1246 gst_byte_writer_put_uint8_unchecked (&bw, 0x00);
1251 gst_byte_writer_put_uint8_unchecked (&bw, 0x74);
1252 gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
1253 self->cdp_hdr_sequence_cntr++;
1254 /* We calculate the checksum afterwards */
1255 gst_byte_writer_put_uint8_unchecked (&bw, 0);
1257 len = gst_byte_writer_get_pos (&bw);
1258 gst_byte_writer_set_pos (&bw, 2);
1259 gst_byte_writer_put_uint8_unchecked (&bw, len);
1262 for (i = 0; i < len; i++) {
1266 checksum = 256 - checksum;
1267 cdp[len - 1] = checksum;
1272 /* Converts CDP into raw CEA708 cc_data */
1274 convert_cea708_cdp_cea708_cc_data_internal (GstCCConverter * self,
1275 const guint8 * cdp, guint cdp_len, guint8 cc_data[MAX_CDP_PACKET_LEN],
1276 GstVideoTimeCode * tc, const struct cdp_fps_entry **out_fps_entry)
1283 const struct cdp_fps_entry *fps_entry;
1285 *out_fps_entry = &null_fps_entry;
1286 memset (tc, 0, sizeof (*tc));
1288 /* Header + footer length */
1290 GST_WARNING_OBJECT (self, "cdp packet too short (%u). expected at "
1291 "least %u", cdp_len, 11);
1295 gst_byte_reader_init (&br, cdp, cdp_len);
1296 u16 = gst_byte_reader_get_uint16_be_unchecked (&br);
1297 if (u16 != 0x9669) {
1298 GST_WARNING_OBJECT (self, "cdp packet does not have initial magic bytes "
1303 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1304 if (u8 != cdp_len) {
1305 GST_WARNING_OBJECT (self, "cdp packet length (%u) does not match passed "
1306 "in value (%u)", u8, cdp_len);
1310 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1311 fps_entry = cdp_fps_entry_from_id (u8);
1312 if (!fps_entry || fps_entry->fps_n == 0) {
1313 GST_WARNING_OBJECT (self, "cdp packet does not have a valid framerate "
1318 flags = gst_byte_reader_get_uint8_unchecked (&br);
1320 if ((flags & 0x40) == 0) {
1321 GST_DEBUG_OBJECT (self, "cdp packet does have any cc_data");
1325 /* cdp_hdr_sequence_cntr */
1326 gst_byte_reader_skip_unchecked (&br, 2);
1328 /* time_code_present */
1330 guint8 hours, minutes, seconds, frames, fields;
1331 gboolean drop_frame;
1333 if (gst_byte_reader_get_remaining (&br) < 5) {
1334 GST_WARNING_OBJECT (self, "cdp packet does not have enough data to "
1335 "contain a timecode (%u). Need at least 5 bytes",
1336 gst_byte_reader_get_remaining (&br));
1339 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1341 GST_WARNING_OBJECT (self, "cdp packet does not have timecode start byte "
1342 "of 0x71, found 0x%02x", u8);
1346 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1347 if ((u8 & 0xc0) != 0xc0) {
1348 GST_WARNING_OBJECT (self, "reserved bits are not 0xc0, found 0x%02x", u8);
1352 hours = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
1354 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1355 if ((u8 & 0x80) != 0x80) {
1356 GST_WARNING_OBJECT (self, "reserved bit is not 0x80, found 0x%02x", u8);
1359 minutes = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
1361 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1366 seconds = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
1368 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1370 GST_WARNING_OBJECT (self, "reserved bit is not 0x0, found 0x%02x", u8);
1374 drop_frame = ! !(u8 & 0x80);
1375 frames = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
1377 gst_video_time_code_init (tc, fps_entry->fps_n, fps_entry->fps_d, NULL,
1378 drop_frame ? GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME :
1379 GST_VIDEO_TIME_CODE_FLAGS_NONE, hours, minutes, seconds, frames,
1383 /* ccdata_present */
1387 if (gst_byte_reader_get_remaining (&br) < 2) {
1388 GST_WARNING_OBJECT (self, "not enough data to contain valid cc_data");
1391 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1393 GST_WARNING_OBJECT (self, "missing cc_data start code of 0x72, "
1394 "found 0x%02x", u8);
1398 cc_count = gst_byte_reader_get_uint8_unchecked (&br);
1399 if ((cc_count & 0xe0) != 0xe0) {
1400 GST_WARNING_OBJECT (self, "reserved bits are not 0xe0, found 0x%02x", u8);
1406 if (gst_byte_reader_get_remaining (&br) < len) {
1407 GST_WARNING_OBJECT (self, "not enough bytes (%u) left for the number of "
1408 "byte triples (%u)", gst_byte_reader_get_remaining (&br), cc_count);
1412 memcpy (cc_data, gst_byte_reader_get_data_unchecked (&br, len), len);
1415 *out_fps_entry = fps_entry;
1417 /* skip everything else we don't care about */
1422 copy_from_stored_data (GstCCConverter * self, guint8 * out_ccp,
1423 guint * ccp_size, guint8 * cea608_1, guint * cea608_1_len,
1424 guint8 * cea608_2, guint * cea608_2_len)
1426 guint ccp_in_size = 0, cea608_1_in_size = 0, cea608_2_in_size = 0;
1428 g_assert ((out_ccp && ccp_size) || (!out_ccp && !ccp_size));
1429 g_assert ((cea608_1 && cea608_1_len) || (!cea608_1 && !cea608_1_len));
1430 g_assert ((cea608_2 && cea608_2_len) || (!cea608_2 && !cea608_2_len));
1433 ccp_in_size = *ccp_size;
1437 cea608_1_in_size = *cea608_1_len;
1441 cea608_2_in_size = *cea608_2_len;
1445 if (out_ccp && self->scratch_ccp_len > 0) {
1446 GST_DEBUG_OBJECT (self, "copying from previous scratch ccp buffer of "
1447 "%u bytes", self->scratch_ccp_len);
1448 if (ccp_in_size < *ccp_size + self->scratch_ccp_len) {
1449 GST_WARNING_OBJECT (self, "output buffer too small %u < %u", ccp_in_size,
1450 *ccp_size + self->scratch_ccp_len);
1453 memcpy (&out_ccp[*ccp_size], self->scratch_ccp, self->scratch_ccp_len);
1454 *ccp_size += self->scratch_ccp_len;
1457 if (cea608_1 && self->scratch_cea608_1_len > 0) {
1458 GST_DEBUG_OBJECT (self, "copying from previous scratch cea608 field 1 "
1459 "buffer of %u bytes", self->scratch_cea608_1_len);
1460 if (cea608_1_in_size < *cea608_1_len + self->scratch_cea608_1_len) {
1461 GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
1462 cea608_1_in_size, *cea608_1_len + self->scratch_cea608_1_len);
1465 memcpy (&cea608_1[*cea608_1_len], self->scratch_cea608_1,
1466 self->scratch_cea608_1_len);
1467 *cea608_1_len += self->scratch_cea608_1_len;
1470 if (cea608_2 && self->scratch_cea608_2_len > 0) {
1471 GST_DEBUG_OBJECT (self, "copying from previous scratch cea608 field 2 "
1472 "buffer of %u bytes", self->scratch_cea608_2_len);
1473 if (cea608_2_in_size < *cea608_2_len + self->scratch_cea608_2_len) {
1474 GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
1475 cea608_2_in_size, *cea608_2_len + self->scratch_cea608_2_len);
1478 memcpy (&cea608_2[*cea608_2_len], self->scratch_cea608_2,
1479 self->scratch_cea608_2_len);
1480 *cea608_2_len += self->scratch_cea608_2_len;
1496 cc_data_to_cea608_ccp (GstCCConverter * self, guint8 * cc_data,
1497 guint cc_data_len, guint8 * out_ccp, guint * ccp_size, guint8 * cea608_1,
1498 guint * cea608_1_len, guint8 * cea608_2, guint * cea608_2_len,
1499 const struct cdp_fps_entry *in_fps_entry)
1501 guint ccp_in_size = 0, cea608_1_in_size = 0, cea608_2_in_size = 0;
1503 g_assert (cc_data || cc_data_len == 0);
1506 ccp_in_size = *ccp_size;
1508 cea608_1_in_size = *cea608_1_len;
1510 cea608_2_in_size = *cea608_2_len;
1512 if (!copy_from_stored_data (self, out_ccp, ccp_size, cea608_1, cea608_1_len,
1513 cea608_2, cea608_2_len))
1517 gint ccp_offset = 0;
1518 guint new_cea608_1_len = 0, new_cea608_2_len = 0;
1519 guint8 *new_cea608_1 = cea608_1, *new_cea608_2 = cea608_2;
1522 new_cea608_1_len = cea608_1_in_size - *cea608_1_len;
1523 new_cea608_1 = &cea608_1[*cea608_1_len];
1526 new_cea608_2_len = cea608_2_in_size - *cea608_2_len;
1527 new_cea608_2 = &cea608_2[*cea608_2_len];
1530 cc_data_len = compact_cc_data (cc_data, cc_data_len);
1532 if (cc_data_len / 3 > in_fps_entry->max_cc_count) {
1533 GST_WARNING_OBJECT (self, "Too many cc_data triples in CDP packet %u. "
1534 "Truncating to %u", cc_data_len / 3, in_fps_entry->max_cc_count);
1535 cc_data_len = 3 * in_fps_entry->max_cc_count;
1538 ccp_offset = cc_data_extract_cea608 (cc_data, cc_data_len, new_cea608_1,
1539 &new_cea608_1_len, new_cea608_2, &new_cea608_2_len);
1540 if (ccp_offset < 0) {
1541 GST_WARNING_OBJECT (self, "Failed to extract cea608 from cc_data");
1545 if ((new_cea608_1_len + new_cea608_2_len) / 2 >
1546 in_fps_entry->max_cea608_count) {
1547 GST_WARNING_OBJECT (self, "Too many cea608 triples in CDP packet %u. "
1548 "Truncating to %u", (new_cea608_1_len + new_cea608_2_len) / 2,
1549 in_fps_entry->max_cea608_count);
1550 if ((new_cea608_1_len + new_cea608_2_len) / 2 >
1551 in_fps_entry->max_cea608_count) {
1552 new_cea608_1_len = 2 * in_fps_entry->max_cea608_count;
1553 new_cea608_2_len = 0;
1556 2 * in_fps_entry->max_cea608_count - new_cea608_1_len;
1561 *cea608_1_len += new_cea608_1_len;
1563 *cea608_2_len += new_cea608_2_len;
1566 if (ccp_in_size < *ccp_size + cc_data_len - ccp_offset) {
1567 GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
1568 ccp_in_size, *ccp_size + cc_data_len - ccp_offset);
1571 memcpy (&out_ccp[*ccp_size], &cc_data[ccp_offset],
1572 cc_data_len - ccp_offset);
1573 *ccp_size += cc_data_len - ccp_offset;
1590 cdp_to_cea608_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1591 guint8 * out_ccp, guint * ccp_size, guint8 * cea608_1, guint * cea608_1_len,
1592 guint8 * cea608_2, guint * cea608_2_len, GstVideoTimeCode * out_tc,
1593 const struct cdp_fps_entry **in_fps_entry)
1595 guint8 cc_data[MAX_CDP_PACKET_LEN];
1596 guint cc_data_len = 0;
1600 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1603 convert_cea708_cdp_cea708_cc_data_internal (self, in.data, in.size,
1604 cc_data, out_tc, in_fps_entry);
1606 gst_buffer_unmap (inbuf, &in);
1607 self->input_frames++;
1610 return cc_data_to_cea608_ccp (self, inbuf ? cc_data : NULL, cc_data_len,
1611 out_ccp, ccp_size, cea608_1, cea608_1_len, cea608_2, cea608_2_len,
1612 inbuf ? *in_fps_entry : NULL);
1615 static GstFlowReturn
1616 convert_cea608_raw_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
1622 n = gst_buffer_get_size (inbuf);
1624 GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
1625 gst_buffer_set_size (outbuf, 0);
1632 GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u. Truncating to %u", n,
1637 gst_buffer_set_size (outbuf, 3 * n);
1639 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1640 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1642 /* We have to assume that each value is from the first field and
1643 * don't know from which line offset it originally is */
1644 for (i = 0; i < n; i++) {
1645 out.data[i * 3] = 0x80;
1646 out.data[i * 3 + 1] = in.data[i * 2];
1647 out.data[i * 3 + 2] = in.data[i * 2 + 1];
1650 gst_buffer_unmap (inbuf, &in);
1651 gst_buffer_unmap (outbuf, &out);
1656 static GstFlowReturn
1657 convert_cea608_raw_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1663 n = gst_buffer_get_size (inbuf);
1665 GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
1666 gst_buffer_set_size (outbuf, 0);
1673 GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u. Truncating to %u", n,
1678 gst_buffer_set_size (outbuf, 3 * n);
1680 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1681 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1683 /* We have to assume that each value is from the first field and
1684 * don't know from which line offset it originally is */
1685 for (i = 0; i < n; i++) {
1686 out.data[i * 3] = 0xfc;
1687 out.data[i * 3 + 1] = in.data[i * 2];
1688 out.data[i * 3 + 2] = in.data[i * 2 + 1];
1691 gst_buffer_unmap (inbuf, &in);
1692 gst_buffer_unmap (outbuf, &out);
1697 static GstFlowReturn
1698 convert_cea608_raw_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
1699 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
1702 const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
1703 guint cc_data_len = MAX_CDP_PACKET_LEN;
1704 guint cea608_1_len = MAX_CDP_PACKET_LEN;
1705 guint8 cc_data[MAX_CDP_PACKET_LEN], cea608_1[MAX_CEA608_LEN];
1707 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
1708 if (!in_fps_entry || in_fps_entry->fps_n == 0)
1709 g_assert_not_reached ();
1711 if (!copy_from_stored_data (self, NULL, 0, cea608_1, &cea608_1_len, NULL, 0))
1717 n = gst_buffer_get_size (inbuf);
1719 GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
1720 gst_buffer_set_size (outbuf, 0);
1726 if (n > in_fps_entry->max_cea608_count) {
1727 GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u. Truncating to %u",
1728 n, in_fps_entry->max_cea608_count);
1729 n = in_fps_entry->max_cea608_count;
1732 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1733 for (i = 0; i < n; i++) {
1734 guint byte1 = in.data[i * 2 + 0];
1735 guint byte2 = in.data[i * 2 + 1];
1736 if (byte1 != 0x80 || byte2 != 0x80) {
1737 cea608_1[cea608_1_len++] = byte1;
1738 cea608_1[cea608_1_len++] = byte2;
1741 gst_buffer_unmap (inbuf, &in);
1742 self->input_frames++;
1745 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
1746 if (!out_fps_entry || out_fps_entry->fps_n == 0)
1747 g_assert_not_reached ();
1749 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
1750 cea608_1, &cea608_1_len, NULL, 0, tc_meta ? &tc_meta->tc : NULL,
1754 if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
1755 cea608_1_len, NULL, 0, cc_data, &cc_data_len,
1756 &self->last_cea608_written_was_field1))
1759 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1761 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
1762 out.data, out.size, &self->current_output_timecode, out_fps_entry);
1763 self->output_frames++;
1764 gst_buffer_unmap (outbuf, &out);
1767 gst_buffer_set_size (outbuf, cc_data_len);
1776 static GstFlowReturn
1777 convert_cea608_s334_1a_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
1784 n = gst_buffer_get_size (inbuf);
1786 GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
1793 GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
1797 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1798 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1800 for (i = 0; i < n; i++) {
1801 if (in.data[i * 3] & 0x80) {
1802 out.data[i * 2] = in.data[i * 3 + 1];
1803 out.data[i * 2 + 1] = in.data[i * 3 + 2];
1808 gst_buffer_unmap (inbuf, &in);
1809 gst_buffer_unmap (outbuf, &out);
1811 gst_buffer_set_size (outbuf, 2 * cea608);
1816 static GstFlowReturn
1817 convert_cea608_s334_1a_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1823 n = gst_buffer_get_size (inbuf);
1825 GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
1832 GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
1836 gst_buffer_set_size (outbuf, 3 * n);
1838 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1839 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1841 for (i = 0; i < n; i++) {
1842 out.data[i * 3] = (in.data[i * 3] & 0x80) ? 0xfc : 0xfd;
1843 out.data[i * 3 + 1] = in.data[i * 3 + 1];
1844 out.data[i * 3 + 2] = in.data[i * 3 + 2];
1847 gst_buffer_unmap (inbuf, &in);
1848 gst_buffer_unmap (outbuf, &out);
1853 static GstFlowReturn
1854 convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
1855 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
1858 const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
1859 guint cc_data_len = MAX_CDP_PACKET_LEN;
1860 guint cea608_1_len = MAX_CDP_PACKET_LEN, cea608_2_len = MAX_CDP_PACKET_LEN;
1861 guint8 cc_data[MAX_CDP_PACKET_LEN];
1862 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
1865 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
1866 if (!in_fps_entry || in_fps_entry->fps_n == 0)
1867 g_assert_not_reached ();
1869 if (!copy_from_stored_data (self, NULL, 0, cea608_1, &cea608_1_len,
1870 cea608_2, &cea608_2_len))
1874 n = gst_buffer_get_size (inbuf);
1876 GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
1882 if (n > in_fps_entry->max_cea608_count) {
1883 GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
1884 n = in_fps_entry->max_cea608_count;
1887 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1889 for (i = 0; i < n; i++) {
1890 guint byte1 = in.data[i * 3 + 1];
1891 guint byte2 = in.data[i * 3 + 2];
1893 if (in.data[i * 3] & 0x80) {
1894 if (byte1 != 0x80 || byte2 != 0x80) {
1895 cea608_1[cea608_1_len++] = byte1;
1896 cea608_1[cea608_1_len++] = byte2;
1899 if (byte1 != 0x80 || byte2 != 0x80) {
1900 cea608_2[cea608_2_len++] = byte1;
1901 cea608_2[cea608_2_len++] = byte2;
1905 gst_buffer_unmap (inbuf, &in);
1906 self->input_frames++;
1909 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
1910 if (!out_fps_entry || out_fps_entry->fps_n == 0)
1911 g_assert_not_reached ();
1913 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
1914 cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
1915 tc_meta ? &tc_meta->tc : NULL,
1916 self->last_cea608_written_was_field1)) {
1920 if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
1921 cea608_1_len, cea608_2, cea608_2_len, cc_data, &cc_data_len,
1922 &self->last_cea608_written_was_field1)) {
1926 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1928 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
1929 out.data, out.size, &self->current_output_timecode, out_fps_entry);
1930 self->output_frames++;
1931 gst_buffer_unmap (outbuf, &out);
1934 gst_buffer_set_size (outbuf, cc_data_len);
1943 static GstFlowReturn
1944 convert_cea708_cc_data_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
1951 n = gst_buffer_get_size (inbuf);
1953 GST_WARNING_OBJECT (self, "Invalid raw CEA708 buffer size");
1960 GST_WARNING_OBJECT (self, "Too many CEA708 triplets %u", n);
1964 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1965 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1967 for (i = 0; i < n; i++) {
1968 /* We can only really copy the first field here as there can't be any
1969 * signalling in raw CEA608 and we must not mix the streams of different
1972 if (in.data[i * 3] == 0xfc) {
1973 out.data[cea608 * 2] = in.data[i * 3 + 1];
1974 out.data[cea608 * 2 + 1] = in.data[i * 3 + 2];
1979 gst_buffer_unmap (inbuf, &in);
1980 gst_buffer_unmap (outbuf, &out);
1982 gst_buffer_set_size (outbuf, 2 * cea608);
1987 static GstFlowReturn
1988 convert_cea708_cc_data_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
1995 n = gst_buffer_get_size (inbuf);
1997 GST_WARNING_OBJECT (self, "Invalid raw CEA708 buffer size");
2004 GST_WARNING_OBJECT (self, "Too many CEA708 triplets %u", n);
2008 gst_buffer_map (inbuf, &in, GST_MAP_READ);
2009 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2011 for (i = 0; i < n; i++) {
2012 if (in.data[i * 3] == 0xfc || in.data[i * 3] == 0xfd) {
2013 /* We have to assume a line offset of 0 */
2014 out.data[cea608 * 3] = in.data[i * 3] == 0xfc ? 0x80 : 0x00;
2015 out.data[cea608 * 3 + 1] = in.data[i * 3 + 1];
2016 out.data[cea608 * 3 + 2] = in.data[i * 3 + 2];
2021 gst_buffer_unmap (inbuf, &in);
2022 gst_buffer_unmap (outbuf, &out);
2024 gst_buffer_set_size (outbuf, 3 * cea608);
2029 static GstFlowReturn
2030 convert_cea708_cc_data_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
2031 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2034 const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
2035 guint in_cc_data_len;
2036 guint cc_data_len = MAX_CDP_PACKET_LEN, ccp_data_len = MAX_CDP_PACKET_LEN;
2037 guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2038 guint8 cc_data[MAX_CDP_PACKET_LEN], ccp_data[MAX_CDP_PACKET_LEN];
2039 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2043 gst_buffer_map (inbuf, &in, GST_MAP_READ);
2044 in_cc_data = in.data;
2045 in_cc_data_len = in.size;
2046 self->input_frames++;
2052 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
2053 if (!in_fps_entry || in_fps_entry->fps_n == 0)
2054 g_assert_not_reached ();
2056 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2057 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2058 g_assert_not_reached ();
2060 if (!cc_data_to_cea608_ccp (self, in_cc_data, in_cc_data_len, ccp_data,
2061 &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
2064 gst_buffer_unmap (inbuf, &in);
2069 gst_buffer_unmap (inbuf, &in);
2071 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
2072 &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
2073 tc_meta ? &tc_meta->tc : NULL, self->last_cea608_written_was_field1))
2076 if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
2077 cea608_1, cea608_1_len, cea608_2, cea608_2_len, cc_data,
2078 &cc_data_len, &self->last_cea608_written_was_field1))
2081 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2083 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
2084 out.data, out.size, &self->current_output_timecode, out_fps_entry);
2085 self->output_frames++;
2086 gst_buffer_unmap (outbuf, &out);
2089 gst_buffer_set_size (outbuf, cc_data_len);
2098 static GstFlowReturn
2099 convert_cea708_cdp_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
2100 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2103 GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2104 guint8 cea608_1[MAX_CEA608_LEN];
2105 guint cea608_1_len = MAX_CEA608_LEN;
2106 const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2108 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2109 if (!cdp_to_cea608_cc_data (self, inbuf, NULL, NULL, cea608_1, &cea608_1_len,
2110 NULL, NULL, &tc, &in_fps_entry)) {
2111 gst_buffer_set_size (outbuf, 0);
2115 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2116 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2117 out_fps_entry = in_fps_entry;
2119 if (fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
2120 cea608_1, &cea608_1_len, NULL, NULL, &tc, FALSE)) {
2121 guint i, out_size = (guint) out.size;
2123 self->output_frames++;
2124 if (!write_cea608 (self, TRUE, out_fps_entry, cea608_1, cea608_1_len, NULL,
2125 0, out.data, &out_size, NULL)) {
2126 gst_buffer_unmap (outbuf, &out);
2127 return GST_FLOW_ERROR;
2130 /* remove the first byte from each cea608 packet */
2131 for (i = 0; i < out_size / 3; i++) {
2132 out.data[i * 2 + 0] = out.data[i * 3 + 1];
2133 out.data[i * 2 + 1] = out.data[i * 3 + 2];
2135 cea608_1_len = out_size / 3 * 2;
2139 gst_buffer_unmap (outbuf, &out);
2141 gst_buffer_set_size (outbuf, cea608_1_len);
2143 if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
2144 gst_buffer_add_video_time_code_meta (outbuf,
2145 &self->current_output_timecode);
2146 gst_video_time_code_increment_frame (&self->current_output_timecode);
2152 static GstFlowReturn
2153 convert_cea708_cdp_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
2154 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2157 GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2158 const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2159 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2160 guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2161 guint i, cc_data_len;
2163 if (!cdp_to_cea608_cc_data (self, inbuf, NULL, NULL, cea608_1, &cea608_1_len,
2164 cea608_2, &cea608_2_len, &tc, &in_fps_entry))
2167 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2168 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2169 out_fps_entry = in_fps_entry;
2171 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
2172 cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
2173 self->last_cea608_written_was_field1))
2176 cc_data_len = gst_buffer_get_sizes (outbuf, NULL, NULL);
2178 gst_buffer_map (outbuf, &out, GST_MAP_READWRITE);
2179 if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
2180 cea608_1_len, cea608_2, cea608_2_len, out.data, &cc_data_len,
2181 &self->last_cea608_written_was_field1)) {
2182 gst_buffer_unmap (outbuf, &out);
2186 for (i = 0; i < cc_data_len / 3; i++) {
2187 guint byte = out.data[i * 3];
2188 /* We have to assume a line offset of 0 */
2189 out.data[i * 3] = (byte == 0xfc || byte == 0xf8) ? 0x80 : 0x00;
2192 gst_buffer_unmap (outbuf, &out);
2193 self->output_frames++;
2195 gst_buffer_set_size (outbuf, cc_data_len);
2197 if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
2198 gst_buffer_add_video_time_code_meta (outbuf,
2199 &self->current_output_timecode);
2200 gst_video_time_code_increment_frame (&self->current_output_timecode);
2206 gst_buffer_set_size (outbuf, 0);
2210 static GstFlowReturn
2211 convert_cea708_cdp_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
2212 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2215 GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2216 const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2217 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2218 guint8 ccp_data[MAX_CDP_PACKET_LEN];
2219 guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2220 guint ccp_data_len = MAX_CDP_PACKET_LEN;
2223 if (!cdp_to_cea608_cc_data (self, inbuf, ccp_data, &ccp_data_len,
2224 cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc, &in_fps_entry))
2227 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2228 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2229 out_fps_entry = in_fps_entry;
2231 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
2232 &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
2233 self->last_cea608_written_was_field1))
2236 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2237 out_len = (guint) out.size;
2238 if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
2239 cea608_1, cea608_1_len, cea608_2, cea608_2_len, out.data, &out_len,
2240 &self->last_cea608_written_was_field1)) {
2241 gst_buffer_unmap (outbuf, &out);
2246 gst_buffer_unmap (outbuf, &out);
2247 self->output_frames++;
2249 if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
2250 gst_buffer_add_video_time_code_meta (outbuf,
2251 &self->current_output_timecode);
2252 gst_video_time_code_increment_frame (&self->current_output_timecode);
2256 gst_buffer_set_size (outbuf, out_len);
2261 static GstFlowReturn
2262 convert_cea708_cdp_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
2266 GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2267 const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2268 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2269 guint8 ccp_data[MAX_CDP_PACKET_LEN], cc_data[MAX_CDP_PACKET_LEN];
2270 guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2271 guint ccp_data_len = MAX_CDP_PACKET_LEN, cc_data_len = MAX_CDP_PACKET_LEN;
2274 if (!cdp_to_cea608_cc_data (self, inbuf, ccp_data, &ccp_data_len,
2275 cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc, &in_fps_entry))
2278 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2279 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2280 out_fps_entry = in_fps_entry;
2282 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
2283 &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
2284 self->last_cea608_written_was_field1))
2287 if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
2288 cea608_1, cea608_1_len, cea608_2, cea608_2_len, cc_data,
2289 &cc_data_len, &self->last_cea608_written_was_field1)) {
2293 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2295 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
2296 out.data, out.size, &self->current_output_timecode, out_fps_entry);
2298 gst_buffer_unmap (outbuf, &out);
2299 self->output_frames++;
2302 gst_buffer_set_size (outbuf, out_len);
2307 static GstFlowReturn
2308 gst_cc_converter_transform (GstCCConverter * self, GstBuffer * inbuf,
2311 GstVideoTimeCodeMeta *tc_meta = NULL;
2312 GstFlowReturn ret = GST_FLOW_OK;
2314 GST_DEBUG_OBJECT (self, "Converting %" GST_PTR_FORMAT " from %u to %u", inbuf,
2315 self->input_caption_type, self->output_caption_type);
2318 tc_meta = gst_buffer_get_video_time_code_meta (inbuf);
2321 if (self->current_output_timecode.config.fps_n <= 0) {
2322 /* XXX: this assumes the input time codes are well-formed and increase
2323 * at the rate of one frame for each input buffer */
2324 const struct cdp_fps_entry *in_fps_entry;
2325 gint scale_n, scale_d;
2327 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
2328 if (!in_fps_entry || in_fps_entry->fps_n == 0)
2329 scale_n = scale_d = 1;
2331 get_framerate_output_scale (self, in_fps_entry, &scale_n, &scale_d);
2333 interpolate_time_code_with_framerate (self, &tc_meta->tc,
2334 self->out_fps_n, self->out_fps_d, scale_n, scale_d,
2335 &self->current_output_timecode);
2339 switch (self->input_caption_type) {
2340 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2342 switch (self->output_caption_type) {
2343 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2344 ret = convert_cea608_raw_cea608_s334_1a (self, inbuf, outbuf);
2346 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2347 ret = convert_cea608_raw_cea708_cc_data (self, inbuf, outbuf);
2349 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2350 ret = convert_cea608_raw_cea708_cdp (self, inbuf, outbuf, tc_meta);
2352 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2354 g_assert_not_reached ();
2359 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2361 switch (self->output_caption_type) {
2362 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2363 ret = convert_cea608_s334_1a_cea608_raw (self, inbuf, outbuf);
2365 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2366 ret = convert_cea608_s334_1a_cea708_cc_data (self, inbuf, outbuf);
2368 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2370 convert_cea608_s334_1a_cea708_cdp (self, inbuf, outbuf, tc_meta);
2372 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2374 g_assert_not_reached ();
2379 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2381 switch (self->output_caption_type) {
2382 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2383 ret = convert_cea708_cc_data_cea608_raw (self, inbuf, outbuf);
2385 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2386 ret = convert_cea708_cc_data_cea608_s334_1a (self, inbuf, outbuf);
2388 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2390 convert_cea708_cc_data_cea708_cdp (self, inbuf, outbuf, tc_meta);
2392 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2394 g_assert_not_reached ();
2399 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2401 switch (self->output_caption_type) {
2402 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2403 ret = convert_cea708_cdp_cea608_raw (self, inbuf, outbuf, tc_meta);
2405 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2407 convert_cea708_cdp_cea608_s334_1a (self, inbuf, outbuf, tc_meta);
2409 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2411 convert_cea708_cdp_cea708_cc_data (self, inbuf, outbuf, tc_meta);
2413 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2414 ret = convert_cea708_cdp_cea708_cdp (self, inbuf, outbuf);
2417 g_assert_not_reached ();
2423 g_assert_not_reached ();
2427 if (ret != GST_FLOW_OK) {
2428 GST_DEBUG_OBJECT (self, "returning %s", gst_flow_get_name (ret));
2432 GST_DEBUG_OBJECT (self, "Converted to %" GST_PTR_FORMAT, outbuf);
2434 if (gst_buffer_get_size (outbuf) > 0) {
2435 if (self->current_output_timecode.config.fps_n > 0) {
2436 gst_buffer_add_video_time_code_meta (outbuf,
2437 &self->current_output_timecode);
2438 gst_video_time_code_increment_frame (&self->current_output_timecode);
2448 gst_cc_converter_transform_meta (GstBaseTransform * base, GstBuffer * outbuf,
2449 GstMeta * meta, GstBuffer * inbuf)
2451 const GstMetaInfo *info = meta->info;
2453 /* we do this manually for framerate scaling */
2454 if (info->api == GST_VIDEO_TIME_CODE_META_API_TYPE)
2457 return GST_BASE_TRANSFORM_CLASS (parent_class)->transform_meta (base, outbuf,
2462 can_generate_output (GstCCConverter * self)
2464 int input_frame_n, input_frame_d, output_frame_n, output_frame_d;
2465 int output_time_cmp;
2467 if (self->in_fps_n == 0 || self->out_fps_n == 0)
2470 /* compute the relative frame count for each */
2471 if (!gst_util_fraction_multiply (self->in_fps_d, self->in_fps_n,
2472 self->input_frames, 1, &input_frame_n, &input_frame_d))
2473 /* we should never overflow */
2474 g_assert_not_reached ();
2476 if (!gst_util_fraction_multiply (self->out_fps_d, self->out_fps_n,
2477 self->output_frames, 1, &output_frame_n, &output_frame_d))
2478 /* we should never overflow */
2479 g_assert_not_reached ();
2481 output_time_cmp = gst_util_fraction_compare (input_frame_n, input_frame_d,
2482 output_frame_n, output_frame_d);
2484 /* if the next output frame is at or before the current input frame */
2485 if (output_time_cmp >= 0)
2492 reset_counters (GstCCConverter * self)
2494 self->scratch_ccp_len = 0;
2495 self->scratch_cea608_1_len = 0;
2496 self->scratch_cea608_2_len = 0;
2497 self->input_frames = 0;
2498 self->output_frames = 1;
2499 gst_video_time_code_clear (&self->current_output_timecode);
2500 gst_clear_buffer (&self->previous_buffer);
2501 self->last_cea608_written_was_field1 = FALSE;
2504 static GstFlowReturn
2505 drain_input (GstCCConverter * self)
2507 GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (self);
2508 GstBaseTransform *trans = GST_BASE_TRANSFORM (self);
2509 GstFlowReturn ret = GST_FLOW_OK;
2511 while (self->scratch_ccp_len > 0 || self->scratch_cea608_1_len > 0
2512 || self->scratch_cea608_2_len > 0 || can_generate_output (self)) {
2515 if (!self->previous_buffer) {
2516 GST_WARNING_OBJECT (self, "Attempt to draining without a previous "
2517 "buffer. Aborting");
2521 outbuf = gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
2523 if (bclass->copy_metadata) {
2524 if (!bclass->copy_metadata (trans, self->previous_buffer, outbuf)) {
2525 /* something failed, post a warning */
2526 GST_ELEMENT_WARNING (self, STREAM, NOT_IMPLEMENTED,
2527 ("could not copy metadata"), (NULL));
2531 ret = gst_cc_converter_transform (self, NULL, outbuf);
2532 if (gst_buffer_get_size (outbuf) <= 0) {
2533 /* try to move the output along */
2534 self->input_frames++;
2535 gst_buffer_unref (outbuf);
2537 } else if (ret != GST_FLOW_OK) {
2538 gst_buffer_unref (outbuf);
2542 ret = gst_pad_push (GST_BASE_TRANSFORM_SRC_PAD (trans), outbuf);
2543 if (ret != GST_FLOW_OK) {
2551 static GstFlowReturn
2552 gst_cc_converter_generate_output (GstBaseTransform * base, GstBuffer ** outbuf)
2554 GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (base);
2555 GstCCConverter *self = GST_CCCONVERTER (base);
2556 GstBuffer *inbuf = base->queued_buf;
2560 base->queued_buf = NULL;
2561 if (!inbuf && !can_generate_output (self)) {
2565 if (gst_base_transform_is_passthrough (base)) {
2569 if (inbuf && GST_BUFFER_IS_DISCONT (inbuf)) {
2570 ret = drain_input (self);
2571 reset_counters (self);
2572 if (ret != GST_FLOW_OK)
2576 *outbuf = gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
2577 if (*outbuf == NULL)
2581 gst_buffer_replace (&self->previous_buffer, inbuf);
2583 if (bclass->copy_metadata) {
2584 if (!bclass->copy_metadata (base, self->previous_buffer, *outbuf)) {
2585 /* something failed, post a warning */
2586 GST_ELEMENT_WARNING (self, STREAM, NOT_IMPLEMENTED,
2587 ("could not copy metadata"), (NULL));
2591 ret = gst_cc_converter_transform (self, inbuf, *outbuf);
2592 if (gst_buffer_get_size (*outbuf) <= 0) {
2593 gst_buffer_unref (*outbuf);
2599 gst_buffer_unref (inbuf);
2607 gst_buffer_unref (inbuf);
2609 GST_WARNING_OBJECT (self, "could not allocate buffer");
2610 return GST_FLOW_ERROR;
2615 gst_cc_converter_sink_event (GstBaseTransform * trans, GstEvent * event)
2617 GstCCConverter *self = GST_CCCONVERTER (trans);
2619 switch (GST_EVENT_TYPE (event)) {
2621 GST_DEBUG_OBJECT (self, "received EOS");
2626 case GST_EVENT_FLUSH_START:
2627 reset_counters (self);
2633 return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
2637 gst_cc_converter_start (GstBaseTransform * base)
2639 GstCCConverter *self = GST_CCCONVERTER (base);
2641 /* Resetting this is not really needed but makes debugging easier */
2642 self->cdp_hdr_sequence_cntr = 0;
2643 self->current_output_timecode = (GstVideoTimeCode) GST_VIDEO_TIME_CODE_INIT;
2644 reset_counters (self);
2650 gst_cc_converter_stop (GstBaseTransform * base)
2652 GstCCConverter *self = GST_CCCONVERTER (base);
2654 gst_video_time_code_clear (&self->current_output_timecode);
2655 gst_clear_buffer (&self->previous_buffer);
2661 gst_cc_converter_set_property (GObject * object, guint prop_id,
2662 const GValue * value, GParamSpec * pspec)
2664 GstCCConverter *filter = GST_CCCONVERTER (object);
2668 filter->cdp_mode = g_value_get_flags (value);
2671 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2677 gst_cc_converter_get_property (GObject * object, guint prop_id, GValue * value,
2680 GstCCConverter *filter = GST_CCCONVERTER (object);
2684 g_value_set_flags (value, filter->cdp_mode);
2687 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2693 gst_cc_converter_class_init (GstCCConverterClass * klass)
2695 GObjectClass *gobject_class;
2696 GstElementClass *gstelement_class;
2697 GstBaseTransformClass *basetransform_class;
2699 gobject_class = (GObjectClass *) klass;
2700 gstelement_class = (GstElementClass *) klass;
2701 basetransform_class = (GstBaseTransformClass *) klass;
2703 gobject_class->set_property = gst_cc_converter_set_property;
2704 gobject_class->get_property = gst_cc_converter_get_property;
2707 * GstCCConverter:cdp-mode
2709 * Only insert the selection sections into CEA 708 CDP packets.
2711 * Various software does not handle any other information than CC data
2712 * contained in CDP packets and might fail parsing the packets otherwise.
2716 g_object_class_install_property (G_OBJECT_CLASS (klass),
2717 PROP_CDP_MODE, g_param_spec_flags ("cdp-mode",
2719 "Select which CDP sections to store in CDP packets",
2720 GST_TYPE_CC_CONVERTER_CDP_MODE, DEFAULT_CDP_MODE,
2721 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2723 gst_element_class_set_static_metadata (gstelement_class,
2724 "Closed Caption Converter",
2725 "Filter/ClosedCaption",
2726 "Converts Closed Captions between different formats",
2727 "Sebastian Dröge <sebastian@centricular.com>");
2729 gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
2730 gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
2732 basetransform_class->start = GST_DEBUG_FUNCPTR (gst_cc_converter_start);
2733 basetransform_class->stop = GST_DEBUG_FUNCPTR (gst_cc_converter_stop);
2734 basetransform_class->sink_event =
2735 GST_DEBUG_FUNCPTR (gst_cc_converter_sink_event);
2736 basetransform_class->transform_size =
2737 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_size);
2738 basetransform_class->transform_caps =
2739 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_caps);
2740 basetransform_class->fixate_caps =
2741 GST_DEBUG_FUNCPTR (gst_cc_converter_fixate_caps);
2742 basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_cc_converter_set_caps);
2743 basetransform_class->transform_meta =
2744 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_meta);
2745 basetransform_class->generate_output =
2746 GST_DEBUG_FUNCPTR (gst_cc_converter_generate_output);
2747 basetransform_class->passthrough_on_same_caps = TRUE;
2749 GST_DEBUG_CATEGORY_INIT (gst_cc_converter_debug, "ccconverter",
2750 0, "Closed Caption converter");
2752 gst_type_mark_as_plugin_api (GST_TYPE_CC_CONVERTER_CDP_MODE, 0);
2756 gst_cc_converter_init (GstCCConverter * self)
2758 self->cdp_mode = DEFAULT_CDP_MODE;