From 4f2fde0163ecb84e5b7dc00b1e37efbedcc4dbae Mon Sep 17 00:00:00 2001 From: Adrian Fiergolski Date: Tue, 2 Nov 2021 12:49:31 +0100 Subject: [PATCH] avtp: rvf: add AVTP RVF payload support Add AVTP Raw Video Format payload support. The element supports only GRAY16_LE input format, so: - active pixels (no vertical blanking), - progressive mode, - 8 and 16-bit pixel depth, - mono pixel format, - grayscale colorspace. Part-of: --- .../gst-docs/symbols/symbol_index.json | 3 + .../docs/plugins/gst_plugins_cache.json | 23 + .../gst-plugins-bad/ext/avtp/gstavtp.c | 29 +- .../gst-plugins-bad/ext/avtp/gstavtprvfpay.c | 552 ++++++++++++++++++ .../gst-plugins-bad/ext/avtp/gstavtprvfpay.h | 72 +++ .../gst-plugins-bad/ext/avtp/meson.build | 1 + 6 files changed, 667 insertions(+), 13 deletions(-) create mode 100644 subprojects/gst-plugins-bad/ext/avtp/gstavtprvfpay.c create mode 100644 subprojects/gst-plugins-bad/ext/avtp/gstavtprvfpay.h diff --git a/subprojects/gst-docs/symbols/symbol_index.json b/subprojects/gst-docs/symbols/symbol_index.json index 1806dfed71..d209988be0 100644 --- a/subprojects/gst-docs/symbols/symbol_index.json +++ b/subprojects/gst-docs/symbols/symbol_index.json @@ -7357,6 +7357,8 @@ "GstAvtpCvfDepay!src", "GstAvtpCvfPay", "GstAvtpCvfPay!sink", + "GstAvtpRvfPay", + "GstAvtpRvfPay!sink", "GstAvtpSink", "GstAvtpSink!sink", "GstAvtpSink:address", @@ -34938,6 +34940,7 @@ "element-avtpcrfsync", "element-avtpcvfdepay", "element-avtpcvfpay", + "element-avtprvfpay", "element-avtpsink", "element-avtpsrc", "element-avwait", diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index f262c444c9..f72e9fc731 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -3600,6 +3600,29 @@ }, "rank": "none" }, + "avtprvfpay": { + "author": "Adrian Fiergolski ", + "description": "Payload-encode raw video into RVF AVTPDU (IEEE 1722)", + "hierarchy": [ + "GstAvtpRvfPay", + "GstAvtpVfPayBase", + "GstAvtpBasePayload", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Codec/Payloader/Network/AVTP", + "long-name": "AVTP Raw Video Format (RVF) payloader", + "pad-templates": { + "sink": { + "caps": "video/x-raw:\n format: { GRAY16_LE }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n", + "direction": "sink", + "presence": "always" + } + }, + "rank": "none" + }, "avtpsink": { "author": "Andre Guedes ", "description": "Send AVTPDUs over the network", diff --git a/subprojects/gst-plugins-bad/ext/avtp/gstavtp.c b/subprojects/gst-plugins-bad/ext/avtp/gstavtp.c index 2a42d19d1e..8e6e170fc2 100644 --- a/subprojects/gst-plugins-bad/ext/avtp/gstavtp.c +++ b/subprojects/gst-plugins-bad/ext/avtp/gstavtp.c @@ -149,8 +149,9 @@ * Each element has its own configuration properties, with some being common * to several elements. Basic properties are: * - * * streamid (avtpaafpay, avtpcvfpay, avtpaafdepay, avtpcvfdepay, - * avtpcrfsync, avtpcrfcheck): Stream ID associated with the stream. + * * streamid (avtpaafpay, avtprvfpay, avtpcvfpay, avtpcvfdepay, + * avtprvfdepay, avtpcrfsync, avtpcrfcheck): Stream ID associated with the + * stream. * * * ifname (avtpsink, avtpsrc, avtpcrfsync, avtpcrfcheck): Network interface * used to send/receive AVTP packets. @@ -160,23 +161,23 @@ * * priority (avtpsink): Priority used by the plugin to transmit AVTP * traffic. * - * * mtt (avtpaafpay, avtpcvfpay): Maximum Transit Time, in nanoseconds, as - * defined in AVTP spec. + * * mtt (avtpaafpay, avtprvfpay, avtpcvfpay): Maximum Transit Time, in + * nanoseconds, as defined in AVTP spec. * - * * tu (avtpaafpay, avtpcvfpay): Maximum Time Uncertainty, in nanoseconds, as - * defined in AVTP spec. + * * tu (avtpaafpay, avtprvfpay, avtpcvfpay): Maximum Time Uncertainty, in + * nanoseconds, as defined in AVTP spec. * - * * processing-deadline (avtpaafpay, avtpcvfpay, avtpsink): Maximum amount of - * time, in nanoseconds, that the pipeline is expected to process any - * buffer. This value should be in sync between the one used on the - * payloader and the sink, as this time is also taken into consideration to - * define the correct presentation time of the packets on the AVTP listener + * * processing-deadline (avtpaafpay, avtprvfpay, avtpcvfpay, avtpsink): + * Maximum amount of time, in nanoseconds, that the pipeline is expected to + * process any buffer. This value should be in sync between the one used on + * the payloader and the sink, as this time is also taken into consideration + * to define the correct presentation time of the packets on the AVTP listener * side. It should be as low as possible (zero if possible). * * * timestamp-mode (avtpaafpay): AAF timestamping mode, as defined in AVTP spec. * - * * mtu (avtpcvfpay): Maximum Transmit Unit of the underlying network, used - * to determine when to fragment a CVF packet and how big it should be. + * * mtu (avtprvfpay, avtpcvfpay): Maximum Transmit Unit of the underlying network, + * used to determine when to fragment a RVF/CVF packet and how big it should be. * * Check each element documentation for more details. * @@ -241,6 +242,7 @@ #include "gstavtpaafpay.h" #include "gstavtpcvfdepay.h" #include "gstavtpcvfpay.h" +#include "gstavtprvfpay.h" #include "gstavtpsink.h" #include "gstavtpsrc.h" #include "gstavtpcrfsync.h" @@ -255,6 +257,7 @@ plugin_init (GstPlugin * plugin) ret |= GST_ELEMENT_REGISTER (avtpaafdepay, plugin); ret |= GST_ELEMENT_REGISTER (avtpsink, plugin); ret |= GST_ELEMENT_REGISTER (avtpsrc, plugin); + ret |= GST_ELEMENT_REGISTER (avtprvfpay, plugin); ret |= GST_ELEMENT_REGISTER (avtpcvfpay, plugin); ret |= GST_ELEMENT_REGISTER (avtpcvfdepay, plugin); ret |= GST_ELEMENT_REGISTER (avtpcrfsync, plugin); diff --git a/subprojects/gst-plugins-bad/ext/avtp/gstavtprvfpay.c b/subprojects/gst-plugins-bad/ext/avtp/gstavtprvfpay.c new file mode 100644 index 0000000000..5ce1b880e5 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/avtp/gstavtprvfpay.c @@ -0,0 +1,552 @@ +/* + * GStreamer AVTP Plugin + * Copyright (c) 2021, Fastree3D + * Adrian Fiergolski + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/** + * SECTION:element-avtprvfpay + * @see_also: avtprvfdepay + * + * Payload raw video into AVTPDUs according + * to IEEE 1722-2016. For detailed information see + * https://standards.ieee.org/standard/1722-2016.html. + * + * + * Example pipeline + * |[ + * gst-launch-1.0 videotestsrc ! avtprvfpay ! avtpsink + * ]| This example pipeline will payload raw video. Refer to the avtprvfdepay + * example to depayload and play the AVTP stream. + * + */ + +#include +#include + +#include +#include "gstavtprvfpay.h" + +GST_DEBUG_CATEGORY_STATIC (avtprvfpay_debug); +#define GST_CAT_DEFAULT avtprvfpay_debug + +/* prototypes */ +static GstStateChangeReturn gst_avtp_rvf_change_state (GstElement * + element, GstStateChange transition); + +static gboolean gst_avtp_rvf_pay_new_caps (GstAvtpVfPayBase * avtpvfpaybase, + GstCaps * caps); +static gboolean gst_avtp_rvf_pay_prepare_avtp_packets (GstAvtpVfPayBase * + avtpvfpaybase, GstBuffer * buffer, GPtrArray * avtp_packets); + +enum +{ + PROP_0, +}; + +#define AVTP_RVF_HEADER_SIZE (sizeof(struct avtp_stream_pdu) + sizeof(uint64_t)) + +/* pad templates */ + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{GRAY16_LE}")) + ); + +/* class initialization */ + +#define gst_avtp_rvf_pay_parent_class parent_class +G_DEFINE_TYPE (GstAvtpRvfPay, gst_avtp_rvf_pay, GST_TYPE_AVTP_VF_PAY_BASE); +GST_ELEMENT_REGISTER_DEFINE (avtprvfpay, "avtprvfpay", GST_RANK_NONE, + GST_TYPE_AVTP_RVF_PAY); + +static void +gst_avtp_rvf_pay_class_init (GstAvtpRvfPayClass * klass) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GstAvtpVfPayBaseClass *avtpvfpaybase_class = + GST_AVTP_VF_PAY_BASE_CLASS (klass); + + gst_element_class_add_static_pad_template (gstelement_class, &sink_template); + + gst_element_class_set_static_metadata (gstelement_class, + "AVTP Raw Video Format (RVF) payloader", + "Codec/Payloader/Network/AVTP", + "Payload-encode raw video into RVF AVTPDU (IEEE 1722)", + "Adrian Fiergolski "); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_avtp_rvf_change_state); + + avtpvfpaybase_class->new_caps = GST_DEBUG_FUNCPTR (gst_avtp_rvf_pay_new_caps); + avtpvfpaybase_class->prepare_avtp_packets = + GST_DEBUG_FUNCPTR (gst_avtp_rvf_pay_prepare_avtp_packets); + + GST_DEBUG_CATEGORY_INIT (avtprvfpay_debug, "avtprvfpay", + 0, "debug category for avtprvfpay element"); +} + +static void +gst_avtp_rvf_pay_init (GstAvtpRvfPay * avtprvfpay) +{ + avtprvfpay->header = NULL; + /* size of the payload */ + avtprvfpay->fragment_size = 0; + /* large raster: number of data bytes for a fragment at the end of line */ + avtprvfpay->fragment_eol_size = 0; + avtprvfpay->fragment_padding = NULL; + avtprvfpay->num_lines = 0; + /* size of the line in bytes */ + avtprvfpay->line_size = 0; + /* large raster: maximum i_seq_num */ + avtprvfpay->i_seq_max = 0; +} + +static gboolean +gst_avtp_rvf_pay_new_caps (GstAvtpVfPayBase * avtpvfpaybase, GstCaps * caps) +{ + GstAvtpRvfPay *avtprvfpay = GST_AVTP_RVF_PAY (avtpvfpaybase); + GstMapInfo map; + struct avtp_stream_pdu *pdu; + + GstVideoInfo info; + unsigned int fps_up, fps_down; + + gboolean ret = FALSE; + + gsize fragment_padding_size; + + GST_DEBUG_OBJECT (avtprvfpay, "gst_avtp_rvf_pay_new_caps"); + + gst_buffer_map (avtprvfpay->header, &map, GST_MAP_WRITE); + pdu = (struct avtp_stream_pdu *) map.data; + + if (!gst_video_info_from_caps (&info, caps)) { + GST_ERROR_OBJECT (avtprvfpay, + "Can't retrieve the video information from caps"); + goto error; + } + + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_ACTIVE_PIXELS, info.width); + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_TOTAL_LINES, info.height); + + switch (info.interlace_mode) { + case GST_VIDEO_INTERLACE_MODE_PROGRESSIVE: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_I, 0); + break; + /* to-do: support for interleaved modes */ + case GST_VIDEO_INTERLACE_MODE_INTERLEAVED: + case GST_VIDEO_INTERLACE_MODE_FIELDS: + case GST_VIDEO_INTERLACE_MODE_ALTERNATE: + default: + GST_ERROR_OBJECT (avtprvfpay, "Unsupported interlace mode"); + goto error; + } + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_F, 0); + + switch (*info.finfo->depth) { + case 8: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_PIXEL_DEPTH, + AVTP_RVF_PIXEL_DEPTH_8); + break; + case 16: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_PIXEL_DEPTH, + AVTP_RVF_PIXEL_DEPTH_16); + break; + /* to-do: add support for 10 and 12 bit pixel depth + * it requires shifting of the buffer data */ + case 10: + case 12: + default: + GST_ERROR_OBJECT (avtprvfpay, "Unsupported pixel depth"); + goto error; + } + + if (info.finfo->n_planes != 1) { + GST_ERROR_OBJECT (avtprvfpay, "Planar formats are not supported"); + goto error; + } + //All pixels are active + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_AP, 1); + + switch (info.finfo->format) { + case GST_VIDEO_FORMAT_GRAY16_LE: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_PIXEL_FORMAT, + AVTP_RVF_PIXEL_FORMAT_MONO); + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_COLORSPACE, + AVTP_RVF_COLORSPACE_GRAY); + break; + /* to-do: support more formats */ + default: + GST_ERROR_OBJECT (avtprvfpay, "Unsupported video format"); + goto error; + } + + avtprvfpay->line_size = + (info.finfo->n_components * (*info.finfo->depth) * info.width) / 8; + //video_data_payload field for large rasters + if (avtprvfpay->line_size > avtpvfpaybase->mtu - AVTP_RVF_HEADER_SIZE) { + avtprvfpay->num_lines = 0; + + avtprvfpay->fragment_size = avtpvfpaybase->mtu - AVTP_RVF_HEADER_SIZE; + avtprvfpay->fragment_eol_size = + avtprvfpay->line_size % avtprvfpay->fragment_size; + avtprvfpay->i_seq_max = avtprvfpay->line_size / avtprvfpay->fragment_size; + + fragment_padding_size = + avtprvfpay->fragment_size - avtprvfpay->fragment_eol_size; + } + //video_data_payload field for small rasters + else { + //only full lines + avtprvfpay->num_lines = + (avtpvfpaybase->mtu - AVTP_RVF_HEADER_SIZE) / avtprvfpay->line_size; + + //Full video frame is smaller than MTU + if (avtprvfpay->num_lines > info.height) + avtprvfpay->num_lines = info.height; + + //num_lines field is 4 bit only + if (avtprvfpay->num_lines > 15) + avtprvfpay->num_lines = 15; + + avtprvfpay->fragment_size = avtprvfpay->num_lines * avtprvfpay->line_size; + avtprvfpay->fragment_eol_size = 0; + avtprvfpay->i_seq_max = 0; + + fragment_padding_size = + avtprvfpay->fragment_size - + ((info.height % avtprvfpay->num_lines) * avtprvfpay->line_size); + } + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_NUM_LINES, avtprvfpay->num_lines); + + //FPS + fps_down = info.fps_n / info.fps_d; //Round down + fps_up = (info.fps_n + (info.fps_d - 1)) / info.fps_d; //Round up + + if (fps_down == fps_up) { + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_PD, 0); + } else { + if ((info.fps_n * 1001) == info.fps_d * 1000 * fps_up) + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_PD, 1); + else + GST_ERROR_OBJECT (avtprvfpay, "Unsupported frame rate"); + goto error; + } + + switch (fps_up) { + case 1: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_1); + break; + case 2: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_2); + break; + case 5: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_5); + break; + case 10: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_10); + break; + case 15: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_15); + break; + case 20: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_20); + break; + case 24: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_24); + break; + case 25: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_25); + break; + case 30: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_30); + break; + case 48: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_48); + break; + case 50: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_50); + break; + case 60: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_60); + break; + case 72: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_72); + break; + case 85: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_85); + break; + case 100: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_100); + break; + case 120: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_120); + break; + case 150: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_150); + break; + case 200: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_200); + break; + case 240: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_240); + break; + case 300: + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_FRAME_RATE, + AVTP_RVF_FRAME_RATE_300); + break; + default: + GST_ERROR_OBJECT (avtprvfpay, "Unsupported frame rate"); + goto error; + } + + //padding bytes + avtprvfpay->fragment_padding = gst_buffer_new_allocate (NULL, + fragment_padding_size, NULL); + if (G_UNLIKELY (avtprvfpay->fragment_padding == NULL)) { + GST_ERROR_OBJECT (avtprvfpay, + "Could not allocate memory for padding bytes"); + goto error; + } + gst_buffer_memset (avtprvfpay->fragment_padding, 0, 0U, + fragment_padding_size); + + ret = TRUE; + +error: + gst_buffer_unmap (avtprvfpay->header, &map); + + return ret; +} + +static GstStateChangeReturn +gst_avtp_rvf_change_state (GstElement * element, GstStateChange transition) +{ + GstAvtpRvfPay *avtprvfpay = GST_AVTP_RVF_PAY (element); + GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (avtprvfpay); + GstStateChangeReturn ret; + + if (transition == GST_STATE_CHANGE_NULL_TO_READY) { + GstMapInfo map; + struct avtp_stream_pdu *pdu; + int res; + + avtprvfpay->header = gst_buffer_new_allocate (NULL, + AVTP_RVF_HEADER_SIZE, NULL); + if (avtprvfpay->header == NULL) { + GST_ERROR_OBJECT (avtprvfpay, "Could not allocate buffer"); + return GST_STATE_CHANGE_FAILURE; + } + + gst_buffer_map (avtprvfpay->header, &map, GST_MAP_WRITE); + pdu = (struct avtp_stream_pdu *) map.data; + + res = avtp_rvf_pdu_init (pdu); + g_assert (res == 0); + + res = + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_STREAM_ID, + avtpbasepayload->streamid); + g_assert (res == 0); + + gst_buffer_unmap (avtprvfpay->header, &map); + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) { + return ret; + } + + if (transition == GST_STATE_CHANGE_READY_TO_NULL) { + gst_buffer_unref (avtprvfpay->header); + } + + return ret; +} + +/* Checks if stream is large raster video (see: 12.2.9) */ +static gboolean +is_large_raster (GstAvtpRvfPay * avtprvfpay) +{ + return avtprvfpay->num_lines == 0; +} + +static gboolean +gst_avtp_rvf_pay_prepare_avtp_packets (GstAvtpVfPayBase * avtpvfpaybase, + GstBuffer * buffer, GPtrArray * avtp_packets) +{ + GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (avtpvfpaybase); + GstAvtpRvfPay *avtprvfpay = GST_AVTP_RVF_PAY (avtpvfpaybase); + GstBuffer *header; + GstMapInfo map; + guint64 avtp_time; + gsize offset, buffer_size; + gsize i_seq_num, line_number; + + GST_LOG_OBJECT (avtprvfpay, + "Preparing AVTP packets for video frame whose size is %" G_GSIZE_FORMAT, + gst_buffer_get_size (buffer)); + + /* Calculate timestamps using PTS as base + * - code inherited from avtpbasepayload. + * Also worth noting: `avtpbasepayload->latency` is updated after + * first call to gst_avtp_base_payload_calc_ptime, so we MUST call + * it before using the latency value */ + avtp_time = gst_avtp_base_payload_calc_ptime (avtpbasepayload, buffer); + + offset = 0; + buffer_size = gst_buffer_get_size (buffer); + i_seq_num = 0; + line_number = 1; + while (offset != buffer_size) { + GstBuffer *packet; + struct avtp_stream_pdu *pdu; + gint res; + GstBuffer *fragment; + gsize fragment_size; + + /* Copy header to reuse common fields and change what is needed */ + header = gst_buffer_copy (avtprvfpay->header); + gst_buffer_map (header, &map, GST_MAP_WRITE); + pdu = (struct avtp_stream_pdu *) map.data; + + /* Prepare the fragment */ + if (is_large_raster (avtprvfpay)) { + if (i_seq_num == avtprvfpay->i_seq_max) + fragment_size = avtprvfpay->fragment_eol_size; + else + fragment_size = avtprvfpay->fragment_size; + } else { + guint reamaining_size = buffer_size - offset; + if (reamaining_size < avtprvfpay->fragment_size) + fragment_size = reamaining_size; + else + fragment_size = avtprvfpay->fragment_size; + } + + fragment = gst_buffer_copy_region (buffer, + GST_BUFFER_COPY_MEMORY, offset, fragment_size); + + offset += fragment_size; + + /* video_data_payload is always the same size + * so add padding bytes if needed */ + if (fragment_size != avtprvfpay->fragment_size) { + fragment = gst_buffer_append (fragment, avtprvfpay->fragment_padding); + } + + GST_DEBUG_OBJECT (avtprvfpay, + "Generated fragment with size %" G_GSIZE_FORMAT, + avtprvfpay->fragment_size); + + /* Stream data len includes AVTP raw header len as this is part of + * the payload too. It's just the uint64_t */ + res = + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_STREAM_DATA_LEN, + avtprvfpay->fragment_size + sizeof (uint64_t)); + g_assert (res == 0); + + res = + avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_SEQ_NUM, + avtpbasepayload->seqnum++); + g_assert (res == 0); + + /* AVTP_TS fields */ + if ((is_large_raster (avtprvfpay) && i_seq_num == 0) + || (!is_large_raster (avtprvfpay) + && line_number == 1)) { + res = avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_TV, 1); + g_assert (res == 0); + + res = avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_TIMESTAMP, avtp_time); + g_assert (res == 0); + + GST_LOG_OBJECT (avtprvfpay, "TV packet sent, PTS: %" GST_TIME_FORMAT + " DTS: %" GST_TIME_FORMAT " AVTP_TS: %" GST_TIME_FORMAT, + GST_TIME_ARGS (avtp_time), + GST_TIME_ARGS (GST_BUFFER_DTS (buffer)), + GST_TIME_ARGS (avtp_time & 0xffffffff)); + } + + /* Set ef */ + if (offset == buffer_size) { //last fragment + res = avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_EF, 1); + g_assert (res == 0); + } + + /* Set line_number */ + res = avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_LINE_NUMBER, line_number); + g_assert (res == 0); + + if (is_large_raster (avtprvfpay)) { + if (i_seq_num == avtprvfpay->i_seq_max) + line_number++; + } else { + line_number += avtprvfpay->num_lines; + } + + /* Handle i_seq_num only for large raster */ + if (is_large_raster (avtprvfpay)) { + res = avtp_rvf_pdu_set (pdu, AVTP_RVF_FIELD_RAW_I_SEQ_NUM, i_seq_num); + g_assert (res == 0); + + if (i_seq_num < avtprvfpay->i_seq_max) + i_seq_num++; + else + i_seq_num = 0; + } + + packet = gst_buffer_append (header, fragment); + + /* Keep original timestamps */ + GST_BUFFER_PTS (packet) = GST_BUFFER_PTS (buffer); + GST_BUFFER_DTS (packet) = GST_BUFFER_DTS (buffer); + + g_ptr_array_add (avtp_packets, packet); + + gst_buffer_unmap (header, &map); + } + + gst_buffer_unref (buffer); + + GST_LOG_OBJECT (avtprvfpay, "Prepared %u AVTP packets", avtp_packets->len); + + return TRUE; +} diff --git a/subprojects/gst-plugins-bad/ext/avtp/gstavtprvfpay.h b/subprojects/gst-plugins-bad/ext/avtp/gstavtprvfpay.h new file mode 100644 index 0000000000..88ef09bce6 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/avtp/gstavtprvfpay.h @@ -0,0 +1,72 @@ +/* + * GStreamer AVTP Plugin + * Copyright (c) 2021, Fastree3D + * Adrian Fiergolski + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef __GST_AVTP_RVF_PAY_H__ +#define __GST_AVTP_RVF_PAY_H__ + +#include + +#include "gstavtpvfpaybase.h" + +G_BEGIN_DECLS +#define GST_TYPE_AVTP_RVF_PAY (gst_avtp_rvf_pay_get_type()) +#define GST_AVTP_RVF_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVTP_RVF_PAY,GstAvtpRvfPay)) +#define GST_AVTP_RVF_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVTP_RVF_PAY,GstAvtpRvfPayClass)) +#define GST_IS_AVTP_RVF_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVTP_RVF_PAY)) +#define GST_IS_AVTP_RVF_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVTP_RVF_PAY)) +typedef struct _GstAvtpRvfPay GstAvtpRvfPay; +typedef struct _GstAvtpRvfPayClass GstAvtpRvfPayClass; + +struct _GstAvtpRvfPay +{ + GstAvtpVfPayBase vfbase; + + GstBuffer *header; + //size of the buffer fragment + gsize fragment_size; + //large raster: size of the end of line fragment + gsize fragment_eol_size; + //padding bytes appended to the last fragment of the frame + GstBuffer *fragment_padding; + //number of lines per fragment + guint num_lines; + //size of the single line + gsize line_size; + //maximum value of i_seq_num + guint8 i_seq_max; +}; + +struct _GstAvtpRvfPayClass +{ + GstAvtpVfPayBaseClass parent_class; +}; + +GType gst_avtp_rvf_pay_get_type (void); + +GST_ELEMENT_REGISTER_DECLARE (avtprvfpay); + +G_END_DECLS +#endif /* __GST_AVTP_RVF_PAY_H__ */ diff --git a/subprojects/gst-plugins-bad/ext/avtp/meson.build b/subprojects/gst-plugins-bad/ext/avtp/meson.build index 05e1be72c7..4c2eb24aa8 100644 --- a/subprojects/gst-plugins-bad/ext/avtp/meson.build +++ b/subprojects/gst-plugins-bad/ext/avtp/meson.build @@ -4,6 +4,7 @@ avtp_sources = [ 'gstavtpaafpay.c', 'gstavtpcvfdepay.c', 'gstavtpcvfpay.c', + 'gstavtprvfpay.c', 'gstavtpvfpaybase.c', 'gstavtpbasedepayload.c', 'gstavtpbasepayload.c',