Permalink
Comparing changes
Open a pull request
- 5 commits
- 6 files changed
- 0 commit comments
- 2 contributors
Unified
Split
Showing
with
243 additions
and 45 deletions.
- +1 −0 .gitignore
- +9 −3 tests/unit/Makefile.am
- +91 −0 tests/unit/test_gstrecorder_filename.c
- +0 −10 tests/unit/test_gstswitchopts.c
- +86 −28 tools/gstrecorder.c
- +56 −4 tools/gstswitchserver.c
| @@ -8,6 +8,7 @@ | ||
| *.txt | ||
| imgurbash.sh.* | ||
| .coverage | ||
| .gdb_history | ||
| .idea | ||
| /INSTALL | ||
| /Makefile | ||
| @@ -1,15 +1,21 @@ | ||
| include $(top_srcdir)/build/glib-tap.mk | ||
| LDADD = \ | ||
| $(GLIB_LIBS) $(GST_LIBS) | ||
| LDADD = $(GTK_LIBS) $(GLIB_LIBS) $(GST_LIBS) | ||
| CFLAGS += $(GST_CFLAGS) | ||
| test_gstswitchopts_SOURCES = test_gstswitchopts.c | ||
| test_gstswitchopts_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) \ | ||
| $(GCOV_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -DLOG_PREFIX="\"./tests\"" | ||
| test_gstrecorder_filename_SOURCES = test_gstrecorder_filename.c ../../tools/gstworker.c | ||
| test_gstrecorder_filename_CFLAGS = -fprofile-arcs $(GST_CFLAGS) $(GST_BASE_CFLAGS) \ | ||
| $(GCOV_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -DLOG_PREFIX="\"./tests\"" | ||
| test_gstrecorder_filename_LDFLAGS = $(GCOV_LFLAGS) | ||
| dist_test_data = \ | ||
| $(NULL) | ||
| test_programs = \ | ||
| test_gstswitchopts \ | ||
| test_gstrecorder_filename \ | ||
| test_gstcomposite \ | ||
| $(NULL) | ||
| @@ -0,0 +1,91 @@ | ||
| #include "tools/gstrecorder.c" | ||
| #include <fcntl.h> | ||
| #define BASEDIR "/tmp/unittests/" | ||
| // fake options struct, not used in these tests | ||
| GstSwitchServerOpts opts; | ||
| gboolean verbose = FALSE; | ||
| static char * | ||
| filepath (char *buf, size_t len, char const *fn) | ||
| { | ||
| snprintf (buf, len, BASEDIR "%d/%s", getpid (), fn); | ||
| return buf; | ||
| } | ||
| static gboolean | ||
| exists (const char *filepath, gboolean dir) | ||
| { | ||
| struct stat s; | ||
| if (stat (filepath, &s) == 0) { | ||
| if (!dir || S_ISDIR (s.st_mode)) | ||
| return TRUE; | ||
| } | ||
| return FALSE; | ||
| } | ||
| static gboolean | ||
| touch (const char *filepath) | ||
| { | ||
| int fd = | ||
| open (filepath, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); | ||
| if (fd != -1) { | ||
| close (fd); | ||
| return TRUE; | ||
| } | ||
| return FALSE; | ||
| } | ||
| static int | ||
| create_new_directory (const char *name) | ||
| { | ||
| char buf[256]; | ||
| gst_recorder_mkdirs (filepath (buf, sizeof buf, name)); | ||
| return exists (buf, TRUE); | ||
| } | ||
| static gboolean | ||
| create_capture_file (const char *name) | ||
| { | ||
| char buf[256]; | ||
| char const *capfilename = | ||
| gst_recorder_new_filename (filepath (buf, sizeof buf, name)); | ||
| return touch (capfilename); | ||
| } | ||
| static void | ||
| test_mkdirs () | ||
| { | ||
| g_assert_cmpint (create_new_directory ("testdir1"), !=, 0); | ||
| g_assert_cmpint (create_new_directory ("testdir2"), !=, 0); | ||
| g_assert_cmpint (create_new_directory ("testdir1/testdir3"), !=, 0); | ||
| g_assert_cmpint (create_new_directory ("testdir3/testdir1"), !=, 0); | ||
| } | ||
| static void | ||
| test_new_filename () | ||
| { | ||
| g_assert_true (create_capture_file ("%Y%m%d_%T")); | ||
| g_assert_true (create_capture_file ("%Y%m/%d_%T")); | ||
| g_assert_true (create_capture_file ("%Y/%m/%d/%T")); | ||
| } | ||
| int | ||
| main (int argc, char *argv[]) | ||
| { | ||
| g_test_init (&argc, &argv, NULL); | ||
| gst_init (&argc, &argv); | ||
| g_test_set_nonfatal_assertions (); | ||
| g_test_add_func ("/gstswitch/options/mkdirs", test_mkdirs); | ||
| g_test_add_func ("/gstswitch/options/filename", test_new_filename); | ||
| return g_test_run (); | ||
| } |
| @@ -1,16 +1,6 @@ | ||
| #include "tools/gstswitchopts.c" | ||
| static char const *test_bad_strings[] = { | ||
| "video/x-raw,height=[400,800],width=500,framerate=25/1", | ||
| "720p@75", "sadfasf", | ||
| "video/x-raw,height=10,width=500,framerate=25/1", | ||
| "video/x-raw,height=400,width=10,framerate=25/1", | ||
| "video/x-raw,height=400,width=10,framerate=1001/1", | ||
| "pal@75", | ||
| NULL | ||
| }; | ||
| static void | ||
| test_strings_good (void) | ||
| { | ||
| @@ -33,6 +33,7 @@ | ||
| #include <stdio.h> | ||
| #include <string.h> | ||
| #include <time.h> | ||
| #include <sys/stat.h> | ||
| #include "gstswitchserver.h" | ||
| #include "gstcomposite.h" | ||
| #include "gstrecorder.h" | ||
| @@ -170,45 +171,102 @@ gst_recorder_set_property (GstRecorder * rec, guint property_id, | ||
| } | ||
| } | ||
| /* | ||
| * @param dir - directory to create | ||
| * @return nothing | ||
| * Create a directory and all intermediary directories | ||
| * if necessary. Note that errors are ignored here, if | ||
| * the resulting path is in fact unusable having early | ||
| * warning here is not necessary | ||
| */ | ||
| static void | ||
| gst_recorder_mkdirs (const char *dir) | ||
| { | ||
| char tmp[256]; | ||
| strncpy (tmp, dir, sizeof (tmp)); | ||
| size_t len = strlen (tmp); | ||
| if (len > 0) { | ||
| if (tmp[len - 1] == '/') | ||
| tmp[len--] = 0; | ||
| if (len > 0) { | ||
| size_t at = 1; // skip leading slash | ||
| while (at < len) { | ||
| char *p = strchr (tmp + at, '/'); | ||
| if (p != NULL && *p == '/') { | ||
| *p = '\0'; | ||
| mkdir (tmp, S_IRWXU); | ||
| *p = '/'; | ||
| at = p - tmp + 1; | ||
| } else | ||
| at = len; | ||
| } | ||
| mkdir (tmp, S_IRWXU); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * @param rec The GstRecorder instance. | ||
| * @memberof GstRecorder | ||
| * @param filepath - file file/path of a unix file | ||
| * @return length of the path potion of the file, excluding the separator | ||
| */ | ||
| static size_t | ||
| gst_recorder_pathlen (const char *filepath) | ||
| { | ||
| if (filepath != NULL && strlen (filepath) > 0) { | ||
| char const *sep = strrchr (filepath + 1, '/'); | ||
| if (sep != NULL) | ||
| return sep - filepath; | ||
| } | ||
| return 0; | ||
| } | ||
| /** | ||
| * @param filename Template name of the file to save | ||
| * @return the file name string, need to be freed after used | ||
| * | ||
| * This is used to generate a new recording file name for the recorder. | ||
| */ | ||
| static const gchar * | ||
| gst_recorder_new_filename (GstRecorder * rec) | ||
| gst_recorder_new_filename (const gchar * filename) | ||
| { | ||
| time_t t; | ||
| struct tm *tm; | ||
| gchar stamp[128]; | ||
| const gchar *dot = NULL; | ||
| const gchar *filename = opts.record_filename; | ||
| if (!filename) { | ||
| if (!filename) | ||
| return NULL; | ||
| } | ||
| t = time (NULL); | ||
| tm = localtime (&t); | ||
| if (tm == NULL) { | ||
| static gint num = 0; | ||
| num += 1; | ||
| snprintf (stamp, sizeof (stamp), "%d", num); | ||
| } else { | ||
| strftime (stamp, sizeof (stamp), "%F %H%M%S", tm); | ||
| gchar fnbuf[256]; | ||
| time_t t = time (NULL); | ||
| struct tm *tm = localtime (&t); | ||
| // Note: reserve some space for collision suffix | ||
| strftime (fnbuf, sizeof (fnbuf) - 5, filename, tm); | ||
| // We now have a fully built name in our buffer | ||
| // If there is at least one directory present, make sure they exist | ||
| size_t pathlen = gst_recorder_pathlen (fnbuf); | ||
| if (pathlen > 0) { | ||
| fnbuf[pathlen] = '\0'; | ||
| gst_recorder_mkdirs (fnbuf); | ||
| fnbuf[pathlen] = '/'; | ||
| } | ||
| if ((dot = g_strrstr (filename, "."))) { | ||
| const gchar *s = g_strndup (filename, dot - filename); | ||
| filename = g_strdup_printf ("%s %s%s", s, stamp, dot); | ||
| g_free ((gpointer) s); | ||
| } else { | ||
| filename = g_strdup_printf ("%s %s.dat", filename, stamp); | ||
| pathlen = strlen (fnbuf); // reuse for length of file/path | ||
| // handle name collisions by adding a suffix/extension | ||
| size_t suffix = 0; | ||
| while (1) { | ||
| struct stat s; | ||
| if (-1 == stat (fnbuf, &s)) { | ||
| if (ENOENT == errno) | ||
| break; | ||
| else { | ||
| perror (fnbuf); | ||
| return NULL; // can't record | ||
| } | ||
| } | ||
| snprintf (fnbuf + pathlen, 256 - pathlen, ".%03d", (int) suffix++); | ||
| // can't record if we've used up our additions | ||
| if (suffix > 999) | ||
| return NULL; | ||
| } | ||
| return filename; | ||
| return g_strdup (fnbuf); | ||
| } | ||
| /** | ||
| @@ -221,7 +279,7 @@ gst_recorder_new_filename (GstRecorder * rec) | ||
| static GString * | ||
| gst_recorder_get_pipeline_string (GstRecorder * rec) | ||
| { | ||
| const gchar *filename = gst_recorder_new_filename (rec); | ||
| const gchar *filename = gst_recorder_new_filename (opts.record_filename); | ||
| GString *desc; | ||
| //INFO ("Recording to %s and port %d", filename, rec->sink_port); | ||
| @@ -42,13 +42,19 @@ | ||
| #include <signal.h> | ||
| #include <sys/types.h> | ||
| #include <unistd.h> | ||
| #include <string.h> | ||
| #include <sys/stat.h> | ||
| #define GST_SWITCH_SERVER_DEFAULT_HOST "localhost" | ||
| #define GST_SWITCH_SERVER_DEFAULT_VIDEO_ACCEPTOR_PORT 3000 | ||
| #define GST_SWITCH_SERVER_DEFAULT_AUDIO_ACCEPTOR_PORT 4000 | ||
| #define GST_SWITCH_SERVER_DEFAULT_CONTROLLER_PORT 5000 | ||
| #define GST_SWITCH_SERVER_LISTEN_BACKLOG 8 /* client connection queue */ | ||
| #define GST_SWITCH_SERVER_HOST_SPEC "%q" | ||
| #define GST_SWITCH_SERVER_DEFAULT_RECORD_FILE GST_SWITCH_SERVER_HOST_SPEC "_record_%Y%m%d%T" | ||
| #define GST_SWITCH_SERVER_DEFAULT_RECORD_EXT ".avi" | ||
| #define GST_SWITCH_SERVER_LOCK_MAIN_LOOP(srv) (g_mutex_lock (&(srv)->main_loop_lock)) | ||
| #define GST_SWITCH_SERVER_UNLOCK_MAIN_LOOP(srv) (g_mutex_unlock (&(srv)->main_loop_lock)) | ||
| #define GST_SWITCH_SERVER_LOCK_VIDEO_ACCEPTOR(srv) (g_mutex_lock (&(srv)->video_acceptor_lock)) | ||
| @@ -80,14 +86,59 @@ GstSwitchServerOpts opts = { | ||
| gboolean verbose = FALSE; | ||
| static gboolean gparse_record_filename(gchar *name, gchar *value, gpointer data, GError **error) | ||
| { | ||
| size_t maxpathlen = 256; | ||
| gchar fnbuf[maxpathlen+1]; | ||
| size_t fnlen = 0; | ||
| if (value == NULL || (fnlen = strlen(value)) == 0) { | ||
| // file not specified, use the default filename.ext | ||
| strncpy(fnbuf, GST_SWITCH_SERVER_DEFAULT_RECORD_FILE, sizeof(fnbuf) - 5); | ||
| strcat(fnbuf, GST_SWITCH_SERVER_DEFAULT_RECORD_EXT); | ||
| } else if (fnlen < maxpathlen) { | ||
| strncpy(fnbuf, value, sizeof(fnbuf)); | ||
| if ((fnlen < 5 || strchr(value + fnlen - 5, '.') == NULL) && fnlen + 4 < maxpathlen) | ||
| strcat(fnbuf, GST_SWITCH_SERVER_DEFAULT_RECORD_EXT); | ||
| } else { | ||
| GError *err = g_error_new(G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "%s path/filename too long: %s\n", name, value); | ||
| g_propagate_error(error, err); | ||
| return FALSE; | ||
| } | ||
| // Do manual substitution of %q => hostname up front since this is static | ||
| // Time and date tokens are substituted within the recorder as the recording | ||
| // is started or cut to accurately reflect the date/time of recording | ||
| char *h = strstr(fnbuf, GST_SWITCH_SERVER_HOST_SPEC); | ||
| if (h != NULL) { | ||
| int mnlen = maxpathlen - fnlen; // limit size to what is available | ||
| char hostname[mnlen + 1]; | ||
| if (gethostname(hostname, mnlen) != 0) | ||
| strcpy(hostname, GST_SWITCH_SERVER_DEFAULT_HOST); | ||
| else hostname[mnlen] = '\0'; | ||
| int hnlen = strlen(hostname); | ||
| hostname[hnlen] = '\0'; // guarantee nul termination | ||
| fnlen = strlen(fnbuf); | ||
| do { | ||
| size_t over = fnlen - (h - fnbuf) + 1; | ||
| memmove(h + hnlen, h + 2, over); | ||
| memcpy(h, hostname, hnlen); | ||
| fnlen += (hnlen - 2); // adjust the result length | ||
| } while ((h = strstr(fnbuf, GST_SWITCH_SERVER_HOST_SPEC)) != NULL); | ||
| } | ||
| opts.record_filename = g_strdup(fnbuf); | ||
| return TRUE; | ||
| } | ||
| static GOptionEntry entries[] = { | ||
| {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, | ||
| "Prompt more messages", NULL}, | ||
| {"test-switch", 't', 0, G_OPTION_ARG_STRING, &opts.test_switch, | ||
| "Perform switch test", "OUTPUT"}, | ||
| {"record", 'r', 0, G_OPTION_ARG_STRING, &opts.record_filename, | ||
| "Enable recorder and record into the specified FILENAME", | ||
| "FILENAME"}, | ||
| {"record", 'r', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, | ||
| (gpointer)gparse_record_filename, | ||
| "Enable recorder and record into the specified FILENAME"}, | ||
| {"video-input-port", 'p', 0, G_OPTION_ARG_INT, &opts.video_input_port, | ||
| "Specify the video input listen port.", "NUM"}, | ||
| {"audio-input-port", 'a', 0, G_OPTION_ARG_INT, &opts.audio_input_port, | ||
| @@ -1750,6 +1801,7 @@ static unsigned long long i = 0; | ||
| void | ||
| my_handler (int signum) | ||
| { | ||
| extern void __gcov_flush(); | ||
| printf ("received signal\n"); | ||
| printf ("%llu\n", i); | ||
| __gcov_flush (); /* dump coverage data on receiving SIGUSR1 */ | ||
| @@ -1761,7 +1813,7 @@ main (int argc, char *argv[]) | ||
| { | ||
| struct sigaction new_action, old_action; | ||
| int n; | ||
| /* setup signal hander */ | ||
| new_action.sa_handler = my_handler; | ||
| sigemptyset (&new_action.sa_mask); | ||