/* GStreamer * Copyright (C) 2019 Stéphane Cerveau * * 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-zxing * @title: zxing * * Detect bar codes in the video streams and send them as element messages to * the #GstBus if #GstZXing:message property is %TRUE. * If the #GstZXing:attach-frame property is %TRUE, the posted barcode message * includes a sample of the frame where the barcode was detected (Since 1.18). * * The element generates messages named `barcode`. The structure contains these fields: * * * #GstClockTime `timestamp`: the timestamp of the buffer that triggered the message. * * gchar * `type`: the symbol type. * * gchar * `symbol`: the detected bar code data. * * #GstClockTime `stream-time`: timestamp converted to stream-time. * * #GstClockTime `running-time`: timestamp converted to running-time. * * #GstSample `frame`: the frame in which the barcode message was detected, if * the .#GstZXing:attach-frame property was set to %TRUE (Since 1.18) * * This element is based on the c++ implementation of zxing which can found * at . * * ## Example launch lines * |[ * gst-launch-1.0 -m v4l2src ! videoconvert ! zxing ! videoconvert ! xvimagesink * ]| This pipeline will detect barcodes and send them as messages. * |[ * gst-launch-1.0 -m v4l2src ! tee name=t ! queue ! videoconvert ! zxing ! fakesink t. ! queue ! videoconvert ! xvimagesink * ]| Same as above, but running the filter on a branch to keep the display in color * * Since: 1.18 * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstzxing.h" #include #include #include #include "ReadBarcode.h" #include "TextUtfEncoding.h" #include "ZXVersion.h" using namespace ZXing; GST_DEBUG_CATEGORY_STATIC (zxing_debug); #define GST_CAT_DEFAULT zxing_debug #define DEFAULT_MESSAGE TRUE #define DEFAULT_ATTACH_FRAME FALSE #define DEFAULT_TRY_ROTATE FALSE #define DEFAULT_TRY_FASTER FALSE enum { PROP_0, PROP_MESSAGE, PROP_ATTACH_FRAME, PROP_TRY_ROTATE, PROP_TRY_FASTER, PROP_FORMAT, }; /** * GstBarCodeFormat: * * @BARCODE_FORMAT_ALL all format * @BARCODE_FORMAT_AZTEC aztec format * @BARCODE_FORMAT_CODABAR codabar format * @BARCODE_FORMAT_CODE_39 code39 format * @BARCODE_FORMAT_CODE_93 code 93 format * @BARCODE_FORMAT_CODE_128 code 128 format * @BARCODE_FORMAT_DATA_MATRIX data matrix format * @BARCODE_FORMAT_EAN_8 EAN 8 format * @BARCODE_FORMAT_EAN_13 EAN 13 format * @BARCODE_FORMAT_ITF ITF format * @BARCODE_FORMAT_MAXICODE maxicode format * @BARCODE_FORMAT_PDF_417 pdf 417 format * @BARCODE_FORMAT_QR_CODE QR code format * @BARCODE_FORMAT_RSS_14 RSS 14 format * @BARCODE_FORMAT_RSS_EXPANDED RSS expanded format * @BARCODE_FORMAT_UPC_A UPC_A format * @BARCODE_FORMAT_UPC_E UPC_E format * @BARCODE_FORMAT_UPC_EAN_EXTENSION UPC_EAN_EXTENSION format * * Since: 1.18 */ typedef enum { BARCODE_FORMAT_ALL, BARCODE_FORMAT_AZTEC, BARCODE_FORMAT_CODABAR, BARCODE_FORMAT_CODE_39, BARCODE_FORMAT_CODE_93, BARCODE_FORMAT_CODE_128, BARCODE_FORMAT_DATA_MATRIX, BARCODE_FORMAT_EAN_8, BARCODE_FORMAT_EAN_13, BARCODE_FORMAT_ITF, BARCODE_FORMAT_MAXICODE, BARCODE_FORMAT_PDF_417, BARCODE_FORMAT_QR_CODE, BARCODE_FORMAT_RSS_14, BARCODE_FORMAT_RSS_EXPANDED, BARCODE_FORMAT_UPC_A, BARCODE_FORMAT_UPC_E, BARCODE_FORMAT_UPC_EAN_EXTENSION } GstBarCodeFormat; static const GEnumValue barcode_formats[] = { {BARCODE_FORMAT_ALL, "ALL", "all"}, {BARCODE_FORMAT_AZTEC, "AZTEC", "aztec"}, {BARCODE_FORMAT_CODABAR, "CODABAR", "codabar"}, {BARCODE_FORMAT_CODE_39, "CODE_39", "code_39"}, {BARCODE_FORMAT_CODE_93, "CODE_93", "code_93"}, {BARCODE_FORMAT_CODE_128, "CODE_128", "code_128"}, {BARCODE_FORMAT_DATA_MATRIX, "PNG", "png"}, {BARCODE_FORMAT_EAN_8, "EAN_8", "ean_8"}, {BARCODE_FORMAT_EAN_13, "EAN_13", "ean_13"}, {BARCODE_FORMAT_ITF, "ITF", "itf"}, {BARCODE_FORMAT_MAXICODE, "MAXICODE", "maxicode"}, {BARCODE_FORMAT_PDF_417, "PDF_417", "pdf_417"}, {BARCODE_FORMAT_QR_CODE, "QR_CODE", "qr_code"}, {BARCODE_FORMAT_RSS_14, "RSS_14", "rss_14"}, {BARCODE_FORMAT_RSS_EXPANDED, "RSS_EXPANDED", "rss_expanded"}, {BARCODE_FORMAT_UPC_A, "UPC_A", "upc_a"}, {BARCODE_FORMAT_UPC_E, "UPC_E", "upc_e"}, {BARCODE_FORMAT_UPC_EAN_EXTENSION, "UPC_EAN_EXTENSION", "upc_ean_expansion"}, {0, NULL, NULL}, }; #define GST_TYPE_BARCODE_FORMAT (gst_barcode_format_get_type()) static GType gst_barcode_format_get_type (void) { static GType barcode_format_type = 0; if (!barcode_format_type) { barcode_format_type = g_enum_register_static ("GstBarCodeFormat", barcode_formats); } return barcode_format_type; } #define ZXING_YUV_CAPS \ "{ARGB, xRGB, Y444, Y42B, I420, Y41B, YUV9, YV12}" static GstStaticPadTemplate gst_zxing_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (ZXING_YUV_CAPS)) ); static GstStaticPadTemplate gst_zxing_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (ZXING_YUV_CAPS)) ); /** * GstZXing: * * Opaque data structure. */ struct _GstZXing { /*< private > */ GstVideoFilter videofilter; /* properties */ gboolean message; gboolean attach_frame; gboolean rotate; gboolean faster; ImageFormat image_format; GstBarCodeFormat barcode_format; }; static void gst_zxing_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_zxing_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static gboolean gst_zxing_set_info (GstVideoFilter * vfilter, GstCaps * in, GstVideoInfo * in_info, GstCaps * out, GstVideoInfo * out_info); static GstFlowReturn gst_zxing_transform_frame_ip (GstVideoFilter * vfilter, GstVideoFrame * frame); #define gst_zxing_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstZXing, gst_zxing, GST_TYPE_VIDEO_FILTER, GST_DEBUG_CATEGORY_INIT (zxing_debug, "zxing", 0, "debug category for zxing element")); GST_ELEMENT_REGISTER_DEFINE (zxing, "zxing", GST_RANK_MARGINAL, GST_TYPE_ZXING); static void gst_zxing_class_init (GstZXingClass * g_class) { GObjectClass *gobject_class; GstElementClass *gstelement_class; GstVideoFilterClass *vfilter_class; gobject_class = G_OBJECT_CLASS (g_class); gstelement_class = GST_ELEMENT_CLASS (g_class); vfilter_class = GST_VIDEO_FILTER_CLASS (g_class); gobject_class->set_property = gst_zxing_set_property; gobject_class->get_property = gst_zxing_get_property; /** * GstZXing::message: * * Post a barcode message for each detected code. * * Since: 1.18 */ g_object_class_install_property (gobject_class, PROP_MESSAGE, g_param_spec_boolean ("message", "message", "Post a barcode message for each detected code", DEFAULT_MESSAGE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); /** * GstZXing::attach-frame: * * Attach a frame dump to each barcode message * * Since: 1.18 */ g_object_class_install_property (gobject_class, PROP_ATTACH_FRAME, g_param_spec_boolean ("attach-frame", "Attach frame", "Attach a frame dump to each barcode message", DEFAULT_ATTACH_FRAME, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); /** * GstZXing::try-rotate: * * Try to rotate the frame to detect barcode (slower) * * Since: 1.18 */ g_object_class_install_property (gobject_class, PROP_TRY_ROTATE, g_param_spec_boolean ("try-rotate", "Try rotate", "Try to rotate the frame to detect barcode (slower)", DEFAULT_TRY_ROTATE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); /** * GstZXing::try-faster: * * Try faster to analyze the frame * * Since: 1.18 */ g_object_class_install_property (gobject_class, PROP_TRY_FASTER, g_param_spec_boolean ("try-faster", "Try faster", "Try faster to analyze the frame", DEFAULT_TRY_FASTER, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); /** * GstZXing::format: * * Barcode image format * * Since: 1.18 */ g_object_class_install_property (gobject_class, PROP_FORMAT, g_param_spec_enum ("format", "barcode format", "Barcode image format", GST_TYPE_BARCODE_FORMAT, BARCODE_FORMAT_ALL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); gst_element_class_set_static_metadata (gstelement_class, "Barcode detector", "Filter/Analyzer/Video", "Detect bar codes in the video streams with zxing library", "Stéphane Cerveau "); gst_element_class_add_static_pad_template (gstelement_class, &gst_zxing_sink_template); gst_element_class_add_static_pad_template (gstelement_class, &gst_zxing_src_template); vfilter_class->transform_frame_ip = GST_DEBUG_FUNCPTR (gst_zxing_transform_frame_ip); vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_zxing_set_info); gst_type_mark_as_plugin_api (GST_TYPE_BARCODE_FORMAT, (GstPluginAPIFlags)0); } static void gst_zxing_init (GstZXing * zxing) { zxing->message = DEFAULT_MESSAGE; zxing->attach_frame = DEFAULT_ATTACH_FRAME; zxing->rotate = DEFAULT_TRY_ROTATE; zxing->faster = DEFAULT_TRY_FASTER; zxing->image_format = ImageFormat::None; zxing->barcode_format = BARCODE_FORMAT_ALL; } static void gst_zxing_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstZXing *zxing; g_return_if_fail (GST_IS_ZXING (object)); zxing = GST_ZXING (object); switch (prop_id) { case PROP_MESSAGE: zxing->message = g_value_get_boolean (value); break; case PROP_ATTACH_FRAME: zxing->attach_frame = g_value_get_boolean (value); break; case PROP_TRY_ROTATE: zxing->rotate = g_value_get_boolean (value); break; case PROP_TRY_FASTER: zxing->faster = g_value_get_boolean (value); break; case PROP_FORMAT: zxing->barcode_format = (GstBarCodeFormat)g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_zxing_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstZXing *zxing; g_return_if_fail (GST_IS_ZXING (object)); zxing = GST_ZXING (object); switch (prop_id) { case PROP_MESSAGE: g_value_set_boolean (value, zxing->message); break; case PROP_ATTACH_FRAME: g_value_set_boolean (value, zxing->attach_frame); break; case PROP_TRY_ROTATE: g_value_set_boolean (value, zxing->rotate); break; case PROP_TRY_FASTER: g_value_set_boolean (value, zxing->faster); break; case PROP_FORMAT: g_value_set_enum (value, zxing->barcode_format); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gst_zxing_set_info (GstVideoFilter * vfilter, GstCaps * in, GstVideoInfo * in_info, GstCaps * out, GstVideoInfo * out_info) { GstZXing *zxing = GST_ZXING (vfilter); switch (in_info->finfo->format) { case GST_VIDEO_FORMAT_ARGB: case GST_VIDEO_FORMAT_xRGB: zxing->image_format = ImageFormat::XRGB; break; case GST_VIDEO_FORMAT_Y444: case GST_VIDEO_FORMAT_Y42B: case GST_VIDEO_FORMAT_I420: case GST_VIDEO_FORMAT_Y41B: case GST_VIDEO_FORMAT_YUV9: case GST_VIDEO_FORMAT_YV12: zxing->image_format = ImageFormat::Lum; break; default: zxing->image_format = ImageFormat::None; GST_WARNING_OBJECT (zxing, "This format is not supported %s", gst_video_format_to_string(in_info->finfo->format)); } return TRUE; } static GstFlowReturn gst_zxing_transform_frame_ip (GstVideoFilter * vfilter, GstVideoFrame * frame) { GstZXing *zxing = GST_ZXING (vfilter); gpointer data; gint height, width; #if ZXING_VERSION_MAJOR >= 2 && ZXING_VERSION_MINOR >= 2 ReaderOptions hints; #else DecodeHints hints; #endif hints.setTryRotate(zxing->rotate); hints.setTryHarder(!zxing->faster); hints.setFormats(BarcodeFormatFromString (barcode_formats[zxing->barcode_format].value_name)); /* all formats we support start with an 8-bit Y plane. zxing doesn't need * to know about the chroma plane(s) */ data = GST_VIDEO_FRAME_COMP_DATA (frame, 0); width = GST_VIDEO_FRAME_WIDTH (frame); height = GST_VIDEO_FRAME_HEIGHT (frame); auto result = ReadBarcode ({(unsigned char *)data, width, height, zxing->image_format}, hints); if (result.isValid ()) { GST_DEBUG_OBJECT (zxing, "Symbol found. Text: %s Format: %s", result.text ().c_str (), #if ZXING_VERSION_MAJOR >= 2 ToString (result.format ()).c_str ()); #else ToString (result.format ())); #endif } else { goto out; } /* extract results */ if (zxing->message) { GstMessage *m; GstStructure *s; GstSample *sample; GstCaps *sample_caps; GstClockTime timestamp, running_time, stream_time; timestamp = GST_BUFFER_TIMESTAMP (frame->buffer); running_time = gst_segment_to_running_time (&GST_BASE_TRANSFORM (zxing)->segment, GST_FORMAT_TIME, timestamp); stream_time = gst_segment_to_stream_time (&GST_BASE_TRANSFORM (zxing)->segment, GST_FORMAT_TIME, timestamp); s = gst_structure_new ("barcode", "timestamp", G_TYPE_UINT64, timestamp, "stream-time", G_TYPE_UINT64, stream_time, "running-time", G_TYPE_UINT64, running_time, #if ZXING_VERSION_MAJOR >= 2 "type", G_TYPE_STRING, ToString (result.format ()).c_str (), #else "type", G_TYPE_STRING, ToString (result.format ()), #endif "symbol", G_TYPE_STRING, result.text ().c_str (), NULL); if (zxing->attach_frame) { /* create a sample from image */ sample_caps = gst_video_info_to_caps (&frame->info); sample = gst_sample_new (frame->buffer, sample_caps, NULL, NULL); gst_caps_unref (sample_caps); gst_structure_set (s, "frame", GST_TYPE_SAMPLE, sample, NULL); gst_sample_unref (sample); } m = gst_message_new_element (GST_OBJECT (zxing), s); gst_element_post_message (GST_ELEMENT (zxing), m); } else if (zxing->attach_frame) GST_WARNING_OBJECT (zxing, "attach-frame=true has no effect if message=false"); out: return GST_FLOW_OK; }