1 /* GStreamer
2  *
3  * Unit tests for glimagesink
4  *
5  * Copyright (C) 2014 Julien Isorce <j.isorce@samsung.com>
6  * Copyright (C) 2016 Matthew Waters <matthew@centricular.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include <gst/gst.h>
30 #include <gst/video/video.h>
31 #include <gst/check/gstcheck.h>
32 
33 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
34     GST_PAD_SRC,
35     GST_PAD_ALWAYS,
36     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGBA"))
37     );
38 
39 static GstElement *sinkelement = NULL;
40 static GstPad *srcpad = NULL;
41 
42 #define PAD_FUNC(name, type, param, check) \
43   static gpointer do_##name##_func (type * param) { \
44   fail_unless (gst_pad_##name (srcpad, param) == check); \
45   return NULL; \
46 }
47 
48 /* *INDENT-OFF* */
49 PAD_FUNC (peer_query, GstQuery, query, TRUE)
50 PAD_FUNC (push, GstBuffer, buf, GST_FLOW_OK)
51 /* *INDENT-ON* */
52 
53 /* On OSX it's required to have a main loop running in the
54  * main thread while using the display connection.
55  * So the call that use this display connection needs to be
56  * done in another thread.
57  * On other platforms a direct call can be done. */
58 #ifdef __APPLE__
59 static GMainLoop *loop;
60 static GThread *thread;
61 static GMutex lock;
62 static GCond cond;
63 
64 static gboolean
_unlock_mutex(gpointer * unused)65 _unlock_mutex (gpointer * unused)
66 {
67   g_cond_broadcast (&cond);
68   g_mutex_unlock (&lock);
69 
70   return G_SOURCE_REMOVE;
71 }
72 
73 static gpointer
_thread_main(gpointer * unused)74 _thread_main (gpointer * unused)
75 {
76   g_mutex_lock (&lock);
77   g_idle_add ((GSourceFunc) _unlock_mutex, NULL);
78 
79   g_main_loop_run (loop);
80 
81   g_main_loop_unref (loop);
82   loop = NULL;
83 
84   return NULL;
85 }
86 
87 static void
_start_thread(void)88 _start_thread (void)
89 {
90   loop = g_main_loop_new (NULL, FALSE);
91   g_mutex_init (&lock);
92   g_cond_init (&cond);
93 
94   thread = g_thread_new ("GLOSXTestThread", (GThreadFunc) _thread_main, NULL);
95 
96   g_mutex_lock (&lock);
97   while (!loop) {
98     g_cond_wait (&cond, &lock);
99   }
100   g_mutex_unlock (&lock);
101 }
102 
103 static void
_stop_thread(void)104 _stop_thread (void)
105 {
106   g_main_loop_quit (loop);
107   g_thread_join (thread);
108 
109   g_mutex_clear (&lock);
110   g_cond_clear (&cond);
111 }
112 #endif
113 
114 static void
setup_glimagesink(void)115 setup_glimagesink (void)
116 {
117   GstCaps *caps = NULL;
118 
119   sinkelement = gst_check_setup_element ("glimagesink");
120   srcpad = gst_check_setup_src_pad (sinkelement, &srctemplate);
121   gst_pad_set_active (srcpad, TRUE);
122 
123   caps =
124       gst_caps_from_string
125       ("video/x-raw, width=320, height=240, format=RGBA, framerate=30/1");
126   gst_check_setup_events (srcpad, sinkelement, caps, GST_FORMAT_TIME);
127   gst_caps_unref (caps);
128 }
129 
130 static void
cleanup_glimagesink(void)131 cleanup_glimagesink (void)
132 {
133   gst_element_set_state (sinkelement, GST_STATE_NULL);
134   gst_element_get_state (sinkelement, NULL, NULL, GST_CLOCK_TIME_NONE);
135   gst_pad_set_active (srcpad, FALSE);
136   gst_check_teardown_src_pad (sinkelement);
137   gst_check_teardown_element (sinkelement);
138 }
139 
140 /* Verify that glimagesink releases the buffers it currently
141  * owns, upon a drain query. */
GST_START_TEST(test_query_drain)142 GST_START_TEST (test_query_drain)
143 {
144   GstBuffer *buf = NULL;
145   GstBufferPool *originpool = NULL;
146   GstBufferPool *pool = NULL;
147   GstCaps *caps = NULL;
148   GstQuery *query = NULL;
149   GstStructure *config = NULL;
150   gint i = 0;
151   guint min = 0;
152   guint max = 0;
153   guint size = 0;
154   const gint maxbuffers = 4;
155 
156 #ifdef __APPLE__
157   _start_thread ();
158 #endif
159 
160   /* GstBaseSink handles the drain query as well. */
161   g_object_set (sinkelement, "enable-last-sample", TRUE, NULL);
162 
163   ASSERT_SET_STATE (sinkelement, GST_STATE_PLAYING, GST_STATE_CHANGE_ASYNC);
164 
165   caps = gst_pad_get_current_caps (srcpad);
166   fail_unless (gst_caps_is_fixed (caps));
167 
168   /* Let's retrieve the GstGLBufferPool to change its min
169    * and max nb buffers. For that just send an allocation
170    * query and change the pool config. */
171   query = gst_query_new_allocation (caps, TRUE);
172   do_peer_query_func (query);
173 
174   fail_unless (gst_query_get_n_allocation_pools (query) == 1);
175 
176   gst_query_parse_nth_allocation_pool (query, 0, &originpool, &size, &min,
177       &max);
178   fail_unless (originpool != NULL);
179   gst_query_unref (query);
180 
181   config = gst_buffer_pool_get_config (originpool);
182   gst_buffer_pool_config_set_params (config, caps, size, maxbuffers,
183       maxbuffers);
184   fail_unless (gst_buffer_pool_set_config (originpool, config));
185 
186   /* The gl pool is setup and ready to be activated. */
187   fail_unless (gst_buffer_pool_set_active (originpool, TRUE));
188 
189   /* Let's build an upstream pool that will be feed with
190    * gl buffers. */
191   pool = gst_buffer_pool_new ();
192   config = gst_buffer_pool_get_config (pool);
193   gst_buffer_pool_config_set_params (config, caps, size, maxbuffers,
194       maxbuffers);
195   fail_unless (gst_buffer_pool_set_config (pool, config));
196   gst_caps_unref (caps);
197 
198   fail_unless (gst_buffer_pool_set_active (pool, TRUE));
199 
200   /* Unpopulate the pool and forget about its initial buffers
201    * It is necessary because the pool has to know there are
202    * N outstanding buffers. */
203   for (i = 0; i < maxbuffers; ++i) {
204     fail_unless (gst_buffer_pool_acquire_buffer (pool, &buf,
205             NULL) == GST_FLOW_OK);
206     gst_object_replace ((GstObject **) & buf->pool, NULL);
207     gst_buffer_unref (buf);
208   }
209 
210   /* Transfer buffers from the gl pool to the upstream pool. */
211   for (i = 0; i < maxbuffers; ++i) {
212     fail_unless (gst_buffer_pool_acquire_buffer (originpool, &buf,
213             NULL) == GST_FLOW_OK);
214     gst_object_replace ((GstObject **) & buf->pool, (GstObject *) pool);
215     gst_buffer_unref (buf);
216   }
217 
218   /* Push a lot of buffers like if a real pipeline was running. */
219   for (i = 0; i < 10 * maxbuffers; ++i) {
220     fail_unless (gst_buffer_pool_acquire_buffer (pool, &buf,
221             NULL) == GST_FLOW_OK);
222     do_push_func (buf);
223   }
224 
225   /* Claim back buffers to the upstream pool. This is the point
226    * of this unit test, i.e. this test checks that glimagesink
227    * releases the buffers it currently owns, upon drain query. */
228   query = gst_query_new_drain ();
229   do_peer_query_func (query);
230   gst_query_unref (query);
231 
232   /* Transfer buffers back to the downstream pool to be release
233    * properly. This also make sure that all buffers are returned.
234    * Indeed gst_buffer_pool_acquire_buffer is blocking here and
235    * we have set a maximum. */
236   for (i = 0; i < maxbuffers; ++i) {
237     fail_unless (gst_buffer_pool_acquire_buffer (pool, &buf,
238             NULL) == GST_FLOW_OK);
239     gst_object_replace ((GstObject **) & buf->pool, (GstObject *) originpool);
240     gst_buffer_unref (buf);
241   }
242 
243   fail_unless (gst_buffer_pool_set_active (originpool, FALSE));
244   gst_object_unref (originpool);
245 
246   /* At this point the gl pool contains all its buffers. We can
247    * inactivate it to release the textures. Note that only the gl
248    * pool can release the textures properly because it has a
249    * reference on the gl context. */
250   fail_unless (gst_buffer_pool_set_active (pool, FALSE));
251   gst_object_unref (pool);
252 
253 #ifdef __APPLE__
254   _stop_thread ();
255 #endif
256 }
257 
258 GST_END_TEST;
259 
260 static Suite *
glimagesink_suite(void)261 glimagesink_suite (void)
262 {
263   Suite *s = suite_create ("glimagesink");
264   TCase *tc = tcase_create ("general");
265 
266   tcase_set_timeout (tc, 5);
267 
268   tcase_add_checked_fixture (tc, setup_glimagesink, cleanup_glimagesink);
269   tcase_add_test (tc, test_query_drain);
270   suite_add_tcase (s, tc);
271 
272   return s;
273 }
274 
275 int
main(int argc,char ** argv)276 main (int argc, char **argv)
277 {
278   Suite *s;
279   g_setenv ("GST_GL_XINITTHREADS", "1", TRUE);
280   gst_check_init (&argc, &argv);
281   s = glimagesink_suite ();
282   return gst_check_run_suite (s, "glimagesink", __FILE__);
283 }
284