<div dir="ltr"><div>Hi Laurent,</div><div>Will address these changes in addition to one Umang suggested within this week itself.</div><div><br></div><div>Regards,</div><div><i><b>Vedant Paranjape</b></i><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Aug 18, 2021 at 12:04 AM Laurent Pinchart <<a href="mailto:laurent.pinchart@ideasonboard.com">laurent.pinchart@ideasonboard.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hi Nicolas,<br>
<br>
On Tue, Aug 17, 2021 at 12:24:05PM -0400, Nicolas Dufresne wrote:<br>
> Le samedi 14 août 2021 à 02:46 +0300, Laurent Pinchart a écrit :<br>
> > From: Vedant Paranjape <<a href="mailto:vedantparanjape160201@gmail.com" target="_blank">vedantparanjape160201@gmail.com</a>><br>
> > <br>
> > This patch adds a test to test if single stream using libcamera's<br>
> > gstreamer element works.<br>
> > <br>
> > We need to work around two distinct issues with ASan when enabled in the<br>
> > build:<br>
> > <br>
> > - glib has a known leak at initialization time. This is covered by the<br>
> > suppression file shipped with glib, but it's not clear how to use it<br>
> > automatically. For now, disable leak detection to avoid test failures.<br>
> <br>
> Are Valgrind suppression usable for Asan ? Glib installs it into:<br>
> <br>
> /usr/share/glib-2.0/valgrind/glib.supp<br>
> <br>
> For GStreamer suppressions, we don't install them (yet, under discussion). They<br>
> are located in each repos. I would enable GStreamer leak tracer though, it does<br>
> not have such a high run-time overhead, and will detect any GObject or<br>
> GstMiniObject leak, as long as the test calls gst_deinit() at the end (all tests<br>
> should).<br>
<br>
Yes, I think they are usable. I was however unsure whether the path to<br>
the supression file was standard, especially considering embedded<br>
systems. I thus went for a workaround. It would be nice if there was a<br>
standard mechanism to pick suppressions automatically.<br>
<br>
> > - GStreamer spawns a child process to scan plugins. If GStreamer is<br>
> > compiled without ASan (which is likely) but libcamera is, dlopen()ing<br>
> > the libcamera plugin will cause an ASan link order verification<br>
> > failure. Disable the verification child processes to work around the<br>
> > problem. This requires gcc 8 or newer.<br>
> <br>
> Have you considered simply disabling forks ? See gst_registry_fork_set_enabled()<br>
<br>
No, as I had no idea that existed :-)<br>
<br>
> > Signed-off-by: Vedant Paranjape <<a href="mailto:vedantparanjape160201@gmail.com" target="_blank">vedantparanjape160201@gmail.com</a>><br>
> > Reviewed-by: Paul Elder <<a href="mailto:paul.elder@ideasonboard.com" target="_blank">paul.elder@ideasonboard.com</a>><br>
> > Reviewed-by: Kieran Bingham <<a href="mailto:kieran.bingham@ideasonboard.com" target="_blank">kieran.bingham@ideasonboard.com</a>><br>
> > Tested-by: Kieran Bingham <kieran.bingham@@<a href="http://ideasonboard.com" rel="noreferrer" target="_blank">ideasonboard.com</a>><br>
> > Reviewed-by: Laurent Pinchart <<a href="mailto:laurent.pinchart@ideasonboard.com" target="_blank">laurent.pinchart@ideasonboard.com</a>><br>
> > Signed-off-by: Laurent Pinchart <<a href="mailto:laurent.pinchart@ideasonboard.com" target="_blank">laurent.pinchart@ideasonboard.com</a>><br>
> > ---<br>
> > This version incorporates changes coming from my review of v10, and<br>
> > fixes for ASan issues. ASan is a bit of a pain with GStreamer, for two<br>
> > independent reasons as explained in the commit message. I'd like to find<br>
> > a way to use leak suppression files to handle the glib initialization<br>
> > leak, but that's a big rabbit hole. The workaround for the second issue<br>
> > is acceptable in my opinion.<br>
> > <br>
> > The biggest trouble with these workarounds is that they don't work with<br>
> > gcc version older than 8. As the stable version of the most common Linux<br>
> > distributions ship gcc 8 or newer, skipping this test for gcc 7 (which<br>
> > is the oldest version that libcamera supports) is also acceptable in my<br>
> > opinion.<br>
> > <br>
> > Changes since v10:<br>
> > <br>
> > - Disable ASan leak detection<br>
> > - Disable ASan link order verification for child processes<br>
> > - Include source_path.h<br>
> > - Remove unneeded explicit std::string construction<br>
> > - Declare variables at usage site<br>
> > - Make constants constexpr<br>
> > - Add a variable for the message type<br>
> > - Blank space fixes<br>
> > ---<br>
> > .../gstreamer_single_stream_test.cpp | 188 ++++++++++++++++++<br>
> > test/gstreamer/meson.build | 19 ++<br>
> > test/meson.build | 1 +<br>
> > 3 files changed, 208 insertions(+)<br>
> > create mode 100644 test/gstreamer/gstreamer_single_stream_test.cpp<br>
> > create mode 100644 test/gstreamer/meson.build<br>
> > <br>
> > diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp<br>
> > new file mode 100644<br>
> > index 000000000000..e26673b3471a<br>
> > --- /dev/null<br>
> > +++ b/test/gstreamer/gstreamer_single_stream_test.cpp<br>
> > @@ -0,0 +1,188 @@<br>
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */<br>
> > +/*<br>
> > + * Copyright (C) 2021, Vedant Paranjape<br>
> > + *<br>
> > + * ipa_interface_test.cpp - Test the IPA interface<br>
> > + */<br>
> > +<br>
> > +#include <iostream><br>
> > +#include <unistd.h><br>
> > +<br>
> > +#include <libcamera/base/utils.h><br>
> > +<br>
> > +#include "libcamera/internal/source_paths.h"<br>
> > +<br>
> > +#include <gst/gst.h><br>
> > +<br>
> > +#include "test.h"<br>
> > +<br>
> > +using namespace std;<br>
> > +<br>
> > +extern "C" {<br>
> > +const char *__asan_default_options()<br>
> > +{<br>
> > + /*<br>
> > + * Disable leak detection due to a known global variable initialization<br>
> > + * leak in glib's g_quark_init(). This should ideally be handled by<br>
> > + * using a suppression file instead of disabling leak detection.<br>
> > + */<br>
> > + return "detect_leaks=false";<br>
> <br>
> Have you consider GST_TRACERS=leaks ? It will heck for refcounted object leaks<br>
> on gstreamer/glib side, with very low overhead, and will abort on gst_deinit()<br>
> if it found some leaks.<br>
<br>
Again, no idea it existed :-) It could be enabled in init() below.<br>
<br>
> > +}<br>
> > +}<br>
> > +<br>
> > +class GstreamerSingleStreamTest : public Test<br>
> > +{<br>
> > +protected:<br>
> > + int init() override<br>
> > + {<br>
> > + /*<br>
> > + * GStreamer spawns a process to run the gst-plugin-scanner<br>
> > + * helper. If libcamera is compiled with ASan enabled, and as<br>
> > + * GStreamer is most likely not, this will cause the ASan link<br>
> > + * order check to fail when gst-plugin-scanner dlopen()s the<br>
> > + * plugin as many libraries will have already been loaded by<br>
> > + * then. Work around this issue by disabling the link order<br>
> > + * check. This will only affect child processes, as ASan is<br>
> > + * already loaded for this process by the time this code is<br>
> > + * executed, and should thus hopefully be safe.<br>
> > + *<br>
> > + * This option is not available in gcc older than 8, the only<br>
> > + * option in that case is to skip the test.<br>
> > + */<br>
> > +#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && __GNUC__ < 8<br>
> > + return TestSkip;<br>
> > +#endif<br>
> > + setenv("ASAN_OPTIONS", "verify_asan_link_order=0", 1);<br>
> <br>
> I think gst_registry_fork_set_enabled(FALSE) would be much cleaner, what do you<br>
> think ?<br>
<br>
If it does the job and has no drawback, that's fine with me.<br>
<br>
> > +<br>
> > + /* Initialize GStreamer */<br>
> > + GError *errInit = nullptr;<br>
> > + if (!gst_init_check(nullptr, nullptr, &errInit)) {<br>
> > + g_printerr("Could not initialize GStreamer: %s\n",<br>
> > + errInit ? errInit->message : "unknown error");<br>
> > + if (errInit)<br>
> > + g_error_free(errInit);<br>
> > +<br>
> > + return TestFail;<br>
> > + }<br>
> > +<br>
> > + /*<br>
> > + * Remove the system libcamera plugin, if any, and add the<br>
> > + * plugin from the build directory.<br>
> > + */<br>
> > + GstRegistry *registry = gst_registry_get();<br>
> > + GstPlugin *plugin = gst_registry_lookup(registry, "libgstlibcamera.so");<br>
> > + if (plugin) {<br>
> > + gst_registry_remove_plugin(registry, plugin);<br>
> > + gst_object_unref(plugin);<br>
> > + }<br>
> > +<br>
> > + std::string path = libcamera::utils::libcameraBuildPath()<br>
> > + + "src/gstreamer";<br>
> > + if (!gst_registry_scan_path(registry, path.c_str())) {<br>
> > + g_printerr("Failed to add plugin to registry\n");<br>
> > + gst_deinit();<br>
> > + return TestFail;<br>
> > + }<br>
> > +<br>
> > + /* Create the elements */<br>
> > + libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera");<br>
> > + convert0_ = gst_element_factory_make("videoconvert", "convert0");<br>
> > + sink0_ = gst_element_factory_make("fakesink", "sink0");<br>
> <br>
> Please use fakevideosink instead.<br>
<br>
That's what Vedant was doing, and the element wasn't present on Kieran's<br>
system. For my own culture, what's the advantage of using fakevideosink<br>
over fakesink ?<br>
<br>
> > +<br>
> > + /* Create the empty pipeline_ */<br>
> > + pipeline_ = gst_pipeline_new("test-pipeline");<br>
> > +<br>
> > + if (!pipeline_ || !convert0_ || !sink0_ || !libcameraSrc_) {<br>
> <br>
> pipeline_ cannot be NULL, glib will abort if malloc failed.<br>
<br>
Good point.<br>
<br>
> > + g_printerr("Not all elements could be created. %p.%p.%p.%p\n",<br>
> > + pipeline_, convert0_, sink0_, libcameraSrc_);<br>
> > + if (pipeline_)<br>
> > + gst_object_unref(pipeline_);<br>
> > + if (convert0_)<br>
> > + gst_object_unref(convert0_);<br>
> > + if (sink0_)<br>
> > + gst_object_unref(sink0_);<br>
> > + if (libcameraSrc_)<br>
> > + gst_object_unref(libcameraSrc_);<br>
> <br>
> Use local g_autoptr() and g_steal_pointer() on success perhaps ? This will<br>
> reduce a lot the about of error prone code in failure case. Note that in other<br>
> software I've seen, an abort is used instead, as clean exit is just coding<br>
> overhead for a test.<br>
> <br>
> An alternative, add them immediately to the pipeline, they are not owned by your<br>
> class anyway.<br>
> <br>
> > + gst_deinit();<br>
> > +<br>
> > + return TestFail;<br>
> > + }<br>
> > +<br>
> > + return TestPass;<br>
> > + }<br>
> > +<br>
> > + void cleanup() override<br>
> > + {<br>
> > + gst_object_unref(pipeline_);<br>
> > + gst_deinit();<br>
> > + }<br>
> > +<br>
> > + int run() override<br>
> > + {<br>
> > + GstStateChangeReturn ret;<br>
> > +<br>
> > + /* Build the pipeline */<br>
> > + gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, convert0_, sink0_, NULL);<br>
> <br>
> This can fail (e.g. duplicate name).<br>
> <br>
> > + if (gst_element_link_many(libcameraSrc_, convert0_, sink0_, NULL) != TRUE) {<br>
> > + g_printerr("Elements could not be linked.\n");<br>
> > + return TestFail;<br>
> > + }<br>
> > +<br>
> > + /* Start playing */<br>
> > + ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);<br>
> > + if (ret == GST_STATE_CHANGE_FAILURE) {<br>
> > + g_printerr("Unable to set the pipeline to the playing state.\n");<br>
> > + return TestFail;<br>
> > + }<br>
> > +<br>
> > + /* Wait until error or EOS or timeout after 2 seconds */<br>
> > + constexpr GstMessageType msgType =<br>
> > + static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS);<br>
> > + constexpr GstClockTime timeout = 2000000000;<br>
<br>
> Perhaps 2 * GST_SECOND.<br>
<br>
Much better.<br>
<br>
> > +<br>
> > + g_autoptr(GstBus) bus = gst_element_get_bus(pipeline_);<br>
> > + g_autoptr(GstMessage) msg = gst_bus_timed_pop_filtered(bus, timeout, msgType);<br>
> > +<br>
> > + gst_element_set_state(pipeline_, GST_STATE_NULL);<br>
> > +<br>
> > + /* Parse error message */<br>
> > + if (msg == NULL)<br>
> > + return TestPass;<br>
> <br>
> I would like some minimal validation. I would expect that after 2s some frames<br>
> got "rendered" properly. You can read the GstStructure property "stats" from<br>
> fakevideosink / fakesink, and read the "rendered" field. Make sure this not zero<br>
> perhaps ?<br>
<br>
Very good idea.<br>
<br>
Vedant, I think there's work for you :-)<br>
<br>
> > +<br>
> > + switch (GST_MESSAGE_TYPE(msg)) {<br>
> > + case GST_MESSAGE_ERROR:<br>
> > + gstreamer_print_error(msg);<br>
> > + break;<br>
> > + case GST_MESSAGE_EOS:<br>
> > + g_print("End-Of-Stream reached.\n");<br>
> > + break;<br>
> > + default:<br>
> > + g_printerr("Unexpected message received.\n");<br>
> > + break;<br>
> > + }<br>
> > +<br>
> > + return TestFail;<br>
> > + }<br>
> > +<br>
> > +private:<br>
> > + void gstreamer_print_error(GstMessage *msg)<br>
> > + {<br>
> > + GError *err;<br>
> > + gchar *debug_info;<br>
> > +<br>
> > + gst_message_parse_error(msg, &err, &debug_info);<br>
> > + g_printerr("Error received from element %s: %s\n",<br>
> > + GST_OBJECT_NAME(msg->src), err->message);<br>
> > + g_printerr("Debugging information: %s\n",<br>
> > + debug_info ? debug_info : "none");<br>
> > + g_clear_error(&err);<br>
> > + g_free(debug_info);<br>
> > + }<br>
> > +<br>
> > + GstElement *pipeline_;<br>
> > + GstElement *libcameraSrc_;<br>
> > + GstElement *convert0_;<br>
> > + GstElement *sink0_;<br>
> > +};<br>
> > +<br>
> > +TEST_REGISTER(GstreamerSingleStreamTest)<br>
> > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build<br>
> > new file mode 100644<br>
> > index 000000000000..b99aa0da0ba3<br>
> > --- /dev/null<br>
> > +++ b/test/gstreamer/meson.build<br>
> > @@ -0,0 +1,19 @@<br>
> > +# SPDX-License-Identifier: CC0-1.0<br>
> > +<br>
> > +if not gst_enabled<br>
> > + subdir_done()<br>
> > +endif<br>
> > +<br>
> > +gstreamer_tests = [<br>
> > + ['single_stream_test', 'gstreamer_single_stream_test.cpp'],<br>
> > +]<br>
> > +gstreamer_dep = dependency('gstreamer-1.0', required: true)<br>
> > +<br>
> > +foreach t : gstreamer_tests<br>
> > + exe = executable(t[0], t[1],<br>
> > + dependencies : [libcamera_private, gstreamer_dep],<br>
> > + link_with : test_libraries,<br>
> > + include_directories : test_includes_internal)<br>
> > +<br>
> > + test(t[0], exe, suite : 'gstreamer', is_parallel : false)<br>
> > +endforeach<br>
> > diff --git a/test/meson.build b/test/meson.build<br>
> > index 3bceb5df586f..d0466f17d7b6 100644<br>
> > --- a/test/meson.build<br>
> > +++ b/test/meson.build<br>
> > @@ -11,6 +11,7 @@ subdir('libtest')<br>
> > <br>
> > subdir('camera')<br>
> > subdir('controls')<br>
> > +subdir('gstreamer')<br>
> > subdir('ipa')<br>
> > subdir('ipc')<br>
> > subdir('log')<br>
<br>
-- <br>
Regards,<br>
<br>
Laurent Pinchart<br>
</blockquote></div>