ccconverter: implement field conversion of CEA 608 control codes

As specified in EIA/CEA-608-B section 8.4:

When closed captioning is used on line 21, field 2, it shall conform
to all of the applicable specifications and recommended practices as
defined for field 1 services with the following differences:

a) The non-printing character of the miscellaneous control-character pairs
  that fall in the range of 14h, 20h to 14h, 2Fh in field 1, shall be replaced
  with 15h, 20h to 15h, 2Fh when used in field 2.
b) The non-printing character of the miscellaneous control-character pairs
  that fall in the range of 1Ch, 20h to 1Ch, 2Fh in field 1, shall be replaced
  with 1Dh, 20h to 1Dh, 2Fh when used in field 2.

This means simply switching the "field" field in the caps isn't enough for
converting raw 608 from one field to another, some control codes also
need to be amended.

+ Adds simple test

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4126>
This commit is contained in:
Mathieu Duponchelle 2023-03-07 15:39:04 +01:00 committed by GStreamer Marge Bot
parent b4cadf35bd
commit 2eab37f231
2 changed files with 95 additions and 5 deletions

View file

@ -483,13 +483,9 @@ gst_cc_converter_set_caps (GstBaseTransform * base, GstCaps * incaps,
gst_video_time_code_clear (&self->current_output_timecode);
/* For raw 608 caps in and out, we can ignore the field to determine
* passthrough. We can also ignore framerate at this point, as our transform_caps
* implementation guarantees that it is either absent or present on both sides.
*/
if (gst_caps_is_subset (incaps, gst_static_caps_get (&raw_608_caps)) &&
gst_caps_is_subset (outcaps, gst_static_caps_get (&raw_608_caps))) {
passthrough = TRUE;
passthrough = self->in_field == self->out_field;
} else {
/* Caps can be different but we can passthrough as long as they can
* intersect, i.e. have same caps name and format */
@ -730,6 +726,85 @@ convert_cea608_raw_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
return GST_FLOW_OK;
}
static inline guint8
eia608_parity_strip (guint8 cc_data)
{
return cc_data & 0x7F;
}
static GstFlowReturn
convert_cea608_raw_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
GstBuffer * outbuf)
{
GstMapInfo in, out;
guint i, n;
g_assert (self->in_field != self->out_field);
n = gst_buffer_get_size (inbuf);
if (n & 1) {
GST_WARNING_OBJECT (self, "Invalid raw CEA608 buffer size");
gst_buffer_set_size (outbuf, 0);
return GST_FLOW_OK;
}
n /= 2;
if (n > 3) {
GST_WARNING_OBJECT (self, "Too many CEA608 pairs %u. Truncating to %u", n,
3);
n = 3;
}
gst_buffer_set_size (outbuf, 2 * n);
gst_buffer_map (inbuf, &in, GST_MAP_READ);
gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
/* EIA/CEA-608-B 8.4 Closed Caption Mode:
*
* When closed captioning is used on line 21, field 2,
* it shall conform to all of the applicable specifications and
* recommended practices as defined for field 1 services with the
* following differences:
*
* a) The non-printing character of the miscellaneous control-character pairs
* that fall in the range of 14h, 20h to 14h, 2Fh in field 1, shall be
* replaced with 15h, 20h to 15h, 2Fh when used in field 2.
*
* b) The non-printing character of the miscellaneous control-character pairs
* that fall in the range of 1Ch, 20h to 1Ch, 2Fh in field 1, shall be replaced
* with 1Dh, 20h to 1Dh, 2Fh when used in field 2.
*/
for (i = 0; i < n; i++) {
guint8 cc_data_1 = eia608_parity_strip (in.data[i * 2]);
guint8 cc_data_2 = eia608_parity_strip (in.data[i * 2 + 1]);
out.data[i * 2] = in.data[i * 2];
out.data[i * 2 + 1] = in.data[i * 2 + 1];
if (self->in_field == 0 && self->out_field == 1) {
if (cc_data_1 == 0x14 && cc_data_2 >= 0x20 && cc_data_2 <= 0x2f) {
out.data[i * 2] = 0x15;
} else if (cc_data_1 == 0x1c && cc_data_2 >= 0x20 && cc_data_2 <= 0x2f) {
out.data[i * 2] = 0x9d;
}
} else if (self->in_field == 1 && self->out_field == 0) {
if (cc_data_1 == 0x15 && cc_data_2 >= 0x20 && cc_data_2 <= 0x2f) {
out.data[i * 2] = 0x94;
} else if (cc_data_1 == 0x1d && cc_data_2 >= 0x20 && cc_data_2 <= 0x2f) {
out.data[i * 2] = 0x1c;
}
}
}
gst_buffer_unmap (inbuf, &in);
gst_buffer_unmap (outbuf, &out);
return GST_FLOW_OK;
}
static GstFlowReturn
convert_cea608_raw_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
GstBuffer * outbuf)
@ -1370,6 +1445,8 @@ gst_cc_converter_transform (GstCCConverter * self, GstBuffer * inbuf,
ret = convert_cea608_raw_cea708_cdp (self, inbuf, outbuf, tc_meta);
break;
case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
ret = convert_cea608_raw_cea608_raw (self, inbuf, outbuf);
break;
default:
g_assert_not_reached ();
break;

View file

@ -1067,6 +1067,18 @@ GST_START_TEST (convert_cea708_cdp_cea608_raw_field_one)
GST_END_TEST;
GST_START_TEST (convert_cea608_raw_cea608_raw_field_one)
{
/* Control codes must be translated when converting from field 0 to field 1 */
const guint8 in[] = { 0x94, 0x20 };
const guint8 out[] = { 0x15, 0x20 };
check_conversion_tc_passthrough (in, sizeof (in), out, sizeof (out),
"closedcaption/x-cea-608,format=(string)raw,field=(int)0",
"closedcaption/x-cea-608,format=(string)raw,field=(int)1");
}
GST_END_TEST;
GST_START_TEST (convert_cea708_cdp_cea708_cc_data_double_input_data)
{
/* caps say 60fps, data has 30fps. Ensure data is taken alternatatively from
@ -1290,6 +1302,7 @@ ccextractor_suite (void)
tcase_add_test (tc, convert_cea608_s334_1a_cea608_raw_field_one);
tcase_add_test (tc, convert_cea708_cc_data_cea608_raw_field_one);
tcase_add_test (tc, convert_cea708_cdp_cea608_raw_field_one);
tcase_add_test (tc, convert_cea608_raw_cea608_raw_field_one);
return s;
}