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/camerabin/gst-camera-perf.c | 726 +++++++++++++++++++++++++++++ 1 file changed, 726 insertions(+) create mode 100644 tests/examples/camerabin/gst-camera-perf.c (limited to 'tests/examples/camerabin/gst-camera-perf.c') 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; +} -- cgit v1.2.1