From patchwork Fri Aug 13 23:46:52 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13352 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 97961C3240 for ; Fri, 13 Aug 2021 23:47:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id ECA276888F; Sat, 14 Aug 2021 01:47:04 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ALN2DAH6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5EC6868823 for ; Sat, 14 Aug 2021 01:47:03 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C5D6C3F0; Sat, 14 Aug 2021 01:47:02 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1628898423; bh=xx81ZVx4g5H/PDx45IynMlQsm6mNuYVPmKzkTSpmQZ0=; h=From:To:Cc:Subject:Date:From; b=ALN2DAH6x4gvoG5kxaU0u34VSOy5WtbJL/62qeyLPG/JytiL0fSVVrvLxQ+YBgZaS 0gsTcUkJUhPsdjgIsSL/L/OydPuMiQKOyHR6QUihAV33bku2PsDwemtd1fXmfZ1U2R FneqDvH5ArT5FVpzhfzhCFYYsAZpD+fuQ6YqAN0E= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Sat, 14 Aug 2021 02:46:52 +0300 Message-Id: <20210813234652.32200-1-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v11] test: gstreamer: Add test for gstreamer single stream X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Vedant Paranjape Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Vedant Paranjape This patch adds a test to test if single stream using libcamera's gstreamer element works. We need to work around two distinct issues with ASan when enabled in the build: - glib has a known leak at initialization time. This is covered by the suppression file shipped with glib, but it's not clear how to use it automatically. For now, disable leak detection to avoid test failures. - GStreamer spawns a child process to scan plugins. If GStreamer is compiled without ASan (which is likely) but libcamera is, dlopen()ing the libcamera plugin will cause an ASan link order verification failure. Disable the verification child processes to work around the problem. This requires gcc 8 or newer. Signed-off-by: Vedant Paranjape Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham Tested-by: Kieran Bingham Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart Reviewed-by: Umang Jain --- This version incorporates changes coming from my review of v10, and fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two independent reasons as explained in the commit message. I'd like to find a way to use leak suppression files to handle the glib initialization leak, but that's a big rabbit hole. The workaround for the second issue is acceptable in my opinion. The biggest trouble with these workarounds is that they don't work with gcc version older than 8. As the stable version of the most common Linux distributions ship gcc 8 or newer, skipping this test for gcc 7 (which is the oldest version that libcamera supports) is also acceptable in my opinion. Changes since v10: - Disable ASan leak detection - Disable ASan link order verification for child processes - Include source_path.h - Remove unneeded explicit std::string construction - Declare variables at usage site - Make constants constexpr - Add a variable for the message type - Blank space fixes --- .../gstreamer_single_stream_test.cpp | 188 ++++++++++++++++++ test/gstreamer/meson.build | 19 ++ test/meson.build | 1 + 3 files changed, 208 insertions(+) create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp create mode 100644 test/gstreamer/meson.build diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp new file mode 100644 index 000000000000..e26673b3471a --- /dev/null +++ b/test/gstreamer/gstreamer_single_stream_test.cpp @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021, Vedant Paranjape + * + * ipa_interface_test.cpp - Test the IPA interface + */ + +#include +#include + +#include + +#include "libcamera/internal/source_paths.h" + +#include + +#include "test.h" + +using namespace std; + +extern "C" { +const char *__asan_default_options() +{ + /* + * Disable leak detection due to a known global variable initialization + * leak in glib's g_quark_init(). This should ideally be handled by + * using a suppression file instead of disabling leak detection. + */ + return "detect_leaks=false"; +} +} + +class GstreamerSingleStreamTest : public Test +{ +protected: + int init() override + { + /* + * GStreamer spawns a process to run the gst-plugin-scanner + * helper. If libcamera is compiled with ASan enabled, and as + * GStreamer is most likely not, this will cause the ASan link + * order check to fail when gst-plugin-scanner dlopen()s the + * plugin as many libraries will have already been loaded by + * then. Work around this issue by disabling the link order + * check. This will only affect child processes, as ASan is + * already loaded for this process by the time this code is + * executed, and should thus hopefully be safe. + * + * This option is not available in gcc older than 8, the only + * option in that case is to skip the test. + */ +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8 + return TestSkip; +#endif + setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1); + + /* Initialize GStreamer */ + GError *errInit = nullptr; + if (!gst_init_check(nullptr, nullptr, &errInit)) { + g_printerr("Could not initialize GStreamer: %s\n", + errInit ? errInit->message : "unknown error"); + if (errInit) + g_error_free(errInit); + + return TestFail; + } + + /* + * Remove the system libcamera plugin, if any, and add the + * plugin from the build directory. + */ + GstRegistry *registry = gst_registry_get(); + GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so"); + if (plugin) { + gst_registry_remove_plugin(registry, plugin); + gst_object_unref(plugin); + } + + std::string path = libcamera::utils::libcameraBuildPath() + + "src/gstreamer"; + if (!gst_registry_scan_path(registry, path.c_str())) { + g_printerr("Failed to add plugin to registry\n"); + gst_deinit(); + return TestFail; + } + + /* Create the elements */ + libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera"); + convert0_ = gst_element_factory_make("videoconvert", "convert0"); + sink0_ = gst_element_factory_make("fakesink", "sink0"); + + /* Create the empty pipeline_ */ + pipeline_ = gst_pipeline_new("test-pipeline"); + + if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) { + g_printerr("Not all elements could be created. %p.%p.%p.%p\n", + pipeline_, convert0_, sink0_, libcameraSrc_); + if (pipeline_) + gst_object_unref(pipeline_); + if (convert0_) + gst_object_unref(convert0_); + if (sink0_) + gst_object_unref(sink0_); + if (libcameraSrc_) + gst_object_unref(libcameraSrc_); + gst_deinit(); + + return TestFail; + } + + return TestPass; + } + + void cleanup() override + { + gst_object_unref(pipeline_); + gst_deinit(); + } + + int run() override + { + GstStateChangeReturn ret; + + /* Build the pipeline */ + gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL); + if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) { + g_printerr("Elements could not be linked.\n"); + return TestFail; + } + + /* Start playing */ + ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + g_printerr("Unable to set the pipeline to the playing state.\n"); + return TestFail; + } + + /* Wait until error or EOS or timeout after 2 seconds */ + constexpr GstMessageType msgType = + static_cast(GST_MESSAGE_ERROR | GST_MESSAGE_EOS); + constexpr GstClockTime timeout = 2000000000; + + g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_); + g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType); + + gst_element_set_state(pipeline_, GST_STATE_NULL); + + /* Parse error message */ + if (msg == NULL) + return TestPass; + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ERROR: + gstreamer_print_error(msg); + break; + case GST_MESSAGE_EOS: + g_print("End-Of-Stream reached.\n"); + break; + default: + g_printerr("Unexpected message received.\n"); + break; + } + + return TestFail; + } + +private: + void gstreamer_print_error(GstMessage *msg) + { + GError *err; + gchar *debug_info; + + gst_message_parse_error(msg, &err, &debug_info); + g_printerr("Error received from element %s: %s\n", + GST_OBJECT_NAME(msg->src), err->message); + g_printerr("Debugging information: %s\n", + debug_info ? debug_info : "none"); + g_clear_error(&err); + g_free(debug_info); + } + + GstElement *pipeline_; + GstElement *libcameraSrc_; + GstElement *convert0_; + GstElement *sink0_; +}; + +TEST_REGISTER(GstreamerSingleStreamTest) diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build new file mode 100644 index 000000000000..b99aa0da0ba3 --- /dev/null +++ b/test/gstreamer/meson.build @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: CC0-1.0 + +if not gst_enabled + subdir_done() +endif + +gstreamer_tests = [ + ['single_stream_test', 'gstreamer_single_stream_test.cpp'], +] +gstreamer_dep = dependency('gstreamer-1.0', required: true) + +foreach t : gstreamer_tests + exe = executable(t[0], t[1], + dependencies : [libcamera_private, gstreamer_dep], + link_with : test_libraries, + include_directories : test_includes_internal) + + test(t[0], exe, suite : 'gstreamer', is_parallel : false) +endforeach diff --git a/test/meson.build b/test/meson.build index 3bceb5df586f..d0466f17d7b6 100644 --- a/test/meson.build +++ b/test/meson.build @@ -11,6 +11,7 @@ subdir('libtest') subdir('camera') subdir('controls') +subdir('gstreamer') subdir('ipa') subdir('ipc') subdir('log')