/* * Copyright © 2007,2008 Jaap Haitsma * Copyright © 2007-2009 daniel g. siegel * Copyright © 2008 Ryan Zeigler * Copyright © 2010 Yuvaraj Pandian T * Copyright © 2011 Luciana Fujii Pontello * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include /* Avoid a warning. */ #define GST_USE_UNSTABLE_API #include #include #include #include "cheese-camera.h" #include "cheese-camera-device.h" #include "cheese-camera-device-monitor.h" #define CHEESE_VIDEO_ENC_PRESET "Profile Realtime" #define CHEESE_VIDEO_ENC_ALT_PRESET "Cheese Realtime" /** * SECTION:cheese-camera * @short_description: A representation of the video capture device inside * #CheeseWidget * @stability: Unstable * @include: cheese/cheese-camera.h * * #CheeseCamera represents the video capture device used to drive a * #CheeseWidget. */ struct _CheeseCameraPrivate { GstBus *bus; GstElement *camerabin; GstElement *video_filter_bin; GstElement *effects_preview_bin; GstElement *video_source; GstElement *camera_source; ClutterActor *video_texture; GstElement *effect_filter, *effects_capsfilter; GstElement *video_balance; GstElement *camera_tee, *effects_tee; GstElement *main_valve, *effects_valve; gchar *current_effect_desc; gboolean is_recording; gboolean pipeline_is_playing; gboolean effect_pipeline_is_playing; gchar *photo_filename; guint num_camera_devices; CheeseCameraDevice *device; /* an array of CheeseCameraDevices */ GPtrArray *camera_devices; guint selected_device; CheeseVideoFormat *current_format; gchar *initial_name; CheeseCameraDeviceMonitor *monitor; }; G_DEFINE_TYPE_WITH_PRIVATE (CheeseCamera, cheese_camera, G_TYPE_OBJECT) #define CHEESE_CAMERA_ERROR cheese_camera_error_quark () enum { PROP_0, PROP_VIDEO_TEXTURE, PROP_DEVICE, PROP_FORMAT, PROP_NUM_CAMERA_DEVICES, PROP_LAST }; enum { PHOTO_SAVED, PHOTO_TAKEN, VIDEO_SAVED, STATE_FLAGS_CHANGED, LAST_SIGNAL }; static guint camera_signals[LAST_SIGNAL]; static GParamSpec *properties[PROP_LAST]; GST_DEBUG_CATEGORY (cheese_camera_cat); #define GST_CAT_DEFAULT cheese_camera_cat GQuark cheese_camera_error_quark (void); GQuark cheese_camera_error_quark (void) { return g_quark_from_static_string ("cheese-camera-error-quark"); } /* * cheese_camera_photo_data: * @camera: a #CheeseCamera * @sample: the #GstSample containing photo data * * Create a #GdkPixbuf containing photo data captured from @camera, and emit it * in the ::photo-taken signal. */ static void cheese_camera_photo_data (CheeseCamera *camera, GstSample *sample) { GstBuffer *buffer; GstCaps *caps; const GstStructure *structure; gint width, height, stride; GdkPixbuf *pixbuf; const gint bits_per_pixel = 8; guchar *data = NULL; CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); GstMapInfo mapinfo = {0, }; buffer = gst_sample_get_buffer (sample); caps = gst_sample_get_caps (sample); structure = gst_caps_get_structure (caps, 0); gst_structure_get_int (structure, "width", &width); gst_structure_get_int (structure, "height", &height); gst_buffer_map (buffer, &mapinfo, GST_MAP_READ); stride = mapinfo.size / height; data = g_memdup (mapinfo.data, mapinfo.size); pixbuf = gdk_pixbuf_new_from_data (data ? data : mapinfo.data, GDK_COLORSPACE_RGB, FALSE, bits_per_pixel, width, height, stride, data ? (GdkPixbufDestroyNotify) g_free : NULL, NULL); gst_buffer_unmap (buffer, &mapinfo); g_object_set (G_OBJECT (priv->camerabin), "post-previews", FALSE, NULL); g_signal_emit (camera, camera_signals[PHOTO_TAKEN], 0, pixbuf); g_object_unref (pixbuf); } /* * cheese_camera_bus_message_cb: * @bus: a #GstBus * @message: the #GstMessage * @camera: the #CheeseCamera * * Process messages create by the @camera on the @bus. Emit * ::state-flags-changed if the state of the camera has changed. */ static void cheese_camera_bus_message_cb (GstBus *bus, GstMessage *message, CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); GstMessageType type; type = GST_MESSAGE_TYPE (message); if (type == GST_MESSAGE_WARNING) { GError *err = NULL; gchar *debug = NULL; gst_message_parse_warning (message, &err, &debug); if (err && err->message) { g_warning ("%s: %s\n", err->message, debug); g_error_free (err); } else { g_warning ("Unparsable GST_MESSAGE_WARNING message.\n"); } g_free (debug); } else if (type == GST_MESSAGE_ERROR) { GError *err = NULL; gchar *debug = NULL; gst_message_parse_error (message, &err, &debug); if (err && err->message) { g_warning ("%s: %s\n", err->message, debug); g_error_free (err); } else { g_warning ("Unparsable GST_MESSAGE_ERROR message.\n"); } cheese_camera_stop (camera); g_signal_emit (camera, camera_signals[STATE_FLAGS_CHANGED], 0, GST_STATE_NULL); g_free (debug); } else if (type == GST_MESSAGE_STATE_CHANGED) { if (strcmp (GST_MESSAGE_SRC_NAME (message), "camerabin") == 0) { GstState old, new; gst_message_parse_state_changed (message, &old, &new, NULL); if (new == GST_STATE_PLAYING) { g_signal_emit (camera, camera_signals[STATE_FLAGS_CHANGED], 0, new); cheese_camera_toggle_effects_pipeline (camera, priv->effect_pipeline_is_playing); } } } else if (type == GST_MESSAGE_ELEMENT) { const GstStructure *structure; GstSample *sample; const GValue *image; if (strcmp (GST_MESSAGE_SRC_NAME (message), "camera_source") == 0) { structure = gst_message_get_structure (message); if (strcmp (gst_structure_get_name (structure), "preview-image") == 0) { if (gst_structure_has_field_typed (structure, "sample", GST_TYPE_SAMPLE)) { image = gst_structure_get_value (structure, "sample"); if (image) { sample = gst_value_get_sample (image); cheese_camera_photo_data (camera, sample); } else { g_warning ("Could not get buffer from bus message"); } } } } if (strcmp (GST_MESSAGE_SRC_NAME (message), "camerabin") == 0) { structure = gst_message_get_structure (message); if (strcmp (gst_structure_get_name (structure), "image-done") == 0) { const gchar *filename = gst_structure_get_string (structure, "filename"); if (priv->photo_filename != NULL && filename != NULL && (strcmp (priv->photo_filename, filename) == 0)) { g_signal_emit (camera, camera_signals[PHOTO_SAVED], 0); } } else if (strcmp (gst_structure_get_name (structure), "video-done") == 0) { g_signal_emit (camera, camera_signals[VIDEO_SAVED], 0); priv->is_recording = FALSE; } } } } /* * cheese_camera_add_device: * @monitor: a #CheeseCameraDeviceMonitor * @device: a #CheeseCameraDevice * @camera: a #CheeseCamera * * Handle the CheeseCameraDeviceMonitor::added signal and add the new * #CheeseCameraDevice to the list of current devices. */ static void cheese_camera_add_device (CheeseCameraDeviceMonitor *monitor, CheeseCameraDevice *device, CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); g_ptr_array_add (priv->camera_devices, device); priv->num_camera_devices++; g_object_notify_by_pspec (G_OBJECT (camera), properties[PROP_NUM_CAMERA_DEVICES]); } /* * cheese_camera_remove_device: * @monitor: a #CheeseCameraDeviceMonitor * @device: a #CheeseCameraDevice * @camera: a #CheeseCamera * * Handle the CheeseCameraDeviceMonitor::removed signal and remove the * #CheeseCameraDevice from the list of current devices. */ static void cheese_camera_remove_device (CheeseCameraDeviceMonitor *monitor, CheeseCameraDevice *device, CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); if (g_ptr_array_remove (priv->camera_devices, (gpointer) device)) { priv->num_camera_devices--; g_object_notify_by_pspec (G_OBJECT (camera), properties[PROP_NUM_CAMERA_DEVICES]); } } /* * cheese_camera_detect_camera_devices: * @camera: a #CheeseCamera * * Enumerate the physical camera devices present on the system, and add them to * the list of #CheeseCameraDevice objects. */ static void cheese_camera_detect_camera_devices (CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); priv->num_camera_devices = 0; priv->camera_devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->monitor = cheese_camera_device_monitor_new (); g_signal_connect (G_OBJECT (priv->monitor), "added", G_CALLBACK (cheese_camera_add_device), camera); g_signal_connect (G_OBJECT (priv->monitor), "removed", G_CALLBACK (cheese_camera_remove_device), camera); cheese_camera_device_monitor_coldplug (priv->monitor); } /* * cheese_camera_on_decodebin_pad_added: * @camera: a #CheeseCamera * @pad: new decode bin #GstPad * * A callback fired when a new source pad appears on the video source decodebin. * Exposes the pad as the source pad of @video_source #GstBin. */ static void cheese_camera_on_decodebin_pad_added (CheeseCamera *camera, GstPad *pad) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); GstPad *ghostpad; ghostpad = gst_element_get_static_pad (priv->video_source, "src"); gst_ghost_pad_set_target (GST_GHOST_PAD (ghostpad), pad); gst_object_unref (ghostpad); } /* * cheese_camera_set_camera_source: * @camera: a #CheeseCamera * * Set the currently-selected video capture device as the source for a video * steam. * * Returns: %TRUE if successful, %FALSE otherwise */ static gboolean cheese_camera_set_camera_source (CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); guint i; CheeseCameraDevice *selected_camera; GstElement *src, *filter, *decodebin; if (priv->video_source) gst_object_unref (priv->video_source); /* If we have a matching video device use that one, otherwise use the first */ priv->selected_device = 0; selected_camera = g_ptr_array_index (priv->camera_devices, 0); for (i = 1; i < priv->num_camera_devices; i++) { CheeseCameraDevice *dev = g_ptr_array_index (priv->camera_devices, i); if (dev == priv->device) { selected_camera = dev; priv->selected_device = i; break; } } priv->video_source = gst_bin_new (NULL); if (priv->video_source == NULL) { return FALSE; } src = cheese_camera_device_get_src (selected_camera); gst_bin_add (GST_BIN (priv->video_source), src); filter = gst_element_factory_make ("capsfilter", "video_source_filter"); gst_bin_add (GST_BIN (priv->video_source), filter); decodebin = gst_element_factory_make ("decodebin", NULL); g_signal_connect_swapped (decodebin, "pad-added", G_CALLBACK (cheese_camera_on_decodebin_pad_added), camera); gst_bin_add (GST_BIN (priv->video_source), decodebin); gst_element_link_many (src, filter, decodebin, NULL); gst_element_add_pad (priv->video_source, gst_ghost_pad_new_no_target ("src", GST_PAD_SRC)); return TRUE; } /* * cheese_camera_set_error_element_not_found: * @error: return location for errors, or %NULL * @factoryname: the name of the #GstElement which was not found * * Create a #GError to warn that a required GStreamer element was not found. */ static void cheese_camera_set_error_element_not_found (GError **error, const gchar *factoryname) { g_return_if_fail (error == NULL || *error == NULL); g_set_error (error, CHEESE_CAMERA_ERROR, CHEESE_CAMERA_ERROR_ELEMENT_NOT_FOUND, "%s%s.", _("One or more needed GStreamer elements are missing: "), factoryname); } /* * cheese_camera_set_video_recording: * @camera: a #CheeseCamera * @error: a return location for errors, or %NULL * */ static void cheese_camera_set_video_recording (CheeseCamera *camera, GError **error) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); GstEncodingContainerProfile *prof; GstEncodingVideoProfile *v_prof; GstCaps *caps; GstElement *video_enc; const gchar *video_preset; gboolean res; /* Check if we can use global preset for vp8enc. */ video_enc = gst_element_factory_make ("vp8enc", "vp8enc"); video_preset = (gchar *) &CHEESE_VIDEO_ENC_PRESET; res = gst_preset_load_preset (GST_PRESET (video_enc), video_preset); if (res == FALSE) { g_warning("Can't find vp8enc preset: \"%s\", using alternate preset:" " \"%s\". If you see this, make a bug report!", video_preset, CHEESE_VIDEO_ENC_ALT_PRESET); /* If global preset not found, then probably we use wrong preset name, * or old gstreamer version. In any case, we should try to control * keep poker face and not fail. DON'T FORGET TO MAKE A BUG REPORT!*/ video_preset = (gchar *) &CHEESE_VIDEO_ENC_ALT_PRESET; res = gst_preset_load_preset (GST_PRESET (video_enc), video_preset); if (res == FALSE) { g_warning ("Can't find vp8enc preset: \"%s\", " "creating new userspace preset.", video_preset); /* Seems like we do first run and userspace preset do not exist. * Let us create a new one. It will be probably located some where here: * ~/.local/share/gstreamer-1.0/presets/GstVP8Enc.prs */ g_object_set (G_OBJECT (video_enc), "speed", 2, NULL); g_object_set (G_OBJECT (video_enc), "max-latency", 1, NULL); gst_preset_save_preset (GST_PRESET (video_enc), video_preset); } } gst_object_unref(video_enc); /* create profile for webm encoding */ caps = gst_caps_from_string("video/webm"); prof = gst_encoding_container_profile_new("WebM audio/video", "Standard WebM/VP8/Vorbis", caps, NULL); gst_caps_unref (caps); caps = gst_caps_from_string("video/x-vp8"); v_prof = gst_encoding_video_profile_new(caps, NULL, NULL, 0); gst_encoding_video_profile_set_variableframerate(v_prof, TRUE); gst_encoding_profile_set_preset((GstEncodingProfile*) v_prof, video_preset); gst_encoding_container_profile_add_profile(prof, (GstEncodingProfile*) v_prof); gst_caps_unref (caps); caps = gst_caps_from_string("audio/x-vorbis"); gst_encoding_container_profile_add_profile(prof, (GstEncodingProfile*) gst_encoding_audio_profile_new(caps, NULL, NULL, 0)); gst_caps_unref (caps); g_object_set (priv->camerabin, "video-profile", prof, NULL); gst_encoding_profile_unref (prof); } /* * cheese_camera_create_effects_preview_bin: * @camera: a #CheeseCamera * @error: a return location for errors * * Create the #GstBin for effect previews. * * Returns: %TRUE if the bin creation was successful, %FALSE otherwise */ static gboolean cheese_camera_create_effects_preview_bin (CheeseCamera *camera, GError **error) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); gboolean ok = TRUE; GstElement *scale; GstPad *pad; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); priv->effects_preview_bin = gst_bin_new ("effects_preview_bin"); if ((priv->effects_valve = gst_element_factory_make ("valve", "effects_valve")) == NULL) { cheese_camera_set_error_element_not_found (error, "effects_valve"); return FALSE; } g_object_set (G_OBJECT (priv->effects_valve), "drop", TRUE, NULL); if ((scale = gst_element_factory_make ("videoscale", "effects_scale")) == NULL) { cheese_camera_set_error_element_not_found (error, "videoscale"); return FALSE; } if ((priv->effects_capsfilter = gst_element_factory_make ("capsfilter", "effects_capsfilter")) == NULL) { cheese_camera_set_error_element_not_found (error, "capsfilter"); return FALSE; } if ((priv->effects_tee = gst_element_factory_make ("tee", "effects_tee")) == NULL) { cheese_camera_set_error_element_not_found (error, "tee"); return FALSE; } gst_bin_add_many (GST_BIN (priv->effects_preview_bin), priv->effects_valve, scale, priv->effects_capsfilter, priv->effects_tee, NULL); ok &= gst_element_link_many (priv->effects_valve, scale, priv->effects_capsfilter, priv->effects_tee, NULL); /* add ghostpads */ pad = gst_element_get_static_pad (priv->effects_valve, "sink"); gst_element_add_pad (priv->effects_preview_bin, gst_ghost_pad_new ("sink", pad)); gst_object_unref (GST_OBJECT (pad)); if (!ok) g_error ("Unable to create effects preview bin"); return TRUE; } /* * cheese_camera_create_video_filter_bin: * @camera: a #CheeseCamera * @error: a return location for errors, or %NULL * * Create the #GstBin for video filtering. * * Returns: %TRUE if the bin creation was successful, %FALSE and sets @error * otherwise */ static gboolean cheese_camera_create_video_filter_bin (CheeseCamera *camera, GError **error) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); gboolean ok = TRUE; GstPad *pad; cheese_camera_create_effects_preview_bin (camera, error); priv->video_filter_bin = gst_bin_new ("video_filter_bin"); if ((priv->camera_tee = gst_element_factory_make ("tee", "camera_tee")) == NULL) { cheese_camera_set_error_element_not_found (error, "tee"); return FALSE; } if ((priv->main_valve = gst_element_factory_make ("valve", "main_valve")) == NULL) { cheese_camera_set_error_element_not_found (error, "main_valve"); return FALSE; } if ((priv->effect_filter = gst_element_factory_make ("identity", "effect")) == NULL) { cheese_camera_set_error_element_not_found (error, "identity"); return FALSE; } priv->current_effect_desc = g_strdup("identity"); if ((priv->video_balance = gst_element_factory_make ("videobalance", "video_balance")) == NULL) { cheese_camera_set_error_element_not_found (error, "videobalance"); return FALSE; } if (error != NULL && *error != NULL) return FALSE; gst_bin_add_many (GST_BIN (priv->video_filter_bin), priv->camera_tee, priv->main_valve, priv->effect_filter, priv->video_balance, priv->effects_preview_bin, NULL); ok &= gst_element_link_many (priv->camera_tee, priv->main_valve, priv->effect_filter, priv->video_balance, NULL); gst_pad_link (gst_element_get_request_pad (priv->camera_tee, "src_%u"), gst_element_get_static_pad (priv->effects_preview_bin, "sink")); /* add ghostpads */ pad = gst_element_get_static_pad (priv->video_balance, "src"); gst_element_add_pad (priv->video_filter_bin, gst_ghost_pad_new ("src", pad)); gst_object_unref (GST_OBJECT (pad)); pad = gst_element_get_static_pad (priv->camera_tee, "sink"); gst_element_add_pad (priv->video_filter_bin, gst_ghost_pad_new ("sink", pad)); gst_object_unref (GST_OBJECT (pad)); if (!ok) g_error ("Unable to create filter bin"); return TRUE; } /* * cheese_camera_get_num_camera_devices: * @camera: a #CheeseCamera * * Get the number of #CheeseCameraDevice found on the system managed by * @camera. * * Returns: the number of #CheeseCameraDevice objects on the system */ static guint cheese_camera_get_num_camera_devices (CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); return priv->num_camera_devices; } /** * cheese_camera_get_selected_device: * @camera: a #CheeseCamera * * Get the currently-selected #CheeseCameraDevice of the @camera. * * Returns: (transfer none): a #CheeseCameraDevice, or %NULL if there is no * selected device */ CheeseCameraDevice * cheese_camera_get_selected_device (CheeseCamera *camera) { CheeseCameraPrivate *priv; g_return_val_if_fail (CHEESE_IS_CAMERA (camera), NULL); priv = cheese_camera_get_instance_private (camera); if (cheese_camera_get_num_camera_devices (camera) > 0) return CHEESE_CAMERA_DEVICE ( g_ptr_array_index (priv->camera_devices, priv->selected_device)); else return NULL; } /** * cheese_camera_switch_camera_device: * @camera: a #CheeseCamera * * Toggle the playing/recording state of the @camera. */ void cheese_camera_switch_camera_device (CheeseCamera *camera) { CheeseCameraPrivate *priv; gboolean pipeline_was_playing; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); /* gboolean was_recording = FALSE; */ pipeline_was_playing = FALSE; if (priv->is_recording) { cheese_camera_stop_video_recording (camera); /* was_recording = TRUE; */ } if (priv->pipeline_is_playing) { cheese_camera_stop (camera); pipeline_was_playing = TRUE; } cheese_camera_set_camera_source (camera); if (pipeline_was_playing) { cheese_camera_play (camera); } /* if (was_recording) * { * Restart recording... ? * } */ } /** * cheese_camera_play: * @camera: a #CheeseCamera * * Set the state of the GStreamer pipeline associated with the #CheeseCamera to * playing. */ static void cheese_camera_set_new_caps (CheeseCamera *camera) { CheeseCameraPrivate *priv; CheeseCameraDevice *device; GstCaps *caps; gchar *caps_desc; int width, height; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); device = g_ptr_array_index (priv->camera_devices, priv->selected_device); caps = cheese_camera_device_get_caps_for_format (device, priv->current_format); if (gst_caps_is_empty (caps)) { gst_caps_unref (caps); g_boxed_free (CHEESE_TYPE_VIDEO_FORMAT, priv->current_format); priv->current_format = cheese_camera_device_get_best_format (device); g_object_notify_by_pspec (G_OBJECT (camera), properties[PROP_FORMAT]); caps = cheese_camera_device_get_caps_for_format (device, priv->current_format); } if (!gst_caps_is_empty (caps)) { guint i; GST_INFO_OBJECT (camera, "SETTING caps %" GST_PTR_FORMAT, caps); g_object_set (gst_bin_get_by_name (GST_BIN (priv->video_source), "video_source_filter"), "caps", caps, NULL); /* If the selected caps are image/jpeg, video_source will convert them * to raw video internally. Therefore, camerabin should always use * video/x-raw regardless of the caps passed to video_source_filter. */ caps = gst_caps_make_writable (caps); for (i = 0; i != gst_caps_get_size (caps); ++i) { gst_structure_set_name (gst_caps_get_structure (caps, i), "video/x-raw"); } g_object_set (priv->camerabin, "viewfinder-caps", caps, "image-capture-caps", caps, NULL); /* GStreamer >= 1.1.4 expects fully-specified video-capture-source caps. */ caps = gst_caps_fixate (caps); g_object_set (priv->camerabin, "video-capture-caps", caps, NULL); gst_caps_unref (caps); width = priv->current_format->width; width = width > 640 ? 640 : width; height = width * priv->current_format->height / priv->current_format->width; /* GStreamer will crash if this is not a multiple of 2! */ height = (height + 1) & ~1; caps_desc = g_strdup_printf ("video/x-raw, width=%d, height=%d", width, height); caps = gst_caps_from_string (caps_desc); g_free (caps_desc); g_object_set (priv->effects_capsfilter, "caps", caps, NULL); } gst_caps_unref (caps); } void cheese_camera_play (CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); cheese_camera_set_new_caps (camera); g_object_set (priv->camera_source, "video-source", priv->video_source, NULL); g_object_set (priv->main_valve, "drop", FALSE, NULL); gst_element_set_state (priv->camerabin, GST_STATE_PLAYING); priv->pipeline_is_playing = TRUE; } /** * cheese_camera_stop: * @camera: a #CheeseCamera * * Set the state of the GStreamer pipeline associated with the #CheeseCamera to * NULL. */ void cheese_camera_stop (CheeseCamera *camera) { CheeseCameraPrivate *priv; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); if (priv->camerabin != NULL) gst_element_set_state (priv->camerabin, GST_STATE_NULL); priv->pipeline_is_playing = FALSE; } /* * cheese_camera_change_effect_filter: * @camera: a #CheeseCamera * @new_filter: the new effect filter to apply * * Change the current effect to that of @element. */ static void cheese_camera_change_effect_filter (CheeseCamera *camera, GstElement *new_filter) { CheeseCameraPrivate *priv; gboolean ok; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); g_object_set (G_OBJECT (priv->main_valve), "drop", TRUE, NULL); gst_element_unlink_many (priv->main_valve, priv->effect_filter, priv->video_balance, NULL); g_object_ref (priv->effect_filter); gst_bin_remove (GST_BIN (priv->video_filter_bin), priv->effect_filter); gst_element_set_state (priv->effect_filter, GST_STATE_NULL); g_object_unref (priv->effect_filter); gst_bin_add (GST_BIN (priv->video_filter_bin), new_filter); ok = gst_element_link_many (priv->main_valve, new_filter, priv->video_balance, NULL); gst_element_set_state (new_filter, GST_STATE_PAUSED); g_return_if_fail (ok); g_object_set (G_OBJECT (priv->main_valve), "drop", FALSE, NULL); priv->effect_filter = new_filter; } /* * cheese_camera_element_from_effect: * @camera: a #CheeseCamera * @effect: the #CheeseEffect to use as the template * * Create a new #GstElement based on the @effect template. * * Returns: a new #GstElement */ static GstElement * cheese_camera_element_from_effect (CheeseCamera *camera, CheeseEffect *effect) { gchar *effects_pipeline_desc; gchar *name; GstElement *effect_filter; GError *err = NULL; gchar *effect_desc; GstElement *colorspace1; GstElement *colorspace2; GstPad *pad; g_object_get (G_OBJECT (effect), "pipeline-desc", &effect_desc, "name", &name, NULL); effects_pipeline_desc = g_strconcat ("videoconvert name=colorspace1 ! ", effect_desc, " ! videoconvert name=colorspace2", NULL); g_free (effect_desc); effect_filter = gst_parse_bin_from_description (effects_pipeline_desc, FALSE, &err); g_free (effects_pipeline_desc); if (!effect_filter || (err != NULL)) { g_clear_error (&err); g_warning ("Error with effect filter %s. Ignored", name); g_free (name); return NULL; } g_free (name); /* Add ghost pads to effect_filter bin */ colorspace1 = gst_bin_get_by_name (GST_BIN (effect_filter), "colorspace1"); colorspace2 = gst_bin_get_by_name (GST_BIN (effect_filter), "colorspace2"); pad = gst_element_get_static_pad (colorspace1, "sink"); gst_element_add_pad (effect_filter, gst_ghost_pad_new ("sink", pad)); gst_object_unref (GST_OBJECT (pad)); gst_object_unref (GST_OBJECT (colorspace1)); pad = gst_element_get_static_pad (colorspace2, "src"); gst_element_add_pad (effect_filter, gst_ghost_pad_new ("src", pad)); gst_object_unref (GST_OBJECT (pad)); gst_object_unref (GST_OBJECT (colorspace2)); return effect_filter; } /** * cheese_camera_set_effect: * @camera: a #CheeseCamera * @effect: a #CheeseEffect * * Set the @effect on the @camera. */ void cheese_camera_set_effect (CheeseCamera *camera, CheeseEffect *effect) { CheeseCameraPrivate *priv; const gchar *effect_desc = cheese_effect_get_pipeline_desc (effect); GstElement *effect_filter; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); if (strcmp (priv->current_effect_desc, effect_desc) == 0) { GST_INFO_OBJECT (camera, "Effect is: \"%s\", not updating", effect_desc); return; } GST_INFO_OBJECT (camera, "Changing effect to: \"%s\"", effect_desc); if (strcmp (effect_desc, "identity") == 0) effect_filter = gst_element_factory_make ("identity", "effect"); else effect_filter = cheese_camera_element_from_effect (camera, effect); if (effect_filter != NULL) { cheese_camera_change_effect_filter (camera, effect_filter); g_free (priv->current_effect_desc); priv->current_effect_desc = g_strdup (effect_desc); } } /** * cheese_camera_toggle_effects_pipeline: * @camera: a #CheeseCamera * @active: %TRUE if effects pipeline is active, %FALSE otherwise * * Control whether the effects pipeline is enabled for @camera. */ void cheese_camera_toggle_effects_pipeline (CheeseCamera *camera, gboolean active) { CheeseCameraPrivate *priv; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); if (active) { g_object_set (G_OBJECT (priv->effects_valve), "drop", FALSE, NULL); if (!priv->is_recording) g_object_set (G_OBJECT (priv->main_valve), "drop", TRUE, NULL); } else { g_object_set (G_OBJECT (priv->effects_valve), "drop", TRUE, NULL); g_object_set (G_OBJECT (priv->main_valve), "drop", FALSE, NULL); } priv->effect_pipeline_is_playing = active; } static void cheese_camera_connected_size_change_cb (ClutterGstContent *content, gint width, gint height, ClutterActor *actor) { clutter_actor_set_size (actor, width, height); } /** * cheese_camera_connect_effect_texture: * @camera: a #CheeseCamera * @effect: a #CheeseEffect * @texture: a #ClutterActor * * Connect the supplied @texture to the @camera, using @effect. */ void cheese_camera_connect_effect_texture (CheeseCamera *camera, CheeseEffect *effect, ClutterActor *texture) { CheeseCameraPrivate *priv; GstElement *effect_filter; GstElement *display_element; GstElement *display_queue; GstElement *control_valve; gboolean ok; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); ok = TRUE; g_object_set (G_OBJECT (priv->effects_valve), "drop", TRUE, NULL); control_valve = gst_element_factory_make ("valve", NULL); g_object_set (G_OBJECT (effect), "control-valve", control_valve, NULL); display_queue = gst_element_factory_make ("queue", NULL); effect_filter = cheese_camera_element_from_effect (camera, effect); display_element = GST_ELEMENT (clutter_gst_video_sink_new ()); g_object_set (G_OBJECT (texture), "content", g_object_new (CLUTTER_GST_TYPE_CONTENT, "sink", display_element, NULL), NULL); g_signal_connect (G_OBJECT (clutter_actor_get_content (texture)), "size-change", G_CALLBACK (cheese_camera_connected_size_change_cb), texture); gst_bin_add_many (GST_BIN (priv->video_filter_bin), control_valve, effect_filter, display_queue, display_element, NULL); ok = gst_element_link_many (priv->effects_tee, control_valve, effect_filter, display_queue, display_element, NULL); g_return_if_fail (ok); /* HACK: I don't understand GStreamer enough to know why this works. */ gst_element_set_state (control_valve, GST_STATE_PLAYING); gst_element_set_state (effect_filter, GST_STATE_PLAYING); gst_element_set_state (display_queue, GST_STATE_PLAYING); gst_element_set_state (display_element, GST_STATE_PLAYING); gst_element_set_locked_state (display_element, TRUE); if (!ok) g_warning ("Could not create effects pipeline"); g_object_set (G_OBJECT (priv->effects_valve), "drop", FALSE, NULL); } /* * cheese_camera_set_tags: * @camera: a #CheeseCamera * * Set tags on the camerabin element, such as the stream creation time and the * name of the application. Call this just before starting the capture process. */ static void cheese_camera_set_tags (CheeseCamera *camera) { CheeseCameraPrivate *priv; CheeseCameraDevice *device; const gchar *device_name; GstDateTime *datetime; GstTagList *taglist; device = cheese_camera_get_selected_device (camera); device_name = cheese_camera_device_get_name (device); datetime = gst_date_time_new_now_local_time(); taglist = gst_tag_list_new ( GST_TAG_APPLICATION_NAME, PACKAGE_STRING, GST_TAG_DATE_TIME, datetime, GST_TAG_DEVICE_MODEL, device_name, GST_TAG_KEYWORDS, PACKAGE_NAME, NULL); priv = cheese_camera_get_instance_private (camera); gst_tag_setter_merge_tags (GST_TAG_SETTER (priv->camerabin), taglist, GST_TAG_MERGE_REPLACE); gst_date_time_unref (datetime); gst_tag_list_unref (taglist); } /** * cheese_camera_start_video_recording: * @camera: a #CheeseCamera * @filename: (type filename): the name of the video file to where the * recording will be saved * * Start a video recording with the @camera and save it to @filename. */ void cheese_camera_start_video_recording (CheeseCamera *camera, const gchar *filename) { CheeseCameraPrivate *priv; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); g_object_set (priv->camerabin, "mode", MODE_VIDEO, NULL); g_object_set (priv->camerabin, "location", filename, NULL); cheese_camera_set_tags (camera); g_signal_emit_by_name (priv->camerabin, "start-capture", 0); priv->is_recording = TRUE; } /* * cheese_camera_force_stop_video_recording: * @data: a #CheeseCamera * * Forcibly stop a #CheeseCamera from recording video. * * Returns: %FALSE */ static gboolean cheese_camera_force_stop_video_recording (gpointer data) { CheeseCamera *camera = CHEESE_CAMERA (data); CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); if (priv->is_recording) { GST_WARNING ("Cannot cleanly shutdown recording pipeline, forcing"); g_signal_emit (camera, camera_signals[VIDEO_SAVED], 0); cheese_camera_stop (camera); cheese_camera_play (camera); priv->is_recording = FALSE; } return FALSE; } /** * cheese_camera_stop_video_recording: * @camera: a #CheeseCamera * * Stop recording video on the @camera. */ void cheese_camera_stop_video_recording (CheeseCamera *camera) { CheeseCameraPrivate *priv; GstState state; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); gst_element_get_state (priv->camerabin, &state, NULL, 0); if (state == GST_STATE_PLAYING) { g_signal_emit_by_name (priv->camerabin, "stop-capture", 0); } else { cheese_camera_force_stop_video_recording (camera); } } /** * cheese_camera_take_photo: * @camera: a #CheeseCamera * @filename: (type filename): name of the file to save a photo to * * Save a photo taken with the @camera to a new file at @filename. * * Returns: %TRUE on success, %FALSE if an error occurred */ gboolean cheese_camera_take_photo (CheeseCamera *camera, const gchar *filename) { CheeseCameraPrivate *priv; gboolean ready; g_return_val_if_fail (CHEESE_IS_CAMERA (camera), FALSE); priv = cheese_camera_get_instance_private (camera); g_object_get (priv->camera_source, "ready-for-capture", &ready, NULL); if (!ready) { GST_WARNING ("Still waiting for previous photo data, ignoring new request"); return FALSE; } g_free (priv->photo_filename); priv->photo_filename = g_strdup (filename); /* Take the photo*/ /* Only copy the data if we're giving away a pixbuf, * not if we're throwing everything away straight away */ if (priv->photo_filename == NULL) return FALSE; g_object_set (priv->camerabin, "location", priv->photo_filename, NULL); g_object_set (priv->camerabin, "mode", MODE_IMAGE, NULL); cheese_camera_set_tags (camera); g_signal_emit_by_name (priv->camerabin, "start-capture", 0); return TRUE; } /** * cheese_camera_take_photo_pixbuf: * @camera: a #CheeseCamera * * Take a photo with the @camera and emit it in the ::capture-start signal as a * #GdkPixbuf. * * Returns: %TRUE if the photo was successfully captured, %FALSE otherwise */ gboolean cheese_camera_take_photo_pixbuf (CheeseCamera *camera) { CheeseCameraPrivate *priv; GstCaps *caps; gboolean ready; g_return_val_if_fail (CHEESE_IS_CAMERA (camera), FALSE); priv = cheese_camera_get_instance_private (camera); g_object_get (priv->camera_source, "ready-for-capture", &ready, NULL); if (!ready) { GST_WARNING ("Still waiting for previous photo data, ignoring new request"); return FALSE; } caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, "RGB", NULL); g_object_set (G_OBJECT (priv->camerabin), "post-previews", TRUE, NULL); g_object_set (G_OBJECT (priv->camerabin), "preview-caps", caps, NULL); gst_caps_unref (caps); if (priv->photo_filename) g_free (priv->photo_filename); priv->photo_filename = NULL; /* Take the photo */ g_object_set (priv->camerabin, "location", NULL, NULL); g_object_set (priv->camerabin, "mode", MODE_IMAGE, NULL); g_signal_emit_by_name (priv->camerabin, "start-capture", 0); return TRUE; } static void cheese_camera_finalize (GObject *object) { CheeseCamera *camera; CheeseCameraPrivate *priv; camera = CHEESE_CAMERA (object); priv = cheese_camera_get_instance_private (camera); cheese_camera_stop (camera); if (priv->camerabin != NULL) gst_object_unref (priv->camerabin); if (priv->photo_filename) g_free (priv->photo_filename); g_free (priv->current_effect_desc); g_clear_object (&priv->device); g_boxed_free (CHEESE_TYPE_VIDEO_FORMAT, priv->current_format); /* Free CheeseCameraDevice array */ g_ptr_array_free (priv->camera_devices, TRUE); g_free (priv->initial_name); g_clear_object (&priv->monitor); G_OBJECT_CLASS (cheese_camera_parent_class)->finalize (object); } static void cheese_camera_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { CheeseCamera *self; CheeseCameraPrivate *priv; self = CHEESE_CAMERA (object); priv = cheese_camera_get_instance_private (self); switch (prop_id) { case PROP_VIDEO_TEXTURE: g_value_set_pointer (value, priv->video_texture); break; case PROP_DEVICE: g_value_set_object (value, priv->device); break; case PROP_FORMAT: g_value_set_boxed (value, priv->current_format); break; case PROP_NUM_CAMERA_DEVICES: g_value_set_uint (value, priv->num_camera_devices); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cheese_camera_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { CheeseCamera *self; CheeseCameraPrivate *priv; self = CHEESE_CAMERA (object); priv = cheese_camera_get_instance_private (self); switch (prop_id) { case PROP_VIDEO_TEXTURE: priv->video_texture = g_value_get_pointer (value); break; case PROP_DEVICE: g_clear_object (&priv->device); priv->device = g_value_dup_object (value); break; case PROP_FORMAT: if (priv->current_format != NULL) g_boxed_free (CHEESE_TYPE_VIDEO_FORMAT, priv->current_format); priv->current_format = g_value_dup_boxed (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cheese_camera_class_init (CheeseCameraClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); #ifndef GST_DISABLE_GST_DEBUG if (cheese_camera_cat == NULL) GST_DEBUG_CATEGORY_INIT (cheese_camera_cat, "cheese-camera", 0, "Cheese Camera"); #endif object_class->finalize = cheese_camera_finalize; object_class->get_property = cheese_camera_get_property; object_class->set_property = cheese_camera_set_property; /** * CheeseCamera::photo-saved: * @camera: a #CheeseCamera * * Emitted when a photo was saved to disk. */ camera_signals[PHOTO_SAVED] = g_signal_new ("photo-saved", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (CheeseCameraClass, photo_saved), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * CheeseCamera::photo-taken: * @camera: a #CheeseCamera * @pixbuf: a #GdkPixbuf of the photo which was taken * * Emitted when a photo was taken. */ camera_signals[PHOTO_TAKEN] = g_signal_new ("photo-taken", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (CheeseCameraClass, photo_taken), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDK_TYPE_PIXBUF); /** * CheeseCamera::video-saved: * @camera: a #CheeseCamera * * Emitted when a video was saved to disk. */ camera_signals[VIDEO_SAVED] = g_signal_new ("video-saved", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (CheeseCameraClass, video_saved), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * CheeseCamera::state-flags-changed: * @camera: a #CheeseCamera * @state: the #GstState which @camera changed to * * Emitted when the state of the @camera #GstElement changed. */ camera_signals[STATE_FLAGS_CHANGED] = g_signal_new ("state-flags-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (CheeseCameraClass, state_flags_changed), NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); /** * CheeseCamera:video-texture: * * The video texture for the #CheeseCamera to render into. */ properties[PROP_VIDEO_TEXTURE] = g_param_spec_pointer ("video-texture", "Video texture", "The video texture for the CheeseCamera to render into", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * CheeseCamera:device: * * The device object to capture from. */ properties[PROP_DEVICE] = g_param_spec_object ("device", "Device", "The device object to capture from", CHEESE_TYPE_CAMERA_DEVICE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * CheeseCamera:format: * * The format of the video capture device. */ properties[PROP_FORMAT] = g_param_spec_boxed ("format", "Video format", "The format of the video capture device", CHEESE_TYPE_VIDEO_FORMAT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * CheeseCamera:num-camera-devices: * * The currently number of camera devices available for being used. */ properties[PROP_NUM_CAMERA_DEVICES] = g_param_spec_uint ("num-camera-devices", "Number of camera devices", "The currently number of camera devices available on the system", 0, G_MAXUINT8, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST, properties); } static void cheese_camera_init (CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); priv->is_recording = FALSE; priv->pipeline_is_playing = FALSE; } /** * cheese_camera_new: * @video_texture: an actor in which to render the video * @name: (allow-none): the name of the device * @x_resolution: the resolution width * @y_resolution: the resolution height * * Create a new #CheeseCamera object. * * Returns: a new #CheeseCamera */ CheeseCamera * cheese_camera_new (ClutterActor *video_texture, const gchar *name, gint x_resolution, gint y_resolution) { CheeseCamera *camera; CheeseVideoFormat format = { x_resolution, y_resolution }; camera = g_object_new (CHEESE_TYPE_CAMERA, "video-texture", video_texture, "format", &format, NULL); if (name) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); priv->initial_name = g_strdup (name); } return camera; } /** * cheese_camera_set_device: * @camera: a #CheeseCamera * @device: the device object * * Set the active video capture device of the @camera. */ void cheese_camera_set_device (CheeseCamera *camera, CheeseCameraDevice *device) { g_return_if_fail (CHEESE_IS_CAMERA (camera)); g_object_set (camera, "device", device, NULL); } static void cheese_camera_size_change_cb (ClutterGstContent *content, gint width, gint height, CheeseCamera* camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); clutter_actor_set_size (priv->video_texture, width, height); } /** * cheese_camera_setup: * @camera: a #CheeseCamera * @device: (allow-none): the video capture device, or %NULL * @error: return location for a #GError, or %NULL * * Setup a video capture device. */ void cheese_camera_setup (CheeseCamera *camera, CheeseCameraDevice *device, GError **error) { CheeseCameraPrivate *priv; GError *tmp_error = NULL; GstElement *video_sink; g_return_if_fail (error == NULL || *error == NULL); g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); cheese_camera_detect_camera_devices (camera); if (priv->num_camera_devices < 1) { g_set_error (error, CHEESE_CAMERA_ERROR, CHEESE_CAMERA_ERROR_NO_DEVICE, _("No device found")); return; } if (device != NULL) { cheese_camera_set_device (camera, device); } else { guint i; for (i = 0; i < priv->num_camera_devices; i++) { device = g_ptr_array_index (priv->camera_devices, i); if (g_strcmp0 (cheese_camera_device_get_name (device), priv->initial_name) == 0 || g_strcmp0 (cheese_camera_device_get_path (device), priv->initial_name) == 0) { cheese_camera_set_device (camera, device); break; } } } if ((priv->camerabin = gst_element_factory_make ("camerabin", "camerabin")) == NULL) { cheese_camera_set_error_element_not_found (error, "camerabin"); } if ((priv->camera_source = gst_element_factory_make ("wrappercamerabinsrc", "camera_source")) == NULL) { cheese_camera_set_error_element_not_found (error, "wrappercamerabinsrc"); } g_object_set (priv->camerabin, "camera-source", priv->camera_source, NULL); /* Create a clutter-gst sink and set it as camerabin sink*/ video_sink = GST_ELEMENT (clutter_gst_video_sink_new ()); g_object_set (G_OBJECT (priv->video_texture), "content", g_object_new (CLUTTER_GST_TYPE_CONTENT, "sink", video_sink, NULL), NULL); g_signal_connect (G_OBJECT (clutter_actor_get_content (priv->video_texture)), "size-change", G_CALLBACK(cheese_camera_size_change_cb), camera); g_object_set (G_OBJECT (priv->camerabin), "viewfinder-sink", video_sink, NULL); /* Set flags to enable conversions*/ cheese_camera_set_camera_source (camera); cheese_camera_set_video_recording (camera, &tmp_error); cheese_camera_create_video_filter_bin (camera, &tmp_error); if (tmp_error != NULL || (error != NULL && *error != NULL)) { g_propagate_prefixed_error (error, tmp_error, _("One or more needed GStreamer elements are missing: ")); GST_WARNING ("%s", (*error)->message); return; } g_object_set (G_OBJECT (priv->camera_source), "video-source-filter", priv->video_filter_bin, NULL); priv->bus = gst_element_get_bus (priv->camerabin); gst_bus_add_signal_watch (priv->bus); g_signal_connect (G_OBJECT (priv->bus), "message", G_CALLBACK (cheese_camera_bus_message_cb), camera); } /** * cheese_camera_get_camera_devices: * @camera: a #CheeseCamera * * Get the list of #CheeseCameraDevice objects, representing active video * capture devices on the system. * * Returns: (element-type Cheese.CameraDevice) (transfer container): an array * of #CheeseCameraDevice */ GPtrArray * cheese_camera_get_camera_devices (CheeseCamera *camera) { CheeseCameraPrivate *priv; g_return_val_if_fail (CHEESE_IS_CAMERA (camera), NULL); priv = cheese_camera_get_instance_private (camera); return g_ptr_array_ref (priv->camera_devices); } /** * cheese_camera_get_video_formats: * @camera: a #CheeseCamera * * Gets the list of #CheeseVideoFormat supported by the selected * #CheeseCameraDevice on the @camera. * * Returns: (element-type Cheese.VideoFormat) (transfer container): a #GList of * #CheeseVideoFormat, or %NULL if there was no device selected */ GList * cheese_camera_get_video_formats (CheeseCamera *camera) { CheeseCameraDevice *device; g_return_val_if_fail (CHEESE_IS_CAMERA (camera), NULL); device = cheese_camera_get_selected_device (camera); if (device) return cheese_camera_device_get_format_list (device); else return NULL; } /* * cheese_camera_is_playing: * @camera: a #CheeseCamera * * Get whether the @camera is in the playing state. * * Returns: %TRUE if the #CheeseCamera is in the playing state, %FALSE * otherwise */ static gboolean cheese_camera_is_playing (CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); return priv->pipeline_is_playing; } /** * cheese_camera_set_video_format: * @camera: a #CheeseCamera * @format: a #CheeseVideoFormat * * Sets a #CheeseVideoFormat on a #CheeseCamera, restarting the video stream if * necessary. */ void cheese_camera_set_video_format (CheeseCamera *camera, CheeseVideoFormat *format) { CheeseCameraPrivate *priv; g_return_if_fail (CHEESE_IS_CAMERA (camera) || format != NULL); priv = cheese_camera_get_instance_private (camera); if (!(priv->current_format->width == format->width && priv->current_format->height == format->height)) { g_object_set (G_OBJECT (camera), "format", format, NULL); if (cheese_camera_is_playing (camera)) { cheese_camera_stop (camera); cheese_camera_play (camera); } } } /** * cheese_camera_get_current_video_format: * @camera: a #CheeseCamera * * Get the #CheeseVideoFormat that is currently set on the @camera. * * Returns: (transfer none): the #CheeseVideoFormat set on the #CheeseCamera */ const CheeseVideoFormat * cheese_camera_get_current_video_format (CheeseCamera *camera) { CheeseCameraPrivate *priv; g_return_val_if_fail (CHEESE_IS_CAMERA (camera), NULL); priv = cheese_camera_get_instance_private (camera); return priv->current_format; } /** * cheese_camera_get_balance_property_range: * @camera: a #CheeseCamera * @property: name of the balance property * @min: (out): minimum value * @max: (out): maximum value * @def: (out): default value * * Get the minimum, maximum and default values for the requested @property of * the @camera. * * Returns: %TRUE if the operation was successful, %FALSE otherwise */ gboolean cheese_camera_get_balance_property_range (CheeseCamera *camera, const gchar *property, gdouble *min, gdouble *max, gdouble *def) { CheeseCameraPrivate *priv; GParamSpec *pspec; g_return_val_if_fail (CHEESE_IS_CAMERA (camera), FALSE); priv = cheese_camera_get_instance_private (camera); *min = 0.0; *max = 1.0; *def = 0.5; if (!GST_IS_ELEMENT (priv->video_balance)) return FALSE; pspec = g_object_class_find_property ( G_OBJECT_GET_CLASS (G_OBJECT (priv->video_balance)), property); g_return_val_if_fail (G_IS_PARAM_SPEC_DOUBLE (pspec), FALSE); *min = G_PARAM_SPEC_DOUBLE (pspec)->minimum; *max = G_PARAM_SPEC_DOUBLE (pspec)->maximum; *def = G_PARAM_SPEC_DOUBLE (pspec)->default_value; return TRUE; } /** * cheese_camera_set_balance_property: * @camera: A #CheeseCamera * @property: name of the balance property * @value: value to be set * * Set the requested @property on the @camera to @value. */ void cheese_camera_set_balance_property (CheeseCamera *camera, const gchar *property, gdouble value) { CheeseCameraPrivate *priv; g_return_if_fail (CHEESE_IS_CAMERA (camera)); priv = cheese_camera_get_instance_private (camera); g_object_set (G_OBJECT (priv->video_balance), property, value, NULL); } /** * cheese_camera_get_recorded_time: * @camera: A #CheeseCamera * * Get a string representation of the playing time * of the current video recording * * Returns: A string with the time representation. */ gchar * cheese_camera_get_recorded_time (CheeseCamera *camera) { CheeseCameraPrivate *priv = cheese_camera_get_instance_private (camera); GstFormat format = GST_FORMAT_TIME; gint64 curtime; GstElement *videosink; const gint TUNIT_60 = 60; gint total_time; gint hours; gint minutes; gint seconds; gboolean ret = FALSE; g_return_val_if_fail (CHEESE_IS_CAMERA (camera), NULL); videosink = gst_bin_get_by_name (GST_BIN_CAST (priv->camerabin), "videobin-filesink"); if (videosink) { ret = gst_element_query_position (videosink, format, &curtime); gst_object_unref (videosink); } if (ret) { // Substract seconds, minutes and hours. total_time = GST_TIME_AS_SECONDS (curtime); seconds = total_time % TUNIT_60; total_time = total_time - seconds; minutes = (total_time % (TUNIT_60 * TUNIT_60)) / TUNIT_60; total_time = total_time - (minutes * TUNIT_60); hours = total_time / (TUNIT_60 * TUNIT_60); /* Translators: This is a time format, like "09:05:02" for 9 * hours, 5 minutes, and 2 seconds. You may change ":" to * the separator that your locale uses or use "%Id" instead * of "%d" if your locale uses localized digits. */ return g_strdup_printf (C_("time format", "%02i:%02i:%02i"), hours, minutes, seconds); } else { GST_WARNING ("Failed to get time from video filesink from camerabin"); return NULL; } }