diff options
Diffstat (limited to 'test/gstreamer')
-rw-r--r-- | test/gstreamer/gstreamer_device_provider_test.cpp | 70 | ||||
-rw-r--r-- | test/gstreamer/gstreamer_memory_lifetime_test.cpp | 90 | ||||
-rw-r--r-- | test/gstreamer/gstreamer_multi_stream_test.cpp | 111 | ||||
-rw-r--r-- | test/gstreamer/gstreamer_single_stream_test.cpp | 69 | ||||
-rw-r--r-- | test/gstreamer/gstreamer_test.cpp | 174 | ||||
-rw-r--r-- | test/gstreamer/gstreamer_test.h | 34 | ||||
-rw-r--r-- | test/gstreamer/meson.build | 31 |
7 files changed, 579 insertions, 0 deletions
diff --git a/test/gstreamer/gstreamer_device_provider_test.cpp b/test/gstreamer/gstreamer_device_provider_test.cpp new file mode 100644 index 00000000..521c60b8 --- /dev/null +++ b/test/gstreamer/gstreamer_device_provider_test.cpp @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Umang Jain <umang.jain@ideasonboard.com> + * + * GStreamer single stream capture test + */ + +#include <vector> + +#include <libcamera/libcamera.h> +#include <gst/gst.h> + +#include "gstreamer_test.h" +#include "test.h" + +using namespace std; + +class GstreamerDeviceProviderTest : public GstreamerTest, public Test +{ +public: + GstreamerDeviceProviderTest() + : GstreamerTest() + { + } + +protected: + int init() override + { + if (status_ != TestPass) + return status_; + + return TestPass; + } + + int run() override + { + g_autoptr(GstDeviceProvider) provider = NULL; + GList *devices, *l; + std::vector<std::string> cameraNames; + std::unique_ptr<libcamera::CameraManager> cm; + + cm = std::make_unique<libcamera::CameraManager>(); + cm->start(); + for (auto &camera : cm->cameras()) + cameraNames.push_back(camera->id()); + cm->stop(); + cm.reset(); + + provider = gst_device_provider_factory_get_by_name("libcameraprovider"); + devices = gst_device_provider_get_devices(provider); + + for (l = devices; l != NULL; l = g_list_next(l)) { + GstDevice *device = GST_DEVICE(l->data); + g_autofree gchar *gst_name; + + g_autoptr(GstElement) element = gst_device_create_element(device, NULL); + g_object_get(element, "camera-name", &gst_name, NULL); + + if (std::find(cameraNames.begin(), cameraNames.end(), gst_name) == + cameraNames.end()) + return TestFail; + } + + g_list_free_full(devices, (GDestroyNotify)gst_object_unref); + + return TestPass; + } +}; + +TEST_REGISTER(GstreamerDeviceProviderTest) diff --git a/test/gstreamer/gstreamer_memory_lifetime_test.cpp b/test/gstreamer/gstreamer_memory_lifetime_test.cpp new file mode 100644 index 00000000..1738cf56 --- /dev/null +++ b/test/gstreamer/gstreamer_memory_lifetime_test.cpp @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Nicolas Dufresne + * + * gstreamer_memory_lifetime_test.cpp - GStreamer memory lifetime test + */ + +#include <iostream> +#include <unistd.h> + +#include <gst/app/app.h> +#include <gst/gst.h> + +#include "gstreamer_test.h" +#include "test.h" + +using namespace std; + +class GstreamerMemoryLifetimeTest : public GstreamerTest, public Test +{ +public: + GstreamerMemoryLifetimeTest() + : GstreamerTest() + { + } + +protected: + int init() override + { + if (status_ != TestPass) + return status_; + + appsink_ = gst_element_factory_make("appsink", nullptr); + if (!appsink_) { + g_printerr("Your installation is missing 'appsink'\n"); + return TestFail; + } + g_object_ref_sink(appsink_); + + return createPipeline(); + } + + int run() override + { + /* Build the pipeline */ + gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, appsink_, nullptr); + if (gst_element_link(libcameraSrc_, appsink_) != TRUE) { + g_printerr("Elements could not be linked.\n"); + return TestFail; + } + + if (startPipeline() != TestPass) + return TestFail; + + sample_ = gst_app_sink_try_pull_sample(GST_APP_SINK(appsink_), GST_SECOND * 5); + if (!sample_) { + /* Failed to obtain a sample. Abort the test */ + gst_element_set_state(pipeline_, GST_STATE_NULL); + return TestFail; + } + + /* + * Keep the sample referenced and set the pipeline state to + * NULL. This causes the libcamerasrc element to synchronously + * release resources it holds. The sample will be released + * later in cleanup(). + * + * The test case verifies that libcamerasrc keeps alive long + * enough all the resources that are needed until memory + * allocated for frames gets freed. We return TestPass at this + * stage, and any use-after-free will be caught by the test + * crashing in cleanup(). + */ + gst_element_set_state(pipeline_, GST_STATE_NULL); + + return TestPass; + } + + void cleanup() override + { + g_clear_pointer(&sample_, gst_sample_unref); + g_clear_object(&appsink_); + } + +private: + GstElement *appsink_; + GstSample *sample_; +}; + +TEST_REGISTER(GstreamerMemoryLifetimeTest) diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp b/test/gstreamer/gstreamer_multi_stream_test.cpp new file mode 100644 index 00000000..263d1e86 --- /dev/null +++ b/test/gstreamer/gstreamer_multi_stream_test.cpp @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021, Vedant Paranjape + * + * GStreamer multi stream capture test + */ + +#include <iostream> +#include <unistd.h> + +#include <libcamera/libcamera.h> + +#include <gst/gst.h> + +#include "gstreamer_test.h" +#include "test.h" + +#if !GST_CHECK_VERSION(1, 19, 1) +static inline GstPad *gst_element_request_pad_simple(GstElement *element, + const gchar *name) +{ + return gst_element_get_request_pad(element, name); +} +#endif + +using namespace std; + +class GstreamerMultiStreamTest : public GstreamerTest, public Test +{ +public: + GstreamerMultiStreamTest() + : GstreamerTest(2) + { + } + +protected: + int init() override + { + if (status_ != TestPass) + return status_; + + const gchar *streamDescription = "queue ! fakesink"; + g_autoptr(GError) error = NULL; + + stream0_ = gst_parse_bin_from_description_full(streamDescription, TRUE, + NULL, + GST_PARSE_FLAG_FATAL_ERRORS, + &error); + if (!stream0_) { + g_printerr("Stream0 could not be created (%s)\n", error->message); + return TestFail; + } + g_object_ref_sink(stream0_); + + stream1_ = gst_parse_bin_from_description_full(streamDescription, TRUE, + NULL, + GST_PARSE_FLAG_FATAL_ERRORS, + &error); + if (!stream1_) { + g_printerr("Stream1 could not be created (%s)\n", error->message); + return TestFail; + } + g_object_ref_sink(stream1_); + + if (createPipeline() != TestPass) + return TestFail; + + return TestPass; + } + + int run() override + { + /* Build the pipeline */ + gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, + stream0_, stream1_, NULL); + + g_autoptr(GstPad) src_pad = gst_element_get_static_pad(libcameraSrc_, "src"); + g_autoptr(GstPad) request_pad = gst_element_request_pad_simple(libcameraSrc_, "src_%u"); + + { + g_autoptr(GstPad) queue0_sink_pad = gst_element_get_static_pad(stream0_, "sink"); + g_autoptr(GstPad) queue1_sink_pad = gst_element_get_static_pad(stream1_, "sink"); + + if (gst_pad_link(src_pad, queue0_sink_pad) != GST_PAD_LINK_OK || + gst_pad_link(request_pad, queue1_sink_pad) != GST_PAD_LINK_OK) { + g_printerr("Pads could not be linked.\n"); + return TestFail; + } + } + + if (startPipeline() != TestPass) + return TestFail; + + if (processEvent() != TestPass) + return TestFail; + + return TestPass; + } + + void cleanup() override + { + g_clear_object(&stream0_); + g_clear_object(&stream1_); + } + +private: + GstElement *stream0_; + GstElement *stream1_; +}; + +TEST_REGISTER(GstreamerMultiStreamTest) diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp new file mode 100644 index 00000000..3ef2d323 --- /dev/null +++ b/test/gstreamer/gstreamer_single_stream_test.cpp @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021, Vedant Paranjape + * + * GStreamer single stream capture test + */ + +#include <iostream> +#include <unistd.h> + +#include <gst/gst.h> + +#include "gstreamer_test.h" +#include "test.h" + +using namespace std; + +class GstreamerSingleStreamTest : public GstreamerTest, public Test +{ +public: + GstreamerSingleStreamTest() + : GstreamerTest() + { + } + +protected: + int init() override + { + if (status_ != TestPass) + return status_; + + fakesink_ = gst_element_factory_make("fakesink", nullptr); + if (!fakesink_) { + g_printerr("Your installation is missing 'fakesink'\n"); + return TestFail; + } + g_object_ref_sink(fakesink_); + + return createPipeline(); + } + + int run() override + { + /* Build the pipeline */ + gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, fakesink_, nullptr); + if (!gst_element_link(libcameraSrc_, fakesink_)) { + g_printerr("Elements could not be linked.\n"); + return TestFail; + } + + if (startPipeline() != TestPass) + return TestFail; + + if (processEvent() != TestPass) + return TestFail; + + return TestPass; + } + + void cleanup() override + { + g_clear_object(&fakesink_); + } + +private: + GstElement *fakesink_; +}; + +TEST_REGISTER(GstreamerSingleStreamTest) diff --git a/test/gstreamer/gstreamer_test.cpp b/test/gstreamer/gstreamer_test.cpp new file mode 100644 index 00000000..a15fef0e --- /dev/null +++ b/test/gstreamer/gstreamer_test.cpp @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021, Vedant Paranjape + * + * libcamera Gstreamer element API tests + */ + +#include <libcamera/libcamera.h> + +#include <libcamera/base/utils.h> + +#if HAVE_ASAN +#include <sanitizer/asan_interface.h> +#endif + +#include "gstreamer_test.h" + +#include "test.h" + +using namespace std; + +#if HAVE_ASAN +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"; +} +} +#endif + +GstreamerTest::GstreamerTest(unsigned int numStreams) + : pipeline_(nullptr), libcameraSrc_(nullptr) +{ + /* + * GStreamer by default 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 causes 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. Fix this issue by disabling + * spawning of a child helper process when scanning the build directory + * for plugins. + */ + gst_registry_fork_set_enabled(false); + + /* Initialize GStreamer */ + g_autoptr(GError) errInit = NULL; + if (!gst_init_check(nullptr, nullptr, &errInit)) { + g_printerr("Could not initialize GStreamer: %s\n", + errInit ? errInit->message : "unknown error"); + + status_ = TestFail; + return; + } + + /* + * Atleast one camera should be available with numStreams streams, + * otherwise skip the test entirely. + */ + if (!checkMinCameraStreamsAndSetCameraName(numStreams)) { + status_ = TestSkip; + return; + } + + status_ = TestPass; +} + +bool GstreamerTest::checkMinCameraStreamsAndSetCameraName(unsigned int numStreams) +{ + libcamera::CameraManager cm; + bool cameraFound = false; + + cm.start(); + + for (auto &camera : cm.cameras()) { + if (camera->streams().size() < numStreams) + continue; + + cameraFound = true; + cameraName_ = camera->id(); + break; + } + + cm.stop(); + + return cameraFound; +} + +GstreamerTest::~GstreamerTest() +{ + g_clear_object(&pipeline_); + g_clear_object(&libcameraSrc_); + + gst_deinit(); +} + +int GstreamerTest::createPipeline() +{ + libcameraSrc_ = gst_element_factory_make("libcamerasrc", "libcamera"); + pipeline_ = gst_pipeline_new("test-pipeline"); + + if (!libcameraSrc_ || !pipeline_) { + g_printerr("Unable to create pipeline %p.%p\n", + libcameraSrc_, pipeline_); + + return TestFail; + } + + g_object_set(libcameraSrc_, "camera-name", cameraName_.c_str(), NULL); + g_object_ref_sink(libcameraSrc_); + + return TestPass; +} + +int GstreamerTest::startPipeline() +{ + GstStateChangeReturn ret; + + /* 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; + } + + return TestPass; +} + +int GstreamerTest::processEvent() +{ + /* Wait until error or EOS or timeout after 2 seconds */ + constexpr GstMessageType msgType = + static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS); + constexpr GstClockTime timeout = 2 * GST_SECOND; + + 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: + printError(msg); + break; + case GST_MESSAGE_EOS: + g_print("End-Of-Stream reached.\n"); + break; + default: + g_printerr("Unexpected message received.\n"); + break; + } + + return TestFail; +} + +void GstreamerTest::printError(GstMessage *msg) +{ + g_autoptr(GError) err = NULL; + g_autofree gchar *debug_info = NULL; + + 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"); +} diff --git a/test/gstreamer/gstreamer_test.h b/test/gstreamer/gstreamer_test.h new file mode 100644 index 00000000..abb37c1b --- /dev/null +++ b/test/gstreamer/gstreamer_test.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021, Vedant Paranjape + * + * GStreamer test base class + */ + +#pragma once + +#include <iostream> +#include <unistd.h> + +#include <gst/gst.h> + +class GstreamerTest +{ +public: + GstreamerTest(unsigned int numStreams = 1); + virtual ~GstreamerTest(); + +protected: + virtual int createPipeline(); + int startPipeline(); + int processEvent(); + void printError(GstMessage *msg); + + std::string cameraName_; + GstElement *pipeline_; + GstElement *libcameraSrc_; + int status_; + +private: + bool checkMinCameraStreamsAndSetCameraName(unsigned int numStreams); +}; diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build new file mode 100644 index 00000000..e066c582 --- /dev/null +++ b/test/gstreamer/meson.build @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: CC0-1.0 + +if not gst_enabled + subdir_done() +endif + +gstreamer_tests = [ + {'name': 'single_stream_test', 'sources': ['gstreamer_single_stream_test.cpp']}, + {'name': 'multi_stream_test', 'sources': ['gstreamer_multi_stream_test.cpp']}, + {'name': 'device_provider_test', 'sources': ['gstreamer_device_provider_test.cpp']}, + {'name': 'memory_lifetime_test', 'sources': ['gstreamer_memory_lifetime_test.cpp']}, +] +gstreamer_dep = dependency('gstreamer-1.0', required : true) +gstapp_dep = dependency('gstreamer-app-1.0', required : true) + +gstreamer_test_args = [] + +if asan_enabled + gstreamer_test_args += ['-D', 'HAVE_ASAN=1'] +endif + +foreach test : gstreamer_tests + exe = executable(test['name'], test['sources'], 'gstreamer_test.cpp', + cpp_args : gstreamer_test_args, + dependencies : [libcamera_private, gstreamer_dep, gstapp_dep], + link_with : test_libraries, + include_directories : test_includes_internal) + + test(test['name'], exe, suite : 'gstreamer', is_parallel : false, + env : gst_env, should_fail : test.get('should_fail', false)) +endforeach |