Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: timvideos/gst-switch
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 19c5a6a22d7f
Choose a base ref
...
head repository: timvideos/gst-switch
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: a2f1eddf0137
Choose a head ref
  • 5 commits
  • 6 files changed
  • 2 contributors

Commits on Jan 7, 2015

  1. Full patch with fixes for --record filename handling.

     * Support %q for hostname
     * Support % tokens from strftime
     * Create directories for the recording output
     * Specifying the filename is optional
    David Nugent committed Jan 7, 2015
    Copy the full SHA
    4b979df View commit details
  2. Indent fix.

    David Nugent committed Jan 7, 2015
    Copy the full SHA
    1a946bd View commit details
  3. Enable timestamp and hostname variables in --record filename

    Make filename optional when using the --record switch
    
    Fixes issues #48, #49, #50, #69
    David Nugent committed Jan 7, 2015
    Copy the full SHA
    38cc6be View commit details
  4. Copy the full SHA
    52f5d1a View commit details
  5. Indent fixes.

    mithro committed Jan 7, 2015
    Copy the full SHA
    a2f1edd View commit details
Showing with 243 additions and 45 deletions.
  1. +1 −0 .gitignore
  2. +9 −3 tests/unit/Makefile.am
  3. +91 −0 tests/unit/test_gstrecorder_filename.c
  4. +0 −10 tests/unit/test_gstswitchopts.c
  5. +86 −28 tools/gstrecorder.c
  6. +56 −4 tools/gstswitchserver.c
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
*.txt
imgurbash.sh.*
.coverage
.gdb_history
.idea
/INSTALL
/Makefile
12 changes: 9 additions & 3 deletions tests/unit/Makefile.am
Original file line number Diff line number Diff line change
@@ -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)

91 changes: 91 additions & 0 deletions tests/unit/test_gstrecorder_filename.c
Original file line number Diff line number Diff line change
@@ -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 ();
}
10 changes: 0 additions & 10 deletions tests/unit/test_gstswitchopts.c
Original file line number Diff line number Diff line change
@@ -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)
{
114 changes: 86 additions & 28 deletions tools/gstrecorder.c
Original file line number Diff line number Diff line change
@@ -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);
60 changes: 56 additions & 4 deletions tools/gstswitchserver.c
Original file line number Diff line number Diff line change
@@ -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);