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