zxing: initial plugin revision
authorStéphane Cerveau <scerveau@collabora.com>
Fri, 27 Sep 2019 21:02:38 +0000 (23:02 +0200)
committerGuillaume Desmottes <guillaume.desmottes@collabora.com>
Tue, 7 Jan 2020 17:24:50 +0000 (17:24 +0000)
Status:
- scan QR code with low resolution
- Scan barcode with high resolution

ext/meson.build
ext/zxing/gstzxing.cpp [new file with mode: 0644]
ext/zxing/gstzxing.h [new file with mode: 0644]
ext/zxing/gstzxingplugin.c [new file with mode: 0644]
ext/zxing/meson.build [new file with mode: 0644]
meson_options.txt
tests/check/elements/zxing.c [new file with mode: 0644]
tests/check/meson.build

index ab8ec43..2cbddbf 100644 (file)
@@ -65,4 +65,5 @@ subdir('webp')
 subdir('wildmidi')
 subdir('wpe')
 subdir('x265')
+subdir('zxing')
 subdir('zbar')
diff --git a/ext/zxing/gstzxing.cpp b/ext/zxing/gstzxing.cpp
new file mode 100644 (file)
index 0000000..760f8f7
--- /dev/null
@@ -0,0 +1,382 @@
+/* GStreamer
+ * Copyright (C) 2019 Stéphane Cerveau <scerveau@collabora.com>
+ *
+ * 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 generate 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)
+ *
+ * ## 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 ! xvimagesink
+ * ]| Same as above, but running the filter on a branch to keep the display in color
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstzxing.h"
+
+#include <string.h>
+#include <math.h>
+
+#include <gst/video/video.h>
+
+#include "ReadBarcode.h"
+#include "TextUtfEncoding.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,
+};
+
+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
+};
+
+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 \
+    "{ Y800, I420, YV12, NV12, NV21, Y41B, Y42B, YUV9, YVU9 }"
+
+
+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;
+  guint 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 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"));
+
+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;
+
+  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)));
+
+  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)));
+
+  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)));
+  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)));
+  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 <scerveau@collabora.com>");
+
+  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);
+}
+
+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->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->format = 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->format);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstFlowReturn
+gst_zxing_transform_frame_ip (GstVideoFilter * vfilter, GstVideoFrame * frame)
+{
+  GstZXing *zxing = GST_ZXING (vfilter);
+  gpointer data;
+  gint height, width;
+
+  /* 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);
+
+  /*Init a grayscale source */
+  auto result = ReadBarcode (width, height, (unsigned char *) data, width,
+      { BarcodeFormatFromString (barcode_formats[zxing->format].value_name) },
+      zxing->rotate, !zxing->faster);
+  if (result.isValid ()) {
+    GST_DEBUG_OBJECT (zxing, "Symbol found. Text: %s Format: %s",
+        TextUtfEncoding::ToUtf8 (result.text ()).c_str (),
+        ToString (result.format ()));
+  } 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,
+        "type", G_TYPE_STRING, ToString (result.format ()),
+        "symbol", G_TYPE_STRING,
+        TextUtfEncoding::ToUtf8 (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;
+}
diff --git a/ext/zxing/gstzxing.h b/ext/zxing/gstzxing.h
new file mode 100644 (file)
index 0000000..9aa6db3
--- /dev/null
@@ -0,0 +1,33 @@
+/* GStreamer
+ * Copyright (C) 2019 Stéphane Cerveau <scerveau@collabora.com>
+ *
+ * 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_VIDEO_ZXING_H__
+#define __GST_VIDEO_ZXING_H__
+
+#include <gst/video/video.h>
+#include <gst/video/gstvideofilter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ZXING gst_zxing_get_type ()
+G_DECLARE_FINAL_TYPE(GstZXing, gst_zxing, GST, ZXING, GstVideoFilter)
+
+G_END_DECLS
+
+#endif /* __GST_VIDEO_ZXING_H__ */
diff --git a/ext/zxing/gstzxingplugin.c b/ext/zxing/gstzxingplugin.c
new file mode 100644 (file)
index 0000000..ba8c3a3
--- /dev/null
@@ -0,0 +1,44 @@
+/* GStreamer
+ * Copyright (C) 2019 Stéphane Cerveau <scerveau@collabora.com>
+ *
+ * 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.
+ */
+
+
+/*
+ * ZXing GStreamer plugin
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gst/gst.h>
+#include "gstzxing.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  gst_element_register (plugin, "zxing", GST_RANK_MARGINAL, GST_TYPE_ZXING);
+
+  return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    zxing,
+    "ZXing bar code detector plugin",
+    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/ext/zxing/meson.build b/ext/zxing/meson.build
new file mode 100644 (file)
index 0000000..81a7ee3
--- /dev/null
@@ -0,0 +1,18 @@
+zxing_sources = [
+  'gstzxing.cpp',
+  'gstzxingplugin.c',
+]
+zxing_dep = dependency('zxing', version : '>= 0.9', required : get_option('zxing'))
+if zxing_dep.found()
+       gstzxing = library('gstzxing',
+         zxing_sources,
+         c_args : gst_plugins_bad_args,
+         include_directories : [configinc],
+         dependencies : [gstvideo_dep, zxing_dep],
+         install : true,
+         install_dir : plugins_install_dir,
+          override_options : ['cpp_std=c++11'],
+       )
+       pkgconfig.generate(gstzxing, install_dir : plugins_pkgconfig_install_dir)
+       plugins += [gstzxing]
+endif
index 65574fb..5e48b8c 100644 (file)
@@ -161,7 +161,8 @@ option('wildmidi', type : 'feature', value : 'auto', description : 'WildMidi mid
 option('winks', type : 'feature', value : 'auto', description : 'Windows Kernel Streaming video source plugin')
 option('winscreencap', type : 'feature', value : 'auto', description : 'Windows Screen Capture video source plugin')
 option('x265', type : 'feature', value : 'auto', description : 'HEVC/H.265 video encoder plugin')
-option('zbar', type : 'feature', value : 'auto', description : 'Barcode image scanner plugin')
+option('zbar', type : 'feature', value : 'auto', description : 'Barcode image scanner plugin using zbar library')
+option('zxing', type : 'feature', value : 'auto', description : 'Barcode image scanner plugin using zxing-cpp library')
 option('wpe', type : 'feature', value : 'auto', description : 'WPE Web browser plugin')
 option('magicleap', type : 'feature', value : 'auto', description : 'Magic Leap platform support')
 
diff --git a/tests/check/elements/zxing.c b/tests/check/elements/zxing.c
new file mode 100644 (file)
index 0000000..91f2724
--- /dev/null
@@ -0,0 +1,172 @@
+/* GStreamer zxing element unit test
+ *
+ * Copyright (C) 2019 Stéphane Cerveau  <scerveau@collabora.com>
+ *
+ * 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.
+ */
+
+#include <gst/check/gstcheck.h>
+
+static GstElement *
+setup_pipeline (void)
+{
+  GstElement *pipeline, *src, *dec, *csp, *zxing, *sink;
+  gchar *path;
+
+  pipeline = gst_pipeline_new ("pipeline");
+
+  src = gst_element_factory_make ("filesrc", NULL);
+  /* Test file must have size < 4096 otherwise pngparse will be necessary before pngdec. */
+  dec = gst_element_factory_make ("pngdec", NULL);
+  csp = gst_element_factory_make ("videoconvert", NULL);
+  zxing = gst_element_factory_make ("zxing", "zxing");
+  sink = gst_element_factory_make ("fakesink", NULL);
+
+  path = g_build_filename (GST_TEST_FILES_PATH, "barcode.png", NULL);
+  GST_LOG ("reading file '%s'", path);
+  g_object_set (src, "location", path, NULL);
+  g_free (path);
+
+  gst_bin_add_many (GST_BIN (pipeline), src, dec, csp, zxing, sink, NULL);
+  fail_unless (gst_element_link_many (src, dec, csp, zxing, sink, NULL));
+
+  return pipeline;
+}
+
+static GstMessage *
+get_zxing_msg_until_eos (GstElement * pipeline)
+{
+  GstMessage *zxing_msg = NULL;
+
+  do {
+    GstMessage *msg;
+
+    msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipeline), -1,
+        GST_MESSAGE_ELEMENT | GST_MESSAGE_EOS | GST_MESSAGE_ERROR);
+
+    GST_INFO ("message: %" GST_PTR_FORMAT, msg);
+
+    fail_if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR);
+
+    if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) {
+      gst_message_unref (msg);
+      break;
+    }
+
+    if (!g_strcmp0 (GST_OBJECT_NAME (GST_MESSAGE_SRC (msg)), "zxing")
+        && zxing_msg == NULL) {
+      zxing_msg = msg;
+    } else {
+      gst_message_unref (msg);
+    }
+  } while (1);
+  return zxing_msg;
+}
+
+GST_START_TEST (test_still_image)
+{
+  GstMessage *zxing_msg;
+  const GstStructure *s;
+  GstElement *pipeline;
+  const gchar *type, *symbol;
+
+  pipeline = setup_pipeline ();
+
+  fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING),
+      GST_STATE_CHANGE_ASYNC);
+
+  zxing_msg = get_zxing_msg_until_eos (pipeline);
+  fail_unless (zxing_msg != NULL);
+
+  s = gst_message_get_structure (zxing_msg);
+  fail_unless (s != NULL);
+
+  fail_unless (gst_structure_has_name (s, "barcode"));
+  fail_unless (gst_structure_has_field (s, "timestamp"));
+  fail_unless (gst_structure_has_field (s, "stream-time"));
+  fail_unless (gst_structure_has_field (s, "running-time"));
+  fail_unless (gst_structure_has_field (s, "type"));
+  fail_unless (gst_structure_has_field (s, "symbol"));
+  type = gst_structure_get_string (s, "type");
+  fail_unless_equals_string (type, "EAN_13");
+  symbol = gst_structure_get_string (s, "symbol");
+  fail_unless_equals_string (symbol, "9876543210128");
+
+  fail_if (gst_structure_has_field (s, "frame"));
+
+  fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL),
+      GST_STATE_CHANGE_SUCCESS);
+
+  gst_object_unref (pipeline);
+  gst_message_unref (zxing_msg);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_still_image_with_sample)
+{
+  GstMessage *zxing_msg = NULL;
+  const GstStructure *s;
+  GstElement *pipeline;
+  GstSample *sample;
+
+  pipeline = setup_pipeline ();
+  gst_child_proxy_set ((GstChildProxy *) pipeline, "zxing::attach-frame", TRUE,
+      NULL);
+
+  fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING),
+      GST_STATE_CHANGE_ASYNC);
+
+  zxing_msg = get_zxing_msg_until_eos (pipeline);
+  fail_unless (zxing_msg != NULL);
+
+  s = gst_message_get_structure (zxing_msg);
+  fail_unless (s != NULL);
+
+  fail_unless (gst_structure_get (s, "frame", GST_TYPE_SAMPLE, &sample, NULL));
+  fail_unless (gst_sample_get_buffer (sample));
+  fail_unless (gst_sample_get_caps (sample));
+  gst_sample_unref (sample);
+
+  fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL),
+      GST_STATE_CHANGE_SUCCESS);
+
+  gst_object_unref (pipeline);
+  gst_message_unref (zxing_msg);
+}
+
+GST_END_TEST;
+
+static Suite *
+zxing_suite (void)
+{
+  Suite *s = suite_create ("zxing");
+  TCase *tc_chain = tcase_create ("general");
+
+  suite_add_tcase (s, tc_chain);
+
+  if (!gst_registry_check_feature_version (gst_registry_get (), "pngdec", 0, 10,
+          25)) {
+    GST_INFO ("Skipping test, pngdec either not available or too old");
+  } else {
+    tcase_add_test (tc_chain, test_still_image);
+    tcase_add_test (tc_chain, test_still_image_with_sample);
+  }
+
+  return s;
+}
+
+GST_CHECK_MAIN (zxing);
index 7822892..9abcd8f 100644 (file)
@@ -109,6 +109,7 @@ if host_machine.system() != 'windows'
     [['elements/webrtcbin.c'], not libnice_dep.found(), [gstwebrtc_dep]],
     [['elements/x265enc.c'], not x265_dep.found(), [x265_dep]],
     [['elements/zbar.c'], not zbar_dep.found(), [zbar_dep]],
+    [['elements/zxing.c'], not zxing_dep.found(), [zxing_dep]],
   ]
 endif