1 /*
2  * GStreamer
3  * Copyright (C) 2008 Nokia Corporation <multimedia@maemo.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /**
22  * SECTION:camerabingeneral
23  * @short_description: helper functions for #GstCameraBin and it's modules
24  *
25  * Common helper functions for #GstCameraBin, #GstCameraBinImage and
26  * #GstCameraBinVideo.
27  *
28  */
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32 
33 #include <gst/app/gstappsrc.h>
34 #include <gst/app/gstappsink.h>
35 #include <gst/glib-compat-private.h>
36 #include "gstcamerabinpreview.h"
37 #include "gstbasecamerasrc.h"
38 
39 GST_DEBUG_CATEGORY_EXTERN (base_camera_src_debug);
40 #define GST_CAT_DEFAULT base_camera_src_debug
41 
42 static void _gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData *
43     preview, GstCaps * caps);
44 
45 static gboolean
bus_callback(GstBus * bus,GstMessage * message,gpointer user_data)46 bus_callback (GstBus * bus, GstMessage * message, gpointer user_data)
47 {
48   switch (GST_MESSAGE_TYPE (message)) {
49     case GST_MESSAGE_ERROR:{
50       GError *err;
51       GstCameraBinPreviewPipelineData *data;
52 
53       data = user_data;
54 
55       gst_message_parse_error (message, &err, NULL);
56       GST_WARNING ("Error from preview pipeline: %s", err->message);
57       g_error_free (err);
58 
59       /* TODO Not sure if we should post an Error or Warning here */
60       GST_ELEMENT_ERROR (data, CORE, FAILED,
61           ("fatal error in preview pipeline, disposing the pipeline"), (NULL));
62 
63       /* Possible error situations:
64        * 1) cond_wait pending. prevent deadlock by signalling the cond
65        * 2) preview_pipeline_post called with new buffer to handle. returns
66        *    because data->pipeline is set to null
67        * 3) new preview caps incoming. returns because data->pipeline is null
68        */
69 
70       if (data->pipeline) {
71         gst_element_set_state (data->pipeline, GST_STATE_NULL);
72         gst_object_unref (data->pipeline);
73         data->pipeline = NULL;
74       }
75 
76       g_cond_signal (&data->processing_cond);
77 
78       break;
79     }
80     default:
81       break;
82   }
83   return TRUE;
84 }
85 
86 static GstFlowReturn
gst_camerabin_preview_pipeline_new_sample(GstAppSink * appsink,gpointer user_data)87 gst_camerabin_preview_pipeline_new_sample (GstAppSink * appsink,
88     gpointer user_data)
89 {
90   GstSample *sample;
91   GstStructure *s;
92   GstMessage *msg;
93   GstCameraBinPreviewPipelineData *data;
94 
95   data = user_data;
96 
97   sample = gst_app_sink_pull_sample (appsink);
98   s = gst_structure_new (GST_BASE_CAMERA_SRC_PREVIEW_MESSAGE_NAME,
99       "sample", GST_TYPE_SAMPLE, sample, NULL);
100   gst_sample_unref (sample);
101   msg = gst_message_new_element (GST_OBJECT (data->element), s);
102 
103   GST_DEBUG_OBJECT (data->element, "sending message with preview image");
104   if (gst_element_post_message (data->element, msg) == FALSE) {
105     GST_WARNING_OBJECT (data->element,
106         "This element has no bus, therefore no message sent!");
107   }
108 
109   g_mutex_lock (&data->processing_lock);
110 
111   data->processing--;
112   if (data->processing == 0)
113     g_cond_signal (&data->processing_cond);
114 
115   g_mutex_unlock (&data->processing_lock);
116 
117   return GST_FLOW_OK;
118 }
119 
120 /**
121  * gst_camerabin_create_preview_pipeline:
122  * @element: Owner of this pipeline
123  * @filter: Custom filter to process preview data (an extra ref is taken)
124  *
125  * Creates a new previewing pipeline that can receive buffers
126  * to be posted as camerabin preview messages for @element
127  *
128  * Returns: The newly created #GstCameraBinPreviewPipelineData
129  */
130 GstCameraBinPreviewPipelineData *
gst_camerabin_create_preview_pipeline(GstElement * element,GstElement * filter)131 gst_camerabin_create_preview_pipeline (GstElement * element,
132     GstElement * filter)
133 {
134   GstCameraBinPreviewPipelineData *data;
135   GstElement *csp;
136   GstElement *vscale;
137   gboolean added = FALSE;
138   gboolean linkfail = FALSE;
139   GstBus *bus;
140   GstAppSinkCallbacks callbacks = { 0, };
141 
142   data = g_new0 (GstCameraBinPreviewPipelineData, 1);
143 
144   data->pipeline = gst_pipeline_new ("preview-pipeline");
145   data->appsrc = gst_element_factory_make ("appsrc", "preview-appsrc");
146   data->appsink = gst_element_factory_make ("appsink", "preview-appsink");
147   csp = gst_element_factory_make ("videoconvert", "preview-vconv");
148   vscale = gst_element_factory_make ("videoscale", "preview-vscale");
149 
150   if (!data->appsrc || !data->appsink || !csp || !vscale) {
151     goto error;
152   }
153 
154   g_object_set (data->appsrc, "emit-signals", FALSE, NULL);
155   g_object_set (data->appsink, "sync", FALSE, "enable-last-sample",
156       FALSE, NULL);
157 
158   gst_bin_add_many (GST_BIN (data->pipeline), data->appsrc,
159       data->appsink, csp, vscale, NULL);
160   if (filter)
161     gst_bin_add (GST_BIN (data->pipeline), gst_object_ref (filter));
162   added = TRUE;
163 
164   if (filter) {
165     linkfail |=
166         GST_PAD_LINK_FAILED (gst_element_link_pads_full (data->appsrc, "src",
167             filter, NULL, GST_PAD_LINK_CHECK_NOTHING));
168     linkfail |=
169         GST_PAD_LINK_FAILED (gst_element_link_pads_full (filter, NULL,
170             vscale, "sink", GST_PAD_LINK_CHECK_CAPS));
171   } else {
172     linkfail |=
173         GST_PAD_LINK_FAILED (gst_element_link_pads_full (data->appsrc, "src",
174             vscale, "sink", GST_PAD_LINK_CHECK_NOTHING));
175   }
176 
177   linkfail |=
178       GST_PAD_LINK_FAILED (gst_element_link_pads_full (vscale, "src", csp,
179           "sink", GST_PAD_LINK_CHECK_NOTHING));
180   linkfail |=
181       GST_PAD_LINK_FAILED (gst_element_link_pads_full (csp, "src",
182           data->appsink, "sink", GST_PAD_LINK_CHECK_NOTHING));
183 
184   if (linkfail) {
185     GST_WARNING ("Failed to link preview pipeline elements");
186     goto error;
187   }
188 
189   callbacks.new_sample = gst_camerabin_preview_pipeline_new_sample;
190   gst_app_sink_set_callbacks ((GstAppSink *) data->appsink, &callbacks, data,
191       NULL);
192 
193   bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline));
194   gst_bus_add_watch (bus, bus_callback, data);
195   gst_object_unref (bus);
196 
197   g_object_set (data->appsink, "sync", FALSE, NULL);
198 
199   data->element = element;
200   data->filter = filter;
201   data->vscale = vscale;
202 
203   g_mutex_init (&data->processing_lock);
204   g_cond_init (&data->processing_cond);
205 
206   data->pending_preview_caps = NULL;
207   data->processing = 0;
208 
209   return data;
210 error:
211   GST_WARNING ("Failed to create camerabin's preview pipeline");
212   if (!added) {
213     if (csp)
214       gst_object_unref (csp);
215     if (vscale)
216       gst_object_unref (vscale);
217     if (data->appsrc)
218       gst_object_unref (data->appsrc);
219     if (data->appsink)
220       gst_object_unref (data->appsink);
221   }
222   gst_camerabin_destroy_preview_pipeline (data);
223   return NULL;
224 }
225 
226 /**
227  * gst_camerabin_destroy_preview_pipeline:
228  * @preview: the #GstCameraBinPreviewPipelineData
229  *
230  * Frees a #GstCameraBinPreviewPipelineData
231  */
232 void
gst_camerabin_destroy_preview_pipeline(GstCameraBinPreviewPipelineData * preview)233 gst_camerabin_destroy_preview_pipeline (GstCameraBinPreviewPipelineData *
234     preview)
235 {
236   g_return_if_fail (preview != NULL);
237 
238   g_mutex_clear (&preview->processing_lock);
239   g_cond_clear (&preview->processing_cond);
240 
241   if (preview->pipeline) {
242     GstBus *bus;
243 
244     gst_element_set_state (preview->pipeline, GST_STATE_NULL);
245 
246     bus = gst_pipeline_get_bus (GST_PIPELINE (preview->pipeline));
247     gst_bus_remove_watch (bus);
248     gst_object_unref (bus);
249 
250     gst_object_unref (preview->pipeline);
251   }
252   g_free (preview);
253 }
254 
255 /**
256  * gst_camerabin_preview_pipeline_post:
257  * @preview: the #GstCameraBinPreviewPipelineData
258  * @sample: the sample to be posted as a preview
259  *
260  * Converts the @sample to the desired format and posts the preview
261  * message to the bus.
262  *
263  * Returns: %TRUE on success
264  */
265 gboolean
gst_camerabin_preview_pipeline_post(GstCameraBinPreviewPipelineData * preview,GstSample * sample)266 gst_camerabin_preview_pipeline_post (GstCameraBinPreviewPipelineData * preview,
267     GstSample * sample)
268 {
269   g_return_val_if_fail (preview != NULL, FALSE);
270   g_return_val_if_fail (preview->pipeline != NULL, FALSE);
271   g_return_val_if_fail (sample, FALSE);
272 
273   g_mutex_lock (&preview->processing_lock);
274   g_return_val_if_fail (preview->pipeline != NULL, FALSE);
275 
276   if (preview->pending_preview_caps) {
277     if (preview->processing > 0) {
278       g_cond_wait (&preview->processing_cond, &preview->processing_lock);
279     }
280     _gst_camerabin_preview_set_caps (preview, preview->pending_preview_caps);
281     gst_caps_replace (&preview->pending_preview_caps, NULL);
282   }
283 
284   preview->processing++;
285 
286   g_object_set (preview->appsrc, "caps", gst_sample_get_caps (sample), NULL);
287   gst_app_src_push_buffer ((GstAppSrc *) preview->appsrc,
288       gst_buffer_ref (gst_sample_get_buffer (sample)));
289 
290   g_mutex_unlock (&preview->processing_lock);
291 
292   return TRUE;
293 }
294 
295 static void
_gst_camerabin_preview_set_caps(GstCameraBinPreviewPipelineData * preview,GstCaps * caps)296 _gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * preview,
297     GstCaps * caps)
298 {
299   GstState state, pending;
300   GstStateChangeReturn ret;
301 
302   g_return_if_fail (preview != NULL);
303   g_return_if_fail (preview->pipeline != NULL);
304 
305   ret = gst_element_get_state (preview->pipeline, &state, &pending, 0);
306   if (ret == GST_STATE_CHANGE_FAILURE) {
307     /* make it try again */
308     state = GST_STATE_PLAYING;
309     pending = GST_STATE_VOID_PENDING;
310   }
311   gst_element_set_state (preview->pipeline, GST_STATE_NULL);
312   g_object_set (preview->appsink, "caps", caps, NULL);
313   if (pending != GST_STATE_VOID_PENDING)
314     state = pending;
315   gst_element_set_state (preview->pipeline, state);
316 }
317 
318 /**
319  * gst_camerabin_preview_set_caps:
320  * @preview: the #GstCameraBinPreviewPipelineData
321  * @caps: the #GstCaps to be set (a new ref will be taken)
322  *
323  * The caps that preview buffers should have when posted
324  * on the bus
325  */
326 void
gst_camerabin_preview_set_caps(GstCameraBinPreviewPipelineData * preview,GstCaps * caps)327 gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * preview,
328     GstCaps * caps)
329 {
330   g_return_if_fail (preview != NULL);
331 
332   g_mutex_lock (&preview->processing_lock);
333 
334   if (preview->processing == 0) {
335     _gst_camerabin_preview_set_caps (preview, caps);
336   } else {
337     GST_DEBUG ("Preview pipeline busy, storing new caps as pending");
338     gst_caps_replace (&preview->pending_preview_caps, caps);
339   }
340   g_mutex_unlock (&preview->processing_lock);
341 }
342 
343 /**
344  * gst_camerabin_preview_set_filter:
345  * @preview: the #GstCameraBinPreviewPipelineData
346  * @filter: Custom filter to process preview data (an extra ref is taken)
347  *
348  * Set the filter element into preview pipeline.
349  *
350  * Returns: %TRUE on success
351  */
352 gboolean
gst_camerabin_preview_set_filter(GstCameraBinPreviewPipelineData * preview,GstElement * filter)353 gst_camerabin_preview_set_filter (GstCameraBinPreviewPipelineData * preview,
354     GstElement * filter)
355 {
356   gboolean ret = TRUE;
357   GstState current;
358 
359   g_return_val_if_fail (preview != NULL, FALSE);
360 
361   GST_DEBUG ("Preview pipeline setting new filter %p", filter);
362 
363   g_mutex_lock (&preview->processing_lock);
364 
365   gst_element_get_state (preview->pipeline, &current, NULL, 0);
366 
367   if (preview->processing == 0 && current == GST_STATE_NULL) {
368     gboolean linkfail = FALSE;
369 
370     if (preview->filter) {
371       /* Unlink and remove old filter */
372       gst_element_unlink (preview->appsrc, preview->filter);
373       gst_element_unlink (preview->filter, preview->vscale);
374       gst_bin_remove (GST_BIN (preview->pipeline), preview->filter);
375     } else {
376       /* Make room for filter by breaking the link between appsrc and vcale */
377       gst_element_unlink (preview->appsrc, preview->vscale);
378     }
379 
380     if (filter) {
381       /* Add and link the new filter between appsrc and vscale */
382       gst_bin_add (GST_BIN (preview->pipeline), gst_object_ref (filter));
383 
384       linkfail |=
385           GST_PAD_LINK_FAILED (gst_element_link_pads_full (preview->appsrc,
386               "src", filter, NULL, GST_PAD_LINK_CHECK_NOTHING));
387 
388       linkfail |=
389           GST_PAD_LINK_FAILED (gst_element_link_pads_full (filter, NULL,
390               preview->vscale, "sink", GST_PAD_LINK_CHECK_CAPS));
391     } else {
392       /* No filter was given. Just link the appsrc to vscale directly */
393       linkfail |=
394           GST_PAD_LINK_FAILED (gst_element_link_pads_full (preview->appsrc,
395               "src", preview->vscale, "sink", GST_PAD_LINK_CHECK_NOTHING));
396     }
397 
398     if (linkfail) {
399       GST_WARNING ("Linking the filter to pipeline failed");
400       ret = FALSE;
401     } else {
402       GST_DEBUG ("Linking the filter to pipeline successful");
403       preview->filter = filter;
404     }
405   } else {
406     GST_WARNING ("Cannot change filter when pipeline is running");
407     ret = FALSE;
408   }
409   g_mutex_unlock (&preview->processing_lock);
410 
411   return ret;
412 }
413