[libcamera-devel,v3] test: gstreamer: Add a test for gstreamer multi stream
diff mbox series

Message ID 20210914063404.53621-1-vedantparanjape160201@gmail.com
State Accepted
Headers show
Series
  • [libcamera-devel,v3] test: gstreamer: Add a test for gstreamer multi stream
Related show

Commit Message

Vedant Paranjape Sept. 14, 2021, 6:34 a.m. UTC
This patch adds a test to test if multi stream using libcamera's
gstreamer element works.

Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Tested-by: Paul Elder <paul.elder@ideasonboard.com>
---
 .../gstreamer/gstreamer_multi_stream_test.cpp | 138 ++++++++++++++++++
 test/gstreamer/meson.build                    |   1 +
 2 files changed, 139 insertions(+)
 create mode 100644 test/gstreamer/gstreamer_multi_stream_test.cpp

Comments

Umang Jain Sept. 14, 2021, 7:23 a.m. UTC | #1
Hi Vedant,

On 9/14/21 12:04 PM, Vedant Paranjape wrote:
> This patch adds a test to test if multi stream using libcamera's
> gstreamer element works.


Can you please document how and when this test is run?

Currently, my system (with one UVC and vimc) has been able to run 
single_stream_test but multi_stream_test is run:


11/65 libcamera:gstreamer / single_stream_test OK             2.53s
12/65 libcamera:gstreamer / multi_stream_test SKIP           0.07s

What's your test setup where this tests passes. I assume I have to 
satisfy the following condition from the test, in order to run:

                 if (camera->streams().size() > 1) {

                         cameraFound = true;

                         ....

                 }

But I don't know how, looking at the patch. So can you explain (not just 
here but in the patch / commit message as well) ? It shall help others 
as well.

>
> Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> Tested-by: Paul Elder <paul.elder@ideasonboard.com>
> ---
>   .../gstreamer/gstreamer_multi_stream_test.cpp | 138 ++++++++++++++++++
>   test/gstreamer/meson.build                    |   1 +
>   2 files changed, 139 insertions(+)
>   create mode 100644 test/gstreamer/gstreamer_multi_stream_test.cpp


Right off the bat, there is a checkstyle failure: Can you look into it, 
please?

[uajain@fedora libcamera]$ ./utils/checkstyle.py HEAD
-----------------------------------------------------------------------------------------------
9d0efed89b03d57790db4eebefa909bf169ce789 test: gstreamer: Add a test for 
gstreamer multi stream
-----------------------------------------------------------------------------------------------
--- test/gstreamer/gstreamer_multi_stream_test.cpp
+++ test/gstreamer/gstreamer_multi_stream_test.cpp
@@ -104,8 +104,7 @@
          GstPad *queue0_sink_pad = gst_element_get_static_pad(queue0_, 
"sink");
          GstPad *queue1_sink_pad = gst_element_get_static_pad(queue1_, 
"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) {
+        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) {
              if (queue0_sink_pad)
                  gst_object_unref(queue0_sink_pad);
              if (queue1_sink_pad)
---
1 potential issue detected, please review

>
> diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp b/test/gstreamer/gstreamer_multi_stream_test.cpp
> new file mode 100644
> index 000000000000..aba86a3f8e27
> --- /dev/null
> +++ b/test/gstreamer/gstreamer_multi_stream_test.cpp
> @@ -0,0 +1,138 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2021, Vedant Paranjape
> + *
> + * gstreamer_multi_stream_test.cpp - GStreamer multi stream capture test
> + */
> +
> +#include <iostream>
> +#include <unistd.h>
> +
> +#include <libcamera/base/utils.h>
> +
> +#include <libcamera/libcamera.h>
> +
> +#include "libcamera/internal/source_paths.h"
> +
> +#include <gst/gst.h>
> +
> +#include "gstreamer_test.h"
> +#include "test.h"
> +
> +using namespace std;
> +
> +class GstreamerMultiStreamTest : public GstreamerTest, public Test
> +{
> +public:
> +	GstreamerMultiStreamTest()
> +		: GstreamerTest()
> +	{
> +	}
> +
> +protected:
> +	int init() override
> +	{
> +		if (status_ != TestPass)
> +			return status_;
> +
> +		/* Check if platform support multistream output */
> +		libcamera::CameraManager cm;
> +		cm.start();
> +		bool cameraFound = false;
> +		for (auto &camera : cm.cameras()) {
> +			if (camera->streams().size() > 1) {
> +				cameraName = camera->id();
> +				cameraFound = true;
> +				cm.stop();
> +				break;
> +			}
> +		}
> +
> +		if (!cameraFound) {
> +			cm.stop();
> +			return TestSkip;
> +		}
> +
> +		g_autoptr(GstElement) convert0 = gst_element_factory_make("videoconvert", "convert0");
> +		g_autoptr(GstElement) convert1 = gst_element_factory_make("videoconvert", "convert1");
> +		g_autoptr(GstElement) sink0 = gst_element_factory_make("fakesink", "sink0");
> +		g_autoptr(GstElement) sink1 = gst_element_factory_make("fakesink", "sink1");
> +		g_autoptr(GstElement) queue0 = gst_element_factory_make("queue", "queue0");
> +		g_autoptr(GstElement) queue1 = gst_element_factory_make("queue", "queue1");
> +		g_object_ref_sink(convert0);
> +		g_object_ref_sink(convert1);
> +		g_object_ref_sink(sink0);
> +		g_object_ref_sink(sink1);
> +		g_object_ref_sink(queue0);
> +		g_object_ref_sink(queue1);


What's the idea here of first taking ownership by locally scope 
g_autoptr() pointers and then setting to the private members below? Is 
it to validate/null-check them? I would directly set to private members 
and null-check them directly instead, and if it fails, so be it and 
return TestFail. Would you be tempted to take this route?

> +
> +		if (!convert0 || !convert1 || !sink0 || !sink1 || !queue0 || !queue1) {
> +			g_printerr("Not all elements could be created. %p.%p.%p.%p.%p.%p\n",
> +				   convert0, convert1, sink0, sink1, queue0, queue1);
> +
> +			return TestFail;
> +		}
> +
> +		convert0_ = reinterpret_cast<GstElement *>(g_steal_pointer(&convert0));
> +		convert1_ = reinterpret_cast<GstElement *>(g_steal_pointer(&convert1));
> +		sink0_ = reinterpret_cast<GstElement *>(g_steal_pointer(&sink0));
> +		sink1_ = reinterpret_cast<GstElement *>(g_steal_pointer(&sink1));
> +		queue0_ = reinterpret_cast<GstElement *>(g_steal_pointer(&queue0));
> +		queue1_ = reinterpret_cast<GstElement *>(g_steal_pointer(&queue1));
> +
> +		if (createPipeline() != TestPass)
> +			return TestFail;


Should this `if` block be present before you set the elements above?

> +
> +		return TestPass;
> +	}
> +
> +	int run() override
> +	{
> +		g_object_set(libcameraSrc_, "camera-name", cameraName.c_str(), NULL);
> +
> +		/* Build the pipeline */
> +		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, queue0_, queue1_,
> +				 convert0_, convert1_, sink0_, sink1_, NULL);
> +		if (gst_element_link_many(queue0_, convert0_, sink0_, NULL) != TRUE ||
> +		    gst_element_link_many(queue1_, convert1_, sink1_, NULL) != TRUE) {
> +			g_printerr("Elements could not be linked.\n");
> +			return TestFail;
> +		}
> +
> +		g_autoptr(GstPad) src_pad = gst_element_get_static_pad(libcameraSrc_, "src");
> +		g_autoptr(GstPad) request_pad = gst_element_get_request_pad(libcameraSrc_, "src_%u");
> +		GstPad *queue0_sink_pad = gst_element_get_static_pad(queue0_, "sink");
> +		GstPad *queue1_sink_pad = gst_element_get_static_pad(queue1_, "sink");


Why can't you use g_autoptr() for queue0_sink_pad and queue1_sink_pad 
too? gst_element_get_static_pad() will return a [Full] transfer-able 
value, so it should be un-reffed after usage (like you do below). If you 
use g_autoptr() the manual _unref shall be taken care of by itself no? 
Am I missing something? It'll help cleanup error paths.

> +
> +		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) {
> +			if (queue0_sink_pad)
> +				gst_object_unref(queue0_sink_pad);
> +			if (queue1_sink_pad)
> +				gst_object_unref(queue1_sink_pad);
> +			g_printerr("Pads could not be linked.\n");
> +			return TestFail;
> +		}
> +		gst_object_unref(queue0_sink_pad);
> +		gst_object_unref(queue1_sink_pad);
> +
> +		if (startPipeline() != TestPass)
> +			return TestFail;
> +
> +		if (processEvent() != TestPass)
> +			return TestFail;
> +
> +		return TestPass;
> +	}
> +
> +private:
> +	std::string cameraName;
> +	GstElement *convert0_;
> +	GstElement *convert1_;
> +	GstElement *sink0_;
> +	GstElement *sink1_;
> +	GstElement *queue0_;
> +	GstElement *queue1_;
> +};
> +
> +TEST_REGISTER(GstreamerMultiStreamTest)
> diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> index aca53b920365..13652e87d05c 100644
> --- a/test/gstreamer/meson.build
> +++ b/test/gstreamer/meson.build
> @@ -6,6 +6,7 @@ endif
>   
>   gstreamer_tests = [
>       ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> +    ['multi_stream_test',   'gstreamer_multi_stream_test.cpp'],
>   ]
>   gstreamer_dep = dependency('gstreamer-1.0', required: true)
>
Vedant Paranjape Sept. 14, 2021, 8:42 a.m. UTC | #2
Hi Umang,


On Tue, Sep 14, 2021 at 12:53 PM Umang Jain <umang.jain@ideasonboard.com>
wrote:

> Hi Vedant,
>
> On 9/14/21 12:04 PM, Vedant Paranjape wrote:
> > This patch adds a test to test if multi stream using libcamera's
> > gstreamer element works.
>
>
> Can you please document how and when this test is run?
>
> Currently, my system (with one UVC and vimc) has been able to run
> single_stream_test but multi_stream_test is run:
>
>
> 11/65 libcamera:gstreamer / single_stream_test OK             2.53s
> 12/65 libcamera:gstreamer / multi_stream_test SKIP           0.07s
>
> What's your test setup where this tests passes. I assume I have to
> satisfy the following condition from the test, in order to run:
>
>                  if (camera->streams().size() > 1) {
>
>                          cameraFound = true;
>
>                          ....
>
>                  }
>
> But I don't know how, looking at the patch. So can you explain (not just
> here but in the patch / commit message as well) ? It shall help others
> as well.
>

Multistream will run on rpi and IPU pipelines only. Will add it to commit
message.

>
> > Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > Tested-by: Paul Elder <paul.elder@ideasonboard.com>
> > ---
> >   .../gstreamer/gstreamer_multi_stream_test.cpp | 138 ++++++++++++++++++
> >   test/gstreamer/meson.build                    |   1 +
> >   2 files changed, 139 insertions(+)
> >   create mode 100644 test/gstreamer/gstreamer_multi_stream_test.cpp
>
>
> Right off the bat, there is a checkstyle failure: Can you look into it,
> please?
>
> [uajain@fedora libcamera]$ ./utils/checkstyle.py HEAD
>
> -----------------------------------------------------------------------------------------------
> 9d0efed89b03d57790db4eebefa909bf169ce789 test: gstreamer: Add a test for
> gstreamer multi stream
>
> -----------------------------------------------------------------------------------------------
> --- test/gstreamer/gstreamer_multi_stream_test.cpp
> +++ test/gstreamer/gstreamer_multi_stream_test.cpp
> @@ -104,8 +104,7 @@
>           GstPad *queue0_sink_pad = gst_element_get_static_pad(queue0_,
> "sink");
>           GstPad *queue1_sink_pad = gst_element_get_static_pad(queue1_,
> "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) {
> +        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) {
>               if (queue0_sink_pad)
>                   gst_object_unref(queue0_sink_pad);
>               if (queue1_sink_pad)
> ---
> 1 potential issue detected, please review
>

Please see Paul's review !

>
> > diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp
> b/test/gstreamer/gstreamer_multi_stream_test.cpp
> > new file mode 100644
> > index 000000000000..aba86a3f8e27
> > --- /dev/null
> > +++ b/test/gstreamer/gstreamer_multi_stream_test.cpp
> > @@ -0,0 +1,138 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2021, Vedant Paranjape
> > + *
> > + * gstreamer_multi_stream_test.cpp - GStreamer multi stream capture test
> > + */
> > +
> > +#include <iostream>
> > +#include <unistd.h>
> > +
> > +#include <libcamera/base/utils.h>
> > +
> > +#include <libcamera/libcamera.h>
> > +
> > +#include "libcamera/internal/source_paths.h"
> > +
> > +#include <gst/gst.h>
> > +
> > +#include "gstreamer_test.h"
> > +#include "test.h"
> > +
> > +using namespace std;
> > +
> > +class GstreamerMultiStreamTest : public GstreamerTest, public Test
> > +{
> > +public:
> > +     GstreamerMultiStreamTest()
> > +             : GstreamerTest()
> > +     {
> > +     }
> > +
> > +protected:
> > +     int init() override
> > +     {
> > +             if (status_ != TestPass)
> > +                     return status_;
> > +
> > +             /* Check if platform support multistream output */
> > +             libcamera::CameraManager cm;
> > +             cm.start();
> > +             bool cameraFound = false;
> > +             for (auto &camera : cm.cameras()) {
> > +                     if (camera->streams().size() > 1) {
> > +                             cameraName = camera->id();
> > +                             cameraFound = true;
> > +                             cm.stop();
> > +                             break;
> > +                     }
> > +             }
> > +
> > +             if (!cameraFound) {
> > +                     cm.stop();
> > +                     return TestSkip;
> > +             }
> > +
> > +             g_autoptr(GstElement) convert0 =
> gst_element_factory_make("videoconvert", "convert0");
> > +             g_autoptr(GstElement) convert1 =
> gst_element_factory_make("videoconvert", "convert1");
> > +             g_autoptr(GstElement) sink0 =
> gst_element_factory_make("fakesink", "sink0");
> > +             g_autoptr(GstElement) sink1 =
> gst_element_factory_make("fakesink", "sink1");
> > +             g_autoptr(GstElement) queue0 =
> gst_element_factory_make("queue", "queue0");
> > +             g_autoptr(GstElement) queue1 =
> gst_element_factory_make("queue", "queue1");
> > +             g_object_ref_sink(convert0);
> > +             g_object_ref_sink(convert1);
> > +             g_object_ref_sink(sink0);
> > +             g_object_ref_sink(sink1);
> > +             g_object_ref_sink(queue0);
> > +             g_object_ref_sink(queue1);
>
>
> What's the idea here of first taking ownership by locally scope
> g_autoptr() pointers and then setting to the private members below? Is
> it to validate/null-check them? I would directly set to private members
> and null-check them directly instead, and if it fails, so be it and
> return TestFail. Would you be tempted to take this route?
>

I was doing it before, and it let to a lot of repetitive code and buggy
error handling. Also, Laurent and Nicolas were kinda reserved against it.

> +
> > +             if (!convert0 || !convert1 || !sink0 || !sink1 || !queue0
> || !queue1) {
> > +                     g_printerr("Not all elements could be created.
> %p.%p.%p.%p.%p.%p\n",
> > +                                convert0, convert1, sink0, sink1,
> queue0, queue1);
> > +
> > +                     return TestFail;
> > +             }
> > +
> > +             convert0_ = reinterpret_cast<GstElement
> *>(g_steal_pointer(&convert0));
> > +             convert1_ = reinterpret_cast<GstElement
> *>(g_steal_pointer(&convert1));
> > +             sink0_ = reinterpret_cast<GstElement
> *>(g_steal_pointer(&sink0));
> > +             sink1_ = reinterpret_cast<GstElement
> *>(g_steal_pointer(&sink1));
> > +             queue0_ = reinterpret_cast<GstElement
> *>(g_steal_pointer(&queue0));
> > +             queue1_ = reinterpret_cast<GstElement
> *>(g_steal_pointer(&queue1));
> > +
> > +             if (createPipeline() != TestPass)
> > +                     return TestFail;
>
>
> Should this `if` block be present before you set the elements above?
>

This was a good catch, all those elements won't be unrefed if
createPipeline fails. I will make the fix !

> +
> > +             return TestPass;
> > +     }
> > +
> > +     int run() override
> > +     {
> > +             g_object_set(libcameraSrc_, "camera-name",
> cameraName.c_str(), NULL);
> > +
> > +             /* Build the pipeline */
> > +             gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
> queue0_, queue1_,
> > +                              convert0_, convert1_, sink0_, sink1_,
> NULL);
> > +             if (gst_element_link_many(queue0_, convert0_, sink0_,
> NULL) != TRUE ||
> > +                 gst_element_link_many(queue1_, convert1_, sink1_,
> NULL) != TRUE) {
> > +                     g_printerr("Elements could not be linked.\n");
> > +                     return TestFail;
> > +             }
> > +
> > +             g_autoptr(GstPad) src_pad =
> gst_element_get_static_pad(libcameraSrc_, "src");
> > +             g_autoptr(GstPad) request_pad =
> gst_element_get_request_pad(libcameraSrc_, "src_%u");
> > +             GstPad *queue0_sink_pad =
> gst_element_get_static_pad(queue0_, "sink");
> > +             GstPad *queue1_sink_pad =
> gst_element_get_static_pad(queue1_, "sink");
>
>
> Why can't you use g_autoptr() for queue0_sink_pad and queue1_sink_pad
> too? gst_element_get_static_pad() will return a [Full] transfer-able
> value, so it should be un-reffed after usage (like you do below). If you
> use g_autoptr() the manual _unref shall be taken care of by itself no?
> Am I missing something? It'll help cleanup error paths.
>

The pad must be unrefed immediately after use, that is after the pads are
linked, and if I use an autoptr it won't unref right after the if.
I had thought to put the code where pads are created and linked into an
inner scope like this, but then it will be over kill.

func
{
  { // code // }
}

I assume this is a nitpick and not a blocker. I don't see much improvement
by doing so, It'd be a different scenario if I had, say 10 pads.

> +
> > +             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) {
> > +                     if (queue0_sink_pad)
> > +                             gst_object_unref(queue0_sink_pad);
> > +                     if (queue1_sink_pad)
> > +                             gst_object_unref(queue1_sink_pad);
> > +                     g_printerr("Pads could not be linked.\n");
> > +                     return TestFail;
> > +             }
> > +             gst_object_unref(queue0_sink_pad);
> > +             gst_object_unref(queue1_sink_pad);
> > +
> > +             if (startPipeline() != TestPass)
> > +                     return TestFail;
> > +
> > +             if (processEvent() != TestPass)
> > +                     return TestFail;
> > +
> > +             return TestPass;
> > +     }
> > +
> > +private:
> > +     std::string cameraName;
> > +     GstElement *convert0_;
> > +     GstElement *convert1_;
> > +     GstElement *sink0_;
> > +     GstElement *sink1_;
> > +     GstElement *queue0_;
> > +     GstElement *queue1_;
> > +};
> > +
> > +TEST_REGISTER(GstreamerMultiStreamTest)
> > diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> > index aca53b920365..13652e87d05c 100644
> > --- a/test/gstreamer/meson.build
> > +++ b/test/gstreamer/meson.build
> > @@ -6,6 +6,7 @@ endif
> >
> >   gstreamer_tests = [
> >       ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> > +    ['multi_stream_test',   'gstreamer_multi_stream_test.cpp'],
> >   ]
> >   gstreamer_dep = dependency('gstreamer-1.0', required: true)
> >
>

Regards,
Vedant Paranjape
Umang Jain Sept. 14, 2021, 9:38 a.m. UTC | #3
Hi Vedant

On 9/14/21 2:12 PM, Vedant Paranjape wrote:
> Hi Umang,
>
>
> On Tue, Sep 14, 2021 at 12:53 PM Umang Jain <umang.jain@ideasonboard.com>
> wrote:
>
>> Hi Vedant,
>>
>> On 9/14/21 12:04 PM, Vedant Paranjape wrote:
>>> This patch adds a test to test if multi stream using libcamera's
>>> gstreamer element works.
>>
>> Can you please document how and when this test is run?
>>
>> Currently, my system (with one UVC and vimc) has been able to run
>> single_stream_test but multi_stream_test is run:
>>
>>
>> 11/65 libcamera:gstreamer / single_stream_test OK             2.53s
>> 12/65 libcamera:gstreamer / multi_stream_test SKIP           0.07s
>>
>> What's your test setup where this tests passes. I assume I have to
>> satisfy the following condition from the test, in order to run:
>>
>>                   if (camera->streams().size() > 1) {
>>
>>                           cameraFound = true;
>>
>>                           ....
>>
>>                   }
>>
>> But I don't know how, looking at the patch. So can you explain (not just
>> here but in the patch / commit message as well) ? It shall help others
>> as well.
>>
> Multistream will run on rpi and IPU pipelines only. Will add it to commit
> message.


OK, I'll see if I can carve some timeout to test on IPU3 maybe? You are 
testing RPi I assume?

>
>>> Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
>>> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
>>> Tested-by: Paul Elder <paul.elder@ideasonboard.com>
>>> ---
>>>    .../gstreamer/gstreamer_multi_stream_test.cpp | 138 ++++++++++++++++++
>>>    test/gstreamer/meson.build                    |   1 +
>>>    2 files changed, 139 insertions(+)
>>>    create mode 100644 test/gstreamer/gstreamer_multi_stream_test.cpp
>>
>> Right off the bat, there is a checkstyle failure: Can you look into it,
>> please?
>>
>> [uajain@fedora libcamera]$ ./utils/checkstyle.py HEAD
>>
>> -----------------------------------------------------------------------------------------------
>> 9d0efed89b03d57790db4eebefa909bf169ce789 test: gstreamer: Add a test for
>> gstreamer multi stream
>>
>> -----------------------------------------------------------------------------------------------
>> --- test/gstreamer/gstreamer_multi_stream_test.cpp
>> +++ test/gstreamer/gstreamer_multi_stream_test.cpp
>> @@ -104,8 +104,7 @@
>>            GstPad *queue0_sink_pad = gst_element_get_static_pad(queue0_,
>> "sink");
>>            GstPad *queue1_sink_pad = gst_element_get_static_pad(queue1_,
>> "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) {
>> +        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) {
>>                if (queue0_sink_pad)
>>                    gst_object_unref(queue0_sink_pad);
>>                if (queue1_sink_pad)
>> ---
>> 1 potential issue detected, please review
>>
> Please see Paul's review !


Where? Now that you mention it, I see tags  from Paul but the v1 had no 
comments and v2 has couple of Laurent's comments. Are you sure you only 
have one of v1 and one of v2 series versioning?

>
>>> diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp
>> b/test/gstreamer/gstreamer_multi_stream_test.cpp
>>> new file mode 100644
>>> index 000000000000..aba86a3f8e27
>>> --- /dev/null
>>> +++ b/test/gstreamer/gstreamer_multi_stream_test.cpp
>>> @@ -0,0 +1,138 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>>> +/*
>>> + * Copyright (C) 2021, Vedant Paranjape
>>> + *
>>> + * gstreamer_multi_stream_test.cpp - GStreamer multi stream capture test
>>> + */
>>> +
>>> +#include <iostream>
>>> +#include <unistd.h>
>>> +
>>> +#include <libcamera/base/utils.h>
>>> +
>>> +#include <libcamera/libcamera.h>
>>> +
>>> +#include "libcamera/internal/source_paths.h"
>>> +
>>> +#include <gst/gst.h>
>>> +
>>> +#include "gstreamer_test.h"
>>> +#include "test.h"
>>> +
>>> +using namespace std;
>>> +
>>> +class GstreamerMultiStreamTest : public GstreamerTest, public Test
>>> +{
>>> +public:
>>> +     GstreamerMultiStreamTest()
>>> +             : GstreamerTest()
>>> +     {
>>> +     }
>>> +
>>> +protected:
>>> +     int init() override
>>> +     {
>>> +             if (status_ != TestPass)
>>> +                     return status_;
>>> +
>>> +             /* Check if platform support multistream output */
>>> +             libcamera::CameraManager cm;
>>> +             cm.start();
>>> +             bool cameraFound = false;
>>> +             for (auto &camera : cm.cameras()) {
>>> +                     if (camera->streams().size() > 1) {
>>> +                             cameraName = camera->id();
>>> +                             cameraFound = true;
>>> +                             cm.stop();
>>> +                             break;
>>> +                     }
>>> +             }
>>> +
>>> +             if (!cameraFound) {
>>> +                     cm.stop();
>>> +                     return TestSkip;
>>> +             }
>>> +
>>> +             g_autoptr(GstElement) convert0 =
>> gst_element_factory_make("videoconvert", "convert0");
>>> +             g_autoptr(GstElement) convert1 =
>> gst_element_factory_make("videoconvert", "convert1");
>>> +             g_autoptr(GstElement) sink0 =
>> gst_element_factory_make("fakesink", "sink0");
>>> +             g_autoptr(GstElement) sink1 =
>> gst_element_factory_make("fakesink", "sink1");
>>> +             g_autoptr(GstElement) queue0 =
>> gst_element_factory_make("queue", "queue0");
>>> +             g_autoptr(GstElement) queue1 =
>> gst_element_factory_make("queue", "queue1");
>>> +             g_object_ref_sink(convert0);
>>> +             g_object_ref_sink(convert1);
>>> +             g_object_ref_sink(sink0);
>>> +             g_object_ref_sink(sink1);
>>> +             g_object_ref_sink(queue0);
>>> +             g_object_ref_sink(queue1);
>>
>> What's the idea here of first taking ownership by locally scope
>> g_autoptr() pointers and then setting to the private members below? Is
>> it to validate/null-check them? I would directly set to private members
>> and null-check them directly instead, and if it fails, so be it and
>> return TestFail. Would you be tempted to take this route?
>>
> I was doing it before, and it let to a lot of repetitive code and buggy
> error handling. Also, Laurent and Nicolas were kinda reserved against it.


If it was repetitive, I guess you can encapsulate all that in a helper 
function, to be called on error path to cleanup and return TestFail.

>
>> +
>>> +             if (!convert0 || !convert1 || !sink0 || !sink1 || !queue0
>> || !queue1) {
>>> +                     g_printerr("Not all elements could be created.
>> %p.%p.%p.%p.%p.%p\n",
>>> +                                convert0, convert1, sink0, sink1,
>> queue0, queue1);
>>> +
>>> +                     return TestFail;
>>> +             }
>>> +
>>> +             convert0_ = reinterpret_cast<GstElement
>> *>(g_steal_pointer(&convert0));
>>> +             convert1_ = reinterpret_cast<GstElement
>> *>(g_steal_pointer(&convert1));
>>> +             sink0_ = reinterpret_cast<GstElement
>> *>(g_steal_pointer(&sink0));
>>> +             sink1_ = reinterpret_cast<GstElement
>> *>(g_steal_pointer(&sink1));
>>> +             queue0_ = reinterpret_cast<GstElement
>> *>(g_steal_pointer(&queue0));
>>> +             queue1_ = reinterpret_cast<GstElement
>> *>(g_steal_pointer(&queue1));
>>> +
>>> +             if (createPipeline() != TestPass)
>>> +                     return TestFail;
>>
>> Should this `if` block be present before you set the elements above?
>>
> This was a good catch, all those elements won't be unrefed if
> createPipeline fails. I will make the fix !
>
>> +
>>> +             return TestPass;
>>> +     }
>>> +
>>> +     int run() override
>>> +     {
>>> +             g_object_set(libcameraSrc_, "camera-name",
>> cameraName.c_str(), NULL);
>>> +
>>> +             /* Build the pipeline */
>>> +             gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
>> queue0_, queue1_,
>>> +                              convert0_, convert1_, sink0_, sink1_,
>> NULL);
>>> +             if (gst_element_link_many(queue0_, convert0_, sink0_,
>> NULL) != TRUE ||
>>> +                 gst_element_link_many(queue1_, convert1_, sink1_,
>> NULL) != TRUE) {
>>> +                     g_printerr("Elements could not be linked.\n");
>>> +                     return TestFail;
>>> +             }
>>> +
>>> +             g_autoptr(GstPad) src_pad =
>> gst_element_get_static_pad(libcameraSrc_, "src");
>>> +             g_autoptr(GstPad) request_pad =
>> gst_element_get_request_pad(libcameraSrc_, "src_%u");
>>> +             GstPad *queue0_sink_pad =
>> gst_element_get_static_pad(queue0_, "sink");
>>> +             GstPad *queue1_sink_pad =
>> gst_element_get_static_pad(queue1_, "sink");
>>
>>
>> Why can't you use g_autoptr() for queue0_sink_pad and queue1_sink_pad
>> too? gst_element_get_static_pad() will return a [Full] transfer-able
>> value, so it should be un-reffed after usage (like you do below). If you
>> use g_autoptr() the manual _unref shall be taken care of by itself no?
>> Am I missing something? It'll help cleanup error paths.
>>
> The pad must be unrefed immediately after use, that is after the pads are


I referred some documentation and I didn't see the word "immediately" 
anywhere.

The pads needs to be un-reffed after use, but I don't think "immediate 
free" is a requirement. It's bad design upfront and I think gstreamer 
folks would have known it. Can you provide link to documentation which 
made you think it should be "immediately" un-reffed and not when 
function returns (which happens in case g_autoptr is used)?

> linked, and if I use an autoptr it won't unref right after the if.
> I had thought to put the code where pads are created and linked into an
> inner scope like this, but then it will be over kill.
>
> func
> {
>    { // code // }
> }


I have seen this design as well for GLib projects as well, mainly for 
g_autoptr and mutexes combine and don't think it's an overkill.

>
> I assume this is a nitpick and not a blocker. I don't see much improvement
> by doing so, It'd be a different scenario if I had, say 10 pads.


Here as well, I would suggest to get a helper function if you are 
managing so many pads at once.

I'll check if I am able to run the test on platforms available to me and 
incorporate the suggestions. This is just a reading and understanding of 
the code.

>
>> +
>>> +             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) {
>>> +                     if (queue0_sink_pad)
>>> +                             gst_object_unref(queue0_sink_pad);
>>> +                     if (queue1_sink_pad)
>>> +                             gst_object_unref(queue1_sink_pad);
>>> +                     g_printerr("Pads could not be linked.\n");
>>> +                     return TestFail;
>>> +             }
>>> +             gst_object_unref(queue0_sink_pad);
>>> +             gst_object_unref(queue1_sink_pad);
>>> +
>>> +             if (startPipeline() != TestPass)
>>> +                     return TestFail;
>>> +
>>> +             if (processEvent() != TestPass)
>>> +                     return TestFail;
>>> +
>>> +             return TestPass;
>>> +     }
>>> +
>>> +private:
>>> +     std::string cameraName;
>>> +     GstElement *convert0_;
>>> +     GstElement *convert1_;
>>> +     GstElement *sink0_;
>>> +     GstElement *sink1_;
>>> +     GstElement *queue0_;
>>> +     GstElement *queue1_;
>>> +};
>>> +
>>> +TEST_REGISTER(GstreamerMultiStreamTest)
>>> diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
>>> index aca53b920365..13652e87d05c 100644
>>> --- a/test/gstreamer/meson.build
>>> +++ b/test/gstreamer/meson.build
>>> @@ -6,6 +6,7 @@ endif
>>>
>>>    gstreamer_tests = [
>>>        ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
>>> +    ['multi_stream_test',   'gstreamer_multi_stream_test.cpp'],
>>>    ]
>>>    gstreamer_dep = dependency('gstreamer-1.0', required: true)
>>>
> Regards,
> Vedant Paranjape
>
Vedant Paranjape Sept. 14, 2021, 10:28 a.m. UTC | #4
Hi Umang,


On Tue, Sep 14, 2021 at 3:08 PM Umang Jain <umang.jain@ideasonboard.com>
wrote:

> Hi Vedant
>
> On 9/14/21 2:12 PM, Vedant Paranjape wrote:
> > Hi Umang,
> >
> >
> > On Tue, Sep 14, 2021 at 12:53 PM Umang Jain <umang.jain@ideasonboard.com
> >
> > wrote:
> >
> >> Hi Vedant,
> >>
> >> On 9/14/21 12:04 PM, Vedant Paranjape wrote:
> >>> This patch adds a test to test if multi stream using libcamera's
> >>> gstreamer element works.
> >>
> >> Can you please document how and when this test is run?
> >>
> >> Currently, my system (with one UVC and vimc) has been able to run
> >> single_stream_test but multi_stream_test is run:
> >>
> >>
> >> 11/65 libcamera:gstreamer / single_stream_test OK             2.53s
> >> 12/65 libcamera:gstreamer / multi_stream_test SKIP           0.07s
> >>
> >> What's your test setup where this tests passes. I assume I have to
> >> satisfy the following condition from the test, in order to run:
> >>
> >>                   if (camera->streams().size() > 1) {
> >>
> >>                           cameraFound = true;
> >>
> >>                           ....
> >>
> >>                   }
> >>
> >> But I don't know how, looking at the patch. So can you explain (not just
> >> here but in the patch / commit message as well) ? It shall help others
> >> as well.
> >>
> > Multistream will run on rpi and IPU pipelines only. Will add it to commit
> > message.
>
>
> OK, I'll see if I can carve some timeout to test on IPU3 maybe? You are
> testing RPi I assume?
>

Yes

>
> >>> Signed-off-by: Vedant Paranjape <vedantparanjape160201@gmail.com>
> >>> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> >>> Tested-by: Paul Elder <paul.elder@ideasonboard.com>
> >>> ---
> >>>    .../gstreamer/gstreamer_multi_stream_test.cpp | 138
> ++++++++++++++++++
> >>>    test/gstreamer/meson.build                    |   1 +
> >>>    2 files changed, 139 insertions(+)
> >>>    create mode 100644 test/gstreamer/gstreamer_multi_stream_test.cpp
> >>
> >> Right off the bat, there is a checkstyle failure: Can you look into it,
> >> please?
> >>
> >> [uajain@fedora libcamera]$ ./utils/checkstyle.py HEAD
> >>
> >>
> -----------------------------------------------------------------------------------------------
> >> 9d0efed89b03d57790db4eebefa909bf169ce789 test: gstreamer: Add a test for
> >> gstreamer multi stream
> >>
> >>
> -----------------------------------------------------------------------------------------------
> >> --- test/gstreamer/gstreamer_multi_stream_test.cpp
> >> +++ test/gstreamer/gstreamer_multi_stream_test.cpp
> >> @@ -104,8 +104,7 @@
> >>            GstPad *queue0_sink_pad = gst_element_get_static_pad(queue0_,
> >> "sink");
> >>            GstPad *queue1_sink_pad = gst_element_get_static_pad(queue1_,
> >> "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) {
> >> +        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) {
> >>                if (queue0_sink_pad)
> >>                    gst_object_unref(queue0_sink_pad);
> >>                if (queue1_sink_pad)
> >> ---
> >> 1 potential issue detected, please review
> >>
> > Please see Paul's review !
>
>
> Where? Now that you mention it, I see tags  from Paul but the v1 had no
> comments and v2 has couple of Laurent's comments. Are you sure you only
> have one of v1 and one of v2 series versioning?
>

No, Paul was the one who commented on v2. One with Laurent's comments is
pretty old and outdated, as I now have a base class for gstreamer test,
earlier one didn't
Paul's review: https://patchwork.libcamera.org/patch/13813/

>
> >>> diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp
> >> b/test/gstreamer/gstreamer_multi_stream_test.cpp
> >>> new file mode 100644
> >>> index 000000000000..aba86a3f8e27
> >>> --- /dev/null
> >>> +++ b/test/gstreamer/gstreamer_multi_stream_test.cpp
> >>> @@ -0,0 +1,138 @@
> >>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> >>> +/*
> >>> + * Copyright (C) 2021, Vedant Paranjape
> >>> + *
> >>> + * gstreamer_multi_stream_test.cpp - GStreamer multi stream capture
> test
> >>> + */
> >>> +
> >>> +#include <iostream>
> >>> +#include <unistd.h>
> >>> +
> >>> +#include <libcamera/base/utils.h>
> >>> +
> >>> +#include <libcamera/libcamera.h>
> >>> +
> >>> +#include "libcamera/internal/source_paths.h"
> >>> +
> >>> +#include <gst/gst.h>
> >>> +
> >>> +#include "gstreamer_test.h"
> >>> +#include "test.h"
> >>> +
> >>> +using namespace std;
> >>> +
> >>> +class GstreamerMultiStreamTest : public GstreamerTest, public Test
> >>> +{
> >>> +public:
> >>> +     GstreamerMultiStreamTest()
> >>> +             : GstreamerTest()
> >>> +     {
> >>> +     }
> >>> +
> >>> +protected:
> >>> +     int init() override
> >>> +     {
> >>> +             if (status_ != TestPass)
> >>> +                     return status_;
> >>> +
> >>> +             /* Check if platform support multistream output */
> >>> +             libcamera::CameraManager cm;
> >>> +             cm.start();
> >>> +             bool cameraFound = false;
> >>> +             for (auto &camera : cm.cameras()) {
> >>> +                     if (camera->streams().size() > 1) {
> >>> +                             cameraName = camera->id();
> >>> +                             cameraFound = true;
> >>> +                             cm.stop();
> >>> +                             break;
> >>> +                     }
> >>> +             }
> >>> +
> >>> +             if (!cameraFound) {
> >>> +                     cm.stop();
> >>> +                     return TestSkip;
> >>> +             }
> >>> +
> >>> +             g_autoptr(GstElement) convert0 =
> >> gst_element_factory_make("videoconvert", "convert0");
> >>> +             g_autoptr(GstElement) convert1 =
> >> gst_element_factory_make("videoconvert", "convert1");
> >>> +             g_autoptr(GstElement) sink0 =
> >> gst_element_factory_make("fakesink", "sink0");
> >>> +             g_autoptr(GstElement) sink1 =
> >> gst_element_factory_make("fakesink", "sink1");
> >>> +             g_autoptr(GstElement) queue0 =
> >> gst_element_factory_make("queue", "queue0");
> >>> +             g_autoptr(GstElement) queue1 =
> >> gst_element_factory_make("queue", "queue1");
> >>> +             g_object_ref_sink(convert0);
> >>> +             g_object_ref_sink(convert1);
> >>> +             g_object_ref_sink(sink0);
> >>> +             g_object_ref_sink(sink1);
> >>> +             g_object_ref_sink(queue0);
> >>> +             g_object_ref_sink(queue1);
> >>
> >> What's the idea here of first taking ownership by locally scope
> >> g_autoptr() pointers and then setting to the private members below? Is
> >> it to validate/null-check them? I would directly set to private members
> >> and null-check them directly instead, and if it fails, so be it and
> >> return TestFail. Would you be tempted to take this route?
> >>
> > I was doing it before, and it let to a lot of repetitive code and buggy
> > error handling. Also, Laurent and Nicolas were kinda reserved against it.
>
>
> If it was repetitive, I guess you can encapsulate all that in a helper
> function, to be called on error path to cleanup and return TestFail.
>

I prefer using g_autoptr based approach, I'll ping @Nicolas Dufresne
<nicolas@ndufresne.ca> for his views, ~he suggested me to do it this way !


> >
> >> +
> >>> +             if (!convert0 || !convert1 || !sink0 || !sink1 || !queue0
> >> || !queue1) {
> >>> +                     g_printerr("Not all elements could be created.
> >> %p.%p.%p.%p.%p.%p\n",
> >>> +                                convert0, convert1, sink0, sink1,
> >> queue0, queue1);
> >>> +
> >>> +                     return TestFail;
> >>> +             }
> >>> +
> >>> +             convert0_ = reinterpret_cast<GstElement
> >> *>(g_steal_pointer(&convert0));
> >>> +             convert1_ = reinterpret_cast<GstElement
> >> *>(g_steal_pointer(&convert1));
> >>> +             sink0_ = reinterpret_cast<GstElement
> >> *>(g_steal_pointer(&sink0));
> >>> +             sink1_ = reinterpret_cast<GstElement
> >> *>(g_steal_pointer(&sink1));
> >>> +             queue0_ = reinterpret_cast<GstElement
> >> *>(g_steal_pointer(&queue0));
> >>> +             queue1_ = reinterpret_cast<GstElement
> >> *>(g_steal_pointer(&queue1));
> >>> +
> >>> +             if (createPipeline() != TestPass)
> >>> +                     return TestFail;
> >>
> >> Should this `if` block be present before you set the elements above?
> >>
> > This was a good catch, all those elements won't be unrefed if
> > createPipeline fails. I will make the fix !
> >
> >> +
> >>> +             return TestPass;
> >>> +     }
> >>> +
> >>> +     int run() override
> >>> +     {
> >>> +             g_object_set(libcameraSrc_, "camera-name",
> >> cameraName.c_str(), NULL);
> >>> +
> >>> +             /* Build the pipeline */
> >>> +             gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_,
> >> queue0_, queue1_,
> >>> +                              convert0_, convert1_, sink0_, sink1_,
> >> NULL);
> >>> +             if (gst_element_link_many(queue0_, convert0_, sink0_,
> >> NULL) != TRUE ||
> >>> +                 gst_element_link_many(queue1_, convert1_, sink1_,
> >> NULL) != TRUE) {
> >>> +                     g_printerr("Elements could not be linked.\n");
> >>> +                     return TestFail;
> >>> +             }
> >>> +
> >>> +             g_autoptr(GstPad) src_pad =
> >> gst_element_get_static_pad(libcameraSrc_, "src");
> >>> +             g_autoptr(GstPad) request_pad =
> >> gst_element_get_request_pad(libcameraSrc_, "src_%u");
> >>> +             GstPad *queue0_sink_pad =
> >> gst_element_get_static_pad(queue0_, "sink");
> >>> +             GstPad *queue1_sink_pad =
> >> gst_element_get_static_pad(queue1_, "sink");
> >>
> >>
> >> Why can't you use g_autoptr() for queue0_sink_pad and queue1_sink_pad
> >> too? gst_element_get_static_pad() will return a [Full] transfer-able
> >> value, so it should be un-reffed after usage (like you do below). If you
> >> use g_autoptr() the manual _unref shall be taken care of by itself no?
> >> Am I missing something? It'll help cleanup error paths.
> >>
> > The pad must be unrefed immediately after use, that is after the pads are
>
>
> I referred some documentation and I didn't see the word "immediately"
> anywhere.
>
> The pads needs to be un-reffed after use, but I don't think "immediate
> free" is a requirement. It's bad design upfront and I think gstreamer
> folks would have known it. Can you provide link to documentation which
> made you think it should be "immediately" un-reffed and not when
> function returns (which happens in case g_autoptr is used)?
>

I'm not a gstreamer expert, so I just followed the gstreamer example code,
https://gstreamer.freedesktop.org/documentation/tutorials/basic/multithreading-and-pad-availability.html?gi-language=c
It unrefs immediately. @Nicolas Dufresne <nicolas@ndufresne.ca> can you
confirm this ?

> linked, and if I use an autoptr it won't unref right after the if.
> > I had thought to put the code where pads are created and linked into an
> > inner scope like this, but then it will be over kill.
> >
> > func
> > {
> >    { // code // }
> > }
>
>
> I have seen this design as well for GLib projects as well, mainly for
> g_autoptr and mutexes combine and don't think it's an overkill.
>
>
> > I assume this is a nitpick and not a blocker. I don't see much
> improvement
> > by doing so, It'd be a different scenario if I had, say 10 pads.
>
>
> Here as well, I would suggest to get a helper function if you are
> managing so many pads at once.
>

I just stated if there would be 10 pads, I'd think about that, but this
test will only ever have two pads.

I'll check if I am able to run the test on platforms available to me and
> incorporate the suggestions. This is just a reading and understanding of
> the code.
>

Thanks. Please, let me know if it works !

>
> >> +
> >>> +             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) {
> >>> +                     if (queue0_sink_pad)
> >>> +                             gst_object_unref(queue0_sink_pad);
> >>> +                     if (queue1_sink_pad)
> >>> +                             gst_object_unref(queue1_sink_pad);
> >>> +                     g_printerr("Pads could not be linked.\n");
> >>> +                     return TestFail;
> >>> +             }
> >>> +             gst_object_unref(queue0_sink_pad);
> >>> +             gst_object_unref(queue1_sink_pad);
> >>> +
> >>> +             if (startPipeline() != TestPass)
> >>> +                     return TestFail;
> >>> +
> >>> +             if (processEvent() != TestPass)
> >>> +                     return TestFail;
> >>> +
> >>> +             return TestPass;
> >>> +     }
> >>> +
> >>> +private:
> >>> +     std::string cameraName;
> >>> +     GstElement *convert0_;
> >>> +     GstElement *convert1_;
> >>> +     GstElement *sink0_;
> >>> +     GstElement *sink1_;
> >>> +     GstElement *queue0_;
> >>> +     GstElement *queue1_;
> >>> +};
> >>> +
> >>> +TEST_REGISTER(GstreamerMultiStreamTest)
> >>> diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
> >>> index aca53b920365..13652e87d05c 100644
> >>> --- a/test/gstreamer/meson.build
> >>> +++ b/test/gstreamer/meson.build
> >>> @@ -6,6 +6,7 @@ endif
> >>>
> >>>    gstreamer_tests = [
> >>>        ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
> >>> +    ['multi_stream_test',   'gstreamer_multi_stream_test.cpp'],
> >>>    ]
> >>>    gstreamer_dep = dependency('gstreamer-1.0', required: true)
> >>>
> > Regards,
> > Vedant Paranjape
> >
>

Regards,
Vedant Paranjape

Patch
diff mbox series

diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp b/test/gstreamer/gstreamer_multi_stream_test.cpp
new file mode 100644
index 000000000000..aba86a3f8e27
--- /dev/null
+++ b/test/gstreamer/gstreamer_multi_stream_test.cpp
@@ -0,0 +1,138 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Vedant Paranjape
+ *
+ * gstreamer_multi_stream_test.cpp - GStreamer multi stream capture test
+ */
+
+#include <iostream>
+#include <unistd.h>
+
+#include <libcamera/base/utils.h>
+
+#include <libcamera/libcamera.h>
+
+#include "libcamera/internal/source_paths.h"
+
+#include <gst/gst.h>
+
+#include "gstreamer_test.h"
+#include "test.h"
+
+using namespace std;
+
+class GstreamerMultiStreamTest : public GstreamerTest, public Test
+{
+public:
+	GstreamerMultiStreamTest()
+		: GstreamerTest()
+	{
+	}
+
+protected:
+	int init() override
+	{
+		if (status_ != TestPass)
+			return status_;
+
+		/* Check if platform support multistream output */
+		libcamera::CameraManager cm;
+		cm.start();
+		bool cameraFound = false;
+		for (auto &camera : cm.cameras()) {
+			if (camera->streams().size() > 1) {
+				cameraName = camera->id();
+				cameraFound = true;
+				cm.stop();
+				break;
+			}
+		}
+
+		if (!cameraFound) {
+			cm.stop();
+			return TestSkip;
+		}
+
+		g_autoptr(GstElement) convert0 = gst_element_factory_make("videoconvert", "convert0");
+		g_autoptr(GstElement) convert1 = gst_element_factory_make("videoconvert", "convert1");
+		g_autoptr(GstElement) sink0 = gst_element_factory_make("fakesink", "sink0");
+		g_autoptr(GstElement) sink1 = gst_element_factory_make("fakesink", "sink1");
+		g_autoptr(GstElement) queue0 = gst_element_factory_make("queue", "queue0");
+		g_autoptr(GstElement) queue1 = gst_element_factory_make("queue", "queue1");
+		g_object_ref_sink(convert0);
+		g_object_ref_sink(convert1);
+		g_object_ref_sink(sink0);
+		g_object_ref_sink(sink1);
+		g_object_ref_sink(queue0);
+		g_object_ref_sink(queue1);
+
+		if (!convert0 || !convert1 || !sink0 || !sink1 || !queue0 || !queue1) {
+			g_printerr("Not all elements could be created. %p.%p.%p.%p.%p.%p\n",
+				   convert0, convert1, sink0, sink1, queue0, queue1);
+
+			return TestFail;
+		}
+
+		convert0_ = reinterpret_cast<GstElement *>(g_steal_pointer(&convert0));
+		convert1_ = reinterpret_cast<GstElement *>(g_steal_pointer(&convert1));
+		sink0_ = reinterpret_cast<GstElement *>(g_steal_pointer(&sink0));
+		sink1_ = reinterpret_cast<GstElement *>(g_steal_pointer(&sink1));
+		queue0_ = reinterpret_cast<GstElement *>(g_steal_pointer(&queue0));
+		queue1_ = reinterpret_cast<GstElement *>(g_steal_pointer(&queue1));
+
+		if (createPipeline() != TestPass)
+			return TestFail;
+
+		return TestPass;
+	}
+
+	int run() override
+	{
+		g_object_set(libcameraSrc_, "camera-name", cameraName.c_str(), NULL);
+
+		/* Build the pipeline */
+		gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, queue0_, queue1_,
+				 convert0_, convert1_, sink0_, sink1_, NULL);
+		if (gst_element_link_many(queue0_, convert0_, sink0_, NULL) != TRUE ||
+		    gst_element_link_many(queue1_, convert1_, sink1_, NULL) != TRUE) {
+			g_printerr("Elements could not be linked.\n");
+			return TestFail;
+		}
+
+		g_autoptr(GstPad) src_pad = gst_element_get_static_pad(libcameraSrc_, "src");
+		g_autoptr(GstPad) request_pad = gst_element_get_request_pad(libcameraSrc_, "src_%u");
+		GstPad *queue0_sink_pad = gst_element_get_static_pad(queue0_, "sink");
+		GstPad *queue1_sink_pad = gst_element_get_static_pad(queue1_, "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) {
+			if (queue0_sink_pad)
+				gst_object_unref(queue0_sink_pad);
+			if (queue1_sink_pad)
+				gst_object_unref(queue1_sink_pad);
+			g_printerr("Pads could not be linked.\n");
+			return TestFail;
+		}
+		gst_object_unref(queue0_sink_pad);
+		gst_object_unref(queue1_sink_pad);
+
+		if (startPipeline() != TestPass)
+			return TestFail;
+
+		if (processEvent() != TestPass)
+			return TestFail;
+
+		return TestPass;
+	}
+
+private:
+	std::string cameraName;
+	GstElement *convert0_;
+	GstElement *convert1_;
+	GstElement *sink0_;
+	GstElement *sink1_;
+	GstElement *queue0_;
+	GstElement *queue1_;
+};
+
+TEST_REGISTER(GstreamerMultiStreamTest)
diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
index aca53b920365..13652e87d05c 100644
--- a/test/gstreamer/meson.build
+++ b/test/gstreamer/meson.build
@@ -6,6 +6,7 @@  endif
 
 gstreamer_tests = [
     ['single_stream_test',   'gstreamer_single_stream_test.cpp'],
+    ['multi_stream_test',   'gstreamer_multi_stream_test.cpp'],
 ]
 gstreamer_dep = dependency('gstreamer-1.0', required: true)