From 11a78399d8f81a48c8d16dcee1cadc63f9df68ef Mon Sep 17 00:00:00 2001 From: Stefan Kost Date: Fri, 12 Jun 2009 10:40:48 +0300 Subject: camerabin: add camerabin examples gst-camera is a gtk-test app to play with the imagecapture and videorecording. gst-camera-perf is a tool to run various scenarios and take time meassurements (e.g. shot-to-shot). Also sort the output files in configure.ac a bit to be in alphabetical order. --- tests/examples/Makefile.am | 6 +- tests/examples/camerabin/.gitignore | 3 + tests/examples/camerabin/Makefile.am | 39 + tests/examples/camerabin/gst-camera-perf.c | 726 ++++++++++ tests/examples/camerabin/gst-camera-perf.glade | 120 ++ tests/examples/camerabin/gst-camera.c | 1756 ++++++++++++++++++++++++ tests/examples/camerabin/gst-camera.glade | 397 ++++++ 7 files changed, 3044 insertions(+), 3 deletions(-) create mode 100644 tests/examples/camerabin/.gitignore create mode 100644 tests/examples/camerabin/Makefile.am create mode 100644 tests/examples/camerabin/gst-camera-perf.c create mode 100644 tests/examples/camerabin/gst-camera-perf.glade create mode 100644 tests/examples/camerabin/gst-camera.c create mode 100644 tests/examples/camerabin/gst-camera.glade (limited to 'tests/examples') diff --git a/tests/examples/Makefile.am b/tests/examples/Makefile.am index e68204e3..45d94378 100644 --- a/tests/examples/Makefile.am +++ b/tests/examples/Makefile.am @@ -1,5 +1,5 @@ if HAVE_GTK -GTK_EXAMPLES=scaletempo mxf +GTK_EXAMPLES=camerabin mxf scaletempo else GTK_EXAMPLES= endif @@ -10,5 +10,5 @@ else DIRECTFB_DIR= endif -SUBDIRS= $(DIRECTFB_DIR) $(GTK_EXAMPLES) switch shapewipe -DIST_SUBDIRS= directfb switch scaletempo shapewipe mxf +SUBDIRS= $(DIRECTFB_DIR) $(GTK_EXAMPLES) shapewipe switch +DIST_SUBDIRS= directfb mxf scaletempo shapewipe switch diff --git a/tests/examples/camerabin/.gitignore b/tests/examples/camerabin/.gitignore new file mode 100644 index 00000000..eaedc096 --- /dev/null +++ b/tests/examples/camerabin/.gitignore @@ -0,0 +1,3 @@ +gst-camera +gst-camera-perf + diff --git a/tests/examples/camerabin/Makefile.am b/tests/examples/camerabin/Makefile.am new file mode 100644 index 00000000..df88cf14 --- /dev/null +++ b/tests/examples/camerabin/Makefile.am @@ -0,0 +1,39 @@ +GST_CAMERABIN_GLADE_FILES = gst-camera.glade + +if HAVE_GLADE +if HAVE_GTK + +GST_CAMERABIN_GTK_EXAMPLES = gst-camera + +gst_camera_SOURCES = gst-camera.c +gst_camera_CFLAGS = \ + -I$(top_builddir)/gst-libs \ + $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(GLADE_CFLAGS) \ + -DGST_USE_UNSTABLE_API +gst_camera_LDADD = \ + $(top_builddir)/gst-libs/gst/interfaces/libgstphotography-@GST_MAJORMINOR@.la \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstinterfaces-@GST_MAJORMINOR@ \ + $(GST_LIBS) \ + $(GLADE_LIBS) + +gladedir = $(datadir)/gstreamer-@GST_MAJORMINOR@/camera-apps +glade_DATA = $(GST_CAMERABIN_GLADE_FILES) + +INCLUDES = -DCAMERA_APPS_GLADEDIR=\""$(gladedir)"\" + +else +GST_CAMERABIN_GTK_EXAMPLES = +endif +else +GST_CAMERABIN_GTK_EXAMPLES = +endif + +gst_camera_perf_SOURCES = gst-camera-perf.c +gst_camera_perf_CFLAGS = $(GST_CFLAGS) +gst_camera_perf_LDADD = $(GST_LIBS) + +bin_PROGRAMS = gst-camera-perf $(GST_CAMERABIN_GTK_EXAMPLES) + +EXTRA_DIST = $(GST_CAMERABIN_GLADE_FILES) + diff --git a/tests/examples/camerabin/gst-camera-perf.c b/tests/examples/camerabin/gst-camera-perf.c new file mode 100644 index 00000000..ece3d935 --- /dev/null +++ b/tests/examples/camerabin/gst-camera-perf.c @@ -0,0 +1,726 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + * This application runs various tests and messures how long it takes. + * FIXME: It needs to figure sane defaults for different hardware or support + * we could use GOption for specifying the parameters + * The config should have: + * - target times + * - filter-caps + * - preview-caps + * - user-res-fps + * - element-names: videoenc, audioenc, videomux, imageenc, videosrc, audiosrc + * Most of it is interpreted in setup_pipeline() + * + * gcc `pkg-config --cflags --libs gstreamer-0.10` gst-camera-perf.c -ogst-camera-perf + * + * plain linux: + * ./gst-camera-perf --src-colorspace=YUY2 --image-width=320 --image-height=240 --view-framerate-num=15 --view-framerate-den=1 + * + * maemo: + * ./gst-camera-perf --src-colorspace=UYVY --image-width=640 --image-height=480 --view-framerate-num=1491 --view-framerate-den=100 --video-src=v4l2camsrc --audio-enc=nokiaaacenc --video-enc=omx_mpeg4enc --video-mux=hantromp4mux + * ./gst-camera-perf --src-colorspace=UYVY --image-width=640 --image-height=480 --view-framerate-num=2999 --view-framerate-den=100 --video-src=v4l2camsrc --audio-enc=nokiaaacenc --video-enc=omx_mpeg4enc --video-mux=hantromp4mux + * ./gst-camera-perf --src-colorspace=UYVY --image-width=2592 --image-height=1968 --view-framerate-num=399 --view-framerate-den=100 --video-src=v4l2camsrc --audio-enc=nokiaaacenc --video-enc=omx_mpeg4enc --video-mux=hantromp4mux + * ./gst-camera-perf --src-colorspace=UYVY --image-width=2592 --image-height=1968 --view-framerate-num=325 --view-framerate-den=25 --video-src=v4l2camsrc --audio-enc=nokiaaacenc --video-enc=omx_mpeg4enc --video-mux=hantromp4mux --image-enc=dspjpegenc + */ + +/* + * Includes + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +/* + * enums, typedefs and defines + */ + +#define GET_TIME(t) \ +do { \ + t = gst_util_get_timestamp (); \ + GST_INFO("%2d ----------------------------------------", test_ix); \ +} while(0) + +#define DIFF_TIME(e,s,d) d=GST_CLOCK_DIFF(s,e) + +#define CONT_SHOTS 10 +#define TEST_CASES 9 + +typedef struct _ResultType +{ + GstClockTime avg; + GstClockTime min; + GstClockTime max; + guint32 times; +} ResultType; + +/* + * Global vars + */ +static GstElement *camera_bin = NULL; +static GMainLoop *loop = NULL; + +/* commandline options */ +static gchar *audiosrc_name = NULL; +static gchar *videosrc_name = NULL; +static gchar *audioenc_name = NULL; +static gchar *videoenc_name = NULL; +static gchar *imageenc_name = NULL; +static gchar *videomux_name = NULL; +static gchar *src_csp = NULL; +static gint image_width = 0; +static gint image_height = 0; +static gint view_framerate_num = 0; +static gint view_framerate_den = 0; + +/* test configuration for common callbacks */ +static GString *filename = NULL; +static guint32 num_pics = 0; +static guint32 num_pics_cont = 0; +//static guint32 num_vids = 0; +static guint test_ix = 0; +static gboolean signal_sink = FALSE; +static gboolean signal_shot = FALSE; +static gboolean signal_cont = FALSE; +//static gboolean signal_save = FALSE; + +/* time samples and test results */ +static GstClockTime t_initial = G_GUINT64_CONSTANT (0); +static GstClockTime t_final[CONT_SHOTS] = { G_GUINT64_CONSTANT (0), }; + +static GstClockTimeDiff diff; +static ResultType result; + +static const GstClockTime target[TEST_CASES] = { + 1000 * GST_MSECOND, + 0, /* 1500 * GST_MSECOND, not tested */ + 1500 * GST_MSECOND, + 2000 * GST_MSECOND, /* this should be shorter, as we can take next picture before preview is ready */ + 500 * GST_MSECOND, + 0, /* 2000 * GST_MSECOND, not tested */ + 3500 * GST_MSECOND, + 1000 * GST_MSECOND, + 0 /* 1000 * GST_MSECOND, not tested */ +}; + +static const gchar *test_names[TEST_CASES] = { + "Camera OFF to VF on", + "(3A latency)", + "Shot to snapshot", + "Shot to shot", + "Serial shooting", + "(Shutter lag)", + "Image saved", + "Mode change", + "(Video recording)" +}; + +/* + * Prototypes + */ + +static void print_result (void); +static gboolean run_test (gpointer user_data); + +/* + * Callbacks + */ + +static gboolean +img_sink_has_buffer (GstPad * pad, GstBuffer * buf, gpointer user_data) +{ + if (signal_sink) { + signal_sink = FALSE; + GET_TIME (t_final[0]); + } + return TRUE; +} + +static gboolean +img_capture_done (GstElement * camera, GString * fname, gpointer user_data) +{ + gboolean ret = FALSE; + gboolean print_and_restart = FALSE; + + GST_INFO ("shot %d, cont %d, num %d", signal_shot, signal_cont, + num_pics_cont); + + if (signal_shot) { + GET_TIME (t_final[num_pics_cont]); + signal_shot = FALSE; + switch (test_ix) { + case 6: + DIFF_TIME (t_final[num_pics_cont], t_initial, diff); + result.avg = result.min = result.max = diff; + print_and_restart = TRUE; + break; + } + GST_INFO ("%2d shot done", test_ix); + } + + if (signal_cont) { + gint i; + + if (num_pics_cont < CONT_SHOTS) { + gchar tmp[6]; + + GET_TIME (t_final[num_pics_cont]); + num_pics_cont++; + for (i = filename->len - 1; i > 0; --i) { + if (filename->str[i] == '_') + break; + } + snprintf (tmp, 6, "_%04d", num_pics_cont); + memcpy (filename->str + i, tmp, 5); + GST_INFO ("%2d cont new filename '%s'", test_ix, filename->str); + g_object_set (camera_bin, "filename", filename->str, NULL); + // FIXME: is burst capture broken? new filename and return TRUE should be enough + g_signal_emit_by_name (camera_bin, "user-start", NULL); + ret = TRUE; + } else { + GstClockTime max = 0; + GstClockTime min = -1; + GstClockTime total = 0; + GstClockTime first_shot = 0; + GstClockTime snd_shot = 0; + + num_pics_cont = 0; + signal_cont = FALSE; + + DIFF_TIME (t_final[0], t_initial, diff); + max < diff ? max = diff : max; + min > diff ? min = diff : min; + first_shot = diff; + total += diff; + + DIFF_TIME (t_final[1], t_final[0], diff); + max < diff ? max = diff : max; + min > diff ? min = diff : min; + snd_shot = diff; + total += diff; + + for (i = 2; i < CONT_SHOTS; ++i) { + DIFF_TIME (t_final[i], t_final[i - 1], diff); + + max < diff ? max = diff : max; + min > diff ? min = diff : min; + total += diff; + } + + result.avg = total / CONT_SHOTS; + result.min = min; + result.max = max; + print_and_restart = TRUE; + GST_INFO ("%2d cont done", test_ix); + } + } + + switch (test_ix) { + case 2: + case 3: + print_and_restart = TRUE; + break; + } + + if (print_and_restart) { + print_result (); + g_idle_add ((GSourceFunc) run_test, NULL); + return FALSE; + } + return ret; +} + +static gboolean +bus_callback (GstBus * bus, GstMessage * message, gpointer data) +{ + const GstStructure *st; + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR:{ + GError *err; + gchar *debug; + + gst_message_parse_error (message, &err, &debug); + g_print ("Error: %s\n", err->message); + g_error_free (err); + g_free (debug); + + /* Write debug graph to file */ + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (camera_bin), + GST_DEBUG_GRAPH_SHOW_ALL, "camerabin.error"); + + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_EOS: + /* end-of-stream */ + g_main_loop_quit (loop); + break; + default: + st = gst_message_get_structure (message); + if (st) { + if (gst_structure_has_name (st, "image-captured")) { + GST_INFO ("%2d image-captured", test_ix); + switch (test_ix) { + case 3: + GET_TIME (t_final[num_pics_cont]); + DIFF_TIME (t_final[num_pics_cont], t_initial, diff); + result.avg = result.min = result.max = diff; + break; + } + } else if (gst_structure_has_name (st, "preview-image")) { + GST_INFO ("%2d preview-image", test_ix); + switch (test_ix) { + case 2: + GET_TIME (t_final[num_pics_cont]); + DIFF_TIME (t_final[num_pics_cont], t_initial, diff); + result.avg = result.min = result.max = diff; + break; + } + } + } + /* unhandled message */ + break; + } + return TRUE; +} + + +/* + * Helpers + */ + +static void +cleanup_pipeline (void) +{ + if (camera_bin) { + gst_element_set_state (camera_bin, GST_STATE_NULL); + gst_element_get_state (camera_bin, NULL, NULL, GST_CLOCK_TIME_NONE); + gst_object_unref (camera_bin); + camera_bin = NULL; + } +} + +static gboolean +setup_pipeline_video_sink (void) +{ + GstElement *sink = NULL; + GstPad *pad = NULL; + + sink = gst_element_factory_make ("fakesink", NULL); + if (NULL == sink) { + g_warning ("failed to create sink\n"); + goto error; + } + + pad = gst_element_get_static_pad (sink, "sink"); + if (NULL == pad) { + g_warning ("sink has no pad named 'sink'\n"); + goto error; + } + + g_object_set (sink, "sync", TRUE, NULL); + gst_pad_add_buffer_probe (pad, (GCallback) img_sink_has_buffer, NULL); + gst_object_unref (pad); + + g_object_set (camera_bin, "vfsink", sink, NULL); + + return TRUE; +error: + if (sink) + gst_object_unref (sink); + return FALSE; +} + +static gboolean +setup_pipeline_element (const gchar * property_name, const gchar * element_name) +{ + gboolean res = TRUE; + + GstElement *elem; + if (element_name) { + elem = gst_element_factory_make (element_name, NULL); + if (elem) { + g_object_set (camera_bin, property_name, elem, NULL); + } else { + g_warning ("can't create element '%s' for property '%s'", element_name, + property_name); + res = FALSE; + } + } + return res; +} + +static gboolean +setup_pipeline (void) +{ + GstBus *bus; + gboolean res = TRUE; + + g_string_printf (filename, "test_%04u.jpg", num_pics); + + camera_bin = gst_element_factory_make ("camerabin", NULL); + if (NULL == camera_bin) { + g_warning ("can't create camerabin element\n"); + goto error; + } + + g_signal_connect (camera_bin, "img-done", (GCallback) img_capture_done, NULL); + + bus = gst_pipeline_get_bus (GST_PIPELINE (camera_bin)); + gst_bus_add_watch (bus, bus_callback, NULL); + gst_object_unref (bus); + + if (!setup_pipeline_video_sink ()) { + goto error; + } + + /* set properties */ + + if (src_csp && strlen (src_csp) == 4) { + GstCaps *filter_caps; + + /* FIXME: why do we need to set this? */ + filter_caps = gst_caps_new_simple ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, + GST_MAKE_FOURCC (src_csp[0], src_csp[1], src_csp[2], src_csp[3]), NULL); + if (filter_caps) { + g_object_set (camera_bin, "filename", filename->str, + "filter-caps", filter_caps, NULL); + gst_caps_unref (filter_caps); + } else { + g_warning ("can't make filter-caps with format=%s\n", src_csp); + goto error; + } + } + + /* configure used elements */ + res &= setup_pipeline_element ("audiosrc", audiosrc_name); + res &= setup_pipeline_element ("videosrc", videosrc_name); + res &= setup_pipeline_element ("audioenc", audioenc_name); + res &= setup_pipeline_element ("videoenc", videoenc_name); + res &= setup_pipeline_element ("imageenc", imageenc_name); + res &= setup_pipeline_element ("videomux", videomux_name); + if (!res) { + goto error; + } + + /* configure a resolution and framerate */ + if (image_width && image_height && view_framerate_num && view_framerate_den) { + g_signal_emit_by_name (camera_bin, "user-res-fps", image_width, + image_height, view_framerate_num, view_framerate_den, NULL); + } + + if (GST_STATE_CHANGE_FAILURE == + gst_element_set_state (camera_bin, GST_STATE_READY)) { + g_warning ("can't set camerabin to ready\n"); + goto error; + } + + if (GST_STATE_CHANGE_FAILURE == + gst_element_set_state (camera_bin, GST_STATE_PLAYING)) { + g_warning ("can't set camerabin to playing\n"); + goto error; + } + return TRUE; +error: + cleanup_pipeline (); + return FALSE; +} + +/* + * Tests + */ + +/* 01) Camera OFF to VF On + * + * This only tests the time it takes to create the pipeline and CameraBin + * element and have the first video frame available in ViewFinder. + * It is not testing the real init time. To do it, the timer must start before + * the app. + */ +static gboolean +test_01 (void) +{ + GET_TIME (t_initial); + if (setup_pipeline ()) { + /* MAKE SURE THE PIPELINE IS IN PLAYING STATE BEFORE START TAKING PICTURES + AND SO ON (otherwise it will deadlock) */ + gst_element_get_state (camera_bin, NULL, NULL, GST_CLOCK_TIME_NONE); + } + + GET_TIME (t_final[0]); + DIFF_TIME (t_final[0], t_initial, diff); + + result.avg = result.min = result.max = diff; + result.times = 1; + return TRUE; +} + + +/* 03) Shot to snapshot + * + * It tests the time between pressing the Shot button and having the photo shown + * in ViewFinder + */ +static gboolean +test_03 (void) +{ + GstCaps *snap_caps; + + /* FIXME: add options */ + snap_caps = gst_caps_from_string ("video/x-raw-rgb,width=320,height=240"); + g_object_set (camera_bin, "preview-caps", snap_caps, NULL); + gst_caps_unref (snap_caps); + + GET_TIME (t_initial); + g_signal_emit_by_name (camera_bin, "user-start", 0); + + /* the actual results are fetched in bus_callback::preview-image */ + result.times = 1; + return FALSE; +} + + +/* 04) Shot to shot + * It tests the time for being able to take a second shot after the first one. + */ +static gboolean +test_04 (void) +{ + GET_TIME (t_initial); + g_signal_emit_by_name (camera_bin, "user-start", 0); + + /* the actual results are fetched in bus_callback::image-captured */ + result.times = 1; + return FALSE; +} + +/* 05) Serial shooting + * + * It tests the time between shots in continuous mode. + */ +static gboolean +test_05 (void) +{ + signal_cont = TRUE; + GET_TIME (t_initial); + g_signal_emit_by_name (camera_bin, "user-start", 0); + + /* the actual results are fetched in img_capture_done */ + result.times = CONT_SHOTS; + return FALSE; +} + + +/* 07) Image saved + * + * It tests the time between pressing the Shot and the final image is saved to + * file system. + */ +static gboolean +test_07 (void) +{ + // signal_save = TRUE; + signal_shot = TRUE; + + GET_TIME (t_initial); + g_signal_emit_by_name (camera_bin, "user-start", 0); + /* call "user-stop" just to go back to initial state (view-finder) again */ + g_signal_emit_by_name (camera_bin, "user-stop", 0); + /* the actual results are fetched in img_capture_done */ + result.times = 1; + return FALSE; +} + + +/* 08) Mode change + * + * It tests the time it takes to change between still image and video recording + * mode (In this test we change the mode few times). + */ +static gboolean +test_08 (void) +{ + GstClockTime total = 0; + GstClockTime max = 0; + GstClockTime min = -1; + const gint count = 6; + gint i; + + for (i = 0; i < count; ++i) { + GET_TIME (t_final[i]); + g_object_set (camera_bin, "mode", (i + 1) & 1, NULL); + GET_TIME (t_final[i + 1]); + } + + for (i = 0; i < count; ++i) { + DIFF_TIME (t_final[i + 1], t_final[i], diff); + total += diff; + if (diff > max) + max = diff; + if (diff < min) + min = diff; + } + + result.avg = total / count; + result.min = min; + result.max = max; + result.times = count; + + /* just make sure we are back to still image mode again */ + g_object_set (camera_bin, "mode", 0, NULL); + return TRUE; +} + +typedef gboolean (*test_case) (void); +static test_case test_cases[TEST_CASES] = { + test_01, + NULL, + test_03, + test_04, + test_05, + NULL, + test_07, + test_08 +}; + +static void +print_result (void) +{ + printf ("| %6.02f%% ", 100.0f * (float) result.max / (float) target[test_ix]); + printf ("|%5u ms ", (guint) GST_TIME_AS_MSECONDS (target[test_ix])); + printf ("|%5u ms ", (guint) GST_TIME_AS_MSECONDS (result.avg)); + printf ("|%5u ms ", (guint) GST_TIME_AS_MSECONDS (result.min)); + printf ("|%5u ms ", (guint) GST_TIME_AS_MSECONDS (result.max)); + printf ("| %3d ", result.times); + printf ("| %-19s |\n", test_names[test_ix]); + test_ix++; +} + +static gboolean +run_test (gpointer user_data) +{ + gboolean ret = TRUE; + + printf ("| %02d ", test_ix + 1); + if (test_cases[test_ix]) { + memset (&result, 0, sizeof (ResultType)); + ret = test_cases[test_ix] (); + + //while (g_main_context_pending (NULL)) g_main_context_iteration (NULL,FALSE); + if (ret) { + print_result (); + } + + } else { + printf ("| test not implemented "); + printf ("| %-19s |\n", test_names[test_ix]); + test_ix++; + } + + if (!camera_bin || test_ix == TEST_CASES) { + GST_INFO ("done"); + g_main_loop_quit (loop); + return FALSE; + } else { + GST_INFO ("%2d result: %d", test_ix, ret); + return ret; + } +} + +int +main (int argc, char *argv[]) +{ + GOptionEntry options[] = { + {"audio-src", '\0', 0, G_OPTION_ARG_STRING, &audiosrc_name, + "audio source used in video recording", NULL}, + {"video-src", '\0', 0, G_OPTION_ARG_STRING, &videosrc_name, + "video source used in still capture and video recording", NULL}, + {"audio-enc", '\0', 0, G_OPTION_ARG_STRING, &audioenc_name, + "audio encoder used in video recording", NULL}, + {"video-enc", '\0', 0, G_OPTION_ARG_STRING, &videoenc_name, + "video encoder used in video recording", NULL}, + {"image-enc", '\0', 0, G_OPTION_ARG_STRING, &imageenc_name, + "image encoder used in still capture", NULL}, + {"video-mux", '\0', 0, G_OPTION_ARG_STRING, &videomux_name, + "muxer used in video recording", NULL}, + {"image-width", '\0', 0, G_OPTION_ARG_INT, &image_width, + "width for image capture", NULL}, + {"image-height", '\0', 0, G_OPTION_ARG_INT, &image_height, + "height for image capture", NULL}, + {"view-framerate-num", '\0', 0, G_OPTION_ARG_INT, &view_framerate_num, + "framerate numerator for viewfinder", NULL}, + {"view-framerate-den", '\0', 0, G_OPTION_ARG_INT, &view_framerate_den, + "framerate denominator for viewfinder", NULL}, + {"src-colorspace", '\0', 0, G_OPTION_ARG_STRING, &src_csp, + "colorspace format for videosource (e.g. YUY2, UYVY)", NULL}, + {NULL} + }; + GOptionContext *ctx; + GError *err = NULL; + + if (!g_thread_supported ()) + g_thread_init (NULL); + + ctx = g_option_context_new (NULL); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_print ("Error initializing: %s\n", err->message); + exit (1); + } + g_option_context_free (ctx); + + /* init */ + filename = g_string_new_len ("", 16); + loop = g_main_loop_new (NULL, FALSE); + + /* run */ + puts (""); + puts ("+---------------------------------------------------------------------------------------+"); + puts ("| test | rate | target | avg | min | max | trials | description |"); + puts ("+---------------------------------------------------------------------------------------+"); + g_idle_add ((GSourceFunc) run_test, NULL); + g_main_loop_run (loop); + puts ("+---------------------------------------------------------------------------------------+"); + puts (""); + + fflush (stdout); + + /* free */ + cleanup_pipeline (); + g_main_loop_unref (loop); + g_string_free (filename, TRUE); + g_free (audiosrc_name); + g_free (videosrc_name); + g_free (audioenc_name); + g_free (videoenc_name); + g_free (imageenc_name); + g_free (videomux_name); + g_free (src_csp); + + return 0; +} diff --git a/tests/examples/camerabin/gst-camera-perf.glade b/tests/examples/camerabin/gst-camera-perf.glade new file mode 100644 index 00000000..fe6098ec --- /dev/null +++ b/tests/examples/camerabin/gst-camera-perf.glade @@ -0,0 +1,120 @@ + + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 400 + 600 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + True + True + 200 + + + + 100 + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + + + + + + True + False + 0 + + + + True + True + start + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + True + + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_NONE + GTK_CORNER_TOP_LEFT + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_IN + + + + True + True + == Please wait few seconds after press start == + False + True + GTK_JUSTIFY_LEFT + True + True + 0.5 + 0 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + + + + + 0 + True + True + + + + + True + True + + + + + + + diff --git a/tests/examples/camerabin/gst-camera.c b/tests/examples/camerabin/gst-camera.c new file mode 100644 index 00000000..f894418c --- /dev/null +++ b/tests/examples/camerabin/gst-camera.c @@ -0,0 +1,1756 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + * This is a demo application to test the camerabin element. + * If you have question don't hesitate in contact me edgard.lima@indt.org.br + */ + +/* + * Includes + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include // g_fopen() + +/* + * enums, typedefs and defines + */ + +#ifdef USE_MP4 +#define VID_FILE_EXT "mp4" +#else +#define VID_FILE_EXT "ogg" +#endif + +#define PREVIEW_TIME_MS (2 * 1000) +#define N_BURST_IMAGES 10 +#define DEFAULT_GLADE_FILE "gst-camera.glade" +#define SHARED_GLADE_FILE CAMERA_APPS_GLADEDIR"/"DEFAULT_GLADE_FILE + +/* Names of default elements */ +#define CAMERA_APP_VIDEOSRC "v4l2src" +#define CAMERA_APP_IMAGE_POSTPROC "dummy" + +#ifdef HAVE_GST_PHOTO_IFACE_H +#define EV_COMP_MAX 3.0 +#define EV_COMP_MIN -3.0 +#define EV_COMP_STEP 0.5 +#endif + +#define DEFAULT_VF_CAPS \ + "video/x-raw-yuv, width = (int) 320, height = (int) 240, framerate = (fraction) 1496/100;" \ + "video/x-raw-yuv, width = (int) 640, height = (int) 480, framerate = (fraction) 1494/100;" \ + "video/x-raw-yuv, width = (int) 800, height = (int) 480, framerate = (fraction) 2503/100;" \ + "video/x-raw-yuv, width = (int) 800, height = (int) 480, framerate = (fraction) 2988/100;" \ + "video/x-raw-yuv, width = (int) 800, height = (int) 480, framerate = (fraction) 1494/100;" \ + "video/x-raw-yuv, width = (int) 720, height = (int) 480, framerate = (fraction) 1494/100" + +#define PREVIEW_CAPS \ + "video/x-raw-rgb, width = (int) 640, height = (int) 480" + +/* states: + (image) <---> (video_stopped) <---> (video_recording) +*/ +typedef enum _tag_CaptureState +{ + CAP_STATE_IMAGE, + CAP_STATE_VIDEO_STOPED, + CAP_STATE_VIDEO_PAUSED, + CAP_STATE_VIDEO_RECORDING, +} CaptureState; + +/* + * Global Vars + */ + +static GladeXML *ui_glade_xml = NULL; +static GtkWidget *ui_main_window = NULL; +static GtkWidget *ui_drawing = NULL; +static GtkWidget *ui_drawing_frame = NULL; +static GtkWidget *ui_chk_continous = NULL; +static GtkButton *ui_bnt_shot = NULL; +static GtkButton *ui_bnt_pause = NULL; +static GtkWidget *ui_chk_mute = NULL; +static GtkWidget *ui_vbox_color_controls = NULL; +static GtkWidget *ui_chk_rawmsg = NULL; + +static GtkWidget *ui_rdbntImageCapture = NULL; +static GtkWidget *ui_rdbntVideoCapture = NULL; +static GtkWidget *ui_menuitem_photography = NULL; +static GtkWidget *ui_menuitem_capture = NULL; + +static GtkComboBox *ui_cbbox_resolution = NULL; +static guint ui_cbbox_resolution_count = 0; + +static CaptureState capture_state = CAP_STATE_IMAGE; + +static GstElement *gst_camera_bin = NULL; +static GstElement *gst_videosrc = NULL; + +static GString *filename = NULL; +static guint32 num_pics = 0; +static guint32 num_pics_cont = 0; +static guint32 num_vids = 0; + +static gint max_fr_n = 0; +static gint max_fr_d = 0; +static gchar *video_post; +static gchar *image_post; + +static GList *video_caps_list = NULL; + +#ifdef HAVE_GST_PHOTO_IFACE_H +static gchar *iso_speed_labels[] = { "auto", "100", "200", "400" }; + +static struct +{ + gchar *label; + gint width; + gint height; +} image_resolution_label_map[] = { + { + "View finder resolution", 0, 0}, { + "VGA", 640, 480}, { + "1,3Mpix (1280x960)", 1280, 960}, { + "3Mpix (2048x1536)", 2048, 1536}, { + "3,7Mpix 16:9 (2592x1456)", 2592, 1456}, { + "5Mpix (2592x1968)", 2592, 1968} +}; +#endif + +/* + * functions prototypes + */ +static gboolean me_gst_setup_pipeline (const gchar * imagepost, + const gchar * videopost); +static void me_gst_cleanup_element (void); + +static gboolean capture_mode_set_state (CaptureState state); +static void capture_mode_config_gui (void); +static gboolean capture_mode_stop (void); + +static void on_windowMain_delete_event (GtkWidget * widget, GdkEvent * event, + gpointer user_data); +static void on_buttonShot_clicked (GtkButton * button, gpointer user_data); +static void on_buttonPause_clicked (GtkButton * button, gpointer user_data); +static void on_comboboxResolution_changed (GtkComboBox * widget, + gpointer user_data); +static void on_radiobuttonImageCapture_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_radiobuttonVideoCapture_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_rbBntVidEffNone_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_rbBntVidEffEdge_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_rbBntVidEffAging_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_rbBntVidEffDice_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_rbBntVidEffWarp_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_rbBntVidEffShagadelic_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_rbBntVidEffVertigo_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_rbBntVidEffRev_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_rbBntVidEffQuark_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_chkbntMute_toggled (GtkToggleButton * togglebutton, + gpointer user_data); +static void on_chkbtnRawMsg_toggled (GtkToggleButton * togglebutton, + gpointer data); +static void on_hscaleZoom_value_changed (GtkRange * range, gpointer user_data); + +static void ui_connect_signals (void); +static gboolean ui_create (void); +static void destroy_color_controls (void); +static void create_color_controls (void); +static void init_view_finder_resolution_combobox (void); + +#ifdef HAVE_GST_PHOTO_IFACE_H +static void menuitem_toggle_active (GtkWidget * widget, gpointer data); +static void sub_menu_initialize (GtkWidget * widget, gpointer data); +static void fill_photography_menu (GtkMenuItem * parent_item); +#endif + +/* + * functions implementation + */ + +static void +set_filename (GString * name) +{ + const gchar *datadir; + + if (capture_state == CAP_STATE_IMAGE) { + g_string_printf (name, G_DIR_SEPARATOR_S "test_%04u.jpg", num_pics); + datadir = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); + } else { + g_string_printf (name, G_DIR_SEPARATOR_S "test_%04u.%s", num_vids, + VID_FILE_EXT); + datadir = g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS); + } + + if (datadir == NULL) { + // FIXME: maemo + //#define DEFAULT_IMAGEDIR "$HOME/MyDocs/.images/" + //#define DEFAULT_VIDEODIR "$HOME/MyDocs/.videos/" + gchar *curdir = g_get_current_dir (); + g_string_prepend (name, curdir); + g_free (curdir); + } else { + g_string_prepend (name, datadir); + } +} + +static GstBusSyncReply +set_xwindow (GstMessage ** message, gpointer data) +{ + GstBusSyncReply ret = GST_BUS_PASS; + const GstStructure *s = gst_message_get_structure (*message); + + if (!s || !gst_structure_has_name (s, "prepare-xwindow-id")) { + goto done; + } + + gst_x_overlay_set_xwindow_id (GST_X_OVERLAY (GST_MESSAGE_SRC (*message)), + GDK_WINDOW_XWINDOW (ui_drawing->window)); + + gst_message_unref (*message); + *message = NULL; + ret = GST_BUS_DROP; +done: + return ret; +} + +/* Write raw image buffer to file if found from message */ +static void +handle_element_message (GstMessage * msg) +{ + const GstStructure *st; + const GValue *image; + GstBuffer *buf = NULL; + guint8 *data = NULL; + gchar *caps_string; + guint size = 0; + gchar *filename = NULL; + FILE *f = NULL; + size_t written; + + st = gst_message_get_structure (msg); + if (g_str_equal (gst_structure_get_name (st), "autofocus-done")) { + gtk_button_set_label (ui_bnt_pause, "Focus"); + } else if (gst_structure_has_field_typed (st, "buffer", GST_TYPE_BUFFER)) { + image = gst_structure_get_value (st, "buffer"); + if (image) { + buf = gst_value_get_buffer (image); + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + if (g_str_equal (gst_structure_get_name (st), "raw-image")) { + filename = g_strdup_printf ("test_%04u.raw", num_pics); + } else if (g_str_equal (gst_structure_get_name (st), "preview-image")) { + filename = g_strdup_printf ("test_%04u_vga.rgb", num_pics); + } else { + /* for future purposes */ + g_print ("unknown buffer received\n"); + return; + } + caps_string = gst_caps_to_string (GST_BUFFER_CAPS (buf)); + g_print ("writing buffer to %s, buffer caps: %s\n", + filename, caps_string); + g_free (caps_string); + f = g_fopen (filename, "w"); + if (f) { + written = fwrite (data, size, 1, f); + if (!written) { + g_print ("errro writing file\n"); + } + fclose (f); + } else { + g_print ("error opening file for raw image writing\n"); + } + g_free (filename); + } + } else if (g_str_equal (gst_structure_get_name (st), "photo-capture-start")) { + g_print ("=== CLICK ===\n"); + } +} + +static GstBusSyncReply +my_bus_sync_callback (GstBus * bus, GstMessage * message, gpointer data) +{ + GstBusSyncReply ret = GST_BUS_PASS; + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ELEMENT: + ret = set_xwindow (&message, data); + break; + default: + /* unhandled message */ + break; + } + return ret; +} + +static gboolean +my_bus_callback (GstBus * bus, GstMessage * message, gpointer data) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_WARNING:{ + GError *err; + gchar *debug; + + gst_message_parse_warning (message, &err, &debug); + g_print ("Warning: %s\n", err->message); + g_error_free (err); + g_free (debug); + break; + } + case GST_MESSAGE_ERROR:{ + GError *err; + gchar *debug; + + gst_message_parse_error (message, &err, &debug); + g_print ("Error: %s\n", err->message); + g_error_free (err); + g_free (debug); + + me_gst_cleanup_element (); + gtk_main_quit (); + break; + } + case GST_MESSAGE_EOS: + /* end-of-stream */ + gtk_main_quit (); + break; + case GST_MESSAGE_STATE_CHANGED:{ + GstState old, new, pending; + + gst_message_parse_state_changed (message, &old, &new, &pending); + + /* Create/destroy color controls according videosrc state */ + if (GST_MESSAGE_SRC (message) == GST_OBJECT (gst_videosrc)) { + if (old == GST_STATE_PAUSED && new == GST_STATE_READY) { + destroy_color_controls (); + } else if (old == GST_STATE_READY && new == GST_STATE_PAUSED) { + create_color_controls (); + } + } + + /* we only care about pipeline state change messages */ + if (GST_IS_PIPELINE (GST_MESSAGE_SRC (message))) { + /* dump graph for pipeline state changes */ + gchar *dump_name = g_strdup_printf ("camerabin.%s_%s", + gst_element_state_get_name (old), + gst_element_state_get_name (new)); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (GST_MESSAGE_SRC (message)), + GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | + GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS, dump_name); + g_free (dump_name); + } + } + break; + case GST_MESSAGE_ELEMENT: + { + handle_element_message (message); + break; + } + default: + /* unhandled message */ + break; + } + return TRUE; +} + +static void +me_set_next_cont_file_name (GString * filename) +{ + /* FIXME: better file naming (possible with signal) */ + if (G_UNLIKELY (num_pics_cont == 1)) { + gint i; + for (i = filename->len - 1; i > 0; --i) { + if (filename->str[i] == '.') + break; + } + g_string_insert (filename, i, "_0001"); + } else { + gchar tmp[6]; + gint i; + for (i = filename->len - 1; i > 0; --i) { + if (filename->str[i] == '_') + break; + } + snprintf (tmp, 6, "_%04d", num_pics_cont); + memcpy (filename->str + i, tmp, 5); + } +} + +static gboolean +stop_image_preview (gpointer data) +{ + g_return_val_if_fail (data != NULL, FALSE); + + g_signal_emit_by_name (data, "user-stop", 0); + + return FALSE; +} + +static gboolean +me_image_capture_done (GstElement * camera, const gchar * fname, + gpointer user_data) +{ + gboolean cont = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ui_chk_continous)); + GString *filename = g_string_new (fname); + + if (num_pics_cont < N_BURST_IMAGES && cont) { + num_pics_cont++; + me_set_next_cont_file_name (filename); + g_object_set (G_OBJECT (camera), "filename", filename->str, NULL); + g_string_free (filename, TRUE); + } else { + gtk_widget_set_sensitive (GTK_WIDGET (ui_bnt_shot), TRUE); + printf ("%u image(s) saved\n", num_pics_cont + 1); + fflush (stdout); + num_pics_cont = 0; + + g_timeout_add (PREVIEW_TIME_MS, (GSourceFunc) stop_image_preview, camera); + + cont = FALSE; + } + return cont; +} + +static gboolean +me_gst_setup_pipeline_create_post_bin (const gchar * post, gboolean video) +{ + GstElement *vpp = NULL; + GstElement *bin, *c1, *c2, *filter; + GstPad *pad; + GstCaps *caps; + + /* this function uses a bin just because it needs ffmpegcolorspace. For + * performance reason one should provide an element without need for color + * convertion */ + + vpp = gst_element_factory_make (post, NULL); + if (NULL == vpp) { + fprintf (stderr, "cannot create \'%s\' element\n", post); + fflush (stderr); + goto done; + } + c1 = gst_element_factory_make ("ffmpegcolorspace", NULL); + c2 = gst_element_factory_make ("ffmpegcolorspace", NULL); + if (NULL == c1 || NULL == c2) { + fprintf (stderr, "cannot create \'ffmpegcolorspace\' element\n"); + fflush (stderr); + goto done; + } + filter = gst_element_factory_make ("capsfilter", NULL); + if (NULL == filter) { + fprintf (stderr, "cannot create \'capsfilter\' element\n"); + fflush (stderr); + goto done; + } + bin = gst_bin_new (video ? "vid_postproc_bin" : "img_postproc_bin"); + if (NULL == bin) { + goto done; + } + + caps = gst_caps_new_simple ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'), NULL); + g_object_set (G_OBJECT (filter), "caps", caps, NULL); + gst_caps_unref (caps); + + gst_bin_add_many (GST_BIN (bin), c1, vpp, c2, filter, NULL); + if (!gst_element_link_many (c1, vpp, c2, filter, NULL)) { + fprintf (stderr, "cannot link video post proc elements\n"); + fflush (stderr); + goto done; + } + + pad = gst_element_get_static_pad (c1, "sink"); + gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad)); + gst_object_unref (GST_OBJECT (pad)); + + pad = gst_element_get_static_pad (filter, "src"); + gst_element_add_pad (bin, gst_ghost_pad_new ("src", pad)); + gst_object_unref (GST_OBJECT (pad)); + + g_object_set (gst_camera_bin, (video ? "videopp" : "imagepp"), bin, NULL); + return TRUE; +done: + return FALSE; +} + +static void +me_gst_setup_pipeline_create_codecs (void) +{ +#ifdef USE_MP4 + g_object_set (gst_camera_bin, "videoenc", + gst_element_factory_make ("omx_mpeg4enc", NULL), NULL); + + g_object_set (gst_camera_bin, "audioenc", + gst_element_factory_make ("omx_aacenc", NULL), NULL); + + g_object_set (gst_camera_bin, "videomux", + gst_element_factory_make ("hantromp4mux", NULL), NULL); +#else + /* using defaults theora, vorbis, ogg */ +#endif +} + +static gboolean +me_gst_setup_pipeline_create_img_post_bin (const gchar * imagepost) +{ + return me_gst_setup_pipeline_create_post_bin (imagepost, FALSE); +} + +static gboolean +me_gst_setup_pipeline_create_vid_post_bin (const gchar * videopost) +{ + return me_gst_setup_pipeline_create_post_bin (videopost, TRUE); +} + +static gboolean +me_gst_setup_pipeline (const gchar * imagepost, const gchar * videopost) +{ + GstBus *bus; + GstCaps *preview_caps; + + set_filename (filename); + + me_gst_cleanup_element (); + + gst_camera_bin = gst_element_factory_make ("camerabin", NULL); + if (NULL == gst_camera_bin) { + goto done; + } + + g_signal_connect (gst_camera_bin, "img-done", + (GCallback) me_image_capture_done, NULL); + + preview_caps = gst_caps_from_string (PREVIEW_CAPS); + + bus = gst_pipeline_get_bus (GST_PIPELINE (gst_camera_bin)); + gst_bus_add_watch (bus, my_bus_callback, NULL); + gst_bus_set_sync_handler (bus, my_bus_sync_callback, NULL); + gst_object_unref (bus); + + /* set properties */ + g_object_set (gst_camera_bin, "filename", filename->str, NULL); + g_object_set (gst_camera_bin, "preview-caps", preview_caps, NULL); + gst_caps_unref (preview_caps); + + gst_videosrc = gst_element_factory_make (CAMERA_APP_VIDEOSRC, NULL); + if (gst_videosrc) { + g_object_set (G_OBJECT (gst_camera_bin), "videosrc", gst_videosrc, NULL); + } + + if (imagepost) { + if (!me_gst_setup_pipeline_create_img_post_bin (imagepost)) + goto done; + } else { + /* Use default image postprocessing element */ + GstElement *ipp = + gst_element_factory_make (CAMERA_APP_IMAGE_POSTPROC, NULL); + if (ipp) { + g_object_set (G_OBJECT (gst_camera_bin), "imagepp", ipp, NULL); + } + } + + if (videopost) { + if (!me_gst_setup_pipeline_create_vid_post_bin (videopost)) + goto done; + } + + me_gst_setup_pipeline_create_codecs (); + + if (GST_STATE_CHANGE_FAILURE == + gst_element_set_state (gst_camera_bin, GST_STATE_READY)) { + goto done; + } + + if (!gst_videosrc) { + g_object_get (G_OBJECT (gst_camera_bin), "videosrc", &gst_videosrc, NULL); + } + + init_view_finder_resolution_combobox (); + + if (GST_STATE_CHANGE_FAILURE == + gst_element_set_state (gst_camera_bin, GST_STATE_PAUSED)) { + goto done; + } else { + gst_element_get_state (gst_camera_bin, NULL, NULL, GST_CLOCK_TIME_NONE); + } + + if (GST_STATE_CHANGE_FAILURE == + gst_element_set_state (gst_camera_bin, GST_STATE_PLAYING)) { + goto done; + } else { + gst_element_get_state (gst_camera_bin, NULL, NULL, GST_CLOCK_TIME_NONE); + } + +#ifdef HAVE_GST_PHOTO_IFACE_H + /* Initialize menus to default settings */ + GtkWidget *sub_menu = + gtk_menu_item_get_submenu (GTK_MENU_ITEM (ui_menuitem_capture)); + gtk_container_foreach (GTK_CONTAINER (sub_menu), sub_menu_initialize, NULL); + sub_menu = + gtk_menu_item_get_submenu (GTK_MENU_ITEM (ui_menuitem_photography)); + gtk_container_foreach (GTK_CONTAINER (sub_menu), sub_menu_initialize, NULL); +#endif + + capture_state = CAP_STATE_IMAGE; + return TRUE; +done: + fprintf (stderr, "error to create pipeline\n"); + fflush (stderr); + me_gst_cleanup_element (); + return FALSE; +} + +static void +me_gst_cleanup_element () +{ + if (gst_camera_bin) { + gst_element_set_state (gst_camera_bin, GST_STATE_NULL); + gst_element_get_state (gst_camera_bin, NULL, NULL, GST_CLOCK_TIME_NONE); + gst_object_unref (gst_camera_bin); + gst_camera_bin = NULL; + + g_list_foreach (video_caps_list, (GFunc) gst_caps_unref, NULL); + g_list_free (video_caps_list); + video_caps_list = NULL; + } +} + +static gboolean +capture_mode_stop () +{ + if (capture_state == CAP_STATE_VIDEO_PAUSED + || capture_state == CAP_STATE_VIDEO_RECORDING) { + return capture_mode_set_state (CAP_STATE_VIDEO_STOPED); + } else { + return TRUE; + } +} + +static void +capture_mode_config_gui () +{ + switch (capture_state) { + case CAP_STATE_IMAGE: + gtk_button_set_label (ui_bnt_shot, "Shot"); + gtk_button_set_label (ui_bnt_pause, "Focus"); + gtk_widget_set_sensitive (GTK_WIDGET (ui_bnt_pause), TRUE); + gtk_widget_show (ui_chk_continous); + gtk_widget_show (ui_chk_rawmsg); + gtk_widget_hide (ui_chk_mute); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ui_rdbntImageCapture), + TRUE); + break; + case CAP_STATE_VIDEO_STOPED: + gtk_button_set_label (ui_bnt_shot, "Rec"); + gtk_button_set_label (ui_bnt_pause, "Pause"); + gtk_widget_set_sensitive (GTK_WIDGET (ui_bnt_pause), FALSE); + gtk_widget_show (GTK_WIDGET (ui_bnt_pause)); + gtk_widget_show (ui_chk_mute); + gtk_widget_hide (ui_chk_continous); + gtk_widget_hide (ui_chk_rawmsg); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ui_rdbntVideoCapture), + TRUE); + break; + case CAP_STATE_VIDEO_PAUSED: + gtk_button_set_label (ui_bnt_pause, "Cont"); + break; + case CAP_STATE_VIDEO_RECORDING: + gtk_button_set_label (ui_bnt_shot, "Stop"); + gtk_button_set_label (ui_bnt_pause, "Pause"); + gtk_widget_set_sensitive (GTK_WIDGET (ui_bnt_pause), TRUE); + break; + default: + break; + } +} + +static gboolean +capture_mode_set_state (CaptureState state) +{ + if (capture_state == state) + return TRUE; + + switch (capture_state) { + case CAP_STATE_IMAGE: + if (state == CAP_STATE_VIDEO_PAUSED) { + goto done; + } + g_object_set (gst_camera_bin, "mode", 1, NULL); + capture_state = CAP_STATE_VIDEO_STOPED; + if (state == CAP_STATE_VIDEO_RECORDING) + capture_mode_set_state (state); + break; + case CAP_STATE_VIDEO_STOPED: + if (state == CAP_STATE_VIDEO_PAUSED) { + goto done; + } + capture_state = state; + if (state == CAP_STATE_IMAGE) + g_object_set (gst_camera_bin, "mode", 0, NULL); + else { /* state == CAP_STATE_VIDEO_RECORDING */ + g_object_set (gst_camera_bin, "mode", 1, NULL); + g_signal_emit_by_name (gst_camera_bin, "user-start", 0); + } + break; + case CAP_STATE_VIDEO_PAUSED: + if (state == CAP_STATE_VIDEO_RECORDING) { + g_signal_emit_by_name (gst_camera_bin, "user-start", 0); + capture_state = CAP_STATE_VIDEO_RECORDING; + } else { + g_signal_emit_by_name (gst_camera_bin, "user-stop", 0); + capture_state = CAP_STATE_VIDEO_STOPED; + if (state == CAP_STATE_IMAGE) + capture_mode_set_state (state); + } + break; + case CAP_STATE_VIDEO_RECORDING: + if (state == CAP_STATE_VIDEO_PAUSED) { + g_signal_emit_by_name (gst_camera_bin, "user-pause", 0); + capture_state = CAP_STATE_VIDEO_PAUSED; + } else { + g_signal_emit_by_name (gst_camera_bin, "user-stop", 0); + capture_state = CAP_STATE_VIDEO_STOPED; + if (state == CAP_STATE_IMAGE) + capture_mode_set_state (state); + } + break; + } + return TRUE; +done: + return FALSE; +} + +static void +on_windowMain_delete_event (GtkWidget * widget, GdkEvent * event, gpointer data) +{ + capture_mode_set_state (CAP_STATE_IMAGE); + capture_mode_config_gui (); + me_gst_cleanup_element (); + gtk_main_quit (); +} + +static void +set_metadata (void) +{ + /* for more information about image metadata tags, see: + * http://webcvs.freedesktop.org/gstreamer/gst-plugins-bad/tests/icles/metadata_editor.c + * and for the mapping: + * http://webcvs.freedesktop.org/gstreamer/gst-plugins-bad/ext/metadata/metadata_mapping.htm?view=co + */ + + GstTagSetter *setter = GST_TAG_SETTER (gst_camera_bin); + GTimeVal time = { 0, 0 }; + gchar *date_str, *desc_str; + + g_get_current_time (&time); + date_str = g_time_val_to_iso8601 (&time); /* this is UTC */ + desc_str = g_strdup_printf ("picture taken by %s", g_get_real_name ()); + + gst_tag_setter_add_tags (setter, GST_TAG_MERGE_REPLACE, + "date-time-original", date_str, + "date-time-modified", date_str, + "creator-tool", "camerabin-demo", + GST_TAG_DESCRIPTION, desc_str, + GST_TAG_TITLE, "My picture", GST_TAG_COPYRIGHT, "LGPL", NULL); + + g_free (date_str); + g_free (desc_str); +} + +static void +on_buttonShot_clicked (GtkButton * button, gpointer user_data) +{ + switch (capture_state) { + case CAP_STATE_IMAGE: + { + gtk_widget_set_sensitive (GTK_WIDGET (ui_bnt_shot), FALSE); + set_filename (filename); + num_pics++; + g_object_set (gst_camera_bin, "filename", filename->str, NULL); + + set_metadata (); + g_signal_emit_by_name (gst_camera_bin, "user-start", 0); + } + break; + case CAP_STATE_VIDEO_STOPED: + set_filename (filename); + num_vids++; + g_object_set (gst_camera_bin, "filename", filename->str, NULL); + capture_mode_set_state (CAP_STATE_VIDEO_RECORDING); + capture_mode_config_gui (); + break; + case CAP_STATE_VIDEO_PAUSED: + /* fall trough */ + case CAP_STATE_VIDEO_RECORDING: + capture_mode_set_state (CAP_STATE_VIDEO_STOPED); + capture_mode_config_gui (); + break; + default: + break; + } +} + +static void +on_buttonPause_clicked (GtkButton * button, gpointer user_data) +{ + switch (capture_state) { + case CAP_STATE_IMAGE: + if (g_str_equal (gtk_button_get_label (ui_bnt_pause), "Focus")) { + /* Start autofocus */ + gst_photography_set_autofocus (GST_PHOTOGRAPHY (gst_camera_bin), TRUE); + gtk_button_set_label (ui_bnt_pause, "Cancel Focus"); + } else { + /* Cancel autofocus */ + gst_photography_set_autofocus (GST_PHOTOGRAPHY (gst_camera_bin), FALSE); + gtk_button_set_label (ui_bnt_pause, "Focus"); + } + break; + case CAP_STATE_VIDEO_STOPED: + break; + case CAP_STATE_VIDEO_PAUSED: + capture_mode_set_state (CAP_STATE_VIDEO_RECORDING); + capture_mode_config_gui (); + break; + case CAP_STATE_VIDEO_RECORDING: + capture_mode_set_state (CAP_STATE_VIDEO_PAUSED); + capture_mode_config_gui (); + break; + default: + break; + } +} + +static gboolean +on_drawingareaView_configure_event (GtkWidget * widget, + GdkEventConfigure * event, gpointer data) +{ + Display *display = GDK_WINDOW_XDISPLAY (GDK_WINDOW (widget->window)); + + XMoveResizeWindow (display, GDK_WINDOW_XID (GDK_WINDOW (widget->window)), + widget->allocation.x, widget->allocation.y, + widget->allocation.width, widget->allocation.height); + XSync (display, False); + + return TRUE; +} + +static void +on_comboboxResolution_changed (GtkComboBox * widget, gpointer user_data) +{ + GstStructure *st; + gint w = 0, h = 0; + GstCaps *video_caps = + g_list_nth_data (video_caps_list, gtk_combo_box_get_active (widget)); + + if (video_caps) { + + gst_element_set_state (gst_camera_bin, GST_STATE_READY); + + st = gst_caps_get_structure (video_caps, 0); + + gst_structure_get_int (st, "width", &w); + gst_structure_get_int (st, "height", &h); + + if (w && h) { + g_object_set (ui_drawing_frame, "ratio", (gfloat) w / (gfloat) h, NULL); + } + + g_object_set (G_OBJECT (gst_camera_bin), "filter-caps", video_caps, NULL); + + gst_element_set_state (gst_camera_bin, GST_STATE_PLAYING); + } +} + +static void +on_radiobuttonImageCapture_toggled (GtkToggleButton * togglebutton, + gpointer user_data) +{ + if (gtk_toggle_button_get_active (togglebutton)) { + if (capture_state != CAP_STATE_IMAGE) { + capture_mode_set_state (CAP_STATE_IMAGE); + capture_mode_config_gui (); + } + } +} + +static void +on_radiobuttonVideoCapture_toggled (GtkToggleButton * togglebutton, + gpointer user_data) +{ + if (gtk_toggle_button_get_active (togglebutton)) { + if (capture_state == CAP_STATE_IMAGE) { + capture_mode_set_state (CAP_STATE_VIDEO_STOPED); + capture_mode_config_gui (); + } + } +} + +static void +on_rbBntVidEff_toggled (GtkToggleButton * togglebutton, gchar * effect) +{ + if (gtk_toggle_button_get_active (togglebutton)) { + /* lets also use those effects to image */ + video_post = effect; + image_post = effect; + capture_mode_stop (); + + me_gst_cleanup_element (); + if (!me_gst_setup_pipeline (image_post, video_post)) + gtk_main_quit (); + capture_mode_config_gui (); + } +} + +static void +on_rbBntVidEffNone_toggled (GtkToggleButton * togglebutton, gpointer data) +{ + on_rbBntVidEff_toggled (togglebutton, NULL); +} + +static void +on_rbBntVidEffEdge_toggled (GtkToggleButton * togglebutton, gpointer data) +{ + on_rbBntVidEff_toggled (togglebutton, "edgetv"); +} + +static void +on_rbBntVidEffAging_toggled (GtkToggleButton * togglebutton, gpointer user_data) +{ + on_rbBntVidEff_toggled (togglebutton, "agingtv"); +} + +static void +on_rbBntVidEffDice_toggled (GtkToggleButton * togglebutton, gpointer user_data) +{ + on_rbBntVidEff_toggled (togglebutton, "dicetv"); +} + +static void +on_rbBntVidEffWarp_toggled (GtkToggleButton * togglebutton, gpointer data) +{ + on_rbBntVidEff_toggled (togglebutton, "warptv"); +} + +static void +on_rbBntVidEffShagadelic_toggled (GtkToggleButton * togglebutton, gpointer data) +{ + on_rbBntVidEff_toggled (togglebutton, "shagadelictv"); +} + +static void +on_rbBntVidEffVertigo_toggled (GtkToggleButton * togglebutton, gpointer data) +{ + on_rbBntVidEff_toggled (togglebutton, "vertigotv"); +} + +static void +on_rbBntVidEffRev_toggled (GtkToggleButton * togglebutton, gpointer data) +{ + on_rbBntVidEff_toggled (togglebutton, "revtv"); +} + +static void +on_rbBntVidEffQuark_toggled (GtkToggleButton * togglebutton, gpointer data) +{ + on_rbBntVidEff_toggled (togglebutton, "quarktv"); +} + +static void +on_chkbntMute_toggled (GtkToggleButton * togglebutton, gpointer data) +{ + g_object_set (gst_camera_bin, "mute", + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (togglebutton)), NULL); +} + +static void +on_chkbtnRawMsg_toggled (GtkToggleButton * togglebutton, gpointer data) +{ + const gchar *env_var = "CAMSRC_PUBLISH_RAW"; + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (togglebutton))) { + g_setenv (env_var, "1", TRUE); + } else { + g_unsetenv (env_var); + } +} + +static void +on_hscaleZoom_value_changed (GtkRange * range, gpointer user_data) +{ + gint zoom = gtk_range_get_value (range); + g_object_set (gst_camera_bin, "zoom", zoom, NULL); +} + +static void +on_color_control_value_changed (GtkRange * range, gpointer user_data) +{ + GstColorBalance *balance = GST_COLOR_BALANCE (gst_camera_bin); + gint val = gtk_range_get_value (range); + GstColorBalanceChannel *channel = (GstColorBalanceChannel *) user_data; + gst_color_balance_set_value (balance, channel, val); +} + + +gboolean +on_key_released (GtkWidget * widget, GdkEventKey * event, gpointer user_data) +{ + g_return_val_if_fail (event != NULL, FALSE); + + switch (event->keyval) { + case GDK_F11: +#ifdef HAVE_GST_PHOTO_IFACE_H + gst_photography_set_autofocus (GST_PHOTOGRAPHY (gst_camera_bin), FALSE); +#endif + break; + default: + break; + } + + return FALSE; +} + +gboolean +on_key_pressed (GtkWidget * widget, GdkEventKey * event, gpointer user_data) +{ + g_return_val_if_fail (event != NULL, FALSE); + + switch (event->keyval) { + case GDK_F11: +#ifdef HAVE_GST_PHOTO_IFACE_H + gst_photography_set_autofocus (GST_PHOTOGRAPHY (gst_camera_bin), TRUE); +#endif + break; + case 0x0: + on_buttonShot_clicked (NULL, NULL); + break; + default: + break; + } + + return FALSE; +} + +static void +ui_connect_signals (void) +{ + glade_xml_signal_connect (ui_glade_xml, "on_windowMain_delete_event", + (GCallback) on_windowMain_delete_event); + + glade_xml_signal_connect (ui_glade_xml, "on_buttonShot_clicked", + (GCallback) on_buttonShot_clicked); + + glade_xml_signal_connect (ui_glade_xml, "on_buttonPause_clicked", + (GCallback) on_buttonPause_clicked); + + glade_xml_signal_connect (ui_glade_xml, "on_drawingareaView_configure_event", + (GCallback) on_drawingareaView_configure_event); + + glade_xml_signal_connect (ui_glade_xml, "on_comboboxResolution_changed", + (GCallback) on_comboboxResolution_changed); + + glade_xml_signal_connect (ui_glade_xml, "on_radiobuttonImageCapture_toggled", + (GCallback) on_radiobuttonImageCapture_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_radiobuttonVideoCapture_toggled", + (GCallback) on_radiobuttonVideoCapture_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_rbBntVidEffNone_toggled", + (GCallback) on_rbBntVidEffNone_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_rbBntVidEffEdge_toggled", + (GCallback) on_rbBntVidEffEdge_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_rbBntVidEffAging_toggled", + (GCallback) on_rbBntVidEffAging_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_rbBntVidEffDice_toggled", + (GCallback) on_rbBntVidEffDice_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_rbBntVidEffWarp_toggled", + (GCallback) on_rbBntVidEffWarp_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_rbBntVidEffShagadelic_toggled", + (GCallback) on_rbBntVidEffShagadelic_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_rbBntVidEffVertigo_toggled", + (GCallback) on_rbBntVidEffVertigo_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_rbBntVidEffRev_toggled", + (GCallback) on_rbBntVidEffRev_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_rbBntVidEffQuark_toggled", + (GCallback) on_rbBntVidEffQuark_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_chkbntMute_toggled", + (GCallback) on_chkbntMute_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_chkbtnRawMsg_toggled", + (GCallback) on_chkbtnRawMsg_toggled); + + glade_xml_signal_connect (ui_glade_xml, "on_hscaleZoom_value_changed", + (GCallback) on_hscaleZoom_value_changed); + + g_signal_connect (ui_main_window, "key-press-event", + (GCallback) on_key_pressed, NULL); + + g_signal_connect (ui_main_window, "key-release-event", + (GCallback) on_key_released, NULL); +} + +static gchar * +format_value_callback (GtkScale * scale, gdouble value, gpointer user_data) +{ + GstColorBalanceChannel *channel = (GstColorBalanceChannel *) user_data; + + return g_strdup_printf ("%s: %d", channel->label, (gint) value); +} + +static gint +create_menu_items_from_structure (GstStructure * structure) +{ + const GValue *framerate_list = NULL; + const gchar *structure_name; + GString *item_str = NULL; + guint j, num_items_created = 0, num_framerates = 1; + gint w = 0, h = 0, n = 0, d = 1; + guint32 fourcc = 0; + + g_return_val_if_fail (structure != NULL, 0); + + structure_name = gst_structure_get_name (structure); + + /* lets filter yuv only */ + if (0 == strcmp (structure_name, "video/x-raw-yuv")) { + item_str = g_string_new_len ("", 128); + + if (gst_structure_has_field_typed (structure, "format", GST_TYPE_FOURCC)) { + gst_structure_get_fourcc (structure, "format", &fourcc); + } + + if (gst_structure_has_field_typed (structure, "width", GST_TYPE_INT_RANGE)) { + const GValue *wrange = gst_structure_get_value (structure, "width"); + /* If range found, use the maximum */ + w = gst_value_get_int_range_max (wrange); + } else if (gst_structure_has_field_typed (structure, "width", G_TYPE_INT)) { + gst_structure_get_int (structure, "width", &w); + } + + if (gst_structure_has_field_typed (structure, "height", GST_TYPE_INT_RANGE)) { + const GValue *hrange = gst_structure_get_value (structure, "height"); + /* If range found, use the maximum */ + h = gst_value_get_int_range_max (hrange); + } else if (gst_structure_has_field_typed (structure, "height", G_TYPE_INT)) { + gst_structure_get_int (structure, "height", &h); + } + + if (gst_structure_has_field_typed (structure, "framerate", + GST_TYPE_FRACTION)) { + gst_structure_get_fraction (structure, "framerate", &n, &d); + } else if (gst_structure_has_field_typed (structure, "framerate", + GST_TYPE_LIST)) { + framerate_list = gst_structure_get_value (structure, "framerate"); + num_framerates = gst_value_list_get_size (framerate_list); + } else if (gst_structure_has_field_typed (structure, "framerate", + GST_TYPE_FRACTION_RANGE)) { + const GValue *fr = gst_structure_get_value (structure, "framerate"); + const GValue *frmax = gst_value_get_fraction_range_max (fr); + max_fr_n = gst_value_get_fraction_numerator (frmax); + max_fr_d = gst_value_get_fraction_denominator (frmax); + } + + if (max_fr_n || max_fr_d) { + goto range_found; + } + + for (j = 0; j < num_framerates; j++) { + GstCaps *video_caps; + + if (framerate_list) { + const GValue *item = gst_value_list_get_value (framerate_list, j); + n = gst_value_get_fraction_numerator (item); + d = gst_value_get_fraction_denominator (item); + } + g_string_assign (item_str, structure_name); + g_string_append_printf (item_str, " (%" GST_FOURCC_FORMAT ")", + GST_FOURCC_ARGS (fourcc)); + g_string_append_printf (item_str, ", %dx%d at %d/%d", w, h, n, d); + gtk_combo_box_append_text (ui_cbbox_resolution, item_str->str); + + video_caps = + gst_caps_new_simple (structure_name, "format", GST_TYPE_FOURCC, + fourcc, + "width", G_TYPE_INT, w, "height", G_TYPE_INT, h, + "framerate", GST_TYPE_FRACTION, n, d, NULL); + video_caps_list = g_list_append (video_caps_list, video_caps); + num_items_created++; + } + } + +range_found: + + if (item_str) { + g_string_free (item_str, TRUE); + } + + return num_items_created; +} + +static void +fill_resolution_combo (GstCaps * caps) +{ + guint size, num_items, i; + GstStructure *st; + + max_fr_n = max_fr_d = 0; + + /* Create new items */ + size = gst_caps_get_size (caps); + + for (i = 0; i < size; i++) { + st = gst_caps_get_structure (caps, i); + num_items = create_menu_items_from_structure (st); + ui_cbbox_resolution_count += num_items; + } +} + +static GstCaps * +create_default_caps () +{ + GstCaps *default_caps; + + default_caps = gst_caps_from_string (DEFAULT_VF_CAPS); + + return default_caps; +} + +static void +init_view_finder_resolution_combobox () +{ + GstCaps *input_caps = NULL, *default_caps = NULL, *intersect = NULL; + + g_object_get (gst_camera_bin, "inputcaps", &input_caps, NULL); + if (input_caps) { + fill_resolution_combo (input_caps); + } + + /* Fill in default items if supported */ + default_caps = create_default_caps (); + intersect = gst_caps_intersect (default_caps, input_caps); + if (intersect) { + fill_resolution_combo (intersect); + gst_caps_unref (intersect); + } + gst_caps_unref (default_caps); + + if (input_caps) { + gst_caps_unref (input_caps); + } + + /* Set some item active */ + gtk_combo_box_set_active (ui_cbbox_resolution, ui_cbbox_resolution_count - 1); +} + +static void +destroy_color_controls () +{ + GList *widgets, *item; + GtkWidget *widget = NULL; + + widgets = gtk_container_get_children (GTK_CONTAINER (ui_vbox_color_controls)); + for (item = widgets; item; item = g_list_next (item)) { + widget = GTK_WIDGET (item->data); + g_signal_handlers_disconnect_by_func (widget, (GFunc) format_value_callback, + g_object_get_data (G_OBJECT (widget), "channel")); + g_signal_handlers_disconnect_by_func (widget, + (GFunc) on_color_control_value_changed, + g_object_get_data (G_OBJECT (widget), "channel")); + gtk_container_remove (GTK_CONTAINER (ui_vbox_color_controls), widget); + } + g_list_free (widgets); +} + +static void +create_color_controls () +{ + GstColorBalance *balance = NULL; + const GList *controls, *item; + GstColorBalanceChannel *channel; + GtkWidget *hscale; + + if (GST_IS_COLOR_BALANCE (gst_camera_bin)) { + balance = GST_COLOR_BALANCE (gst_camera_bin); + } + + if (NULL == balance) { + goto done; + } + + controls = gst_color_balance_list_channels (balance); + for (item = controls; item; item = g_list_next (item)) { + channel = item->data; + + hscale = gtk_hscale_new ((GtkAdjustment *) + gtk_adjustment_new (gst_color_balance_get_value (balance, channel), + channel->min_value, channel->max_value, 1, 10, 10)); + + g_signal_connect (GTK_RANGE (hscale), "value-changed", + (GCallback) on_color_control_value_changed, (gpointer) channel); + g_signal_connect (GTK_SCALE (hscale), "format-value", + (GCallback) format_value_callback, (gpointer) channel); + g_object_set_data (G_OBJECT (hscale), "channel", (gpointer) channel); + + gtk_box_pack_start (GTK_BOX (ui_vbox_color_controls), GTK_WIDGET (hscale), + FALSE, TRUE, 0); + } + + gtk_widget_show_all (ui_vbox_color_controls); +done: + return; +} + +#ifdef HAVE_GST_PHOTO_IFACE_H +static void +menuitem_toggle_active (GtkWidget * widget, gpointer data) +{ + gboolean active; + g_object_get (G_OBJECT (widget), "active", &active, NULL); + if (active) { + gtk_check_menu_item_toggled (GTK_CHECK_MENU_ITEM (widget)); + } +} + +static void +sub_menu_initialize (GtkWidget * widget, gpointer data) +{ + GtkWidget *submenu; + submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + gtk_container_foreach (GTK_CONTAINER (submenu), menuitem_toggle_active, NULL); +} + +void +photo_menuitem_toggled_cb (GtkRadioMenuItem * menuitem, gpointer user_data) +{ + gboolean active = FALSE, ret = FALSE; + GEnumClass *eclass = (GEnumClass *) user_data; + GType etype = G_ENUM_CLASS_TYPE (eclass); + GEnumValue *val; + gint set_value = -1; + + /* Get value using menu item name */ + val = + g_enum_get_value_by_nick (eclass, + gtk_widget_get_name (GTK_WIDGET (menuitem))); + + g_object_get (G_OBJECT (menuitem), "active", &active, NULL); + if (active) { + if (etype == GST_TYPE_WHITE_BALANCE_MODE) { + GstWhiteBalanceMode mode; + ret = + gst_photography_set_white_balance_mode (GST_PHOTOGRAPHY + (gst_camera_bin), val->value); + gst_photography_get_white_balance_mode (GST_PHOTOGRAPHY (gst_camera_bin), + &mode); + set_value = (gint) mode; + } else if (etype == GST_TYPE_SCENE_MODE) { + GstSceneMode mode; + ret = + gst_photography_set_scene_mode (GST_PHOTOGRAPHY (gst_camera_bin), + val->value); + gst_photography_get_scene_mode (GST_PHOTOGRAPHY (gst_camera_bin), &mode); + set_value = (gint) mode; + } else if (etype == GST_TYPE_COLOUR_TONE_MODE) { + GstColourToneMode mode; + ret = + gst_photography_set_colour_tone_mode (GST_PHOTOGRAPHY + (gst_camera_bin), val->value); + gst_photography_get_colour_tone_mode (GST_PHOTOGRAPHY (gst_camera_bin), + &mode); + set_value = (gint) mode; + } else if (etype == GST_TYPE_FLASH_MODE) { + GstFlashMode mode; + ret = + gst_photography_set_flash_mode (GST_PHOTOGRAPHY (gst_camera_bin), + val->value); + gst_photography_get_flash_mode (GST_PHOTOGRAPHY (gst_camera_bin), &mode); + set_value = (gint) mode; + } + + if (!ret) { + g_print ("%s setting failed\n", val->value_name); + } else if (val->value != set_value) { + g_print ("%s setting failed, got %d\n", val->value_nick, set_value); + } + } +} + +void +photo_iso_speed_toggled_cb (GtkRadioMenuItem * menuitem, gpointer user_data) +{ + gboolean active; + const gchar *name; + guint val = 0, set_val = G_MAXUINT; + + g_object_get (G_OBJECT (menuitem), "active", &active, NULL); + if (active) { + name = gtk_widget_get_name (GTK_WIDGET (menuitem)); + /* iso auto setting = 0 */ + /* FIXME: check what values other than 0 can be set */ + if (!g_str_equal (name, "auto")) { + sscanf (name, "%d", &val); + } + if (!gst_photography_set_iso_speed (GST_PHOTOGRAPHY (gst_camera_bin), val)) { + g_print ("ISO speed (%d) setting failed\n", val); + } else { + gst_photography_get_iso_speed (GST_PHOTOGRAPHY (gst_camera_bin), + &set_val); + if (val != set_val) { + g_print ("ISO speed (%d) setting failed, got %d\n", val, set_val); + } + } + } +} + +void +photo_ev_comp_toggled_cb (GtkRadioMenuItem * menuitem, gpointer user_data) +{ + gboolean active; + const gchar *name; + gfloat val = 0.0, set_val = G_MAXFLOAT; + + g_object_get (G_OBJECT (menuitem), "active", &active, NULL); + if (active) { + name = gtk_widget_get_name (GTK_WIDGET (menuitem)); + sscanf (name, "%f", &val); + if (!gst_photography_set_ev_compensation (GST_PHOTOGRAPHY (gst_camera_bin), + val)) { + g_print ("EV compensation (%.1f) setting failed\n", val); + } else { + gst_photography_get_ev_compensation (GST_PHOTOGRAPHY (gst_camera_bin), + &set_val); + if (val != set_val) { + g_print ("EV compensation (%.1f) setting failed, got %.1f\n", val, + set_val); + } + } + } +} + +static void +photo_add_submenu_from_enum (GtkMenuItem * parent_item, GType enum_type) +{ + GTypeClass *tclass; + GEnumClass *eclass; + GtkWidget *new_item = NULL, *new_submenu = NULL; + guint i; + GEnumValue *val; + GSList *group = NULL; + + g_return_if_fail (parent_item && enum_type && G_TYPE_IS_CLASSED (enum_type)); + + tclass = g_type_class_ref (enum_type); + eclass = G_ENUM_CLASS (tclass); + new_submenu = gtk_menu_new (); + + for (i = 0; i < eclass->n_values; i++) { + val = g_enum_get_value (eclass, i); + new_item = gtk_radio_menu_item_new_with_label (group, val->value_nick); + /* Store enum nick as the menu item name */ + gtk_widget_set_name (new_item, val->value_nick); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (new_item)); + g_signal_connect (new_item, "toggled", + (GCallback) photo_menuitem_toggled_cb, eclass); + gtk_menu_shell_append (GTK_MENU_SHELL (new_submenu), new_item); + gtk_widget_show (new_item); + } + + gtk_menu_item_set_submenu (parent_item, new_submenu); + g_type_class_unref (tclass); +} + +static void +add_submenu_from_list (GtkMenuItem * parent_item, GList * labels, + GCallback toggled_cb) +{ + GtkWidget *new_item = NULL, *new_submenu = NULL; + GSList *group = NULL; + GList *l; + + new_submenu = gtk_menu_new (); + + for (l = labels; l != NULL; l = g_list_next (l)) { + const gchar *label = l->data; + new_item = gtk_radio_menu_item_new_with_label (group, label); + if (g_str_equal (label, "0")) { + /* Let's set zero as default */ + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (new_item), TRUE); + } + gtk_widget_set_name (new_item, label); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (new_item)); + g_signal_connect (new_item, "toggled", toggled_cb, NULL); + gtk_menu_shell_append (GTK_MENU_SHELL (new_submenu), new_item); + gtk_widget_show (new_item); + } + + gtk_menu_item_set_submenu (parent_item, new_submenu); +} + +static GtkMenuItem * +add_menuitem (GtkMenu * parent_menu, const gchar * item_name) +{ + GtkWidget *new_item; + + new_item = gtk_menu_item_new_with_label (item_name); + gtk_menu_shell_append (GTK_MENU_SHELL (parent_menu), new_item); + gtk_widget_show (new_item); + + return GTK_MENU_ITEM (new_item); +} + +GList * +create_iso_speed_labels () +{ + GList *labels = NULL; + gint i; + for (i = 0; i < G_N_ELEMENTS (iso_speed_labels); i++) { + labels = g_list_append (labels, iso_speed_labels[i]); + } + return labels; +} + +GList * +create_ev_comp_labels () +{ + GList *labels = NULL; + gdouble comp; + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + + for (comp = EV_COMP_MIN; comp <= EV_COMP_MAX; comp += EV_COMP_STEP) { + g_ascii_dtostr (buf, sizeof (buf), comp); + labels = g_list_append (labels, g_strdup (buf)); + } + return labels; +} + +static void +fill_photography_menu (GtkMenuItem * parent_item) +{ + GtkWidget *photo_menu = gtk_menu_new (); + GtkMenuItem *item = NULL; + GList *labels = NULL; + + /* Add menu items and create and associate submenus to each item */ + item = add_menuitem (GTK_MENU (photo_menu), "AWB"); + photo_add_submenu_from_enum (item, GST_TYPE_WHITE_BALANCE_MODE); + + item = add_menuitem (GTK_MENU (photo_menu), "Colour Tone"); + photo_add_submenu_from_enum (item, GST_TYPE_COLOUR_TONE_MODE); + + item = add_menuitem (GTK_MENU (photo_menu), "Scene"); + photo_add_submenu_from_enum (item, GST_TYPE_SCENE_MODE); + + item = add_menuitem (GTK_MENU (photo_menu), "Flash"); + photo_add_submenu_from_enum (item, GST_TYPE_FLASH_MODE); + + item = add_menuitem (GTK_MENU (photo_menu), "ISO"); + labels = create_iso_speed_labels (); + add_submenu_from_list (item, labels, (GCallback) photo_iso_speed_toggled_cb); + g_list_free (labels); + + item = add_menuitem (GTK_MENU (photo_menu), "EV comp"); + labels = create_ev_comp_labels (); + add_submenu_from_list (item, labels, (GCallback) photo_ev_comp_toggled_cb); + g_list_free (labels); + + gtk_menu_item_set_submenu (parent_item, photo_menu); +} + +void +capture_image_res_toggled_cb (GtkRadioMenuItem * menuitem, gpointer user_data) +{ + gboolean active; + const gchar *label; + gint i; + + g_object_get (G_OBJECT (menuitem), "active", &active, NULL); + if (active) { + label = gtk_widget_get_name (GTK_WIDGET (menuitem)); + /* Look for width and height corresponding to the label */ + for (i = 0; i < G_N_ELEMENTS (image_resolution_label_map); i++) { + if (g_str_equal (label, image_resolution_label_map[i].label)) { + /* set found values */ + g_signal_emit_by_name (gst_camera_bin, "user-image-res", + image_resolution_label_map[i].width, + image_resolution_label_map[i].height, 0); + break; + } + } + } +} + +GList * +create_image_resolution_labels () +{ + GList *labels = NULL; + int i; + for (i = 0; i < G_N_ELEMENTS (image_resolution_label_map); i++) { + labels = g_list_append (labels, image_resolution_label_map[i].label); + } + return labels; +} + +static void +fill_capture_menu (GtkMenuItem * parent_item) +{ + GtkWidget *capture_menu = gtk_menu_new (); + GtkMenuItem *item = NULL; + GList *labels = NULL; + + /* Add menu items and create and associate submenus to each item */ + item = add_menuitem (GTK_MENU (capture_menu), "Image resolution"); + + labels = create_image_resolution_labels (); + add_submenu_from_list (item, labels, + (GCallback) capture_image_res_toggled_cb); + g_list_free (labels); + + gtk_menu_item_set_submenu (parent_item, capture_menu); +} +#endif /* HAVE_GST_PHOTO_IFACE_H */ + +static gboolean +ui_create (void) +{ + gchar *gladefile = DEFAULT_GLADE_FILE; + + if (!g_file_test (gladefile, G_FILE_TEST_EXISTS)) { + gladefile = SHARED_GLADE_FILE; + } + + ui_glade_xml = glade_xml_new (gladefile, NULL, NULL); + if (!ui_glade_xml) { + fprintf (stderr, "glade_xml_new failed for %s\n", gladefile); + fflush (stderr); + goto done; + } + + ui_main_window = glade_xml_get_widget (ui_glade_xml, "windowMain"); + ui_drawing = glade_xml_get_widget (ui_glade_xml, "drawingareaView"); + ui_drawing_frame = glade_xml_get_widget (ui_glade_xml, "drawingareaFrame"); + ui_chk_continous = glade_xml_get_widget (ui_glade_xml, "chkbntContinous"); + ui_chk_rawmsg = glade_xml_get_widget (ui_glade_xml, "chkbtnRawMsg"); + ui_bnt_shot = GTK_BUTTON (glade_xml_get_widget (ui_glade_xml, "buttonShot")); + ui_bnt_pause = + GTK_BUTTON (glade_xml_get_widget (ui_glade_xml, "buttonPause")); + ui_cbbox_resolution = + GTK_COMBO_BOX (glade_xml_get_widget (ui_glade_xml, "comboboxResolution")); + ui_chk_mute = glade_xml_get_widget (ui_glade_xml, "chkbntMute"); + ui_vbox_color_controls = glade_xml_get_widget (ui_glade_xml, + "vboxColorControls"); + ui_rdbntImageCapture = glade_xml_get_widget (ui_glade_xml, + "radiobuttonImageCapture"); + ui_rdbntVideoCapture = glade_xml_get_widget (ui_glade_xml, + "radiobuttonVideoCapture"); + + ui_menuitem_photography = glade_xml_get_widget (ui_glade_xml, + "menuitemPhotography"); + ui_menuitem_capture = glade_xml_get_widget (ui_glade_xml, "menuitemCapture"); +#ifdef HAVE_GST_PHOTO_IFACE_H + if (ui_menuitem_photography) { + fill_photography_menu (GTK_MENU_ITEM (ui_menuitem_photography)); + } + + if (ui_menuitem_capture) { + fill_capture_menu (GTK_MENU_ITEM (ui_menuitem_capture)); + } +#endif + if (!(ui_main_window && ui_drawing && ui_chk_continous && ui_bnt_shot && + ui_bnt_pause && ui_cbbox_resolution && ui_chk_mute && + ui_vbox_color_controls && ui_rdbntImageCapture && + ui_rdbntVideoCapture && ui_chk_rawmsg && ui_menuitem_photography && + ui_menuitem_capture)) { + fprintf (stderr, "Some widgets couldn't be created\n"); + fflush (stderr); + goto done; + } + + gtk_widget_set_double_buffered (ui_drawing, FALSE); + ui_connect_signals (); + gtk_widget_show_all (ui_main_window); + capture_mode_config_gui (); + return TRUE; +done: + return FALSE; +} + +/* + * main + */ + +int +main (int argc, char *argv[]) +{ + int ret = 0; + + gst_init (&argc, &argv); + gtk_init (&argc, &argv); + + filename = g_string_new_len ("", 16); + + /* create UI */ + if (!ui_create ()) { + ret = -1; + goto done; + } + /* create pipeline and run */ + if (me_gst_setup_pipeline (NULL, NULL)) { + gtk_main (); + } + +done: + me_gst_cleanup_element (); + g_string_free (filename, TRUE); + return ret; +} diff --git a/tests/examples/camerabin/gst-camera.glade b/tests/examples/camerabin/gst-camera.glade new file mode 100644 index 00000000..5230c832 --- /dev/null +++ b/tests/examples/camerabin/gst-camera.glade @@ -0,0 +1,397 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gst-camera + + + + True + + + True + + + True + + + True + Photography + True + + + True + + + + + + + True + Capture + True + + + + + False + + + + + True + + + + + 1 + + + + + False + + + + + True + + + True + True + Image capture + True + 0 + True + True + + + + + + True + True + Video rec + True + 0 + True + True + radiobuttonImageCapture + + + + 1 + + + + + True + True + Shot + True + 0 + + + + 2 + + + + + True + True + Pause + True + 0 + + + + 3 + + + + + True + True + mute + True + 0 + True + + + + 4 + + + + + True + True + continous + True + 0 + True + + + 5 + + + + + True + True + Send raw image after still image capture as gstreamer message + raw msg + True + 0 + True + + + + 6 + + + + + False + 1 + + + + + True + 1 + 3 + + + True + + + True + video effects: + + + + + True + True + none + True + 0 + True + True + + + + 1 + + + + + True + True + edged + True + 0 + True + True + rbBntVidEffNone + + + + 2 + + + + + True + True + aging + True + 0 + True + True + rbBntVidEffNone + + + + 3 + + + + + True + True + dice + True + 0 + True + True + rbBntVidEffNone + + + + 4 + + + + + True + True + warp + True + 0 + True + True + rbBntVidEffNone + + + + 5 + + + + + True + True + shagadelic + True + 0 + True + True + rbBntVidEffNone + + + + 6 + + + + + True + True + vertigo + True + 0 + True + True + rbBntVidEffNone + + + + 7 + + + + + True + True + rev + True + 0 + True + True + rbBntVidEffNone + + + + 8 + + + + + True + True + quark + True + 0 + True + True + rbBntVidEffNone + + + + 9 + + + + + + + + + + 200 + 200 + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + + + + + + + 2 + 3 + + + + + + True + 0 + GTK_SHADOW_NONE + 1 + False + + + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + + + + 1 + 2 + + + + + 2 + + + + + True + True + 100 100 1100 10 100 100 + 0 + GTK_POS_LEFT + + + + False + 3 + + + + + + -- cgit v1.2.1