• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * GStreamer
3  * Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25 
26 #include <gst/gst.h>
27 #include <gst/base/base.h>
28 #include <gst/video/video.h>
29 #include <string.h>
30 
31 #include "gstccconverter.h"
32 
33 GST_DEBUG_CATEGORY_STATIC (gst_cc_converter_debug);
34 #define GST_CAT_DEFAULT gst_cc_converter_debug
35 
36 /**
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
41  *
42  * Since: 1.20
43  */
44 
45 enum
46 {
47   PROP_0,
48   PROP_CDP_MODE,
49 };
50 
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)
52 
53 /* Ordered by the amount of information they can contain */
54 #define CC_CAPS \
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"
59 
60 #define VAL_OR_0(v) ((v) ? (*(v)) : 0)
61 
62 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
63     GST_PAD_SINK,
64     GST_PAD_ALWAYS,
65     GST_STATIC_CAPS (CC_CAPS));
66 
67 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
68     GST_PAD_SRC,
69     GST_PAD_ALWAYS,
70     GST_STATIC_CAPS (CC_CAPS));
71 
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);
76 
77 #define GST_TYPE_CC_CONVERTER_CDP_MODE (gst_cc_converter_cdp_mode_get_type())
78 static GType
gst_cc_converter_cdp_mode_get_type(void)79 gst_cc_converter_cdp_mode_get_type (void)
80 {
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",
85         "cc-data"},
86     {GST_CC_CONVERTER_CDP_MODE_CC_SVC_INFO,
87         "Store CC service information in CDP packets", "cc-svc-info"},
88     {0, NULL, NULL}
89   };
90   static GType id = 0;
91 
92   if (g_once_init_enter ((gsize *) & id)) {
93     GType _id;
94 
95     _id = g_flags_register_static ("GstCCConverterCDPMode", values);
96 
97     g_once_init_leave ((gsize *) & id, _id);
98   }
99 
100   return id;
101 }
102 
103 static gboolean
gst_cc_converter_transform_size(GstBaseTransform * base,GstPadDirection direction,GstCaps * caps,gsize size,GstCaps * othercaps,gsize * othersize)104 gst_cc_converter_transform_size (GstBaseTransform * base,
105     GstPadDirection direction,
106     GstCaps * caps, gsize size, GstCaps * othercaps, gsize * othersize)
107 {
108   /* We can't really convert from an output size to an input size */
109   if (direction != GST_PAD_SINK)
110     return FALSE;
111 
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 */
115 
116   *othersize = MAX_CDP_PACKET_LEN;
117 
118   return TRUE;
119 }
120 
121 static GstCaps *
gst_cc_converter_transform_caps(GstBaseTransform * base,GstPadDirection direction,GstCaps * caps,GstCaps * filter)122 gst_cc_converter_transform_caps (GstBaseTransform * base,
123     GstPadDirection direction, GstCaps * caps, GstCaps * filter)
124 {
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}");
134 
135   GstCCConverter *self = GST_CCCONVERTER (base);
136   guint i, n;
137   GstCaps *res, *templ;
138 
139   templ = gst_pad_get_pad_template_caps (base->srcpad);
140 
141   GST_DEBUG_OBJECT (self, "direction %s from caps %" GST_PTR_FORMAT,
142       direction == GST_PAD_SRC ? "src" : "sink", caps);
143 
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");
149 
150     if (gst_structure_has_name (s, "closedcaption/x-cea-608")) {
151 
152       if (direction == GST_PAD_SRC) {
153         /* SRC direction: We produce upstream caps
154          *
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.
158          *
159          * We can convert everything to CEA608.
160          */
161         res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps_framerate));
162         if (framerate) {
163           /* we can only keep the same framerate for non-cdp */
164           GstCaps *tmp;
165 
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);
169         } else {
170           res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
171         }
172       } else {
173         /* SINK: We produce downstream caps
174          *
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.
178          *
179          * If upstream provided a framerate we can pass that through, possibly
180          * filtered for the CDP case.
181          */
182         if (framerate) {
183           GstCaps *tmp;
184           GstStructure *t;
185 
186           /* Create caps that contain the intersection of all framerates with
187            * the CDP allowed framerates */
188           tmp =
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);
196 
197             tmp =
198                 gst_caps_make_writable (gst_static_caps_get
199                 (&cdp_caps_framerate));
200 
201             res = gst_caps_merge (res, tmp);
202           } else {
203             gst_caps_unref (tmp);
204           }
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);
209         } else {
210           res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
211         }
212       }
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
216          *
217          * Downstream wanted CEA708 caps. If downstream wants *only* CDP we
218          * either need CDP from upstream, or anything else with a CDP
219          * framerate.
220          * If downstream also wants non-CDP we can accept anything.
221          *
222          * We pass through any framerate as-is, except for filtering
223          * for CDP framerates if downstream wants only CDP.
224          */
225 
226         if (g_strcmp0 (gst_structure_get_string (s, "format"), "cdp") == 0) {
227           /* Downstream wants only CDP */
228 
229           /* We need CDP from upstream in that case */
230           res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps_framerate));
231 
232           /* Or anything else with a CDP framerate */
233           if (framerate) {
234             GstCaps *tmp;
235             GstStructure *t;
236             const GValue *cdp_framerate;
237 
238             /* Create caps that contain the intersection of all framerates with
239              * the CDP allowed framerates */
240             tmp =
241                 gst_caps_make_writable (gst_static_caps_get
242                 (&cdp_caps_framerate));
243             t = gst_caps_get_structure (tmp, 0);
244 
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);
251           } else {
252             GstCaps *tmp, *cdp_caps;
253             const GValue *cdp_framerate;
254 
255             /* Get all CDP framerates, we can accept anything that has those
256              * framerates */
257             cdp_caps = gst_static_caps_get (&cdp_caps_framerate);
258             cdp_framerate =
259                 gst_structure_get_value (gst_caps_get_structure (cdp_caps, 0),
260                 "framerate");
261 
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);
265 
266             res = gst_caps_merge (res, tmp);
267           }
268         } else {
269           /* Downstream wants not only CDP, we can do everything */
270           res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps_framerate));
271           if (framerate) {
272             /* we can only keep the same framerate for non-cdp */
273             GstCaps *tmp;
274 
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);
278           } else {
279             res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
280           }
281         }
282       } else {
283         GstCaps *tmp;
284 
285         /* SINK: We produce downstream caps
286          *
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.
291          *
292          * If upstream provided a framerate we pass that through for non-CDP
293          * output, and pass it through filtered for CDP output.
294          */
295 
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
299            * framerate */
300           res = gst_caps_merge (res, gst_static_caps_get (&cdp_caps_framerate));
301         } else if (framerate) {
302           const GValue *cdp_framerate;
303           GstStructure *t;
304 
305           /* Upstream did not provide CDP. We can only do CDP if upstream
306            * happened to have a CDP framerate */
307 
308           /* Create caps that contain the intersection of all framerates with
309            * the CDP allowed framerates */
310           tmp =
311               gst_caps_make_writable (gst_static_caps_get
312               (&cdp_caps_framerate));
313           t = gst_caps_get_structure (tmp, 0);
314 
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);
320 
321             res = gst_caps_merge (res, tmp);
322           } else {
323             gst_clear_caps (&tmp);
324           }
325         }
326         /* We can always convert CEA708 to all non-CDP formats */
327         if (framerate) {
328           /* we can only keep the same framerate for non-cdp */
329           GstCaps *tmp;
330 
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);
334         } else {
335           res = gst_caps_merge (res, gst_static_caps_get (&non_cdp_caps));
336         }
337       }
338     } else {
339       g_assert_not_reached ();
340     }
341   }
342 
343   GST_DEBUG_OBJECT (self, "pre filter caps %" GST_PTR_FORMAT, res);
344 
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
347    * in the end */
348   if (filter) {
349     GstCaps *tmp;
350     filter = gst_caps_intersect_full (templ, filter, GST_CAPS_INTERSECT_FIRST);
351 
352     tmp = gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
353     gst_caps_unref (res);
354     gst_caps_unref (filter);
355     res = tmp;
356   }
357 
358   gst_caps_unref (templ);
359 
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);
364 
365   return res;
366 }
367 
368 static GstCaps *
gst_cc_converter_fixate_caps(GstBaseTransform * base,GstPadDirection direction,GstCaps * incaps,GstCaps * outcaps)369 gst_cc_converter_fixate_caps (GstBaseTransform * base,
370     GstPadDirection direction, GstCaps * incaps, GstCaps * outcaps)
371 {
372   GstCCConverter *self = GST_CCCONVERTER (base);
373   const GstStructure *s;
374   GstStructure *t;
375   const GValue *framerate;
376   GstCaps *intersection, *templ;
377 
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);
381 
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));
387   }
388 
389   /* Otherwise prefer caps in the order of our template caps */
390   templ = gst_pad_get_pad_template_caps (base->srcpad);
391   intersection =
392       gst_caps_intersect_full (templ, outcaps, GST_CAPS_INTERSECT_FIRST);
393   gst_caps_unref (outcaps);
394   outcaps = intersection;
395 
396   outcaps =
397       GST_BASE_TRANSFORM_CLASS (parent_class)->fixate_caps (base, direction,
398       incaps, outcaps);
399 
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);
404   if (!framerate) {
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");
408   } else {
409     /* or passthrough the input framerate if possible */
410     guint n, d;
411 
412     n = gst_value_get_fraction_numerator (framerate);
413     d = gst_value_get_fraction_denominator (framerate);
414 
415     if (gst_structure_has_field (t, "framerate"))
416       gst_structure_fixate_field_nearest_fraction (t, "framerate", n, d);
417     else
418       gst_structure_set (t, "framerate", GST_TYPE_FRACTION, n, d, NULL);
419   }
420 
421   GST_DEBUG_OBJECT (self,
422       "Fixated caps %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT, incaps, outcaps);
423 
424   return outcaps;
425 }
426 
427 static gboolean
gst_cc_converter_set_caps(GstBaseTransform * base,GstCaps * incaps,GstCaps * outcaps)428 gst_cc_converter_set_caps (GstBaseTransform * base, GstCaps * incaps,
429     GstCaps * outcaps)
430 {
431   GstCCConverter *self = GST_CCCONVERTER (base);
432   const GstStructure *s;
433   gboolean passthrough;
434 
435   self->input_caption_type = gst_video_caption_type_from_caps (incaps);
436   self->output_caption_type = gst_video_caption_type_from_caps (outcaps);
437 
438   if (self->input_caption_type == GST_VIDEO_CAPTION_TYPE_UNKNOWN ||
439       self->output_caption_type == GST_VIDEO_CAPTION_TYPE_UNKNOWN)
440     goto invalid_caps;
441 
442   s = gst_caps_get_structure (incaps, 0);
443   if (!gst_structure_get_fraction (s, "framerate", &self->in_fps_n,
444           &self->in_fps_d))
445     self->in_fps_n = self->in_fps_d = 0;
446 
447   s = gst_caps_get_structure (outcaps, 0);
448   if (!gst_structure_get_fraction (s, "framerate", &self->out_fps_n,
449           &self->out_fps_d))
450     self->out_fps_n = self->out_fps_d = 0;
451 
452   gst_video_time_code_clear (&self->current_output_timecode);
453 
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);
458 
459   GST_DEBUG_OBJECT (self,
460       "Got caps %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT " (passthrough %d)",
461       incaps, outcaps, passthrough);
462 
463   return TRUE;
464 
465 invalid_caps:
466   {
467     GST_ERROR_OBJECT (self,
468         "Invalid caps: in %" GST_PTR_FORMAT " out: %" GST_PTR_FORMAT, incaps,
469         outcaps);
470     return FALSE;
471   }
472 }
473 
474 struct cdp_fps_entry
475 {
476   guint8 fps_idx;
477   guint fps_n, fps_d;
478   guint max_cc_count;
479   guint max_ccp_count;
480   guint max_cea608_count;
481 };
482 
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},
492 };
493 static const struct cdp_fps_entry null_fps_entry = { 0, 0, 0, 0 };
494 
495 static const struct cdp_fps_entry *
cdp_fps_entry_from_id(guint8 id)496 cdp_fps_entry_from_id (guint8 id)
497 {
498   int i;
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];
502   }
503   return &null_fps_entry;
504 }
505 
506 static const struct cdp_fps_entry *
cdp_fps_entry_from_fps(guint fps_n,guint fps_d)507 cdp_fps_entry_from_fps (guint fps_n, guint fps_d)
508 {
509   int i;
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];
513   }
514   return &null_fps_entry;
515 }
516 
517 static void
get_framerate_output_scale(GstCCConverter * self,const struct cdp_fps_entry * in_fps_entry,gint * scale_n,gint * scale_d)518 get_framerate_output_scale (GstCCConverter * self,
519     const struct cdp_fps_entry *in_fps_entry, gint * scale_n, gint * scale_d)
520 {
521   if (self->in_fps_n == 0 || self->out_fps_d == 0) {
522     *scale_n = 1;
523     *scale_d = 1;
524     return;
525   }
526 
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 ();
532 }
533 
534 static gboolean
interpolate_time_code_with_framerate(GstCCConverter * self,const GstVideoTimeCode * tc,gint out_fps_n,gint out_fps_d,gint scale_n,gint scale_d,GstVideoTimeCode * out)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)
538 {
539   gchar *tc_str;
540   gint output_n, output_d;
541   guint output_frame;
542   GstVideoTimeCodeFlags flags;
543 
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);
549 
550   if (!tc || tc->config.fps_n == 0)
551     return FALSE;
552 
553   if (!gst_util_fraction_multiply (tc->frames, 1, scale_n, scale_d, &output_n,
554           &output_d))
555     /* we should never overflow */
556     g_assert_not_reached ();
557 
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);
561   g_free (tc_str);
562 
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;
566   }
567 
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;
577   }
578 
579   output_frame = output_n / output_d;
580 
581   *out = (GstVideoTimeCode) GST_VIDEO_TIME_CODE_INIT;
582   do {
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
585      * minute */
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);
590     output_frame++;
591   } while ((flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) != 0
592       && output_frame < 10 && !gst_video_time_code_is_valid (out));
593 
594   tc_str = gst_video_time_code_to_string (out);
595   GST_TRACE_OBJECT (self, "interpolated to %s", tc_str);
596   g_free (tc_str);
597 
598   return TRUE;
599 }
600 
601 /* remove padding bytes from a cc_data packet. Returns the length of the new
602  * data in @cc_data */
603 static guint
compact_cc_data(guint8 * cc_data,guint cc_data_len)604 compact_cc_data (guint8 * cc_data, guint cc_data_len)
605 {
606   gboolean started_ccp = FALSE;
607   guint out_len = 0;
608   guint i;
609 
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);
613   }
614 
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;
618 
619     if (!started_ccp && (cc_type == 0x00 || cc_type == 0x01)) {
620       if (cc_valid) {
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];
625       }
626       continue;
627     }
628 
629     if (cc_type & 0x10)
630       started_ccp = TRUE;
631 
632     if (!cc_valid)
633       continue;
634 
635     if (cc_type == 0x00 || cc_type == 0x01) {
636       GST_WARNING ("Invalid cc_data.  cea608 bytes after cea708");
637       return 0;
638     }
639 
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];
643   }
644 
645   GST_LOG ("compacted cc_data from %u to %u", cc_data_len, out_len);
646 
647   return out_len;
648 }
649 
650 static gint
cc_data_extract_cea608(guint8 * cc_data,guint cc_data_len,guint8 * cea608_field1,guint * cea608_field1_len,guint8 * cea608_field2,guint * cea608_field2_len)651 cc_data_extract_cea608 (guint8 * cc_data, guint cc_data_len,
652     guint8 * cea608_field1, guint * cea608_field1_len,
653     guint8 * cea608_field2, guint * cea608_field2_len)
654 {
655   guint i, field_1_len = 0, field_2_len = 0;
656 
657   if (cea608_field1_len) {
658     field_1_len = *cea608_field1_len;
659     *cea608_field1_len = 0;
660   }
661   if (cea608_field2_len) {
662     field_2_len = *cea608_field2_len;
663     *cea608_field2_len = 0;
664   }
665 
666   if (cc_data_len % 3 != 0) {
667     GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple "
668         "of 3", cc_data_len);
669     cc_data_len = cc_data_len - (cc_data_len % 3);
670   }
671 
672   for (i = 0; i < cc_data_len / 3; i++) {
673     gboolean cc_valid = (cc_data[i * 3] & 0x04) == 0x04;
674     guint8 cc_type = cc_data[i * 3] & 0x03;
675 
676     GST_TRACE ("0x%02x 0x%02x 0x%02x, valid: %u, type: 0b%u%u",
677         cc_data[i * 3 + 0], cc_data[i * 3 + 1], cc_data[i * 3 + 2], cc_valid,
678         cc_type & 0x2, cc_type & 0x1);
679 
680     if (cc_type == 0x00) {
681       if (!cc_valid)
682         continue;
683 
684       if (cea608_field1 && cea608_field1_len) {
685         if (*cea608_field1_len + 2 > field_1_len) {
686           GST_WARNING ("Too many cea608 input bytes %u for field 1",
687               *cea608_field1_len + 2);
688           return -1;
689         }
690         cea608_field1[(*cea608_field1_len)++] = cc_data[i * 3 + 1];
691         cea608_field1[(*cea608_field1_len)++] = cc_data[i * 3 + 2];
692       }
693     } else if (cc_type == 0x01) {
694       if (!cc_valid)
695         continue;
696 
697       if (cea608_field2 && cea608_field2_len) {
698         if (*cea608_field2_len + 2 > field_2_len) {
699           GST_WARNING ("Too many cea608 input bytes %u for field 2",
700               *cea608_field2_len + 2);
701           return -1;
702         }
703         cea608_field2[(*cea608_field2_len)++] = cc_data[i * 3 + 1];
704         cea608_field2[(*cea608_field2_len)++] = cc_data[i * 3 + 2];
705       }
706     } else {
707       /* all cea608 packets must be at the beginning of a cc_data */
708       break;
709     }
710   }
711 
712   g_assert_cmpint (i * 3, <=, cc_data_len);
713 
714   GST_LOG ("Extracted cea608-1 of length %u and cea608-2 of length %u",
715       VAL_OR_0 (cea608_field1_len), VAL_OR_0 (cea608_field2_len));
716 
717   return i * 3;
718 }
719 
720 static void
store_cc_data(GstCCConverter * self,const guint8 * ccp_data,guint ccp_data_len,const guint8 * cea608_1,guint cea608_1_len,const guint8 * cea608_2,guint cea608_2_len)721 store_cc_data (GstCCConverter * self, const guint8 * ccp_data,
722     guint ccp_data_len, const guint8 * cea608_1, guint cea608_1_len,
723     const guint8 * cea608_2, guint cea608_2_len)
724 {
725   GST_DEBUG_OBJECT (self, "holding data of len ccp:%u, cea608 1:%u, "
726       "cea608 2:%u until next input buffer", ccp_data_len, cea608_1_len,
727       cea608_2_len);
728 
729   if (ccp_data && ccp_data_len > 0) {
730     memcpy (self->scratch_ccp, ccp_data, ccp_data_len);
731     self->scratch_ccp_len = ccp_data_len;
732   } else {
733     self->scratch_ccp_len = 0;
734   }
735   g_assert_cmpint (self->scratch_ccp_len, <, sizeof (self->scratch_ccp));
736 
737   if (cea608_1 && cea608_1_len > 0) {
738     memcpy (self->scratch_cea608_1, cea608_1, cea608_1_len);
739     self->scratch_cea608_1_len = cea608_1_len;
740   } else {
741     self->scratch_cea608_1_len = 0;
742   }
743   g_assert_cmpint (self->scratch_cea608_1_len, <,
744       sizeof (self->scratch_cea608_1));
745 
746   if (cea608_2 && cea608_2_len > 0) {
747     memcpy (self->scratch_cea608_2, cea608_2, cea608_2_len);
748     self->scratch_cea608_2_len = cea608_2_len;
749   } else {
750     self->scratch_cea608_2_len = 0;
751   }
752   g_assert_cmpint (self->scratch_cea608_2_len, <,
753       sizeof (self->scratch_cea608_2));
754 }
755 
756 static gboolean
combine_cc_data(GstCCConverter * self,gboolean pad_cea608,const struct cdp_fps_entry * out_fps_entry,const guint8 * ccp_data,guint ccp_data_len,const guint8 * cea608_1,guint cea608_1_len,const guint8 * cea608_2,guint cea608_2_len,guint8 * out,guint * out_size)757 combine_cc_data (GstCCConverter * self, gboolean pad_cea608,
758     const struct cdp_fps_entry *out_fps_entry, const guint8 * ccp_data,
759     guint ccp_data_len, const guint8 * cea608_1, guint cea608_1_len,
760     const guint8 * cea608_2, guint cea608_2_len, guint8 * out, guint * out_size)
761 {
762   guint i = 0, out_i = 0, max_size = 0, cea608_1_i = 0, cea608_2_i = 0;
763   guint cea608_output_count;
764   guint total_cea608_1_count, total_cea608_2_count;
765 
766   g_assert (out);
767   g_assert (out_size);
768   g_assert (!ccp_data || ccp_data_len % 3 == 0);
769   g_assert (!cea608_1 || cea608_1_len % 2 == 0);
770   g_assert (!cea608_2 || cea608_2_len % 2 == 0);
771   cea608_1_len /= 2;
772   cea608_2_len /= 2;
773 #if 0
774   /* FIXME: if cea608 field 2 is generated, field 1 needs to be generated,
775    * However that is not possible for 60fps (where only one cea608 field fits)
776    * without adding previous output buffer tracking */
777   g_assert_cmpint (cea608_1_len >= cea608_2_len);
778 #endif
779   g_assert_cmpint (cea608_1_len + cea608_2_len, <=,
780       out_fps_entry->max_cea608_count);
781 
782   total_cea608_1_count = cea608_1_len;
783   total_cea608_2_count = cea608_2_len;
784 
785 #if 0
786   /* FIXME: if cea608 field 2 is generated, field 1 needs to be generated. */
787   if (cea608_1_len < cea608_2_len)
788     total_cea608_1_count += cea608_2_len - cea608_1_len;
789 #endif
790 
791   max_size = ccp_data_len + (total_cea608_1_count + total_cea608_2_count) * 3;
792   if (*out_size < max_size) {
793     GST_WARNING_OBJECT (self, "Output data too small (%u < %u)", *out_size,
794         max_size);
795     return FALSE;
796   }
797 
798   /* FIXME: interlacing, tff, rff, ensuring cea608 field1 is generated if
799    * field2 exists even across packets */
800 
801   cea608_output_count = cea608_1_len + cea608_2_len;
802   if (pad_cea608) {
803     for (i = total_cea608_1_count + total_cea608_2_count;
804         i < out_fps_entry->max_cea608_count; i++) {
805       /* try to pad evenly */
806       if (i > cea608_1_len / 2)
807         total_cea608_1_count++;
808       else
809         total_cea608_2_count++;
810       cea608_output_count++;
811     }
812   }
813 
814   GST_LOG ("writing %u cea608-1 fields and %u cea608-2 fields",
815       total_cea608_1_count, total_cea608_2_count);
816   g_assert_cmpint (total_cea608_1_count + total_cea608_2_count, <=,
817       out_fps_entry->max_cea608_count);
818 
819   while (cea608_1_i + cea608_2_i < cea608_output_count) {
820     if (cea608_1_i < cea608_1_len) {
821       out[out_i++] = 0xfc;
822       out[out_i++] = cea608_1[cea608_1_i * 2];
823       out[out_i++] = cea608_1[cea608_1_i * 2 + 1];
824       cea608_1_i++;
825       i++;
826     } else if (cea608_1_i < total_cea608_1_count) {
827       out[out_i++] = 0xf8;
828       out[out_i++] = 0x80;
829       out[out_i++] = 0x80;
830       cea608_1_i++;
831       i++;
832     }
833 
834     if (cea608_2_i < cea608_2_len) {
835       out[out_i++] = 0xfd;
836       out[out_i++] = cea608_2[cea608_2_i * 2];
837       out[out_i++] = cea608_2[cea608_2_i * 2 + 1];
838       cea608_2_i++;
839       i++;
840     } else if (cea608_2_i < total_cea608_2_count) {
841       out[out_i++] = 0xf9;
842       out[out_i++] = 0x80;
843       out[out_i++] = 0x80;
844       cea608_2_i++;
845       i++;
846     }
847   }
848 
849   g_assert_cmpint (out_i / 3, <=, out_fps_entry->max_cea608_count);
850 
851   *out_size = out_i;
852 
853   if (ccp_data) {
854     memcpy (&out[out_i], ccp_data, ccp_data_len);
855     *out_size += ccp_data_len;
856   }
857 
858   g_assert_cmpint (*out_size, <, MAX_CDP_PACKET_LEN);
859 
860   return TRUE;
861 }
862 
863 /* takes cc_data cea608_1, cea608_2 and attempts to fit it into a hypothetical
864  * output packet.  Any leftover data is stored for later addition.  Returns
865  * whether any output can be generated. @ccp_data_len, @cea608_1_len,
866  * @cea608_2_len are also updated to reflect the size of that data to add to
867  * the output packet */
868 static gboolean
fit_and_scale_cc_data(GstCCConverter * self,const struct cdp_fps_entry * in_fps_entry,const struct cdp_fps_entry * out_fps_entry,const guint8 * ccp_data,guint * ccp_data_len,const guint8 * cea608_1,guint * cea608_1_len,const guint8 * cea608_2,guint * cea608_2_len,const GstVideoTimeCode * tc)869 fit_and_scale_cc_data (GstCCConverter * self,
870     const struct cdp_fps_entry *in_fps_entry,
871     const struct cdp_fps_entry *out_fps_entry, const guint8 * ccp_data,
872     guint * ccp_data_len, const guint8 * cea608_1, guint * cea608_1_len,
873     const guint8 * cea608_2, guint * cea608_2_len, const GstVideoTimeCode * tc)
874 {
875   if (!in_fps_entry || in_fps_entry->fps_n == 0) {
876     in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
877     if (!in_fps_entry || in_fps_entry->fps_n == 0)
878       g_assert_not_reached ();
879   }
880 
881   /* This is slightly looser than checking for the exact framerate as the cdp
882    * spec allow for 0.1% difference between framerates to be considered equal */
883   if (in_fps_entry->max_cc_count == out_fps_entry->max_cc_count) {
884     if (tc && tc->config.fps_n != 0)
885       interpolate_time_code_with_framerate (self, tc, out_fps_entry->fps_n,
886           out_fps_entry->fps_d, 1, 1, &self->current_output_timecode);
887 
888     self->scratch_ccp_len = 0;
889     self->scratch_cea608_1_len = 0;
890     self->scratch_cea608_2_len = 0;
891     self->input_frames = 0;
892     self->output_frames = 0;
893   } else {
894     int input_frame_n, input_frame_d, output_frame_n, output_frame_d;
895     int output_time_cmp, scale_n, scale_d, rate_cmp;
896 
897     /* TODO: handle input discont */
898 
899     /* compute the relative frame count for each */
900     if (!gst_util_fraction_multiply (self->in_fps_d, self->in_fps_n,
901             self->input_frames, 1, &input_frame_n, &input_frame_d))
902       /* we should never overflow */
903       g_assert_not_reached ();
904 
905     if (!gst_util_fraction_multiply (self->out_fps_d, self->out_fps_n,
906             self->output_frames, 1, &output_frame_n, &output_frame_d))
907       /* we should never overflow */
908       g_assert_not_reached ();
909 
910     output_time_cmp = gst_util_fraction_compare (input_frame_n, input_frame_d,
911         output_frame_n, output_frame_d);
912 
913     /* compute the relative rates of the two framerates */
914     get_framerate_output_scale (self, in_fps_entry, &scale_n, &scale_d);
915 
916     rate_cmp = gst_util_fraction_compare (scale_n, scale_d, 1, 1);
917 
918     GST_TRACE_OBJECT (self, "performing framerate conversion at scale %d/%d "
919         "of cc data of with sizes, ccp:%u, cea608-1:%u, cea608-2:%u", scale_n,
920         scale_d, VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
921         VAL_OR_0 (cea608_2_len));
922 
923     if (rate_cmp == 0) {
924       /* we are not scaling. Should never happen with current conditions
925        * above */
926       g_assert_not_reached ();
927     } else if (output_time_cmp < 0) {
928       /* we can't generate an output yet */
929       guint cd_len = ccp_data_len ? *ccp_data_len : 0;
930       guint c1_len = cea608_1_len ? *cea608_1_len : 0;
931       guint c2_len = cea608_2_len ? *cea608_2_len : 0;
932 
933       store_cc_data (self, ccp_data, cd_len, cea608_1, c1_len, cea608_2,
934           c2_len);
935       if (ccp_data_len)
936         *ccp_data_len = 0;
937       if (cea608_1_len)
938         *cea608_1_len = 0;
939       if (cea608_2_len)
940         *cea608_2_len = 0;
941       return FALSE;
942     } else if (rate_cmp != 0) {
943       /* we are changing the framerate and may overflow the max output packet
944        * size. Split them where necessary. */
945       gint extra_ccp = 0, extra_cea608_1 = 0, extra_cea608_2 = 0;
946       gint ccp_off = 0, cea608_1_off = 0, cea608_2_off = 0;
947 
948       if (output_time_cmp == 0) {
949         /* we have completed a cycle and can reset our counters to avoid
950          * overflow. Anything that fits into the output packet will be written */
951         GST_LOG_OBJECT (self, "cycle completed, resetting frame counters");
952         self->scratch_ccp_len = 0;
953         self->scratch_cea608_1_len = 0;
954         self->scratch_cea608_2_len = 0;
955         self->input_frames = 0;
956         self->output_frames = 0;
957       }
958 
959       if (ccp_data_len) {
960         extra_ccp = *ccp_data_len - 3 * out_fps_entry->max_ccp_count;
961         extra_ccp = MAX (0, extra_ccp);
962         ccp_off = *ccp_data_len - extra_ccp;
963       }
964 
965       if (cea608_1_len) {
966         extra_cea608_1 = *cea608_1_len - 2 * out_fps_entry->max_cea608_count;
967         extra_cea608_1 = MAX (0, extra_cea608_1);
968         cea608_1_off = *cea608_1_len - extra_cea608_1;
969       }
970 
971       if (cea608_2_len) {
972         /* this prefers using field1 data first. This may not be quite correct */
973         if (extra_cea608_1 > 0) {
974           /* all the cea608 space is for field 1 */
975           extra_cea608_2 = *cea608_2_len;
976           cea608_2_off = 0;
977         } else if (cea608_1_len) {
978           /* cea608 space is shared between field 1 and field 2 */
979           extra_cea608_2 =
980               *cea608_1_len + *cea608_2_len -
981               2 * out_fps_entry->max_cea608_count;
982           extra_cea608_2 = MAX (0, extra_cea608_2);
983           cea608_2_off = *cea608_2_len - extra_cea608_2;
984         } else {
985           /* all of the cea608 space is for field 2 */
986           extra_cea608_2 = *cea608_2_len - 2 * out_fps_entry->max_cea608_count;
987           extra_cea608_2 = MAX (0, extra_cea608_2);
988           cea608_2_off = *cea608_2_len - extra_cea608_2;
989         }
990       }
991 
992       if (extra_ccp > 0 || extra_cea608_1 > 0 || extra_cea608_2 > 0) {
993         /* packet would overflow, push extra bytes into the next packet */
994         GST_DEBUG_OBJECT (self, "buffer would overflow by %u ccp bytes, "
995             "%u cea608 field 1 bytes, or %u cea608 field 2 bytes", extra_ccp,
996             extra_cea608_1, extra_cea608_2);
997         store_cc_data (self, &ccp_data[ccp_off], extra_ccp,
998             &cea608_1[cea608_1_off], extra_cea608_1, &cea608_2[cea608_2_off],
999             extra_cea608_2);
1000         if (ccp_data_len)
1001           *ccp_data_len = MIN (*ccp_data_len, ccp_off);
1002         if (cea608_1_len)
1003           *cea608_1_len = MIN (*cea608_1_len, cea608_1_off);
1004         if (cea608_2_len)
1005           *cea608_2_len = MIN (*cea608_2_len, cea608_2_off);
1006       } else {
1007         GST_DEBUG_OBJECT (self, "section sizes of %u ccp bytes, "
1008             "%u cea608 field 1 bytes, and %u cea608 field 2 bytes fit within "
1009             "output packet", VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
1010             VAL_OR_0 (cea608_2_len));
1011         self->scratch_ccp_len = 0;
1012         self->scratch_cea608_1_len = 0;
1013         self->scratch_cea608_2_len = 0;
1014       }
1015     } else {
1016       g_assert_not_reached ();
1017     }
1018 
1019     if (tc && tc->config.fps_n != 0)
1020       interpolate_time_code_with_framerate (self, tc, out_fps_entry->fps_n,
1021           out_fps_entry->fps_d, scale_n, scale_d,
1022           &self->current_output_timecode);
1023   }
1024 
1025   g_assert_cmpint (VAL_OR_0 (ccp_data_len) + (VAL_OR_0 (cea608_1_len) +
1026           VAL_OR_0 (cea608_2_len)) / 2 * 3, <=,
1027       3 * out_fps_entry->max_cc_count);
1028 
1029   GST_DEBUG_OBJECT (self, "write out packet with lengths ccp:%u, cea608-1:%u, "
1030       "cea608-2:%u", VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
1031       VAL_OR_0 (cea608_2_len));
1032 
1033   return TRUE;
1034 }
1035 
1036 /* Converts raw CEA708 cc_data and an optional timecode into CDP */
1037 static guint
convert_cea708_cc_data_cea708_cdp_internal(GstCCConverter * self,const guint8 * cc_data,guint cc_data_len,guint8 * cdp,guint cdp_len,const GstVideoTimeCode * tc,const struct cdp_fps_entry * fps_entry)1038 convert_cea708_cc_data_cea708_cdp_internal (GstCCConverter * self,
1039     const guint8 * cc_data, guint cc_data_len, guint8 * cdp, guint cdp_len,
1040     const GstVideoTimeCode * tc, const struct cdp_fps_entry *fps_entry)
1041 {
1042   GstByteWriter bw;
1043   guint8 flags, checksum;
1044   guint i, len;
1045 
1046   GST_DEBUG_OBJECT (self, "writing out cdp packet from cc_data with length %u",
1047       cc_data_len);
1048 
1049   gst_byte_writer_init_with_data (&bw, cdp, cdp_len, FALSE);
1050   gst_byte_writer_put_uint16_be_unchecked (&bw, 0x9669);
1051   /* Write a length of 0 for now */
1052   gst_byte_writer_put_uint8_unchecked (&bw, 0);
1053 
1054   gst_byte_writer_put_uint8_unchecked (&bw, fps_entry->fps_idx);
1055 
1056   if (cc_data_len / 3 > fps_entry->max_cc_count) {
1057     GST_WARNING_OBJECT (self, "Too many cc_data triplets for framerate: %u. "
1058         "Truncating to %u", cc_data_len / 3, fps_entry->max_cc_count);
1059     cc_data_len = 3 * fps_entry->max_cc_count;
1060   }
1061 
1062   /* caption_service_active */
1063   flags = 0x02;
1064 
1065   /* ccdata_present */
1066   if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_CC_DATA))
1067     flags |= 0x40;
1068 
1069   /* time_code_present */
1070   if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_TIME_CODE) && tc
1071       && tc->config.fps_n > 0)
1072     flags |= 0x80;
1073 
1074   /* reserved */
1075   flags |= 0x01;
1076 
1077   gst_byte_writer_put_uint8_unchecked (&bw, flags);
1078 
1079   gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
1080 
1081   if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_TIME_CODE) && tc
1082       && tc->config.fps_n > 0) {
1083     guint8 u8;
1084 
1085     gst_byte_writer_put_uint8_unchecked (&bw, 0x71);
1086     /* reserved 11 - 2 bits */
1087     u8 = 0xc0;
1088     /* tens of hours - 2 bits */
1089     u8 |= ((tc->hours / 10) & 0x3) << 4;
1090     /* units of hours - 4 bits */
1091     u8 |= (tc->hours % 10) & 0xf;
1092     gst_byte_writer_put_uint8_unchecked (&bw, u8);
1093 
1094     /* reserved 1 - 1 bit */
1095     u8 = 0x80;
1096     /* tens of minutes - 3 bits */
1097     u8 |= ((tc->minutes / 10) & 0x7) << 4;
1098     /* units of minutes - 4 bits */
1099     u8 |= (tc->minutes % 10) & 0xf;
1100     gst_byte_writer_put_uint8_unchecked (&bw, u8);
1101 
1102     /* field flag - 1 bit */
1103     u8 = tc->field_count < 2 ? 0x00 : 0x80;
1104     /* tens of seconds - 3 bits */
1105     u8 |= ((tc->seconds / 10) & 0x7) << 4;
1106     /* units of seconds - 4 bits */
1107     u8 |= (tc->seconds % 10) & 0xf;
1108     gst_byte_writer_put_uint8_unchecked (&bw, u8);
1109 
1110     /* drop frame flag - 1 bit */
1111     u8 = (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) ? 0x80 :
1112         0x00;
1113     /* reserved0 - 1 bit */
1114     /* tens of frames - 2 bits */
1115     u8 |= ((tc->frames / 10) & 0x3) << 4;
1116     /* units of frames 4 bits */
1117     u8 |= (tc->frames % 10) & 0xf;
1118     gst_byte_writer_put_uint8_unchecked (&bw, u8);
1119   }
1120 
1121   if ((self->cdp_mode & GST_CC_CONVERTER_CDP_MODE_CC_DATA)) {
1122     gst_byte_writer_put_uint8_unchecked (&bw, 0x72);
1123     gst_byte_writer_put_uint8_unchecked (&bw, 0xe0 | fps_entry->max_cc_count);
1124     gst_byte_writer_put_data_unchecked (&bw, cc_data, cc_data_len);
1125     while (fps_entry->max_cc_count > cc_data_len / 3) {
1126       gst_byte_writer_put_uint8_unchecked (&bw, 0xfa);
1127       gst_byte_writer_put_uint8_unchecked (&bw, 0x00);
1128       gst_byte_writer_put_uint8_unchecked (&bw, 0x00);
1129       cc_data_len += 3;
1130     }
1131   }
1132 
1133   gst_byte_writer_put_uint8_unchecked (&bw, 0x74);
1134   gst_byte_writer_put_uint16_be_unchecked (&bw, self->cdp_hdr_sequence_cntr);
1135   self->cdp_hdr_sequence_cntr++;
1136   /* We calculate the checksum afterwards */
1137   gst_byte_writer_put_uint8_unchecked (&bw, 0);
1138 
1139   len = gst_byte_writer_get_pos (&bw);
1140   gst_byte_writer_set_pos (&bw, 2);
1141   gst_byte_writer_put_uint8_unchecked (&bw, len);
1142 
1143   checksum = 0;
1144   for (i = 0; i < len; i++) {
1145     checksum += cdp[i];
1146   }
1147   checksum &= 0xff;
1148   checksum = 256 - checksum;
1149   cdp[len - 1] = checksum;
1150 
1151   return len;
1152 }
1153 
1154 /* Converts CDP into raw CEA708 cc_data */
1155 static guint
convert_cea708_cdp_cea708_cc_data_internal(GstCCConverter * self,const guint8 * cdp,guint cdp_len,guint8 cc_data[MAX_CDP_PACKET_LEN],GstVideoTimeCode * tc,const struct cdp_fps_entry ** out_fps_entry)1156 convert_cea708_cdp_cea708_cc_data_internal (GstCCConverter * self,
1157     const guint8 * cdp, guint cdp_len, guint8 cc_data[MAX_CDP_PACKET_LEN],
1158     GstVideoTimeCode * tc, const struct cdp_fps_entry **out_fps_entry)
1159 {
1160   GstByteReader br;
1161   guint16 u16;
1162   guint8 u8;
1163   guint8 flags;
1164   guint len = 0;
1165   const struct cdp_fps_entry *fps_entry;
1166 
1167   *out_fps_entry = &null_fps_entry;
1168   memset (tc, 0, sizeof (*tc));
1169 
1170   /* Header + footer length */
1171   if (cdp_len < 11) {
1172     GST_WARNING_OBJECT (self, "cdp packet too short (%u). expected at "
1173         "least %u", cdp_len, 11);
1174     return 0;
1175   }
1176 
1177   gst_byte_reader_init (&br, cdp, cdp_len);
1178   u16 = gst_byte_reader_get_uint16_be_unchecked (&br);
1179   if (u16 != 0x9669) {
1180     GST_WARNING_OBJECT (self, "cdp packet does not have initial magic bytes "
1181         "of 0x9669");
1182     return 0;
1183   }
1184 
1185   u8 = gst_byte_reader_get_uint8_unchecked (&br);
1186   if (u8 != cdp_len) {
1187     GST_WARNING_OBJECT (self, "cdp packet length (%u) does not match passed "
1188         "in value (%u)", u8, cdp_len);
1189     return 0;
1190   }
1191 
1192   u8 = gst_byte_reader_get_uint8_unchecked (&br);
1193   fps_entry = cdp_fps_entry_from_id (u8);
1194   if (!fps_entry || fps_entry->fps_n == 0) {
1195     GST_WARNING_OBJECT (self, "cdp packet does not have a valid framerate "
1196         "id (0x%02x", u8);
1197     return 0;
1198   }
1199 
1200   flags = gst_byte_reader_get_uint8_unchecked (&br);
1201   /* No cc_data? */
1202   if ((flags & 0x40) == 0) {
1203     GST_DEBUG_OBJECT (self, "cdp packet does have any cc_data");
1204     return 0;
1205   }
1206 
1207   /* cdp_hdr_sequence_cntr */
1208   gst_byte_reader_skip_unchecked (&br, 2);
1209 
1210   /* time_code_present */
1211   if (flags & 0x80) {
1212     guint8 hours, minutes, seconds, frames, fields;
1213     gboolean drop_frame;
1214 
1215     if (gst_byte_reader_get_remaining (&br) < 5) {
1216       GST_WARNING_OBJECT (self, "cdp packet does not have enough data to "
1217           "contain a timecode (%u). Need at least 5 bytes",
1218           gst_byte_reader_get_remaining (&br));
1219       return 0;
1220     }
1221     u8 = gst_byte_reader_get_uint8_unchecked (&br);
1222     if (u8 != 0x71) {
1223       GST_WARNING_OBJECT (self, "cdp packet does not have timecode start byte "
1224           "of 0x71, found 0x%02x", u8);
1225       return 0;
1226     }
1227 
1228     u8 = gst_byte_reader_get_uint8_unchecked (&br);
1229     if ((u8 & 0xc0) != 0xc0) {
1230       GST_WARNING_OBJECT (self, "reserved bits are not 0xc0, found 0x%02x", u8);
1231       return 0;
1232     }
1233 
1234     hours = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
1235 
1236     u8 = gst_byte_reader_get_uint8_unchecked (&br);
1237     if ((u8 & 0x80) != 0x80) {
1238       GST_WARNING_OBJECT (self, "reserved bit is not 0x80, found 0x%02x", u8);
1239       return 0;
1240     }
1241     minutes = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
1242 
1243     u8 = gst_byte_reader_get_uint8_unchecked (&br);
1244     if (u8 & 0x80)
1245       fields = 2;
1246     else
1247       fields = 1;
1248     seconds = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf);
1249 
1250     u8 = gst_byte_reader_get_uint8_unchecked (&br);
1251     if (u8 & 0x40) {
1252       GST_WARNING_OBJECT (self, "reserved bit is not 0x0, found 0x%02x", u8);
1253       return 0;
1254     }
1255 
1256     drop_frame = ! !(u8 & 0x80);
1257     frames = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf);
1258 
1259     gst_video_time_code_init (tc, fps_entry->fps_n, fps_entry->fps_d, NULL,
1260         drop_frame ? GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME :
1261         GST_VIDEO_TIME_CODE_FLAGS_NONE, hours, minutes, seconds, frames,
1262         fields);
1263   }
1264 
1265   /* ccdata_present */
1266   if (flags & 0x40) {
1267     guint8 cc_count;
1268 
1269     if (gst_byte_reader_get_remaining (&br) < 2) {
1270       GST_WARNING_OBJECT (self, "not enough data to contain valid cc_data");
1271       return 0;
1272     }
1273     u8 = gst_byte_reader_get_uint8_unchecked (&br);
1274     if (u8 != 0x72) {
1275       GST_WARNING_OBJECT (self, "missing cc_data start code of 0x72, "
1276           "found 0x%02x", u8);
1277       return 0;
1278     }
1279 
1280     cc_count = gst_byte_reader_get_uint8_unchecked (&br);
1281     if ((cc_count & 0xe0) != 0xe0) {
1282       GST_WARNING_OBJECT (self, "reserved bits are not 0xe0, found 0x%02x", u8);
1283       return 0;
1284     }
1285     cc_count &= 0x1f;
1286 
1287     len = 3 * cc_count;
1288     if (gst_byte_reader_get_remaining (&br) < len)
1289       return 0;
1290 
1291     memcpy (cc_data, gst_byte_reader_get_data_unchecked (&br, len), len);
1292   }
1293 
1294   *out_fps_entry = fps_entry;
1295 
1296   /* skip everything else we don't care about */
1297   return len;
1298 }
1299 
1300 static gboolean
copy_from_stored_data(GstCCConverter * self,guint8 * out_ccp,guint * ccp_size,guint8 * cea608_1,guint * cea608_1_len,guint8 * cea608_2,guint * cea608_2_len)1301 copy_from_stored_data (GstCCConverter * self, guint8 * out_ccp,
1302     guint * ccp_size, guint8 * cea608_1, guint * cea608_1_len,
1303     guint8 * cea608_2, guint * cea608_2_len)
1304 {
1305   guint ccp_in_size = 0, cea608_1_in_size = 0, cea608_2_in_size = 0;
1306 
1307   g_assert ((out_ccp && ccp_size) || (!out_ccp && !ccp_size));
1308   g_assert ((cea608_1 && cea608_1_len) || (!cea608_1 && !cea608_1_len));
1309   g_assert ((cea608_2 && cea608_2_len) || (!cea608_2 && !cea608_2_len));
1310 
1311   if (ccp_size) {
1312     ccp_in_size = *ccp_size;
1313     *ccp_size = 0;
1314   }
1315   if (cea608_1_len) {
1316     cea608_1_in_size = *cea608_1_len;
1317     *cea608_1_len = 0;
1318   }
1319   if (cea608_2_len) {
1320     cea608_2_in_size = *cea608_2_len;
1321     *cea608_2_len = 0;
1322   }
1323 
1324   if (out_ccp && self->scratch_ccp_len > 0) {
1325     GST_DEBUG_OBJECT (self, "copying from previous scratch ccp buffer of "
1326         "%u bytes", self->scratch_ccp_len);
1327     if (ccp_in_size < *ccp_size + self->scratch_ccp_len) {
1328       GST_WARNING_OBJECT (self, "output buffer too small %u < %u", ccp_in_size,
1329           *ccp_size + self->scratch_ccp_len);
1330       goto fail;
1331     }
1332     memcpy (&out_ccp[*ccp_size], self->scratch_ccp, self->scratch_ccp_len);
1333     *ccp_size += self->scratch_ccp_len;
1334   }
1335 
1336   if (cea608_1 && self->scratch_cea608_1_len > 0) {
1337     GST_DEBUG_OBJECT (self, "copying from previous scratch cea608 field 1 "
1338         "buffer of %u bytes", self->scratch_cea608_1_len);
1339     if (cea608_1_in_size < *cea608_1_len + self->scratch_cea608_1_len) {
1340       GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
1341           cea608_1_in_size, *cea608_1_len + self->scratch_cea608_1_len);
1342       goto fail;
1343     }
1344     memcpy (&cea608_1[*cea608_1_len], self->scratch_cea608_1,
1345         self->scratch_cea608_1_len);
1346     *cea608_1_len += self->scratch_cea608_1_len;
1347   }
1348 
1349   if (cea608_2 && self->scratch_cea608_2_len > 0) {
1350     GST_DEBUG_OBJECT (self, "copying from previous scratch cea608 field 2 "
1351         "buffer of %u bytes", self->scratch_cea608_2_len);
1352     if (cea608_2_in_size < *cea608_2_len + self->scratch_cea608_2_len) {
1353       GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
1354           cea608_2_in_size, *cea608_2_len + self->scratch_cea608_2_len);
1355       goto fail;
1356     }
1357     memcpy (&cea608_2[*cea608_2_len], self->scratch_cea608_2,
1358         self->scratch_cea608_2_len);
1359     *cea608_2_len += self->scratch_cea608_2_len;
1360   }
1361 
1362   return TRUE;
1363 
1364 fail:
1365   if (ccp_size)
1366     *ccp_size = 0;
1367   if (cea608_1_len)
1368     *cea608_1_len = 0;
1369   if (cea608_2_len)
1370     *cea608_2_len = 0;
1371   return FALSE;
1372 }
1373 
1374 static gboolean
cc_data_to_cea608_ccp(GstCCConverter * self,guint8 * cc_data,guint cc_data_len,guint8 * out_ccp,guint * ccp_size,guint8 * cea608_1,guint * cea608_1_len,guint8 * cea608_2,guint * cea608_2_len,const struct cdp_fps_entry * in_fps_entry)1375 cc_data_to_cea608_ccp (GstCCConverter * self, guint8 * cc_data,
1376     guint cc_data_len, guint8 * out_ccp, guint * ccp_size, guint8 * cea608_1,
1377     guint * cea608_1_len, guint8 * cea608_2, guint * cea608_2_len,
1378     const struct cdp_fps_entry *in_fps_entry)
1379 {
1380   guint ccp_in_size = 0, cea608_1_in_size = 0, cea608_2_in_size = 0;
1381 
1382   g_assert (cc_data || cc_data_len == 0);
1383 
1384   if (ccp_size)
1385     ccp_in_size = *ccp_size;
1386   if (cea608_1_len)
1387     cea608_1_in_size = *cea608_1_len;
1388   if (cea608_2_len)
1389     cea608_2_in_size = *cea608_2_len;
1390 
1391   if (!copy_from_stored_data (self, out_ccp, ccp_size, cea608_1, cea608_1_len,
1392           cea608_2, cea608_2_len))
1393     goto fail;
1394 
1395   if (cc_data) {
1396     gint ccp_offset = 0;
1397     guint new_cea608_1_len = 0, new_cea608_2_len = 0;
1398     guint8 *new_cea608_1 = cea608_1, *new_cea608_2 = cea608_2;
1399 
1400     if (cea608_1_len) {
1401       new_cea608_1_len = cea608_1_in_size - *cea608_1_len;
1402       new_cea608_1 = &cea608_1[*cea608_1_len];
1403     }
1404     if (cea608_2_len) {
1405       new_cea608_2_len = cea608_2_in_size - *cea608_2_len;
1406       new_cea608_2 = &cea608_2[*cea608_2_len];
1407     }
1408 
1409     cc_data_len = compact_cc_data (cc_data, cc_data_len);
1410 
1411     if (cc_data_len / 3 > in_fps_entry->max_cc_count) {
1412       GST_WARNING_OBJECT (self, "Too many cc_data triples in CDP packet %u. "
1413           "Truncating to %u", cc_data_len / 3, in_fps_entry->max_cc_count);
1414       cc_data_len = 3 * in_fps_entry->max_cc_count;
1415     }
1416 
1417     ccp_offset = cc_data_extract_cea608 (cc_data, cc_data_len, new_cea608_1,
1418         &new_cea608_1_len, new_cea608_2, &new_cea608_2_len);
1419     if (ccp_offset < 0) {
1420       GST_WARNING_OBJECT (self, "Failed to extract cea608 from cc_data");
1421       goto fail;
1422     }
1423 
1424     if ((new_cea608_1_len + new_cea608_2_len) / 2 >
1425         in_fps_entry->max_cea608_count) {
1426       GST_WARNING_OBJECT (self, "Too many cea608 triples in CDP packet %u. "
1427           "Truncating to %u", (new_cea608_1_len + new_cea608_2_len) / 2,
1428           in_fps_entry->max_cea608_count);
1429       if ((new_cea608_1_len + new_cea608_2_len) / 2 >
1430           in_fps_entry->max_cea608_count) {
1431         new_cea608_1_len = 2 * in_fps_entry->max_cea608_count;
1432         new_cea608_2_len = 0;
1433       } else {
1434         new_cea608_2_len =
1435             2 * in_fps_entry->max_cea608_count - new_cea608_1_len;
1436       }
1437     }
1438 
1439     if (cea608_1_len)
1440       *cea608_1_len += new_cea608_1_len;
1441     if (cea608_2_len)
1442       *cea608_2_len += new_cea608_2_len;
1443 
1444     if (out_ccp) {
1445       if (ccp_in_size < *ccp_size + cc_data_len - ccp_offset) {
1446         GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
1447             ccp_in_size, *ccp_size + cc_data_len - ccp_offset);
1448         goto fail;
1449       }
1450       memcpy (&out_ccp[*ccp_size], &cc_data[ccp_offset],
1451           cc_data_len - ccp_offset);
1452       *ccp_size += cc_data_len - ccp_offset;
1453     }
1454   }
1455 
1456   return TRUE;
1457 
1458 fail:
1459   if (ccp_size)
1460     *ccp_size = 0;
1461   if (cea608_1_len)
1462     *cea608_1_len = 0;
1463   if (cea608_2_len)
1464     *cea608_2_len = 0;
1465   return FALSE;
1466 }
1467 
1468 static gboolean
cdp_to_cea608_cc_data(GstCCConverter * self,GstBuffer * inbuf,guint8 * out_ccp,guint * ccp_size,guint8 * cea608_1,guint * cea608_1_len,guint8 * cea608_2,guint * cea608_2_len,GstVideoTimeCode * out_tc,const struct cdp_fps_entry ** in_fps_entry)1469 cdp_to_cea608_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1470     guint8 * out_ccp, guint * ccp_size, guint8 * cea608_1, guint * cea608_1_len,
1471     guint8 * cea608_2, guint * cea608_2_len, GstVideoTimeCode * out_tc,
1472     const struct cdp_fps_entry **in_fps_entry)
1473 {
1474   guint8 cc_data[MAX_CDP_PACKET_LEN];
1475   guint cc_data_len = 0;
1476   GstMapInfo in;
1477 
1478   if (inbuf) {
1479     gst_buffer_map (inbuf, &in, GST_MAP_READ);
1480 
1481     cc_data_len =
1482         convert_cea708_cdp_cea708_cc_data_internal (self, in.data, in.size,
1483         cc_data, out_tc, in_fps_entry);
1484 
1485     gst_buffer_unmap (inbuf, &in);
1486     self->input_frames++;
1487   }
1488 
1489   return cc_data_to_cea608_ccp (self, inbuf ? cc_data : NULL, cc_data_len,
1490       out_ccp, ccp_size, cea608_1, cea608_1_len, cea608_2, cea608_2_len,
1491       inbuf ? *in_fps_entry : NULL);
1492 }
1493 
1494 static GstFlowReturn
convert_cea608_raw_cea608_s334_1a(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf)1495 convert_cea608_raw_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
1496     GstBuffer * outbuf)
1497 {
1498   GstMapInfo in, out;
1499   guint i, n;
1500 
1501   n = gst_buffer_get_size (inbuf);
1502   if (n & 1) {
1503     GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
1504     gst_buffer_set_size (outbuf, 0);
1505     return GST_FLOW_OK;
1506   }
1507 
1508   n /= 2;
1509 
1510   if (n > 3) {
1511     GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u.  Truncating to %u", n,
1512         3);
1513     n = 3;
1514   }
1515 
1516   gst_buffer_set_size (outbuf, 3 * n);
1517 
1518   gst_buffer_map (inbuf, &in, GST_MAP_READ);
1519   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1520 
1521   /* We have to assume that each value is from the first field and
1522    * don't know from which line offset it originally is */
1523   for (i = 0; i < n; i++) {
1524     out.data[i * 3] = 0x80;
1525     out.data[i * 3 + 1] = in.data[i * 2];
1526     out.data[i * 3 + 2] = in.data[i * 2 + 1];
1527   }
1528 
1529   gst_buffer_unmap (inbuf, &in);
1530   gst_buffer_unmap (outbuf, &out);
1531 
1532   return GST_FLOW_OK;
1533 }
1534 
1535 static GstFlowReturn
convert_cea608_raw_cea708_cc_data(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf)1536 convert_cea608_raw_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1537     GstBuffer * outbuf)
1538 {
1539   GstMapInfo in, out;
1540   guint i, n;
1541 
1542   n = gst_buffer_get_size (inbuf);
1543   if (n & 1) {
1544     GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
1545     gst_buffer_set_size (outbuf, 0);
1546     return GST_FLOW_OK;
1547   }
1548 
1549   n /= 2;
1550 
1551   if (n > 3) {
1552     GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u. Truncating to %u", n,
1553         3);
1554     n = 3;
1555   }
1556 
1557   gst_buffer_set_size (outbuf, 3 * n);
1558 
1559   gst_buffer_map (inbuf, &in, GST_MAP_READ);
1560   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1561 
1562   /* We have to assume that each value is from the first field and
1563    * don't know from which line offset it originally is */
1564   for (i = 0; i < n; i++) {
1565     out.data[i * 3] = 0xfc;
1566     out.data[i * 3 + 1] = in.data[i * 2];
1567     out.data[i * 3 + 2] = in.data[i * 2 + 1];
1568   }
1569 
1570   gst_buffer_unmap (inbuf, &in);
1571   gst_buffer_unmap (outbuf, &out);
1572 
1573   return GST_FLOW_OK;
1574 }
1575 
1576 static GstFlowReturn
convert_cea608_raw_cea708_cdp(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf,const GstVideoTimeCodeMeta * tc_meta)1577 convert_cea608_raw_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
1578     GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
1579 {
1580   GstMapInfo in, out;
1581   const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
1582   guint cc_data_len = MAX_CDP_PACKET_LEN;
1583   guint cea608_1_len = MAX_CDP_PACKET_LEN;
1584   guint8 cc_data[MAX_CDP_PACKET_LEN], cea608_1[MAX_CEA608_LEN];
1585 
1586   in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
1587   if (!in_fps_entry || in_fps_entry->fps_n == 0)
1588     g_assert_not_reached ();
1589 
1590   if (!copy_from_stored_data (self, NULL, 0, cea608_1, &cea608_1_len, NULL, 0))
1591     goto drop;
1592 
1593   if (inbuf) {
1594     guint n = 0;
1595 
1596     n = gst_buffer_get_size (inbuf);
1597     if (n & 1) {
1598       GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
1599       gst_buffer_set_size (outbuf, 0);
1600       return GST_FLOW_OK;
1601     }
1602 
1603     n /= 2;
1604 
1605     if (n > in_fps_entry->max_cea608_count) {
1606       GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u. Truncating to %u",
1607           n, in_fps_entry->max_cea608_count);
1608       n = in_fps_entry->max_cea608_count;
1609     }
1610 
1611     gst_buffer_map (inbuf, &in, GST_MAP_READ);
1612     memcpy (&cea608_1[cea608_1_len], in.data, n * 2);
1613     gst_buffer_unmap (inbuf, &in);
1614     cea608_1_len += n * 2;
1615     self->input_frames++;
1616   }
1617 
1618   out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
1619   if (!out_fps_entry || out_fps_entry->fps_n == 0)
1620     g_assert_not_reached ();
1621 
1622   if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
1623           cea608_1, &cea608_1_len, NULL, 0, tc_meta ? &tc_meta->tc : NULL))
1624     goto drop;
1625 
1626   if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
1627           cea608_1_len, NULL, 0, cc_data, &cc_data_len))
1628     goto drop;
1629 
1630   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1631   cc_data_len =
1632       convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
1633       out.data, out.size, &self->current_output_timecode, out_fps_entry);
1634   self->output_frames++;
1635   gst_buffer_unmap (outbuf, &out);
1636 
1637 out:
1638   gst_buffer_set_size (outbuf, cc_data_len);
1639 
1640   return GST_FLOW_OK;
1641 
1642 drop:
1643   cc_data_len = 0;
1644   goto out;
1645 }
1646 
1647 static GstFlowReturn
convert_cea608_s334_1a_cea608_raw(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf)1648 convert_cea608_s334_1a_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
1649     GstBuffer * outbuf)
1650 {
1651   GstMapInfo in, out;
1652   guint i, n;
1653   guint cea608 = 0;
1654 
1655   n = gst_buffer_get_size (inbuf);
1656   if (n % 3 != 0) {
1657     GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
1658     n = n - (n % 3);
1659   }
1660 
1661   n /= 3;
1662 
1663   if (n > 3) {
1664     GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
1665     n = 3;
1666   }
1667 
1668   gst_buffer_map (inbuf, &in, GST_MAP_READ);
1669   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1670 
1671   for (i = 0; i < n; i++) {
1672     if (in.data[i * 3] & 0x80) {
1673       out.data[i * 2] = in.data[i * 3 + 1];
1674       out.data[i * 2 + 1] = in.data[i * 3 + 2];
1675       cea608++;
1676     }
1677   }
1678 
1679   gst_buffer_unmap (inbuf, &in);
1680   gst_buffer_unmap (outbuf, &out);
1681 
1682   gst_buffer_set_size (outbuf, 2 * cea608);
1683 
1684   return GST_FLOW_OK;
1685 }
1686 
1687 static GstFlowReturn
convert_cea608_s334_1a_cea708_cc_data(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf)1688 convert_cea608_s334_1a_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
1689     GstBuffer * outbuf)
1690 {
1691   GstMapInfo in, out;
1692   guint i, n;
1693 
1694   n = gst_buffer_get_size (inbuf);
1695   if (n % 3 != 0) {
1696     GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
1697     n = n - (n % 3);
1698   }
1699 
1700   n /= 3;
1701 
1702   if (n > 3) {
1703     GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
1704     n = 3;
1705   }
1706 
1707   gst_buffer_set_size (outbuf, 3 * n);
1708 
1709   gst_buffer_map (inbuf, &in, GST_MAP_READ);
1710   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1711 
1712   for (i = 0; i < n; i++) {
1713     out.data[i * 3] = (in.data[i * 3] & 0x80) ? 0xfc : 0xfd;
1714     out.data[i * 3 + 1] = in.data[i * 3 + 1];
1715     out.data[i * 3 + 2] = in.data[i * 3 + 2];
1716   }
1717 
1718   gst_buffer_unmap (inbuf, &in);
1719   gst_buffer_unmap (outbuf, &out);
1720 
1721   return GST_FLOW_OK;
1722 }
1723 
1724 static GstFlowReturn
convert_cea608_s334_1a_cea708_cdp(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf,const GstVideoTimeCodeMeta * tc_meta)1725 convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
1726     GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
1727 {
1728   GstMapInfo in, out;
1729   const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
1730   guint cc_data_len = MAX_CDP_PACKET_LEN;
1731   guint cea608_1_len = MAX_CDP_PACKET_LEN, cea608_2_len = MAX_CDP_PACKET_LEN;
1732   guint8 cc_data[MAX_CDP_PACKET_LEN];
1733   guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
1734   guint i, n;
1735 
1736   in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
1737   if (!in_fps_entry || in_fps_entry->fps_n == 0)
1738     g_assert_not_reached ();
1739 
1740   if (!copy_from_stored_data (self, NULL, 0, cea608_1, &cea608_1_len,
1741           cea608_2, &cea608_2_len))
1742     goto drop;
1743 
1744   if (inbuf) {
1745     n = gst_buffer_get_size (inbuf);
1746     if (n % 3 != 0) {
1747       GST_WARNING_OBJECT (self, "Invalid S334-1A CEA608 buffer size");
1748       n = n - (n % 3);
1749     }
1750 
1751     n /= 3;
1752 
1753     if (n > in_fps_entry->max_cea608_count) {
1754       GST_WARNING_OBJECT (self, "Too many S334-1A CEA608 triplets %u", n);
1755       n = in_fps_entry->max_cea608_count;
1756     }
1757 
1758     gst_buffer_map (inbuf, &in, GST_MAP_READ);
1759 
1760     for (i = 0; i < n; i++) {
1761       if (in.data[i * 3] & 0x80) {
1762         cea608_1[cea608_1_len++] = in.data[i * 3 + 1];
1763         cea608_1[cea608_1_len++] = in.data[i * 3 + 2];
1764       } else {
1765         cea608_2[cea608_2_len++] = in.data[i * 3 + 1];
1766         cea608_2[cea608_2_len++] = in.data[i * 3 + 2];
1767       }
1768     }
1769     gst_buffer_unmap (inbuf, &in);
1770     self->input_frames++;
1771   }
1772 
1773   out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
1774   if (!out_fps_entry || out_fps_entry->fps_n == 0)
1775     g_assert_not_reached ();
1776 
1777   if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
1778           cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
1779           tc_meta ? &tc_meta->tc : NULL)) {
1780     goto drop;
1781   }
1782 
1783   if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
1784           cea608_1_len, cea608_2, cea608_2_len, cc_data, &cc_data_len)) {
1785     goto drop;
1786   }
1787 
1788   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1789   cc_data_len =
1790       convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
1791       out.data, out.size, &self->current_output_timecode, out_fps_entry);
1792   self->output_frames++;
1793   gst_buffer_unmap (outbuf, &out);
1794 
1795 out:
1796   gst_buffer_set_size (outbuf, cc_data_len);
1797 
1798   return GST_FLOW_OK;
1799 
1800 drop:
1801   cc_data_len = 0;
1802   goto out;
1803 }
1804 
1805 static GstFlowReturn
convert_cea708_cc_data_cea608_raw(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf)1806 convert_cea708_cc_data_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
1807     GstBuffer * outbuf)
1808 {
1809   GstMapInfo in, out;
1810   guint i, n;
1811   guint cea608 = 0;
1812 
1813   n = gst_buffer_get_size (inbuf);
1814   if (n % 3 != 0) {
1815     GST_WARNING_OBJECT (self, "Invalid raw CEA708 buffer size");
1816     n = n - (n % 3);
1817   }
1818 
1819   n /= 3;
1820 
1821   if (n > 25) {
1822     GST_WARNING_OBJECT (self, "Too many CEA708 triplets %u", n);
1823     n = 25;
1824   }
1825 
1826   gst_buffer_map (inbuf, &in, GST_MAP_READ);
1827   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1828 
1829   for (i = 0; i < n; i++) {
1830     /* We can only really copy the first field here as there can't be any
1831      * signalling in raw CEA608 and we must not mix the streams of different
1832      * fields
1833      */
1834     if (in.data[i * 3] == 0xfc) {
1835       out.data[cea608 * 2] = in.data[i * 3 + 1];
1836       out.data[cea608 * 2 + 1] = in.data[i * 3 + 2];
1837       cea608++;
1838     }
1839   }
1840 
1841   gst_buffer_unmap (inbuf, &in);
1842   gst_buffer_unmap (outbuf, &out);
1843 
1844   gst_buffer_set_size (outbuf, 2 * cea608);
1845 
1846   return GST_FLOW_OK;
1847 }
1848 
1849 static GstFlowReturn
convert_cea708_cc_data_cea608_s334_1a(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf)1850 convert_cea708_cc_data_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
1851     GstBuffer * outbuf)
1852 {
1853   GstMapInfo in, out;
1854   guint i, n;
1855   guint cea608 = 0;
1856 
1857   n = gst_buffer_get_size (inbuf);
1858   if (n % 3 != 0) {
1859     GST_WARNING_OBJECT (self, "Invalid raw CEA708 buffer size");
1860     n = n - (n % 3);
1861   }
1862 
1863   n /= 3;
1864 
1865   if (n > 25) {
1866     GST_WARNING_OBJECT (self, "Too many CEA708 triplets %u", n);
1867     n = 25;
1868   }
1869 
1870   gst_buffer_map (inbuf, &in, GST_MAP_READ);
1871   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1872 
1873   for (i = 0; i < n; i++) {
1874     if (in.data[i * 3] == 0xfc || in.data[i * 3] == 0xfd) {
1875       /* We have to assume a line offset of 0 */
1876       out.data[cea608 * 3] = in.data[i * 3] == 0xfc ? 0x80 : 0x00;
1877       out.data[cea608 * 3 + 1] = in.data[i * 3 + 1];
1878       out.data[cea608 * 3 + 2] = in.data[i * 3 + 2];
1879       cea608++;
1880     }
1881   }
1882 
1883   gst_buffer_unmap (inbuf, &in);
1884   gst_buffer_unmap (outbuf, &out);
1885 
1886   gst_buffer_set_size (outbuf, 3 * cea608);
1887 
1888   return GST_FLOW_OK;
1889 }
1890 
1891 static GstFlowReturn
convert_cea708_cc_data_cea708_cdp(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf,const GstVideoTimeCodeMeta * tc_meta)1892 convert_cea708_cc_data_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
1893     GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
1894 {
1895   GstMapInfo in, out;
1896   const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
1897   guint in_cc_data_len;
1898   guint cc_data_len = MAX_CDP_PACKET_LEN, ccp_data_len = MAX_CDP_PACKET_LEN;
1899   guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
1900   guint8 cc_data[MAX_CDP_PACKET_LEN], ccp_data[MAX_CDP_PACKET_LEN];
1901   guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
1902   guint8 *in_cc_data;
1903 
1904   if (inbuf) {
1905     gst_buffer_map (inbuf, &in, GST_MAP_READ);
1906     in_cc_data = in.data;
1907     in_cc_data_len = in.size;
1908     self->input_frames++;
1909   } else {
1910     in_cc_data = NULL;
1911     in_cc_data_len = 0;
1912   }
1913 
1914   in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
1915   if (!in_fps_entry || in_fps_entry->fps_n == 0)
1916     g_assert_not_reached ();
1917 
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 ();
1921 
1922   if (!cc_data_to_cea608_ccp (self, in_cc_data, in_cc_data_len, ccp_data,
1923           &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
1924           in_fps_entry)) {
1925     if (inbuf)
1926       gst_buffer_unmap (inbuf, &in);
1927     goto drop;
1928   }
1929 
1930   if (inbuf)
1931     gst_buffer_unmap (inbuf, &in);
1932 
1933   if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
1934           &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
1935           tc_meta ? &tc_meta->tc : NULL))
1936     goto drop;
1937 
1938   if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
1939           cea608_1, cea608_1_len, cea608_2, cea608_2_len, cc_data,
1940           &cc_data_len))
1941     goto drop;
1942 
1943   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1944   cc_data_len =
1945       convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
1946       out.data, out.size, &self->current_output_timecode, out_fps_entry);
1947   self->output_frames++;
1948   gst_buffer_unmap (outbuf, &out);
1949 
1950 out:
1951   gst_buffer_set_size (outbuf, cc_data_len);
1952 
1953   return GST_FLOW_OK;
1954 
1955 drop:
1956   cc_data_len = 0;
1957   goto out;
1958 }
1959 
1960 static GstFlowReturn
convert_cea708_cdp_cea608_raw(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf,const GstVideoTimeCodeMeta * tc_meta)1961 convert_cea708_cdp_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
1962     GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
1963 {
1964   GstMapInfo out;
1965   GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
1966   guint cea608_1_len;
1967   const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
1968 
1969   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
1970   cea608_1_len = (guint) out.size;
1971   if (!cdp_to_cea608_cc_data (self, inbuf, NULL, NULL, out.data, &cea608_1_len,
1972           NULL, NULL, &tc, &in_fps_entry)) {
1973     gst_buffer_set_size (outbuf, 0);
1974     return GST_FLOW_OK;
1975   }
1976 
1977   out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
1978   if (!out_fps_entry || out_fps_entry->fps_n == 0)
1979     out_fps_entry = in_fps_entry;
1980 
1981   if (fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
1982           out.data, &cea608_1_len, NULL, NULL, &tc)) {
1983     self->output_frames++;
1984   } else {
1985     cea608_1_len = 0;
1986   }
1987   gst_buffer_unmap (outbuf, &out);
1988 
1989   gst_buffer_set_size (outbuf, cea608_1_len);
1990 
1991   if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
1992     gst_buffer_add_video_time_code_meta (outbuf,
1993         &self->current_output_timecode);
1994     gst_video_time_code_increment_frame (&self->current_output_timecode);
1995   }
1996 
1997   return GST_FLOW_OK;
1998 }
1999 
2000 static GstFlowReturn
convert_cea708_cdp_cea608_s334_1a(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf,const GstVideoTimeCodeMeta * tc_meta)2001 convert_cea708_cdp_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
2002     GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2003 {
2004   GstMapInfo out;
2005   GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2006   const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2007   guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2008   guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2009   guint i, cc_data_len;
2010 
2011   if (!cdp_to_cea608_cc_data (self, inbuf, NULL, NULL, cea608_1, &cea608_1_len,
2012           cea608_2, &cea608_2_len, &tc, &in_fps_entry))
2013     goto drop;
2014 
2015   out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2016   if (!out_fps_entry || out_fps_entry->fps_n == 0)
2017     out_fps_entry = in_fps_entry;
2018 
2019   if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
2020           cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc))
2021     goto drop;
2022 
2023   cc_data_len = gst_buffer_get_sizes (outbuf, NULL, NULL);
2024 
2025   gst_buffer_map (outbuf, &out, GST_MAP_READWRITE);
2026   if (!combine_cc_data (self, FALSE, out_fps_entry, NULL, 0, cea608_1,
2027           cea608_1_len, cea608_2, cea608_2_len, out.data, &cc_data_len)) {
2028     gst_buffer_unmap (outbuf, &out);
2029     goto drop;
2030   }
2031 
2032   for (i = 0; i < cc_data_len / 3; i++)
2033     /* We have to assume a line offset of 0 */
2034     out.data[i * 3] = out.data[i * 3] == 0xfc ? 0x80 : 0x00;
2035 
2036   gst_buffer_unmap (outbuf, &out);
2037   self->output_frames++;
2038 
2039   gst_buffer_set_size (outbuf, cc_data_len);
2040 
2041   if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
2042     gst_buffer_add_video_time_code_meta (outbuf,
2043         &self->current_output_timecode);
2044     gst_video_time_code_increment_frame (&self->current_output_timecode);
2045   }
2046 
2047   return GST_FLOW_OK;
2048 
2049 drop:
2050   gst_buffer_set_size (outbuf, 0);
2051   return GST_FLOW_OK;
2052 }
2053 
2054 static GstFlowReturn
convert_cea708_cdp_cea708_cc_data(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf,const GstVideoTimeCodeMeta * tc_meta)2055 convert_cea708_cdp_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
2056     GstBuffer * outbuf, const GstVideoTimeCodeMeta * tc_meta)
2057 {
2058   GstMapInfo out;
2059   GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2060   const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2061   guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2062   guint8 ccp_data[MAX_CDP_PACKET_LEN];
2063   guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2064   guint ccp_data_len = MAX_CDP_PACKET_LEN;
2065   guint out_len = 0;
2066 
2067   if (!cdp_to_cea608_cc_data (self, inbuf, ccp_data, &ccp_data_len,
2068           cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc, &in_fps_entry))
2069     goto out;
2070 
2071   out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2072   if (!out_fps_entry || out_fps_entry->fps_n == 0)
2073     out_fps_entry = in_fps_entry;
2074 
2075   if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
2076           &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc))
2077     goto out;
2078 
2079   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2080   out_len = (guint) out.size;
2081   if (!combine_cc_data (self, FALSE, out_fps_entry, ccp_data, ccp_data_len,
2082           cea608_1, cea608_1_len, cea608_2, cea608_2_len, out.data, &out_len)) {
2083     gst_buffer_unmap (outbuf, &out);
2084     out_len = 0;
2085     goto out;
2086   }
2087 
2088   gst_buffer_unmap (outbuf, &out);
2089   self->output_frames++;
2090 
2091   if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
2092     gst_buffer_add_video_time_code_meta (outbuf,
2093         &self->current_output_timecode);
2094     gst_video_time_code_increment_frame (&self->current_output_timecode);
2095   }
2096 
2097 out:
2098   gst_buffer_set_size (outbuf, out_len);
2099 
2100   return GST_FLOW_OK;
2101 }
2102 
2103 static GstFlowReturn
convert_cea708_cdp_cea708_cdp(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf)2104 convert_cea708_cdp_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
2105     GstBuffer * outbuf)
2106 {
2107   GstMapInfo out;
2108   GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
2109   const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
2110   guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
2111   guint8 ccp_data[MAX_CDP_PACKET_LEN], cc_data[MAX_CDP_PACKET_LEN];
2112   guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
2113   guint ccp_data_len = MAX_CDP_PACKET_LEN, cc_data_len = MAX_CDP_PACKET_LEN;
2114   guint out_len = 0;
2115 
2116   if (!cdp_to_cea608_cc_data (self, inbuf, ccp_data, &ccp_data_len,
2117           cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc, &in_fps_entry))
2118     goto out;
2119 
2120   out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
2121   if (!out_fps_entry || out_fps_entry->fps_n == 0)
2122     out_fps_entry = in_fps_entry;
2123 
2124   if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
2125           &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc))
2126     goto out;
2127 
2128   if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
2129           cea608_1, cea608_1_len, cea608_2, cea608_2_len, cc_data,
2130           &cc_data_len)) {
2131     goto out;
2132   }
2133 
2134   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
2135   out_len =
2136       convert_cea708_cc_data_cea708_cdp_internal (self, cc_data, cc_data_len,
2137       out.data, out.size, &self->current_output_timecode, out_fps_entry);
2138 
2139   gst_buffer_unmap (outbuf, &out);
2140   self->output_frames++;
2141 
2142 out:
2143   gst_buffer_set_size (outbuf, out_len);
2144 
2145   return GST_FLOW_OK;
2146 }
2147 
2148 static GstFlowReturn
gst_cc_converter_transform(GstCCConverter * self,GstBuffer * inbuf,GstBuffer * outbuf)2149 gst_cc_converter_transform (GstCCConverter * self, GstBuffer * inbuf,
2150     GstBuffer * outbuf)
2151 {
2152   GstVideoTimeCodeMeta *tc_meta = NULL;
2153   GstFlowReturn ret = GST_FLOW_OK;
2154 
2155   GST_DEBUG_OBJECT (self, "Converting %" GST_PTR_FORMAT " from %u to %u", inbuf,
2156       self->input_caption_type, self->output_caption_type);
2157 
2158   if (inbuf)
2159     tc_meta = gst_buffer_get_video_time_code_meta (inbuf);
2160 
2161   if (tc_meta) {
2162     if (self->current_output_timecode.config.fps_n <= 0) {
2163       /* XXX: this assumes the input time codes are well-formed and increase
2164        * at the rate of one frame for each input buffer */
2165       const struct cdp_fps_entry *in_fps_entry;
2166       gint scale_n, scale_d;
2167 
2168       in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
2169       if (!in_fps_entry || in_fps_entry->fps_n == 0)
2170         scale_n = scale_d = 1;
2171       else
2172         get_framerate_output_scale (self, in_fps_entry, &scale_n, &scale_d);
2173 
2174       interpolate_time_code_with_framerate (self, &tc_meta->tc,
2175           self->out_fps_n, self->out_fps_d, scale_n, scale_d,
2176           &self->current_output_timecode);
2177     }
2178   }
2179 
2180   switch (self->input_caption_type) {
2181     case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2182 
2183       switch (self->output_caption_type) {
2184         case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2185           ret = convert_cea608_raw_cea608_s334_1a (self, inbuf, outbuf);
2186           break;
2187         case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2188           ret = convert_cea608_raw_cea708_cc_data (self, inbuf, outbuf);
2189           break;
2190         case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2191           ret = convert_cea608_raw_cea708_cdp (self, inbuf, outbuf, tc_meta);
2192           break;
2193         case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2194         default:
2195           g_assert_not_reached ();
2196           break;
2197       }
2198 
2199       break;
2200     case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2201 
2202       switch (self->output_caption_type) {
2203         case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2204           ret = convert_cea608_s334_1a_cea608_raw (self, inbuf, outbuf);
2205           break;
2206         case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2207           ret = convert_cea608_s334_1a_cea708_cc_data (self, inbuf, outbuf);
2208           break;
2209         case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2210           ret =
2211               convert_cea608_s334_1a_cea708_cdp (self, inbuf, outbuf, tc_meta);
2212           break;
2213         case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2214         default:
2215           g_assert_not_reached ();
2216           break;
2217       }
2218 
2219       break;
2220     case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2221 
2222       switch (self->output_caption_type) {
2223         case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2224           ret = convert_cea708_cc_data_cea608_raw (self, inbuf, outbuf);
2225           break;
2226         case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2227           ret = convert_cea708_cc_data_cea608_s334_1a (self, inbuf, outbuf);
2228           break;
2229         case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2230           ret =
2231               convert_cea708_cc_data_cea708_cdp (self, inbuf, outbuf, tc_meta);
2232           break;
2233         case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2234         default:
2235           g_assert_not_reached ();
2236           break;
2237       }
2238 
2239       break;
2240     case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2241 
2242       switch (self->output_caption_type) {
2243         case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
2244           ret = convert_cea708_cdp_cea608_raw (self, inbuf, outbuf, tc_meta);
2245           break;
2246         case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
2247           ret =
2248               convert_cea708_cdp_cea608_s334_1a (self, inbuf, outbuf, tc_meta);
2249           break;
2250         case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
2251           ret =
2252               convert_cea708_cdp_cea708_cc_data (self, inbuf, outbuf, tc_meta);
2253           break;
2254         case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
2255           ret = convert_cea708_cdp_cea708_cdp (self, inbuf, outbuf);
2256           break;
2257         default:
2258           g_assert_not_reached ();
2259           break;
2260       }
2261 
2262       break;
2263     default:
2264       g_assert_not_reached ();
2265       break;
2266   }
2267 
2268   if (ret != GST_FLOW_OK) {
2269     GST_DEBUG_OBJECT (self, "returning %s", gst_flow_get_name (ret));
2270     return ret;
2271   }
2272 
2273   GST_DEBUG_OBJECT (self, "Converted to %" GST_PTR_FORMAT, outbuf);
2274 
2275   if (gst_buffer_get_size (outbuf) > 0) {
2276     if (self->current_output_timecode.config.fps_n > 0) {
2277       gst_buffer_add_video_time_code_meta (outbuf,
2278           &self->current_output_timecode);
2279       gst_video_time_code_increment_frame (&self->current_output_timecode);
2280     }
2281 
2282     return GST_FLOW_OK;
2283   } else {
2284     return GST_FLOW_OK;
2285   }
2286 }
2287 
2288 static gboolean
gst_cc_converter_transform_meta(GstBaseTransform * base,GstBuffer * outbuf,GstMeta * meta,GstBuffer * inbuf)2289 gst_cc_converter_transform_meta (GstBaseTransform * base, GstBuffer * outbuf,
2290     GstMeta * meta, GstBuffer * inbuf)
2291 {
2292   const GstMetaInfo *info = meta->info;
2293 
2294   /* we do this manually for framerate scaling */
2295   if (info->api == GST_VIDEO_TIME_CODE_META_API_TYPE)
2296     return FALSE;
2297 
2298   return GST_BASE_TRANSFORM_CLASS (parent_class)->transform_meta (base, outbuf,
2299       meta, inbuf);
2300 }
2301 
2302 static gboolean
can_generate_output(GstCCConverter * self)2303 can_generate_output (GstCCConverter * self)
2304 {
2305   int input_frame_n, input_frame_d, output_frame_n, output_frame_d;
2306   int output_time_cmp;
2307 
2308   if (self->in_fps_n == 0 || self->out_fps_n == 0)
2309     return FALSE;
2310 
2311   /* compute the relative frame count for each */
2312   if (!gst_util_fraction_multiply (self->in_fps_d, self->in_fps_n,
2313           self->input_frames, 1, &input_frame_n, &input_frame_d))
2314     /* we should never overflow */
2315     g_assert_not_reached ();
2316 
2317   if (!gst_util_fraction_multiply (self->out_fps_d, self->out_fps_n,
2318           self->output_frames, 1, &output_frame_n, &output_frame_d))
2319     /* we should never overflow */
2320     g_assert_not_reached ();
2321 
2322   output_time_cmp = gst_util_fraction_compare (input_frame_n, input_frame_d,
2323       output_frame_n, output_frame_d);
2324 
2325   /* if the next output frame is at or before the current input frame */
2326   if (output_time_cmp >= 0)
2327     return TRUE;
2328 
2329   return FALSE;
2330 }
2331 
2332 static void
reset_counters(GstCCConverter * self)2333 reset_counters (GstCCConverter * self)
2334 {
2335   self->scratch_ccp_len = 0;
2336   self->scratch_cea608_1_len = 0;
2337   self->scratch_cea608_2_len = 0;
2338   self->input_frames = 0;
2339   self->output_frames = 1;
2340   gst_video_time_code_clear (&self->current_output_timecode);
2341   gst_clear_buffer (&self->previous_buffer);
2342 }
2343 
2344 static GstFlowReturn
drain_input(GstCCConverter * self)2345 drain_input (GstCCConverter * self)
2346 {
2347   GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (self);
2348   GstBaseTransform *trans = GST_BASE_TRANSFORM (self);
2349   GstFlowReturn ret = GST_FLOW_OK;
2350 
2351   while (self->scratch_ccp_len > 0 || self->scratch_cea608_1_len > 0
2352       || self->scratch_cea608_2_len > 0 || can_generate_output (self)) {
2353     GstBuffer *outbuf;
2354 
2355     if (!self->previous_buffer) {
2356       GST_WARNING_OBJECT (self, "Attempt to draining without a previous "
2357           "buffer.  Aborting");
2358       return GST_FLOW_OK;
2359     }
2360 
2361     outbuf = gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
2362 
2363     if (bclass->copy_metadata) {
2364       if (!bclass->copy_metadata (trans, self->previous_buffer, outbuf)) {
2365         /* something failed, post a warning */
2366         GST_ELEMENT_WARNING (self, STREAM, NOT_IMPLEMENTED,
2367             ("could not copy metadata"), (NULL));
2368       }
2369     }
2370 
2371     ret = gst_cc_converter_transform (self, NULL, outbuf);
2372     if (gst_buffer_get_size (outbuf) <= 0) {
2373       /* try to move the output along */
2374       self->input_frames++;
2375       gst_buffer_unref (outbuf);
2376       continue;
2377     } else if (ret != GST_FLOW_OK) {
2378       gst_buffer_unref (outbuf);
2379       return ret;
2380     }
2381 
2382     ret = gst_pad_push (GST_BASE_TRANSFORM_SRC_PAD (trans), outbuf);
2383     if (ret != GST_FLOW_OK) {
2384       return ret;
2385     }
2386   }
2387 
2388   return ret;
2389 }
2390 
2391 static GstFlowReturn
gst_cc_converter_generate_output(GstBaseTransform * base,GstBuffer ** outbuf)2392 gst_cc_converter_generate_output (GstBaseTransform * base, GstBuffer ** outbuf)
2393 {
2394   GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (base);
2395   GstCCConverter *self = GST_CCCONVERTER (base);
2396   GstBuffer *inbuf = base->queued_buf;
2397   GstFlowReturn ret;
2398 
2399   *outbuf = NULL;
2400   base->queued_buf = NULL;
2401   if (!inbuf && !can_generate_output (self)) {
2402     return GST_FLOW_OK;
2403   }
2404 
2405   if (gst_base_transform_is_passthrough (base)) {
2406     *outbuf = inbuf;
2407     ret = GST_FLOW_OK;
2408   } else {
2409     if (inbuf && GST_BUFFER_IS_DISCONT (inbuf)) {
2410       ret = drain_input (self);
2411       reset_counters (self);
2412       if (ret != GST_FLOW_OK)
2413         return ret;
2414     }
2415 
2416     *outbuf = gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
2417     if (*outbuf == NULL)
2418       goto no_buffer;
2419 
2420     if (inbuf)
2421       gst_buffer_replace (&self->previous_buffer, inbuf);
2422 
2423     if (bclass->copy_metadata) {
2424       if (!bclass->copy_metadata (base, self->previous_buffer, *outbuf)) {
2425         /* something failed, post a warning */
2426         GST_ELEMENT_WARNING (self, STREAM, NOT_IMPLEMENTED,
2427             ("could not copy metadata"), (NULL));
2428       }
2429     }
2430 
2431     ret = gst_cc_converter_transform (self, inbuf, *outbuf);
2432     if (gst_buffer_get_size (*outbuf) <= 0) {
2433       gst_buffer_unref (*outbuf);
2434       *outbuf = NULL;
2435       ret = GST_FLOW_OK;
2436     }
2437 
2438     if (inbuf)
2439       gst_buffer_unref (inbuf);
2440   }
2441 
2442   return ret;
2443 
2444 no_buffer:
2445   {
2446     if (inbuf)
2447       gst_buffer_unref (inbuf);
2448     *outbuf = NULL;
2449     GST_WARNING_OBJECT (self, "could not allocate buffer");
2450     return GST_FLOW_ERROR;
2451   }
2452 }
2453 
2454 static gboolean
gst_cc_converter_sink_event(GstBaseTransform * trans,GstEvent * event)2455 gst_cc_converter_sink_event (GstBaseTransform * trans, GstEvent * event)
2456 {
2457   GstCCConverter *self = GST_CCCONVERTER (trans);
2458 
2459   switch (GST_EVENT_TYPE (event)) {
2460     case GST_EVENT_EOS:
2461       GST_DEBUG_OBJECT (self, "received EOS");
2462 
2463       drain_input (self);
2464 
2465       /* fallthrough */
2466     case GST_EVENT_FLUSH_START:
2467       reset_counters (self);
2468       break;
2469     default:
2470       break;
2471   }
2472 
2473   return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
2474 }
2475 
2476 static gboolean
gst_cc_converter_start(GstBaseTransform * base)2477 gst_cc_converter_start (GstBaseTransform * base)
2478 {
2479   GstCCConverter *self = GST_CCCONVERTER (base);
2480 
2481   /* Resetting this is not really needed but makes debugging easier */
2482   self->cdp_hdr_sequence_cntr = 0;
2483   self->current_output_timecode = (GstVideoTimeCode) GST_VIDEO_TIME_CODE_INIT;
2484   self->input_frames = 0;
2485   self->output_frames = 1;
2486   self->scratch_ccp_len = 0;
2487   self->scratch_cea608_1_len = 0;
2488   self->scratch_cea608_2_len = 0;
2489 
2490   return TRUE;
2491 }
2492 
2493 static gboolean
gst_cc_converter_stop(GstBaseTransform * base)2494 gst_cc_converter_stop (GstBaseTransform * base)
2495 {
2496   GstCCConverter *self = GST_CCCONVERTER (base);
2497 
2498   gst_video_time_code_clear (&self->current_output_timecode);
2499   gst_clear_buffer (&self->previous_buffer);
2500 
2501   return TRUE;
2502 }
2503 
2504 static void
gst_cc_converter_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)2505 gst_cc_converter_set_property (GObject * object, guint prop_id,
2506     const GValue * value, GParamSpec * pspec)
2507 {
2508   GstCCConverter *filter = GST_CCCONVERTER (object);
2509 
2510   switch (prop_id) {
2511     case PROP_CDP_MODE:
2512       filter->cdp_mode = g_value_get_flags (value);
2513       break;
2514     default:
2515       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2516       break;
2517   }
2518 }
2519 
2520 static void
gst_cc_converter_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)2521 gst_cc_converter_get_property (GObject * object, guint prop_id, GValue * value,
2522     GParamSpec * pspec)
2523 {
2524   GstCCConverter *filter = GST_CCCONVERTER (object);
2525 
2526   switch (prop_id) {
2527     case PROP_CDP_MODE:
2528       g_value_set_flags (value, filter->cdp_mode);
2529       break;
2530     default:
2531       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2532       break;
2533   }
2534 }
2535 
2536 static void
gst_cc_converter_class_init(GstCCConverterClass * klass)2537 gst_cc_converter_class_init (GstCCConverterClass * klass)
2538 {
2539   GObjectClass *gobject_class;
2540   GstElementClass *gstelement_class;
2541   GstBaseTransformClass *basetransform_class;
2542 
2543   gobject_class = (GObjectClass *) klass;
2544   gstelement_class = (GstElementClass *) klass;
2545   basetransform_class = (GstBaseTransformClass *) klass;
2546 
2547   gobject_class->set_property = gst_cc_converter_set_property;
2548   gobject_class->get_property = gst_cc_converter_get_property;
2549 
2550   /**
2551    * GstCCConverter:cdp-mode
2552    *
2553    * Only insert the selection sections into CEA 708 CDP packets.
2554    *
2555    * Various software does not handle any other information than CC data
2556    * contained in CDP packets and might fail parsing the packets otherwise.
2557    *
2558    * Since: 1.20
2559    */
2560   g_object_class_install_property (G_OBJECT_CLASS (klass),
2561       PROP_CDP_MODE, g_param_spec_flags ("cdp-mode",
2562           "CDP Mode",
2563           "Select which CDP sections to store in CDP packets",
2564           GST_TYPE_CC_CONVERTER_CDP_MODE, DEFAULT_CDP_MODE,
2565           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2566 
2567   gst_element_class_set_static_metadata (gstelement_class,
2568       "Closed Caption Converter",
2569       "Filter/ClosedCaption",
2570       "Converts Closed Captions between different formats",
2571       "Sebastian Dröge <sebastian@centricular.com>");
2572 
2573   gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
2574   gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
2575 
2576   basetransform_class->start = GST_DEBUG_FUNCPTR (gst_cc_converter_start);
2577   basetransform_class->stop = GST_DEBUG_FUNCPTR (gst_cc_converter_stop);
2578   basetransform_class->sink_event =
2579       GST_DEBUG_FUNCPTR (gst_cc_converter_sink_event);
2580   basetransform_class->transform_size =
2581       GST_DEBUG_FUNCPTR (gst_cc_converter_transform_size);
2582   basetransform_class->transform_caps =
2583       GST_DEBUG_FUNCPTR (gst_cc_converter_transform_caps);
2584   basetransform_class->fixate_caps =
2585       GST_DEBUG_FUNCPTR (gst_cc_converter_fixate_caps);
2586   basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_cc_converter_set_caps);
2587   basetransform_class->transform_meta =
2588       GST_DEBUG_FUNCPTR (gst_cc_converter_transform_meta);
2589   basetransform_class->generate_output =
2590       GST_DEBUG_FUNCPTR (gst_cc_converter_generate_output);
2591   basetransform_class->passthrough_on_same_caps = TRUE;
2592 
2593   GST_DEBUG_CATEGORY_INIT (gst_cc_converter_debug, "ccconverter",
2594       0, "Closed Caption converter");
2595 
2596   gst_type_mark_as_plugin_api (GST_TYPE_CC_CONVERTER_CDP_MODE, 0);
2597 }
2598 
2599 static void
gst_cc_converter_init(GstCCConverter * self)2600 gst_cc_converter_init (GstCCConverter * self)
2601 {
2602   self->cdp_mode = DEFAULT_CDP_MODE;
2603 }
2604