mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-17 03:35:21 +00:00
ptp: Initial implementation of a PTP clock
GstPtpClock implements a PTP (IEEE1588:2008) ordinary clock in slave-only mode, that allows a GStreamer pipeline to synchronize to a PTP network clock in some specific domain. The PTP subsystem can be initialized with gst_ptp_init(), which then starts a helper process to do the actual communication via the PTP ports. This is required as PTP listens on ports < 1024 and thus requires special privileges. Once this helper process is started, the main process will synchronize to all PTP domains that are detected on the selected interfaces. gst_ptp_clock_new() then allows to create a GstClock that provides the PTP time from a master clock inside a specific PTP domain. This clock will only return valid timestamps once the timestamps in the PTP domain are known. To check this, the GstPtpClock::internal-clock property and the related notify::clock signal can be used. Once the internal clock is not NULL, the PTP domain's time is known. Alternatively you can wait for this with gst_ptp_clock_wait_ready(). To gather statistics about the PTP clock synchronization, gst_ptp_statistics_callback_add() can be used. This gives the application the possibility to collect all kinds of statistics from the clock synchronization. https://bugzilla.gnome.org/show_bug.cgi?id=749391
This commit is contained in:
parent
fe3249b0e1
commit
8d77759834
15 changed files with 3449 additions and 10 deletions
107
configure.ac
107
configure.ac
|
@ -258,6 +258,105 @@ if test "x$USE_POISONING" = xyes; then
|
|||
[Define if we should poison deallocated memory])
|
||||
fi
|
||||
|
||||
dnl PTP support parts
|
||||
AC_MSG_CHECKING([whether PTP support can be enabled])
|
||||
case "$host_os" in
|
||||
*android*)
|
||||
dnl Can't run on Android because of permissions
|
||||
HAVE_PTP=no
|
||||
;;
|
||||
mingw*|pw32*|cygwin*)
|
||||
dnl Not ported to Windows yet
|
||||
HAVE_PTP=no
|
||||
;;
|
||||
darwin*)
|
||||
dnl Can't run on iOS because of permissions
|
||||
AC_CHECK_HEADER(MobileCoreServices/MobileCoreServices.h, HAVE_PTP="no", HAVE_PTP="yes", [-])
|
||||
;;
|
||||
linux*|darwin*|solaris*|netbsd*|freebsd*|openbsd*|kfreebsd*|dragonfly*|gnu*)
|
||||
HAVE_PTP=yes
|
||||
;;
|
||||
*)
|
||||
HAVE_PTP=no
|
||||
;;
|
||||
esac
|
||||
AC_MSG_RESULT([$HAVE_PTP])
|
||||
|
||||
dnl user/group to change to in gst-ptp-helper
|
||||
AC_ARG_WITH([ptp-helper-setuid-user],
|
||||
AS_HELP_STRING([--with-ptp-helper-setuid-user],[User to switch to when installing gst-ptp-helper setuid root]),
|
||||
[
|
||||
if test "x$withval" != "x"
|
||||
then
|
||||
AC_DEFINE_UNQUOTED(HAVE_PTP_HELPER_SETUID_USER, "$withval", [PTP helper setuid user])
|
||||
fi
|
||||
], []
|
||||
)
|
||||
|
||||
dnl group/group to change to in gst-ptp-helper
|
||||
AC_ARG_WITH([ptp-helper-setuid-group],
|
||||
AS_HELP_STRING([--with-ptp-helper-setuid-group],[Group to switch to when installing gst-ptp-helper setuid root]),
|
||||
[
|
||||
if test "x$withval" != "x"
|
||||
then
|
||||
AC_DEFINE_UNQUOTED(HAVE_PTP_HELPER_SETUID_GROUP, "$withval", [PTP helper setuid group])
|
||||
fi
|
||||
], []
|
||||
)
|
||||
|
||||
AC_ARG_WITH(
|
||||
ptp-helper-permissions,
|
||||
AC_HELP_STRING(
|
||||
[--with-ptp-helper-permissions],
|
||||
[how to gain PTP permissions (none, setuid-root, capabilities, auto)]),
|
||||
[],
|
||||
[with_ptp_helper_permissions=auto])
|
||||
|
||||
gst_ptp_have_cap=no
|
||||
AG_GST_CHECK_LIBHEADER(CAP, cap,
|
||||
cap_init, ,
|
||||
sys/capability.h,
|
||||
CAP_LIBS="-lcap"
|
||||
AC_SUBST(CAP_LIBS)
|
||||
gst_ptp_have_cap=yes)
|
||||
|
||||
AC_PATH_PROG([SETCAP], [setcap], [no], [$PATH:/usr/bin:/bin:/usr/sbin:/sbin])
|
||||
|
||||
if test "x$HAVE_PTP" = "xyes"; then
|
||||
AC_DEFINE(HAVE_PTP, 1, [PTP support available])
|
||||
|
||||
AC_MSG_CHECKING([how to install gst-ptp-helper])
|
||||
if test "x$with_ptp_helper_permissions" = "xauto"; then
|
||||
if test "x$gst_ptp_have_cap" = "xyes" -a "x$SETCAP" != "xno"; then
|
||||
with_ptp_helper_permissions="capabilities"
|
||||
else
|
||||
with_ptp_helper_permissions="setuid-root"
|
||||
fi
|
||||
fi
|
||||
AC_MSG_RESULT([$with_ptp_helper_permissions])
|
||||
|
||||
case "$with_ptp_helper_permissions" in
|
||||
none)
|
||||
;;
|
||||
setuid-root)
|
||||
AC_DEFINE(HAVE_PTP_HELPER_SETUID, 1,
|
||||
[Use setuid-root for permissions in PTP helper])
|
||||
;;
|
||||
capabilities)
|
||||
AC_DEFINE(HAVE_PTP_HELPER_CAPABILITIES, 1,
|
||||
[Use capabilities for permissions in PTP helper])
|
||||
;;
|
||||
*)
|
||||
AC_MSG_ERROR(Invalid parameter [$with_ptp_helper_permissions])
|
||||
;;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(HAVE_PTP, test "x$HAVE_PTP" = "xyes")
|
||||
AM_CONDITIONAL(HAVE_PTP_HELPER_SETUID, test "x$with_ptp_helper_permissions" = "xsetuid-root")
|
||||
AM_CONDITIONAL(HAVE_PTP_HELPER_CAPABILITIES, test "x$with_ptp_helper_permissions" = "xcapabilities")
|
||||
|
||||
dnl *** checks for platform ***
|
||||
|
||||
dnl * hardware/architecture *
|
||||
|
@ -806,6 +905,12 @@ AC_DEFINE_UNQUOTED(GST_PLUGIN_SCANNER_INSTALLED,
|
|||
"$GST_PLUGIN_SCANNER_INSTALLED", [location of the installed gst-plugin-scanner])
|
||||
AC_SUBST(GST_PLUGIN_SCANNER_INSTALLED)
|
||||
|
||||
dnl ptp helper locations
|
||||
AS_AC_EXPAND(GST_PTP_HELPER_INSTALLED,${libexecdir}/gstreamer-$GST_API_VERSION/gst-ptp-helper)
|
||||
AC_DEFINE_UNQUOTED(GST_PTP_HELPER_INSTALLED,
|
||||
"$GST_PTP_HELPER_INSTALLED", [location of the installed gst-ptp-helper])
|
||||
AC_SUBST(GST_PTP_HELPER_INSTALLED)
|
||||
|
||||
dnl things for our internal libcheck (must be called even if building
|
||||
dnl libcheck is disabled because it defines conditionals)
|
||||
AG_GST_CHECK_CHECKS()
|
||||
|
@ -842,6 +947,7 @@ tests/examples/helloworld/Makefile
|
|||
tests/examples/manual/Makefile
|
||||
tests/examples/memory/Makefile
|
||||
tests/examples/netclock/Makefile
|
||||
tests/examples/ptp/Makefile
|
||||
tests/examples/streamiddemux/Makefile
|
||||
tests/examples/streams/Makefile
|
||||
tools/Makefile
|
||||
|
@ -945,6 +1051,7 @@ Configuration
|
|||
Plugin support : ${enable_plugin}
|
||||
Static plugins : ${enable_static_plugins}
|
||||
Unit testing support : ${BUILD_CHECK}
|
||||
PTP clock support : ${HAVE_PTP}
|
||||
|
||||
Debug : ${USE_DEBUG}
|
||||
Profiling : ${USE_PROFILING}
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
<xi:include href="xml/gstnetclientclock.xml" />
|
||||
<xi:include href="xml/gstnettimepacket.xml" />
|
||||
<xi:include href="xml/gstnettimeprovider.xml" />
|
||||
<xi:include href="xml/gstptpclock.xml" />
|
||||
</chapter>
|
||||
|
||||
<chapter id="gstreamer-check">
|
||||
|
|
|
@ -941,6 +941,32 @@ GST_IS_NET_TIME_PROVIDER_CLASS
|
|||
gst_net_time_provider_get_type
|
||||
</SECTION>
|
||||
|
||||
<SECTION>
|
||||
<FILE>gstptpclock</FILE>
|
||||
<TITLE>GstPtpClock</TITLE>
|
||||
<INCLUDE>gst/net/net.h</INCLUDE>
|
||||
gst_ptp_init
|
||||
gst_ptp_deinit
|
||||
gst_ptp_is_initialized
|
||||
gst_ptp_is_supported
|
||||
|
||||
GstPtpClock
|
||||
gst_ptp_clock_new
|
||||
|
||||
gst_ptp_statistics_callback_add
|
||||
gst_ptp_statistics_callback_remove
|
||||
<SUBSECTION Standard>
|
||||
GstPtpClockClass
|
||||
GstPtpClockPrivate
|
||||
GST_PTP_CLOCK
|
||||
GST_IS_PTP_CLOCK
|
||||
GST_TYPE_PTP_CLOCK
|
||||
GST_PTP_CLOCK_CLASS
|
||||
GST_IS_PTP_CLOCK_CLASS
|
||||
<SUBSECTION Private>
|
||||
gst_ptp_clock_get_type
|
||||
</SECTION>
|
||||
|
||||
<SECTION>
|
||||
<FILE>gstcheck</FILE>
|
||||
<TITLE>GstCheck</TITLE>
|
||||
|
|
|
@ -7,8 +7,33 @@ gst_completion_helper_@GST_API_VERSION@_LDADD = $(GST_OBJ_LIBS)
|
|||
|
||||
bashhelpersdir = $(BASH_HELPERS_DIR)
|
||||
dist_bashhelpers_DATA = gst
|
||||
endif
|
||||
|
||||
helpers_PROGRAMS = gst-plugin-scanner
|
||||
helpersdir=$(libexecdir)/gstreamer-$(GST_API_VERSION)
|
||||
|
||||
gst_plugin_scanner_SOURCES = gst-plugin-scanner.c
|
||||
gst_plugin_scanner_CFLAGS = $(GST_OBJ_CFLAGS)
|
||||
gst_plugin_scanner_LDADD = $(GST_OBJ_LIBS)
|
||||
|
||||
if HAVE_PTP
|
||||
helpers_PROGRAMS += gst-ptp-helper
|
||||
gst_ptp_helper_SOURCES = gst-ptp-helper.c
|
||||
gst_ptp_helper_CFLAGS = $(GST_OBJ_CFLAGS) $(GIO_CFLAGS)
|
||||
gst_ptp_helper_LDADD = $(GST_OBJ_LIBS) $(GIO_LIBS) $(CAP_LIBS)
|
||||
endif
|
||||
|
||||
install-exec-hook:
|
||||
if HAVE_PTP
|
||||
if HAVE_PTP_HELPER_SETUID
|
||||
chown root $(DESTDIR)$(helpersdir)/gst-ptp-helper
|
||||
chmod u+s $(DESTDIR)$(helpersdir)/gst-ptp-helper
|
||||
endif
|
||||
if HAVE_PTP_HELPER_CAPABILITIES
|
||||
$(SETCAP) cap_net_bind_service,cap_net_admin+ep $(DESTDIR)$(helpersdir)/gst-ptp-helper
|
||||
endif
|
||||
endif
|
||||
if ENABLE_BASH_COMPLETION
|
||||
$(MKDIR_P) $(DESTDIR)$(BASH_HELPERS_DIR) && \
|
||||
cd $(DESTDIR)$(bindir) && \
|
||||
$(INSTALL) `echo "gst-completion-helper-" | sed '$(transform)'`@GST_API_VERSION@$(EXEEXT) \
|
||||
|
@ -19,13 +44,6 @@ uninstall-hook:
|
|||
rm -f $(DESTDIR)$(BASH_HELPERS_DIR)/gst-completion-helper-@GST_API_VERSION@$(EXEEXT)
|
||||
endif
|
||||
|
||||
helpers_PROGRAMS = gst-plugin-scanner
|
||||
helpersdir=$(libexecdir)/gstreamer-$(GST_API_VERSION)
|
||||
|
||||
gst_plugin_scanner_SOURCES = gst-plugin-scanner.c
|
||||
gst_plugin_scanner_CFLAGS = $(GST_OBJ_CFLAGS)
|
||||
gst_plugin_scanner_LDADD = $(GST_OBJ_LIBS)
|
||||
|
||||
# clean out the old one to make sure everything is udpated correctly
|
||||
# remove again after release
|
||||
CLEANFILES = plugin-scanner
|
||||
|
|
560
libs/gst/helpers/gst-ptp-helper.c
Normal file
560
libs/gst/helpers/gst-ptp-helper.c
Normal file
|
@ -0,0 +1,560 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2015 Sebastian Dröge <sebastian@centricular.com>
|
||||
*
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/* Helper process that runs setuid root or with appropriate privileges to
|
||||
* listen on ports < 1024, do multicast operations and get MAC addresses of
|
||||
* interfaces. Privileges are dropped after these operations are done.
|
||||
*
|
||||
* It listens on the PTP multicast group on port 319 and 320 and forwards
|
||||
* everything received there to stdout, while forwarding everything received
|
||||
* on stdout to those sockets.
|
||||
* Additionally it provides the MAC address of a network interface via stdout
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAVE_PTP_HELPER_SETUID
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PTP_HELPER_CAPABILITIES
|
||||
#include <sys/capability.h>
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/net/gstptp_private.h>
|
||||
|
||||
#define PTP_MULTICAST_GROUP "224.0.1.129"
|
||||
#define PTP_EVENT_PORT 319
|
||||
#define PTP_GENERAL_PORT 320
|
||||
|
||||
static gchar **ifaces = NULL;
|
||||
static gboolean verbose = FALSE;
|
||||
static guint64 clock_id = (guint64) - 1;
|
||||
static guint8 clock_id_array[8];
|
||||
|
||||
static GOptionEntry opt_entries[] = {
|
||||
{"interface", 'i', 0, G_OPTION_ARG_STRING_ARRAY, &ifaces,
|
||||
"Interface to listen on", NULL},
|
||||
{"clock-id", 'c', 0, G_OPTION_ARG_INT64, &clock_id,
|
||||
"PTP clock id", NULL},
|
||||
{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
|
||||
"Be verbose", NULL},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
static GSocketAddress *event_saddr, *general_saddr;
|
||||
static GSocket *socket_event, *socket_general;
|
||||
static GIOChannel *stdin_channel, *stdout_channel;
|
||||
|
||||
static gboolean
|
||||
have_socket_data_cb (GSocket * socket, GIOCondition condition,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar buffer[8192];
|
||||
gssize read;
|
||||
gsize written;
|
||||
GError *err = NULL;
|
||||
GIOStatus status;
|
||||
StdIOHeader header = { 0, };
|
||||
|
||||
read = g_socket_receive (socket, buffer, sizeof (buffer), NULL, &err);
|
||||
if (read == -1)
|
||||
g_error ("Failed to read from socket: %s", err->message);
|
||||
|
||||
if (verbose)
|
||||
g_message ("Received %" G_GSSIZE_FORMAT " bytes from %s socket", read,
|
||||
(socket == socket_event ? "event" : "general"));
|
||||
|
||||
header.size = read;
|
||||
header.type = (socket == socket_event) ? TYPE_EVENT : TYPE_GENERAL;
|
||||
|
||||
status =
|
||||
g_io_channel_write_chars (stdout_channel, (gchar *) & header,
|
||||
sizeof (header), &written, &err);
|
||||
if (status == G_IO_STATUS_ERROR) {
|
||||
g_error ("Failed to write to stdout: %s", err->message);
|
||||
} else if (status == G_IO_STATUS_EOF) {
|
||||
g_message ("EOF on stdout");
|
||||
exit (0);
|
||||
} else if (status != G_IO_STATUS_NORMAL) {
|
||||
g_error ("Unexpected stdout write status: %d", status);
|
||||
} else if (written != sizeof (header)) {
|
||||
g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written);
|
||||
}
|
||||
|
||||
status =
|
||||
g_io_channel_write_chars (stdout_channel, buffer, read, &written, &err);
|
||||
if (status == G_IO_STATUS_ERROR) {
|
||||
g_error ("Failed to write to stdout: %s", err->message);
|
||||
} else if (status == G_IO_STATUS_EOF) {
|
||||
g_message ("EOF on stdout");
|
||||
exit (0);
|
||||
} else if (status != G_IO_STATUS_NORMAL) {
|
||||
g_error ("Unexpected stdout write status: %d", status);
|
||||
} else if (written != read) {
|
||||
g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written);
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
have_stdin_data_cb (GIOChannel * channel, GIOCondition condition,
|
||||
gpointer user_data)
|
||||
{
|
||||
GIOStatus status;
|
||||
StdIOHeader header = { 0, };
|
||||
gchar buffer[8192];
|
||||
GError *err = NULL;
|
||||
gsize read;
|
||||
gssize written;
|
||||
|
||||
if ((condition & G_IO_STATUS_EOF)) {
|
||||
g_message ("EOF on stdin");
|
||||
exit (0);
|
||||
}
|
||||
|
||||
status =
|
||||
g_io_channel_read_chars (channel, (gchar *) & header, sizeof (header),
|
||||
&read, &err);
|
||||
if (status == G_IO_STATUS_ERROR) {
|
||||
g_error ("Failed to read from stdin: %s", err->message);
|
||||
} else if (status == G_IO_STATUS_EOF) {
|
||||
g_message ("EOF on stdin");
|
||||
exit (0);
|
||||
} else if (status != G_IO_STATUS_NORMAL) {
|
||||
g_error ("Unexpected stdin read status: %d", status);
|
||||
} else if (read != sizeof (header)) {
|
||||
g_error ("Unexpected read size: %" G_GSIZE_FORMAT, read);
|
||||
} else if (header.size > 8192) {
|
||||
g_error ("Unexpected size: %u", header.size);
|
||||
}
|
||||
|
||||
status = g_io_channel_read_chars (channel, buffer, header.size, &read, &err);
|
||||
if (status == G_IO_STATUS_ERROR) {
|
||||
g_error ("Failed to read from stdin: %s", err->message);
|
||||
} else if (status == G_IO_STATUS_EOF) {
|
||||
g_message ("EOF on stdin");
|
||||
exit (0);
|
||||
} else if (status != G_IO_STATUS_NORMAL) {
|
||||
g_error ("Unexpected stdin read status: %d", status);
|
||||
} else if (read != header.size) {
|
||||
g_error ("Unexpected read size: %" G_GSIZE_FORMAT, read);
|
||||
}
|
||||
|
||||
switch (header.type) {
|
||||
case TYPE_EVENT:
|
||||
case TYPE_GENERAL:
|
||||
written =
|
||||
g_socket_send_to (header.type ==
|
||||
TYPE_EVENT ? socket_event : socket_general,
|
||||
(header.type == TYPE_EVENT ? event_saddr : general_saddr), buffer,
|
||||
header.size, NULL, &err);
|
||||
if (written == -1)
|
||||
g_error ("Failed to write to socket: %s", err->message);
|
||||
else if (written != header.size)
|
||||
g_error ("Unexpected write size: %" G_GSSIZE_FORMAT, written);
|
||||
|
||||
if (verbose)
|
||||
g_message ("Sent %" G_GSSIZE_FORMAT " bytes to %s socket", read,
|
||||
(header.type == TYPE_EVENT ? "event" : "general"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
setup_sockets (void)
|
||||
{
|
||||
GInetAddress *bind_addr, *mcast_addr;
|
||||
GSocketAddress *bind_saddr;
|
||||
GSource *socket_event_source, *socket_general_source;
|
||||
gchar **probed_ifaces = NULL;
|
||||
GError *err = NULL;
|
||||
|
||||
/* Create sockets */
|
||||
socket_event =
|
||||
g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM,
|
||||
G_SOCKET_PROTOCOL_UDP, &err);
|
||||
if (!socket_event)
|
||||
g_error ("Couldn't create event socket: %s", err->message);
|
||||
|
||||
socket_general =
|
||||
g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM,
|
||||
G_SOCKET_PROTOCOL_UDP, &err);
|
||||
if (!socket_general)
|
||||
g_error ("Couldn't create general socket: %s", err->message);
|
||||
|
||||
/* Bind sockets */
|
||||
bind_addr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
|
||||
bind_saddr = g_inet_socket_address_new (bind_addr, PTP_EVENT_PORT);
|
||||
if (!g_socket_bind (socket_event, bind_saddr, TRUE, &err))
|
||||
g_error ("Couldn't bind event socket: %s", err->message);
|
||||
g_object_unref (bind_saddr);
|
||||
bind_saddr = g_inet_socket_address_new (bind_addr, PTP_GENERAL_PORT);
|
||||
if (!g_socket_bind (socket_general, bind_saddr, TRUE, &err))
|
||||
g_error ("Couldn't bind general socket: %s", err->message);
|
||||
g_object_unref (bind_saddr);
|
||||
g_object_unref (bind_addr);
|
||||
|
||||
/* Probe all non-loopback interfaces */
|
||||
if (!ifaces) {
|
||||
struct ifreq ifr;
|
||||
struct ifconf ifc;
|
||||
gchar buf[8192];
|
||||
|
||||
ifc.ifc_len = sizeof (buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl (g_socket_get_fd (socket_event), SIOCGIFCONF, &ifc) != -1) {
|
||||
struct ifreq *it = ifc.ifc_req;
|
||||
const struct ifreq *const end =
|
||||
it + (ifc.ifc_len / sizeof (struct ifreq));
|
||||
guint idx = 0;
|
||||
|
||||
probed_ifaces = g_new0 (gchar *, ifc.ifc_len + 1);
|
||||
|
||||
for (; it != end; ++it) {
|
||||
strcpy (ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl (g_socket_get_fd (socket_event), SIOCGIFFLAGS, &ifr) == 0) {
|
||||
if ((ifr.ifr_flags & IFF_LOOPBACK))
|
||||
continue;
|
||||
probed_ifaces[idx] = g_strdup (it->ifr_name);
|
||||
idx++;
|
||||
} else {
|
||||
g_warning ("can't get flags of interface '%s'", it->ifr_name);
|
||||
probed_ifaces[idx] = g_strdup (it->ifr_name);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx != 0)
|
||||
ifaces = probed_ifaces;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get a clock id from the MAC address if none was given */
|
||||
if (clock_id == (guint64) - 1) {
|
||||
struct ifreq ifr;
|
||||
gboolean success = FALSE;
|
||||
|
||||
if (ifaces) {
|
||||
gchar **ptr = ifaces;
|
||||
|
||||
while (*ptr) {
|
||||
strcpy (ifr.ifr_name, *ptr);
|
||||
if (ioctl (g_socket_get_fd (socket_event), SIOCGIFHWADDR, &ifr) == 0) {
|
||||
clock_id_array[0] = ifr.ifr_hwaddr.sa_data[0];
|
||||
clock_id_array[1] = ifr.ifr_hwaddr.sa_data[1];
|
||||
clock_id_array[2] = ifr.ifr_hwaddr.sa_data[2];
|
||||
clock_id_array[3] = 0xff;
|
||||
clock_id_array[4] = 0xfe;
|
||||
clock_id_array[5] = ifr.ifr_hwaddr.sa_data[3];
|
||||
clock_id_array[6] = ifr.ifr_hwaddr.sa_data[4];
|
||||
clock_id_array[7] = ifr.ifr_hwaddr.sa_data[5];
|
||||
success = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ptr++;
|
||||
} else {
|
||||
struct ifconf ifc;
|
||||
gchar buf[8192];
|
||||
|
||||
ifc.ifc_len = sizeof (buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl (g_socket_get_fd (socket_event), SIOCGIFCONF, &ifc) != -1) {
|
||||
struct ifreq *it = ifc.ifc_req;
|
||||
const struct ifreq *const end =
|
||||
it + (ifc.ifc_len / sizeof (struct ifreq));
|
||||
|
||||
for (; it != end; ++it) {
|
||||
strcpy (ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl (g_socket_get_fd (socket_event), SIOCGIFFLAGS, &ifr) == 0) {
|
||||
if ((ifr.ifr_flags & IFF_LOOPBACK))
|
||||
continue;
|
||||
|
||||
if (ioctl (g_socket_get_fd (socket_event), SIOCGIFHWADDR,
|
||||
&ifr) == 0) {
|
||||
clock_id_array[0] = ifr.ifr_hwaddr.sa_data[0];
|
||||
clock_id_array[1] = ifr.ifr_hwaddr.sa_data[1];
|
||||
clock_id_array[2] = ifr.ifr_hwaddr.sa_data[2];
|
||||
clock_id_array[3] = 0xff;
|
||||
clock_id_array[4] = 0xfe;
|
||||
clock_id_array[5] = ifr.ifr_hwaddr.sa_data[3];
|
||||
clock_id_array[6] = ifr.ifr_hwaddr.sa_data[4];
|
||||
clock_id_array[7] = ifr.ifr_hwaddr.sa_data[5];
|
||||
success = TRUE;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
g_warning ("can't get flags of interface '%s'", it->ifr_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
g_warning ("can't get any MAC address, using random clock id");
|
||||
clock_id = (((guint64) g_random_int ()) << 32) | (g_random_int ());
|
||||
GST_WRITE_UINT64_BE (clock_id_array, clock_id);
|
||||
clock_id_array[3] = 0xff;
|
||||
clock_id_array[4] = 0xfe;
|
||||
}
|
||||
} else {
|
||||
GST_WRITE_UINT64_BE (clock_id_array, clock_id);
|
||||
}
|
||||
|
||||
/* Join multicast groups */
|
||||
mcast_addr = g_inet_address_new_from_string (PTP_MULTICAST_GROUP);
|
||||
if (ifaces) {
|
||||
gchar **ptr = ifaces;
|
||||
gboolean success = FALSE;
|
||||
|
||||
while (*ptr) {
|
||||
gint c = 0;
|
||||
if (!g_socket_join_multicast_group (socket_event, mcast_addr, FALSE, *ptr,
|
||||
&err))
|
||||
g_warning ("Couldn't join multicast group on interface '%s': %s",
|
||||
*ptr, err->message);
|
||||
else
|
||||
c++;
|
||||
|
||||
if (!g_socket_join_multicast_group (socket_general, mcast_addr, FALSE,
|
||||
*ptr, &err))
|
||||
g_warning ("Couldn't join multicast group on interface '%s': %s",
|
||||
*ptr, err->message);
|
||||
else
|
||||
c++;
|
||||
|
||||
if (c == 2)
|
||||
success = TRUE;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
/* Join multicast group without any interface */
|
||||
if (!g_socket_join_multicast_group (socket_event, mcast_addr, FALSE, NULL,
|
||||
&err))
|
||||
g_error ("Couldn't join multicast group: %s", err->message);
|
||||
if (!g_socket_join_multicast_group (socket_general, mcast_addr, FALSE,
|
||||
NULL, &err))
|
||||
g_error ("Couldn't join multicast group: %s", err->message);
|
||||
}
|
||||
} else {
|
||||
/* Join multicast group without any interface */
|
||||
if (!g_socket_join_multicast_group (socket_event, mcast_addr, FALSE, NULL,
|
||||
&err))
|
||||
g_error ("Couldn't join multicast group: %s", err->message);
|
||||
if (!g_socket_join_multicast_group (socket_general, mcast_addr, FALSE, NULL,
|
||||
&err))
|
||||
g_error ("Couldn't join multicast group: %s", err->message);
|
||||
}
|
||||
|
||||
event_saddr = g_inet_socket_address_new (mcast_addr, PTP_EVENT_PORT);
|
||||
general_saddr = g_inet_socket_address_new (mcast_addr, PTP_GENERAL_PORT);
|
||||
|
||||
/* Create socket sources */
|
||||
socket_event_source =
|
||||
g_socket_create_source (socket_event, G_IO_IN | G_IO_PRI, NULL);
|
||||
g_source_set_priority (socket_event_source, G_PRIORITY_HIGH);
|
||||
g_source_set_callback (socket_event_source, (GSourceFunc) have_socket_data_cb,
|
||||
NULL, NULL);
|
||||
g_source_attach (socket_event_source, NULL);
|
||||
socket_general_source =
|
||||
g_socket_create_source (socket_general, G_IO_IN | G_IO_PRI, NULL);
|
||||
g_source_set_priority (socket_general_source, G_PRIORITY_DEFAULT);
|
||||
g_source_set_callback (socket_general_source,
|
||||
(GSourceFunc) have_socket_data_cb, NULL, NULL);
|
||||
g_source_attach (socket_general_source, NULL);
|
||||
|
||||
g_strfreev (probed_ifaces);
|
||||
}
|
||||
|
||||
static void
|
||||
drop_privileges (void)
|
||||
{
|
||||
#ifdef HAVE_PTP_HELPER_SETUID
|
||||
/* Switch to the given user/group */
|
||||
#ifdef HAVE_PTP_HELPER_SETUID_GROUP
|
||||
{
|
||||
struct group *grp;
|
||||
|
||||
grp = getgrnam (HAVE_PTP_HELPER_SETUID_GROUP);
|
||||
if (!grp)
|
||||
g_error ("Failed to get group information '%s': %s",
|
||||
HAVE_PTP_HELPER_SETUID_GROUP, g_strerror (errno));
|
||||
|
||||
if (setgid (grp->gr_gid) != 0)
|
||||
g_error ("Failed to change to group '%s': %s",
|
||||
HAVE_PTP_HELPER_SETUID_GROUP, g_strerror (errno));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PTP_HELPER_SETUID_USER
|
||||
{
|
||||
struct passwd *pwd;
|
||||
|
||||
pwd = getpwnam (HAVE_PTP_HELPER_SETUID_USER);
|
||||
if (!pwd)
|
||||
g_error ("Failed to get user information '%s': %s",
|
||||
HAVE_PTP_HELPER_SETUID_USER, g_strerror (errno));
|
||||
|
||||
#ifndef HAVE_PTP_HELPER_SETUID_GROUP
|
||||
if (setgid (pwd->pw_gid) != 0)
|
||||
g_error ("Failed to change to user group '%s': %s",
|
||||
HAVE_PTP_HELPER_SETUID_USER, g_strerror (errno));
|
||||
#endif
|
||||
|
||||
if (setuid (pwd->pw_uid) != 0)
|
||||
g_error ("Failed to change to user '%s': %s", HAVE_PTP_HELPER_SETUID_USER,
|
||||
g_strerror (errno));
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#ifdef HAVE_PTP_HELPER_CAPABILITIES
|
||||
/* Drop all capabilities */
|
||||
{
|
||||
cap_t caps;
|
||||
|
||||
caps = cap_get_proc ();
|
||||
if (caps == 0)
|
||||
g_error ("Failed to get process caps: %s", g_strerror (errno));
|
||||
if (cap_clear (caps) != 0)
|
||||
g_error ("Failed to clear caps: %s", g_strerror (errno));
|
||||
if (cap_set_proc (caps) != 0)
|
||||
g_error ("Failed to set process caps: %s", g_strerror (errno));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
setup_stdio_channels (void)
|
||||
{
|
||||
GSource *stdin_source;
|
||||
|
||||
/* Create stdin source */
|
||||
stdin_channel = g_io_channel_unix_new (STDIN_FILENO);
|
||||
if (g_io_channel_set_encoding (stdin_channel, NULL,
|
||||
NULL) == G_IO_STATUS_ERROR)
|
||||
g_error ("Failed to set stdin to binary encoding");
|
||||
g_io_channel_set_buffered (stdin_channel, FALSE);
|
||||
stdin_source =
|
||||
g_io_create_watch (stdin_channel, G_IO_IN | G_IO_PRI | G_IO_HUP);
|
||||
g_source_set_priority (stdin_source, G_PRIORITY_DEFAULT);
|
||||
g_source_set_callback (stdin_source, (GSourceFunc) have_stdin_data_cb, NULL,
|
||||
NULL);
|
||||
g_source_attach (stdin_source, NULL);
|
||||
|
||||
/* Create stdout channel */
|
||||
stdout_channel = g_io_channel_unix_new (STDOUT_FILENO);
|
||||
if (g_io_channel_set_encoding (stdout_channel, NULL,
|
||||
NULL) == G_IO_STATUS_ERROR)
|
||||
g_error ("Failed to set stdout to binary encoding");
|
||||
g_io_channel_set_buffered (stdout_channel, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
write_clock_id (void)
|
||||
{
|
||||
GError *err = NULL;
|
||||
GIOStatus status;
|
||||
StdIOHeader header = { 0, };
|
||||
gsize written;
|
||||
|
||||
/* Write clock id to stdout */
|
||||
|
||||
header.type = TYPE_CLOCK_ID;
|
||||
header.size = 8;
|
||||
status =
|
||||
g_io_channel_write_chars (stdout_channel, (gchar *) & header,
|
||||
sizeof (header), &written, &err);
|
||||
if (status == G_IO_STATUS_ERROR) {
|
||||
g_error ("Failed to write to stdout: %s", err->message);
|
||||
} else if (status == G_IO_STATUS_EOF) {
|
||||
g_message ("EOF on stdout");
|
||||
exit (0);
|
||||
} else if (status != G_IO_STATUS_NORMAL) {
|
||||
g_error ("Unexpected stdout write status: %d", status);
|
||||
} else if (written != sizeof (header)) {
|
||||
g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written);
|
||||
}
|
||||
|
||||
status =
|
||||
g_io_channel_write_chars (stdout_channel,
|
||||
(const gchar *) clock_id_array, sizeof (clock_id_array), &written, &err);
|
||||
if (status == G_IO_STATUS_ERROR) {
|
||||
g_error ("Failed to write to stdout: %s", err->message);
|
||||
} else if (status == G_IO_STATUS_EOF) {
|
||||
g_message ("EOF on stdout");
|
||||
exit (0);
|
||||
} else if (status != G_IO_STATUS_NORMAL) {
|
||||
g_error ("Unexpected stdout write status: %d", status);
|
||||
} else if (written != sizeof (clock_id_array)) {
|
||||
g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written);
|
||||
}
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar ** argv)
|
||||
{
|
||||
GOptionContext *opt_ctx;
|
||||
GMainLoop *loop;
|
||||
GError *err = NULL;
|
||||
|
||||
opt_ctx = g_option_context_new ("- GStreamer PTP helper process");
|
||||
g_option_context_add_main_entries (opt_ctx, opt_entries, NULL);
|
||||
if (!g_option_context_parse (opt_ctx, &argc, &argv, &err))
|
||||
g_error ("Error parsing options: %s", err->message);
|
||||
g_option_context_free (opt_ctx);
|
||||
|
||||
setup_sockets ();
|
||||
drop_privileges ();
|
||||
setup_stdio_channels ();
|
||||
write_clock_id ();
|
||||
|
||||
/* Get running */
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
g_main_loop_run (loop);
|
||||
|
||||
/* We never exit cleanly, so don't do cleanup */
|
||||
g_assert_not_reached ();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -8,19 +8,36 @@ libgstnet_@GST_API_VERSION@_include_HEADERS = \
|
|||
gstnetclientclock.h \
|
||||
gstnetcontrolmessagemeta.h \
|
||||
gstnettimepacket.h \
|
||||
gstnettimeprovider.h
|
||||
gstnettimeprovider.h \
|
||||
gstptpclock.h
|
||||
|
||||
libgstnet_@GST_API_VERSION@_la_SOURCES = \
|
||||
gstnetaddressmeta.c \
|
||||
gstnetclientclock.c \
|
||||
gstnetcontrolmessagemeta.c \
|
||||
gstnettimepacket.c \
|
||||
gstnettimeprovider.c
|
||||
gstnettimeprovider.c \
|
||||
gstptpclock.c
|
||||
|
||||
noinst_HEADERS = gstptp_private.h
|
||||
|
||||
libgstnet_@GST_API_VERSION@_la_CFLAGS = $(GST_OBJ_CFLAGS) $(GIO_CFLAGS)
|
||||
libgstnet_@GST_API_VERSION@_la_LIBADD = $(GST_OBJ_LIBS) $(GIO_LIBS)
|
||||
libgstnet_@GST_API_VERSION@_la_LIBADD = $(GST_OBJ_LIBS) $(GIO_LIBS) \
|
||||
$(top_builddir)/libs/gst/base/libgstbase-@GST_API_VERSION@.la
|
||||
libgstnet_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS)
|
||||
|
||||
# try to prevent packaging errors
|
||||
check-libexecdir-consistency:
|
||||
@if test "${GST_PTP_HELPER_INSTALLED}" != "${libexecdir}/gstreamer-$(GST_API_VERSION)/gst-ptp-helper"; then \
|
||||
echo "*** Inconsistent libexecdir! Please use ./configure --libexecdir=/foo/bar"; \
|
||||
echo "*** to set the libexecdir and not make libexecdir=/foo/bar or the like."; \
|
||||
echo "*** The same goes for prefix, libdir etc."; \
|
||||
echo "*** ${GST_PTP_HELPER_INSTALLED} != ${libexecdir}/gstreamer-$(GST_API_VERSION)/gst-ptp-helper"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
all-local: check-libexecdir-consistency
|
||||
|
||||
CLEANFILES = *.gcno *.gcda *.gcov
|
||||
|
||||
%.c.gcov: .libs/libgstnet_@GST_API_VERSION@_la-%.gcda %.c
|
||||
|
|
19
libs/gst/net/gstptp_private.h
Normal file
19
libs/gst/net/gstptp_private.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef __GST_PTP_PRIVATE_H__
|
||||
#define __GST_PTP_PRIVATE_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
enum
|
||||
{
|
||||
TYPE_EVENT,
|
||||
TYPE_GENERAL,
|
||||
TYPE_CLOCK_ID
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint16 size;
|
||||
guint8 type;
|
||||
} StdIOHeader;
|
||||
|
||||
#endif /* __GST_PTP_PRIVATE_H__ */
|
2431
libs/gst/net/gstptpclock.c
Normal file
2431
libs/gst/net/gstptpclock.c
Normal file
File diff suppressed because it is too large
Load diff
142
libs/gst/net/gstptpclock.h
Normal file
142
libs/gst/net/gstptpclock.h
Normal file
|
@ -0,0 +1,142 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2015 Sebastian Dröge <sebastian@centricular.com>
|
||||
*
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_PTP_CLOCK_H__
|
||||
#define __GST_PTP_CLOCK_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gstsystemclock.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_PTP_CLOCK \
|
||||
(gst_ptp_clock_get_type())
|
||||
#define GST_PTP_CLOCK(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PTP_CLOCK,GstPtpClock))
|
||||
#define GST_PTP_CLOCK_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PTP_CLOCK,GstPtpClockClass))
|
||||
#define GST_IS_PTP_CLOCK(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PTP_CLOCK))
|
||||
#define GST_IS_PTP_CLOCK_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PTP_CLOCK))
|
||||
|
||||
typedef struct _GstPtpClock GstPtpClock;
|
||||
typedef struct _GstPtpClockClass GstPtpClockClass;
|
||||
typedef struct _GstPtpClockPrivate GstPtpClockPrivate;
|
||||
|
||||
/**
|
||||
* GstPtpClock:
|
||||
*
|
||||
* Opaque #GstPtpClock structure.
|
||||
*/
|
||||
struct _GstPtpClock {
|
||||
GstSystemClock clock;
|
||||
|
||||
/*< private >*/
|
||||
GstPtpClockPrivate *priv;
|
||||
|
||||
gpointer _gst_reserved[GST_PADDING];
|
||||
};
|
||||
|
||||
struct _GstPtpClockClass {
|
||||
GstSystemClockClass parent_class;
|
||||
|
||||
/*< private >*/
|
||||
gpointer _gst_reserved[GST_PADDING];
|
||||
};
|
||||
|
||||
/**
|
||||
* GST_PTP_CLOCK_ID_NONE:
|
||||
* PTP clock identification that can be passed to gst_ptp_init() to
|
||||
* automatically select one based on the MAC address of interfaces
|
||||
*/
|
||||
#define GST_PTP_CLOCK_ID_NONE ((guint64) -1)
|
||||
|
||||
GType gst_ptp_clock_get_type (void);
|
||||
|
||||
gboolean gst_ptp_is_supported (void);
|
||||
gboolean gst_ptp_is_initialized (void);
|
||||
gboolean gst_ptp_init (guint64 clock_id,
|
||||
gchar ** interfaces);
|
||||
void gst_ptp_deinit (void);
|
||||
|
||||
#define GST_PTP_STATISTICS_NEW_DOMAIN_FOUND "GstPtpStatisticsNewDomainFound"
|
||||
#define GST_PTP_STATISTICS_BEST_MASTER_CLOCK_SELECTED "GstPtpStatisticsBestMasterClockSelected"
|
||||
#define GST_PTP_STATISTICS_PATH_DELAY_MEASURED "GstPtpStatisticsPathDelayMeasured"
|
||||
#define GST_PTP_STATISTICS_TIME_UPDATED "GstPtpStatisticsTimeUpdated"
|
||||
|
||||
/**
|
||||
* GstPtpStatisticsCallback:
|
||||
* @domain: PTP domain identifier
|
||||
* @stats: New statistics
|
||||
* @user_data: Data passed to gst_ptp_statistics_callback_add()
|
||||
*
|
||||
* The statistics can be the following structures:
|
||||
*
|
||||
* GST_PTP_STATISTICS_NEW_DOMAIN_FOUND:
|
||||
* "domain" G_TYPE_UINT The domain identifier of the domain
|
||||
* "clock" GST_TYPE_CLOCK The internal clock that is slaved to the
|
||||
* PTP domain
|
||||
*
|
||||
* GST_PTP_STATISTICS_BEST_MASTER_CLOCK_SELECTED:
|
||||
* "domain" G_TYPE_UINT The domain identifier of the domain
|
||||
* "master-clock-id" G_TYPE_UINT64 PTP clock identifier of the selected master
|
||||
* clock
|
||||
* "master-clock-port" G_TYPE_UINT PTP port number of the selected master clock
|
||||
* "grandmaster-clock-id" G_TYPE_UINT64 PTP clock identifier of the grandmaster clock
|
||||
*
|
||||
* GST_PTP_STATISTICS_PATH_DELAY_MEASURED:
|
||||
* "domain" G_TYPE_UINT The domain identifier of the domain
|
||||
* "mean-path-delay-avg" GST_TYPE_CLOCK_TIME Average mean path delay
|
||||
* "mean-path-delay" GST_TYPE_CLOCK_TIME Latest mean path delay
|
||||
* "delay-request-delay" GST_TYPE_CLOCK_TIME Delay of DELAY_REQ / DELAY_RESP messages
|
||||
*
|
||||
* GST_PTP_STATISTICS_TIME_UPDATED:
|
||||
* "domain" G_TYPE_UINT The domain identifier of the domain
|
||||
* "mean-path-delay-avg" GST_TYPE_CLOCK_TIME Average mean path delay
|
||||
* "local-time" GST_TYPE_CLOCK_TIME Local time that corresponds to ptp-time
|
||||
* "ptp-time" GST_TYPE_CLOCK_TIME Newly measured PTP time at local-time
|
||||
* "estimated-ptp-time" GST_TYPE_CLOCK_TIME Estimated PTP time based on previous measurements
|
||||
* "discontinuity" G_TYPE_INT64 Difference between estimated and measured PTP time
|
||||
* "synced" G_TYPE_BOOLEAN Currently synced to the remote clock
|
||||
* "r-squared" G_TYPE_DOUBLE R² of clock estimation regression
|
||||
* "internal-time" GST_TYPE_CLOCK_TIME Internal time clock parameter
|
||||
* "external-time" GST_TYPE_CLOCK_TIME External time clock parameter
|
||||
* "rate-num" G_TYPE_UINT64 Internal/external rate numerator
|
||||
* "rate-den" G_TYPE_UINT64 Internal/external rate denominator
|
||||
* "rate" G_TYPE_DOUBLE Internal/external rate
|
||||
*
|
||||
* If %FALSE is returned, the callback is removed and never called again.
|
||||
*
|
||||
*/
|
||||
typedef gboolean (*GstPtpStatisticsCallback) (guint8 domain,
|
||||
const GstStructure * stats,
|
||||
gpointer user_data);
|
||||
gulong gst_ptp_statistics_callback_add (GstPtpStatisticsCallback callback,
|
||||
gpointer user_data, GDestroyNotify destroy_data);
|
||||
void gst_ptp_statistics_callback_remove (gulong id);
|
||||
|
||||
GstClock* gst_ptp_clock_new (const gchar *name,
|
||||
guint domain);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_PTP_CLOCK_H__ */
|
||||
|
|
@ -27,5 +27,6 @@
|
|||
#include <gst/net/gstnetclientclock.h>
|
||||
#include <gst/net/gstnettimepacket.h>
|
||||
#include <gst/net/gstnettimeprovider.h>
|
||||
#include <gst/net/gstptpclock.h>
|
||||
|
||||
#endif /* __GST_NET__H__ */
|
||||
|
|
|
@ -11,6 +11,7 @@ always_dirs = \
|
|||
manual \
|
||||
memory \
|
||||
netclock \
|
||||
ptp \
|
||||
stepping \
|
||||
streamiddemux \
|
||||
streams
|
||||
|
|
1
tests/examples/ptp/.gitignore
vendored
Normal file
1
tests/examples/ptp/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
ptp-print-times
|
7
tests/examples/ptp/Makefile.am
Normal file
7
tests/examples/ptp/Makefile.am
Normal file
|
@ -0,0 +1,7 @@
|
|||
noinst_PROGRAMS = ptp-print-times
|
||||
|
||||
ptp_print_times_LDADD = \
|
||||
$(top_builddir)/libs/gst/net/libgstnet-@GST_API_VERSION@.la \
|
||||
$(GST_OBJ_LIBS)
|
||||
ptp_print_times_CFLAGS = $(GST_OBJ_CFLAGS)
|
||||
|
100
tests/examples/ptp/ptp-print-times.c
Normal file
100
tests/examples/ptp/ptp-print-times.c
Normal file
|
@ -0,0 +1,100 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2015 Sebastian Dröge <sebastian@centricular.com>
|
||||
*
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/* Create a PTP client clock and print times and statistics.
|
||||
*
|
||||
* When running this from a GStreamer build tree, you will have to set
|
||||
* GST_PTP_HELPER to libs/gst/helpers/.libs/gst-ptp-helper and also
|
||||
* make sure that it has the right permissions (setuid root or appropriate
|
||||
* capabilities
|
||||
*
|
||||
* You can test this with any PTP compatible clock, e.g. ptpd from here: http://ptpd.sourceforge.net/
|
||||
*
|
||||
* For testing the accuracy, you can use the PTP reflector available from
|
||||
* http://code.centricular.com/ptp-clock-reflector/ or here
|
||||
* https://github.com/sdroege/ptp-clock-reflector
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/net/net.h>
|
||||
|
||||
static gint domain = 0;
|
||||
static gboolean stats = FALSE;
|
||||
|
||||
static GOptionEntry opt_entries[] = {
|
||||
{"domain", 'd', 0, G_OPTION_ARG_INT, &domain,
|
||||
"PTP domain", NULL},
|
||||
{"stats", 's', 0, G_OPTION_ARG_NONE, &stats,
|
||||
"Print PTP statistics", NULL},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
static gboolean
|
||||
stats_cb (guint8 d, const GstStructure * stats, gpointer user_data)
|
||||
{
|
||||
if (d == domain) {
|
||||
gchar *stats_str = gst_structure_to_string (stats);
|
||||
g_print ("Got stats: %s\n", stats_str);
|
||||
g_free (stats_str);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar ** argv)
|
||||
{
|
||||
GOptionContext *opt_ctx;
|
||||
GstClock *clock;
|
||||
GError *err = NULL;
|
||||
|
||||
opt_ctx = g_option_context_new ("- GStreamer PTP clock test app");
|
||||
g_option_context_add_main_entries (opt_ctx, opt_entries, NULL);
|
||||
g_option_context_add_group (opt_ctx, gst_init_get_option_group ());
|
||||
if (!g_option_context_parse (opt_ctx, &argc, &argv, &err))
|
||||
g_error ("Error parsing options: %s", err->message);
|
||||
g_option_context_free (opt_ctx);
|
||||
|
||||
if (!gst_ptp_init (GST_PTP_CLOCK_ID_NONE, NULL))
|
||||
g_error ("failed to init ptp");
|
||||
|
||||
if (stats)
|
||||
gst_ptp_statistics_callback_add (stats_cb, NULL, NULL);
|
||||
|
||||
clock = gst_ptp_clock_new ("test-clock", domain);
|
||||
|
||||
gst_clock_wait_for_sync (GST_CLOCK (clock), GST_CLOCK_TIME_NONE);
|
||||
|
||||
while (TRUE) {
|
||||
GstClockTime local, remote;
|
||||
GstClockTimeDiff diff;
|
||||
|
||||
local = g_get_real_time () * 1000;
|
||||
remote = gst_clock_get_time (clock);
|
||||
diff = GST_CLOCK_DIFF (local, remote);
|
||||
|
||||
g_print ("local: %" GST_TIME_FORMAT " ptp: %" GST_TIME_FORMAT " diff: %s%"
|
||||
GST_TIME_FORMAT "\n", GST_TIME_ARGS (local), GST_TIME_ARGS (remote),
|
||||
(diff < 0 ? "-" : " "), GST_TIME_ARGS (ABS (diff)));
|
||||
g_usleep (100000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -16,3 +16,11 @@ EXPORTS
|
|||
gst_net_time_packet_serialize
|
||||
gst_net_time_provider_get_type
|
||||
gst_net_time_provider_new
|
||||
gst_ptp_clock_get_type
|
||||
gst_ptp_clock_new
|
||||
gst_ptp_deinit
|
||||
gst_ptp_init
|
||||
gst_ptp_is_initialized
|
||||
gst_ptp_is_supported
|
||||
gst_ptp_statistics_callback_add
|
||||
gst_ptp_statistics_callback_remove
|
||||
|
|
Loading…
Reference in a new issue