• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * GStreamer AVTP Plugin
3  * Copyright (C) 2019 Intel Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /**
22  * SECTION:element-avtpcrfcheck
23  * @see_also: avtpcrfsync
24  *
25  * Validate whether the presentation time for the AVTPDU aligns with the CRF
26  * stream. For detailed information see Chapter 10 of
27  * https://standards.ieee.org/standard/1722-2016.html.
28  *
29  * <refsect2>
30  * <title>Example pipeline</title>
31  * |[
32  * gst-launch-1.0 avtpsrc ! avtpcrfcheck ! avtpaafdepay ! autoaudiosink
33  * ]| This example pipeline will validate AVTP timestamps for AVTPDUs. Refer to
34  * the avtpcrfsync to adjust the AVTP timestamps for the packet.
35  * </refsect2>
36  */
37 
38 #include <avtp.h>
39 #include <avtp_crf.h>
40 #include <avtp_cvf.h>
41 #include <glib.h>
42 #include <math.h>
43 
44 #include "gstavtpcrfbase.h"
45 #include "gstavtpcrfcheck.h"
46 #include "gstavtpcrfutil.h"
47 
48 GST_DEBUG_CATEGORY_STATIC (avtpcrfcheck_debug);
49 #define GST_CAT_DEFAULT (avtpcrfcheck_debug)
50 
51 #define DEFAULT_DROP_INVALID FALSE
52 
53 enum
54 {
55   PROP_0,
56   PROP_DROP_INVALID,
57 };
58 
59 #define gst_avtp_crf_check_parent_class parent_class
60 G_DEFINE_TYPE (GstAvtpCrfCheck, gst_avtp_crf_check, GST_TYPE_AVTP_CRF_BASE);
61 GST_ELEMENT_REGISTER_DEFINE (avtpcrfcheck, "avtpcrfcheck", GST_RANK_NONE,
62     GST_TYPE_AVTP_CRF_CHECK);
63 
64 static void gst_avtp_crf_check_set_property (GObject * object, guint prop_id,
65     const GValue * value, GParamSpec * pspec);
66 static void gst_avtp_crf_check_get_property (GObject * object, guint prop_id,
67     GValue * value, GParamSpec * pspec);
68 static GstFlowReturn gst_avtp_crf_check_transform_ip (GstBaseTransform * parent,
69     GstBuffer * buffer);
70 
71 static void
gst_avtp_crf_check_class_init(GstAvtpCrfCheckClass * klass)72 gst_avtp_crf_check_class_init (GstAvtpCrfCheckClass * klass)
73 {
74   GObjectClass *object_class = G_OBJECT_CLASS (klass);
75   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
76 
77   gst_element_class_set_static_metadata (element_class,
78       "Clock Reference Format (CRF) Checker",
79       "Filter/Network/AVTP",
80       "Check if the AVTP presentation time is synchronized with clock provided by a CRF stream",
81       "Vedang Patel <vedang.patel@intel.com>");
82 
83   object_class->get_property =
84       GST_DEBUG_FUNCPTR (gst_avtp_crf_check_get_property);
85   object_class->set_property =
86       GST_DEBUG_FUNCPTR (gst_avtp_crf_check_set_property);
87   GST_BASE_TRANSFORM_CLASS (klass)->transform_ip =
88       GST_DEBUG_FUNCPTR (gst_avtp_crf_check_transform_ip);
89 
90   g_object_class_install_property (object_class, PROP_DROP_INVALID,
91       g_param_spec_boolean ("drop-invalid", "Drop invalid packets",
92           "Drop the packets which are not within 25%% of the sample period of the CRF timestamps",
93           DEFAULT_DROP_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
94 
95   GST_DEBUG_CATEGORY_INIT (avtpcrfcheck_debug, "avtpcrfcheck", 0,
96       "CRF Checker");
97 }
98 
99 static void
gst_avtp_crf_check_init(GstAvtpCrfCheck * avtpcrfcheck)100 gst_avtp_crf_check_init (GstAvtpCrfCheck * avtpcrfcheck)
101 {
102   avtpcrfcheck->drop_invalid = DEFAULT_DROP_INVALID;
103 }
104 
105 static void
gst_avtp_crf_check_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)106 gst_avtp_crf_check_set_property (GObject * object, guint prop_id,
107     const GValue * value, GParamSpec * pspec)
108 {
109   GstAvtpCrfCheck *avtpcrfcheck = GST_AVTP_CRF_CHECK (object);
110 
111   GST_DEBUG_OBJECT (avtpcrfcheck, "prop_id %u", prop_id);
112 
113   switch (prop_id) {
114     case PROP_DROP_INVALID:
115       avtpcrfcheck->drop_invalid = g_value_get_boolean (value);
116       break;
117     default:
118       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
119       break;
120   }
121 }
122 
123 static void
gst_avtp_crf_check_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)124 gst_avtp_crf_check_get_property (GObject * object, guint prop_id,
125     GValue * value, GParamSpec * pspec)
126 {
127   GstAvtpCrfCheck *avtpcrfcheck = GST_AVTP_CRF_CHECK (object);
128 
129   GST_DEBUG_OBJECT (avtpcrfcheck, "prop_id %u", prop_id);
130 
131   switch (prop_id) {
132     case PROP_DROP_INVALID:
133       g_value_set_boolean (value, avtpcrfcheck->drop_invalid);
134       break;
135     default:
136       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137       break;
138   }
139 }
140 
141 static void
post_qos_message(GstBaseTransform * parent,GstBuffer * buffer)142 post_qos_message (GstBaseTransform * parent, GstBuffer * buffer)
143 {
144   GstAvtpCrfBase *avtpcrfbase = GST_AVTP_CRF_BASE (parent);
145   guint64 running_time =
146       gst_segment_to_running_time (&avtpcrfbase->element.segment,
147       GST_FORMAT_TIME, GST_BUFFER_DTS_OR_PTS (buffer));
148   guint64 stream_time =
149       gst_segment_to_running_time (&avtpcrfbase->element.segment,
150       GST_FORMAT_TIME, GST_BUFFER_DTS_OR_PTS (buffer));
151   guint64 timestamp = GST_BUFFER_DTS_OR_PTS (buffer);
152   guint64 duration = GST_BUFFER_DURATION (buffer);
153 
154   GstMessage *qos_msg =
155       gst_message_new_qos (GST_OBJECT (parent), FALSE, running_time,
156       stream_time, timestamp, duration);
157   gst_element_post_message (GST_ELEMENT (parent), qos_msg);
158 }
159 
160 static GstFlowReturn
gst_avtp_crf_check_transform_ip(GstBaseTransform * parent,GstBuffer * buffer)161 gst_avtp_crf_check_transform_ip (GstBaseTransform * parent, GstBuffer * buffer)
162 {
163   GstAvtpCrfBase *avtpcrfbase = GST_AVTP_CRF_BASE (parent);
164   GstAvtpCrfCheck *avtpcrfcheck = GST_AVTP_CRF_CHECK (avtpcrfbase);
165   GstAvtpCrfThreadData *thread_data = &avtpcrfbase->thread_data;
166   GstClockTime current_ts = thread_data->current_ts;
167   gdouble avg_period = thread_data->average_period;
168   GstClockTime tstamp, adjusted_tstamp;
169   struct avtp_stream_pdu *pdu;
170   GstClockTime h264_time;
171   GstMapInfo info;
172   gboolean res;
173 
174   if (!avg_period || !current_ts)
175     return GST_FLOW_OK;
176 
177   res = gst_buffer_map (buffer, &info, GST_MAP_READ);
178   if (!res) {
179     GST_ELEMENT_ERROR (avtpcrfcheck, RESOURCE, OPEN_WRITE,
180         ("cannot access buffer"), (NULL));
181     return GST_FLOW_ERROR;
182   }
183 
184   if (!buffer_size_valid (&info)) {
185     GST_DEBUG_OBJECT (avtpcrfcheck, "Malformed AVTPDU, discarding it");
186     goto exit;
187   }
188 
189   pdu = (struct avtp_stream_pdu *) info.data;
190 
191   if (h264_tstamp_valid (pdu)) {
192     GstClockTime adjusted_h264_time;
193 
194     res = avtp_cvf_pdu_get (pdu, AVTP_CVF_FIELD_H264_TIMESTAMP, &h264_time);
195     g_assert (res == 0);
196     /* Extrapolate tstamp to 64 bit and assume it's greater than CRF timestamp. */
197     h264_time |= current_ts & 0xFFFFFFFF00000000;
198     if (h264_time < current_ts)
199       h264_time += (1ULL << 32);
200 
201     /*
202      * float typecasted to guint64 truncates the decimal part. So, round() it
203      * before casting.
204      */
205     adjusted_h264_time =
206         (GstClockTime) roundl (current_ts + roundl ((h264_time -
207                 current_ts) / avg_period) * avg_period);
208 
209     if (llabs ((gint64) adjusted_h264_time - (gint64) h264_time) >
210         0.25 * thread_data->average_period) {
211       GST_LOG_OBJECT (avtpcrfcheck,
212           "H264 timestamp not synchronized. Expected: %" G_GUINT64_FORMAT
213           " Actual: %" G_GUINT64_FORMAT,
214           adjusted_h264_time & 0xFFFFFFFF, h264_time & 0xFFFFFFFF);
215       if (avtpcrfcheck->drop_invalid) {
216         post_qos_message (parent, buffer);
217         gst_buffer_unmap (buffer, &info);
218         return GST_BASE_TRANSFORM_FLOW_DROPPED;
219       }
220     }
221   }
222 
223   tstamp = get_avtp_tstamp (avtpcrfbase, pdu);
224   if (tstamp == GST_CLOCK_TIME_NONE)
225     goto exit;
226 
227   /*
228    * Extrapolate the 32-bit AVTP Timestamp to 64-bit and assume it's greater
229    * than the 64-bit CRF timestamp.
230    */
231   tstamp |= current_ts & 0xFFFFFFFF00000000;
232   if (tstamp < current_ts)
233     tstamp += (1ULL << 32);
234 
235   /*
236    * float typecasted to guint64 truncates the decimal part. So, round() it
237    * before casting.
238    */
239   adjusted_tstamp =
240       (GstClockTime) roundl (current_ts + roundl ((tstamp -
241               current_ts) / avg_period) * avg_period);
242   if (llabs ((gint64) adjusted_tstamp - (gint64) tstamp) >
243       0.25 * thread_data->average_period) {
244     GST_LOG_OBJECT (avtpcrfcheck,
245         "AVTP Timestamp not synchronized. Expected: %" G_GUINT64_FORMAT
246         " Actual: %" G_GUINT64_FORMAT,
247         adjusted_tstamp & 0xFFFFFFFF, tstamp & 0xFFFFFFFF);
248     if (avtpcrfcheck->drop_invalid) {
249       post_qos_message (parent, buffer);
250       gst_buffer_unmap (buffer, &info);
251       return GST_BASE_TRANSFORM_FLOW_DROPPED;
252     }
253   }
254 
255 exit:
256   gst_buffer_unmap (buffer, &info);
257   return GST_FLOW_OK;
258 }
259