From 156865541fb18e93b445f759f940989bc66ccd7d Mon Sep 17 00:00:00 2001 From: Mathieu Duponchelle Date: Sat, 23 Feb 2019 00:23:01 +0100 Subject: [PATCH] closedcaption: add line21 encoder This element acts as a counterpart of line21encoder. Also adds a simple test validating each element using the other. --- ext/closedcaption/Makefile.am | 10 +- ext/closedcaption/gstclosedcaption.c | 4 + ext/closedcaption/gstline21enc.c | 224 +++++++++++++++++++++++++++ ext/closedcaption/gstline21enc.h | 61 ++++++++ ext/closedcaption/meson.build | 5 +- tests/check/Makefile.am | 2 +- tests/check/elements/line21.c | 103 ++++++++++++ tests/check/meson.build | 1 + 8 files changed, 405 insertions(+), 5 deletions(-) create mode 100644 ext/closedcaption/gstline21enc.c create mode 100644 ext/closedcaption/gstline21enc.h create mode 100644 tests/check/elements/line21.c diff --git a/ext/closedcaption/Makefile.am b/ext/closedcaption/Makefile.am index dd69142156..7a2a839a80 100644 --- a/ext/closedcaption/Makefile.am +++ b/ext/closedcaption/Makefile.am @@ -3,13 +3,16 @@ plugin_LTLIBRARIES = libgstclosedcaption.la zvbi_sources = \ bit_slicer.c \ decoder.c \ - raw_decoder.c \ + io-sim.c \ + raw_decoder.c \ sampling_par.c zvbi_headers = \ bcd.h \ bit_slicer.h \ decoder.h \ + hamm.h \ + io-sim.h \ macros.h \ misc.h \ raw_decoder.h \ @@ -25,6 +28,7 @@ libgstclosedcaption_la_SOURCES = \ gstcea708decoder.c \ gstceaccoverlay.c \ gstline21dec.c \ + gstline21enc.c \ gstclosedcaption.c libgstclosedcaption_la_CFLAGS = \ @@ -37,7 +41,8 @@ libgstclosedcaption_la_LIBADD = \ $(GST_PLUGINS_BASE_LIBS) -lgstvideo-@GST_API_VERSION@ \ $(GST_BASE_LIBS) \ $(GST_LIBS) \ - $(PANGO_LIBS) + $(PANGO_LIBS) \ + $(LIBM) libgstclosedcaption_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) @@ -48,4 +53,5 @@ noinst_HEADERS = \ gstcea708decoder.h \ gstceaccoverlay.h \ gstline21dec.h \ + gstline21enc.h \ $(zvbi_headers) diff --git a/ext/closedcaption/gstclosedcaption.c b/ext/closedcaption/gstclosedcaption.c index dde31c8f86..daab0af3bb 100644 --- a/ext/closedcaption/gstclosedcaption.c +++ b/ext/closedcaption/gstclosedcaption.c @@ -30,6 +30,7 @@ #include "gstccextractor.h" #include "gstline21dec.h" #include "gstceaccoverlay.h" +#include "gstline21enc.h" static gboolean closedcaption_init (GstPlugin * plugin) @@ -51,6 +52,9 @@ closedcaption_init (GstPlugin * plugin) ret &= gst_element_register (plugin, "cc708overlay", GST_RANK_PRIMARY, GST_TYPE_CEA_CC_OVERLAY); + ret &= gst_element_register (plugin, "line21encoder", GST_RANK_NONE, + GST_TYPE_LINE21ENCODER); + return ret; } diff --git a/ext/closedcaption/gstline21enc.c b/ext/closedcaption/gstline21enc.c new file mode 100644 index 0000000000..1a63fe542c --- /dev/null +++ b/ext/closedcaption/gstline21enc.c @@ -0,0 +1,224 @@ +/* + * GStreamer + * Copyright (C) 2019 Mathieu Duponchelle + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-line21encoder + * @title: line21encoder + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include "gstline21enc.h" +#include "io-sim.h" + +GST_DEBUG_CATEGORY_STATIC (gst_line_21_encoder_debug); +#define GST_CAT_DEFAULT gst_line_21_encoder_debug + +#define CAPS "video/x-raw, format={ I420, YUY2, YVYU, UYVY, VYUY }, width=(int)720, height=(int)[ 23, MAX ], interlace-mode=interleaved" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (CAPS)); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (CAPS)); + +G_DEFINE_TYPE (GstLine21Encoder, gst_line_21_encoder, GST_TYPE_VIDEO_FILTER); +#define parent_class gst_line_21_encoder_parent_class + +static gboolean gst_line_21_encoder_set_info (GstVideoFilter * filter, + GstCaps * incaps, GstVideoInfo * in_info, + GstCaps * outcaps, GstVideoInfo * out_info); +static GstFlowReturn gst_line_21_encoder_transform_ip (GstVideoFilter * filter, + GstVideoFrame * frame); + +static void +gst_line_21_encoder_class_init (GstLine21EncoderClass * klass) +{ + GstElementClass *gstelement_class; + GstVideoFilterClass *filter_class; + + gstelement_class = (GstElementClass *) klass; + filter_class = (GstVideoFilterClass *) klass; + + gst_element_class_set_static_metadata (gstelement_class, + "Line 21 CC Encoder", + "Filter/Video/ClosedCaption", + "Inject line21 CC in SD video streams", + "Mathieu Duponchelle "); + + gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate); + gst_element_class_add_static_pad_template (gstelement_class, &srctemplate); + + filter_class->set_info = gst_line_21_encoder_set_info; + filter_class->transform_frame_ip = gst_line_21_encoder_transform_ip; + + GST_DEBUG_CATEGORY_INIT (gst_line_21_encoder_debug, "line21encoder", + 0, "Line 21 CC Encoder"); + vbi_initialize_gst_debug (); +} + +static void +gst_line_21_encoder_init (GstLine21Encoder * filter) +{ +} + +static vbi_pixfmt +vbi_pixfmt_from_gst_video_format (GstVideoFormat format) +{ + switch (format) { + case GST_VIDEO_FORMAT_I420: + return VBI_PIXFMT_YUV420; + case GST_VIDEO_FORMAT_YUY2: + return VBI_PIXFMT_YUYV; + case GST_VIDEO_FORMAT_YVYU: + return VBI_PIXFMT_YVYU; + case GST_VIDEO_FORMAT_UYVY: + return VBI_PIXFMT_UYVY; + case GST_VIDEO_FORMAT_VYUY: + return VBI_PIXFMT_VYUY; + default: + g_assert_not_reached (); + return (vbi_pixfmt) 0; + } +#undef NATIVE_VBI_FMT +} + +static gboolean +gst_line_21_encoder_set_info (GstVideoFilter * filter, + GstCaps * incaps, GstVideoInfo * in_info, + GstCaps * outcaps, GstVideoInfo * out_info) +{ + GstLine21Encoder *self = GST_LINE21ENCODER (filter); + + self->info = *in_info; + + /* + * Set up blank / black / white levels fit for NTSC, no actual relation + * with the height of the video + */ + self->sp.scanning = 525; + /* The pixel format */ + self->sp.sampling_format = + vbi_pixfmt_from_gst_video_format (GST_VIDEO_INFO_FORMAT (&self->info)); + /* Sampling rate. For BT.601 it's 13.5MHz */ + self->sp.sampling_rate = 13.5e6; + /* Stride */ + self->sp.bytes_per_line = GST_VIDEO_INFO_COMP_STRIDE (&self->info, 0); + /* Horizontal offset of the VBI image */ + self->sp.offset = 122; + + /* FIXME: magic numbers */ + self->sp.start[0] = 21; + self->sp.count[0] = 1; + self->sp.start[1] = 284; + self->sp.count[1] = 1; + + self->sp.interlaced = FALSE; + self->sp.synchronous = TRUE; + + return TRUE; +} + +static GstFlowReturn +gst_line_21_encoder_transform_ip (GstVideoFilter * filter, + GstVideoFrame * frame) +{ + GstLine21Encoder *self = GST_LINE21ENCODER (filter); + GstVideoCaptionMeta *cc_meta; + guint8 *buf; + vbi_sliced sliced[2]; + gpointer iter = NULL; + GstFlowReturn ret = GST_FLOW_ERROR; + + sliced[0].id = VBI_SLICED_CAPTION_525_F1; + sliced[0].line = self->sp.start[0]; + sliced[1].id = VBI_SLICED_CAPTION_525_F2; + sliced[1].line = self->sp.start[1]; + + sliced[0].data[0] = 0x80; + sliced[0].data[1] = 0x80; + sliced[1].data[0] = 0x80; + sliced[1].data[1] = 0x80; + + /* We loop over caption metas until we find the first CEA608 meta */ + while ((cc_meta = (GstVideoCaptionMeta *) + gst_buffer_iterate_meta_filtered (frame->buffer, &iter, + GST_VIDEO_CAPTION_META_API_TYPE))) { + guint n = cc_meta->size; + guint i; + + if (cc_meta->caption_type != GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A) + continue; + + if (n % 3 != 0) { + GST_ERROR_OBJECT (filter, "Invalid S334-1A CEA608 buffer size"); + goto done; + } + + n /= 3; + + if (n >= 3) { + GST_ERROR_OBJECT (filter, "Too many S334-1A CEA608 triplets %u", n); + goto done; + } + + for (i = 0; i < n; i++) { + if (cc_meta->data[i * 3] & 0x80) { + sliced[0].data[0] = cc_meta->data[i * 3 + 1]; + sliced[0].data[1] = cc_meta->data[i * 3 + 2]; + } else { + sliced[1].data[0] = cc_meta->data[i * 3 + 1]; + sliced[1].data[1] = cc_meta->data[i * 3 + 2]; + } + } + + break; + } + + /* We've encoded this meta, it can now be removed */ + if (cc_meta) + gst_buffer_remove_meta (frame->buffer, (GstMeta *) cc_meta); + + buf = + (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (frame, + 0) + 21 * GST_VIDEO_INFO_COMP_STRIDE (&self->info, 0); + + if (!vbi_raw_video_image (buf, GST_VIDEO_INFO_COMP_STRIDE (&self->info, + 0) * 2, &self->sp, 0, 0, 0, 0x000000FF, 0, sliced, 2)) { + GST_ERROR_OBJECT (filter, "Failed to encode CC data"); + goto done; + } + + ret = GST_FLOW_OK; + +done: + return ret; +} diff --git a/ext/closedcaption/gstline21enc.h b/ext/closedcaption/gstline21enc.h new file mode 100644 index 0000000000..ac89f2cd5d --- /dev/null +++ b/ext/closedcaption/gstline21enc.h @@ -0,0 +1,61 @@ +/* + * GStreamer + * Copyright (C) 2019 Mathieu Duponchelle + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_LINE21ENCODER_H__ +#define __GST_LINE21ENCODER_H__ + +#include +#include +#include +#include "io-sim.h" + +G_BEGIN_DECLS +#define GST_TYPE_LINE21ENCODER \ + (gst_line_21_encoder_get_type()) +#define GST_LINE21ENCODER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_LINE21ENCODER,GstLine21Encoder)) +#define GST_LINE21ENCODER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_LINE21ENCODER,GstLine21EncoderClass)) +#define GST_IS_LINE21ENCODER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_LINE21ENCODER)) +#define GST_IS_LINE21ENCODER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_LINE21ENCODER)) + +typedef struct _GstLine21Encoder GstLine21Encoder; +typedef struct _GstLine21EncoderClass GstLine21EncoderClass; + +struct _GstLine21Encoder +{ + GstVideoFilter parent; + + vbi_sampling_par sp; + + GstVideoInfo info; +}; + +struct _GstLine21EncoderClass +{ + GstVideoFilterClass parent_class; +}; + +GType gst_line_21_encoder_get_type (void); + +G_END_DECLS +#endif /* __GST_LINE21ENCODER_H__ */ diff --git a/ext/closedcaption/meson.build b/ext/closedcaption/meson.build index c5c971b2e5..50329df25c 100644 --- a/ext/closedcaption/meson.build +++ b/ext/closedcaption/meson.build @@ -12,11 +12,12 @@ zvbi_sources = [ if pangocairo_dep.found() gstclosedcaption = library('gstclosedcaption', 'gstcccombiner.c', 'gstccextractor.c', 'gstccconverter.c', 'gstclosedcaption.c', - 'gstline21dec.c', 'gstcea708decoder.c', 'gstceaccoverlay.c', zvbi_sources, + 'gstline21dec.c', 'gstcea708decoder.c', 'gstceaccoverlay.c', 'gstline21enc.c', + zvbi_sources, c_args : gst_plugins_bad_args, link_args : noseh_link_args, include_directories : [configinc], - dependencies : [gstvideo_dep, gstbase_dep, gst_dep, pangocairo_dep], + dependencies : [gstvideo_dep, gstbase_dep, gst_dep, pangocairo_dep, libm], install : true, install_dir : plugins_install_dir, ) diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 36518d4496..a07ce82777 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -41,7 +41,7 @@ check_assrender = endif if USE_PANGO -check_closedcaption = elements/ccconverter elements/cccombiner elements/ccextractor +check_closedcaption = elements/ccconverter elements/cccombiner elements/ccextractor elements/line21 else check_closedcaption = endif diff --git a/tests/check/elements/line21.c b/tests/check/elements/line21.c new file mode 100644 index 0000000000..029ff238f3 --- /dev/null +++ b/tests/check/elements/line21.c @@ -0,0 +1,103 @@ +/* GStreamer + * + * Copyright (C) 2019 Mathieu Duponchelle + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include + +GST_START_TEST (basic) +{ + GstHarness *h; + GstBuffer *buf, *outbuf; + GstVideoInfo info; + GstVideoCaptionMeta *in_cc_meta, *out_cc_meta; + guint i; + guint8 empty_data[] = { 0x90, 0x80, 0x80, 0x0, 0x80, 0x80 }; + guint8 full_data[] = { 0x90, 0x42, 0x43, 0x0, 0x44, 0x45 }; + GstCaps *caps = gst_caps_new_simple ("video/x-raw", + "format", G_TYPE_STRING, "I420", + "width", G_TYPE_INT, 720, + "height", G_TYPE_INT, 625, + "interlace-mode", G_TYPE_STRING, "interleaved", + NULL); + + h = gst_harness_new_parse ("line21encoder ! line21decoder"); + gst_harness_set_caps (h, gst_caps_ref (caps), gst_caps_ref (caps)); + + gst_video_info_from_caps (&info, caps); + + gst_caps_unref (caps); + + buf = gst_buffer_new_and_alloc (info.size); + outbuf = gst_harness_push_and_pull (h, buf); + + fail_unless (outbuf != NULL); + fail_unless_equals_int (gst_buffer_get_n_meta (outbuf, + GST_VIDEO_CAPTION_META_API_TYPE), 1); + + out_cc_meta = gst_buffer_get_video_caption_meta (outbuf); + + fail_unless (out_cc_meta != NULL); + fail_unless (out_cc_meta->size == 6); + + for (i = 0; i < out_cc_meta->size; i++) + fail_unless (out_cc_meta->data[i] == empty_data[i]); + + gst_buffer_unref (outbuf); + + buf = gst_buffer_new_and_alloc (info.size); + gst_buffer_add_video_caption_meta (buf, GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A, + full_data, 6); + in_cc_meta = gst_buffer_get_video_caption_meta (buf); + outbuf = gst_harness_push_and_pull (h, buf); + + fail_unless (outbuf != NULL); + fail_unless_equals_int (gst_buffer_get_n_meta (outbuf, + GST_VIDEO_CAPTION_META_API_TYPE), 1); + + out_cc_meta = gst_buffer_get_video_caption_meta (outbuf); + fail_unless (in_cc_meta != out_cc_meta); + + for (i = 0; i < out_cc_meta->size; i++) + fail_unless (out_cc_meta->data[i] == full_data[i]); + + gst_buffer_unref (outbuf); +} + +GST_END_TEST; + +static Suite * +line21_suite (void) +{ + Suite *s = suite_create ("line21"); + TCase *tc = tcase_create ("general"); + + suite_add_tcase (s, tc); + + tcase_add_test (tc, basic); + + return s; +} + +GST_CHECK_MAIN (line21); diff --git a/tests/check/meson.build b/tests/check/meson.build index 1007b75545..90a334745c 100644 --- a/tests/check/meson.build +++ b/tests/check/meson.build @@ -58,6 +58,7 @@ if host_machine.system() != 'windows' [['elements/ccconverter.c']], [['elements/cccombiner.c']], [['elements/ccextractor.c']], + [['elements/line21.c']], [['elements/curlhttpsink.c'], not curl_dep.found(), [curl_dep]], [['elements/curlhttpsrc.c'], not curl_dep.found(), [curl_dep, gio_dep]], [['elements/curlfilesink.c'],