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 MAX_CDP_PACKET_LEN bytes large */
69 *othersize = MAX_CDP_PACKET_LEN;
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->in_fps_n,
425 self->in_fps_n = self->in_fps_d = 0;
427 s = gst_caps_get_structure (outcaps, 0);
428 if (!gst_structure_get_fraction (s, "framerate", &self->out_fps_n,
430 self->out_fps_n = self->out_fps_d = 0;
432 /* Caps can be different but we can passthrough as long as they can
433 * intersect, i.e. have same caps name and format */
434 passthrough = gst_caps_can_intersect (incaps, outcaps);
435 gst_base_transform_set_passthrough (base, passthrough);
437 GST_DEBUG_OBJECT (self,
438 "Got caps %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT " (passthrough %d)",
439 incaps, outcaps, passthrough);
445 GST_ERROR_OBJECT (self,
446 "Invalid caps: in %" GST_PTR_FORMAT " out: %" GST_PTR_FORMAT, incaps,
459 static const struct cdp_fps_entry cdp_fps_table[] = {
460 {0x1f, 24000, 1001, 25},
463 {0x4f, 30000, 1001, 20},
466 {0x7f, 60000, 1001, 10},
469 static const struct cdp_fps_entry null_fps_entry = { 0, 0, 0, 0 };
471 static const struct cdp_fps_entry *
472 cdp_fps_entry_from_id (guint8 id)
475 for (i = 0; i < G_N_ELEMENTS (cdp_fps_table); i++) {
476 if (cdp_fps_table[i].fps_idx == id)
477 return &cdp_fps_table[i];
479 return &null_fps_entry;
482 static const struct cdp_fps_entry *
483 cdp_fps_entry_from_fps (guint fps_n, guint fps_d)
486 for (i = 0; i < G_N_ELEMENTS (cdp_fps_table); i++) {
487 if (cdp_fps_table[i].fps_n == fps_n && cdp_fps_table[i].fps_d == fps_d)
488 return &cdp_fps_table[i];
490 return &null_fps_entry;
493 /* Converts raw CEA708 cc_data and an optional timecode into CDP */
495 convert_cea708_cc_data_cea708_cdp_internal (GstCCConverter * self,
496 const guint8 * cc_data, guint cc_data_len, guint8 * cdp, guint cdp_len,
497 const GstVideoTimeCode * tc, const struct cdp_fps_entry *fps_entry)
500 guint8 flags, checksum;
503 gst_byte_writer_init_with_data (&bw, cdp, cdp_len, FALSE);
504 gst_byte_writer_put_uint16_be_unchecked (&bw, 0x9669);
505 /* Write a length of 0 for now */
506 gst_byte_writer_put_uint8_unchecked (&bw, 0);
508 gst_byte_writer_put_uint8_unchecked (&bw, fps_entry->fps_idx);
510 if (cc_data_len / 3 > fps_entry->max_cc_count) {
511 GST_WARNING_OBJECT (self, "Too many cc_data triplet for framerate: %u > %u",
512 cc_data_len / 3, fps_entry->max_cc_count);
513 cc_data_len = 3 * fps_entry->max_cc_count;
516 /* ccdata_present | caption_service_active */
519 /* time_code_present */
526 gst_byte_writer_put_uint8_unchecked (&bw, flags);
528 gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
531 gst_byte_writer_put_uint8_unchecked (&bw, 0x71);
532 gst_byte_writer_put_uint8_unchecked (&bw, 0xc0 |
533 (((tc->hours % 10) & 0x3) << 4) |
534 ((tc->hours - (tc->hours % 10)) & 0xf));
536 gst_byte_writer_put_uint8_unchecked (&bw, 0x80 |
537 (((tc->minutes % 10) & 0x7) << 4) |
538 ((tc->minutes - (tc->minutes % 10)) & 0xf));
540 gst_byte_writer_put_uint8_unchecked (&bw,
542 2 ? 0x00 : 0x80) | (((tc->seconds %
543 10) & 0x7) << 4) | ((tc->seconds -
544 (tc->seconds % 10)) & 0xf));
546 gst_byte_writer_put_uint8_unchecked (&bw,
547 ((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) ? 0x80 :
548 0x00) | (((tc->frames % 10) & 0x3) << 4) | ((tc->frames -
549 (tc->frames % 10)) & 0xf));
552 gst_byte_writer_put_uint8_unchecked (&bw, 0x72);
553 gst_byte_writer_put_uint8_unchecked (&bw, 0xe0 | fps_entry->max_cc_count);
554 gst_byte_writer_put_data_unchecked (&bw, cc_data, cc_data_len);
555 while (fps_entry->max_cc_count > cc_data_len / 3) {
556 gst_byte_writer_put_uint8_unchecked (&bw, 0xf8);
557 gst_byte_writer_put_uint8_unchecked (&bw, 0x00);
558 gst_byte_writer_put_uint8_unchecked (&bw, 0x00);
562 gst_byte_writer_put_uint8_unchecked (&bw, 0x74);
563 gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
564 self->cdp_hdr_sequence_cntr++;
565 /* We calculate the checksum afterwards */
566 gst_byte_writer_put_uint8_unchecked (&bw, 0);
568 len = gst_byte_writer_get_pos (&bw);
569 gst_byte_writer_set_pos (&bw, 2);
570 gst_byte_writer_put_uint8_unchecked (&bw, len);
573 for (i = 0; i < len; i++) {
577 checksum = 256 - checksum;
578 cdp[len - 1] = checksum;
583 /* Converts CDP into raw CEA708 cc_data */
585 convert_cea708_cdp_cea708_cc_data_internal (GstCCConverter * self,
586 const guint8 * cdp, guint cdp_len, guint8 cc_data[MAX_CDP_PACKET_LEN],
587 GstVideoTimeCode * tc, const struct cdp_fps_entry ** out_fps_entry)
594 const struct cdp_fps_entry *fps_entry;
596 *out_fps_entry = &null_fps_entry;
597 memset (tc, 0, sizeof (*tc));
599 /* Header + footer length */
603 gst_byte_reader_init (&br, cdp, cdp_len);
604 u16 = gst_byte_reader_get_uint16_be_unchecked (&br);
608 u8 = gst_byte_reader_get_uint8_unchecked (&br);
612 u8 = gst_byte_reader_get_uint8_unchecked (&br);
613 fps_entry = cdp_fps_entry_from_id (u8);
614 if (!fps_entry || fps_entry->fps_n == 0)
617 flags = gst_byte_reader_get_uint8_unchecked (&br);
619 if ((flags & 0x40) == 0)
622 /* cdp_hdr_sequence_cntr */
623 gst_byte_reader_skip_unchecked (&br, 2);
625 /* time_code_present */
627 guint8 hours, minutes, seconds, frames, fields;
630 if (gst_byte_reader_get_remaining (&br) < 5)
632 if (gst_byte_reader_get_uint8_unchecked (&br) != 0x71)
635 u8 = gst_byte_reader_get_uint8_unchecked (&br);
636 if ((u8 & 0xc) != 0xc)
639 hours = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
641 u8 = gst_byte_reader_get_uint8_unchecked (&br);
642 if ((u8 & 0x80) != 0x80)
644 minutes = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
646 u8 = gst_byte_reader_get_uint8_unchecked (&br);
651 seconds = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
653 u8 = gst_byte_reader_get_uint8_unchecked (&br);
657 drop_frame = ! !(u8 & 0x80);
658 frames = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
660 gst_video_time_code_init (tc, fps_entry->fps_n, fps_entry->fps_d, NULL,
661 drop_frame ? GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME :
662 GST_VIDEO_TIME_CODE_FLAGS_NONE, hours, minutes, seconds, frames,
670 if (gst_byte_reader_get_remaining (&br) < 2)
672 if (gst_byte_reader_get_uint8_unchecked (&br) != 0x72)
675 cc_count = gst_byte_reader_get_uint8_unchecked (&br);
676 if ((cc_count & 0xe0) != 0xe0)
681 if (gst_byte_reader_get_remaining (&br) < len)
684 memcpy (cc_data, gst_byte_reader_get_data_unchecked (&br, len), len);
687 *out_fps_entry = fps_entry;
689 /* skip everything else we don't care about */
695 convert_cea608_raw_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
701 n = gst_buffer_get_size (inbuf);
703 GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
704 gst_buffer_set_size (outbuf, 0);
711 GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u", n);
715 gst_buffer_set_size (outbuf, 3 * n);
717 gst_buffer_map (inbuf, &in, GST_MAP_READ);
718 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
720 /* We have to assume that each value is from the first field and
721 * don't know from which line offset it originally is */
722 for (i = 0; i < n; i++) {
723 out.data[i * 3] = 0x80;
724 out.data[i * 3 + 1] = in.data[i * 2];
725 out.data[i * 3 + 2] = in.data[i * 2 + 1];
728 gst_buffer_unmap (inbuf, &in);
729 gst_buffer_unmap (outbuf, &out);
735 convert_cea608_raw_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
741 n = gst_buffer_get_size (inbuf);
743 GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
744 gst_buffer_set_size (outbuf, 0);
751 GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u", n);
755 gst_buffer_set_size (outbuf, 3 * n);
757 gst_buffer_map (inbuf, &in, GST_MAP_READ);
758 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
760 /* We have to assume that each value is from the first field and
761 * don't know from which line offset it originally is */
762 for (i = 0; i < n; i++) {
763 out.data[i * 3] = 0xfc;
764 out.data[i * 3 + 1] = in.data[i * 2];
765 out.data[i * 3 + 2] = in.data[i * 2 + 1];
768 gst_buffer_unmap (inbuf, &in);
769 gst_buffer_unmap (outbuf, &out);
775 convert_cea608_raw_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
780 guint8 cc_data[MAX_CDP_PACKET_LEN];
781 const GstVideoTimeCodeMeta *tc_meta;
782 const struct cdp_fps_entry *fps_entry;
784 n = gst_buffer_get_size (inbuf);
786 GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
787 gst_buffer_set_size (outbuf, 0);
794 GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u", n);
798 gst_buffer_map (inbuf, &in, GST_MAP_READ);
799 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
801 for (i = 0; i < n; i++) {
802 cc_data[i * 3] = 0xfc;
803 cc_data[i * 3 + 1] = in.data[i * 2];
804 cc_data[i * 3 + 2] = in.data[i * 2 + 1];
807 fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
808 if (!fps_entry || fps_entry->fps_n == 0)
809 g_assert_not_reached ();
811 tc_meta = gst_buffer_get_video_time_code_meta (inbuf);
813 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, n * 3,
814 out.data, out.size, tc_meta ? &tc_meta->tc : NULL, fps_entry);
816 gst_buffer_unmap (inbuf, &in);
817 gst_buffer_unmap (outbuf, &out);
819 gst_buffer_set_size (outbuf, len);
825 convert_cea608_s334_1a_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
832 n = gst_buffer_get_size (inbuf);
834 GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
841 GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
845 gst_buffer_map (inbuf, &in, GST_MAP_READ);
846 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
848 for (i = 0; i < n; i++) {
849 if (in.data[i * 3] & 0x80) {
850 out.data[i * 2] = in.data[i * 3 + 1];
851 out.data[i * 2 + 1] = in.data[i * 3 + 2];
856 gst_buffer_unmap (inbuf, &in);
857 gst_buffer_unmap (outbuf, &out);
859 gst_buffer_set_size (outbuf, 2 * cea608);
865 convert_cea608_s334_1a_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
871 n = gst_buffer_get_size (inbuf);
873 GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
880 GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
884 gst_buffer_set_size (outbuf, 3 * n);
886 gst_buffer_map (inbuf, &in, GST_MAP_READ);
887 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
889 for (i = 0; i < n; i++) {
890 out.data[i * 3] = (in.data[i * 3] & 0x80) ? 0xfc : 0xfd;
891 out.data[i * 3 + 1] = in.data[i * 3 + 1];
892 out.data[i * 3 + 2] = in.data[i * 3 + 2];
895 gst_buffer_unmap (inbuf, &in);
896 gst_buffer_unmap (outbuf, &out);
902 convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
907 guint8 cc_data[MAX_CDP_PACKET_LEN];
908 const GstVideoTimeCodeMeta *tc_meta;
909 const struct cdp_fps_entry *fps_entry;
911 n = gst_buffer_get_size (inbuf);
913 GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
920 GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
924 gst_buffer_map (inbuf, &in, GST_MAP_READ);
925 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
927 for (i = 0; i < n; i++) {
928 cc_data[i * 3] = (in.data[i * 3] & 0x80) ? 0xfc : 0xfd;
929 cc_data[i * 3 + 1] = in.data[i * 3 + 1];
930 cc_data[i * 3 + 2] = in.data[i * 3 + 2];
933 fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
934 if (!fps_entry || fps_entry->fps_n == 0)
935 g_assert_not_reached ();
937 tc_meta = gst_buffer_get_video_time_code_meta (inbuf);
939 convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, n * 3,
940 out.data, out.size, tc_meta ? &tc_meta->tc : NULL, fps_entry);
942 gst_buffer_unmap (inbuf, &in);
943 gst_buffer_unmap (outbuf, &out);
945 gst_buffer_set_size (outbuf, len);
951 convert_cea708_cc_data_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
958 n = gst_buffer_get_size (inbuf);
960 GST_WARNING_OBJECT (self, "Invalid raw CEA708 buffer size");
967 GST_WARNING_OBJECT (self, "Too many CEA708 triplets %u", n);
971 gst_buffer_map (inbuf, &in, GST_MAP_READ);
972 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
974 for (i = 0; i < n; i++) {
975 /* We can only really copy the first field here as there can't be any
976 * signalling in raw CEA608 and we must not mix the streams of different
979 if (in.data[i * 3] == 0xfc) {
980 out.data[cea608 * 2] = in.data[i * 3 + 1];
981 out.data[cea608 * 2 + 1] = in.data[i * 3 + 2];
986 gst_buffer_unmap (inbuf, &in);
987 gst_buffer_unmap (outbuf, &out);
989 gst_buffer_set_size (outbuf, 2 * cea608);
995 convert_cea708_cc_data_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
1002 n = gst_buffer_get_size (inbuf);
1004 GST_WARNING_OBJECT (self, "Invalid raw CEA708 buffer size");
1011 GST_WARNING_OBJECT (self, "Too many CEA708 triplets %u", n);
1015 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1016 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1018 for (i = 0; i < n; i++) {
1019 if (in.data[i * 3] == 0xfc || in.data[i * 3] == 0xfd) {
1020 /* We have to assume a line offset of 0 */
1021 out.data[cea608 * 3] = in.data[i * 3] == 0xfc ? 0x80 : 0x00;
1022 out.data[cea608 * 3 + 1] = in.data[i * 3 + 1];
1023 out.data[cea608 * 3 + 2] = in.data[i * 3 + 2];
1028 gst_buffer_unmap (inbuf, &in);
1029 gst_buffer_unmap (outbuf, &out);
1031 gst_buffer_set_size (outbuf, 3 * cea608);
1036 static GstFlowReturn
1037 convert_cea708_cc_data_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
1043 const GstVideoTimeCodeMeta *tc_meta;
1044 const struct cdp_fps_entry *fps_entry;
1046 n = gst_buffer_get_size (inbuf);
1048 GST_WARNING_OBJECT (self, "Invalid raw CEA708 buffer size");
1055 GST_WARNING_OBJECT (self, "Too many CEA708 triplets %u", n);
1059 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1060 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1062 fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
1063 if (!fps_entry || fps_entry->fps_n == 0)
1064 g_assert_not_reached ();
1066 tc_meta = gst_buffer_get_video_time_code_meta (inbuf);
1068 convert_cea708_cc_data_cea708_cdp_internal (self, in.data, in.size,
1069 out.data, out.size, tc_meta ? &tc_meta->tc : NULL, fps_entry);
1071 gst_buffer_unmap (inbuf, &in);
1072 gst_buffer_unmap (outbuf, &out);
1074 gst_buffer_set_size (outbuf, len);
1079 static GstFlowReturn
1080 convert_cea708_cdp_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
1085 GstVideoTimeCode tc;
1086 guint8 cc_data[MAX_CDP_PACKET_LEN];
1087 guint len, cea608 = 0;
1088 const struct cdp_fps_entry *fps_entry;
1090 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1091 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1094 convert_cea708_cdp_cea708_cc_data_internal (self, in.data, in.size,
1095 cc_data, &tc, &fps_entry);
1098 if (len > fps_entry->max_cc_count) {
1099 GST_WARNING_OBJECT (self, "Too many cc_data triples in CDP packet %u", len);
1100 len = fps_entry->max_cc_count;
1103 for (i = 0; i < len; i++) {
1104 /* We can only really copy the first field here as there can't be any
1105 * signalling in raw CEA608 and we must not mix the streams of different
1108 if (cc_data[i * 3] == 0xfc) {
1109 out.data[cea608 * 2] = cc_data[i * 3 + 1];
1110 out.data[cea608 * 2 + 1] = cc_data[i * 3 + 2];
1115 gst_buffer_unmap (inbuf, &in);
1116 gst_buffer_unmap (outbuf, &out);
1118 gst_buffer_set_size (outbuf, 2 * cea608);
1120 if (tc.config.fps_n != 0 && !gst_buffer_get_video_time_code_meta (inbuf))
1121 gst_buffer_add_video_time_code_meta (outbuf, &tc);
1126 static GstFlowReturn
1127 convert_cea708_cdp_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
1132 GstVideoTimeCode tc;
1133 guint8 cc_data[MAX_CDP_PACKET_LEN];
1134 guint len, cea608 = 0;
1135 const struct cdp_fps_entry *fps_entry;
1137 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1138 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1141 convert_cea708_cdp_cea708_cc_data_internal (self, in.data, in.size,
1142 cc_data, &tc, &fps_entry);
1145 if (len > fps_entry->max_cc_count) {
1146 GST_WARNING_OBJECT (self, "Too many cc_data triples in CDP packet %u", len);
1147 len = fps_entry->max_cc_count;
1150 for (i = 0; i < len; i++) {
1151 if (cc_data[i * 3] == 0xfc || cc_data[i * 3] == 0xfd) {
1152 /* We have to assume a line offset of 0 */
1153 out.data[cea608 * 3] = cc_data[i * 3] == 0xfc ? 0x80 : 0x00;
1154 out.data[cea608 * 3 + 1] = cc_data[i * 3 + 1];
1155 out.data[cea608 * 3 + 2] = cc_data[i * 3 + 2];
1160 gst_buffer_unmap (inbuf, &in);
1161 gst_buffer_unmap (outbuf, &out);
1163 gst_buffer_set_size (outbuf, 3 * cea608);
1165 if (tc.config.fps_n != 0 && !gst_buffer_get_video_time_code_meta (inbuf))
1166 gst_buffer_add_video_time_code_meta (outbuf, &tc);
1171 static GstFlowReturn
1172 convert_cea708_cdp_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1176 GstVideoTimeCode tc;
1178 const struct cdp_fps_entry *fps_entry;
1180 gst_buffer_map (inbuf, &in, GST_MAP_READ);
1181 gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1184 convert_cea708_cdp_cea708_cc_data_internal (self, in.data, in.size,
1185 out.data, &tc, &fps_entry);
1187 gst_buffer_unmap (inbuf, &in);
1188 gst_buffer_unmap (outbuf, &out);
1190 if (len / 3 > fps_entry->max_cc_count) {
1191 GST_WARNING_OBJECT (self, "Too many cc_data triples in CDP packet %u",
1193 len = 3 * fps_entry->max_cc_count;
1196 gst_buffer_set_size (outbuf, len);
1198 if (tc.config.fps_n != 0 && !gst_buffer_get_video_time_code_meta (inbuf))
1199 gst_buffer_add_video_time_code_meta (outbuf, &tc);
1204 static GstFlowReturn
1205 gst_cc_converter_transform (GstCCConverter * self, GstBuffer * inbuf,
1208 GstVideoTimeCodeMeta *tc_meta = gst_buffer_get_video_time_code_meta (inbuf);
1209 GstFlowReturn ret = GST_FLOW_OK;
1211 GST_DEBUG_OBJECT (self, "Converting %" GST_PTR_FORMAT " from %u to %u", inbuf,
1212 self->input_caption_type, self->output_caption_type);
1214 switch (self->input_caption_type) {
1215 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1217 switch (self->output_caption_type) {
1218 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1219 ret = convert_cea608_raw_cea608_s334_1a (self, inbuf, outbuf);
1221 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1222 ret = convert_cea608_raw_cea708_cc_data (self, inbuf, outbuf);
1224 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1225 ret = convert_cea608_raw_cea708_cdp (self, inbuf, outbuf);
1227 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1229 g_assert_not_reached ();
1234 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1236 switch (self->output_caption_type) {
1237 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1238 ret = convert_cea608_s334_1a_cea608_raw (self, inbuf, outbuf);
1240 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1241 ret = convert_cea608_s334_1a_cea708_cc_data (self, inbuf, outbuf);
1243 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1244 ret = convert_cea608_s334_1a_cea708_cdp (self, inbuf, outbuf);
1246 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1248 g_assert_not_reached ();
1253 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1255 switch (self->output_caption_type) {
1256 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1257 ret = convert_cea708_cc_data_cea608_raw (self, inbuf, outbuf);
1259 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1260 ret = convert_cea708_cc_data_cea608_s334_1a (self, inbuf, outbuf);
1262 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1263 ret = convert_cea708_cc_data_cea708_cdp (self, inbuf, outbuf);
1265 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1267 g_assert_not_reached ();
1272 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1274 switch (self->output_caption_type) {
1275 case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
1276 ret = convert_cea708_cdp_cea608_raw (self, inbuf, outbuf);
1278 case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
1279 ret = convert_cea708_cdp_cea608_s334_1a (self, inbuf, outbuf);
1281 case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
1282 ret = convert_cea708_cdp_cea708_cc_data (self, inbuf, outbuf);
1284 case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
1286 g_assert_not_reached ();
1292 g_assert_not_reached ();
1296 if (ret != GST_FLOW_OK)
1300 gst_buffer_add_video_time_code_meta (outbuf, &tc_meta->tc);
1302 GST_DEBUG_OBJECT (self, "Converted to %" GST_PTR_FORMAT, outbuf);
1304 return gst_buffer_get_size (outbuf) >
1305 0 ? GST_FLOW_OK : GST_BASE_TRANSFORM_FLOW_DROPPED;
1308 static GstFlowReturn
1309 gst_cc_converter_generate_output (GstBaseTransform * base, GstBuffer ** outbuf)
1311 GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (base);
1312 GstCCConverter *self = GST_CCCONVERTER (base);
1313 GstBuffer *inbuf = base->queued_buf;
1317 base->queued_buf = NULL;
1322 if (gst_base_transform_is_passthrough (base)) {
1326 ret = bclass->prepare_output_buffer (base, inbuf, outbuf);
1328 if (ret != GST_FLOW_OK || *outbuf == NULL)
1331 ret = gst_cc_converter_transform (self, inbuf, *outbuf);
1332 gst_buffer_unref (inbuf);
1339 gst_buffer_unref (inbuf);
1341 GST_WARNING_OBJECT (self, "could not get buffer from pool: %s",
1342 gst_flow_get_name (ret));
1348 gst_cc_converter_start (GstBaseTransform * base)
1350 GstCCConverter *self = GST_CCCONVERTER (base);
1352 /* Resetting this is not really needed but makes debugging easier */
1353 self->cdp_hdr_sequence_cntr = 0;
1359 gst_cc_converter_class_init (GstCCConverterClass * klass)
1361 GstElementClass *gstelement_class;
1362 GstBaseTransformClass *basetransform_class;
1364 gstelement_class = (GstElementClass *) klass;
1365 basetransform_class = (GstBaseTransformClass *) klass;
1367 gst_element_class_set_static_metadata (gstelement_class,
1368 "Closed Caption Converter",
1369 "Filter/ClosedCaption",
1370 "Converts Closed Captions between different formats",
1371 "Sebastian Dröge <sebastian@centricular.com>");
1373 gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
1374 gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
1376 basetransform_class->start = GST_DEBUG_FUNCPTR (gst_cc_converter_start);
1377 basetransform_class->transform_size =
1378 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_size);
1379 basetransform_class->transform_caps =
1380 GST_DEBUG_FUNCPTR (gst_cc_converter_transform_caps);
1381 basetransform_class->fixate_caps =
1382 GST_DEBUG_FUNCPTR (gst_cc_converter_fixate_caps);
1383 basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_cc_converter_set_caps);
1384 basetransform_class->generate_output =
1385 GST_DEBUG_FUNCPTR (gst_cc_converter_generate_output);
1386 basetransform_class->passthrough_on_same_caps = TRUE;
1388 GST_DEBUG_CATEGORY_INIT (gst_cc_converter_debug, "ccconverter",
1389 0, "Closed Caption converter");
1393 gst_cc_converter_init (GstCCConverter * self)