| /* |
| * GStreamer |
| * Copyright (C) 2010 Texas Instruments, Inc |
| * Copyright (C) 2011 Thiago Santos <thiago.sousa.santos@collabora.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| |
| /** |
| * SECTION:element-wrappercamerabinsrc |
| * |
| * A camera bin src element that wraps a default video source with a single |
| * pad into the 3pad model that camerabin2 expects. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif |
| |
| #include <gst/interfaces/photography.h> |
| #include <gst/gst-i18n-plugin.h> |
| |
| #include "gstwrappercamerabinsrc.h" |
| #include "gstdigitalzoom.h" |
| #include "camerabingeneral.h" |
| |
| enum |
| { |
| PROP_0, |
| PROP_VIDEO_SRC, |
| PROP_VIDEO_SRC_FILTER |
| }; |
| |
| GST_DEBUG_CATEGORY (wrapper_camera_bin_src_debug); |
| #define GST_CAT_DEFAULT wrapper_camera_bin_src_debug |
| |
| #define gst_wrapper_camera_bin_src_parent_class parent_class |
| G_DEFINE_TYPE (GstWrapperCameraBinSrc, gst_wrapper_camera_bin_src, |
| GST_TYPE_BASE_CAMERA_SRC); |
| |
| static GstStaticPadTemplate vfsrc_template = |
| GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME, |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS_ANY); |
| |
| static GstStaticPadTemplate imgsrc_template = |
| GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME, |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS_ANY); |
| |
| static GstStaticPadTemplate vidsrc_template = |
| GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME, |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS_ANY); |
| |
| static void set_capsfilter_caps (GstWrapperCameraBinSrc * self, |
| GstCaps * new_caps); |
| |
| static void |
| gst_wrapper_camera_bin_src_dispose (GObject * object) |
| { |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (object); |
| |
| if (self->src_pad) { |
| gst_object_unref (self->src_pad); |
| self->src_pad = NULL; |
| } |
| if (self->video_tee_sink) { |
| gst_object_unref (self->video_tee_sink); |
| self->video_tee_sink = NULL; |
| } |
| if (self->video_tee_vf_pad) { |
| gst_object_unref (self->video_tee_vf_pad); |
| self->video_tee_vf_pad = NULL; |
| } |
| if (self->app_vid_src) { |
| gst_object_unref (self->app_vid_src); |
| self->app_vid_src = NULL; |
| } |
| if (self->app_vid_filter) { |
| gst_object_unref (self->app_vid_filter); |
| self->app_vid_filter = NULL; |
| } |
| if (self->srcfilter_pad) { |
| gst_object_unref (self->srcfilter_pad); |
| self->srcfilter_pad = NULL; |
| } |
| gst_caps_replace (&self->image_capture_caps, NULL); |
| |
| G_OBJECT_CLASS (parent_class)->dispose (object); |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_finalize (GstWrapperCameraBinSrc * self) |
| { |
| G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (self)); |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_set_property (GObject * object, |
| guint prop_id, const GValue * value, GParamSpec * pspec) |
| { |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (object); |
| |
| switch (prop_id) { |
| case PROP_VIDEO_SRC: |
| if (GST_STATE (self) != GST_STATE_NULL) { |
| GST_ELEMENT_ERROR (self, CORE, FAILED, |
| ("camerasrc must be in NULL state when setting the video source element"), |
| (NULL)); |
| } else { |
| if (self->app_vid_src) |
| gst_object_unref (self->app_vid_src); |
| self->app_vid_src = g_value_get_object (value); |
| if (self->app_vid_src) |
| gst_object_ref (self->app_vid_src); |
| } |
| break; |
| case PROP_VIDEO_SRC_FILTER: |
| if (GST_STATE (self) != GST_STATE_NULL) { |
| GST_ELEMENT_ERROR (self, CORE, FAILED, |
| ("camerasrc must be in NULL state when setting the video source filter element"), |
| (NULL)); |
| } else { |
| if (self->app_vid_filter) |
| gst_object_unref (self->app_vid_filter); |
| self->app_vid_filter = g_value_get_object (value); |
| if (self->app_vid_filter) |
| gst_object_ref (self->app_vid_filter); |
| } |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_get_property (GObject * object, |
| guint prop_id, GValue * value, GParamSpec * pspec) |
| { |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (object); |
| |
| switch (prop_id) { |
| case PROP_VIDEO_SRC: |
| if (self->src_vid_src) |
| g_value_set_object (value, self->src_vid_src); |
| else |
| g_value_set_object (value, self->app_vid_src); |
| break; |
| case PROP_VIDEO_SRC_FILTER: |
| if (self->video_filter) |
| g_value_set_object (value, self->video_filter); |
| else |
| g_value_set_object (value, self->app_vid_filter); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_reset_src_zoom (GstWrapperCameraBinSrc * self) |
| { |
| if (self->src_crop) { |
| g_object_set (self->src_crop, "top", 0, "left", 0, "bottom", 0, "right", 0, |
| NULL); |
| } |
| } |
| |
| static void |
| gst_wrapper_camera_bin_reset_video_src_caps (GstWrapperCameraBinSrc * self, |
| GstCaps * new_filter_caps) |
| { |
| GST_DEBUG_OBJECT (self, "Resetting src caps to %" GST_PTR_FORMAT, |
| new_filter_caps); |
| if (self->src_vid_src) { |
| GstCaps *src_neg_caps; /* negotiated caps on src_filter */ |
| gboolean ret = FALSE; |
| |
| /* After pipe was negotiated src_filter do not have any filter caps. |
| * In this situation we should compare negotiated caps on capsfilter pad |
| * with requested range of caps. If one of this caps intersect, |
| * then we can avoid reseting. |
| */ |
| src_neg_caps = gst_pad_get_current_caps (self->srcfilter_pad); |
| if (src_neg_caps && new_filter_caps && gst_caps_is_fixed (new_filter_caps)) |
| ret = gst_caps_can_intersect (src_neg_caps, new_filter_caps); |
| else if (new_filter_caps == NULL) { |
| /* If new_filter_caps = NULL, then some body wont to empty |
| * capsfilter (set to ANY). In this case we will need to reset pipe, |
| * but if capsfilter is actually empthy, then we can avoid |
| * one more reseting. |
| */ |
| GstCaps *old_filter_caps; /* range of caps on capsfilter */ |
| |
| g_object_get (G_OBJECT (self->src_filter), |
| "caps", &old_filter_caps, NULL); |
| ret = gst_caps_is_any (old_filter_caps); |
| gst_caps_unref (old_filter_caps); |
| } |
| if (src_neg_caps) |
| gst_caps_unref (src_neg_caps); |
| |
| if (ret) { |
| GST_DEBUG_OBJECT (self, "Negotiated caps on srcfilter intersect " |
| "with requested caps, do not reset it."); |
| return; |
| } |
| |
| set_capsfilter_caps (self, new_filter_caps); |
| } |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_set_output (GstWrapperCameraBinSrc * self, |
| GstPad * old_pad, GstPad * output_pad) |
| { |
| GstQuery *drain = gst_query_new_drain (); |
| gst_pad_peer_query (self->src_pad, drain); |
| gst_query_unref (drain); |
| |
| if (old_pad) |
| gst_ghost_pad_set_target (GST_GHOST_PAD (old_pad), NULL); |
| if (output_pad) |
| gst_ghost_pad_set_target (GST_GHOST_PAD (output_pad), self->src_pad); |
| } |
| |
| /** |
| * gst_wrapper_camera_bin_src_imgsrc_probe: |
| * |
| * Buffer probe called before sending each buffer to image queue. |
| */ |
| static GstPadProbeReturn |
| gst_wrapper_camera_bin_src_imgsrc_probe (GstPad * pad, GstPadProbeInfo * info, |
| gpointer data) |
| { |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (data); |
| GstBaseCameraSrc *camerasrc = GST_BASE_CAMERA_SRC (data); |
| GstBuffer *buffer = GST_BUFFER (info->data); |
| GstPadProbeReturn ret = GST_PAD_PROBE_DROP; |
| |
| GST_LOG_OBJECT (self, "Image probe, mode %d, capture count %d bufsize: %" |
| G_GSIZE_FORMAT, camerasrc->mode, self->image_capture_count, |
| gst_buffer_get_size (buffer)); |
| |
| g_mutex_lock (&camerasrc->capturing_mutex); |
| if (self->image_capture_count > 0) { |
| GstSample *sample; |
| GstCaps *caps; |
| ret = GST_PAD_PROBE_OK; |
| self->image_capture_count--; |
| |
| /* post preview */ |
| /* TODO This can likely be optimized if the viewfinder caps is the same as |
| * the preview caps, avoiding another scaling of the same buffer. */ |
| GST_DEBUG_OBJECT (self, "Posting preview for image"); |
| caps = gst_pad_get_current_caps (pad); |
| sample = gst_sample_new (buffer, caps, NULL, NULL); |
| gst_base_camera_src_post_preview (camerasrc, sample); |
| gst_caps_unref (caps); |
| gst_sample_unref (sample); |
| |
| if (self->image_capture_count == 0) { |
| GstCaps *anycaps = gst_caps_new_any (); |
| |
| /* Get back to viewfinder */ |
| gst_wrapper_camera_bin_src_reset_src_zoom (self); |
| gst_wrapper_camera_bin_reset_video_src_caps (self, anycaps); |
| gst_wrapper_camera_bin_src_set_output (self, self->imgsrc, self->vfsrc); |
| gst_base_camera_src_finish_capture (camerasrc); |
| |
| gst_caps_unref (anycaps); |
| } |
| } |
| g_mutex_unlock (&camerasrc->capturing_mutex); |
| return ret; |
| } |
| |
| /** |
| * gst_wrapper_camera_bin_src_vidsrc_probe: |
| * |
| * Buffer probe called before sending each buffer to video queue. |
| */ |
| static GstPadProbeReturn |
| gst_wrapper_camera_bin_src_vidsrc_probe (GstPad * pad, GstPadProbeInfo * info, |
| gpointer data) |
| { |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (data); |
| GstBaseCameraSrc *camerasrc = GST_BASE_CAMERA_SRC_CAST (self); |
| GstPadProbeReturn ret = GST_PAD_PROBE_DROP; |
| GstBuffer *buffer = GST_BUFFER (info->data); |
| |
| GST_LOG_OBJECT (self, "Video probe, mode %d, capture status %d", |
| camerasrc->mode, self->video_rec_status); |
| |
| /* TODO do we want to lock for every buffer? */ |
| /* |
| * Note that we can use gst_pad_push_event here because we are a buffer |
| * probe. |
| */ |
| /* TODO shouldn't access this directly */ |
| g_mutex_lock (&camerasrc->capturing_mutex); |
| if (self->video_rec_status == GST_VIDEO_RECORDING_STATUS_DONE) { |
| /* NOP */ |
| } else if (self->video_rec_status == GST_VIDEO_RECORDING_STATUS_STARTING) { |
| GstClockTime ts; |
| GstSegment segment; |
| GstCaps *caps; |
| GstSample *sample; |
| |
| GST_DEBUG_OBJECT (self, "Starting video recording"); |
| self->video_rec_status = GST_VIDEO_RECORDING_STATUS_RUNNING; |
| |
| ts = GST_BUFFER_TIMESTAMP (buffer); |
| if (!GST_CLOCK_TIME_IS_VALID (ts)) |
| ts = 0; |
| gst_segment_init (&segment, GST_FORMAT_TIME); |
| segment.start = ts; |
| gst_pad_push_event (self->vidsrc, gst_event_new_segment (&segment)); |
| |
| /* post preview */ |
| GST_DEBUG_OBJECT (self, "Posting preview for video"); |
| caps = gst_pad_get_current_caps (pad); |
| sample = gst_sample_new (buffer, caps, NULL, NULL); |
| gst_base_camera_src_post_preview (camerasrc, sample); |
| gst_caps_unref (caps); |
| gst_sample_unref (sample); |
| |
| ret = GST_PAD_PROBE_OK; |
| } else if (self->video_rec_status == GST_VIDEO_RECORDING_STATUS_FINISHING) { |
| GstPad *peer; |
| |
| /* send eos */ |
| GST_DEBUG_OBJECT (self, "Finishing video recording, pushing eos"); |
| |
| peer = gst_pad_get_peer (self->vidsrc); |
| |
| if (peer) { |
| /* send to the peer as we don't want our pads with eos flag */ |
| gst_pad_send_event (peer, gst_event_new_eos ()); |
| gst_object_unref (peer); |
| } else { |
| GST_WARNING_OBJECT (camerasrc, "No peer pad for vidsrc"); |
| } |
| self->video_rec_status = GST_VIDEO_RECORDING_STATUS_DONE; |
| |
| gst_pad_unlink (self->src_pad, self->video_tee_sink); |
| gst_wrapper_camera_bin_src_set_output (self, self->vfsrc, self->vfsrc); |
| gst_base_camera_src_finish_capture (camerasrc); |
| } else { |
| ret = GST_PAD_PROBE_OK; |
| } |
| g_mutex_unlock (&camerasrc->capturing_mutex); |
| return ret; |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_caps_cb (GstPad * pad, GParamSpec * pspec, |
| gpointer user_data) |
| { |
| GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC (user_data); |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (user_data); |
| GstCaps *caps; |
| GstStructure *in_st = NULL; |
| |
| caps = gst_pad_get_current_caps (pad); |
| |
| GST_DEBUG_OBJECT (self, "src-filter caps changed to %" GST_PTR_FORMAT, caps); |
| |
| if (caps && gst_caps_get_size (caps)) { |
| in_st = gst_caps_get_structure (caps, 0); |
| if (in_st) { |
| gst_structure_get_int (in_st, "width", &bcamsrc->width); |
| gst_structure_get_int (in_st, "height", &bcamsrc->height); |
| |
| GST_DEBUG_OBJECT (self, "Source dimensions now: %dx%d", bcamsrc->width, |
| bcamsrc->height); |
| } |
| } |
| |
| /* Update zoom */ |
| gst_base_camera_src_setup_zoom (bcamsrc); |
| |
| if (caps) |
| gst_caps_unref (caps); |
| }; |
| |
| static void |
| gst_wrapper_camera_bin_src_max_zoom_cb (GObject * self, GParamSpec * pspec, |
| gpointer user_data) |
| { |
| GstBaseCameraSrc *bcamsrc = (GstBaseCameraSrc *) user_data; |
| |
| g_object_get (self, "max-zoom", &bcamsrc->max_zoom, NULL); |
| g_object_notify (G_OBJECT (bcamsrc), "max-zoom"); |
| } |
| |
| static gboolean |
| gst_wrapper_camera_bin_src_src_event (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| gboolean ret = TRUE; |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (parent); |
| |
| GST_DEBUG_OBJECT (self, "Handling event %p %" GST_PTR_FORMAT, event, event); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_RECONFIGURE: |
| if (pad == self->imgsrc) { |
| GST_DEBUG_OBJECT (self, "Image mode reconfigure event received"); |
| self->image_renegotiate = TRUE; |
| } else if (pad == self->vidsrc) { |
| GST_DEBUG_OBJECT (self, "Video mode reconfigure event received"); |
| self->video_renegotiate = TRUE; |
| } |
| if (pad == self->imgsrc || pad == self->vidsrc) { |
| gst_event_unref (event); |
| return ret; |
| } |
| break; |
| default: |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * check_and_replace_src |
| * @self: #GstWrapperCamerabinSrcCameraSrc object |
| * |
| * Checks if the current videosrc needs to be replaced |
| */ |
| static gboolean |
| check_and_replace_src (GstWrapperCameraBinSrc * self) |
| { |
| GstBin *cbin = GST_BIN_CAST (self); |
| GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC_CAST (self); |
| |
| if (self->src_vid_src && self->src_vid_src == self->app_vid_src) { |
| GST_DEBUG_OBJECT (self, "No need to change current videosrc"); |
| return TRUE; |
| } |
| |
| if (self->src_vid_src) { |
| GST_DEBUG_OBJECT (self, "Removing old video source"); |
| if (self->src_max_zoom_signal_id) { |
| g_signal_handler_disconnect (self->src_vid_src, |
| self->src_max_zoom_signal_id); |
| self->src_max_zoom_signal_id = 0; |
| } |
| if (self->src_event_probe_id) { |
| GstPad *pad; |
| pad = gst_element_get_static_pad (self->src_vid_src, "src"); |
| gst_pad_remove_probe (pad, self->src_event_probe_id); |
| gst_object_unref (pad); |
| self->src_event_probe_id = 0; |
| } |
| gst_bin_remove (GST_BIN_CAST (self), self->src_vid_src); |
| self->src_vid_src = NULL; |
| } |
| |
| GST_DEBUG_OBJECT (self, "Adding new video source"); |
| |
| /* Add application set or default video src element */ |
| if (!(self->src_vid_src = gst_camerabin_setup_default_element (cbin, |
| self->app_vid_src, "autovideosrc", DEFAULT_VIDEOSRC, |
| "camerasrc-real-src"))) { |
| self->src_vid_src = NULL; |
| goto fail; |
| } |
| |
| if (!gst_bin_add (cbin, self->src_vid_src)) { |
| goto fail; |
| } |
| |
| /* check if we already have the next element to link to */ |
| if (self->src_crop) { |
| if (!gst_element_link_pads (self->src_vid_src, "src", self->src_crop, |
| "sink")) { |
| goto fail; |
| } |
| } |
| |
| /* we listen for changes to max-zoom in the video src so that |
| * we can proxy them to the basecamerasrc property */ |
| if (g_object_class_find_property (G_OBJECT_GET_CLASS (bcamsrc), "max-zoom")) { |
| self->src_max_zoom_signal_id = |
| g_signal_connect (G_OBJECT (self->src_vid_src), "notify::max-zoom", |
| (GCallback) gst_wrapper_camera_bin_src_max_zoom_cb, bcamsrc); |
| } |
| |
| return TRUE; |
| |
| fail: |
| if (self->src_vid_src) |
| gst_element_set_state (self->src_vid_src, GST_STATE_NULL); |
| return FALSE; |
| } |
| |
| /** |
| * gst_wrapper_camera_bin_src_construct_pipeline: |
| * @bcamsrc: camerasrc object |
| * |
| * This function creates and links the elements of the camerasrc bin |
| * videosrc ! cspconv ! srcfilter ! cspconv ! capsfilter ! crop ! scale ! \ |
| * capsfilter |
| * |
| * Returns: TRUE, if elements were successfully created, FALSE otherwise |
| */ |
| static gboolean |
| gst_wrapper_camera_bin_src_construct_pipeline (GstBaseCameraSrc * bcamsrc) |
| { |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (bcamsrc); |
| GstBin *cbin = GST_BIN (bcamsrc); |
| GstElement *filter_csp; |
| GstElement *src_csp; |
| GstElement *capsfilter; |
| GstElement *video_recording_tee; |
| gboolean ret = FALSE; |
| GstPad *tee_pad; |
| |
| /* checks and adds a new video src if needed */ |
| if (!check_and_replace_src (self)) |
| goto done; |
| |
| if (!self->elements_created) { |
| |
| GST_DEBUG_OBJECT (self, "constructing pipeline"); |
| |
| if (!(self->src_crop = |
| gst_camerabin_create_and_add_element (cbin, "videocrop", |
| "src-crop"))) |
| goto done; |
| |
| if (!gst_camerabin_create_and_add_element (cbin, "videoconvert", |
| "src-videoconvert")) |
| goto done; |
| |
| if (self->app_vid_filter) { |
| self->video_filter = gst_object_ref (self->app_vid_filter); |
| |
| if (!gst_camerabin_add_element (cbin, self->video_filter)) |
| goto done; |
| if (!gst_camerabin_create_and_add_element (cbin, "videoconvert", |
| "filter-videoconvert")) |
| goto done; |
| } |
| |
| if (!(self->src_filter = |
| gst_camerabin_create_and_add_element (cbin, "capsfilter", |
| "src-capsfilter"))) |
| goto done; |
| |
| /* attach to notify::caps on the first capsfilter and use a callback |
| * to recalculate the zoom properties when these caps change and to |
| * propagate the caps to the second capsfilter */ |
| self->srcfilter_pad = gst_element_get_static_pad (self->src_filter, "src"); |
| g_signal_connect (self->srcfilter_pad, "notify::caps", |
| G_CALLBACK (gst_wrapper_camera_bin_src_caps_cb), self); |
| |
| if (!(self->digitalzoom = g_object_new (GST_TYPE_DIGITAL_ZOOM, NULL))) { |
| GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, |
| (_("Digitalzoom element couldn't be created")), (NULL)); |
| |
| goto done; |
| } |
| if (!gst_camerabin_add_element_full (GST_BIN_CAST (self), NULL, |
| self->digitalzoom, "sink")) |
| goto done; |
| |
| /* keep a 'tee' element that has 2 source pads, one is linked to the |
| * vidsrc pad and the other is linked as needed to the viewfinder |
| * when video recording is hapenning */ |
| video_recording_tee = gst_element_factory_make ("tee", "video_rec_tee"); |
| gst_bin_add (GST_BIN_CAST (self), video_recording_tee); /* TODO check returns */ |
| self->video_tee_vf_pad = |
| gst_element_get_request_pad (video_recording_tee, "src_%u"); |
| self->video_tee_sink = |
| gst_element_get_static_pad (video_recording_tee, "sink"); |
| tee_pad = gst_element_get_request_pad (video_recording_tee, "src_%u"); |
| gst_ghost_pad_set_target (GST_GHOST_PAD (self->vidsrc), tee_pad); |
| gst_object_unref (tee_pad); |
| |
| /* viewfinder pad */ |
| self->src_pad = gst_element_get_static_pad (self->digitalzoom, "src"); |
| gst_ghost_pad_set_target (GST_GHOST_PAD (self->vfsrc), self->src_pad); |
| |
| gst_pad_set_active (self->vfsrc, TRUE); |
| gst_pad_set_active (self->imgsrc, TRUE); /* XXX ??? */ |
| gst_pad_set_active (self->vidsrc, TRUE); /* XXX ??? */ |
| |
| gst_pad_add_probe (self->imgsrc, GST_PAD_PROBE_TYPE_BUFFER, |
| gst_wrapper_camera_bin_src_imgsrc_probe, self, NULL); |
| gst_pad_add_probe (self->video_tee_sink, GST_PAD_PROBE_TYPE_BUFFER, |
| gst_wrapper_camera_bin_src_vidsrc_probe, self, NULL); |
| } |
| |
| /* Do this even if pipeline is constructed */ |
| |
| if (self->video_filter) { |
| /* check if we need to replace the current one */ |
| if (self->video_filter != self->app_vid_filter) { |
| gst_bin_remove (cbin, self->video_filter); |
| gst_object_unref (self->video_filter); |
| self->video_filter = NULL; |
| filter_csp = gst_bin_get_by_name (cbin, "filter-videoconvert"); |
| gst_bin_remove (cbin, filter_csp); |
| gst_object_unref (filter_csp); |
| filter_csp = NULL; |
| } |
| } |
| |
| if (!self->video_filter) { |
| if (self->app_vid_filter) { |
| self->video_filter = gst_object_ref (self->app_vid_filter); |
| filter_csp = gst_element_factory_make ("videoconvert", |
| "filter-videoconvert"); |
| gst_bin_add_many (cbin, self->video_filter, filter_csp, NULL); |
| src_csp = gst_bin_get_by_name (cbin, "src-videoconvert"); |
| capsfilter = gst_bin_get_by_name (cbin, "src-capsfilter"); |
| if (gst_pad_is_linked (gst_element_get_static_pad (src_csp, "src"))) |
| gst_element_unlink (src_csp, capsfilter); |
| if (!gst_element_link_many (src_csp, self->video_filter, filter_csp, |
| capsfilter, NULL)) { |
| gst_object_unref (src_csp); |
| gst_object_unref (capsfilter); |
| goto done; |
| } |
| gst_object_unref (src_csp); |
| gst_object_unref (capsfilter); |
| } |
| } |
| ret = TRUE; |
| self->elements_created = TRUE; |
| done: |
| return ret; |
| } |
| |
| /** |
| * adapt_image_capture: |
| * @self: camerasrc object |
| * @in_caps: caps object that describes incoming image format |
| * |
| * Adjust capsfilters and crop according image capture caps if necessary. |
| * The captured image format from video source might be different from |
| * what application requested, so we can try to fix that in camerabin. |
| * |
| */ |
| static void |
| adapt_image_capture (GstWrapperCameraBinSrc * self, GstCaps * in_caps) |
| { |
| GstStructure *in_st, *req_st; |
| gint in_width = 0, in_height = 0, req_width = 0, req_height = 0, crop = 0; |
| gdouble ratio_w, ratio_h; |
| |
| GST_LOG_OBJECT (self, "in caps: %" GST_PTR_FORMAT, in_caps); |
| GST_LOG_OBJECT (self, "requested caps: %" GST_PTR_FORMAT, |
| self->image_capture_caps); |
| |
| in_st = gst_caps_get_structure (in_caps, 0); |
| gst_structure_get_int (in_st, "width", &in_width); |
| gst_structure_get_int (in_st, "height", &in_height); |
| |
| req_st = gst_caps_get_structure (self->image_capture_caps, 0); |
| gst_structure_get_int (req_st, "width", &req_width); |
| gst_structure_get_int (req_st, "height", &req_height); |
| |
| GST_INFO_OBJECT (self, "we requested %dx%d, and got %dx%d", req_width, |
| req_height, in_width, in_height); |
| |
| /* Crop if requested aspect ratio differs from incoming frame aspect ratio */ |
| if (self->src_crop) { |
| gint base_crop_top = 0, base_crop_bottom = 0; |
| gint base_crop_left = 0, base_crop_right = 0; |
| |
| ratio_w = (gdouble) in_width / req_width; |
| ratio_h = (gdouble) in_height / req_height; |
| |
| if (ratio_w < ratio_h) { |
| crop = in_height - (req_height * ratio_w); |
| base_crop_top = crop / 2; |
| base_crop_bottom = crop / 2; |
| } else { |
| crop = in_width - (req_width * ratio_h); |
| base_crop_left = crop / 2; |
| base_crop_right += crop / 2; |
| } |
| |
| GST_INFO_OBJECT (self, |
| "setting base crop: left:%d, right:%d, top:%d, bottom:%d", |
| base_crop_left, base_crop_right, base_crop_top, base_crop_bottom); |
| g_object_set (G_OBJECT (self->src_crop), |
| "top", base_crop_top, "bottom", base_crop_bottom, |
| "left", base_crop_left, "right", base_crop_right, NULL); |
| } |
| |
| /* Update capsfilters */ |
| set_capsfilter_caps (self, self->image_capture_caps); |
| } |
| |
| /** |
| * img_capture_prepared: |
| * @data: camerasrc object |
| * @caps: caps describing the prepared image format |
| * |
| * Callback which is called after image capture has been prepared. |
| */ |
| static void |
| img_capture_prepared (gpointer data, GstCaps * caps) |
| { |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (data); |
| |
| GST_INFO_OBJECT (self, "image capture prepared"); |
| |
| /* It is possible we are about to get something else that we requested */ |
| if (!gst_caps_can_intersect (self->image_capture_caps, caps)) { |
| adapt_image_capture (self, caps); |
| } else { |
| set_capsfilter_caps (self, self->image_capture_caps); |
| } |
| } |
| |
| static GstPadProbeReturn |
| start_image_capture (GstPad * pad, GstPadProbeInfo * info, gpointer udata) |
| { |
| GstWrapperCameraBinSrc *self = udata; |
| GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC (self); |
| GstPhotography *photography = |
| (GstPhotography *) gst_bin_get_by_interface (GST_BIN_CAST (bcamsrc), |
| GST_TYPE_PHOTOGRAPHY); |
| GstCaps *caps; |
| |
| GST_DEBUG_OBJECT (self, "Starting image capture"); |
| |
| /* unlink from the viewfinder, link to the imagesrc pad to wait for |
| * the buffer to pass */ |
| gst_wrapper_camera_bin_src_set_output (self, self->vfsrc, self->imgsrc); |
| |
| if (self->image_renegotiate) { |
| self->image_renegotiate = FALSE; |
| |
| /* clean capsfilter caps so they don't interfere here */ |
| g_object_set (self->src_filter, "caps", NULL, NULL); |
| |
| caps = gst_pad_get_allowed_caps (self->imgsrc); |
| gst_caps_replace (&self->image_capture_caps, caps); |
| gst_caps_unref (caps); |
| |
| /* We caught this event in the src pad event handler and now we want to |
| * actually push it upstream */ |
| gst_pad_mark_reconfigure (pad); |
| } |
| |
| if (photography) { |
| GST_DEBUG_OBJECT (self, "prepare image capture caps %" GST_PTR_FORMAT, |
| self->image_capture_caps); |
| if (!gst_photography_prepare_for_capture (photography, |
| (GstPhotographyCapturePrepared) img_capture_prepared, |
| self->image_capture_caps, self)) { |
| GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, |
| ("Failed to prepare image capture"), |
| ("Prepare capture call didn't succeed for the given caps")); |
| self->image_capture_count = 0; |
| } |
| gst_object_unref (photography); |
| } else { |
| gst_wrapper_camera_bin_reset_video_src_caps (self, |
| self->image_capture_caps); |
| } |
| |
| self->image_capture_probe = 0; |
| return GST_PAD_PROBE_REMOVE; |
| } |
| |
| static GstPadProbeReturn |
| start_video_capture (GstPad * pad, GstPadProbeInfo * info, gpointer udata) |
| { |
| GstWrapperCameraBinSrc *self = udata; |
| GstCaps *caps; |
| |
| GST_DEBUG_OBJECT (self, "Starting video capture"); |
| |
| if (self->video_renegotiate) { |
| GstCaps *anycaps = gst_caps_new_any (); |
| gst_wrapper_camera_bin_reset_video_src_caps (self, anycaps); |
| gst_caps_unref (anycaps); |
| |
| /* clean capsfilter caps so they don't interfere here */ |
| g_object_set (self->src_filter, "caps", NULL, NULL); |
| } |
| |
| /* unlink from the viewfinder, link to the imagesrc pad, wait for |
| * the buffer to pass */ |
| gst_wrapper_camera_bin_src_set_output (self, self->vfsrc, NULL); |
| gst_pad_link (self->src_pad, self->video_tee_sink); |
| gst_ghost_pad_set_target (GST_GHOST_PAD (self->vfsrc), |
| self->video_tee_vf_pad); |
| |
| if (self->video_renegotiate) { |
| GST_DEBUG_OBJECT (self, "Getting allowed videosrc caps"); |
| caps = gst_pad_get_allowed_caps (self->vidsrc); |
| GST_DEBUG_OBJECT (self, "Video src caps %" GST_PTR_FORMAT, caps); |
| |
| self->video_renegotiate = FALSE; |
| gst_wrapper_camera_bin_reset_video_src_caps (self, caps); |
| gst_caps_unref (caps); |
| } |
| self->video_capture_probe = 0; |
| |
| return GST_PAD_PROBE_REMOVE; |
| } |
| |
| static gboolean |
| gst_wrapper_camera_bin_src_set_mode (GstBaseCameraSrc * bcamsrc, |
| GstCameraBinMode mode) |
| { |
| GstPhotography *photography = |
| (GstPhotography *) gst_bin_get_by_interface (GST_BIN_CAST (bcamsrc), |
| GST_TYPE_PHOTOGRAPHY); |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (bcamsrc); |
| |
| if (mode == MODE_IMAGE) { |
| self->image_renegotiate = TRUE; |
| } else { |
| self->video_renegotiate = TRUE; |
| } |
| self->mode = mode; |
| |
| if (photography) { |
| if (g_object_class_find_property (G_OBJECT_GET_CLASS (photography), |
| "capture-mode")) { |
| g_object_set (G_OBJECT (photography), "capture-mode", mode, NULL); |
| } |
| gst_object_unref (photography); |
| } else { |
| GstCaps *anycaps = gst_caps_new_any (); |
| gst_wrapper_camera_bin_reset_video_src_caps (self, anycaps); |
| gst_caps_unref (anycaps); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| set_videosrc_zoom (GstWrapperCameraBinSrc * self, gfloat zoom) |
| { |
| gboolean ret = FALSE; |
| |
| if (g_object_class_find_property (G_OBJECT_GET_CLASS (self->src_vid_src), |
| "zoom")) { |
| g_object_set (G_OBJECT (self->src_vid_src), "zoom", zoom, NULL); |
| ret = TRUE; |
| } |
| return ret; |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_set_zoom (GstBaseCameraSrc * bcamsrc, gfloat zoom) |
| { |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (bcamsrc); |
| |
| GST_INFO_OBJECT (self, "setting zoom %f", zoom); |
| |
| if (set_videosrc_zoom (self, zoom)) { |
| g_object_set (self->digitalzoom, "zoom", (gfloat) 1.0, NULL); |
| GST_INFO_OBJECT (self, "zoom set using videosrc"); |
| } else { |
| GST_INFO_OBJECT (self, "zoom set using digitalzoom"); |
| g_object_set (self->digitalzoom, "zoom", zoom, NULL); |
| } |
| } |
| |
| /** |
| * update_aspect_filter: |
| * @self: camerasrc object |
| * @new_caps: new caps of next buffers arriving to view finder sink element |
| * |
| * Updates aspect ratio capsfilter to maintain aspect ratio, if we need to |
| * scale frames for showing them in view finder. |
| */ |
| static void |
| update_aspect_filter (GstWrapperCameraBinSrc * self, GstCaps * new_caps) |
| { |
| /* XXX why not instead add a preserve-aspect-ratio property to videoscale? */ |
| #if 0 |
| if (camera->flags & GST_CAMERABIN_FLAG_VIEWFINDER_SCALE) { |
| GstCaps *sink_caps, *ar_caps; |
| GstStructure *st; |
| gint in_w = 0, in_h = 0, sink_w = 0, sink_h = 0, target_w = 0, target_h = 0; |
| gdouble ratio_w, ratio_h; |
| GstPad *sink_pad; |
| const GValue *range; |
| |
| sink_pad = gst_element_get_static_pad (camera->view_sink, "sink"); |
| |
| if (sink_pad) { |
| sink_caps = gst_pad_get_caps (sink_pad); |
| gst_object_unref (sink_pad); |
| if (sink_caps) { |
| if (!gst_caps_is_any (sink_caps)) { |
| GST_DEBUG_OBJECT (camera, "sink element caps %" GST_PTR_FORMAT, |
| sink_caps); |
| /* Get maximum resolution that view finder sink accepts */ |
| st = gst_caps_get_structure (sink_caps, 0); |
| if (gst_structure_has_field_typed (st, "width", GST_TYPE_INT_RANGE)) { |
| range = gst_structure_get_value (st, "width"); |
| sink_w = gst_value_get_int_range_max (range); |
| } |
| if (gst_structure_has_field_typed (st, "height", GST_TYPE_INT_RANGE)) { |
| range = gst_structure_get_value (st, "height"); |
| sink_h = gst_value_get_int_range_max (range); |
| } |
| GST_DEBUG_OBJECT (camera, "sink element accepts max %dx%d", sink_w, |
| sink_h); |
| |
| /* Get incoming frames' resolution */ |
| if (sink_h && sink_w) { |
| st = gst_caps_get_structure (new_caps, 0); |
| gst_structure_get_int (st, "width", &in_w); |
| gst_structure_get_int (st, "height", &in_h); |
| GST_DEBUG_OBJECT (camera, "new caps with %dx%d", in_w, in_h); |
| } |
| } |
| gst_caps_unref (sink_caps); |
| } |
| } |
| |
| /* If we get bigger frames than view finder sink accepts, then we scale. |
| If we scale we need to adjust aspect ratio capsfilter caps in order |
| to maintain aspect ratio while scaling. */ |
| if (in_w && in_h && (in_w > sink_w || in_h > sink_h)) { |
| ratio_w = (gdouble) sink_w / in_w; |
| ratio_h = (gdouble) sink_h / in_h; |
| |
| if (ratio_w < ratio_h) { |
| target_w = sink_w; |
| target_h = (gint) (ratio_w * in_h); |
| } else { |
| target_w = (gint) (ratio_h * in_w); |
| target_h = sink_h; |
| } |
| |
| GST_DEBUG_OBJECT (camera, "setting %dx%d filter to maintain aspect ratio", |
| target_w, target_h); |
| ar_caps = gst_caps_copy (new_caps); |
| gst_caps_set_simple (ar_caps, "width", G_TYPE_INT, target_w, "height", |
| G_TYPE_INT, target_h, NULL); |
| } else { |
| GST_DEBUG_OBJECT (camera, "no scaling"); |
| ar_caps = new_caps; |
| } |
| |
| GST_DEBUG_OBJECT (camera, "aspect ratio filter caps %" GST_PTR_FORMAT, |
| ar_caps); |
| g_object_set (G_OBJECT (camera->aspect_filter), "caps", ar_caps, NULL); |
| if (ar_caps != new_caps) |
| gst_caps_unref (ar_caps); |
| } |
| #endif |
| } |
| |
| |
| /** |
| * set_capsfilter_caps: |
| * @self: camerasrc object |
| * @new_caps: pointer to caps object to set |
| * |
| * Set given caps to camerabin capsfilters. |
| */ |
| static void |
| set_capsfilter_caps (GstWrapperCameraBinSrc * self, GstCaps * new_caps) |
| { |
| GST_INFO_OBJECT (self, "new_caps:%" GST_PTR_FORMAT, new_caps); |
| |
| /* Update zoom */ |
| gst_base_camera_src_setup_zoom (GST_BASE_CAMERA_SRC (self)); |
| |
| /* Update capsfilters */ |
| g_object_set (G_OBJECT (self->src_filter), "caps", new_caps, NULL); |
| update_aspect_filter (self, new_caps); |
| GST_INFO_OBJECT (self, "updated"); |
| } |
| |
| static gboolean |
| gst_wrapper_camera_bin_src_start_capture (GstBaseCameraSrc * camerasrc) |
| { |
| GstWrapperCameraBinSrc *src = GST_WRAPPER_CAMERA_BIN_SRC (camerasrc); |
| GstPad *pad; |
| gboolean ret = TRUE; |
| |
| pad = gst_element_get_static_pad (src->src_vid_src, "src"); |
| |
| /* TODO should we access this directly? Maybe a macro is better? */ |
| if (src->mode == MODE_IMAGE) { |
| src->image_capture_count = 1; |
| |
| src->image_capture_probe = |
| gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_IDLE, start_image_capture, |
| src, NULL); |
| } else if (src->mode == MODE_VIDEO) { |
| if (src->video_rec_status == GST_VIDEO_RECORDING_STATUS_DONE) { |
| src->video_rec_status = GST_VIDEO_RECORDING_STATUS_STARTING; |
| src->video_capture_probe = |
| gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_IDLE, start_video_capture, |
| src, NULL); |
| } |
| } else { |
| g_assert_not_reached (); |
| ret = FALSE; |
| } |
| gst_object_unref (pad); |
| return ret; |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_stop_capture (GstBaseCameraSrc * camerasrc) |
| { |
| GstWrapperCameraBinSrc *src = GST_WRAPPER_CAMERA_BIN_SRC (camerasrc); |
| |
| /* TODO shoud we access this directly? Maybe a macro is better? */ |
| if (src->mode == MODE_VIDEO) { |
| if (src->video_rec_status == GST_VIDEO_RECORDING_STATUS_STARTING) { |
| GST_DEBUG_OBJECT (src, "Aborting, had not started recording"); |
| src->video_rec_status = GST_VIDEO_RECORDING_STATUS_DONE; |
| |
| } else if (src->video_rec_status == GST_VIDEO_RECORDING_STATUS_RUNNING) { |
| GST_DEBUG_OBJECT (src, "Marking video recording as finishing"); |
| src->video_rec_status = GST_VIDEO_RECORDING_STATUS_FINISHING; |
| } |
| } else { |
| /* TODO check what happens when we try to stop a image capture */ |
| } |
| } |
| |
| static GstStateChangeReturn |
| gst_wrapper_camera_bin_src_change_state (GstElement * element, |
| GstStateChange trans) |
| { |
| GstStateChangeReturn ret; |
| GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (element); |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans); |
| |
| if (ret == GST_STATE_CHANGE_FAILURE) |
| goto end; |
| |
| switch (trans) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| self->video_renegotiate = TRUE; |
| self->image_renegotiate = TRUE; |
| break; |
| case GST_STATE_CHANGE_READY_TO_NULL: |
| break; |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| break; |
| default: |
| break; |
| } |
| |
| end: |
| return ret; |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_class_init (GstWrapperCameraBinSrcClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| GstBaseCameraSrcClass *gstbasecamerasrc_class; |
| |
| gobject_class = G_OBJECT_CLASS (klass); |
| gstelement_class = GST_ELEMENT_CLASS (klass); |
| gstbasecamerasrc_class = GST_BASE_CAMERA_SRC_CLASS (klass); |
| |
| gobject_class->dispose = gst_wrapper_camera_bin_src_dispose; |
| gobject_class->finalize = |
| (GObjectFinalizeFunc) gst_wrapper_camera_bin_src_finalize; |
| gobject_class->set_property = gst_wrapper_camera_bin_src_set_property; |
| gobject_class->get_property = gst_wrapper_camera_bin_src_get_property; |
| |
| /* g_object_class_install_property .... */ |
| g_object_class_install_property (gobject_class, PROP_VIDEO_SRC, |
| g_param_spec_object ("video-source", "Video source", |
| "The video source element to be used", |
| GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_VIDEO_SRC_FILTER, |
| g_param_spec_object ("video-source-filter", "Video source filter", |
| "Optional video source filter element", |
| GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gstelement_class->change_state = gst_wrapper_camera_bin_src_change_state; |
| |
| gstbasecamerasrc_class->construct_pipeline = |
| gst_wrapper_camera_bin_src_construct_pipeline; |
| gstbasecamerasrc_class->set_zoom = gst_wrapper_camera_bin_src_set_zoom; |
| gstbasecamerasrc_class->set_mode = gst_wrapper_camera_bin_src_set_mode; |
| gstbasecamerasrc_class->start_capture = |
| gst_wrapper_camera_bin_src_start_capture; |
| gstbasecamerasrc_class->stop_capture = |
| gst_wrapper_camera_bin_src_stop_capture; |
| |
| GST_DEBUG_CATEGORY_INIT (wrapper_camera_bin_src_debug, "wrappercamerabinsrc", |
| 0, "wrapper camera src"); |
| |
| gst_element_class_add_static_pad_template (gstelement_class, &vfsrc_template); |
| |
| gst_element_class_add_static_pad_template (gstelement_class, |
| &imgsrc_template); |
| |
| gst_element_class_add_static_pad_template (gstelement_class, |
| &vidsrc_template); |
| |
| gst_element_class_set_static_metadata (gstelement_class, |
| "Wrapper camera src element for camerabin2", "Source/Video", |
| "Wrapper camera src element for camerabin2", |
| "Thiago Santos <thiago.sousa.santos@collabora.com>"); |
| } |
| |
| static void |
| gst_wrapper_camera_bin_src_init (GstWrapperCameraBinSrc * self) |
| { |
| self->vfsrc = |
| gst_ghost_pad_new_no_target (GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME, |
| GST_PAD_SRC); |
| gst_element_add_pad (GST_ELEMENT (self), self->vfsrc); |
| |
| self->imgsrc = |
| gst_ghost_pad_new_no_target (GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME, |
| GST_PAD_SRC); |
| gst_element_add_pad (GST_ELEMENT (self), self->imgsrc); |
| |
| self->vidsrc = |
| gst_ghost_pad_new_no_target (GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME, |
| GST_PAD_SRC); |
| gst_element_add_pad (GST_ELEMENT (self), self->vidsrc); |
| |
| gst_pad_set_event_function (self->imgsrc, |
| GST_DEBUG_FUNCPTR (gst_wrapper_camera_bin_src_src_event)); |
| gst_pad_set_event_function (self->vidsrc, |
| GST_DEBUG_FUNCPTR (gst_wrapper_camera_bin_src_src_event)); |
| |
| /* TODO where are variables reset? */ |
| self->image_capture_count = 0; |
| self->video_rec_status = GST_VIDEO_RECORDING_STATUS_DONE; |
| self->video_renegotiate = TRUE; |
| self->image_renegotiate = TRUE; |
| self->mode = GST_BASE_CAMERA_SRC_CAST (self)->mode; |
| self->app_vid_filter = NULL; |
| } |
| |
| gboolean |
| gst_wrapper_camera_bin_src_plugin_init (GstPlugin * plugin) |
| { |
| return gst_element_register (plugin, "wrappercamerabinsrc", GST_RANK_NONE, |
| gst_wrapper_camera_bin_src_get_type ()); |
| } |