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
36 /* Ordered by the amount of information they can contain */
38 "closedcaption/x-cea-708,format=(string) cdp; " \
39 "closedcaption/x-cea-708,format=(string) cc_data; " \
40 "closedcaption/x-cea-608,format=(string) s334-1a; " \
41 "closedcaption/x-cea-608,format=(string) raw"
43 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
46 GST_STATIC_CAPS (CC_CAPS));
48 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
51 GST_STATIC_CAPS (CC_CAPS));
53 G_DEFINE_TYPE (GstCCConverter, gst_cc_converter, GST_TYPE_BASE_TRANSFORM);
54 #define parent_class gst_cc_converter_parent_class
57 gst_cc_converter_transform_size (GstBaseTransform * base,
58 GstPadDirection direction,
59 GstCaps * caps, gsize size, GstCaps * othercaps, gsize * othersize)
61 /* We can't really convert from an output size to an input size */
62 if (direction != GST_PAD_SINK)
65 /* Assume worst-case here and over-allocate, and in ::transform() we then
66 * downsize the buffer as needed. The worst-case is one CDP packet, which
67 * can be up to 256 bytes large */
75 gst_cc_converter_transform_caps (GstBaseTransform * base,
76 GstPadDirection direction, GstCaps * caps, GstCaps * filter)
78 static GstStaticCaps non_cdp_caps =
79 GST_STATIC_CAPS ("closedcaption/x-cea-708, format=(string)cc_data; "
80 "closedcaption/x-cea-608,format=(string) s334-1a; "
81 "closedcaption/x-cea-608,format=(string) raw");
82 static GstStaticCaps cdp_caps =
83 GST_STATIC_CAPS ("closedcaption/x-cea-708, format=(string)cdp");
84 static GstStaticCaps cdp_caps_framerate =
85 GST_STATIC_CAPS ("closedcaption/x-cea-708, format=(string)cdp, "
86 "framerate=(fraction){60/1, 60000/1001, 50/1, 30/1, 30000/1001, 25/1, 24/1, 24000/1001}");
88 GstCCConverter *self = GST_CCCONVERTER (base);
92 templ = gst_pad_get_pad_template_caps (base->srcpad);
94 res = gst_caps_new_empty ();
95 n = gst_caps_get_size (caps);
96 for (i = 0; i < n; i++) {
97 const GstStructure *s = gst_caps_get_structure (caps, i);
99 if (gst_structure_has_name (s, "closedcaption/x-cea-608")) {
100 const GValue *framerate;
102 framerate = gst_structure_get_value (s, "framerate");
104 if (direction == GST_PAD_SRC) {
105 /* SRC direction: We produce upstream caps
107 * Downstream wanted CEA608 caps. If it had a framerate, we
108 * also need upstream to provide exactly that same framerate
109 * and otherwise we don't care.
111 * We can convert everything to CEA608.
117 gst_caps_merge (gst_static_caps_get (&cdp_caps),
118 gst_static_caps_get (&non_cdp_caps));
119 tmp = gst_caps_make_writable (tmp);
120 gst_caps_set_value (tmp, "framerate", framerate);
121 res = gst_caps_merge (res, tmp);
123 res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps));
124 res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
127 /* SINK: We produce downstream caps
129 * Upstream provided CEA608 caps. We can convert that to CDP if
130 * also a CDP compatible framerate was provided, and we can convert
131 * it to anything else regardless.
133 * If upstream provided a framerate we can pass that through, possibly
134 * filtered for the CDP case.
140 /* Create caps that contain the intersection of all framerates with
141 * the CDP allowed framerates */
143 gst_caps_make_writable (gst_static_caps_get
144 (&cdp_caps_framerate));
145 t = gst_caps_get_structure (tmp, 0);
146 gst_structure_set_name (t, "closedcaption/x-cea-608");
147 gst_structure_remove_field (t, "format");
148 u = gst_structure_intersect (s, t);
149 gst_caps_unref (tmp);
152 const GValue *cdp_framerate;
154 /* There's an intersection between the framerates so we can convert
155 * into CDP with exactly those framerates */
156 cdp_framerate = gst_structure_get_value (u, "framerate");
157 tmp = gst_caps_make_writable (gst_static_caps_get (&cdp_caps));
158 gst_caps_set_value (tmp, "framerate", cdp_framerate);
159 gst_structure_free (u);
161 res = gst_caps_merge (res, tmp);
164 /* And we can convert to everything else with the given framerate */
165 tmp = gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
166 gst_caps_set_value (tmp, "framerate", framerate);
167 res = gst_caps_merge (res, tmp);
169 res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
172 } else if (gst_structure_has_name (s, "closedcaption/x-cea-708")) {
173 const GValue *framerate;
175 framerate = gst_structure_get_value (s, "framerate");
177 if (direction == GST_PAD_SRC) {
178 /* SRC direction: We produce upstream caps
180 * Downstream wanted CEA708 caps. If downstream wants *only* CDP we
181 * either need CDP from upstream, or anything else with a CDP
183 * If downstream also wants non-CDP we can accept anything.
185 * We pass through any framerate as-is, except for filtering
186 * for CDP framerates if downstream wants only CDP.
189 if (g_strcmp0 (gst_structure_get_string (s, "format"), "cdp") == 0) {
190 /* Downstream wants only CDP */
192 /* We need CDP from upstream in that case */
196 tmp = gst_caps_make_writable (gst_static_caps_get (&cdp_caps));
197 gst_caps_set_value (tmp, "framerate", framerate);
198 res = gst_caps_merge (res, tmp);
200 res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps));
203 /* Or anything else with a CDP framerate */
208 /* Create caps that contain the intersection of all framerates with
209 * the CDP allowed framerates */
211 gst_caps_make_writable (gst_static_caps_get
212 (&cdp_caps_framerate));
213 t = gst_caps_get_structure (tmp, 0);
214 gst_structure_set_name (t, "closedcaption/x-cea-708");
215 gst_structure_remove_field (t, "format");
216 u = gst_structure_intersect (s, t);
217 gst_caps_unref (tmp);
220 const GValue *cdp_framerate;
222 /* There's an intersection between the framerates so we can convert
223 * into CDP with exactly those framerates from anything else */
224 cdp_framerate = gst_structure_get_value (u, "framerate");
227 gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
228 gst_caps_set_value (tmp, "framerate", cdp_framerate);
229 res = gst_caps_merge (res, tmp);
232 GstCaps *tmp, *cdp_caps;
233 const GValue *cdp_framerate;
235 /* Get all CDP framerates, we can accept anything that has those
237 cdp_caps = gst_static_caps_get (&cdp_caps_framerate);
239 gst_structure_get_value (gst_caps_get_structure (cdp_caps, 0),
242 tmp = gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
243 gst_caps_set_value (tmp, "framerate", cdp_framerate);
244 gst_caps_unref (cdp_caps);
246 res = gst_caps_merge (res, tmp);
249 /* Downstream wants not only CDP, we can do everything */
255 gst_caps_merge (gst_static_caps_get (&cdp_caps),
256 gst_static_caps_get (&non_cdp_caps));
257 tmp = gst_caps_make_writable (tmp);
258 gst_caps_set_value (tmp, "framerate", framerate);
259 res = gst_caps_merge (res, tmp);
261 res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps));
262 res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
268 /* SINK: We produce downstream caps
270 * Upstream provided CEA708 caps. If upstream provided CDP we can
271 * output CDP, no matter what (-> passthrough). If upstream did not
272 * provide CDP, we can output CDP only if the framerate fits.
273 * We can always produce everything else apart from CDP.
275 * If upstream provided a framerate we pass that through for non-CDP
276 * output, and pass it through filtered for CDP output.
279 if (gst_structure_can_intersect (s,
280 gst_caps_get_structure (gst_static_caps_get (&cdp_caps), 0))) {
281 /* Upstream provided CDP caps, we can do everything independent of
284 tmp = gst_caps_make_writable (gst_static_caps_get (&cdp_caps));
285 gst_caps_set_value (tmp, "framerate", framerate);
286 res = gst_caps_merge (res, tmp);
288 res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps));
290 } else if (framerate) {
293 /* Upstream did not provide CDP. We can only do CDP if upstream
294 * happened to have a CDP framerate */
296 /* Create caps that contain the intersection of all framerates with
297 * the CDP allowed framerates */
299 gst_caps_make_writable (gst_static_caps_get
300 (&cdp_caps_framerate));
301 t = gst_caps_get_structure (tmp, 0);
302 gst_structure_set_name (t, "closedcaption/x-cea-708");
303 gst_structure_remove_field (t, "format");
304 u = gst_structure_intersect (s, t);
305 gst_caps_unref (tmp);
308 const GValue *cdp_framerate;
310 /* There's an intersection between the framerates so we can convert
311 * into CDP with exactly those framerates */
312 cdp_framerate = gst_structure_get_value (u, "framerate");
313 tmp = gst_caps_make_writable (gst_static_caps_get (&cdp_caps));
314 gst_caps_set_value (tmp, "framerate", cdp_framerate);
315 gst_structure_free (u);
317 res = gst_caps_merge (res, tmp);
321 /* We can always convert CEA708 to all non-CDP formats */
323 tmp = gst_caps_make_writable (gst_static_caps_get (&non_cdp_caps));
324 gst_caps_set_value (tmp, "framerate", framerate);
325 res = gst_caps_merge (res, tmp);
327 res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
331 g_assert_not_reached ();
335 /* We can convert anything into anything but it might involve loss of
336 * information so always filter according to the order in our template caps
340 filter = gst_caps_intersect_full (templ, filter, GST_CAPS_INTERSECT_FIRST);
342 tmp = gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
343 gst_caps_unref (res);
344 gst_caps_unref (filter);
348 gst_caps_unref (templ);
350 GST_DEBUG_OBJECT (self,
351 "Transformed in direction %s caps %" GST_PTR_FORMAT " to %"
352 GST_PTR_FORMAT, direction == GST_PAD_SRC ? "src" : "sink", caps, res);
358 gst_cc_converter_fixate_caps (GstBaseTransform * base,
359 GstPadDirection direction, GstCaps * incaps, GstCaps * outcaps)
361 GstCCConverter *self = GST_CCCONVERTER (base);
362 const GstStructure *s;
364 const GValue *framerate;
365 GstCaps *intersection, *templ;
367 /* Prefer passthrough if we can */
368 if (gst_caps_is_subset (incaps, outcaps)) {
369 gst_caps_unref (outcaps);
370 return GST_BASE_TRANSFORM_CLASS (parent_class)->fixate_caps (base,
371 direction, incaps, gst_caps_ref (incaps));
374 /* Otherwise prefer caps in the order of our template caps */
375 templ = gst_pad_get_pad_template_caps (base->srcpad);
377 gst_caps_intersect_full (templ, outcaps, GST_CAPS_INTERSECT_FIRST);
378 gst_caps_unref (outcaps);
379 outcaps = intersection;
382 GST_BASE_TRANSFORM_CLASS (parent_class)->fixate_caps (base, direction,
385 if (direction == GST_PAD_SRC)
388 /* if we generate caps for the source pad, pass through any framerate
389 * upstream might've given us and remove any framerate that might've
390 * been added by basetransform due to intersecting with downstream */
391 s = gst_caps_get_structure (incaps, 0);
392 framerate = gst_structure_get_value (s, "framerate");
393 outcaps = gst_caps_make_writable (outcaps);
394 t = gst_caps_get_structure (outcaps, 0);
396 gst_structure_set_value (t, "framerate", framerate);
398 gst_structure_remove_field (t, "framerate");
401 GST_DEBUG_OBJECT (self,
402 "Fixated caps %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT, incaps, outcaps);
408 gst_cc_converter_set_caps (GstBaseTransform * base, GstCaps * incaps,
411 GstCCConverter *self = GST_CCCONVERTER (base);
412 const GstStructure *s;
413 gboolean passthrough;
415 self->input_caption_type = gst_video_caption_type_from_caps (incaps);
416 self->output_caption_type = gst_video_caption_type_from_caps (outcaps);
418 if (self->input_caption_type == GST_VIDEO_CAPTION_TYPE_UNKNOWN ||
419 self->output_caption_type == GST_VIDEO_CAPTION_TYPE_UNKNOWN)
422 s = gst_caps_get_structure (incaps, 0);
423 if (!gst_structure_get_fraction (s, "framerate", &self->fps_n, &self->fps_d))
424 self->fps_n = self->fps_d = 0;
426 /* Caps can be different but we can passthrough as long as they can
427 * intersect, i.e. have same caps name and format */
428 passthrough = gst_caps_can_intersect (incaps, outcaps);
429 gst_base_transform_set_passthrough (base, passthrough);
431 GST_DEBUG_OBJECT (self,
432 "Got caps %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT " (passthrough %d)",
433 incaps, outcaps, passthrough);
439 GST_ERROR_OBJECT (self,
440 "Invalid caps: in %" GST_PTR_FORMAT " out: %" GST_PTR_FORMAT, incaps,
446 /* Converts raw CEA708 cc_data and an optional timecode into CDP */
448 convert_cea708_cc_data_cea708_cdp_internal (GstCCConverter * self,
449 const guint8 * cc_data, guint cc_data_len, guint8 * cdp, guint cdp_len,
450 const GstVideoTimeCodeMeta * tc_meta)
453 guint8 flags, checksum;
457 gst_byte_writer_init_with_data (&bw, cdp, cdp_len, FALSE);
458 gst_byte_writer_put_uint16_be_unchecked (&bw, 0x9669);
459 /* Write a length of 0 for now */
460 gst_byte_writer_put_uint8_unchecked (&bw, 0);
461 if (self->fps_n == 24000 && self->fps_d == 1001) {
462 gst_byte_writer_put_uint8_unchecked (&bw, 0x1f);
464 } else if (self->fps_n == 24 && self->fps_d == 1) {
465 gst_byte_writer_put_uint8_unchecked (&bw, 0x2f);
467 } else if (self->fps_n == 25 && self->fps_d == 1) {
468 gst_byte_writer_put_uint8_unchecked (&bw, 0x3f);
470 } else if (self->fps_n == 30000 && self->fps_d == 1001) {
471 gst_byte_writer_put_uint8_unchecked (&bw, 0x4f);
473 } else if (self->fps_n == 30 && self->fps_d == 1) {
474 gst_byte_writer_put_uint8_unchecked (&bw, 0x5f);
476 } else if (self->fps_n == 50 && self->fps_d == 1) {
477 gst_byte_writer_put_uint8_unchecked (&bw, 0x6f);
479 } else if (self->fps_n == 60000 && self->fps_d == 1001) {
480 gst_byte_writer_put_uint8_unchecked (&bw, 0x7f);
482 } else if (self->fps_n == 60 && self->fps_d == 1) {
483 gst_byte_writer_put_uint8_unchecked (&bw, 0x8f);
486 g_assert_not_reached ();
489 if (cc_data_len / 3 > cc_count) {
490 GST_ERROR_OBJECT (self, "Too many cc_data triplet for framerate: %u > %u",
491 cc_data_len / 3, cc_count);
495 /* ccdata_present | caption_service_active */
498 /* time_code_present */
505 gst_byte_writer_put_uint8_unchecked (&bw, flags);
507 gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
510 const GstVideoTimeCode *tc = &tc_meta->tc;
512 gst_byte_writer_put_uint8_unchecked (&bw, 0x71);
513 gst_byte_writer_put_uint8_unchecked (&bw, 0xc0 |
514 (((tc->hours % 10) & 0x3) << 4) |
515 ((tc->hours - (tc->hours % 10)) & 0xf));
517 gst_byte_writer_put_uint8_unchecked (&bw, 0x80 |
518 (((tc->minutes % 10) & 0x7) << 4) |
519 ((tc->minutes - (tc->minutes % 10)) & 0xf));
521 gst_byte_writer_put_uint8_unchecked (&bw,
523 2 ? 0x00 : 0x80) | (((tc->seconds %
524 10) & 0x7) << 4) | ((tc->seconds -
525 (tc->seconds % 10)) & 0xf));
527 gst_byte_writer_put_uint8_unchecked (&bw,
528 ((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) ? 0x80 :
529 0x00) | (((tc->frames % 10) & 0x3) << 4) | ((tc->frames -
530 (tc->frames % 10)) & 0xf));
533 gst_byte_writer_put_uint8_unchecked (&bw, 0x72);
534 gst_byte_writer_put_uint8_unchecked (&bw, 0xe0 | cc_count);
535 gst_byte_writer_put_data_unchecked (&bw, cc_data, cc_data_len);
536 if (cc_count > cc_data_len / 3) {
537 gst_byte_writer_fill (&bw, 0, 3 * cc_count - cc_data_len);
540 gst_byte_writer_put_uint8_unchecked (&bw, 0x74);
541 gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
542 self->cdp_hdr_sequence_cntr++;
543 /* We calculate the checksum afterwards */
544 gst_byte_writer_put_uint8_unchecked (&bw, 0);
546 len = gst_byte_writer_get_pos (&bw);
547 gst_byte_writer_set_pos (&bw, 2);
548 gst_byte_writer_put_uint8_unchecked (&bw, len);
551 for (i = 0; i < len; i++) {
555 checksum = 256 - checksum;
556 cdp[len - 1] = checksum;
561 /* Converts CDP into raw CEA708 cc_data */
563 convert_cea708_cdp_cea708_cc_data_internal (GstCCConverter * self,
564 const guint8 * cdp, guint cdp_len, guint8 cc_data[256],
565 GstVideoTimeCode * tc)
574 memset (tc, 0, sizeof (*tc));
576 /* Header + footer length */
580 gst_byte_reader_init (&br, cdp, cdp_len);
581 u16 = gst_byte_reader_get_uint16_be_unchecked (&br);
585 u8 = gst_byte_reader_get_uint8_unchecked (&br);
589 u8 = gst_byte_reader_get_uint8_unchecked (&br);
627 flags = gst_byte_reader_get_uint8_unchecked (&br);
629 if ((flags & 0x40) == 0)
632 /* cdp_hdr_sequence_cntr */
633 gst_byte_reader_skip_unchecked (&br, 2);
635 /* time_code_present */
637 guint8 hours, minutes, seconds, frames, fields;
640 if (gst_byte_reader_get_remaining (&br) < 5)
642 if (gst_byte_reader_get_uint8_unchecked (&br) != 0x71)
645 u8 = gst_byte_reader_get_uint8_unchecked (&br);
646 if ((u8 & 0xc) != 0xc)
649 hours = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
651 u8 = gst_byte_reader_get_uint8_unchecked (&br);
652 if ((u8 & 0x80) != 0x80)
654 minutes = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
656 u8 = gst_byte_reader_get_uint8_unchecked (&br);
661 seconds = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
663 u8 = gst_byte_reader_get_uint8_unchecked (&br);
667 drop_frame = ! !(u8 & 0x80);
668 frames = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
670 gst_video_time_code_init (tc, fps_n, fps_d, NULL,
671 drop_frame ? GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME :
672 GST_VIDEO_TIME_CODE_FLAGS_NONE, hours, minutes, seconds, frames,
680 if (gst_byte_reader_get_remaining (&br) < 2)
682 if (gst_byte_reader_get_uint8_unchecked (&br) != 0x72)
685 cc_count = gst_byte_reader_get_uint8_unchecked (&br);
686 if ((cc_count & 0xe0) != 0xe0)
691 if (gst_byte_reader_get_remaining (&br) < len)
694 memcpy (cc_data, gst_byte_reader_get_data_unchecked (&br, len), len);
697 /* skip everything else we don't care about */
703 convert_cea608_raw_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
709 n = gst_buffer_get_size (inbuf);
711 GST_ERROR_OBJECT (self, "Invalid raw CEA608 buffer size");
712 return GST_FLOW_ERROR;
718 GST_ERROR_OBJECT (self, "Too many CEA608 pairs %u", n);
719 return GST_FLOW_ERROR;
722 gst_buffer_set_size (outbuf, 3 * n);
724 gst_buffer_map (inbuf, &in, GST_MAP_READ);
725 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
727 /* We have to assume that each value is from the first field and
728 * don't know from which line offset it originally is */
729 for (i = 0; i < n; i++) {
730 out.data[i * 3] = 0x80;
731 out.data[i * 3 + 1] = in.data[i * 2];
732 out.data[i * 3 + 2] = in.data[i * 2 + 1];
735 gst_buffer_unmap (inbuf, &in);
736 gst_buffer_unmap (outbuf, &out);
742 convert_cea608_raw_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
748 n = gst_buffer_get_size (inbuf);
750 GST_ERROR_OBJECT (self, "Invalid raw CEA608 buffer size");
751 return GST_FLOW_ERROR;
757 GST_ERROR_OBJECT (self, "Too many CEA608 pairs %u", n);
758 return GST_FLOW_ERROR;
761 gst_buffer_set_size (outbuf, 3 * n);
763 gst_buffer_map (inbuf, &in, GST_MAP_READ);
764 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
766 /* We have to assume that each value is from the first field and
767 * don't know from which line offset it originally is */
768 for (i = 0; i < n; i++) {
769 out.data[i * 3] = 0xfc;
770 out.data[i * 3 + 1] = in.data[i * 2];
771 out.data[i * 3 + 2] = in.data[i * 2 + 1];
774 gst_buffer_unmap (inbuf, &in);
775 gst_buffer_unmap (outbuf, &out);
781 convert_cea608_raw_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
788 n = gst_buffer_get_size (inbuf);
790 GST_ERROR_OBJECT (self, "Invalid raw CEA608 buffer size");
791 return GST_FLOW_ERROR;
797 GST_ERROR_OBJECT (self, "Too many CEA608 pairs %u", n);
798 return GST_FLOW_ERROR;
801 gst_buffer_map (inbuf, &in, GST_MAP_READ);
802 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
804 for (i = 0; i < n; i++) {
805 cc_data[i * 3] = 0xfc;
806 cc_data[i * 3 + 1] = in.data[i * 2];
807 cc_data[i * 3 + 2] = in.data[i * 2 + 1];
811 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, n * 3,
812 out.data, out.size, gst_buffer_get_video_time_code_meta (inbuf));
814 gst_buffer_unmap (inbuf, &in);
815 gst_buffer_unmap (outbuf, &out);
818 return GST_FLOW_ERROR;
820 gst_buffer_set_size (outbuf, len);
826 convert_cea608_s334_1a_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
833 n = gst_buffer_get_size (inbuf);
835 GST_ERROR_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
836 return GST_FLOW_ERROR;
842 GST_ERROR_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
843 return GST_FLOW_ERROR;
846 gst_buffer_map (inbuf, &in, GST_MAP_READ);
847 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
849 for (i = 0; i < n; i++) {
850 if (in.data[i * 3] & 0x80) {
851 out.data[i * 2] = in.data[i * 3 + 1];
852 out.data[i * 2 + 1] = in.data[i * 3 + 2];
857 gst_buffer_unmap (inbuf, &in);
858 gst_buffer_unmap (outbuf, &out);
860 gst_buffer_set_size (outbuf, 2 * cea608);
866 convert_cea608_s334_1a_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
872 n = gst_buffer_get_size (inbuf);
874 GST_ERROR_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
875 return GST_FLOW_ERROR;
881 GST_ERROR_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
882 return GST_FLOW_ERROR;
885 gst_buffer_set_size (outbuf, 3 * n);
887 gst_buffer_map (inbuf, &in, GST_MAP_READ);
888 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
890 for (i = 0; i < n; i++) {
891 out.data[i * 3] = (in.data[i * 3] & 0x80) ? 0xfc : 0xfd;
892 out.data[i * 3 + 1] = in.data[i * 3 + 1];
893 out.data[i * 3 + 2] = in.data[i * 3 + 2];
896 gst_buffer_unmap (inbuf, &in);
897 gst_buffer_unmap (outbuf, &out);
903 convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
910 n = gst_buffer_get_size (inbuf);
912 GST_ERROR_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
913 return GST_FLOW_ERROR;
919 GST_ERROR_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
920 return GST_FLOW_ERROR;
923 gst_buffer_map (inbuf, &in, GST_MAP_READ);
924 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
926 for (i = 0; i < n; i++) {
927 cc_data[i * 3] = (in.data[i * 3] & 0x80) ? 0xfc : 0xfd;
928 cc_data[i * 3 + 1] = in.data[i * 3 + 1];
929 cc_data[i * 3 + 2] = in.data[i * 3 + 2];
933 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, n * 3,
934 out.data, out.size, gst_buffer_get_video_time_code_meta (inbuf));
936 gst_buffer_unmap (inbuf, &in);
937 gst_buffer_unmap (outbuf, &out);
940 return GST_FLOW_ERROR;
942 gst_buffer_set_size (outbuf, len);
948 convert_cea708_cc_data_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
955 n = gst_buffer_get_size (inbuf);
957 GST_ERROR_OBJECT (self, "Invalid raw CEA708 buffer size");
958 return GST_FLOW_ERROR;
964 GST_ERROR_OBJECT (self, "Too many CEA708 triplets %u", n);
965 return GST_FLOW_ERROR;
968 gst_buffer_map (inbuf, &in, GST_MAP_READ);
969 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
971 for (i = 0; i < n; i++) {
972 /* We can only really copy the first field here as there can't be any
973 * signalling in raw CEA608 and we must not mix the streams of different
976 if (in.data[i * 3] == 0xfc) {
977 out.data[cea608 * 2] = in.data[i * 3 + 1];
978 out.data[cea608 * 2 + 1] = in.data[i * 3 + 2];
983 gst_buffer_unmap (inbuf, &in);
984 gst_buffer_unmap (outbuf, &out);
986 gst_buffer_set_size (outbuf, 2 * cea608);
992 convert_cea708_cc_data_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
999 n = gst_buffer_get_size (inbuf);
1001 GST_ERROR_OBJECT (self, "Invalid raw CEA708 buffer size");
1002 return GST_FLOW_ERROR;
1008 GST_ERROR_OBJECT (self, "Too many CEA708 triplets %u", n);
1009 return GST_FLOW_ERROR;
1012 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1013 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1015 for (i = 0; i < n; i++) {
1016 if (in.data[i * 3] == 0xfc || in.data[i * 3] == 0xfd) {
1017 /* We have to assume a line offset of 0 */
1018 out.data[cea608 * 3] = in.data[i * 3] == 0xfc ? 0x80 : 0x00;
1019 out.data[cea608 * 3 + 1] = in.data[i * 3 + 1];
1020 out.data[cea608 * 3 + 2] = in.data[i * 3 + 2];
1025 gst_buffer_unmap (inbuf, &in);
1026 gst_buffer_unmap (outbuf, &out);
1028 gst_buffer_set_size (outbuf, 3 * cea608);
1033 static GstFlowReturn
1034 convert_cea708_cc_data_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
1041 n = gst_buffer_get_size (inbuf);
1043 GST_ERROR_OBJECT (self, "Invalid raw CEA708 buffer size");
1044 return GST_FLOW_ERROR;
1050 GST_ERROR_OBJECT (self, "Too many CEA708 triplets %u", n);
1051 return GST_FLOW_ERROR;
1054 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1055 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1058 convert_cea708_cc_data_cea708_cdp_internal (self, in.data, in.size,
1059 out.data, out.size, gst_buffer_get_video_time_code_meta (inbuf));
1061 gst_buffer_unmap (inbuf, &in);
1062 gst_buffer_unmap (outbuf, &out);
1065 return GST_FLOW_ERROR;
1067 gst_buffer_set_size (outbuf, len);
1072 static GstFlowReturn
1073 convert_cea708_cdp_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
1078 GstVideoTimeCode tc;
1079 guint8 cc_data[256];
1080 guint len, cea608 = 0;
1082 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1083 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1086 convert_cea708_cdp_cea708_cc_data_internal (self, in.data, in.size,
1091 GST_ERROR_OBJECT (self, "Too many cc_data triples in CDP packet %u", len);
1092 return GST_FLOW_ERROR;
1095 for (i = 0; i < len; i++) {
1096 /* We can only really copy the first field here as there can't be any
1097 * signalling in raw CEA608 and we must not mix the streams of different
1100 if (cc_data[i * 3] == 0xfc) {
1101 out.data[cea608 * 2] = cc_data[i * 3 + 1];
1102 out.data[cea608 * 2 + 1] = cc_data[i * 3 + 2];
1107 gst_buffer_unmap (inbuf, &in);
1108 gst_buffer_unmap (outbuf, &out);
1110 gst_buffer_set_size (outbuf, 2 * cea608);
1112 if (tc.config.fps_n != 0 && !gst_buffer_get_video_time_code_meta (inbuf))
1113 gst_buffer_add_video_time_code_meta (outbuf, &tc);
1118 static GstFlowReturn
1119 convert_cea708_cdp_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
1124 GstVideoTimeCode tc;
1125 guint8 cc_data[256];
1126 guint len, cea608 = 0;
1128 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1129 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1132 convert_cea708_cdp_cea708_cc_data_internal (self, in.data, in.size,
1137 GST_ERROR_OBJECT (self, "Too many cc_data triples in CDP packet %u", len);
1138 return GST_FLOW_ERROR;
1141 for (i = 0; i < len; i++) {
1142 if (cc_data[i * 3] == 0xfc || cc_data[i * 3] == 0xfd) {
1143 /* We have to assume a line offset of 0 */
1144 out.data[cea608 * 3] = cc_data[i * 3] == 0xfc ? 0x80 : 0x00;
1145 out.data[cea608 * 3 + 1] = cc_data[i * 3 + 1];
1146 out.data[cea608 * 3 + 2] = cc_data[i * 3 + 2];
1151 gst_buffer_unmap (inbuf, &in);
1152 gst_buffer_unmap (outbuf, &out);
1154 gst_buffer_set_size (outbuf, 3 * cea608);
1156 if (tc.config.fps_n != 0 && !gst_buffer_get_video_time_code_meta (inbuf))
1157 gst_buffer_add_video_time_code_meta (outbuf, &tc);
1162 static GstFlowReturn
1163 convert_cea708_cdp_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1167 GstVideoTimeCode tc;
1170 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1171 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1174 convert_cea708_cdp_cea708_cc_data_internal (self, in.data, in.size,
1177 gst_buffer_unmap (inbuf, &in);
1178 gst_buffer_unmap (outbuf, &out);
1181 GST_ERROR_OBJECT (self, "Too many cc_data triples in CDP packet %u",
1183 return GST_FLOW_ERROR;
1186 gst_buffer_set_size (outbuf, len);
1188 if (tc.config.fps_n != 0 && !gst_buffer_get_video_time_code_meta (inbuf))
1189 gst_buffer_add_video_time_code_meta (outbuf, &tc);
1194 static GstFlowReturn
1195 gst_cc_converter_transform (GstBaseTransform * base, GstBuffer * inbuf,
1198 GstCCConverter *self = GST_CCCONVERTER (base);
1199 GstVideoTimeCodeMeta *tc_meta = gst_buffer_get_video_time_code_meta (inbuf);
1200 GstFlowReturn ret = GST_FLOW_OK;
1202 GST_DEBUG_OBJECT (base, "Converting %" GST_PTR_FORMAT " from %u to %u", inbuf,
1203 self->input_caption_type, self->output_caption_type);
1205 switch (self->input_caption_type) {
1206 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1208 switch (self->output_caption_type) {
1209 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1210 ret = convert_cea608_raw_cea608_s334_1a (self, inbuf, outbuf);
1212 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1213 ret = convert_cea608_raw_cea708_cc_data (self, inbuf, outbuf);
1215 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1216 ret = convert_cea608_raw_cea708_cdp (self, inbuf, outbuf);
1218 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1220 g_assert_not_reached ();
1225 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1227 switch (self->output_caption_type) {
1228 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1229 ret = convert_cea608_s334_1a_cea608_raw (self, inbuf, outbuf);
1231 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1232 ret = convert_cea608_s334_1a_cea708_cc_data (self, inbuf, outbuf);
1234 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1235 ret = convert_cea608_s334_1a_cea708_cdp (self, inbuf, outbuf);
1237 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1239 g_assert_not_reached ();
1244 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1246 switch (self->output_caption_type) {
1247 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1248 ret = convert_cea708_cc_data_cea608_raw (self, inbuf, outbuf);
1250 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1251 ret = convert_cea708_cc_data_cea608_s334_1a (self, inbuf, outbuf);
1253 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1254 ret = convert_cea708_cc_data_cea708_cdp (self, inbuf, outbuf);
1256 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1258 g_assert_not_reached ();
1263 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1265 switch (self->output_caption_type) {
1266 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1267 ret = convert_cea708_cdp_cea608_raw (self, inbuf, outbuf);
1269 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1270 ret = convert_cea708_cdp_cea608_s334_1a (self, inbuf, outbuf);
1272 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1273 ret = convert_cea708_cdp_cea708_cc_data (self, inbuf, outbuf);
1275 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1277 g_assert_not_reached ();
1283 g_assert_not_reached ();
1287 if (ret != GST_FLOW_OK)
1291 gst_buffer_add_video_time_code_meta (outbuf, &tc_meta->tc);
1293 GST_DEBUG_OBJECT (self, "Converted to %" GST_PTR_FORMAT, outbuf);
1295 return gst_buffer_get_size (outbuf) >
1296 0 ? GST_FLOW_OK : GST_BASE_TRANSFORM_FLOW_DROPPED;
1300 gst_cc_converter_start (GstBaseTransform * base)
1302 GstCCConverter *self = GST_CCCONVERTER (base);
1304 /* Resetting this is not really needed but makes debugging easier */
1305 self->cdp_hdr_sequence_cntr = 0;
1311 gst_cc_converter_class_init (GstCCConverterClass * klass)
1313 GstElementClass *gstelement_class;
1314 GstBaseTransformClass *basetransform_class;
1316 gstelement_class = (GstElementClass *) klass;
1317 basetransform_class = (GstBaseTransformClass *) klass;
1319 gst_element_class_set_static_metadata (gstelement_class,
1320 "Closed Caption Converter",
1321 "Filter/ClosedCaption",
1322 "Converts Closed Captions between different formats",
1323 "Sebastian Dröge <sebastian@centricular.com>");
1325 gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
1326 gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
1328 basetransform_class->start = GST_DEBUG_FUNCPTR (gst_cc_converter_start);
1329 basetransform_class->transform_size =
1330 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_size);
1331 basetransform_class->transform_caps =
1332 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_caps);
1333 basetransform_class->fixate_caps =
1334 GST_DEBUG_FUNCPTR (gst_cc_converter_fixate_caps);
1335 basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_cc_converter_set_caps);
1336 basetransform_class->transform =
1337 GST_DEBUG_FUNCPTR (gst_cc_converter_transform);
1338 basetransform_class->passthrough_on_same_caps = TRUE;
1340 GST_DEBUG_CATEGORY_INIT (gst_cc_converter_debug, "ccconverter",
1341 0, "Closed Caption converter");
1345 gst_cc_converter_init (GstCCConverter * self)