mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 20:21:24 +00:00
264 lines
8.4 KiB
C
264 lines
8.4 KiB
C
|
/*
|
||
|
* GStreamer AVTP Plugin
|
||
|
* Copyright (C) 2019 Intel Corporation
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* SECTION:element-avtpcrfcheck
|
||
|
* @see_also: avtpcrfsync
|
||
|
*
|
||
|
* Validate whether the presentation time for the AVTPDU aligns with the CRF
|
||
|
* stream. For detailed information see Chapter 10 of
|
||
|
* https://standards.ieee.org/standard/1722-2016.html.
|
||
|
*
|
||
|
* <refsect2>
|
||
|
* <title>Example pipeline</title>
|
||
|
* |[
|
||
|
* gst-launch-1.0 avtpsrc ! avtpcrfcheck ! avtpaafdepay ! autoaudiosink
|
||
|
* ]| This example pipeline will validate AVTP timestamps for AVTPDUs. Refer to
|
||
|
* the avtpcrfsync to adjust the AVTP timestamps for the packet.
|
||
|
* </refsect2>
|
||
|
*/
|
||
|
|
||
|
#include <avtp.h>
|
||
|
#include <avtp_crf.h>
|
||
|
#include <avtp_cvf.h>
|
||
|
#include <glib.h>
|
||
|
#include <math.h>
|
||
|
|
||
|
#include "gstavtpcrfbase.h"
|
||
|
#include "gstavtpcrfcheck.h"
|
||
|
#include "gstavtpcrfutil.h"
|
||
|
|
||
|
GST_DEBUG_CATEGORY_STATIC (avtpcrfcheck_debug);
|
||
|
#define GST_CAT_DEFAULT (avtpcrfcheck_debug)
|
||
|
|
||
|
#define DEFAULT_DROP_INVALID FALSE
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
PROP_0,
|
||
|
PROP_DROP_INVALID,
|
||
|
};
|
||
|
|
||
|
#define gst_avtp_crf_check_parent_class parent_class
|
||
|
G_DEFINE_TYPE (GstAvtpCrfCheck, gst_avtp_crf_check, GST_TYPE_AVTP_CRF_BASE);
|
||
|
|
||
|
static void gst_avtp_crf_check_set_property (GObject * object, guint prop_id,
|
||
|
const GValue * value, GParamSpec * pspec);
|
||
|
static void gst_avtp_crf_check_get_property (GObject * object, guint prop_id,
|
||
|
GValue * value, GParamSpec * pspec);
|
||
|
static GstFlowReturn gst_avtp_crf_check_transform_ip (GstBaseTransform * parent,
|
||
|
GstBuffer * buffer);
|
||
|
|
||
|
static void
|
||
|
gst_avtp_crf_check_class_init (GstAvtpCrfCheckClass * klass)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
||
|
|
||
|
gst_element_class_set_static_metadata (element_class,
|
||
|
"Clock Reference Format (CRF) Checker",
|
||
|
"Filter/Network/AVTP",
|
||
|
"Check if the AVTP presentation time is synchronized with clock provided by a CRF stream",
|
||
|
"Vedang Patel <vedang.patel@intel.com>");
|
||
|
|
||
|
object_class->get_property =
|
||
|
GST_DEBUG_FUNCPTR (gst_avtp_crf_check_get_property);
|
||
|
object_class->set_property =
|
||
|
GST_DEBUG_FUNCPTR (gst_avtp_crf_check_set_property);
|
||
|
GST_BASE_TRANSFORM_CLASS (klass)->transform_ip =
|
||
|
GST_DEBUG_FUNCPTR (gst_avtp_crf_check_transform_ip);
|
||
|
|
||
|
g_object_class_install_property (object_class, PROP_DROP_INVALID,
|
||
|
g_param_spec_boolean ("drop-invalid", "Drop invalid packets",
|
||
|
"Drop the packets which are not within 25%% of the sample period of the CRF timestamps",
|
||
|
DEFAULT_DROP_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||
|
|
||
|
GST_DEBUG_CATEGORY_INIT (avtpcrfcheck_debug, "avtpcrfcheck", 0,
|
||
|
"CRF Checker");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_avtp_crf_check_init (GstAvtpCrfCheck * avtpcrfcheck)
|
||
|
{
|
||
|
avtpcrfcheck->drop_invalid = DEFAULT_DROP_INVALID;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_avtp_crf_check_set_property (GObject * object, guint prop_id,
|
||
|
const GValue * value, GParamSpec * pspec)
|
||
|
{
|
||
|
GstAvtpCrfCheck *avtpcrfcheck = GST_AVTP_CRF_CHECK (object);
|
||
|
|
||
|
GST_DEBUG_OBJECT (avtpcrfcheck, "prop_id %u", prop_id);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_DROP_INVALID:
|
||
|
avtpcrfcheck->drop_invalid = g_value_get_boolean (value);
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_avtp_crf_check_get_property (GObject * object, guint prop_id,
|
||
|
GValue * value, GParamSpec * pspec)
|
||
|
{
|
||
|
GstAvtpCrfCheck *avtpcrfcheck = GST_AVTP_CRF_CHECK (object);
|
||
|
|
||
|
GST_DEBUG_OBJECT (avtpcrfcheck, "prop_id %u", prop_id);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_DROP_INVALID:
|
||
|
g_value_set_boolean (value, avtpcrfcheck->drop_invalid);
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
post_qos_message (GstBaseTransform * parent, GstBuffer * buffer)
|
||
|
{
|
||
|
GstAvtpCrfBase *avtpcrfbase = GST_AVTP_CRF_BASE (parent);
|
||
|
guint64 running_time =
|
||
|
gst_segment_to_running_time (&avtpcrfbase->element.segment,
|
||
|
GST_FORMAT_TIME, GST_BUFFER_DTS_OR_PTS (buffer));
|
||
|
guint64 stream_time =
|
||
|
gst_segment_to_running_time (&avtpcrfbase->element.segment,
|
||
|
GST_FORMAT_TIME, GST_BUFFER_DTS_OR_PTS (buffer));
|
||
|
guint64 timestamp = GST_BUFFER_DTS_OR_PTS (buffer);
|
||
|
guint64 duration = GST_BUFFER_DURATION (buffer);
|
||
|
|
||
|
GstMessage *qos_msg =
|
||
|
gst_message_new_qos (GST_OBJECT (parent), FALSE, running_time,
|
||
|
stream_time, timestamp, duration);
|
||
|
gst_element_post_message (GST_ELEMENT (parent), qos_msg);
|
||
|
}
|
||
|
|
||
|
static GstFlowReturn
|
||
|
gst_avtp_crf_check_transform_ip (GstBaseTransform * parent, GstBuffer * buffer)
|
||
|
{
|
||
|
GstAvtpCrfBase *avtpcrfbase = GST_AVTP_CRF_BASE (parent);
|
||
|
GstAvtpCrfCheck *avtpcrfcheck = GST_AVTP_CRF_CHECK (avtpcrfbase);
|
||
|
GstAvtpCrfThreadData *thread_data = &avtpcrfbase->thread_data;
|
||
|
GstClockTime current_ts = thread_data->current_ts;
|
||
|
gdouble avg_period = thread_data->average_period;
|
||
|
GstClockTime tstamp, adjusted_tstamp;
|
||
|
struct avtp_stream_pdu *pdu;
|
||
|
GstClockTime h264_time;
|
||
|
GstMapInfo info;
|
||
|
gboolean res;
|
||
|
|
||
|
if (!avg_period || !current_ts)
|
||
|
return GST_FLOW_OK;
|
||
|
|
||
|
res = gst_buffer_map (buffer, &info, GST_MAP_READ);
|
||
|
if (!res) {
|
||
|
GST_ELEMENT_ERROR (avtpcrfcheck, RESOURCE, OPEN_WRITE,
|
||
|
("cannot access buffer"), (NULL));
|
||
|
return GST_FLOW_ERROR;
|
||
|
}
|
||
|
|
||
|
if (!buffer_size_valid (&info)) {
|
||
|
GST_DEBUG_OBJECT (avtpcrfcheck, "Malformed AVTPDU, discarding it");
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
pdu = (struct avtp_stream_pdu *) info.data;
|
||
|
|
||
|
if (h264_tstamp_valid (pdu)) {
|
||
|
GstClockTime adjusted_h264_time;
|
||
|
|
||
|
res = avtp_cvf_pdu_get (pdu, AVTP_CVF_FIELD_H264_TIMESTAMP, &h264_time);
|
||
|
g_assert (res == 0);
|
||
|
/* Extrapolate tstamp to 64 bit and assume it's greater than CRF timestamp. */
|
||
|
h264_time |= current_ts & 0xFFFFFFFF00000000;
|
||
|
if (h264_time < current_ts)
|
||
|
h264_time += (1ULL << 32);
|
||
|
|
||
|
/*
|
||
|
* float typecasted to guint64 truncates the decimal part. So, round() it
|
||
|
* before casting.
|
||
|
*/
|
||
|
adjusted_h264_time =
|
||
|
(GstClockTime) roundl (current_ts + roundl ((h264_time -
|
||
|
current_ts) / avg_period) * avg_period);
|
||
|
|
||
|
if (llabs ((gint64) adjusted_h264_time - (gint64) h264_time) >
|
||
|
0.25 * thread_data->average_period) {
|
||
|
GST_LOG_OBJECT (avtpcrfcheck,
|
||
|
"H264 timestamp not synchronized. Expected: %" G_GUINT64_FORMAT
|
||
|
" Actual: %" G_GUINT64_FORMAT,
|
||
|
adjusted_h264_time & 0xFFFFFFFF, h264_time & 0xFFFFFFFF);
|
||
|
if (avtpcrfcheck->drop_invalid) {
|
||
|
post_qos_message (parent, buffer);
|
||
|
gst_buffer_unmap (buffer, &info);
|
||
|
return GST_BASE_TRANSFORM_FLOW_DROPPED;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tstamp = get_avtp_tstamp (avtpcrfbase, pdu);
|
||
|
if (tstamp == GST_CLOCK_TIME_NONE)
|
||
|
goto exit;
|
||
|
|
||
|
/*
|
||
|
* Extrapolate the 32-bit AVTP Timestamp to 64-bit and assume it's greater
|
||
|
* than the 64-bit CRF timestamp.
|
||
|
*/
|
||
|
tstamp |= current_ts & 0xFFFFFFFF00000000;
|
||
|
if (tstamp < current_ts)
|
||
|
tstamp += (1ULL << 32);
|
||
|
|
||
|
/*
|
||
|
* float typecasted to guint64 truncates the decimal part. So, round() it
|
||
|
* before casting.
|
||
|
*/
|
||
|
adjusted_tstamp =
|
||
|
(GstClockTime) roundl (current_ts + roundl ((tstamp -
|
||
|
current_ts) / avg_period) * avg_period);
|
||
|
if (llabs ((gint64) adjusted_tstamp - (gint64) tstamp) >
|
||
|
0.25 * thread_data->average_period) {
|
||
|
GST_LOG_OBJECT (avtpcrfcheck,
|
||
|
"AVTP Timestamp not synchronized. Expected: %" G_GUINT64_FORMAT
|
||
|
" Actual: %" G_GUINT64_FORMAT,
|
||
|
adjusted_tstamp & 0xFFFFFFFF, tstamp & 0xFFFFFFFF);
|
||
|
if (avtpcrfcheck->drop_invalid) {
|
||
|
post_qos_message (parent, buffer);
|
||
|
gst_buffer_unmap (buffer, &info);
|
||
|
return GST_BASE_TRANSFORM_FLOW_DROPPED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
gst_buffer_unmap (buffer, &info);
|
||
|
return GST_FLOW_OK;
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
gst_avtp_crf_check_plugin_init (GstPlugin * plugin)
|
||
|
{
|
||
|
return gst_element_register (plugin, "avtpcrfcheck", GST_RANK_NONE,
|
||
|
GST_TYPE_AVTP_CRF_CHECK);
|
||
|
}
|