diff --git a/ext/avtp/gstavtp.c b/ext/avtp/gstavtp.c index 26d1d87139..c94b608c18 100644 --- a/ext/avtp/gstavtp.c +++ b/ext/avtp/gstavtp.c @@ -43,23 +43,46 @@ * * AVTP mime type is pretty simple and has no fields. * - * ### PTP Clock + * ### gPTP Setup * - * The AVTP plugin elements require that GStreamer pipeline clock be in sync - * with the network generalized PTP clock (gPTP). Applications using the AVTP - * plugin elements can achieve that by using GstPtpClock as the pipeline clock. + * The Linuxptp project provides the ptp4l daemon, which synchronizes the PTP + * clock from NIC, and the pmc tool which communicates with ptp4l to get/set + * some runtime settings. The project also provides the phc2sys daemon which + * synchronizes the PTP clock and system clock. * - * Note that GstPtpClock is a UDP slave only clock, meaning that some other - * endpoint needs to provide the gPTP master clock. + * The AVTP plugin requires system clock is synchronized with PTP clock and + * TAI offset is properly set in the kernel. ptp4l and phc2sys can be set up + * in many different ways, below we provide an example that fullfils the plugin + * requirements. For further information check ptp4l(8) and phc2sys(8). * - * One can use, on another endpoint on the network, Linuxptp project ptp4l - * daemon to provide a gPTP master clock on the network over UDP: + * In the following instructions, replace $IFNAME by your PTP capable NIC + * interface. The gPTP.cfg file mentioned below can be found in /usr/share/ + * doc/linuxptp/ (depending on your distro). * - * $ ptp4l -i $IFNAME + * Synchronize PTP clock with PTP time: * - * For further information check ptp4l(8). + * $ ptp4l -f gPTP.cfg -i $IFNAME * - * ### FQTSS Setup + * Enable TAI offset to be automatically set by phc2sys: + * + * $ pmc -u -t 1 -b 0 'SET GRANDMASTER_SETTINGS_NP \ + * clockClass 248 clockAccuracy 0xfe \ + * offsetScaledLogVariance 0xffff \ + * currentUtcOffset 37 leap61 0 leap59 0 \ + * currentUtcOffsetValid 1 ptpTimescale 1 \ + * timeTraceable 1 frequencyTraceable 0 timeSource 0xa0' + * + * Synchronize system clock with PTP clock: + * + * $ phc2sys -f gPTP.cfg -s $IFNAME -c CLOCK_REALTIME -w + * + * The commands above should be run on both AVTP Talker and Listener hosts. + * + * With clocks properly synchronized, applications using the AVTP plugin + * should use GstSytemClock with GST_CLOCK_TYPE_REALTIME as the pipeline + * clock. + * + * ### Traffic Control Setup * * FQTSS (Forwarding and Queuing Enhancements for Time-Sensitive Streams) can be * enabled on Linux with the help of the mqprio and cbs qdiscs provided by the @@ -71,18 +94,27 @@ * On the host that will run as AVTP Talker (pipeline that generates the video * stream), run the following commands: * - * Configure mpqrio qdisc (replace $HANDLE_ID by an unused handle ID): + * Configure mpqrio qdisc (replace $MQPRIO_HANDLE_ID by an unused handle ID): * - * $ tc qdisc add dev $IFNAME parent root handle $HANDLE_ID mqprio \ + * $ tc qdisc add dev $IFNAME parent root handle $MQPRIO_HANDLE_ID mqprio \ * num_tc 3 map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \ * queues 1@0 1@1 2@2 hw 0 * - * Configure cbs qdisc: + * Configure cbs qdisc (replace $CBS_HANDLE_ID by an unused handle ID): * - * $ tc qdisc replace dev $IFNAME parent $HANDLE_ID:1 cbs idleslope 27756 \ - * sendslope -972244 hicredit 42 locredit -1499 offload 1 + * $ tc qdisc replace dev $IFNAME parent $MQPRIO_HANDLE_ID:1 \ + * handle $CBS_HANDLE_ID cbs idleslope 27756 sendslope -972244 \ + * hicredit 42 locredit -1499 offload 1 * - * No FQTSS configuration is required at the host running as AVTP Listener. + * Also, the plugin implements a transmission scheduling mechanism that relies + * on ETF qdisc so make sure it is properly configured in your system. It could + * be configured in many ways, below follows an example. + * + * $ tc qdisc add dev $IFNAME parent $CBS_HANDLE_ID:1 etf \ + * clockid CLOCK_TAI delta 500000 offload + * + * No Traffic Control configuration is required at the host running as AVTP + * Listener. * * ### Capabilities * diff --git a/ext/avtp/gstavtpsink.c b/ext/avtp/gstavtpsink.c index 017d659b1f..5f469dd65c 100644 --- a/ext/avtp/gstavtpsink.c +++ b/ext/avtp/gstavtpsink.c @@ -45,6 +45,7 @@ #include #include +#include #include #include #include @@ -62,6 +63,10 @@ GST_DEBUG_CATEGORY_STATIC (avtpsink_debug); #define DEFAULT_ADDRESS "01:AA:AA:AA:AA:AA" #define DEFAULT_PRIORITY 0 +#define NSEC_PER_SEC 1000000000 +#define TAI_OFFSET (37ULL * NSEC_PER_SEC) +#define UTC_TO_TAI(t) (t + TAI_OFFSET) + enum { PROP_0, @@ -89,6 +94,8 @@ static gboolean gst_avtp_sink_start (GstBaseSink * basesink); static gboolean gst_avtp_sink_stop (GstBaseSink * basesink); static GstFlowReturn gst_avtp_sink_render (GstBaseSink * basesink, GstBuffer * buffer); +static void gst_avtp_sink_get_times (GstBaseSink * bsink, GstBuffer * buffer, + GstClockTime * start, GstClockTime * end); static void gst_avtp_sink_class_init (GstAvtpSinkClass * klass) @@ -127,6 +134,7 @@ gst_avtp_sink_class_init (GstAvtpSinkClass * klass) basesink_class->start = GST_DEBUG_FUNCPTR (gst_avtp_sink_start); basesink_class->stop = GST_DEBUG_FUNCPTR (gst_avtp_sink_stop); basesink_class->render = GST_DEBUG_FUNCPTR (gst_avtp_sink_render); + basesink_class->get_times = GST_DEBUG_FUNCPTR (gst_avtp_sink_get_times); GST_DEBUG_CATEGORY_INIT (avtpsink_debug, "avtpsink", 0, "AVTP Sink"); } @@ -211,6 +219,7 @@ gst_avtp_sink_init_socket (GstAvtpSink * avtpsink) unsigned int index; guint8 addr[ETH_ALEN]; struct sockaddr_ll sk_addr; + struct sock_txtime txtime_cfg; index = if_nametoindex (avtpsink->ifname); if (!index) { @@ -232,6 +241,16 @@ gst_avtp_sink_init_socket (GstAvtpSink * avtpsink) goto err; } + txtime_cfg.clockid = CLOCK_TAI; + txtime_cfg.flags = 0; + res = setsockopt (fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg, + sizeof (txtime_cfg)); + if (res < 0) { + GST_ERROR_OBJECT (avtpsink, "Failed to set SO_TXTIME: %s", strerror + (errno)); + goto err; + } + res = sscanf (avtpsink->address, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5]); if (res != 6) { @@ -257,6 +276,28 @@ err: return FALSE; } +static void +gst_avtp_sink_init_msghdr (GstAvtpSink * avtpsink) +{ + struct msghdr *msg; + struct cmsghdr *cmsg; + + msg = g_malloc0 (sizeof (struct msghdr)); + msg->msg_name = &avtpsink->sk_addr; + msg->msg_namelen = sizeof (avtpsink->sk_addr); + msg->msg_iovlen = 1; + msg->msg_iov = g_malloc0 (sizeof (struct iovec)); + msg->msg_controllen = CMSG_SPACE (sizeof (__u64)); + msg->msg_control = g_malloc0 (msg->msg_controllen); + + cmsg = CMSG_FIRSTHDR (msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_TXTIME; + cmsg->cmsg_len = CMSG_LEN (sizeof (__u64)); + + avtpsink->msg = msg; +} + static gboolean gst_avtp_sink_start (GstBaseSink * basesink) { @@ -265,6 +306,8 @@ gst_avtp_sink_start (GstBaseSink * basesink) if (!gst_avtp_sink_init_socket (avtpsink)) return FALSE; + gst_avtp_sink_init_msghdr (avtpsink); + GST_DEBUG_OBJECT (avtpsink, "AVTP sink started"); return TRUE; @@ -275,26 +318,81 @@ gst_avtp_sink_stop (GstBaseSink * basesink) { GstAvtpSink *avtpsink = GST_AVTP_SINK (basesink); + g_free (avtpsink->msg->msg_iov); + g_free (avtpsink->msg->msg_control); + g_free (avtpsink->msg); close (avtpsink->sk_fd); GST_DEBUG_OBJECT (avtpsink, "AVTP sink stopped"); return TRUE; } +/* This function was heavily inspired by gst_base_sink_adjust_time() from + * GstBaseSink. + */ +static GstClockTime +gst_avtp_sink_adjust_time (GstBaseSink * basesink, GstClockTime time) +{ + GstClockTimeDiff ts_offset; + GstClockTime render_delay; + + /* don't do anything funny with invalid timestamps */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time))) + return time; + + time += gst_base_sink_get_latency (basesink); + + /* apply offset, be careful for underflows */ + ts_offset = gst_base_sink_get_ts_offset (basesink); + if (ts_offset < 0) { + ts_offset = -ts_offset; + if (ts_offset < time) + time -= ts_offset; + else + time = 0; + } else + time += ts_offset; + + /* subtract the render delay again, which was included in the latency */ + render_delay = gst_base_sink_get_render_delay (basesink); + if (time > render_delay) + time -= render_delay; + else + time = 0; + + return time; +} + static GstFlowReturn gst_avtp_sink_render (GstBaseSink * basesink, GstBuffer * buffer) { ssize_t n; GstMapInfo info; GstAvtpSink *avtpsink = GST_AVTP_SINK (basesink); + struct iovec *iov = avtpsink->msg->msg_iov; + + if (G_LIKELY (basesink->sync)) { + GstClockTime base_time, running_time; + struct cmsghdr *cmsg = CMSG_FIRSTHDR (avtpsink->msg); + + g_assert (GST_BUFFER_DTS_OR_PTS (buffer) != GST_CLOCK_TIME_NONE); + + base_time = gst_element_get_base_time (GST_ELEMENT (avtpsink)); + running_time = gst_segment_to_running_time (&basesink->segment, + basesink->segment.format, GST_BUFFER_DTS_OR_PTS (buffer)); + running_time = gst_avtp_sink_adjust_time (basesink, running_time); + *(__u64 *) CMSG_DATA (cmsg) = UTC_TO_TAI (base_time + running_time); + } if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) { GST_ERROR_OBJECT (avtpsink, "Failed to map buffer"); return GST_FLOW_ERROR; } - n = sendto (avtpsink->sk_fd, info.data, info.size, 0, - (struct sockaddr *) &avtpsink->sk_addr, sizeof (avtpsink->sk_addr)); + iov->iov_base = info.data; + iov->iov_len = info.size; + + n = sendmsg (avtpsink->sk_fd, avtpsink->msg, 0); if (n < 0) { GST_INFO_OBJECT (avtpsink, "Failed to send AVTPDU: %s", strerror (errno)); goto out; @@ -309,6 +407,18 @@ out: return GST_FLOW_OK; } +static void +gst_avtp_sink_get_times (GstBaseSink * bsink, GstBuffer * buffer, + GstClockTime * start, GstClockTime * end) +{ + /* Rendering synchronization is handled by the GstAvtpSink class itself, not + * GstBaseSink so we set 'start' and 'end' to GST_CLOCK_TIME_NONE to signal + * that to the base class. + */ + *start = GST_CLOCK_TIME_NONE; + *end = GST_CLOCK_TIME_NONE; +} + gboolean gst_avtp_sink_plugin_init (GstPlugin * plugin) { diff --git a/ext/avtp/gstavtpsink.h b/ext/avtp/gstavtpsink.h index 20860ea7d9..327d92e576 100644 --- a/ext/avtp/gstavtpsink.h +++ b/ext/avtp/gstavtpsink.h @@ -51,6 +51,7 @@ struct _GstAvtpSink int sk_fd; struct sockaddr_ll sk_addr; + struct msghdr * msg; }; struct _GstAvtpSinkClass diff --git a/ext/avtp/meson.build b/ext/avtp/meson.build index f8a15c70b9..e0f2f7960f 100644 --- a/ext/avtp/meson.build +++ b/ext/avtp/meson.build @@ -12,7 +12,7 @@ avtp_sources = [ avtp_dep = dependency('avtp', required: get_option('avtp')) -if avtp_dep.found() +if avtp_dep.found() and cc.has_type('struct sock_txtime', prefix : '#include ') gstavtp = library('gstavtp', avtp_sources, c_args : gst_plugins_bad_args,