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);
650 #define CC_DATA_EXTRACT_TOO_MANY_FIELD1 -2
651 #define CC_DATA_EXTRACT_TOO_MANY_FIELD2 -3
654 cc_data_extract_cea608 (guint8 * cc_data, guint cc_data_len,
655 guint8 * cea608_field1, guint * cea608_field1_len,
656 guint8 * cea608_field2, guint * cea608_field2_len)
658 guint i, field_1_len = 0, field_2_len = 0;
660 if (cea608_field1_len) {
661 field_1_len = *cea608_field1_len;
662 *cea608_field1_len = 0;
664 if (cea608_field2_len) {
665 field_2_len = *cea608_field2_len;
666 *cea608_field2_len = 0;
669 if (cc_data_len % 3 != 0) {
670 GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple "
671 "of 3", cc_data_len);
672 cc_data_len = cc_data_len - (cc_data_len % 3);
675 for (i = 0; i < cc_data_len / 3; i++) {
676 guint8 byte0 = cc_data[i * 3 + 0];
677 guint8 byte1 = cc_data[i * 3 + 1];
678 guint8 byte2 = cc_data[i * 3 + 2];
679 gboolean cc_valid = (byte0 & 0x04) == 0x04;
680 guint8 cc_type = byte0 & 0x03;
682 GST_TRACE ("0x%02x 0x%02x 0x%02x, valid: %u, type: 0b%u%u", byte0, byte1,
683 byte2, cc_valid, (cc_type & 0x2) == 0x2, (cc_type & 0x1) == 0x1);
685 if (cc_type == 0x00) {
689 if (cea608_field1 && cea608_field1_len) {
690 if (*cea608_field1_len + 2 > field_1_len) {
691 GST_WARNING ("Too many cea608 input bytes %u for field 1",
692 *cea608_field1_len + 2);
693 return CC_DATA_EXTRACT_TOO_MANY_FIELD1;
696 if (byte1 != 0x80 || byte2 != 0x80) {
697 cea608_field1[(*cea608_field1_len)++] = byte1;
698 cea608_field1[(*cea608_field1_len)++] = byte2;
701 } else if (cc_type == 0x01) {
705 if (cea608_field2 && cea608_field2_len) {
706 if (*cea608_field2_len + 2 > field_2_len) {
707 GST_WARNING ("Too many cea608 input bytes %u for field 2",
708 *cea608_field2_len + 2);
709 return CC_DATA_EXTRACT_TOO_MANY_FIELD2;
711 if (byte1 != 0x80 || byte2 != 0x80) {
712 cea608_field2[(*cea608_field2_len)++] = byte1;
713 cea608_field2[(*cea608_field2_len)++] = byte2;
717 /* all cea608 packets must be at the beginning of a cc_data */
722 g_assert_cmpint (i * 3, <=, cc_data_len);
724 GST_LOG ("Extracted cea608-1 of length %u and cea608-2 of length %u",
725 VAL_OR_0 (cea608_field1_len), VAL_OR_0 (cea608_field2_len));
731 store_cc_data (GstCCConverter * self, const guint8 * ccp_data,
732 guint ccp_data_len, const guint8 * cea608_1, guint cea608_1_len,
733 const guint8 * cea608_2, guint cea608_2_len)
735 GST_TRACE_OBJECT (self, "attempting to hold data of len ccp:%u, cea608 1:%u, "
736 "cea608 2:%u until next output buffer", ccp_data_len, cea608_1_len,
739 if (ccp_data && ccp_data_len > 0) {
740 if (ccp_data_len > sizeof (self->scratch_ccp)) {
741 GST_ELEMENT_WARNING (self, STREAM, DECODE,
742 ("Closed Caption internal buffer overun. Dropping data"),
743 ("CCP scratch buffer requires space for %u bytes but only %"
744 G_GSIZE_FORMAT " bytes are available", ccp_data_len,
745 sizeof (self->scratch_ccp)));
746 self->scratch_ccp_len = 0;
748 memcpy (self->scratch_ccp, ccp_data, ccp_data_len);
749 self->scratch_ccp_len = ccp_data_len;
752 self->scratch_ccp_len = 0;
754 g_assert_cmpint (self->scratch_ccp_len, <=, sizeof (self->scratch_ccp));
756 if (cea608_1 && cea608_1_len > 0) {
757 if (cea608_1_len > sizeof (self->scratch_cea608_1)) {
758 GST_ELEMENT_WARNING (self, STREAM, DECODE,
759 ("Closed Caption internal buffer overun. Dropping data"),
760 ("CEA608 field 1 scratch buffer requires space for %u bytes but "
761 "only %" G_GSIZE_FORMAT " bytes are available", cea608_1_len,
762 sizeof (self->scratch_cea608_1_len)));
763 self->scratch_cea608_1_len = 0;
765 memcpy (self->scratch_cea608_1, cea608_1, cea608_1_len);
766 self->scratch_cea608_1_len = cea608_1_len;
769 self->scratch_cea608_1_len = 0;
771 g_assert_cmpint (self->scratch_cea608_1_len, <=,
772 sizeof (self->scratch_cea608_1));
774 if (cea608_2 && cea608_2_len > 0) {
775 if (cea608_2_len > sizeof (self->scratch_cea608_2)) {
776 GST_ELEMENT_WARNING (self, STREAM, DECODE,
777 ("Closed Caption internal buffer overun. Dropping data"),
778 ("CEA608 field 2 scratch buffer requires space for %u bytes but "
779 "only %" G_GSIZE_FORMAT " bytes are available", cea608_2_len,
780 sizeof (self->scratch_cea608_2_len)));
781 self->scratch_cea608_2_len = 0;
783 memcpy (self->scratch_cea608_2, cea608_2, cea608_2_len);
784 self->scratch_cea608_2_len = cea608_2_len;
787 self->scratch_cea608_2_len = 0;
789 g_assert_cmpint (self->scratch_cea608_2_len, <=,
790 sizeof (self->scratch_cea608_2));
792 GST_DEBUG_OBJECT (self, "holding data of len ccp:%u, cea608 1:%u, "
793 "cea608 2:%u until next output buffer", self->scratch_ccp_len,
794 self->scratch_cea608_1_len, self->scratch_cea608_2_len);
798 write_cea608 (GstCCConverter * self, gboolean pad_cea608,
799 const struct cdp_fps_entry *out_fps_entry, const guint8 * cea608_1,
800 guint cea608_1_len, const guint8 * cea608_2, guint cea608_2_len,
801 guint8 * out, guint * out_size, gboolean * is_last_cea608_field1)
803 guint i = 0, out_i = 0, max_size = 0, cea608_1_i = 0, cea608_2_i = 0;
804 guint cea608_output_count;
805 guint total_cea608_1_count, total_cea608_2_count;
806 gboolean write_cea608_field2_first = FALSE;
807 gboolean wrote_field1_last = FALSE;
808 gboolean wrote_first = FALSE;
812 g_assert (!cea608_1 || cea608_1_len % 2 == 0);
813 g_assert (!cea608_2 || cea608_2_len % 2 == 0);
814 g_assert (cea608_1 || cea608_2);
816 if (is_last_cea608_field1)
817 write_cea608_field2_first = *is_last_cea608_field1;
822 /* FIXME: if cea608 field 2 is generated, field 1 needs to be generated,
823 * However that is not possible for 60fps (where only one cea608 field fits)
824 * without adding previous output buffer tracking */
825 g_assert_cmpint (cea608_1_len >= cea608_2_len);
827 g_assert_cmpint (cea608_1_len + cea608_2_len, <=,
828 out_fps_entry->max_cea608_count);
831 /* FIXME: if cea608 field 2 is generated, field 1 needs to be generated. */
832 if (cea608_1_len < cea608_2_len)
833 total_cea608_1_count += cea608_2_len - cea608_1_len;
837 max_size = out_fps_entry->max_cea608_count * 3;
839 max_size = (cea608_1_len + cea608_2_len) * 3;
841 if (*out_size < max_size) {
842 GST_WARNING_OBJECT (self, "Output data too small (%u < %u) for cea608 data",
843 *out_size, max_size);
847 /* FIXME: interlacing, tff, rff, ensuring cea608 field1 is generated if
848 * field2 exists even across packets */
850 total_cea608_1_count = cea608_1_len;
851 total_cea608_2_count = cea608_2_len;
852 cea608_output_count = cea608_1_len + cea608_2_len;
854 guint max_cea608_count = out_fps_entry->max_cea608_count;
856 total_cea608_1_count = max_cea608_count / 2;
857 total_cea608_2_count = max_cea608_count / 2;
859 if (total_cea608_1_count + total_cea608_2_count < max_cea608_count) {
860 if (write_cea608_field2_first) {
861 total_cea608_2_count++;
863 total_cea608_1_count++;
867 cea608_output_count = total_cea608_1_count + total_cea608_2_count;
870 GST_LOG ("writing %u cea608-1 fields and %u cea608-2 fields",
871 total_cea608_1_count, total_cea608_2_count);
872 g_assert_cmpint (total_cea608_1_count + total_cea608_2_count, <=,
873 out_fps_entry->max_cea608_count);
875 wrote_first = !write_cea608_field2_first;
876 while (cea608_1_i + cea608_2_i < cea608_output_count) {
878 if (cea608_1_i < cea608_1_len) {
880 out[out_i++] = cea608_1[cea608_1_i * 2];
881 out[out_i++] = cea608_1[cea608_1_i * 2 + 1];
884 wrote_field1_last = TRUE;
885 } else if (cea608_1_i < total_cea608_1_count) {
891 wrote_field1_last = TRUE;
895 if (cea608_2_i < cea608_2_len) {
897 out[out_i++] = cea608_2[cea608_2_i * 2];
898 out[out_i++] = cea608_2[cea608_2_i * 2 + 1];
901 wrote_field1_last = FALSE;
902 } else if (cea608_2_i < total_cea608_2_count) {
908 wrote_field1_last = FALSE;
914 g_assert_cmpint (out_i / 3, <=, out_fps_entry->max_cea608_count);
917 if (is_last_cea608_field1)
918 *is_last_cea608_field1 = wrote_field1_last;
924 combine_cc_data (GstCCConverter * self, gboolean pad_cea608,
925 const struct cdp_fps_entry *out_fps_entry, const guint8 * ccp_data,
926 guint ccp_data_len, const guint8 * cea608_1, guint cea608_1_len,
927 const guint8 * cea608_2, guint cea608_2_len, guint8 * out, guint * out_size,
928 gboolean * last_cea608_is_field1)
930 guint out_i = 0, max_size = 0;
931 guint cea608_size = *out_size;
935 g_assert (!ccp_data || ccp_data_len % 3 == 0);
937 if (cea608_1 || cea608_2) {
938 if (!write_cea608 (self, pad_cea608, out_fps_entry, cea608_1, cea608_1_len,
939 cea608_2, cea608_2_len, out, &cea608_size, last_cea608_is_field1))
945 max_size = ccp_data_len + cea608_size;
946 if (*out_size < max_size) {
947 GST_WARNING_OBJECT (self, "Output data too small (%u < %u)", *out_size,
952 *out_size = cea608_size;
955 memcpy (&out[out_i], ccp_data, ccp_data_len);
956 *out_size += ccp_data_len;
959 g_assert_cmpint (*out_size, <, MAX_CDP_PACKET_LEN);
964 /* takes cc_data cea608_1, cea608_2 and attempts to fit it into a hypothetical
965 * output packet. Any leftover data is stored for later addition. Returns
966 * whether any output can be generated. @ccp_data_len, @cea608_1_len,
967 * @cea608_2_len are also updated to reflect the size of that data to add to
968 * the output packet */
970 fit_and_scale_cc_data (GstCCConverter * self,
971 const struct cdp_fps_entry *in_fps_entry,
972 const struct cdp_fps_entry *out_fps_entry, const guint8 * ccp_data,
973 guint * ccp_data_len, const guint8 * cea608_1, guint * cea608_1_len,
974 const guint8 * cea608_2, guint * cea608_2_len, const GstVideoTimeCode * tc,
975 gboolean use_cea608_field2_first)
977 if (!in_fps_entry || in_fps_entry->fps_n == 0) {
978 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
979 if (!in_fps_entry || in_fps_entry->fps_n == 0)
980 g_assert_not_reached ();
983 /* This is slightly looser than checking for the exact framerate as the cdp
984 * spec allow for 0.1% difference between framerates to be considered equal */
986 int input_frame_n, input_frame_d, output_frame_n, output_frame_d;
987 int output_time_cmp, scale_n, scale_d;
989 /* TODO: handle input discont */
991 if (self->in_fps_n == 0) {
992 input_frame_n = self->input_frames;
995 /* compute the relative frame count for each */
996 if (!gst_util_fraction_multiply (self->in_fps_d, self->in_fps_n,
997 self->input_frames, 1, &input_frame_n, &input_frame_d))
998 /* we should never overflow */
999 g_assert_not_reached ();
1002 if (self->in_fps_n == 0) {
1003 output_frame_n = self->output_frames;
1006 if (!gst_util_fraction_multiply (self->out_fps_d, self->out_fps_n,
1007 self->output_frames, 1, &output_frame_n, &output_frame_d))
1008 /* we should never overflow */
1009 g_assert_not_reached ();
1012 output_time_cmp = gst_util_fraction_compare (input_frame_n, input_frame_d,
1013 output_frame_n, output_frame_d);
1015 /* compute the relative rates of the two framerates */
1016 get_framerate_output_scale (self, in_fps_entry, &scale_n, &scale_d);
1018 GST_TRACE_OBJECT (self, "performing conversion at scale %d/%d "
1019 "of cc data of with sizes, ccp:%u, cea608-1:%u, cea608-2:%u", scale_n,
1020 scale_d, VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
1021 VAL_OR_0 (cea608_2_len));
1023 if (output_time_cmp < 0) {
1024 /* we can't generate an output yet */
1025 guint cd_len = ccp_data_len ? *ccp_data_len : 0;
1026 guint c1_len = cea608_1_len ? *cea608_1_len : 0;
1027 guint c2_len = cea608_2_len ? *cea608_2_len : 0;
1029 store_cc_data (self, ccp_data, cd_len, cea608_1, c1_len, cea608_2,
1039 /* we are changing the framerate and may overflow the max output packet
1040 * size. Split them where necessary. */
1041 gint extra_ccp = 0, extra_cea608_1 = 0, extra_cea608_2 = 0;
1042 gint ccp_off = 0, cea608_1_off = 0, cea608_2_off = 0;
1043 gboolean wrote_first = FALSE;
1044 gint field2_padding = 0;
1046 if (output_time_cmp == 0) {
1047 /* we have completed a cycle and can reset our counters to avoid
1048 * overflow. Anything that fits into the output packet will be written */
1049 GST_LOG_OBJECT (self, "cycle completed, resetting frame counters");
1050 self->scratch_ccp_len = 0;
1051 self->scratch_cea608_1_len = 0;
1052 self->scratch_cea608_2_len = 0;
1053 self->input_frames = 0;
1054 self->output_frames = 0;
1058 extra_ccp = *ccp_data_len - 3 * out_fps_entry->max_ccp_count;
1059 extra_ccp = MAX (0, extra_ccp);
1060 ccp_off = *ccp_data_len - extra_ccp;
1063 extra_cea608_1 = VAL_OR_0 (cea608_1_len);
1064 extra_cea608_2 = VAL_OR_0 (cea608_2_len);
1066 wrote_first = !use_cea608_field2_first;
1067 /* try to push data into the packets. Anything 'extra' will be
1068 * stored for later */
1069 while (extra_cea608_1 > 0 || extra_cea608_2 > 0) {
1070 gint avail_1, avail_2;
1072 avail_1 = VAL_OR_0 (cea608_1_len) - extra_cea608_1;
1073 avail_2 = VAL_OR_0 (cea608_2_len) - extra_cea608_2;
1074 if (avail_1 + avail_2 + field2_padding >=
1075 2 * out_fps_entry->max_cea608_count)
1078 if (wrote_first && extra_cea608_1 > 0) {
1079 extra_cea608_1 -= 2;
1080 g_assert_cmpint (extra_cea608_1, >=, 0);
1082 g_assert_cmpint (cea608_1_off, <=, *cea608_1_len);
1085 avail_1 = VAL_OR_0 (cea608_1_len) - extra_cea608_1;
1086 avail_2 = VAL_OR_0 (cea608_2_len) - extra_cea608_2;
1087 if (avail_1 + avail_2 + field2_padding >=
1088 2 * out_fps_entry->max_cea608_count)
1091 if (extra_cea608_2 > 0) {
1092 extra_cea608_2 -= 2;
1093 g_assert_cmpint (extra_cea608_2, >=, 0);
1095 g_assert_cmpint (cea608_2_off, <=, *cea608_2_len);
1096 } else if (!wrote_first) {
1097 /* we need to insert field 2 padding if we don't have data and are
1098 * requested to start with field2 */
1099 field2_padding += 2;
1104 GST_TRACE_OBJECT (self, "allocated sizes ccp:%u, cea608-1:%u, "
1105 "cea608-2:%u", ccp_off, cea608_1_off, cea608_2_off);
1107 if (extra_ccp > 0 || extra_cea608_1 > 0 || extra_cea608_2 > 0) {
1108 /* packet would overflow, push extra bytes into the next packet */
1109 GST_DEBUG_OBJECT (self, "buffer would overflow by %u ccp bytes, "
1110 "%u cea608 field 1 bytes, or %u cea608 field 2 bytes", extra_ccp,
1111 extra_cea608_1, extra_cea608_2);
1112 store_cc_data (self, &ccp_data[ccp_off], extra_ccp,
1113 &cea608_1[cea608_1_off], extra_cea608_1, &cea608_2[cea608_2_off],
1116 *ccp_data_len = MIN (*ccp_data_len, ccp_off);
1118 *cea608_1_len = MIN (*cea608_1_len, cea608_1_off);
1120 *cea608_2_len = MIN (*cea608_2_len, cea608_2_off);
1122 GST_DEBUG_OBJECT (self, "section sizes of %u ccp bytes, "
1123 "%u cea608 field 1 bytes, and %u cea608 field 2 bytes fit within "
1124 "output packet", VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
1125 VAL_OR_0 (cea608_2_len));
1126 self->scratch_ccp_len = 0;
1127 self->scratch_cea608_1_len = 0;
1128 self->scratch_cea608_2_len = 0;
1132 if (tc && tc->config.fps_n != 0)
1133 interpolate_time_code_with_framerate (self, tc, out_fps_entry->fps_n,
1134 out_fps_entry->fps_d, scale_n, scale_d,
1135 &self->current_output_timecode);
1138 g_assert_cmpint (VAL_OR_0 (ccp_data_len) + (VAL_OR_0 (cea608_1_len) +
1139 VAL_OR_0 (cea608_2_len)) / 2 * 3, <=,
1140 3 * out_fps_entry->max_cc_count);
1142 GST_DEBUG_OBJECT (self, "write out packet with lengths ccp:%u, cea608-1:%u, "
1143 "cea608-2:%u", VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
1144 VAL_OR_0 (cea608_2_len));
1149 /* Converts raw CEA708 cc_data and an optional timecode into CDP */
1151 convert_cea708_cc_data_cea708_cdp_internal (GstCCConverter * self,
1152 const guint8 * cc_data, guint cc_data_len, guint8 * cdp, guint cdp_len,
1153 const GstVideoTimeCode * tc, const struct cdp_fps_entry *fps_entry)
1156 guint8 flags, checksum;
1159 GST_DEBUG_OBJECT (self, "writing out cdp packet from cc_data with length %u",
1162 gst_byte_writer_init_with_data (&bw, cdp, cdp_len, FALSE);
1163 gst_byte_writer_put_uint16_be_unchecked (&bw, 0x9669);
1164 /* Write a length of 0 for now */
1165 gst_byte_writer_put_uint8_unchecked (&bw, 0);
1167 gst_byte_writer_put_uint8_unchecked (&bw, fps_entry->fps_idx);
1169 if (cc_data_len / 3 > fps_entry->max_cc_count) {
1170 GST_WARNING_OBJECT (self, "Too many cc_data triplets for framerate: %u. "
1171 "Truncating to %u", cc_data_len / 3, fps_entry->max_cc_count);
1172 cc_data_len = 3 * fps_entry->max_cc_count;
1175 /* caption_service_active */
1178 /* ccdata_present */
1179 if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_CC_DATA))
1182 /* time_code_present */
1183 if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_TIME_CODE) && tc
1184 && tc->config.fps_n > 0)
1190 gst_byte_writer_put_uint8_unchecked (&bw, flags);
1192 gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
1194 if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_TIME_CODE) && tc
1195 && tc->config.fps_n > 0) {
1198 gst_byte_writer_put_uint8_unchecked (&bw, 0x71);
1199 /* reserved 11 - 2 bits */
1201 /* tens of hours - 2 bits */
1202 u8 |= ((tc->hours / 10) & 0x3) << 4;
1203 /* units of hours - 4 bits */
1204 u8 |= (tc->hours % 10) & 0xf;
1205 gst_byte_writer_put_uint8_unchecked (&bw, u8);
1207 /* reserved 1 - 1 bit */
1209 /* tens of minutes - 3 bits */
1210 u8 |= ((tc->minutes / 10) & 0x7) << 4;
1211 /* units of minutes - 4 bits */
1212 u8 |= (tc->minutes % 10) & 0xf;
1213 gst_byte_writer_put_uint8_unchecked (&bw, u8);
1215 /* field flag - 1 bit */
1216 u8 = tc->field_count < 2 ? 0x00 : 0x80;
1217 /* tens of seconds - 3 bits */
1218 u8 |= ((tc->seconds / 10) & 0x7) << 4;
1219 /* units of seconds - 4 bits */
1220 u8 |= (tc->seconds % 10) & 0xf;
1221 gst_byte_writer_put_uint8_unchecked (&bw, u8);
1223 /* drop frame flag - 1 bit */
1224 u8 = (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) ? 0x80 :
1226 /* reserved0 - 1 bit */
1227 /* tens of frames - 2 bits */
1228 u8 |= ((tc->frames / 10) & 0x3) << 4;
1229 /* units of frames 4 bits */
1230 u8 |= (tc->frames % 10) & 0xf;
1231 gst_byte_writer_put_uint8_unchecked (&bw, u8);
1234 if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_CC_DATA)) {
1235 gst_byte_writer_put_uint8_unchecked (&bw, 0x72);
1236 gst_byte_writer_put_uint8_unchecked (&bw, 0xe0 | fps_entry->max_cc_count);
1237 gst_byte_writer_put_data_unchecked (&bw, cc_data, cc_data_len);
1238 while (fps_entry->max_cc_count > cc_data_len / 3) {
1239 gst_byte_writer_put_uint8_unchecked (&bw, 0xfa);
1240 gst_byte_writer_put_uint8_unchecked (&bw, 0x00);
1241 gst_byte_writer_put_uint8_unchecked (&bw, 0x00);
1246 gst_byte_writer_put_uint8_unchecked (&bw, 0x74);
1247 gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
1248 self->cdp_hdr_sequence_cntr++;
1249 /* We calculate the checksum afterwards */
1250 gst_byte_writer_put_uint8_unchecked (&bw, 0);
1252 len = gst_byte_writer_get_pos (&bw);
1253 gst_byte_writer_set_pos (&bw, 2);
1254 gst_byte_writer_put_uint8_unchecked (&bw, len);
1257 for (i = 0; i < len; i++) {
1261 checksum = 256 - checksum;
1262 cdp[len - 1] = checksum;
1267 /* Converts CDP into raw CEA708 cc_data */
1269 convert_cea708_cdp_cea708_cc_data_internal (GstCCConverter * self,
1270 const guint8 * cdp, guint cdp_len, guint8 cc_data[MAX_CDP_PACKET_LEN],
1271 GstVideoTimeCode * tc, const struct cdp_fps_entry **out_fps_entry)
1278 const struct cdp_fps_entry *fps_entry;
1280 *out_fps_entry = &null_fps_entry;
1281 memset (tc, 0, sizeof (*tc));
1283 /* Header + footer length */
1285 GST_WARNING_OBJECT (self, "cdp packet too short (%u). expected at "
1286 "least %u", cdp_len, 11);
1290 gst_byte_reader_init (&br, cdp, cdp_len);
1291 u16 = gst_byte_reader_get_uint16_be_unchecked (&br);
1292 if (u16 != 0x9669) {
1293 GST_WARNING_OBJECT (self, "cdp packet does not have initial magic bytes "
1298 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1299 if (u8 != cdp_len) {
1300 GST_WARNING_OBJECT (self, "cdp packet length (%u) does not match passed "
1301 "in value (%u)", u8, cdp_len);
1305 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1306 fps_entry = cdp_fps_entry_from_id (u8);
1307 if (!fps_entry || fps_entry->fps_n == 0) {
1308 GST_WARNING_OBJECT (self, "cdp packet does not have a valid framerate "
1313 flags = gst_byte_reader_get_uint8_unchecked (&br);
1315 if ((flags & 0x40) == 0) {
1316 GST_DEBUG_OBJECT (self, "cdp packet does have any cc_data");
1320 /* cdp_hdr_sequence_cntr */
1321 gst_byte_reader_skip_unchecked (&br, 2);
1323 /* time_code_present */
1325 guint8 hours, minutes, seconds, frames, fields;
1326 gboolean drop_frame;
1328 if (gst_byte_reader_get_remaining (&br) < 5) {
1329 GST_WARNING_OBJECT (self, "cdp packet does not have enough data to "
1330 "contain a timecode (%u). Need at least 5 bytes",
1331 gst_byte_reader_get_remaining (&br));
1334 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1336 GST_WARNING_OBJECT (self, "cdp packet does not have timecode start byte "
1337 "of 0x71, found 0x%02x", u8);
1341 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1342 if ((u8 & 0xc0) != 0xc0) {
1343 GST_WARNING_OBJECT (self, "reserved bits are not 0xc0, found 0x%02x", u8);
1347 hours = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
1349 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1350 if ((u8 & 0x80) != 0x80) {
1351 GST_WARNING_OBJECT (self, "reserved bit is not 0x80, found 0x%02x", u8);
1354 minutes = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
1356 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1361 seconds = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
1363 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1365 GST_WARNING_OBJECT (self, "reserved bit is not 0x0, found 0x%02x", u8);
1369 drop_frame = ! !(u8 & 0x80);
1370 frames = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
1372 gst_video_time_code_init (tc, fps_entry->fps_n, fps_entry->fps_d, NULL,
1373 drop_frame ? GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME :
1374 GST_VIDEO_TIME_CODE_FLAGS_NONE, hours, minutes, seconds, frames,
1378 /* ccdata_present */
1382 if (gst_byte_reader_get_remaining (&br) < 2) {
1383 GST_WARNING_OBJECT (self, "not enough data to contain valid cc_data");
1386 u8 = gst_byte_reader_get_uint8_unchecked (&br);
1388 GST_WARNING_OBJECT (self, "missing cc_data start code of 0x72, "
1389 "found 0x%02x", u8);
1393 cc_count = gst_byte_reader_get_uint8_unchecked (&br);
1394 if ((cc_count & 0xe0) != 0xe0) {
1395 GST_WARNING_OBJECT (self, "reserved bits are not 0xe0, found 0x%02x", u8);
1401 if (gst_byte_reader_get_remaining (&br) < len) {
1402 GST_WARNING_OBJECT (self, "not enough bytes (%u) left for the number of "
1403 "byte triples (%u)", gst_byte_reader_get_remaining (&br), cc_count);
1407 memcpy (cc_data, gst_byte_reader_get_data_unchecked (&br, len), len);
1410 *out_fps_entry = fps_entry;
1412 /* skip everything else we don't care about */
1417 copy_from_stored_data (GstCCConverter * self, guint8 * out_ccp,
1418 guint * ccp_size, guint8 * cea608_1, guint * cea608_1_len,
1419 guint8 * cea608_2, guint * cea608_2_len)
1421 guint ccp_in_size = 0, cea608_1_in_size = 0, cea608_2_in_size = 0;
1423 g_assert ((out_ccp && ccp_size) || (!out_ccp && !ccp_size));
1424 g_assert ((cea608_1 && cea608_1_len) || (!cea608_1 && !cea608_1_len));
1425 g_assert ((cea608_2 && cea608_2_len) || (!cea608_2 && !cea608_2_len));
1428 ccp_in_size = *ccp_size;
1432 cea608_1_in_size = *cea608_1_len;
1436 cea608_2_in_size = *cea608_2_len;
1440 if (out_ccp && self->scratch_ccp_len > 0) {
1441 GST_DEBUG_OBJECT (self, "copying from previous scratch ccp buffer of "
1442 "%u bytes", self->scratch_ccp_len);
1443 if (ccp_in_size < *ccp_size + self->scratch_ccp_len) {
1444 GST_WARNING_OBJECT (self, "output buffer too small %u < %u", ccp_in_size,
1445 *ccp_size + self->scratch_ccp_len);
1448 memcpy (&out_ccp[*ccp_size], self->scratch_ccp, self->scratch_ccp_len);
1449 *ccp_size += self->scratch_ccp_len;
1452 if (cea608_1 && self->scratch_cea608_1_len > 0) {
1453 GST_DEBUG_OBJECT (self, "copying from previous scratch cea608 field 1 "
1454 "buffer of %u bytes", self->scratch_cea608_1_len);
1455 if (cea608_1_in_size < *cea608_1_len + self->scratch_cea608_1_len) {
1456 GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
1457 cea608_1_in_size, *cea608_1_len + self->scratch_cea608_1_len);
1460 memcpy (&cea608_1[*cea608_1_len], self->scratch_cea608_1,
1461 self->scratch_cea608_1_len);
1462 *cea608_1_len += self->scratch_cea608_1_len;
1465 if (cea608_2 && self->scratch_cea608_2_len > 0) {
1466 GST_DEBUG_OBJECT (self, "copying from previous scratch cea608 field 2 "
1467 "buffer of %u bytes", self->scratch_cea608_2_len);
1468 if (cea608_2_in_size < *cea608_2_len + self->scratch_cea608_2_len) {
1469 GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
1470 cea608_2_in_size, *cea608_2_len + self->scratch_cea608_2_len);
1473 memcpy (&cea608_2[*cea608_2_len], self->scratch_cea608_2,
1474 self->scratch_cea608_2_len);
1475 *cea608_2_len += self->scratch_cea608_2_len;
1491 cc_data_to_cea608_ccp (GstCCConverter * self, guint8 * cc_data,
1492 guint cc_data_len, guint8 * out_ccp, guint * ccp_size, guint8 * cea608_1,
1493 guint * cea608_1_len, guint8 * cea608_2, guint * cea608_2_len,
1494 const struct cdp_fps_entry *in_fps_entry)
1496 guint ccp_in_size = 0, cea608_1_in_size = 0, cea608_2_in_size = 0;
1498 g_assert (cc_data || cc_data_len == 0);
1501 ccp_in_size = *ccp_size;
1503 cea608_1_in_size = *cea608_1_len;
1505 cea608_2_in_size = *cea608_2_len;
1507 if (!copy_from_stored_data (self, out_ccp, ccp_size, cea608_1, cea608_1_len,
1508 cea608_2, cea608_2_len))
1512 gint ccp_offset = 0;
1513 guint new_cea608_1_len = 0, new_cea608_2_len = 0;
1514 guint8 *new_cea608_1 = cea608_1, *new_cea608_2 = cea608_2;
1516 cc_data_len = compact_cc_data (cc_data, cc_data_len);
1518 if (cc_data_len / 3 > in_fps_entry->max_cc_count) {
1519 GST_WARNING_OBJECT (self, "Too many cc_data triples in CDP packet %u. "
1520 "Truncating to %u", cc_data_len / 3, in_fps_entry->max_cc_count);
1521 cc_data_len = 3 * in_fps_entry->max_cc_count;
1526 new_cea608_1_len = cea608_1_in_size - *cea608_1_len;
1527 new_cea608_1 = &cea608_1[*cea608_1_len];
1530 new_cea608_2_len = cea608_2_in_size - *cea608_2_len;
1531 new_cea608_2 = &cea608_2[*cea608_2_len];
1534 ccp_offset = cc_data_extract_cea608 (cc_data, cc_data_len, new_cea608_1,
1535 &new_cea608_1_len, new_cea608_2, &new_cea608_2_len);
1537 if (ccp_offset == CC_DATA_EXTRACT_TOO_MANY_FIELD1 && cea608_1_len) {
1538 GST_WARNING_OBJECT (self, "cea608 field 1 overflow, dropping all "
1539 "previously stored field 1 data and trying again");
1541 } else if (ccp_offset == CC_DATA_EXTRACT_TOO_MANY_FIELD2 && cea608_2_len) {
1542 GST_WARNING_OBJECT (self, "cea608 field 2 overflow, dropping all "
1543 "previously stored field 2 data and trying again");
1545 } else if (ccp_offset < 0) {
1546 GST_WARNING_OBJECT (self, "Failed to extract cea608 from cc_data");
1554 if ((new_cea608_1_len + new_cea608_2_len) / 2 >
1555 in_fps_entry->max_cea608_count) {
1556 GST_WARNING_OBJECT (self, "Too many cea608 triples in CDP packet %u. "
1557 "Truncating to %u", (new_cea608_1_len + new_cea608_2_len) / 2,
1558 in_fps_entry->max_cea608_count);
1559 if ((new_cea608_1_len + new_cea608_2_len) / 2 >
1560 in_fps_entry->max_cea608_count) {
1561 new_cea608_1_len = 2 * in_fps_entry->max_cea608_count;
1562 new_cea608_2_len = 0;
1565 2 * in_fps_entry->max_cea608_count - new_cea608_1_len;
1570 *cea608_1_len += new_cea608_1_len;
1572 *cea608_2_len += new_cea608_2_len;
1575 if (ccp_in_size < *ccp_size + cc_data_len - ccp_offset) {
1576 GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
1577 ccp_in_size, *ccp_size + cc_data_len - ccp_offset);
1580 memcpy (&out_ccp[*ccp_size], &cc_data[ccp_offset],
1581 cc_data_len - ccp_offset);
1582 *ccp_size += cc_data_len - ccp_offset;
1599 cdp_to_cea608_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1600 guint8 * out_ccp, guint * ccp_size, guint8 * cea608_1, guint * cea608_1_len,
1601 guint8 * cea608_2, guint * cea608_2_len, GstVideoTimeCode * out_tc,
1602 const struct cdp_fps_entry **in_fps_entry)
1604 guint8 cc_data[MAX_CDP_PACKET_LEN];
1605 guint cc_data_len = 0;
1609 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1612 convert_cea708_cdp_cea708_cc_data_internal (self, in.data, in.size,
1613 cc_data, out_tc, in_fps_entry);
1615 gst_buffer_unmap (inbuf, &in);
1616 self->input_frames++;
1619 return cc_data_to_cea608_ccp (self, inbuf ? cc_data : NULL, cc_data_len,
1620 out_ccp, ccp_size, cea608_1, cea608_1_len, cea608_2, cea608_2_len,
1621 inbuf ? *in_fps_entry : NULL);
1624 static GstFlowReturn
1625 convert_cea608_raw_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
1631 n = gst_buffer_get_size (inbuf);
1633 GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
1634 gst_buffer_set_size (outbuf, 0);
1641 GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u. Truncating to %u", n,
1646 gst_buffer_set_size (outbuf, 3 * n);
1648 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1649 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1651 /* We have to assume that each value is from the first field and
1652 * don't know from which line offset it originally is */
1653 for (i = 0; i < n; i++) {
1654 out.data[i * 3] = 0x80;
1655 out.data[i * 3 + 1] = in.data[i * 2];
1656 out.data[i * 3 + 2] = in.data[i * 2 + 1];
1659 gst_buffer_unmap (inbuf, &in);
1660 gst_buffer_unmap (outbuf, &out);
1665 static GstFlowReturn
1666 convert_cea608_raw_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1672 n = gst_buffer_get_size (inbuf);
1674 GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
1675 gst_buffer_set_size (outbuf, 0);
1682 GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u. Truncating to %u", n,
1687 gst_buffer_set_size (outbuf, 3 * n);
1689 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1690 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1692 /* We have to assume that each value is from the first field and
1693 * don't know from which line offset it originally is */
1694 for (i = 0; i < n; i++) {
1695 out.data[i * 3] = 0xfc;
1696 out.data[i * 3 + 1] = in.data[i * 2];
1697 out.data[i * 3 + 2] = in.data[i * 2 + 1];
1700 gst_buffer_unmap (inbuf, &in);
1701 gst_buffer_unmap (outbuf, &out);
1706 static GstFlowReturn
1707 convert_cea608_raw_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
1708 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
1711 const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
1712 guint cc_data_len = MAX_CDP_PACKET_LEN;
1713 guint cea608_1_len = MAX_CDP_PACKET_LEN;
1714 guint8 cc_data[MAX_CDP_PACKET_LEN], cea608_1[MAX_CEA608_LEN];
1716 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
1717 if (!in_fps_entry || in_fps_entry->fps_n == 0)
1718 g_assert_not_reached ();
1720 if (!copy_from_stored_data (self, NULL, 0, cea608_1, &cea608_1_len, NULL, 0))
1726 n = gst_buffer_get_size (inbuf);
1728 GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
1729 gst_buffer_set_size (outbuf, 0);
1735 if (n > in_fps_entry->max_cea608_count) {
1736 GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u. Truncating to %u",
1737 n, in_fps_entry->max_cea608_count);
1738 n = in_fps_entry->max_cea608_count;
1741 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1742 for (i = 0; i < n; i++) {
1743 guint byte1 = in.data[i * 2 + 0];
1744 guint byte2 = in.data[i * 2 + 1];
1745 if (byte1 != 0x80 || byte2 != 0x80) {
1746 cea608_1[cea608_1_len++] = byte1;
1747 cea608_1[cea608_1_len++] = byte2;
1750 gst_buffer_unmap (inbuf, &in);
1751 self->input_frames++;
1754 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
1755 if (!out_fps_entry || out_fps_entry->fps_n == 0)
1756 g_assert_not_reached ();
1758 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
1759 cea608_1, &cea608_1_len, NULL, 0, tc_meta ? &tc_meta->tc : NULL,
1763 if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
1764 cea608_1_len, NULL, 0, cc_data, &cc_data_len,
1765 &self->last_cea608_written_was_field1))
1768 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1770 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
1771 out.data, out.size, &self->current_output_timecode, out_fps_entry);
1772 self->output_frames++;
1773 gst_buffer_unmap (outbuf, &out);
1776 gst_buffer_set_size (outbuf, cc_data_len);
1785 static GstFlowReturn
1786 convert_cea608_s334_1a_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
1793 n = gst_buffer_get_size (inbuf);
1795 GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
1802 GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
1806 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1807 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1809 for (i = 0; i < n; i++) {
1810 if (in.data[i * 3] & 0x80) {
1811 out.data[i * 2] = in.data[i * 3 + 1];
1812 out.data[i * 2 + 1] = in.data[i * 3 + 2];
1817 gst_buffer_unmap (inbuf, &in);
1818 gst_buffer_unmap (outbuf, &out);
1820 gst_buffer_set_size (outbuf, 2 * cea608);
1825 static GstFlowReturn
1826 convert_cea608_s334_1a_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1832 n = gst_buffer_get_size (inbuf);
1834 GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
1841 GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
1845 gst_buffer_set_size (outbuf, 3 * n);
1847 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1848 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1850 for (i = 0; i < n; i++) {
1851 out.data[i * 3] = (in.data[i * 3] & 0x80) ? 0xfc : 0xfd;
1852 out.data[i * 3 + 1] = in.data[i * 3 + 1];
1853 out.data[i * 3 + 2] = in.data[i * 3 + 2];
1856 gst_buffer_unmap (inbuf, &in);
1857 gst_buffer_unmap (outbuf, &out);
1862 static GstFlowReturn
1863 convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
1864 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
1867 const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
1868 guint cc_data_len = MAX_CDP_PACKET_LEN;
1869 guint cea608_1_len = MAX_CDP_PACKET_LEN, cea608_2_len = MAX_CDP_PACKET_LEN;
1870 guint8 cc_data[MAX_CDP_PACKET_LEN];
1871 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
1874 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
1875 if (!in_fps_entry || in_fps_entry->fps_n == 0)
1876 g_assert_not_reached ();
1878 if (!copy_from_stored_data (self, NULL, 0, cea608_1, &cea608_1_len,
1879 cea608_2, &cea608_2_len))
1883 n = gst_buffer_get_size (inbuf);
1885 GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
1891 if (n > in_fps_entry->max_cea608_count) {
1892 GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
1893 n = in_fps_entry->max_cea608_count;
1896 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1898 for (i = 0; i < n; i++) {
1899 guint byte1 = in.data[i * 3 + 1];
1900 guint byte2 = in.data[i * 3 + 2];
1902 if (in.data[i * 3] & 0x80) {
1903 if (byte1 != 0x80 || byte2 != 0x80) {
1904 cea608_1[cea608_1_len++] = byte1;
1905 cea608_1[cea608_1_len++] = byte2;
1908 if (byte1 != 0x80 || byte2 != 0x80) {
1909 cea608_2[cea608_2_len++] = byte1;
1910 cea608_2[cea608_2_len++] = byte2;
1914 gst_buffer_unmap (inbuf, &in);
1915 self->input_frames++;
1918 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
1919 if (!out_fps_entry || out_fps_entry->fps_n == 0)
1920 g_assert_not_reached ();
1922 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
1923 cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
1924 tc_meta ? &tc_meta->tc : NULL,
1925 self->last_cea608_written_was_field1)) {
1929 if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
1930 cea608_1_len, cea608_2, cea608_2_len, cc_data, &cc_data_len,
1931 &self->last_cea608_written_was_field1)) {
1935 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1937 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
1938 out.data, out.size, &self->current_output_timecode, out_fps_entry);
1939 self->output_frames++;
1940 gst_buffer_unmap (outbuf, &out);
1943 gst_buffer_set_size (outbuf, cc_data_len);
1952 static GstFlowReturn
1953 convert_cea708_cc_data_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
1960 n = gst_buffer_get_size (inbuf);
1962 GST_WARNING_OBJECT (self, "Invalid raw CEA708 buffer size");
1969 GST_WARNING_OBJECT (self, "Too many CEA708 triplets %u", n);
1973 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1974 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1976 for (i = 0; i < n; i++) {
1977 /* We can only really copy the first field here as there can't be any
1978 * signalling in raw CEA608 and we must not mix the streams of different
1981 if (in.data[i * 3] == 0xfc) {
1982 out.data[cea608 * 2] = in.data[i * 3 + 1];
1983 out.data[cea608 * 2 + 1] = in.data[i * 3 + 2];
1988 gst_buffer_unmap (inbuf, &in);
1989 gst_buffer_unmap (outbuf, &out);
1991 gst_buffer_set_size (outbuf, 2 * cea608);
1996 static GstFlowReturn
1997 convert_cea708_cc_data_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
2004 n = gst_buffer_get_size (inbuf);
2006 GST_WARNING_OBJECT (self, "Invalid raw CEA708 buffer size");
2013 GST_WARNING_OBJECT (self, "Too many CEA708 triplets %u", n);
2017 gst_buffer_map (inbuf, &in, GST_MAP_READ);
2018 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2020 for (i = 0; i < n; i++) {
2021 if (in.data[i * 3] == 0xfc || in.data[i * 3] == 0xfd) {
2022 /* We have to assume a line offset of 0 */
2023 out.data[cea608 * 3] = in.data[i * 3] == 0xfc ? 0x80 : 0x00;
2024 out.data[cea608 * 3 + 1] = in.data[i * 3 + 1];
2025 out.data[cea608 * 3 + 2] = in.data[i * 3 + 2];
2030 gst_buffer_unmap (inbuf, &in);
2031 gst_buffer_unmap (outbuf, &out);
2033 gst_buffer_set_size (outbuf, 3 * cea608);
2038 static GstFlowReturn
2039 convert_cea708_cc_data_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
2040 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2043 const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
2044 guint in_cc_data_len;
2045 guint cc_data_len = MAX_CDP_PACKET_LEN, ccp_data_len = MAX_CDP_PACKET_LEN;
2046 guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2047 guint8 cc_data[MAX_CDP_PACKET_LEN], ccp_data[MAX_CDP_PACKET_LEN];
2048 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2052 gst_buffer_map (inbuf, &in, GST_MAP_READ);
2053 in_cc_data = in.data;
2054 in_cc_data_len = in.size;
2055 self->input_frames++;
2061 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
2062 if (!in_fps_entry || in_fps_entry->fps_n == 0)
2063 g_assert_not_reached ();
2065 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2066 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2067 g_assert_not_reached ();
2069 if (!cc_data_to_cea608_ccp (self, in_cc_data, in_cc_data_len, ccp_data,
2070 &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
2073 gst_buffer_unmap (inbuf, &in);
2078 gst_buffer_unmap (inbuf, &in);
2080 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
2081 &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
2082 tc_meta ? &tc_meta->tc : NULL, self->last_cea608_written_was_field1))
2085 if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
2086 cea608_1, cea608_1_len, cea608_2, cea608_2_len, cc_data,
2087 &cc_data_len, &self->last_cea608_written_was_field1))
2090 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2092 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
2093 out.data, out.size, &self->current_output_timecode, out_fps_entry);
2094 self->output_frames++;
2095 gst_buffer_unmap (outbuf, &out);
2098 gst_buffer_set_size (outbuf, cc_data_len);
2107 static GstFlowReturn
2108 convert_cea708_cdp_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
2109 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2112 GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2113 guint8 cea608_1[MAX_CEA608_LEN];
2114 guint cea608_1_len = MAX_CEA608_LEN;
2115 const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2117 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2118 if (!cdp_to_cea608_cc_data (self, inbuf, NULL, NULL, cea608_1, &cea608_1_len,
2119 NULL, NULL, &tc, &in_fps_entry)) {
2120 gst_buffer_set_size (outbuf, 0);
2124 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2125 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2126 out_fps_entry = in_fps_entry;
2128 if (fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
2129 cea608_1, &cea608_1_len, NULL, NULL, &tc, FALSE)) {
2130 guint i, out_size = (guint) out.size;
2132 self->output_frames++;
2133 if (!write_cea608 (self, TRUE, out_fps_entry, cea608_1, cea608_1_len, NULL,
2134 0, out.data, &out_size, NULL)) {
2135 gst_buffer_unmap (outbuf, &out);
2136 return GST_FLOW_ERROR;
2139 /* remove the first byte from each cea608 packet */
2140 for (i = 0; i < out_size / 3; i++) {
2141 out.data[i * 2 + 0] = out.data[i * 3 + 1];
2142 out.data[i * 2 + 1] = out.data[i * 3 + 2];
2144 cea608_1_len = out_size / 3 * 2;
2148 gst_buffer_unmap (outbuf, &out);
2150 gst_buffer_set_size (outbuf, cea608_1_len);
2152 if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
2153 gst_buffer_add_video_time_code_meta (outbuf,
2154 &self->current_output_timecode);
2155 gst_video_time_code_increment_frame (&self->current_output_timecode);
2161 static GstFlowReturn
2162 convert_cea708_cdp_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
2163 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2166 GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2167 const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2168 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2169 guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2170 guint i, cc_data_len;
2172 if (!cdp_to_cea608_cc_data (self, inbuf, NULL, NULL, cea608_1, &cea608_1_len,
2173 cea608_2, &cea608_2_len, &tc, &in_fps_entry))
2176 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2177 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2178 out_fps_entry = in_fps_entry;
2180 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
2181 cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
2182 self->last_cea608_written_was_field1))
2185 cc_data_len = gst_buffer_get_sizes (outbuf, NULL, NULL);
2187 gst_buffer_map (outbuf, &out, GST_MAP_READWRITE);
2188 if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
2189 cea608_1_len, cea608_2, cea608_2_len, out.data, &cc_data_len,
2190 &self->last_cea608_written_was_field1)) {
2191 gst_buffer_unmap (outbuf, &out);
2195 for (i = 0; i < cc_data_len / 3; i++) {
2196 guint byte = out.data[i * 3];
2197 /* We have to assume a line offset of 0 */
2198 out.data[i * 3] = (byte == 0xfc || byte == 0xf8) ? 0x80 : 0x00;
2201 gst_buffer_unmap (outbuf, &out);
2202 self->output_frames++;
2204 gst_buffer_set_size (outbuf, cc_data_len);
2206 if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
2207 gst_buffer_add_video_time_code_meta (outbuf,
2208 &self->current_output_timecode);
2209 gst_video_time_code_increment_frame (&self->current_output_timecode);
2215 gst_buffer_set_size (outbuf, 0);
2219 static GstFlowReturn
2220 convert_cea708_cdp_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
2221 GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2224 GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2225 const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2226 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2227 guint8 ccp_data[MAX_CDP_PACKET_LEN];
2228 guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2229 guint ccp_data_len = MAX_CDP_PACKET_LEN;
2232 if (!cdp_to_cea608_cc_data (self, inbuf, ccp_data, &ccp_data_len,
2233 cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc, &in_fps_entry))
2236 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2237 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2238 out_fps_entry = in_fps_entry;
2240 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
2241 &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
2242 self->last_cea608_written_was_field1))
2245 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2246 out_len = (guint) out.size;
2247 if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
2248 cea608_1, cea608_1_len, cea608_2, cea608_2_len, out.data, &out_len,
2249 &self->last_cea608_written_was_field1)) {
2250 gst_buffer_unmap (outbuf, &out);
2255 gst_buffer_unmap (outbuf, &out);
2256 self->output_frames++;
2258 if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
2259 gst_buffer_add_video_time_code_meta (outbuf,
2260 &self->current_output_timecode);
2261 gst_video_time_code_increment_frame (&self->current_output_timecode);
2265 gst_buffer_set_size (outbuf, out_len);
2270 static GstFlowReturn
2271 convert_cea708_cdp_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
2275 GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2276 const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2277 guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2278 guint8 ccp_data[MAX_CDP_PACKET_LEN], cc_data[MAX_CDP_PACKET_LEN];
2279 guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2280 guint ccp_data_len = MAX_CDP_PACKET_LEN, cc_data_len = MAX_CDP_PACKET_LEN;
2283 if (!cdp_to_cea608_cc_data (self, inbuf, ccp_data, &ccp_data_len,
2284 cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc, &in_fps_entry))
2287 out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2288 if (!out_fps_entry || out_fps_entry->fps_n == 0)
2289 out_fps_entry = in_fps_entry;
2291 if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
2292 &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
2293 self->last_cea608_written_was_field1))
2296 if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
2297 cea608_1, cea608_1_len, cea608_2, cea608_2_len, cc_data,
2298 &cc_data_len, &self->last_cea608_written_was_field1)) {
2302 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2304 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
2305 out.data, out.size, &self->current_output_timecode, out_fps_entry);
2307 gst_buffer_unmap (outbuf, &out);
2308 self->output_frames++;
2311 gst_buffer_set_size (outbuf, out_len);
2316 static GstFlowReturn
2317 gst_cc_converter_transform (GstCCConverter * self, GstBuffer * inbuf,
2320 GstVideoTimeCodeMeta *tc_meta = NULL;
2321 GstFlowReturn ret = GST_FLOW_OK;
2323 GST_DEBUG_OBJECT (self, "Converting %" GST_PTR_FORMAT " from %u to %u", inbuf,
2324 self->input_caption_type, self->output_caption_type);
2327 tc_meta = gst_buffer_get_video_time_code_meta (inbuf);
2330 if (self->current_output_timecode.config.fps_n <= 0) {
2331 /* XXX: this assumes the input time codes are well-formed and increase
2332 * at the rate of one frame for each input buffer */
2333 const struct cdp_fps_entry *in_fps_entry;
2334 gint scale_n, scale_d;
2336 in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
2337 if (!in_fps_entry || in_fps_entry->fps_n == 0)
2338 scale_n = scale_d = 1;
2340 get_framerate_output_scale (self, in_fps_entry, &scale_n, &scale_d);
2342 interpolate_time_code_with_framerate (self, &tc_meta->tc,
2343 self->out_fps_n, self->out_fps_d, scale_n, scale_d,
2344 &self->current_output_timecode);
2348 switch (self->input_caption_type) {
2349 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2351 switch (self->output_caption_type) {
2352 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2353 ret = convert_cea608_raw_cea608_s334_1a (self, inbuf, outbuf);
2355 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2356 ret = convert_cea608_raw_cea708_cc_data (self, inbuf, outbuf);
2358 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2359 ret = convert_cea608_raw_cea708_cdp (self, inbuf, outbuf, tc_meta);
2361 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2363 g_assert_not_reached ();
2368 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2370 switch (self->output_caption_type) {
2371 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2372 ret = convert_cea608_s334_1a_cea608_raw (self, inbuf, outbuf);
2374 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2375 ret = convert_cea608_s334_1a_cea708_cc_data (self, inbuf, outbuf);
2377 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2379 convert_cea608_s334_1a_cea708_cdp (self, inbuf, outbuf, tc_meta);
2381 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2383 g_assert_not_reached ();
2388 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2390 switch (self->output_caption_type) {
2391 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2392 ret = convert_cea708_cc_data_cea608_raw (self, inbuf, outbuf);
2394 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2395 ret = convert_cea708_cc_data_cea608_s334_1a (self, inbuf, outbuf);
2397 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2399 convert_cea708_cc_data_cea708_cdp (self, inbuf, outbuf, tc_meta);
2401 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2403 g_assert_not_reached ();
2408 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2410 switch (self->output_caption_type) {
2411 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2412 ret = convert_cea708_cdp_cea608_raw (self, inbuf, outbuf, tc_meta);
2414 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2416 convert_cea708_cdp_cea608_s334_1a (self, inbuf, outbuf, tc_meta);
2418 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2420 convert_cea708_cdp_cea708_cc_data (self, inbuf, outbuf, tc_meta);
2422 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2423 ret = convert_cea708_cdp_cea708_cdp (self, inbuf, outbuf);
2426 g_assert_not_reached ();
2432 g_assert_not_reached ();
2436 if (ret != GST_FLOW_OK) {
2437 GST_DEBUG_OBJECT (self, "returning %s", gst_flow_get_name (ret));
2441 GST_DEBUG_OBJECT (self, "Converted to %" GST_PTR_FORMAT, outbuf);
2443 if (gst_buffer_get_size (outbuf) > 0) {
2444 if (self->current_output_timecode.config.fps_n > 0) {
2445 gst_buffer_add_video_time_code_meta (outbuf,
2446 &self->current_output_timecode);
2447 gst_video_time_code_increment_frame (&self->current_output_timecode);
2457 gst_cc_converter_transform_meta (GstBaseTransform * base, GstBuffer * outbuf,
2458 GstMeta * meta, GstBuffer * inbuf)
2460 const GstMetaInfo *info = meta->info;
2462 /* we do this manually for framerate scaling */
2463 if (info->api == GST_VIDEO_TIME_CODE_META_API_TYPE)
2466 return GST_BASE_TRANSFORM_CLASS (parent_class)->transform_meta (base, outbuf,
2471 can_generate_output (GstCCConverter * self)
2473 int input_frame_n, input_frame_d, output_frame_n, output_frame_d;
2474 int output_time_cmp;
2476 if (self->in_fps_n == 0 || self->out_fps_n == 0)
2479 /* compute the relative frame count for each */
2480 if (!gst_util_fraction_multiply (self->in_fps_d, self->in_fps_n,
2481 self->input_frames, 1, &input_frame_n, &input_frame_d))
2482 /* we should never overflow */
2483 g_assert_not_reached ();
2485 if (!gst_util_fraction_multiply (self->out_fps_d, self->out_fps_n,
2486 self->output_frames, 1, &output_frame_n, &output_frame_d))
2487 /* we should never overflow */
2488 g_assert_not_reached ();
2490 output_time_cmp = gst_util_fraction_compare (input_frame_n, input_frame_d,
2491 output_frame_n, output_frame_d);
2493 /* if the next output frame is at or before the current input frame */
2494 if (output_time_cmp >= 0)
2501 reset_counters (GstCCConverter * self)
2503 self->scratch_ccp_len = 0;
2504 self->scratch_cea608_1_len = 0;
2505 self->scratch_cea608_2_len = 0;
2506 self->input_frames = 0;
2507 self->output_frames = 1;
2508 gst_video_time_code_clear (&self->current_output_timecode);
2509 gst_clear_buffer (&self->previous_buffer);
2510 self->last_cea608_written_was_field1 = FALSE;
2513 static GstFlowReturn
2514 drain_input (GstCCConverter * self)
2516 GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (self);
2517 GstBaseTransform *trans = GST_BASE_TRANSFORM (self);
2518 GstFlowReturn ret = GST_FLOW_OK;
2520 while (self->scratch_ccp_len > 0 || self->scratch_cea608_1_len > 0
2521 || self->scratch_cea608_2_len > 0 || can_generate_output (self)) {
2524 if (!self->previous_buffer) {
2525 GST_WARNING_OBJECT (self, "Attempt to draining without a previous "
2526 "buffer. Aborting");
2530 outbuf = gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
2532 if (bclass->copy_metadata) {
2533 if (!bclass->copy_metadata (trans, self->previous_buffer, outbuf)) {
2534 /* something failed, post a warning */
2535 GST_ELEMENT_WARNING (self, STREAM, NOT_IMPLEMENTED,
2536 ("could not copy metadata"), (NULL));
2540 ret = gst_cc_converter_transform (self, NULL, outbuf);
2541 if (gst_buffer_get_size (outbuf) <= 0) {
2542 /* try to move the output along */
2543 self->input_frames++;
2544 gst_buffer_unref (outbuf);
2546 } else if (ret != GST_FLOW_OK) {
2547 gst_buffer_unref (outbuf);
2551 ret = gst_pad_push (GST_BASE_TRANSFORM_SRC_PAD (trans), outbuf);
2552 if (ret != GST_FLOW_OK) {
2560 static GstFlowReturn
2561 gst_cc_converter_generate_output (GstBaseTransform * base, GstBuffer ** outbuf)
2563 GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (base);
2564 GstCCConverter *self = GST_CCCONVERTER (base);
2565 GstBuffer *inbuf = base->queued_buf;
2569 base->queued_buf = NULL;
2570 if (!inbuf && !can_generate_output (self)) {
2574 if (gst_base_transform_is_passthrough (base)) {
2578 if (inbuf && GST_BUFFER_IS_DISCONT (inbuf)) {
2579 ret = drain_input (self);
2580 reset_counters (self);
2581 if (ret != GST_FLOW_OK)
2585 *outbuf = gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
2586 if (*outbuf == NULL)
2590 gst_buffer_replace (&self->previous_buffer, inbuf);
2592 if (bclass->copy_metadata) {
2593 if (!bclass->copy_metadata (base, self->previous_buffer, *outbuf)) {
2594 /* something failed, post a warning */
2595 GST_ELEMENT_WARNING (self, STREAM, NOT_IMPLEMENTED,
2596 ("could not copy metadata"), (NULL));
2600 ret = gst_cc_converter_transform (self, inbuf, *outbuf);
2601 if (gst_buffer_get_size (*outbuf) <= 0) {
2602 gst_buffer_unref (*outbuf);
2608 gst_buffer_unref (inbuf);
2616 gst_buffer_unref (inbuf);
2618 GST_WARNING_OBJECT (self, "could not allocate buffer");
2619 return GST_FLOW_ERROR;
2624 gst_cc_converter_sink_event (GstBaseTransform * trans, GstEvent * event)
2626 GstCCConverter *self = GST_CCCONVERTER (trans);
2628 switch (GST_EVENT_TYPE (event)) {
2630 GST_DEBUG_OBJECT (self, "received EOS");
2635 case GST_EVENT_FLUSH_START:
2636 reset_counters (self);
2642 return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
2646 gst_cc_converter_start (GstBaseTransform * base)
2648 GstCCConverter *self = GST_CCCONVERTER (base);
2650 /* Resetting this is not really needed but makes debugging easier */
2651 self->cdp_hdr_sequence_cntr = 0;
2652 self->current_output_timecode = (GstVideoTimeCode) GST_VIDEO_TIME_CODE_INIT;
2653 reset_counters (self);
2659 gst_cc_converter_stop (GstBaseTransform * base)
2661 GstCCConverter *self = GST_CCCONVERTER (base);
2663 gst_video_time_code_clear (&self->current_output_timecode);
2664 gst_clear_buffer (&self->previous_buffer);
2670 gst_cc_converter_set_property (GObject * object, guint prop_id,
2671 const GValue * value, GParamSpec * pspec)
2673 GstCCConverter *filter = GST_CCCONVERTER (object);
2677 filter->cdp_mode = g_value_get_flags (value);
2680 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2686 gst_cc_converter_get_property (GObject * object, guint prop_id, GValue * value,
2689 GstCCConverter *filter = GST_CCCONVERTER (object);
2693 g_value_set_flags (value, filter->cdp_mode);
2696 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2702 gst_cc_converter_class_init (GstCCConverterClass * klass)
2704 GObjectClass *gobject_class;
2705 GstElementClass *gstelement_class;
2706 GstBaseTransformClass *basetransform_class;
2708 gobject_class = (GObjectClass *) klass;
2709 gstelement_class = (GstElementClass *) klass;
2710 basetransform_class = (GstBaseTransformClass *) klass;
2712 gobject_class->set_property = gst_cc_converter_set_property;
2713 gobject_class->get_property = gst_cc_converter_get_property;
2716 * GstCCConverter:cdp-mode
2718 * Only insert the selection sections into CEA 708 CDP packets.
2720 * Various software does not handle any other information than CC data
2721 * contained in CDP packets and might fail parsing the packets otherwise.
2725 g_object_class_install_property (G_OBJECT_CLASS (klass),
2726 PROP_CDP_MODE, g_param_spec_flags ("cdp-mode",
2728 "Select which CDP sections to store in CDP packets",
2729 GST_TYPE_CC_CONVERTER_CDP_MODE, DEFAULT_CDP_MODE,
2730 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2732 gst_element_class_set_static_metadata (gstelement_class,
2733 "Closed Caption Converter",
2734 "Filter/ClosedCaption",
2735 "Converts Closed Captions between different formats",
2736 "Sebastian Dröge <sebastian@centricular.com>");
2738 gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
2739 gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
2741 basetransform_class->start = GST_DEBUG_FUNCPTR (gst_cc_converter_start);
2742 basetransform_class->stop = GST_DEBUG_FUNCPTR (gst_cc_converter_stop);
2743 basetransform_class->sink_event =
2744 GST_DEBUG_FUNCPTR (gst_cc_converter_sink_event);
2745 basetransform_class->transform_size =
2746 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_size);
2747 basetransform_class->transform_caps =
2748 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_caps);
2749 basetransform_class->fixate_caps =
2750 GST_DEBUG_FUNCPTR (gst_cc_converter_fixate_caps);
2751 basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_cc_converter_set_caps);
2752 basetransform_class->transform_meta =
2753 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_meta);
2754 basetransform_class->generate_output =
2755 GST_DEBUG_FUNCPTR (gst_cc_converter_generate_output);
2756 basetransform_class->passthrough_on_same_caps = TRUE;
2758 GST_DEBUG_CATEGORY_INIT (gst_cc_converter_debug, "ccconverter",
2759 0, "Closed Caption converter");
2761 gst_type_mark_as_plugin_api (GST_TYPE_CC_CONVERTER_CDP_MODE, 0);
2765 gst_cc_converter_init (GstCCConverter * self)
2767 self->cdp_mode = DEFAULT_CDP_MODE;